flyspace-cli 0.1.9 → 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-JG6LNX5D.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,9 +408,23 @@ 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 } from "fs";
443
- import { join as join2 } from "path";
411
+ import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync } from "fs";
412
+ import { basename, dirname as dirname2, join as join2 } from "path";
444
413
  import { homedir, platform } from "os";
414
+ function flyspaceHome(entry = process.argv[1]) {
415
+ let cliPath;
416
+ try {
417
+ cliPath = realpathSync(entry ?? "");
418
+ } catch {
419
+ return void 0;
420
+ }
421
+ const at = cliPath.indexOf("/lib/node_modules/flyspace-cli/");
422
+ if (at <= 0) return void 0;
423
+ const npmPrefix = cliPath.slice(0, at);
424
+ if (basename(npmPrefix) !== "cli") return void 0;
425
+ const home = dirname2(npmPrefix);
426
+ return existsSync2(join2(home, "bin", "flyspace")) && existsSync2(join2(home, "node", "bin", "node")) ? home : void 0;
427
+ }
445
428
  function serviceLabel(profile) {
446
429
  return profile === DEFAULT_PROFILE ? "ai.flyspace" : `ai.flyspace.${profile}`;
447
430
  }
@@ -464,8 +447,8 @@ function systemdUnitPath(profile) {
464
447
  return join2(homedir(), ".config", "systemd", "user", `${systemdUnitName(profile)}.service`);
465
448
  }
466
449
  function isServiceInstalled(profile) {
467
- if (platform() === "darwin") return existsSync3(plistPath(profile));
468
- if (platform() === "linux") return existsSync3(systemdUnitPath(profile));
450
+ if (platform() === "darwin") return existsSync2(plistPath(profile));
451
+ if (platform() === "linux") return existsSync2(systemdUnitPath(profile));
469
452
  return false;
470
453
  }
471
454
  function serviceBinaryMissing(file) {
@@ -476,7 +459,7 @@ function serviceBinaryMissing(file) {
476
459
  return false;
477
460
  }
478
461
  const bin = text.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/)?.[1] ?? text.match(/^ExecStart=(\S+)/m)?.[1];
479
- return !!bin && !existsSync3(bin);
462
+ return !!bin && !existsSync2(bin);
480
463
  }
