arisa 2.3.52 → 2.3.54
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/bin/arisa.js +73 -0
- package/package.json +1 -1
- package/src/daemon/auto-install.ts +9 -2
- package/src/daemon/setup.ts +11 -2
- package/src/shared/ai-cli.ts +4 -1
package/bin/arisa.js
CHANGED
|
@@ -499,6 +499,79 @@ if (isRoot() && arisaUserExists()) {
|
|
|
499
499
|
process.umask(0o000);
|
|
500
500
|
}
|
|
501
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
|
+
// Interactive when TTY — asks which CLIs to install.
|
|
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
|
+
const rl = require("node:readline");
|
|
533
|
+
const ask = (q) =>
|
|
534
|
+
new Promise((resolve) => {
|
|
535
|
+
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
536
|
+
iface.question(q, (a) => { iface.close(); resolve(a.trim()); });
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
toInstall = [];
|
|
540
|
+
for (const cli of missing) {
|
|
541
|
+
const label = cli.name === "claude" ? "Claude" : "Codex";
|
|
542
|
+
const answer = await ask(`Install ${label} (${cli.pkg})? (Y/n): `);
|
|
543
|
+
if (answer.toLowerCase() !== "n") {
|
|
544
|
+
toInstall.push(cli);
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
if (toInstall.length === 0) {
|
|
550
|
+
process.stdout.write(" Skipping CLI installation.\n");
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
for (const { name, pkg } of toInstall) {
|
|
555
|
+
process.stdout.write(`\nInstalling ${name}...\n`);
|
|
556
|
+
const result = spawnSync("bun", ["add", "-g", pkg], {
|
|
557
|
+
stdio: "inherit",
|
|
558
|
+
timeout: 180000,
|
|
559
|
+
});
|
|
560
|
+
if (result.status === 0) {
|
|
561
|
+
process.stdout.write(` \u2713 ${name} installed\n`);
|
|
562
|
+
} else {
|
|
563
|
+
process.stdout.write(` \u2717 ${name} install failed\n`);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Re-grant read+execute access after installing new binaries
|
|
568
|
+
grantBunAccess();
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
if (isRoot() && arisaUserExists()) {
|
|
572
|
+
await preflightInstallClis();
|
|
573
|
+
}
|
|
574
|
+
|
|
502
575
|
// Then fall through to normal daemon startup
|
|
503
576
|
|
|
504
577
|
// ── Non-root flow (unchanged) ───────────────────────────────────────
|
package/package.json
CHANGED
|
@@ -33,13 +33,20 @@ async function installCli(cli: AgentCliName): Promise<boolean> {
|
|
|
33
33
|
log.info(`Auto-install: installing ${cli} (${pkg})...`);
|
|
34
34
|
|
|
35
35
|
try {
|
|
36
|
-
// Install into root's bun (arisa has read+execute access)
|
|
37
36
|
const cmd = ["bun", "add", "-g", pkg];
|
|
37
|
+
const env = { ...process.env };
|
|
38
|
+
|
|
39
|
+
// When not root, BUN_INSTALL may point to root's dir (read-only for us).
|
|
40
|
+
// Install to user's own bun dir instead.
|
|
41
|
+
if (!isRunningAsRoot()) {
|
|
42
|
+
const home = process.env.HOME || "/home/arisa";
|
|
43
|
+
env.BUN_INSTALL = `${home}/.bun`;
|
|
44
|
+
}
|
|
38
45
|
|
|
39
46
|
const proc = Bun.spawn(cmd, {
|
|
40
47
|
stdout: "pipe",
|
|
41
48
|
stderr: "pipe",
|
|
42
|
-
env
|
|
49
|
+
env,
|
|
43
50
|
});
|
|
44
51
|
|
|
45
52
|
const timeout = setTimeout(() => proc.kill(), INSTALL_TIMEOUT);
|
package/src/daemon/setup.ts
CHANGED
|
@@ -15,7 +15,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync } from "fs";
|
|
|
15
15
|
import { dirname, join } from "path";
|
|
16
16
|
import { dataDir } from "../shared/paths";
|
|
17
17
|
import { secrets, setSecret } from "../shared/secrets";
|
|
18
|
-
import { isAgentCliInstalled, buildBunWrappedAgentCliCommand, type AgentCliName } from "../shared/ai-cli";
|
|
18
|
+
import { isAgentCliInstalled, isRunningAsRoot, buildBunWrappedAgentCliCommand, type AgentCliName } from "../shared/ai-cli";
|
|
19
19
|
|
|
20
20
|
const ENV_PATH = join(dataDir, ".env");
|
|
21
21
|
|
|
@@ -249,11 +249,20 @@ async function isCliAuthenticated(cli: AgentCliName): Promise<boolean> {
|
|
|
249
249
|
|
|
250
250
|
async function installCli(cli: AgentCliName): Promise<boolean> {
|
|
251
251
|
try {
|
|
252
|
-
// Install into root's bun (arisa has read+execute access)
|
|
253
252
|
const cmd = ["bun", "add", "-g", CLI_PACKAGES[cli]];
|
|
253
|
+
const env = { ...process.env };
|
|
254
|
+
|
|
255
|
+
// When not root, BUN_INSTALL may point to root's dir (read-only for us).
|
|
256
|
+
// Install to user's own bun dir instead.
|
|
257
|
+
if (!isRunningAsRoot()) {
|
|
258
|
+
const home = process.env.HOME || "/home/arisa";
|
|
259
|
+
env.BUN_INSTALL = `${home}/.bun`;
|
|
260
|
+
}
|
|
261
|
+
|
|
254
262
|
const proc = Bun.spawn(cmd, {
|
|
255
263
|
stdout: "inherit",
|
|
256
264
|
stderr: "inherit",
|
|
265
|
+
env,
|
|
257
266
|
});
|
|
258
267
|
const timeout = setTimeout(() => proc.kill(), 180_000);
|
|
259
268
|
const exitCode = await proc.exited;
|
package/src/shared/ai-cli.ts
CHANGED
|
@@ -41,11 +41,13 @@ function cliOverrideEnvVar(cli: AgentCliName): string | undefined {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
function candidatePaths(cli: AgentCliName): string[] {
|
|
44
|
+
const arisaBunBin = `${ARISA_HOME}/.bun/bin`;
|
|
45
|
+
|
|
44
46
|
if (isRunningAsRoot()) {
|
|
45
|
-
// When root, CLIs are installed under arisa user's bun
|
|
46
47
|
return unique([
|
|
47
48
|
cliOverrideEnvVar(cli),
|
|
48
49
|
join(ROOT_BUN_BIN, cli),
|
|
50
|
+
join(arisaBunBin, cli),
|
|
49
51
|
]);
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -61,6 +63,7 @@ function candidatePaths(cli: AgentCliName): string[] {
|
|
|
61
63
|
return unique([
|
|
62
64
|
cliOverrideEnvVar(cli),
|
|
63
65
|
bunInstall ? join(bunInstall, "bin", cli) : null,
|
|
66
|
+
join(arisaBunBin, cli),
|
|
64
67
|
join(bunDir, cli),
|
|
65
68
|
fromPath,
|
|
66
69
|
...fromEnvPath,
|