flyspace-cli 0.1.30 → 0.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +315 -235
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -642,6 +642,26 @@ async function requestHostDb(req) {
642
642
  send2({ type: "db", id, req });
643
643
  });
644
644
  }
645
+ var pendingUrls = /* @__PURE__ */ new Map();
646
+ function handleAttachmentUrlResult(id, url) {
647
+ pendingUrls.get(id)?.(url);
648
+ pendingUrls.delete(id);
649
+ }
650
+ async function requestAttachmentUrl(key) {
651
+ const send2 = await whenConnected();
652
+ const id = randomUUID();
653
+ return new Promise((resolve7, reject) => {
654
+ const timer2 = setTimeout(() => {
655
+ pendingUrls.delete(id);
656
+ reject(new Error("Data API did not answer in time"));
657
+ }, REQUEST_TIMEOUT_MS);
658
+ pendingUrls.set(id, (url) => {
659
+ clearTimeout(timer2);
660
+ resolve7(url);
661
+ });
662
+ send2({ type: "attachment-url", id, key });
663
+ });
664
+ }
645
665
  var BODYLESS_STATUS = /* @__PURE__ */ new Set([101, 204, 205, 304]);
646
666
  function dbFetch(send2) {
647
667
  return async (input2, init2) => {
@@ -861,14 +881,17 @@ function isServiceInstalled(profile) {
861
881
  return false;
862
882
  }
863
883
  function serviceBinaryMissing(file) {
884
+ const bin = serviceBinary(file);
885
+ return !!bin && !existsSync3(bin);
886
+ }
887
+ function serviceBinary(file) {
864
888
  let text;
865
889
  try {
866
890
  text = readFileSync5(file, "utf-8");
867
891
  } catch {
868
- return false;
892
+ return void 0;
869
893
  }
870
- const bin = text.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/)?.[1] ?? text.match(/^ExecStart=(\S+)/m)?.[1];
871
- return !!bin && !existsSync3(bin);
894
+ return text.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/)?.[1] ?? text.match(/^ExecStart=(\S+)/m)?.[1];
872
895
  }
