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.
Files changed (2) hide show
  1. package/dist/cli.js +506 -381
  2. 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 mkdirSync4 } from "fs";
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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 existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
439
- import { dirname as dirname2, join as join2 } from "path";
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 join2(paths.BASE_CONFIG_DIR, "device.key");
800
+ return join5(paths.BASE_CONFIG_DIR, "device.key");
454
801
  }
455
802
  function credentialsPath() {
456
- return join2(paths.BASE_CONFIG_DIR, "credentials.json");
803
+ return join5(paths.BASE_CONFIG_DIR, "credentials.json");
457
804
  }
458
805
  function ensureDeviceKey(path = deviceKeyPath()) {
459
806
  let created = false;
460
- if (!existsSync3(path)) {
807
+ if (!existsSync6(path)) {
461
808
  const { privateKey: privateKey2 } = generateKeyPairSync("ed25519");
462
- mkdirSync2(dirname2(path), { recursive: true });
463
- writeFileSync3(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
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(readFileSync3(path, "utf8"));
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 (!existsSync3(path)) return null;
820
+ if (!existsSync6(path)) return null;
474
821
  try {
475
- return JSON.parse(readFileSync3(path, "utf8"));
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
- mkdirSync2(dirname2(path), { recursive: true });
482
- writeFileSync3(path, `${JSON.stringify(creds, null, 2)}
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 existsSync4,
953
+ existsSync as existsSync7,
607
954
  lstatSync,
608
- mkdirSync as mkdirSync3,
609
- readdirSync,
610
- readFileSync as readFileSync4,
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 join3, resolve as resolve3 } from "path";
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"] || join3(homedir(), "Flyspace");
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"] || join3(homedir(), ".claude");
977
+ return process.env["CLAUDE_CONFIG_DIR"] || join6(homedir3(), ".claude");
631
978
  }
632
979
  function codexDir() {
633
- return process.env["CODEX_HOME"] || join3(homedir(), ".codex");
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 (existsSync4(join3(claudeDir(), ".credentials.json"))) return true;
984
+ if (existsSync7(join6(claudeDir(), ".credentials.json"))) return true;
638
985
  try {
639
- const raw = JSON.parse(readFileSync4(join3(homedir(), ".claude.json"), "utf-8"));
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"] || existsSync4(join3(codexDir(), "auth.json"));
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 = readdirSync(dir, { withFileTypes: true });
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 = join3(dir, e.name);
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: statSync(full).mtimeMs });
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 = readFileSync4(file, { encoding: "utf-8", flag: "r" }).slice(0, MAX_JSONL_HEAD_BYTES);
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 = readFileSync4(join3(codexDir(), "config.toml"), "utf-8");
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 = homedir();
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 !statSync(path).isDirectory();
1058
+ return !statSync2(path).isDirectory();
712
1059
  } catch {
713
1060
  return true;
714
1061
  }
715
1062
  }
716
1063
  function gitRepos() {
717
- const home = homedir();
1064
+ const home = homedir3();
718
1065
  const found = [];
719
1066
  for (const rootName of GIT_SCAN_ROOTS) {
720
- const root = join3(home, rootName);
1067
+ const root = join6(home, rootName);
721
1068
  let entries2;
722
1069
  try {
723
- entries2 = readdirSync(root, { withFileTypes: true });
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() && existsSync4(join3(root, e.name, ".git"))) found.push(join3(root, e.name));
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 = resolve3(rawPath);
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 = join3(claudeDir(), "projects");
1119
+ const claudeProjects = join6(claudeDir(), "projects");
773
1120
  try {
774
- for (const e of readdirSync(claudeProjects, { withFileTypes: true })) {
1121
+ for (const e of readdirSync2(claudeProjects, { withFileTypes: true })) {
775
1122
  if (!e.isDirectory()) continue;
776
- const files = jsonlFiles(join3(claudeProjects, e.name), 0);
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(join3(codexDir(), "sessions"), 4);
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 = statSync(repo).mtimeMs;
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 = join3(flyspaceRoot(), slug);
832
- mkdirSync3(dir, { recursive: true });
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 = join3(dir, item.name);
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 = resolve3(item.path);
846
- if (!statSync(target).isDirectory()) throw new Error(`not a directory: ${target}`);
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
- mkdirSync3(dest, { recursive: true });
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 (platform() === "darwin") return `macOS (Darwin ${release()})`;
895
- return `${platform()} ${release()}`;
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 = platform() === "darwin" ? "open" : platform() === "win32" ? "explorer" : "xdg-open";
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
- ok(`Logged in as ${existing.userEmail} on ${existing.deviceName}`);
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
- const workspaces = await servedWorkspaces();
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 = join4(flyspaceRoot(), slug);
1226
- mkdirSync4(dir, { recursive: true });
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
- console.log(` Start the Host: ${BOLD2}flyspace dashboard --mode host${RESET2}`);
1234
- console.log(" Then go back to your browser to finish onboarding.\n");
1235
- if (!workspaces.length) process.exitCode = 1;
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, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
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
- warn(`Unsupported platform: ${os}. You'll need to configure autostart manually.`);
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 existsSync7, unlinkSync as unlinkSync2 } from "fs";
1490
- import { execSync as execSync3 } from "child_process";
1491
- import { basename as basename2, join as join7 } from "path";
1492
- import { homedir as homedir4, platform as platform4 } from "os";
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
- execSync3(cmd, { encoding: "utf-8", stdio: "pipe" });
1654
+ execSync4(cmd, { encoding: "utf-8", stdio: "pipe" });
1505
1655
  }
1506
1656
  async function runUninstall() {
1507
- const os = platform4();
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 (!existsSync7(plist)) continue;
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 = join7(homedir4(), ".config", "systemd", "user", `${unitName}.service`);
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
- const installed = listProfiles().filter(isServiceInstalled);
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.5";
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 confirm2, select, checkbox } from "@inquirer/prompts";
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 confirm3 = new Hono();
3424
- confirm3.get("/confirm", withResolvedProfile, async (c) => {
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
- confirm3.post("/confirm", withResolvedProfile, async (c) => {
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
- confirm3.get("/invite", withResolvedProfile, async (c) => {
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
- confirm3.post("/invite", withResolvedProfile, async (c) => {
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
- confirm3.get("/new-workspace", async (c) => {
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
- confirm3.post("/new-workspace", async (c) => {
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 confirm3;
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 workspaces = await listWorkspaces();
3816
- const slugs = /* @__PURE__ */ new Set([...workspaces.map((w) => w.slug), ...listProfiles()]);
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 slugs = /* @__PURE__ */ new Set([...(await listWorkspaces()).map((w) => w.slug), ...listProfiles()]);
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
- app.use("/*", serveStatic({ root: WEB_DIST }));
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 confirm2({
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 confirm2({
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 profile = getProfile();
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 (profile: ${profile}, mode: ${mode})`);
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.5");
18682
+ printRaw("0.1.6");
18558
18683
  break;
18559
18684
  case "-h":
18560
18685
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flyspace-cli",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "AI-native headless community OS",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,