arisa 2.3.36 → 2.3.38
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/package.json +1 -1
- package/src/daemon/setup.ts +113 -15
- package/src/shared/ai-cli.ts +3 -3
package/package.json
CHANGED
package/src/daemon/setup.ts
CHANGED
|
@@ -140,10 +140,16 @@ export async function runSetup(): Promise<boolean> {
|
|
|
140
140
|
console.log(`\nConfig saved to ${ENV_PATH}`);
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
-
// ─── Phase 2: CLI Installation
|
|
143
|
+
// ─── Phase 2: CLI Installation + Auth ───────────────────────────
|
|
144
144
|
|
|
145
|
-
if (
|
|
146
|
-
|
|
145
|
+
if (process.stdin.isTTY) {
|
|
146
|
+
if (isFirstRun) {
|
|
147
|
+
// First run: offer to install missing CLIs + login
|
|
148
|
+
await setupClis(inq, vars);
|
|
149
|
+
} else {
|
|
150
|
+
// Subsequent runs: check if any installed CLI needs auth, offer login
|
|
151
|
+
await checkCliAuth(inq, vars);
|
|
152
|
+
}
|
|
147
153
|
}
|
|
148
154
|
|
|
149
155
|
return true;
|
|
@@ -228,6 +234,65 @@ async function setupClis(inq: typeof import("@inquirer/prompts") | null, vars: R
|
|
|
228
234
|
}
|
|
229
235
|
}
|
|
230
236
|
|
|
237
|
+
/**
|
|
238
|
+
* On non-first runs, check if installed CLIs are authenticated.
|
|
239
|
+
* If not, offer to login interactively.
|
|
240
|
+
*/
|
|
241
|
+
async function checkCliAuth(inq: typeof import("@inquirer/prompts") | null, vars: Record<string, string>) {
|
|
242
|
+
const clis: AgentCliName[] = [];
|
|
243
|
+
if (isAgentCliInstalled("claude")) clis.push("claude");
|
|
244
|
+
if (isAgentCliInstalled("codex")) clis.push("codex");
|
|
245
|
+
if (clis.length === 0) return;
|
|
246
|
+
|
|
247
|
+
for (const cli of clis) {
|
|
248
|
+
const authed = await isCliAuthenticated(cli);
|
|
249
|
+
if (authed) {
|
|
250
|
+
console.log(`[setup] ${cli} ✓ authenticated`);
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`[setup] ${cli} ✗ not authenticated`);
|
|
255
|
+
let doLogin = true;
|
|
256
|
+
if (inq) {
|
|
257
|
+
doLogin = await inq.confirm({ message: `Log in to ${cli === "claude" ? "Claude" : "Codex"}?`, default: true });
|
|
258
|
+
} else {
|
|
259
|
+
const answer = await readLine(`\nLog in to ${cli === "claude" ? "Claude" : "Codex"}? (Y/n): `);
|
|
260
|
+
doLogin = answer.toLowerCase() !== "n";
|
|
261
|
+
}
|
|
262
|
+
if (doLogin) {
|
|
263
|
+
console.log();
|
|
264
|
+
await runInteractiveLogin(cli, vars);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Quick probe: is this CLI authenticated?
|
|
271
|
+
* Claude: check CLAUDE_CODE_OAUTH_TOKEN env/.env, or `claude auth status`
|
|
272
|
+
* Codex: no simple auth check, assume OK if installed
|
|
273
|
+
*/
|
|
274
|
+
async function isCliAuthenticated(cli: AgentCliName): Promise<boolean> {
|
|
275
|
+
try {
|
|
276
|
+
if (cli === "claude") {
|
|
277
|
+
// setup-token auth: token lives in env var (not .credentials.json)
|
|
278
|
+
if (process.env.CLAUDE_CODE_OAUTH_TOKEN?.startsWith("sk-ant-")) {
|
|
279
|
+
console.log(`[setup] claude auth via CLAUDE_CODE_OAUTH_TOKEN env var`);
|
|
280
|
+
return true;
|
|
281
|
+
}
|
|
282
|
+
// Native CLI auth: check `claude auth status`
|
|
283
|
+
const cmd = buildBunWrappedAgentCliCommand("claude", ["auth", "status"], { skipPreload: true });
|
|
284
|
+
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
285
|
+
const stdout = await new Response(proc.stdout).text();
|
|
286
|
+
const exitCode = await proc.exited;
|
|
287
|
+
return exitCode === 0 && stdout.includes('"loggedIn": true');
|
|
288
|
+
}
|
|
289
|
+
// Codex: no simple auth check, assume OK if installed
|
|
290
|
+
return true;
|
|
291
|
+
} catch {
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
231
296
|
async function installCli(cli: AgentCliName): Promise<boolean> {
|
|
232
297
|
try {
|
|
233
298
|
// Install into root's bun (arisa has read+execute access)
|
|
@@ -255,8 +320,51 @@ async function runInteractiveLogin(cli: AgentCliName, vars: Record<string, strin
|
|
|
255
320
|
console.log(`Starting ${cli} login...`);
|
|
256
321
|
|
|
257
322
|
try {
|
|
258
|
-
|
|
259
|
-
|
|
323
|
+
if (cli === "claude") {
|
|
324
|
+
// `claude setup-token` generates a long-lived (1 year) OAuth token for
|
|
325
|
+
// headless/CI environments. It prints the token to stdout but does NOT
|
|
326
|
+
// write .credentials.json. We must capture it and save to .env.
|
|
327
|
+
const proc = Bun.spawn(buildBunWrappedAgentCliCommand(cli, args, { skipPreload: true }), {
|
|
328
|
+
stdin: "inherit",
|
|
329
|
+
stdout: "pipe",
|
|
330
|
+
stderr: "inherit",
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
let output = "";
|
|
334
|
+
const reader = (proc.stdout as ReadableStream<Uint8Array>).getReader();
|
|
335
|
+
const decoder = new TextDecoder();
|
|
336
|
+
while (true) {
|
|
337
|
+
const { done, value } = await reader.read();
|
|
338
|
+
if (done) break;
|
|
339
|
+
const chunk = decoder.decode(value, { stream: true });
|
|
340
|
+
process.stdout.write(chunk);
|
|
341
|
+
output += chunk;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const exitCode = await proc.exited;
|
|
345
|
+
if (exitCode !== 0) {
|
|
346
|
+
console.log(` ✗ claude login failed (exit ${exitCode})`);
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Extract token from output (format: sk-ant-oat01-...)
|
|
351
|
+
const tokenMatch = output.match(/(sk-ant-oat01-[A-Za-z0-9_-]+)/);
|
|
352
|
+
if (tokenMatch) {
|
|
353
|
+
const token = tokenMatch[1];
|
|
354
|
+
console.log(` [token] ${token.slice(0, 20)}...${token.slice(-6)} (${token.length} chars)`);
|
|
355
|
+
vars.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
356
|
+
process.env.CLAUDE_CODE_OAUTH_TOKEN = token;
|
|
357
|
+
saveEnv(vars);
|
|
358
|
+
console.log(" ✓ token saved to .env");
|
|
359
|
+
} else {
|
|
360
|
+
console.log(" ⚠ could not extract token from output — set CLAUDE_CODE_OAUTH_TOKEN manually in ~/.arisa/.env");
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
console.log(` ✓ claude login successful`);
|
|
364
|
+
return true;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// For codex and others: inherit all stdio
|
|
260
368
|
const proc = Bun.spawn(buildBunWrappedAgentCliCommand(cli, args, { skipPreload: true }), {
|
|
261
369
|
stdin: "inherit",
|
|
262
370
|
stdout: "inherit",
|
|
@@ -266,16 +374,6 @@ async function runInteractiveLogin(cli: AgentCliName, vars: Record<string, strin
|
|
|
266
374
|
|
|
267
375
|
if (exitCode === 0) {
|
|
268
376
|
console.log(` ✓ ${cli} login successful`);
|
|
269
|
-
|
|
270
|
-
// Clean up stale CLAUDE_CODE_OAUTH_TOKEN from .env if present —
|
|
271
|
-
// it overrides the CLI's own credential store and breaks token refresh.
|
|
272
|
-
if (cli === "claude" && vars.CLAUDE_CODE_OAUTH_TOKEN) {
|
|
273
|
-
delete vars.CLAUDE_CODE_OAUTH_TOKEN;
|
|
274
|
-
delete process.env.CLAUDE_CODE_OAUTH_TOKEN;
|
|
275
|
-
saveEnv(vars);
|
|
276
|
-
console.log(" ✓ removed stale CLAUDE_CODE_OAUTH_TOKEN from .env (CLI manages auth internally)");
|
|
277
|
-
}
|
|
278
|
-
|
|
279
377
|
return true;
|
|
280
378
|
} else {
|
|
281
379
|
console.log(` ✗ ${cli} login failed (exit ${exitCode})`);
|
package/src/shared/ai-cli.ts
CHANGED
|
@@ -81,10 +81,10 @@ export function isAgentCliInstalled(cli: AgentCliName): boolean {
|
|
|
81
81
|
const INK_SHIM = join(dirname(new URL(import.meta.url).pathname), "ink-shim.js");
|
|
82
82
|
|
|
83
83
|
// Env vars that must survive the su - login shell reset.
|
|
84
|
-
//
|
|
85
|
-
//
|
|
86
|
-
// Injecting a stale accessToken via env var breaks refresh.
|
|
84
|
+
// CLAUDE_CODE_OAUTH_TOKEN is a long-lived (1 year) token from `claude setup-token`
|
|
85
|
+
// — the headless auth method. It must be passed through to the CLI.
|
|
87
86
|
const PASSTHROUGH_VARS = [
|
|
87
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
88
88
|
"ANTHROPIC_API_KEY",
|
|
89
89
|
"OPENAI_API_KEY",
|
|
90
90
|
];
|