873
896
  function profileServiceFile(profile) {
874
897
  return platform() === "darwin" ? plistPath(profile) : systemdUnitPath(profile);
@@ -1835,7 +1858,7 @@ async function runAuthCmd(args) {
1835
1858
  }
1836
1859
 
1837
1860
  // ../api/src/version.ts
1838
- var CLI_VERSION = true ? "0.1.30" : null;
1861
+ var CLI_VERSION = true ? "0.1.32" : null;
1839
1862
  function isOlderVersion(a, b) {
1840
1863
  const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
1841
1864
  const [pa, pb] = [parse(a), parse(b)];
@@ -2381,8 +2404,8 @@ async function runProfiles(args = []) {
2381
2404
 
2382
2405
  // src/commands/upgrade.ts
2383
2406
  import { execSync as execSync6 } from "child_process";
2384
- import { realpathSync as realpathSync3 } from "fs";
2385
- import { join as join11 } from "path";
2407
+ import { existsSync as existsSync9, readFileSync as readFileSync10, realpathSync as realpathSync3 } from "fs";
2408
+ import { dirname as dirname5, join as join11 } from "path";
2386
2409
  function restartInstalledProfiles() {
2387
2410
  if (isHostServiceInstalled()) {
2388
2411
  console.log(restartHostService() ? "\nHost restarted." : "\nHost restart failed; run: flyspace setup --start-host --restart");
@@ -2402,36 +2425,68 @@ Restarting ${installed.length} instance(s)...`);
2402
2425
  }
2403
2426
  }
2404
2427
  }
2405
- function npmInstallCommand() {
2406
- let prefix;
2428
+ function installTarget(entry) {
2429
+ const home2 = flyspaceHome(entry) ?? (entry.endsWith("/bin/flyspace") ? dirname5(dirname5(entry)) : void 0);
2430
+ if (home2 && existsSync9(join11(home2, "cli", "lib", "node_modules", "flyspace-cli"))) {
2431
+ return { prefix: join11(home2, "cli"), nodeBin: join11(home2, "node", "bin") };
2432
+ }
2407
2433
  try {
2408
- const entry = realpathSync3(process.argv[1] ?? "");
2409
- const at = entry.indexOf(`/lib/node_modules/${"flyspace-cli"}/`);
2410
- if (at > 0) prefix = entry.slice(0, at);
2434
+ const real = realpathSync3(entry);
2435
+ const at = real.indexOf(`/lib/node_modules/${"flyspace-cli"}/`);
2436
+ if (at > 0) return { prefix: real.slice(0, at), nodeBin: join11(real.slice(0, at), "bin") };
2411
2437
  } catch {
2412
2438
  }
2413
- const global = `-g${prefix ? ` --prefix "${prefix}"` : ""}`;
2439
+ return void 0;
2440
+ }
2441
+ function installTargets() {
2442
+ const files = [
2443
+ ...isHostServiceInstalled() ? [hostServiceFile()] : [],
2444
+ ...listProfiles().filter(isServiceInstalled).map(profileServiceFile)
2445
+ ];
2446
+ const entries2 = [process.argv[1] ?? "", ...files.map(serviceBinary).filter((b) => !!b)];
2447
+ const byPrefix = /* @__PURE__ */ new Map();
2448
+ for (const target of entries2.map(installTarget)) {
2449
+ if (target && !byPrefix.has(target.prefix)) byPrefix.set(target.prefix, target);
2450
+ }
2451
+ return [...byPrefix.values()];
2452
+ }
2453
+ function installedVersion(target) {
2454
+ try {
2455
+ const pkg = join11(target.prefix, "lib", "node_modules", "flyspace-cli", "package.json");
2456
+ return JSON.parse(readFileSync10(pkg, "utf-8")).version;
2457
+ } catch {
2458
+ return void 0;
2459
+ }
2460
+ }
2461
+ function npmInstallCommand(target) {
2462
+ const global = `-g${target ? ` --prefix "${target.prefix}"` : ""}`;
2414
2463
  return `npm install ${global} --ignore-scripts ${"flyspace-cli"}@latest && npm rebuild ${global} better-sqlite3`;
2415
2464
  }
2416
- function npmEnv() {
2465
+ function npmEnv(target) {
2417
2466
  const home2 = flyspaceHome();
2418
- return home2 ? { ...process.env, PATH: `${join11(home2, "node", "bin")}:${process.env.PATH ?? ""}` } : process.env;
2467
+ const nodeBin = target?.nodeBin ?? (home2 ? join11(home2, "node", "bin") : void 0);
2468
+ return nodeBin ? { ...process.env, PATH: `${nodeBin}:${process.env.PATH ?? ""}` } : process.env;
2419
2469
  }
2420
2470
  async function runUpgrade() {
2421
- const current = "0.1.30";
2471
+ const current = "0.1.32";
2422
2472
  console.log(`Current version: ${current}`);
2423
2473
  console.log("Checking for updates...\n");
2424
2474
  try {
2425
2475
  const latest2 = execSync6(`npm view ${"flyspace-cli"} version`, { encoding: "utf-8", env: npmEnv() }).trim();
2426
- if (latest2 === current) {
2476
+ const stale = installTargets().filter((t) => installedVersion(t) !== latest2);
2477
+ if (stale.length === 0) {
2427
2478
  console.log(`Already on the latest version (${current}).`);
2428
2479
  return;
2429
2480
  }
2430
2481
  console.log(`New version available: ${latest2}`);
2431
- console.log("Upgrading...\n");
2432
- execSync6(npmInstallCommand(), { stdio: "inherit", env: npmEnv() });
2482
+ for (const target of stale) {
2483
+ console.log(`
2484
+ Upgrading ${target.prefix} (${installedVersion(target) ?? "unknown"})...
2485
+ `);
2486
+ execSync6(npmInstallCommand(target), { stdio: "inherit", env: npmEnv(target) });
2487
+ }
2433
2488
  console.log(`
2434
- Upgraded from ${current} to ${latest2}.`);
2489
+ Upgraded to ${latest2}.`);
2435
2490
  restartInstalledProfiles();
2436
2491
  console.log("\nReload your shell to use the new version:");
2437
2492
  console.log(" source ~/.zshrc # or source ~/.bashrc");
@@ -2447,13 +2502,13 @@ You can upgrade manually with: ${npmInstallCommand()}`);
2447
2502
  import { WebClient } from "@slack/web-api";
2448
2503
 
2449
2504
  // src/automations/config-file.ts
2450
- import { readFileSync as readFileSync10, existsSync as existsSync9 } from "fs";
2505
+ import { readFileSync as readFileSync11, existsSync as existsSync10 } from "fs";
2451
2506
  import yaml from "js-yaml";
2452
2507
  var EMPTY_CONFIG = { schedules: [], triggers: [], mention_triggers: [] };
2453
2508
  function loadAutomationsFile() {
2454
2509
  const filePath = paths.AUTOMATIONS_FILE;
2455
- if (!existsSync9(filePath)) return { ...EMPTY_CONFIG };
2456
- const raw = readFileSync10(filePath, "utf-8");
2510
+ if (!existsSync10(filePath)) return { ...EMPTY_CONFIG };
2511
+ const raw = readFileSync11(filePath, "utf-8");
2457
2512
  if (!raw.trim()) return { ...EMPTY_CONFIG };
2458
2513
  const parsed = yaml.load(raw);
2459
2514
  return {
@@ -2809,7 +2864,7 @@ async function runAutomationCmd(args) {
2809
2864
  }
2810
2865
 
2811
2866
  // src/commands/usage.ts
2812
- import { existsSync as existsSync10 } from "fs";
2867
+ import { existsSync as existsSync11 } from "fs";
2813
2868
  var HELP4 = `
2814
2869
  Usage: flyspace usage [options]
2815
2870
 
@@ -2936,7 +2991,7 @@ async function runUsage(args) {
2936
2991
  return;
2937
2992
  }
2938
2993
  const { days, by, explain, json } = parsed;
2939
- if (!existsSync10(paths.DB_FILE)) {
2994
+ if (!existsSync11(paths.DB_FILE)) {
2940
2995
  console.log("No usage recorded yet (the database does not exist).");
2941
2996
  return;
2942
2997
  }
@@ -3051,13 +3106,17 @@ async function runRestart() {
3051
3106
 
3052
3107
  // src/commands/logs.ts
3053
3108
  import { spawn as spawn3 } from "child_process";
3054
- import { existsSync as existsSync11 } from "fs";
3109
+ import { existsSync as existsSync12 } from "fs";
3055
3110
  import { join as join12 } from "path";
3111
+ function serviceLogFile(errors) {
3112
+ const suffix = errors ? "-error.log" : ".log";
3113
+ const candidates = ["host", "custie"].map((name) => join12(paths.LOG_DIR, `${name}${suffix}`));
3114
+ return candidates.find((file) => existsSync12(file)) ?? candidates[0];
3115
+ }
3056
3116
  async function runLogs(args) {
3057
3117
  const showErrors = args.includes("--error") || args.includes("-e");
3058
- const filename = showErrors ? "custie-error.log" : "custie.log";
3059
- const logFile = join12(paths.LOG_DIR, filename);
3060
- if (!existsSync11(logFile)) {
3118
+ const logFile = serviceLogFile(showErrors);
3119
+ if (!existsSync12(logFile)) {
3061
3120
  console.error(`Log file not found: ${logFile}`);
3062
3121
  console.error("Is the service installed? Run `flyspace install` first.");
3063
3122
  process.exit(1);
@@ -3078,7 +3137,7 @@ async function runLogs(args) {
3078
3137
 
3079
3138
  // src/commands/slack.ts
3080
3139
  import { execFileSync as execFileSync3 } from "child_process";
3081
- import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync7 } from "fs";
3140
+ import { existsSync as existsSync13, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
3082
3141
  import { resolve as resolve4 } from "path";
3083
3142
  import { tmpdir as tmpdir2, homedir as homedir6 } from "os";
3084
3143
  import { input, confirm as confirm4, select, checkbox } from "@inquirer/prompts";
@@ -5164,7 +5223,7 @@ async function threadReplies(client2, nameOrId, ts2) {
5164
5223
  async function postMessage(client2, args) {
5165
5224
  const channelArg = getArg2(args, "--channel");
5166
5225
  const textFilePath = getArg2(args, "--text-file");
5167
- const text = textFilePath ? readFileSync11(textFilePath, "utf8") : getArg2(args, "--text");
5226
+ const text = textFilePath ? readFileSync12(textFilePath, "utf8") : getArg2(args, "--text");
5168
5227
  const threadTs = getArg2(args, "--thread");
5169
5228
  const markdown = args.includes("--markdown");
5170
5229
  const broadcast = args.includes("--broadcast") && Boolean(threadTs);
@@ -5262,7 +5321,7 @@ function defaultClaudeConfigDir() {
5262
5321
  return process.env.CLAUDE_CONFIG_DIR || resolve4(homedir6(), ".claude");
5263
5322
  }
5264
5323
  function hasClaudeCredentials(configDir2) {
5265
- if (existsSync12(resolve4(configDir2, ".credentials.json"))) {
5324
+ if (existsSync13(resolve4(configDir2, ".credentials.json"))) {
5266
5325
  return true;
5267
5326
  }
5268
5327
  if (process.platform === "darwin") {
@@ -5319,7 +5378,7 @@ async function ensureClaudeCodeReady() {
5319
5378
  }
5320
5379
  function getManifestYaml(botName) {
5321
5380
  const manifestPath = resolve4(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
5322
- const raw = readFileSync11(manifestPath, "utf-8");
5381
+ const raw = readFileSync12(manifestPath, "utf-8");
5323
5382
  return raw.replace(/name: Custie/g, `name: ${botName}`);
5324
5383
  }
5325
5384
  async function stepPersonalise() {
@@ -5748,10 +5807,10 @@ function printNextSteps() {
5748
5807
  console.log(" Run \x1B[1mflyspace service install\x1B[0m\n");
5749
5808
  }
5750
5809
  function copyDefaultPrompt(botName) {
5751
- if (!existsSync12(paths.PROMPT_FILE)) {
5810
+ if (!existsSync13(paths.PROMPT_FILE)) {
5752
5811
  const defaultPrompt = resolve4(paths.PACKAGE_ROOT, "system.default.md");
5753
- if (existsSync12(defaultPrompt)) {
5754
- const content = readFileSync11(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
5812
+ if (existsSync13(defaultPrompt)) {
5813
+ const content = readFileSync12(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
5755
5814
  writeFileSync7(paths.PROMPT_FILE, content);
5756
5815
  success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
5757
5816
  }
@@ -5764,7 +5823,7 @@ async function runSlackSetup(args) {
5764
5823
  const prereq = await ensureClaudeCodeReady();
5765
5824
  if (!prereq) return;
5766
5825
  const { claudeConfigDir } = prereq;
5767
- if (existsSync12(paths.CONFIG_FILE)) {
5826
+ if (existsSync13(paths.CONFIG_FILE)) {
5768
5827
  const reconfigure = await confirm4({
5769
5828
  message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
5770
5829
  default: false
@@ -6075,10 +6134,10 @@ async function runChatCmd(args) {
6075
6134
  }
6076
6135
 
6077
6136
  // src/commands/memory.ts
6078
- import { readFileSync as readFileSync13 } from "fs";
6137
+ import { readFileSync as readFileSync14 } from "fs";
6079
6138
 
6080
6139
  // src/store/claude-memory-import.ts
6081
- import { existsSync as existsSync13, readdirSync as readdirSync3, readFileSync as readFileSync12 } from "fs";
6140
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync13 } from "fs";
6082
6141
  import { homedir as homedir7 } from "os";
6083
6142
  import { basename as basename5, join as join14 } from "path";
6084
6143
  import dotenv from "dotenv";
@@ -6088,7 +6147,7 @@ function claudeMemoryDir(cwd, configDir2) {
6088
6147
  function profileClaudeConfigDirFor(profile) {
6089
6148
  const dir = profile === "default" ? paths.BASE_CONFIG_DIR : join14(paths.PROFILES_DIR, profile);
6090
6149
  const file = join14(dir, "config.env");
6091
- const env = existsSync13(file) ? dotenv.parse(readFileSync12(file)) : {};
6150
+ const env = existsSync14(file) ? dotenv.parse(readFileSync13(file)) : {};
6092
6151
  return env["CLAUDE_CONFIG_DIR"]?.trim() || join14(homedir7(), ".claude");
6093
6152
  }
6094
6153
  var TYPE_MAP = {
@@ -6134,11 +6193,11 @@ function parseClaudeMemory(fileName, text, channelId) {
6134
6193
  return input2;
6135
6194
  }
6136
6195
  async function importClaudeMemories(dir, channelId, opts = {}) {
6137
- if (!existsSync13(dir)) throw new Error(`No Claude memory directory at ${dir}`);
6196
+ if (!existsSync14(dir)) throw new Error(`No Claude memory directory at ${dir}`);
6138
6197
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
6139
6198
  const results = [];
6140
6199
  for (const file of files) {
6141
- const parsed = parseClaudeMemory(file, readFileSync12(join14(dir, file), "utf-8"), channelId);
6200
+ const parsed = parseClaudeMemory(file, readFileSync13(join14(dir, file), "utf-8"), channelId);
6142
6201
  if ("skip" in parsed) {
6143
6202
  results.push({ file, outcome: "skipped", reason: parsed.skip });
6144
6203
  continue;
@@ -6250,7 +6309,7 @@ ${memory.body}`);
6250
6309
  );
6251
6310
  }
6252
6311
  if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
6253
- const body = bodyArg === "-" ? readFileSync13(0, "utf-8") : bodyArg;
6312
+ const body = bodyArg === "-" ? readFileSync14(0, "utf-8") : bodyArg;
6254
6313
  const thread = getArg4(rest, "--thread");
6255
6314
  const { created, changed } = await saveMemory(
6256
6315
  target,
@@ -6349,12 +6408,12 @@ async function runMcpCmd(args) {
6349
6408
 
6350
6409
  // src/commands/dashboard.ts
6351
6410
  import { spawn as spawn8, spawnSync } from "child_process";
6352
- import { existsSync as existsSync32 } from "fs";
6411
+ import { existsSync as existsSync33 } from "fs";
6353
6412
  import { platform as platform7 } from "os";
6354
- import { join as join29 } from "path";
6413
+ import { join as join28 } from "path";
6355
6414
 
6356
6415
  // src/reconcile.ts
6357
- import { existsSync as existsSync14, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
6416
+ import { existsSync as existsSync15, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
6358
6417
  async function boundWorkspaces2() {
6359
6418
  return (await boundWorkspaces())?.workspaces ?? null;
6360
6419
  }
@@ -6365,12 +6424,12 @@ async function servedWorkspaces2() {
6365
6424
  async function reconcileProfile(profile) {
6366
6425
  return runWithProfile(profile, async () => {
6367
6426
  const log5 = [];
6368
- const hadConfigDir = existsSync14(paths.CONFIG_DIR);
6369
- const hadDataDir = existsSync14(paths.DATA_DIR);
6427
+ const hadConfigDir = existsSync15(paths.CONFIG_DIR);
6428
+ const hadDataDir = existsSync15(paths.DATA_DIR);
6370
6429
  ensureDirs();
6371
6430
  if (!hadConfigDir) log5.push(`config dir created (${paths.CONFIG_DIR})`);
6372
6431
  if (!hadDataDir) log5.push(`data dir created (${paths.DATA_DIR})`);
6373
- if (!existsSync14(paths.CONFIG_FILE)) {
6432
+ if (!existsSync15(paths.CONFIG_FILE)) {
6374
6433
  writeFileSync8(paths.CONFIG_FILE, `BOT_NAME=${profile}
6375
6434
  `);
6376
6435
  log5.push(`config.env written (${paths.CONFIG_FILE})`);
@@ -6527,7 +6586,7 @@ var requireDocsAccess = async (c, next) => {
6527
6586
  import { Hono as Hono29 } from "hono";
6528
6587
 
6529
6588
  // ../api/src/dashboard/public-share-routes.ts
6530
- import { existsSync as existsSync16, readFileSync as readFileSync15, statSync as statSync3 } from "fs";
6589
+ import { existsSync as existsSync17, readFileSync as readFileSync16, statSync as statSync3 } from "fs";
6531
6590
  import { join as join16 } from "path";
6532
6591
  import { Hono as Hono5 } from "hono";
6533
6592
  import { GetObjectCommand } from "@aws-sdk/client-s3";
@@ -6993,11 +7052,11 @@ function injectShareHead(html, head) {
6993
7052
 
6994
7053
  // ../api/src/dashboard/app-icon.ts
6995
7054
  import { createHash as createHash2 } from "crypto";
6996
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
7055
+ import { existsSync as existsSync16, readFileSync as readFileSync15 } from "fs";
6997
7056
  import { join as join15 } from "path";
6998
7057
  import sharp2 from "sharp";
6999
7058
  var PLATE = "#0A0A0F";
7000
- var BRAND_DIR = existsSync15(join15(paths.WEB_ROOT, "public")) ? join15(paths.WEB_ROOT, "public") : join15(paths.PACKAGE_ROOT, "dist", "brand");
7059
+ var BRAND_DIR = existsSync16(join15(paths.WEB_ROOT, "public")) ? join15(paths.WEB_ROOT, "public") : join15(paths.PACKAGE_ROOT, "dist", "brand");
7001
7060
  var MARK_FILE = join15(BRAND_DIR, "icons", "icon-512.png");
7002
7061
  var MARK_SVG_FILE = join15(BRAND_DIR, "favicon.svg");
7003
7062
  var LAYOUT = {
@@ -7022,7 +7081,7 @@ function roundedPlate(size, fill = PLATE) {
7022
7081
  }
7023
7082
  async function renderBadge(box) {
7024
7083
  const transparent = { r: 0, g: 0, b: 0, alpha: 0 };
7025
- const raster = await sharp2(readFileSync14(MARK_SVG_FILE)).resize(512, 512, { fit: "contain", background: transparent }).png().toBuffer();
7084
+ const raster = await sharp2(readFileSync15(MARK_SVG_FILE)).resize(512, 512, { fit: "contain", background: transparent }).png().toBuffer();
7026
7085
  return sharp2(raster).trim().resize(box, box, { fit: "contain", background: transparent }).png().toBuffer();
7027
7086
  }
7028
7087
  async function sampleBackground(logo) {
@@ -7077,7 +7136,7 @@ async function renderAppIcon({
7077
7136
  if (hit) return hit;
7078
7137
  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,
7079
7138
  // either on its usual dark plate or, for a favicon, trimmed and bare.
7080
- (transparent ? await renderBadge(size) : await sharp2(readFileSync14(MARK_FILE)).resize(size, size, { fit: "cover" }).png().toBuffer());
7139
+ (transparent ? await renderBadge(size) : await sharp2(readFileSync15(MARK_FILE)).resize(size, size, { fit: "cover" }).png().toBuffer());
7081
7140
  cache3.set(key, out);
7082
7141
  return out;
7083
7142
  }
@@ -8295,7 +8354,7 @@ var shellCache = null;
8295
8354
  function readShell(path) {
8296
8355
  const { mtimeMs } = statSync3(path);
8297
8356
  if (shellCache?.mtimeMs === mtimeMs) return shellCache.html;
8298
- const html = readFileSync15(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
8357
+ const html = readFileSync16(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
8299
8358
  shellCache = { mtimeMs, html };
8300
8359
  return html;
8301
8360
  }
@@ -8316,16 +8375,16 @@ function serveBuildFile(c, webDist, dir, name) {
8316
8375
  return c.text("not found", 404);
8317
8376
  }
8318
8377
  const path = join16(webDist, dir, name);
8319
- if (!existsSync16(path)) return c.text("not found", 404);
8378
+ if (!existsSync17(path)) return c.text("not found", 404);
8320
8379
  c.header("Content-Type", type);
8321
8380
  c.header("Cache-Control", "public, max-age=31536000, immutable");
8322
8381
  if (ext === ".css") {
8323
- return c.body(readFileSync15(path, "utf8").replaceAll("url(/fonts/", `url(${BUILD_PREFIX}/fonts/`));
8382
+ return c.body(readFileSync16(path, "utf8").replaceAll("url(/fonts/", `url(${BUILD_PREFIX}/fonts/`));
8324
8383
  }
8325
8384
  if (ext === ".js") {
8326
- return c.body(readFileSync15(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
8385
+ return c.body(readFileSync16(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
8327
8386
  }
8328
- return c.body(new Uint8Array(readFileSync15(path)));
8387
+ return c.body(new Uint8Array(readFileSync16(path)));
8329
8388
  }
8330
8389
  var HTML_SHARE_CSP = "sandbox allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox";
8331
8390
  function servePublishedHtml(c, share) {
@@ -8462,7 +8521,7 @@ ${analytics}` : head);
8462
8521
  const lang = canonicalLanguage(requested);
8463
8522
  if (!isTranslatableLanguage(lang)) return next();
8464
8523
  const shell = join16(webDist, "index.html");
8465
- if (!existsSync16(shell)) return next();
8524
+ if (!existsSync17(shell)) return next();
8466
8525
  const share = await getDocShare(c.req.param("id"));
8467
8526
  if (!share) return next();
8468
8527
  if (requested !== lang) return c.redirect(`/share/${share.id}/${lang}`, 302);
@@ -8498,7 +8557,7 @@ ${analytics}` : head);
8498
8557
  if (!share) return next();
8499
8558
  if (share.kind === "html") return servePublishedHtml(c, share);
8500
8559
  const shell = join16(webDist, "index.html");
8501
- if (!existsSync16(shell)) return next();
8560
+ if (!existsSync17(shell)) return next();
8502
8561
  return readerPage(
8503
8562
  c,
8504
8563
  share,
@@ -8564,7 +8623,7 @@ function createGuestRouter() {
8564
8623
  }
8565
8624
 
8566
8625
  // ../api/src/dashboard/auth-routes.ts
8567
- import { Hono as Hono8 } from "hono";
8626
+ import { Hono as Hono9 } from "hono";
8568
8627
 
8569
8628
  // ../api/src/dashboard/supabase-server-client.ts
8570
8629
  import { createServerClient, parseCookieHeader, serializeCookieHeader } from "@supabase/ssr";
@@ -8675,7 +8734,7 @@ function pruneExpired(now) {
8675
8734
  }
8676
8735
  }
8677
8736
  var SUPPORT_NAME = "Flyspace Support";
8678
- var SUPPORT_AVATAR_URL = "/logo/flyspace-mark.svg";
8737
+ var SUPPORT_AVATAR_URL = "/logo/flyspace-support.svg";
8679
8738
  async function resolveAuthContext(c, opts = {}) {
8680
8739
  const cookie = c.req?.header("Cookie") ?? "";
8681
8740
  if (!cookie) return resolveAuthContextUncached(c);
@@ -8806,7 +8865,7 @@ async function verifyTurnstileToken(token) {
8806
8865
  }
8807
8866
 
8808
8867
  // ../api/src/dashboard/onboarding-routes.ts
8809
- import { Hono as Hono7 } from "hono";
8868
+ import { Hono as Hono8 } from "hono";
8810
8869
 
8811
8870
  // ../api/src/store/device-store.ts
8812
8871
  import { createHash as createHash7, createPublicKey as createPublicKey2, randomBytes as randomBytes4, randomInt } from "crypto";
@@ -9035,23 +9094,83 @@ async function userEmail(userId) {
9035
9094
  }
9036
9095
 
9037
9096
  // ../api/src/dashboard/agent-bridge-data.ts
9038
- import { randomBytes as randomBytes5, randomUUID as randomUUID2, verify as verifyRaw } from "crypto";
9097
+ import { randomBytes as randomBytes5, randomUUID as randomUUID3, verify as verifyRaw } from "crypto";
9039
9098
  import { WebSocket, WebSocketServer } from "ws";
9040
9099
 
9041
9100
  // ../api/src/dashboard/host-identity.ts
9042
9101
  import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey3, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
9043
- import { existsSync as existsSync17, mkdirSync as mkdirSync9, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
9044
- import { dirname as dirname5, join as join17 } from "path";
9102
+ import { existsSync as existsSync18, mkdirSync as mkdirSync9, readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
9103
+ import { dirname as dirname6, join as join17 } from "path";
9045
9104
  function hostPrivateKeyPath() {
9046
9105
  return process.env["FLYSPACE_HOST_KEY_PATH"] || join17(paths.BASE_CONFIG_DIR, "host.key");
9047
9106
  }
9048
9107
  function loadHostPrivateKey(path = hostPrivateKeyPath()) {
9049
- if (!existsSync17(path)) {
9108
+ if (!existsSync18(path)) {
9050
9109
  throw new Error(
9051
9110
  `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.`
9052
9111
  );
9053
9112
  }
9054
- return createPrivateKey2(readFileSync16(path, "utf8"));
9113
+ return createPrivateKey2(readFileSync17(path, "utf8"));
9114
+ }
9115
+
9116
+ // ../api/src/dashboard/uploads-routes.ts
9117
+ import { randomUUID as randomUUID2 } from "crypto";
9118
+ import { Hono as Hono7 } from "hono";
9119
+ import { PutObjectCommand as PutObjectCommand2, GetObjectCommand as GetObjectCommand3 } from "@aws-sdk/client-s3";
9120
+ import { getSignedUrl as getSignedUrl3 } from "@aws-sdk/s3-request-presigner";
9121
+ var MAX_FILE_BYTES = 10 * 1024 * 1024;
9122
+ var PRESIGN_TTL_SECONDS2 = 300;
9123
+ var KEY_PREFIX = "chat";
9124
+ var READABLE_PREFIXES = [KEY_PREFIX, SCRATCH_PREFIX];
9125
+ function sanitizeFilename(name) {
9126
+ const cleaned = name.replace(/[/\\]/g, "_").replace(/\s+/g, "-").replace(/[^\w.-]/g, "").trim();
9127
+ return (cleaned || "file").slice(-100);
9128
+ }
9129
+ function encodeKeyForUrl(key) {
9130
+ return key.split("/").map(encodeURIComponent).join("/");
9131
+ }
9132
+ async function presignAttachmentRead(key) {
9133
+ if (!READABLE_PREFIXES.some((prefix) => key.startsWith(`${prefix}/`))) return null;
9134
+ return getSignedUrl3(getR2Client(), new GetObjectCommand3({ Bucket: getR2Bucket(), Key: key }), {
9135
+ expiresIn: PRESIGN_TTL_SECONDS2
9136
+ });
9137
+ }
9138
+ function createUploadsRouter() {
9139
+ const app = new Hono7();
9140
+ app.post("/uploads", async (c) => {
9141
+ const body = await c.req.parseBody().catch(() => null);
9142
+ const file = body?.["file"];
9143
+ if (!(file instanceof File)) return c.json({ error: "missing file" }, 400);
9144
+ if (file.size > MAX_FILE_BYTES) {
9145
+ return c.json({ error: `file must be ${MAX_FILE_BYTES / (1024 * 1024)}MB or smaller` }, 400);
9146
+ }
9147
+ const key = `${KEY_PREFIX}/${getProfile()}/${randomUUID2()}-${sanitizeFilename(file.name)}`;
9148
+ const buffer = Buffer.from(await file.arrayBuffer());
9149
+ await getR2Client().send(
9150
+ new PutObjectCommand2({
9151
+ Bucket: getR2Bucket(),
9152
+ Key: key,
9153
+ Body: buffer,
9154
+ ContentType: file.type || "application/octet-stream"
9155
+ })
9156
+ );
9157
+ return c.json(
9158
+ {
9159
+ url: `/api/attachments/${encodeKeyForUrl(key)}`,
9160
+ name: file.name,
9161
+ type: file.type,
9162
+ size: file.size
9163
+ },
9164
+ 201
9165
+ );
9166
+ });
9167
+ app.get("/attachments/:key{.+}", async (c) => {
9168
+ const url = await presignAttachmentRead(c.req.param("key"));
9169
+ if (!url) return c.json({ error: "not found" }, 404);
9170
+ c.header("Cache-Control", `private, max-age=${PRESIGN_TTL_SECONDS2 - 60}`);
9171
+ return c.redirect(url, 302);
9172
+ });
9173
+ return app;
9055
9174
  }
9056
9175
 
9057
9176
  // ../core/src/bridge-protocol.ts
@@ -9216,7 +9335,7 @@ function hostUnavailable(c, agent, method) {
9216
9335
  return c.json({ error: "host offline", reason: agent.getHostCallFailure?.(method) ?? "offline" }, 503);
9217
9336
  }
9218
9337
  function createOnboardingRouter(agent) {
9219
- const app = new Hono7();
9338
+ const app = new Hono8();
9220
9339
  app.get("/scan", async (c) => {
9221
9340
  const ctx = await resolveAuthContext(c);
9222
9341
  if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
@@ -9354,7 +9473,7 @@ async function ensureGreetingThread(workspaceId, channelId, ownerName) {
9354
9473
 
9355
9474
  // ../api/src/dashboard/auth-routes.ts
9356
9475
  function createAuthRouter() {
9357
- const auth = new Hono8();
9476
+ const auth = new Hono9();
9358
9477
  const emailedLink = (deliver) => async (c) => {
9359
9478
  const body = await c.req.json().catch(() => ({}));
9360
9479
  const email = body.email?.trim().toLowerCase();
@@ -9434,7 +9553,7 @@ var requireWorkspaceMembership = async (c, next) => {
9434
9553
  };
9435
9554
 
9436
9555
  // ../api/src/dashboard/billing-routes.ts
9437
- import { Hono as Hono9 } from "hono";
9556
+ import { Hono as Hono10 } from "hono";
9438
9557
  function isPlan(value) {
9439
9558
  return value === "local" || value === "cloud";
9440
9559
  }
@@ -9450,7 +9569,7 @@ async function requireActivePlan(c, next) {
9450
9569
  return next();
9451
9570
  }
9452
9571
  function createBillingRouter() {
9453
- const billing = new Hono9();
9572
+ const billing = new Hono10();
9454
9573
  billing.get("/", async (c) => {
9455
9574
  const auth = c.get("auth");
9456
9575
  const { planStatus, stripeSubscriptionId } = await getWorkspaceBilling(auth.workspaceId);
@@ -9506,7 +9625,7 @@ function createBillingRouter() {
9506
9625
  }
9507
9626
 
9508
9627
  // ../api/src/dashboard/admin-routes.ts
9509
- import { Hono as Hono10 } from "hono";
9628
+ import { Hono as Hono11 } from "hono";
9510
9629
 
9511
9630
  // ../api/src/dashboard/admin-middleware.ts
9512
9631
  function platformAdminEmails() {
@@ -9532,7 +9651,7 @@ var requirePlatformOperator = async (c, next) => {
9532
9651
  // ../api/src/dashboard/admin-routes.ts
9533
9652
  var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
9534
9653
  function createAdminRouter() {
9535
- const admin = new Hono10();
9654
+ const admin = new Hono11();
9536
9655
  admin.use("*", requirePlatformOperator);
9537
9656
  admin.get("/workspaces", async (c) => {
9538
9657
  const summaries = await listWorkspaceSummaries();
@@ -9645,11 +9764,11 @@ var BOT_LABEL = "Workspace Agent";
9645
9764
  var BOT_DM_KEY = "claude";
9646
9765
 
9647
9766
  // ../api/src/dashboard/data-api.ts
9648
- import { existsSync as existsSync19 } from "fs";
9767
+ import { existsSync as existsSync20 } from "fs";
9649
9768
  import sharp5 from "sharp";
9650
9769
 
9651
9770
  // ../api/src/dashboard/automations-routes.ts
9652
- import { Hono as Hono11 } from "hono";
9771
+ import { Hono as Hono12 } from "hono";
9653
9772
  var TYPES2 = ["schedule", "trigger", "mention-trigger"];
9654
9773
  function isType(t) {
9655
9774
  return TYPES2.includes(t);
@@ -9666,7 +9785,7 @@ function statusFor(message) {
9666
9785
  return 400;
9667
9786
  }
9668
9787
  function createAutomationWriteRouter() {
9669
- const app = new Hono11();
9788
+ const app = new Hono12();
9670
9789
  const manager = () => new AutomationManager();
9671
9790
  app.get("/:type/:name", (c) => {
9672
9791
  const item = manager().get(c.req.param("name"));
@@ -9722,9 +9841,9 @@ function createAutomationWriteRouter() {
9722
9841
  }
9723
9842
 
9724
9843
  // ../api/src/dashboard/categories-routes.ts
9725
- import { Hono as Hono12 } from "hono";
9844
+ import { Hono as Hono13 } from "hono";
9726
9845
  function createCategoryWriteRouter() {
9727
- const app = new Hono12();
9846
+ const app = new Hono13();
9728
9847
  app.get("/", async (c) => {
9729
9848
  const { data, error: error2 } = await getSupabaseClient().from("categories").select("*").eq("profile", getProfile()).order("position", { ascending: true });
9730
9849
  if (error2) return c.json({ error: error2.message }, 500);
@@ -9778,7 +9897,7 @@ function createCategoryWriteRouter() {
9778
9897
  }
9779
9898
 
9780
9899
  // ../api/src/dashboard/chat-routes.ts
9781
- import { Hono as Hono13 } from "hono";
9900
+ import { Hono as Hono14 } from "hono";
9782
9901
 
9783
9902
  // ../api/src/dashboard/fork-summary.ts
9784
9903
  var GROQ_API_URL4 = "https://api.groq.com/openai/v1/chat/completions";
@@ -10265,7 +10384,7 @@ async function generateAutoTitle(threadId, channelId, body) {
10265
10384
  await supabase.from("threads").update({ auto_title: autoTitle }).eq("id", threadId).is("title", null);
10266
10385
  }
10267
10386
  function createChatRouter(agent = {}) {
10268
- const app = new Hono13();
10387
+ const app = new Hono14();
10269
10388
  const {
10270
10389
  onChatMessage: onMessage,
10271
10390
  isAgentThinking,
@@ -10779,7 +10898,7 @@ function createChatRouter(agent = {}) {
10779
10898
  }
10780
10899
 
10781
10900
  // ../api/src/dashboard/doc-shares-routes.ts
10782
- import { Hono as Hono14 } from "hono";
10901
+ import { Hono as Hono15 } from "hono";
10783
10902
 
10784
10903
  // ../api/src/dashboard/doc-share-features.ts
10785
10904
  import { DeleteObjectCommand as DeleteObjectCommand2 } from "@aws-sdk/client-s3";
@@ -10839,7 +10958,7 @@ async function updateShareFeatures(share, requested) {
10839
10958
 
10840
10959
  // ../api/src/dashboard/doc-share-publish.ts
10841
10960
  import { randomBytes as randomBytes6 } from "crypto";
10842
- import { CopyObjectCommand, PutObjectCommand as PutObjectCommand2 } from "@aws-sdk/client-s3";
10961
+ import { CopyObjectCommand, PutObjectCommand as PutObjectCommand3 } from "@aws-sdk/client-s3";
10843
10962
 
10844
10963
  // ../api/src/dashboard/bridge-types.ts
10845
10964
  var SHARE_ASSET_PLACEHOLDER = "/share/{{flyspace-share-id}}/assets/";
@@ -10935,7 +11054,7 @@ async function publishDocShare(input2, readBundle) {
10935
11054
  );
10936
11055
  } else {
10937
11056
  await client2.send(
10938
- new PutObjectCommand2({
11057
+ new PutObjectCommand3({
10939
11058
  Bucket: bucket,
10940
11059
  Key: key,
10941
11060
  Body: Buffer.from(asset.data, "base64"),
@@ -11114,7 +11233,7 @@ function resolveChannel2(c) {
11114
11233
  }
11115
11234
  var LIST_LIMIT = 500;
11116
11235
  function createDocShareListRouter() {
11117
- const app = new Hono14();
11236
+ const app = new Hono15();
11118
11237
  app.get("/shares", async (c) => {
11119
11238
  const viewer = c.get("auth");
11120
11239
  const readable = await filterReadableDocsChannels(
@@ -11158,7 +11277,7 @@ function createDocShareListRouter() {
11158
11277
  return app;
11159
11278
  }
11160
11279
  function createDocShareWriteRouter(agent = {}) {
11161
- const app = new Hono14();
11280
+ const app = new Hono15();
11162
11281
  app.use("/:id/docs/share", requireDocsAccess);
11163
11282
  app.use("/:id/docs/share/*", requireDocsAccess);
11164
11283
  app.use("/:id/docs/shares", requireDocsAccess);
@@ -11331,7 +11450,7 @@ function createDocShareWriteRouter(agent = {}) {
11331
11450
  }
11332
11451
 
11333
11452
  // ../api/src/dashboard/search-routes.ts
11334
- import { Hono as Hono15 } from "hono";
11453
+ import { Hono as Hono16 } from "hono";
11335
11454
  function matchScore(q, fields) {
11336
11455
  const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
11337
11456
  if (terms.length === 0) return 0;
@@ -11479,7 +11598,7 @@ function searchAutomations(q) {
11479
11598
  }
11480
11599
  var RESULT_LIMIT = 30;
11481
11600
  function createSearchRouter() {
11482
- const app = new Hono15();
11601
+ const app = new Hono16();
11483
11602
  app.get("/", async (c) => {
11484
11603
  const q = c.req.query("q")?.trim();
11485
11604
  if (!q) return c.json({ results: [] });
@@ -11495,14 +11614,14 @@ function createSearchRouter() {
11495
11614
  }
11496
11615
 
11497
11616
  // ../api/src/dashboard/feature-flags-routes.ts
11498
- import { Hono as Hono16 } from "hono";
11617
+ import { Hono as Hono17 } from "hono";
11499
11618
  async function isFeatureEnabled(c, flagId) {
11500
11619
  const auth = c.get("auth");
11501
11620
  const enabled = await listEnabledFeatureFlags(auth.workspaceId);
11502
11621
  return resolveFeatureFlags(enabled, isDevHost(c))[flagId];
11503
11622
  }
11504
11623
  function createFeatureFlagsRouter() {
11505
- const app = new Hono16();
11624
+ const app = new Hono17();
11506
11625
  app.get("/", async (c) => {
11507
11626
  const auth = c.get("auth");
11508
11627
  const devHost = isDevHost(c);
@@ -11527,67 +11646,6 @@ function createFeatureFlagsRouter() {
11527
11646
  return app;
11528
11647
  }
11529
11648
 
11530
- // ../api/src/dashboard/uploads-routes.ts
11531
- import { randomUUID as randomUUID3 } from "crypto";
11532
- import { Hono as Hono17 } from "hono";
11533
- import { PutObjectCommand as PutObjectCommand3, GetObjectCommand as GetObjectCommand3 } from "@aws-sdk/client-s3";
11534
- import { getSignedUrl as getSignedUrl3 } from "@aws-sdk/s3-request-presigner";
11535
- var MAX_FILE_BYTES = 10 * 1024 * 1024;
11536
- var PRESIGN_TTL_SECONDS2 = 300;
11537
- var KEY_PREFIX = "chat";
11538
- var READABLE_PREFIXES = [KEY_PREFIX, SCRATCH_PREFIX];
11539
- function sanitizeFilename(name) {
11540
- const cleaned = name.replace(/[/\\]/g, "_").replace(/\s+/g, "-").replace(/[^\w.-]/g, "").trim();
11541
- return (cleaned || "file").slice(-100);
11542
- }
11543
- function encodeKeyForUrl(key) {
11544
- return key.split("/").map(encodeURIComponent).join("/");
11545
- }
11546
- function createUploadsRouter() {
11547
- const app = new Hono17();
11548
- app.post("/uploads", async (c) => {
11549
- const body = await c.req.parseBody().catch(() => null);
11550
- const file = body?.["file"];
11551
- if (!(file instanceof File)) return c.json({ error: "missing file" }, 400);
11552
- if (file.size > MAX_FILE_BYTES) {
11553
- return c.json({ error: `file must be ${MAX_FILE_BYTES / (1024 * 1024)}MB or smaller` }, 400);
11554
- }
11555
- const key = `${KEY_PREFIX}/${getProfile()}/${randomUUID3()}-${sanitizeFilename(file.name)}`;
11556
- const buffer = Buffer.from(await file.arrayBuffer());
11557
- await getR2Client().send(
11558
- new PutObjectCommand3({
11559
- Bucket: getR2Bucket(),
11560
- Key: key,
11561
- Body: buffer,
11562
- ContentType: file.type || "application/octet-stream"
11563
- })
11564
- );
11565
- return c.json(
11566
- {
11567
- url: `/api/attachments/${encodeKeyForUrl(key)}`,
11568
- name: file.name,
11569
- type: file.type,
11570
- size: file.size
11571
- },
11572
- 201
11573
- );
11574
- });
11575
- app.get("/attachments/:key{.+}", async (c) => {
11576
- const key = c.req.param("key");
11577
- if (!READABLE_PREFIXES.some((prefix) => key.startsWith(`${prefix}/`))) {
11578
- return c.json({ error: "not found" }, 404);
11579
- }
11580
- const url = await getSignedUrl3(
11581
- getR2Client(),
11582
- new GetObjectCommand3({ Bucket: getR2Bucket(), Key: key }),
11583
- { expiresIn: PRESIGN_TTL_SECONDS2 }
11584
- );
11585
- c.header("Cache-Control", `private, max-age=${PRESIGN_TTL_SECONDS2 - 60}`);
11586
- return c.redirect(url, 302);
11587
- });
11588
- return app;
11589
- }
11590
-
11591
11649
  // ../api/src/dashboard/transcribe-routes.ts
11592
11650
  import { Hono as Hono18 } from "hono";
11593
11651
  var GROQ_TRANSCRIBE_URL = "https://api.groq.com/openai/v1/audio/transcriptions";
@@ -13245,7 +13303,7 @@ async function sessionUsageForThreads(workspace, threadTsList) {
13245
13303
  }
13246
13304
 
13247
13305
  // ../api/src/dashboard/usage-report.ts
13248
- import { existsSync as existsSync18 } from "fs";
13306
+ import { existsSync as existsSync19 } from "fs";
13249
13307
  function mergeByLabel(buckets) {
13250
13308
  const merged = /* @__PURE__ */ new Map();
13251
13309
  for (const bucket of buckets) {
@@ -13291,7 +13349,7 @@ function toReport(days, rows, labelChannel) {
13291
13349
  };
13292
13350
  }
13293
13351
  function fromLocalStore(days, labelChannel) {
13294
- const store = existsSync18(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13352
+ const store = existsSync19(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13295
13353
  if (!store) return { ...EMPTY, days };
13296
13354
  const window = days === 0 ? void 0 : days;
13297
13355
  try {
@@ -13333,7 +13391,7 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
13333
13391
  const { totals, byDay } = await channelUsageForWorkspace(workspace, aliasIds, window);
13334
13392
  return { days, totals: totals.runs > 0 ? totals : null, byDay };
13335
13393
  } catch {
13336
- const store = existsSync18(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13394
+ const store = existsSync19(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13337
13395
  if (!store) return { days, totals: null, byDay: [] };
13338
13396
  try {
13339
13397
  const targetLabel = labelChannel(channelId);
@@ -13355,7 +13413,7 @@ async function buildThreadUsage(threadTs) {
13355
13413
  if (totals.runs === 0) return { threadTs, totals: null, byModel: [] };
13356
13414
  return { threadTs, totals, byModel };
13357
13415
  } catch {
13358
- const store = existsSync18(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13416
+ const store = existsSync19(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13359
13417
  if (!store) return { threadTs, totals: null, byModel: [] };
13360
13418
  try {
13361
13419
  const totals = store.threadTotals(threadTs);
@@ -13793,7 +13851,7 @@ function registerDataRoutes(api, agent = {}) {
13793
13851
  runs.set(name, { lastRun: run4.lastRunAt.toISOString(), lastStatus: run4.lastStatus });
13794
13852
  }
13795
13853
  } catch {
13796
- const runStore = existsSync19(paths.DB_FILE) ? getLocalCache().openAutomationRunStore(paths.DB_FILE) : null;
13854
+ const runStore = existsSync20(paths.DB_FILE) ? getLocalCache().openAutomationRunStore(paths.DB_FILE) : null;
13797
13855
  if (runStore) {
13798
13856
  try {
13799
13857
  for (const run4 of runStore.listLastRuns()) {
@@ -13855,7 +13913,7 @@ function registerDataRoutes(api, agent = {}) {
13855
13913
  const runs = (await recentRunsForWorkspace(getProfile(), 200)).map(toResponse5);
13856
13914
  return c.json({ runs });
13857
13915
  } catch {
13858
- const runStore = existsSync19(paths.DB_FILE) ? getLocalCache().openAutomationRunStore(paths.DB_FILE) : null;
13916
+ const runStore = existsSync20(paths.DB_FILE) ? getLocalCache().openAutomationRunStore(paths.DB_FILE) : null;
13859
13917
  if (!runStore) return c.json({ runs: [] });
13860
13918
  try {
13861
13919
  const runs = runStore.listRuns(200).map(toResponse5);
@@ -13977,7 +14035,7 @@ function registerDataRoutes(api, agent = {}) {
13977
14035
  } catch {
13978
14036
  latest2 = /* @__PURE__ */ new Map();
13979
14037
  totals = /* @__PURE__ */ new Map();
13980
- const usageStore = existsSync19(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
14038
+ const usageStore = existsSync20(paths.DB_FILE) ? getLocalCache().openUsageStore(paths.DB_FILE) : null;
13981
14039
  if (usageStore) {
13982
14040
  try {
13983
14041
  latest2 = usageStore.latestByThread(threadTsList);
@@ -14243,11 +14301,11 @@ function createDeviceAuthRouter() {
14243
14301
  }
14244
14302
 
14245
14303
  // ../api/src/dashboard/skills-routes.ts
14246
- import { dirname as dirname6 } from "path";
14304
+ import { dirname as dirname7 } from "path";
14247
14305
 
14248
14306
  // ../api/src/dashboard/skill-mirror.ts
14249
14307
  import { createHash as createHash11 } from "crypto";
14250
- import { existsSync as existsSync20, statSync as statSync4 } from "fs";
14308
+ import { existsSync as existsSync21, statSync as statSync4 } from "fs";
14251
14309
  var SYNC_INTERVAL_MS = 6e4;
14252
14310
  var UPSERT_BATCH = 50;
14253
14311
  function mirrorScopeOf(ref) {
@@ -14255,7 +14313,7 @@ function mirrorScopeOf(ref) {
14255
14313
  }
14256
14314
  function isDir(path) {
14257
14315
  try {
14258
- return existsSync20(path) && statSync4(path).isDirectory();
14316
+ return existsSync21(path) && statSync4(path).isDirectory();
14259
14317
  } catch {
14260
14318
  return false;
14261
14319
  }
@@ -14580,12 +14638,11 @@ function createGatedRouter(agent, register) {
14580
14638
  }
14581
14639
 
14582
14640
  // src/dashboard/host-api.ts
14583
- import { existsSync as existsSync29, readFileSync as readFileSync23, statSync as statSync8 } from "fs";
14584
- import { join as join26 } from "path";
14641
+ import { existsSync as existsSync30, readFileSync as readFileSync24, statSync as statSync8 } from "fs";
14585
14642
  import sharp6 from "sharp";
14586
14643
 
14587
14644
  // src/dashboard/channels-routes.ts
14588
- import { existsSync as existsSync21, mkdirSync as mkdirSync10 } from "fs";
14645
+ import { existsSync as existsSync22, mkdirSync as mkdirSync10 } from "fs";
14589
14646
  import { Hono as Hono30 } from "hono";
14590
14647
  function coerceAccess(raw) {
14591
14648
  if (raw === "open" || raw === "*" || raw === "all") return raw;
@@ -14624,7 +14681,7 @@ function coerceMemberEmails(raw) {
14624
14681
  function ensureCwdDir(cwd) {
14625
14682
  const expanded = expandPath(cwd);
14626
14683
  if (isLogicalCwd(cwd) || isUnderLogicalRoot(expanded)) return;
14627
- if (!existsSync21(expanded)) mkdirSync10(expanded, { recursive: true });
14684
+ if (!existsSync22(expanded)) mkdirSync10(expanded, { recursive: true });
14628
14685
  }
14629
14686
  function toResponse3(channel) {
14630
14687
  return {
@@ -14774,13 +14831,13 @@ function createChannelWriteRouter() {
14774
14831
  import {
14775
14832
  closeSync as closeSync2,
14776
14833
  createReadStream,
14777
- existsSync as existsSync25,
14834
+ existsSync as existsSync26,
14778
14835
  lstatSync as lstatSync2,
14779
14836
  mkdirSync as mkdirSync11,
14780
14837
  openSync as openSync2,
14781
14838
  readSync as readSync2,
14782
14839
  readdirSync as readdirSync5,
14783
- readFileSync as readFileSync20,
14840
+ readFileSync as readFileSync21,
14784
14841
  realpathSync as realpathSync4,
14785
14842
  renameSync,
14786
14843
  rmSync as rmSync8,
@@ -14788,12 +14845,12 @@ import {
14788
14845
  writeFileSync as writeFileSync11
14789
14846
  } from "fs";
14790
14847
  import { Readable } from "stream";
14791
- import { basename as basename8, dirname as dirname7, extname, join as join23, relative, resolve as resolve5, sep } from "path";
14848
+ import { basename as basename8, dirname as dirname8, extname, join as join23, relative, resolve as resolve5, sep } from "path";
14792
14849
  import { Hono as Hono31 } from "hono";
14793
14850
  import { streamSSE as streamSSE2 } from "hono/streaming";
14794
14851
 
14795
14852
  // src/dashboard/docs-watcher.ts
14796
- import { existsSync as existsSync22, watch } from "fs";
14853
+ import { existsSync as existsSync23, watch } from "fs";
14797
14854
  import { basename as basename6, join as join18 } from "path";
14798
14855
  var DEBOUNCE_MS = 200;
14799
14856
  var MAX_PENDING = 200;
@@ -14835,7 +14892,7 @@ function watchDocs(cwd, listener) {
14835
14892
  const current = entries.get(cwd);
14836
14893
  if (!current || filename === null) return;
14837
14894
  const rel = filename.split("\\").join("/");
14838
- if (rel === root && !existsSync22(join18(cwd, rel))) return;
14895
+ if (rel === root && !existsSync23(join18(cwd, rel))) return;
14839
14896
  if (isIgnoredDocPath(rel)) return;
14840
14897
  if (current.pending.size < MAX_PENDING) current.pending.add(rel);
14841
14898
  if (current.timer === null) current.timer = setTimeout(() => flush(cwd), DEBOUNCE_MS);
@@ -15072,7 +15129,7 @@ async function readOrQueueDocAudio(input2) {
15072
15129
  }
15073
15130
 
15074
15131
  // src/dashboard/artifact-export.ts
15075
- import { readFileSync as readFileSync19 } from "fs";
15132
+ import { readFileSync as readFileSync20 } from "fs";
15076
15133
 
15077
15134
  // src/dashboard/chat-agent.ts
15078
15135
  import { homedir as homedir9 } from "os";
@@ -15290,7 +15347,7 @@ async function withMcpConfig(profile, connectionIds, fn) {
15290
15347
  }
15291
15348
 
15292
15349
  // src/claude/agents.ts
15293
- import { existsSync as existsSync23, readdirSync as readdirSync4, readFileSync as readFileSync17 } from "fs";
15350
+ import { existsSync as existsSync24, readdirSync as readdirSync4, readFileSync as readFileSync18 } from "fs";
15294
15351
  import { homedir as homedir8 } from "os";
15295
15352
  import { basename as basename7, join as join20 } from "path";
15296
15353
  import yaml2 from "js-yaml";
@@ -15305,7 +15362,7 @@ function expandHome(p) {
15305
15362
  function parseFrontmatter(file) {
15306
15363
  let content;
15307
15364
  try {
15308
- content = readFileSync17(file, "utf-8");
15365
+ content = readFileSync18(file, "utf-8");
15309
15366
  } catch {
15310
15367
  return {};
15311
15368
  }
@@ -15321,7 +15378,7 @@ function parseFrontmatter(file) {
15321
15378
  }
15322
15379
  }
15323
15380
  function collectFrom(root, source) {
15324
- if (!existsSync23(root)) return [];
15381
+ if (!existsSync24(root)) return [];
15325
15382
  let entries2;
15326
15383
  try {
15327
15384
  entries2 = readdirSync4(root).filter((f) => f.endsWith(".md"));
@@ -15351,7 +15408,7 @@ function readAgentPersona(configDir2, cwd, name) {
15351
15408
  const agent = listAgents(configDir2, cwd).find((a) => a.name === name);
15352
15409
  if (!agent) return void 0;
15353
15410
  try {
15354
- const content = readFileSync17(agent.path, "utf-8");
15411
+ const content = readFileSync18(agent.path, "utf-8");
15355
15412
  return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
15356
15413
  } catch {
15357
15414
  return void 0;
@@ -15360,7 +15417,7 @@ function readAgentPersona(configDir2, cwd, name) {
15360
15417
 
15361
15418
  // src/dashboard/memory-extraction-runner.ts
15362
15419
  import { spawn as spawn4 } from "child_process";
15363
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync18, rmSync as rmSync7 } from "fs";
15420
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync19, rmSync as rmSync7 } from "fs";
15364
15421
  import { tmpdir as tmpdir4 } from "os";
15365
15422
  import { join as join21 } from "path";
15366
15423
  var CLAUDE_MODEL = "sonnet";
@@ -15452,7 +15509,7 @@ function run3(inv, cwd) {
15452
15509
  let raw = stdout;
15453
15510
  if (inv.outputFile) {
15454
15511
  try {
15455
- raw = readFileSync18(inv.outputFile, "utf-8");
15512
+ raw = readFileSync19(inv.outputFile, "utf-8");
15456
15513
  } catch {
15457
15514
  raw = "";
15458
15515
  }
@@ -15769,23 +15826,43 @@ function extractAttachmentKeys(body) {
15769
15826
  }
15770
15827
  return keys;
15771
15828
  }
15829
+ async function fetchObject(key) {
15830
+ if (process.env["CLOUDFLARE_R2_ACCESS_KEY_ID"]) {
15831
+ const response2 = await getR2Client().send(
15832
+ new GetObjectCommand5({ Bucket: getR2Bucket(), Key: key })
15833
+ );
15834
+ return {
15835
+ mimetype: response2.ContentType ?? "application/octet-stream",
15836
+ contentLength: response2.ContentLength,
15837
+ read: async () => response2.Body?.transformToByteArray()
15838
+ };
15839
+ }
15840
+ const url = await requestAttachmentUrl(key);
15841
+ if (!url) throw new Error("Data API refused the attachment");
15842
+ const response = await fetch(url);
15843
+ if (!response.ok) throw new Error(`presigned GET answered ${response.status}`);
15844
+ const length = response.headers.get("content-length");
15845
+ return {
15846
+ mimetype: response.headers.get("content-type") ?? "application/octet-stream",
15847
+ contentLength: length === null ? void 0 : Number(length),
15848
+ read: async () => new Uint8Array(await response.arrayBuffer())
15849
+ };
15850
+ }
15772
15851
  async function downloadOne(key) {
15773
15852
  const lastSegment = key.slice(key.lastIndexOf("/") + 1);
15774
15853
  const name = displayName(lastSegment);
15775
15854
  try {
15776
- const response = await getR2Client().send(
15777
- new GetObjectCommand5({ Bucket: getR2Bucket(), Key: key })
15778
- );
15779
- const mimetype = response.ContentType ?? "application/octet-stream";
15855
+ const response = await fetchObject(key);
15856
+ const mimetype = response.mimetype;
15780
15857
  const sizeLimit = mimetypeSizeLimit(mimetype);
15781
15858
  if (sizeLimit === null) {
15782
15859
  if (debug) console.log(`[attachment-downloader] unsupported mimetype: ${mimetype} (${key})`);
15783
15860
  return { kind: "skip", skipped: { name, mimetype, reason: "unsupported" } };
15784
15861
  }
15785
- if (response.ContentLength !== void 0 && response.ContentLength > sizeLimit) {
15862
+ if (response.contentLength !== void 0 && response.contentLength > sizeLimit) {
15786
15863
  return { kind: "skip", skipped: { name, mimetype, reason: "too_large" } };
15787
15864
  }
15788
- const bytes = await response.Body?.transformToByteArray();
15865
+ const bytes = await response.read();
15789
15866
  if (!bytes) {
15790
15867
  return { kind: "skip", skipped: { name, mimetype, reason: "download_failed" } };
15791
15868
  }
@@ -15817,11 +15894,11 @@ async function downloadChatAttachments(body) {
15817
15894
  }
15818
15895
 
15819
15896
  // src/dashboard/doc-mention-guard.ts
15820
- import { existsSync as existsSync24, statSync as statSync5 } from "fs";
15897
+ import { existsSync as existsSync25, statSync as statSync5 } from "fs";
15821
15898
  var DOC_MENTION_RE = /(?<![\w@/`])@((?:[\w.-]+\/)+[\w.-]+\.[A-Za-z0-9]{1,6})(?::(\d+))?(?![\w-])/g;
15822
15899
  function isRealDoc(root, rel) {
15823
15900
  const abs = resolveDocPath(root, rel);
15824
- if (!abs || !existsSync24(abs)) return false;
15901
+ if (!abs || !existsSync25(abs)) return false;
15825
15902
  try {
15826
15903
  return statSync5(abs).isFile();
15827
15904
  } catch {
@@ -17091,7 +17168,7 @@ async function exportDocAsArtifact(channelId, docPath) {
17091
17168
  );
17092
17169
  const abs = resolveDocPath(channelConfig.cwd, docPath);
17093
17170
  if (!abs) throw new Error(`doc not found: ${docPath}`);
17094
- const content = readFileSync19(abs, "utf8");
17171
+ const content = readFileSync20(abs, "utf8");
17095
17172
  const title = extractTitle(abs, "md");
17096
17173
  const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
17097
17174
  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).
@@ -17372,12 +17449,12 @@ function readGitBranch(dir) {
17372
17449
  try {
17373
17450
  let gitDir = join23(dir, ".git");
17374
17451
  if (statSync6(gitDir).isFile()) {
17375
- const pointer = readFileSync20(gitDir, "utf8").trim();
17452
+ const pointer = readFileSync21(gitDir, "utf8").trim();
17376
17453
  const match = pointer.match(/^gitdir:\s*(.+)$/);
17377
17454
  if (!match) return void 0;
17378
17455
  gitDir = resolve5(dir, match[1]);
17379
17456
  }
17380
- const head = readFileSync20(join23(gitDir, "HEAD"), "utf8").trim();
17457
+ const head = readFileSync21(join23(gitDir, "HEAD"), "utf8").trim();
17381
17458
  const branchMatch = head.match(/^ref:\s*refs\/heads\/(.+)$/);
17382
17459
  if (branchMatch) return branchMatch[1];
17383
17460
  return /^[0-9a-f]{7,40}$/i.test(head) ? head.slice(0, 7) : void 0;
@@ -17406,7 +17483,7 @@ function listDocLevel(root, relDir = "", sort = "newest", showHidden = false) {
17406
17483
  if (SKIP_DIRS.has(entry.name)) continue;
17407
17484
  const probe3 = probeDir(abs, showHidden);
17408
17485
  if (!probe3.hasDocs && probe3.hasOther) continue;
17409
- const isRepo = existsSync25(join23(abs, ".git"));
17486
+ const isRepo = existsSync26(join23(abs, ".git"));
17410
17487
  dirs.push({
17411
17488
  path,
17412
17489
  name: entry.name,
@@ -17612,7 +17689,7 @@ function moveDocEntry(root, from, to) {
17612
17689
  return { status: "bad-target" };
17613
17690
  }
17614
17691
  }
17615
- const absToDir = dirname7(absTo);
17692
+ const absToDir = dirname8(absTo);
17616
17693
  if (resolveDirPath(root, relative(root, absToDir)) === null) return { status: "bad-target" };
17617
17694
  try {
17618
17695
  if (!statSync6(absToDir).isDirectory()) return { status: "bad-target" };
@@ -17703,7 +17780,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
17703
17780
  }
17704
17781
  if (!stat.isFile()) return c.json({ error: "not found" }, 404);
17705
17782
  if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
17706
- return c.body(withDocLinkBridge(readFileSync20(abs, "utf8")), 200, {
17783
+ return c.body(withDocLinkBridge(readFileSync21(abs, "utf8")), 200, {
17707
17784
  "Content-Type": "text/html; charset=utf-8",
17708
17785
  "Content-Security-Policy": HTML_SHARE_CSP,
17709
17786
  "X-Content-Type-Options": "nosniff",
@@ -17888,7 +17965,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
17888
17965
  title: kind ? extractTitle(abs, kind) : basename8(abs),
17889
17966
  mtime: stat.mtime.toISOString(),
17890
17967
  tooLarge: tooLarge || void 0,
17891
- content: tooLarge ? "" : readFileSync20(abs, "utf8")
17968
+ content: tooLarge ? "" : readFileSync21(abs, "utf8")
17892
17969
  });
17893
17970
  });
17894
17971
  app.put("/:id/docs/content", async (c) => {
@@ -17931,7 +18008,7 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
17931
18008
  channel,
17932
18009
  rel,
17933
18010
  title: extractTitle(abs, "md"),
17934
- content: readFileSync20(abs, "utf8")
18011
+ content: readFileSync21(abs, "utf8")
17935
18012
  };
17936
18013
  };
17937
18014
  app.get("/:id/docs/translation/:lang", async (c) => {
@@ -17986,8 +18063,8 @@ function withDocLinkBridge(html) {
17986
18063
  }
17987
18064
 
17988
18065
  // src/dashboard/skills-routes-host.ts
17989
- import { existsSync as existsSync26, statSync as statSync7 } from "fs";
17990
- import { dirname as dirname8 } from "path";
18066
+ import { existsSync as existsSync27, statSync as statSync7 } from "fs";
18067
+ import { dirname as dirname9 } from "path";
17991
18068
  import { Hono as Hono32 } from "hono";
17992
18069
  function workspaceScope() {
17993
18070
  const claudeConfigDir = profileClaudeConfigDir();
@@ -17995,7 +18072,7 @@ function workspaceScope() {
17995
18072
  return {
17996
18073
  dir: workspaceSkillsDir(claudeConfigDir),
17997
18074
  // `~/.claude` itself, which exists wherever the CLI has ever run.
17998
- root: dirname8(workspaceSkillsDir(claudeConfigDir)),
18075
+ root: dirname9(workspaceSkillsDir(claudeConfigDir)),
17999
18076
  cwd,
18000
18077
  claudeConfigDir,
18001
18078
  // Only plugin skills. Project skills belong to a specific channel's cwd, so
@@ -18021,7 +18098,7 @@ function inheritedSkills(scope, own) {
18021
18098
  }
18022
18099
  function rootExists(dir) {
18023
18100
  try {
18024
- return existsSync26(dir) && statSync7(dir).isDirectory();
18101
+ return existsSync27(dir) && statSync7(dir).isDirectory();
18025
18102
  } catch {
18026
18103
  return false;
18027
18104
  }
@@ -18131,7 +18208,7 @@ async function resolveAgentIdentities() {
18131
18208
 
18132
18209
  // ../api/src/dashboard/providers.ts
18133
18210
  import { execFileSync as execFileSync4 } from "child_process";
18134
- import { existsSync as existsSync27, readFileSync as readFileSync21 } from "fs";
18211
+ import { existsSync as existsSync28, readFileSync as readFileSync22 } from "fs";
18135
18212
  import { homedir as homedir10 } from "os";
18136
18213
  import { join as join24, resolve as resolve6 } from "path";
18137
18214
  var PROVIDER_LABELS = {
@@ -18154,7 +18231,7 @@ function binaryOnPath(bin) {
18154
18231
  function claudeAuthAvailable() {
18155
18232
  if (profileEnv("CLAUDE_CODE_OAUTH_TOKEN")?.trim()) return true;
18156
18233
  const configDir2 = profileClaudeConfigDir() || resolve6(homedir10(), ".claude");
18157
- if (existsSync27(join24(configDir2, ".credentials.json"))) return true;
18234
+ if (existsSync28(join24(configDir2, ".credentials.json"))) return true;
18158
18235
  if (process.platform !== "darwin") return false;
18159
18236
  try {
18160
18237
  execFileSync4("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
@@ -18200,7 +18277,7 @@ function claudeAccount() {
18200
18277
  function codexAccount() {
18201
18278
  const home2 = process.env["CODEX_HOME"]?.trim() || join24(homedir10(), ".codex");
18202
18279
  try {
18203
- const auth = JSON.parse(readFileSync21(join24(home2, "auth.json"), "utf8"));
18280
+ const auth = JSON.parse(readFileSync22(join24(home2, "auth.json"), "utf8"));
18204
18281
  const payload = auth.tokens?.id_token?.split(".")[1];
18205
18282
  if (!payload) return void 0;
18206
18283
  const claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
@@ -18231,9 +18308,9 @@ function providerStatuses() {
18231
18308
  // src/dashboard/provider-login-host.ts
18232
18309
  import { spawn as spawn6 } from "child_process";
18233
18310
  import { randomUUID as randomUUID5 } from "crypto";
18234
- import { chmodSync, existsSync as existsSync28, mkdirSync as mkdirSync12, readFileSync as readFileSync22, writeFileSync as writeFileSync12 } from "fs";
18311
+ import { chmodSync, existsSync as existsSync29, mkdirSync as mkdirSync12, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
18235
18312
  import { createRequire } from "module";
18236
- import { dirname as dirname9, join as join25 } from "path";
18313
+ import { dirname as dirname10, join as join25 } from "path";
18237
18314
  import { Hono as Hono33 } from "hono";
18238
18315
  var SESSION_TTL_MS = 10 * 6e4;
18239
18316
  var URL_WAIT_MS = 3e4;
@@ -18296,10 +18373,10 @@ function waitFor(s, parse, ms) {
18296
18373
  }
18297
18374
  function loadPty() {
18298
18375
  const require2 = createRequire(import.meta.url);
18299
- const root = dirname9(require2.resolve("node-pty/package.json"));
18376
+ const root = dirname10(require2.resolve("node-pty/package.json"));
18300
18377
  const helper = join25(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
18301
18378
  try {
18302
- if (existsSync28(helper)) chmodSync(helper, 493);
18379
+ if (existsSync29(helper)) chmodSync(helper, 493);
18303
18380
  } catch {
18304
18381
  }
18305
18382
  return require2("node-pty");
@@ -18339,8 +18416,8 @@ async function startClaude() {
18339
18416
  return { id, url };
18340
18417
  }
18341
18418
  function saveClaudeToken(configFile, token) {
18342
- mkdirSync12(dirname9(configFile), { recursive: true });
18343
- const current = existsSync28(configFile) ? readFileSync22(configFile, "utf-8") : "";
18419
+ mkdirSync12(dirname10(configFile), { recursive: true });
18420
+ const current = existsSync29(configFile) ? readFileSync23(configFile, "utf-8") : "";
18344
18421
  writeFileSync12(configFile, upsertEnvLine(current, "CLAUDE_CODE_OAUTH_TOKEN", token), { mode: 384 });
18345
18422
  const cached = cachedProfileConfig();
18346
18423
  if (cached) cached.env["CLAUDE_CODE_OAUTH_TOKEN"] = token;
@@ -18557,9 +18634,8 @@ function registerHostRoutes(api) {
18557
18634
  api.get("/logs", (c) => {
18558
18635
  const errors = c.req.query("error") === "true" || c.req.query("error") === "1";
18559
18636
  const limit = Math.min(Number(c.req.query("limit")) || 200, 2e3);
18560
- const filename = errors ? "custie-error.log" : "custie.log";
18561
- const file = join26(paths.LOG_DIR, filename);
18562
- const exists = existsSync29(file);
18637
+ const file = serviceLogFile(errors);
18638
+ const exists = existsSync30(file);
18563
18639
  return c.json({
18564
18640
  file,
18565
18641
  exists,
@@ -18569,8 +18645,8 @@ function registerHostRoutes(api) {
18569
18645
  });
18570
18646
  }
18571
18647
  function tailFile(file, limit) {
18572
- if (!existsSync29(file)) return [];
18573
- const lines = readFileSync23(file, "utf-8").split("\n");
18648
+ if (!existsSync30(file)) return [];
18649
+ const lines = readFileSync24(file, "utf-8").split("\n");
18574
18650
  if (lines.at(-1) === "") lines.pop();
18575
18651
  return lines.slice(-limit);
18576
18652
  }
@@ -18588,7 +18664,7 @@ function createHostApiRouter(agent = {}) {
18588
18664
 
18589
18665
  // src/dashboard/agent-bridge-host.ts
18590
18666
  import { sign as signRaw, timingSafeEqual as timingSafeEqual2 } from "crypto";
18591
- import { existsSync as existsSync30, readFileSync as readFileSync25 } from "fs";
18667
+ import { existsSync as existsSync31, readFileSync as readFileSync26 } from "fs";
18592
18668
  import dotenv2 from "dotenv";
18593
18669
  import { WebSocket as WebSocket2 } from "ws";
18594
18670
 
@@ -18622,7 +18698,7 @@ async function handleHostRequest(req) {
18622
18698
  }
18623
18699
 
18624
18700
  // src/dashboard/doc-share-publisher.ts
18625
- import { readFileSync as readFileSync24, statSync as statSync9 } from "fs";
18701
+ import { readFileSync as readFileSync25, statSync as statSync9 } from "fs";
18626
18702
  import { extname as extname2, posix } from "path";
18627
18703
  var MAX_ASSET_BYTES = 20 * 1024 * 1024;
18628
18704
  var MAX_CONTENT_BYTES2 = 2 * 1024 * 1024;
@@ -18737,7 +18813,7 @@ function readDocShareBundle({ cwd, sourcePath }) {
18737
18813
  }
18738
18814
  if (!stat.isFile()) return { status: "not-found" };
18739
18815
  if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
18740
- const source = readFileSync24(abs, "utf8");
18816
+ const source = readFileSync25(abs, "utf8");
18741
18817
  const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
18742
18818
  if (kind === "excalidraw") {
18743
18819
  try {
@@ -18808,7 +18884,7 @@ function readDocShareBundle({ cwd, sourcePath }) {
18808
18884
  let text = null;
18809
18885
  try {
18810
18886
  const size = statSync9(docAbs).size;
18811
- if (size <= budget) text = readFileSync24(docAbs, "utf8");
18887
+ if (size <= budget) text = readFileSync25(docAbs, "utf8");
18812
18888
  } catch {
18813
18889
  continue;
18814
18890
  }
@@ -18825,7 +18901,7 @@ function readDocShareBundle({ cwd, sourcePath }) {
18825
18901
  let body;
18826
18902
  try {
18827
18903
  if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
18828
- body = readFileSync24(resolved2.abs);
18904
+ body = readFileSync25(resolved2.abs);
18829
18905
  } catch {
18830
18906
  continue;
18831
18907
  }
@@ -18854,12 +18930,12 @@ function readDocShareBundle({ cwd, sourcePath }) {
18854
18930
  // src/dashboard/host-upgrade.ts
18855
18931
  import { spawn as spawn7 } from "child_process";
18856
18932
  import { mkdirSync as mkdirSync13, openSync as openSync3 } from "fs";
18857
- import { join as join27 } from "path";
18933
+ import { join as join26 } from "path";
18858
18934
  var running = false;
18859
18935
  function startHostUpgrade() {
18860
18936
  if (running) return { started: false };
18861
18937
  mkdirSync13(paths.LOG_DIR, { recursive: true });
18862
- const log5 = openSync3(join27(paths.LOG_DIR, "host-upgrade.log"), "a");
18938
+ const log5 = openSync3(join26(paths.LOG_DIR, "host-upgrade.log"), "a");
18863
18939
  const child = spawn7(process.execPath, [process.argv[1] ?? "", "upgrade"], {
18864
18940
  detached: true,
18865
18941
  stdio: ["ignore", log5, log5]
@@ -18880,7 +18956,7 @@ function dataSocketUrl() {
18880
18956
  return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
18881
18957
  }
18882
18958
  function listLocalSessions() {
18883
- if (!existsSync30(paths.DB_FILE)) return [];
18959
+ if (!existsSync31(paths.DB_FILE)) return [];
18884
18960
  const store = new SessionStore(paths.DB_FILE);
18885
18961
  try {
18886
18962
  return store.listSessions();
@@ -18922,7 +18998,7 @@ function buildHostConfigSnapshot() {
18922
18998
  const snapshot2 = {};
18923
18999
  for (const profile of listProfiles()) {
18924
19000
  snapshot2[profile] = runWithProfile(profile, () => ({
18925
- env: existsSync30(paths.CONFIG_FILE) ? dotenv2.parse(readFileSync25(paths.CONFIG_FILE, "utf-8")) : {},
19001
+ env: existsSync31(paths.CONFIG_FILE) ? dotenv2.parse(readFileSync26(paths.CONFIG_FILE, "utf-8")) : {},
18926
19002
  agentModel: loadDashboardSettings().agent
18927
19003
  }));
18928
19004
  }
@@ -19002,6 +19078,10 @@ function connectHostSocket(url, callbacks, onAuthenticated, onWorkspaces) {
19002
19078
  handleHostDbResult(msg.id, msg.res);
19003
19079
  return;
19004
19080
  }
19081
+ if (msg.type === "attachment-url-result") {
19082
+ handleAttachmentUrlResult(msg.id, msg.url);
19083
+ return;
19084
+ }
19005
19085
  if (msg.type === "call") {
19006
19086
  void handleCall(msg, callbacks, ws);
19007
19087
  }
@@ -19168,8 +19248,8 @@ function startPushWatcher() {
19168
19248
  }
19169
19249
 
19170
19250
  // src/dashboard/machine-server.ts
19171
- import { existsSync as existsSync31 } from "fs";
19172
- import { join as join28 } from "path";
19251
+ import { existsSync as existsSync32 } from "fs";
19252
+ import { join as join27 } from "path";
19173
19253
  import { serveStatic } from "@hono/node-server/serve-static";
19174
19254
 
19175
19255
  // src/dashboard/app-icon-routes.ts
@@ -19251,7 +19331,7 @@ function pngResponse(c, png) {
19251
19331
  }
19252
19332
 
19253
19333
  // src/dashboard/machine-server.ts
19254
- var WEB_DIST = join28(paths.WEB_ROOT, "dist");
19334
+ var WEB_DIST = join27(paths.WEB_ROOT, "dist");
19255
19335
  async function bootMachine() {
19256
19336
  await reconcileAll().catch(
19257
19337
  (err) => console.error("[dashboard] reconcileAll failed:", err.message)
@@ -19271,14 +19351,14 @@ async function bootMachine() {
19271
19351
  function mountSpa(app) {
19272
19352
  app.route("/share", createPublicShareAssetRouter(WEB_DIST));
19273
19353
  app.route("/", createAppIconRouter());
19274
- if (existsSync31(WEB_DIST) || existsSync31(join28(paths.WEB_ROOT, "package.json"))) {
19354
+ if (existsSync32(WEB_DIST) || existsSync32(join27(paths.WEB_ROOT, "package.json"))) {
19275
19355
  app.use("/*", serveStatic({ root: WEB_DIST }));
19276
19356
  }
19277
19357
  app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
19278
- const spaFallback = serveStatic({ path: join28(WEB_DIST, "index.html") });
19358
+ const spaFallback = serveStatic({ path: join27(WEB_DIST, "index.html") });
19279
19359
  app.get("*", async (c, next) => {
19280
- if (!existsSync31(join28(WEB_DIST, "index.html"))) {
19281
- const isCheckout = existsSync31(join28(paths.WEB_ROOT, "package.json"));
19360
+ if (!existsSync32(join27(WEB_DIST, "index.html"))) {
19361
+ const isCheckout = existsSync32(join27(paths.WEB_ROOT, "package.json"));
19282
19362
  return c.text(
19283
19363
  isCheckout ? `Workspace UI build missing (${WEB_DIST}).
19284
19364
  Run \`pnpm build:web\` from the repo root.
@@ -19412,8 +19492,8 @@ function openBrowser2(url) {
19412
19492
  }
19413
19493
  }
19414
19494
  function ensureWebBuild() {
19415
- if (existsSync32(join29(paths.WEB_ROOT, "dist", "index.html"))) return;
19416
- if (!existsSync32(join29(paths.WEB_ROOT, "package.json"))) return;
19495
+ if (existsSync33(join28(paths.WEB_ROOT, "dist", "index.html"))) return;
19496
+ if (!existsSync33(join28(paths.WEB_ROOT, "package.json"))) return;
19417
19497
  console.log(" packages/ui/dist missing, building the dashboard UI...");
19418
19498
  const result = spawnSync("pnpm", ["build"], {
19419
19499
  cwd: paths.WEB_ROOT,
@@ -19463,7 +19543,7 @@ async function runDashboard(args) {
19463
19543
  }
19464
19544
 
19465
19545
  // src/commands/workspace.ts
19466
- import { existsSync as existsSync33, mkdirSync as mkdirSync14, writeFileSync as writeFileSync13 } from "fs";
19546
+ import { existsSync as existsSync34, mkdirSync as mkdirSync14, writeFileSync as writeFileSync13 } from "fs";
19467
19547
  var USAGE7 = `
19468
19548
  Usage: flyspace workspace <subcommand> [options]
19469
19549
 
@@ -19549,7 +19629,7 @@ function provisionLocal(botName) {
19549
19629
  ensureDirs();
19550
19630
  console.log(` config dir ready (${paths.CONFIG_DIR})`);
19551
19631
  console.log(` data dir ready (${paths.DATA_DIR})`);
19552
- if (existsSync33(paths.CONFIG_FILE)) {
19632
+ if (existsSync34(paths.CONFIG_FILE)) {
19553
19633
  console.log(" config.env exists");
19554
19634
  } else {
19555
19635
  writeFileSync13(paths.CONFIG_FILE, `BOT_NAME=${botName}
@@ -19566,7 +19646,7 @@ async function provisionChannels(specs) {
19566
19646
  continue;
19567
19647
  }
19568
19648
  const cwd = expandPath(spec.cwd);
19569
- if (!existsSync33(cwd)) {
19649
+ if (!existsSync34(cwd)) {
19570
19650
  mkdirSync14(cwd, { recursive: true });
19571
19651
  console.log(` created cwd ${cwd}`);
19572
19652
  }
@@ -19644,10 +19724,10 @@ async function runReconcileCmd(args) {
19644
19724
  }
19645
19725
 
19646
19726
  // src/cli.ts
19647
- import { join as join30 } from "path";
19727
+ import { join as join29 } from "path";
19648
19728
  var home = flyspaceHome();
19649
19729
  if (home) {
19650
- const bin = join30(home, "bin");
19730
+ const bin = join29(home, "bin");
19651
19731
  const path = process.env.PATH ?? "";
19652
19732
  if (!path.split(":").includes(bin)) process.env.PATH = path ? `${bin}:${path}` : bin;
19653
19733
  }
@@ -19785,7 +19865,7 @@ async function main() {
19785
19865
  break;
19786
19866
  case "-v":
19787
19867
  case "--version":
19788
- printRaw("0.1.30");
19868
+ printRaw("0.1.32");
19789
19869
  break;
19790
19870
  case "-h":
19791
19871
  case "--help":