@aisa-one/cli 0.2.4 → 0.5.0
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/CHANGELOG.md +172 -1
- package/LICENSE +21 -0
- package/README.md +163 -169
- package/dist/api.d.ts +10 -0
- package/dist/api.js +14 -3
- package/dist/catalog.d.ts +1 -18
- package/dist/catalog.js +1 -24
- package/dist/cli-error.d.ts +17 -0
- package/dist/cli-error.js +25 -0
- package/dist/cli-guidance.d.ts +14 -0
- package/dist/cli-guidance.js +55 -0
- package/dist/commands/account.d.ts +17 -0
- package/dist/commands/account.js +45 -1
- package/dist/commands/api.d.ts +0 -11
- package/dist/commands/api.js +6 -175
- package/dist/commands/auth.d.ts +2 -1
- package/dist/commands/auth.js +43 -9
- package/dist/commands/brand-logos.d.ts +6 -0
- package/dist/commands/brand-logos.js +21 -0
- package/dist/commands/configCmd.js +8 -3
- package/dist/commands/connect-shared.d.ts +136 -0
- package/dist/commands/connect-shared.js +143 -0
- package/dist/commands/connect-t2.d.ts +18 -0
- package/dist/commands/connect-t2.js +1599 -0
- package/dist/commands/connect-terminal.d.ts +65 -0
- package/dist/commands/connect-terminal.js +822 -0
- package/dist/commands/connect.d.ts +31 -0
- package/dist/commands/connect.js +3181 -0
- package/dist/commands/flow.d.ts +685 -0
- package/dist/commands/flow.js +697 -0
- package/dist/commands/install.d.ts +91 -0
- package/dist/commands/install.js +258 -0
- package/dist/commands/llm-config.d.ts +103 -0
- package/dist/commands/llm-config.js +452 -0
- package/dist/commands/manifest.d.ts +68 -0
- package/dist/commands/manifest.js +90 -0
- package/dist/commands/mcp.d.ts +79 -2
- package/dist/commands/mcp.js +262 -48
- package/dist/commands/oauth-login.d.ts +33 -0
- package/dist/commands/oauth-login.js +333 -0
- package/dist/commands/prompt.d.ts +96 -0
- package/dist/commands/prompt.js +397 -0
- package/dist/commands/serve-results.d.ts +61 -0
- package/dist/commands/serve-results.js +245 -0
- package/dist/commands/serve-signin.d.ts +28 -0
- package/dist/commands/serve-signin.js +90 -0
- package/dist/commands/signin-page.d.ts +49 -0
- package/dist/commands/signin-page.js +250 -0
- package/dist/commands/skills.js +87 -50
- package/dist/commands/tool-help.d.ts +69 -0
- package/dist/commands/tool-help.js +189 -0
- package/dist/commands/tool-input.d.ts +22 -0
- package/dist/commands/tool-input.js +220 -0
- package/dist/commands/tools.d.ts +12 -0
- package/dist/commands/tools.js +307 -0
- package/dist/commands/update.d.ts +11 -0
- package/dist/commands/update.js +92 -0
- package/dist/commands/vscode.d.ts +65 -0
- package/dist/commands/vscode.js +173 -0
- package/dist/commands/wrappers.d.ts +23 -0
- package/dist/commands/wrappers.js +102 -0
- package/dist/completion.js +3 -80
- package/dist/config.js +76 -2
- package/dist/constants.d.ts +78 -38
- package/dist/constants.js +80 -102
- package/dist/index.js +155 -357
- package/dist/json-text.d.ts +12 -0
- package/dist/json-text.js +60 -0
- package/dist/router.d.ts +39 -0
- package/dist/router.js +59 -0
- package/dist/skills-registry.js +12 -3
- package/dist/utils/banner.d.ts +9 -0
- package/dist/utils/banner.js +41 -0
- package/dist/utils/browser.d.ts +19 -0
- package/dist/utils/browser.js +31 -0
- package/dist/utils/exec.d.ts +43 -0
- package/dist/utils/exec.js +61 -0
- package/dist/utils/http.d.ts +53 -0
- package/dist/utils/http.js +124 -0
- package/dist/utils/install-method.d.ts +36 -0
- package/dist/utils/install-method.js +48 -0
- package/dist/utils/journal.d.ts +57 -0
- package/dist/utils/journal.js +188 -0
- package/dist/utils/key-check.d.ts +20 -0
- package/dist/utils/key-check.js +24 -0
- package/dist/utils/update-check.d.ts +23 -0
- package/dist/utils/update-check.js +116 -0
- package/dist/utils/width.d.ts +32 -0
- package/dist/utils/width.js +73 -0
- package/package.json +8 -4
- package/vscode-extension/README.md +6 -0
- package/vscode-extension/aisa-vscode.vsix +0 -0
- package/dist/commands/finance.d.ts +0 -17
- package/dist/commands/finance.js +0 -342
- package/dist/commands/run.d.ts +0 -28
- package/dist/commands/run.js +0 -182
- package/dist/commands/search.d.ts +0 -9
- package/dist/commands/search.js +0 -81
- package/dist/commands/twitter.d.ts +0 -138
- package/dist/commands/twitter.js +0 -374
- package/dist/commands/video.d.ts +0 -49
- package/dist/commands/video.js +0 -311
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installing a coding agent on the user's behalf.
|
|
3
|
+
*
|
|
4
|
+
* Two rules decide everything here:
|
|
5
|
+
*
|
|
6
|
+
* 1. **Never sudo.** A CLI that escalates on a user's machine is a CLI nobody
|
|
7
|
+
* should trust, and there is no terminal to type a password into anyway.
|
|
8
|
+
* Where a global npm prefix is not writable we print the command and let
|
|
9
|
+
* the user run it — the flow resumes on a re-check rather than failing.
|
|
10
|
+
* 2. **Prefer the vendor's own user-space installer.** Claude Code's script
|
|
11
|
+
* lands in ~/.local/bin, so the permission question never arises. npm is
|
|
12
|
+
* the fallback, and the one place rule 1 gets exercised.
|
|
13
|
+
*
|
|
14
|
+
* Windows is deliberately out of scope for now: the installers differ
|
|
15
|
+
* (PowerShell, winget) and nothing here has been tested there. `supported()`
|
|
16
|
+
* says so rather than half-working.
|
|
17
|
+
*/
|
|
18
|
+
export interface Installer {
|
|
19
|
+
id: string;
|
|
20
|
+
label: string;
|
|
21
|
+
/** The binary that must appear on PATH once installed. */
|
|
22
|
+
binary: string;
|
|
23
|
+
/** Preferred command, shown verbatim before it runs. */
|
|
24
|
+
command: string;
|
|
25
|
+
/** Where the preferred command puts things, for the "not on PATH" hint. */
|
|
26
|
+
installDir?: string;
|
|
27
|
+
/** Set when the command is an npm install; enables the broken-install retry. */
|
|
28
|
+
npmPackage?: string;
|
|
29
|
+
/** For a curl installer: the URL it depends on, probed (3s) before use.
|
|
30
|
+
* The npm-registry probe cannot stand in for this — the two hosts can be
|
|
31
|
+
* blocked independently, and a blackholed curl would otherwise sit at the
|
|
32
|
+
* full install timeout before the npm fallback even started. */
|
|
33
|
+
probeUrl?: string;
|
|
34
|
+
}
|
|
35
|
+
export declare const INSTALLERS: Record<string, Installer>;
|
|
36
|
+
export declare function supported(): boolean;
|
|
37
|
+
export type NpmChannel =
|
|
38
|
+
/** The user's npm already points somewhere non-default — use it as is. */
|
|
39
|
+
{
|
|
40
|
+
kind: "user";
|
|
41
|
+
}
|
|
42
|
+
/** The official registry answers — no flag needed. */
|
|
43
|
+
| {
|
|
44
|
+
kind: "official";
|
|
45
|
+
}
|
|
46
|
+
/** Official unreachable, the mirror answers — pass --registry once. */
|
|
47
|
+
| {
|
|
48
|
+
kind: "mirror";
|
|
49
|
+
registry: string;
|
|
50
|
+
}
|
|
51
|
+
/** Nothing answers — let the install fail with its own network error. */
|
|
52
|
+
| {
|
|
53
|
+
kind: "offline";
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Decide which npm registry an install should use, by probing the channel
|
|
57
|
+
* itself rather than guessing at geography. An IP-location lookup answers
|
|
58
|
+
* the wrong question (an IP inside a firewall says nothing about whether a
|
|
59
|
+
* corporate proxy reaches the official registry, and the lookup service is
|
|
60
|
+
* one more thing to be unreachable); a HEAD request for the exact package
|
|
61
|
+
* we are about to install answers the right one. Worst case adds one fixed
|
|
62
|
+
* timeout, instead of the minutes-long install failure it replaces.
|
|
63
|
+
*
|
|
64
|
+
* The mirror is used only as a fallback, never by preference: it is a third
|
|
65
|
+
* party in the supply chain, trusted widely but still one more link.
|
|
66
|
+
*/
|
|
67
|
+
export declare function pickNpmChannel(pkg: string, probe?: (url: string) => Promise<boolean>, userRegistry?: () => string | null): Promise<NpmChannel>;
|
|
68
|
+
/** Is the binary already on PATH and able to start? */
|
|
69
|
+
export declare function isInstalled(binary: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Can we write to npm's global prefix? Asked before running an npm install so
|
|
72
|
+
* the failure is a printed command rather than a wall of EACCES.
|
|
73
|
+
*/
|
|
74
|
+
export declare function npmPrefixWritable(): boolean;
|
|
75
|
+
export type InstallOutcome = {
|
|
76
|
+
ok: true;
|
|
77
|
+
alreadyInstalled: boolean;
|
|
78
|
+
} | {
|
|
79
|
+
ok: false;
|
|
80
|
+
reason: "unsupported-platform" | "needs-manual";
|
|
81
|
+
command: string;
|
|
82
|
+
detail: string;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Install one agent, or explain what the user should run instead.
|
|
86
|
+
*
|
|
87
|
+
* Never throws for an install failure: the caller reports it and the rest of
|
|
88
|
+
* the run continues, because a failed install must not cost the user the MCP
|
|
89
|
+
* servers they also asked for.
|
|
90
|
+
*/
|
|
91
|
+
export declare function installAgent(id: string): Promise<InstallOutcome>;
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
import { accessSync, constants, existsSync } from "node:fs";
|
|
2
|
+
import { runShell, runSync, QUICK_TIMEOUT_MS } from "../utils/exec.js";
|
|
3
|
+
export const INSTALLERS = {
|
|
4
|
+
"claude-code": {
|
|
5
|
+
id: "claude-code",
|
|
6
|
+
label: "Claude Code",
|
|
7
|
+
binary: "claude",
|
|
8
|
+
// The vendor installer, which needs no elevated permissions. The npm
|
|
9
|
+
// package (also published by Anthropic) is the fallback channel — it is
|
|
10
|
+
// what makes the mirror path below work where claude.ai is unreachable.
|
|
11
|
+
// Curl gets its own timeouts: a blackholed connection must fail in
|
|
12
|
+
// seconds, not sit until the overall install deadline.
|
|
13
|
+
command: "curl -fsSL --connect-timeout 10 --max-time 180 https://claude.ai/install.sh | bash",
|
|
14
|
+
installDir: "~/.local/bin",
|
|
15
|
+
npmPackage: "@anthropic-ai/claude-code",
|
|
16
|
+
probeUrl: "https://claude.ai/install.sh",
|
|
17
|
+
},
|
|
18
|
+
codex: {
|
|
19
|
+
id: "codex",
|
|
20
|
+
label: "Codex",
|
|
21
|
+
binary: "codex",
|
|
22
|
+
command: "npm install -g @openai/codex",
|
|
23
|
+
npmPackage: "@openai/codex",
|
|
24
|
+
},
|
|
25
|
+
opencode: {
|
|
26
|
+
id: "opencode",
|
|
27
|
+
label: "opencode",
|
|
28
|
+
binary: "opencode",
|
|
29
|
+
command: "npm install -g opencode-ai",
|
|
30
|
+
npmPackage: "opencode-ai",
|
|
31
|
+
},
|
|
32
|
+
// The CLI itself. connect usually runs via npx, which installs nothing —
|
|
33
|
+
// without this step the user walks away with no `aisa` command for
|
|
34
|
+
// balance, top-up or key rotation, and has to rediscover npx every time.
|
|
35
|
+
"aisa-cli": {
|
|
36
|
+
id: "aisa-cli",
|
|
37
|
+
label: "AIsa CLI",
|
|
38
|
+
binary: "aisa",
|
|
39
|
+
command: "npm install -g @aisa-one/cli",
|
|
40
|
+
npmPackage: "@aisa-one/cli",
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
export function supported() {
|
|
44
|
+
return process.platform === "darwin" || process.platform === "linux";
|
|
45
|
+
}
|
|
46
|
+
// ── registry channel ────────────────────────────────────────────────────────
|
|
47
|
+
const NPM_OFFICIAL = "https://registry.npmjs.org";
|
|
48
|
+
const NPM_MIRROR = "https://registry.npmmirror.com";
|
|
49
|
+
/**
|
|
50
|
+
* Decide which npm registry an install should use, by probing the channel
|
|
51
|
+
* itself rather than guessing at geography. An IP-location lookup answers
|
|
52
|
+
* the wrong question (an IP inside a firewall says nothing about whether a
|
|
53
|
+
* corporate proxy reaches the official registry, and the lookup service is
|
|
54
|
+
* one more thing to be unreachable); a HEAD request for the exact package
|
|
55
|
+
* we are about to install answers the right one. Worst case adds one fixed
|
|
56
|
+
* timeout, instead of the minutes-long install failure it replaces.
|
|
57
|
+
*
|
|
58
|
+
* The mirror is used only as a fallback, never by preference: it is a third
|
|
59
|
+
* party in the supply chain, trusted widely but still one more link.
|
|
60
|
+
*/
|
|
61
|
+
export async function pickNpmChannel(pkg, probe = headOk, userRegistry = npmConfiguredRegistry) {
|
|
62
|
+
const configured = userRegistry();
|
|
63
|
+
if (configured && !configured.startsWith(NPM_OFFICIAL))
|
|
64
|
+
return { kind: "user" };
|
|
65
|
+
const path = `/${pkg.replace("/", "%2f")}`;
|
|
66
|
+
if (await probe(NPM_OFFICIAL + path))
|
|
67
|
+
return { kind: "official" };
|
|
68
|
+
if (await probe(NPM_MIRROR + path))
|
|
69
|
+
return { kind: "mirror", registry: NPM_MIRROR };
|
|
70
|
+
return { kind: "offline" };
|
|
71
|
+
}
|
|
72
|
+
async function headOk(url) {
|
|
73
|
+
try {
|
|
74
|
+
const res = await fetch(url, { method: "HEAD", signal: AbortSignal.timeout(3_000) });
|
|
75
|
+
return res.ok;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function npmConfiguredRegistry() {
|
|
82
|
+
try {
|
|
83
|
+
const r = runSync("npm", ["config", "get", "registry"], { timeout: QUICK_TIMEOUT_MS });
|
|
84
|
+
return r.status === 0 ? r.stdout.trim() || null : null;
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Run the binary and report what actually happened, because "not working"
|
|
92
|
+
* has two very different causes with two very different fixes: absent from
|
|
93
|
+
* PATH (fix the PATH) versus present but failing to start (fix the install —
|
|
94
|
+
* seen live when npm's optional-dependency bug installed codex without its
|
|
95
|
+
* platform binary and every invocation threw "Missing optional dependency").
|
|
96
|
+
* The timeout is generous: macOS Gatekeeper can stall a binary's first run.
|
|
97
|
+
*/
|
|
98
|
+
function probeBinary(binary) {
|
|
99
|
+
const res = runSync(binary, ["--version"]);
|
|
100
|
+
if (res.status === 0)
|
|
101
|
+
return { ok: true };
|
|
102
|
+
if (res.error?.code === "ENOENT") {
|
|
103
|
+
return { ok: false, onPath: false, reason: "not on PATH" };
|
|
104
|
+
}
|
|
105
|
+
const line = (res.stderr || res.stdout || "")
|
|
106
|
+
.split("\n")
|
|
107
|
+
.map((l) => l.trim())
|
|
108
|
+
.find((l) => l.includes("Error") || l.length > 0) ?? `exit status ${res.status}`;
|
|
109
|
+
return { ok: false, onPath: true, reason: line.slice(0, 160) };
|
|
110
|
+
}
|
|
111
|
+
/** Is the binary already on PATH and able to start? */
|
|
112
|
+
export function isInstalled(binary) {
|
|
113
|
+
return probeBinary(binary).ok;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Can we write to npm's global prefix? Asked before running an npm install so
|
|
117
|
+
* the failure is a printed command rather than a wall of EACCES.
|
|
118
|
+
*/
|
|
119
|
+
export function npmPrefixWritable() {
|
|
120
|
+
try {
|
|
121
|
+
const r = runSync("npm", ["config", "get", "prefix"], { timeout: QUICK_TIMEOUT_MS });
|
|
122
|
+
const prefix = r.status === 0 ? r.stdout.trim() : "";
|
|
123
|
+
if (!prefix || !existsSync(prefix))
|
|
124
|
+
return false;
|
|
125
|
+
accessSync(prefix, constants.W_OK);
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Install one agent, or explain what the user should run instead.
|
|
134
|
+
*
|
|
135
|
+
* Never throws for an install failure: the caller reports it and the rest of
|
|
136
|
+
* the run continues, because a failed install must not cost the user the MCP
|
|
137
|
+
* servers they also asked for.
|
|
138
|
+
*/
|
|
139
|
+
export async function installAgent(id) {
|
|
140
|
+
const installer = INSTALLERS[id];
|
|
141
|
+
if (!installer) {
|
|
142
|
+
return { ok: false, reason: "needs-manual", command: "", detail: `unknown agent ${id}` };
|
|
143
|
+
}
|
|
144
|
+
if (isInstalled(installer.binary))
|
|
145
|
+
return { ok: true, alreadyInstalled: true };
|
|
146
|
+
if (!supported()) {
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
reason: "unsupported-platform",
|
|
150
|
+
command: installer.command,
|
|
151
|
+
detail: "automatic install is macOS/Linux only for now",
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
// Pick the registry channel before running anything: one fixed 3s probe
|
|
155
|
+
// replaces the minutes-long hang a firewalled user would otherwise sit
|
|
156
|
+
// through before the fallback even started.
|
|
157
|
+
const channel = installer.npmPackage
|
|
158
|
+
? await pickNpmChannel(installer.npmPackage)
|
|
159
|
+
: { kind: "official" };
|
|
160
|
+
const flag = channel.kind === "mirror" ? ` --registry=${channel.registry}` : "";
|
|
161
|
+
const npmCommand = installer.npmPackage
|
|
162
|
+
? `npm install -g ${installer.npmPackage}${flag}`
|
|
163
|
+
: undefined;
|
|
164
|
+
// A curl installer needs its vendor host reachable — a fact the registry
|
|
165
|
+
// probe cannot vouch for, so it gets its own 3s probe. Unreachable (or the
|
|
166
|
+
// whole official channel is): go straight to the npm channel, which
|
|
167
|
+
// carries the same vendor-published package.
|
|
168
|
+
const isNpmInstaller = installer.command.startsWith("npm ");
|
|
169
|
+
const vendorReachable = isNpmInstaller || channel.kind === "mirror" || !installer.probeUrl
|
|
170
|
+
? true
|
|
171
|
+
: await headOk(installer.probeUrl);
|
|
172
|
+
const command = isNpmInstaller
|
|
173
|
+
? npmCommand
|
|
174
|
+
: (channel.kind === "mirror" || !vendorReachable) && npmCommand
|
|
175
|
+
? npmCommand
|
|
176
|
+
: installer.command;
|
|
177
|
+
if (command.startsWith("npm ") && !npmPrefixWritable()) {
|
|
178
|
+
return {
|
|
179
|
+
ok: false,
|
|
180
|
+
reason: "needs-manual",
|
|
181
|
+
command,
|
|
182
|
+
detail: "npm's global prefix is not writable by this user",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Shell, because the vendor installer is a pipeline. The command is
|
|
186
|
+
// printed by the caller before this runs; nothing is installed silently.
|
|
187
|
+
const first = await runShell(command);
|
|
188
|
+
// The installer may have succeeded even when the command reported an error
|
|
189
|
+
// (a killed pipe, a noisy postinstall): believe the binary, not the exit
|
|
190
|
+
// code, before spending a retry.
|
|
191
|
+
if (!first.ok && probeBinary(installer.binary).ok) {
|
|
192
|
+
return { ok: true, alreadyInstalled: false };
|
|
193
|
+
}
|
|
194
|
+
if (!first.ok) {
|
|
195
|
+
// A failed install gets exactly one more chance. Curl channel: retry over
|
|
196
|
+
// npm (same vendor package, different transport). npm channel: a clean
|
|
197
|
+
// uninstall + reinstall — npm's optional-dependency bug can fail the
|
|
198
|
+
// install itself (seen live with opencode: the platform binary gets
|
|
199
|
+
// "reify mark deleted" and the postinstall exits 1), and the reinstall
|
|
200
|
+
// is the remedy that works by hand.
|
|
201
|
+
const fallback = npmCommand && command !== npmCommand
|
|
202
|
+
? npmCommand
|
|
203
|
+
: installer.npmPackage
|
|
204
|
+
// Retry over the top, never after removing what is there. See the
|
|
205
|
+
// note further down: the uninstall half of this cost a user their
|
|
206
|
+
// working install when the reinstall could not reach the registry.
|
|
207
|
+
? npmCommand ?? command
|
|
208
|
+
: undefined;
|
|
209
|
+
if (!fallback) {
|
|
210
|
+
return {
|
|
211
|
+
ok: false,
|
|
212
|
+
reason: "needs-manual",
|
|
213
|
+
command,
|
|
214
|
+
detail: first.detail,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const second = await runShell(fallback);
|
|
218
|
+
if (!second.ok && !probeBinary(installer.binary).ok) {
|
|
219
|
+
return {
|
|
220
|
+
ok: false,
|
|
221
|
+
reason: "needs-manual",
|
|
222
|
+
command: npmCommand ?? command,
|
|
223
|
+
detail: second.detail,
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
let probe = probeBinary(installer.binary);
|
|
228
|
+
if (!probe.ok && probe.onPath && installer.npmPackage) {
|
|
229
|
+
// The binary exists but did not start. npm's optional-dependency bug
|
|
230
|
+
// leaves exactly this — package installed, platform binary missing — and
|
|
231
|
+
// reinstalling over the top fixes it.
|
|
232
|
+
//
|
|
233
|
+
// 🔴 What this must never do again is uninstall first. `probeBinary`
|
|
234
|
+
// reports a timeout the same way it reports a broken install, so a slow
|
|
235
|
+
// start on a loaded machine was enough to condemn a working tool; the
|
|
236
|
+
// uninstall then succeeded, the reinstall failed on a flaky download, and
|
|
237
|
+
// the user was left with nothing where their agent used to be. Observed
|
|
238
|
+
// on 2026-08-25 against a working `claude`, more than once.
|
|
239
|
+
//
|
|
240
|
+
// Installing over an existing copy is what npm does anyway. If it fails,
|
|
241
|
+
// the user still has what they had.
|
|
242
|
+
await runShell(npmCommand ?? command);
|
|
243
|
+
probe = probeBinary(installer.binary);
|
|
244
|
+
}
|
|
245
|
+
if (!probe.ok) {
|
|
246
|
+
return {
|
|
247
|
+
ok: false,
|
|
248
|
+
reason: "needs-manual",
|
|
249
|
+
command: installer.command,
|
|
250
|
+
detail: !probe.onPath
|
|
251
|
+
? installer.installDir
|
|
252
|
+
? `installed, but ${installer.binary} is not on PATH — add ${installer.installDir} to it`
|
|
253
|
+
: `install reported success but ${installer.binary} is not on PATH`
|
|
254
|
+
: `installed, but ${installer.binary} fails to start: ${probe.reason}`,
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
return { ok: true, alreadyInstalled: false };
|
|
258
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export interface ModelChoice {
|
|
2
|
+
/** The workhorse model (Claude Code's sonnet/opus tiers, Codex's default). */
|
|
3
|
+
model: string;
|
|
4
|
+
/** The cheap model Claude Code reaches for on small tasks. */
|
|
5
|
+
smallModel: string;
|
|
6
|
+
/** Context window in tokens. Codex needs it declared; without it the CLI
|
|
7
|
+
* warns "Model metadata not found" and falls back to defaults that
|
|
8
|
+
* degrade its behaviour. */
|
|
9
|
+
contextWindow: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Defaults follow the agent, not our preference.
|
|
13
|
+
*
|
|
14
|
+
* Each coding agent is tuned around a model family: its system prompt, tool
|
|
15
|
+
* schemas and patch format were written and evaluated against one. Codex is
|
|
16
|
+
* OpenAI's, and `gpt-5.3-codex` is the model trained for it; Claude Code is
|
|
17
|
+
* Anthropic's. Sending either somewhere else works — the gateway speaks both
|
|
18
|
+
* protocols — but it is not the pairing the agent was built for, and it is
|
|
19
|
+
* not what a user installing that agent expects. Both remain switchable.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_MODELS: ModelChoice;
|
|
22
|
+
export declare const CODEX_DEFAULT_MODELS: ModelChoice;
|
|
23
|
+
/** The default pairing for one agent. */
|
|
24
|
+
export declare function defaultModelsFor(client: string): ModelChoice;
|
|
25
|
+
export type ConfigResult = {
|
|
26
|
+
ok: true;
|
|
27
|
+
path: string;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
reason: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function writeClaudeCodeLLM(apiKey: string, models?: ModelChoice): ConfigResult;
|
|
33
|
+
export declare function removeClaudeCodeLLM(): ConfigResult;
|
|
34
|
+
/**
|
|
35
|
+
* Codex talks the Responses API, not Chat Completions, so its base URL is the
|
|
36
|
+
* `/v1` root our gateway serves `/v1/responses` from — hence the separate
|
|
37
|
+
* constant and `wire_api = "responses"`.
|
|
38
|
+
*/
|
|
39
|
+
export declare function writeCodexLLM(apiKey: string, models?: ModelChoice): ConfigResult;
|
|
40
|
+
export declare function removeCodexLLM(): ConfigResult;
|
|
41
|
+
/**
|
|
42
|
+
* Codex keeps MCP servers in the same config.toml. Note `http_headers`, not
|
|
43
|
+
* `headers` — Codex's field name, and getting it wrong fails silently at the
|
|
44
|
+
* first authenticated call rather than at load.
|
|
45
|
+
*/
|
|
46
|
+
export declare function writeCodexMCP(servers: Array<{
|
|
47
|
+
slug: string;
|
|
48
|
+
endpoint: string;
|
|
49
|
+
}>, apiKey: string | undefined): ConfigResult;
|
|
50
|
+
/**
|
|
51
|
+
* Swap an entry's env-var token reference for the literal Authorization header.
|
|
52
|
+
*
|
|
53
|
+
* `codex mcp add --bearer-token-env-var` is the official way to add a keyed
|
|
54
|
+
* entry without triggering the OAuth flow, but it records only the variable
|
|
55
|
+
* NAME — at runtime Codex reads the token from its own process environment,
|
|
56
|
+
* and nothing guarantees the user's shell exports it. `http_headers` is the
|
|
57
|
+
* field Codex accepts for a literal header (`bearer_token` is explicitly
|
|
58
|
+
* rejected by its config loader), and it matches what the Claude Code path
|
|
59
|
+
* already stores via --header. The env-var reference is removed so the entry
|
|
60
|
+
* has exactly one source of credentials.
|
|
61
|
+
*/
|
|
62
|
+
export declare function patchCodexMCPAuth(name: string, apiKey: string): ConfigResult;
|
|
63
|
+
/**
|
|
64
|
+
* Register AIsa as a custom provider and point the default models at it.
|
|
65
|
+
*
|
|
66
|
+
* `@ai-sdk/anthropic` is the runtime (opencode loads providers as Vercel AI
|
|
67
|
+
* SDK packages): our gateway's /v1/messages face is live and verified with a
|
|
68
|
+
* real Claude Code session, and the anthropic SDK is the pairing that face
|
|
69
|
+
* was built for. Models are declared explicitly — a custom provider has no
|
|
70
|
+
* models.dev entry to inherit from.
|
|
71
|
+
*/
|
|
72
|
+
export declare function writeOpencodeLLM(apiKey: string, models?: ModelChoice): ConfigResult;
|
|
73
|
+
export declare function removeOpencodeLLM(): ConfigResult;
|
|
74
|
+
/**
|
|
75
|
+
* Write the MCP entries. With a key the entry carries the literal
|
|
76
|
+
* Authorization header; without one, `oauth: {}` tells opencode to run its
|
|
77
|
+
* own MCP OAuth (dynamic client registration per its schema).
|
|
78
|
+
*/
|
|
79
|
+
export declare function writeOpencodeMCP(servers: Array<{
|
|
80
|
+
slug: string;
|
|
81
|
+
endpoint: string;
|
|
82
|
+
}>, apiKey: string | undefined): ConfigResult;
|
|
83
|
+
/**
|
|
84
|
+
* Codex backup: provider + catalog in the base config, top-level defaults
|
|
85
|
+
* untouched, and a standalone aisa.config.toml that `codex --profile aisa`
|
|
86
|
+
* (or the codex-aisa wrapper) layers on top of it.
|
|
87
|
+
*
|
|
88
|
+
* That standalone file is Codex ≥0.149's replacement for the old
|
|
89
|
+
* `[profiles.aisa]` table — `--profile <name>` now means "layer
|
|
90
|
+
* `$CODEX_HOME/<name>.config.toml`", and Codex refuses to start at all if
|
|
91
|
+
* the legacy table for that name still exists in config.toml alongside it
|
|
92
|
+
* (hit live 2026-08-24, codex-cli 0.149.1: "cannot be used while
|
|
93
|
+
* config.toml contains legacy `profile = \"aisa\"` or `[profiles.aisa]`").
|
|
94
|
+
* A machine still carrying that table from an earlier version of this
|
|
95
|
+
* writer gets it removed here — a one-time, one-way migration; nothing a
|
|
96
|
+
* user typed into it themselves would collide with a section this narrow.
|
|
97
|
+
*/
|
|
98
|
+
export declare function writeCodexAisaProfile(apiKey: string, models?: ModelChoice): ConfigResult;
|
|
99
|
+
/**
|
|
100
|
+
* opencode backup: provider declared, `model`/`small_model` left alone — the
|
|
101
|
+
* user picks aisa/<model> in the TUI whenever they want it.
|
|
102
|
+
*/
|
|
103
|
+
export declare function writeOpencodeAisaBackup(apiKey: string, models?: ModelChoice): ConfigResult;
|