flyspace-cli 0.1.10 → 0.1.12

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 CHANGED
@@ -32,6 +32,7 @@ import {
32
32
  buildSystemPrompt,
33
33
  canSeeChannel,
34
34
  channelSkillsDir,
35
+ claudeAuthFailed,
35
36
  codexAuthAvailable,
36
37
  createChannel,
37
38
  createMembership,
@@ -152,7 +153,7 @@ import {
152
153
  warnOnBrokenChannelCwds,
153
154
  workspaceSkillsDir,
154
155
  writeSkillFile
155
- } from "./chunk-UYLVSUD6.js";
156
+ } from "./chunk-VKTJBYA3.js";
156
157
  import {
157
158
  firstReplyBody,
158
159
  getThread,
@@ -313,42 +314,10 @@ async function runStart() {
313
314
  await startServer();
314
315
  }
315
316
 
316
- // src/commands/prompt.ts
317
- import { existsSync, copyFileSync } from "fs";
317
+ // src/commands/config.ts
318
+ import { existsSync, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
318
319
  import { spawn } from "child_process";
319
320
  import { resolve } from "path";
320
- async function runPrompt() {
321
- ensureDirs();
322
- if (!existsSync(paths.PROMPT_FILE)) {
323
- const defaultPrompt = resolve(paths.PACKAGE_ROOT, "system.default.md");
324
- if (!existsSync(defaultPrompt)) {
325
- console.error("[flyspace] system.default.md not found in package root.");
326
- process.exit(1);
327
- }
328
- copyFileSync(defaultPrompt, paths.PROMPT_FILE);
329
- console.log(`[flyspace] Copied default prompt to ${paths.PROMPT_FILE}`);
330
- }
331
- const editor = process.env["EDITOR"] || "vi";
332
- console.log(`[flyspace] Opening ${paths.PROMPT_FILE} in ${editor}...`);
333
- const child = spawn(editor, [paths.PROMPT_FILE], { stdio: "inherit" });
334
- return new Promise((resolve8, reject) => {
335
- child.on("close", (code) => {
336
- if (code === 0) {
337
- resolve8();
338
- } else {
339
- reject(new Error(`Editor exited with code ${code}`));
340
- }
341
- });
342
- child.on("error", (err) => {
343
- reject(new Error(`Failed to open editor: ${err.message}`));
344
- });
345
- });
346
- }
347
-
348
- // src/commands/config.ts
349
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
350
- import { spawn as spawn2 } from "child_process";
351
- import { resolve as resolve2 } from "path";
352
321
  function maskToken(value) {
353
322
  if (!value || value.length < 12) return "****";
354
323
  return value.slice(0, 8) + "..." + value.slice(-4);
@@ -364,12 +333,12 @@ function printConfig() {
364
333
  console.log("\n Paths:");
365
334
  console.log(` Config dir: ${paths.CONFIG_DIR}`);
366
335
  console.log(` Data dir: ${paths.DATA_DIR}`);
367
- console.log(` Config file: ${paths.CONFIG_FILE} ${existsSync2(paths.CONFIG_FILE) ? "(exists)" : "(not found)"}`);
368
- console.log(` Prompt file: ${paths.PROMPT_FILE} ${existsSync2(paths.PROMPT_FILE) ? "(exists)" : "(not found)"}`);
369
- console.log(` Database: ${paths.DB_FILE} ${existsSync2(paths.DB_FILE) ? "(exists)" : "(not found)"}`);
336
+ console.log(` Config file: ${paths.CONFIG_FILE} ${existsSync(paths.CONFIG_FILE) ? "(exists)" : "(not found)"}`);
337
+ console.log(` Prompt file: ${paths.PROMPT_FILE} ${existsSync(paths.PROMPT_FILE) ? "(exists)" : "(not found)"}`);
338
+ console.log(` Database: ${paths.DB_FILE} ${existsSync(paths.DB_FILE) ? "(exists)" : "(not found)"}`);
370
339
  console.log(` Log dir: ${paths.LOG_DIR}`);
371
- const repoEnv = resolve2(process.cwd(), ".env");
372
- console.log(` Repo .env: ${repoEnv} ${existsSync2(repoEnv) ? "(exists)" : "(not found)"}`);
340
+ const repoEnv = resolve(process.cwd(), ".env");
341
+ console.log(` Repo .env: ${repoEnv} ${existsSync(repoEnv) ? "(exists)" : "(not found)"}`);
373
342
  const envKeys = [
374
343
  "SLACK_BOT_TOKEN",
375
344
  "SLACK_APP_TOKEN",
@@ -393,9 +362,9 @@ function printConfig() {
393
362
  }
394
363
  function editConfig() {
395
364
  ensureDirs();
396
- if (!existsSync2(paths.CONFIG_FILE)) {
397
- const defaultEnv = resolve2(paths.PACKAGE_ROOT, ".env.example");
398
- if (existsSync2(defaultEnv)) {
365
+ if (!existsSync(paths.CONFIG_FILE)) {
366
+ const defaultEnv = resolve(paths.PACKAGE_ROOT, ".env.example");
367
+ if (existsSync(defaultEnv)) {
399
368
  const content = readFileSync2(defaultEnv, "utf-8");
400
369
  writeFileSync2(paths.CONFIG_FILE, content);
401
370
  } else {
@@ -405,11 +374,11 @@ function editConfig() {
405
374
  }
406
375
  const editor = process.env["EDITOR"] || "vi";
407
376
  console.log(`[flyspace] Opening ${paths.CONFIG_FILE} in ${editor}...`);
408
- const child = spawn2(editor, [paths.CONFIG_FILE], { stdio: "inherit" });
409
- return new Promise((resolve8, reject) => {
377
+ const child = spawn(editor, [paths.CONFIG_FILE], { stdio: "inherit" });
378
+ return new Promise((resolve7, reject) => {
410
379
  child.on("close", (code) => {
411
380
  if (code === 0) {
412
- resolve8();
381
+ resolve7();
413
382
  } else {
414
383
  reject(new Error(`Editor exited with code ${code}`));
415
384
  }
@@ -439,7 +408,7 @@ import { confirm as confirm2 } from "@inquirer/prompts";
439
408
 
440
409
  // src/service.ts
441
410
  import { execSync } from "child_process";
442
- import { existsSync as existsSync3, readFileSync as readFileSync3, realpathSync } from "fs";
411
+ import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync } from "fs";
443
412
  import { basename, dirname as dirname2, join as join2 } from "path";
444
413
  import { homedir, platform } from "os";
445
414
  function flyspaceHome(entry = process.argv[1]) {
@@ -454,7 +423,7 @@ function flyspaceHome(entry = process.argv[1]) {
454
423
  const npmPrefix = cliPath.slice(0, at);
455
424
  if (basename(npmPrefix) !== "cli") return void 0;
456
425
  const home = dirname2(npmPrefix);
457
- return existsSync3(join2(home, "bin", "flyspace")) && existsSync3(join2(home, "node", "bin", "node")) ? home : void 0;
426
+ return existsSync2(join2(home, "bin", "flyspace")) && existsSync2(join2(home, "node", "bin", "node")) ? home : void 0;
458
427
  }
459
428
  function serviceLabel(profile) {
460
429
  return profile === DEFAULT_PROFILE ? "ai.flyspace" : `ai.flyspace.${profile}`;
@@ -478,8 +447,8 @@ function systemdUnitPath(profile) {
478
447
  return join2(homedir(), ".config", "systemd", "user", `${systemdUnitName(profile)}.service`);
479
448
  }
480
449
  function isServiceInstalled(profile) {
481
- if (platform() === "darwin") return existsSync3(plistPath(profile));
482
- if (platform() === "linux") return existsSync3(systemdUnitPath(profile));
450
+ if (platform() === "darwin") return existsSync2(plistPath(profile));
451
+ if (platform() === "linux") return existsSync2(systemdUnitPath(profile));
483
452
  return false;
484
453
  }
485
454
  function serviceBinaryMissing(file) {
@@ -490,7 +459,7 @@ function serviceBinaryMissing(file) {
490
459
  return false;
491
460
  }
492
461
  const bin = text.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/)?.[1] ?? text.match(/^ExecStart=(\S+)/m)?.[1];
493
- return !!bin && !existsSync3(bin);
462
+ return !!bin && !existsSync2(bin);
494
463
  }
495
464
  function profileServiceFile(profile) {
496
465
  return platform() === "darwin" ? plistPath(profile) : systemdUnitPath(profile);
@@ -529,7 +498,7 @@ function hostPlistPath() {
529
498
  return join2(homedir(), "Library", "LaunchAgents", `${HOST_SERVICE_LABEL}.plist`);
530
499
  }
531
500
  function isHostServiceInstalled() {
532
- if (platform() === "darwin" || platform() === "linux") return existsSync3(hostServiceFile());
501
+ if (platform() === "darwin" || platform() === "linux") return existsSync2(hostServiceFile());
533
502
  return false;
534
503
  }
535
504
  function restartHostService() {
@@ -569,9 +538,9 @@ function isHostServiceRunning() {
569
538
  }
570
539
 
571
540
  // src/commands/install.ts
572
- import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync } from "fs";
541
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync } from "fs";
573
542
  import { execSync as execSync2 } from "child_process";
574
- import { dirname as dirname3, join as join3, resolve as resolve3 } from "path";
543
+ import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
575
544
  import { homedir as homedir2, platform as platform2 } from "os";
576
545
  function log(msg) {
577
546
  console.log(`
@@ -603,7 +572,7 @@ function detectNodeBinDir() {
603
572
  }
604
573
  function nodeShimDirs() {
605
574
  return [join3(homedir2(), ".local", "share", "mise", "shims"), join3(homedir2(), ".asdf", "shims")].filter(
606
- (d) => existsSync4(d)
575
+ (d) => existsSync3(d)
607
576
  );
608
577
  }
609
578
  function detectCustieBin() {
@@ -611,7 +580,7 @@ function detectCustieBin() {
611
580
  if (home) return join3(home, "bin", "flyspace");
612
581
  const argv1 = process.argv[1];
613
582
  if (argv1) {
614
- return resolve3(argv1);
583
+ return resolve2(argv1);
615
584
  }
616
585
  try {
617
586
  return run("which flyspace", { silent: true });
@@ -620,7 +589,7 @@ function detectCustieBin() {
620
589
  }
621
590
  }
622
591
  function readEnvFile() {
623
- if (!existsSync4(paths.CONFIG_FILE)) return {};
592
+ if (!existsSync3(paths.CONFIG_FILE)) return {};
624
593
  const vars = {};
625
594
  for (const line of readFileSync4(paths.CONFIG_FILE, "utf-8").split("\n")) {
626
595
  const trimmed = line.trim();
@@ -668,7 +637,7 @@ async function installMacOS(spec) {
668
637
  const logDir = paths.LOG_DIR;
669
638
  mkdirSync2(logDir, { recursive: true });
670
639
  const envVars = spec.env;
671
- const homebrewBin = existsSync4("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
640
+ const homebrewBin = existsSync3("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
672
641
  const pathParts = [
673
642
  detectNodeBinDir(),
674
643
  claudeBinDir,
@@ -736,7 +705,7 @@ async function installLinux(spec) {
736
705
  const logDir = paths.LOG_DIR;
737
706
  mkdirSync2(logDir, { recursive: true });
738
707
  const envLines = Object.entries(spec.env).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
739
- const linuxbrewBin = existsSync4("/home/linuxbrew/.linuxbrew/bin") ? "/home/linuxbrew/.linuxbrew/bin" : void 0;
708
+ const linuxbrewBin = existsSync3("/home/linuxbrew/.linuxbrew/bin") ? "/home/linuxbrew/.linuxbrew/bin" : void 0;
740
709
  const pathParts = [
741
710
  detectNodeBinDir(),
742
711
  claudeBinDir,
@@ -796,13 +765,13 @@ async function installHostService() {
796
765
  }
797
766
 
798
767
  // src/commands/profiles.ts
799
- import { existsSync as existsSync5, readdirSync, statSync } from "fs";
768
+ import { existsSync as existsSync4, readdirSync, statSync } from "fs";
800
769
  import { execSync as execSync3 } from "child_process";
801
770
  import { join as join4 } from "path";
802
771
  import { platform as platform3 } from "os";
803
772
  function listProfiles() {
804
773
  const profiles = [];
805
- if (existsSync5(paths.PROFILES_DIR)) {
774
+ if (existsSync4(paths.PROFILES_DIR)) {
806
775
  for (const entry of readdirSync(paths.PROFILES_DIR)) {
807
776
  try {
808
777
  if (statSync(join4(paths.PROFILES_DIR, entry)).isDirectory()) {
@@ -831,7 +800,7 @@ function isServiceRunning2(profile) {
831
800
 
832
801
  // src/auth/device-auth.ts
833
802
  import { createPrivateKey, createPublicKey, generateKeyPairSync, sign } from "crypto";
834
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
803
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
835
804
  import { dirname as dirname4, join as join5 } from "path";
836
805
  var DEFAULT_API_URL = "https://app.flyspace.ai";
837
806
  var EXPIRY_SKEW_MS = 6e4;
@@ -853,7 +822,7 @@ function credentialsPath() {
853
822
  }
854
823
  function ensureDeviceKey(path = deviceKeyPath()) {
855
824
  let created = false;
856
- if (!existsSync6(path)) {
825
+ if (!existsSync5(path)) {
857
826
  const { privateKey: privateKey2 } = generateKeyPairSync("ed25519");
858
827
  mkdirSync3(dirname4(path), { recursive: true });
859
828
  writeFileSync4(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
@@ -866,7 +835,7 @@ function ensureDeviceKey(path = deviceKeyPath()) {
866
835
  return { privateKey, publicKeyBase64, created };
867
836
  }
868
837
  function loadCredentials(path = credentialsPath()) {
869
- if (!existsSync6(path)) return null;
838
+ if (!existsSync5(path)) return null;
870
839
  try {
871
840
  return JSON.parse(readFileSync5(path, "utf8"));
872
841
  } catch {
@@ -1010,7 +979,7 @@ async function getAccessToken() {
1010
979
  // src/dashboard/onboarding-scan.ts
1011
980
  import { execFileSync } from "child_process";
1012
981
  import {
1013
- existsSync as existsSync7,
982
+ existsSync as existsSync6,
1014
983
  lstatSync,
1015
984
  mkdirSync as mkdirSync4,
1016
985
  readdirSync as readdirSync2,
@@ -1019,7 +988,7 @@ import {
1019
988
  symlinkSync
1020
989
  } from "fs";
1021
990
  import { homedir as homedir3 } from "os";
1022
- import { basename as basename2, join as join6, resolve as resolve4 } from "path";
991
+ import { basename as basename2, join as join6, resolve as resolve3 } from "path";
1023
992
  var MAX_JSONL_HEAD_BYTES = 64 * 1024;
1024
993
  var GIT_SCAN_ROOTS = ["Workspaces", "Projects", "code"];
1025
994
  function flyspaceRoot() {
@@ -1041,7 +1010,7 @@ function codexDir() {
1041
1010
  }
1042
1011
  function claudeLoggedIn() {
1043
1012
  if (process.env["ANTHROPIC_API_KEY"]) return true;
1044
- if (existsSync7(join6(claudeDir(), ".credentials.json"))) return true;
1013
+ if (existsSync6(join6(claudeDir(), ".credentials.json"))) return true;
1045
1014
  try {
1046
1015
  const raw = JSON.parse(readFileSync6(join6(homedir3(), ".claude.json"), "utf-8"));
1047
1016
  if (raw.oauthAccount) return true;
@@ -1058,7 +1027,7 @@ function claudeLoggedIn() {
1058
1027
  }
1059
1028
  }
1060
1029
  function codexLoggedIn() {
1061
- return !!process.env["OPENAI_API_KEY"] || existsSync7(join6(codexDir(), "auth.json"));
1030
+ return !!process.env["OPENAI_API_KEY"] || existsSync6(join6(codexDir(), "auth.json"));
1062
1031
  }
1063
1032
  function jsonlFiles(dir, depth) {
1064
1033
  const out = [];
@@ -1132,7 +1101,7 @@ function gitRepos() {
1132
1101
  continue;
1133
1102
  }
1134
1103
  for (const e of entries2) {
1135
- if (e.isDirectory() && existsSync7(join6(root, e.name, ".git"))) found.push(join6(root, e.name));
1104
+ if (e.isDirectory() && existsSync6(join6(root, e.name, ".git"))) found.push(join6(root, e.name));
1136
1105
  }
1137
1106
  }
1138
1107
  return found;
@@ -1157,7 +1126,7 @@ function scanOnboarding() {
1157
1126
  const byPath = /* @__PURE__ */ new Map();
1158
1127
  const history = { claude: false, codex: false };
1159
1128
  const add = (rawPath, source, mtime, sessions, trusted = false) => {
1160
- const path = resolve4(rawPath);
1129
+ const path = resolve3(rawPath);
1161
1130
  if (isExcluded(path)) return;
1162
1131
  const existing = byPath.get(path);
1163
1132
  if (existing) {
@@ -1249,7 +1218,7 @@ function linkProjectFolders(slug, items) {
1249
1218
  }
1250
1219
  if (!exists) {
1251
1220
  if (item.path) {
1252
- const target = resolve4(item.path);
1221
+ const target = resolve3(item.path);
1253
1222
  if (!statSync2(target).isDirectory()) throw new Error(`not a directory: ${target}`);
1254
1223
  makeLink(target, dest);
1255
1224
  } else {
@@ -1262,7 +1231,7 @@ function linkProjectFolders(slug, items) {
1262
1231
  }
1263
1232
 
1264
1233
  // src/commands/auth.ts
1265
- import { spawn as spawn3 } from "child_process";
1234
+ import { spawn as spawn2 } from "child_process";
1266
1235
  import { confirm } from "@inquirer/prompts";
1267
1236
  import { hostname, platform as platform4, release } from "os";
1268
1237
  var BOLD = "\x1B[1m";
@@ -1309,7 +1278,7 @@ function openBrowser(url) {
1309
1278
  if (process.env["SSH_CONNECTION"] || process.env["SSH_TTY"]) return false;
1310
1279
  const cmd = platform4() === "darwin" ? "open" : platform4() === "win32" ? "explorer" : "xdg-open";
1311
1280
  try {
1312
- const child = spawn3(cmd, [url], { stdio: "ignore", detached: true });
1281
+ const child = spawn2(cmd, [url], { stdio: "ignore", detached: true });
1313
1282
  child.on("error", () => {
1314
1283
  });
1315
1284
  child.unref();
@@ -1454,7 +1423,7 @@ async function runStatus() {
1454
1423
  const printLocal = (workspaces) => {
1455
1424
  console.log("");
1456
1425
  printIdentity({ ...creds, workspaces }, creds.deviceName);
1457
- console.log(` ${DIM}API${RESET} ${creds.apiUrl}`);
1426
+ if (creds.apiUrl !== DEFAULT_API_URL) console.log(` ${DIM}API${RESET} ${creds.apiUrl}`);
1458
1427
  console.log(` ${DIM}Key${RESET} ${tilde(deviceKeyPath())}`);
1459
1428
  console.log(` ${DIM}Credentials${RESET} ${tilde(credentialsPath())}`);
1460
1429
  };
@@ -1571,7 +1540,7 @@ async function runAuthCmd(args) {
1571
1540
  }
1572
1541
 
1573
1542
  // src/version.ts
1574
- var CLI_VERSION = true ? "0.1.10" : null;
1543
+ var CLI_VERSION = true ? "0.1.12" : null;
1575
1544
  function isOlderVersion(a, b) {
1576
1545
  const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
1577
1546
  const [pa, pb] = [parse2(a), parse2(b)];
@@ -1849,7 +1818,8 @@ ${BOLD3}Flyspace Doctor${RESET3}
1849
1818
  }
1850
1819
 
1851
1820
  // src/commands/uninstall.ts
1852
- import { existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
1821
+ import { existsSync as existsSync7, readFileSync as readFileSync7, rmSync as rmSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
1822
+ import { confirm as confirm3 } from "@inquirer/prompts";
1853
1823
  import { execSync as execSync5 } from "child_process";
1854
1824
  import { basename as basename3, join as join8 } from "path";
1855
1825
  import { homedir as homedir4, platform as platform5 } from "os";
@@ -1866,14 +1836,13 @@ function warn2(msg) {
1866
1836
  function run2(cmd) {
1867
1837
  execSync5(cmd, { encoding: "utf-8", stdio: "pipe" });
1868
1838
  }
1869
- async function runUninstall() {
1839
+ async function runServiceUninstall(profile = getProfile()) {
1870
1840
  const os = platform5();
1871
- const profile = getProfile();
1872
1841
  log2("Uninstalling Flyspace service...");
1873
1842
  if (os === "darwin") {
1874
1843
  const removed = [];
1875
1844
  for (const plist of [plistPath(profile), legacyPlistPath(profile)]) {
1876
- if (!existsSync8(plist)) continue;
1845
+ if (!existsSync7(plist)) continue;
1877
1846
  try {
1878
1847
  run2(`launchctl unload "${plist}"`);
1879
1848
  } catch {
@@ -1908,11 +1877,51 @@ async function runUninstall() {
1908
1877
  } else {
1909
1878
  warn2(`Unsupported platform: ${os}`);
1910
1879
  }
1911
- console.log("\n Config and data directories were NOT removed.");
1880
+ }
1881
+ async function runUninstall(args = []) {
1882
+ const prefix = process.env["FLYSPACE_PREFIX"] || join8(homedir4(), ".flyspace");
1883
+ const targets = [paths.BASE_CONFIG_DIR, paths.BASE_DATA_DIR, prefix].filter(existsSync7);
1884
+ console.log("\n This removes Flyspace from this machine:");
1885
+ console.log(" - revokes this device and removes every background service");
1886
+ for (const t of targets) console.log(` - deletes ${t}`);
1887
+ console.log(" Your channel folders (~/Flyspace) are kept.\n");
1888
+ if (!args.includes("--yes") && !args.includes("-y")) {
1889
+ if (!await confirm3({ message: "Continue?", default: false })) {
1890
+ console.log(" Cancelled.");
1891
+ return;
1892
+ }
1893
+ }
1894
+ const creds = loadCredentials();
1895
+ if (creds) {
1896
+ try {
1897
+ const token = await getAccessToken();
1898
+ if (token) await revokeDevice(creds.apiUrl, token);
1899
+ success2("Device revoked.");
1900
+ } catch {
1901
+ warn2("Could not reach the server to revoke this device. Remove it from Settings later.");
1902
+ }
1903
+ }
1904
+ for (const profile of ["default", ...listProfiles()]) {
1905
+ await runServiceUninstall(profile);
1906
+ }
1907
+ for (const t of targets) {
1908
+ rmSync3(t, { recursive: true, force: true });
1909
+ success2(`Deleted ${t}`);
1910
+ }
1911
+ for (const rc of [".zshrc", ".bashrc", ".profile"].map((f) => join8(homedir4(), f))) {
1912
+ if (!existsSync7(rc)) continue;
1913
+ const before = readFileSync7(rc, "utf-8");
1914
+ const after = before.replace(/\n# Flyspace CLI\nexport PATH="[^"\n]*\.flyspace\/bin:\$PATH"\n?/g, "\n");
1915
+ if (after !== before) {
1916
+ writeFileSync5(rc, after);
1917
+ success2(`Removed PATH entry from ${rc}`);
1918
+ }
1919
+ }
1920
+ console.log("\n Flyspace is uninstalled. Open a new terminal to finish.\n");
1912
1921
  }
1913
1922
 
1914
1923
  // src/commands/profiles-cmd.ts
1915
- import { rmSync as rmSync3 } from "fs";
1924
+ import { rmSync as rmSync4 } from "fs";
1916
1925
  var USAGE = `
1917
1926
  Usage: flyspace profiles [list]
1918
1927
  flyspace profiles remove <name>
@@ -1969,7 +1978,7 @@ async function removeCmd(name) {
1969
1978
  }
1970
1979
  const { configDir: configDir2, dataDir } = runWithProfile(name, () => ({ configDir: paths.CONFIG_DIR, dataDir: paths.DATA_DIR }));
1971
1980
  if (isServiceInstalled(name)) await runWithProfile(name, runUninstall);
1972
- rmSync3(configDir2, { recursive: true, force: true });
1981
+ rmSync4(configDir2, { recursive: true, force: true });
1973
1982
  console.log(`\x1B[32m+\x1B[0m Local config removed: ${configDir2}`);
1974
1983
  console.log(`
1975
1984
  Kept: ${dataDir} (sessions, logs) and any ~/Flyspace/${name}/ channel folders.`);
@@ -2023,7 +2032,7 @@ function npmEnv() {
2023
2032
  return home ? { ...process.env, PATH: `${join9(home, "node", "bin")}:${process.env.PATH ?? ""}` } : process.env;
2024
2033
  }
2025
2034
  async function runUpgrade() {
2026
- const current = "0.1.10";
2035
+ const current = "0.1.12";
2027
2036
  console.log(`Current version: ${current}`);
2028
2037
  console.log("Checking for updates...\n");
2029
2038
  try {
@@ -2394,7 +2403,7 @@ async function runAutomationCmd(args) {
2394
2403
  }
2395
2404
 
2396
2405
  // src/commands/usage.ts
2397
- import { existsSync as existsSync9 } from "fs";
2406
+ import { existsSync as existsSync8 } from "fs";
2398
2407
  var HELP4 = `
2399
2408
  Usage: flyspace usage [options]
2400
2409
 
@@ -2521,7 +2530,7 @@ async function runUsage(args) {
2521
2530
  return;
2522
2531
  }
2523
2532
  const { days, by, explain, json } = parsed;
2524
- if (!existsSync9(paths.DB_FILE)) {
2533
+ if (!existsSync8(paths.DB_FILE)) {
2525
2534
  console.log("No usage recorded yet (the database does not exist).");
2526
2535
  return;
2527
2536
  }
@@ -2635,21 +2644,21 @@ async function runRestart() {
2635
2644
  }
2636
2645
 
2637
2646
  // src/commands/logs.ts
2638
- import { spawn as spawn4 } from "child_process";
2639
- import { existsSync as existsSync10 } from "fs";
2647
+ import { spawn as spawn3 } from "child_process";
2648
+ import { existsSync as existsSync9 } from "fs";
2640
2649
  import { join as join10 } from "path";
2641
2650
  async function runLogs(args) {
2642
2651
  const showErrors = args.includes("--error") || args.includes("-e");
2643
2652
  const filename = showErrors ? "custie-error.log" : "custie.log";
2644
2653
  const logFile = join10(paths.LOG_DIR, filename);
2645
- if (!existsSync10(logFile)) {
2654
+ if (!existsSync9(logFile)) {
2646
2655
  console.error(`Log file not found: ${logFile}`);
2647
2656
  console.error("Is the service installed? Run `flyspace install` first.");
2648
2657
  process.exit(1);
2649
2658
  }
2650
2659
  console.log(`Tailing ${logFile} (Ctrl+C to stop)
2651
2660
  `);
2652
- const tail = spawn4("tail", ["-f", "-n", "50", logFile], {
2661
+ const tail = spawn3("tail", ["-f", "-n", "50", logFile], {
2653
2662
  stdio: "inherit"
2654
2663
  });
2655
2664
  tail.on("error", (err) => {
@@ -2663,10 +2672,10 @@ async function runLogs(args) {
2663
2672
 
2664
2673
  // src/commands/slack.ts
2665
2674
  import { execFileSync as execFileSync4 } from "child_process";
2666
- import { existsSync as existsSync27, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
2667
- import { resolve as resolve7 } from "path";
2675
+ import { existsSync as existsSync26, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
2676
+ import { resolve as resolve6 } from "path";
2668
2677
  import { tmpdir as tmpdir4, homedir as homedir9 } from "os";
2669
- import { input, confirm as confirm3, select, checkbox } from "@inquirer/prompts";
2678
+ import { input, confirm as confirm4, select, checkbox } from "@inquirer/prompts";
2670
2679
  import { WebClient as WebClient3 } from "@slack/web-api";
2671
2680
 
2672
2681
  // src/store/auth-links.ts
@@ -3833,8 +3842,8 @@ function setupPage(c, token, invite, form = {}) {
3833
3842
  );
3834
3843
  }
3835
3844
  function createAuthConfirmRouter() {
3836
- const confirm4 = new Hono();
3837
- confirm4.get("/confirm", withResolvedProfile, async (c) => {
3845
+ const confirm5 = new Hono();
3846
+ confirm5.get("/confirm", withResolvedProfile, async (c) => {
3838
3847
  const settings = await loadWorkspaceBranding();
3839
3848
  const display = fontOf(settings.font).stack;
3840
3849
  const name = settings.displayName?.trim() || getProfile();
@@ -3871,7 +3880,7 @@ function createAuthConfirmRouter() {
3871
3880
  )
3872
3881
  );
3873
3882
  });
3874
- confirm4.post("/confirm", withResolvedProfile, async (c) => {
3883
+ confirm5.post("/confirm", withResolvedProfile, async (c) => {
3875
3884
  const form = await c.req.parseBody();
3876
3885
  const params = readParams({
3877
3886
  token: typeof form["token"] === "string" ? form["token"] : void 0,
@@ -3882,7 +3891,7 @@ function createAuthConfirmRouter() {
3882
3891
  if (!target) return c.text("SUPABASE_URL is not configured", 500);
3883
3892
  return c.redirect(target, 303);
3884
3893
  });
3885
- confirm4.get("/invite", withResolvedProfile, async (c) => {
3894
+ confirm5.get("/invite", withResolvedProfile, async (c) => {
3886
3895
  const token = c.req.query("token")?.trim() ?? "";
3887
3896
  const lookup = await findInvite(token);
3888
3897
  const brandingWorkspace = lookup.state === "valid" ? await getWorkspaceById(lookup.invite.workspaceId) : null;
@@ -3928,7 +3937,7 @@ function createAuthConfirmRouter() {
3928
3937
  )
3929
3938
  );
3930
3939
  });
3931
- confirm4.post("/invite", withResolvedProfile, async (c) => {
3940
+ confirm5.post("/invite", withResolvedProfile, async (c) => {
3932
3941
  const form = await c.req.parseBody();
3933
3942
  const token = typeof form["token"] === "string" ? form["token"].trim() : "";
3934
3943
  const back = `/auth/invite?token=${encodeURIComponent(token)}`;
@@ -3945,7 +3954,7 @@ function createAuthConfirmRouter() {
3945
3954
  if (!await consumeInvite(lookup.invite.id)) return c.redirect(back, 303);
3946
3955
  return c.redirect(target, 303);
3947
3956
  });
3948
- confirm4.get("/new-workspace", async (c) => {
3957
+ confirm5.get("/new-workspace", async (c) => {
3949
3958
  const token = c.req.query("token")?.trim() ?? "";
3950
3959
  const lookup = INVITE_TOKEN.test(token) ? await lookupSetupInvite(token) : { state: "unknown" };
3951
3960
  if (lookup.state === "valid") return c.html(setupPage(c, token, lookup.invite));
@@ -3973,7 +3982,7 @@ function createAuthConfirmRouter() {
3973
3982
  status
3974
3983
  );
3975
3984
  });
3976
- confirm4.post("/new-workspace", async (c) => {
3985
+ confirm5.post("/new-workspace", async (c) => {
3977
3986
  const form = await c.req.parseBody();
3978
3987
  const token = typeof form["token"] === "string" ? form["token"].trim() : "";
3979
3988
  const slug = typeof form["slug"] === "string" ? form["slug"].trim().toLowerCase() : "";
@@ -4080,7 +4089,7 @@ function createAuthConfirmRouter() {
4080
4089
  }
4081
4090
  return c.redirect(`${origin}/login?email=${encodeURIComponent(invite.email)}`, 303);
4082
4091
  });
4083
- return confirm4;
4092
+ return confirm5;
4084
4093
  }
4085
4094
  function confirmLinkFor(origin, actionLink, intent) {
4086
4095
  try {
@@ -4191,7 +4200,7 @@ async function deliverPasswordResetLink(email, redirectTo) {
4191
4200
  }
4192
4201
 
4193
4202
  // src/dashboard/server.ts
4194
- import { existsSync as existsSync26 } from "fs";
4203
+ import { existsSync as existsSync25 } from "fs";
4195
4204
  import { createServer } from "net";
4196
4205
  import { join as join23 } from "path";
4197
4206
  import { Hono as Hono33 } from "hono";
@@ -4199,7 +4208,7 @@ import { serve } from "@hono/node-server";
4199
4208
  import { serveStatic } from "@hono/node-server/serve-static";
4200
4209
 
4201
4210
  // src/reconcile.ts
4202
- import { existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
4211
+ import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
4203
4212
  async function boundWorkspaces2() {
4204
4213
  return (await boundWorkspaces())?.workspaces ?? null;
4205
4214
  }
@@ -4210,13 +4219,13 @@ async function servedWorkspaces2() {
4210
4219
  async function reconcileProfile(profile) {
4211
4220
  return runWithProfile(profile, async () => {
4212
4221
  const log5 = [];
4213
- const hadConfigDir = existsSync11(paths.CONFIG_DIR);
4214
- const hadDataDir = existsSync11(paths.DATA_DIR);
4222
+ const hadConfigDir = existsSync10(paths.CONFIG_DIR);
4223
+ const hadDataDir = existsSync10(paths.DATA_DIR);
4215
4224
  ensureDirs();
4216
4225
  if (!hadConfigDir) log5.push(`config dir created (${paths.CONFIG_DIR})`);
4217
4226
  if (!hadDataDir) log5.push(`data dir created (${paths.DATA_DIR})`);
4218
- if (!existsSync11(paths.CONFIG_FILE)) {
4219
- writeFileSync5(paths.CONFIG_FILE, `BOT_NAME=${profile}
4227
+ if (!existsSync10(paths.CONFIG_FILE)) {
4228
+ writeFileSync6(paths.CONFIG_FILE, `BOT_NAME=${profile}
4220
4229
  `);
4221
4230
  log5.push(`config.env written (${paths.CONFIG_FILE})`);
4222
4231
  }
@@ -4397,7 +4406,7 @@ var requireDocsAccess = async (c, next) => {
4397
4406
  import { Hono as Hono30 } from "hono";
4398
4407
 
4399
4408
  // src/dashboard/public-share-routes.ts
4400
- import { existsSync as existsSync12, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
4409
+ import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
4401
4410
  import { join as join13 } from "path";
4402
4411
  import { Hono as Hono3 } from "hono";
4403
4412
  import { GetObjectCommand } from "@aws-sdk/client-s3";
@@ -4863,7 +4872,7 @@ function injectShareHead(html, head) {
4863
4872
 
4864
4873
  // src/dashboard/app-icon.ts
4865
4874
  import { createHash as createHash2 } from "crypto";
4866
- import { readFileSync as readFileSync7 } from "fs";
4875
+ import { readFileSync as readFileSync8 } from "fs";
4867
4876
  import { join as join12 } from "path";
4868
4877
  import sharp from "sharp";
4869
4878
  var PLATE = "#0A0A0F";
@@ -4891,7 +4900,7 @@ function roundedPlate(size, fill = PLATE) {
4891
4900
  }
4892
4901
  async function renderBadge(box) {
4893
4902
  const transparent = { r: 0, g: 0, b: 0, alpha: 0 };
4894
- const raster = await sharp(readFileSync7(MARK_SVG_FILE)).resize(512, 512, { fit: "contain", background: transparent }).png().toBuffer();
4903
+ const raster = await sharp(readFileSync8(MARK_SVG_FILE)).resize(512, 512, { fit: "contain", background: transparent }).png().toBuffer();
4895
4904
  return sharp(raster).trim().resize(box, box, { fit: "contain", background: transparent }).png().toBuffer();
4896
4905
  }
4897
4906
  async function sampleBackground(logo) {
@@ -4946,7 +4955,7 @@ async function renderAppIcon({
4946
4955
  if (hit) return hit;
4947
4956
  const out = logo && await composeIcon(logo, size, maskable, plain).catch(() => null) || // No workspace logo to lead with — the stock mark is still the best icon,
4948
4957
  // either on its usual dark plate or, for a favicon, trimmed and bare.
4949
- (transparent ? await renderBadge(size) : await sharp(readFileSync7(MARK_FILE)).resize(size, size, { fit: "cover" }).png().toBuffer());
4958
+ (transparent ? await renderBadge(size) : await sharp(readFileSync8(MARK_FILE)).resize(size, size, { fit: "cover" }).png().toBuffer());
4950
4959
  cache3.set(key, out);
4951
4960
  return out;
4952
4961
  }
@@ -5458,7 +5467,7 @@ function truncationNotice(lang, voice) {
5458
5467
  if (lang === "en" || lang.startsWith("en-")) return TRUNCATION_NOTICE.en;
5459
5468
  return null;
5460
5469
  }
5461
- var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
5470
+ var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
5462
5471
  async function synthesizeWithRetry(script, voice) {
5463
5472
  let lastError;
5464
5473
  for (let attempt = 1; attempt <= SYNTH_ATTEMPTS; attempt += 1) {
@@ -6164,7 +6173,7 @@ var shellCache = null;
6164
6173
  function readShell(path) {
6165
6174
  const { mtimeMs } = statSync3(path);
6166
6175
  if (shellCache?.mtimeMs === mtimeMs) return shellCache.html;
6167
- const html = readFileSync8(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
6176
+ const html = readFileSync9(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
6168
6177
  shellCache = { mtimeMs, html };
6169
6178
  return html;
6170
6179
  }
@@ -6185,16 +6194,16 @@ function serveBuildFile(c, webDist, dir, name) {
6185
6194
  return c.text("not found", 404);
6186
6195
  }
6187
6196
  const path = join13(webDist, dir, name);
6188
- if (!existsSync12(path)) return c.text("not found", 404);
6197
+ if (!existsSync11(path)) return c.text("not found", 404);
6189
6198
  c.header("Content-Type", type);
6190
6199
  c.header("Cache-Control", "public, max-age=31536000, immutable");
6191
6200
  if (ext === ".css") {
6192
- return c.body(readFileSync8(path, "utf8").replaceAll("url(/fonts/", `url(${BUILD_PREFIX}/fonts/`));
6201
+ return c.body(readFileSync9(path, "utf8").replaceAll("url(/fonts/", `url(${BUILD_PREFIX}/fonts/`));
6193
6202
  }
6194
6203
  if (ext === ".js") {
6195
- return c.body(readFileSync8(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
6204
+ return c.body(readFileSync9(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
6196
6205
  }
6197
- return c.body(new Uint8Array(readFileSync8(path)));
6206
+ return c.body(new Uint8Array(readFileSync9(path)));
6198
6207
  }
6199
6208
  var HTML_SHARE_CSP = "sandbox allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox";
6200
6209
  function servePublishedHtml(c, share) {
@@ -6331,7 +6340,7 @@ ${analytics}` : head);
6331
6340
  const lang = canonicalLanguage(requested);
6332
6341
  if (!isTranslatableLanguage(lang)) return next();
6333
6342
  const shell = join13(webDist, "index.html");
6334
- if (!existsSync12(shell)) return next();
6343
+ if (!existsSync11(shell)) return next();
6335
6344
  const share = await getDocShare(c.req.param("id"));
6336
6345
  if (!share) return next();
6337
6346
  if (requested !== lang) return c.redirect(`/share/${share.id}/${lang}`, 302);
@@ -6367,7 +6376,7 @@ ${analytics}` : head);
6367
6376
  if (!share) return next();
6368
6377
  if (share.kind === "html") return servePublishedHtml(c, share);
6369
6378
  const shell = join13(webDist, "index.html");
6370
- if (!existsSync12(shell)) return next();
6379
+ if (!existsSync11(shell)) return next();
6371
6380
  return readerPage(
6372
6381
  c,
6373
6382
  share,
@@ -6983,26 +6992,21 @@ async function resolveAgentIdentities() {
6983
6992
  );
6984
6993
  }
6985
6994
  async function offlineAgentIdentities() {
6986
- const dm = findChannelByAgentName(BOT_DM_KEY);
6987
- if (!dm) return [];
6988
- return [
6989
- {
6990
- name: BOT_DM_KEY,
6991
- label: BOT_LABEL,
6992
- kind: "main",
6993
- channelId: dm.id,
6994
- ...await loadBotIdentity()
6995
- }
6996
- ];
6995
+ const identity = await loadBotIdentity();
6996
+ const dm = findChannelByAgentName(BOT_DM_KEY) ?? await ensureAgentDmChannel(BOT_DM_KEY, {
6997
+ name: identity.displayName ?? BOT_LABEL,
6998
+ cwd: resolveChannelConfig("").cwd
6999
+ });
7000
+ return [{ name: BOT_DM_KEY, label: BOT_LABEL, kind: "main", channelId: dm.id, ...identity }];
6997
7001
  }
6998
7002
 
6999
7003
  // src/dashboard/host-api.ts
7000
- import { existsSync as existsSync21, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
7004
+ import { existsSync as existsSync20, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
7001
7005
  import { join as join20 } from "path";
7002
7006
  import sharp3 from "sharp";
7003
7007
 
7004
7008
  // src/dashboard/channels-routes.ts
7005
- import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
7009
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
7006
7010
  import { Hono as Hono9 } from "hono";
7007
7011
 
7008
7012
  // src/dashboard/feature-flags-routes.ts
@@ -7150,7 +7154,7 @@ function coerceMemberEmails(raw) {
7150
7154
  }
7151
7155
  function ensureCwdDir(cwd) {
7152
7156
  const expanded = isLogicalCwd(cwd) ? resolveLogicalCwd(cwd) : expandPath(cwd);
7153
- if (!existsSync13(expanded)) mkdirSync7(expanded, { recursive: true });
7157
+ if (!existsSync12(expanded)) mkdirSync7(expanded, { recursive: true });
7154
7158
  }
7155
7159
  function toResponse(channel) {
7156
7160
  return {
@@ -7299,25 +7303,25 @@ function createChannelWriteRouter() {
7299
7303
  // src/dashboard/docs-routes.ts
7300
7304
  import {
7301
7305
  closeSync,
7302
- existsSync as existsSync17,
7306
+ existsSync as existsSync16,
7303
7307
  lstatSync as lstatSync2,
7304
7308
  mkdirSync as mkdirSync8,
7305
7309
  openSync,
7306
7310
  readSync,
7307
7311
  readdirSync as readdirSync4,
7308
- readFileSync as readFileSync11,
7312
+ readFileSync as readFileSync12,
7309
7313
  realpathSync as realpathSync3,
7310
7314
  renameSync,
7311
- rmSync as rmSync5,
7315
+ rmSync as rmSync6,
7312
7316
  statSync as statSync5,
7313
- writeFileSync as writeFileSync7
7317
+ writeFileSync as writeFileSync8
7314
7318
  } from "fs";
7315
- import { basename as basename6, dirname as dirname5, extname, join as join18, relative, resolve as resolve5, sep } from "path";
7319
+ import { basename as basename6, dirname as dirname5, extname, join as join18, relative, resolve as resolve4, sep } from "path";
7316
7320
  import { Hono as Hono10 } from "hono";
7317
7321
  import { streamSSE } from "hono/streaming";
7318
7322
 
7319
7323
  // src/dashboard/docs-watcher.ts
7320
- import { existsSync as existsSync14, watch } from "fs";
7324
+ import { existsSync as existsSync13, watch } from "fs";
7321
7325
  import { basename as basename4, join as join14 } from "path";
7322
7326
  var DEBOUNCE_MS = 200;
7323
7327
  var MAX_PENDING = 200;
@@ -7359,7 +7363,7 @@ function watchDocs(cwd, listener) {
7359
7363
  const current = entries.get(cwd);
7360
7364
  if (!current || filename === null) return;
7361
7365
  const rel = filename.split("\\").join("/");
7362
- if (rel === root && !existsSync14(join14(cwd, rel))) return;
7366
+ if (rel === root && !existsSync13(join14(cwd, rel))) return;
7363
7367
  if (isIgnoredDocPath(rel)) return;
7364
7368
  if (current.pending.size < MAX_PENDING) current.pending.add(rel);
7365
7369
  if (current.timer === null) current.timer = setTimeout(() => flush(cwd), DEBOUNCE_MS);
@@ -7596,13 +7600,13 @@ async function readOrQueueDocAudio(input2) {
7596
7600
  }
7597
7601
 
7598
7602
  // src/dashboard/artifact-export.ts
7599
- import { readFileSync as readFileSync10 } from "fs";
7603
+ import { readFileSync as readFileSync11 } from "fs";
7600
7604
 
7601
7605
  // src/dashboard/chat-agent.ts
7602
7606
  import { homedir as homedir7 } from "os";
7603
7607
 
7604
7608
  // src/mcp/config.ts
7605
- import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
7609
+ import { mkdtempSync, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
7606
7610
  import { tmpdir as tmpdir2 } from "os";
7607
7611
  import { join as join15 } from "path";
7608
7612
 
@@ -8113,7 +8117,7 @@ function needsRefresh(expiresAt, now = Date.now()) {
8113
8117
  if (!expiresAt) return false;
8114
8118
  return new Date(expiresAt).getTime() - now < REFRESH_SKEW_MS;
8115
8119
  }
8116
- var sleep3 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
8120
+ var sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
8117
8121
  async function refreshConnection(params) {
8118
8122
  const { workspaceId, connection, provider } = params;
8119
8123
  const deps = params.deps ?? {};
@@ -8292,10 +8296,10 @@ async function buildMcpConfig(profile, connectionIds) {
8292
8296
  function writeMcpConfigFile(config) {
8293
8297
  const dir = mkdtempSync(join15(tmpdir2(), "flyspace-mcp-"));
8294
8298
  const path = join15(dir, "mcp-config.json");
8295
- writeFileSync6(path, JSON.stringify(config), { mode: 384 });
8299
+ writeFileSync7(path, JSON.stringify(config), { mode: 384 });
8296
8300
  return {
8297
8301
  path,
8298
- cleanup: () => rmSync4(dir, { recursive: true, force: true })
8302
+ cleanup: () => rmSync5(dir, { recursive: true, force: true })
8299
8303
  };
8300
8304
  }
8301
8305
  async function withMcpConfig(profile, connectionIds, fn) {
@@ -8312,7 +8316,7 @@ async function withMcpConfig(profile, connectionIds, fn) {
8312
8316
  }
8313
8317
 
8314
8318
  // src/claude/agents.ts
8315
- import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
8319
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
8316
8320
  import { homedir as homedir6 } from "os";
8317
8321
  import { basename as basename5, join as join16 } from "path";
8318
8322
  import yaml from "js-yaml";
@@ -8327,7 +8331,7 @@ function expandHome(p) {
8327
8331
  function parseFrontmatter(file) {
8328
8332
  let content;
8329
8333
  try {
8330
- content = readFileSync9(file, "utf-8");
8334
+ content = readFileSync10(file, "utf-8");
8331
8335
  } catch {
8332
8336
  return {};
8333
8337
  }
@@ -8343,7 +8347,7 @@ function parseFrontmatter(file) {
8343
8347
  }
8344
8348
  }
8345
8349
  function collectFrom(root, source) {
8346
- if (!existsSync15(root)) return [];
8350
+ if (!existsSync14(root)) return [];
8347
8351
  let entries2;
8348
8352
  try {
8349
8353
  entries2 = readdirSync3(root).filter((f) => f.endsWith(".md"));
@@ -8373,7 +8377,7 @@ function readAgentPersona(configDir2, cwd, name) {
8373
8377
  const agent = listAgents(configDir2, cwd).find((a) => a.name === name);
8374
8378
  if (!agent) return void 0;
8375
8379
  try {
8376
- const content = readFileSync9(agent.path, "utf-8");
8380
+ const content = readFileSync10(agent.path, "utf-8");
8377
8381
  return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
8378
8382
  } catch {
8379
8383
  return void 0;
@@ -8606,7 +8610,7 @@ function formatMemoryPrompt(channelId, memories) {
8606
8610
  }
8607
8611
 
8608
8612
  // src/dashboard/rollover-summary.ts
8609
- import { spawn as spawn5 } from "child_process";
8613
+ import { spawn as spawn4 } from "child_process";
8610
8614
  import { tmpdir as tmpdir3 } from "os";
8611
8615
  var HAIKU_MODEL = "haiku";
8612
8616
  var CJK_PATTERN = /[ -〿぀-ヿ㐀-䶿一-鿿豈-﫿＀-￯]/;
@@ -8647,7 +8651,7 @@ Rules:
8647
8651
  - No preamble like "Summary:" \u2014 start directly with the summary text.`;
8648
8652
  var HAIKU_TIMEOUT_MS = 6e4;
8649
8653
  function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
8650
- return new Promise((resolve8, reject) => {
8654
+ return new Promise((resolve7, reject) => {
8651
8655
  const args = [
8652
8656
  "--print",
8653
8657
  "--model",
@@ -8666,7 +8670,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
8666
8670
  if (claudeConfigDir) env["CLAUDE_CONFIG_DIR"] = claudeConfigDir;
8667
8671
  if (claudeCodeOauthToken) env["CLAUDE_CODE_OAUTH_TOKEN"] = claudeCodeOauthToken;
8668
8672
  else delete env["CLAUDE_CODE_OAUTH_TOKEN"];
8669
- const child = spawn5("claude", args, {
8673
+ const child = spawn4("claude", args, {
8670
8674
  // A stateless rewrite touches no files — tmpdir keeps it that way and
8671
8675
  // avoids picking up an unrelated project's own CLAUDE.md/AGENTS.md,
8672
8676
  // unlike the channel's own cwd the main turn runs in.
@@ -8698,7 +8702,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
8698
8702
  reject(new Error("claude -p --model haiku returned empty output"));
8699
8703
  return;
8700
8704
  }
8701
- resolve8(text);
8705
+ resolve7(text);
8702
8706
  });
8703
8707
  });
8704
8708
  }
@@ -8945,11 +8949,11 @@ async function downloadChatAttachments(body) {
8945
8949
  }
8946
8950
 
8947
8951
  // src/dashboard/doc-mention-guard.ts
8948
- import { existsSync as existsSync16, statSync as statSync4 } from "fs";
8952
+ import { existsSync as existsSync15, statSync as statSync4 } from "fs";
8949
8953
  var DOC_MENTION_RE = /(?<![\w@/`])@((?:[\w.-]+\/)+[\w.-]+\.[A-Za-z0-9]{1,6})(?::(\d+))?(?![\w-])/g;
8950
8954
  function isRealDoc(root, rel) {
8951
8955
  const abs = resolveDocPath(root, rel);
8952
- if (!abs || !existsSync16(abs)) return false;
8956
+ if (!abs || !existsSync15(abs)) return false;
8953
8957
  try {
8954
8958
  return statSync4(abs).isFile();
8955
8959
  } catch {
@@ -9460,12 +9464,14 @@ var ChatAgent = class {
9460
9464
  };
9461
9465
  }
9462
9466
  }
9463
- const docsAccess = await this.resolveSenderDocsAccess(
9464
- owningProfile,
9465
- msg.author,
9466
- thread.channel_id
9467
- );
9468
- if (docsAccess && docsAccess !== "write") channelConfig = { ...channelConfig, docsAccess };
9467
+ const { access: docsAccess, unverified: docsAccessUnverified } = await this.resolveSenderDocsAccess(owningProfile, msg.author, thread.channel_id);
9468
+ if (docsAccess && docsAccess !== "write") {
9469
+ channelConfig = {
9470
+ ...channelConfig,
9471
+ docsAccess,
9472
+ ...docsAccessUnverified ? { docsAccessUnverified } : {}
9473
+ };
9474
+ }
9469
9475
  const agentMemory = await listEnabledFeatureFlagsBySlug(owningProfile).then((ids) => resolveFeatureFlags(ids, false)["agent-memory"]).catch((err) => {
9470
9476
  console.error("[chat-agent] feature flag lookup failed:", err.message);
9471
9477
  return false;
@@ -9482,7 +9488,7 @@ var ChatAgent = class {
9482
9488
  try {
9483
9489
  const channelKey = `chat:${thread.channel_id}`;
9484
9490
  const session = store.getSession(channelKey, msg.threadId);
9485
- const docsNote = docsAccessPromptNote(channelConfig.docsAccess);
9491
+ const docsNote = docsAccessPromptNote(channelConfig.docsAccess, channelConfig.docsAccessUnverified);
9486
9492
  const header = (docsNote ? `[access: ${docsNote}]
9487
9493
 
9488
9494
  ` : "") + `[context: chat channel=${thread.channel_id}` + (thread.title ? `, thread="${thread.title}"` : "") + `, sender=${msg.author}. This is the Flyspace dashboard, not Slack \u2014 it renders full Markdown, so use normal paragraphs, line breaks, and real Markdown lists instead of Slack-style single-line replies. When you name a file that lives in this channel's folder, write it as @ + its cwd-relative path (@docs/spec.md, @web/src/App.tsx) as plain text, never inside backticks or a Markdown link \u2014 the dashboard turns that into a chip the reader clicks to open the file. A trailing line number (@src/app.ts:120) is fine. Paths outside the cwd stay in backticks.]
@@ -9750,22 +9756,22 @@ ${msg.body}` : msg.body;
9750
9756
  let ready;
9751
9757
  try {
9752
9758
  channel = this.supabase().channel(`agent-thinking:${threadId}`);
9753
- ready = new Promise((resolve8) => {
9759
+ ready = new Promise((resolve7) => {
9754
9760
  const timeout = setTimeout(() => {
9755
9761
  console.error(`[chat-agent] thinking-channel ${threadId}: subscribe timed out after 10s`);
9756
- resolve8(false);
9762
+ resolve7(false);
9757
9763
  }, 1e4);
9758
9764
  channel.subscribe((status, err) => {
9759
9765
  if (status === "SUBSCRIBED") {
9760
9766
  clearTimeout(timeout);
9761
- resolve8(true);
9767
+ resolve7(true);
9762
9768
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
9763
9769
  console.error(
9764
9770
  `[chat-agent] thinking-channel ${threadId}: ${status}`,
9765
9771
  err?.message ?? ""
9766
9772
  );
9767
9773
  clearTimeout(timeout);
9768
- resolve8(false);
9774
+ resolve7(false);
9769
9775
  }
9770
9776
  });
9771
9777
  });
@@ -9812,19 +9818,19 @@ ${msg.body}` : msg.body;
9812
9818
  let ready;
9813
9819
  try {
9814
9820
  channel = this.supabase().channel("channel-activity");
9815
- ready = new Promise((resolve8) => {
9821
+ ready = new Promise((resolve7) => {
9816
9822
  const timeout = setTimeout(() => {
9817
9823
  console.error("[chat-agent] channel-activity: subscribe timed out after 10s");
9818
- resolve8(false);
9824
+ resolve7(false);
9819
9825
  }, 1e4);
9820
9826
  channel.subscribe((status, err) => {
9821
9827
  if (status === "SUBSCRIBED") {
9822
9828
  clearTimeout(timeout);
9823
- resolve8(true);
9829
+ resolve7(true);
9824
9830
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
9825
9831
  console.error(`[chat-agent] channel-activity: ${status}`, err?.message ?? "");
9826
9832
  clearTimeout(timeout);
9827
- resolve8(false);
9833
+ resolve7(false);
9828
9834
  }
9829
9835
  });
9830
9836
  });
@@ -9851,15 +9857,17 @@ ${msg.body}` : msg.body;
9851
9857
  /**
9852
9858
  * The sender's Docs level on `channelId`, or undefined when no restriction
9853
9859
  * applies (non-human author, or a profile with no Supabase workspace). A
9854
- * lookup failure resolves to 'none': better to withhold files than leak them.
9860
+ * lookup failure resolves to 'none' (better to withhold files than leak
9861
+ * them), flagged `unverified` so the agent reports a system error instead of
9862
+ * telling the sender they lack access.
9855
9863
  */
9856
9864
  async resolveSenderDocsAccess(profile, author, channelId) {
9857
- if (!isHumanAuthor(author)) return void 0;
9865
+ if (!isHumanAuthor(author)) return {};
9858
9866
  try {
9859
- return await resolveDocsAccessByHandle(profile, author, channelId) ?? void 0;
9867
+ return { access: await resolveDocsAccessByHandle(profile, author, channelId) ?? void 0 };
9860
9868
  } catch (err) {
9861
9869
  console.error("[chat-agent] docs access lookup failed:", err.message);
9862
- return "none";
9870
+ return { access: "none", unverified: true };
9863
9871
  }
9864
9872
  }
9865
9873
  async tryAsk(channelConfig, prompt, sessionId, onActivity, signal, usageContext) {
@@ -9886,6 +9894,7 @@ ${msg.body}` : msg.body;
9886
9894
  mcpConfigPath,
9887
9895
  ...usageContext ? { usageContext } : {},
9888
9896
  ...channelConfig.docsAccess ? { docsAccess: channelConfig.docsAccess } : {},
9897
+ ...channelConfig.docsAccessUnverified ? { docsAccessUnverified: true } : {},
9889
9898
  ...channelConfig.memoryPrompt ? { memoryPrompt: channelConfig.memoryPrompt } : {},
9890
9899
  ...channelConfig.disableAutoMemory ? { disableAutoMemory: true } : {}
9891
9900
  },
@@ -9925,7 +9934,7 @@ ${msg.body}` : msg.body;
9925
9934
  resolveSelectedOutputStyle(channelConfig.claudeConfigDir)?.body
9926
9935
  ),
9927
9936
  channelConfig.memoryPrompt,
9928
- docsAccessPromptNote(channelConfig.docsAccess)
9937
+ docsAccessPromptNote(channelConfig.docsAccess, channelConfig.docsAccessUnverified)
9929
9938
  ].filter(Boolean).join("\n\n---\n\n"),
9930
9939
  // Codex has no per-tool deny list, so any restricted requester gets
9931
9940
  // its read-only sandbox (no file writes). It can still read: see the
@@ -10202,7 +10211,7 @@ async function exportDocAsArtifact(channelId, docPath) {
10202
10211
  );
10203
10212
  const abs = resolveDocPath(channelConfig.cwd, docPath);
10204
10213
  if (!abs) throw new Error(`doc not found: ${docPath}`);
10205
- const content = readFileSync10(abs, "utf8");
10214
+ const content = readFileSync11(abs, "utf8");
10206
10215
  const title = extractTitle(abs, "md");
10207
10216
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
10208
10217
  const prompt = `Publish the document at \`${docPath}\` (already read below) as a Claude Artifact, then record the result as a stub file under this channel's \`docs/artifacts/\` folder (create the folder if it doesn't exist).
@@ -10431,7 +10440,7 @@ function extractTitle(absPath, kind) {
10431
10440
  }
10432
10441
  function resolveDirPath(root, rel) {
10433
10442
  if (rel.includes("\0")) return null;
10434
- const abs = resolve5(root, rel);
10443
+ const abs = resolve4(root, rel);
10435
10444
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10436
10445
  return abs;
10437
10446
  }
@@ -10481,12 +10490,12 @@ function readGitBranch(dir) {
10481
10490
  try {
10482
10491
  let gitDir = join18(dir, ".git");
10483
10492
  if (statSync5(gitDir).isFile()) {
10484
- const pointer = readFileSync11(gitDir, "utf8").trim();
10493
+ const pointer = readFileSync12(gitDir, "utf8").trim();
10485
10494
  const match = pointer.match(/^gitdir:\s*(.+)$/);
10486
10495
  if (!match) return void 0;
10487
- gitDir = resolve5(dir, match[1]);
10496
+ gitDir = resolve4(dir, match[1]);
10488
10497
  }
10489
- const head = readFileSync11(join18(gitDir, "HEAD"), "utf8").trim();
10498
+ const head = readFileSync12(join18(gitDir, "HEAD"), "utf8").trim();
10490
10499
  const branchMatch = head.match(/^ref:\s*refs\/heads\/(.+)$/);
10491
10500
  if (branchMatch) return branchMatch[1];
10492
10501
  return /^[0-9a-f]{7,40}$/i.test(head) ? head.slice(0, 7) : void 0;
@@ -10515,7 +10524,7 @@ function listDocLevel(root, relDir = "", sort = "newest", showHidden = false) {
10515
10524
  if (SKIP_DIRS.has(entry.name)) continue;
10516
10525
  const probe3 = probeDir(abs, showHidden);
10517
10526
  if (!probe3.hasDocs && probe3.hasOther) continue;
10518
- const isRepo = existsSync17(join18(abs, ".git"));
10527
+ const isRepo = existsSync16(join18(abs, ".git"));
10519
10528
  dirs.push({
10520
10529
  path,
10521
10530
  name: entry.name,
@@ -10571,7 +10580,7 @@ function flatDocs(root, {
10571
10580
  }
10572
10581
  function resolveDocPath(root, rel) {
10573
10582
  if (!rel || rel.includes("\0")) return null;
10574
- const abs = resolve5(root, rel);
10583
+ const abs = resolve4(root, rel);
10575
10584
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10576
10585
  const ext = extname(abs).toLowerCase();
10577
10586
  if (!(ext in DOC_EXTENSIONS) && !(ext in CODE_EXTENSIONS)) return null;
@@ -10593,12 +10602,12 @@ function writeDocContent(root, rel, content, baseMtime) {
10593
10602
  if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) {
10594
10603
  return { status: "too-large" };
10595
10604
  }
10596
- writeFileSync7(abs, content, "utf8");
10605
+ writeFileSync8(abs, content, "utf8");
10597
10606
  return { status: "ok", mtime: statSync5(abs).mtime.toISOString() };
10598
10607
  }
10599
10608
  function resolveRawPath(root, rel) {
10600
10609
  if (!rel || rel.includes("\0")) return null;
10601
- const abs = resolve5(root, rel);
10610
+ const abs = resolve4(root, rel);
10602
10611
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10603
10612
  const contentType = MEDIA_EXTENSIONS[extname(abs).toLowerCase()];
10604
10613
  if (!contentType) return null;
@@ -10606,14 +10615,14 @@ function resolveRawPath(root, rel) {
10606
10615
  }
10607
10616
  function resolveHtmlPath(root, rel) {
10608
10617
  if (!rel || rel.includes("\0")) return null;
10609
- const abs = resolve5(root, rel);
10618
+ const abs = resolve4(root, rel);
10610
10619
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10611
10620
  if (DOC_EXTENSIONS[extname(abs).toLowerCase()] !== "html") return null;
10612
10621
  return abs;
10613
10622
  }
10614
10623
  function resolveDownloadPath(root, rel) {
10615
10624
  if (!rel || rel.includes("\0")) return null;
10616
- const abs = resolve5(root, rel);
10625
+ const abs = resolve4(root, rel);
10617
10626
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10618
10627
  if (!isListableFile(basename6(abs))) return null;
10619
10628
  const contentType = MEDIA_EXTENSIONS[extname(abs).toLowerCase()] ?? "application/octet-stream";
@@ -10654,7 +10663,7 @@ function saveUploadedFile(root, relDir, name, data) {
10654
10663
  if (data.byteLength > MAX_UPLOAD_BYTES) return { status: "too-large" };
10655
10664
  const final = uniqueName(absDir, safe);
10656
10665
  const abs = join18(absDir, final);
10657
- writeFileSync7(abs, data);
10666
+ writeFileSync8(abs, data);
10658
10667
  return { status: "ok", path: relative(root, abs).split(sep).join("/"), name: final };
10659
10668
  }
10660
10669
  function createDocFolder(root, relDir, name) {
@@ -10692,7 +10701,7 @@ function createDocFile(root, relDir, name, content) {
10692
10701
  if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) return { status: "too-large" };
10693
10702
  const abs = join18(absDir, safe);
10694
10703
  try {
10695
- writeFileSync7(abs, content, { encoding: "utf8", flag: "wx" });
10704
+ writeFileSync8(abs, content, { encoding: "utf8", flag: "wx" });
10696
10705
  } catch (err) {
10697
10706
  if (err.code === "EEXIST") return { status: "exists" };
10698
10707
  throw err;
@@ -10701,7 +10710,7 @@ function createDocFile(root, relDir, name, content) {
10701
10710
  }
10702
10711
  function moveDocEntry(root, from, to) {
10703
10712
  if (!from || !to || from.includes("\0") || to.includes("\0")) return { status: "not-found" };
10704
- const absFrom = resolve5(root, from);
10713
+ const absFrom = resolve4(root, from);
10705
10714
  if (!absFrom.startsWith(root + sep)) return { status: "not-found" };
10706
10715
  let stat;
10707
10716
  try {
@@ -10711,7 +10720,7 @@ function moveDocEntry(root, from, to) {
10711
10720
  }
10712
10721
  const isDir2 = stat.isDirectory();
10713
10722
  if (!isDir2 && !stat.isFile()) return { status: "not-found" };
10714
- const absTo = resolve5(root, to);
10723
+ const absTo = resolve4(root, to);
10715
10724
  if (!absTo.startsWith(root + sep)) return { status: "bad-target" };
10716
10725
  const name = sanitizeUploadName(basename6(absTo));
10717
10726
  if (!name) return { status: "bad-target" };
@@ -10741,7 +10750,7 @@ function moveDocEntry(root, from, to) {
10741
10750
  }
10742
10751
  function deleteDocEntry(root, path) {
10743
10752
  if (!path || path.includes("\0")) return { status: "not-found" };
10744
- const abs = resolve5(root, path);
10753
+ const abs = resolve4(root, path);
10745
10754
  if (!abs.startsWith(root + sep)) return { status: "not-found" };
10746
10755
  let stat;
10747
10756
  try {
@@ -10751,7 +10760,7 @@ function deleteDocEntry(root, path) {
10751
10760
  }
10752
10761
  const isDir2 = stat.isDirectory();
10753
10762
  if (!isDir2 && !stat.isFile() && !stat.isSymbolicLink()) return { status: "not-found" };
10754
- rmSync5(abs, { recursive: isDir2 });
10763
+ rmSync6(abs, { recursive: isDir2 });
10755
10764
  return {
10756
10765
  status: "ok",
10757
10766
  path: relative(root, abs).split(sep).join("/"),
@@ -10790,7 +10799,7 @@ function createDocsRouter() {
10790
10799
  }
10791
10800
  if (!stat.isFile()) return c.json({ error: "not found" }, 404);
10792
10801
  if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
10793
- const data = readFileSync11(resolved2.abs);
10802
+ const data = readFileSync12(resolved2.abs);
10794
10803
  const headers = { "Content-Type": resolved2.contentType };
10795
10804
  if (download) {
10796
10805
  const filename = basename6(resolved2.abs).replace(/"/g, "");
@@ -10810,7 +10819,7 @@ function createDocsRouter() {
10810
10819
  }
10811
10820
  if (!stat.isFile()) return c.json({ error: "not found" }, 404);
10812
10821
  if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
10813
- return c.body(new Uint8Array(readFileSync11(abs)), 200, {
10822
+ return c.body(new Uint8Array(readFileSync12(abs)), 200, {
10814
10823
  "Content-Type": "text/html; charset=utf-8",
10815
10824
  "Content-Security-Policy": HTML_SHARE_CSP,
10816
10825
  "X-Content-Type-Options": "nosniff",
@@ -10995,7 +11004,7 @@ function createDocsRouter() {
10995
11004
  title: kind ? extractTitle(abs, kind) : basename6(abs),
10996
11005
  mtime: stat.mtime.toISOString(),
10997
11006
  tooLarge: tooLarge || void 0,
10998
- content: tooLarge ? "" : readFileSync11(abs, "utf8")
11007
+ content: tooLarge ? "" : readFileSync12(abs, "utf8")
10999
11008
  });
11000
11009
  });
11001
11010
  app.put("/:id/docs/content", async (c) => {
@@ -11038,7 +11047,7 @@ function createDocsRouter() {
11038
11047
  channel,
11039
11048
  rel,
11040
11049
  title: extractTitle(abs, "md"),
11041
- content: readFileSync11(abs, "utf8")
11050
+ content: readFileSync12(abs, "utf8")
11042
11051
  };
11043
11052
  };
11044
11053
  app.get("/:id/docs/translation/:lang", async (c) => {
@@ -11234,13 +11243,13 @@ function relay(agent, pathOf) {
11234
11243
  }
11235
11244
 
11236
11245
  // src/dashboard/skills-routes.ts
11237
- import { existsSync as existsSync19, statSync as statSync7 } from "fs";
11246
+ import { existsSync as existsSync18, statSync as statSync7 } from "fs";
11238
11247
  import { dirname as dirname6 } from "path";
11239
11248
  import { Hono as Hono12 } from "hono";
11240
11249
 
11241
11250
  // src/dashboard/skill-mirror.ts
11242
11251
  import { createHash as createHash8 } from "crypto";
11243
- import { existsSync as existsSync18, statSync as statSync6 } from "fs";
11252
+ import { existsSync as existsSync17, statSync as statSync6 } from "fs";
11244
11253
  var SYNC_INTERVAL_MS = 6e4;
11245
11254
  var UPSERT_BATCH = 50;
11246
11255
  function mirrorScopeOf(ref) {
@@ -11248,7 +11257,7 @@ function mirrorScopeOf(ref) {
11248
11257
  }
11249
11258
  function isDir(path) {
11250
11259
  try {
11251
- return existsSync18(path) && statSync6(path).isDirectory();
11260
+ return existsSync17(path) && statSync6(path).isDirectory();
11252
11261
  } catch {
11253
11262
  return false;
11254
11263
  }
@@ -11389,7 +11398,7 @@ function requireAdmin2(auth) {
11389
11398
  }
11390
11399
  function rootExists(dir) {
11391
11400
  try {
11392
- return existsSync19(dir) && statSync7(dir).isDirectory();
11401
+ return existsSync18(dir) && statSync7(dir).isDirectory();
11393
11402
  } catch {
11394
11403
  return false;
11395
11404
  }
@@ -11578,9 +11587,9 @@ function registerMirroredSkillRoutes(api, agent) {
11578
11587
 
11579
11588
  // src/dashboard/providers.ts
11580
11589
  import { execFileSync as execFileSync2 } from "child_process";
11581
- import { existsSync as existsSync20 } from "fs";
11590
+ import { existsSync as existsSync19 } from "fs";
11582
11591
  import { homedir as homedir8 } from "os";
11583
- import { join as join19, resolve as resolve6 } from "path";
11592
+ import { join as join19, resolve as resolve5 } from "path";
11584
11593
  var PROVIDER_LABELS = {
11585
11594
  claude: "Claude Code",
11586
11595
  codex: "Codex"
@@ -11597,8 +11606,8 @@ function binaryOnPath(bin) {
11597
11606
  }
11598
11607
  function claudeAuthAvailable() {
11599
11608
  if (profileEnv("CLAUDE_CODE_OAUTH_TOKEN")?.trim()) return true;
11600
- const configDir2 = profileClaudeConfigDir() || resolve6(homedir8(), ".claude");
11601
- if (existsSync20(join19(configDir2, ".credentials.json"))) return true;
11609
+ const configDir2 = profileClaudeConfigDir() || resolve5(homedir8(), ".claude");
11610
+ if (existsSync19(join19(configDir2, ".credentials.json"))) return true;
11602
11611
  if (process.platform !== "darwin") return false;
11603
11612
  try {
11604
11613
  execFileSync2("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
@@ -11626,13 +11635,23 @@ function probe2(runner) {
11626
11635
  }
11627
11636
  return { runner, label: label2, connected: true };
11628
11637
  }
11638
+ function withAuthFailure(status) {
11639
+ if (!claudeAuthFailed()) return status;
11640
+ return {
11641
+ ...status,
11642
+ connected: false,
11643
+ reason: `Sign-in expired and could not be refreshed. On the Host, run claude setup-token, add the token to ~/.config/flyspace/profiles/${getProfile()}/config.env as CLAUDE_CODE_OAUTH_TOKEN, then restart the Host.`
11644
+ };
11645
+ }
11629
11646
  function providerStatuses() {
11630
11647
  const key = getProfile();
11631
11648
  const hit = cache6.get(key);
11632
- if (hit && Date.now() - hit.at < TTL_MS2) return hit.statuses;
11633
- const statuses = ["claude", "codex"].map(probe2);
11634
- cache6.set(key, { at: Date.now(), statuses });
11635
- return statuses;
11649
+ let statuses = hit && Date.now() - hit.at < TTL_MS2 ? hit.statuses : void 0;
11650
+ if (!statuses) {
11651
+ statuses = ["claude", "codex"].map(probe2);
11652
+ cache6.set(key, { at: Date.now(), statuses });
11653
+ }
11654
+ return statuses.map((st) => st.runner === "claude" && st.connected ? withAuthFailure(st) : st);
11636
11655
  }
11637
11656
 
11638
11657
  // src/dashboard/host-api.ts
@@ -11742,7 +11761,7 @@ function registerHostRoutes(api) {
11742
11761
  const limit = Math.min(Number(c.req.query("limit")) || 200, 2e3);
11743
11762
  const filename = errors ? "custie-error.log" : "custie.log";
11744
11763
  const file = join20(paths.LOG_DIR, filename);
11745
- const exists = existsSync21(file);
11764
+ const exists = existsSync20(file);
11746
11765
  return c.json({
11747
11766
  file,
11748
11767
  exists,
@@ -11752,14 +11771,14 @@ function registerHostRoutes(api) {
11752
11771
  });
11753
11772
  }
11754
11773
  function tailFile(file, limit) {
11755
- if (!existsSync21(file)) return [];
11756
- const lines = readFileSync12(file, "utf-8").split("\n");
11774
+ if (!existsSync20(file)) return [];
11775
+ const lines = readFileSync13(file, "utf-8").split("\n");
11757
11776
  if (lines.at(-1) === "") lines.pop();
11758
11777
  return lines.slice(-limit);
11759
11778
  }
11760
11779
 
11761
11780
  // src/dashboard/data-api.ts
11762
- import { existsSync as existsSync23 } from "fs";
11781
+ import { existsSync as existsSync22 } from "fs";
11763
11782
  import sharp5 from "sharp";
11764
11783
 
11765
11784
  // src/dashboard/automations-routes.ts
@@ -13533,7 +13552,7 @@ function createTranscribeRouter() {
13533
13552
  }
13534
13553
 
13535
13554
  // src/dashboard/avatars-routes.ts
13536
- import { randomUUID as randomUUID2 } from "crypto";
13555
+ import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
13537
13556
  import { Hono as Hono20 } from "hono";
13538
13557
  import sharp4 from "sharp";
13539
13558
  import { PutObjectCommand as PutObjectCommand4, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
@@ -13541,6 +13560,16 @@ var AVATAR_SIZE = 192;
13541
13560
  var MAX_AVATAR_BYTES = 2 * 1024 * 1024;
13542
13561
  var AVATAR_MIME_TYPES = ["image/png", "image/jpeg"];
13543
13562
  var KEY_PREFIX3 = "avatar";
13563
+ var GRAVATAR_TIMEOUT_MS = 5e3;
13564
+ async function fetchGravatar(email) {
13565
+ const hash = createHash10("sha256").update(email.trim().toLowerCase()).digest("hex");
13566
+ const res = await fetch(`https://gravatar.com/avatar/${hash}?s=${AVATAR_SIZE}&d=404`, {
13567
+ signal: AbortSignal.timeout(GRAVATAR_TIMEOUT_MS)
13568
+ }).catch(() => null);
13569
+ if (!res?.ok) return null;
13570
+ const type = res.headers.get("content-type")?.split(";")[0] ?? "";
13571
+ return new File([await res.arrayBuffer()], "gravatar", { type });
13572
+ }
13544
13573
  async function saveAvatarKey(userId, avatarKey) {
13545
13574
  const { data, error: getError } = await getSupabaseClient().auth.admin.getUserById(userId);
13546
13575
  if (getError || !data.user) {
@@ -13602,6 +13631,17 @@ function createAvatarsRouter() {
13602
13631
  }
13603
13632
  return c.json({ avatarUrl: await signAvatarUrl(key) }, 201);
13604
13633
  });
13634
+ app.post("/avatars/gravatar", async (c) => {
13635
+ const ctx = await resolveAuthContext(c);
13636
+ if (!ctx) return c.json({ error: "not signed in" }, 401);
13637
+ if (ctx.avatarKey) return c.json({ avatarUrl: await signAvatarUrl(ctx.avatarKey) });
13638
+ const file = await fetchGravatar(ctx.email);
13639
+ if (!file) return c.json({ avatarUrl: null });
13640
+ const stored = await storeAvatar(file, ctx.userId);
13641
+ if ("error" in stored) return c.json({ avatarUrl: null });
13642
+ await saveAvatarKey(ctx.userId, stored.key);
13643
+ return c.json({ avatarUrl: await signAvatarUrl(stored.key) }, 201);
13644
+ });
13605
13645
  app.delete("/avatars", async (c) => {
13606
13646
  const ctx = await resolveAuthContext(c);
13607
13647
  if (!ctx) return c.json({ error: "not signed in" }, 401);
@@ -14606,7 +14646,7 @@ async function sessionUsageForThreads(workspace, threadTsList) {
14606
14646
  }
14607
14647
 
14608
14648
  // src/dashboard/usage-report.ts
14609
- import { existsSync as existsSync22 } from "fs";
14649
+ import { existsSync as existsSync21 } from "fs";
14610
14650
  function mergeByLabel(buckets) {
14611
14651
  const merged = /* @__PURE__ */ new Map();
14612
14652
  for (const bucket of buckets) {
@@ -14652,7 +14692,7 @@ function toReport(days, rows, labelChannel) {
14652
14692
  };
14653
14693
  }
14654
14694
  function fromLocalStore(days, labelChannel) {
14655
- if (!existsSync22(paths.DB_FILE)) return { ...EMPTY, days };
14695
+ if (!existsSync21(paths.DB_FILE)) return { ...EMPTY, days };
14656
14696
  const window = days === 0 ? void 0 : days;
14657
14697
  const store = new UsageStore(paths.DB_FILE);
14658
14698
  try {
@@ -14694,7 +14734,7 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
14694
14734
  const { totals, byDay } = await channelUsageForWorkspace(workspace, aliasIds, window);
14695
14735
  return { days, totals: totals.runs > 0 ? totals : null, byDay };
14696
14736
  } catch {
14697
- if (!existsSync22(paths.DB_FILE)) return { days, totals: null, byDay: [] };
14737
+ if (!existsSync21(paths.DB_FILE)) return { days, totals: null, byDay: [] };
14698
14738
  const store = new UsageStore(paths.DB_FILE);
14699
14739
  try {
14700
14740
  const targetLabel = labelChannel(channelId);
@@ -14716,7 +14756,7 @@ async function buildThreadUsage(threadTs) {
14716
14756
  if (totals.runs === 0) return { threadTs, totals: null, byModel: [] };
14717
14757
  return { threadTs, totals, byModel };
14718
14758
  } catch {
14719
- if (!existsSync22(paths.DB_FILE)) return { threadTs, totals: null, byModel: [] };
14759
+ if (!existsSync21(paths.DB_FILE)) return { threadTs, totals: null, byModel: [] };
14720
14760
  const store = new UsageStore(paths.DB_FILE);
14721
14761
  try {
14722
14762
  const totals = store.threadTotals(threadTs);
@@ -15076,10 +15116,10 @@ function createOnboardingRouter(agent) {
15076
15116
  var HOST_STATUS_CHANNEL = "host-status";
15077
15117
  var channelReady = null;
15078
15118
  function getHostStatusChannel() {
15079
- channelReady ??= new Promise((resolve8) => {
15119
+ channelReady ??= new Promise((resolve7) => {
15080
15120
  const channel = getSupabaseClient().channel(HOST_STATUS_CHANNEL);
15081
15121
  channel.subscribe((state) => {
15082
- if (state === "SUBSCRIBED") resolve8(channel);
15122
+ if (state === "SUBSCRIBED") resolve7(channel);
15083
15123
  });
15084
15124
  });
15085
15125
  return channelReady;
@@ -15245,7 +15285,7 @@ function registerDataRoutes(api, agent = {}) {
15245
15285
  runs.set(name, { lastRun: run3.lastRunAt.toISOString(), lastStatus: run3.lastStatus });
15246
15286
  }
15247
15287
  } catch {
15248
- if (existsSync23(paths.DB_FILE)) {
15288
+ if (existsSync22(paths.DB_FILE)) {
15249
15289
  const runStore = new AutomationRunStore(paths.DB_FILE);
15250
15290
  try {
15251
15291
  for (const run3 of runStore.listLastRuns()) {
@@ -15307,7 +15347,7 @@ function registerDataRoutes(api, agent = {}) {
15307
15347
  const runs = (await recentRunsForWorkspace(getProfile(), 200)).map(toResponse5);
15308
15348
  return c.json({ runs });
15309
15349
  } catch {
15310
- if (!existsSync23(paths.DB_FILE)) return c.json({ runs: [] });
15350
+ if (!existsSync22(paths.DB_FILE)) return c.json({ runs: [] });
15311
15351
  const runStore = new AutomationRunStore(paths.DB_FILE);
15312
15352
  try {
15313
15353
  const runs = runStore.listRuns(200).map(toResponse5);
@@ -15429,7 +15469,7 @@ function registerDataRoutes(api, agent = {}) {
15429
15469
  } catch {
15430
15470
  latest = /* @__PURE__ */ new Map();
15431
15471
  totals = /* @__PURE__ */ new Map();
15432
- if (existsSync23(paths.DB_FILE)) {
15472
+ if (existsSync22(paths.DB_FILE)) {
15433
15473
  const usageStore = new UsageStore(paths.DB_FILE);
15434
15474
  try {
15435
15475
  latest = usageStore.latestByThread(threadTsList);
@@ -15488,24 +15528,24 @@ import { Hono as Hono29 } from "hono";
15488
15528
 
15489
15529
  // src/dashboard/agent-bridge.ts
15490
15530
  import { randomBytes as randomBytes8, randomUUID as randomUUID4, sign as signRaw, timingSafeEqual as timingSafeEqual2, verify as verifyRaw } from "crypto";
15491
- import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
15531
+ import { existsSync as existsSync24, readFileSync as readFileSync17 } from "fs";
15492
15532
  import dotenv from "dotenv";
15493
15533
  import { WebSocket, WebSocketServer } from "ws";
15494
15534
 
15495
15535
  // src/dashboard/host-identity.ts
15496
15536
  import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
15497
- import { existsSync as existsSync24, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
15537
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
15498
15538
  import { dirname as dirname7, join as join21 } from "path";
15499
15539
  function hostPrivateKeyPath() {
15500
15540
  return process.env["FLYSPACE_HOST_KEY_PATH"] || join21(paths.BASE_CONFIG_DIR, "host.key");
15501
15541
  }
15502
15542
  function loadHostPrivateKey(path = hostPrivateKeyPath()) {
15503
- if (!existsSync24(path)) {
15543
+ if (!existsSync23(path)) {
15504
15544
  throw new Error(
15505
15545
  `No Host identity at ${path}. Run \`pnpm run generate-host-key\` on this machine, then set FLYSPACE_HOST_PUBLIC_KEY on the Data process to the printed public key.`
15506
15546
  );
15507
15547
  }
15508
- return createPrivateKey2(readFileSync13(path, "utf8"));
15548
+ return createPrivateKey2(readFileSync14(path, "utf8"));
15509
15549
  }
15510
15550
  function loadHostPublicKey() {
15511
15551
  const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
@@ -15521,7 +15561,7 @@ function loadHostPublicKey() {
15521
15561
  // src/dashboard/host-lock.ts
15522
15562
  import { execFileSync as execFileSync3 } from "child_process";
15523
15563
  import { randomBytes as randomBytes5 } from "crypto";
15524
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
15564
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
15525
15565
  import { join as join22 } from "path";
15526
15566
  function hostLockPath() {
15527
15567
  return join22(paths.BASE_DATA_DIR, "host.lock");
@@ -15539,7 +15579,7 @@ function isLiveFlyspace(pid) {
15539
15579
  }
15540
15580
  function readLockPid(path) {
15541
15581
  try {
15542
- const pid = Number(JSON.parse(readFileSync14(path, "utf8")).pid);
15582
+ const pid = Number(JSON.parse(readFileSync15(path, "utf8")).pid);
15543
15583
  return Number.isInteger(pid) && pid > 0 ? pid : null;
15544
15584
  } catch {
15545
15585
  return null;
@@ -15555,9 +15595,9 @@ function acquireHostLock() {
15555
15595
  });
15556
15596
  for (let attempt = 0; attempt < 2; attempt++) {
15557
15597
  try {
15558
- writeFileSync9(path, body, { flag: "wx", mode: 384 });
15598
+ writeFileSync10(path, body, { flag: "wx", mode: 384 });
15559
15599
  const release2 = () => {
15560
- if (readLockPid(path) === process.pid) rmSync6(path, { force: true });
15600
+ if (readLockPid(path) === process.pid) rmSync7(path, { force: true });
15561
15601
  };
15562
15602
  process.once("exit", release2);
15563
15603
  return release2;
@@ -15571,7 +15611,7 @@ function acquireHostLock() {
15571
15611
  `Another Flyspace Host is already running on this machine (pid ${holder}). Stop it first, or check for a second CLI install (\`which -a flyspace\`). Two Hosts overwrite each other's skill mirror.`
15572
15612
  );
15573
15613
  }
15574
- rmSync6(path, { force: true });
15614
+ rmSync7(path, { force: true });
15575
15615
  }
15576
15616
  }
15577
15617
  throw new Error(`Could not take the Host lock at ${path}`);
@@ -15580,13 +15620,13 @@ function publishHostEndpoint(port) {
15580
15620
  const path = hostLockPath();
15581
15621
  if (readLockPid(path) !== process.pid) return null;
15582
15622
  const token = randomBytes5(32).toString("hex");
15583
- const lock = JSON.parse(readFileSync14(path, "utf8"));
15584
- writeFileSync9(path, JSON.stringify({ ...lock, port, token }), { mode: 384 });
15623
+ const lock = JSON.parse(readFileSync15(path, "utf8"));
15624
+ writeFileSync10(path, JSON.stringify({ ...lock, port, token }), { mode: 384 });
15585
15625
  return token;
15586
15626
  }
15587
15627
  function readHostEndpoint() {
15588
15628
  try {
15589
- const { pid, port, token } = JSON.parse(readFileSync14(hostLockPath(), "utf8"));
15629
+ const { pid, port, token } = JSON.parse(readFileSync15(hostLockPath(), "utf8"));
15590
15630
  if (pid === process.pid || !Number.isInteger(port) || typeof token !== "string") return null;
15591
15631
  return { port, token };
15592
15632
  } catch {
@@ -15595,12 +15635,12 @@ function readHostEndpoint() {
15595
15635
  }
15596
15636
 
15597
15637
  // src/store/device-store.ts
15598
- import { createHash as createHash10, createPublicKey as createPublicKey3, randomBytes as randomBytes6, randomInt } from "crypto";
15638
+ import { createHash as createHash11, createPublicKey as createPublicKey3, randomBytes as randomBytes6, randomInt } from "crypto";
15599
15639
  var DEVICE_CODE_TTL_MS = 10 * 6e4;
15600
15640
  var ACCESS_TOKEN_TTL_MS = 15 * 6e4;
15601
15641
  var CHALLENGE_TTL_MS = 2 * 6e4;
15602
15642
  var POLL_INTERVAL_S = 5;
15603
- var sha256 = (s) => createHash10("sha256").update(s).digest("hex");
15643
+ var sha256 = (s) => createHash11("sha256").update(s).digest("hex");
15604
15644
  function parseEd25519PublicKey(b64) {
15605
15645
  if (typeof b64 !== "string" || b64.length > 200) return null;
15606
15646
  try {
@@ -15826,7 +15866,7 @@ async function userEmail(userId) {
15826
15866
 
15827
15867
  // src/dashboard/doc-share-publisher.ts
15828
15868
  import { randomBytes as randomBytes7 } from "crypto";
15829
- import { readFileSync as readFileSync15, statSync as statSync9 } from "fs";
15869
+ import { readFileSync as readFileSync16, statSync as statSync9 } from "fs";
15830
15870
  import { extname as extname2, posix } from "path";
15831
15871
  import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
15832
15872
 
@@ -16013,7 +16053,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
16013
16053
  }
16014
16054
  if (!stat.isFile()) return { status: "not-found" };
16015
16055
  if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
16016
- const source = readFileSync15(abs, "utf8");
16056
+ const source = readFileSync16(abs, "utf8");
16017
16057
  const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
16018
16058
  if (kind === "excalidraw") {
16019
16059
  try {
@@ -16093,7 +16133,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
16093
16133
  let text = null;
16094
16134
  try {
16095
16135
  const size = statSync9(docAbs).size;
16096
- if (size <= budget) text = readFileSync15(docAbs, "utf8");
16136
+ if (size <= budget) text = readFileSync16(docAbs, "utf8");
16097
16137
  } catch {
16098
16138
  continue;
16099
16139
  }
@@ -16110,7 +16150,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
16110
16150
  let body;
16111
16151
  try {
16112
16152
  if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
16113
- body = readFileSync15(resolved2.abs);
16153
+ body = readFileSync16(resolved2.abs);
16114
16154
  } catch {
16115
16155
  continue;
16116
16156
  }
@@ -16201,8 +16241,12 @@ var METHODS = {
16201
16241
  agent_usage: ["GET", "HEAD", "POST"],
16202
16242
  skill_files: ALL,
16203
16243
  threads: ["GET", "HEAD", "POST", "PATCH"],
16204
- thread_replies: ["GET", "HEAD", "POST", "PATCH"]
16244
+ thread_replies: ["GET", "HEAD", "POST", "PATCH"],
16245
+ memberships: ["GET", "HEAD"],
16246
+ channel_members: ["GET", "HEAD"],
16247
+ channel_docs_access: ["GET", "HEAD"]
16205
16248
  };
16249
+ var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members", "channel_docs_access"]);
16206
16250
  var FORWARDED_HEADERS = ["accept", "content-type", "prefer", "range"];
16207
16251
  var deny = (message, status = 403) => ({
16208
16252
  status,
@@ -16245,6 +16289,13 @@ async function ownedChannelIds(forward, slugs) {
16245
16289
  if (!rows) return null;
16246
16290
  return rows.flatMap((r) => [r.id, r.external_channel_id].filter((v) => !!v));
16247
16291
  }
16292
+ async function ownedWorkspaceIds(forward, slugs) {
16293
+ const rows = await getJson(
16294
+ forward,
16295
+ `/rest/v1/workspaces?select=id&slug=${encodeURIComponent(inList(slugs))}`
16296
+ );
16297
+ return rows ? rows.map((r) => r.id) : null;
16298
+ }
16248
16299
  async function threadsOwned(forward, threadIds, channelIds) {
16249
16300
  const wanted = [...new Set(threadIds)];
16250
16301
  if (wanted.length === 0 || channelIds.length === 0) return false;
@@ -16302,10 +16353,16 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
16302
16353
  }
16303
16354
  }
16304
16355
  params.append(slugColumn, inList(scope));
16356
+ } else if (table === "memberships") {
16357
+ const workspaceIds = await ownedWorkspaceIds(forward, workspaces);
16358
+ if (!workspaceIds) return deny("Could not resolve the device workspaces.", 502);
16359
+ params.append("workspace_id", inList(workspaceIds));
16305
16360
  } else {
16306
16361
  const channelIds = await ownedChannelIds(forward, workspaces);
16307
16362
  if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
16308
- if (table === "threads") {
16363
+ if (CHANNEL_SCOPED.has(table)) {
16364
+ params.append("channel_id", inList(channelIds));
16365
+ } else if (table === "threads") {
16309
16366
  if (method === "POST") {
16310
16367
  const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
16311
16368
  if (!ok3) return deny("A thread must belong to a channel of the device workspaces.");
@@ -16390,14 +16447,14 @@ function handleHostDbResult(id, res) {
16390
16447
  }
16391
16448
  function whenConnected() {
16392
16449
  if (sender) return Promise.resolve(sender);
16393
- return new Promise((resolve8, reject) => {
16450
+ return new Promise((resolve7, reject) => {
16394
16451
  const timer2 = setTimeout(() => {
16395
16452
  onSender = onSender.filter((w) => w !== wake);
16396
16453
  reject(new Error("Host is not connected to the Data API"));
16397
16454
  }, REQUEST_TIMEOUT_MS3);
16398
16455
  const wake = () => {
16399
16456
  clearTimeout(timer2);
16400
- resolve8(sender);
16457
+ resolve7(sender);
16401
16458
  };
16402
16459
  onSender.push(wake);
16403
16460
  });
@@ -16405,12 +16462,12 @@ function whenConnected() {
16405
16462
  async function request(req) {
16406
16463
  const send2 = await whenConnected();
16407
16464
  const id = randomUUID3();
16408
- return new Promise((resolve8, reject) => {
16465
+ return new Promise((resolve7, reject) => {
16409
16466
  const timer2 = setTimeout(() => {
16410
16467
  pending.delete(id);
16411
16468
  reject(new Error("Data API did not answer in time"));
16412
16469
  }, REQUEST_TIMEOUT_MS3);
16413
- pending.set(id, { resolve: resolve8, reject, timer: timer2 });
16470
+ pending.set(id, { resolve: resolve7, reject, timer: timer2 });
16414
16471
  send2({ type: "db", id, req });
16415
16472
  });
16416
16473
  }
@@ -16470,7 +16527,7 @@ function dataSocketUrl() {
16470
16527
  return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
16471
16528
  }
16472
16529
  function listLocalSessions() {
16473
- if (!existsSync25(paths.DB_FILE)) return [];
16530
+ if (!existsSync24(paths.DB_FILE)) return [];
16474
16531
  const store = new SessionStore(paths.DB_FILE);
16475
16532
  try {
16476
16533
  return store.listSessions();
@@ -16507,7 +16564,7 @@ function buildHostConfigSnapshot() {
16507
16564
  const snapshot3 = {};
16508
16565
  for (const profile of listProfiles()) {
16509
16566
  snapshot3[profile] = runWithProfile(profile, () => ({
16510
- env: existsSync25(paths.CONFIG_FILE) ? dotenv.parse(readFileSync16(paths.CONFIG_FILE, "utf-8")) : {},
16567
+ env: existsSync24(paths.CONFIG_FILE) ? dotenv.parse(readFileSync17(paths.CONFIG_FILE, "utf-8")) : {},
16511
16568
  agentModel: loadDashboardSettings().agent
16512
16569
  }));
16513
16570
  }
@@ -16718,16 +16775,16 @@ function callHost(method, args) {
16718
16775
  return Promise.resolve(null);
16719
16776
  }
16720
16777
  const id = randomUUID4();
16721
- return new Promise((resolve8) => {
16778
+ return new Promise((resolve7) => {
16722
16779
  const timer2 = setTimeout(() => {
16723
16780
  pending2.delete(id);
16724
16781
  console.error(`[host-socket] ${method} -> timed out`);
16725
- resolve8(null);
16782
+ resolve7(null);
16726
16783
  }, CALL_TIMEOUT_MS);
16727
16784
  pending2.set(id, {
16728
16785
  resolve: (value) => {
16729
16786
  clearTimeout(timer2);
16730
- resolve8(value);
16787
+ resolve7(value);
16731
16788
  }
16732
16789
  });
16733
16790
  send(ws, { type: "call", id, method, profile, args });
@@ -17549,10 +17606,10 @@ var WEB_DIST = join23(paths.PACKAGE_ROOT, "web", "dist");
17549
17606
  var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
17550
17607
  async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
17551
17608
  for (let port = start; port < start + attempts; port++) {
17552
- const free = await new Promise((resolve8) => {
17609
+ const free = await new Promise((resolve7) => {
17553
17610
  const probe3 = createServer();
17554
- probe3.once("error", () => resolve8(false));
17555
- probe3.once("listening", () => probe3.close(() => resolve8(true)));
17611
+ probe3.once("error", () => resolve7(false));
17612
+ probe3.once("listening", () => probe3.close(() => resolve7(true)));
17556
17613
  probe3.listen(port, host);
17557
17614
  });
17558
17615
  if (free) return port;
@@ -17619,14 +17676,14 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
17619
17676
  } else {
17620
17677
  app.route("/share", createPublicShareAssetRouter(WEB_DIST));
17621
17678
  app.route("/", createAppIconRouter());
17622
- if (existsSync26(WEB_DIST) || existsSync26(join23(paths.PACKAGE_ROOT, "web", "package.json"))) {
17679
+ if (existsSync25(WEB_DIST) || existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"))) {
17623
17680
  app.use("/*", serveStatic({ root: WEB_DIST }));
17624
17681
  }
17625
17682
  app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
17626
17683
  const spaFallback = serveStatic({ path: join23(WEB_DIST, "index.html") });
17627
17684
  app.get("*", async (c, next) => {
17628
- if (!existsSync26(join23(WEB_DIST, "index.html"))) {
17629
- const isCheckout = existsSync26(join23(paths.PACKAGE_ROOT, "web", "package.json"));
17685
+ if (!existsSync25(join23(WEB_DIST, "index.html"))) {
17686
+ const isCheckout = existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"));
17630
17687
  return c.text(
17631
17688
  isCheckout ? `Workspace UI build missing (${WEB_DIST}).
17632
17689
  Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
@@ -18064,7 +18121,7 @@ async function threadReplies(client2, nameOrId, ts2) {
18064
18121
  async function postMessage(client2, args) {
18065
18122
  const channelArg = getArg2(args, "--channel");
18066
18123
  const textFilePath = getArg2(args, "--text-file");
18067
- const text = textFilePath ? readFileSync17(textFilePath, "utf8") : getArg2(args, "--text");
18124
+ const text = textFilePath ? readFileSync18(textFilePath, "utf8") : getArg2(args, "--text");
18068
18125
  const threadTs = getArg2(args, "--thread");
18069
18126
  const markdown = args.includes("--markdown");
18070
18127
  const broadcast = args.includes("--broadcast") && Boolean(threadTs);
@@ -18159,10 +18216,10 @@ function hasClaudeBinary() {
18159
18216
  }
18160
18217
  }
18161
18218
  function defaultClaudeConfigDir() {
18162
- return process.env.CLAUDE_CONFIG_DIR || resolve7(homedir9(), ".claude");
18219
+ return process.env.CLAUDE_CONFIG_DIR || resolve6(homedir9(), ".claude");
18163
18220
  }
18164
18221
  function hasClaudeCredentials(configDir2) {
18165
- if (existsSync27(resolve7(configDir2, ".credentials.json"))) {
18222
+ if (existsSync26(resolve6(configDir2, ".credentials.json"))) {
18166
18223
  return true;
18167
18224
  }
18168
18225
  if (process.platform === "darwin") {
@@ -18199,7 +18256,7 @@ async function ensureClaudeCodeReady() {
18199
18256
  console.log(" Flyspace spawns Claude Code as a subprocess. Without credentials, the bot");
18200
18257
  console.log(" will prompt /login inside Slack at runtime and you cannot complete the");
18201
18258
  console.log(" browser sign-in from there.\n");
18202
- if (claudeConfigDir !== resolve7(homedir9(), ".claude")) {
18259
+ if (claudeConfigDir !== resolve6(homedir9(), ".claude")) {
18203
18260
  console.log(" Tip: run with the same CLAUDE_CONFIG_DIR you use for this Claude Code install:");
18204
18261
  console.log(` \x1B[1mCLAUDE_CONFIG_DIR=${claudeConfigDir} claude\x1B[0m
18205
18262
  `);
@@ -18207,7 +18264,7 @@ async function ensureClaudeCodeReady() {
18207
18264
  console.log(" Open a new terminal and run \x1B[1mclaude\x1B[0m to complete the sign-in flow,");
18208
18265
  console.log(" then come back here.\n");
18209
18266
  }
18210
- const proceed = await confirm3({
18267
+ const proceed = await confirm4({
18211
18268
  message: "Continue setup anyway?",
18212
18269
  default: false
18213
18270
  });
@@ -18218,8 +18275,8 @@ async function ensureClaudeCodeReady() {
18218
18275
  return { claudeConfigDir };
18219
18276
  }
18220
18277
  function getManifestYaml(botName) {
18221
- const manifestPath = resolve7(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
18222
- const raw = readFileSync17(manifestPath, "utf-8");
18278
+ const manifestPath = resolve6(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
18279
+ const raw = readFileSync18(manifestPath, "utf-8");
18223
18280
  return raw.replace(/name: Custie/g, `name: ${botName}`);
18224
18281
  }
18225
18282
  async function stepPersonalise() {
@@ -18432,7 +18489,7 @@ async function stepBrowserTokens(botName) {
18432
18489
  })
18433
18490
  ));
18434
18491
  const DEBUG = process.argv.includes("--debug");
18435
- const USER_DATA_DIR = resolve7(tmpdir4(), "custie-playwright-profile");
18492
+ const USER_DATA_DIR = resolve6(tmpdir4(), "custie-playwright-profile");
18436
18493
  let context;
18437
18494
  try {
18438
18495
  log4("Opening browser \u2014 a Slack login page will appear.\n");
@@ -18464,14 +18521,14 @@ async function stepBrowserTokens(botName) {
18464
18521
  const appLink = page2.locator('a[href*="/apps/A"]').first();
18465
18522
  const hasExistingApp = await appLink.isVisible({ timeout: 3e3 }).catch(() => false);
18466
18523
  if (hasExistingApp) {
18467
- const tokenPromise = new Promise((resolve8) => {
18524
+ const tokenPromise = new Promise((resolve7) => {
18468
18525
  page2.on("request", (req) => {
18469
18526
  if (token) return;
18470
18527
  const postData = req.postData() || "";
18471
18528
  const match = postData.match(/xoxc-[a-f0-9-]+/);
18472
18529
  if (match) {
18473
18530
  token = match[0];
18474
- resolve8(token);
18531
+ resolve7(token);
18475
18532
  }
18476
18533
  });
18477
18534
  });
@@ -18522,7 +18579,7 @@ async function stepBrowserTokens(botName) {
18522
18579
  log4("Almost done! The browser will now open an install page.\n");
18523
18580
  console.log(' \x1B[1mPlease click "Install to Workspace", then click "Allow".\x1B[0m\n');
18524
18581
  await page2.goto(installUrl);
18525
- const installPage = await new Promise((resolve8, reject) => {
18582
+ const installPage = await new Promise((resolve7, reject) => {
18526
18583
  let settled = false;
18527
18584
  let interval;
18528
18585
  let timeout;
@@ -18535,7 +18592,7 @@ async function stepBrowserTokens(botName) {
18535
18592
  };
18536
18593
  const checkUrl = (p) => {
18537
18594
  if (p.url().includes("install-on-team?success=1")) {
18538
- finish(() => resolve8(p));
18595
+ finish(() => resolve7(p));
18539
18596
  }
18540
18597
  };
18541
18598
  context.on("page", (newPage) => {
@@ -18594,7 +18651,7 @@ async function stepBrowserTokens(botName) {
18594
18651
  try {
18595
18652
  const pages = context?.pages();
18596
18653
  if (pages?.[0]) {
18597
- const screenshotPath = resolve7(paths.PACKAGE_ROOT, "setup-error.png");
18654
+ const screenshotPath = resolve6(paths.PACKAGE_ROOT, "setup-error.png");
18598
18655
  await pages[0].screenshot({ path: screenshotPath, fullPage: true });
18599
18656
  warn4(`Screenshot saved to: ${screenshotPath}`);
18600
18657
  }
@@ -18636,25 +18693,23 @@ function writeConfigFile(config) {
18636
18693
  `OWNER_USER_ID=${config.ownerUserId}`,
18637
18694
  ``
18638
18695
  ].join("\n");
18639
- writeFileSync10(paths.CONFIG_FILE, envContent);
18696
+ writeFileSync11(paths.CONFIG_FILE, envContent);
18640
18697
  success4(`Config written to ${paths.CONFIG_FILE}`);
18641
18698
  }
18642
18699
  function printNextSteps() {
18643
18700
  log4("Setup complete! Next steps:\n");
18644
- console.log(" 1. Customise the system prompt (optional):");
18645
- console.log(` Run \x1B[1mflyspace prompt\x1B[0m to edit ${paths.PROMPT_FILE}
18646
- `);
18701
+ console.log(" 1. Customise the system prompt (optional) in Settings \u2192 Prompt.\n");
18647
18702
  console.log(" 2. Start the bot:");
18648
18703
  console.log(" Run \x1B[1mflyspace start\x1B[0m\n");
18649
18704
  console.log(" 3. Install as a background service (optional):");
18650
- console.log(" Run \x1B[1mflyspace install\x1B[0m\n");
18705
+ console.log(" Run \x1B[1mflyspace service install\x1B[0m\n");
18651
18706
  }
18652
18707
  function copyDefaultPrompt(botName) {
18653
- if (!existsSync27(paths.PROMPT_FILE)) {
18654
- const defaultPrompt = resolve7(paths.PACKAGE_ROOT, "system.default.md");
18655
- if (existsSync27(defaultPrompt)) {
18656
- const content = readFileSync17(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
18657
- writeFileSync10(paths.PROMPT_FILE, content);
18708
+ if (!existsSync26(paths.PROMPT_FILE)) {
18709
+ const defaultPrompt = resolve6(paths.PACKAGE_ROOT, "system.default.md");
18710
+ if (existsSync26(defaultPrompt)) {
18711
+ const content = readFileSync18(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
18712
+ writeFileSync11(paths.PROMPT_FILE, content);
18658
18713
  success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
18659
18714
  }
18660
18715
  }
@@ -18666,8 +18721,8 @@ async function runSlackSetup(args) {
18666
18721
  const prereq = await ensureClaudeCodeReady();
18667
18722
  if (!prereq) return;
18668
18723
  const { claudeConfigDir } = prereq;
18669
- if (existsSync27(paths.CONFIG_FILE)) {
18670
- const reconfigure = await confirm3({
18724
+ if (existsSync26(paths.CONFIG_FILE)) {
18725
+ const reconfigure = await confirm4({
18671
18726
  message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
18672
18727
  default: false
18673
18728
  });
@@ -18976,7 +19031,85 @@ async function runChatCmd(args) {
18976
19031
  }
18977
19032
 
18978
19033
  // src/commands/memory.ts
18979
- import { readFileSync as readFileSync18 } from "fs";
19034
+ import { readFileSync as readFileSync20 } from "fs";
19035
+
19036
+ // src/store/claude-memory-import.ts
19037
+ import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "fs";
19038
+ import { homedir as homedir10 } from "os";
19039
+ import { basename as basename8, join as join24 } from "path";
19040
+ import dotenv2 from "dotenv";
19041
+ function claudeMemoryDir(cwd, configDir2) {
19042
+ return join24(configDir2, "projects", cwd.replaceAll(/[^a-zA-Z0-9]/g, "-"), "memory");
19043
+ }
19044
+ function profileClaudeConfigDirFor(profile) {
19045
+ const dir = profile === "default" ? paths.BASE_CONFIG_DIR : join24(paths.PROFILES_DIR, profile);
19046
+ const file = join24(dir, "config.env");
19047
+ const env = existsSync27(file) ? dotenv2.parse(readFileSync19(file)) : {};
19048
+ return env["CLAUDE_CONFIG_DIR"]?.trim() || join24(homedir10(), ".claude");
19049
+ }
19050
+ var TYPE_MAP = {
19051
+ user: "preference",
19052
+ feedback: "preference",
19053
+ project: "fact",
19054
+ reference: "reference"
19055
+ };
19056
+ var DESCRIPTION_MAX2 = 200;
19057
+ function unquote2(value) {
19058
+ const v = value.trim();
19059
+ return /^(["']).*\1$/.test(v) ? v.slice(1, -1) : v;
19060
+ }
19061
+ function slugify(value) {
19062
+ return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
19063
+ }
19064
+ function parseClaudeMemory(fileName, text, channelId) {
19065
+ const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text.replaceAll("\r\n", "\n"));
19066
+ if (!match) return { skip: "no frontmatter" };
19067
+ const fields = {};
19068
+ for (const line of match[1].split("\n")) {
19069
+ const kv = /^\s*([A-Za-z_]+):\s*(.*)$/.exec(line);
19070
+ if (kv && kv[2] && !(kv[1] in fields)) fields[kv[1]] = unquote2(kv[2]);
19071
+ }
19072
+ const name = slugify(fields["name"] || basename8(fileName, ".md"));
19073
+ let description = (fields["description"] ?? "").replaceAll(/\s+/g, " ").trim();
19074
+ if (description.length > DESCRIPTION_MAX2) {
19075
+ description = `${description.slice(0, DESCRIPTION_MAX2 - 3).trimEnd()}...`;
19076
+ }
19077
+ const input2 = {
19078
+ channelId,
19079
+ name,
19080
+ description,
19081
+ type: TYPE_MAP[fields["type"] ?? ""] ?? "fact",
19082
+ body: match[2].trim(),
19083
+ createdBy: "agent"
19084
+ };
19085
+ try {
19086
+ validateMemoryInput(input2);
19087
+ } catch (err) {
19088
+ return { skip: err.message };
19089
+ }
19090
+ return input2;
19091
+ }
19092
+ async function importClaudeMemories(dir, channelId, opts = {}) {
19093
+ if (!existsSync27(dir)) throw new Error(`No Claude memory directory at ${dir}`);
19094
+ const files = readdirSync5(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
19095
+ const results = [];
19096
+ for (const file of files) {
19097
+ const parsed = parseClaudeMemory(file, readFileSync19(join24(dir, file), "utf-8"), channelId);
19098
+ if ("skip" in parsed) {
19099
+ results.push({ file, outcome: "skipped", reason: parsed.skip });
19100
+ continue;
19101
+ }
19102
+ if (await getChannelMemory(channelId, parsed.name)) {
19103
+ results.push({ file, name: parsed.name, outcome: "exists" });
19104
+ continue;
19105
+ }
19106
+ if (!opts.dryRun) await saveChannelMemory(parsed, "agent");
19107
+ results.push({ file, name: parsed.name, outcome: "imported" });
19108
+ }
19109
+ return results;
19110
+ }
19111
+
19112
+ // src/commands/memory.ts
18980
19113
  var USAGE5 = `
18981
19114
  Usage: flyspace memory <subcommand> --channel <name-or-id> [options]
18982
19115
 
@@ -18991,6 +19124,10 @@ var USAGE5 = `
18991
19124
  --body <text | -> "-" reads the body from stdin
18992
19125
  [--thread <id>] The thread the memory came from
18993
19126
  delete <name-or-id> [--thread <id>] Delete one memory
19127
+ import [--dir <path>] [--dry-run] Copy Claude Code's native auto memory for the
19128
+ channel's folder in; names already present are kept.
19129
+ Defaults to the channel profile's CLAUDE_CONFIG_DIR
19130
+ (or ~/.claude)/projects/<cwd slug>/memory
18994
19131
 
18995
19132
  Types: ${MEMORY_TYPES.join(", ")}
18996
19133
  `;
@@ -19056,7 +19193,7 @@ ${memory.body}`);
19056
19193
  );
19057
19194
  }
19058
19195
  if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
19059
- const body = bodyArg === "-" ? readFileSync18(0, "utf-8") : bodyArg;
19196
+ const body = bodyArg === "-" ? readFileSync20(0, "utf-8") : bodyArg;
19060
19197
  const thread = getArg4(rest, "--thread");
19061
19198
  const { created, changed } = await saveChannelMemory(
19062
19199
  {
@@ -19082,6 +19219,20 @@ ${memory.body}`);
19082
19219
  console.log(`Deleted memory ${ref}.`);
19083
19220
  break;
19084
19221
  }
19222
+ case "import": {
19223
+ const dir = getArg4(rest, "--dir") ?? claudeMemoryDir(channel.cwd, profileClaudeConfigDirFor(channel.profile));
19224
+ const dryRun = rest.includes("--dry-run");
19225
+ const results = await importClaudeMemories(dir, channel.id, { dryRun });
19226
+ for (const r of results) {
19227
+ const detail = r.outcome === "skipped" ? r.reason : r.name;
19228
+ console.log(`${r.outcome.padEnd(8)} ${r.file} ${detail}`);
19229
+ }
19230
+ const count = (o) => results.filter((r) => r.outcome === o).length;
19231
+ console.log(
19232
+ `${dryRun ? "Would import" : "Imported"} ${count("imported")}, ${count("exists")} already present, ${count("skipped")} skipped, from ${dir}.`
19233
+ );
19234
+ break;
19235
+ }
19085
19236
  default:
19086
19237
  console.error(`Unknown subcommand: ${subcommand}`);
19087
19238
  console.log(USAGE5);
@@ -19126,10 +19277,10 @@ async function runMcpCmd(args) {
19126
19277
  }
19127
19278
 
19128
19279
  // src/commands/dashboard.ts
19129
- import { spawn as spawn6, spawnSync } from "child_process";
19280
+ import { spawn as spawn5, spawnSync } from "child_process";
19130
19281
  import { existsSync as existsSync28 } from "fs";
19131
19282
  import { platform as platform7 } from "os";
19132
- import { join as join24 } from "path";
19283
+ import { join as join25 } from "path";
19133
19284
  function flagValue(args, name) {
19134
19285
  const eq = args.find((a) => a.startsWith(`--${name}=`));
19135
19286
  if (eq) return eq.slice(`--${name}=`.length);
@@ -19139,13 +19290,13 @@ function flagValue(args, name) {
19139
19290
  function openBrowser2(url) {
19140
19291
  const cmd = platform7() === "darwin" ? "open" : platform7() === "win32" ? "start" : "xdg-open";
19141
19292
  try {
19142
- spawn6(cmd, [url], { stdio: "ignore", detached: true }).unref();
19293
+ spawn5(cmd, [url], { stdio: "ignore", detached: true }).unref();
19143
19294
  } catch {
19144
19295
  }
19145
19296
  }
19146
19297
  function ensureWebBuild() {
19147
- if (existsSync28(join24(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
19148
- if (!existsSync28(join24(paths.PACKAGE_ROOT, "web", "package.json"))) return;
19298
+ if (existsSync28(join25(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
19299
+ if (!existsSync28(join25(paths.PACKAGE_ROOT, "web", "package.json"))) return;
19149
19300
  console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
19150
19301
  const result = spawnSync("pnpm", ["--dir", "web", "build"], {
19151
19302
  cwd: paths.PACKAGE_ROOT,
@@ -19195,7 +19346,7 @@ async function runDashboard(args) {
19195
19346
  }
19196
19347
 
19197
19348
  // src/commands/workspace.ts
19198
- import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
19349
+ import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
19199
19350
  var USAGE7 = `
19200
19351
  Usage: flyspace workspace <subcommand> [options]
19201
19352
 
@@ -19284,7 +19435,7 @@ function provisionLocal(botName) {
19284
19435
  if (existsSync29(paths.CONFIG_FILE)) {
19285
19436
  console.log(" config.env exists");
19286
19437
  } else {
19287
- writeFileSync11(paths.CONFIG_FILE, `BOT_NAME=${botName}
19438
+ writeFileSync12(paths.CONFIG_FILE, `BOT_NAME=${botName}
19288
19439
  `);
19289
19440
  console.log(` config.env written (${paths.CONFIG_FILE})`);
19290
19441
  }
@@ -19384,18 +19535,16 @@ var USAGE9 = `
19384
19535
  setup Set up this machine as a Host (login, agent CLIs, ~/Flyspace)
19385
19536
  auth Log in, check status, or log out this device
19386
19537
  doctor Check this Host end to end (login, agent CLIs, services)
19387
- start Start the agent host (Slack only if configured)
19388
- install Install as a system service (launchd / systemd)
19389
- uninstall Remove the system service
19538
+ start Start the agent host in the foreground
19539
+ service Run the host in the background (install / uninstall)
19390
19540
  stop Stop the service (without uninstalling)
19391
19541
  restart Restart the service
19392
19542
  logs Tail the service log (-e for error log)
19393
19543
  upgrade Upgrade Flyspace to the latest version
19394
- prompt Edit the system prompt in $EDITOR
19544
+ uninstall Remove Flyspace from this machine (CLI, services, config)
19395
19545
  config Show resolved config (--edit to edit, --path for file path)
19396
19546
  automation Manage scheduled tasks and triggers
19397
19547
  usage Show agent token usage and cost for this workspace
19398
- slack Query Slack (channels, users, post messages)
19399
19548
  chat Inspect dashboard chat channels and move threads between them
19400
19549
  memory Read and write a channel's persistent agent memory
19401
19550
  profiles List local profiles (bound here? scheduler?) or remove one
@@ -19453,11 +19602,20 @@ async function main() {
19453
19602
  case "reconcile":
19454
19603
  await runReconcileCmd(args.slice(1));
19455
19604
  break;
19605
+ case "service":
19606
+ if (args[1] === "install") await runInstall();
19607
+ else if (args[1] === "uninstall") await runServiceUninstall();
19608
+ else {
19609
+ printRaw("Usage: flyspace service <install|uninstall>");
19610
+ process.exit(1);
19611
+ }
19612
+ break;
19613
+ // Pre-rename spelling, kept so existing docs and scripts keep working.
19456
19614
  case "install":
19457
19615
  await runInstall();
19458
19616
  break;
19459
19617
  case "uninstall":
19460
- await runUninstall();
19618
+ await runUninstall(args.slice(1));
19461
19619
  break;
19462
19620
  case "stop":
19463
19621
  await runStop();
@@ -19471,9 +19629,6 @@ async function main() {
19471
19629
  case "upgrade":
19472
19630
  await runUpgrade();
19473
19631
  break;
19474
- case "prompt":
19475
- await runPrompt();
19476
- break;
19477
19632
  case "config":
19478
19633
  await runConfig(args.slice(1));
19479
19634
  break;
@@ -19504,7 +19659,7 @@ async function main() {
19504
19659
  break;
19505
19660
  case "-v":
19506
19661
  case "--version":
19507
- printRaw("0.1.10");
19662
+ printRaw("0.1.12");
19508
19663
  break;
19509
19664
  case "-h":
19510
19665
  case "--help":