481
464
  function profileServiceFile(profile) {
482
465
  return platform() === "darwin" ? plistPath(profile) : systemdUnitPath(profile);
@@ -515,7 +498,7 @@ function hostPlistPath() {
515
498
  return join2(homedir(), "Library", "LaunchAgents", `${HOST_SERVICE_LABEL}.plist`);
516
499
  }
517
500
  function isHostServiceInstalled() {
518
- if (platform() === "darwin" || platform() === "linux") return existsSync3(hostServiceFile());
501
+ if (platform() === "darwin" || platform() === "linux") return existsSync2(hostServiceFile());
519
502
  return false;
520
503
  }
521
504
  function restartHostService() {
@@ -555,9 +538,9 @@ function isHostServiceRunning() {
555
538
  }
556
539
 
557
540
  // src/commands/install.ts
558
- 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";
559
542
  import { execSync as execSync2 } from "child_process";
560
- import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
543
+ import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
561
544
  import { homedir as homedir2, platform as platform2 } from "os";
562
545
  function log(msg) {
563
546
  console.log(`
@@ -578,20 +561,26 @@ function run(cmd, opts = {}) {
578
561
  function detectClaudeBinDir() {
579
562
  try {
580
563
  const claudePath = execSync2("which claude", { encoding: "utf-8", stdio: "pipe" }).trim();
581
- return claudePath ? dirname2(claudePath) : void 0;
564
+ return claudePath ? dirname3(claudePath) : void 0;
582
565
  } catch {
583
566
  return void 0;
584
567
  }
585
568
  }
569
+ function detectNodeBinDir() {
570
+ const home = flyspaceHome();
571
+ return home ? join3(home, "node", "bin") : dirname3(process.execPath);
572
+ }
586
573
  function nodeShimDirs() {
587
574
  return [join3(homedir2(), ".local", "share", "mise", "shims"), join3(homedir2(), ".asdf", "shims")].filter(
588
- (d) => existsSync4(d)
575
+ (d) => existsSync3(d)
589
576
  );
590
577
  }
591
578
  function detectCustieBin() {
579
+ const home = flyspaceHome();
580
+ if (home) return join3(home, "bin", "flyspace");
592
581
  const argv1 = process.argv[1];
593
582
  if (argv1) {
594
- return resolve3(argv1);
583
+ return resolve2(argv1);
595
584
  }
596
585
  try {
597
586
  return run("which flyspace", { silent: true });
@@ -600,7 +589,7 @@ function detectCustieBin() {
600
589
  }
601
590
  }
602
591
  function readEnvFile() {
603
- if (!existsSync4(paths.CONFIG_FILE)) return {};
592
+ if (!existsSync3(paths.CONFIG_FILE)) return {};
604
593
  const vars = {};
605
594
  for (const line of readFileSync4(paths.CONFIG_FILE, "utf-8").split("\n")) {
606
595
  const trimmed = line.trim();
@@ -644,14 +633,13 @@ function hostSpec() {
644
633
  async function installMacOS(spec) {
645
634
  log("Setting up macOS LaunchAgent...");
646
635
  const custieBin = detectCustieBin();
647
- const nodePath = process.execPath;
648
636
  const claudeBinDir = detectClaudeBinDir();
649
637
  const logDir = paths.LOG_DIR;
650
638
  mkdirSync2(logDir, { recursive: true });
651
639
  const envVars = spec.env;
652
- const homebrewBin = existsSync4("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
640
+ const homebrewBin = existsSync3("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
653
641
  const pathParts = [
654
- dirname2(nodePath),
642
+ detectNodeBinDir(),
655
643
  claudeBinDir,
656
644
  ...nodeShimDirs(),
657
645
  homebrewBin,
@@ -695,7 +683,7 @@ ${Object.entries(envVars).map(([k, v]) => ` <key>${k}</key>
695
683
  </dict>
696
684
  </plist>`;
697
685
  const agentPlistPath = spec.plistPath;
698
- mkdirSync2(dirname2(agentPlistPath), { recursive: true });
686
+ mkdirSync2(dirname3(agentPlistPath), { recursive: true });
699
687
  try {
700
688
  run(`launchctl unload "${agentPlistPath}"`, { silent: true });
701
689
  } catch {
@@ -713,14 +701,13 @@ ${Object.entries(envVars).map(([k, v]) => ` <key>${k}</key>
713
701
  async function installLinux(spec) {
714
702
  log("Setting up systemd user service...");
715
703
  const custieBin = detectCustieBin();
716
- const nodePath = process.execPath;
717
704
  const claudeBinDir = detectClaudeBinDir();
718
705
  const logDir = paths.LOG_DIR;
719
706
  mkdirSync2(logDir, { recursive: true });
720
707
  const envLines = Object.entries(spec.env).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
721
- 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;
722
709
  const pathParts = [
723
- dirname2(nodePath),
710
+ detectNodeBinDir(),
724
711
  claudeBinDir,
725
712
  ...nodeShimDirs(),
726
713
  linuxbrewBin,
@@ -778,13 +765,13 @@ async function installHostService() {
778
765
  }
779
766
 
780
767
  // src/commands/profiles.ts
781
- import { existsSync as existsSync5, readdirSync, statSync } from "fs";
768
+ import { existsSync as existsSync4, readdirSync, statSync } from "fs";
782
769
  import { execSync as execSync3 } from "child_process";
783
770
  import { join as join4 } from "path";
784
771
  import { platform as platform3 } from "os";
785
772
  function listProfiles() {
786
773
  const profiles = [];
787
- if (existsSync5(paths.PROFILES_DIR)) {
774
+ if (existsSync4(paths.PROFILES_DIR)) {
788
775
  for (const entry of readdirSync(paths.PROFILES_DIR)) {
789
776
  try {
790
777
  if (statSync(join4(paths.PROFILES_DIR, entry)).isDirectory()) {
@@ -813,8 +800,8 @@ function isServiceRunning2(profile) {
813
800
 
814
801
  // src/auth/device-auth.ts
815
802
  import { createPrivateKey, createPublicKey, generateKeyPairSync, sign } from "crypto";
816
- import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
817
- import { dirname as dirname3, join as join5 } from "path";
803
+ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
804
+ import { dirname as dirname4, join as join5 } from "path";
818
805
  var DEFAULT_API_URL = "https://app.flyspace.ai";
819
806
  var EXPIRY_SKEW_MS = 6e4;
820
807
  var AuthApiError = class extends Error {
@@ -835,9 +822,9 @@ function credentialsPath() {
835
822
  }
836
823
  function ensureDeviceKey(path = deviceKeyPath()) {
837
824
  let created = false;
838
- if (!existsSync6(path)) {
825
+ if (!existsSync5(path)) {
839
826
  const { privateKey: privateKey2 } = generateKeyPairSync("ed25519");
840
- mkdirSync3(dirname3(path), { recursive: true });
827
+ mkdirSync3(dirname4(path), { recursive: true });
841
828
  writeFileSync4(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
842
829
  created = true;
843
830
  }
@@ -848,7 +835,7 @@ function ensureDeviceKey(path = deviceKeyPath()) {
848
835
  return { privateKey, publicKeyBase64, created };
849
836
  }
850
837
  function loadCredentials(path = credentialsPath()) {
851
- if (!existsSync6(path)) return null;
838
+ if (!existsSync5(path)) return null;
852
839
  try {
853
840
  return JSON.parse(readFileSync5(path, "utf8"));
854
841
  } catch {
@@ -856,7 +843,7 @@ function loadCredentials(path = credentialsPath()) {
856
843
  }
857
844
  }
858
845
  function saveCredentials(creds, path = credentialsPath()) {
859
- mkdirSync3(dirname3(path), { recursive: true });
846
+ mkdirSync3(dirname4(path), { recursive: true });
860
847
  writeFileSync4(path, `${JSON.stringify(creds, null, 2)}
861
848
  `, { mode: 384 });
862
849
  }
@@ -992,7 +979,7 @@ async function getAccessToken() {
992
979
  // src/dashboard/onboarding-scan.ts
993
980
  import { execFileSync } from "child_process";
994
981
  import {
995
- existsSync as existsSync7,
982
+ existsSync as existsSync6,
996
983
  lstatSync,
997
984
  mkdirSync as mkdirSync4,
998
985
  readdirSync as readdirSync2,
@@ -1001,7 +988,7 @@ import {
1001
988
  symlinkSync
1002
989
  } from "fs";
1003
990
  import { homedir as homedir3 } from "os";
1004
- import { basename, join as join6, resolve as resolve4 } from "path";
991
+ import { basename as basename2, join as join6, resolve as resolve3 } from "path";
1005
992
  var MAX_JSONL_HEAD_BYTES = 64 * 1024;
1006
993
  var GIT_SCAN_ROOTS = ["Workspaces", "Projects", "code"];
1007
994
  function flyspaceRoot() {
@@ -1023,7 +1010,7 @@ function codexDir() {
1023
1010
  }
1024
1011
  function claudeLoggedIn() {
1025
1012
  if (process.env["ANTHROPIC_API_KEY"]) return true;
1026
- if (existsSync7(join6(claudeDir(), ".credentials.json"))) return true;
1013
+ if (existsSync6(join6(claudeDir(), ".credentials.json"))) return true;
1027
1014
  try {
1028
1015
  const raw = JSON.parse(readFileSync6(join6(homedir3(), ".claude.json"), "utf-8"));
1029
1016
  if (raw.oauthAccount) return true;
@@ -1040,7 +1027,7 @@ function claudeLoggedIn() {
1040
1027
  }
1041
1028
  }
1042
1029
  function codexLoggedIn() {
1043
- return !!process.env["OPENAI_API_KEY"] || existsSync7(join6(codexDir(), "auth.json"));
1030
+ return !!process.env["OPENAI_API_KEY"] || existsSync6(join6(codexDir(), "auth.json"));
1044
1031
  }
1045
1032
  function jsonlFiles(dir, depth) {
1046
1033
  const out = [];
@@ -1114,7 +1101,7 @@ function gitRepos() {
1114
1101
  continue;
1115
1102
  }
1116
1103
  for (const e of entries2) {
1117
- 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));
1118
1105
  }
1119
1106
  }
1120
1107
  return found;
@@ -1139,7 +1126,7 @@ function scanOnboarding() {
1139
1126
  const byPath = /* @__PURE__ */ new Map();
1140
1127
  const history = { claude: false, codex: false };
1141
1128
  const add = (rawPath, source, mtime, sessions, trusted = false) => {
1142
- const path = resolve4(rawPath);
1129
+ const path = resolve3(rawPath);
1143
1130
  if (isExcluded(path)) return;
1144
1131
  const existing = byPath.get(path);
1145
1132
  if (existing) {
@@ -1150,7 +1137,7 @@ function scanOnboarding() {
1150
1137
  } else {
1151
1138
  byPath.set(path, {
1152
1139
  path,
1153
- name: basename(path),
1140
+ name: basename2(path),
1154
1141
  sources: [source],
1155
1142
  lastUsed: mtime,
1156
1143
  sessions,
@@ -1231,7 +1218,7 @@ function linkProjectFolders(slug, items) {
1231
1218
  }
1232
1219
  if (!exists) {
1233
1220
  if (item.path) {
1234
- const target = resolve4(item.path);
1221
+ const target = resolve3(item.path);
1235
1222
  if (!statSync2(target).isDirectory()) throw new Error(`not a directory: ${target}`);
1236
1223
  makeLink(target, dest);
1237
1224
  } else {
@@ -1244,7 +1231,7 @@ function linkProjectFolders(slug, items) {
1244
1231
  }
1245
1232
 
1246
1233
  // src/commands/auth.ts
1247
- import { spawn as spawn3 } from "child_process";
1234
+ import { spawn as spawn2 } from "child_process";
1248
1235
  import { confirm } from "@inquirer/prompts";
1249
1236
  import { hostname, platform as platform4, release } from "os";
1250
1237
  var BOLD = "\x1B[1m";
@@ -1291,7 +1278,7 @@ function openBrowser(url) {
1291
1278
  if (process.env["SSH_CONNECTION"] || process.env["SSH_TTY"]) return false;
1292
1279
  const cmd = platform4() === "darwin" ? "open" : platform4() === "win32" ? "explorer" : "xdg-open";
1293
1280
  try {
1294
- const child = spawn3(cmd, [url], { stdio: "ignore", detached: true });
1281
+ const child = spawn2(cmd, [url], { stdio: "ignore", detached: true });
1295
1282
  child.on("error", () => {
1296
1283
  });
1297
1284
  child.unref();
@@ -1436,7 +1423,7 @@ async function runStatus() {
1436
1423
  const printLocal = (workspaces) => {
1437
1424
  console.log("");
1438
1425
  printIdentity({ ...creds, workspaces }, creds.deviceName);
1439
- console.log(` ${DIM}API${RESET} ${creds.apiUrl}`);
1426
+ if (creds.apiUrl !== DEFAULT_API_URL) console.log(` ${DIM}API${RESET} ${creds.apiUrl}`);
1440
1427
  console.log(` ${DIM}Key${RESET} ${tilde(deviceKeyPath())}`);
1441
1428
  console.log(` ${DIM}Credentials${RESET} ${tilde(credentialsPath())}`);
1442
1429
  };
@@ -1553,7 +1540,7 @@ async function runAuthCmd(args) {
1553
1540
  }
1554
1541
 
1555
1542
  // src/version.ts
1556
- var CLI_VERSION = true ? "0.1.9" : null;
1543
+ var CLI_VERSION = true ? "0.1.12" : null;
1557
1544
  function isOlderVersion(a, b) {
1558
1545
  const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
1559
1546
  const [pa, pb] = [parse2(a), parse2(b)];
@@ -1831,9 +1818,10 @@ ${BOLD3}Flyspace Doctor${RESET3}
1831
1818
  }
1832
1819
 
1833
1820
  // src/commands/uninstall.ts
1834
- 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";
1835
1823
  import { execSync as execSync5 } from "child_process";
1836
- import { basename as basename2, join as join8 } from "path";
1824
+ import { basename as basename3, join as join8 } from "path";
1837
1825
  import { homedir as homedir4, platform as platform5 } from "os";
1838
1826
  function log2(msg) {
1839
1827
  console.log(`
@@ -1848,21 +1836,20 @@ function warn2(msg) {
1848
1836
  function run2(cmd) {
1849
1837
  execSync5(cmd, { encoding: "utf-8", stdio: "pipe" });
1850
1838
  }
1851
- async function runUninstall() {
1839
+ async function runServiceUninstall(profile = getProfile()) {
1852
1840
  const os = platform5();
1853
- const profile = getProfile();
1854
1841
  log2("Uninstalling Flyspace service...");
1855
1842
  if (os === "darwin") {
1856
1843
  const removed = [];
1857
1844
  for (const plist of [plistPath(profile), legacyPlistPath(profile)]) {
1858
- if (!existsSync8(plist)) continue;
1845
+ if (!existsSync7(plist)) continue;
1859
1846
  try {
1860
1847
  run2(`launchctl unload "${plist}"`);
1861
1848
  } catch {
1862
1849
  }
1863
1850
  try {
1864
1851
  unlinkSync2(plist);
1865
- removed.push(basename2(plist));
1852
+ removed.push(basename3(plist));
1866
1853
  } catch {
1867
1854
  }
1868
1855
  }
@@ -1890,11 +1877,51 @@ async function runUninstall() {
1890
1877
  } else {
1891
1878
  warn2(`Unsupported platform: ${os}`);
1892
1879
  }
1893
- 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");
1894
1921
  }
1895
1922
 
1896
1923
  // src/commands/profiles-cmd.ts
1897
- import { rmSync as rmSync3 } from "fs";
1924
+ import { rmSync as rmSync4 } from "fs";
1898
1925
  var USAGE = `
1899
1926
  Usage: flyspace profiles [list]
1900
1927
  flyspace profiles remove <name>
@@ -1951,7 +1978,7 @@ async function removeCmd(name) {
1951
1978
  }
1952
1979
  const { configDir: configDir2, dataDir } = runWithProfile(name, () => ({ configDir: paths.CONFIG_DIR, dataDir: paths.DATA_DIR }));
1953
1980
  if (isServiceInstalled(name)) await runWithProfile(name, runUninstall);
1954
- rmSync3(configDir2, { recursive: true, force: true });
1981
+ rmSync4(configDir2, { recursive: true, force: true });
1955
1982
  console.log(`\x1B[32m+\x1B[0m Local config removed: ${configDir2}`);
1956
1983
  console.log(`
1957
1984
  Kept: ${dataDir} (sessions, logs) and any ~/Flyspace/${name}/ channel folders.`);
@@ -1968,7 +1995,8 @@ async function runProfiles(args = []) {
1968
1995
 
1969
1996
  // src/commands/upgrade.ts
1970
1997
  import { execSync as execSync6 } from "child_process";
1971
- import { realpathSync } from "fs";
1998
+ import { realpathSync as realpathSync2 } from "fs";
1999
+ import { join as join9 } from "path";
1972
2000
  function restartInstalledProfiles() {
1973
2001
  if (isHostServiceInstalled()) {
1974
2002
  console.log(restartHostService() ? "\nHost restarted." : "\nHost restart failed; run: flyspace setup --start-host --restart");
@@ -1991,26 +2019,31 @@ Restarting ${installed.length} instance(s)...`);
1991
2019
  function npmInstallCommand() {
1992
2020
  let prefix;
1993
2021
  try {
1994
- const entry = realpathSync(process.argv[1] ?? "");
2022
+ const entry = realpathSync2(process.argv[1] ?? "");
1995
2023
  const at = entry.indexOf(`/lib/node_modules/${"flyspace-cli"}/`);
1996
2024
  if (at > 0) prefix = entry.slice(0, at);
1997
2025
  } catch {
1998
2026
  }
1999
- return `npm install -g${prefix ? ` --prefix "${prefix}"` : ""} ${"flyspace-cli"}@latest`;
2027
+ const global = `-g${prefix ? ` --prefix "${prefix}"` : ""}`;
2028
+ return `npm install ${global} --ignore-scripts ${"flyspace-cli"}@latest && npm rebuild ${global} better-sqlite3`;
2029
+ }
2030
+ function npmEnv() {
2031
+ const home = flyspaceHome();
2032
+ return home ? { ...process.env, PATH: `${join9(home, "node", "bin")}:${process.env.PATH ?? ""}` } : process.env;
2000
2033
  }
2001
2034
  async function runUpgrade() {
2002
- const current = "0.1.9";
2035
+ const current = "0.1.12";
2003
2036
  console.log(`Current version: ${current}`);
2004
2037
  console.log("Checking for updates...\n");
2005
2038
  try {
2006
- const latest = execSync6(`npm view ${"flyspace-cli"} version`, { encoding: "utf-8" }).trim();
2039
+ const latest = execSync6(`npm view ${"flyspace-cli"} version`, { encoding: "utf-8", env: npmEnv() }).trim();
2007
2040
  if (latest === current) {
2008
2041
  console.log(`Already on the latest version (${current}).`);
2009
2042
  return;
2010
2043
  }
2011
2044
  console.log(`New version available: ${latest}`);
2012
2045
  console.log("Upgrading...\n");
2013
- execSync6(npmInstallCommand(), { stdio: "inherit" });
2046
+ execSync6(npmInstallCommand(), { stdio: "inherit", env: npmEnv() });
2014
2047
  console.log(`
2015
2048
  Upgraded from ${current} to ${latest}.`);
2016
2049
  restartInstalledProfiles();
@@ -2370,7 +2403,7 @@ async function runAutomationCmd(args) {
2370
2403
  }
2371
2404
 
2372
2405
  // src/commands/usage.ts
2373
- import { existsSync as existsSync9 } from "fs";
2406
+ import { existsSync as existsSync8 } from "fs";
2374
2407
  var HELP4 = `
2375
2408
  Usage: flyspace usage [options]
2376
2409
 
@@ -2497,7 +2530,7 @@ async function runUsage(args) {
2497
2530
  return;
2498
2531
  }
2499
2532
  const { days, by, explain, json } = parsed;
2500
- if (!existsSync9(paths.DB_FILE)) {
2533
+ if (!existsSync8(paths.DB_FILE)) {
2501
2534
  console.log("No usage recorded yet (the database does not exist).");
2502
2535
  return;
2503
2536
  }
@@ -2611,21 +2644,21 @@ async function runRestart() {
2611
2644
  }
2612
2645
 
2613
2646
  // src/commands/logs.ts
2614
- import { spawn as spawn4 } from "child_process";
2615
- import { existsSync as existsSync10 } from "fs";
2616
- import { join as join9 } from "path";
2647
+ import { spawn as spawn3 } from "child_process";
2648
+ import { existsSync as existsSync9 } from "fs";
2649
+ import { join as join10 } from "path";
2617
2650
  async function runLogs(args) {
2618
2651
  const showErrors = args.includes("--error") || args.includes("-e");
2619
2652
  const filename = showErrors ? "custie-error.log" : "custie.log";
2620
- const logFile = join9(paths.LOG_DIR, filename);
2621
- if (!existsSync10(logFile)) {
2653
+ const logFile = join10(paths.LOG_DIR, filename);
2654
+ if (!existsSync9(logFile)) {
2622
2655
  console.error(`Log file not found: ${logFile}`);
2623
2656
  console.error("Is the service installed? Run `flyspace install` first.");
2624
2657
  process.exit(1);
2625
2658
  }
2626
2659
  console.log(`Tailing ${logFile} (Ctrl+C to stop)
2627
2660
  `);
2628
- const tail = spawn4("tail", ["-f", "-n", "50", logFile], {
2661
+ const tail = spawn3("tail", ["-f", "-n", "50", logFile], {
2629
2662
  stdio: "inherit"
2630
2663
  });
2631
2664
  tail.on("error", (err) => {
@@ -2639,10 +2672,10 @@ async function runLogs(args) {
2639
2672
 
2640
2673
  // src/commands/slack.ts
2641
2674
  import { execFileSync as execFileSync4 } from "child_process";
2642
- import { existsSync as existsSync27, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
2643
- 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";
2644
2677
  import { tmpdir as tmpdir4, homedir as homedir9 } from "os";
2645
- import { input, confirm as confirm3, select, checkbox } from "@inquirer/prompts";
2678
+ import { input, confirm as confirm4, select, checkbox } from "@inquirer/prompts";
2646
2679
  import { WebClient as WebClient3 } from "@slack/web-api";
2647
2680
 
2648
2681
  // src/store/auth-links.ts
@@ -2696,7 +2729,7 @@ async function emailForToken(token, type) {
2696
2729
 
2697
2730
  // src/dashboard/profile-config.ts
2698
2731
  import { homedir as homedir5 } from "os";
2699
- import { join as join10 } from "path";
2732
+ import { join as join11 } from "path";
2700
2733
 
2701
2734
  // src/dashboard/profile-config-cache.ts
2702
2735
  var cache = /* @__PURE__ */ new Map();
@@ -2715,7 +2748,7 @@ function profileEnv(key) {
2715
2748
  function profileClaudeConfigDir() {
2716
2749
  const config = cachedProfileConfig();
2717
2750
  if (!config) return process.env["CLAUDE_CONFIG_DIR"] || void 0;
2718
- return config.env["CLAUDE_CONFIG_DIR"]?.trim() || join10(homedir5(), ".claude");
2751
+ return config.env["CLAUDE_CONFIG_DIR"]?.trim() || join11(homedir5(), ".claude");
2719
2752
  }
2720
2753
  function profileOauthToken() {
2721
2754
  const env = cachedProfileConfig()?.env ?? {};
@@ -3809,8 +3842,8 @@ function setupPage(c, token, invite, form = {}) {
3809
3842
  );
3810
3843
  }
3811
3844
  function createAuthConfirmRouter() {
3812
- const confirm4 = new Hono();
3813
- confirm4.get("/confirm", withResolvedProfile, async (c) => {
3845
+ const confirm5 = new Hono();
3846
+ confirm5.get("/confirm", withResolvedProfile, async (c) => {
3814
3847
  const settings = await loadWorkspaceBranding();
3815
3848
  const display = fontOf(settings.font).stack;
3816
3849
  const name = settings.displayName?.trim() || getProfile();
@@ -3847,7 +3880,7 @@ function createAuthConfirmRouter() {
3847
3880
  )
3848
3881
  );
3849
3882
  });
3850
- confirm4.post("/confirm", withResolvedProfile, async (c) => {
3883
+ confirm5.post("/confirm", withResolvedProfile, async (c) => {
3851
3884
  const form = await c.req.parseBody();
3852
3885
  const params = readParams({
3853
3886
  token: typeof form["token"] === "string" ? form["token"] : void 0,
@@ -3858,7 +3891,7 @@ function createAuthConfirmRouter() {
3858
3891
  if (!target) return c.text("SUPABASE_URL is not configured", 500);
3859
3892
  return c.redirect(target, 303);
3860
3893
  });
3861
- confirm4.get("/invite", withResolvedProfile, async (c) => {
3894
+ confirm5.get("/invite", withResolvedProfile, async (c) => {
3862
3895
  const token = c.req.query("token")?.trim() ?? "";
3863
3896
  const lookup = await findInvite(token);
3864
3897
  const brandingWorkspace = lookup.state === "valid" ? await getWorkspaceById(lookup.invite.workspaceId) : null;
@@ -3904,7 +3937,7 @@ function createAuthConfirmRouter() {
3904
3937
  )
3905
3938
  );
3906
3939
  });
3907
- confirm4.post("/invite", withResolvedProfile, async (c) => {
3940
+ confirm5.post("/invite", withResolvedProfile, async (c) => {
3908
3941
  const form = await c.req.parseBody();
3909
3942
  const token = typeof form["token"] === "string" ? form["token"].trim() : "";
3910
3943
  const back = `/auth/invite?token=${encodeURIComponent(token)}`;
@@ -3921,7 +3954,7 @@ function createAuthConfirmRouter() {
3921
3954
  if (!await consumeInvite(lookup.invite.id)) return c.redirect(back, 303);
3922
3955
  return c.redirect(target, 303);
3923
3956
  });
3924
- confirm4.get("/new-workspace", async (c) => {
3957
+ confirm5.get("/new-workspace", async (c) => {
3925
3958
  const token = c.req.query("token")?.trim() ?? "";
3926
3959
  const lookup = INVITE_TOKEN.test(token) ? await lookupSetupInvite(token) : { state: "unknown" };
3927
3960
  if (lookup.state === "valid") return c.html(setupPage(c, token, lookup.invite));
@@ -3949,7 +3982,7 @@ function createAuthConfirmRouter() {
3949
3982
  status
3950
3983
  );
3951
3984
  });
3952
- confirm4.post("/new-workspace", async (c) => {
3985
+ confirm5.post("/new-workspace", async (c) => {
3953
3986
  const form = await c.req.parseBody();
3954
3987
  const token = typeof form["token"] === "string" ? form["token"].trim() : "";
3955
3988
  const slug = typeof form["slug"] === "string" ? form["slug"].trim().toLowerCase() : "";
@@ -4056,7 +4089,7 @@ function createAuthConfirmRouter() {
4056
4089
  }
4057
4090
  return c.redirect(`${origin}/login?email=${encodeURIComponent(invite.email)}`, 303);
4058
4091
  });
4059
- return confirm4;
4092
+ return confirm5;
4060
4093
  }
4061
4094
  function confirmLinkFor(origin, actionLink, intent) {
4062
4095
  try {
@@ -4167,15 +4200,15 @@ async function deliverPasswordResetLink(email, redirectTo) {
4167
4200
  }
4168
4201
 
4169
4202
  // src/dashboard/server.ts
4170
- import { existsSync as existsSync26 } from "fs";
4203
+ import { existsSync as existsSync25 } from "fs";
4171
4204
  import { createServer } from "net";
4172
- import { join as join22 } from "path";
4205
+ import { join as join23 } from "path";
4173
4206
  import { Hono as Hono33 } from "hono";
4174
4207
  import { serve } from "@hono/node-server";
4175
4208
  import { serveStatic } from "@hono/node-server/serve-static";
4176
4209
 
4177
4210
  // src/reconcile.ts
4178
- 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";
4179
4212
  async function boundWorkspaces2() {
4180
4213
  return (await boundWorkspaces())?.workspaces ?? null;
4181
4214
  }
@@ -4186,13 +4219,13 @@ async function servedWorkspaces2() {
4186
4219
  async function reconcileProfile(profile) {
4187
4220
  return runWithProfile(profile, async () => {
4188
4221
  const log5 = [];
4189
- const hadConfigDir = existsSync11(paths.CONFIG_DIR);
4190
- const hadDataDir = existsSync11(paths.DATA_DIR);
4222
+ const hadConfigDir = existsSync10(paths.CONFIG_DIR);
4223
+ const hadDataDir = existsSync10(paths.DATA_DIR);
4191
4224
  ensureDirs();
4192
4225
  if (!hadConfigDir) log5.push(`config dir created (${paths.CONFIG_DIR})`);
4193
4226
  if (!hadDataDir) log5.push(`data dir created (${paths.DATA_DIR})`);
4194
- if (!existsSync11(paths.CONFIG_FILE)) {
4195
- writeFileSync5(paths.CONFIG_FILE, `BOT_NAME=${profile}
4227
+ if (!existsSync10(paths.CONFIG_FILE)) {
4228
+ writeFileSync6(paths.CONFIG_FILE, `BOT_NAME=${profile}
4196
4229
  `);
4197
4230
  log5.push(`config.env written (${paths.CONFIG_FILE})`);
4198
4231
  }
@@ -4373,8 +4406,8 @@ var requireDocsAccess = async (c, next) => {
4373
4406
  import { Hono as Hono30 } from "hono";
4374
4407
 
4375
4408
  // src/dashboard/public-share-routes.ts
4376
- import { existsSync as existsSync12, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
4377
- import { join as join12 } from "path";
4409
+ import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
4410
+ import { join as join13 } from "path";
4378
4411
  import { Hono as Hono3 } from "hono";
4379
4412
  import { GetObjectCommand } from "@aws-sdk/client-s3";
4380
4413
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
@@ -4839,12 +4872,12 @@ function injectShareHead(html, head) {
4839
4872
 
4840
4873
  // src/dashboard/app-icon.ts
4841
4874
  import { createHash as createHash2 } from "crypto";
4842
- import { readFileSync as readFileSync7 } from "fs";
4843
- import { join as join11 } from "path";
4875
+ import { readFileSync as readFileSync8 } from "fs";
4876
+ import { join as join12 } from "path";
4844
4877
  import sharp from "sharp";
4845
4878
  var PLATE = "#0A0A0F";
4846
- var MARK_FILE = join11(paths.PACKAGE_ROOT, "web", "public", "icons", "icon-512.png");
4847
- var MARK_SVG_FILE = join11(paths.PACKAGE_ROOT, "web", "public", "favicon.svg");
4879
+ var MARK_FILE = join12(paths.PACKAGE_ROOT, "web", "public", "icons", "icon-512.png");
4880
+ var MARK_SVG_FILE = join12(paths.PACKAGE_ROOT, "web", "public", "favicon.svg");
4848
4881
  var LAYOUT = {
4849
4882
  standard: { logo: 0.7, logoCx: 0.5, logoCy: 0.48, badge: 0.27, badgeCx: 0.22, badgeCy: 0.76 },
4850
4883
  maskable: { logo: 0.56, logoCx: 0.5, logoCy: 0.48, badge: 0.23, badgeCx: 0.33, badgeCy: 0.66 }
@@ -4867,7 +4900,7 @@ function roundedPlate(size, fill = PLATE) {
4867
4900
  }
4868
4901
  async function renderBadge(box) {
4869
4902
  const transparent = { r: 0, g: 0, b: 0, alpha: 0 };
4870
- 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();
4871
4904
  return sharp(raster).trim().resize(box, box, { fit: "contain", background: transparent }).png().toBuffer();
4872
4905
  }
4873
4906
  async function sampleBackground(logo) {
@@ -4922,7 +4955,7 @@ async function renderAppIcon({
4922
4955
  if (hit) return hit;
4923
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,
4924
4957
  // either on its usual dark plate or, for a favicon, trimmed and bare.
4925
- (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());
4926
4959
  cache3.set(key, out);
4927
4960
  return out;
4928
4961
  }
@@ -5434,7 +5467,7 @@ function truncationNotice(lang, voice) {
5434
5467
  if (lang === "en" || lang.startsWith("en-")) return TRUNCATION_NOTICE.en;
5435
5468
  return null;
5436
5469
  }
5437
- var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
5470
+ var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
5438
5471
  async function synthesizeWithRetry(script, voice) {
5439
5472
  let lastError;
5440
5473
  for (let attempt = 1; attempt <= SYNTH_ATTEMPTS; attempt += 1) {
@@ -6140,7 +6173,7 @@ var shellCache = null;
6140
6173
  function readShell(path) {
6141
6174
  const { mtimeMs } = statSync3(path);
6142
6175
  if (shellCache?.mtimeMs === mtimeMs) return shellCache.html;
6143
- const html = readFileSync8(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
6176
+ const html = readFileSync9(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
6144
6177
  shellCache = { mtimeMs, html };
6145
6178
  return html;
6146
6179
  }
@@ -6160,17 +6193,17 @@ function serveBuildFile(c, webDist, dir, name) {
6160
6193
  if (!type || !/^[A-Za-z0-9._-]+$/.test(name) || name.includes("..")) {
6161
6194
  return c.text("not found", 404);
6162
6195
  }
6163
- const path = join12(webDist, dir, name);
6164
- if (!existsSync12(path)) return c.text("not found", 404);
6196
+ const path = join13(webDist, dir, name);
6197
+ if (!existsSync11(path)) return c.text("not found", 404);
6165
6198
  c.header("Content-Type", type);
6166
6199
  c.header("Cache-Control", "public, max-age=31536000, immutable");
6167
6200
  if (ext === ".css") {
6168
- 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/`));
6169
6202
  }
6170
6203
  if (ext === ".js") {
6171
- return c.body(readFileSync8(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
6204
+ return c.body(readFileSync9(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
6172
6205
  }
6173
- return c.body(new Uint8Array(readFileSync8(path)));
6206
+ return c.body(new Uint8Array(readFileSync9(path)));
6174
6207
  }
6175
6208
  var HTML_SHARE_CSP = "sandbox allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox";
6176
6209
  function servePublishedHtml(c, share) {
@@ -6306,8 +6339,8 @@ ${analytics}` : head);
6306
6339
  const requested = c.req.param("lang");
6307
6340
  const lang = canonicalLanguage(requested);
6308
6341
  if (!isTranslatableLanguage(lang)) return next();
6309
- const shell = join12(webDist, "index.html");
6310
- if (!existsSync12(shell)) return next();
6342
+ const shell = join13(webDist, "index.html");
6343
+ if (!existsSync11(shell)) return next();
6311
6344
  const share = await getDocShare(c.req.param("id"));
6312
6345
  if (!share) return next();
6313
6346
  if (requested !== lang) return c.redirect(`/share/${share.id}/${lang}`, 302);
@@ -6342,8 +6375,8 @@ ${analytics}` : head);
6342
6375
  const share = await getDocShare(c.req.param("id"));
6343
6376
  if (!share) return next();
6344
6377
  if (share.kind === "html") return servePublishedHtml(c, share);
6345
- const shell = join12(webDist, "index.html");
6346
- if (!existsSync12(shell)) return next();
6378
+ const shell = join13(webDist, "index.html");
6379
+ if (!existsSync11(shell)) return next();
6347
6380
  return readerPage(
6348
6381
  c,
6349
6382
  share,
@@ -6959,26 +6992,21 @@ async function resolveAgentIdentities() {
6959
6992
  );
6960
6993
  }
6961
6994
  async function offlineAgentIdentities() {
6962
- const dm = findChannelByAgentName(BOT_DM_KEY);
6963
- if (!dm) return [];
6964
- return [
6965
- {
6966
- name: BOT_DM_KEY,
6967
- label: BOT_LABEL,
6968
- kind: "main",
6969
- channelId: dm.id,
6970
- ...await loadBotIdentity()
6971
- }
6972
- ];
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 }];
6973
7001
  }
6974
7002
 
6975
7003
  // src/dashboard/host-api.ts
6976
- import { existsSync as existsSync21, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
6977
- import { join as join19 } from "path";
7004
+ import { existsSync as existsSync20, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
7005
+ import { join as join20 } from "path";
6978
7006
  import sharp3 from "sharp";
6979
7007
 
6980
7008
  // src/dashboard/channels-routes.ts
6981
- import { existsSync as existsSync13, mkdirSync as mkdirSync7 } from "fs";
7009
+ import { existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
6982
7010
  import { Hono as Hono9 } from "hono";
6983
7011
 
6984
7012
  // src/dashboard/feature-flags-routes.ts
@@ -7010,6 +7038,11 @@ var featureFlags = {
7010
7038
  title: "Dictation",
7011
7039
  description: "A mic button in the message composer that turns speech into text as you talk. Each phrase uses speech-to-text.",
7012
7040
  stage: "dev"
7041
+ },
7042
+ "agent-memory": {
7043
+ title: "Agent memory",
7044
+ description: "Channel memory the agent reads every turn, saved by the agent and picked up from idle threads, with a Memory tab and an undo chip. While on, Claude's built-in auto memory is switched off so the two can be compared; turning this off keeps every memory.",
7045
+ stage: "beta"
7013
7046
  }
7014
7047
  };
7015
7048
  var featureFlagIds = Object.keys(featureFlags);
@@ -7035,6 +7068,11 @@ async function listEnabledFeatureFlags(workspaceId) {
7035
7068
  if (error2) throw new Error(`listEnabledFeatureFlags(${workspaceId}): ${error2.message}`);
7036
7069
  return (data ?? []).map((r) => r.flag_id);
7037
7070
  }
7071
+ async function listEnabledFeatureFlagsBySlug(slug) {
7072
+ const { data, error: error2 } = await getSupabaseClient().from("workspaces").select("id").eq("slug", slug).maybeSingle();
7073
+ if (error2) throw new Error(`listEnabledFeatureFlagsBySlug(${slug}): ${error2.message}`);
7074
+ return data ? listEnabledFeatureFlags(data.id) : [];
7075
+ }
7038
7076
  async function enableFeatureFlag(workspaceId, flagId, userId) {
7039
7077
  const { error: error2 } = await getSupabaseClient().from("feature_flags").upsert(
7040
7078
  { workspace_id: workspaceId, flag_id: flagId, enabled_by: userId },
@@ -7116,7 +7154,7 @@ function coerceMemberEmails(raw) {
7116
7154
  }
7117
7155
  function ensureCwdDir(cwd) {
7118
7156
  const expanded = isLogicalCwd(cwd) ? resolveLogicalCwd(cwd) : expandPath(cwd);
7119
- if (!existsSync13(expanded)) mkdirSync7(expanded, { recursive: true });
7157
+ if (!existsSync12(expanded)) mkdirSync7(expanded, { recursive: true });
7120
7158
  }
7121
7159
  function toResponse(channel) {
7122
7160
  return {
@@ -7265,26 +7303,26 @@ function createChannelWriteRouter() {
7265
7303
  // src/dashboard/docs-routes.ts
7266
7304
  import {
7267
7305
  closeSync,
7268
- existsSync as existsSync17,
7306
+ existsSync as existsSync16,
7269
7307
  lstatSync as lstatSync2,
7270
7308
  mkdirSync as mkdirSync8,
7271
7309
  openSync,
7272
7310
  readSync,
7273
7311
  readdirSync as readdirSync4,
7274
- readFileSync as readFileSync11,
7275
- realpathSync as realpathSync2,
7312
+ readFileSync as readFileSync12,
7313
+ realpathSync as realpathSync3,
7276
7314
  renameSync,
7277
- rmSync as rmSync5,
7315
+ rmSync as rmSync6,
7278
7316
  statSync as statSync5,
7279
- writeFileSync as writeFileSync7
7317
+ writeFileSync as writeFileSync8
7280
7318
  } from "fs";
7281
- import { basename as basename5, dirname as dirname4, extname, join as join17, 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";
7282
7320
  import { Hono as Hono10 } from "hono";
7283
7321
  import { streamSSE } from "hono/streaming";
7284
7322
 
7285
7323
  // src/dashboard/docs-watcher.ts
7286
- import { existsSync as existsSync14, watch } from "fs";
7287
- import { basename as basename3, join as join13 } from "path";
7324
+ import { existsSync as existsSync13, watch } from "fs";
7325
+ import { basename as basename4, join as join14 } from "path";
7288
7326
  var DEBOUNCE_MS = 200;
7289
7327
  var MAX_PENDING = 200;
7290
7328
  var IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
@@ -7320,12 +7358,12 @@ function flush(cwd) {
7320
7358
  function watchDocs(cwd, listener) {
7321
7359
  let entry = entries.get(cwd);
7322
7360
  if (!entry) {
7323
- const root = basename3(cwd);
7361
+ const root = basename4(cwd);
7324
7362
  const watcher = watch(cwd, { recursive: true, persistent: false }, (_event, filename) => {
7325
7363
  const current = entries.get(cwd);
7326
7364
  if (!current || filename === null) return;
7327
7365
  const rel = filename.split("\\").join("/");
7328
- if (rel === root && !existsSync14(join13(cwd, rel))) return;
7366
+ if (rel === root && !existsSync13(join14(cwd, rel))) return;
7329
7367
  if (isIgnoredDocPath(rel)) return;
7330
7368
  if (current.pending.size < MAX_PENDING) current.pending.add(rel);
7331
7369
  if (current.timer === null) current.timer = setTimeout(() => flush(cwd), DEBOUNCE_MS);
@@ -7562,15 +7600,15 @@ async function readOrQueueDocAudio(input2) {
7562
7600
  }
7563
7601
 
7564
7602
  // src/dashboard/artifact-export.ts
7565
- import { readFileSync as readFileSync10 } from "fs";
7603
+ import { readFileSync as readFileSync11 } from "fs";
7566
7604
 
7567
7605
  // src/dashboard/chat-agent.ts
7568
7606
  import { homedir as homedir7 } from "os";
7569
7607
 
7570
7608
  // src/mcp/config.ts
7571
- import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
7609
+ import { mkdtempSync, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
7572
7610
  import { tmpdir as tmpdir2 } from "os";
7573
- import { join as join14 } from "path";
7611
+ import { join as join15 } from "path";
7574
7612
 
7575
7613
  // src/mcp/crypto.ts
7576
7614
  import { createCipheriv, createDecipheriv, randomBytes as randomBytes3, timingSafeEqual } from "crypto";
@@ -8079,7 +8117,7 @@ function needsRefresh(expiresAt, now = Date.now()) {
8079
8117
  if (!expiresAt) return false;
8080
8118
  return new Date(expiresAt).getTime() - now < REFRESH_SKEW_MS;
8081
8119
  }
8082
- var sleep3 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
8120
+ var sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
8083
8121
  async function refreshConnection(params) {
8084
8122
  const { workspaceId, connection, provider } = params;
8085
8123
  const deps = params.deps ?? {};
@@ -8256,12 +8294,12 @@ async function buildMcpConfig(profile, connectionIds) {
8256
8294
  return { config, skipped };
8257
8295
  }
8258
8296
  function writeMcpConfigFile(config) {
8259
- const dir = mkdtempSync(join14(tmpdir2(), "flyspace-mcp-"));
8260
- const path = join14(dir, "mcp-config.json");
8261
- writeFileSync6(path, JSON.stringify(config), { mode: 384 });
8297
+ const dir = mkdtempSync(join15(tmpdir2(), "flyspace-mcp-"));
8298
+ const path = join15(dir, "mcp-config.json");
8299
+ writeFileSync7(path, JSON.stringify(config), { mode: 384 });
8262
8300
  return {
8263
8301
  path,
8264
- cleanup: () => rmSync4(dir, { recursive: true, force: true })
8302
+ cleanup: () => rmSync5(dir, { recursive: true, force: true })
8265
8303
  };
8266
8304
  }
8267
8305
  async function withMcpConfig(profile, connectionIds, fn) {
@@ -8278,22 +8316,22 @@ async function withMcpConfig(profile, connectionIds, fn) {
8278
8316
  }
8279
8317
 
8280
8318
  // src/claude/agents.ts
8281
- 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";
8282
8320
  import { homedir as homedir6 } from "os";
8283
- import { basename as basename4, join as join15 } from "path";
8321
+ import { basename as basename5, join as join16 } from "path";
8284
8322
  import yaml from "js-yaml";
8285
8323
  function defaultConfigDir() {
8286
- return join15(homedir6(), ".claude");
8324
+ return join16(homedir6(), ".claude");
8287
8325
  }
8288
8326
  function expandHome(p) {
8289
8327
  if (p === "~") return homedir6();
8290
- if (p.startsWith("~/")) return join15(homedir6(), p.slice(2));
8328
+ if (p.startsWith("~/")) return join16(homedir6(), p.slice(2));
8291
8329
  return p;
8292
8330
  }
8293
8331
  function parseFrontmatter(file) {
8294
8332
  let content;
8295
8333
  try {
8296
- content = readFileSync9(file, "utf-8");
8334
+ content = readFileSync10(file, "utf-8");
8297
8335
  } catch {
8298
8336
  return {};
8299
8337
  }
@@ -8309,7 +8347,7 @@ function parseFrontmatter(file) {
8309
8347
  }
8310
8348
  }
8311
8349
  function collectFrom(root, source) {
8312
- if (!existsSync15(root)) return [];
8350
+ if (!existsSync14(root)) return [];
8313
8351
  let entries2;
8314
8352
  try {
8315
8353
  entries2 = readdirSync3(root).filter((f) => f.endsWith(".md"));
@@ -8317,17 +8355,17 @@ function collectFrom(root, source) {
8317
8355
  return [];
8318
8356
  }
8319
8357
  return entries2.map((file) => {
8320
- const path = join15(root, file);
8358
+ const path = join16(root, file);
8321
8359
  const fm = parseFrontmatter(path);
8322
- const name = fm.name?.trim() || basename4(file, ".md");
8360
+ const name = fm.name?.trim() || basename5(file, ".md");
8323
8361
  return { name, description: (fm.description ?? "").trim(), source, path };
8324
8362
  });
8325
8363
  }
8326
8364
  function listAgents(configDir2, cwd) {
8327
8365
  const base = configDir2 ? expandHome(configDir2) : defaultConfigDir();
8328
8366
  const collected = [
8329
- ...collectFrom(join15(cwd, ".claude", "agents"), "project"),
8330
- ...collectFrom(join15(base, "agents"), "user")
8367
+ ...collectFrom(join16(cwd, ".claude", "agents"), "project"),
8368
+ ...collectFrom(join16(base, "agents"), "user")
8331
8369
  ];
8332
8370
  const byName = /* @__PURE__ */ new Map();
8333
8371
  for (const agent of collected) {
@@ -8339,7 +8377,7 @@ function readAgentPersona(configDir2, cwd, name) {
8339
8377
  const agent = listAgents(configDir2, cwd).find((a) => a.name === name);
8340
8378
  if (!agent) return void 0;
8341
8379
  try {
8342
- const content = readFileSync9(agent.path, "utf-8");
8380
+ const content = readFileSync10(agent.path, "utf-8");
8343
8381
  return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
8344
8382
  } catch {
8345
8383
  return void 0;
@@ -8572,7 +8610,7 @@ function formatMemoryPrompt(channelId, memories) {
8572
8610
  }
8573
8611
 
8574
8612
  // src/dashboard/rollover-summary.ts
8575
- import { spawn as spawn5 } from "child_process";
8613
+ import { spawn as spawn4 } from "child_process";
8576
8614
  import { tmpdir as tmpdir3 } from "os";
8577
8615
  var HAIKU_MODEL = "haiku";
8578
8616
  var CJK_PATTERN = /[ -〿぀-ヿ㐀-䶿一-鿿豈-﫿＀-￯]/;
@@ -8613,7 +8651,7 @@ Rules:
8613
8651
  - No preamble like "Summary:" \u2014 start directly with the summary text.`;
8614
8652
  var HAIKU_TIMEOUT_MS = 6e4;
8615
8653
  function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
8616
- return new Promise((resolve8, reject) => {
8654
+ return new Promise((resolve7, reject) => {
8617
8655
  const args = [
8618
8656
  "--print",
8619
8657
  "--model",
@@ -8632,7 +8670,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
8632
8670
  if (claudeConfigDir) env["CLAUDE_CONFIG_DIR"] = claudeConfigDir;
8633
8671
  if (claudeCodeOauthToken) env["CLAUDE_CODE_OAUTH_TOKEN"] = claudeCodeOauthToken;
8634
8672
  else delete env["CLAUDE_CODE_OAUTH_TOKEN"];
8635
- const child = spawn5("claude", args, {
8673
+ const child = spawn4("claude", args, {
8636
8674
  // A stateless rewrite touches no files — tmpdir keeps it that way and
8637
8675
  // avoids picking up an unrelated project's own CLAUDE.md/AGENTS.md,
8638
8676
  // unlike the channel's own cwd the main turn runs in.
@@ -8664,7 +8702,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
8664
8702
  reject(new Error("claude -p --model haiku returned empty output"));
8665
8703
  return;
8666
8704
  }
8667
- resolve8(text);
8705
+ resolve7(text);
8668
8706
  });
8669
8707
  });
8670
8708
  }
@@ -8839,7 +8877,7 @@ ${recent.map((m) => `${m.author}: ${m.body}`).join("\n")}`;
8839
8877
 
8840
8878
  // src/dashboard/attachment-downloader.ts
8841
8879
  import { writeFile } from "fs/promises";
8842
- import { join as join16 } from "path";
8880
+ import { join as join17 } from "path";
8843
8881
  import { GetObjectCommand as GetObjectCommand4 } from "@aws-sdk/client-s3";
8844
8882
  var debug = process.env["DEBUG"] === "true";
8845
8883
  var ATTACHMENT_LINK = /\]\(\/api\/attachments\/([^)?\s]+)(?:\?[^)\s]*)?\)/g;
@@ -8886,7 +8924,7 @@ async function downloadOne(key) {
8886
8924
  if (bytes.length > sizeLimit) {
8887
8925
  return { kind: "skip", skipped: { name, mimetype, reason: "too_large" } };
8888
8926
  }
8889
- const localPath = join16(paths.UPLOADS_DIR, lastSegment);
8927
+ const localPath = join17(paths.UPLOADS_DIR, lastSegment);
8890
8928
  await writeFile(localPath, bytes);
8891
8929
  if (debug)
8892
8930
  console.log(`[attachment-downloader] saved ${key} -> ${localPath} (${bytes.length} bytes)`);
@@ -8911,11 +8949,11 @@ async function downloadChatAttachments(body) {
8911
8949
  }
8912
8950
 
8913
8951
  // src/dashboard/doc-mention-guard.ts
8914
- import { existsSync as existsSync16, statSync as statSync4 } from "fs";
8952
+ import { existsSync as existsSync15, statSync as statSync4 } from "fs";
8915
8953
  var DOC_MENTION_RE = /(?<![\w@/`])@((?:[\w.-]+\/)+[\w.-]+\.[A-Za-z0-9]{1,6})(?::(\d+))?(?![\w-])/g;
8916
8954
  function isRealDoc(root, rel) {
8917
8955
  const abs = resolveDocPath(root, rel);
8918
- if (!abs || !existsSync16(abs)) return false;
8956
+ if (!abs || !existsSync15(abs)) return false;
8919
8957
  try {
8920
8958
  return statSync4(abs).isFile();
8921
8959
  } catch {
@@ -9426,13 +9464,20 @@ var ChatAgent = class {
9426
9464
  };
9427
9465
  }
9428
9466
  }
9429
- const docsAccess = await this.resolveSenderDocsAccess(
9430
- owningProfile,
9431
- msg.author,
9432
- thread.channel_id
9433
- );
9434
- if (docsAccess && docsAccess !== "write") channelConfig = { ...channelConfig, docsAccess };
9435
- if (docsAccess !== "none" && isUuid(thread.channel_id)) {
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
+ }
9475
+ const agentMemory = await listEnabledFeatureFlagsBySlug(owningProfile).then((ids) => resolveFeatureFlags(ids, false)["agent-memory"]).catch((err) => {
9476
+ console.error("[chat-agent] feature flag lookup failed:", err.message);
9477
+ return false;
9478
+ });
9479
+ if (agentMemory) channelConfig = { ...channelConfig, disableAutoMemory: true };
9480
+ if (agentMemory && docsAccess !== "none" && isUuid(thread.channel_id)) {
9436
9481
  const memoryPrompt = await listChannelMemories(thread.channel_id).then((memories) => formatMemoryPrompt(thread.channel_id, memories)).catch((err) => {
9437
9482
  console.error("[chat-agent] memory lookup failed:", err.message);
9438
9483
  return void 0;
@@ -9443,7 +9488,7 @@ var ChatAgent = class {
9443
9488
  try {
9444
9489
  const channelKey = `chat:${thread.channel_id}`;
9445
9490
  const session = store.getSession(channelKey, msg.threadId);
9446
- const docsNote = docsAccessPromptNote(channelConfig.docsAccess);
9491
+ const docsNote = docsAccessPromptNote(channelConfig.docsAccess, channelConfig.docsAccessUnverified);
9447
9492
  const header = (docsNote ? `[access: ${docsNote}]
9448
9493
 
9449
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.]
@@ -9711,22 +9756,22 @@ ${msg.body}` : msg.body;
9711
9756
  let ready;
9712
9757
  try {
9713
9758
  channel = this.supabase().channel(`agent-thinking:${threadId}`);
9714
- ready = new Promise((resolve8) => {
9759
+ ready = new Promise((resolve7) => {
9715
9760
  const timeout = setTimeout(() => {
9716
9761
  console.error(`[chat-agent] thinking-channel ${threadId}: subscribe timed out after 10s`);
9717
- resolve8(false);
9762
+ resolve7(false);
9718
9763
  }, 1e4);
9719
9764
  channel.subscribe((status, err) => {
9720
9765
  if (status === "SUBSCRIBED") {
9721
9766
  clearTimeout(timeout);
9722
- resolve8(true);
9767
+ resolve7(true);
9723
9768
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
9724
9769
  console.error(
9725
9770
  `[chat-agent] thinking-channel ${threadId}: ${status}`,
9726
9771
  err?.message ?? ""
9727
9772
  );
9728
9773
  clearTimeout(timeout);
9729
- resolve8(false);
9774
+ resolve7(false);
9730
9775
  }
9731
9776
  });
9732
9777
  });
@@ -9773,19 +9818,19 @@ ${msg.body}` : msg.body;
9773
9818
  let ready;
9774
9819
  try {
9775
9820
  channel = this.supabase().channel("channel-activity");
9776
- ready = new Promise((resolve8) => {
9821
+ ready = new Promise((resolve7) => {
9777
9822
  const timeout = setTimeout(() => {
9778
9823
  console.error("[chat-agent] channel-activity: subscribe timed out after 10s");
9779
- resolve8(false);
9824
+ resolve7(false);
9780
9825
  }, 1e4);
9781
9826
  channel.subscribe((status, err) => {
9782
9827
  if (status === "SUBSCRIBED") {
9783
9828
  clearTimeout(timeout);
9784
- resolve8(true);
9829
+ resolve7(true);
9785
9830
  } else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
9786
9831
  console.error(`[chat-agent] channel-activity: ${status}`, err?.message ?? "");
9787
9832
  clearTimeout(timeout);
9788
- resolve8(false);
9833
+ resolve7(false);
9789
9834
  }
9790
9835
  });
9791
9836
  });
@@ -9812,15 +9857,17 @@ ${msg.body}` : msg.body;
9812
9857
  /**
9813
9858
  * The sender's Docs level on `channelId`, or undefined when no restriction
9814
9859
  * applies (non-human author, or a profile with no Supabase workspace). A
9815
- * 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.
9816
9863
  */
9817
9864
  async resolveSenderDocsAccess(profile, author, channelId) {
9818
- if (!isHumanAuthor(author)) return void 0;
9865
+ if (!isHumanAuthor(author)) return {};
9819
9866
  try {
9820
- return await resolveDocsAccessByHandle(profile, author, channelId) ?? void 0;
9867
+ return { access: await resolveDocsAccessByHandle(profile, author, channelId) ?? void 0 };
9821
9868
  } catch (err) {
9822
9869
  console.error("[chat-agent] docs access lookup failed:", err.message);
9823
- return "none";
9870
+ return { access: "none", unverified: true };
9824
9871
  }
9825
9872
  }
9826
9873
  async tryAsk(channelConfig, prompt, sessionId, onActivity, signal, usageContext) {
@@ -9847,7 +9894,9 @@ ${msg.body}` : msg.body;
9847
9894
  mcpConfigPath,
9848
9895
  ...usageContext ? { usageContext } : {},
9849
9896
  ...channelConfig.docsAccess ? { docsAccess: channelConfig.docsAccess } : {},
9850
- ...channelConfig.memoryPrompt ? { memoryPrompt: channelConfig.memoryPrompt } : {}
9897
+ ...channelConfig.docsAccessUnverified ? { docsAccessUnverified: true } : {},
9898
+ ...channelConfig.memoryPrompt ? { memoryPrompt: channelConfig.memoryPrompt } : {},
9899
+ ...channelConfig.disableAutoMemory ? { disableAutoMemory: true } : {}
9851
9900
  },
9852
9901
  channelConfig.claudeConfigDir,
9853
9902
  sessionId,
@@ -9885,7 +9934,7 @@ ${msg.body}` : msg.body;
9885
9934
  resolveSelectedOutputStyle(channelConfig.claudeConfigDir)?.body
9886
9935
  ),
9887
9936
  channelConfig.memoryPrompt,
9888
- docsAccessPromptNote(channelConfig.docsAccess)
9937
+ docsAccessPromptNote(channelConfig.docsAccess, channelConfig.docsAccessUnverified)
9889
9938
  ].filter(Boolean).join("\n\n---\n\n"),
9890
9939
  // Codex has no per-tool deny list, so any restricted requester gets
9891
9940
  // its read-only sandbox (no file writes). It can still read: see the
@@ -10162,7 +10211,7 @@ async function exportDocAsArtifact(channelId, docPath) {
10162
10211
  );
10163
10212
  const abs = resolveDocPath(channelConfig.cwd, docPath);
10164
10213
  if (!abs) throw new Error(`doc not found: ${docPath}`);
10165
- const content = readFileSync10(abs, "utf8");
10214
+ const content = readFileSync11(abs, "utf8");
10166
10215
  const title = extractTitle(abs, "md");
10167
10216
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
10168
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).
@@ -10377,9 +10426,9 @@ function readHead(absPath) {
10377
10426
  }
10378
10427
  }
10379
10428
  function extractTitle(absPath, kind) {
10380
- if (kind === "url" || kind === "excalidraw") return basename5(absPath);
10429
+ if (kind === "url" || kind === "excalidraw") return basename6(absPath);
10381
10430
  const head = readHead(absPath);
10382
- if (!head) return basename5(absPath);
10431
+ if (!head) return basename6(absPath);
10383
10432
  if (kind === "md") {
10384
10433
  const m = head.match(/^#{1,3}\s+(.+?)\s*$/m);
10385
10434
  if (m) return m[1].replace(/[*_`]/g, "").trim();
@@ -10387,11 +10436,11 @@ function extractTitle(absPath, kind) {
10387
10436
  const t = head.match(/<title[^>]*>([^<]+)<\/title>/i) ?? head.match(/<h1[^>]*>([^<]+)<\/h1>/i);
10388
10437
  if (t) return t[1].trim();
10389
10438
  }
10390
- return basename5(absPath);
10439
+ return basename6(absPath);
10391
10440
  }
10392
10441
  function resolveDirPath(root, rel) {
10393
10442
  if (rel.includes("\0")) return null;
10394
- const abs = resolve5(root, rel);
10443
+ const abs = resolve4(root, rel);
10395
10444
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10396
10445
  return abs;
10397
10446
  }
@@ -10419,7 +10468,7 @@ function probeDir(absDir, showHidden) {
10419
10468
  return false;
10420
10469
  }
10421
10470
  if (!showHidden && entry.name.startsWith(".")) continue;
10422
- const abs = join17(dir, entry.name);
10471
+ const abs = join18(dir, entry.name);
10423
10472
  const kind = entryKind(entry, abs);
10424
10473
  if (kind === "dir") {
10425
10474
  if (SKIP_DIRS.has(entry.name)) hasOther = true;
@@ -10439,14 +10488,14 @@ function probeDir(absDir, showHidden) {
10439
10488
  }
10440
10489
  function readGitBranch(dir) {
10441
10490
  try {
10442
- let gitDir = join17(dir, ".git");
10491
+ let gitDir = join18(dir, ".git");
10443
10492
  if (statSync5(gitDir).isFile()) {
10444
- const pointer = readFileSync11(gitDir, "utf8").trim();
10493
+ const pointer = readFileSync12(gitDir, "utf8").trim();
10445
10494
  const match = pointer.match(/^gitdir:\s*(.+)$/);
10446
10495
  if (!match) return void 0;
10447
- gitDir = resolve5(dir, match[1]);
10496
+ gitDir = resolve4(dir, match[1]);
10448
10497
  }
10449
- const head = readFileSync11(join17(gitDir, "HEAD"), "utf8").trim();
10498
+ const head = readFileSync12(join18(gitDir, "HEAD"), "utf8").trim();
10450
10499
  const branchMatch = head.match(/^ref:\s*refs\/heads\/(.+)$/);
10451
10500
  if (branchMatch) return branchMatch[1];
10452
10501
  return /^[0-9a-f]{7,40}$/i.test(head) ? head.slice(0, 7) : void 0;
@@ -10468,14 +10517,14 @@ function listDocLevel(root, relDir = "", sort = "newest", showHidden = false) {
10468
10517
  for (const entry of entries2) {
10469
10518
  if (dirs.length + docs.length >= MAX_ENTRIES) break;
10470
10519
  if (!showHidden && entry.name.startsWith(".")) continue;
10471
- const abs = join17(absDir, entry.name);
10520
+ const abs = join18(absDir, entry.name);
10472
10521
  const path = relative(root, abs).split(sep).join("/");
10473
10522
  const kind = entryKind(entry, abs);
10474
10523
  if (kind === "dir") {
10475
10524
  if (SKIP_DIRS.has(entry.name)) continue;
10476
10525
  const probe3 = probeDir(abs, showHidden);
10477
10526
  if (!probe3.hasDocs && probe3.hasOther) continue;
10478
- const isRepo = existsSync17(join17(abs, ".git"));
10527
+ const isRepo = existsSync16(join18(abs, ".git"));
10479
10528
  dirs.push({
10480
10529
  path,
10481
10530
  name: entry.name,
@@ -10511,7 +10560,7 @@ function flatDocs(root, {
10511
10560
  for (const entry of entries2) {
10512
10561
  if (hits.length >= MAX_FLAT_DOCS) return;
10513
10562
  if (!showHidden && entry.name.startsWith(".")) continue;
10514
- const abs = join17(dir, entry.name);
10563
+ const abs = join18(dir, entry.name);
10515
10564
  const kind = entryKind(entry, abs);
10516
10565
  if (kind === "dir") {
10517
10566
  if (!SKIP_DIRS.has(entry.name)) walk(abs, depth + 1);
@@ -10531,7 +10580,7 @@ function flatDocs(root, {
10531
10580
  }
10532
10581
  function resolveDocPath(root, rel) {
10533
10582
  if (!rel || rel.includes("\0")) return null;
10534
- const abs = resolve5(root, rel);
10583
+ const abs = resolve4(root, rel);
10535
10584
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10536
10585
  const ext = extname(abs).toLowerCase();
10537
10586
  if (!(ext in DOC_EXTENSIONS) && !(ext in CODE_EXTENSIONS)) return null;
@@ -10553,12 +10602,12 @@ function writeDocContent(root, rel, content, baseMtime) {
10553
10602
  if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) {
10554
10603
  return { status: "too-large" };
10555
10604
  }
10556
- writeFileSync7(abs, content, "utf8");
10605
+ writeFileSync8(abs, content, "utf8");
10557
10606
  return { status: "ok", mtime: statSync5(abs).mtime.toISOString() };
10558
10607
  }
10559
10608
  function resolveRawPath(root, rel) {
10560
10609
  if (!rel || rel.includes("\0")) return null;
10561
- const abs = resolve5(root, rel);
10610
+ const abs = resolve4(root, rel);
10562
10611
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10563
10612
  const contentType = MEDIA_EXTENSIONS[extname(abs).toLowerCase()];
10564
10613
  if (!contentType) return null;
@@ -10566,16 +10615,16 @@ function resolveRawPath(root, rel) {
10566
10615
  }
10567
10616
  function resolveHtmlPath(root, rel) {
10568
10617
  if (!rel || rel.includes("\0")) return null;
10569
- const abs = resolve5(root, rel);
10618
+ const abs = resolve4(root, rel);
10570
10619
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10571
10620
  if (DOC_EXTENSIONS[extname(abs).toLowerCase()] !== "html") return null;
10572
10621
  return abs;
10573
10622
  }
10574
10623
  function resolveDownloadPath(root, rel) {
10575
10624
  if (!rel || rel.includes("\0")) return null;
10576
- const abs = resolve5(root, rel);
10625
+ const abs = resolve4(root, rel);
10577
10626
  if (abs !== root && !abs.startsWith(root + sep)) return null;
10578
- if (!isListableFile(basename5(abs))) return null;
10627
+ if (!isListableFile(basename6(abs))) return null;
10579
10628
  const contentType = MEDIA_EXTENSIONS[extname(abs).toLowerCase()] ?? "application/octet-stream";
10580
10629
  return { abs, contentType };
10581
10630
  }
@@ -10591,7 +10640,7 @@ function uniqueName(absDir, name) {
10591
10640
  let candidate = name;
10592
10641
  for (let n = 1; n < 1e3; n++) {
10593
10642
  try {
10594
- statSync5(join17(absDir, candidate));
10643
+ statSync5(join18(absDir, candidate));
10595
10644
  } catch {
10596
10645
  return candidate;
10597
10646
  }
@@ -10613,8 +10662,8 @@ function saveUploadedFile(root, relDir, name, data) {
10613
10662
  if (!(ext in DOC_EXTENSIONS) && !ASSET_LIST_EXTENSIONS.has(ext)) return { status: "bad-type" };
10614
10663
  if (data.byteLength > MAX_UPLOAD_BYTES) return { status: "too-large" };
10615
10664
  const final = uniqueName(absDir, safe);
10616
- const abs = join17(absDir, final);
10617
- writeFileSync7(abs, data);
10665
+ const abs = join18(absDir, final);
10666
+ writeFileSync8(abs, data);
10618
10667
  return { status: "ok", path: relative(root, abs).split(sep).join("/"), name: final };
10619
10668
  }
10620
10669
  function createDocFolder(root, relDir, name) {
@@ -10627,7 +10676,7 @@ function createDocFolder(root, relDir, name) {
10627
10676
  }
10628
10677
  const safe = sanitizeUploadName(name);
10629
10678
  if (!safe) return { status: "bad-name" };
10630
- const abs = join17(absDir, safe);
10679
+ const abs = join18(absDir, safe);
10631
10680
  try {
10632
10681
  statSync5(abs);
10633
10682
  return { status: "exists" };
@@ -10650,9 +10699,9 @@ function createDocFile(root, relDir, name, content) {
10650
10699
  if (!(ext in DOC_EXTENSIONS) && !(ext in CODE_EXTENSIONS)) return { status: "bad-name" };
10651
10700
  if (SKIP_FILES.has(safe)) return { status: "bad-name" };
10652
10701
  if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) return { status: "too-large" };
10653
- const abs = join17(absDir, safe);
10702
+ const abs = join18(absDir, safe);
10654
10703
  try {
10655
- writeFileSync7(abs, content, { encoding: "utf8", flag: "wx" });
10704
+ writeFileSync8(abs, content, { encoding: "utf8", flag: "wx" });
10656
10705
  } catch (err) {
10657
10706
  if (err.code === "EEXIST") return { status: "exists" };
10658
10707
  throw err;
@@ -10661,7 +10710,7 @@ function createDocFile(root, relDir, name, content) {
10661
10710
  }
10662
10711
  function moveDocEntry(root, from, to) {
10663
10712
  if (!from || !to || from.includes("\0") || to.includes("\0")) return { status: "not-found" };
10664
- const absFrom = resolve5(root, from);
10713
+ const absFrom = resolve4(root, from);
10665
10714
  if (!absFrom.startsWith(root + sep)) return { status: "not-found" };
10666
10715
  let stat;
10667
10716
  try {
@@ -10671,9 +10720,9 @@ function moveDocEntry(root, from, to) {
10671
10720
  }
10672
10721
  const isDir2 = stat.isDirectory();
10673
10722
  if (!isDir2 && !stat.isFile()) return { status: "not-found" };
10674
- const absTo = resolve5(root, to);
10723
+ const absTo = resolve4(root, to);
10675
10724
  if (!absTo.startsWith(root + sep)) return { status: "bad-target" };
10676
- const name = sanitizeUploadName(basename5(absTo));
10725
+ const name = sanitizeUploadName(basename6(absTo));
10677
10726
  if (!name) return { status: "bad-target" };
10678
10727
  if (!isDir2) {
10679
10728
  const ext = extname(name).toLowerCase();
@@ -10681,19 +10730,19 @@ function moveDocEntry(root, from, to) {
10681
10730
  return { status: "bad-target" };
10682
10731
  }
10683
10732
  }
10684
- const absToDir = dirname4(absTo);
10733
+ const absToDir = dirname5(absTo);
10685
10734
  if (resolveDirPath(root, relative(root, absToDir)) === null) return { status: "bad-target" };
10686
10735
  try {
10687
10736
  if (!statSync5(absToDir).isDirectory()) return { status: "bad-target" };
10688
10737
  } catch {
10689
10738
  return { status: "bad-target" };
10690
10739
  }
10691
- const finalAbs = join17(absToDir, name);
10740
+ const finalAbs = join18(absToDir, name);
10692
10741
  if (isDir2 && (finalAbs === absFrom || finalAbs.startsWith(absFrom + sep))) {
10693
10742
  return { status: "into-self" };
10694
10743
  }
10695
10744
  try {
10696
- if (realpathSync2(finalAbs) !== realpathSync2(absFrom)) return { status: "exists" };
10745
+ if (realpathSync3(finalAbs) !== realpathSync3(absFrom)) return { status: "exists" };
10697
10746
  } catch {
10698
10747
  }
10699
10748
  renameSync(absFrom, finalAbs);
@@ -10701,7 +10750,7 @@ function moveDocEntry(root, from, to) {
10701
10750
  }
10702
10751
  function deleteDocEntry(root, path) {
10703
10752
  if (!path || path.includes("\0")) return { status: "not-found" };
10704
- const abs = resolve5(root, path);
10753
+ const abs = resolve4(root, path);
10705
10754
  if (!abs.startsWith(root + sep)) return { status: "not-found" };
10706
10755
  let stat;
10707
10756
  try {
@@ -10711,7 +10760,7 @@ function deleteDocEntry(root, path) {
10711
10760
  }
10712
10761
  const isDir2 = stat.isDirectory();
10713
10762
  if (!isDir2 && !stat.isFile() && !stat.isSymbolicLink()) return { status: "not-found" };
10714
- rmSync5(abs, { recursive: isDir2 });
10763
+ rmSync6(abs, { recursive: isDir2 });
10715
10764
  return {
10716
10765
  status: "ok",
10717
10766
  path: relative(root, abs).split(sep).join("/"),
@@ -10750,10 +10799,10 @@ function createDocsRouter() {
10750
10799
  }
10751
10800
  if (!stat.isFile()) return c.json({ error: "not found" }, 404);
10752
10801
  if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
10753
- const data = readFileSync11(resolved2.abs);
10802
+ const data = readFileSync12(resolved2.abs);
10754
10803
  const headers = { "Content-Type": resolved2.contentType };
10755
10804
  if (download) {
10756
- const filename = basename5(resolved2.abs).replace(/"/g, "");
10805
+ const filename = basename6(resolved2.abs).replace(/"/g, "");
10757
10806
  headers["Content-Disposition"] = `attachment; filename="${filename}"`;
10758
10807
  }
10759
10808
  return c.body(new Uint8Array(data), 200, headers);
@@ -10770,7 +10819,7 @@ function createDocsRouter() {
10770
10819
  }
10771
10820
  if (!stat.isFile()) return c.json({ error: "not found" }, 404);
10772
10821
  if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
10773
- return c.body(new Uint8Array(readFileSync11(abs)), 200, {
10822
+ return c.body(new Uint8Array(readFileSync12(abs)), 200, {
10774
10823
  "Content-Type": "text/html; charset=utf-8",
10775
10824
  "Content-Security-Policy": HTML_SHARE_CSP,
10776
10825
  "X-Content-Type-Options": "nosniff",
@@ -10952,10 +11001,10 @@ function createDocsRouter() {
10952
11001
  // by; only docs get a parsed title.
10953
11002
  kind: kind ?? "code",
10954
11003
  lang: kind ? void 0 : CODE_EXTENSIONS[ext],
10955
- title: kind ? extractTitle(abs, kind) : basename5(abs),
11004
+ title: kind ? extractTitle(abs, kind) : basename6(abs),
10956
11005
  mtime: stat.mtime.toISOString(),
10957
11006
  tooLarge: tooLarge || void 0,
10958
- content: tooLarge ? "" : readFileSync11(abs, "utf8")
11007
+ content: tooLarge ? "" : readFileSync12(abs, "utf8")
10959
11008
  });
10960
11009
  });
10961
11010
  app.put("/:id/docs/content", async (c) => {
@@ -10998,7 +11047,7 @@ function createDocsRouter() {
10998
11047
  channel,
10999
11048
  rel,
11000
11049
  title: extractTitle(abs, "md"),
11001
- content: readFileSync11(abs, "utf8")
11050
+ content: readFileSync12(abs, "utf8")
11002
11051
  };
11003
11052
  };
11004
11053
  app.get("/:id/docs/translation/:lang", async (c) => {
@@ -11194,13 +11243,13 @@ function relay(agent, pathOf) {
11194
11243
  }
11195
11244
 
11196
11245
  // src/dashboard/skills-routes.ts
11197
- import { existsSync as existsSync19, statSync as statSync7 } from "fs";
11198
- import { dirname as dirname5 } from "path";
11246
+ import { existsSync as existsSync18, statSync as statSync7 } from "fs";
11247
+ import { dirname as dirname6 } from "path";
11199
11248
  import { Hono as Hono12 } from "hono";
11200
11249
 
11201
11250
  // src/dashboard/skill-mirror.ts
11202
11251
  import { createHash as createHash8 } from "crypto";
11203
- import { existsSync as existsSync18, statSync as statSync6 } from "fs";
11252
+ import { existsSync as existsSync17, statSync as statSync6 } from "fs";
11204
11253
  var SYNC_INTERVAL_MS = 6e4;
11205
11254
  var UPSERT_BATCH = 50;
11206
11255
  function mirrorScopeOf(ref) {
@@ -11208,7 +11257,7 @@ function mirrorScopeOf(ref) {
11208
11257
  }
11209
11258
  function isDir(path) {
11210
11259
  try {
11211
- return existsSync18(path) && statSync6(path).isDirectory();
11260
+ return existsSync17(path) && statSync6(path).isDirectory();
11212
11261
  } catch {
11213
11262
  return false;
11214
11263
  }
@@ -11320,7 +11369,7 @@ function workspaceScope() {
11320
11369
  return {
11321
11370
  dir: workspaceSkillsDir(claudeConfigDir),
11322
11371
  // `~/.claude` itself, which exists wherever the CLI has ever run.
11323
- root: dirname5(workspaceSkillsDir(claudeConfigDir)),
11372
+ root: dirname6(workspaceSkillsDir(claudeConfigDir)),
11324
11373
  cwd,
11325
11374
  claudeConfigDir,
11326
11375
  // Only plugin skills. Project skills belong to a specific channel's cwd, so
@@ -11349,7 +11398,7 @@ function requireAdmin2(auth) {
11349
11398
  }
11350
11399
  function rootExists(dir) {
11351
11400
  try {
11352
- return existsSync19(dir) && statSync7(dir).isDirectory();
11401
+ return existsSync18(dir) && statSync7(dir).isDirectory();
11353
11402
  } catch {
11354
11403
  return false;
11355
11404
  }
@@ -11450,7 +11499,7 @@ function mirrorBackend(agent) {
11450
11499
  }
11451
11500
  }
11452
11501
  return {
11453
- dir: own[0] ? dirname5(dirname5(own[0].path)) : "",
11502
+ dir: own[0] ? dirname6(dirname6(own[0].path)) : "",
11454
11503
  skills: own.map(({ name, description, mtime }) => ({ name, description, mtime })),
11455
11504
  inherited: inherited.sort(byName)
11456
11505
  };
@@ -11538,9 +11587,9 @@ function registerMirroredSkillRoutes(api, agent) {
11538
11587
 
11539
11588
  // src/dashboard/providers.ts
11540
11589
  import { execFileSync as execFileSync2 } from "child_process";
11541
- import { existsSync as existsSync20 } from "fs";
11590
+ import { existsSync as existsSync19 } from "fs";
11542
11591
  import { homedir as homedir8 } from "os";
11543
- import { join as join18, resolve as resolve6 } from "path";
11592
+ import { join as join19, resolve as resolve5 } from "path";
11544
11593
  var PROVIDER_LABELS = {
11545
11594
  claude: "Claude Code",
11546
11595
  codex: "Codex"
@@ -11557,8 +11606,8 @@ function binaryOnPath(bin) {
11557
11606
  }
11558
11607
  function claudeAuthAvailable() {
11559
11608
  if (profileEnv("CLAUDE_CODE_OAUTH_TOKEN")?.trim()) return true;
11560
- const configDir2 = profileClaudeConfigDir() || resolve6(homedir8(), ".claude");
11561
- if (existsSync20(join18(configDir2, ".credentials.json"))) return true;
11609
+ const configDir2 = profileClaudeConfigDir() || resolve5(homedir8(), ".claude");
11610
+ if (existsSync19(join19(configDir2, ".credentials.json"))) return true;
11562
11611
  if (process.platform !== "darwin") return false;
11563
11612
  try {
11564
11613
  execFileSync2("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
@@ -11586,13 +11635,23 @@ function probe2(runner) {
11586
11635
  }
11587
11636
  return { runner, label: label2, connected: true };
11588
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
+ }
11589
11646
  function providerStatuses() {
11590
11647
  const key = getProfile();
11591
11648
  const hit = cache6.get(key);
11592
- if (hit && Date.now() - hit.at < TTL_MS2) return hit.statuses;
11593
- const statuses = ["claude", "codex"].map(probe2);
11594
- cache6.set(key, { at: Date.now(), statuses });
11595
- 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);
11596
11655
  }
11597
11656
 
11598
11657
  // src/dashboard/host-api.ts
@@ -11701,8 +11760,8 @@ function registerHostRoutes(api) {
11701
11760
  const errors = c.req.query("error") === "true" || c.req.query("error") === "1";
11702
11761
  const limit = Math.min(Number(c.req.query("limit")) || 200, 2e3);
11703
11762
  const filename = errors ? "custie-error.log" : "custie.log";
11704
- const file = join19(paths.LOG_DIR, filename);
11705
- const exists = existsSync21(file);
11763
+ const file = join20(paths.LOG_DIR, filename);
11764
+ const exists = existsSync20(file);
11706
11765
  return c.json({
11707
11766
  file,
11708
11767
  exists,
@@ -11712,14 +11771,14 @@ function registerHostRoutes(api) {
11712
11771
  });
11713
11772
  }
11714
11773
  function tailFile(file, limit) {
11715
- if (!existsSync21(file)) return [];
11716
- const lines = readFileSync12(file, "utf-8").split("\n");
11774
+ if (!existsSync20(file)) return [];
11775
+ const lines = readFileSync13(file, "utf-8").split("\n");
11717
11776
  if (lines.at(-1) === "") lines.pop();
11718
11777
  return lines.slice(-limit);
11719
11778
  }
11720
11779
 
11721
11780
  // src/dashboard/data-api.ts
11722
- import { existsSync as existsSync23 } from "fs";
11781
+ import { existsSync as existsSync22 } from "fs";
11723
11782
  import sharp5 from "sharp";
11724
11783
 
11725
11784
  // src/dashboard/automations-routes.ts
@@ -13493,7 +13552,7 @@ function createTranscribeRouter() {
13493
13552
  }
13494
13553
 
13495
13554
  // src/dashboard/avatars-routes.ts
13496
- import { randomUUID as randomUUID2 } from "crypto";
13555
+ import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
13497
13556
  import { Hono as Hono20 } from "hono";
13498
13557
  import sharp4 from "sharp";
13499
13558
  import { PutObjectCommand as PutObjectCommand4, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
@@ -13501,6 +13560,16 @@ var AVATAR_SIZE = 192;
13501
13560
  var MAX_AVATAR_BYTES = 2 * 1024 * 1024;
13502
13561
  var AVATAR_MIME_TYPES = ["image/png", "image/jpeg"];
13503
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
+ }
13504
13573
  async function saveAvatarKey(userId, avatarKey) {
13505
13574
  const { data, error: getError } = await getSupabaseClient().auth.admin.getUserById(userId);
13506
13575
  if (getError || !data.user) {
@@ -13562,6 +13631,17 @@ function createAvatarsRouter() {
13562
13631
  }
13563
13632
  return c.json({ avatarUrl: await signAvatarUrl(key) }, 201);
13564
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
+ });
13565
13645
  app.delete("/avatars", async (c) => {
13566
13646
  const ctx = await resolveAuthContext(c);
13567
13647
  if (!ctx) return c.json({ error: "not signed in" }, 401);
@@ -14566,7 +14646,7 @@ async function sessionUsageForThreads(workspace, threadTsList) {
14566
14646
  }
14567
14647
 
14568
14648
  // src/dashboard/usage-report.ts
14569
- import { existsSync as existsSync22 } from "fs";
14649
+ import { existsSync as existsSync21 } from "fs";
14570
14650
  function mergeByLabel(buckets) {
14571
14651
  const merged = /* @__PURE__ */ new Map();
14572
14652
  for (const bucket of buckets) {
@@ -14612,7 +14692,7 @@ function toReport(days, rows, labelChannel) {
14612
14692
  };
14613
14693
  }
14614
14694
  function fromLocalStore(days, labelChannel) {
14615
- if (!existsSync22(paths.DB_FILE)) return { ...EMPTY, days };
14695
+ if (!existsSync21(paths.DB_FILE)) return { ...EMPTY, days };
14616
14696
  const window = days === 0 ? void 0 : days;
14617
14697
  const store = new UsageStore(paths.DB_FILE);
14618
14698
  try {
@@ -14654,7 +14734,7 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
14654
14734
  const { totals, byDay } = await channelUsageForWorkspace(workspace, aliasIds, window);
14655
14735
  return { days, totals: totals.runs > 0 ? totals : null, byDay };
14656
14736
  } catch {
14657
- if (!existsSync22(paths.DB_FILE)) return { days, totals: null, byDay: [] };
14737
+ if (!existsSync21(paths.DB_FILE)) return { days, totals: null, byDay: [] };
14658
14738
  const store = new UsageStore(paths.DB_FILE);
14659
14739
  try {
14660
14740
  const targetLabel = labelChannel(channelId);
@@ -14676,7 +14756,7 @@ async function buildThreadUsage(threadTs) {
14676
14756
  if (totals.runs === 0) return { threadTs, totals: null, byModel: [] };
14677
14757
  return { threadTs, totals, byModel };
14678
14758
  } catch {
14679
- if (!existsSync22(paths.DB_FILE)) return { threadTs, totals: null, byModel: [] };
14759
+ if (!existsSync21(paths.DB_FILE)) return { threadTs, totals: null, byModel: [] };
14680
14760
  const store = new UsageStore(paths.DB_FILE);
14681
14761
  try {
14682
14762
  const totals = store.threadTotals(threadTs);
@@ -14790,6 +14870,12 @@ var requireChannelVisible = async (c, next) => {
14790
14870
  c.set("memoryChannelId", channel.id);
14791
14871
  await next();
14792
14872
  };
14873
+ var requireAgentMemory = async (c, next) => {
14874
+ if (!await isFeatureEnabled(c, "agent-memory")) {
14875
+ return c.json({ error: "feature not enabled" }, 403);
14876
+ }
14877
+ await next();
14878
+ };
14793
14879
  function createMemoriesRouter() {
14794
14880
  const router = new Hono27();
14795
14881
  for (const path of [
@@ -14798,7 +14884,7 @@ function createMemoriesRouter() {
14798
14884
  "/:id/memory-events",
14799
14885
  "/:id/memory-events/*"
14800
14886
  ]) {
14801
- router.use(path, requireChannelVisible, requireDocsAccess);
14887
+ router.use(path, requireAgentMemory, requireChannelVisible, requireDocsAccess);
14802
14888
  }
14803
14889
  router.get("/:id/memories", async (c) => {
14804
14890
  return c.json({ memories: await listChannelMemories(c.get("memoryChannelId")) });
@@ -15030,10 +15116,10 @@ function createOnboardingRouter(agent) {
15030
15116
  var HOST_STATUS_CHANNEL = "host-status";
15031
15117
  var channelReady = null;
15032
15118
  function getHostStatusChannel() {
15033
- channelReady ??= new Promise((resolve8) => {
15119
+ channelReady ??= new Promise((resolve7) => {
15034
15120
  const channel = getSupabaseClient().channel(HOST_STATUS_CHANNEL);
15035
15121
  channel.subscribe((state) => {
15036
- if (state === "SUBSCRIBED") resolve8(channel);
15122
+ if (state === "SUBSCRIBED") resolve7(channel);
15037
15123
  });
15038
15124
  });
15039
15125
  return channelReady;
@@ -15199,7 +15285,7 @@ function registerDataRoutes(api, agent = {}) {
15199
15285
  runs.set(name, { lastRun: run3.lastRunAt.toISOString(), lastStatus: run3.lastStatus });
15200
15286
  }
15201
15287
  } catch {
15202
- if (existsSync23(paths.DB_FILE)) {
15288
+ if (existsSync22(paths.DB_FILE)) {
15203
15289
  const runStore = new AutomationRunStore(paths.DB_FILE);
15204
15290
  try {
15205
15291
  for (const run3 of runStore.listLastRuns()) {
@@ -15261,7 +15347,7 @@ function registerDataRoutes(api, agent = {}) {
15261
15347
  const runs = (await recentRunsForWorkspace(getProfile(), 200)).map(toResponse5);
15262
15348
  return c.json({ runs });
15263
15349
  } catch {
15264
- if (!existsSync23(paths.DB_FILE)) return c.json({ runs: [] });
15350
+ if (!existsSync22(paths.DB_FILE)) return c.json({ runs: [] });
15265
15351
  const runStore = new AutomationRunStore(paths.DB_FILE);
15266
15352
  try {
15267
15353
  const runs = runStore.listRuns(200).map(toResponse5);
@@ -15383,7 +15469,7 @@ function registerDataRoutes(api, agent = {}) {
15383
15469
  } catch {
15384
15470
  latest = /* @__PURE__ */ new Map();
15385
15471
  totals = /* @__PURE__ */ new Map();
15386
- if (existsSync23(paths.DB_FILE)) {
15472
+ if (existsSync22(paths.DB_FILE)) {
15387
15473
  const usageStore = new UsageStore(paths.DB_FILE);
15388
15474
  try {
15389
15475
  latest = usageStore.latestByThread(threadTsList);
@@ -15442,24 +15528,24 @@ import { Hono as Hono29 } from "hono";
15442
15528
 
15443
15529
  // src/dashboard/agent-bridge.ts
15444
15530
  import { randomBytes as randomBytes8, randomUUID as randomUUID4, sign as signRaw, timingSafeEqual as timingSafeEqual2, verify as verifyRaw } from "crypto";
15445
- import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
15531
+ import { existsSync as existsSync24, readFileSync as readFileSync17 } from "fs";
15446
15532
  import dotenv from "dotenv";
15447
15533
  import { WebSocket, WebSocketServer } from "ws";
15448
15534
 
15449
15535
  // src/dashboard/host-identity.ts
15450
15536
  import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
15451
- import { existsSync as existsSync24, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
15452
- import { dirname as dirname6, join as join20 } from "path";
15537
+ import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
15538
+ import { dirname as dirname7, join as join21 } from "path";
15453
15539
  function hostPrivateKeyPath() {
15454
- return process.env["FLYSPACE_HOST_KEY_PATH"] || join20(paths.BASE_CONFIG_DIR, "host.key");
15540
+ return process.env["FLYSPACE_HOST_KEY_PATH"] || join21(paths.BASE_CONFIG_DIR, "host.key");
15455
15541
  }
15456
15542
  function loadHostPrivateKey(path = hostPrivateKeyPath()) {
15457
- if (!existsSync24(path)) {
15543
+ if (!existsSync23(path)) {
15458
15544
  throw new Error(
15459
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.`
15460
15546
  );
15461
15547
  }
15462
- return createPrivateKey2(readFileSync13(path, "utf8"));
15548
+ return createPrivateKey2(readFileSync14(path, "utf8"));
15463
15549
  }
15464
15550
  function loadHostPublicKey() {
15465
15551
  const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
@@ -15475,10 +15561,10 @@ function loadHostPublicKey() {
15475
15561
  // src/dashboard/host-lock.ts
15476
15562
  import { execFileSync as execFileSync3 } from "child_process";
15477
15563
  import { randomBytes as randomBytes5 } from "crypto";
15478
- import { mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
15479
- import { join as join21 } from "path";
15564
+ import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
15565
+ import { join as join22 } from "path";
15480
15566
  function hostLockPath() {
15481
- return join21(paths.BASE_DATA_DIR, "host.lock");
15567
+ return join22(paths.BASE_DATA_DIR, "host.lock");
15482
15568
  }
15483
15569
  function isLiveFlyspace(pid) {
15484
15570
  try {
@@ -15493,7 +15579,7 @@ function isLiveFlyspace(pid) {
15493
15579
  }
15494
15580
  function readLockPid(path) {
15495
15581
  try {
15496
- const pid = Number(JSON.parse(readFileSync14(path, "utf8")).pid);
15582
+ const pid = Number(JSON.parse(readFileSync15(path, "utf8")).pid);
15497
15583
  return Number.isInteger(pid) && pid > 0 ? pid : null;
15498
15584
  } catch {
15499
15585
  return null;
@@ -15509,9 +15595,9 @@ function acquireHostLock() {
15509
15595
  });
15510
15596
  for (let attempt = 0; attempt < 2; attempt++) {
15511
15597
  try {
15512
- writeFileSync9(path, body, { flag: "wx", mode: 384 });
15598
+ writeFileSync10(path, body, { flag: "wx", mode: 384 });
15513
15599
  const release2 = () => {
15514
- if (readLockPid(path) === process.pid) rmSync6(path, { force: true });
15600
+ if (readLockPid(path) === process.pid) rmSync7(path, { force: true });
15515
15601
  };
15516
15602
  process.once("exit", release2);
15517
15603
  return release2;
@@ -15525,7 +15611,7 @@ function acquireHostLock() {
15525
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.`
15526
15612
  );
15527
15613
  }
15528
- rmSync6(path, { force: true });
15614
+ rmSync7(path, { force: true });
15529
15615
  }
15530
15616
  }
15531
15617
  throw new Error(`Could not take the Host lock at ${path}`);
@@ -15534,13 +15620,13 @@ function publishHostEndpoint(port) {
15534
15620
  const path = hostLockPath();
15535
15621
  if (readLockPid(path) !== process.pid) return null;
15536
15622
  const token = randomBytes5(32).toString("hex");
15537
- const lock = JSON.parse(readFileSync14(path, "utf8"));
15538
- 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 });
15539
15625
  return token;
15540
15626
  }
15541
15627
  function readHostEndpoint() {
15542
15628
  try {
15543
- const { pid, port, token } = JSON.parse(readFileSync14(hostLockPath(), "utf8"));
15629
+ const { pid, port, token } = JSON.parse(readFileSync15(hostLockPath(), "utf8"));
15544
15630
  if (pid === process.pid || !Number.isInteger(port) || typeof token !== "string") return null;
15545
15631
  return { port, token };
15546
15632
  } catch {
@@ -15549,12 +15635,12 @@ function readHostEndpoint() {
15549
15635
  }
15550
15636
 
15551
15637
  // src/store/device-store.ts
15552
- 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";
15553
15639
  var DEVICE_CODE_TTL_MS = 10 * 6e4;
15554
15640
  var ACCESS_TOKEN_TTL_MS = 15 * 6e4;
15555
15641
  var CHALLENGE_TTL_MS = 2 * 6e4;
15556
15642
  var POLL_INTERVAL_S = 5;
15557
- var sha256 = (s) => createHash10("sha256").update(s).digest("hex");
15643
+ var sha256 = (s) => createHash11("sha256").update(s).digest("hex");
15558
15644
  function parseEd25519PublicKey(b64) {
15559
15645
  if (typeof b64 !== "string" || b64.length > 200) return null;
15560
15646
  try {
@@ -15780,7 +15866,7 @@ async function userEmail(userId) {
15780
15866
 
15781
15867
  // src/dashboard/doc-share-publisher.ts
15782
15868
  import { randomBytes as randomBytes7 } from "crypto";
15783
- import { readFileSync as readFileSync15, statSync as statSync9 } from "fs";
15869
+ import { readFileSync as readFileSync16, statSync as statSync9 } from "fs";
15784
15870
  import { extname as extname2, posix } from "path";
15785
15871
  import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
15786
15872
 
@@ -15967,7 +16053,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
15967
16053
  }
15968
16054
  if (!stat.isFile()) return { status: "not-found" };
15969
16055
  if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
15970
- const source = readFileSync15(abs, "utf8");
16056
+ const source = readFileSync16(abs, "utf8");
15971
16057
  const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
15972
16058
  if (kind === "excalidraw") {
15973
16059
  try {
@@ -16047,7 +16133,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
16047
16133
  let text = null;
16048
16134
  try {
16049
16135
  const size = statSync9(docAbs).size;
16050
- if (size <= budget) text = readFileSync15(docAbs, "utf8");
16136
+ if (size <= budget) text = readFileSync16(docAbs, "utf8");
16051
16137
  } catch {
16052
16138
  continue;
16053
16139
  }
@@ -16064,7 +16150,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
16064
16150
  let body;
16065
16151
  try {
16066
16152
  if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
16067
- body = readFileSync15(resolved2.abs);
16153
+ body = readFileSync16(resolved2.abs);
16068
16154
  } catch {
16069
16155
  continue;
16070
16156
  }
@@ -16155,8 +16241,12 @@ var METHODS = {
16155
16241
  agent_usage: ["GET", "HEAD", "POST"],
16156
16242
  skill_files: ALL,
16157
16243
  threads: ["GET", "HEAD", "POST", "PATCH"],
16158
- 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"]
16159
16248
  };
16249
+ var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members", "channel_docs_access"]);
16160
16250
  var FORWARDED_HEADERS = ["accept", "content-type", "prefer", "range"];
16161
16251
  var deny = (message, status = 403) => ({
16162
16252
  status,
@@ -16199,6 +16289,13 @@ async function ownedChannelIds(forward, slugs) {
16199
16289
  if (!rows) return null;
16200
16290
  return rows.flatMap((r) => [r.id, r.external_channel_id].filter((v) => !!v));
16201
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
+ }
16202
16299
  async function threadsOwned(forward, threadIds, channelIds) {
16203
16300
  const wanted = [...new Set(threadIds)];
16204
16301
  if (wanted.length === 0 || channelIds.length === 0) return false;
@@ -16256,10 +16353,16 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
16256
16353
  }
16257
16354
  }
16258
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));
16259
16360
  } else {
16260
16361
  const channelIds = await ownedChannelIds(forward, workspaces);
16261
16362
  if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
16262
- if (table === "threads") {
16363
+ if (CHANNEL_SCOPED.has(table)) {
16364
+ params.append("channel_id", inList(channelIds));
16365
+ } else if (table === "threads") {
16263
16366
  if (method === "POST") {
16264
16367
  const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
16265
16368
  if (!ok3) return deny("A thread must belong to a channel of the device workspaces.");
@@ -16344,14 +16447,14 @@ function handleHostDbResult(id, res) {
16344
16447
  }
16345
16448
  function whenConnected() {
16346
16449
  if (sender) return Promise.resolve(sender);
16347
- return new Promise((resolve8, reject) => {
16450
+ return new Promise((resolve7, reject) => {
16348
16451
  const timer2 = setTimeout(() => {
16349
16452
  onSender = onSender.filter((w) => w !== wake);
16350
16453
  reject(new Error("Host is not connected to the Data API"));
16351
16454
  }, REQUEST_TIMEOUT_MS3);
16352
16455
  const wake = () => {
16353
16456
  clearTimeout(timer2);
16354
- resolve8(sender);
16457
+ resolve7(sender);
16355
16458
  };
16356
16459
  onSender.push(wake);
16357
16460
  });
@@ -16359,12 +16462,12 @@ function whenConnected() {
16359
16462
  async function request(req) {
16360
16463
  const send2 = await whenConnected();
16361
16464
  const id = randomUUID3();
16362
- return new Promise((resolve8, reject) => {
16465
+ return new Promise((resolve7, reject) => {
16363
16466
  const timer2 = setTimeout(() => {
16364
16467
  pending.delete(id);
16365
16468
  reject(new Error("Data API did not answer in time"));
16366
16469
  }, REQUEST_TIMEOUT_MS3);
16367
- pending.set(id, { resolve: resolve8, reject, timer: timer2 });
16470
+ pending.set(id, { resolve: resolve7, reject, timer: timer2 });
16368
16471
  send2({ type: "db", id, req });
16369
16472
  });
16370
16473
  }
@@ -16424,7 +16527,7 @@ function dataSocketUrl() {
16424
16527
  return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
16425
16528
  }
16426
16529
  function listLocalSessions() {
16427
- if (!existsSync25(paths.DB_FILE)) return [];
16530
+ if (!existsSync24(paths.DB_FILE)) return [];
16428
16531
  const store = new SessionStore(paths.DB_FILE);
16429
16532
  try {
16430
16533
  return store.listSessions();
@@ -16461,7 +16564,7 @@ function buildHostConfigSnapshot() {
16461
16564
  const snapshot3 = {};
16462
16565
  for (const profile of listProfiles()) {
16463
16566
  snapshot3[profile] = runWithProfile(profile, () => ({
16464
- 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")) : {},
16465
16568
  agentModel: loadDashboardSettings().agent
16466
16569
  }));
16467
16570
  }
@@ -16672,16 +16775,16 @@ function callHost(method, args) {
16672
16775
  return Promise.resolve(null);
16673
16776
  }
16674
16777
  const id = randomUUID4();
16675
- return new Promise((resolve8) => {
16778
+ return new Promise((resolve7) => {
16676
16779
  const timer2 = setTimeout(() => {
16677
16780
  pending2.delete(id);
16678
16781
  console.error(`[host-socket] ${method} -> timed out`);
16679
- resolve8(null);
16782
+ resolve7(null);
16680
16783
  }, CALL_TIMEOUT_MS);
16681
16784
  pending2.set(id, {
16682
16785
  resolve: (value) => {
16683
16786
  clearTimeout(timer2);
16684
- resolve8(value);
16787
+ resolve7(value);
16685
16788
  }
16686
16789
  });
16687
16790
  send(ws, { type: "call", id, method, profile, args });
@@ -17499,14 +17602,14 @@ function startHostStatusBroadcast() {
17499
17602
 
17500
17603
  // src/dashboard/server.ts
17501
17604
  var DEFAULT_DASHBOARD_PORT = 4747;
17502
- var WEB_DIST = join22(paths.PACKAGE_ROOT, "web", "dist");
17605
+ var WEB_DIST = join23(paths.PACKAGE_ROOT, "web", "dist");
17503
17606
  var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
17504
17607
  async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
17505
17608
  for (let port = start; port < start + attempts; port++) {
17506
- const free = await new Promise((resolve8) => {
17609
+ const free = await new Promise((resolve7) => {
17507
17610
  const probe3 = createServer();
17508
- probe3.once("error", () => resolve8(false));
17509
- probe3.once("listening", () => probe3.close(() => resolve8(true)));
17611
+ probe3.once("error", () => resolve7(false));
17612
+ probe3.once("listening", () => probe3.close(() => resolve7(true)));
17510
17613
  probe3.listen(port, host);
17511
17614
  });
17512
17615
  if (free) return port;
@@ -17573,14 +17676,14 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
17573
17676
  } else {
17574
17677
  app.route("/share", createPublicShareAssetRouter(WEB_DIST));
17575
17678
  app.route("/", createAppIconRouter());
17576
- if (existsSync26(WEB_DIST) || existsSync26(join22(paths.PACKAGE_ROOT, "web", "package.json"))) {
17679
+ if (existsSync25(WEB_DIST) || existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"))) {
17577
17680
  app.use("/*", serveStatic({ root: WEB_DIST }));
17578
17681
  }
17579
17682
  app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
17580
- const spaFallback = serveStatic({ path: join22(WEB_DIST, "index.html") });
17683
+ const spaFallback = serveStatic({ path: join23(WEB_DIST, "index.html") });
17581
17684
  app.get("*", async (c, next) => {
17582
- if (!existsSync26(join22(WEB_DIST, "index.html"))) {
17583
- const isCheckout = existsSync26(join22(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"));
17584
17687
  return c.text(
17585
17688
  isCheckout ? `Workspace UI build missing (${WEB_DIST}).
17586
17689
  Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
@@ -17635,7 +17738,7 @@ function watchServices() {
17635
17738
 
17636
17739
  // src/slack/image-uploader.ts
17637
17740
  import { readFile } from "fs/promises";
17638
- import { basename as basename6 } from "path";
17741
+ import { basename as basename7 } from "path";
17639
17742
  import sharp6 from "sharp";
17640
17743
  var MAX_WIDTH = 800;
17641
17744
  async function uploadImage(client2, { channel, filePath, threadTs, text }) {
@@ -17644,7 +17747,7 @@ async function uploadImage(client2, { channel, filePath, threadTs, text }) {
17644
17747
  const base = {
17645
17748
  channel_id: channel,
17646
17749
  file: resized,
17647
- filename: basename6(filePath),
17750
+ filename: basename7(filePath),
17648
17751
  ...text ? { initial_comment: text } : {}
17649
17752
  };
17650
17753
  const result = await client2.filesUploadV2(threadTs ? { ...base, thread_ts: threadTs } : base);
@@ -18018,7 +18121,7 @@ async function threadReplies(client2, nameOrId, ts2) {
18018
18121
  async function postMessage(client2, args) {
18019
18122
  const channelArg = getArg2(args, "--channel");
18020
18123
  const textFilePath = getArg2(args, "--text-file");
18021
- const text = textFilePath ? readFileSync17(textFilePath, "utf8") : getArg2(args, "--text");
18124
+ const text = textFilePath ? readFileSync18(textFilePath, "utf8") : getArg2(args, "--text");
18022
18125
  const threadTs = getArg2(args, "--thread");
18023
18126
  const markdown = args.includes("--markdown");
18024
18127
  const broadcast = args.includes("--broadcast") && Boolean(threadTs);
@@ -18113,10 +18216,10 @@ function hasClaudeBinary() {
18113
18216
  }
18114
18217
  }
18115
18218
  function defaultClaudeConfigDir() {
18116
- return process.env.CLAUDE_CONFIG_DIR || resolve7(homedir9(), ".claude");
18219
+ return process.env.CLAUDE_CONFIG_DIR || resolve6(homedir9(), ".claude");
18117
18220
  }
18118
18221
  function hasClaudeCredentials(configDir2) {
18119
- if (existsSync27(resolve7(configDir2, ".credentials.json"))) {
18222
+ if (existsSync26(resolve6(configDir2, ".credentials.json"))) {
18120
18223
  return true;
18121
18224
  }
18122
18225
  if (process.platform === "darwin") {
@@ -18153,7 +18256,7 @@ async function ensureClaudeCodeReady() {
18153
18256
  console.log(" Flyspace spawns Claude Code as a subprocess. Without credentials, the bot");
18154
18257
  console.log(" will prompt /login inside Slack at runtime and you cannot complete the");
18155
18258
  console.log(" browser sign-in from there.\n");
18156
- if (claudeConfigDir !== resolve7(homedir9(), ".claude")) {
18259
+ if (claudeConfigDir !== resolve6(homedir9(), ".claude")) {
18157
18260
  console.log(" Tip: run with the same CLAUDE_CONFIG_DIR you use for this Claude Code install:");
18158
18261
  console.log(` \x1B[1mCLAUDE_CONFIG_DIR=${claudeConfigDir} claude\x1B[0m
18159
18262
  `);
@@ -18161,7 +18264,7 @@ async function ensureClaudeCodeReady() {
18161
18264
  console.log(" Open a new terminal and run \x1B[1mclaude\x1B[0m to complete the sign-in flow,");
18162
18265
  console.log(" then come back here.\n");
18163
18266
  }
18164
- const proceed = await confirm3({
18267
+ const proceed = await confirm4({
18165
18268
  message: "Continue setup anyway?",
18166
18269
  default: false
18167
18270
  });
@@ -18172,8 +18275,8 @@ async function ensureClaudeCodeReady() {
18172
18275
  return { claudeConfigDir };
18173
18276
  }
18174
18277
  function getManifestYaml(botName) {
18175
- const manifestPath = resolve7(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
18176
- const raw = readFileSync17(manifestPath, "utf-8");
18278
+ const manifestPath = resolve6(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
18279
+ const raw = readFileSync18(manifestPath, "utf-8");
18177
18280
  return raw.replace(/name: Custie/g, `name: ${botName}`);
18178
18281
  }
18179
18282
  async function stepPersonalise() {
@@ -18386,7 +18489,7 @@ async function stepBrowserTokens(botName) {
18386
18489
  })
18387
18490
  ));
18388
18491
  const DEBUG = process.argv.includes("--debug");
18389
- const USER_DATA_DIR = resolve7(tmpdir4(), "custie-playwright-profile");
18492
+ const USER_DATA_DIR = resolve6(tmpdir4(), "custie-playwright-profile");
18390
18493
  let context;
18391
18494
  try {
18392
18495
  log4("Opening browser \u2014 a Slack login page will appear.\n");
@@ -18418,14 +18521,14 @@ async function stepBrowserTokens(botName) {
18418
18521
  const appLink = page2.locator('a[href*="/apps/A"]').first();
18419
18522
  const hasExistingApp = await appLink.isVisible({ timeout: 3e3 }).catch(() => false);
18420
18523
  if (hasExistingApp) {
18421
- const tokenPromise = new Promise((resolve8) => {
18524
+ const tokenPromise = new Promise((resolve7) => {
18422
18525
  page2.on("request", (req) => {
18423
18526
  if (token) return;
18424
18527
  const postData = req.postData() || "";
18425
18528
  const match = postData.match(/xoxc-[a-f0-9-]+/);
18426
18529
  if (match) {
18427
18530
  token = match[0];
18428
- resolve8(token);
18531
+ resolve7(token);
18429
18532
  }
18430
18533
  });
18431
18534
  });
@@ -18476,7 +18579,7 @@ async function stepBrowserTokens(botName) {
18476
18579
  log4("Almost done! The browser will now open an install page.\n");
18477
18580
  console.log(' \x1B[1mPlease click "Install to Workspace", then click "Allow".\x1B[0m\n');
18478
18581
  await page2.goto(installUrl);
18479
- const installPage = await new Promise((resolve8, reject) => {
18582
+ const installPage = await new Promise((resolve7, reject) => {
18480
18583
  let settled = false;
18481
18584
  let interval;
18482
18585
  let timeout;
@@ -18489,7 +18592,7 @@ async function stepBrowserTokens(botName) {
18489
18592
  };
18490
18593
  const checkUrl = (p) => {
18491
18594
  if (p.url().includes("install-on-team?success=1")) {
18492
- finish(() => resolve8(p));
18595
+ finish(() => resolve7(p));
18493
18596
  }
18494
18597
  };
18495
18598
  context.on("page", (newPage) => {
@@ -18548,7 +18651,7 @@ async function stepBrowserTokens(botName) {
18548
18651
  try {
18549
18652
  const pages = context?.pages();
18550
18653
  if (pages?.[0]) {
18551
- const screenshotPath = resolve7(paths.PACKAGE_ROOT, "setup-error.png");
18654
+ const screenshotPath = resolve6(paths.PACKAGE_ROOT, "setup-error.png");
18552
18655
  await pages[0].screenshot({ path: screenshotPath, fullPage: true });
18553
18656
  warn4(`Screenshot saved to: ${screenshotPath}`);
18554
18657
  }
@@ -18590,25 +18693,23 @@ function writeConfigFile(config) {
18590
18693
  `OWNER_USER_ID=${config.ownerUserId}`,
18591
18694
  ``
18592
18695
  ].join("\n");
18593
- writeFileSync10(paths.CONFIG_FILE, envContent);
18696
+ writeFileSync11(paths.CONFIG_FILE, envContent);
18594
18697
  success4(`Config written to ${paths.CONFIG_FILE}`);
18595
18698
  }
18596
18699
  function printNextSteps() {
18597
18700
  log4("Setup complete! Next steps:\n");
18598
- console.log(" 1. Customise the system prompt (optional):");
18599
- console.log(` Run \x1B[1mflyspace prompt\x1B[0m to edit ${paths.PROMPT_FILE}
18600
- `);
18701
+ console.log(" 1. Customise the system prompt (optional) in Settings \u2192 Prompt.\n");
18601
18702
  console.log(" 2. Start the bot:");
18602
18703
  console.log(" Run \x1B[1mflyspace start\x1B[0m\n");
18603
18704
  console.log(" 3. Install as a background service (optional):");
18604
- console.log(" Run \x1B[1mflyspace install\x1B[0m\n");
18705
+ console.log(" Run \x1B[1mflyspace service install\x1B[0m\n");
18605
18706
  }
18606
18707
  function copyDefaultPrompt(botName) {
18607
- if (!existsSync27(paths.PROMPT_FILE)) {
18608
- const defaultPrompt = resolve7(paths.PACKAGE_ROOT, "system.default.md");
18609
- if (existsSync27(defaultPrompt)) {
18610
- const content = readFileSync17(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
18611
- 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);
18612
18713
  success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
18613
18714
  }
18614
18715
  }
@@ -18620,8 +18721,8 @@ async function runSlackSetup(args) {
18620
18721
  const prereq = await ensureClaudeCodeReady();
18621
18722
  if (!prereq) return;
18622
18723
  const { claudeConfigDir } = prereq;
18623
- if (existsSync27(paths.CONFIG_FILE)) {
18624
- const reconfigure = await confirm3({
18724
+ if (existsSync26(paths.CONFIG_FILE)) {
18725
+ const reconfigure = await confirm4({
18625
18726
  message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
18626
18727
  default: false
18627
18728
  });
@@ -18930,7 +19031,85 @@ async function runChatCmd(args) {
18930
19031
  }
18931
19032
 
18932
19033
  // src/commands/memory.ts
18933
- 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
18934
19113
  var USAGE5 = `
18935
19114
  Usage: flyspace memory <subcommand> --channel <name-or-id> [options]
18936
19115
 
@@ -18945,6 +19124,10 @@ var USAGE5 = `
18945
19124
  --body <text | -> "-" reads the body from stdin
18946
19125
  [--thread <id>] The thread the memory came from
18947
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
18948
19131
 
18949
19132
  Types: ${MEMORY_TYPES.join(", ")}
18950
19133
  `;
@@ -19010,7 +19193,7 @@ ${memory.body}`);
19010
19193
  );
19011
19194
  }
19012
19195
  if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
19013
- const body = bodyArg === "-" ? readFileSync18(0, "utf-8") : bodyArg;
19196
+ const body = bodyArg === "-" ? readFileSync20(0, "utf-8") : bodyArg;
19014
19197
  const thread = getArg4(rest, "--thread");
19015
19198
  const { created, changed } = await saveChannelMemory(
19016
19199
  {
@@ -19036,6 +19219,20 @@ ${memory.body}`);
19036
19219
  console.log(`Deleted memory ${ref}.`);
19037
19220
  break;
19038
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
+ }
19039
19236
  default:
19040
19237
  console.error(`Unknown subcommand: ${subcommand}`);
19041
19238
  console.log(USAGE5);
@@ -19080,10 +19277,10 @@ async function runMcpCmd(args) {
19080
19277
  }
19081
19278
 
19082
19279
  // src/commands/dashboard.ts
19083
- import { spawn as spawn6, spawnSync } from "child_process";
19280
+ import { spawn as spawn5, spawnSync } from "child_process";
19084
19281
  import { existsSync as existsSync28 } from "fs";
19085
19282
  import { platform as platform7 } from "os";
19086
- import { join as join23 } from "path";
19283
+ import { join as join25 } from "path";
19087
19284
  function flagValue(args, name) {
19088
19285
  const eq = args.find((a) => a.startsWith(`--${name}=`));
19089
19286
  if (eq) return eq.slice(`--${name}=`.length);
@@ -19093,13 +19290,13 @@ function flagValue(args, name) {
19093
19290
  function openBrowser2(url) {
19094
19291
  const cmd = platform7() === "darwin" ? "open" : platform7() === "win32" ? "start" : "xdg-open";
19095
19292
  try {
19096
- spawn6(cmd, [url], { stdio: "ignore", detached: true }).unref();
19293
+ spawn5(cmd, [url], { stdio: "ignore", detached: true }).unref();
19097
19294
  } catch {
19098
19295
  }
19099
19296
  }
19100
19297
  function ensureWebBuild() {
19101
- if (existsSync28(join23(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
19102
- if (!existsSync28(join23(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;
19103
19300
  console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
19104
19301
  const result = spawnSync("pnpm", ["--dir", "web", "build"], {
19105
19302
  cwd: paths.PACKAGE_ROOT,
@@ -19149,7 +19346,7 @@ async function runDashboard(args) {
19149
19346
  }
19150
19347
 
19151
19348
  // src/commands/workspace.ts
19152
- 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";
19153
19350
  var USAGE7 = `
19154
19351
  Usage: flyspace workspace <subcommand> [options]
19155
19352
 
@@ -19238,7 +19435,7 @@ function provisionLocal(botName) {
19238
19435
  if (existsSync29(paths.CONFIG_FILE)) {
19239
19436
  console.log(" config.env exists");
19240
19437
  } else {
19241
- writeFileSync11(paths.CONFIG_FILE, `BOT_NAME=${botName}
19438
+ writeFileSync12(paths.CONFIG_FILE, `BOT_NAME=${botName}
19242
19439
  `);
19243
19440
  console.log(` config.env written (${paths.CONFIG_FILE})`);
19244
19441
  }
@@ -19338,18 +19535,16 @@ var USAGE9 = `
19338
19535
  setup Set up this machine as a Host (login, agent CLIs, ~/Flyspace)
19339
19536
  auth Log in, check status, or log out this device
19340
19537
  doctor Check this Host end to end (login, agent CLIs, services)
19341
- start Start the agent host (Slack only if configured)
19342
- install Install as a system service (launchd / systemd)
19343
- uninstall Remove the system service
19538
+ start Start the agent host in the foreground
19539
+ service Run the host in the background (install / uninstall)
19344
19540
  stop Stop the service (without uninstalling)
19345
19541
  restart Restart the service
19346
19542
  logs Tail the service log (-e for error log)
19347
19543
  upgrade Upgrade Flyspace to the latest version
19348
- prompt Edit the system prompt in $EDITOR
19544
+ uninstall Remove Flyspace from this machine (CLI, services, config)
19349
19545
  config Show resolved config (--edit to edit, --path for file path)
19350
19546
  automation Manage scheduled tasks and triggers
19351
19547
  usage Show agent token usage and cost for this workspace
19352
- slack Query Slack (channels, users, post messages)
19353
19548
  chat Inspect dashboard chat channels and move threads between them
19354
19549
  memory Read and write a channel's persistent agent memory
19355
19550
  profiles List local profiles (bound here? scheduler?) or remove one
@@ -19407,11 +19602,20 @@ async function main() {
19407
19602
  case "reconcile":
19408
19603
  await runReconcileCmd(args.slice(1));
19409
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.
19410
19614
  case "install":
19411
19615
  await runInstall();
19412
19616
  break;
19413
19617
  case "uninstall":
19414
- await runUninstall();
19618
+ await runUninstall(args.slice(1));
19415
19619
  break;
19416
19620
  case "stop":
19417
19621
  await runStop();
@@ -19425,9 +19629,6 @@ async function main() {
19425
19629
  case "upgrade":
19426
19630
  await runUpgrade();
19427
19631
  break;
19428
- case "prompt":
19429
- await runPrompt();
19430
- break;
19431
19632
  case "config":
19432
19633
  await runConfig(args.slice(1));
19433
19634
  break;
@@ -19458,7 +19659,7 @@ async function main() {
19458
19659
  break;
19459
19660
  case "-v":
19460
19661
  case "--version":
19461
- printRaw("0.1.9");
19662
+ printRaw("0.1.12");
19462
19663
  break;
19463
19664
  case "-h":
19464
19665
  case "--help":