arisa 2.2.12 → 2.3.1
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 +23 -276
- package/package.json +1 -1
- package/src/daemon/setup.ts +6 -3
- package/src/shared/ai-cli.ts +30 -0
package/bin/arisa.js
CHANGED
|
@@ -403,7 +403,9 @@ function printForegroundNotice() {
|
|
|
403
403
|
process.stdout.write("Use `arisa start` to run it as a background service.\n");
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
// ── Root
|
|
406
|
+
// ── Root: create arisa user for claude/codex execution ──────────────
|
|
407
|
+
// Daemon runs as root. Only claude/codex CLI calls run as user arisa
|
|
408
|
+
// (Claude CLI refuses to run as root). This avoids two heavy bun processes.
|
|
407
409
|
|
|
408
410
|
function isRoot() {
|
|
409
411
|
return process.getuid?.() === 0;
|
|
@@ -413,24 +415,8 @@ function arisaUserExists() {
|
|
|
413
415
|
return spawnSync("id", ["arisa"], { stdio: "ignore" }).status === 0;
|
|
414
416
|
}
|
|
415
417
|
|
|
416
|
-
function
|
|
417
|
-
return arisaUserExists() && existsSync("/home/arisa/.bun/bin/bun")
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
function isArisaConfigured() {
|
|
421
|
-
const envPath = "/home/arisa/.arisa/.env";
|
|
422
|
-
if (!existsSync(envPath)) return false;
|
|
423
|
-
const content = readFileSync(envPath, "utf8");
|
|
424
|
-
return content.includes("TELEGRAM_BOT_TOKEN=");
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
function detectSudoGroup() {
|
|
428
|
-
// Debian/Ubuntu use 'sudo', RHEL/Fedora use 'wheel'
|
|
429
|
-
const sudoGroup = spawnSync("getent", ["group", "sudo"], { stdio: "ignore" });
|
|
430
|
-
if (sudoGroup.status === 0) return "sudo";
|
|
431
|
-
const wheelGroup = spawnSync("getent", ["group", "wheel"], { stdio: "ignore" });
|
|
432
|
-
if (wheelGroup.status === 0) return "wheel";
|
|
433
|
-
return null;
|
|
418
|
+
function isArisaUserProvisioned() {
|
|
419
|
+
return arisaUserExists() && existsSync("/home/arisa/.bun/bin/bun");
|
|
434
420
|
}
|
|
435
421
|
|
|
436
422
|
function step(ok, msg) {
|
|
@@ -438,283 +424,44 @@ function step(ok, msg) {
|
|
|
438
424
|
}
|
|
439
425
|
|
|
440
426
|
const ARISA_BUN_ENV = 'export BUN_INSTALL=/home/arisa/.bun && export PATH=/home/arisa/.bun/bin:$PATH';
|
|
441
|
-
const SHARED_ARISA_ROOT = "/opt/arisa/node_modules/arisa";
|
|
442
|
-
const sharedDaemonEntry = join(SHARED_ARISA_ROOT, "src", "daemon", "index.ts");
|
|
443
|
-
|
|
444
|
-
function runAsInherit(cmd) {
|
|
445
|
-
return spawnSync("su", ["-", "arisa", "-c", `${ARISA_BUN_ENV} && ${cmd}`], {
|
|
446
|
-
stdio: "inherit",
|
|
447
|
-
timeout: 180_000,
|
|
448
|
-
});
|
|
449
|
-
}
|
|
450
427
|
|
|
451
428
|
function provisionArisaUser() {
|
|
452
|
-
process.stdout.write("
|
|
429
|
+
process.stdout.write("Creating user 'arisa' for Claude/Codex CLI execution...\n");
|
|
453
430
|
|
|
454
431
|
// 1. Create user
|
|
455
|
-
const useradd = spawnSync("useradd", ["-m", "-s", "/bin/bash", "arisa"], {
|
|
456
|
-
stdio: "pipe",
|
|
457
|
-
});
|
|
432
|
+
const useradd = spawnSync("useradd", ["-m", "-s", "/bin/bash", "arisa"], { stdio: "pipe" });
|
|
458
433
|
if (useradd.status !== 0) {
|
|
459
434
|
step(false, `Failed to create user: ${(useradd.stderr || "").toString().trim()}`);
|
|
460
435
|
process.exit(1);
|
|
461
436
|
}
|
|
462
437
|
step(true, "User arisa created");
|
|
463
438
|
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
// 2. Install bun (curl, not bun — low memory footprint)
|
|
471
|
-
process.stdout.write(" Installing bun (this may take a minute)...\n");
|
|
472
|
-
const bunInstall = runAsInherit("curl -fsSL https://bun.sh/install | bash");
|
|
439
|
+
// 2. Install bun for arisa (curl — lightweight, no bun child process)
|
|
440
|
+
process.stdout.write(" Installing bun for arisa (this may take a minute)...\n");
|
|
441
|
+
const bunInstall = spawnSync("su", ["-", "arisa", "-c", "curl -fsSL https://bun.sh/install | bash"], {
|
|
442
|
+
stdio: "inherit",
|
|
443
|
+
timeout: 180_000,
|
|
444
|
+
});
|
|
473
445
|
if (bunInstall.status !== 0) {
|
|
474
446
|
step(false, "Failed to install bun");
|
|
475
447
|
process.exit(1);
|
|
476
448
|
}
|
|
477
449
|
step(true, "Bun installed for arisa");
|
|
478
450
|
|
|
479
|
-
//
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
writeFileSync(profilePath, profileContent + bunPath, "utf8");
|
|
485
|
-
spawnSync("chown", ["arisa:arisa", profilePath], { stdio: "ignore" });
|
|
486
|
-
}
|
|
451
|
+
// 3. Write ink-shim for non-TTY execution (prevents Ink setRawMode crash)
|
|
452
|
+
const shimPath = "/home/arisa/.arisa-ink-shim.js";
|
|
453
|
+
writeFileSync(shimPath, 'if(process.stdin&&!process.stdin.setRawMode)process.stdin.setRawMode=()=>process.stdin;\n');
|
|
454
|
+
spawnSync("chown", ["arisa:arisa", shimPath], { stdio: "ignore" });
|
|
455
|
+
step(true, "Ink shim installed");
|
|
487
456
|
|
|
488
|
-
|
|
489
|
-
const sharedDir = "/opt/arisa";
|
|
490
|
-
const globalModules = resolve(pkgRoot, "..");
|
|
491
|
-
mkdirSync(sharedDir, { recursive: true });
|
|
492
|
-
spawnSync("cp", ["-r", globalModules, join(sharedDir, "node_modules")], { stdio: "pipe" });
|
|
493
|
-
spawnSync("chown", ["-R", "arisa:arisa", sharedDir], { stdio: "pipe" });
|
|
494
|
-
step(true, "Arisa copied to /opt/arisa");
|
|
495
|
-
|
|
496
|
-
// 4. Migrate data
|
|
497
|
-
const rootArisa = "/root/.arisa";
|
|
498
|
-
if (existsSync(rootArisa)) {
|
|
499
|
-
const destArisa = "/home/arisa/.arisa";
|
|
500
|
-
spawnSync("cp", ["-r", rootArisa, destArisa], { stdio: "pipe" });
|
|
501
|
-
spawnSync("chown", ["-R", "arisa:arisa", destArisa], { stdio: "pipe" });
|
|
502
|
-
step(true, "Data migrated to /home/arisa/.arisa/");
|
|
503
|
-
}
|
|
457
|
+
process.stdout.write(" Done. Claude/Codex will run as user arisa.\n\n");
|
|
504
458
|
}
|
|
505
459
|
|
|
506
|
-
//
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
function writeSystemdSystemUnit() {
|
|
511
|
-
const unit = `[Unit]
|
|
512
|
-
Description=Arisa Agent Runtime
|
|
513
|
-
After=network-online.target
|
|
514
|
-
Wants=network-online.target
|
|
515
|
-
|
|
516
|
-
[Service]
|
|
517
|
-
Type=simple
|
|
518
|
-
User=arisa
|
|
519
|
-
WorkingDirectory=${SHARED_ARISA_ROOT}
|
|
520
|
-
ExecStart=/home/arisa/.bun/bin/bun ${sharedDaemonEntry}
|
|
521
|
-
Restart=always
|
|
522
|
-
RestartSec=5
|
|
523
|
-
Environment=ARISA_PROJECT_DIR=${SHARED_ARISA_ROOT}
|
|
524
|
-
Environment=BUN_INSTALL=/home/arisa/.bun
|
|
525
|
-
Environment=PATH=/home/arisa/.bun/bin:/usr/local/bin:/usr/bin:/bin
|
|
526
|
-
|
|
527
|
-
[Install]
|
|
528
|
-
WantedBy=multi-user.target
|
|
529
|
-
`;
|
|
530
|
-
writeFileSync(systemdSystemUnitPath, unit, "utf8");
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function runSystemdSystem(commandArgs) {
|
|
534
|
-
const child = runCommand("systemctl", commandArgs, { stdio: "pipe" });
|
|
535
|
-
if (child.status !== 0) {
|
|
536
|
-
const stderr = child.stderr || "Unknown systemd error";
|
|
537
|
-
process.stderr.write(stderr.endsWith("\n") ? stderr : `${stderr}\n`);
|
|
538
|
-
return { ok: false, status: child.status ?? 1 };
|
|
539
|
-
}
|
|
540
|
-
return { ok: true, status: 0, stdout: child.stdout || "" };
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
function startSystemdSystem() {
|
|
544
|
-
const start = runSystemdSystem(["start", "arisa"]);
|
|
545
|
-
if (!start.ok) return start.status;
|
|
546
|
-
process.stdout.write("Arisa service started.\n");
|
|
547
|
-
return 0;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
function stopSystemdSystem() {
|
|
551
|
-
const stop = runSystemdSystem(["stop", "arisa"]);
|
|
552
|
-
if (!stop.ok) return stop.status;
|
|
553
|
-
process.stdout.write("Arisa service stopped.\n");
|
|
554
|
-
return 0;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
function restartSystemdSystem() {
|
|
558
|
-
const restart = runSystemdSystem(["restart", "arisa"]);
|
|
559
|
-
if (!restart.ok) return restart.status;
|
|
560
|
-
process.stdout.write("Arisa service restarted.\n");
|
|
561
|
-
return 0;
|
|
562
|
-
}
|
|
563
|
-
|
|
564
|
-
function statusSystemdSystem() {
|
|
565
|
-
const result = runCommand("systemctl", ["status", "arisa"], { stdio: "inherit" });
|
|
566
|
-
return result.status ?? 1;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
function isSystemdActive() {
|
|
570
|
-
const result = runCommand("systemctl", ["is-active", "arisa"], { stdio: "pipe" });
|
|
571
|
-
return result.status === 0;
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
function canUseSystemdSystem() {
|
|
575
|
-
if (platform() !== "linux") return false;
|
|
576
|
-
if (!commandExists("systemctl")) return false;
|
|
577
|
-
const probe = runCommand("systemctl", ["is-system-running"], { stdio: "pipe" });
|
|
578
|
-
const state = (probe.stdout || "").trim();
|
|
579
|
-
return probe.status === 0 || state === "degraded" || state === "running";
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
function runArisaForeground() {
|
|
583
|
-
const result = spawnSync("su", ["-", "arisa", "-c", `${ARISA_BUN_ENV} && export ARISA_PROJECT_DIR=${SHARED_ARISA_ROOT} && exec /home/arisa/.bun/bin/bun ${sharedDaemonEntry}`], {
|
|
584
|
-
stdio: "inherit",
|
|
585
|
-
});
|
|
586
|
-
return result.status ?? 1;
|
|
587
|
-
}
|
|
588
|
-
|
|
589
|
-
// ── Minimal setup (runs as root, no second bun process) ─────────────
|
|
590
|
-
|
|
591
|
-
function askLine(promptText) {
|
|
592
|
-
process.stdout.write(promptText);
|
|
593
|
-
const result = spawnSync("bash", ["-c", "read -r line; echo \"$line\""], {
|
|
594
|
-
stdio: ["inherit", "pipe", "inherit"],
|
|
595
|
-
});
|
|
596
|
-
return (result.stdout || "").toString().trim();
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
function runMinimalSetup() {
|
|
600
|
-
const arisaDataDir = "/home/arisa/.arisa";
|
|
601
|
-
const envPath = join(arisaDataDir, ".env");
|
|
602
|
-
|
|
603
|
-
// Load existing .env if any
|
|
604
|
-
const vars = {};
|
|
605
|
-
if (existsSync(envPath)) {
|
|
606
|
-
for (const line of readFileSync(envPath, "utf8").split("\n")) {
|
|
607
|
-
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.+)$/);
|
|
608
|
-
if (match) vars[match[1]] = match[2].trim();
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
|
|
612
|
-
if (!vars.TELEGRAM_BOT_TOKEN) {
|
|
613
|
-
process.stdout.write("\nArisa Setup\n\n");
|
|
614
|
-
const token = askLine("Telegram Bot Token (from https://t.me/BotFather): ");
|
|
615
|
-
if (!token) {
|
|
616
|
-
process.stdout.write("No token provided. Cannot start without Telegram Bot Token.\n");
|
|
617
|
-
process.exit(1);
|
|
618
|
-
}
|
|
619
|
-
vars.TELEGRAM_BOT_TOKEN = token;
|
|
620
|
-
process.stdout.write(" Token saved.\n");
|
|
621
|
-
}
|
|
622
|
-
|
|
623
|
-
if (!vars.OPENAI_API_KEY) {
|
|
624
|
-
const key = askLine("OpenAI API Key (optional, enter to skip): ");
|
|
625
|
-
if (key) {
|
|
626
|
-
vars.OPENAI_API_KEY = key;
|
|
627
|
-
process.stdout.write(" Key saved.\n");
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
vars.ARISA_SETUP_COMPLETE = "1";
|
|
632
|
-
|
|
633
|
-
// Write .env
|
|
634
|
-
mkdirSync(arisaDataDir, { recursive: true });
|
|
635
|
-
const content = Object.entries(vars).map(([k, v]) => `${k}=${v}`).join("\n") + "\n";
|
|
636
|
-
writeFileSync(envPath, content, "utf8");
|
|
637
|
-
spawnSync("chown", ["-R", "arisa:arisa", arisaDataDir], { stdio: "ignore" });
|
|
638
|
-
|
|
639
|
-
process.stdout.write(`\nConfig saved to ${envPath}\n`);
|
|
640
|
-
}
|
|
641
|
-
|
|
642
|
-
// ── Root guard ──────────────────────────────────────────────────────
|
|
643
|
-
|
|
644
|
-
if (isRoot()) {
|
|
645
|
-
if (!isProvisioned()) {
|
|
646
|
-
provisionArisaUser();
|
|
647
|
-
if (canUseSystemdSystem()) {
|
|
648
|
-
writeSystemdSystemUnit();
|
|
649
|
-
spawnSync("systemctl", ["daemon-reload"], { stdio: "inherit" });
|
|
650
|
-
spawnSync("systemctl", ["enable", "arisa"], { stdio: "inherit" });
|
|
651
|
-
step(true, "Systemd service enabled (auto-starts on reboot)");
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
// Minimal setup: collect tokens here (no second bun process)
|
|
656
|
-
if (!isArisaConfigured()) {
|
|
657
|
-
runMinimalSetup();
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// Already provisioned + configured — route commands
|
|
661
|
-
if (command === "help" || command === "--help" || command === "-h") {
|
|
662
|
-
printHelp();
|
|
663
|
-
process.exit(0);
|
|
664
|
-
}
|
|
665
|
-
if (command === "version" || command === "--version" || command === "-v") {
|
|
666
|
-
printVersion();
|
|
667
|
-
process.exit(0);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
const hasSystemd = canUseSystemdSystem();
|
|
671
|
-
|
|
672
|
-
if (isDefaultInvocation) {
|
|
673
|
-
if (hasSystemd) {
|
|
674
|
-
if (!isSystemdActive()) {
|
|
675
|
-
const start = startSystemdSystem();
|
|
676
|
-
if (start !== 0) process.exit(start);
|
|
677
|
-
}
|
|
678
|
-
process.stdout.write("\nArisa is running. Management commands:\n");
|
|
679
|
-
process.stdout.write(" Status: systemctl status arisa\n");
|
|
680
|
-
process.stdout.write(" Logs: journalctl -u arisa -f\n");
|
|
681
|
-
process.stdout.write(" Restart: systemctl restart arisa\n");
|
|
682
|
-
process.stdout.write(" Stop: systemctl stop arisa\n\n");
|
|
683
|
-
process.exit(0);
|
|
684
|
-
}
|
|
685
|
-
// No systemd → foreground (two bun processes, but no other option)
|
|
686
|
-
process.exit(runArisaForeground());
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
switch (command) {
|
|
690
|
-
case "start":
|
|
691
|
-
if (hasSystemd) process.exit(startSystemdSystem());
|
|
692
|
-
process.exit(runArisaForeground());
|
|
693
|
-
break;
|
|
694
|
-
case "stop":
|
|
695
|
-
if (hasSystemd) process.exit(stopSystemdSystem());
|
|
696
|
-
process.stderr.write("No systemd available. Stop the foreground process with Ctrl+C.\n");
|
|
697
|
-
process.exit(1);
|
|
698
|
-
break;
|
|
699
|
-
case "restart":
|
|
700
|
-
if (hasSystemd) process.exit(restartSystemdSystem());
|
|
701
|
-
process.stderr.write("No systemd available. Restart the foreground process manually.\n");
|
|
702
|
-
process.exit(1);
|
|
703
|
-
break;
|
|
704
|
-
case "status":
|
|
705
|
-
if (hasSystemd) process.exit(statusSystemdSystem());
|
|
706
|
-
process.stderr.write("No systemd available.\n");
|
|
707
|
-
process.exit(1);
|
|
708
|
-
break;
|
|
709
|
-
case "daemon":
|
|
710
|
-
case "run":
|
|
711
|
-
process.exit(runArisaForeground());
|
|
712
|
-
default:
|
|
713
|
-
process.stderr.write(`Unknown command: ${command}\n\n`);
|
|
714
|
-
printHelp();
|
|
715
|
-
process.exit(1);
|
|
716
|
-
}
|
|
460
|
+
// Provision arisa user if running as root and not yet done
|
|
461
|
+
if (isRoot() && !isArisaUserProvisioned()) {
|
|
462
|
+
provisionArisaUser();
|
|
717
463
|
}
|
|
464
|
+
// Then fall through to normal daemon startup (as root)
|
|
718
465
|
|
|
719
466
|
// ── Non-root flow (unchanged) ───────────────────────────────────────
|
|
720
467
|
|
package/package.json
CHANGED
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, buildBunWrappedAgentCliCommand, isRunningAsRoot, type AgentCliName } from "../shared/ai-cli";
|
|
19
19
|
|
|
20
20
|
const ENV_PATH = join(dataDir, ".env");
|
|
21
21
|
const SETUP_DONE_KEY = "ARISA_SETUP_COMPLETE";
|
|
@@ -230,11 +230,14 @@ async function setupClis(inq: typeof import("@inquirer/prompts") | null, vars: R
|
|
|
230
230
|
|
|
231
231
|
async function installCli(cli: AgentCliName): Promise<boolean> {
|
|
232
232
|
try {
|
|
233
|
-
const
|
|
233
|
+
const cmd = isRunningAsRoot()
|
|
234
|
+
? ["su", "-", "arisa", "-c", `export BUN_INSTALL=/home/arisa/.bun && export PATH=/home/arisa/.bun/bin:$PATH && bun add -g ${CLI_PACKAGES[cli]}`]
|
|
235
|
+
: ["bun", "add", "-g", CLI_PACKAGES[cli]];
|
|
236
|
+
const proc = Bun.spawn(cmd, {
|
|
234
237
|
stdout: "inherit",
|
|
235
238
|
stderr: "inherit",
|
|
236
239
|
});
|
|
237
|
-
const timeout = setTimeout(() => proc.kill(),
|
|
240
|
+
const timeout = setTimeout(() => proc.kill(), 180_000);
|
|
238
241
|
const exitCode = await proc.exited;
|
|
239
242
|
clearTimeout(timeout);
|
|
240
243
|
return exitCode === 0;
|
package/src/shared/ai-cli.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module shared/ai-cli
|
|
3
3
|
* @role Resolve agent CLI binaries and execute them via Bun runtime.
|
|
4
|
+
* When running as root, wraps calls with su - arisa to satisfy
|
|
5
|
+
* Claude CLI's non-root requirement.
|
|
4
6
|
*/
|
|
5
7
|
|
|
6
8
|
import { existsSync } from "fs";
|
|
@@ -8,6 +10,18 @@ import { delimiter, dirname, join } from "path";
|
|
|
8
10
|
|
|
9
11
|
export type AgentCliName = "claude" | "codex";
|
|
10
12
|
|
|
13
|
+
const ARISA_USER_BUN = "/home/arisa/.bun/bin";
|
|
14
|
+
const ARISA_INK_SHIM = "/home/arisa/.arisa-ink-shim.js";
|
|
15
|
+
const ARISA_BUN_ENV = `export BUN_INSTALL=/home/arisa/.bun && export PATH=${ARISA_USER_BUN}:$PATH`;
|
|
16
|
+
|
|
17
|
+
export function isRunningAsRoot(): boolean {
|
|
18
|
+
return process.getuid?.() === 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function shellEscape(arg: string): string {
|
|
22
|
+
return "'" + arg.replace(/'/g, "'\\''") + "'";
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
function unique(paths: Array<string | null | undefined>): string[] {
|
|
12
26
|
const seen = new Set<string>();
|
|
13
27
|
const out: string[] = [];
|
|
@@ -25,6 +39,14 @@ function cliOverrideEnvVar(cli: AgentCliName): string | undefined {
|
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
function candidatePaths(cli: AgentCliName): string[] {
|
|
42
|
+
if (isRunningAsRoot()) {
|
|
43
|
+
// When root, CLIs are installed under arisa user's bun
|
|
44
|
+
return unique([
|
|
45
|
+
cliOverrideEnvVar(cli),
|
|
46
|
+
join(ARISA_USER_BUN, cli),
|
|
47
|
+
]);
|
|
48
|
+
}
|
|
49
|
+
|
|
28
50
|
const bunInstall = process.env.BUN_INSTALL?.trim();
|
|
29
51
|
const bunDir = dirname(process.execPath);
|
|
30
52
|
const fromPath = Bun.which(cli);
|
|
@@ -57,6 +79,14 @@ export function isAgentCliInstalled(cli: AgentCliName): boolean {
|
|
|
57
79
|
const INK_SHIM = join(dirname(new URL(import.meta.url).pathname), "ink-shim.js");
|
|
58
80
|
|
|
59
81
|
export function buildBunWrappedAgentCliCommand(cli: AgentCliName, args: string[]): string[] {
|
|
82
|
+
if (isRunningAsRoot()) {
|
|
83
|
+
// Run as arisa user — Claude CLI refuses to run as root
|
|
84
|
+
const cliPath = resolveAgentCliPath(cli) || join(ARISA_USER_BUN, cli);
|
|
85
|
+
const shimPath = existsSync(ARISA_INK_SHIM) ? ARISA_INK_SHIM : INK_SHIM;
|
|
86
|
+
const inner = ["bun", "--preload", shimPath, cliPath, ...args].map(shellEscape).join(" ");
|
|
87
|
+
return ["su", "-", "arisa", "-c", `${ARISA_BUN_ENV} && ${inner}`];
|
|
88
|
+
}
|
|
89
|
+
|
|
60
90
|
const cliPath = resolveAgentCliPath(cli);
|
|
61
91
|
if (!cliPath) {
|
|
62
92
|
throw new Error(`${cli} CLI not found`);
|