Compare commits

..
7 Commits
Author SHA1 Message Date
fraujulian d2205cb7ab docs(scripts): document customizations
CI / Capabilities and shell quality (push) Failing after 13s
CI / Python tests and compilation (push) Successful in 11s
CI / Build and inspect generated packages (push) Skipped
CI / Bash integration and installation guards (push) Skipped
CI / Installed client configuration smoke tests (push) Skipped
CI / PowerShell tests on Ubuntu (push) Successful in 28s
CI / CI configuration security checks (push) Successful in 3s
CI / Qwen retrieval integration (push) Canceled after 6m58s
2026-09-18 17:26:28 +02:00
fraujulian 3676891292 test(customizations): cover credentials and overlays 2026-09-18 17:26:12 +02:00
fraujulian 2aa86ea643 feat(build): render client customizations 2026-09-18 17:25:53 +02:00
fraujulian 9f5059ed07 feat(instructions): add client overlays 2026-09-18 17:25:33 +02:00
fraujulian d808026eec feat(credentials): add scoped credential helpers 2026-09-18 17:25:17 +02:00
fraujulian 027eaa158a fix(extensions): update managed mcporter 2026-09-18 16:56:39 +02:00
fraujulian e2c38093e9 fix(mcp): allow direct fallback without mcporter 2026-09-18 16:56:20 +02:00
15 changed files with 403 additions and 8 deletions

No files matched your search

