arisa 2.3.50 → 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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/daemon/setup.ts +89 -142
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "2.3.50",
3
+ "version": "2.3.51",
4
4
  "description": "Arisa - dynamic agent runtime with daemon/core architecture that evolves through user interaction",
5
5
  "keywords": [
6
6
  "tinyclaw",
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * @module daemon/setup
3
- * @role Interactive first-run setup with inquirer prompts.
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
- * - Run interactive login flows for installed CLIs
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
- if (isFirstRun) console.log("\n🔧 Arisa Setup\n");
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 && isFirstRun) {
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
- }
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
- 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;
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 if (hasOpenAI) {
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 + Auth ───────────────────────────
142
+ // ─── Phase 2: CLI Installation ──────────────────────────────────
147
143
 
148
144
  if (process.stdin.isTTY) {
149
- if (isFirstRun) {
150
- // First run: offer to install missing CLIs + login
151
- await setupClis(inq, vars);
152
- } else {
153
- // Subsequent runs: check if any installed CLI needs auth, offer login
154
- await checkCliAuth(inq, vars);
155
- }
156
- }
157
-
158
- return true;
159
- }
160
-
161
- async function setupClis(inq: typeof import("@inquirer/prompts") | null, vars: Record<string, string>) {
162
- let claudeInstalled = isAgentCliInstalled("claude");
163
- let codexInstalled = isAgentCliInstalled("codex");
164
-
165
- console.log("\nCLI Status:");
166
- console.log(` ${claudeInstalled ? "✓" : "✗"} Claude${claudeInstalled ? "" : " — not installed"}`);
167
- console.log(` ${codexInstalled ? "" : "✗"} Codex${codexInstalled ? "" : " — not installed"}`);
168
-
169
- // Install missing CLIs
170
- const missing: AgentCliName[] = [];
171
- if (!claudeInstalled) missing.push("claude");
172
- if (!codexInstalled) missing.push("codex");
173
-
174
- if (missing.length > 0) {
175
- let toInstall: AgentCliName[] = [];
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
+ }
176
172
 
177
- if (inq) {
178
- toInstall = await inq.checkbox({
179
- message: "Install missing CLIs? (space to select, enter to confirm)",
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;
190
- }
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
+ }
191
178
 
192
- for (const cli of toInstall) {
193
- console.log(`\nInstalling ${cli}...`);
194
- const ok = await installCli(cli);
195
- console.log(ok ? ` ✓ ${cli} installed` : ` ✗ ${cli} install failed`);
179
+ // Refresh status
180
+ claudeInstalled = isAgentCliInstalled("claude");
181
+ codexInstalled = isAgentCliInstalled("codex");
196
182
  }
197
183
 
198
- // Refresh status
199
- claudeInstalled = isAgentCliInstalled("claude");
200
- codexInstalled = isAgentCliInstalled("codex");
201
- }
202
-
203
- // Login CLIs
204
- if (claudeInstalled) {
205
- let doLogin = true;
206
- if (inq) {
207
- doLogin = await inq.confirm({ message: "Log in to Claude?", default: true });
208
- } else {
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
- }
184
+ // ─── Phase 3: CLI Authentication ────────────────────────────────
217
185
 
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
- }
186
+ const installed: AgentCliName[] = [];
187
+ if (claudeInstalled) installed.push("claude");
188
+ if (codexInstalled) installed.push("codex");
231
189
 
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
- * On non-first runs, check if installed CLIs are authenticated.
242
- * If not, offer to login interactively.
243
- */
244
- async function checkCliAuth(inq: typeof import("@inquirer/prompts") | null, vars: Record<string, string>) {
245
- const clis: AgentCliName[] = [];
246
- if (isAgentCliInstalled("claude")) clis.push("claude");
247
- if (isAgentCliInstalled("codex")) clis.push("codex");
248
- if (clis.length === 0) return;
249
-
250
- for (const cli of clis) {
251
- const authed = await isCliAuthenticated(cli);
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
- console.log(`[setup] ${cli} not authenticated`);
258
- let doLogin = true;
259
- if (inq) {
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: no simple auth check, assume OK if installed
223
+ * Codex: check OPENAI_API_KEY
276
224
  */
277
225
  async function isCliAuthenticated(cli: AgentCliName): Promise<boolean> {
278
226
  try {
@@ -317,7 +265,6 @@ async function installCli(cli: AgentCliName): Promise<boolean> {
317
265
  }
318
266
  }
319
267
 
320
-
321
268
  async function runInteractiveLogin(cli: AgentCliName, vars: Record<string, string>): Promise<boolean> {
322
269
  const args = cli === "claude"
323
270
  ? ["setup-token"]