arisa 2.3.49 → 2.3.51
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 +91 -151
package/package.json
CHANGED
package/src/daemon/setup.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module daemon/setup
|
|
3
|
-
* @role
|
|
3
|
+
* @role Idempotent startup setup — runs every boot, checks real state.
|
|
4
4
|
* @responsibilities
|
|
5
5
|
* - Check required config (TELEGRAM_BOT_TOKEN)
|
|
6
6
|
* - Check optional config (OPENAI_API_KEY)
|
|
7
7
|
* - Detect / install missing CLIs (Claude, Codex)
|
|
8
|
-
* -
|
|
8
|
+
* - Check CLI auth and offer login if needed
|
|
9
9
|
* - Persist tokens to both .env and encrypted DB
|
|
10
10
|
* @dependencies shared/paths, shared/secrets, shared/ai-cli
|
|
11
11
|
* @effects Reads stdin, writes runtime .env, spawns install/login processes
|
|
@@ -18,7 +18,6 @@ import { secrets, setSecret } from "../shared/secrets";
|
|
|
18
18
|
import { isAgentCliInstalled, buildBunWrappedAgentCliCommand, type AgentCliName } from "../shared/ai-cli";
|
|
19
19
|
|
|
20
20
|
const ENV_PATH = join(dataDir, ".env");
|
|
21
|
-
const SETUP_DONE_KEY = "ARISA_SETUP_COMPLETE";
|
|
22
21
|
|
|
23
22
|
const CLI_PACKAGES: Record<AgentCliName, string> = {
|
|
24
23
|
claude: "@anthropic-ai/claude-code",
|
|
@@ -32,6 +31,8 @@ function loadExistingEnv(): Record<string, string> {
|
|
|
32
31
|
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.+)$/);
|
|
33
32
|
if (match) vars[match[1]] = match[2].trim();
|
|
34
33
|
}
|
|
34
|
+
// Clean up legacy flag — state is now derived from actual config
|
|
35
|
+
delete vars["ARISA_SETUP_COMPLETE"];
|
|
35
36
|
return vars;
|
|
36
37
|
}
|
|
37
38
|
|
|
@@ -61,8 +62,6 @@ export async function runSetup(): Promise<boolean> {
|
|
|
61
62
|
const telegramSecret = await secrets.telegram();
|
|
62
63
|
const openaiSecret = await secrets.openai();
|
|
63
64
|
let changed = false;
|
|
64
|
-
const setupDone = vars[SETUP_DONE_KEY] === "1" || process.env[SETUP_DONE_KEY] === "1";
|
|
65
|
-
const isFirstRun = !setupDone;
|
|
66
65
|
|
|
67
66
|
// Try to load inquirer for interactive mode
|
|
68
67
|
let inq: typeof import("@inquirer/prompts") | null = null;
|
|
@@ -80,7 +79,7 @@ export async function runSetup(): Promise<boolean> {
|
|
|
80
79
|
const hasOpenAI = !!(vars.OPENAI_API_KEY || process.env.OPENAI_API_KEY || openaiSecret);
|
|
81
80
|
|
|
82
81
|
if (!hasTelegram) {
|
|
83
|
-
|
|
82
|
+
console.log("\n🔧 Arisa Setup\n");
|
|
84
83
|
|
|
85
84
|
let token: string;
|
|
86
85
|
if (inq) {
|
|
@@ -109,170 +108,119 @@ export async function runSetup(): Promise<boolean> {
|
|
|
109
108
|
console.log(`[setup] TELEGRAM_BOT_TOKEN found in ${src}`);
|
|
110
109
|
}
|
|
111
110
|
|
|
112
|
-
if (!hasOpenAI
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
111
|
+
if (!hasOpenAI) {
|
|
112
|
+
if (process.stdin.isTTY) {
|
|
113
|
+
let key: string;
|
|
114
|
+
if (inq) {
|
|
115
|
+
key = await inq.input({
|
|
116
|
+
message: "OpenAI API Key (optional — voice + image, enter to skip):",
|
|
117
|
+
});
|
|
118
|
+
} else {
|
|
119
|
+
console.log("\nOpenAI API Key (optional — enables voice transcription + image analysis).");
|
|
120
|
+
key = await readLine("OPENAI_API_KEY (enter to skip): ");
|
|
121
|
+
}
|
|
122
122
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
123
|
+
if (key.trim()) {
|
|
124
|
+
vars.OPENAI_API_KEY = key.trim();
|
|
125
|
+
await setSecret("OPENAI_API_KEY", key.trim()).catch((e) =>
|
|
126
|
+
console.warn(`[setup] Could not persist OPENAI_API_KEY to encrypted DB: ${e}`)
|
|
127
|
+
);
|
|
128
|
+
console.log("[setup] OPENAI_API_KEY saved to .env + encrypted DB");
|
|
129
|
+
changed = true;
|
|
130
|
+
}
|
|
130
131
|
}
|
|
131
|
-
} else
|
|
132
|
+
} else {
|
|
132
133
|
const src = openaiSecret ? "encrypted DB" : vars.OPENAI_API_KEY ? ".env" : "env var";
|
|
133
134
|
console.log(`[setup] OPENAI_API_KEY found in ${src}`);
|
|
134
135
|
}
|
|
135
136
|
|
|
136
|
-
// Save tokens
|
|
137
|
-
if (!setupDone) {
|
|
138
|
-
vars[SETUP_DONE_KEY] = "1";
|
|
139
|
-
changed = true;
|
|
140
|
-
}
|
|
141
137
|
if (changed) {
|
|
142
138
|
saveEnv(vars);
|
|
143
139
|
console.log(`\nConfig saved to ${ENV_PATH}`);
|
|
144
140
|
}
|
|
145
141
|
|
|
146
|
-
// ─── Phase 2: CLI Installation
|
|
142
|
+
// ─── Phase 2: CLI Installation ──────────────────────────────────
|
|
147
143
|
|
|
148
144
|
if (process.stdin.isTTY) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
145
|
+
let claudeInstalled = isAgentCliInstalled("claude");
|
|
146
|
+
let codexInstalled = isAgentCliInstalled("codex");
|
|
147
|
+
|
|
148
|
+
console.log("\nCLI Status:");
|
|
149
|
+
console.log(` ${claudeInstalled ? "✓" : "✗"} Claude${claudeInstalled ? "" : " — not installed"}`);
|
|
150
|
+
console.log(` ${codexInstalled ? "✓" : "✗"} Codex${codexInstalled ? "" : " — not installed"}`);
|
|
151
|
+
|
|
152
|
+
const missing: AgentCliName[] = [];
|
|
153
|
+
if (!claudeInstalled) missing.push("claude");
|
|
154
|
+
if (!codexInstalled) missing.push("codex");
|
|
155
|
+
|
|
156
|
+
if (missing.length > 0) {
|
|
157
|
+
let toInstall: AgentCliName[] = [];
|
|
158
|
+
|
|
159
|
+
if (inq) {
|
|
160
|
+
toInstall = await inq.checkbox({
|
|
161
|
+
message: "Install missing CLIs? (space to select, enter to confirm)",
|
|
162
|
+
choices: missing.map((cli) => ({
|
|
163
|
+
name: `${cli === "claude" ? "Claude" : "Codex"} (${CLI_PACKAGES[cli]})`,
|
|
164
|
+
value: cli as AgentCliName,
|
|
165
|
+
checked: true,
|
|
166
|
+
})),
|
|
167
|
+
});
|
|
168
|
+
} else {
|
|
169
|
+
const answer = await readLine("\nInstall missing CLIs? (Y/n): ");
|
|
170
|
+
if (answer.toLowerCase() !== "n") toInstall = missing;
|
|
171
|
+
}
|
|
173
172
|
|
|
174
|
-
|
|
175
|
-
|
|
173
|
+
for (const cli of toInstall) {
|
|
174
|
+
console.log(`\nInstalling ${cli}...`);
|
|
175
|
+
const ok = await installCli(cli);
|
|
176
|
+
console.log(ok ? ` ✓ ${cli} installed` : ` ✗ ${cli} install failed`);
|
|
177
|
+
}
|
|
176
178
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
choices: missing.map((cli) => ({
|
|
181
|
-
name: `${cli === "claude" ? "Claude" : "Codex"} (${CLI_PACKAGES[cli]})`,
|
|
182
|
-
value: cli as AgentCliName,
|
|
183
|
-
checked: true,
|
|
184
|
-
})),
|
|
185
|
-
});
|
|
186
|
-
} else {
|
|
187
|
-
// Non-inquirer fallback: install all
|
|
188
|
-
const answer = await readLine("\nInstall missing CLIs? (Y/n): ");
|
|
189
|
-
if (answer.toLowerCase() !== "n") toInstall = missing;
|
|
179
|
+
// Refresh status
|
|
180
|
+
claudeInstalled = isAgentCliInstalled("claude");
|
|
181
|
+
codexInstalled = isAgentCliInstalled("codex");
|
|
190
182
|
}
|
|
191
183
|
|
|
192
|
-
|
|
193
|
-
console.log(`\nInstalling ${cli}...`);
|
|
194
|
-
const ok = await installCli(cli);
|
|
195
|
-
console.log(ok ? ` ✓ ${cli} installed` : ` ✗ ${cli} install failed`);
|
|
196
|
-
}
|
|
184
|
+
// ─── Phase 3: CLI Authentication ────────────────────────────────
|
|
197
185
|
|
|
198
|
-
|
|
199
|
-
claudeInstalled
|
|
200
|
-
codexInstalled
|
|
201
|
-
}
|
|
186
|
+
const installed: AgentCliName[] = [];
|
|
187
|
+
if (claudeInstalled) installed.push("claude");
|
|
188
|
+
if (codexInstalled) installed.push("codex");
|
|
202
189
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
const answer = await readLine("\nLog in to Claude? (Y/n): ");
|
|
210
|
-
doLogin = answer.toLowerCase() !== "n";
|
|
211
|
-
}
|
|
212
|
-
if (doLogin) {
|
|
213
|
-
console.log();
|
|
214
|
-
await runInteractiveLogin("claude", vars);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (codexInstalled) {
|
|
219
|
-
let doLogin = true;
|
|
220
|
-
if (inq) {
|
|
221
|
-
doLogin = await inq.confirm({ message: "Log in to Codex?", default: true });
|
|
222
|
-
} else {
|
|
223
|
-
const answer = await readLine("\nLog in to Codex? (Y/n): ");
|
|
224
|
-
doLogin = answer.toLowerCase() !== "n";
|
|
225
|
-
}
|
|
226
|
-
if (doLogin) {
|
|
227
|
-
console.log();
|
|
228
|
-
await runInteractiveLogin("codex", vars);
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (!claudeInstalled && !codexInstalled) {
|
|
233
|
-
console.log("\n⚠ No CLIs installed. Arisa needs at least one to work.");
|
|
234
|
-
console.log(" The daemon will auto-install them in the background.\n");
|
|
235
|
-
} else {
|
|
236
|
-
console.log("\n✓ Setup complete!\n");
|
|
237
|
-
}
|
|
238
|
-
}
|
|
190
|
+
for (const cli of installed) {
|
|
191
|
+
const authed = await isCliAuthenticated(cli);
|
|
192
|
+
if (authed) {
|
|
193
|
+
console.log(`[setup] ${cli} ✓ authenticated`);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
239
196
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
if (authed) {
|
|
253
|
-
console.log(`[setup] ${cli} ✓ authenticated`);
|
|
254
|
-
continue;
|
|
197
|
+
console.log(`[setup] ${cli} ✗ not authenticated`);
|
|
198
|
+
let doLogin = true;
|
|
199
|
+
if (inq) {
|
|
200
|
+
doLogin = await inq.confirm({ message: `Log in to ${cli === "claude" ? "Claude" : "Codex"}?`, default: true });
|
|
201
|
+
} else {
|
|
202
|
+
const answer = await readLine(`\nLog in to ${cli === "claude" ? "Claude" : "Codex"}? (Y/n): `);
|
|
203
|
+
doLogin = answer.toLowerCase() !== "n";
|
|
204
|
+
}
|
|
205
|
+
if (doLogin) {
|
|
206
|
+
console.log();
|
|
207
|
+
await runInteractiveLogin(cli, vars);
|
|
208
|
+
}
|
|
255
209
|
}
|
|
256
210
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
doLogin = await inq.confirm({ message: `Log in to ${cli === "claude" ? "Claude" : "Codex"}?`, default: true });
|
|
261
|
-
} else {
|
|
262
|
-
const answer = await readLine(`\nLog in to ${cli === "claude" ? "Claude" : "Codex"}? (Y/n): `);
|
|
263
|
-
doLogin = answer.toLowerCase() !== "n";
|
|
264
|
-
}
|
|
265
|
-
if (doLogin) {
|
|
266
|
-
console.log();
|
|
267
|
-
await runInteractiveLogin(cli, vars);
|
|
211
|
+
if (installed.length === 0) {
|
|
212
|
+
console.log("\n⚠ No CLIs installed. Arisa needs at least one to work.");
|
|
213
|
+
console.log(" The daemon will auto-install them in the background.\n");
|
|
268
214
|
}
|
|
269
215
|
}
|
|
216
|
+
|
|
217
|
+
return true;
|
|
270
218
|
}
|
|
271
219
|
|
|
272
220
|
/**
|
|
273
221
|
* Quick probe: is this CLI authenticated?
|
|
274
222
|
* Claude: check CLAUDE_CODE_OAUTH_TOKEN env/.env, or `claude auth status`
|
|
275
|
-
* Codex:
|
|
223
|
+
* Codex: check OPENAI_API_KEY
|
|
276
224
|
*/
|
|
277
225
|
async function isCliAuthenticated(cli: AgentCliName): Promise<boolean> {
|
|
278
226
|
try {
|
|
@@ -289,16 +237,9 @@ async function isCliAuthenticated(cli: AgentCliName): Promise<boolean> {
|
|
|
289
237
|
const exitCode = await proc.exited;
|
|
290
238
|
return exitCode === 0 && stdout.includes('"loggedIn": true');
|
|
291
239
|
}
|
|
292
|
-
// Codex:
|
|
240
|
+
// Codex: needs OPENAI_API_KEY — no reliable way to check device-auth
|
|
293
241
|
if (cli === "codex") {
|
|
294
|
-
|
|
295
|
-
// device-auth stores config in ~/.codex/ — try a quick dry-run
|
|
296
|
-
const cmd = buildBunWrappedAgentCliCommand("codex", ["--help"], { skipPreload: true });
|
|
297
|
-
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
298
|
-
const stderr = await new Response(proc.stderr).text();
|
|
299
|
-
await proc.exited;
|
|
300
|
-
// If stderr mentions login/auth, not authenticated
|
|
301
|
-
return !(/login|authenticate|OPENAI_API_KEY/i.test(stderr));
|
|
242
|
+
return !!(process.env.OPENAI_API_KEY);
|
|
302
243
|
}
|
|
303
244
|
return true;
|
|
304
245
|
} catch {
|
|
@@ -324,7 +265,6 @@ async function installCli(cli: AgentCliName): Promise<boolean> {
|
|
|
324
265
|
}
|
|
325
266
|
}
|
|
326
267
|
|
|
327
|
-
|
|
328
268
|
async function runInteractiveLogin(cli: AgentCliName, vars: Record<string, string>): Promise<boolean> {
|
|
329
269
|
const args = cli === "claude"
|
|
330
270
|
? ["setup-token"]
|