flyspace-cli 0.1.5 → 0.1.6
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/dist/cli.js +506 -381
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -430,13 +430,360 @@ async function runConfig(args) {
|
|
|
430
430
|
}
|
|
431
431
|
|
|
432
432
|
// src/commands/setup.ts
|
|
433
|
-
import { mkdirSync as
|
|
433
|
+
import { mkdirSync as mkdirSync5 } from "fs";
|
|
434
|
+
import { join as join7 } from "path";
|
|
435
|
+
import { confirm as confirm2 } from "@inquirer/prompts";
|
|
436
|
+
|
|
437
|
+
// src/service.ts
|
|
438
|
+
import { execSync } from "child_process";
|
|
439
|
+
import { existsSync as existsSync3 } from "fs";
|
|
440
|
+
import { join as join2 } from "path";
|
|
441
|
+
import { homedir, platform } from "os";
|
|
442
|
+
function serviceLabel(profile) {
|
|
443
|
+
return profile === DEFAULT_PROFILE ? "ai.flyspace" : `ai.flyspace.${profile}`;
|
|
444
|
+
}
|
|
445
|
+
function plistName(profile) {
|
|
446
|
+
return `${serviceLabel(profile)}.plist`;
|
|
447
|
+
}
|
|
448
|
+
function plistPath(profile) {
|
|
449
|
+
return join2(homedir(), "Library", "LaunchAgents", plistName(profile));
|
|
450
|
+
}
|
|
451
|
+
function legacyServiceLabel(profile) {
|
|
452
|
+
return profile === DEFAULT_PROFILE ? "io.flycoder.flyspace" : `io.flycoder.flyspace.${profile}`;
|
|
453
|
+
}
|
|
454
|
+
function legacyPlistPath(profile) {
|
|
455
|
+
return join2(homedir(), "Library", "LaunchAgents", `${legacyServiceLabel(profile)}.plist`);
|
|
456
|
+
}
|
|
457
|
+
function systemdUnitName(profile) {
|
|
458
|
+
return profile === DEFAULT_PROFILE ? "flyspace" : `flyspace-${profile}`;
|
|
459
|
+
}
|
|
460
|
+
function systemdUnitPath(profile) {
|
|
461
|
+
return join2(homedir(), ".config", "systemd", "user", `${systemdUnitName(profile)}.service`);
|
|
462
|
+
}
|
|
463
|
+
function isServiceInstalled(profile) {
|
|
464
|
+
if (platform() === "darwin") return existsSync3(plistPath(profile));
|
|
465
|
+
if (platform() === "linux") return existsSync3(systemdUnitPath(profile));
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
function restartService(profile) {
|
|
469
|
+
const os = platform();
|
|
470
|
+
if (os === "darwin") {
|
|
471
|
+
const plist = plistPath(profile);
|
|
472
|
+
try {
|
|
473
|
+
execSync(`launchctl unload "${plist}"`, { stdio: "pipe" });
|
|
474
|
+
} catch {
|
|
475
|
+
}
|
|
476
|
+
try {
|
|
477
|
+
execSync(`launchctl load "${plist}"`, { stdio: "pipe" });
|
|
478
|
+
return true;
|
|
479
|
+
} catch {
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (os === "linux") {
|
|
484
|
+
try {
|
|
485
|
+
execSync(`systemctl --user restart ${systemdUnitName(profile)}`, { stdio: "pipe" });
|
|
486
|
+
return true;
|
|
487
|
+
} catch {
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
var HOST_SERVICE_LABEL = "ai.flyspace.dashboard";
|
|
494
|
+
var HOST_SYSTEMD_UNIT = "flyspace-host";
|
|
495
|
+
function hostPlistPath() {
|
|
496
|
+
return join2(homedir(), "Library", "LaunchAgents", `${HOST_SERVICE_LABEL}.plist`);
|
|
497
|
+
}
|
|
498
|
+
function isHostServiceInstalled() {
|
|
499
|
+
if (platform() === "darwin") return existsSync3(hostPlistPath());
|
|
500
|
+
if (platform() === "linux") {
|
|
501
|
+
return existsSync3(join2(homedir(), ".config", "systemd", "user", `${HOST_SYSTEMD_UNIT}.service`));
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
function restartHostService() {
|
|
506
|
+
const os = platform();
|
|
507
|
+
try {
|
|
508
|
+
if (os === "darwin") {
|
|
509
|
+
execSync(`launchctl kickstart -k gui/${process.getuid?.()}/${HOST_SERVICE_LABEL}`, { stdio: "pipe" });
|
|
510
|
+
return true;
|
|
511
|
+
}
|
|
512
|
+
if (os === "linux") {
|
|
513
|
+
execSync(`systemctl --user restart ${HOST_SYSTEMD_UNIT}`, { stdio: "pipe" });
|
|
514
|
+
return true;
|
|
515
|
+
}
|
|
516
|
+
} catch {
|
|
517
|
+
}
|
|
518
|
+
return false;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// src/commands/install.ts
|
|
522
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
523
|
+
import { execSync as execSync2 } from "child_process";
|
|
524
|
+
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
525
|
+
import { homedir as homedir2, platform as platform2 } from "os";
|
|
526
|
+
function log(msg) {
|
|
527
|
+
console.log(`
|
|
528
|
+
\x1B[36m>\x1B[0m ${msg}`);
|
|
529
|
+
}
|
|
530
|
+
function success(msg) {
|
|
531
|
+
console.log(`\x1B[32m+\x1B[0m ${msg}`);
|
|
532
|
+
}
|
|
533
|
+
function warn(msg) {
|
|
534
|
+
console.log(`\x1B[33m!\x1B[0m ${msg}`);
|
|
535
|
+
}
|
|
536
|
+
function run(cmd, opts = {}) {
|
|
537
|
+
return execSync2(cmd, {
|
|
538
|
+
encoding: "utf-8",
|
|
539
|
+
stdio: opts.silent ? "pipe" : "inherit"
|
|
540
|
+
}).toString().trim();
|
|
541
|
+
}
|
|
542
|
+
function detectClaudeBinDir() {
|
|
543
|
+
try {
|
|
544
|
+
const claudePath = execSync2("which claude", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
545
|
+
return claudePath ? dirname2(claudePath) : void 0;
|
|
546
|
+
} catch {
|
|
547
|
+
return void 0;
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
function detectCustieBin() {
|
|
551
|
+
const argv1 = process.argv[1];
|
|
552
|
+
if (argv1) {
|
|
553
|
+
return resolve3(argv1);
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
return run("which flyspace", { silent: true });
|
|
557
|
+
} catch {
|
|
558
|
+
return "flyspace";
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function readEnvFile() {
|
|
562
|
+
if (!existsSync4(paths.CONFIG_FILE)) return {};
|
|
563
|
+
const vars = {};
|
|
564
|
+
for (const line of readFileSync3(paths.CONFIG_FILE, "utf-8").split("\n")) {
|
|
565
|
+
const trimmed = line.trim();
|
|
566
|
+
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
567
|
+
const eq = trimmed.indexOf("=");
|
|
568
|
+
if (eq === -1) continue;
|
|
569
|
+
const key = trimmed.slice(0, eq).trim();
|
|
570
|
+
const val = trimmed.slice(eq + 1).trim();
|
|
571
|
+
if (val) vars[key] = val;
|
|
572
|
+
}
|
|
573
|
+
return vars;
|
|
574
|
+
}
|
|
575
|
+
function escapeXml(str) {
|
|
576
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
577
|
+
}
|
|
578
|
+
function profileSpec() {
|
|
579
|
+
const profile = getProfile();
|
|
580
|
+
return {
|
|
581
|
+
label: serviceLabel(profile),
|
|
582
|
+
plistPath: plistPath(profile),
|
|
583
|
+
unitName: systemdUnitName(profile),
|
|
584
|
+
description: `Flyspace agent host${profile === "default" ? "" : ` (${profile})`}`,
|
|
585
|
+
// The default profile keeps the bare `start` args so its plist stays
|
|
586
|
+
// byte-for-byte identical to pre-profile installs (no reinstall needed).
|
|
587
|
+
args: profile === "default" ? ["start"] : ["start", "--profile", profile],
|
|
588
|
+
env: readEnvFile(),
|
|
589
|
+
logName: "custie"
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
function hostSpec() {
|
|
593
|
+
return {
|
|
594
|
+
label: HOST_SERVICE_LABEL,
|
|
595
|
+
plistPath: hostPlistPath(),
|
|
596
|
+
unitName: HOST_SYSTEMD_UNIT,
|
|
597
|
+
description: "Flyspace agent Host",
|
|
598
|
+
args: ["dashboard", "--mode", "host", "--no-open"],
|
|
599
|
+
env: {},
|
|
600
|
+
logName: "host"
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
async function installMacOS(spec) {
|
|
604
|
+
log("Setting up macOS LaunchAgent...");
|
|
605
|
+
const custieBin = detectCustieBin();
|
|
606
|
+
const nodePath = process.execPath;
|
|
607
|
+
const claudeBinDir = detectClaudeBinDir();
|
|
608
|
+
const logDir = paths.LOG_DIR;
|
|
609
|
+
mkdirSync2(logDir, { recursive: true });
|
|
610
|
+
const envVars = spec.env;
|
|
611
|
+
const homebrewBin = existsSync4("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
|
|
612
|
+
const asdfShims = join3(homedir2(), ".asdf", "shims");
|
|
613
|
+
const asdfBin = existsSync4(asdfShims) ? asdfShims : void 0;
|
|
614
|
+
const pathParts = [
|
|
615
|
+
dirname2(nodePath),
|
|
616
|
+
claudeBinDir,
|
|
617
|
+
asdfBin,
|
|
618
|
+
homebrewBin,
|
|
619
|
+
"/usr/local/bin",
|
|
620
|
+
"/usr/bin",
|
|
621
|
+
"/bin"
|
|
622
|
+
].filter(Boolean);
|
|
623
|
+
const programArgsXml = [custieBin, ...spec.args].map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
624
|
+
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
625
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
626
|
+
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
627
|
+
<plist version="1.0">
|
|
628
|
+
<dict>
|
|
629
|
+
<key>Label</key>
|
|
630
|
+
<string>${spec.label}</string>
|
|
631
|
+
|
|
632
|
+
<key>ProgramArguments</key>
|
|
633
|
+
<array>
|
|
634
|
+
${programArgsXml}
|
|
635
|
+
</array>
|
|
636
|
+
|
|
637
|
+
<key>EnvironmentVariables</key>
|
|
638
|
+
<dict>
|
|
639
|
+
<key>PATH</key>
|
|
640
|
+
<string>${pathParts.join(":")}</string>
|
|
641
|
+
${Object.entries(envVars).map(([k, v]) => ` <key>${k}</key>
|
|
642
|
+
<string>${escapeXml(v)}</string>`).join("\n")}
|
|
643
|
+
</dict>
|
|
644
|
+
|
|
645
|
+
<key>RunAtLoad</key>
|
|
646
|
+
<true/>
|
|
647
|
+
|
|
648
|
+
<key>KeepAlive</key>
|
|
649
|
+
<true/>
|
|
650
|
+
|
|
651
|
+
<key>StandardOutPath</key>
|
|
652
|
+
<string>${join3(logDir, `${spec.logName}.log`)}</string>
|
|
653
|
+
|
|
654
|
+
<key>StandardErrorPath</key>
|
|
655
|
+
<string>${join3(logDir, `${spec.logName}-error.log`)}</string>
|
|
656
|
+
</dict>
|
|
657
|
+
</plist>`;
|
|
658
|
+
const agentPlistPath = spec.plistPath;
|
|
659
|
+
mkdirSync2(dirname2(agentPlistPath), { recursive: true });
|
|
660
|
+
try {
|
|
661
|
+
run(`launchctl unload "${agentPlistPath}"`, { silent: true });
|
|
662
|
+
} catch {
|
|
663
|
+
}
|
|
664
|
+
try {
|
|
665
|
+
unlinkSync(agentPlistPath);
|
|
666
|
+
} catch {
|
|
667
|
+
}
|
|
668
|
+
writeFileSync3(agentPlistPath, plist);
|
|
669
|
+
run(`launchctl load "${agentPlistPath}"`, { silent: true });
|
|
670
|
+
success(`LaunchAgent installed and loaded (${spec.label}).`);
|
|
671
|
+
console.log(` Logs: ${logDir}`);
|
|
672
|
+
console.log(` Check: launchctl list | grep flyspace`);
|
|
673
|
+
}
|
|
674
|
+
async function installLinux(spec) {
|
|
675
|
+
log("Setting up systemd user service...");
|
|
676
|
+
const custieBin = detectCustieBin();
|
|
677
|
+
const nodePath = process.execPath;
|
|
678
|
+
const claudeBinDir = detectClaudeBinDir();
|
|
679
|
+
const logDir = paths.LOG_DIR;
|
|
680
|
+
mkdirSync2(logDir, { recursive: true });
|
|
681
|
+
const envLines = Object.entries(spec.env).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
682
|
+
const linuxbrewBin = existsSync4("/home/linuxbrew/.linuxbrew/bin") ? "/home/linuxbrew/.linuxbrew/bin" : void 0;
|
|
683
|
+
const pathParts = [
|
|
684
|
+
dirname2(nodePath),
|
|
685
|
+
claudeBinDir,
|
|
686
|
+
linuxbrewBin,
|
|
687
|
+
"/usr/local/bin",
|
|
688
|
+
"/usr/bin",
|
|
689
|
+
"/bin"
|
|
690
|
+
].filter(Boolean);
|
|
691
|
+
const execStart = [custieBin, ...spec.args].join(" ");
|
|
692
|
+
const unit = `[Unit]
|
|
693
|
+
Description=${spec.description}
|
|
694
|
+
After=network-online.target
|
|
695
|
+
Wants=network-online.target
|
|
696
|
+
|
|
697
|
+
[Service]
|
|
698
|
+
Type=simple
|
|
699
|
+
ExecStart=${execStart}
|
|
700
|
+
Restart=on-failure
|
|
701
|
+
RestartSec=5
|
|
702
|
+
${envLines}
|
|
703
|
+
Environment="PATH=${pathParts.join(":")}"
|
|
704
|
+
|
|
705
|
+
StandardOutput=append:${join3(logDir, `${spec.logName}.log`)}
|
|
706
|
+
StandardError=append:${join3(logDir, `${spec.logName}-error.log`)}
|
|
707
|
+
|
|
708
|
+
[Install]
|
|
709
|
+
WantedBy=default.target
|
|
710
|
+
`;
|
|
711
|
+
const unitName = spec.unitName;
|
|
712
|
+
const unitDir = join3(homedir2(), ".config", "systemd", "user");
|
|
713
|
+
mkdirSync2(unitDir, { recursive: true });
|
|
714
|
+
writeFileSync3(join3(unitDir, `${unitName}.service`), unit);
|
|
715
|
+
run("systemctl --user daemon-reload", { silent: true });
|
|
716
|
+
run(`systemctl --user enable --now ${unitName}`, { silent: true });
|
|
717
|
+
success(`systemd user service installed and started (${unitName}).`);
|
|
718
|
+
console.log(` Logs: ${logDir}`);
|
|
719
|
+
console.log(` Check: systemctl --user status ${unitName}`);
|
|
720
|
+
}
|
|
721
|
+
async function installService(spec) {
|
|
722
|
+
const os = platform2();
|
|
723
|
+
if (os === "darwin") await installMacOS(spec);
|
|
724
|
+
else if (os === "linux") await installLinux(spec);
|
|
725
|
+
else {
|
|
726
|
+
warn(`Unsupported platform: ${os}. You'll need to configure autostart manually.`);
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
return true;
|
|
730
|
+
}
|
|
731
|
+
async function runInstall() {
|
|
732
|
+
ensureDirs();
|
|
733
|
+
await installService(profileSpec());
|
|
734
|
+
}
|
|
735
|
+
async function installHostService() {
|
|
736
|
+
ensureDirs();
|
|
737
|
+
return installService(hostSpec());
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// src/commands/profiles.ts
|
|
741
|
+
import { existsSync as existsSync5, readdirSync, statSync } from "fs";
|
|
742
|
+
import { execSync as execSync3 } from "child_process";
|
|
434
743
|
import { join as join4 } from "path";
|
|
744
|
+
import { platform as platform3 } from "os";
|
|
745
|
+
function listProfiles() {
|
|
746
|
+
const profiles = [];
|
|
747
|
+
if (existsSync5(paths.PROFILES_DIR)) {
|
|
748
|
+
for (const entry of readdirSync(paths.PROFILES_DIR)) {
|
|
749
|
+
try {
|
|
750
|
+
if (statSync(join4(paths.PROFILES_DIR, entry)).isDirectory()) {
|
|
751
|
+
profiles.push(entry);
|
|
752
|
+
}
|
|
753
|
+
} catch {
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
return profiles;
|
|
758
|
+
}
|
|
759
|
+
function isServiceRunning(profile) {
|
|
760
|
+
try {
|
|
761
|
+
if (platform3() === "darwin") {
|
|
762
|
+
const out = execSync3("launchctl list", { encoding: "utf-8", stdio: "pipe" });
|
|
763
|
+
return out.split("\n").some((line) => line.endsWith(` ${serviceLabel(profile)}`));
|
|
764
|
+
}
|
|
765
|
+
if (platform3() === "linux") {
|
|
766
|
+
execSync3(`systemctl --user is-active ${systemdUnitName(profile)}`, { stdio: "pipe" });
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
} catch {
|
|
770
|
+
}
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
async function runProfiles() {
|
|
774
|
+
const profiles = listProfiles();
|
|
775
|
+
console.log("\n Profiles:\n");
|
|
776
|
+
for (const profile of profiles) {
|
|
777
|
+
const status = isServiceRunning(profile) ? "\x1B[32mrunning\x1B[0m" : "\x1B[90mstopped\x1B[0m";
|
|
778
|
+
console.log(` ${profile.padEnd(20)} ${status}`);
|
|
779
|
+
}
|
|
780
|
+
console.log("\n Target a profile with --profile <name> on any command.\n");
|
|
781
|
+
}
|
|
435
782
|
|
|
436
783
|
// src/auth/device-auth.ts
|
|
437
784
|
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign } from "crypto";
|
|
438
|
-
import { existsSync as
|
|
439
|
-
import { dirname as
|
|
785
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
786
|
+
import { dirname as dirname3, join as join5 } from "path";
|
|
440
787
|
var DEFAULT_API_URL = "https://app.flyspace.ai";
|
|
441
788
|
var EXPIRY_SKEW_MS = 6e4;
|
|
442
789
|
var AuthApiError = class extends Error {
|
|
@@ -450,36 +797,36 @@ function apiUrl() {
|
|
|
450
797
|
return (process.env["FLYSPACE_API_URL"] || DEFAULT_API_URL).replace(/\/+$/, "");
|
|
451
798
|
}
|
|
452
799
|
function deviceKeyPath() {
|
|
453
|
-
return
|
|
800
|
+
return join5(paths.BASE_CONFIG_DIR, "device.key");
|
|
454
801
|
}
|
|
455
802
|
function credentialsPath() {
|
|
456
|
-
return
|
|
803
|
+
return join5(paths.BASE_CONFIG_DIR, "credentials.json");
|
|
457
804
|
}
|
|
458
805
|
function ensureDeviceKey(path = deviceKeyPath()) {
|
|
459
806
|
let created = false;
|
|
460
|
-
if (!
|
|
807
|
+
if (!existsSync6(path)) {
|
|
461
808
|
const { privateKey: privateKey2 } = generateKeyPairSync("ed25519");
|
|
462
|
-
|
|
463
|
-
|
|
809
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
810
|
+
writeFileSync4(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
|
|
464
811
|
created = true;
|
|
465
812
|
}
|
|
466
|
-
const privateKey = createPrivateKey(
|
|
813
|
+
const privateKey = createPrivateKey(readFileSync4(path, "utf8"));
|
|
467
814
|
const publicKeyBase64 = createPublicKey(privateKey).export({ type: "spki", format: "der" }).toString(
|
|
468
815
|
"base64"
|
|
469
816
|
);
|
|
470
817
|
return { privateKey, publicKeyBase64, created };
|
|
471
818
|
}
|
|
472
819
|
function loadCredentials(path = credentialsPath()) {
|
|
473
|
-
if (!
|
|
820
|
+
if (!existsSync6(path)) return null;
|
|
474
821
|
try {
|
|
475
|
-
return JSON.parse(
|
|
822
|
+
return JSON.parse(readFileSync4(path, "utf8"));
|
|
476
823
|
} catch {
|
|
477
824
|
return null;
|
|
478
825
|
}
|
|
479
826
|
}
|
|
480
827
|
function saveCredentials(creds, path = credentialsPath()) {
|
|
481
|
-
|
|
482
|
-
|
|
828
|
+
mkdirSync3(dirname3(path), { recursive: true });
|
|
829
|
+
writeFileSync4(path, `${JSON.stringify(creds, null, 2)}
|
|
483
830
|
`, { mode: 384 });
|
|
484
831
|
}
|
|
485
832
|
function clearLocalAuth() {
|
|
@@ -603,20 +950,20 @@ async function getAccessToken() {
|
|
|
603
950
|
// src/dashboard/onboarding-scan.ts
|
|
604
951
|
import { execFileSync } from "child_process";
|
|
605
952
|
import {
|
|
606
|
-
existsSync as
|
|
953
|
+
existsSync as existsSync7,
|
|
607
954
|
lstatSync,
|
|
608
|
-
mkdirSync as
|
|
609
|
-
readdirSync,
|
|
610
|
-
readFileSync as
|
|
611
|
-
statSync,
|
|
955
|
+
mkdirSync as mkdirSync4,
|
|
956
|
+
readdirSync as readdirSync2,
|
|
957
|
+
readFileSync as readFileSync5,
|
|
958
|
+
statSync as statSync2,
|
|
612
959
|
symlinkSync
|
|
613
960
|
} from "fs";
|
|
614
|
-
import { homedir } from "os";
|
|
615
|
-
import { basename, join as
|
|
961
|
+
import { homedir as homedir3 } from "os";
|
|
962
|
+
import { basename, join as join6, resolve as resolve4 } from "path";
|
|
616
963
|
var MAX_JSONL_HEAD_BYTES = 64 * 1024;
|
|
617
964
|
var GIT_SCAN_ROOTS = ["Workspaces", "Projects", "code"];
|
|
618
965
|
function flyspaceRoot() {
|
|
619
|
-
return process.env["FLYSPACE_ROOT"] ||
|
|
966
|
+
return process.env["FLYSPACE_ROOT"] || join6(homedir3(), "Flyspace");
|
|
620
967
|
}
|
|
621
968
|
function onPath(cmd) {
|
|
622
969
|
try {
|
|
@@ -627,16 +974,16 @@ function onPath(cmd) {
|
|
|
627
974
|
}
|
|
628
975
|
}
|
|
629
976
|
function claudeDir() {
|
|
630
|
-
return process.env["CLAUDE_CONFIG_DIR"] ||
|
|
977
|
+
return process.env["CLAUDE_CONFIG_DIR"] || join6(homedir3(), ".claude");
|
|
631
978
|
}
|
|
632
979
|
function codexDir() {
|
|
633
|
-
return process.env["CODEX_HOME"] ||
|
|
980
|
+
return process.env["CODEX_HOME"] || join6(homedir3(), ".codex");
|
|
634
981
|
}
|
|
635
982
|
function claudeLoggedIn() {
|
|
636
983
|
if (process.env["ANTHROPIC_API_KEY"]) return true;
|
|
637
|
-
if (
|
|
984
|
+
if (existsSync7(join6(claudeDir(), ".credentials.json"))) return true;
|
|
638
985
|
try {
|
|
639
|
-
const raw = JSON.parse(
|
|
986
|
+
const raw = JSON.parse(readFileSync5(join6(homedir3(), ".claude.json"), "utf-8"));
|
|
640
987
|
if (raw.oauthAccount) return true;
|
|
641
988
|
} catch {
|
|
642
989
|
}
|
|
@@ -651,23 +998,23 @@ function claudeLoggedIn() {
|
|
|
651
998
|
}
|
|
652
999
|
}
|
|
653
1000
|
function codexLoggedIn() {
|
|
654
|
-
return !!process.env["OPENAI_API_KEY"] ||
|
|
1001
|
+
return !!process.env["OPENAI_API_KEY"] || existsSync7(join6(codexDir(), "auth.json"));
|
|
655
1002
|
}
|
|
656
1003
|
function jsonlFiles(dir, depth) {
|
|
657
1004
|
const out = [];
|
|
658
1005
|
let entries2;
|
|
659
1006
|
try {
|
|
660
|
-
entries2 =
|
|
1007
|
+
entries2 = readdirSync2(dir, { withFileTypes: true });
|
|
661
1008
|
} catch {
|
|
662
1009
|
return out;
|
|
663
1010
|
}
|
|
664
1011
|
for (const e of entries2) {
|
|
665
|
-
const full =
|
|
1012
|
+
const full = join6(dir, e.name);
|
|
666
1013
|
if (e.isDirectory()) {
|
|
667
1014
|
if (depth > 0) out.push(...jsonlFiles(full, depth - 1));
|
|
668
1015
|
} else if (e.name.endsWith(".jsonl")) {
|
|
669
1016
|
try {
|
|
670
|
-
out.push({ file: full, mtime:
|
|
1017
|
+
out.push({ file: full, mtime: statSync2(full).mtimeMs });
|
|
671
1018
|
} catch {
|
|
672
1019
|
}
|
|
673
1020
|
}
|
|
@@ -677,7 +1024,7 @@ function jsonlFiles(dir, depth) {
|
|
|
677
1024
|
function cwdFromTranscript(file) {
|
|
678
1025
|
let head;
|
|
679
1026
|
try {
|
|
680
|
-
head =
|
|
1027
|
+
head = readFileSync5(file, { encoding: "utf-8", flag: "r" }).slice(0, MAX_JSONL_HEAD_BYTES);
|
|
681
1028
|
} catch {
|
|
682
1029
|
return null;
|
|
683
1030
|
}
|
|
@@ -694,38 +1041,38 @@ function cwdFromTranscript(file) {
|
|
|
694
1041
|
}
|
|
695
1042
|
function codexTrustedPaths() {
|
|
696
1043
|
try {
|
|
697
|
-
const toml =
|
|
1044
|
+
const toml = readFileSync5(join6(codexDir(), "config.toml"), "utf-8");
|
|
698
1045
|
return [...toml.matchAll(/^\[projects\.(?:"([^"]+)"|'([^']+)')\]/gm)].map((m) => m[1] ?? m[2]);
|
|
699
1046
|
} catch {
|
|
700
1047
|
return [];
|
|
701
1048
|
}
|
|
702
1049
|
}
|
|
703
1050
|
function isExcluded(path) {
|
|
704
|
-
const home =
|
|
1051
|
+
const home = homedir3();
|
|
705
1052
|
if (path === home || path === "/") return true;
|
|
706
1053
|
if (path === "/tmp" || path.startsWith("/tmp/")) return true;
|
|
707
1054
|
if (path === "/private/tmp" || path.startsWith("/private/tmp/")) return true;
|
|
708
1055
|
const root = flyspaceRoot();
|
|
709
1056
|
if (path === root || path.startsWith(`${root}/`)) return true;
|
|
710
1057
|
try {
|
|
711
|
-
return !
|
|
1058
|
+
return !statSync2(path).isDirectory();
|
|
712
1059
|
} catch {
|
|
713
1060
|
return true;
|
|
714
1061
|
}
|
|
715
1062
|
}
|
|
716
1063
|
function gitRepos() {
|
|
717
|
-
const home =
|
|
1064
|
+
const home = homedir3();
|
|
718
1065
|
const found = [];
|
|
719
1066
|
for (const rootName of GIT_SCAN_ROOTS) {
|
|
720
|
-
const root =
|
|
1067
|
+
const root = join6(home, rootName);
|
|
721
1068
|
let entries2;
|
|
722
1069
|
try {
|
|
723
|
-
entries2 =
|
|
1070
|
+
entries2 = readdirSync2(root, { withFileTypes: true });
|
|
724
1071
|
} catch {
|
|
725
1072
|
continue;
|
|
726
1073
|
}
|
|
727
1074
|
for (const e of entries2) {
|
|
728
|
-
if (e.isDirectory() &&
|
|
1075
|
+
if (e.isDirectory() && existsSync7(join6(root, e.name, ".git"))) found.push(join6(root, e.name));
|
|
729
1076
|
}
|
|
730
1077
|
}
|
|
731
1078
|
return found;
|
|
@@ -750,7 +1097,7 @@ function scanOnboarding() {
|
|
|
750
1097
|
const byPath = /* @__PURE__ */ new Map();
|
|
751
1098
|
const history = { claude: false, codex: false };
|
|
752
1099
|
const add = (rawPath, source, mtime, sessions, trusted = false) => {
|
|
753
|
-
const path =
|
|
1100
|
+
const path = resolve4(rawPath);
|
|
754
1101
|
if (isExcluded(path)) return;
|
|
755
1102
|
const existing = byPath.get(path);
|
|
756
1103
|
if (existing) {
|
|
@@ -769,11 +1116,11 @@ function scanOnboarding() {
|
|
|
769
1116
|
});
|
|
770
1117
|
}
|
|
771
1118
|
};
|
|
772
|
-
const claudeProjects =
|
|
1119
|
+
const claudeProjects = join6(claudeDir(), "projects");
|
|
773
1120
|
try {
|
|
774
|
-
for (const e of
|
|
1121
|
+
for (const e of readdirSync2(claudeProjects, { withFileTypes: true })) {
|
|
775
1122
|
if (!e.isDirectory()) continue;
|
|
776
|
-
const files = jsonlFiles(
|
|
1123
|
+
const files = jsonlFiles(join6(claudeProjects, e.name), 0);
|
|
777
1124
|
if (!files.length) continue;
|
|
778
1125
|
history.claude = true;
|
|
779
1126
|
files.sort((a, b) => b.mtime - a.mtime);
|
|
@@ -786,7 +1133,7 @@ function scanOnboarding() {
|
|
|
786
1133
|
}
|
|
787
1134
|
} catch {
|
|
788
1135
|
}
|
|
789
|
-
const codexSessions = jsonlFiles(
|
|
1136
|
+
const codexSessions = jsonlFiles(join6(codexDir(), "sessions"), 4);
|
|
790
1137
|
if (codexSessions.length) history.codex = true;
|
|
791
1138
|
const perCwd = /* @__PURE__ */ new Map();
|
|
792
1139
|
for (const f of codexSessions) {
|
|
@@ -806,7 +1153,7 @@ function scanOnboarding() {
|
|
|
806
1153
|
for (const repo of gitRepos()) {
|
|
807
1154
|
let mtime = 0;
|
|
808
1155
|
try {
|
|
809
|
-
mtime =
|
|
1156
|
+
mtime = statSync2(repo).mtimeMs;
|
|
810
1157
|
} catch {
|
|
811
1158
|
}
|
|
812
1159
|
add(repo, "git", mtime, 0);
|
|
@@ -828,12 +1175,12 @@ function makeLink(target, linkPath) {
|
|
|
828
1175
|
}
|
|
829
1176
|
function linkProjectFolders(slug, items) {
|
|
830
1177
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) throw new Error(`invalid workspace slug: ${slug}`);
|
|
831
|
-
const dir =
|
|
832
|
-
|
|
1178
|
+
const dir = join6(flyspaceRoot(), slug);
|
|
1179
|
+
mkdirSync4(dir, { recursive: true });
|
|
833
1180
|
const results = [];
|
|
834
1181
|
for (const item of items) {
|
|
835
1182
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(item.name)) throw new Error(`invalid channel name: ${item.name}`);
|
|
836
|
-
const dest =
|
|
1183
|
+
const dest = join6(dir, item.name);
|
|
837
1184
|
let exists = true;
|
|
838
1185
|
try {
|
|
839
1186
|
lstatSync(dest);
|
|
@@ -842,11 +1189,11 @@ function linkProjectFolders(slug, items) {
|
|
|
842
1189
|
}
|
|
843
1190
|
if (!exists) {
|
|
844
1191
|
if (item.path) {
|
|
845
|
-
const target =
|
|
846
|
-
if (!
|
|
1192
|
+
const target = resolve4(item.path);
|
|
1193
|
+
if (!statSync2(target).isDirectory()) throw new Error(`not a directory: ${target}`);
|
|
847
1194
|
makeLink(target, dest);
|
|
848
1195
|
} else {
|
|
849
|
-
|
|
1196
|
+
mkdirSync4(dest, { recursive: true });
|
|
850
1197
|
}
|
|
851
1198
|
}
|
|
852
1199
|
results.push({ name: item.name, cwd: `${slug}/${item.name}` });
|
|
@@ -857,7 +1204,7 @@ function linkProjectFolders(slug, items) {
|
|
|
857
1204
|
// src/commands/auth.ts
|
|
858
1205
|
import { spawn as spawn3 } from "child_process";
|
|
859
1206
|
import { confirm } from "@inquirer/prompts";
|
|
860
|
-
import { hostname, platform, release } from "os";
|
|
1207
|
+
import { hostname, platform as platform4, release } from "os";
|
|
861
1208
|
var BOLD = "\x1B[1m";
|
|
862
1209
|
var DIM = "\x1B[2m";
|
|
863
1210
|
var GREEN = "\x1B[32m";
|
|
@@ -891,8 +1238,8 @@ function defaultDeviceName() {
|
|
|
891
1238
|
return hostname().replace(/\.local$/, "");
|
|
892
1239
|
}
|
|
893
1240
|
function platformLabel() {
|
|
894
|
-
if (
|
|
895
|
-
return `${
|
|
1241
|
+
if (platform4() === "darwin") return `macOS (Darwin ${release()})`;
|
|
1242
|
+
return `${platform4()} ${release()}`;
|
|
896
1243
|
}
|
|
897
1244
|
function formatCountdown(ms) {
|
|
898
1245
|
const s = Math.max(0, Math.round(ms / 1e3));
|
|
@@ -900,7 +1247,7 @@ function formatCountdown(ms) {
|
|
|
900
1247
|
}
|
|
901
1248
|
function openBrowser(url) {
|
|
902
1249
|
if (process.env["SSH_CONNECTION"] || process.env["SSH_TTY"]) return false;
|
|
903
|
-
const cmd =
|
|
1250
|
+
const cmd = platform4() === "darwin" ? "open" : platform4() === "win32" ? "explorer" : "xdg-open";
|
|
904
1251
|
try {
|
|
905
1252
|
const child = spawn3(cmd, [url], { stdio: "ignore", detached: true });
|
|
906
1253
|
child.on("error", () => {
|
|
@@ -1178,6 +1525,11 @@ var HELP2 = `
|
|
|
1178
1525
|
Options:
|
|
1179
1526
|
--name <name> Device name shown in the Workspace (default: hostname)
|
|
1180
1527
|
--no-browser Only print the approval URL, don't open a browser
|
|
1528
|
+
--yes Keep the current login without asking
|
|
1529
|
+
--start-host Run the agent Host in the background (installs it as a
|
|
1530
|
+
service the first time)
|
|
1531
|
+
--restart With --start-host: restart the Host and this machine's
|
|
1532
|
+
workspace services so they pick up a new CLI version
|
|
1181
1533
|
|
|
1182
1534
|
To switch accounts, run: flyspace auth login --force
|
|
1183
1535
|
`;
|
|
@@ -1199,19 +1551,50 @@ async function servedWorkspaces() {
|
|
|
1199
1551
|
return creds.workspaces ?? [];
|
|
1200
1552
|
}
|
|
1201
1553
|
}
|
|
1554
|
+
function loginArgs(args) {
|
|
1555
|
+
const out = [];
|
|
1556
|
+
for (let i = 0; i < args.length; i++) {
|
|
1557
|
+
const a = args[i];
|
|
1558
|
+
if (a === "--name") out.push(a, args[++i] ?? "");
|
|
1559
|
+
else if (a.startsWith("--name=") || a === "--no-browser") out.push(a);
|
|
1560
|
+
}
|
|
1561
|
+
return out;
|
|
1562
|
+
}
|
|
1563
|
+
function restartHostServices(workspaces) {
|
|
1564
|
+
if (restartHostService()) ok("Host restarted on the new version");
|
|
1565
|
+
else bad("Could not restart the Host service run: flyspace setup --start-host --restart");
|
|
1566
|
+
for (const slug of workspaces) {
|
|
1567
|
+
if (!isServiceInstalled(slug)) continue;
|
|
1568
|
+
if (restartService(slug)) ok(`${slug} scheduler restarted`);
|
|
1569
|
+
else bad(`${slug} scheduler did not restart run: flyspace restart --profile ${slug}`);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1202
1572
|
async function runSetup(args) {
|
|
1203
1573
|
if (args.includes("-h") || args.includes("--help")) return void console.log(HELP2);
|
|
1204
1574
|
console.log(`
|
|
1205
1575
|
${BOLD2}Flyspace Setup${RESET2}
|
|
1206
1576
|
`);
|
|
1207
1577
|
const existing = loadCredentials();
|
|
1578
|
+
let workspaces;
|
|
1208
1579
|
if (existing) {
|
|
1209
|
-
|
|
1580
|
+
workspaces = await servedWorkspaces();
|
|
1581
|
+
console.log(` This machine is already a Host:`);
|
|
1582
|
+
console.log(` Account ${existing.userEmail}`);
|
|
1583
|
+
console.log(` Device ${existing.deviceName}`);
|
|
1584
|
+
console.log(` Workspaces ${workspaces.length ? workspaces.join(", ") : "(none)"}`);
|
|
1585
|
+
const others = listProfiles().filter((p) => p !== "default" && !workspaces.includes(p));
|
|
1586
|
+
if (others.length) console.log(` ${DIM2}Other local profiles (left alone): ${others.join(", ")}${RESET2}`);
|
|
1587
|
+
console.log("");
|
|
1588
|
+
const keep = args.includes("--yes") || !process.stdin.isTTY ? true : await confirm2({ message: "Keep using this account and device?", default: true });
|
|
1589
|
+
if (!keep) {
|
|
1590
|
+
await login([...loginArgs(args), "--force"]);
|
|
1591
|
+
workspaces = await servedWorkspaces();
|
|
1592
|
+
}
|
|
1210
1593
|
} else {
|
|
1211
|
-
await login(args);
|
|
1594
|
+
await login(loginArgs(args));
|
|
1595
|
+
workspaces = await servedWorkspaces();
|
|
1212
1596
|
}
|
|
1213
|
-
|
|
1214
|
-
if (workspaces.length) ok(`Serving ${workspaces.join(", ")}`);
|
|
1597
|
+
if (workspaces.length) ok(`Registered as the Host for ${workspaces.join(", ")}`);
|
|
1215
1598
|
else bad(`Not serving any workspace. Only workspace admins can add a Host.`);
|
|
1216
1599
|
const runners = detectRunners();
|
|
1217
1600
|
for (const r of runners) {
|
|
@@ -1222,274 +1605,41 @@ ${BOLD2}Flyspace Setup${RESET2}
|
|
|
1222
1605
|
const anyReady = runners.some((r) => r.installed && r.loggedIn);
|
|
1223
1606
|
for (const slug of workspaces) {
|
|
1224
1607
|
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) continue;
|
|
1225
|
-
const dir =
|
|
1226
|
-
|
|
1608
|
+
const dir = join7(flyspaceRoot(), slug);
|
|
1609
|
+
mkdirSync5(dir, { recursive: true });
|
|
1227
1610
|
ok(`Folder ${tilde2(dir)}`);
|
|
1228
1611
|
}
|
|
1612
|
+
let hostRunning = false;
|
|
1613
|
+
if (args.includes("--start-host") && workspaces.length) {
|
|
1614
|
+
if (!isHostServiceInstalled()) {
|
|
1615
|
+
hostRunning = await installHostService();
|
|
1616
|
+
if (hostRunning) ok(`Host running in the background ${DIM2}logs: ${tilde2(paths.LOG_DIR)}/host.log${RESET2}`);
|
|
1617
|
+
} else {
|
|
1618
|
+
hostRunning = true;
|
|
1619
|
+
if (args.includes("--restart")) restartHostServices(workspaces);
|
|
1620
|
+
else ok("Host service already installed");
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1229
1623
|
console.log("\n Next:");
|
|
1230
1624
|
if (!anyReady) {
|
|
1231
1625
|
console.log(` Install and log in to ${BOLD2}claude${RESET2} or ${BOLD2}codex${RESET2} (above) so agents can run here.`);
|
|
1232
1626
|
}
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
}
|
|
1237
|
-
|
|
1238
|
-
// src/commands/install.ts
|
|
1239
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync4, unlinkSync } from "fs";
|
|
1240
|
-
import { execSync as execSync2 } from "child_process";
|
|
1241
|
-
import { dirname as dirname3, join as join6, resolve as resolve4 } from "path";
|
|
1242
|
-
import { homedir as homedir3, platform as platform3 } from "os";
|
|
1243
|
-
|
|
1244
|
-
// src/service.ts
|
|
1245
|
-
import { execSync } from "child_process";
|
|
1246
|
-
import { existsSync as existsSync5 } from "fs";
|
|
1247
|
-
import { join as join5 } from "path";
|
|
1248
|
-
import { homedir as homedir2, platform as platform2 } from "os";
|
|
1249
|
-
function serviceLabel(profile) {
|
|
1250
|
-
return profile === DEFAULT_PROFILE ? "ai.flyspace" : `ai.flyspace.${profile}`;
|
|
1251
|
-
}
|
|
1252
|
-
function plistName(profile) {
|
|
1253
|
-
return `${serviceLabel(profile)}.plist`;
|
|
1254
|
-
}
|
|
1255
|
-
function plistPath(profile) {
|
|
1256
|
-
return join5(homedir2(), "Library", "LaunchAgents", plistName(profile));
|
|
1257
|
-
}
|
|
1258
|
-
function legacyServiceLabel(profile) {
|
|
1259
|
-
return profile === DEFAULT_PROFILE ? "io.flycoder.flyspace" : `io.flycoder.flyspace.${profile}`;
|
|
1260
|
-
}
|
|
1261
|
-
function legacyPlistPath(profile) {
|
|
1262
|
-
return join5(homedir2(), "Library", "LaunchAgents", `${legacyServiceLabel(profile)}.plist`);
|
|
1263
|
-
}
|
|
1264
|
-
function systemdUnitName(profile) {
|
|
1265
|
-
return profile === DEFAULT_PROFILE ? "flyspace" : `flyspace-${profile}`;
|
|
1266
|
-
}
|
|
1267
|
-
function systemdUnitPath(profile) {
|
|
1268
|
-
return join5(homedir2(), ".config", "systemd", "user", `${systemdUnitName(profile)}.service`);
|
|
1269
|
-
}
|
|
1270
|
-
function isServiceInstalled(profile) {
|
|
1271
|
-
if (platform2() === "darwin") return existsSync5(plistPath(profile));
|
|
1272
|
-
if (platform2() === "linux") return existsSync5(systemdUnitPath(profile));
|
|
1273
|
-
return false;
|
|
1274
|
-
}
|
|
1275
|
-
function restartService(profile) {
|
|
1276
|
-
const os = platform2();
|
|
1277
|
-
if (os === "darwin") {
|
|
1278
|
-
const plist = plistPath(profile);
|
|
1279
|
-
try {
|
|
1280
|
-
execSync(`launchctl unload "${plist}"`, { stdio: "pipe" });
|
|
1281
|
-
} catch {
|
|
1282
|
-
}
|
|
1283
|
-
try {
|
|
1284
|
-
execSync(`launchctl load "${plist}"`, { stdio: "pipe" });
|
|
1285
|
-
return true;
|
|
1286
|
-
} catch {
|
|
1287
|
-
return false;
|
|
1288
|
-
}
|
|
1289
|
-
}
|
|
1290
|
-
if (os === "linux") {
|
|
1291
|
-
try {
|
|
1292
|
-
execSync(`systemctl --user restart ${systemdUnitName(profile)}`, { stdio: "pipe" });
|
|
1293
|
-
return true;
|
|
1294
|
-
} catch {
|
|
1295
|
-
return false;
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
return false;
|
|
1299
|
-
}
|
|
1300
|
-
|
|
1301
|
-
// src/commands/install.ts
|
|
1302
|
-
function log(msg) {
|
|
1303
|
-
console.log(`
|
|
1304
|
-
\x1B[36m>\x1B[0m ${msg}`);
|
|
1305
|
-
}
|
|
1306
|
-
function success(msg) {
|
|
1307
|
-
console.log(`\x1B[32m+\x1B[0m ${msg}`);
|
|
1308
|
-
}
|
|
1309
|
-
function warn(msg) {
|
|
1310
|
-
console.log(`\x1B[33m!\x1B[0m ${msg}`);
|
|
1311
|
-
}
|
|
1312
|
-
function run(cmd, opts = {}) {
|
|
1313
|
-
return execSync2(cmd, {
|
|
1314
|
-
encoding: "utf-8",
|
|
1315
|
-
stdio: opts.silent ? "pipe" : "inherit"
|
|
1316
|
-
}).toString().trim();
|
|
1317
|
-
}
|
|
1318
|
-
function detectClaudeBinDir() {
|
|
1319
|
-
try {
|
|
1320
|
-
const claudePath = execSync2("which claude", { encoding: "utf-8", stdio: "pipe" }).trim();
|
|
1321
|
-
return claudePath ? dirname3(claudePath) : void 0;
|
|
1322
|
-
} catch {
|
|
1323
|
-
return void 0;
|
|
1324
|
-
}
|
|
1325
|
-
}
|
|
1326
|
-
function detectCustieBin() {
|
|
1327
|
-
const argv1 = process.argv[1];
|
|
1328
|
-
if (argv1) {
|
|
1329
|
-
return resolve4(argv1);
|
|
1330
|
-
}
|
|
1331
|
-
try {
|
|
1332
|
-
return run("which flyspace", { silent: true });
|
|
1333
|
-
} catch {
|
|
1334
|
-
return "flyspace";
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
function readEnvFile() {
|
|
1338
|
-
if (!existsSync6(paths.CONFIG_FILE)) return {};
|
|
1339
|
-
const vars = {};
|
|
1340
|
-
for (const line of readFileSync5(paths.CONFIG_FILE, "utf-8").split("\n")) {
|
|
1341
|
-
const trimmed = line.trim();
|
|
1342
|
-
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
1343
|
-
const eq = trimmed.indexOf("=");
|
|
1344
|
-
if (eq === -1) continue;
|
|
1345
|
-
const key = trimmed.slice(0, eq).trim();
|
|
1346
|
-
const val = trimmed.slice(eq + 1).trim();
|
|
1347
|
-
if (val) vars[key] = val;
|
|
1348
|
-
}
|
|
1349
|
-
return vars;
|
|
1350
|
-
}
|
|
1351
|
-
function escapeXml(str) {
|
|
1352
|
-
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
1353
|
-
}
|
|
1354
|
-
async function installMacOS() {
|
|
1355
|
-
log("Setting up macOS LaunchAgent...");
|
|
1356
|
-
const profile = getProfile();
|
|
1357
|
-
const custieBin = detectCustieBin();
|
|
1358
|
-
const nodePath = process.execPath;
|
|
1359
|
-
const claudeBinDir = detectClaudeBinDir();
|
|
1360
|
-
const logDir = paths.LOG_DIR;
|
|
1361
|
-
mkdirSync5(logDir, { recursive: true });
|
|
1362
|
-
const envVars = readEnvFile();
|
|
1363
|
-
const homebrewBin = existsSync6("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
|
|
1364
|
-
const asdfShims = join6(homedir3(), ".asdf", "shims");
|
|
1365
|
-
const asdfBin = existsSync6(asdfShims) ? asdfShims : void 0;
|
|
1366
|
-
const pathParts = [
|
|
1367
|
-
dirname3(nodePath),
|
|
1368
|
-
claudeBinDir,
|
|
1369
|
-
asdfBin,
|
|
1370
|
-
homebrewBin,
|
|
1371
|
-
"/usr/local/bin",
|
|
1372
|
-
"/usr/bin",
|
|
1373
|
-
"/bin"
|
|
1374
|
-
].filter(Boolean);
|
|
1375
|
-
const programArgs = profile === "default" ? ["start"] : ["start", "--profile", profile];
|
|
1376
|
-
const programArgsXml = [custieBin, ...programArgs].map((a) => ` <string>${escapeXml(a)}</string>`).join("\n");
|
|
1377
|
-
const plist = `<?xml version="1.0" encoding="UTF-8"?>
|
|
1378
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
|
1379
|
-
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
1380
|
-
<plist version="1.0">
|
|
1381
|
-
<dict>
|
|
1382
|
-
<key>Label</key>
|
|
1383
|
-
<string>${serviceLabel(profile)}</string>
|
|
1384
|
-
|
|
1385
|
-
<key>ProgramArguments</key>
|
|
1386
|
-
<array>
|
|
1387
|
-
${programArgsXml}
|
|
1388
|
-
</array>
|
|
1389
|
-
|
|
1390
|
-
<key>EnvironmentVariables</key>
|
|
1391
|
-
<dict>
|
|
1392
|
-
<key>PATH</key>
|
|
1393
|
-
<string>${pathParts.join(":")}</string>
|
|
1394
|
-
${Object.entries(envVars).map(([k, v]) => ` <key>${k}</key>
|
|
1395
|
-
<string>${escapeXml(v)}</string>`).join("\n")}
|
|
1396
|
-
</dict>
|
|
1397
|
-
|
|
1398
|
-
<key>RunAtLoad</key>
|
|
1399
|
-
<true/>
|
|
1400
|
-
|
|
1401
|
-
<key>KeepAlive</key>
|
|
1402
|
-
<true/>
|
|
1403
|
-
|
|
1404
|
-
<key>StandardOutPath</key>
|
|
1405
|
-
<string>${join6(logDir, "custie.log")}</string>
|
|
1406
|
-
|
|
1407
|
-
<key>StandardErrorPath</key>
|
|
1408
|
-
<string>${join6(logDir, "custie-error.log")}</string>
|
|
1409
|
-
</dict>
|
|
1410
|
-
</plist>`;
|
|
1411
|
-
const agentPlistPath = plistPath(profile);
|
|
1412
|
-
mkdirSync5(dirname3(agentPlistPath), { recursive: true });
|
|
1413
|
-
try {
|
|
1414
|
-
run(`launchctl unload "${agentPlistPath}"`, { silent: true });
|
|
1415
|
-
} catch {
|
|
1416
|
-
}
|
|
1417
|
-
try {
|
|
1418
|
-
unlinkSync(agentPlistPath);
|
|
1419
|
-
} catch {
|
|
1420
|
-
}
|
|
1421
|
-
writeFileSync4(agentPlistPath, plist);
|
|
1422
|
-
run(`launchctl load "${agentPlistPath}"`, { silent: true });
|
|
1423
|
-
success(`LaunchAgent installed and loaded (profile: ${profile}).`);
|
|
1424
|
-
console.log(` Logs: ${logDir}`);
|
|
1425
|
-
console.log(` Check: launchctl list | grep flyspace`);
|
|
1426
|
-
}
|
|
1427
|
-
async function installLinux() {
|
|
1428
|
-
log("Setting up systemd user service...");
|
|
1429
|
-
const profile = getProfile();
|
|
1430
|
-
const custieBin = detectCustieBin();
|
|
1431
|
-
const nodePath = process.execPath;
|
|
1432
|
-
const claudeBinDir = detectClaudeBinDir();
|
|
1433
|
-
const logDir = paths.LOG_DIR;
|
|
1434
|
-
mkdirSync5(logDir, { recursive: true });
|
|
1435
|
-
const envVars = readEnvFile();
|
|
1436
|
-
const envLines = Object.entries(envVars).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
1437
|
-
const linuxbrewBin = existsSync6("/home/linuxbrew/.linuxbrew/bin") ? "/home/linuxbrew/.linuxbrew/bin" : void 0;
|
|
1438
|
-
const pathParts = [
|
|
1439
|
-
dirname3(nodePath),
|
|
1440
|
-
claudeBinDir,
|
|
1441
|
-
linuxbrewBin,
|
|
1442
|
-
"/usr/local/bin",
|
|
1443
|
-
"/usr/bin",
|
|
1444
|
-
"/bin"
|
|
1445
|
-
].filter(Boolean);
|
|
1446
|
-
const execStart = profile === "default" ? `${custieBin} start` : `${custieBin} start --profile ${profile}`;
|
|
1447
|
-
const unit = `[Unit]
|
|
1448
|
-
Description=Flyspace agent host${profile === "default" ? "" : ` (${profile})`}
|
|
1449
|
-
After=network-online.target
|
|
1450
|
-
Wants=network-online.target
|
|
1451
|
-
|
|
1452
|
-
[Service]
|
|
1453
|
-
Type=simple
|
|
1454
|
-
ExecStart=${execStart}
|
|
1455
|
-
Restart=on-failure
|
|
1456
|
-
RestartSec=5
|
|
1457
|
-
${envLines}
|
|
1458
|
-
Environment="PATH=${pathParts.join(":")}"
|
|
1459
|
-
|
|
1460
|
-
StandardOutput=append:${join6(logDir, "custie.log")}
|
|
1461
|
-
StandardError=append:${join6(logDir, "custie-error.log")}
|
|
1462
|
-
|
|
1463
|
-
[Install]
|
|
1464
|
-
WantedBy=default.target
|
|
1465
|
-
`;
|
|
1466
|
-
const unitName = systemdUnitName(profile);
|
|
1467
|
-
const unitDir = join6(homedir3(), ".config", "systemd", "user");
|
|
1468
|
-
mkdirSync5(unitDir, { recursive: true });
|
|
1469
|
-
writeFileSync4(join6(unitDir, `${unitName}.service`), unit);
|
|
1470
|
-
run("systemctl --user daemon-reload", { silent: true });
|
|
1471
|
-
run(`systemctl --user enable --now ${unitName}`, { silent: true });
|
|
1472
|
-
success(`systemd user service installed and started (profile: ${profile}).`);
|
|
1473
|
-
console.log(` Logs: ${logDir}`);
|
|
1474
|
-
console.log(` Check: systemctl --user status ${unitName}`);
|
|
1475
|
-
}
|
|
1476
|
-
async function runInstall() {
|
|
1477
|
-
ensureDirs();
|
|
1478
|
-
const os = platform3();
|
|
1479
|
-
if (os === "darwin") {
|
|
1480
|
-
await installMacOS();
|
|
1481
|
-
} else if (os === "linux") {
|
|
1482
|
-
await installLinux();
|
|
1627
|
+
if (hostRunning) {
|
|
1628
|
+
console.log(" The Host keeps running after you close this terminal and starts again at login.");
|
|
1629
|
+
const stop = process.platform === "darwin" ? `launchctl bootout gui/$(id -u)/${HOST_SERVICE_LABEL}` : `systemctl --user disable --now ${HOST_SYSTEMD_UNIT}`;
|
|
1630
|
+
console.log(` To stop it: ${BOLD2}${stop}${RESET2}`);
|
|
1483
1631
|
} else {
|
|
1484
|
-
|
|
1632
|
+
console.log(` Start the Host: ${BOLD2}flyspace setup --start-host${RESET2}`);
|
|
1485
1633
|
}
|
|
1634
|
+
console.log(" Then go back to your browser to finish onboarding.\n");
|
|
1635
|
+
if (!workspaces.length) process.exitCode = 1;
|
|
1486
1636
|
}
|
|
1487
1637
|
|
|
1488
1638
|
// src/commands/uninstall.ts
|
|
1489
|
-
import { existsSync as
|
|
1490
|
-
import { execSync as
|
|
1491
|
-
import { basename as basename2, join as
|
|
1492
|
-
import { homedir as homedir4, platform as
|
|
1639
|
+
import { existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
1640
|
+
import { execSync as execSync4 } from "child_process";
|
|
1641
|
+
import { basename as basename2, join as join8 } from "path";
|
|
1642
|
+
import { homedir as homedir4, platform as platform5 } from "os";
|
|
1493
1643
|
function log2(msg) {
|
|
1494
1644
|
console.log(`
|
|
1495
1645
|
\x1B[36m>\x1B[0m ${msg}`);
|
|
@@ -1501,16 +1651,16 @@ function warn2(msg) {
|
|
|
1501
1651
|
console.log(`\x1B[33m!\x1B[0m ${msg}`);
|
|
1502
1652
|
}
|
|
1503
1653
|
function run2(cmd) {
|
|
1504
|
-
|
|
1654
|
+
execSync4(cmd, { encoding: "utf-8", stdio: "pipe" });
|
|
1505
1655
|
}
|
|
1506
1656
|
async function runUninstall() {
|
|
1507
|
-
const os =
|
|
1657
|
+
const os = platform5();
|
|
1508
1658
|
const profile = getProfile();
|
|
1509
1659
|
log2("Uninstalling Flyspace service...");
|
|
1510
1660
|
if (os === "darwin") {
|
|
1511
1661
|
const removed = [];
|
|
1512
1662
|
for (const plist of [plistPath(profile), legacyPlistPath(profile)]) {
|
|
1513
|
-
if (!
|
|
1663
|
+
if (!existsSync8(plist)) continue;
|
|
1514
1664
|
try {
|
|
1515
1665
|
run2(`launchctl unload "${plist}"`);
|
|
1516
1666
|
} catch {
|
|
@@ -1532,7 +1682,7 @@ async function runUninstall() {
|
|
|
1532
1682
|
run2(`systemctl --user disable --now ${unitName}`);
|
|
1533
1683
|
} catch {
|
|
1534
1684
|
}
|
|
1535
|
-
const unitPath =
|
|
1685
|
+
const unitPath = join8(homedir4(), ".config", "systemd", "user", `${unitName}.service`);
|
|
1536
1686
|
try {
|
|
1537
1687
|
unlinkSync2(unitPath);
|
|
1538
1688
|
} catch {
|
|
@@ -1548,53 +1698,13 @@ async function runUninstall() {
|
|
|
1548
1698
|
console.log("\n Config and data directories were NOT removed.");
|
|
1549
1699
|
}
|
|
1550
1700
|
|
|
1551
|
-
// src/commands/profiles.ts
|
|
1552
|
-
import { existsSync as existsSync8, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
1553
|
-
import { execSync as execSync4 } from "child_process";
|
|
1554
|
-
import { join as join8 } from "path";
|
|
1555
|
-
import { platform as platform5 } from "os";
|
|
1556
|
-
function listProfiles() {
|
|
1557
|
-
const profiles = [];
|
|
1558
|
-
if (existsSync8(paths.PROFILES_DIR)) {
|
|
1559
|
-
for (const entry of readdirSync2(paths.PROFILES_DIR)) {
|
|
1560
|
-
try {
|
|
1561
|
-
if (statSync2(join8(paths.PROFILES_DIR, entry)).isDirectory()) {
|
|
1562
|
-
profiles.push(entry);
|
|
1563
|
-
}
|
|
1564
|
-
} catch {
|
|
1565
|
-
}
|
|
1566
|
-
}
|
|
1567
|
-
}
|
|
1568
|
-
return profiles;
|
|
1569
|
-
}
|
|
1570
|
-
function isServiceRunning(profile) {
|
|
1571
|
-
try {
|
|
1572
|
-
if (platform5() === "darwin") {
|
|
1573
|
-
const out = execSync4("launchctl list", { encoding: "utf-8", stdio: "pipe" });
|
|
1574
|
-
return out.split("\n").some((line) => line.endsWith(` ${serviceLabel(profile)}`));
|
|
1575
|
-
}
|
|
1576
|
-
if (platform5() === "linux") {
|
|
1577
|
-
execSync4(`systemctl --user is-active ${systemdUnitName(profile)}`, { stdio: "pipe" });
|
|
1578
|
-
return true;
|
|
1579
|
-
}
|
|
1580
|
-
} catch {
|
|
1581
|
-
}
|
|
1582
|
-
return false;
|
|
1583
|
-
}
|
|
1584
|
-
async function runProfiles() {
|
|
1585
|
-
const profiles = listProfiles();
|
|
1586
|
-
console.log("\n Profiles:\n");
|
|
1587
|
-
for (const profile of profiles) {
|
|
1588
|
-
const status = isServiceRunning(profile) ? "\x1B[32mrunning\x1B[0m" : "\x1B[90mstopped\x1B[0m";
|
|
1589
|
-
console.log(` ${profile.padEnd(20)} ${status}`);
|
|
1590
|
-
}
|
|
1591
|
-
console.log("\n Target a profile with --profile <name> on any command.\n");
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
1701
|
// src/commands/upgrade.ts
|
|
1595
1702
|
import { execSync as execSync5 } from "child_process";
|
|
1596
1703
|
function restartInstalledProfiles() {
|
|
1597
|
-
|
|
1704
|
+
if (isHostServiceInstalled()) {
|
|
1705
|
+
console.log(restartHostService() ? "\nHost restarted." : "\nHost restart failed; run: flyspace setup --start-host --restart");
|
|
1706
|
+
}
|
|
1707
|
+
const installed = (loadCredentials()?.workspaces ?? listProfiles()).filter(isServiceInstalled);
|
|
1598
1708
|
if (installed.length === 0) {
|
|
1599
1709
|
console.log("\nNo installed services to restart.");
|
|
1600
1710
|
return;
|
|
@@ -1610,7 +1720,7 @@ Restarting ${installed.length} instance(s)...`);
|
|
|
1610
1720
|
}
|
|
1611
1721
|
}
|
|
1612
1722
|
async function runUpgrade() {
|
|
1613
|
-
const current = "0.1.
|
|
1723
|
+
const current = "0.1.6";
|
|
1614
1724
|
console.log(`Current version: ${current}`);
|
|
1615
1725
|
console.log("Checking for updates...\n");
|
|
1616
1726
|
try {
|
|
@@ -2253,7 +2363,7 @@ import { execFileSync as execFileSync4 } from "child_process";
|
|
|
2253
2363
|
import { existsSync as existsSync27, readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
|
|
2254
2364
|
import { resolve as resolve7 } from "path";
|
|
2255
2365
|
import { tmpdir as tmpdir3, homedir as homedir9 } from "os";
|
|
2256
|
-
import { input, confirm as
|
|
2366
|
+
import { input, confirm as confirm3, select, checkbox } from "@inquirer/prompts";
|
|
2257
2367
|
import { WebClient as WebClient3 } from "@slack/web-api";
|
|
2258
2368
|
|
|
2259
2369
|
// src/store/auth-links.ts
|
|
@@ -3420,8 +3530,8 @@ function setupPage(c, token, invite, form = {}) {
|
|
|
3420
3530
|
);
|
|
3421
3531
|
}
|
|
3422
3532
|
function createAuthConfirmRouter() {
|
|
3423
|
-
const
|
|
3424
|
-
|
|
3533
|
+
const confirm4 = new Hono();
|
|
3534
|
+
confirm4.get("/confirm", withResolvedProfile, async (c) => {
|
|
3425
3535
|
const settings = await loadWorkspaceBranding();
|
|
3426
3536
|
const display = fontOf(settings.font).stack;
|
|
3427
3537
|
const name = settings.displayName?.trim() || getProfile();
|
|
@@ -3458,7 +3568,7 @@ function createAuthConfirmRouter() {
|
|
|
3458
3568
|
)
|
|
3459
3569
|
);
|
|
3460
3570
|
});
|
|
3461
|
-
|
|
3571
|
+
confirm4.post("/confirm", withResolvedProfile, async (c) => {
|
|
3462
3572
|
const form = await c.req.parseBody();
|
|
3463
3573
|
const params = readParams({
|
|
3464
3574
|
token: typeof form["token"] === "string" ? form["token"] : void 0,
|
|
@@ -3469,7 +3579,7 @@ function createAuthConfirmRouter() {
|
|
|
3469
3579
|
if (!target) return c.text("SUPABASE_URL is not configured", 500);
|
|
3470
3580
|
return c.redirect(target, 303);
|
|
3471
3581
|
});
|
|
3472
|
-
|
|
3582
|
+
confirm4.get("/invite", withResolvedProfile, async (c) => {
|
|
3473
3583
|
const token = c.req.query("token")?.trim() ?? "";
|
|
3474
3584
|
const lookup = await findInvite(token);
|
|
3475
3585
|
const brandingWorkspace = lookup.state === "valid" ? await getWorkspaceById(lookup.invite.workspaceId) : null;
|
|
@@ -3515,7 +3625,7 @@ function createAuthConfirmRouter() {
|
|
|
3515
3625
|
)
|
|
3516
3626
|
);
|
|
3517
3627
|
});
|
|
3518
|
-
|
|
3628
|
+
confirm4.post("/invite", withResolvedProfile, async (c) => {
|
|
3519
3629
|
const form = await c.req.parseBody();
|
|
3520
3630
|
const token = typeof form["token"] === "string" ? form["token"].trim() : "";
|
|
3521
3631
|
const back = `/auth/invite?token=${encodeURIComponent(token)}`;
|
|
@@ -3532,7 +3642,7 @@ function createAuthConfirmRouter() {
|
|
|
3532
3642
|
if (!await consumeInvite(lookup.invite.id)) return c.redirect(back, 303);
|
|
3533
3643
|
return c.redirect(target, 303);
|
|
3534
3644
|
});
|
|
3535
|
-
|
|
3645
|
+
confirm4.get("/new-workspace", async (c) => {
|
|
3536
3646
|
const token = c.req.query("token")?.trim() ?? "";
|
|
3537
3647
|
const lookup = INVITE_TOKEN.test(token) ? await lookupSetupInvite(token) : { state: "unknown" };
|
|
3538
3648
|
if (lookup.state === "valid") return c.html(setupPage(c, token, lookup.invite));
|
|
@@ -3560,7 +3670,7 @@ function createAuthConfirmRouter() {
|
|
|
3560
3670
|
status
|
|
3561
3671
|
);
|
|
3562
3672
|
});
|
|
3563
|
-
|
|
3673
|
+
confirm4.post("/new-workspace", async (c) => {
|
|
3564
3674
|
const form = await c.req.parseBody();
|
|
3565
3675
|
const token = typeof form["token"] === "string" ? form["token"].trim() : "";
|
|
3566
3676
|
const slug = typeof form["slug"] === "string" ? form["slug"].trim().toLowerCase() : "";
|
|
@@ -3667,7 +3777,7 @@ function createAuthConfirmRouter() {
|
|
|
3667
3777
|
}
|
|
3668
3778
|
return c.redirect(`${origin}/login?email=${encodeURIComponent(invite.email)}`, 303);
|
|
3669
3779
|
});
|
|
3670
|
-
return
|
|
3780
|
+
return confirm4;
|
|
3671
3781
|
}
|
|
3672
3782
|
function confirmLinkFor(origin, actionLink, intent) {
|
|
3673
3783
|
try {
|
|
@@ -3787,6 +3897,17 @@ import { serveStatic } from "@hono/node-server/serve-static";
|
|
|
3787
3897
|
|
|
3788
3898
|
// src/reconcile.ts
|
|
3789
3899
|
import { existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
|
|
3900
|
+
async function boundWorkspaces() {
|
|
3901
|
+
const creds = loadCredentials();
|
|
3902
|
+
if (!creds) return null;
|
|
3903
|
+
try {
|
|
3904
|
+
const me = await fetchWhoAmI(creds.apiUrl, await getAccessToken());
|
|
3905
|
+
saveCredentials({ ...loadCredentials() ?? creds, workspaces: me.workspaces });
|
|
3906
|
+
return me.workspaces;
|
|
3907
|
+
} catch {
|
|
3908
|
+
return creds.workspaces ?? [];
|
|
3909
|
+
}
|
|
3910
|
+
}
|
|
3790
3911
|
async function reconcileProfile(profile) {
|
|
3791
3912
|
return runWithProfile(profile, async () => {
|
|
3792
3913
|
const log5 = [];
|
|
@@ -3812,8 +3933,8 @@ async function reconcileProfile(profile) {
|
|
|
3812
3933
|
});
|
|
3813
3934
|
}
|
|
3814
3935
|
async function reconcileAll() {
|
|
3815
|
-
const
|
|
3816
|
-
const slugs =
|
|
3936
|
+
const bound = hasSupabaseCredentials() ? null : await boundWorkspaces();
|
|
3937
|
+
const slugs = new Set(bound ?? [...(await listWorkspaces()).map((w) => w.slug), ...listProfiles()]);
|
|
3817
3938
|
const results = /* @__PURE__ */ new Map();
|
|
3818
3939
|
for (const slug of slugs) {
|
|
3819
3940
|
try {
|
|
@@ -3826,7 +3947,8 @@ async function reconcileAll() {
|
|
|
3826
3947
|
return results;
|
|
3827
3948
|
}
|
|
3828
3949
|
async function ensureServices() {
|
|
3829
|
-
const
|
|
3950
|
+
const bound = await boundWorkspaces();
|
|
3951
|
+
const slugs = new Set(bound ?? [...(await listWorkspaces()).map((w) => w.slug), ...listProfiles()]);
|
|
3830
3952
|
slugs.delete(DEFAULT_PROFILE);
|
|
3831
3953
|
const installed = [];
|
|
3832
3954
|
for (const slug of slugs) {
|
|
@@ -16802,7 +16924,9 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
16802
16924
|
} else {
|
|
16803
16925
|
app.route("/share", createPublicShareAssetRouter(WEB_DIST));
|
|
16804
16926
|
app.route("/", createAppIconRouter());
|
|
16805
|
-
|
|
16927
|
+
if (existsSync26(WEB_DIST) || existsSync26(join22(paths.PACKAGE_ROOT, "web", "package.json"))) {
|
|
16928
|
+
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
16929
|
+
}
|
|
16806
16930
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
16807
16931
|
const spaFallback = serveStatic({ path: join22(WEB_DIST, "index.html") });
|
|
16808
16932
|
app.get("*", async (c, next) => {
|
|
@@ -17381,7 +17505,7 @@ async function ensureClaudeCodeReady() {
|
|
|
17381
17505
|
console.log(" Open a new terminal and run \x1B[1mclaude\x1B[0m to complete the sign-in flow,");
|
|
17382
17506
|
console.log(" then come back here.\n");
|
|
17383
17507
|
}
|
|
17384
|
-
const proceed = await
|
|
17508
|
+
const proceed = await confirm3({
|
|
17385
17509
|
message: "Continue setup anyway?",
|
|
17386
17510
|
default: false
|
|
17387
17511
|
});
|
|
@@ -17841,7 +17965,7 @@ async function runSlackSetup(args) {
|
|
|
17841
17965
|
if (!prereq) return;
|
|
17842
17966
|
const { claudeConfigDir } = prereq;
|
|
17843
17967
|
if (existsSync27(paths.CONFIG_FILE)) {
|
|
17844
|
-
const reconfigure = await
|
|
17968
|
+
const reconfigure = await confirm3({
|
|
17845
17969
|
message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
|
|
17846
17970
|
default: false
|
|
17847
17971
|
});
|
|
@@ -18231,9 +18355,10 @@ async function runDashboard(args) {
|
|
|
18231
18355
|
if (mode === "host") acquireHostLock();
|
|
18232
18356
|
ensureWebBuild();
|
|
18233
18357
|
const server = await startDashboardServer(preferredPort, host, mode);
|
|
18234
|
-
const
|
|
18358
|
+
const served = mode === "host" ? loadCredentials()?.workspaces : void 0;
|
|
18359
|
+
const scope = served ? `workspaces: ${served.join(", ") || "none"}` : `profile: ${getProfile()}`;
|
|
18235
18360
|
console.log(`
|
|
18236
|
-
Flyspace dashboard (
|
|
18361
|
+
Flyspace dashboard (${scope}, mode: ${mode})`);
|
|
18237
18362
|
console.log(` ${server.url}`);
|
|
18238
18363
|
if (server.port !== preferredPort) {
|
|
18239
18364
|
console.log(` (port ${preferredPort} was busy \u2014 using ${server.port})`);
|
|
@@ -18554,7 +18679,7 @@ async function main() {
|
|
|
18554
18679
|
break;
|
|
18555
18680
|
case "-v":
|
|
18556
18681
|
case "--version":
|
|
18557
|
-
printRaw("0.1.
|
|
18682
|
+
printRaw("0.1.6");
|
|
18558
18683
|
break;
|
|
18559
18684
|
case "-h":
|
|
18560
18685
|
case "--help":
|