+7
View File
@@ -25,6 +25,12 @@ Run scripts from the repository root. PowerShell and Bash commands have equivale
| `commands/repository-context.ps1`, `commands/repository-context.sh` | Refresh the local repository-context cache. |
| `commands/session-state.ps1`, `commands/session-state.sh` | Read or update the local agent session state. |
| `commands/telemetry.ps1`, `commands/telemetry.sh` | Append a local telemetry event. |
| `commands/add-instruction.ps1`, `commands/add-instruction.sh` | Append a client-specific user instruction outside managed configuration. |
| `commands/add-credentials.ps1`, `commands/add-credentials.sh` | Store a client-scoped tool credential in the operating-system credential store. |
Custom instructions are read before generated rules, skills, and plugins. They remain under `~/.my-ai-configuration/instructions/`, so installation and update scripts never manage or reset them.
On Windows, credentials are stored in Windows Credential Manager. Use `~/.my-ai-configuration/bin/with-credential.ps1` or `.sh` to run a tool with a stored key available only in that child process. The key must be a valid environment-variable name; its value is never written to configuration.
Qwen retrieval uses `shared/retrieval/benchmark.py`; run it through `scripts/lib/semantic-retrieval.py benchmark --root . --home <home-directory>`.
@@ -53,6 +59,7 @@ Qwen retrieval uses `shared/retrieval/benchmark.py`; run it through `scripts/lib
| Script | Purpose |
| --- | --- |
| `lib/install-options.ps1`, `lib/install-options.sh` | Read installation options and calculate client-specific choices. |
| `lib/credentials.ps1`, `lib/with-credential.sh` | Write a tool credential to Windows Credential Manager and expose it to one child process. |
| `lib/install-options.py` | Filter configuration templates according to installation options. |
| `lib/install-targets.ps1`, `lib/install-targets.sh` | Resolve configuration targets for the selected client and shell. |
| `lib/manifest.ps1`, `lib/manifest.sh` | Synchronize managed files and their ownership manifest. |
+39
View File
@@ -0,0 +1,39 @@
[CmdletBinding()]
param(
[ValidateSet('Codex', 'Claude', 'Both')][string]$Client,
[string]$Key,
[switch]$ValueFromStdin,
[string]$HomePath = [Environment]::GetFolderPath('UserProfile')
)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
. (Join-Path $root 'scripts/lib/install-targets.ps1')
$selectedClient = $Client
$selectedKey = $Key
. (Join-Path $root 'scripts/lib/credentials.ps1')
$Client = $selectedClient
$Key = $selectedKey
if (-not $Client) { $Client = Read-InstallClient -Client $Client }
if (-not $Key) { $Key = Read-Host 'Credential key' }
Assert-CredentialKey $Key
if (-not [System.IO.Path]::IsPathRooted($HomePath)) { throw 'HomePath must be absolute.' }
if ($ValueFromStdin) {
$text = [Console]::In.ReadToEnd().TrimEnd("`r", "`n")
$Value = ConvertTo-SecureString $text -AsPlainText -Force
$text = $null
} else {
$Value = Read-Host 'Credential value' -AsSecureString
}
$bin = Join-Path $HomePath '.my-ai-configuration/bin'
New-Item -ItemType Directory -Path $bin -Force | Out-Null
foreach ($source in @('credentials.ps1', 'with-credential.sh')) {
$destination = Join-Path $bin $(if ($source -eq 'credentials.ps1') { 'with-credential.ps1' } else { $source })
if (-not (Test-Path -LiteralPath $destination)) { Copy-Item (Join-Path $root "scripts/lib/$source") $destination }
}
foreach ($target in $(if ($Client -eq 'Both') { @('Codex', 'Claude') } else { @($Client) })) {
Set-ManagedCredential $target $Key $Value
Write-Output "Credential stored for $($target.ToLowerInvariant())."
}
$Value = $null
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
client=
key=
home_path=${HOME:?HOME is required}
while [ $# -gt 0 ]; do
case "$1" in
--client) client=${2:?--client requires a value}; shift 2 ;;
--key) key=${2:?--key requires a value}; shift 2 ;;
--home) home_path=${2:?--home requires a value}; shift 2 ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
[ -n "$client" ] || read -r -p 'Select client (codex, claude, or both) ' client
[ -n "$key" ] || read -r -p 'Credential key ' key
case "$client" in codex|Codex|claude|Claude|both|Both) ;; *) printf 'Unknown --client value: %s\n' "$client" >&2; exit 1 ;; esac
case "$key" in [A-Za-z_][A-Za-z0-9_]* ) ;; *) printf 'Credential key must be an environment-variable-compatible name.\n' >&2; exit 1 ;; esac
case "$home_path" in /*|[A-Za-z]:[\\/]*) ;; *) printf 'Home path must be absolute.\n' >&2; exit 1 ;; esac
IFS= read -r -s -p 'Credential value ' value
printf '\n'
powershell_command=$(command -v powershell.exe || command -v powershell || command -v pwsh || true)
[ -n "$powershell_command" ] || { printf 'PowerShell is required to store credentials in the Windows credential store.\n' >&2; exit 1; }
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
printf '%s' "$value" | "$powershell_command" -NoProfile -ExecutionPolicy Bypass -File "$script_dir/add-credentials.ps1" -Client "$client" -Key "$key" -ValueFromStdin -HomePath "$home_path"
unset value
+23
View File
@@ -0,0 +1,23 @@
[CmdletBinding()]
param(
[ValidateSet('Codex','Claude','Both')][string]$Client,
[AllowEmptyString()][string]$Instruction,
[string]$HomePath = [Environment]::GetFolderPath('UserProfile')
)
$ErrorActionPreference = 'Stop'
$root = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
. (Join-Path $root 'scripts/lib/install-targets.ps1')
if (-not $Client) { $Client = Read-InstallClient -Client $Client }
if (-not $PSBoundParameters.ContainsKey('Instruction')) { $Instruction = Read-Host 'Instruction' }
if ([string]::IsNullOrWhiteSpace($Instruction) -or $Instruction.Length -gt 65536) { throw 'Instruction must contain between 1 and 65536 characters.' }
if (-not [System.IO.Path]::IsPathRooted($HomePath)) { throw 'HomePath must be absolute.' }
$directory = Join-Path $HomePath '.my-ai-configuration/instructions'
New-Item -ItemType Directory -Path $directory -Force | Out-Null
foreach ($target in $(if ($Client -eq 'Both') { @('codex','claude') } else { @($Client.ToLowerInvariant()) })) {
$path = Join-Path $directory "$target.md"
$prefix = if ((Test-Path -LiteralPath $path) -and (Get-Item -LiteralPath $path).Length -gt 0 -and -not (Get-Content -LiteralPath $path -Raw).EndsWith([Environment]::NewLine)) { [Environment]::NewLine } else { '' }
[System.IO.File]::AppendAllText($path, $prefix + $Instruction.TrimEnd() + [Environment]::NewLine, [System.Text.UTF8Encoding]::new($false))
Write-Output "Instruction added for $target."
}
+42
View File
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
set -euo pipefail
client=
instruction=
home_path=${HOME:?HOME is required}
while [ $# -gt 0 ]; do
case "$1" in
--client) client=${2:?--client requires a value}; shift 2 ;;
--instruction) instruction=${2:?--instruction requires a value}; shift 2 ;;
--home) home_path=${2:?--home requires a value}; shift 2 ;;
*) printf 'Unknown argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
case "$client" in
codex|Codex) targets=(codex) ;;
claude|Claude) targets=(claude) ;;
both|Both) targets=(codex claude) ;;
'')
read -r -p 'Select client (codex, claude, or both) ' client
case "$client" in
codex|Codex) targets=(codex) ;;
claude|Claude) targets=(claude) ;;
both|Both) targets=(codex claude) ;;
*) printf 'Unknown --client value: %s\n' "$client" >&2; exit 1 ;;
esac
;;
*) printf 'Unknown --client value: %s\n' "$client" >&2; exit 1 ;;
esac
if [ -z "$instruction" ]; then read -r -p 'Instruction ' instruction; fi
if [ -z "${instruction//[[:space:]]/}" ] || [ ${#instruction} -gt 65536 ]; then printf 'Instruction must contain between 1 and 65536 characters.\n' >&2; exit 1; fi
case "$home_path" in /*|[A-Za-z]:[\\/]*) ;; *) printf 'Home path must be absolute.\n' >&2; exit 1 ;; esac
directory="$home_path/.my-ai-configuration/instructions"
mkdir -p -- "$directory"
for target in "${targets[@]}"; do
path="$directory/$target.md"
[ ! -s "$path" ] || [ "$(tail -c 1 -- "$path" | wc -l)" -eq 1 ] || printf '\n' >> "$path"
printf '%s\n' "$instruction" >> "$path"
printf 'Instruction added for %s.\n' "$target"
done
+3 -2
View File
@@ -153,12 +153,13 @@ foreach ($shell in @('powershell','bash')) {
$sharedTemplate = Get-Content (Join-Path $shared 'global-instructions.md') -Raw
$generalContent = Get-Content (Join-Path $shared 'rules/general.md') -Raw
$credentialHelperExtension = if ($shell -eq 'powershell') { 'ps1' } else { 'sh' }
$agentsContent = $sharedTemplate.Replace('__RULE_LOADING__', $codexRuleLoading)
$agentsContent = $sharedTemplate.Replace('__RULE_LOADING__', $codexRuleLoading).Replace('__CLIENT__', 'codex').Replace('__SHELL__', $credentialHelperExtension)
$agentsContent = $agentsContent.TrimEnd() + "`r`n`r`n---`r`n`r`n$generalContent"
Set-Content (Join-Path $output "codex-$shell/AGENTS.md") $agentsContent -Encoding UTF8
$claudeContent = $sharedTemplate.Replace('__RULE_LOADING__', $claudeRuleLoading)
$claudeContent = $sharedTemplate.Replace('__RULE_LOADING__', $claudeRuleLoading).Replace('__CLIENT__', 'claude').Replace('__SHELL__', $credentialHelperExtension)
$claudeContent = $claudeContent.TrimEnd() + "`r`n`r`n---`r`n`r`n$generalContent"
Set-Content (Join-Path $output "claude-$shell/CLAUDE.md") $claudeContent -Encoding UTF8
+6
View File
@@ -153,11 +153,17 @@ ${claude_rule_loading_list%$'\n'}"
shared_template=$(<"$shared/global-instructions.md")
general_content=$(<"$shared/rules/general.md")
credential_helper_extension=sh
[ "$shell" != powershell ] || credential_helper_extension=ps1
agents_content=${shared_template//__RULE_LOADING__/$codex_rule_loading}
agents_content=${agents_content//__CLIENT__/codex}
agents_content=${agents_content//__SHELL__/$credential_helper_extension}
printf '%s\n\n---\n\n%s\n' "${agents_content%$'\n'}" "$general_content" > "$output/codex-$shell/AGENTS.md"
claude_content=${shared_template//__RULE_LOADING__/$claude_rule_loading}
claude_content=${claude_content//__CLIENT__/claude}
claude_content=${claude_content//__SHELL__/$credential_helper_extension}
printf '%s\n\n---\n\n%s\n' "${claude_content%$'\n'}" "$general_content" > "$output/claude-$shell/CLAUDE.md"
cp "$root/adapters/codex/config/config.toml" "$output/codex-$shell/config.toml"
+125
View File
@@ -0,0 +1,125 @@
[CmdletBinding()]
param(
[ValidateSet('Codex', 'Claude')][string]$Client,
[string]$Key,
[Parameter(Position = 2)][string]$FilePath,
[Parameter(Position = 3, ValueFromRemainingArguments = $true)][string[]]$ArgumentList
)
$ErrorActionPreference = 'Stop'
function Assert-CredentialKey {
param([string]$Value)
if ($Value -notmatch '^[A-Za-z_][A-Za-z0-9_]{0,127}$') { throw 'Credential key must be an environment-variable-compatible name.' }
}
function Get-CredentialTarget {
param([string]$Client, [string]$Key)
Assert-CredentialKey $Key
return "MyAiConfiguration/$($Client.ToLowerInvariant())/$Key"
}
function Initialize-NativeCredentialApi {
if ('MyAiConfiguration.NativeCredential' -as [type]) { return }
Add-Type -TypeDefinition @'
using System;
using System.Runtime.InteropServices;
namespace MyAiConfiguration {
public static class NativeCredential {
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct CREDENTIAL {
public UInt32 Flags;
public UInt32 Type;
public IntPtr TargetName;
public IntPtr Comment;
public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
public UInt32 CredentialBlobSize;
public IntPtr CredentialBlob;
public UInt32 Persist;
public UInt32 AttributeCount;
public IntPtr Attributes;
public IntPtr TargetAlias;
public IntPtr UserName;
}
[DllImport("Advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredWrite(ref CREDENTIAL credential, UInt32 flags);
[DllImport("Advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredRead(string target, UInt32 type, UInt32 flags, out IntPtr credential);
[DllImport("Advapi32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
public static extern bool CredDelete(string target, UInt32 type, UInt32 flags);
[DllImport("Advapi32.dll", SetLastError = true)]
public static extern void CredFree(IntPtr buffer);
}
}
'@
}
function Set-ManagedCredential {
param([ValidateSet('Codex', 'Claude')][string]$Client, [string]$Key, [securestring]$Value)
if (-not $IsWindows -and $PSVersionTable.PSVersion.Major -ge 6) { throw 'The OS credential store is currently supported on Windows only.' }
Initialize-NativeCredentialApi
$plain = [System.Net.NetworkCredential]::new('', $Value).Password
$bytes = [Text.Encoding]::UTF8.GetBytes($plain)
if ($bytes.Length -eq 0 -or $bytes.Length -gt 2560) { throw 'Credential value must contain between 1 and 2560 UTF-8 bytes.' }
$native = New-Object MyAiConfiguration.NativeCredential+CREDENTIAL
$native.Type = 1
$native.Persist = 2
$native.TargetName = [Runtime.InteropServices.Marshal]::StringToCoTaskMemUni((Get-CredentialTarget $Client $Key))
$native.UserName = [Runtime.InteropServices.Marshal]::StringToCoTaskMemUni($Key)
$native.CredentialBlobSize = $bytes.Length
$native.CredentialBlob = [Runtime.InteropServices.Marshal]::AllocCoTaskMem($bytes.Length)
try {
[Runtime.InteropServices.Marshal]::Copy($bytes, 0, $native.CredentialBlob, $bytes.Length)
if (-not [MyAiConfiguration.NativeCredential]::CredWrite([ref]$native, 0)) { throw [ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error()) }
} finally {
[Array]::Clear($bytes, 0, $bytes.Length)
$plain = $null
if ($native.TargetName -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::FreeCoTaskMem($native.TargetName) }
if ($native.UserName -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::FreeCoTaskMem($native.UserName) }
if ($native.CredentialBlob -ne [IntPtr]::Zero) { [Runtime.InteropServices.Marshal]::FreeCoTaskMem($native.CredentialBlob) }
}
}
function Get-ManagedCredential {
param([ValidateSet('Codex', 'Claude')][string]$Client, [string]$Key)
if (-not $IsWindows -and $PSVersionTable.PSVersion.Major -ge 6) { throw 'The OS credential store is currently supported on Windows only.' }
Initialize-NativeCredentialApi
$pointer = [IntPtr]::Zero
if (-not [MyAiConfiguration.NativeCredential]::CredRead((Get-CredentialTarget $Client $Key), 1, 0, [ref]$pointer)) { throw 'Credential was not found.' }
try {
$native = [Runtime.InteropServices.Marshal]::PtrToStructure($pointer, [type]'MyAiConfiguration.NativeCredential+CREDENTIAL')
$bytes = New-Object byte[] $native.CredentialBlobSize
[Runtime.InteropServices.Marshal]::Copy($native.CredentialBlob, $bytes, 0, $bytes.Length)
try { return [Text.Encoding]::UTF8.GetString($bytes) } finally { [Array]::Clear($bytes, 0, $bytes.Length) }
} finally {
[MyAiConfiguration.NativeCredential]::CredFree($pointer)
}
}
function Remove-ManagedCredential {
param([ValidateSet('Codex', 'Claude')][string]$Client, [string]$Key)
if (-not $IsWindows -and $PSVersionTable.PSVersion.Major -ge 6) { throw 'The OS credential store is currently supported on Windows only.' }
Initialize-NativeCredentialApi
if (-not [MyAiConfiguration.NativeCredential]::CredDelete((Get-CredentialTarget $Client $Key), 1, 0)) { throw [ComponentModel.Win32Exception]::new([Runtime.InteropServices.Marshal]::GetLastWin32Error()) }
}
function Invoke-WithManagedCredential {
param([ValidateSet('Codex', 'Claude')][string]$Client, [string]$Key, [string]$FilePath, [string[]]$ArgumentList)
Assert-CredentialKey $Key
if (-not (Test-Path -LiteralPath $FilePath -PathType Leaf)) { throw 'Child command was not found.' }
$prior = [Environment]::GetEnvironmentVariable($Key, 'Process')
$value = Get-ManagedCredential $Client $Key
try {
[Environment]::SetEnvironmentVariable($Key, $value, 'Process')
& $FilePath @ArgumentList
return $LASTEXITCODE
} finally {
[Environment]::SetEnvironmentVariable($Key, $prior, 'Process')
$value = $null
}
}
if ($FilePath) {
if (-not $Client -or -not $Key) { throw 'Client and key are required.' }
exit (Invoke-WithManagedCredential $Client $Key $FilePath $ArgumentList)
}
+4 -3
View File
@@ -344,10 +344,11 @@ class Manager:
if not re.fullmatch(r'[a-z0-9@/_.-]+', package):
raise ValueError('Invalid CLI package.')
record = next((r for r in self.state['resources'] if r['client'] == client and r['name'] == entry['name']), None)
if record is not None:
installed = shutil.which(package) is not None
if record is not None and installed and not self.update:
return
self.command(['npm', 'install', '--global', package])
if not self.dry_run:
self.command(['npm', 'update' if record is not None and installed else 'install', '--global', package])
if not self.dry_run and record is None:
self.state['resources'].append(dict(client=client, name=entry['name'], kind='cli', package=package))
self.save()
+15
View File
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
client=${1:?client is required}
key=${2:?credential key is required}
shift 2
[ $# -gt 0 ] || { printf 'Child command is required.\n' >&2; exit 1; }
case "$client" in codex|Codex) client=Codex ;; claude|Claude) client=Claude ;; *) printf 'Unknown client: %s\n' "$client" >&2; exit 1 ;; esac
case "$key" in [A-Za-z_][A-Za-z0-9_]* ) ;; *) printf 'Credential key must be an environment-variable-compatible name.\n' >&2; exit 1 ;; esac
powershell_command=$(command -v powershell.exe || command -v powershell || command -v pwsh || true)
[ -n "$powershell_command" ] || { printf 'PowerShell is required to read the Windows credential store.\n' >&2; exit 1; }
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
exec "$powershell_command" -NoProfile -ExecutionPolicy Bypass -File "$script_dir/with-credential.ps1" -Client "$client" -Key "$key" -FilePath "$1" "${@:2}"
+14
View File
@@ -272,6 +272,20 @@ class ManagedExtensionTests(unittest.TestCase):
self.assertIn(['claude', 'plugin', 'install', 'qmd@qmd', '--scope', 'user'], self.commands)
self.assertNotIn(['codex', 'mcp', 'add', 'qmd', '--', 'qmd', 'mcp'], self.commands)
def test_managed_mcporter_updates_when_requested(self):
self.manager().sync([mcporter()], ['codex'], {'MCPorter'})
self.commands.clear()
with patch.object(extensions.shutil, 'which', return_value='mcporter'):
self.manager(update=True).sync([mcporter()], ['codex'], {'MCPorter'})
self.assertEqual([['npm', 'update', '--global', 'mcporter']], self.commands)
def test_managed_mcporter_reinstalls_when_command_is_missing(self):
self.manager().sync([mcporter()], ['codex'], {'MCPorter'})
self.commands.clear()
with patch.object(extensions.shutil, 'which', return_value=None):
self.manager().sync([mcporter()], ['codex'], {'MCPorter'})
self.assertEqual([['npm', 'install', '--global', 'mcporter']], self.commands)
def test_mcporter_replaces_direct_context7_plugin(self):
self.manager().sync([mcp(), mcporter()], ['codex'], {'Context7', 'MCPorter'})
config = str(self.home / '.mcporter/mcporter.json')
+2 -1
View File
@@ -57,7 +57,8 @@ foreach ($shell in @('powershell','bash')) {
}
$docFile = if ($client -eq 'codex') { 'AGENTS.md' } else { 'CLAUDE.md' }
$docContent = Get-Content -LiteralPath (Join-Path $package $docFile) -Raw
if ($docContent -notmatch [regex]::Escape('Use MCP services only through the `mcporter` CLI.')) { throw "$package must require MCPorter for MCP services" }
if ($docContent -notmatch [regex]::Escape('When MCPorter is active (installed and configured), use MCP services only through the `mcporter` CLI.')) { throw "$package must route active MCPorter services through its CLI" }
if ($docContent -notmatch [regex]::Escape('If MCPorter is not active, use the direct native MCP service available to you.')) { throw "$package must allow direct native MCP without MCPorter" }
if (([regex]::Matches($docContent, [regex]::Escape('Apply instructions in this order'))).Count -ne 1) { throw "$client-$shell must embed the priority rule exactly once" }
if ($client -eq 'codex' -and $docContent -match 'Always load and apply `rules/general\.md`') { throw "$package must not still instruct loading general.md by path" }
if ($client -eq 'claude' -and (Test-Path (Join-Path $package 'rules/general.md'))) { throw 'Claude must not automatically load a second copy of general rules.' }
+2 -1
View File
@@ -75,7 +75,8 @@ for shell in powershell bash; do
doc_file=AGENTS.md
[ "$client" != claude ] || doc_file=CLAUDE.md
doc_content=$(cat "$package/$doc_file")
printf '%s' "$doc_content" | grep -Fq 'Use MCP services only through the `mcporter` CLI.'
printf '%s' "$doc_content" | grep -Fq 'When MCPorter is active (installed and configured), use MCP services only through the `mcporter` CLI.'
printf '%s' "$doc_content" | grep -Fq 'If MCPorter is not active, use the direct native MCP service available to you.'
occurrences=$(printf '%s' "$doc_content" | grep -o 'Apply instructions in this order' | wc -l)
[ "$occurrences" -eq 1 ] || { printf '%s-%s must embed the priority rule exactly once\n' "$client" "$shell" >&2; exit 1; }
if [ "$client" = codex ] && printf '%s' "$doc_content" | grep -q 'Always load and apply `rules/general\.md`'; then
+88
View File
@@ -0,0 +1,88 @@
import shutil
import subprocess
import tempfile
import unittest
import uuid
from pathlib import Path
ROOT = Path(__file__).parents[2]
class UserCustomizationTests(unittest.TestCase):
def test_build_references_the_shell_specific_credential_helper(self):
powershell = shutil.which('powershell') or shutil.which('pwsh')
if powershell is None:
self.skipTest('PowerShell is unavailable.')
result = subprocess.run([
powershell, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
str(ROOT / 'scripts/commands/build.ps1'), '-Summary',
], capture_output=True, text=True, timeout=30)
self.assertEqual(0, result.returncode, result.stderr)
self.assertIn('with-credential.ps1', (ROOT / 'generated/codex-powershell/AGENTS.md').read_text())
self.assertIn('with-credential.sh', (ROOT / 'generated/claude-bash/CLAUDE.md').read_text())
def test_add_instruction_creates_client_overlays_outside_managed_configuration(self):
powershell = shutil.which('powershell') or shutil.which('pwsh')
if powershell is None:
self.skipTest('PowerShell is unavailable.')
with tempfile.TemporaryDirectory() as directory:
home = Path(directory)
result = subprocess.run([
powershell, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
str(ROOT / 'scripts/commands/add-instruction.ps1'),
'-Client', 'Both', '-Instruction', 'Always use the local release process.',
'-HomePath', str(home),
], capture_output=True, text=True, timeout=20)
self.assertEqual(0, result.returncode, result.stderr)
for client in ('codex', 'claude'):
self.assertEqual('Always use the local release process.\n',
(home / '.my-ai-configuration/instructions' / f'{client}.md').read_text())
appended = subprocess.run([
powershell, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
str(ROOT / 'scripts/commands/add-instruction.ps1'),
'-Client', 'Codex', '-Instruction', 'Keep the release notes current.',
'-HomePath', str(home),
], capture_output=True, text=True, timeout=20)
self.assertEqual(0, appended.returncode, appended.stderr)
self.assertEqual('Always use the local release process.\nKeep the release notes current.\n',
(home / '.my-ai-configuration/instructions/codex.md').read_text())
def test_add_credentials_installs_a_client_scoped_helper(self):
powershell = shutil.which('powershell') or shutil.which('pwsh')
if powershell is None:
self.skipTest('PowerShell is unavailable.')
with tempfile.TemporaryDirectory() as directory:
home = Path(directory)
key = f'MY_TEST_TOKEN_{uuid.uuid4().hex}'
stored = False
try:
result = subprocess.run([
powershell, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File',
str(ROOT / 'scripts/commands/add-credentials.ps1'),
'-Client', 'Codex', '-Key', key, '-ValueFromStdin',
'-HomePath', str(home),
], input='test-value', capture_output=True, text=True, timeout=20)
self.assertEqual(0, result.returncode, result.stderr)
stored = True
helper = home / '.my-ai-configuration/bin/with-credential.ps1'
self.assertTrue(helper.is_file())
self.assertTrue((home / '.my-ai-configuration/bin/with-credential.sh').is_file())
child = f"if ([Environment]::GetEnvironmentVariable('{key}', 'Process')) {{ exit 0 }}; exit 1"
injected = subprocess.run([
powershell, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', str(helper),
'-Client', 'Codex', '-Key', key, '-FilePath', powershell,
'-NoProfile', '-Command', child,
], capture_output=True, text=True, timeout=20)
self.assertEqual(0, injected.returncode, injected.stderr)
finally:
cleanup = subprocess.run([
powershell, '-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command',
f". '{ROOT / 'scripts/lib/credentials.ps1'}'; Remove-ManagedCredential -Client Codex -Key {key}",
], capture_output=True, text=True, timeout=20)
if stored:
self.assertEqual(0, cleanup.returncode, cleanup.stderr)
if __name__ == '__main__':
unittest.main()
+5 -1
View File
@@ -2,8 +2,12 @@
This file is generated into client-specific instruction files. Do not edit generated copies.
Before applying generated instructions, read `~/.my-ai-configuration/instructions/__CLIENT__.md` when it exists. Treat its contents as direct user instructions that override generated rules, skills, and plugins. System and developer safety requirements remain non-overridable.
For a tool credential, invoke `~/.my-ai-configuration/bin/with-credential.__SHELL__` with the client, credential key, and child command. It exposes the credential only to that child process. Never print credentials or place them in commands.
__RULE_LOADING__
Use MCP services only through the `mcporter` CLI. Do not invoke direct MCP tools or create, edit, or use native MCP connections, including `/mcp`. If MCPorter or its required server is unavailable, report that limitation instead of bypassing it.
When MCPorter is active (installed and configured), use MCP services only through the `mcporter` CLI. Do not bypass it with direct MCP tools or native MCP connections, including `/mcp`; report unavailable required servers. If MCPorter is not active, use the direct native MCP service available to you.
Main agent owns state and decisions. Delegate only for independent work where benefit exceeds context/tool cost. Use one implementer for contained work; subagents never spawn agents. Pass minimal context and verify handoffs.