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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arisa",
3
- "version": "2.3.52",
3
+ "version": "2.3.54",
4
4
  "description": "Arisa - dynamic agent runtime with daemon/core architecture that evolves through user interaction",
5
5
  "keywords": [
6
6
  "tinyclaw",
@@ -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: { ...process.env },
49
+ env,
43
50
  });
44
51
 
45
52
  const timeout = setTimeout(() => proc.kill(), INSTALL_TIMEOUT);
@@ -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;
@@ -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,