aptly 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +21 -5
- package/README.ja.md +21 -5
- package/README.md +21 -5
- package/README.zh-CN.md +21 -5
- package/dist/bin/aptly.js +57 -5
- package/dist/src/cli-input.d.ts +5 -0
- package/dist/src/cli-input.js +49 -0
- package/dist/src/core.js +9 -0
- package/dist/src/credential-setup.d.ts +14 -0
- package/dist/src/credential-setup.js +56 -0
- package/dist/src/files.js +10 -3
- package/dist/src/provider.js +6 -3
- package/dist/src/runtime.d.ts +5 -1
- package/dist/src/runtime.js +19 -3
- package/dist/src/secret-store.d.ts +27 -0
- package/dist/src/secret-store.js +263 -0
- package/dist/src/server.js +2 -1
- package/dist/src/setup.d.ts +5 -0
- package/dist/src/setup.js +201 -22
- package/dist/src/types.d.ts +4 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/windows-powershell.d.ts +2 -0
- package/dist/src/windows-powershell.js +11 -0
- package/dist/src/windows-private.d.ts +11 -0
- package/dist/src/windows-private.js +98 -0
- package/docs/guide/en.md +105 -15
- package/docs/guide/ja.md +105 -15
- package/docs/guide/ko.md +105 -15
- package/docs/guide/zh-CN.md +105 -15
- package/package.json +3 -2
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { win32 } from 'node:path';
|
|
2
|
+
/** Use the operator's Windows system directory, never cwd or PATH lookup. */
|
|
3
|
+
export function windowsPowerShellPath(env = process.env) {
|
|
4
|
+
const root = env.SystemRoot ?? 'C:\\Windows';
|
|
5
|
+
// Reject drive-relative, UNC/device, traversal and control-character roots.
|
|
6
|
+
if (!/^[A-Za-z]:[\\/]/.test(root) || /[\x00-\x1f\x7f]/.test(root) ||
|
|
7
|
+
root.slice(2).includes(':') || root.split(/[\\/]/).some(part => part === '.' || part === '..')) {
|
|
8
|
+
throw new Error('windows_system_directory_invalid');
|
|
9
|
+
}
|
|
10
|
+
return win32.join(root, 'System32', 'WindowsPowerShell', 'v1.0', 'powershell.exe');
|
|
11
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
type Runner = (executable: string, args: readonly string[], input: string) => string;
|
|
2
|
+
interface WindowsPrivateOptions {
|
|
3
|
+
execute?: Runner;
|
|
4
|
+
}
|
|
5
|
+
/** Preserve the existing file DACL, including inheritance protection, before writing stage content. */
|
|
6
|
+
export declare function copyWindowsFilePermissions(source: string, target: string, options?: WindowsPrivateOptions): void;
|
|
7
|
+
/** Existing paths are inspected, never repaired or granted new permissions. */
|
|
8
|
+
export declare function assertWindowsPrivatePath(path: string, options?: WindowsPrivateOptions): void;
|
|
9
|
+
/** Only call immediately after this process exclusively created the directory. */
|
|
10
|
+
export declare function protectNewWindowsPrivateDirectory(path: string, options?: WindowsPrivateOptions): void;
|
|
11
|
+
export {};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { windowsPowerShellPath } from './windows-powershell.js';
|
|
3
|
+
// Static code only: paths are JSON on stdin, never PowerShell source/arguments.
|
|
4
|
+
// Windows PowerShell's .NET Framework ACL APIs keep inherited ACEs visible.
|
|
5
|
+
const script = String.raw `
|
|
6
|
+
$ErrorActionPreference = 'Stop'
|
|
7
|
+
[Console]::InputEncoding = [Text.UTF8Encoding]::new($false)
|
|
8
|
+
try {
|
|
9
|
+
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
10
|
+
$path = [string]$request.path
|
|
11
|
+
if ([string]::IsNullOrWhiteSpace($path) -or ![IO.Path]::IsPathRooted($path)) { throw 'invalid' }
|
|
12
|
+
$attributes = [IO.File]::GetAttributes($path)
|
|
13
|
+
if (($attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'invalid' }
|
|
14
|
+
$isDirectory = ($attributes -band [IO.FileAttributes]::Directory) -ne 0
|
|
15
|
+
$sidType = [Security.Principal.SecurityIdentifier]
|
|
16
|
+
$current = [Security.Principal.WindowsIdentity]::GetCurrent().User
|
|
17
|
+
$allowed = @($current.Value, 'S-1-5-18', 'S-1-5-32-544')
|
|
18
|
+
if ($request.action -eq 'protect-new-directory') {
|
|
19
|
+
if (!$isDirectory) { throw 'invalid' }
|
|
20
|
+
$acl = [Security.AccessControl.DirectorySecurity]::new()
|
|
21
|
+
$acl.SetOwner($current)
|
|
22
|
+
$acl.SetAccessRuleProtection($true, $false)
|
|
23
|
+
foreach ($sid in $allowed) {
|
|
24
|
+
$identity = [Security.Principal.SecurityIdentifier]::new($sid)
|
|
25
|
+
$rule = [Security.AccessControl.FileSystemAccessRule]::new($identity, [Security.AccessControl.FileSystemRights]::FullControl, ([Security.AccessControl.InheritanceFlags]::ContainerInherit -bor [Security.AccessControl.InheritanceFlags]::ObjectInherit), [Security.AccessControl.PropagationFlags]::None, [Security.AccessControl.AccessControlType]::Allow)
|
|
26
|
+
$acl.AddAccessRule($rule)
|
|
27
|
+
}
|
|
28
|
+
[IO.Directory]::SetAccessControl($path, $acl)
|
|
29
|
+
} elseif ($request.action -ne 'assert-private') { throw 'invalid' }
|
|
30
|
+
$acl = if ($isDirectory) { [IO.Directory]::GetAccessControl($path) } else { [IO.File]::GetAccessControl($path) }
|
|
31
|
+
if ($allowed -notcontains $acl.GetOwner($sidType).Value) { throw 'invalid' }
|
|
32
|
+
$descriptor = [Security.AccessControl.RawSecurityDescriptor]::new($acl.GetSecurityDescriptorBinaryForm(), 0)
|
|
33
|
+
if ($null -eq $descriptor.DiscretionaryAcl) { throw 'invalid' }
|
|
34
|
+
foreach ($rule in $acl.GetAccessRules($true, $true, $sidType)) {
|
|
35
|
+
if ($rule.AccessControlType -eq [Security.AccessControl.AccessControlType]::Allow -and $allowed -notcontains $rule.IdentityReference.Value) { throw 'invalid' }
|
|
36
|
+
}
|
|
37
|
+
[Console]::Out.Write('private')
|
|
38
|
+
} catch { [Console]::Out.Write('rejected'); exit 1 }
|
|
39
|
+
`;
|
|
40
|
+
const run = (executable, args, input) => execFileSync(executable, [...args], {
|
|
41
|
+
input, encoding: 'utf8', windowsHide: true, timeout: 10000, maxBuffer: 4096, stdio: ['pipe', 'pipe', 'pipe']
|
|
42
|
+
});
|
|
43
|
+
const copyScript = String.raw `
|
|
44
|
+
$ErrorActionPreference = 'Stop'
|
|
45
|
+
[Console]::InputEncoding = [Text.UTF8Encoding]::new($false)
|
|
46
|
+
try {
|
|
47
|
+
$request = [Console]::In.ReadToEnd() | ConvertFrom-Json
|
|
48
|
+
$source = [string]$request.source
|
|
49
|
+
$target = [string]$request.target
|
|
50
|
+
if (![IO.Path]::IsPathRooted($source) -or ![IO.Path]::IsPathRooted($target)) { throw 'invalid' }
|
|
51
|
+
$source = [IO.Path]::GetFullPath($source)
|
|
52
|
+
$target = [IO.Path]::GetFullPath($target)
|
|
53
|
+
if ([string]::Equals($source, $target, [StringComparison]::OrdinalIgnoreCase) -or ![string]::Equals([IO.Path]::GetDirectoryName($source), [IO.Path]::GetDirectoryName($target), [StringComparison]::OrdinalIgnoreCase)) { throw 'invalid' }
|
|
54
|
+
foreach ($path in @($source, $target)) {
|
|
55
|
+
$attributes = [IO.File]::GetAttributes($path)
|
|
56
|
+
if (($attributes -band [IO.FileAttributes]::Directory) -ne 0 -or ($attributes -band [IO.FileAttributes]::ReparsePoint) -ne 0) { throw 'invalid' }
|
|
57
|
+
}
|
|
58
|
+
$sections = [Security.AccessControl.AccessControlSections]::Access
|
|
59
|
+
$sourceAcl = [IO.File]::GetAccessControl($source, $sections)
|
|
60
|
+
$expected = $sourceAcl.GetSecurityDescriptorSddlForm($sections)
|
|
61
|
+
$initial = [IO.File]::GetAccessControl($target, $sections).GetSecurityDescriptorSddlForm($sections)
|
|
62
|
+
# A sibling stage normally inherits the same ACL. Avoid reapplying inherited
|
|
63
|
+
# ACEs when the full DACL descriptor (including protection) already matches.
|
|
64
|
+
if ($expected -ceq $initial) { [Console]::Out.Write('private'); exit 0 }
|
|
65
|
+
$acl = [Security.AccessControl.FileSecurity]::new()
|
|
66
|
+
$acl.SetSecurityDescriptorSddlForm($expected, $sections)
|
|
67
|
+
[IO.File]::SetAccessControl($target, $acl)
|
|
68
|
+
$actual = [IO.File]::GetAccessControl($target, $sections).GetSecurityDescriptorSddlForm($sections)
|
|
69
|
+
if ($expected -cne $actual) { throw 'invalid' }
|
|
70
|
+
[Console]::Out.Write('private')
|
|
71
|
+
} catch { [Console]::Out.Write('rejected'); exit 1 }
|
|
72
|
+
`;
|
|
73
|
+
function runScript(scriptText, input, options) {
|
|
74
|
+
try {
|
|
75
|
+
const executable = windowsPowerShellPath();
|
|
76
|
+
const result = (options.execute ?? run)(executable, ['-NoLogo', '-NoProfile', '-NonInteractive', '-EncodedCommand', Buffer.from(scriptText, 'utf16le').toString('base64')], JSON.stringify(input));
|
|
77
|
+
if (result !== 'private')
|
|
78
|
+
throw new Error();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new Error('windows_private_permissions');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function invoke(path, action, options) {
|
|
85
|
+
if (typeof path !== 'string' || !path || path.includes('\0'))
|
|
86
|
+
throw new Error('windows_private_permissions');
|
|
87
|
+
runScript(script, { path, action }, options);
|
|
88
|
+
}
|
|
89
|
+
/** Preserve the existing file DACL, including inheritance protection, before writing stage content. */
|
|
90
|
+
export function copyWindowsFilePermissions(source, target, options = {}) {
|
|
91
|
+
if ([source, target].some(path => typeof path !== 'string' || !path || path.includes('\0')))
|
|
92
|
+
throw new Error('windows_private_permissions');
|
|
93
|
+
runScript(copyScript, { source, target }, options);
|
|
94
|
+
}
|
|
95
|
+
/** Existing paths are inspected, never repaired or granted new permissions. */
|
|
96
|
+
export function assertWindowsPrivatePath(path, options = {}) { invoke(path, 'assert-private', options); }
|
|
97
|
+
/** Only call immediately after this process exclusively created the directory. */
|
|
98
|
+
export function protectNewWindowsPrivateDirectory(path, options = {}) { invoke(path, 'protect-new-directory', options); }
|
package/docs/guide/en.md
CHANGED
|
@@ -4,47 +4,137 @@
|
|
|
4
4
|
|
|
5
5
|
[Back to README](../../README.en.md)
|
|
6
6
|
|
|
7
|
-
Korean is the canonical documentation. This guide
|
|
7
|
+
Korean is the canonical documentation. This guide targets Aptly 0.1.1. Package availability does not establish model access for every account. Claude Code or Codex controls native subagent execution, approvals, sandboxing, and MCP permissions.
|
|
8
8
|
|
|
9
9
|
## Installation
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Requires Node.js 24 or later and Claude Code or Codex. When 0.1.1 is available on npm, use these global installation commands:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
npm install -g aptly@0.1.1
|
|
15
|
+
aptly setup --client all --mode defaults
|
|
16
|
+
aptly --version
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
`aptly --version` should print `0.1.1`. Switching Node.js environments may require reinstalling.
|
|
20
20
|
|
|
21
21
|
`postinstall` registers the MCP server and a `PreToolUse` Hook in the current user's existing host configuration directories. If you use `CODEX_HOME` or `CLAUDE_CONFIG_DIR`, pass the same environment to installation. Automatic registration skips host directories that do not exist; explicit `setup` can create them. To register only one host, use `--client codex` or `--client claude`.
|
|
22
22
|
|
|
23
23
|
New registrations set `ROUTER_PROVIDER=decisions`, the Hook's `defaults` policy, and a private ledger for each host. Other settings and Hooks, and existing Aptly commands, environments, providers, and ledger paths, are preserved. Existing fixture settings are not automatically switched to paid mode.
|
|
24
24
|
|
|
25
|
+
### Configure with the CLI
|
|
26
|
+
|
|
27
|
+
The `set` commands require 0.1.1 or later. Commands do not automatically switch existing launch paths. API-key setup accepts only the current package’s standard Node/server registration with a verified OS-source-compatible runtime. Custom wrappers, older versions, and other installation prefixes are rejected with `credential_launcher_unverified`; explicitly switch the registration or upgrade it before trying again.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
aptly set default
|
|
31
|
+
aptly set env APTLY_CODEX_MODELS
|
|
32
|
+
aptly set env OPENAI_API_KEY
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
The first command registers Claude Code and Codex and adds missing default environment settings. It preserves existing launch commands, environment whitelists, providers, custom candidates, and advisory policies. The second prompts for a value with hidden input and changes only the named variable. The third stores the API key in the OS credential store and connects it to the selected host at startup. Do not pass values as command arguments.
|
|
36
|
+
|
|
37
|
+
Both hosts are selected by default. Use `--client codex`, `--client claude`, or `--client all` to select targets. For example, enter a JSON string such as `["haiku","sonnet","opus"]` for Claude candidates.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
aptly set env APTLY_CLAUDE_MODELS --client claude
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Use explicit `--stdin` for automation. This is a public-variable example; never put a real key in a `printf` command or shell history.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
printf '%s\n' '["gpt-6.1-terra"]' | aptly set env APTLY_CODEX_MODELS --stdin
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Allowed public keys are `ROUTER_PROVIDER` (`fixture` or `decisions`), `APTLY_CLAUDE_MODELS`, `APTLY_CODEX_MODELS`, `CLAUDE_MODELS`, `CODEX_MODELS`, and `APTLY_MODEL_INFO_PATH`. Candidates must be JSON array strings of supported models. Invalid values, duplicates, and primary-name/alias conflicts are rejected. `[]` disables that host; a model-cache path must be absolute and inside a private directory. Managed ledger and Hook-policy paths cannot be changed through this command.
|
|
50
|
+
|
|
51
|
+
API keys are stored through macOS Keychain, an installed Linux `secret-tool`, or Windows Credential Manager. The Windows backend uses PowerShell and Win32 Generic credentials in the current-user scope, with a limit of 2,560 UTF-8 bytes. Windows CI verified synthetic key storage, lookup, update and deletion, Unicode paths, isolated host configuration, key references, and ACL preservation. It did not run actual Claude Code or Codex sessions on Windows. Unavailable credential stores return an error; there is no plaintext-file fallback. Keys use separate host accounts, while MCP files contain only `APTLY_SECRET_BACKEND=os` and `APTLY_SECRET_CLIENT` references. Once the OS source is selected, a missing or unreadable key does not silently fall back to another environment key. `set default` preserves existing wrapper/environment authentication; running `set env OPENAI_API_KEY` explicitly selects OS-store authentication.
|
|
52
|
+
|
|
53
|
+
Results contain host statuses and fixed error codes, never key values. Inspect each host when the result is `partial`. `credential_store_failed` requires checking store access/support; `credential_rollback_failed` requires checking whether the previous key was restored. A failed host restores its old key where possible. Do not interpret success on another host as an atomic success across all hosts.
|
|
54
|
+
|
|
55
|
+
A store timeout or malformed acknowledgement may occur after the OS has written the new value. On failure, Aptly attempts to restore the previous value (or remove the new entry if none existed), then reads it back. `credential_rollback_failed` is added to the original failure codes when recovery cannot be confirmed; do not assume either key is active. Check the OS credential store before retrying. This is best-effort recovery, not an atomic transaction across processes or hosts.
|
|
56
|
+
|
|
57
|
+
Restart MCP, then inspect `provider`, candidates, and `credential_status` with `get_routing_capabilities`. `os_store` means a key was loaded, not that the API authenticated it. Default and public-variable setup continue to preserve custom wrappers. API-key setup does not change host settings for an unverified launch path and restores that host's previous key where possible if storage was already attempted. Do not assume a new key automatically takes effect through an existing wrapper. These commands do not bypass Codex `/hooks` trust or host permissions.
|
|
58
|
+
|
|
25
59
|
### Keys and restarting
|
|
26
60
|
|
|
27
|
-
|
|
61
|
+
Pass the classification key, `OPENAI_API_KEY`, through a secure environment or credential launcher to the MCP process. It is separate from host subscription login. Never write key values into command arguments, requests, configuration files, source, logs, or temporary files.
|
|
62
|
+
|
|
63
|
+
### MCP environment setup, step by step
|
|
28
64
|
|
|
29
|
-
|
|
65
|
+
Run `setup`, then merge the examples below into the existing `aptly` registration. Preserve `command`, `args`, credential launchers, `ROUTER_LEDGER_PATH`, and `APTLY_HOOK_POLICY_PATH`. Setup creates the private ledger/policy directories; a manual ledger path must be absolute and inside a user-owned directory with mode 700.
|
|
30
66
|
|
|
31
|
-
|
|
67
|
+
#### Configuration file locations
|
|
32
68
|
|
|
33
|
-
|
|
|
69
|
+
| Host | User registration used by Aptly |
|
|
34
70
|
|---|---|
|
|
35
|
-
|
|
|
36
|
-
| `
|
|
37
|
-
|
|
71
|
+
| Codex | `$CODEX_HOME/config.toml`; `~/.codex/config.toml` when `CODEX_HOME` is unset |
|
|
72
|
+
| Claude Code | `mcpServers.aptly` in `~/.claude.json`; `.claude.json` inside `CLAUDE_CONFIG_DIR` when that directory is configured |
|
|
73
|
+
|
|
74
|
+
Claude Code stores Hooks in `~/.claude/settings.json` by default, or `$CLAUDE_CONFIG_DIR/settings.json` when `CLAUDE_CONFIG_DIR` is set. This is separate from MCP registration. If project `.mcp.json`, managed settings, or account-specific directories are in use, check the active entry in `/mcp` before editing or adding a registration.
|
|
75
|
+
|
|
76
|
+
#### Passing the API key
|
|
77
|
+
|
|
78
|
+
For a direct launch, enter the key in **Bash** without displaying or saving its value, then start the host from that shell. In zsh, run `bash` first:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
read -rsp 'OpenAI API key: ' OPENAI_API_KEY
|
|
82
|
+
printf '\n'
|
|
83
|
+
export OPENAI_API_KEY
|
|
84
|
+
codex
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Replace `codex` with `claude` for Claude Code. This does not save the key permanently. A running GUI does not inherit another terminal’s environment; use its secure environment mechanism or preserve the existing credential launcher.
|
|
88
|
+
|
|
89
|
+
#### Codex example
|
|
90
|
+
|
|
91
|
+
Edit the existing `[mcp_servers.aptly]` tables; do not append duplicate tables. Add `OPENAI_API_KEY` to `env_vars` without removing existing entries. `env_vars` forwards named host variables; `.env` supplies fixed MCP values.
|
|
92
|
+
|
|
93
|
+
```toml
|
|
94
|
+
[mcp_servers.aptly]
|
|
95
|
+
env_vars = ["OPENAI_API_KEY"]
|
|
96
|
+
|
|
97
|
+
[mcp_servers.aptly.env]
|
|
98
|
+
ROUTER_PROVIDER = "decisions"
|
|
99
|
+
APTLY_CLAUDE_MODELS = '["haiku","sonnet","opus"]'
|
|
100
|
+
APTLY_CODEX_MODELS = '["gpt-6-luna","gpt-6.1-terra","gpt-6.1-sol"]'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Claude Code example
|
|
104
|
+
|
|
105
|
+
Merge these values into the existing `mcpServers.aptly.env` object:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"ROUTER_PROVIDER": "decisions",
|
|
110
|
+
"APTLY_CLAUDE_MODELS": "[\"haiku\",\"sonnet\",\"opus\"]",
|
|
111
|
+
"APTLY_CODEX_MODELS": "[\"gpt-6-luna\",\"gpt-6.1-terra\",\"gpt-6.1-sol\"]"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
For direct launches, this optional reference forwards a key already present in the Claude Code environment. Do not add it to a credential-launcher setup. If the variable is missing, the key will not be supplied and the reference may remain unexpanded:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Model candidates are **JSON strings**: leave them unset for defaults or use `[]` to disable that host’s recommendations. Aptly reads the MCP startup environment and does not load project `.env` files.
|
|
124
|
+
|
|
125
|
+
Restart the host or MCP server after changes. **In Codex, review and trust the new Hook through `/hooks`.** Check `/mcp` and `get_routing_capabilities` for `provider`, candidates, and `automatic_hook.mode`. This free query does not authenticate the key; `recommend_agent` may incur a charge. For `unauthorized`, check key forwarding; for `needs_context`, check the task summary.
|
|
126
|
+
|
|
127
|
+
See the official [Codex configuration reference](https://learn.chatgpt.com/docs/config-file/config-reference) and [Claude Code MCP guide](https://code.claude.com/docs/en/mcp) for configuration locations and environment forwarding.
|
|
38
128
|
|
|
39
129
|
### Skipping registration and removing it
|
|
40
130
|
|
|
41
131
|
```sh
|
|
42
|
-
APTLY_SKIP_SETUP=1 npm install
|
|
132
|
+
APTLY_SKIP_SETUP=1 npm install -g aptly@0.1.1
|
|
43
133
|
aptly setup --client codex --mode defaults
|
|
44
134
|
aptly remove --client all
|
|
45
135
|
```
|
|
46
136
|
|
|
47
|
-
|
|
137
|
+
`CI` or npm `--ignore-scripts` also skips registration. Use `setup` to register later. To change mode, remove the registration and run setup with the desired `--mode`; `advisory` only supplies advice. For `partial`, check each host’s error codes. Removal preserves user-modified entries and reports conflicts.
|
|
48
138
|
|
|
49
139
|
## Automatic Hook
|
|
50
140
|
|
package/docs/guide/ja.md
CHANGED
|
@@ -4,47 +4,137 @@
|
|
|
4
4
|
|
|
5
5
|
[README に戻る](../../README.ja.md)
|
|
6
6
|
|
|
7
|
-
韓国語版が正本です。このガイドは 0.1.
|
|
7
|
+
韓国語版が正本です。このガイドは Aptly 0.1.1 を対象とします。パッケージをインストールできることは、すべてのアカウントでモデルにアクセスできることを意味しません。子エージェントの実行・承認・サンドボックス・MCP 権限は、Claude Code または Codex が管理します。
|
|
8
8
|
|
|
9
9
|
## インストール
|
|
10
10
|
|
|
11
|
-
Node.js 24 以上と Claude Code または Codex
|
|
11
|
+
Node.js 24 以上と Claude Code または Codex が必要です。0.1.1 が npm で公開されている場合は、以下のグローバルインストールコマンドを使用してください:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
npm install -g aptly@0.1.1
|
|
15
|
+
aptly setup --client all --mode defaults
|
|
16
|
+
aptly --version
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
`aptly --version` が `0.1.1` を表示することを確認してください。Node.js 環境を切り替えると再インストールが必要な場合があります。
|
|
20
20
|
|
|
21
21
|
`postinstall` は、現在のユーザーの既存のホスト設定ディレクトリに MCP と `PreToolUse` Hook を登録します。`CODEX_HOME`・`CLAUDE_CONFIG_DIR` を使う場合は、インストールプロセスにも同じ環境を渡してください。まだ存在しないホストディレクトリは自動登録ではスキップし、明示的な `setup` で作成できます。必要なホストだけを登録するには、`--client codex` または `--client claude` を使用してください。
|
|
22
22
|
|
|
23
23
|
新規登録では `ROUTER_PROVIDER=decisions`、Hook の `defaults` ポリシー、ホストごとの非公開台帳を指定します。他の既存設定・Hook と、既存の Aptly のコマンド・環境・provider・台帳パスは保持します。既存の fixture 設定を自動で有料モードに切り替えることはありません。
|
|
24
24
|
|
|
25
|
+
### CLI による設定
|
|
26
|
+
|
|
27
|
+
`set` コマンドは 0.1.1 以降で利用できます。既存の起動パスは自動で切り替えません。API キー設定は、現在のパッケージの標準 Node/サーバー登録で、OS 資格情報の参照に対応する実行環境だと確認できた場合にのみ使用できます。独自ラッパー、旧バージョン、別のインストール先は `credential_launcher_unverified` で拒否します。登録を明示的に切り替えるかアップグレードしてから、再度実行してください。
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
aptly set default
|
|
31
|
+
aptly set env APTLY_CODEX_MODELS
|
|
32
|
+
aptly set env OPENAI_API_KEY
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
最初のコマンドは Claude Code と Codex を登録し、未設定のデフォルト環境設定を追加します。既存の起動コマンド、環境変数の許可リスト、分類方式、独自の候補、advisory ポリシーは保持します。次のコマンドは入力を表示せずに値を尋ね、指定した変数だけを変更します。最後のコマンドは API キーを OS の資格情報ストアに保存し、選択したホストの起動時に読み込むよう設定します。値をコマンド引数に渡さないでください。
|
|
36
|
+
|
|
37
|
+
既定では両ホストを選択します。`--client codex`、`--client claude`、`--client all` で対象を指定できます。Claude 候補には `["haiku","sonnet","opus"]` のような JSON 文字列を入力してください。
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
aptly set env APTLY_CLAUDE_MODELS --client claude
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
自動化では `--stdin` を明示してください。以下は公開変数の例です。実際のキーを `printf` コマンドやシェル履歴に含めないでください。
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
printf '%s\n' '["gpt-6.1-terra"]' | aptly set env APTLY_CODEX_MODELS --stdin
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
設定できる公開キーは `ROUTER_PROVIDER`(`fixture` または `decisions`)、`APTLY_CLAUDE_MODELS`、`APTLY_CODEX_MODELS`、`CLAUDE_MODELS`、`CODEX_MODELS`、`APTLY_MODEL_INFO_PATH` です。候補は対応モデルの JSON 配列文字列で、無効な値、重複、正式名と別名の競合は拒否します。`[]` は該当ホストの推薦を無効にします。モデルキャッシュには非公開ディレクトリ内の絶対パスが必要です。台帳や Hook ポリシーの管理パスはこのコマンドで変更できません。
|
|
50
|
+
|
|
51
|
+
API キーは macOS Keychain、インストール済みの Linux `secret-tool`、または Windows Credential Manager に保存します。Windows バックエンドは PowerShell と、現在のユーザー範囲の Win32 Generic 資格情報を使用し、UTF-8 データの上限は 2,560 バイトです。Windows CI では、合成キーの保存・読み取り・更新・削除、Unicode パス、分離したホスト設定、キー参照、ACL の保持を確認しました。Windows 上の実際の Claude Code・Codex セッションを実行して検証したものではありません。ストアを利用できない場合はエラーとなり、平文ファイルには切り替えません。キーはホスト別アカウントに分け、MCP ファイルには `APTLY_SECRET_BACKEND=os` と `APTLY_SECRET_CLIENT` の参照だけを保存します。OS ストアを選択した後、キーがない場合や読み取りに失敗した場合も、別の環境変数キーに黙って切り替えません。`set default` は既存のラッパーや環境による認証を保持し、`set env OPENAI_API_KEY` が OS ストア認証を明示的に選択します。
|
|
52
|
+
|
|
53
|
+
結果にはホストごとの状態と固定エラーコードだけを返し、キーの値は表示しません。`partial` の場合は各ホストを確認してください。`credential_store_failed` ではストアへのアクセスや対応状況を、`credential_rollback_failed` では以前のキーを復元できたかを確認します。失敗したホストの旧キーは可能な場合に復元します。他のホストの成功を、全ホストでの原子的な成功とみなさないでください。
|
|
54
|
+
|
|
55
|
+
保存のタイムアウトや応答解析の失敗は、OS が新しい値を書き込んだ後にも起こり得ます。失敗時は以前の値を復元し、以前の値がなければ新しい項目を削除して、再読み込みで確認します。復旧を確認できない場合は、元のエラーコードに `credential_rollback_failed` を追加します。どちらのキーが有効かを断定せず、再試行前に OS の資格情報ストアを確認してください。これは可能な範囲での復旧であり、プロセスやホストをまたぐ原子的なトランザクションではありません。
|
|
56
|
+
|
|
57
|
+
変更後は MCP を再起動し、`get_routing_capabilities` の `provider`、候補、`credential_status` を確認してください。`os_store` はキーを読み込めたことを示し、API 認証の成功を示すものではありません。デフォルト設定と公開変数の設定では、引き続き独自ラッパーを保持します。API キー設定は未確認の起動パスのホスト設定を変更せず、保存を試みていた場合は可能な限りそのホストの旧キーを復元します。既存のラッパーにも新しいキーが自動で適用されるとは想定しないでください。Codex の `/hooks` 信頼承認やホスト権限は迂回しません。
|
|
58
|
+
|
|
25
59
|
### キーと再起動
|
|
26
60
|
|
|
27
|
-
`OPENAI_API_KEY`
|
|
61
|
+
分類用の `OPENAI_API_KEY` は、安全な環境や資格情報ランチャーを通して MCP プロセスに渡してください。ホストのサブスクリプション認証とは別です。キーの値をコマンド引数・リクエスト・設定ファイル・ソース・ログ・一時ファイルに記録しないでください。
|
|
62
|
+
|
|
63
|
+
### MCP 環境変数の設定手順
|
|
28
64
|
|
|
29
|
-
|
|
65
|
+
`setup` 後、以下の例を既存の `aptly` 登録に統合してください。`command`、`args`、資格情報ランチャー、`ROUTER_LEDGER_PATH`、`APTLY_HOOK_POLICY_PATH` は保持します。Setup は非公開の台帳・ポリシーディレクトリを作成します。台帳を手動指定する場合は、ユーザー所有で権限 700 のディレクトリ内の絶対パスを使ってください。
|
|
30
66
|
|
|
31
|
-
|
|
67
|
+
#### 設定ファイルの場所
|
|
32
68
|
|
|
33
|
-
|
|
|
69
|
+
| ホスト | Aptly が使用するユーザー登録の場所 |
|
|
34
70
|
|---|---|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
71
|
+
| Codex | `$CODEX_HOME/config.toml`。`CODEX_HOME` 未指定の場合は `~/.codex/config.toml` |
|
|
72
|
+
| Claude Code | `~/.claude.json` の `mcpServers.aptly`。`CLAUDE_CONFIG_DIR` 指定時は、そのディレクトリの `.claude.json` |
|
|
73
|
+
|
|
74
|
+
Claude Code の Hook ファイルは通常 `~/.claude/settings.json` ですが、`CLAUDE_CONFIG_DIR` を指定すると `$CLAUDE_CONFIG_DIR/settings.json` になります。MCP 登録とは別です。プロジェクトの `.mcp.json`、管理設定、アカウント別ディレクトリを使う場合は、編集・追加前に `/mcp` で有効な登録を確認してください。
|
|
75
|
+
|
|
76
|
+
#### API キーの渡し方
|
|
77
|
+
|
|
78
|
+
直接起動する場合は、**Bash** でキーを画面やコマンド履歴に残さず入力し、同じシェルからホストを起動します。zsh では先に `bash` を実行してください:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
read -rsp 'OpenAI API key: ' OPENAI_API_KEY
|
|
82
|
+
printf '\n'
|
|
83
|
+
export OPENAI_API_KEY
|
|
84
|
+
codex
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Claude Code では `codex` を `claude` に置き換えます。キーは永続保存されません。起動済み GUI は別のターミナルの環境を継承しないため、アプリの安全な環境受け渡し機能か既存の資格情報ランチャーを使ってください。
|
|
88
|
+
|
|
89
|
+
#### Codex の例
|
|
90
|
+
|
|
91
|
+
既存の `[mcp_servers.aptly]` テーブルを編集し、同じテーブルを重複追加しないでください。`env_vars` の既存項目を保持して `OPENAI_API_KEY` を追加します。`env_vars` はホストの環境変数を名前で転送し、`.env` は MCP に固定値を渡します。
|
|
92
|
+
|
|
93
|
+
```toml
|
|
94
|
+
[mcp_servers.aptly]
|
|
95
|
+
env_vars = ["OPENAI_API_KEY"]
|
|
96
|
+
|
|
97
|
+
[mcp_servers.aptly.env]
|
|
98
|
+
ROUTER_PROVIDER = "decisions"
|
|
99
|
+
APTLY_CLAUDE_MODELS = '["haiku","sonnet","opus"]'
|
|
100
|
+
APTLY_CODEX_MODELS = '["gpt-6-luna","gpt-6.1-terra","gpt-6.1-sol"]'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Claude Code の例
|
|
104
|
+
|
|
105
|
+
既存の `mcpServers.aptly.env` オブジェクトに以下を統合してください:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"ROUTER_PROVIDER": "decisions",
|
|
110
|
+
"APTLY_CLAUDE_MODELS": "[\"haiku\",\"sonnet\",\"opus\"]",
|
|
111
|
+
"APTLY_CODEX_MODELS": "[\"gpt-6-luna\",\"gpt-6.1-terra\",\"gpt-6.1-sol\"]"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
直接起動では、次の任意の参照で Claude Code の環境にあるキーを渡せます。資格情報ランチャーを使う設定には追加しないでください。変数がない場合はキーが渡らず、参照文字列が未展開のまま残ることがあります:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
モデル候補は **JSON 文字列**です。未設定ならデフォルトを使い、`[]` はそのホストの推薦を無効にします。Aptly は MCP 起動時の環境を読み、プロジェクトの `.env` ファイルは自動で読み込みません。
|
|
124
|
+
|
|
125
|
+
変更後はホストまたは MCP を再起動してください。**Codex では `/hooks` で新しい Hook を確認して信頼を承認します。** `/mcp` と `get_routing_capabilities` で `provider`、候補、`automatic_hook.mode` を確認してください。この無料照会はキーの認証を検証せず、`recommend_agent` には料金が発生する場合があります。`unauthorized` ではキーの受け渡し、`needs_context` ではタスク要約を確認してください。
|
|
126
|
+
|
|
127
|
+
設定場所と受け渡し方式の公式説明は、[Codex 構成リファレンス](https://learn.chatgpt.com/docs/config-file/config-reference)と [Claude Code MCP ガイド](https://code.claude.com/docs/en/mcp)を参照してください。
|
|
38
128
|
|
|
39
129
|
### スキップ・削除
|
|
40
130
|
|
|
41
131
|
```sh
|
|
42
|
-
APTLY_SKIP_SETUP=1 npm install
|
|
132
|
+
APTLY_SKIP_SETUP=1 npm install -g aptly@0.1.1
|
|
43
133
|
aptly setup --client codex --mode defaults
|
|
44
134
|
aptly remove --client all
|
|
45
135
|
```
|
|
46
136
|
|
|
47
|
-
`CI`
|
|
137
|
+
`CI` や npm `--ignore-scripts` でも登録をスキップします。後から `setup` で登録できます。モード変更時は登録を削除して希望の `--mode` で再設定します。`advisory` は推薦のみを返します。`partial` ではホスト別のエラーコードを確認してください。削除時はユーザー変更済みの項目を保持し、競合を報告します。
|
|
48
138
|
|
|
49
139
|
## 自動 Hook
|
|
50
140
|
|
package/docs/guide/ko.md
CHANGED
|
@@ -4,47 +4,137 @@
|
|
|
4
4
|
|
|
5
5
|
[README로 돌아가기](../../README.md)
|
|
6
6
|
|
|
7
|
-
한국어가 정본입니다. 이 안내는 0.1.
|
|
7
|
+
한국어가 정본입니다. 이 안내는 Aptly 0.1.1을 기준으로 합니다. 패키지를 설치할 수 있다는 사실이 모든 계정의 모델 접근 가능성을 뜻하지는 않습니다. 실제 자식 실행·승인·샌드박스·MCP 권한은 Claude Code 또는 Codex가 담당합니다.
|
|
8
8
|
|
|
9
9
|
## 설치
|
|
10
10
|
|
|
11
|
-
Node.js 24 이상과 Claude Code 또는 Codex가 필요합니다.
|
|
11
|
+
Node.js 24 이상과 Claude Code 또는 Codex가 필요합니다. 0.1.1이 npm에 게시된 경우 다음 전역 설치 명령을 사용하십시오:
|
|
12
12
|
|
|
13
13
|
```sh
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
npm install -g aptly@0.1.1
|
|
15
|
+
aptly setup --client all --mode defaults
|
|
16
|
+
aptly --version
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
`aptly --version`의 출력은 `0.1.1`이어야 합니다. Node.js 환경을 바꾸면 재설치가 필요할 수 있습니다.
|
|
20
20
|
|
|
21
21
|
`postinstall`은 현재 사용자의 기존 호스트 설정 디렉터리에 MCP와 `PreToolUse` Hook을 등록합니다. `CODEX_HOME`·`CLAUDE_CONFIG_DIR`을 사용한다면 설치 프로세스에도 같은 환경을 전달하십시오. 아직 없는 호스트 디렉터리는 자동 등록에서 건너뛰고, 명시적인 `setup`으로 만들 수 있습니다. 필요한 호스트만 등록하려면 `--client codex` 또는 `--client claude`를 사용하십시오.
|
|
22
22
|
|
|
23
23
|
새 등록은 `ROUTER_PROVIDER=decisions`, Hook `defaults` 정책과 호스트별 비공개 원장을 지정합니다. 기존 다른 설정·Hook, 기존 Aptly의 실행 명령·환경·분류 방식·원장 경로는 보존합니다. 기존 fixture 설정을 자동으로 유료 모드로 바꾸지 않습니다.
|
|
24
24
|
|
|
25
|
+
### CLI로 설정하기
|
|
26
|
+
|
|
27
|
+
`set` 명령은 0.1.1부터 지원합니다. 명령은 기존 실행 경로를 자동 전환하지 않습니다. API 키 설정은 현재 패키지의 표준 Node/서버 등록과 OS 자격 저장소 참조 지원이 확인된 실행 경로에만 허용됩니다. 사용자 지정 래퍼·구버전·다른 설치 위치는 `credential_launcher_unverified`로 거절하며, 등록을 명시적으로 전환하거나 업그레이드한 뒤 다시 실행해야 합니다.
|
|
28
|
+
|
|
29
|
+
```sh
|
|
30
|
+
aptly set default
|
|
31
|
+
aptly set env APTLY_CODEX_MODELS
|
|
32
|
+
aptly set env OPENAI_API_KEY
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
첫 명령은 Claude Code와 Codex 등록 및 누락된 기본 환경변수를 추가합니다. 기존 실행 명령·환경 목록·provider·사용자 후보·advisory 정책은 덮어쓰지 않습니다. 두 번째 명령은 값을 화면에 표시하지 않는 대화형 입력을 받으며, 명시한 변수만 변경합니다. 세 번째 명령은 API 키를 OS 자격 저장소에 보관하고 선택한 호스트가 시작할 때 읽도록 연결합니다. 값을 명령 인자로 붙이지 마십시오.
|
|
36
|
+
|
|
37
|
+
기본 대상은 두 호스트입니다. `--client codex`, `--client claude`, `--client all`로 대상을 선택할 수 있습니다. 예를 들어 Claude 후보 입력에는 `["haiku","sonnet","opus"]` 같은 JSON 문자열을 사용합니다.
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
aptly set env APTLY_CLAUDE_MODELS --client claude
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
자동화에서는 `--stdin`을 명시하십시오. 일반 변수의 예는 다음과 같습니다. 실제 키를 `printf` 명령이나 셸 기록에 넣지 마십시오.
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
printf '%s\n' '["gpt-6.1-terra"]' | aptly set env APTLY_CODEX_MODELS --stdin
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
일반 설정 허용 키는 `ROUTER_PROVIDER`(`fixture` 또는 `decisions`), `APTLY_CLAUDE_MODELS`, `APTLY_CODEX_MODELS`, `CLAUDE_MODELS`, `CODEX_MODELS`, `APTLY_MODEL_INFO_PATH`입니다. 후보는 지원하는 모델의 JSON 배열 문자열이며 잘못된 값·중복·기본 이름과 별칭 충돌은 거절합니다. `[]`는 해당 호스트 추천을 끄고, 모델 캐시 경로는 비공개 디렉터리 안의 절대 경로여야 합니다. 원장·Hook 정책 경로 같은 관리 항목은 이 명령으로 변경하지 않습니다.
|
|
50
|
+
|
|
51
|
+
API 키는 macOS Keychain, Linux의 설치된 `secret-tool`, 또는 Windows Credential Manager에 저장합니다. Windows 백엔드는 PowerShell과 Win32 Generic 자격 증명을 사용하며 현재 사용자 범위에 UTF-8 데이터 최대 2,560바이트를 저장합니다. Windows CI에서 합성 키의 저장·조회·수정·삭제, 한글 경로와 격리된 호스트 설정·키 참조·ACL 보존을 확인했습니다. Windows의 실제 Claude Code·Codex 세션 실행을 검증한 것은 아닙니다. 자격 저장소를 사용할 수 없으면 오류를 반환하고 평문 파일로 대체하지 않습니다. 키는 호스트별 계정으로 분리하고 MCP 파일에는 `APTLY_SECRET_BACKEND=os`·`APTLY_SECRET_CLIENT` 참조만 씁니다. OS 저장소를 명시한 뒤 키가 없거나 읽기에 실패해도 다른 환경변수 키로 조용히 대체하지 않습니다. 기존 래퍼/환경 인증은 `set default`에서 유지하며, `set env OPENAI_API_KEY`를 실행해야 OS 저장소 인증을 명시적으로 선택합니다.
|
|
52
|
+
|
|
53
|
+
명령 결과는 키 값 대신 호스트별 상태와 오류 코드만 제공합니다. `partial`이면 호스트별 결과를 확인하십시오. `credential_store_failed`는 저장소 접근·지원 상태를, `credential_rollback_failed`는 이전 키 복원 여부를 별도로 확인해야 한다는 뜻입니다. 실패한 호스트의 키는 가능한 경우 이전 값으로 복원합니다. 다른 호스트의 성공까지 전체 원자적 성공으로 간주하지 마십시오.
|
|
54
|
+
|
|
55
|
+
저장 시간 초과나 응답 해석 실패는 OS가 새 값을 기록한 뒤에도 발생할 수 있습니다. 실패하면 이전 값을 복원하거나, 이전 값이 없었다면 새 항목을 제거한 뒤 재조회합니다. 복구 여부를 확인하지 못하면 원래 오류 코드와 함께 `credential_rollback_failed`를 반환합니다. 어느 키가 활성 상태인지 단정하지 말고 재시도 전에 OS 자격 증명 저장소를 확인하십시오. 이는 가능한 범위의 복구이며 프로세스·호스트 전체의 원자적 트랜잭션이 아닙니다.
|
|
56
|
+
|
|
57
|
+
변경 후 MCP를 재시작하고 `get_routing_capabilities`의 `provider`, 후보 목록, `credential_status`를 확인하십시오. `os_store`는 저장소에서 키를 읽었다는 뜻이며 API 유효성 검증은 아닙니다. 기본 설정과 일반 변수 설정은 기존 사용자 지정 래퍼를 보존합니다. API 키 설정은 호환성이 확인되지 않은 실행 경로의 호스트 설정을 변경하지 않으며, 이미 저장을 시도했다면 해당 호스트의 이전 키를 가능한 경우 복원합니다. 새 키가 기존 래퍼에도 자동 적용된다고 가정하지 마십시오. 명령은 Codex의 `/hooks` 신뢰 승인이나 호스트 권한을 우회하지 않습니다.
|
|
58
|
+
|
|
25
59
|
### 키와 재시작
|
|
26
60
|
|
|
27
|
-
`OPENAI_API_KEY
|
|
61
|
+
분류용 `OPENAI_API_KEY`를 안전한 환경이나 자격 주입 래퍼를 통해 MCP 프로세스에 전달하십시오. 호스트 구독 로그인과 별개입니다. 키 값을 명령 인자·요청·설정 파일·소스·로그·임시 파일에 기록하지 마십시오.
|
|
62
|
+
|
|
63
|
+
### MCP 환경변수 설정 순서
|
|
28
64
|
|
|
29
|
-
|
|
65
|
+
`setup` 후 아래 예제를 기존 `aptly` 등록에 병합하십시오. `command`, `args`, 자격 주입 래퍼, `ROUTER_LEDGER_PATH`, `APTLY_HOOK_POLICY_PATH`는 보존하십시오. Setup이 비공개 원장·정책 디렉터리를 만듭니다. 원장 경로를 직접 지정한다면 사용자 소유·권한 700인 디렉터리 안의 절대 경로여야 합니다.
|
|
30
66
|
|
|
31
|
-
|
|
67
|
+
#### 설정 파일 위치
|
|
32
68
|
|
|
33
|
-
|
|
|
69
|
+
| 호스트 | Aptly가 사용하는 사용자 등록 위치 |
|
|
34
70
|
|---|---|
|
|
35
|
-
|
|
|
36
|
-
| `
|
|
37
|
-
|
|
71
|
+
| Codex | `$CODEX_HOME/config.toml`; `CODEX_HOME` 미지정 시 `~/.codex/config.toml` |
|
|
72
|
+
| Claude Code | `~/.claude.json`의 `mcpServers.aptly`; `CLAUDE_CONFIG_DIR` 지정 시 해당 디렉터리의 `.claude.json` |
|
|
73
|
+
|
|
74
|
+
Claude Code의 Hook 파일은 기본적으로 `~/.claude/settings.json`이며, `CLAUDE_CONFIG_DIR`을 지정하면 `$CLAUDE_CONFIG_DIR/settings.json`입니다. MCP 등록 파일과는 별개입니다. 프로젝트 `.mcp.json`·관리 설정·계정별 디렉터리를 사용한다면 수정하거나 등록하기 전에 `/mcp`에서 활성 항목을 확인하십시오.
|
|
75
|
+
|
|
76
|
+
#### API 키 전달
|
|
77
|
+
|
|
78
|
+
직접 실행할 때는 **Bash**에서 키를 화면·명령 기록에 남기지 않고 입력한 뒤 같은 셸에서 호스트를 시작합니다. zsh에서는 먼저 `bash`를 실행하십시오:
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
read -rsp 'OpenAI API key: ' OPENAI_API_KEY
|
|
82
|
+
printf '\n'
|
|
83
|
+
export OPENAI_API_KEY
|
|
84
|
+
codex
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Claude Code는 `codex` 대신 `claude`를 실행하십시오. 키는 영구 저장되지 않습니다. 실행 중인 GUI는 다른 터미널의 환경을 상속하지 않으므로 앱의 안전한 환경 전달 기능이나 기존 자격 주입 래퍼를 사용하십시오.
|
|
88
|
+
|
|
89
|
+
#### Codex 예제
|
|
90
|
+
|
|
91
|
+
기존 `[mcp_servers.aptly]` 테이블을 편집하고 같은 테이블을 중복 추가하지 마십시오. `env_vars`의 기존 항목을 유지하면서 `OPENAI_API_KEY`를 추가하십시오. `env_vars`는 호스트 환경변수를 이름으로 전달하고 `.env`는 MCP의 고정 값을 지정합니다.
|
|
92
|
+
|
|
93
|
+
```toml
|
|
94
|
+
[mcp_servers.aptly]
|
|
95
|
+
env_vars = ["OPENAI_API_KEY"]
|
|
96
|
+
|
|
97
|
+
[mcp_servers.aptly.env]
|
|
98
|
+
ROUTER_PROVIDER = "decisions"
|
|
99
|
+
APTLY_CLAUDE_MODELS = '["haiku","sonnet","opus"]'
|
|
100
|
+
APTLY_CODEX_MODELS = '["gpt-6-luna","gpt-6.1-terra","gpt-6.1-sol"]'
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
#### Claude Code 예제
|
|
104
|
+
|
|
105
|
+
기존 `mcpServers.aptly.env` 객체에 다음 값을 병합하십시오:
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"ROUTER_PROVIDER": "decisions",
|
|
110
|
+
"APTLY_CLAUDE_MODELS": "[\"haiku\",\"sonnet\",\"opus\"]",
|
|
111
|
+
"APTLY_CODEX_MODELS": "[\"gpt-6-luna\",\"gpt-6.1-terra\",\"gpt-6.1-sol\"]"
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
직접 실행에서는 다음 선택적 참조로 Claude Code 환경에 이미 있는 키를 전달할 수 있습니다. 자격 주입 래퍼에는 추가하지 마십시오. 변수가 없으면 키가 전달되지 않고 참조 문자열이 그대로 남을 수 있습니다:
|
|
116
|
+
|
|
117
|
+
```json
|
|
118
|
+
{
|
|
119
|
+
"OPENAI_API_KEY": "${OPENAI_API_KEY}"
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
모델 후보는 **JSON 문자열**입니다. 미지정하면 기본 후보를 사용하고 `[]`는 해당 호스트 추천을 끕니다. Aptly는 MCP 시작 환경을 읽으며 프로젝트 `.env` 파일을 자동 로드하지 않습니다.
|
|
124
|
+
|
|
125
|
+
변경 후 호스트 또는 MCP를 재시작하십시오. **Codex에서는 `/hooks`에서 새 Hook을 검토하고 신뢰를 승인하십시오.** `/mcp`와 `get_routing_capabilities`에서 `provider`, 후보, `automatic_hook.mode`를 확인하십시오. 무료 상태 조회는 키 인증을 검증하지 않으며 `recommend_agent`는 비용이 발생할 수 있습니다. `unauthorized`면 키 전달을, `needs_context`면 작업 요약을 확인하십시오.
|
|
126
|
+
|
|
127
|
+
설정 위치·전달 방식의 공식 설명은 [Codex 구성 참조](https://learn.chatgpt.com/docs/config-file/config-reference)와 [Claude Code MCP 안내](https://code.claude.com/docs/en/mcp)를 참고하십시오.
|
|
38
128
|
|
|
39
129
|
### 건너뛰기·제거
|
|
40
130
|
|
|
41
131
|
```sh
|
|
42
|
-
APTLY_SKIP_SETUP=1 npm install
|
|
132
|
+
APTLY_SKIP_SETUP=1 npm install -g aptly@0.1.1
|
|
43
133
|
aptly setup --client codex --mode defaults
|
|
44
134
|
aptly remove --client all
|
|
45
135
|
```
|
|
46
136
|
|
|
47
|
-
`CI
|
|
137
|
+
`CI`나 npm `--ignore-scripts`도 등록을 건너뜁니다. 이후 `setup`으로 등록하십시오. 모드를 바꾸려면 등록을 제거하고 원하는 `--mode`로 다시 설정하십시오. `advisory`는 추천만 전달합니다. `partial`이면 호스트별 오류 코드를 확인하십시오. 제거 시 사용자 변경 항목은 보존하고 충돌을 알립니다.
|
|
48
138
|
|
|
49
139
|
## 자동 Hook
|
|
50
140
|
|