arisa 2.3.55 → 3.0.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/AGENTS.md +102 -0
- package/README.md +120 -165
- package/cli/openai-transcribe/index.js +51 -0
- package/cli/openai-transcribe/package.json +6 -0
- package/cli/openai-transcribe/tool.manifest.json +15 -0
- package/cli/openai-tts/index.js +58 -0
- package/cli/openai-tts/package.json +6 -0
- package/cli/openai-tts/tool.manifest.json +20 -0
- package/cli/web-browser/index.js +146 -0
- package/cli/web-browser/package.json +6 -0
- package/cli/web-browser/tool.manifest.json +8 -0
- package/package.json +26 -44
- package/src/core/agent/agent-manager.js +218 -0
- package/src/core/artifacts/artifact-store.js +102 -0
- package/src/core/config/config-store.js +20 -0
- package/src/core/tools/tool-registry.js +117 -0
- package/src/index.js +27 -0
- package/src/runtime/bootstrap.js +213 -0
- package/src/runtime/create-app.js +22 -0
- package/src/transport/telegram/auth.js +13 -0
- package/src/transport/telegram/bot.js +214 -0
- package/src/transport/telegram/media.js +75 -0
- package/CLAUDE.md +0 -191
- package/SOUL.md +0 -36
- package/bin/arisa.js +0 -644
- package/scripts/dump-commands.ts +0 -26
- package/scripts/test-secrets.ts +0 -22
- package/src/core/attachments.ts +0 -104
- package/src/core/auth.ts +0 -58
- package/src/core/context.ts +0 -30
- package/src/core/file-detector.ts +0 -39
- package/src/core/format.ts +0 -159
- package/src/core/index.ts +0 -456
- package/src/core/intent.ts +0 -119
- package/src/core/media.ts +0 -144
- package/src/core/onboarding.ts +0 -102
- package/src/core/processor.ts +0 -305
- package/src/core/router.ts +0 -64
- package/src/core/scheduler.ts +0 -193
- package/src/daemon/agent-cli.ts +0 -130
- package/src/daemon/auto-install.ts +0 -158
- package/src/daemon/autofix.ts +0 -116
- package/src/daemon/bridge.ts +0 -166
- package/src/daemon/channels/base.ts +0 -10
- package/src/daemon/channels/telegram.ts +0 -306
- package/src/daemon/claude-login.ts +0 -218
- package/src/daemon/codex-login.ts +0 -172
- package/src/daemon/fallback.ts +0 -73
- package/src/daemon/index.ts +0 -272
- package/src/daemon/lifecycle.ts +0 -313
- package/src/daemon/setup.ts +0 -329
- package/src/shared/ai-cli.ts +0 -165
- package/src/shared/config.ts +0 -137
- package/src/shared/db.ts +0 -304
- package/src/shared/deepbase-secure.ts +0 -39
- package/src/shared/ink-shim.js +0 -14
- package/src/shared/logger.ts +0 -42
- package/src/shared/paths.ts +0 -90
- package/src/shared/ports.ts +0 -120
- package/src/shared/secrets.ts +0 -136
- package/src/shared/types.ts +0 -103
- package/tsconfig.json +0 -19
package/bin/arisa.js
DELETED
|
@@ -1,644 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
|
|
3
|
-
const { spawn, spawnSync } = require("node:child_process");
|
|
4
|
-
const {
|
|
5
|
-
closeSync,
|
|
6
|
-
existsSync,
|
|
7
|
-
mkdirSync,
|
|
8
|
-
openSync,
|
|
9
|
-
readFileSync,
|
|
10
|
-
unlinkSync,
|
|
11
|
-
writeFileSync,
|
|
12
|
-
} = require("node:fs");
|
|
13
|
-
const { homedir, platform } = require("node:os");
|
|
14
|
-
const { dirname, join, resolve } = require("node:path");
|
|
15
|
-
|
|
16
|
-
const pkgRoot = resolve(__dirname, "..");
|
|
17
|
-
const daemonEntry = join(pkgRoot, "src", "daemon", "index.ts");
|
|
18
|
-
const coreEntry = join(pkgRoot, "src", "core", "index.ts");
|
|
19
|
-
const homeDir = homedir();
|
|
20
|
-
const arisaDir = join(homeDir, ".arisa");
|
|
21
|
-
const runDir = join(arisaDir, "run");
|
|
22
|
-
const logsDir = join(arisaDir, "logs");
|
|
23
|
-
const pidFile = join(runDir, "arisa.pid");
|
|
24
|
-
const fallbackLogFile = join(logsDir, "service.log");
|
|
25
|
-
const systemdServiceName = "arisa.service";
|
|
26
|
-
const systemdUserDir = join(homeDir, ".config", "systemd", "user");
|
|
27
|
-
const systemdUserUnitPath = join(systemdUserDir, systemdServiceName);
|
|
28
|
-
|
|
29
|
-
const args = process.argv.slice(2);
|
|
30
|
-
const inputCommand = (args[0] || "").toLowerCase();
|
|
31
|
-
const command = inputCommand || "daemon";
|
|
32
|
-
const rest = inputCommand ? args.slice(1) : args;
|
|
33
|
-
const isDefaultInvocation = inputCommand === "";
|
|
34
|
-
|
|
35
|
-
function printHelp() {
|
|
36
|
-
process.stdout.write(
|
|
37
|
-
`Arisa CLI
|
|
38
|
-
|
|
39
|
-
Usage:
|
|
40
|
-
arisa Start daemon in foreground (default)
|
|
41
|
-
arisa start Start service and enable restart-on-boot
|
|
42
|
-
arisa stop Stop service
|
|
43
|
-
arisa status Show service status
|
|
44
|
-
arisa restart Restart service
|
|
45
|
-
arisa daemon Start daemon in foreground
|
|
46
|
-
arisa run Start daemon in foreground
|
|
47
|
-
arisa start --foreground
|
|
48
|
-
Start daemon in foreground (legacy behavior)
|
|
49
|
-
arisa core Start core only
|
|
50
|
-
arisa dev Start core in watch mode
|
|
51
|
-
arisa version Print version
|
|
52
|
-
arisa help Show this help
|
|
53
|
-
`
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function printVersion() {
|
|
58
|
-
const pkgPath = join(pkgRoot, "package.json");
|
|
59
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
60
|
-
process.stdout.write(`${pkg.version}\n`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function commandExists(binary) {
|
|
64
|
-
const probe = spawnSync("sh", ["-lc", `command -v ${binary} >/dev/null 2>&1`], {
|
|
65
|
-
stdio: "ignore",
|
|
66
|
-
});
|
|
67
|
-
return probe.status === 0;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function runCommand(executable, commandArgs, options = {}) {
|
|
71
|
-
return spawnSync(executable, commandArgs, {
|
|
72
|
-
encoding: "utf8",
|
|
73
|
-
...options,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function resolveBunExecutable() {
|
|
78
|
-
if (process.env.BUN_BIN && process.env.BUN_BIN.trim()) {
|
|
79
|
-
return process.env.BUN_BIN.trim();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const bunInstall = process.env.BUN_INSTALL || join(homeDir, ".bun");
|
|
83
|
-
const bunFromInstall = join(bunInstall, "bin", "bun");
|
|
84
|
-
if (existsSync(bunFromInstall)) {
|
|
85
|
-
return bunFromInstall;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return "bun";
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function runWithBun(bunArgs, options = {}) {
|
|
92
|
-
const bunExecutable = resolveBunExecutable();
|
|
93
|
-
const child = runCommand(bunExecutable, bunArgs, {
|
|
94
|
-
stdio: "inherit",
|
|
95
|
-
cwd: process.cwd(),
|
|
96
|
-
env: {
|
|
97
|
-
...process.env,
|
|
98
|
-
ARISA_PROJECT_DIR: process.env.ARISA_PROJECT_DIR || pkgRoot,
|
|
99
|
-
},
|
|
100
|
-
shell: process.platform === "win32",
|
|
101
|
-
...options,
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
if (child.error) {
|
|
105
|
-
if (child.error.code === "ENOENT") {
|
|
106
|
-
process.stderr.write(
|
|
107
|
-
"Arisa requires Bun to run. Install it from https://bun.sh/ and retry.\n"
|
|
108
|
-
);
|
|
109
|
-
process.exit(1);
|
|
110
|
-
}
|
|
111
|
-
process.stderr.write(`${String(child.error)}\n`);
|
|
112
|
-
process.exit(1);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
return child;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function ensureRuntimeDirs() {
|
|
119
|
-
mkdirSync(runDir, { recursive: true });
|
|
120
|
-
mkdirSync(logsDir, { recursive: true });
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function readPid() {
|
|
124
|
-
if (!existsSync(pidFile)) return null;
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const raw = readFileSync(pidFile, "utf8").trim();
|
|
128
|
-
const parsed = Number.parseInt(raw, 10);
|
|
129
|
-
return Number.isFinite(parsed) && parsed > 0 ? parsed : null;
|
|
130
|
-
} catch {
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
function isPidRunning(pid) {
|
|
136
|
-
try {
|
|
137
|
-
process.kill(pid, 0);
|
|
138
|
-
return true;
|
|
139
|
-
} catch (error) {
|
|
140
|
-
return error && error.code === "EPERM";
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function removePidFile() {
|
|
145
|
-
if (!existsSync(pidFile)) return;
|
|
146
|
-
try {
|
|
147
|
-
unlinkSync(pidFile);
|
|
148
|
-
} catch {
|
|
149
|
-
// Best effort cleanup.
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function cleanupStalePidFile() {
|
|
154
|
-
const pid = readPid();
|
|
155
|
-
if (!pid) {
|
|
156
|
-
removePidFile();
|
|
157
|
-
return null;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (!isPidRunning(pid)) {
|
|
161
|
-
removePidFile();
|
|
162
|
-
return null;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return pid;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function startDetachedFallback() {
|
|
169
|
-
ensureRuntimeDirs();
|
|
170
|
-
const runningPid = cleanupStalePidFile();
|
|
171
|
-
if (runningPid) {
|
|
172
|
-
process.stdout.write(
|
|
173
|
-
`Arisa is already running in fallback mode (PID ${runningPid}).\n`
|
|
174
|
-
);
|
|
175
|
-
return 0;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const bunExecutable = resolveBunExecutable();
|
|
179
|
-
const logFd = openSync(fallbackLogFile, "a");
|
|
180
|
-
const child = spawn(bunExecutable, [daemonEntry], {
|
|
181
|
-
detached: true,
|
|
182
|
-
stdio: ["ignore", logFd, logFd],
|
|
183
|
-
cwd: pkgRoot,
|
|
184
|
-
env: {
|
|
185
|
-
...process.env,
|
|
186
|
-
ARISA_PROJECT_DIR: process.env.ARISA_PROJECT_DIR || pkgRoot,
|
|
187
|
-
},
|
|
188
|
-
shell: process.platform === "win32",
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
closeSync(logFd);
|
|
192
|
-
if (!child.pid) {
|
|
193
|
-
process.stderr.write(
|
|
194
|
-
"Failed to start Arisa in fallback mode. Ensure Bun is installed and in PATH.\n"
|
|
195
|
-
);
|
|
196
|
-
return 1;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
child.unref();
|
|
200
|
-
|
|
201
|
-
writeFileSync(pidFile, `${child.pid}\n`, "utf8");
|
|
202
|
-
process.stdout.write(
|
|
203
|
-
`Arisa started in fallback mode (PID ${child.pid}). Logs: ${fallbackLogFile}\n`
|
|
204
|
-
);
|
|
205
|
-
process.stdout.write(
|
|
206
|
-
"Autostart on reboot requires systemd user services.\n"
|
|
207
|
-
);
|
|
208
|
-
return 0;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function stopDetachedFallback() {
|
|
212
|
-
const runningPid = cleanupStalePidFile();
|
|
213
|
-
if (!runningPid) {
|
|
214
|
-
process.stdout.write("Arisa is not running (fallback mode).\n");
|
|
215
|
-
return 0;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
try {
|
|
219
|
-
process.kill(runningPid, "SIGTERM");
|
|
220
|
-
removePidFile();
|
|
221
|
-
process.stdout.write(`Sent SIGTERM to Arisa (PID ${runningPid}).\n`);
|
|
222
|
-
return 0;
|
|
223
|
-
} catch (error) {
|
|
224
|
-
process.stderr.write(
|
|
225
|
-
`Failed to stop Arisa PID ${runningPid}: ${error.message}\n`
|
|
226
|
-
);
|
|
227
|
-
return 1;
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
function statusDetachedFallback() {
|
|
232
|
-
const runningPid = cleanupStalePidFile();
|
|
233
|
-
if (!runningPid) {
|
|
234
|
-
process.stdout.write("Arisa is not running (fallback mode).\n");
|
|
235
|
-
return 1;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
process.stdout.write(`Arisa is running in fallback mode (PID ${runningPid}).\n`);
|
|
239
|
-
process.stdout.write(`Logs: ${fallbackLogFile}\n`);
|
|
240
|
-
return 0;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
function canUseSystemdUser() {
|
|
244
|
-
if (platform() !== "linux") return false;
|
|
245
|
-
if (!commandExists("systemctl")) return false;
|
|
246
|
-
|
|
247
|
-
const probe = runCommand("systemctl", ["--user", "show-environment"], {
|
|
248
|
-
stdio: "pipe",
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
return probe.status === 0;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function writeSystemdUserUnit() {
|
|
255
|
-
mkdirSync(systemdUserDir, { recursive: true });
|
|
256
|
-
|
|
257
|
-
const bunInstall = process.env.BUN_INSTALL || join(homeDir, ".bun");
|
|
258
|
-
const pathValue = process.env.PATH || `${join(bunInstall, "bin")}:/usr/local/bin:/usr/bin:/bin`;
|
|
259
|
-
const bunExecutable = resolveBunExecutable();
|
|
260
|
-
|
|
261
|
-
const unit = `[Unit]
|
|
262
|
-
Description=Arisa Daemon Service
|
|
263
|
-
After=network-online.target
|
|
264
|
-
Wants=network-online.target
|
|
265
|
-
|
|
266
|
-
[Service]
|
|
267
|
-
Type=simple
|
|
268
|
-
WorkingDirectory=${pkgRoot}
|
|
269
|
-
ExecStart=${bunExecutable} ${daemonEntry}
|
|
270
|
-
Restart=always
|
|
271
|
-
RestartSec=3
|
|
272
|
-
Environment=ARISA_PROJECT_DIR=${pkgRoot}
|
|
273
|
-
Environment=BUN_INSTALL=${bunInstall}
|
|
274
|
-
Environment=PATH=${pathValue}
|
|
275
|
-
|
|
276
|
-
[Install]
|
|
277
|
-
WantedBy=default.target
|
|
278
|
-
`;
|
|
279
|
-
|
|
280
|
-
writeFileSync(systemdUserUnitPath, unit, "utf8");
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function runSystemd(commandArgs) {
|
|
284
|
-
const child = runCommand("systemctl", ["--user", ...commandArgs], {
|
|
285
|
-
stdio: "pipe",
|
|
286
|
-
});
|
|
287
|
-
|
|
288
|
-
if (child.status !== 0) {
|
|
289
|
-
const stderr = child.stderr || "Unknown systemd error";
|
|
290
|
-
process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
|
|
291
|
-
return { ok: false, status: child.status ?? 1 };
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
return { ok: true, status: 0, stdout: child.stdout || "" };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
function startSystemdService() {
|
|
298
|
-
writeSystemdUserUnit();
|
|
299
|
-
|
|
300
|
-
const reload = runSystemd(["daemon-reload"]);
|
|
301
|
-
if (!reload.ok) return reload.status;
|
|
302
|
-
|
|
303
|
-
const start = runSystemd(["enable", "--now", systemdServiceName]);
|
|
304
|
-
if (!start.ok) return start.status;
|
|
305
|
-
|
|
306
|
-
process.stdout.write(
|
|
307
|
-
"Arisa service started and enabled (systemd --user).\n"
|
|
308
|
-
);
|
|
309
|
-
process.stdout.write(
|
|
310
|
-
"To keep it running after reboot without login, run: sudo loginctl enable-linger $USER\n"
|
|
311
|
-
);
|
|
312
|
-
return 0;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
function stopSystemdService() {
|
|
316
|
-
const stop = runSystemd(["stop", systemdServiceName]);
|
|
317
|
-
if (!stop.ok) return stop.status;
|
|
318
|
-
|
|
319
|
-
process.stdout.write("Arisa service stopped (systemd --user).\n");
|
|
320
|
-
return 0;
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function restartSystemdService() {
|
|
324
|
-
const restart = runSystemd(["restart", systemdServiceName]);
|
|
325
|
-
if (!restart.ok) return restart.status;
|
|
326
|
-
|
|
327
|
-
process.stdout.write("Arisa service restarted (systemd --user).\n");
|
|
328
|
-
return 0;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function statusSystemdService() {
|
|
332
|
-
const activeResult = runCommand(
|
|
333
|
-
"systemctl",
|
|
334
|
-
["--user", "is-active", systemdServiceName],
|
|
335
|
-
{ stdio: "pipe" }
|
|
336
|
-
);
|
|
337
|
-
const enabledResult = runCommand(
|
|
338
|
-
"systemctl",
|
|
339
|
-
["--user", "is-enabled", systemdServiceName],
|
|
340
|
-
{ stdio: "pipe" }
|
|
341
|
-
);
|
|
342
|
-
|
|
343
|
-
const active = activeResult.status === 0;
|
|
344
|
-
const enabled = enabledResult.status === 0;
|
|
345
|
-
|
|
346
|
-
if (active) {
|
|
347
|
-
process.stdout.write("Arisa service status: active (systemd --user).\n");
|
|
348
|
-
} else {
|
|
349
|
-
process.stdout.write("Arisa service status: inactive (systemd --user).\n");
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
if (enabled) {
|
|
353
|
-
process.stdout.write("Autostart: enabled\n");
|
|
354
|
-
return active ? 0 : 1;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
process.stdout.write("Autostart: disabled\n");
|
|
358
|
-
return active ? 0 : 1;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
function restartDetachedFallback() {
|
|
362
|
-
const stopCode = stopDetachedFallback();
|
|
363
|
-
if (stopCode !== 0) return stopCode;
|
|
364
|
-
return startDetachedFallback();
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
function startService() {
|
|
368
|
-
if (rest.includes("--foreground")) {
|
|
369
|
-
const foregroundArgs = rest.filter((arg) => arg !== "--foreground");
|
|
370
|
-
const child = runWithBun([daemonEntry, ...foregroundArgs]);
|
|
371
|
-
return child.status === null ? 1 : child.status;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
if (canUseSystemdUser()) {
|
|
375
|
-
return startSystemdService();
|
|
376
|
-
}
|
|
377
|
-
return startDetachedFallback();
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
function stopService() {
|
|
381
|
-
if (canUseSystemdUser()) {
|
|
382
|
-
return stopSystemdService();
|
|
383
|
-
}
|
|
384
|
-
return stopDetachedFallback();
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
function statusService() {
|
|
388
|
-
if (canUseSystemdUser()) {
|
|
389
|
-
return statusSystemdService();
|
|
390
|
-
}
|
|
391
|
-
return statusDetachedFallback();
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
function restartService() {
|
|
395
|
-
if (canUseSystemdUser()) {
|
|
396
|
-
return restartSystemdService();
|
|
397
|
-
}
|
|
398
|
-
return restartDetachedFallback();
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
// ── Root: create arisa user for Core process execution ──────────────
|
|
403
|
-
// Daemon runs as root. Core runs as user arisa (Claude CLI refuses root).
|
|
404
|
-
// This means Claude/Codex calls from Core are direct — no su wrapping.
|
|
405
|
-
|
|
406
|
-
function isRoot() {
|
|
407
|
-
return process.getuid?.() === 0;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function arisaUserExists() {
|
|
411
|
-
return spawnSync("id", ["arisa"], { stdio: "ignore" }).status === 0;
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
function isArisaUserProvisioned() {
|
|
415
|
-
return arisaUserExists();
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
function step(ok, msg) {
|
|
419
|
-
process.stdout.write(` ${ok ? "\u2713" : "\u2717"} ${msg}\n`);
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const ROOT_BUN_INSTALL = process.env.BUN_INSTALL || join(homeDir, ".bun");
|
|
423
|
-
const ARISA_BUN_ENV = `export BUN_INSTALL=${ROOT_BUN_INSTALL} && export PATH=${ROOT_BUN_INSTALL}/bin:$PATH`;
|
|
424
|
-
|
|
425
|
-
function provisionArisaUser() {
|
|
426
|
-
process.stdout.write("Creating user 'arisa' for Claude/Codex CLI execution...\n");
|
|
427
|
-
|
|
428
|
-
// 1. Create user with sudo access
|
|
429
|
-
const useradd = spawnSync("useradd", ["-m", "-s", "/bin/bash", "arisa"], { stdio: "pipe" });
|
|
430
|
-
if (useradd.status !== 0) {
|
|
431
|
-
step(false, `Failed to create user: ${(useradd.stderr || "").toString().trim()}`);
|
|
432
|
-
process.exit(1);
|
|
433
|
-
}
|
|
434
|
-
step(true, "User arisa created");
|
|
435
|
-
|
|
436
|
-
// 2. Grant passwordless sudo (needed for full tool execution in Claude/Codex)
|
|
437
|
-
try {
|
|
438
|
-
writeFileSync("/etc/sudoers.d/arisa", "arisa ALL=(ALL) NOPASSWD: ALL\n", { mode: 0o440 });
|
|
439
|
-
step(true, "Passwordless sudo granted");
|
|
440
|
-
} catch (e) {
|
|
441
|
-
// Not fatal — sudo may not be installed in minimal containers
|
|
442
|
-
step(false, `Sudo setup skipped: ${e.message || e}`);
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
// 3. Give arisa access to root's bun (no separate install needed)
|
|
446
|
-
grantBunAccess();
|
|
447
|
-
step(true, "Access to root's bun granted");
|
|
448
|
-
|
|
449
|
-
process.stdout.write(" Done. Core will run as arisa; Claude/Codex calls are direct.\n\n");
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
function grantBunAccess() {
|
|
453
|
-
// Allow arisa to traverse into /root (execute only, not read)
|
|
454
|
-
spawnSync("chmod", ["o+x", homeDir], { stdio: "ignore" });
|
|
455
|
-
// Allow arisa to read+execute root's bun and globally installed CLIs
|
|
456
|
-
spawnSync("chmod", ["-R", "o+rX", ROOT_BUN_INSTALL], { stdio: "ignore" });
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
// Provision arisa user if running as root and not yet done
|
|
460
|
-
if (isRoot() && !isArisaUserProvisioned()) {
|
|
461
|
-
provisionArisaUser();
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// When root + arisa exists: route all runtime data through arisa's home
|
|
465
|
-
// so Core (running as arisa) and Daemon (root) share the same data dir.
|
|
466
|
-
if (isRoot() && arisaUserExists()) {
|
|
467
|
-
// Ensure arisa can access root's bun on every startup
|
|
468
|
-
grantBunAccess();
|
|
469
|
-
const arisaDataDir = "/home/arisa/.arisa";
|
|
470
|
-
const rootDataDir = join("/root", ".arisa");
|
|
471
|
-
|
|
472
|
-
// One-time migration from root's data dir
|
|
473
|
-
if (existsSync(rootDataDir) && !existsSync(arisaDataDir)) {
|
|
474
|
-
spawnSync("cp", ["-r", rootDataDir, arisaDataDir], { stdio: "ignore" });
|
|
475
|
-
spawnSync("chown", ["-R", "arisa:arisa", arisaDataDir], { stdio: "ignore" });
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// Ensure arisa data dir exists with correct ownership
|
|
479
|
-
if (!existsSync(arisaDataDir)) {
|
|
480
|
-
mkdirSync(arisaDataDir, { recursive: true });
|
|
481
|
-
spawnSync("chown", ["-R", "arisa:arisa", arisaDataDir], { stdio: "ignore" });
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Ensure arisa can traverse to and read project files.
|
|
485
|
-
// When installed globally under /root/.bun/..., parent dirs are mode 700.
|
|
486
|
-
// Add o+x (traverse only, not read) on each ancestor so arisa can reach pkgRoot.
|
|
487
|
-
let traverseDir = pkgRoot;
|
|
488
|
-
while (traverseDir !== "/") {
|
|
489
|
-
spawnSync("chmod", ["o+x", traverseDir], { stdio: "ignore" });
|
|
490
|
-
traverseDir = dirname(traverseDir);
|
|
491
|
-
}
|
|
492
|
-
spawnSync("chmod", ["-R", "o+rX", pkgRoot], { stdio: "ignore" });
|
|
493
|
-
|
|
494
|
-
// All processes use arisa's data dir (inherited by Daemon → Core)
|
|
495
|
-
process.env.ARISA_DATA_DIR = arisaDataDir;
|
|
496
|
-
|
|
497
|
-
// Permissive umask so files Daemon (root) creates at runtime in the shared
|
|
498
|
-
// data dir are readable/writable by Core (arisa). Safe in Docker containers.
|
|
499
|
-
process.umask(0o000);
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
// Pre-flight: install missing CLIs while still root (before su arisa).
|
|
503
|
-
// arisa user has read+execute but NOT write access to root's bun dir.
|
|
504
|
-
// Uses @inquirer/prompts checkbox when available (same UI as setup.ts).
|
|
505
|
-
async function preflightInstallClis() {
|
|
506
|
-
const clis = {
|
|
507
|
-
claude: "@anthropic-ai/claude-code",
|
|
508
|
-
codex: "@openai/codex",
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
const bunBinDir = join(ROOT_BUN_INSTALL, "bin");
|
|
512
|
-
const missing = [];
|
|
513
|
-
|
|
514
|
-
for (const [name, pkg] of Object.entries(clis)) {
|
|
515
|
-
if (existsSync(join(bunBinDir, name))) continue;
|
|
516
|
-
missing.push({ name, pkg });
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
if (missing.length === 0) return;
|
|
520
|
-
|
|
521
|
-
// Show status
|
|
522
|
-
process.stdout.write("\nCLI Status:\n");
|
|
523
|
-
for (const name of Object.keys(clis)) {
|
|
524
|
-
const installed = existsSync(join(bunBinDir, name));
|
|
525
|
-
const label = name === "claude" ? "Claude" : "Codex";
|
|
526
|
-
process.stdout.write(` ${installed ? "\u2713" : "\u2717"} ${label}${installed ? "" : " \u2014 not installed"}\n`);
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
let toInstall = missing;
|
|
530
|
-
|
|
531
|
-
if (process.stdin.isTTY) {
|
|
532
|
-
// Try @inquirer/prompts for the same checkbox UI as setup.ts
|
|
533
|
-
let inq = null;
|
|
534
|
-
try { inq = await import("@inquirer/prompts"); } catch {}
|
|
535
|
-
|
|
536
|
-
if (inq) {
|
|
537
|
-
const selected = await inq.checkbox({
|
|
538
|
-
message: "Install missing CLIs? (space to select, enter to confirm)",
|
|
539
|
-
choices: missing.map((cli) => ({
|
|
540
|
-
name: `${cli.name === "claude" ? "Claude" : "Codex"} (${cli.pkg})`,
|
|
541
|
-
value: cli,
|
|
542
|
-
checked: true,
|
|
543
|
-
})),
|
|
544
|
-
});
|
|
545
|
-
toInstall = selected;
|
|
546
|
-
} else {
|
|
547
|
-
// Fallback: simple Y/n
|
|
548
|
-
const rl = require("node:readline");
|
|
549
|
-
const ask = (q) =>
|
|
550
|
-
new Promise((resolve) => {
|
|
551
|
-
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
552
|
-
iface.question(q, (a) => { iface.close(); resolve(a.trim()); });
|
|
553
|
-
});
|
|
554
|
-
const answer = await ask("\nInstall missing CLIs? (Y/n): ");
|
|
555
|
-
if (answer.toLowerCase() === "n") toInstall = [];
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (toInstall.length === 0) {
|
|
560
|
-
process.stdout.write(" Skipping CLI installation.\n");
|
|
561
|
-
return;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
for (const { name, pkg } of toInstall) {
|
|
565
|
-
process.stdout.write(`\nInstalling ${name}...\n`);
|
|
566
|
-
const result = spawnSync("bun", ["add", "-g", pkg], {
|
|
567
|
-
stdio: "inherit",
|
|
568
|
-
timeout: 180000,
|
|
569
|
-
});
|
|
570
|
-
if (result.status === 0) {
|
|
571
|
-
process.stdout.write(` \u2713 ${name} installed\n`);
|
|
572
|
-
} else {
|
|
573
|
-
process.stdout.write(` \u2717 ${name} install failed\n`);
|
|
574
|
-
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
// Re-grant read+execute access after installing new binaries
|
|
578
|
-
grantBunAccess();
|
|
579
|
-
}
|
|
580
|
-
|
|
581
|
-
if (isRoot() && arisaUserExists()) {
|
|
582
|
-
await preflightInstallClis();
|
|
583
|
-
}
|
|
584
|
-
|
|
585
|
-
// Then fall through to normal daemon startup
|
|
586
|
-
|
|
587
|
-
// ── Non-root flow (unchanged) ───────────────────────────────────────
|
|
588
|
-
|
|
589
|
-
if (command === "help" || command === "--help" || command === "-h") {
|
|
590
|
-
printHelp();
|
|
591
|
-
process.exit(0);
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
if (command === "version" || command === "--version" || command === "-v") {
|
|
595
|
-
printVersion();
|
|
596
|
-
process.exit(0);
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
switch (command) {
|
|
600
|
-
case "start":
|
|
601
|
-
process.exit(startService());
|
|
602
|
-
break;
|
|
603
|
-
case "stop":
|
|
604
|
-
process.exit(stopService());
|
|
605
|
-
break;
|
|
606
|
-
case "status":
|
|
607
|
-
process.exit(statusService());
|
|
608
|
-
break;
|
|
609
|
-
case "restart":
|
|
610
|
-
process.exit(restartService());
|
|
611
|
-
break;
|
|
612
|
-
case "daemon":
|
|
613
|
-
case "run": {
|
|
614
|
-
// Single bun process: daemon + core in-process with --watch.
|
|
615
|
-
// When root, run as arisa (Claude CLI refuses root). su without "-"
|
|
616
|
-
// preserves parent env (ARISA_DATA_DIR, tokens, API keys).
|
|
617
|
-
let child;
|
|
618
|
-
if (isRoot() && arisaUserExists()) {
|
|
619
|
-
const bunEnv = `export HOME=/home/arisa && ${ARISA_BUN_ENV}`;
|
|
620
|
-
const cmd = `${bunEnv} && cd ${pkgRoot} && exec bun --watch ${daemonEntry}`;
|
|
621
|
-
child = spawnSync("su", ["arisa", "-s", "/bin/bash", "-c", cmd], {
|
|
622
|
-
stdio: "inherit",
|
|
623
|
-
env: process.env,
|
|
624
|
-
});
|
|
625
|
-
} else {
|
|
626
|
-
child = runWithBun(["--watch", daemonEntry, ...rest]);
|
|
627
|
-
}
|
|
628
|
-
process.exit(child.status === null ? 1 : child.status);
|
|
629
|
-
}
|
|
630
|
-
case "core": {
|
|
631
|
-
await import(coreEntry);
|
|
632
|
-
break;
|
|
633
|
-
}
|
|
634
|
-
case "dev":
|
|
635
|
-
{
|
|
636
|
-
// dev needs --watch which is a bun CLI flag, so child process required
|
|
637
|
-
const child = runWithBun(["--watch", coreEntry, ...rest]);
|
|
638
|
-
process.exit(child.status === null ? 1 : child.status);
|
|
639
|
-
}
|
|
640
|
-
default:
|
|
641
|
-
process.stderr.write(`Unknown command: ${command}\n\n`);
|
|
642
|
-
printHelp();
|
|
643
|
-
process.exit(1);
|
|
644
|
-
}
|
package/scripts/dump-commands.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Diagnostic: prints the exact su/bun commands that Arisa would execute,
|
|
4
|
-
* ready to copy-paste into a terminal.
|
|
5
|
-
*/
|
|
6
|
-
import { buildBunWrappedAgentCliCommand } from "../src/shared/ai-cli";
|
|
7
|
-
|
|
8
|
-
function printable(cmd: string[]): string {
|
|
9
|
-
if (cmd[0] === "su") {
|
|
10
|
-
// cmd = ["su", "arisa", "-s", "/bin/bash", "-c", "<bash script>"]
|
|
11
|
-
// Wrap the -c argument in double quotes — safe because shellEscape only uses single quotes
|
|
12
|
-
return `${cmd.slice(0, 5).join(" ")} "${cmd[5]}"`;
|
|
13
|
-
}
|
|
14
|
-
// Non-root: just join, quoting args with spaces
|
|
15
|
-
return cmd.map(c => /[\s']/.test(c) ? `"${c}"` : c).join(" ");
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const probeArgs = ["-p", "say ok", "--model", "haiku", "--output-format", "text", "--dangerously-skip-permissions"];
|
|
19
|
-
const processorArgs = ["--dangerously-skip-permissions", "--output-format", "text", "--model", "claude-sonnet-4-20250514", "-p", "hello test"];
|
|
20
|
-
|
|
21
|
-
console.log("=== AUTH PROBE (daemon/auto-install.ts) ===\n");
|
|
22
|
-
console.log(printable(buildBunWrappedAgentCliCommand("claude", probeArgs)));
|
|
23
|
-
|
|
24
|
-
console.log("\n\n=== MESSAGE PROCESSOR (core/processor.ts) ===\n");
|
|
25
|
-
console.log(printable(buildBunWrappedAgentCliCommand("claude", processorArgs)));
|
|
26
|
-
console.log();
|
package/scripts/test-secrets.ts
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* Test encrypted secrets loading
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { secrets } from "../src/shared/secrets";
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
console.log("🔐 Testing encrypted secrets...\n");
|
|
10
|
-
|
|
11
|
-
const telegram = await secrets.telegram();
|
|
12
|
-
const openai = await secrets.openai();
|
|
13
|
-
const elevenlabs = await secrets.elevenlabs();
|
|
14
|
-
|
|
15
|
-
console.log("✓ TELEGRAM_BOT_TOKEN:", telegram ? `${telegram.slice(0, 10)}...${telegram.slice(-10)}` : "NOT FOUND");
|
|
16
|
-
console.log("✓ OPENAI_API_KEY:", openai ? `${openai.slice(0, 10)}...${openai.slice(-10)}` : "NOT FOUND");
|
|
17
|
-
console.log("✓ ELEVENLABS_API_KEY:", elevenlabs ? `${elevenlabs.slice(0, 10)}...${elevenlabs.slice(-10)}` : "NOT FOUND");
|
|
18
|
-
|
|
19
|
-
console.log("\n✅ Secrets loaded successfully from encrypted DB");
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
main();
|