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.
- package/package.json +1 -1
- package/src/daemon/setup.ts +89 -142
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
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
|
-
|
|
193
|
-
|
|
194
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
219
|
-
|
|
220
|
-
if (
|
|
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
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
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 {
|
|
@@ -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"]
|