flyspace-cli 0.1.10 → 0.1.13
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/README.md +4 -7
- package/dist/{chunk-UYLVSUD6.js → chunk-VKTJBYA3.js} +38 -7
- package/dist/cli.js +546 -319
- package/dist/index.js +1 -1
- package/package.json +1 -1
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-
|
|
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/
|
|
317
|
-
import { existsSync,
|
|
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} ${
|
|
368
|
-
console.log(` Prompt file: ${paths.PROMPT_FILE} ${
|
|
369
|
-
console.log(` Database: ${paths.DB_FILE} ${
|
|
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 =
|
|
372
|
-
console.log(` Repo .env: ${repoEnv} ${
|
|
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 (!
|
|
397
|
-
const defaultEnv =
|
|
398
|
-
if (
|
|
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 =
|
|
409
|
-
return new Promise((
|
|
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
|
-
|
|
381
|
+
resolve7();
|
|
413
382
|
} else {
|
|
414
383
|
reject(new Error(`Editor exited with code ${code}`));
|
|
415
384
|
}
|
|
@@ -439,7 +408,7 @@ import { confirm as confirm2 } from "@inquirer/prompts";
|
|
|
439
408
|
|
|
440
409
|
// src/service.ts
|
|
441
410
|
import { execSync } from "child_process";
|
|
442
|
-
import { existsSync as
|
|
411
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, realpathSync } from "fs";
|
|
443
412
|
import { basename, dirname as dirname2, join as join2 } from "path";
|
|
444
413
|
import { homedir, platform } from "os";
|
|
445
414
|
function flyspaceHome(entry = process.argv[1]) {
|
|
@@ -454,7 +423,7 @@ function flyspaceHome(entry = process.argv[1]) {
|
|
|
454
423
|
const npmPrefix = cliPath.slice(0, at);
|
|
455
424
|
if (basename(npmPrefix) !== "cli") return void 0;
|
|
456
425
|
const home = dirname2(npmPrefix);
|
|
457
|
-
return
|
|
426
|
+
return existsSync2(join2(home, "bin", "flyspace")) && existsSync2(join2(home, "node", "bin", "node")) ? home : void 0;
|
|
458
427
|
}
|
|
459
428
|
function serviceLabel(profile) {
|
|
460
429
|
return profile === DEFAULT_PROFILE ? "ai.flyspace" : `ai.flyspace.${profile}`;
|
|
@@ -478,8 +447,8 @@ function systemdUnitPath(profile) {
|
|
|
478
447
|
return join2(homedir(), ".config", "systemd", "user", `${systemdUnitName(profile)}.service`);
|
|
479
448
|
}
|
|
480
449
|
function isServiceInstalled(profile) {
|
|
481
|
-
if (platform() === "darwin") return
|
|
482
|
-
if (platform() === "linux") return
|
|
450
|
+
if (platform() === "darwin") return existsSync2(plistPath(profile));
|
|
451
|
+
if (platform() === "linux") return existsSync2(systemdUnitPath(profile));
|
|
483
452
|
return false;
|
|
484
453
|
}
|
|
485
454
|
function serviceBinaryMissing(file) {
|
|
@@ -490,7 +459,7 @@ function serviceBinaryMissing(file) {
|
|
|
490
459
|
return false;
|
|
491
460
|
}
|
|
492
461
|
const bin = text.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/)?.[1] ?? text.match(/^ExecStart=(\S+)/m)?.[1];
|
|
493
|
-
return !!bin && !
|
|
462
|
+
return !!bin && !existsSync2(bin);
|
|
494
463
|
}
|
|
495
464
|
function profileServiceFile(profile) {
|
|
496
465
|
return platform() === "darwin" ? plistPath(profile) : systemdUnitPath(profile);
|
|
@@ -529,7 +498,7 @@ function hostPlistPath() {
|
|
|
529
498
|
return join2(homedir(), "Library", "LaunchAgents", `${HOST_SERVICE_LABEL}.plist`);
|
|
530
499
|
}
|
|
531
500
|
function isHostServiceInstalled() {
|
|
532
|
-
if (platform() === "darwin" || platform() === "linux") return
|
|
501
|
+
if (platform() === "darwin" || platform() === "linux") return existsSync2(hostServiceFile());
|
|
533
502
|
return false;
|
|
534
503
|
}
|
|
535
504
|
function restartHostService() {
|
|
@@ -569,9 +538,9 @@ function isHostServiceRunning() {
|
|
|
569
538
|
}
|
|
570
539
|
|
|
571
540
|
// src/commands/install.ts
|
|
572
|
-
import { existsSync as
|
|
541
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
573
542
|
import { execSync as execSync2 } from "child_process";
|
|
574
|
-
import { dirname as dirname3, join as join3, resolve as
|
|
543
|
+
import { dirname as dirname3, join as join3, resolve as resolve2 } from "path";
|
|
575
544
|
import { homedir as homedir2, platform as platform2 } from "os";
|
|
576
545
|
function log(msg) {
|
|
577
546
|
console.log(`
|
|
@@ -603,7 +572,7 @@ function detectNodeBinDir() {
|
|
|
603
572
|
}
|
|
604
573
|
function nodeShimDirs() {
|
|
605
574
|
return [join3(homedir2(), ".local", "share", "mise", "shims"), join3(homedir2(), ".asdf", "shims")].filter(
|
|
606
|
-
(d) =>
|
|
575
|
+
(d) => existsSync3(d)
|
|
607
576
|
);
|
|
608
577
|
}
|
|
609
578
|
function detectCustieBin() {
|
|
@@ -611,7 +580,7 @@ function detectCustieBin() {
|
|
|
611
580
|
if (home) return join3(home, "bin", "flyspace");
|
|
612
581
|
const argv1 = process.argv[1];
|
|
613
582
|
if (argv1) {
|
|
614
|
-
return
|
|
583
|
+
return resolve2(argv1);
|
|
615
584
|
}
|
|
616
585
|
try {
|
|
617
586
|
return run("which flyspace", { silent: true });
|
|
@@ -620,7 +589,7 @@ function detectCustieBin() {
|
|
|
620
589
|
}
|
|
621
590
|
}
|
|
622
591
|
function readEnvFile() {
|
|
623
|
-
if (!
|
|
592
|
+
if (!existsSync3(paths.CONFIG_FILE)) return {};
|
|
624
593
|
const vars = {};
|
|
625
594
|
for (const line of readFileSync4(paths.CONFIG_FILE, "utf-8").split("\n")) {
|
|
626
595
|
const trimmed = line.trim();
|
|
@@ -668,7 +637,7 @@ async function installMacOS(spec) {
|
|
|
668
637
|
const logDir = paths.LOG_DIR;
|
|
669
638
|
mkdirSync2(logDir, { recursive: true });
|
|
670
639
|
const envVars = spec.env;
|
|
671
|
-
const homebrewBin =
|
|
640
|
+
const homebrewBin = existsSync3("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
|
|
672
641
|
const pathParts = [
|
|
673
642
|
detectNodeBinDir(),
|
|
674
643
|
claudeBinDir,
|
|
@@ -736,7 +705,7 @@ async function installLinux(spec) {
|
|
|
736
705
|
const logDir = paths.LOG_DIR;
|
|
737
706
|
mkdirSync2(logDir, { recursive: true });
|
|
738
707
|
const envLines = Object.entries(spec.env).map(([k, v]) => `Environment="${k}=${v}"`).join("\n");
|
|
739
|
-
const linuxbrewBin =
|
|
708
|
+
const linuxbrewBin = existsSync3("/home/linuxbrew/.linuxbrew/bin") ? "/home/linuxbrew/.linuxbrew/bin" : void 0;
|
|
740
709
|
const pathParts = [
|
|
741
710
|
detectNodeBinDir(),
|
|
742
711
|
claudeBinDir,
|
|
@@ -796,13 +765,13 @@ async function installHostService() {
|
|
|
796
765
|
}
|
|
797
766
|
|
|
798
767
|
// src/commands/profiles.ts
|
|
799
|
-
import { existsSync as
|
|
768
|
+
import { existsSync as existsSync4, readdirSync, statSync } from "fs";
|
|
800
769
|
import { execSync as execSync3 } from "child_process";
|
|
801
770
|
import { join as join4 } from "path";
|
|
802
771
|
import { platform as platform3 } from "os";
|
|
803
772
|
function listProfiles() {
|
|
804
773
|
const profiles = [];
|
|
805
|
-
if (
|
|
774
|
+
if (existsSync4(paths.PROFILES_DIR)) {
|
|
806
775
|
for (const entry of readdirSync(paths.PROFILES_DIR)) {
|
|
807
776
|
try {
|
|
808
777
|
if (statSync(join4(paths.PROFILES_DIR, entry)).isDirectory()) {
|
|
@@ -831,7 +800,7 @@ function isServiceRunning2(profile) {
|
|
|
831
800
|
|
|
832
801
|
// src/auth/device-auth.ts
|
|
833
802
|
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign } from "crypto";
|
|
834
|
-
import { existsSync as
|
|
803
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
835
804
|
import { dirname as dirname4, join as join5 } from "path";
|
|
836
805
|
var DEFAULT_API_URL = "https://app.flyspace.ai";
|
|
837
806
|
var EXPIRY_SKEW_MS = 6e4;
|
|
@@ -853,7 +822,7 @@ function credentialsPath() {
|
|
|
853
822
|
}
|
|
854
823
|
function ensureDeviceKey(path = deviceKeyPath()) {
|
|
855
824
|
let created = false;
|
|
856
|
-
if (!
|
|
825
|
+
if (!existsSync5(path)) {
|
|
857
826
|
const { privateKey: privateKey2 } = generateKeyPairSync("ed25519");
|
|
858
827
|
mkdirSync3(dirname4(path), { recursive: true });
|
|
859
828
|
writeFileSync4(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
|
|
@@ -866,7 +835,7 @@ function ensureDeviceKey(path = deviceKeyPath()) {
|
|
|
866
835
|
return { privateKey, publicKeyBase64, created };
|
|
867
836
|
}
|
|
868
837
|
function loadCredentials(path = credentialsPath()) {
|
|
869
|
-
if (!
|
|
838
|
+
if (!existsSync5(path)) return null;
|
|
870
839
|
try {
|
|
871
840
|
return JSON.parse(readFileSync5(path, "utf8"));
|
|
872
841
|
} catch {
|
|
@@ -1010,7 +979,7 @@ async function getAccessToken() {
|
|
|
1010
979
|
// src/dashboard/onboarding-scan.ts
|
|
1011
980
|
import { execFileSync } from "child_process";
|
|
1012
981
|
import {
|
|
1013
|
-
existsSync as
|
|
982
|
+
existsSync as existsSync6,
|
|
1014
983
|
lstatSync,
|
|
1015
984
|
mkdirSync as mkdirSync4,
|
|
1016
985
|
readdirSync as readdirSync2,
|
|
@@ -1019,7 +988,7 @@ import {
|
|
|
1019
988
|
symlinkSync
|
|
1020
989
|
} from "fs";
|
|
1021
990
|
import { homedir as homedir3 } from "os";
|
|
1022
|
-
import { basename as basename2, join as join6, resolve as
|
|
991
|
+
import { basename as basename2, join as join6, resolve as resolve3 } from "path";
|
|
1023
992
|
var MAX_JSONL_HEAD_BYTES = 64 * 1024;
|
|
1024
993
|
var GIT_SCAN_ROOTS = ["Workspaces", "Projects", "code"];
|
|
1025
994
|
function flyspaceRoot() {
|
|
@@ -1041,7 +1010,7 @@ function codexDir() {
|
|
|
1041
1010
|
}
|
|
1042
1011
|
function claudeLoggedIn() {
|
|
1043
1012
|
if (process.env["ANTHROPIC_API_KEY"]) return true;
|
|
1044
|
-
if (
|
|
1013
|
+
if (existsSync6(join6(claudeDir(), ".credentials.json"))) return true;
|
|
1045
1014
|
try {
|
|
1046
1015
|
const raw = JSON.parse(readFileSync6(join6(homedir3(), ".claude.json"), "utf-8"));
|
|
1047
1016
|
if (raw.oauthAccount) return true;
|
|
@@ -1058,7 +1027,7 @@ function claudeLoggedIn() {
|
|
|
1058
1027
|
}
|
|
1059
1028
|
}
|
|
1060
1029
|
function codexLoggedIn() {
|
|
1061
|
-
return !!process.env["OPENAI_API_KEY"] ||
|
|
1030
|
+
return !!process.env["OPENAI_API_KEY"] || existsSync6(join6(codexDir(), "auth.json"));
|
|
1062
1031
|
}
|
|
1063
1032
|
function jsonlFiles(dir, depth) {
|
|
1064
1033
|
const out = [];
|
|
@@ -1132,7 +1101,7 @@ function gitRepos() {
|
|
|
1132
1101
|
continue;
|
|
1133
1102
|
}
|
|
1134
1103
|
for (const e of entries2) {
|
|
1135
|
-
if (e.isDirectory() &&
|
|
1104
|
+
if (e.isDirectory() && existsSync6(join6(root, e.name, ".git"))) found.push(join6(root, e.name));
|
|
1136
1105
|
}
|
|
1137
1106
|
}
|
|
1138
1107
|
return found;
|
|
@@ -1157,7 +1126,7 @@ function scanOnboarding() {
|
|
|
1157
1126
|
const byPath = /* @__PURE__ */ new Map();
|
|
1158
1127
|
const history = { claude: false, codex: false };
|
|
1159
1128
|
const add = (rawPath, source, mtime, sessions, trusted = false) => {
|
|
1160
|
-
const path =
|
|
1129
|
+
const path = resolve3(rawPath);
|
|
1161
1130
|
if (isExcluded(path)) return;
|
|
1162
1131
|
const existing = byPath.get(path);
|
|
1163
1132
|
if (existing) {
|
|
@@ -1249,7 +1218,7 @@ function linkProjectFolders(slug, items) {
|
|
|
1249
1218
|
}
|
|
1250
1219
|
if (!exists) {
|
|
1251
1220
|
if (item.path) {
|
|
1252
|
-
const target =
|
|
1221
|
+
const target = resolve3(item.path);
|
|
1253
1222
|
if (!statSync2(target).isDirectory()) throw new Error(`not a directory: ${target}`);
|
|
1254
1223
|
makeLink(target, dest);
|
|
1255
1224
|
} else {
|
|
@@ -1262,7 +1231,7 @@ function linkProjectFolders(slug, items) {
|
|
|
1262
1231
|
}
|
|
1263
1232
|
|
|
1264
1233
|
// src/commands/auth.ts
|
|
1265
|
-
import { spawn as
|
|
1234
|
+
import { spawn as spawn2 } from "child_process";
|
|
1266
1235
|
import { confirm } from "@inquirer/prompts";
|
|
1267
1236
|
import { hostname, platform as platform4, release } from "os";
|
|
1268
1237
|
var BOLD = "\x1B[1m";
|
|
@@ -1309,7 +1278,7 @@ function openBrowser(url) {
|
|
|
1309
1278
|
if (process.env["SSH_CONNECTION"] || process.env["SSH_TTY"]) return false;
|
|
1310
1279
|
const cmd = platform4() === "darwin" ? "open" : platform4() === "win32" ? "explorer" : "xdg-open";
|
|
1311
1280
|
try {
|
|
1312
|
-
const child =
|
|
1281
|
+
const child = spawn2(cmd, [url], { stdio: "ignore", detached: true });
|
|
1313
1282
|
child.on("error", () => {
|
|
1314
1283
|
});
|
|
1315
1284
|
child.unref();
|
|
@@ -1454,7 +1423,7 @@ async function runStatus() {
|
|
|
1454
1423
|
const printLocal = (workspaces) => {
|
|
1455
1424
|
console.log("");
|
|
1456
1425
|
printIdentity({ ...creds, workspaces }, creds.deviceName);
|
|
1457
|
-
console.log(` ${DIM}API${RESET} ${creds.apiUrl}`);
|
|
1426
|
+
if (creds.apiUrl !== DEFAULT_API_URL) console.log(` ${DIM}API${RESET} ${creds.apiUrl}`);
|
|
1458
1427
|
console.log(` ${DIM}Key${RESET} ${tilde(deviceKeyPath())}`);
|
|
1459
1428
|
console.log(` ${DIM}Credentials${RESET} ${tilde(credentialsPath())}`);
|
|
1460
1429
|
};
|
|
@@ -1571,7 +1540,7 @@ async function runAuthCmd(args) {
|
|
|
1571
1540
|
}
|
|
1572
1541
|
|
|
1573
1542
|
// src/version.ts
|
|
1574
|
-
var CLI_VERSION = true ? "0.1.
|
|
1543
|
+
var CLI_VERSION = true ? "0.1.13" : null;
|
|
1575
1544
|
function isOlderVersion(a, b) {
|
|
1576
1545
|
const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1577
1546
|
const [pa, pb] = [parse2(a), parse2(b)];
|
|
@@ -1849,7 +1818,8 @@ ${BOLD3}Flyspace Doctor${RESET3}
|
|
|
1849
1818
|
}
|
|
1850
1819
|
|
|
1851
1820
|
// src/commands/uninstall.ts
|
|
1852
|
-
import { existsSync as
|
|
1821
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7, rmSync as rmSync3, unlinkSync as unlinkSync2, writeFileSync as writeFileSync5 } from "fs";
|
|
1822
|
+
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
1853
1823
|
import { execSync as execSync5 } from "child_process";
|
|
1854
1824
|
import { basename as basename3, join as join8 } from "path";
|
|
1855
1825
|
import { homedir as homedir4, platform as platform5 } from "os";
|
|
@@ -1866,14 +1836,13 @@ function warn2(msg) {
|
|
|
1866
1836
|
function run2(cmd) {
|
|
1867
1837
|
execSync5(cmd, { encoding: "utf-8", stdio: "pipe" });
|
|
1868
1838
|
}
|
|
1869
|
-
async function
|
|
1839
|
+
async function runServiceUninstall(profile = getProfile()) {
|
|
1870
1840
|
const os = platform5();
|
|
1871
|
-
const profile = getProfile();
|
|
1872
1841
|
log2("Uninstalling Flyspace service...");
|
|
1873
1842
|
if (os === "darwin") {
|
|
1874
1843
|
const removed = [];
|
|
1875
1844
|
for (const plist of [plistPath(profile), legacyPlistPath(profile)]) {
|
|
1876
|
-
if (!
|
|
1845
|
+
if (!existsSync7(plist)) continue;
|
|
1877
1846
|
try {
|
|
1878
1847
|
run2(`launchctl unload "${plist}"`);
|
|
1879
1848
|
} catch {
|
|
@@ -1908,11 +1877,51 @@ async function runUninstall() {
|
|
|
1908
1877
|
} else {
|
|
1909
1878
|
warn2(`Unsupported platform: ${os}`);
|
|
1910
1879
|
}
|
|
1911
|
-
|
|
1880
|
+
}
|
|
1881
|
+
async function runUninstall(args = []) {
|
|
1882
|
+
const prefix = process.env["FLYSPACE_PREFIX"] || join8(homedir4(), ".flyspace");
|
|
1883
|
+
const targets = [paths.BASE_CONFIG_DIR, paths.BASE_DATA_DIR, prefix].filter(existsSync7);
|
|
1884
|
+
console.log("\n This removes Flyspace from this machine:");
|
|
1885
|
+
console.log(" - revokes this device and removes every background service");
|
|
1886
|
+
for (const t of targets) console.log(` - deletes ${t}`);
|
|
1887
|
+
console.log(" Your channel folders (~/Flyspace) are kept.\n");
|
|
1888
|
+
if (!args.includes("--yes") && !args.includes("-y")) {
|
|
1889
|
+
if (!await confirm3({ message: "Continue?", default: false })) {
|
|
1890
|
+
console.log(" Cancelled.");
|
|
1891
|
+
return;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
const creds = loadCredentials();
|
|
1895
|
+
if (creds) {
|
|
1896
|
+
try {
|
|
1897
|
+
const token = await getAccessToken();
|
|
1898
|
+
if (token) await revokeDevice(creds.apiUrl, token);
|
|
1899
|
+
success2("Device revoked.");
|
|
1900
|
+
} catch {
|
|
1901
|
+
warn2("Could not reach the server to revoke this device. Remove it from Settings later.");
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
for (const profile of ["default", ...listProfiles()]) {
|
|
1905
|
+
await runServiceUninstall(profile);
|
|
1906
|
+
}
|
|
1907
|
+
for (const t of targets) {
|
|
1908
|
+
rmSync3(t, { recursive: true, force: true });
|
|
1909
|
+
success2(`Deleted ${t}`);
|
|
1910
|
+
}
|
|
1911
|
+
for (const rc of [".zshrc", ".bashrc", ".profile"].map((f) => join8(homedir4(), f))) {
|
|
1912
|
+
if (!existsSync7(rc)) continue;
|
|
1913
|
+
const before = readFileSync7(rc, "utf-8");
|
|
1914
|
+
const after = before.replace(/\n# Flyspace CLI\nexport PATH="[^"\n]*\.flyspace\/bin:\$PATH"\n?/g, "\n");
|
|
1915
|
+
if (after !== before) {
|
|
1916
|
+
writeFileSync5(rc, after);
|
|
1917
|
+
success2(`Removed PATH entry from ${rc}`);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
console.log("\n Flyspace is uninstalled. Open a new terminal to finish.\n");
|
|
1912
1921
|
}
|
|
1913
1922
|
|
|
1914
1923
|
// src/commands/profiles-cmd.ts
|
|
1915
|
-
import { rmSync as
|
|
1924
|
+
import { rmSync as rmSync4 } from "fs";
|
|
1916
1925
|
var USAGE = `
|
|
1917
1926
|
Usage: flyspace profiles [list]
|
|
1918
1927
|
flyspace profiles remove <name>
|
|
@@ -1969,7 +1978,7 @@ async function removeCmd(name) {
|
|
|
1969
1978
|
}
|
|
1970
1979
|
const { configDir: configDir2, dataDir } = runWithProfile(name, () => ({ configDir: paths.CONFIG_DIR, dataDir: paths.DATA_DIR }));
|
|
1971
1980
|
if (isServiceInstalled(name)) await runWithProfile(name, runUninstall);
|
|
1972
|
-
|
|
1981
|
+
rmSync4(configDir2, { recursive: true, force: true });
|
|
1973
1982
|
console.log(`\x1B[32m+\x1B[0m Local config removed: ${configDir2}`);
|
|
1974
1983
|
console.log(`
|
|
1975
1984
|
Kept: ${dataDir} (sessions, logs) and any ~/Flyspace/${name}/ channel folders.`);
|
|
@@ -2023,7 +2032,7 @@ function npmEnv() {
|
|
|
2023
2032
|
return home ? { ...process.env, PATH: `${join9(home, "node", "bin")}:${process.env.PATH ?? ""}` } : process.env;
|
|
2024
2033
|
}
|
|
2025
2034
|
async function runUpgrade() {
|
|
2026
|
-
const current = "0.1.
|
|
2035
|
+
const current = "0.1.13";
|
|
2027
2036
|
console.log(`Current version: ${current}`);
|
|
2028
2037
|
console.log("Checking for updates...\n");
|
|
2029
2038
|
try {
|
|
@@ -2394,7 +2403,7 @@ async function runAutomationCmd(args) {
|
|
|
2394
2403
|
}
|
|
2395
2404
|
|
|
2396
2405
|
// src/commands/usage.ts
|
|
2397
|
-
import { existsSync as
|
|
2406
|
+
import { existsSync as existsSync8 } from "fs";
|
|
2398
2407
|
var HELP4 = `
|
|
2399
2408
|
Usage: flyspace usage [options]
|
|
2400
2409
|
|
|
@@ -2521,7 +2530,7 @@ async function runUsage(args) {
|
|
|
2521
2530
|
return;
|
|
2522
2531
|
}
|
|
2523
2532
|
const { days, by, explain, json } = parsed;
|
|
2524
|
-
if (!
|
|
2533
|
+
if (!existsSync8(paths.DB_FILE)) {
|
|
2525
2534
|
console.log("No usage recorded yet (the database does not exist).");
|
|
2526
2535
|
return;
|
|
2527
2536
|
}
|
|
@@ -2635,21 +2644,21 @@ async function runRestart() {
|
|
|
2635
2644
|
}
|
|
2636
2645
|
|
|
2637
2646
|
// src/commands/logs.ts
|
|
2638
|
-
import { spawn as
|
|
2639
|
-
import { existsSync as
|
|
2647
|
+
import { spawn as spawn3 } from "child_process";
|
|
2648
|
+
import { existsSync as existsSync9 } from "fs";
|
|
2640
2649
|
import { join as join10 } from "path";
|
|
2641
2650
|
async function runLogs(args) {
|
|
2642
2651
|
const showErrors = args.includes("--error") || args.includes("-e");
|
|
2643
2652
|
const filename = showErrors ? "custie-error.log" : "custie.log";
|
|
2644
2653
|
const logFile = join10(paths.LOG_DIR, filename);
|
|
2645
|
-
if (!
|
|
2654
|
+
if (!existsSync9(logFile)) {
|
|
2646
2655
|
console.error(`Log file not found: ${logFile}`);
|
|
2647
2656
|
console.error("Is the service installed? Run `flyspace install` first.");
|
|
2648
2657
|
process.exit(1);
|
|
2649
2658
|
}
|
|
2650
2659
|
console.log(`Tailing ${logFile} (Ctrl+C to stop)
|
|
2651
2660
|
`);
|
|
2652
|
-
const tail =
|
|
2661
|
+
const tail = spawn3("tail", ["-f", "-n", "50", logFile], {
|
|
2653
2662
|
stdio: "inherit"
|
|
2654
2663
|
});
|
|
2655
2664
|
tail.on("error", (err) => {
|
|
@@ -2663,10 +2672,10 @@ async function runLogs(args) {
|
|
|
2663
2672
|
|
|
2664
2673
|
// src/commands/slack.ts
|
|
2665
2674
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2666
|
-
import { existsSync as
|
|
2667
|
-
import { resolve as
|
|
2675
|
+
import { existsSync as existsSync26, readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
|
|
2676
|
+
import { resolve as resolve6 } from "path";
|
|
2668
2677
|
import { tmpdir as tmpdir4, homedir as homedir9 } from "os";
|
|
2669
|
-
import { input, confirm as
|
|
2678
|
+
import { input, confirm as confirm4, select, checkbox } from "@inquirer/prompts";
|
|
2670
2679
|
import { WebClient as WebClient3 } from "@slack/web-api";
|
|
2671
2680
|
|
|
2672
2681
|
// src/store/auth-links.ts
|
|
@@ -3833,8 +3842,8 @@ function setupPage(c, token, invite, form = {}) {
|
|
|
3833
3842
|
);
|
|
3834
3843
|
}
|
|
3835
3844
|
function createAuthConfirmRouter() {
|
|
3836
|
-
const
|
|
3837
|
-
|
|
3845
|
+
const confirm5 = new Hono();
|
|
3846
|
+
confirm5.get("/confirm", withResolvedProfile, async (c) => {
|
|
3838
3847
|
const settings = await loadWorkspaceBranding();
|
|
3839
3848
|
const display = fontOf(settings.font).stack;
|
|
3840
3849
|
const name = settings.displayName?.trim() || getProfile();
|
|
@@ -3871,7 +3880,7 @@ function createAuthConfirmRouter() {
|
|
|
3871
3880
|
)
|
|
3872
3881
|
);
|
|
3873
3882
|
});
|
|
3874
|
-
|
|
3883
|
+
confirm5.post("/confirm", withResolvedProfile, async (c) => {
|
|
3875
3884
|
const form = await c.req.parseBody();
|
|
3876
3885
|
const params = readParams({
|
|
3877
3886
|
token: typeof form["token"] === "string" ? form["token"] : void 0,
|
|
@@ -3882,7 +3891,7 @@ function createAuthConfirmRouter() {
|
|
|
3882
3891
|
if (!target) return c.text("SUPABASE_URL is not configured", 500);
|
|
3883
3892
|
return c.redirect(target, 303);
|
|
3884
3893
|
});
|
|
3885
|
-
|
|
3894
|
+
confirm5.get("/invite", withResolvedProfile, async (c) => {
|
|
3886
3895
|
const token = c.req.query("token")?.trim() ?? "";
|
|
3887
3896
|
const lookup = await findInvite(token);
|
|
3888
3897
|
const brandingWorkspace = lookup.state === "valid" ? await getWorkspaceById(lookup.invite.workspaceId) : null;
|
|
@@ -3928,7 +3937,7 @@ function createAuthConfirmRouter() {
|
|
|
3928
3937
|
)
|
|
3929
3938
|
);
|
|
3930
3939
|
});
|
|
3931
|
-
|
|
3940
|
+
confirm5.post("/invite", withResolvedProfile, async (c) => {
|
|
3932
3941
|
const form = await c.req.parseBody();
|
|
3933
3942
|
const token = typeof form["token"] === "string" ? form["token"].trim() : "";
|
|
3934
3943
|
const back = `/auth/invite?token=${encodeURIComponent(token)}`;
|
|
@@ -3945,7 +3954,7 @@ function createAuthConfirmRouter() {
|
|
|
3945
3954
|
if (!await consumeInvite(lookup.invite.id)) return c.redirect(back, 303);
|
|
3946
3955
|
return c.redirect(target, 303);
|
|
3947
3956
|
});
|
|
3948
|
-
|
|
3957
|
+
confirm5.get("/new-workspace", async (c) => {
|
|
3949
3958
|
const token = c.req.query("token")?.trim() ?? "";
|
|
3950
3959
|
const lookup = INVITE_TOKEN.test(token) ? await lookupSetupInvite(token) : { state: "unknown" };
|
|
3951
3960
|
if (lookup.state === "valid") return c.html(setupPage(c, token, lookup.invite));
|
|
@@ -3973,7 +3982,7 @@ function createAuthConfirmRouter() {
|
|
|
3973
3982
|
status
|
|
3974
3983
|
);
|
|
3975
3984
|
});
|
|
3976
|
-
|
|
3985
|
+
confirm5.post("/new-workspace", async (c) => {
|
|
3977
3986
|
const form = await c.req.parseBody();
|
|
3978
3987
|
const token = typeof form["token"] === "string" ? form["token"].trim() : "";
|
|
3979
3988
|
const slug = typeof form["slug"] === "string" ? form["slug"].trim().toLowerCase() : "";
|
|
@@ -4080,7 +4089,7 @@ function createAuthConfirmRouter() {
|
|
|
4080
4089
|
}
|
|
4081
4090
|
return c.redirect(`${origin}/login?email=${encodeURIComponent(invite.email)}`, 303);
|
|
4082
4091
|
});
|
|
4083
|
-
return
|
|
4092
|
+
return confirm5;
|
|
4084
4093
|
}
|
|
4085
4094
|
function confirmLinkFor(origin, actionLink, intent) {
|
|
4086
4095
|
try {
|
|
@@ -4191,15 +4200,15 @@ async function deliverPasswordResetLink(email, redirectTo) {
|
|
|
4191
4200
|
}
|
|
4192
4201
|
|
|
4193
4202
|
// src/dashboard/server.ts
|
|
4194
|
-
import { existsSync as
|
|
4203
|
+
import { existsSync as existsSync25 } from "fs";
|
|
4195
4204
|
import { createServer } from "net";
|
|
4196
4205
|
import { join as join23 } from "path";
|
|
4197
|
-
import { Hono as
|
|
4206
|
+
import { Hono as Hono34 } from "hono";
|
|
4198
4207
|
import { serve } from "@hono/node-server";
|
|
4199
4208
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4200
4209
|
|
|
4201
4210
|
// src/reconcile.ts
|
|
4202
|
-
import { existsSync as
|
|
4211
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
4203
4212
|
async function boundWorkspaces2() {
|
|
4204
4213
|
return (await boundWorkspaces())?.workspaces ?? null;
|
|
4205
4214
|
}
|
|
@@ -4210,13 +4219,13 @@ async function servedWorkspaces2() {
|
|
|
4210
4219
|
async function reconcileProfile(profile) {
|
|
4211
4220
|
return runWithProfile(profile, async () => {
|
|
4212
4221
|
const log5 = [];
|
|
4213
|
-
const hadConfigDir =
|
|
4214
|
-
const hadDataDir =
|
|
4222
|
+
const hadConfigDir = existsSync10(paths.CONFIG_DIR);
|
|
4223
|
+
const hadDataDir = existsSync10(paths.DATA_DIR);
|
|
4215
4224
|
ensureDirs();
|
|
4216
4225
|
if (!hadConfigDir) log5.push(`config dir created (${paths.CONFIG_DIR})`);
|
|
4217
4226
|
if (!hadDataDir) log5.push(`data dir created (${paths.DATA_DIR})`);
|
|
4218
|
-
if (!
|
|
4219
|
-
|
|
4227
|
+
if (!existsSync10(paths.CONFIG_FILE)) {
|
|
4228
|
+
writeFileSync6(paths.CONFIG_FILE, `BOT_NAME=${profile}
|
|
4220
4229
|
`);
|
|
4221
4230
|
log5.push(`config.env written (${paths.CONFIG_FILE})`);
|
|
4222
4231
|
}
|
|
@@ -4394,10 +4403,10 @@ var requireDocsAccess = async (c, next) => {
|
|
|
4394
4403
|
};
|
|
4395
4404
|
|
|
4396
4405
|
// src/dashboard/api.ts
|
|
4397
|
-
import { Hono as
|
|
4406
|
+
import { Hono as Hono31 } from "hono";
|
|
4398
4407
|
|
|
4399
4408
|
// src/dashboard/public-share-routes.ts
|
|
4400
|
-
import { existsSync as
|
|
4409
|
+
import { existsSync as existsSync11, readFileSync as readFileSync9, statSync as statSync3 } from "fs";
|
|
4401
4410
|
import { join as join13 } from "path";
|
|
4402
4411
|
import { Hono as Hono3 } from "hono";
|
|
4403
4412
|
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
|
@@ -4863,7 +4872,7 @@ function injectShareHead(html, head) {
|
|
|
4863
4872
|
|
|
4864
4873
|
// src/dashboard/app-icon.ts
|
|
4865
4874
|
import { createHash as createHash2 } from "crypto";
|
|
4866
|
-
import { readFileSync as
|
|
4875
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
4867
4876
|
import { join as join12 } from "path";
|
|
4868
4877
|
import sharp from "sharp";
|
|
4869
4878
|
var PLATE = "#0A0A0F";
|
|
@@ -4891,7 +4900,7 @@ function roundedPlate(size, fill = PLATE) {
|
|
|
4891
4900
|
}
|
|
4892
4901
|
async function renderBadge(box) {
|
|
4893
4902
|
const transparent = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
4894
|
-
const raster = await sharp(
|
|
4903
|
+
const raster = await sharp(readFileSync8(MARK_SVG_FILE)).resize(512, 512, { fit: "contain", background: transparent }).png().toBuffer();
|
|
4895
4904
|
return sharp(raster).trim().resize(box, box, { fit: "contain", background: transparent }).png().toBuffer();
|
|
4896
4905
|
}
|
|
4897
4906
|
async function sampleBackground(logo) {
|
|
@@ -4946,7 +4955,7 @@ async function renderAppIcon({
|
|
|
4946
4955
|
if (hit) return hit;
|
|
4947
4956
|
const out = logo && await composeIcon(logo, size, maskable, plain).catch(() => null) || // No workspace logo to lead with — the stock mark is still the best icon,
|
|
4948
4957
|
// either on its usual dark plate or, for a favicon, trimmed and bare.
|
|
4949
|
-
(transparent ? await renderBadge(size) : await sharp(
|
|
4958
|
+
(transparent ? await renderBadge(size) : await sharp(readFileSync8(MARK_FILE)).resize(size, size, { fit: "cover" }).png().toBuffer());
|
|
4950
4959
|
cache3.set(key, out);
|
|
4951
4960
|
return out;
|
|
4952
4961
|
}
|
|
@@ -5458,7 +5467,7 @@ function truncationNotice(lang, voice) {
|
|
|
5458
5467
|
if (lang === "en" || lang.startsWith("en-")) return TRUNCATION_NOTICE.en;
|
|
5459
5468
|
return null;
|
|
5460
5469
|
}
|
|
5461
|
-
var sleep2 = (ms) => new Promise((
|
|
5470
|
+
var sleep2 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
5462
5471
|
async function synthesizeWithRetry(script, voice) {
|
|
5463
5472
|
let lastError;
|
|
5464
5473
|
for (let attempt = 1; attempt <= SYNTH_ATTEMPTS; attempt += 1) {
|
|
@@ -6164,7 +6173,7 @@ var shellCache = null;
|
|
|
6164
6173
|
function readShell(path) {
|
|
6165
6174
|
const { mtimeMs } = statSync3(path);
|
|
6166
6175
|
if (shellCache?.mtimeMs === mtimeMs) return shellCache.html;
|
|
6167
|
-
const html =
|
|
6176
|
+
const html = readFileSync9(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
|
|
6168
6177
|
shellCache = { mtimeMs, html };
|
|
6169
6178
|
return html;
|
|
6170
6179
|
}
|
|
@@ -6185,16 +6194,16 @@ function serveBuildFile(c, webDist, dir, name) {
|
|
|
6185
6194
|
return c.text("not found", 404);
|
|
6186
6195
|
}
|
|
6187
6196
|
const path = join13(webDist, dir, name);
|
|
6188
|
-
if (!
|
|
6197
|
+
if (!existsSync11(path)) return c.text("not found", 404);
|
|
6189
6198
|
c.header("Content-Type", type);
|
|
6190
6199
|
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
6191
6200
|
if (ext === ".css") {
|
|
6192
|
-
return c.body(
|
|
6201
|
+
return c.body(readFileSync9(path, "utf8").replaceAll("url(/fonts/", `url(${BUILD_PREFIX}/fonts/`));
|
|
6193
6202
|
}
|
|
6194
6203
|
if (ext === ".js") {
|
|
6195
|
-
return c.body(
|
|
6204
|
+
return c.body(readFileSync9(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
|
|
6196
6205
|
}
|
|
6197
|
-
return c.body(new Uint8Array(
|
|
6206
|
+
return c.body(new Uint8Array(readFileSync9(path)));
|
|
6198
6207
|
}
|
|
6199
6208
|
var HTML_SHARE_CSP = "sandbox allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox";
|
|
6200
6209
|
function servePublishedHtml(c, share) {
|
|
@@ -6331,7 +6340,7 @@ ${analytics}` : head);
|
|
|
6331
6340
|
const lang = canonicalLanguage(requested);
|
|
6332
6341
|
if (!isTranslatableLanguage(lang)) return next();
|
|
6333
6342
|
const shell = join13(webDist, "index.html");
|
|
6334
|
-
if (!
|
|
6343
|
+
if (!existsSync11(shell)) return next();
|
|
6335
6344
|
const share = await getDocShare(c.req.param("id"));
|
|
6336
6345
|
if (!share) return next();
|
|
6337
6346
|
if (requested !== lang) return c.redirect(`/share/${share.id}/${lang}`, 302);
|
|
@@ -6367,7 +6376,7 @@ ${analytics}` : head);
|
|
|
6367
6376
|
if (!share) return next();
|
|
6368
6377
|
if (share.kind === "html") return servePublishedHtml(c, share);
|
|
6369
6378
|
const shell = join13(webDist, "index.html");
|
|
6370
|
-
if (!
|
|
6379
|
+
if (!existsSync11(shell)) return next();
|
|
6371
6380
|
return readerPage(
|
|
6372
6381
|
c,
|
|
6373
6382
|
share,
|
|
@@ -6983,26 +6992,21 @@ async function resolveAgentIdentities() {
|
|
|
6983
6992
|
);
|
|
6984
6993
|
}
|
|
6985
6994
|
async function offlineAgentIdentities() {
|
|
6986
|
-
const
|
|
6987
|
-
|
|
6988
|
-
|
|
6989
|
-
|
|
6990
|
-
|
|
6991
|
-
|
|
6992
|
-
kind: "main",
|
|
6993
|
-
channelId: dm.id,
|
|
6994
|
-
...await loadBotIdentity()
|
|
6995
|
-
}
|
|
6996
|
-
];
|
|
6995
|
+
const identity = await loadBotIdentity();
|
|
6996
|
+
const dm = findChannelByAgentName(BOT_DM_KEY) ?? await ensureAgentDmChannel(BOT_DM_KEY, {
|
|
6997
|
+
name: identity.displayName ?? BOT_LABEL,
|
|
6998
|
+
cwd: resolveChannelConfig("").cwd
|
|
6999
|
+
});
|
|
7000
|
+
return [{ name: BOT_DM_KEY, label: BOT_LABEL, kind: "main", channelId: dm.id, ...identity }];
|
|
6997
7001
|
}
|
|
6998
7002
|
|
|
6999
7003
|
// src/dashboard/host-api.ts
|
|
7000
|
-
import { existsSync as
|
|
7004
|
+
import { existsSync as existsSync20, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
7001
7005
|
import { join as join20 } from "path";
|
|
7002
7006
|
import sharp3 from "sharp";
|
|
7003
7007
|
|
|
7004
7008
|
// src/dashboard/channels-routes.ts
|
|
7005
|
-
import { existsSync as
|
|
7009
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync7 } from "fs";
|
|
7006
7010
|
import { Hono as Hono9 } from "hono";
|
|
7007
7011
|
|
|
7008
7012
|
// src/dashboard/feature-flags-routes.ts
|
|
@@ -7150,7 +7154,7 @@ function coerceMemberEmails(raw) {
|
|
|
7150
7154
|
}
|
|
7151
7155
|
function ensureCwdDir(cwd) {
|
|
7152
7156
|
const expanded = isLogicalCwd(cwd) ? resolveLogicalCwd(cwd) : expandPath(cwd);
|
|
7153
|
-
if (!
|
|
7157
|
+
if (!existsSync12(expanded)) mkdirSync7(expanded, { recursive: true });
|
|
7154
7158
|
}
|
|
7155
7159
|
function toResponse(channel) {
|
|
7156
7160
|
return {
|
|
@@ -7299,25 +7303,25 @@ function createChannelWriteRouter() {
|
|
|
7299
7303
|
// src/dashboard/docs-routes.ts
|
|
7300
7304
|
import {
|
|
7301
7305
|
closeSync,
|
|
7302
|
-
existsSync as
|
|
7306
|
+
existsSync as existsSync16,
|
|
7303
7307
|
lstatSync as lstatSync2,
|
|
7304
7308
|
mkdirSync as mkdirSync8,
|
|
7305
7309
|
openSync,
|
|
7306
7310
|
readSync,
|
|
7307
7311
|
readdirSync as readdirSync4,
|
|
7308
|
-
readFileSync as
|
|
7312
|
+
readFileSync as readFileSync12,
|
|
7309
7313
|
realpathSync as realpathSync3,
|
|
7310
7314
|
renameSync,
|
|
7311
|
-
rmSync as
|
|
7315
|
+
rmSync as rmSync6,
|
|
7312
7316
|
statSync as statSync5,
|
|
7313
|
-
writeFileSync as
|
|
7317
|
+
writeFileSync as writeFileSync8
|
|
7314
7318
|
} from "fs";
|
|
7315
|
-
import { basename as basename6, dirname as dirname5, extname, join as join18, relative, resolve as
|
|
7319
|
+
import { basename as basename6, dirname as dirname5, extname, join as join18, relative, resolve as resolve4, sep } from "path";
|
|
7316
7320
|
import { Hono as Hono10 } from "hono";
|
|
7317
7321
|
import { streamSSE } from "hono/streaming";
|
|
7318
7322
|
|
|
7319
7323
|
// src/dashboard/docs-watcher.ts
|
|
7320
|
-
import { existsSync as
|
|
7324
|
+
import { existsSync as existsSync13, watch } from "fs";
|
|
7321
7325
|
import { basename as basename4, join as join14 } from "path";
|
|
7322
7326
|
var DEBOUNCE_MS = 200;
|
|
7323
7327
|
var MAX_PENDING = 200;
|
|
@@ -7359,7 +7363,7 @@ function watchDocs(cwd, listener) {
|
|
|
7359
7363
|
const current = entries.get(cwd);
|
|
7360
7364
|
if (!current || filename === null) return;
|
|
7361
7365
|
const rel = filename.split("\\").join("/");
|
|
7362
|
-
if (rel === root && !
|
|
7366
|
+
if (rel === root && !existsSync13(join14(cwd, rel))) return;
|
|
7363
7367
|
if (isIgnoredDocPath(rel)) return;
|
|
7364
7368
|
if (current.pending.size < MAX_PENDING) current.pending.add(rel);
|
|
7365
7369
|
if (current.timer === null) current.timer = setTimeout(() => flush(cwd), DEBOUNCE_MS);
|
|
@@ -7596,13 +7600,13 @@ async function readOrQueueDocAudio(input2) {
|
|
|
7596
7600
|
}
|
|
7597
7601
|
|
|
7598
7602
|
// src/dashboard/artifact-export.ts
|
|
7599
|
-
import { readFileSync as
|
|
7603
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
7600
7604
|
|
|
7601
7605
|
// src/dashboard/chat-agent.ts
|
|
7602
7606
|
import { homedir as homedir7 } from "os";
|
|
7603
7607
|
|
|
7604
7608
|
// src/mcp/config.ts
|
|
7605
|
-
import { mkdtempSync, rmSync as
|
|
7609
|
+
import { mkdtempSync, rmSync as rmSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
7606
7610
|
import { tmpdir as tmpdir2 } from "os";
|
|
7607
7611
|
import { join as join15 } from "path";
|
|
7608
7612
|
|
|
@@ -8113,7 +8117,7 @@ function needsRefresh(expiresAt, now = Date.now()) {
|
|
|
8113
8117
|
if (!expiresAt) return false;
|
|
8114
8118
|
return new Date(expiresAt).getTime() - now < REFRESH_SKEW_MS;
|
|
8115
8119
|
}
|
|
8116
|
-
var sleep3 = (ms) => new Promise((
|
|
8120
|
+
var sleep3 = (ms) => new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
8117
8121
|
async function refreshConnection(params) {
|
|
8118
8122
|
const { workspaceId, connection, provider } = params;
|
|
8119
8123
|
const deps = params.deps ?? {};
|
|
@@ -8292,10 +8296,10 @@ async function buildMcpConfig(profile, connectionIds) {
|
|
|
8292
8296
|
function writeMcpConfigFile(config) {
|
|
8293
8297
|
const dir = mkdtempSync(join15(tmpdir2(), "flyspace-mcp-"));
|
|
8294
8298
|
const path = join15(dir, "mcp-config.json");
|
|
8295
|
-
|
|
8299
|
+
writeFileSync7(path, JSON.stringify(config), { mode: 384 });
|
|
8296
8300
|
return {
|
|
8297
8301
|
path,
|
|
8298
|
-
cleanup: () =>
|
|
8302
|
+
cleanup: () => rmSync5(dir, { recursive: true, force: true })
|
|
8299
8303
|
};
|
|
8300
8304
|
}
|
|
8301
8305
|
async function withMcpConfig(profile, connectionIds, fn) {
|
|
@@ -8312,7 +8316,7 @@ async function withMcpConfig(profile, connectionIds, fn) {
|
|
|
8312
8316
|
}
|
|
8313
8317
|
|
|
8314
8318
|
// src/claude/agents.ts
|
|
8315
|
-
import { existsSync as
|
|
8319
|
+
import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync10 } from "fs";
|
|
8316
8320
|
import { homedir as homedir6 } from "os";
|
|
8317
8321
|
import { basename as basename5, join as join16 } from "path";
|
|
8318
8322
|
import yaml from "js-yaml";
|
|
@@ -8327,7 +8331,7 @@ function expandHome(p) {
|
|
|
8327
8331
|
function parseFrontmatter(file) {
|
|
8328
8332
|
let content;
|
|
8329
8333
|
try {
|
|
8330
|
-
content =
|
|
8334
|
+
content = readFileSync10(file, "utf-8");
|
|
8331
8335
|
} catch {
|
|
8332
8336
|
return {};
|
|
8333
8337
|
}
|
|
@@ -8343,7 +8347,7 @@ function parseFrontmatter(file) {
|
|
|
8343
8347
|
}
|
|
8344
8348
|
}
|
|
8345
8349
|
function collectFrom(root, source) {
|
|
8346
|
-
if (!
|
|
8350
|
+
if (!existsSync14(root)) return [];
|
|
8347
8351
|
let entries2;
|
|
8348
8352
|
try {
|
|
8349
8353
|
entries2 = readdirSync3(root).filter((f) => f.endsWith(".md"));
|
|
@@ -8373,7 +8377,7 @@ function readAgentPersona(configDir2, cwd, name) {
|
|
|
8373
8377
|
const agent = listAgents(configDir2, cwd).find((a) => a.name === name);
|
|
8374
8378
|
if (!agent) return void 0;
|
|
8375
8379
|
try {
|
|
8376
|
-
const content =
|
|
8380
|
+
const content = readFileSync10(agent.path, "utf-8");
|
|
8377
8381
|
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
8378
8382
|
} catch {
|
|
8379
8383
|
return void 0;
|
|
@@ -8606,7 +8610,7 @@ function formatMemoryPrompt(channelId, memories) {
|
|
|
8606
8610
|
}
|
|
8607
8611
|
|
|
8608
8612
|
// src/dashboard/rollover-summary.ts
|
|
8609
|
-
import { spawn as
|
|
8613
|
+
import { spawn as spawn4 } from "child_process";
|
|
8610
8614
|
import { tmpdir as tmpdir3 } from "os";
|
|
8611
8615
|
var HAIKU_MODEL = "haiku";
|
|
8612
8616
|
var CJK_PATTERN = /[ -〿-ヿ㐀-䶿一-鿿豈--]/;
|
|
@@ -8647,7 +8651,7 @@ Rules:
|
|
|
8647
8651
|
- No preamble like "Summary:" \u2014 start directly with the summary text.`;
|
|
8648
8652
|
var HAIKU_TIMEOUT_MS = 6e4;
|
|
8649
8653
|
function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
8650
|
-
return new Promise((
|
|
8654
|
+
return new Promise((resolve7, reject) => {
|
|
8651
8655
|
const args = [
|
|
8652
8656
|
"--print",
|
|
8653
8657
|
"--model",
|
|
@@ -8666,7 +8670,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
|
8666
8670
|
if (claudeConfigDir) env["CLAUDE_CONFIG_DIR"] = claudeConfigDir;
|
|
8667
8671
|
if (claudeCodeOauthToken) env["CLAUDE_CODE_OAUTH_TOKEN"] = claudeCodeOauthToken;
|
|
8668
8672
|
else delete env["CLAUDE_CODE_OAUTH_TOKEN"];
|
|
8669
|
-
const child =
|
|
8673
|
+
const child = spawn4("claude", args, {
|
|
8670
8674
|
// A stateless rewrite touches no files — tmpdir keeps it that way and
|
|
8671
8675
|
// avoids picking up an unrelated project's own CLAUDE.md/AGENTS.md,
|
|
8672
8676
|
// unlike the channel's own cwd the main turn runs in.
|
|
@@ -8698,7 +8702,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
|
8698
8702
|
reject(new Error("claude -p --model haiku returned empty output"));
|
|
8699
8703
|
return;
|
|
8700
8704
|
}
|
|
8701
|
-
|
|
8705
|
+
resolve7(text);
|
|
8702
8706
|
});
|
|
8703
8707
|
});
|
|
8704
8708
|
}
|
|
@@ -8945,11 +8949,11 @@ async function downloadChatAttachments(body) {
|
|
|
8945
8949
|
}
|
|
8946
8950
|
|
|
8947
8951
|
// src/dashboard/doc-mention-guard.ts
|
|
8948
|
-
import { existsSync as
|
|
8952
|
+
import { existsSync as existsSync15, statSync as statSync4 } from "fs";
|
|
8949
8953
|
var DOC_MENTION_RE = /(?<![\w@/`])@((?:[\w.-]+\/)+[\w.-]+\.[A-Za-z0-9]{1,6})(?::(\d+))?(?![\w-])/g;
|
|
8950
8954
|
function isRealDoc(root, rel) {
|
|
8951
8955
|
const abs = resolveDocPath(root, rel);
|
|
8952
|
-
if (!abs || !
|
|
8956
|
+
if (!abs || !existsSync15(abs)) return false;
|
|
8953
8957
|
try {
|
|
8954
8958
|
return statSync4(abs).isFile();
|
|
8955
8959
|
} catch {
|
|
@@ -9460,12 +9464,14 @@ var ChatAgent = class {
|
|
|
9460
9464
|
};
|
|
9461
9465
|
}
|
|
9462
9466
|
}
|
|
9463
|
-
const docsAccess = await this.resolveSenderDocsAccess(
|
|
9464
|
-
|
|
9465
|
-
|
|
9466
|
-
|
|
9467
|
-
|
|
9468
|
-
|
|
9467
|
+
const { access: docsAccess, unverified: docsAccessUnverified } = await this.resolveSenderDocsAccess(owningProfile, msg.author, thread.channel_id);
|
|
9468
|
+
if (docsAccess && docsAccess !== "write") {
|
|
9469
|
+
channelConfig = {
|
|
9470
|
+
...channelConfig,
|
|
9471
|
+
docsAccess,
|
|
9472
|
+
...docsAccessUnverified ? { docsAccessUnverified } : {}
|
|
9473
|
+
};
|
|
9474
|
+
}
|
|
9469
9475
|
const agentMemory = await listEnabledFeatureFlagsBySlug(owningProfile).then((ids) => resolveFeatureFlags(ids, false)["agent-memory"]).catch((err) => {
|
|
9470
9476
|
console.error("[chat-agent] feature flag lookup failed:", err.message);
|
|
9471
9477
|
return false;
|
|
@@ -9482,7 +9488,7 @@ var ChatAgent = class {
|
|
|
9482
9488
|
try {
|
|
9483
9489
|
const channelKey = `chat:${thread.channel_id}`;
|
|
9484
9490
|
const session = store.getSession(channelKey, msg.threadId);
|
|
9485
|
-
const docsNote = docsAccessPromptNote(channelConfig.docsAccess);
|
|
9491
|
+
const docsNote = docsAccessPromptNote(channelConfig.docsAccess, channelConfig.docsAccessUnverified);
|
|
9486
9492
|
const header = (docsNote ? `[access: ${docsNote}]
|
|
9487
9493
|
|
|
9488
9494
|
` : "") + `[context: chat channel=${thread.channel_id}` + (thread.title ? `, thread="${thread.title}"` : "") + `, sender=${msg.author}. This is the Flyspace dashboard, not Slack \u2014 it renders full Markdown, so use normal paragraphs, line breaks, and real Markdown lists instead of Slack-style single-line replies. When you name a file that lives in this channel's folder, write it as @ + its cwd-relative path (@docs/spec.md, @web/src/App.tsx) as plain text, never inside backticks or a Markdown link \u2014 the dashboard turns that into a chip the reader clicks to open the file. A trailing line number (@src/app.ts:120) is fine. Paths outside the cwd stay in backticks.]
|
|
@@ -9750,22 +9756,22 @@ ${msg.body}` : msg.body;
|
|
|
9750
9756
|
let ready;
|
|
9751
9757
|
try {
|
|
9752
9758
|
channel = this.supabase().channel(`agent-thinking:${threadId}`);
|
|
9753
|
-
ready = new Promise((
|
|
9759
|
+
ready = new Promise((resolve7) => {
|
|
9754
9760
|
const timeout = setTimeout(() => {
|
|
9755
9761
|
console.error(`[chat-agent] thinking-channel ${threadId}: subscribe timed out after 10s`);
|
|
9756
|
-
|
|
9762
|
+
resolve7(false);
|
|
9757
9763
|
}, 1e4);
|
|
9758
9764
|
channel.subscribe((status, err) => {
|
|
9759
9765
|
if (status === "SUBSCRIBED") {
|
|
9760
9766
|
clearTimeout(timeout);
|
|
9761
|
-
|
|
9767
|
+
resolve7(true);
|
|
9762
9768
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
9763
9769
|
console.error(
|
|
9764
9770
|
`[chat-agent] thinking-channel ${threadId}: ${status}`,
|
|
9765
9771
|
err?.message ?? ""
|
|
9766
9772
|
);
|
|
9767
9773
|
clearTimeout(timeout);
|
|
9768
|
-
|
|
9774
|
+
resolve7(false);
|
|
9769
9775
|
}
|
|
9770
9776
|
});
|
|
9771
9777
|
});
|
|
@@ -9812,19 +9818,19 @@ ${msg.body}` : msg.body;
|
|
|
9812
9818
|
let ready;
|
|
9813
9819
|
try {
|
|
9814
9820
|
channel = this.supabase().channel("channel-activity");
|
|
9815
|
-
ready = new Promise((
|
|
9821
|
+
ready = new Promise((resolve7) => {
|
|
9816
9822
|
const timeout = setTimeout(() => {
|
|
9817
9823
|
console.error("[chat-agent] channel-activity: subscribe timed out after 10s");
|
|
9818
|
-
|
|
9824
|
+
resolve7(false);
|
|
9819
9825
|
}, 1e4);
|
|
9820
9826
|
channel.subscribe((status, err) => {
|
|
9821
9827
|
if (status === "SUBSCRIBED") {
|
|
9822
9828
|
clearTimeout(timeout);
|
|
9823
|
-
|
|
9829
|
+
resolve7(true);
|
|
9824
9830
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
9825
9831
|
console.error(`[chat-agent] channel-activity: ${status}`, err?.message ?? "");
|
|
9826
9832
|
clearTimeout(timeout);
|
|
9827
|
-
|
|
9833
|
+
resolve7(false);
|
|
9828
9834
|
}
|
|
9829
9835
|
});
|
|
9830
9836
|
});
|
|
@@ -9851,15 +9857,17 @@ ${msg.body}` : msg.body;
|
|
|
9851
9857
|
/**
|
|
9852
9858
|
* The sender's Docs level on `channelId`, or undefined when no restriction
|
|
9853
9859
|
* applies (non-human author, or a profile with no Supabase workspace). A
|
|
9854
|
-
* lookup failure resolves to 'none'
|
|
9860
|
+
* lookup failure resolves to 'none' (better to withhold files than leak
|
|
9861
|
+
* them), flagged `unverified` so the agent reports a system error instead of
|
|
9862
|
+
* telling the sender they lack access.
|
|
9855
9863
|
*/
|
|
9856
9864
|
async resolveSenderDocsAccess(profile, author, channelId) {
|
|
9857
|
-
if (!isHumanAuthor(author)) return
|
|
9865
|
+
if (!isHumanAuthor(author)) return {};
|
|
9858
9866
|
try {
|
|
9859
|
-
return await resolveDocsAccessByHandle(profile, author, channelId) ?? void 0;
|
|
9867
|
+
return { access: await resolveDocsAccessByHandle(profile, author, channelId) ?? void 0 };
|
|
9860
9868
|
} catch (err) {
|
|
9861
9869
|
console.error("[chat-agent] docs access lookup failed:", err.message);
|
|
9862
|
-
return "none";
|
|
9870
|
+
return { access: "none", unverified: true };
|
|
9863
9871
|
}
|
|
9864
9872
|
}
|
|
9865
9873
|
async tryAsk(channelConfig, prompt, sessionId, onActivity, signal, usageContext) {
|
|
@@ -9886,6 +9894,7 @@ ${msg.body}` : msg.body;
|
|
|
9886
9894
|
mcpConfigPath,
|
|
9887
9895
|
...usageContext ? { usageContext } : {},
|
|
9888
9896
|
...channelConfig.docsAccess ? { docsAccess: channelConfig.docsAccess } : {},
|
|
9897
|
+
...channelConfig.docsAccessUnverified ? { docsAccessUnverified: true } : {},
|
|
9889
9898
|
...channelConfig.memoryPrompt ? { memoryPrompt: channelConfig.memoryPrompt } : {},
|
|
9890
9899
|
...channelConfig.disableAutoMemory ? { disableAutoMemory: true } : {}
|
|
9891
9900
|
},
|
|
@@ -9925,7 +9934,7 @@ ${msg.body}` : msg.body;
|
|
|
9925
9934
|
resolveSelectedOutputStyle(channelConfig.claudeConfigDir)?.body
|
|
9926
9935
|
),
|
|
9927
9936
|
channelConfig.memoryPrompt,
|
|
9928
|
-
docsAccessPromptNote(channelConfig.docsAccess)
|
|
9937
|
+
docsAccessPromptNote(channelConfig.docsAccess, channelConfig.docsAccessUnverified)
|
|
9929
9938
|
].filter(Boolean).join("\n\n---\n\n"),
|
|
9930
9939
|
// Codex has no per-tool deny list, so any restricted requester gets
|
|
9931
9940
|
// its read-only sandbox (no file writes). It can still read: see the
|
|
@@ -10202,7 +10211,7 @@ async function exportDocAsArtifact(channelId, docPath) {
|
|
|
10202
10211
|
);
|
|
10203
10212
|
const abs = resolveDocPath(channelConfig.cwd, docPath);
|
|
10204
10213
|
if (!abs) throw new Error(`doc not found: ${docPath}`);
|
|
10205
|
-
const content =
|
|
10214
|
+
const content = readFileSync11(abs, "utf8");
|
|
10206
10215
|
const title = extractTitle(abs, "md");
|
|
10207
10216
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10208
10217
|
const prompt = `Publish the document at \`${docPath}\` (already read below) as a Claude Artifact, then record the result as a stub file under this channel's \`docs/artifacts/\` folder (create the folder if it doesn't exist).
|
|
@@ -10431,7 +10440,7 @@ function extractTitle(absPath, kind) {
|
|
|
10431
10440
|
}
|
|
10432
10441
|
function resolveDirPath(root, rel) {
|
|
10433
10442
|
if (rel.includes("\0")) return null;
|
|
10434
|
-
const abs =
|
|
10443
|
+
const abs = resolve4(root, rel);
|
|
10435
10444
|
if (abs !== root && !abs.startsWith(root + sep)) return null;
|
|
10436
10445
|
return abs;
|
|
10437
10446
|
}
|
|
@@ -10481,12 +10490,12 @@ function readGitBranch(dir) {
|
|
|
10481
10490
|
try {
|
|
10482
10491
|
let gitDir = join18(dir, ".git");
|
|
10483
10492
|
if (statSync5(gitDir).isFile()) {
|
|
10484
|
-
const pointer =
|
|
10493
|
+
const pointer = readFileSync12(gitDir, "utf8").trim();
|
|
10485
10494
|
const match = pointer.match(/^gitdir:\s*(.+)$/);
|
|
10486
10495
|
if (!match) return void 0;
|
|
10487
|
-
gitDir =
|
|
10496
|
+
gitDir = resolve4(dir, match[1]);
|
|
10488
10497
|
}
|
|
10489
|
-
const head =
|
|
10498
|
+
const head = readFileSync12(join18(gitDir, "HEAD"), "utf8").trim();
|
|
10490
10499
|
const branchMatch = head.match(/^ref:\s*refs\/heads\/(.+)$/);
|
|
10491
10500
|
if (branchMatch) return branchMatch[1];
|
|
10492
10501
|
return /^[0-9a-f]{7,40}$/i.test(head) ? head.slice(0, 7) : void 0;
|
|
@@ -10515,7 +10524,7 @@ function listDocLevel(root, relDir = "", sort = "newest", showHidden = false) {
|
|
|
10515
10524
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
10516
10525
|
const probe3 = probeDir(abs, showHidden);
|
|
10517
10526
|
if (!probe3.hasDocs && probe3.hasOther) continue;
|
|
10518
|
-
const isRepo =
|
|
10527
|
+
const isRepo = existsSync16(join18(abs, ".git"));
|
|
10519
10528
|
dirs.push({
|
|
10520
10529
|
path,
|
|
10521
10530
|
name: entry.name,
|
|
@@ -10571,7 +10580,7 @@ function flatDocs(root, {
|
|
|
10571
10580
|
}
|
|
10572
10581
|
function resolveDocPath(root, rel) {
|
|
10573
10582
|
if (!rel || rel.includes("\0")) return null;
|
|
10574
|
-
const abs =
|
|
10583
|
+
const abs = resolve4(root, rel);
|
|
10575
10584
|
if (abs !== root && !abs.startsWith(root + sep)) return null;
|
|
10576
10585
|
const ext = extname(abs).toLowerCase();
|
|
10577
10586
|
if (!(ext in DOC_EXTENSIONS) && !(ext in CODE_EXTENSIONS)) return null;
|
|
@@ -10593,12 +10602,12 @@ function writeDocContent(root, rel, content, baseMtime) {
|
|
|
10593
10602
|
if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) {
|
|
10594
10603
|
return { status: "too-large" };
|
|
10595
10604
|
}
|
|
10596
|
-
|
|
10605
|
+
writeFileSync8(abs, content, "utf8");
|
|
10597
10606
|
return { status: "ok", mtime: statSync5(abs).mtime.toISOString() };
|
|
10598
10607
|
}
|
|
10599
10608
|
function resolveRawPath(root, rel) {
|
|
10600
10609
|
if (!rel || rel.includes("\0")) return null;
|
|
10601
|
-
const abs =
|
|
10610
|
+
const abs = resolve4(root, rel);
|
|
10602
10611
|
if (abs !== root && !abs.startsWith(root + sep)) return null;
|
|
10603
10612
|
const contentType = MEDIA_EXTENSIONS[extname(abs).toLowerCase()];
|
|
10604
10613
|
if (!contentType) return null;
|
|
@@ -10606,14 +10615,14 @@ function resolveRawPath(root, rel) {
|
|
|
10606
10615
|
}
|
|
10607
10616
|
function resolveHtmlPath(root, rel) {
|
|
10608
10617
|
if (!rel || rel.includes("\0")) return null;
|
|
10609
|
-
const abs =
|
|
10618
|
+
const abs = resolve4(root, rel);
|
|
10610
10619
|
if (abs !== root && !abs.startsWith(root + sep)) return null;
|
|
10611
10620
|
if (DOC_EXTENSIONS[extname(abs).toLowerCase()] !== "html") return null;
|
|
10612
10621
|
return abs;
|
|
10613
10622
|
}
|
|
10614
10623
|
function resolveDownloadPath(root, rel) {
|
|
10615
10624
|
if (!rel || rel.includes("\0")) return null;
|
|
10616
|
-
const abs =
|
|
10625
|
+
const abs = resolve4(root, rel);
|
|
10617
10626
|
if (abs !== root && !abs.startsWith(root + sep)) return null;
|
|
10618
10627
|
if (!isListableFile(basename6(abs))) return null;
|
|
10619
10628
|
const contentType = MEDIA_EXTENSIONS[extname(abs).toLowerCase()] ?? "application/octet-stream";
|
|
@@ -10654,7 +10663,7 @@ function saveUploadedFile(root, relDir, name, data) {
|
|
|
10654
10663
|
if (data.byteLength > MAX_UPLOAD_BYTES) return { status: "too-large" };
|
|
10655
10664
|
const final = uniqueName(absDir, safe);
|
|
10656
10665
|
const abs = join18(absDir, final);
|
|
10657
|
-
|
|
10666
|
+
writeFileSync8(abs, data);
|
|
10658
10667
|
return { status: "ok", path: relative(root, abs).split(sep).join("/"), name: final };
|
|
10659
10668
|
}
|
|
10660
10669
|
function createDocFolder(root, relDir, name) {
|
|
@@ -10692,7 +10701,7 @@ function createDocFile(root, relDir, name, content) {
|
|
|
10692
10701
|
if (Buffer.byteLength(content, "utf8") > MAX_CONTENT_BYTES) return { status: "too-large" };
|
|
10693
10702
|
const abs = join18(absDir, safe);
|
|
10694
10703
|
try {
|
|
10695
|
-
|
|
10704
|
+
writeFileSync8(abs, content, { encoding: "utf8", flag: "wx" });
|
|
10696
10705
|
} catch (err) {
|
|
10697
10706
|
if (err.code === "EEXIST") return { status: "exists" };
|
|
10698
10707
|
throw err;
|
|
@@ -10701,7 +10710,7 @@ function createDocFile(root, relDir, name, content) {
|
|
|
10701
10710
|
}
|
|
10702
10711
|
function moveDocEntry(root, from, to) {
|
|
10703
10712
|
if (!from || !to || from.includes("\0") || to.includes("\0")) return { status: "not-found" };
|
|
10704
|
-
const absFrom =
|
|
10713
|
+
const absFrom = resolve4(root, from);
|
|
10705
10714
|
if (!absFrom.startsWith(root + sep)) return { status: "not-found" };
|
|
10706
10715
|
let stat;
|
|
10707
10716
|
try {
|
|
@@ -10711,7 +10720,7 @@ function moveDocEntry(root, from, to) {
|
|
|
10711
10720
|
}
|
|
10712
10721
|
const isDir2 = stat.isDirectory();
|
|
10713
10722
|
if (!isDir2 && !stat.isFile()) return { status: "not-found" };
|
|
10714
|
-
const absTo =
|
|
10723
|
+
const absTo = resolve4(root, to);
|
|
10715
10724
|
if (!absTo.startsWith(root + sep)) return { status: "bad-target" };
|
|
10716
10725
|
const name = sanitizeUploadName(basename6(absTo));
|
|
10717
10726
|
if (!name) return { status: "bad-target" };
|
|
@@ -10741,7 +10750,7 @@ function moveDocEntry(root, from, to) {
|
|
|
10741
10750
|
}
|
|
10742
10751
|
function deleteDocEntry(root, path) {
|
|
10743
10752
|
if (!path || path.includes("\0")) return { status: "not-found" };
|
|
10744
|
-
const abs =
|
|
10753
|
+
const abs = resolve4(root, path);
|
|
10745
10754
|
if (!abs.startsWith(root + sep)) return { status: "not-found" };
|
|
10746
10755
|
let stat;
|
|
10747
10756
|
try {
|
|
@@ -10751,17 +10760,17 @@ function deleteDocEntry(root, path) {
|
|
|
10751
10760
|
}
|
|
10752
10761
|
const isDir2 = stat.isDirectory();
|
|
10753
10762
|
if (!isDir2 && !stat.isFile() && !stat.isSymbolicLink()) return { status: "not-found" };
|
|
10754
|
-
|
|
10763
|
+
rmSync6(abs, { recursive: isDir2 });
|
|
10755
10764
|
return {
|
|
10756
10765
|
status: "ok",
|
|
10757
10766
|
path: relative(root, abs).split(sep).join("/"),
|
|
10758
10767
|
type: isDir2 ? "dir" : "file"
|
|
10759
10768
|
};
|
|
10760
10769
|
}
|
|
10761
|
-
function createDocsRouter() {
|
|
10770
|
+
function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
10762
10771
|
const app = new Hono10();
|
|
10763
|
-
app.use("/:id/docs",
|
|
10764
|
-
app.use("/:id/docs/*",
|
|
10772
|
+
app.use("/:id/docs", gate);
|
|
10773
|
+
app.use("/:id/docs/*", gate);
|
|
10765
10774
|
app.get("/:id/docs", (c) => {
|
|
10766
10775
|
const { cwd } = resolveChannelConfig(c.req.param("id"));
|
|
10767
10776
|
const dir = c.req.query("path") ?? "";
|
|
@@ -10790,7 +10799,7 @@ function createDocsRouter() {
|
|
|
10790
10799
|
}
|
|
10791
10800
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
10792
10801
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
10793
|
-
const data =
|
|
10802
|
+
const data = readFileSync12(resolved2.abs);
|
|
10794
10803
|
const headers = { "Content-Type": resolved2.contentType };
|
|
10795
10804
|
if (download) {
|
|
10796
10805
|
const filename = basename6(resolved2.abs).replace(/"/g, "");
|
|
@@ -10810,7 +10819,7 @@ function createDocsRouter() {
|
|
|
10810
10819
|
}
|
|
10811
10820
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
10812
10821
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
10813
|
-
return c.body(new Uint8Array(
|
|
10822
|
+
return c.body(new Uint8Array(readFileSync12(abs)), 200, {
|
|
10814
10823
|
"Content-Type": "text/html; charset=utf-8",
|
|
10815
10824
|
"Content-Security-Policy": HTML_SHARE_CSP,
|
|
10816
10825
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -10995,7 +11004,7 @@ function createDocsRouter() {
|
|
|
10995
11004
|
title: kind ? extractTitle(abs, kind) : basename6(abs),
|
|
10996
11005
|
mtime: stat.mtime.toISOString(),
|
|
10997
11006
|
tooLarge: tooLarge || void 0,
|
|
10998
|
-
content: tooLarge ? "" :
|
|
11007
|
+
content: tooLarge ? "" : readFileSync12(abs, "utf8")
|
|
10999
11008
|
});
|
|
11000
11009
|
});
|
|
11001
11010
|
app.put("/:id/docs/content", async (c) => {
|
|
@@ -11038,7 +11047,7 @@ function createDocsRouter() {
|
|
|
11038
11047
|
channel,
|
|
11039
11048
|
rel,
|
|
11040
11049
|
title: extractTitle(abs, "md"),
|
|
11041
|
-
content:
|
|
11050
|
+
content: readFileSync12(abs, "utf8")
|
|
11042
11051
|
};
|
|
11043
11052
|
};
|
|
11044
11053
|
app.get("/:id/docs/translation/:lang", async (c) => {
|
|
@@ -11234,13 +11243,13 @@ function relay(agent, pathOf) {
|
|
|
11234
11243
|
}
|
|
11235
11244
|
|
|
11236
11245
|
// src/dashboard/skills-routes.ts
|
|
11237
|
-
import { existsSync as
|
|
11246
|
+
import { existsSync as existsSync18, statSync as statSync7 } from "fs";
|
|
11238
11247
|
import { dirname as dirname6 } from "path";
|
|
11239
11248
|
import { Hono as Hono12 } from "hono";
|
|
11240
11249
|
|
|
11241
11250
|
// src/dashboard/skill-mirror.ts
|
|
11242
11251
|
import { createHash as createHash8 } from "crypto";
|
|
11243
|
-
import { existsSync as
|
|
11252
|
+
import { existsSync as existsSync17, statSync as statSync6 } from "fs";
|
|
11244
11253
|
var SYNC_INTERVAL_MS = 6e4;
|
|
11245
11254
|
var UPSERT_BATCH = 50;
|
|
11246
11255
|
function mirrorScopeOf(ref) {
|
|
@@ -11248,7 +11257,7 @@ function mirrorScopeOf(ref) {
|
|
|
11248
11257
|
}
|
|
11249
11258
|
function isDir(path) {
|
|
11250
11259
|
try {
|
|
11251
|
-
return
|
|
11260
|
+
return existsSync17(path) && statSync6(path).isDirectory();
|
|
11252
11261
|
} catch {
|
|
11253
11262
|
return false;
|
|
11254
11263
|
}
|
|
@@ -11389,7 +11398,7 @@ function requireAdmin2(auth) {
|
|
|
11389
11398
|
}
|
|
11390
11399
|
function rootExists(dir) {
|
|
11391
11400
|
try {
|
|
11392
|
-
return
|
|
11401
|
+
return existsSync18(dir) && statSync7(dir).isDirectory();
|
|
11393
11402
|
} catch {
|
|
11394
11403
|
return false;
|
|
11395
11404
|
}
|
|
@@ -11578,9 +11587,9 @@ function registerMirroredSkillRoutes(api, agent) {
|
|
|
11578
11587
|
|
|
11579
11588
|
// src/dashboard/providers.ts
|
|
11580
11589
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11581
|
-
import { existsSync as
|
|
11590
|
+
import { existsSync as existsSync19 } from "fs";
|
|
11582
11591
|
import { homedir as homedir8 } from "os";
|
|
11583
|
-
import { join as join19, resolve as
|
|
11592
|
+
import { join as join19, resolve as resolve5 } from "path";
|
|
11584
11593
|
var PROVIDER_LABELS = {
|
|
11585
11594
|
claude: "Claude Code",
|
|
11586
11595
|
codex: "Codex"
|
|
@@ -11597,8 +11606,8 @@ function binaryOnPath(bin) {
|
|
|
11597
11606
|
}
|
|
11598
11607
|
function claudeAuthAvailable() {
|
|
11599
11608
|
if (profileEnv("CLAUDE_CODE_OAUTH_TOKEN")?.trim()) return true;
|
|
11600
|
-
const configDir2 = profileClaudeConfigDir() ||
|
|
11601
|
-
if (
|
|
11609
|
+
const configDir2 = profileClaudeConfigDir() || resolve5(homedir8(), ".claude");
|
|
11610
|
+
if (existsSync19(join19(configDir2, ".credentials.json"))) return true;
|
|
11602
11611
|
if (process.platform !== "darwin") return false;
|
|
11603
11612
|
try {
|
|
11604
11613
|
execFileSync2("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
|
|
@@ -11626,13 +11635,23 @@ function probe2(runner) {
|
|
|
11626
11635
|
}
|
|
11627
11636
|
return { runner, label: label2, connected: true };
|
|
11628
11637
|
}
|
|
11638
|
+
function withAuthFailure(status) {
|
|
11639
|
+
if (!claudeAuthFailed()) return status;
|
|
11640
|
+
return {
|
|
11641
|
+
...status,
|
|
11642
|
+
connected: false,
|
|
11643
|
+
reason: `Sign-in expired and could not be refreshed. On the Host, run claude setup-token, add the token to ~/.config/flyspace/profiles/${getProfile()}/config.env as CLAUDE_CODE_OAUTH_TOKEN, then restart the Host.`
|
|
11644
|
+
};
|
|
11645
|
+
}
|
|
11629
11646
|
function providerStatuses() {
|
|
11630
11647
|
const key = getProfile();
|
|
11631
11648
|
const hit = cache6.get(key);
|
|
11632
|
-
|
|
11633
|
-
|
|
11634
|
-
|
|
11635
|
-
|
|
11649
|
+
let statuses = hit && Date.now() - hit.at < TTL_MS2 ? hit.statuses : void 0;
|
|
11650
|
+
if (!statuses) {
|
|
11651
|
+
statuses = ["claude", "codex"].map(probe2);
|
|
11652
|
+
cache6.set(key, { at: Date.now(), statuses });
|
|
11653
|
+
}
|
|
11654
|
+
return statuses.map((st) => st.runner === "claude" && st.connected ? withAuthFailure(st) : st);
|
|
11636
11655
|
}
|
|
11637
11656
|
|
|
11638
11657
|
// src/dashboard/host-api.ts
|
|
@@ -11742,7 +11761,7 @@ function registerHostRoutes(api) {
|
|
|
11742
11761
|
const limit = Math.min(Number(c.req.query("limit")) || 200, 2e3);
|
|
11743
11762
|
const filename = errors ? "custie-error.log" : "custie.log";
|
|
11744
11763
|
const file = join20(paths.LOG_DIR, filename);
|
|
11745
|
-
const exists =
|
|
11764
|
+
const exists = existsSync20(file);
|
|
11746
11765
|
return c.json({
|
|
11747
11766
|
file,
|
|
11748
11767
|
exists,
|
|
@@ -11752,14 +11771,14 @@ function registerHostRoutes(api) {
|
|
|
11752
11771
|
});
|
|
11753
11772
|
}
|
|
11754
11773
|
function tailFile(file, limit) {
|
|
11755
|
-
if (!
|
|
11756
|
-
const lines =
|
|
11774
|
+
if (!existsSync20(file)) return [];
|
|
11775
|
+
const lines = readFileSync13(file, "utf-8").split("\n");
|
|
11757
11776
|
if (lines.at(-1) === "") lines.pop();
|
|
11758
11777
|
return lines.slice(-limit);
|
|
11759
11778
|
}
|
|
11760
11779
|
|
|
11761
11780
|
// src/dashboard/data-api.ts
|
|
11762
|
-
import { existsSync as
|
|
11781
|
+
import { existsSync as existsSync22 } from "fs";
|
|
11763
11782
|
import sharp5 from "sharp";
|
|
11764
11783
|
|
|
11765
11784
|
// src/dashboard/automations-routes.ts
|
|
@@ -13533,7 +13552,7 @@ function createTranscribeRouter() {
|
|
|
13533
13552
|
}
|
|
13534
13553
|
|
|
13535
13554
|
// src/dashboard/avatars-routes.ts
|
|
13536
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
13555
|
+
import { createHash as createHash10, randomUUID as randomUUID2 } from "crypto";
|
|
13537
13556
|
import { Hono as Hono20 } from "hono";
|
|
13538
13557
|
import sharp4 from "sharp";
|
|
13539
13558
|
import { PutObjectCommand as PutObjectCommand4, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
|
|
@@ -13541,6 +13560,16 @@ var AVATAR_SIZE = 192;
|
|
|
13541
13560
|
var MAX_AVATAR_BYTES = 2 * 1024 * 1024;
|
|
13542
13561
|
var AVATAR_MIME_TYPES = ["image/png", "image/jpeg"];
|
|
13543
13562
|
var KEY_PREFIX3 = "avatar";
|
|
13563
|
+
var GRAVATAR_TIMEOUT_MS = 5e3;
|
|
13564
|
+
async function fetchGravatar(email) {
|
|
13565
|
+
const hash = createHash10("sha256").update(email.trim().toLowerCase()).digest("hex");
|
|
13566
|
+
const res = await fetch(`https://gravatar.com/avatar/${hash}?s=${AVATAR_SIZE}&d=404`, {
|
|
13567
|
+
signal: AbortSignal.timeout(GRAVATAR_TIMEOUT_MS)
|
|
13568
|
+
}).catch(() => null);
|
|
13569
|
+
if (!res?.ok) return null;
|
|
13570
|
+
const type = res.headers.get("content-type")?.split(";")[0] ?? "";
|
|
13571
|
+
return new File([await res.arrayBuffer()], "gravatar", { type });
|
|
13572
|
+
}
|
|
13544
13573
|
async function saveAvatarKey(userId, avatarKey) {
|
|
13545
13574
|
const { data, error: getError } = await getSupabaseClient().auth.admin.getUserById(userId);
|
|
13546
13575
|
if (getError || !data.user) {
|
|
@@ -13602,6 +13631,17 @@ function createAvatarsRouter() {
|
|
|
13602
13631
|
}
|
|
13603
13632
|
return c.json({ avatarUrl: await signAvatarUrl(key) }, 201);
|
|
13604
13633
|
});
|
|
13634
|
+
app.post("/avatars/gravatar", async (c) => {
|
|
13635
|
+
const ctx = await resolveAuthContext(c);
|
|
13636
|
+
if (!ctx) return c.json({ error: "not signed in" }, 401);
|
|
13637
|
+
if (ctx.avatarKey) return c.json({ avatarUrl: await signAvatarUrl(ctx.avatarKey) });
|
|
13638
|
+
const file = await fetchGravatar(ctx.email);
|
|
13639
|
+
if (!file) return c.json({ avatarUrl: null });
|
|
13640
|
+
const stored = await storeAvatar(file, ctx.userId);
|
|
13641
|
+
if ("error" in stored) return c.json({ avatarUrl: null });
|
|
13642
|
+
await saveAvatarKey(ctx.userId, stored.key);
|
|
13643
|
+
return c.json({ avatarUrl: await signAvatarUrl(stored.key) }, 201);
|
|
13644
|
+
});
|
|
13605
13645
|
app.delete("/avatars", async (c) => {
|
|
13606
13646
|
const ctx = await resolveAuthContext(c);
|
|
13607
13647
|
if (!ctx) return c.json({ error: "not signed in" }, 401);
|
|
@@ -14606,7 +14646,7 @@ async function sessionUsageForThreads(workspace, threadTsList) {
|
|
|
14606
14646
|
}
|
|
14607
14647
|
|
|
14608
14648
|
// src/dashboard/usage-report.ts
|
|
14609
|
-
import { existsSync as
|
|
14649
|
+
import { existsSync as existsSync21 } from "fs";
|
|
14610
14650
|
function mergeByLabel(buckets) {
|
|
14611
14651
|
const merged = /* @__PURE__ */ new Map();
|
|
14612
14652
|
for (const bucket of buckets) {
|
|
@@ -14652,7 +14692,7 @@ function toReport(days, rows, labelChannel) {
|
|
|
14652
14692
|
};
|
|
14653
14693
|
}
|
|
14654
14694
|
function fromLocalStore(days, labelChannel) {
|
|
14655
|
-
if (!
|
|
14695
|
+
if (!existsSync21(paths.DB_FILE)) return { ...EMPTY, days };
|
|
14656
14696
|
const window = days === 0 ? void 0 : days;
|
|
14657
14697
|
const store = new UsageStore(paths.DB_FILE);
|
|
14658
14698
|
try {
|
|
@@ -14694,7 +14734,7 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
|
|
|
14694
14734
|
const { totals, byDay } = await channelUsageForWorkspace(workspace, aliasIds, window);
|
|
14695
14735
|
return { days, totals: totals.runs > 0 ? totals : null, byDay };
|
|
14696
14736
|
} catch {
|
|
14697
|
-
if (!
|
|
14737
|
+
if (!existsSync21(paths.DB_FILE)) return { days, totals: null, byDay: [] };
|
|
14698
14738
|
const store = new UsageStore(paths.DB_FILE);
|
|
14699
14739
|
try {
|
|
14700
14740
|
const targetLabel = labelChannel(channelId);
|
|
@@ -14716,7 +14756,7 @@ async function buildThreadUsage(threadTs) {
|
|
|
14716
14756
|
if (totals.runs === 0) return { threadTs, totals: null, byModel: [] };
|
|
14717
14757
|
return { threadTs, totals, byModel };
|
|
14718
14758
|
} catch {
|
|
14719
|
-
if (!
|
|
14759
|
+
if (!existsSync21(paths.DB_FILE)) return { threadTs, totals: null, byModel: [] };
|
|
14720
14760
|
const store = new UsageStore(paths.DB_FILE);
|
|
14721
14761
|
try {
|
|
14722
14762
|
const totals = store.threadTotals(threadTs);
|
|
@@ -15076,10 +15116,10 @@ function createOnboardingRouter(agent) {
|
|
|
15076
15116
|
var HOST_STATUS_CHANNEL = "host-status";
|
|
15077
15117
|
var channelReady = null;
|
|
15078
15118
|
function getHostStatusChannel() {
|
|
15079
|
-
channelReady ??= new Promise((
|
|
15119
|
+
channelReady ??= new Promise((resolve7) => {
|
|
15080
15120
|
const channel = getSupabaseClient().channel(HOST_STATUS_CHANNEL);
|
|
15081
15121
|
channel.subscribe((state) => {
|
|
15082
|
-
if (state === "SUBSCRIBED")
|
|
15122
|
+
if (state === "SUBSCRIBED") resolve7(channel);
|
|
15083
15123
|
});
|
|
15084
15124
|
});
|
|
15085
15125
|
return channelReady;
|
|
@@ -15245,7 +15285,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15245
15285
|
runs.set(name, { lastRun: run3.lastRunAt.toISOString(), lastStatus: run3.lastStatus });
|
|
15246
15286
|
}
|
|
15247
15287
|
} catch {
|
|
15248
|
-
if (
|
|
15288
|
+
if (existsSync22(paths.DB_FILE)) {
|
|
15249
15289
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
15250
15290
|
try {
|
|
15251
15291
|
for (const run3 of runStore.listLastRuns()) {
|
|
@@ -15307,7 +15347,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15307
15347
|
const runs = (await recentRunsForWorkspace(getProfile(), 200)).map(toResponse5);
|
|
15308
15348
|
return c.json({ runs });
|
|
15309
15349
|
} catch {
|
|
15310
|
-
if (!
|
|
15350
|
+
if (!existsSync22(paths.DB_FILE)) return c.json({ runs: [] });
|
|
15311
15351
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
15312
15352
|
try {
|
|
15313
15353
|
const runs = runStore.listRuns(200).map(toResponse5);
|
|
@@ -15429,7 +15469,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15429
15469
|
} catch {
|
|
15430
15470
|
latest = /* @__PURE__ */ new Map();
|
|
15431
15471
|
totals = /* @__PURE__ */ new Map();
|
|
15432
|
-
if (
|
|
15472
|
+
if (existsSync22(paths.DB_FILE)) {
|
|
15433
15473
|
const usageStore = new UsageStore(paths.DB_FILE);
|
|
15434
15474
|
try {
|
|
15435
15475
|
latest = usageStore.latestByThread(threadTsList);
|
|
@@ -15484,28 +15524,94 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15484
15524
|
// src/dashboard/device-auth-routes.ts
|
|
15485
15525
|
import { verify } from "crypto";
|
|
15486
15526
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
15487
|
-
import { Hono as
|
|
15527
|
+
import { Hono as Hono30 } from "hono";
|
|
15488
15528
|
|
|
15489
15529
|
// src/dashboard/agent-bridge.ts
|
|
15490
15530
|
import { randomBytes as randomBytes8, randomUUID as randomUUID4, sign as signRaw, timingSafeEqual as timingSafeEqual2, verify as verifyRaw } from "crypto";
|
|
15491
|
-
|
|
15531
|
+
|
|
15532
|
+
// src/dashboard/docs-relay.ts
|
|
15533
|
+
import { Hono as Hono29 } from "hono";
|
|
15534
|
+
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
15535
|
+
var FORWARDED_HEADERS = ["content-type", "host", "accept-language"];
|
|
15536
|
+
var RETURNED_HEADERS = ["content-type", "content-disposition", "content-security-policy", "cache-control"];
|
|
15537
|
+
async function handleDocsRequest(req) {
|
|
15538
|
+
const app = new Hono29();
|
|
15539
|
+
app.use("*", async (c, next) => {
|
|
15540
|
+
c.set("auth", req.auth);
|
|
15541
|
+
await next();
|
|
15542
|
+
});
|
|
15543
|
+
app.route("/channels", createDocsRouter({ gate: async (_c, next) => next() }));
|
|
15544
|
+
const res = await app.request(req.path, {
|
|
15545
|
+
method: req.method,
|
|
15546
|
+
headers: req.headers,
|
|
15547
|
+
...req.body !== void 0 && { body: Buffer.from(req.body, "base64") }
|
|
15548
|
+
});
|
|
15549
|
+
const headers = {};
|
|
15550
|
+
for (const name of RETURNED_HEADERS) {
|
|
15551
|
+
const value = res.headers.get(name);
|
|
15552
|
+
if (value) headers[name] = value;
|
|
15553
|
+
}
|
|
15554
|
+
return { status: res.status, headers, body: Buffer.from(await res.arrayBuffer()).toString("base64") };
|
|
15555
|
+
}
|
|
15556
|
+
function registerRelayedDocsRoutes(api, agent) {
|
|
15557
|
+
api.get(
|
|
15558
|
+
"/channels/:id/docs/watch",
|
|
15559
|
+
requireDocsAccess,
|
|
15560
|
+
(c) => streamSSE2(c, async (stream) => {
|
|
15561
|
+
await stream.writeSSE({ event: "unavailable", data: "{}" });
|
|
15562
|
+
})
|
|
15563
|
+
);
|
|
15564
|
+
api.all("/channels/:id/docs", requireDocsAccess, relay2(agent));
|
|
15565
|
+
api.all("/channels/:id/docs/*", requireDocsAccess, relay2(agent));
|
|
15566
|
+
}
|
|
15567
|
+
function relay2(agent) {
|
|
15568
|
+
return async (c) => {
|
|
15569
|
+
const url = new URL(c.req.url);
|
|
15570
|
+
const path = url.pathname.slice(url.pathname.indexOf("/channels/")) + url.search;
|
|
15571
|
+
const headers = {};
|
|
15572
|
+
for (const name of FORWARDED_HEADERS) {
|
|
15573
|
+
const value = c.req.header(name);
|
|
15574
|
+
if (value) headers[name] = value;
|
|
15575
|
+
}
|
|
15576
|
+
const hasBody = c.req.method !== "GET" && c.req.method !== "HEAD";
|
|
15577
|
+
const body = hasBody ? Buffer.from(await c.req.arrayBuffer()).toString("base64") : void 0;
|
|
15578
|
+
const result = await agent.docsRequest?.({
|
|
15579
|
+
method: c.req.method,
|
|
15580
|
+
path,
|
|
15581
|
+
headers,
|
|
15582
|
+
body,
|
|
15583
|
+
auth: c.get("auth")
|
|
15584
|
+
});
|
|
15585
|
+
if (!result) {
|
|
15586
|
+
return c.json({ error: "Docs need the Host machine, which is not connected right now." }, 503);
|
|
15587
|
+
}
|
|
15588
|
+
const empty = c.req.method === "HEAD" || result.status === 204 || result.status === 304;
|
|
15589
|
+
return new Response(empty ? null : Buffer.from(result.body, "base64"), {
|
|
15590
|
+
status: result.status,
|
|
15591
|
+
headers: result.headers
|
|
15592
|
+
});
|
|
15593
|
+
};
|
|
15594
|
+
}
|
|
15595
|
+
|
|
15596
|
+
// src/dashboard/agent-bridge.ts
|
|
15597
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17 } from "fs";
|
|
15492
15598
|
import dotenv from "dotenv";
|
|
15493
15599
|
import { WebSocket, WebSocketServer } from "ws";
|
|
15494
15600
|
|
|
15495
15601
|
// src/dashboard/host-identity.ts
|
|
15496
15602
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
|
|
15497
|
-
import { existsSync as
|
|
15603
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
15498
15604
|
import { dirname as dirname7, join as join21 } from "path";
|
|
15499
15605
|
function hostPrivateKeyPath() {
|
|
15500
15606
|
return process.env["FLYSPACE_HOST_KEY_PATH"] || join21(paths.BASE_CONFIG_DIR, "host.key");
|
|
15501
15607
|
}
|
|
15502
15608
|
function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
15503
|
-
if (!
|
|
15609
|
+
if (!existsSync23(path)) {
|
|
15504
15610
|
throw new Error(
|
|
15505
15611
|
`No Host identity at ${path}. Run \`pnpm run generate-host-key\` on this machine, then set FLYSPACE_HOST_PUBLIC_KEY on the Data process to the printed public key.`
|
|
15506
15612
|
);
|
|
15507
15613
|
}
|
|
15508
|
-
return createPrivateKey2(
|
|
15614
|
+
return createPrivateKey2(readFileSync14(path, "utf8"));
|
|
15509
15615
|
}
|
|
15510
15616
|
function loadHostPublicKey() {
|
|
15511
15617
|
const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
|
|
@@ -15521,7 +15627,7 @@ function loadHostPublicKey() {
|
|
|
15521
15627
|
// src/dashboard/host-lock.ts
|
|
15522
15628
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
15523
15629
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
15524
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
15630
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
15525
15631
|
import { join as join22 } from "path";
|
|
15526
15632
|
function hostLockPath() {
|
|
15527
15633
|
return join22(paths.BASE_DATA_DIR, "host.lock");
|
|
@@ -15539,7 +15645,7 @@ function isLiveFlyspace(pid) {
|
|
|
15539
15645
|
}
|
|
15540
15646
|
function readLockPid(path) {
|
|
15541
15647
|
try {
|
|
15542
|
-
const pid = Number(JSON.parse(
|
|
15648
|
+
const pid = Number(JSON.parse(readFileSync15(path, "utf8")).pid);
|
|
15543
15649
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
15544
15650
|
} catch {
|
|
15545
15651
|
return null;
|
|
@@ -15555,9 +15661,9 @@ function acquireHostLock() {
|
|
|
15555
15661
|
});
|
|
15556
15662
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15557
15663
|
try {
|
|
15558
|
-
|
|
15664
|
+
writeFileSync10(path, body, { flag: "wx", mode: 384 });
|
|
15559
15665
|
const release2 = () => {
|
|
15560
|
-
if (readLockPid(path) === process.pid)
|
|
15666
|
+
if (readLockPid(path) === process.pid) rmSync7(path, { force: true });
|
|
15561
15667
|
};
|
|
15562
15668
|
process.once("exit", release2);
|
|
15563
15669
|
return release2;
|
|
@@ -15571,7 +15677,7 @@ function acquireHostLock() {
|
|
|
15571
15677
|
`Another Flyspace Host is already running on this machine (pid ${holder}). Stop it first, or check for a second CLI install (\`which -a flyspace\`). Two Hosts overwrite each other's skill mirror.`
|
|
15572
15678
|
);
|
|
15573
15679
|
}
|
|
15574
|
-
|
|
15680
|
+
rmSync7(path, { force: true });
|
|
15575
15681
|
}
|
|
15576
15682
|
}
|
|
15577
15683
|
throw new Error(`Could not take the Host lock at ${path}`);
|
|
@@ -15580,13 +15686,13 @@ function publishHostEndpoint(port) {
|
|
|
15580
15686
|
const path = hostLockPath();
|
|
15581
15687
|
if (readLockPid(path) !== process.pid) return null;
|
|
15582
15688
|
const token = randomBytes5(32).toString("hex");
|
|
15583
|
-
const lock = JSON.parse(
|
|
15584
|
-
|
|
15689
|
+
const lock = JSON.parse(readFileSync15(path, "utf8"));
|
|
15690
|
+
writeFileSync10(path, JSON.stringify({ ...lock, port, token }), { mode: 384 });
|
|
15585
15691
|
return token;
|
|
15586
15692
|
}
|
|
15587
15693
|
function readHostEndpoint() {
|
|
15588
15694
|
try {
|
|
15589
|
-
const { pid, port, token } = JSON.parse(
|
|
15695
|
+
const { pid, port, token } = JSON.parse(readFileSync15(hostLockPath(), "utf8"));
|
|
15590
15696
|
if (pid === process.pid || !Number.isInteger(port) || typeof token !== "string") return null;
|
|
15591
15697
|
return { port, token };
|
|
15592
15698
|
} catch {
|
|
@@ -15595,12 +15701,12 @@ function readHostEndpoint() {
|
|
|
15595
15701
|
}
|
|
15596
15702
|
|
|
15597
15703
|
// src/store/device-store.ts
|
|
15598
|
-
import { createHash as
|
|
15704
|
+
import { createHash as createHash11, createPublicKey as createPublicKey3, randomBytes as randomBytes6, randomInt } from "crypto";
|
|
15599
15705
|
var DEVICE_CODE_TTL_MS = 10 * 6e4;
|
|
15600
15706
|
var ACCESS_TOKEN_TTL_MS = 15 * 6e4;
|
|
15601
15707
|
var CHALLENGE_TTL_MS = 2 * 6e4;
|
|
15602
15708
|
var POLL_INTERVAL_S = 5;
|
|
15603
|
-
var sha256 = (s) =>
|
|
15709
|
+
var sha256 = (s) => createHash11("sha256").update(s).digest("hex");
|
|
15604
15710
|
function parseEd25519PublicKey(b64) {
|
|
15605
15711
|
if (typeof b64 !== "string" || b64.length > 200) return null;
|
|
15606
15712
|
try {
|
|
@@ -15826,7 +15932,7 @@ async function userEmail(userId) {
|
|
|
15826
15932
|
|
|
15827
15933
|
// src/dashboard/doc-share-publisher.ts
|
|
15828
15934
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
15829
|
-
import { readFileSync as
|
|
15935
|
+
import { readFileSync as readFileSync16, statSync as statSync9 } from "fs";
|
|
15830
15936
|
import { extname as extname2, posix } from "path";
|
|
15831
15937
|
import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
|
|
15832
15938
|
|
|
@@ -16013,7 +16119,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16013
16119
|
}
|
|
16014
16120
|
if (!stat.isFile()) return { status: "not-found" };
|
|
16015
16121
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
16016
|
-
const source =
|
|
16122
|
+
const source = readFileSync16(abs, "utf8");
|
|
16017
16123
|
const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
|
|
16018
16124
|
if (kind === "excalidraw") {
|
|
16019
16125
|
try {
|
|
@@ -16093,7 +16199,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16093
16199
|
let text = null;
|
|
16094
16200
|
try {
|
|
16095
16201
|
const size = statSync9(docAbs).size;
|
|
16096
|
-
if (size <= budget) text =
|
|
16202
|
+
if (size <= budget) text = readFileSync16(docAbs, "utf8");
|
|
16097
16203
|
} catch {
|
|
16098
16204
|
continue;
|
|
16099
16205
|
}
|
|
@@ -16110,7 +16216,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16110
16216
|
let body;
|
|
16111
16217
|
try {
|
|
16112
16218
|
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
16113
|
-
body =
|
|
16219
|
+
body = readFileSync16(resolved2.abs);
|
|
16114
16220
|
} catch {
|
|
16115
16221
|
continue;
|
|
16116
16222
|
}
|
|
@@ -16201,9 +16307,13 @@ var METHODS = {
|
|
|
16201
16307
|
agent_usage: ["GET", "HEAD", "POST"],
|
|
16202
16308
|
skill_files: ALL,
|
|
16203
16309
|
threads: ["GET", "HEAD", "POST", "PATCH"],
|
|
16204
|
-
thread_replies: ["GET", "HEAD", "POST", "PATCH"]
|
|
16310
|
+
thread_replies: ["GET", "HEAD", "POST", "PATCH"],
|
|
16311
|
+
memberships: ["GET", "HEAD"],
|
|
16312
|
+
channel_members: ["GET", "HEAD"],
|
|
16313
|
+
channel_docs_access: ["GET", "HEAD"]
|
|
16205
16314
|
};
|
|
16206
|
-
var
|
|
16315
|
+
var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members", "channel_docs_access"]);
|
|
16316
|
+
var FORWARDED_HEADERS2 = ["accept", "content-type", "prefer", "range"];
|
|
16207
16317
|
var deny = (message, status = 403) => ({
|
|
16208
16318
|
status,
|
|
16209
16319
|
headers: { "content-type": "application/json" },
|
|
@@ -16245,6 +16355,13 @@ async function ownedChannelIds(forward, slugs) {
|
|
|
16245
16355
|
if (!rows) return null;
|
|
16246
16356
|
return rows.flatMap((r) => [r.id, r.external_channel_id].filter((v) => !!v));
|
|
16247
16357
|
}
|
|
16358
|
+
async function ownedWorkspaceIds(forward, slugs) {
|
|
16359
|
+
const rows = await getJson(
|
|
16360
|
+
forward,
|
|
16361
|
+
`/rest/v1/workspaces?select=id&slug=${encodeURIComponent(inList(slugs))}`
|
|
16362
|
+
);
|
|
16363
|
+
return rows ? rows.map((r) => r.id) : null;
|
|
16364
|
+
}
|
|
16248
16365
|
async function threadsOwned(forward, threadIds, channelIds) {
|
|
16249
16366
|
const wanted = [...new Set(threadIds)];
|
|
16250
16367
|
if (wanted.length === 0 || channelIds.length === 0) return false;
|
|
@@ -16302,10 +16419,16 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
16302
16419
|
}
|
|
16303
16420
|
}
|
|
16304
16421
|
params.append(slugColumn, inList(scope));
|
|
16422
|
+
} else if (table === "memberships") {
|
|
16423
|
+
const workspaceIds = await ownedWorkspaceIds(forward, workspaces);
|
|
16424
|
+
if (!workspaceIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16425
|
+
params.append("workspace_id", inList(workspaceIds));
|
|
16305
16426
|
} else {
|
|
16306
16427
|
const channelIds = await ownedChannelIds(forward, workspaces);
|
|
16307
16428
|
if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16308
|
-
if (table
|
|
16429
|
+
if (CHANNEL_SCOPED.has(table)) {
|
|
16430
|
+
params.append("channel_id", inList(channelIds));
|
|
16431
|
+
} else if (table === "threads") {
|
|
16309
16432
|
if (method === "POST") {
|
|
16310
16433
|
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
16311
16434
|
if (!ok3) return deny("A thread must belong to a channel of the device workspaces.");
|
|
@@ -16326,7 +16449,7 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
16326
16449
|
}
|
|
16327
16450
|
}
|
|
16328
16451
|
const headers = {};
|
|
16329
|
-
for (const name of
|
|
16452
|
+
for (const name of FORWARDED_HEADERS2) {
|
|
16330
16453
|
const value = req.headers[name];
|
|
16331
16454
|
if (value !== void 0) headers[name] = value;
|
|
16332
16455
|
}
|
|
@@ -16390,14 +16513,14 @@ function handleHostDbResult(id, res) {
|
|
|
16390
16513
|
}
|
|
16391
16514
|
function whenConnected() {
|
|
16392
16515
|
if (sender) return Promise.resolve(sender);
|
|
16393
|
-
return new Promise((
|
|
16516
|
+
return new Promise((resolve7, reject) => {
|
|
16394
16517
|
const timer2 = setTimeout(() => {
|
|
16395
16518
|
onSender = onSender.filter((w) => w !== wake);
|
|
16396
16519
|
reject(new Error("Host is not connected to the Data API"));
|
|
16397
16520
|
}, REQUEST_TIMEOUT_MS3);
|
|
16398
16521
|
const wake = () => {
|
|
16399
16522
|
clearTimeout(timer2);
|
|
16400
|
-
|
|
16523
|
+
resolve7(sender);
|
|
16401
16524
|
};
|
|
16402
16525
|
onSender.push(wake);
|
|
16403
16526
|
});
|
|
@@ -16405,12 +16528,12 @@ function whenConnected() {
|
|
|
16405
16528
|
async function request(req) {
|
|
16406
16529
|
const send2 = await whenConnected();
|
|
16407
16530
|
const id = randomUUID3();
|
|
16408
|
-
return new Promise((
|
|
16531
|
+
return new Promise((resolve7, reject) => {
|
|
16409
16532
|
const timer2 = setTimeout(() => {
|
|
16410
16533
|
pending.delete(id);
|
|
16411
16534
|
reject(new Error("Data API did not answer in time"));
|
|
16412
16535
|
}, REQUEST_TIMEOUT_MS3);
|
|
16413
|
-
pending.set(id, { resolve:
|
|
16536
|
+
pending.set(id, { resolve: resolve7, reject, timer: timer2 });
|
|
16414
16537
|
send2({ type: "db", id, req });
|
|
16415
16538
|
});
|
|
16416
16539
|
}
|
|
@@ -16470,7 +16593,7 @@ function dataSocketUrl() {
|
|
|
16470
16593
|
return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
|
|
16471
16594
|
}
|
|
16472
16595
|
function listLocalSessions() {
|
|
16473
|
-
if (!
|
|
16596
|
+
if (!existsSync24(paths.DB_FILE)) return [];
|
|
16474
16597
|
const store = new SessionStore(paths.DB_FILE);
|
|
16475
16598
|
try {
|
|
16476
16599
|
return store.listSessions();
|
|
@@ -16496,6 +16619,7 @@ function localAgentCallbacks(chatAgent) {
|
|
|
16496
16619
|
saveLocalDashboardSettings: (patch) => saveDashboardSettings(patch),
|
|
16497
16620
|
writeSkill: (ref, write) => writeSkillAndSync(ref, write),
|
|
16498
16621
|
outputStylesRequest: (req) => handleOutputStylesRequest(req),
|
|
16622
|
+
docsRequest: (req) => handleDocsRequest(req),
|
|
16499
16623
|
listSessions: () => listLocalSessions(),
|
|
16500
16624
|
scanOnboarding: () => scanOnboarding(),
|
|
16501
16625
|
linkProjectFolders: (slug, items) => linkProjectFolders(slug, items),
|
|
@@ -16507,7 +16631,7 @@ function buildHostConfigSnapshot() {
|
|
|
16507
16631
|
const snapshot3 = {};
|
|
16508
16632
|
for (const profile of listProfiles()) {
|
|
16509
16633
|
snapshot3[profile] = runWithProfile(profile, () => ({
|
|
16510
|
-
env:
|
|
16634
|
+
env: existsSync24(paths.CONFIG_FILE) ? dotenv.parse(readFileSync17(paths.CONFIG_FILE, "utf-8")) : {},
|
|
16511
16635
|
agentModel: loadDashboardSettings().agent
|
|
16512
16636
|
}));
|
|
16513
16637
|
}
|
|
@@ -16519,6 +16643,7 @@ function startLocalProfileConfigHydration() {
|
|
|
16519
16643
|
return () => clearInterval(timer2);
|
|
16520
16644
|
}
|
|
16521
16645
|
var CALL_TIMEOUT_MS = 15e3;
|
|
16646
|
+
var DOCS_CALL_TIMEOUT_MS = 12e4;
|
|
16522
16647
|
var AUTH_TIMEOUT_MS = 5e3;
|
|
16523
16648
|
var HEARTBEAT_INTERVAL_MS = 15e3;
|
|
16524
16649
|
var HEARTBEAT_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 2.5;
|
|
@@ -16708,7 +16833,7 @@ function attachHostSocket(server) {
|
|
|
16708
16833
|
});
|
|
16709
16834
|
return wss;
|
|
16710
16835
|
}
|
|
16711
|
-
function callHost(method, args) {
|
|
16836
|
+
function callHost(method, args, timeoutMs = CALL_TIMEOUT_MS) {
|
|
16712
16837
|
const profile = getProfile();
|
|
16713
16838
|
const ws = hostFor(profile);
|
|
16714
16839
|
if (!ws) {
|
|
@@ -16718,16 +16843,16 @@ function callHost(method, args) {
|
|
|
16718
16843
|
return Promise.resolve(null);
|
|
16719
16844
|
}
|
|
16720
16845
|
const id = randomUUID4();
|
|
16721
|
-
return new Promise((
|
|
16846
|
+
return new Promise((resolve7) => {
|
|
16722
16847
|
const timer2 = setTimeout(() => {
|
|
16723
16848
|
pending2.delete(id);
|
|
16724
16849
|
console.error(`[host-socket] ${method} -> timed out`);
|
|
16725
|
-
|
|
16726
|
-
},
|
|
16850
|
+
resolve7(null);
|
|
16851
|
+
}, timeoutMs);
|
|
16727
16852
|
pending2.set(id, {
|
|
16728
16853
|
resolve: (value) => {
|
|
16729
16854
|
clearTimeout(timer2);
|
|
16730
|
-
|
|
16855
|
+
resolve7(value);
|
|
16731
16856
|
}
|
|
16732
16857
|
});
|
|
16733
16858
|
send(ws, { type: "call", id, method, profile, args });
|
|
@@ -16778,6 +16903,9 @@ function remoteAgentCallbacks() {
|
|
|
16778
16903
|
writeSkill: (ref, write) => callHost("writeSkill", [ref, write]),
|
|
16779
16904
|
// Same posture as writeSkill: `null` becomes a 503.
|
|
16780
16905
|
outputStylesRequest: (req) => callHost("outputStylesRequest", [req]),
|
|
16906
|
+
// Same posture again. Longer timeout: a first translation of a doc runs a
|
|
16907
|
+
// model call on the Host before it answers.
|
|
16908
|
+
docsRequest: (req) => callHost("docsRequest", [req], DOCS_CALL_TIMEOUT_MS),
|
|
16781
16909
|
// `null` (no Host connected) is passed through rather than defaulting to
|
|
16782
16910
|
// `[]` — see listSessions's doc comment on AgentCallbacks.
|
|
16783
16911
|
listSessions: () => callHost("listSessions", []),
|
|
@@ -16915,7 +17043,7 @@ var RELAYED_METHODS = [
|
|
|
16915
17043
|
"listAgentRuns"
|
|
16916
17044
|
];
|
|
16917
17045
|
function relayedAgentCallbacks(local) {
|
|
16918
|
-
const
|
|
17046
|
+
const relay3 = (method) => async (...args) => {
|
|
16919
17047
|
const endpoint = readHostEndpoint();
|
|
16920
17048
|
if (endpoint) {
|
|
16921
17049
|
try {
|
|
@@ -16935,7 +17063,7 @@ function relayedAgentCallbacks(local) {
|
|
|
16935
17063
|
};
|
|
16936
17064
|
return {
|
|
16937
17065
|
...local,
|
|
16938
|
-
...Object.fromEntries(RELAYED_METHODS.map((method) => [method,
|
|
17066
|
+
...Object.fromEntries(RELAYED_METHODS.map((method) => [method, relay3(method)]))
|
|
16939
17067
|
};
|
|
16940
17068
|
}
|
|
16941
17069
|
async function handleLocalRelay(req, callbacks, token) {
|
|
@@ -17038,7 +17166,7 @@ async function boundWorkspaceIds(publicKey, userId) {
|
|
|
17038
17166
|
return new Set(existing ? (await listDeviceWorkspaces(existing)).map((w) => w.id) : []);
|
|
17039
17167
|
}
|
|
17040
17168
|
function createDeviceAuthRouter() {
|
|
17041
|
-
const app = new
|
|
17169
|
+
const app = new Hono30();
|
|
17042
17170
|
app.post("/code", async (c) => {
|
|
17043
17171
|
const body = await readBody(c);
|
|
17044
17172
|
const publicKey = typeof body["public_key"] === "string" ? body["public_key"] : "";
|
|
@@ -17202,7 +17330,7 @@ function createDeviceAuthRouter() {
|
|
|
17202
17330
|
|
|
17203
17331
|
// src/dashboard/api.ts
|
|
17204
17332
|
function createApiRouter(agent = {}, mode = "all") {
|
|
17205
|
-
const api = new
|
|
17333
|
+
const api = new Hono31();
|
|
17206
17334
|
api.use("*", async (c, next) => {
|
|
17207
17335
|
const known = await listKnownWorkspaces();
|
|
17208
17336
|
const fromHost = profileFromHost(trustedHost(c), known);
|
|
@@ -17265,6 +17393,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
17265
17393
|
const entries2 = await agent.listFlatDocs?.({ cwd, query, sort, showHidden }) ?? [];
|
|
17266
17394
|
return c.json({ root: cwd, query, entries: entries2 });
|
|
17267
17395
|
});
|
|
17396
|
+
registerRelayedDocsRoutes(api, agent);
|
|
17268
17397
|
api.get("/settings", async (c) => {
|
|
17269
17398
|
const [branding, local] = await Promise.all([
|
|
17270
17399
|
loadWorkspaceBranding(),
|
|
@@ -17313,7 +17442,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
17313
17442
|
}
|
|
17314
17443
|
|
|
17315
17444
|
// src/dashboard/app-icon-routes.ts
|
|
17316
|
-
import { Hono as
|
|
17445
|
+
import { Hono as Hono32 } from "hono";
|
|
17317
17446
|
var NEUTRAL_MANIFEST = {
|
|
17318
17447
|
id: "/",
|
|
17319
17448
|
name: "Flyspace",
|
|
@@ -17351,7 +17480,7 @@ async function withResolvedProfile2(c, next) {
|
|
|
17351
17480
|
return profile ? runWithProfile(profile, () => next()) : next();
|
|
17352
17481
|
}
|
|
17353
17482
|
function createAppIconRouter() {
|
|
17354
|
-
const app = new
|
|
17483
|
+
const app = new Hono32();
|
|
17355
17484
|
app.get("/manifest.json", withResolvedProfile2, async (c) => {
|
|
17356
17485
|
if (!await resolvedProfileHost(c)) return c.json(NEUTRAL_MANIFEST);
|
|
17357
17486
|
const profile = getProfile();
|
|
@@ -17391,7 +17520,7 @@ function pngResponse(c, png) {
|
|
|
17391
17520
|
}
|
|
17392
17521
|
|
|
17393
17522
|
// src/dashboard/stripe-routes.ts
|
|
17394
|
-
import { Hono as
|
|
17523
|
+
import { Hono as Hono33 } from "hono";
|
|
17395
17524
|
function planStatusFor(status) {
|
|
17396
17525
|
const known = ["trialing", "active", "paused", "canceled"];
|
|
17397
17526
|
return known.includes(status) ? status : null;
|
|
@@ -17434,7 +17563,7 @@ async function handleCardAdded(session) {
|
|
|
17434
17563
|
}
|
|
17435
17564
|
}
|
|
17436
17565
|
function createStripeWebhookRouter() {
|
|
17437
|
-
const stripeRouter = new
|
|
17566
|
+
const stripeRouter = new Hono33();
|
|
17438
17567
|
stripeRouter.post("/webhook", async (c) => {
|
|
17439
17568
|
const secret = process.env["STRIPE_WEBHOOK_SECRET"];
|
|
17440
17569
|
const signature = c.req.header("stripe-signature");
|
|
@@ -17549,10 +17678,10 @@ var WEB_DIST = join23(paths.PACKAGE_ROOT, "web", "dist");
|
|
|
17549
17678
|
var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
17550
17679
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
17551
17680
|
for (let port = start; port < start + attempts; port++) {
|
|
17552
|
-
const free = await new Promise((
|
|
17681
|
+
const free = await new Promise((resolve7) => {
|
|
17553
17682
|
const probe3 = createServer();
|
|
17554
|
-
probe3.once("error", () =>
|
|
17555
|
-
probe3.once("listening", () => probe3.close(() =>
|
|
17683
|
+
probe3.once("error", () => resolve7(false));
|
|
17684
|
+
probe3.once("listening", () => probe3.close(() => resolve7(true)));
|
|
17556
17685
|
probe3.listen(port, host);
|
|
17557
17686
|
});
|
|
17558
17687
|
if (free) return port;
|
|
@@ -17594,7 +17723,7 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17594
17723
|
const mirrorsSkills = mode === "host" || process.env["FLYSPACE_SKILL_SYNC"] === "1";
|
|
17595
17724
|
const stopSkillSync = mirrorsSkills ? installSkillSync(listProfiles()) : () => {
|
|
17596
17725
|
};
|
|
17597
|
-
const app = new
|
|
17726
|
+
const app = new Hono34();
|
|
17598
17727
|
app.use("*", async (c, next) => {
|
|
17599
17728
|
await next();
|
|
17600
17729
|
if (c.req.path.startsWith("/assets/")) {
|
|
@@ -17619,14 +17748,14 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17619
17748
|
} else {
|
|
17620
17749
|
app.route("/share", createPublicShareAssetRouter(WEB_DIST));
|
|
17621
17750
|
app.route("/", createAppIconRouter());
|
|
17622
|
-
if (
|
|
17751
|
+
if (existsSync25(WEB_DIST) || existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"))) {
|
|
17623
17752
|
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
17624
17753
|
}
|
|
17625
17754
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
17626
17755
|
const spaFallback = serveStatic({ path: join23(WEB_DIST, "index.html") });
|
|
17627
17756
|
app.get("*", async (c, next) => {
|
|
17628
|
-
if (!
|
|
17629
|
-
const isCheckout =
|
|
17757
|
+
if (!existsSync25(join23(WEB_DIST, "index.html"))) {
|
|
17758
|
+
const isCheckout = existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"));
|
|
17630
17759
|
return c.text(
|
|
17631
17760
|
isCheckout ? `Workspace UI build missing (${WEB_DIST}).
|
|
17632
17761
|
Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
|
|
@@ -18064,7 +18193,7 @@ async function threadReplies(client2, nameOrId, ts2) {
|
|
|
18064
18193
|
async function postMessage(client2, args) {
|
|
18065
18194
|
const channelArg = getArg2(args, "--channel");
|
|
18066
18195
|
const textFilePath = getArg2(args, "--text-file");
|
|
18067
|
-
const text = textFilePath ?
|
|
18196
|
+
const text = textFilePath ? readFileSync18(textFilePath, "utf8") : getArg2(args, "--text");
|
|
18068
18197
|
const threadTs = getArg2(args, "--thread");
|
|
18069
18198
|
const markdown = args.includes("--markdown");
|
|
18070
18199
|
const broadcast = args.includes("--broadcast") && Boolean(threadTs);
|
|
@@ -18159,10 +18288,10 @@ function hasClaudeBinary() {
|
|
|
18159
18288
|
}
|
|
18160
18289
|
}
|
|
18161
18290
|
function defaultClaudeConfigDir() {
|
|
18162
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
18291
|
+
return process.env.CLAUDE_CONFIG_DIR || resolve6(homedir9(), ".claude");
|
|
18163
18292
|
}
|
|
18164
18293
|
function hasClaudeCredentials(configDir2) {
|
|
18165
|
-
if (
|
|
18294
|
+
if (existsSync26(resolve6(configDir2, ".credentials.json"))) {
|
|
18166
18295
|
return true;
|
|
18167
18296
|
}
|
|
18168
18297
|
if (process.platform === "darwin") {
|
|
@@ -18199,7 +18328,7 @@ async function ensureClaudeCodeReady() {
|
|
|
18199
18328
|
console.log(" Flyspace spawns Claude Code as a subprocess. Without credentials, the bot");
|
|
18200
18329
|
console.log(" will prompt /login inside Slack at runtime and you cannot complete the");
|
|
18201
18330
|
console.log(" browser sign-in from there.\n");
|
|
18202
|
-
if (claudeConfigDir !==
|
|
18331
|
+
if (claudeConfigDir !== resolve6(homedir9(), ".claude")) {
|
|
18203
18332
|
console.log(" Tip: run with the same CLAUDE_CONFIG_DIR you use for this Claude Code install:");
|
|
18204
18333
|
console.log(` \x1B[1mCLAUDE_CONFIG_DIR=${claudeConfigDir} claude\x1B[0m
|
|
18205
18334
|
`);
|
|
@@ -18207,7 +18336,7 @@ async function ensureClaudeCodeReady() {
|
|
|
18207
18336
|
console.log(" Open a new terminal and run \x1B[1mclaude\x1B[0m to complete the sign-in flow,");
|
|
18208
18337
|
console.log(" then come back here.\n");
|
|
18209
18338
|
}
|
|
18210
|
-
const proceed = await
|
|
18339
|
+
const proceed = await confirm4({
|
|
18211
18340
|
message: "Continue setup anyway?",
|
|
18212
18341
|
default: false
|
|
18213
18342
|
});
|
|
@@ -18218,8 +18347,8 @@ async function ensureClaudeCodeReady() {
|
|
|
18218
18347
|
return { claudeConfigDir };
|
|
18219
18348
|
}
|
|
18220
18349
|
function getManifestYaml(botName) {
|
|
18221
|
-
const manifestPath =
|
|
18222
|
-
const raw =
|
|
18350
|
+
const manifestPath = resolve6(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
|
|
18351
|
+
const raw = readFileSync18(manifestPath, "utf-8");
|
|
18223
18352
|
return raw.replace(/name: Custie/g, `name: ${botName}`);
|
|
18224
18353
|
}
|
|
18225
18354
|
async function stepPersonalise() {
|
|
@@ -18432,7 +18561,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18432
18561
|
})
|
|
18433
18562
|
));
|
|
18434
18563
|
const DEBUG = process.argv.includes("--debug");
|
|
18435
|
-
const USER_DATA_DIR =
|
|
18564
|
+
const USER_DATA_DIR = resolve6(tmpdir4(), "custie-playwright-profile");
|
|
18436
18565
|
let context;
|
|
18437
18566
|
try {
|
|
18438
18567
|
log4("Opening browser \u2014 a Slack login page will appear.\n");
|
|
@@ -18464,14 +18593,14 @@ async function stepBrowserTokens(botName) {
|
|
|
18464
18593
|
const appLink = page2.locator('a[href*="/apps/A"]').first();
|
|
18465
18594
|
const hasExistingApp = await appLink.isVisible({ timeout: 3e3 }).catch(() => false);
|
|
18466
18595
|
if (hasExistingApp) {
|
|
18467
|
-
const tokenPromise = new Promise((
|
|
18596
|
+
const tokenPromise = new Promise((resolve7) => {
|
|
18468
18597
|
page2.on("request", (req) => {
|
|
18469
18598
|
if (token) return;
|
|
18470
18599
|
const postData = req.postData() || "";
|
|
18471
18600
|
const match = postData.match(/xoxc-[a-f0-9-]+/);
|
|
18472
18601
|
if (match) {
|
|
18473
18602
|
token = match[0];
|
|
18474
|
-
|
|
18603
|
+
resolve7(token);
|
|
18475
18604
|
}
|
|
18476
18605
|
});
|
|
18477
18606
|
});
|
|
@@ -18522,7 +18651,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18522
18651
|
log4("Almost done! The browser will now open an install page.\n");
|
|
18523
18652
|
console.log(' \x1B[1mPlease click "Install to Workspace", then click "Allow".\x1B[0m\n');
|
|
18524
18653
|
await page2.goto(installUrl);
|
|
18525
|
-
const installPage = await new Promise((
|
|
18654
|
+
const installPage = await new Promise((resolve7, reject) => {
|
|
18526
18655
|
let settled = false;
|
|
18527
18656
|
let interval;
|
|
18528
18657
|
let timeout;
|
|
@@ -18535,7 +18664,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18535
18664
|
};
|
|
18536
18665
|
const checkUrl = (p) => {
|
|
18537
18666
|
if (p.url().includes("install-on-team?success=1")) {
|
|
18538
|
-
finish(() =>
|
|
18667
|
+
finish(() => resolve7(p));
|
|
18539
18668
|
}
|
|
18540
18669
|
};
|
|
18541
18670
|
context.on("page", (newPage) => {
|
|
@@ -18594,7 +18723,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18594
18723
|
try {
|
|
18595
18724
|
const pages = context?.pages();
|
|
18596
18725
|
if (pages?.[0]) {
|
|
18597
|
-
const screenshotPath =
|
|
18726
|
+
const screenshotPath = resolve6(paths.PACKAGE_ROOT, "setup-error.png");
|
|
18598
18727
|
await pages[0].screenshot({ path: screenshotPath, fullPage: true });
|
|
18599
18728
|
warn4(`Screenshot saved to: ${screenshotPath}`);
|
|
18600
18729
|
}
|
|
@@ -18636,25 +18765,23 @@ function writeConfigFile(config) {
|
|
|
18636
18765
|
`OWNER_USER_ID=${config.ownerUserId}`,
|
|
18637
18766
|
``
|
|
18638
18767
|
].join("\n");
|
|
18639
|
-
|
|
18768
|
+
writeFileSync11(paths.CONFIG_FILE, envContent);
|
|
18640
18769
|
success4(`Config written to ${paths.CONFIG_FILE}`);
|
|
18641
18770
|
}
|
|
18642
18771
|
function printNextSteps() {
|
|
18643
18772
|
log4("Setup complete! Next steps:\n");
|
|
18644
|
-
console.log(" 1. Customise the system prompt (optional)
|
|
18645
|
-
console.log(` Run \x1B[1mflyspace prompt\x1B[0m to edit ${paths.PROMPT_FILE}
|
|
18646
|
-
`);
|
|
18773
|
+
console.log(" 1. Customise the system prompt (optional) in Settings \u2192 Prompt.\n");
|
|
18647
18774
|
console.log(" 2. Start the bot:");
|
|
18648
18775
|
console.log(" Run \x1B[1mflyspace start\x1B[0m\n");
|
|
18649
18776
|
console.log(" 3. Install as a background service (optional):");
|
|
18650
|
-
console.log(" Run \x1B[1mflyspace install\x1B[0m\n");
|
|
18777
|
+
console.log(" Run \x1B[1mflyspace service install\x1B[0m\n");
|
|
18651
18778
|
}
|
|
18652
18779
|
function copyDefaultPrompt(botName) {
|
|
18653
|
-
if (!
|
|
18654
|
-
const defaultPrompt =
|
|
18655
|
-
if (
|
|
18656
|
-
const content =
|
|
18657
|
-
|
|
18780
|
+
if (!existsSync26(paths.PROMPT_FILE)) {
|
|
18781
|
+
const defaultPrompt = resolve6(paths.PACKAGE_ROOT, "system.default.md");
|
|
18782
|
+
if (existsSync26(defaultPrompt)) {
|
|
18783
|
+
const content = readFileSync18(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
|
|
18784
|
+
writeFileSync11(paths.PROMPT_FILE, content);
|
|
18658
18785
|
success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
|
|
18659
18786
|
}
|
|
18660
18787
|
}
|
|
@@ -18666,8 +18793,8 @@ async function runSlackSetup(args) {
|
|
|
18666
18793
|
const prereq = await ensureClaudeCodeReady();
|
|
18667
18794
|
if (!prereq) return;
|
|
18668
18795
|
const { claudeConfigDir } = prereq;
|
|
18669
|
-
if (
|
|
18670
|
-
const reconfigure = await
|
|
18796
|
+
if (existsSync26(paths.CONFIG_FILE)) {
|
|
18797
|
+
const reconfigure = await confirm4({
|
|
18671
18798
|
message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
|
|
18672
18799
|
default: false
|
|
18673
18800
|
});
|
|
@@ -18976,7 +19103,85 @@ async function runChatCmd(args) {
|
|
|
18976
19103
|
}
|
|
18977
19104
|
|
|
18978
19105
|
// src/commands/memory.ts
|
|
18979
|
-
import { readFileSync as
|
|
19106
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
19107
|
+
|
|
19108
|
+
// src/store/claude-memory-import.ts
|
|
19109
|
+
import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "fs";
|
|
19110
|
+
import { homedir as homedir10 } from "os";
|
|
19111
|
+
import { basename as basename8, join as join24 } from "path";
|
|
19112
|
+
import dotenv2 from "dotenv";
|
|
19113
|
+
function claudeMemoryDir(cwd, configDir2) {
|
|
19114
|
+
return join24(configDir2, "projects", cwd.replaceAll(/[^a-zA-Z0-9]/g, "-"), "memory");
|
|
19115
|
+
}
|
|
19116
|
+
function profileClaudeConfigDirFor(profile) {
|
|
19117
|
+
const dir = profile === "default" ? paths.BASE_CONFIG_DIR : join24(paths.PROFILES_DIR, profile);
|
|
19118
|
+
const file = join24(dir, "config.env");
|
|
19119
|
+
const env = existsSync27(file) ? dotenv2.parse(readFileSync19(file)) : {};
|
|
19120
|
+
return env["CLAUDE_CONFIG_DIR"]?.trim() || join24(homedir10(), ".claude");
|
|
19121
|
+
}
|
|
19122
|
+
var TYPE_MAP = {
|
|
19123
|
+
user: "preference",
|
|
19124
|
+
feedback: "preference",
|
|
19125
|
+
project: "fact",
|
|
19126
|
+
reference: "reference"
|
|
19127
|
+
};
|
|
19128
|
+
var DESCRIPTION_MAX2 = 200;
|
|
19129
|
+
function unquote2(value) {
|
|
19130
|
+
const v = value.trim();
|
|
19131
|
+
return /^(["']).*\1$/.test(v) ? v.slice(1, -1) : v;
|
|
19132
|
+
}
|
|
19133
|
+
function slugify(value) {
|
|
19134
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
19135
|
+
}
|
|
19136
|
+
function parseClaudeMemory(fileName, text, channelId) {
|
|
19137
|
+
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text.replaceAll("\r\n", "\n"));
|
|
19138
|
+
if (!match) return { skip: "no frontmatter" };
|
|
19139
|
+
const fields = {};
|
|
19140
|
+
for (const line of match[1].split("\n")) {
|
|
19141
|
+
const kv = /^\s*([A-Za-z_]+):\s*(.*)$/.exec(line);
|
|
19142
|
+
if (kv && kv[2] && !(kv[1] in fields)) fields[kv[1]] = unquote2(kv[2]);
|
|
19143
|
+
}
|
|
19144
|
+
const name = slugify(fields["name"] || basename8(fileName, ".md"));
|
|
19145
|
+
let description = (fields["description"] ?? "").replaceAll(/\s+/g, " ").trim();
|
|
19146
|
+
if (description.length > DESCRIPTION_MAX2) {
|
|
19147
|
+
description = `${description.slice(0, DESCRIPTION_MAX2 - 3).trimEnd()}...`;
|
|
19148
|
+
}
|
|
19149
|
+
const input2 = {
|
|
19150
|
+
channelId,
|
|
19151
|
+
name,
|
|
19152
|
+
description,
|
|
19153
|
+
type: TYPE_MAP[fields["type"] ?? ""] ?? "fact",
|
|
19154
|
+
body: match[2].trim(),
|
|
19155
|
+
createdBy: "agent"
|
|
19156
|
+
};
|
|
19157
|
+
try {
|
|
19158
|
+
validateMemoryInput(input2);
|
|
19159
|
+
} catch (err) {
|
|
19160
|
+
return { skip: err.message };
|
|
19161
|
+
}
|
|
19162
|
+
return input2;
|
|
19163
|
+
}
|
|
19164
|
+
async function importClaudeMemories(dir, channelId, opts = {}) {
|
|
19165
|
+
if (!existsSync27(dir)) throw new Error(`No Claude memory directory at ${dir}`);
|
|
19166
|
+
const files = readdirSync5(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
19167
|
+
const results = [];
|
|
19168
|
+
for (const file of files) {
|
|
19169
|
+
const parsed = parseClaudeMemory(file, readFileSync19(join24(dir, file), "utf-8"), channelId);
|
|
19170
|
+
if ("skip" in parsed) {
|
|
19171
|
+
results.push({ file, outcome: "skipped", reason: parsed.skip });
|
|
19172
|
+
continue;
|
|
19173
|
+
}
|
|
19174
|
+
if (await getChannelMemory(channelId, parsed.name)) {
|
|
19175
|
+
results.push({ file, name: parsed.name, outcome: "exists" });
|
|
19176
|
+
continue;
|
|
19177
|
+
}
|
|
19178
|
+
if (!opts.dryRun) await saveChannelMemory(parsed, "agent");
|
|
19179
|
+
results.push({ file, name: parsed.name, outcome: "imported" });
|
|
19180
|
+
}
|
|
19181
|
+
return results;
|
|
19182
|
+
}
|
|
19183
|
+
|
|
19184
|
+
// src/commands/memory.ts
|
|
18980
19185
|
var USAGE5 = `
|
|
18981
19186
|
Usage: flyspace memory <subcommand> --channel <name-or-id> [options]
|
|
18982
19187
|
|
|
@@ -18991,6 +19196,10 @@ var USAGE5 = `
|
|
|
18991
19196
|
--body <text | -> "-" reads the body from stdin
|
|
18992
19197
|
[--thread <id>] The thread the memory came from
|
|
18993
19198
|
delete <name-or-id> [--thread <id>] Delete one memory
|
|
19199
|
+
import [--dir <path>] [--dry-run] Copy Claude Code's native auto memory for the
|
|
19200
|
+
channel's folder in; names already present are kept.
|
|
19201
|
+
Defaults to the channel profile's CLAUDE_CONFIG_DIR
|
|
19202
|
+
(or ~/.claude)/projects/<cwd slug>/memory
|
|
18994
19203
|
|
|
18995
19204
|
Types: ${MEMORY_TYPES.join(", ")}
|
|
18996
19205
|
`;
|
|
@@ -19056,7 +19265,7 @@ ${memory.body}`);
|
|
|
19056
19265
|
);
|
|
19057
19266
|
}
|
|
19058
19267
|
if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
19059
|
-
const body = bodyArg === "-" ?
|
|
19268
|
+
const body = bodyArg === "-" ? readFileSync20(0, "utf-8") : bodyArg;
|
|
19060
19269
|
const thread = getArg4(rest, "--thread");
|
|
19061
19270
|
const { created, changed } = await saveChannelMemory(
|
|
19062
19271
|
{
|
|
@@ -19082,6 +19291,20 @@ ${memory.body}`);
|
|
|
19082
19291
|
console.log(`Deleted memory ${ref}.`);
|
|
19083
19292
|
break;
|
|
19084
19293
|
}
|
|
19294
|
+
case "import": {
|
|
19295
|
+
const dir = getArg4(rest, "--dir") ?? claudeMemoryDir(channel.cwd, profileClaudeConfigDirFor(channel.profile));
|
|
19296
|
+
const dryRun = rest.includes("--dry-run");
|
|
19297
|
+
const results = await importClaudeMemories(dir, channel.id, { dryRun });
|
|
19298
|
+
for (const r of results) {
|
|
19299
|
+
const detail = r.outcome === "skipped" ? r.reason : r.name;
|
|
19300
|
+
console.log(`${r.outcome.padEnd(8)} ${r.file} ${detail}`);
|
|
19301
|
+
}
|
|
19302
|
+
const count = (o) => results.filter((r) => r.outcome === o).length;
|
|
19303
|
+
console.log(
|
|
19304
|
+
`${dryRun ? "Would import" : "Imported"} ${count("imported")}, ${count("exists")} already present, ${count("skipped")} skipped, from ${dir}.`
|
|
19305
|
+
);
|
|
19306
|
+
break;
|
|
19307
|
+
}
|
|
19085
19308
|
default:
|
|
19086
19309
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
19087
19310
|
console.log(USAGE5);
|
|
@@ -19126,10 +19349,10 @@ async function runMcpCmd(args) {
|
|
|
19126
19349
|
}
|
|
19127
19350
|
|
|
19128
19351
|
// src/commands/dashboard.ts
|
|
19129
|
-
import { spawn as
|
|
19352
|
+
import { spawn as spawn5, spawnSync } from "child_process";
|
|
19130
19353
|
import { existsSync as existsSync28 } from "fs";
|
|
19131
19354
|
import { platform as platform7 } from "os";
|
|
19132
|
-
import { join as
|
|
19355
|
+
import { join as join25 } from "path";
|
|
19133
19356
|
function flagValue(args, name) {
|
|
19134
19357
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
19135
19358
|
if (eq) return eq.slice(`--${name}=`.length);
|
|
@@ -19139,13 +19362,13 @@ function flagValue(args, name) {
|
|
|
19139
19362
|
function openBrowser2(url) {
|
|
19140
19363
|
const cmd = platform7() === "darwin" ? "open" : platform7() === "win32" ? "start" : "xdg-open";
|
|
19141
19364
|
try {
|
|
19142
|
-
|
|
19365
|
+
spawn5(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
19143
19366
|
} catch {
|
|
19144
19367
|
}
|
|
19145
19368
|
}
|
|
19146
19369
|
function ensureWebBuild() {
|
|
19147
|
-
if (existsSync28(
|
|
19148
|
-
if (!existsSync28(
|
|
19370
|
+
if (existsSync28(join25(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
|
|
19371
|
+
if (!existsSync28(join25(paths.PACKAGE_ROOT, "web", "package.json"))) return;
|
|
19149
19372
|
console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
|
|
19150
19373
|
const result = spawnSync("pnpm", ["--dir", "web", "build"], {
|
|
19151
19374
|
cwd: paths.PACKAGE_ROOT,
|
|
@@ -19195,7 +19418,7 @@ async function runDashboard(args) {
|
|
|
19195
19418
|
}
|
|
19196
19419
|
|
|
19197
19420
|
// src/commands/workspace.ts
|
|
19198
|
-
import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as
|
|
19421
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
19199
19422
|
var USAGE7 = `
|
|
19200
19423
|
Usage: flyspace workspace <subcommand> [options]
|
|
19201
19424
|
|
|
@@ -19284,7 +19507,7 @@ function provisionLocal(botName) {
|
|
|
19284
19507
|
if (existsSync29(paths.CONFIG_FILE)) {
|
|
19285
19508
|
console.log(" config.env exists");
|
|
19286
19509
|
} else {
|
|
19287
|
-
|
|
19510
|
+
writeFileSync12(paths.CONFIG_FILE, `BOT_NAME=${botName}
|
|
19288
19511
|
`);
|
|
19289
19512
|
console.log(` config.env written (${paths.CONFIG_FILE})`);
|
|
19290
19513
|
}
|
|
@@ -19384,18 +19607,16 @@ var USAGE9 = `
|
|
|
19384
19607
|
setup Set up this machine as a Host (login, agent CLIs, ~/Flyspace)
|
|
19385
19608
|
auth Log in, check status, or log out this device
|
|
19386
19609
|
doctor Check this Host end to end (login, agent CLIs, services)
|
|
19387
|
-
start Start the agent host
|
|
19388
|
-
|
|
19389
|
-
uninstall Remove the system service
|
|
19610
|
+
start Start the agent host in the foreground
|
|
19611
|
+
service Run the host in the background (install / uninstall)
|
|
19390
19612
|
stop Stop the service (without uninstalling)
|
|
19391
19613
|
restart Restart the service
|
|
19392
19614
|
logs Tail the service log (-e for error log)
|
|
19393
19615
|
upgrade Upgrade Flyspace to the latest version
|
|
19394
|
-
|
|
19616
|
+
uninstall Remove Flyspace from this machine (CLI, services, config)
|
|
19395
19617
|
config Show resolved config (--edit to edit, --path for file path)
|
|
19396
19618
|
automation Manage scheduled tasks and triggers
|
|
19397
19619
|
usage Show agent token usage and cost for this workspace
|
|
19398
|
-
slack Query Slack (channels, users, post messages)
|
|
19399
19620
|
chat Inspect dashboard chat channels and move threads between them
|
|
19400
19621
|
memory Read and write a channel's persistent agent memory
|
|
19401
19622
|
profiles List local profiles (bound here? scheduler?) or remove one
|
|
@@ -19453,11 +19674,20 @@ async function main() {
|
|
|
19453
19674
|
case "reconcile":
|
|
19454
19675
|
await runReconcileCmd(args.slice(1));
|
|
19455
19676
|
break;
|
|
19677
|
+
case "service":
|
|
19678
|
+
if (args[1] === "install") await runInstall();
|
|
19679
|
+
else if (args[1] === "uninstall") await runServiceUninstall();
|
|
19680
|
+
else {
|
|
19681
|
+
printRaw("Usage: flyspace service <install|uninstall>");
|
|
19682
|
+
process.exit(1);
|
|
19683
|
+
}
|
|
19684
|
+
break;
|
|
19685
|
+
// Pre-rename spelling, kept so existing docs and scripts keep working.
|
|
19456
19686
|
case "install":
|
|
19457
19687
|
await runInstall();
|
|
19458
19688
|
break;
|
|
19459
19689
|
case "uninstall":
|
|
19460
|
-
await runUninstall();
|
|
19690
|
+
await runUninstall(args.slice(1));
|
|
19461
19691
|
break;
|
|
19462
19692
|
case "stop":
|
|
19463
19693
|
await runStop();
|
|
@@ -19471,9 +19701,6 @@ async function main() {
|
|
|
19471
19701
|
case "upgrade":
|
|
19472
19702
|
await runUpgrade();
|
|
19473
19703
|
break;
|
|
19474
|
-
case "prompt":
|
|
19475
|
-
await runPrompt();
|
|
19476
|
-
break;
|
|
19477
19704
|
case "config":
|
|
19478
19705
|
await runConfig(args.slice(1));
|
|
19479
19706
|
break;
|
|
@@ -19504,7 +19731,7 @@ async function main() {
|
|
|
19504
19731
|
break;
|
|
19505
19732
|
case "-v":
|
|
19506
19733
|
case "--version":
|
|
19507
|
-
printRaw("0.1.
|
|
19734
|
+
printRaw("0.1.13");
|
|
19508
19735
|
break;
|
|
19509
19736
|
case "-h":
|
|
19510
19737
|
case "--help":
|