flyspace-cli 0.1.10 → 0.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -7
- package/dist/{chunk-UYLVSUD6.js → chunk-VKTJBYA3.js} +38 -7
- package/dist/cli.js +455 -300
- 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.12" : null;
|
|
1575
1544
|
function isOlderVersion(a, b) {
|
|
1576
1545
|
const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1577
1546
|
const [pa, pb] = [parse2(a), parse2(b)];
|
|
@@ -1849,7 +1818,8 @@ ${BOLD3}Flyspace Doctor${RESET3}
|
|
|
1849
1818
|
}
|
|
1850
1819
|
|
|
1851
1820
|
// src/commands/uninstall.ts
|
|
1852
|
-
import { existsSync as
|
|
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.12";
|
|
2027
2036
|
console.log(`Current version: ${current}`);
|
|
2028
2037
|
console.log("Checking for updates...\n");
|
|
2029
2038
|
try {
|
|
@@ -2394,7 +2403,7 @@ async function runAutomationCmd(args) {
|
|
|
2394
2403
|
}
|
|
2395
2404
|
|
|
2396
2405
|
// src/commands/usage.ts
|
|
2397
|
-
import { existsSync as
|
|
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,7 +4200,7 @@ 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
4206
|
import { Hono as Hono33 } from "hono";
|
|
@@ -4199,7 +4208,7 @@ import { serve } from "@hono/node-server";
|
|
|
4199
4208
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4200
4209
|
|
|
4201
4210
|
// src/reconcile.ts
|
|
4202
|
-
import { existsSync as
|
|
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
|
}
|
|
@@ -4397,7 +4406,7 @@ var requireDocsAccess = async (c, next) => {
|
|
|
4397
4406
|
import { Hono as Hono30 } from "hono";
|
|
4398
4407
|
|
|
4399
4408
|
// src/dashboard/public-share-routes.ts
|
|
4400
|
-
import { existsSync as
|
|
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,7 +10760,7 @@ function deleteDocEntry(root, path) {
|
|
|
10751
10760
|
}
|
|
10752
10761
|
const isDir2 = stat.isDirectory();
|
|
10753
10762
|
if (!isDir2 && !stat.isFile() && !stat.isSymbolicLink()) return { status: "not-found" };
|
|
10754
|
-
|
|
10763
|
+
rmSync6(abs, { recursive: isDir2 });
|
|
10755
10764
|
return {
|
|
10756
10765
|
status: "ok",
|
|
10757
10766
|
path: relative(root, abs).split(sep).join("/"),
|
|
@@ -10790,7 +10799,7 @@ function createDocsRouter() {
|
|
|
10790
10799
|
}
|
|
10791
10800
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
10792
10801
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
10793
|
-
const data =
|
|
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);
|
|
@@ -15488,24 +15528,24 @@ import { Hono as Hono29 } from "hono";
|
|
|
15488
15528
|
|
|
15489
15529
|
// src/dashboard/agent-bridge.ts
|
|
15490
15530
|
import { randomBytes as randomBytes8, randomUUID as randomUUID4, sign as signRaw, timingSafeEqual as timingSafeEqual2, verify as verifyRaw } from "crypto";
|
|
15491
|
-
import { existsSync as
|
|
15531
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17 } from "fs";
|
|
15492
15532
|
import dotenv from "dotenv";
|
|
15493
15533
|
import { WebSocket, WebSocketServer } from "ws";
|
|
15494
15534
|
|
|
15495
15535
|
// src/dashboard/host-identity.ts
|
|
15496
15536
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
|
|
15497
|
-
import { existsSync as
|
|
15537
|
+
import { existsSync as existsSync23, mkdirSync as mkdirSync9, readFileSync as readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
15498
15538
|
import { dirname as dirname7, join as join21 } from "path";
|
|
15499
15539
|
function hostPrivateKeyPath() {
|
|
15500
15540
|
return process.env["FLYSPACE_HOST_KEY_PATH"] || join21(paths.BASE_CONFIG_DIR, "host.key");
|
|
15501
15541
|
}
|
|
15502
15542
|
function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
15503
|
-
if (!
|
|
15543
|
+
if (!existsSync23(path)) {
|
|
15504
15544
|
throw new Error(
|
|
15505
15545
|
`No Host identity at ${path}. Run \`pnpm run generate-host-key\` on this machine, then set FLYSPACE_HOST_PUBLIC_KEY on the Data process to the printed public key.`
|
|
15506
15546
|
);
|
|
15507
15547
|
}
|
|
15508
|
-
return createPrivateKey2(
|
|
15548
|
+
return createPrivateKey2(readFileSync14(path, "utf8"));
|
|
15509
15549
|
}
|
|
15510
15550
|
function loadHostPublicKey() {
|
|
15511
15551
|
const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
|
|
@@ -15521,7 +15561,7 @@ function loadHostPublicKey() {
|
|
|
15521
15561
|
// src/dashboard/host-lock.ts
|
|
15522
15562
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
15523
15563
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
15524
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
15564
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync15, rmSync as rmSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
15525
15565
|
import { join as join22 } from "path";
|
|
15526
15566
|
function hostLockPath() {
|
|
15527
15567
|
return join22(paths.BASE_DATA_DIR, "host.lock");
|
|
@@ -15539,7 +15579,7 @@ function isLiveFlyspace(pid) {
|
|
|
15539
15579
|
}
|
|
15540
15580
|
function readLockPid(path) {
|
|
15541
15581
|
try {
|
|
15542
|
-
const pid = Number(JSON.parse(
|
|
15582
|
+
const pid = Number(JSON.parse(readFileSync15(path, "utf8")).pid);
|
|
15543
15583
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
15544
15584
|
} catch {
|
|
15545
15585
|
return null;
|
|
@@ -15555,9 +15595,9 @@ function acquireHostLock() {
|
|
|
15555
15595
|
});
|
|
15556
15596
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
15557
15597
|
try {
|
|
15558
|
-
|
|
15598
|
+
writeFileSync10(path, body, { flag: "wx", mode: 384 });
|
|
15559
15599
|
const release2 = () => {
|
|
15560
|
-
if (readLockPid(path) === process.pid)
|
|
15600
|
+
if (readLockPid(path) === process.pid) rmSync7(path, { force: true });
|
|
15561
15601
|
};
|
|
15562
15602
|
process.once("exit", release2);
|
|
15563
15603
|
return release2;
|
|
@@ -15571,7 +15611,7 @@ function acquireHostLock() {
|
|
|
15571
15611
|
`Another Flyspace Host is already running on this machine (pid ${holder}). Stop it first, or check for a second CLI install (\`which -a flyspace\`). Two Hosts overwrite each other's skill mirror.`
|
|
15572
15612
|
);
|
|
15573
15613
|
}
|
|
15574
|
-
|
|
15614
|
+
rmSync7(path, { force: true });
|
|
15575
15615
|
}
|
|
15576
15616
|
}
|
|
15577
15617
|
throw new Error(`Could not take the Host lock at ${path}`);
|
|
@@ -15580,13 +15620,13 @@ function publishHostEndpoint(port) {
|
|
|
15580
15620
|
const path = hostLockPath();
|
|
15581
15621
|
if (readLockPid(path) !== process.pid) return null;
|
|
15582
15622
|
const token = randomBytes5(32).toString("hex");
|
|
15583
|
-
const lock = JSON.parse(
|
|
15584
|
-
|
|
15623
|
+
const lock = JSON.parse(readFileSync15(path, "utf8"));
|
|
15624
|
+
writeFileSync10(path, JSON.stringify({ ...lock, port, token }), { mode: 384 });
|
|
15585
15625
|
return token;
|
|
15586
15626
|
}
|
|
15587
15627
|
function readHostEndpoint() {
|
|
15588
15628
|
try {
|
|
15589
|
-
const { pid, port, token } = JSON.parse(
|
|
15629
|
+
const { pid, port, token } = JSON.parse(readFileSync15(hostLockPath(), "utf8"));
|
|
15590
15630
|
if (pid === process.pid || !Number.isInteger(port) || typeof token !== "string") return null;
|
|
15591
15631
|
return { port, token };
|
|
15592
15632
|
} catch {
|
|
@@ -15595,12 +15635,12 @@ function readHostEndpoint() {
|
|
|
15595
15635
|
}
|
|
15596
15636
|
|
|
15597
15637
|
// src/store/device-store.ts
|
|
15598
|
-
import { createHash as
|
|
15638
|
+
import { createHash as createHash11, createPublicKey as createPublicKey3, randomBytes as randomBytes6, randomInt } from "crypto";
|
|
15599
15639
|
var DEVICE_CODE_TTL_MS = 10 * 6e4;
|
|
15600
15640
|
var ACCESS_TOKEN_TTL_MS = 15 * 6e4;
|
|
15601
15641
|
var CHALLENGE_TTL_MS = 2 * 6e4;
|
|
15602
15642
|
var POLL_INTERVAL_S = 5;
|
|
15603
|
-
var sha256 = (s) =>
|
|
15643
|
+
var sha256 = (s) => createHash11("sha256").update(s).digest("hex");
|
|
15604
15644
|
function parseEd25519PublicKey(b64) {
|
|
15605
15645
|
if (typeof b64 !== "string" || b64.length > 200) return null;
|
|
15606
15646
|
try {
|
|
@@ -15826,7 +15866,7 @@ async function userEmail(userId) {
|
|
|
15826
15866
|
|
|
15827
15867
|
// src/dashboard/doc-share-publisher.ts
|
|
15828
15868
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
15829
|
-
import { readFileSync as
|
|
15869
|
+
import { readFileSync as readFileSync16, statSync as statSync9 } from "fs";
|
|
15830
15870
|
import { extname as extname2, posix } from "path";
|
|
15831
15871
|
import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
|
|
15832
15872
|
|
|
@@ -16013,7 +16053,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16013
16053
|
}
|
|
16014
16054
|
if (!stat.isFile()) return { status: "not-found" };
|
|
16015
16055
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
16016
|
-
const source =
|
|
16056
|
+
const source = readFileSync16(abs, "utf8");
|
|
16017
16057
|
const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
|
|
16018
16058
|
if (kind === "excalidraw") {
|
|
16019
16059
|
try {
|
|
@@ -16093,7 +16133,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16093
16133
|
let text = null;
|
|
16094
16134
|
try {
|
|
16095
16135
|
const size = statSync9(docAbs).size;
|
|
16096
|
-
if (size <= budget) text =
|
|
16136
|
+
if (size <= budget) text = readFileSync16(docAbs, "utf8");
|
|
16097
16137
|
} catch {
|
|
16098
16138
|
continue;
|
|
16099
16139
|
}
|
|
@@ -16110,7 +16150,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16110
16150
|
let body;
|
|
16111
16151
|
try {
|
|
16112
16152
|
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
16113
|
-
body =
|
|
16153
|
+
body = readFileSync16(resolved2.abs);
|
|
16114
16154
|
} catch {
|
|
16115
16155
|
continue;
|
|
16116
16156
|
}
|
|
@@ -16201,8 +16241,12 @@ var METHODS = {
|
|
|
16201
16241
|
agent_usage: ["GET", "HEAD", "POST"],
|
|
16202
16242
|
skill_files: ALL,
|
|
16203
16243
|
threads: ["GET", "HEAD", "POST", "PATCH"],
|
|
16204
|
-
thread_replies: ["GET", "HEAD", "POST", "PATCH"]
|
|
16244
|
+
thread_replies: ["GET", "HEAD", "POST", "PATCH"],
|
|
16245
|
+
memberships: ["GET", "HEAD"],
|
|
16246
|
+
channel_members: ["GET", "HEAD"],
|
|
16247
|
+
channel_docs_access: ["GET", "HEAD"]
|
|
16205
16248
|
};
|
|
16249
|
+
var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members", "channel_docs_access"]);
|
|
16206
16250
|
var FORWARDED_HEADERS = ["accept", "content-type", "prefer", "range"];
|
|
16207
16251
|
var deny = (message, status = 403) => ({
|
|
16208
16252
|
status,
|
|
@@ -16245,6 +16289,13 @@ async function ownedChannelIds(forward, slugs) {
|
|
|
16245
16289
|
if (!rows) return null;
|
|
16246
16290
|
return rows.flatMap((r) => [r.id, r.external_channel_id].filter((v) => !!v));
|
|
16247
16291
|
}
|
|
16292
|
+
async function ownedWorkspaceIds(forward, slugs) {
|
|
16293
|
+
const rows = await getJson(
|
|
16294
|
+
forward,
|
|
16295
|
+
`/rest/v1/workspaces?select=id&slug=${encodeURIComponent(inList(slugs))}`
|
|
16296
|
+
);
|
|
16297
|
+
return rows ? rows.map((r) => r.id) : null;
|
|
16298
|
+
}
|
|
16248
16299
|
async function threadsOwned(forward, threadIds, channelIds) {
|
|
16249
16300
|
const wanted = [...new Set(threadIds)];
|
|
16250
16301
|
if (wanted.length === 0 || channelIds.length === 0) return false;
|
|
@@ -16302,10 +16353,16 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
16302
16353
|
}
|
|
16303
16354
|
}
|
|
16304
16355
|
params.append(slugColumn, inList(scope));
|
|
16356
|
+
} else if (table === "memberships") {
|
|
16357
|
+
const workspaceIds = await ownedWorkspaceIds(forward, workspaces);
|
|
16358
|
+
if (!workspaceIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16359
|
+
params.append("workspace_id", inList(workspaceIds));
|
|
16305
16360
|
} else {
|
|
16306
16361
|
const channelIds = await ownedChannelIds(forward, workspaces);
|
|
16307
16362
|
if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16308
|
-
if (table
|
|
16363
|
+
if (CHANNEL_SCOPED.has(table)) {
|
|
16364
|
+
params.append("channel_id", inList(channelIds));
|
|
16365
|
+
} else if (table === "threads") {
|
|
16309
16366
|
if (method === "POST") {
|
|
16310
16367
|
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
16311
16368
|
if (!ok3) return deny("A thread must belong to a channel of the device workspaces.");
|
|
@@ -16390,14 +16447,14 @@ function handleHostDbResult(id, res) {
|
|
|
16390
16447
|
}
|
|
16391
16448
|
function whenConnected() {
|
|
16392
16449
|
if (sender) return Promise.resolve(sender);
|
|
16393
|
-
return new Promise((
|
|
16450
|
+
return new Promise((resolve7, reject) => {
|
|
16394
16451
|
const timer2 = setTimeout(() => {
|
|
16395
16452
|
onSender = onSender.filter((w) => w !== wake);
|
|
16396
16453
|
reject(new Error("Host is not connected to the Data API"));
|
|
16397
16454
|
}, REQUEST_TIMEOUT_MS3);
|
|
16398
16455
|
const wake = () => {
|
|
16399
16456
|
clearTimeout(timer2);
|
|
16400
|
-
|
|
16457
|
+
resolve7(sender);
|
|
16401
16458
|
};
|
|
16402
16459
|
onSender.push(wake);
|
|
16403
16460
|
});
|
|
@@ -16405,12 +16462,12 @@ function whenConnected() {
|
|
|
16405
16462
|
async function request(req) {
|
|
16406
16463
|
const send2 = await whenConnected();
|
|
16407
16464
|
const id = randomUUID3();
|
|
16408
|
-
return new Promise((
|
|
16465
|
+
return new Promise((resolve7, reject) => {
|
|
16409
16466
|
const timer2 = setTimeout(() => {
|
|
16410
16467
|
pending.delete(id);
|
|
16411
16468
|
reject(new Error("Data API did not answer in time"));
|
|
16412
16469
|
}, REQUEST_TIMEOUT_MS3);
|
|
16413
|
-
pending.set(id, { resolve:
|
|
16470
|
+
pending.set(id, { resolve: resolve7, reject, timer: timer2 });
|
|
16414
16471
|
send2({ type: "db", id, req });
|
|
16415
16472
|
});
|
|
16416
16473
|
}
|
|
@@ -16470,7 +16527,7 @@ function dataSocketUrl() {
|
|
|
16470
16527
|
return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
|
|
16471
16528
|
}
|
|
16472
16529
|
function listLocalSessions() {
|
|
16473
|
-
if (!
|
|
16530
|
+
if (!existsSync24(paths.DB_FILE)) return [];
|
|
16474
16531
|
const store = new SessionStore(paths.DB_FILE);
|
|
16475
16532
|
try {
|
|
16476
16533
|
return store.listSessions();
|
|
@@ -16507,7 +16564,7 @@ function buildHostConfigSnapshot() {
|
|
|
16507
16564
|
const snapshot3 = {};
|
|
16508
16565
|
for (const profile of listProfiles()) {
|
|
16509
16566
|
snapshot3[profile] = runWithProfile(profile, () => ({
|
|
16510
|
-
env:
|
|
16567
|
+
env: existsSync24(paths.CONFIG_FILE) ? dotenv.parse(readFileSync17(paths.CONFIG_FILE, "utf-8")) : {},
|
|
16511
16568
|
agentModel: loadDashboardSettings().agent
|
|
16512
16569
|
}));
|
|
16513
16570
|
}
|
|
@@ -16718,16 +16775,16 @@ function callHost(method, args) {
|
|
|
16718
16775
|
return Promise.resolve(null);
|
|
16719
16776
|
}
|
|
16720
16777
|
const id = randomUUID4();
|
|
16721
|
-
return new Promise((
|
|
16778
|
+
return new Promise((resolve7) => {
|
|
16722
16779
|
const timer2 = setTimeout(() => {
|
|
16723
16780
|
pending2.delete(id);
|
|
16724
16781
|
console.error(`[host-socket] ${method} -> timed out`);
|
|
16725
|
-
|
|
16782
|
+
resolve7(null);
|
|
16726
16783
|
}, CALL_TIMEOUT_MS);
|
|
16727
16784
|
pending2.set(id, {
|
|
16728
16785
|
resolve: (value) => {
|
|
16729
16786
|
clearTimeout(timer2);
|
|
16730
|
-
|
|
16787
|
+
resolve7(value);
|
|
16731
16788
|
}
|
|
16732
16789
|
});
|
|
16733
16790
|
send(ws, { type: "call", id, method, profile, args });
|
|
@@ -17549,10 +17606,10 @@ var WEB_DIST = join23(paths.PACKAGE_ROOT, "web", "dist");
|
|
|
17549
17606
|
var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
17550
17607
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
17551
17608
|
for (let port = start; port < start + attempts; port++) {
|
|
17552
|
-
const free = await new Promise((
|
|
17609
|
+
const free = await new Promise((resolve7) => {
|
|
17553
17610
|
const probe3 = createServer();
|
|
17554
|
-
probe3.once("error", () =>
|
|
17555
|
-
probe3.once("listening", () => probe3.close(() =>
|
|
17611
|
+
probe3.once("error", () => resolve7(false));
|
|
17612
|
+
probe3.once("listening", () => probe3.close(() => resolve7(true)));
|
|
17556
17613
|
probe3.listen(port, host);
|
|
17557
17614
|
});
|
|
17558
17615
|
if (free) return port;
|
|
@@ -17619,14 +17676,14 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17619
17676
|
} else {
|
|
17620
17677
|
app.route("/share", createPublicShareAssetRouter(WEB_DIST));
|
|
17621
17678
|
app.route("/", createAppIconRouter());
|
|
17622
|
-
if (
|
|
17679
|
+
if (existsSync25(WEB_DIST) || existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"))) {
|
|
17623
17680
|
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
17624
17681
|
}
|
|
17625
17682
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
17626
17683
|
const spaFallback = serveStatic({ path: join23(WEB_DIST, "index.html") });
|
|
17627
17684
|
app.get("*", async (c, next) => {
|
|
17628
|
-
if (!
|
|
17629
|
-
const isCheckout =
|
|
17685
|
+
if (!existsSync25(join23(WEB_DIST, "index.html"))) {
|
|
17686
|
+
const isCheckout = existsSync25(join23(paths.PACKAGE_ROOT, "web", "package.json"));
|
|
17630
17687
|
return c.text(
|
|
17631
17688
|
isCheckout ? `Workspace UI build missing (${WEB_DIST}).
|
|
17632
17689
|
Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
|
|
@@ -18064,7 +18121,7 @@ async function threadReplies(client2, nameOrId, ts2) {
|
|
|
18064
18121
|
async function postMessage(client2, args) {
|
|
18065
18122
|
const channelArg = getArg2(args, "--channel");
|
|
18066
18123
|
const textFilePath = getArg2(args, "--text-file");
|
|
18067
|
-
const text = textFilePath ?
|
|
18124
|
+
const text = textFilePath ? readFileSync18(textFilePath, "utf8") : getArg2(args, "--text");
|
|
18068
18125
|
const threadTs = getArg2(args, "--thread");
|
|
18069
18126
|
const markdown = args.includes("--markdown");
|
|
18070
18127
|
const broadcast = args.includes("--broadcast") && Boolean(threadTs);
|
|
@@ -18159,10 +18216,10 @@ function hasClaudeBinary() {
|
|
|
18159
18216
|
}
|
|
18160
18217
|
}
|
|
18161
18218
|
function defaultClaudeConfigDir() {
|
|
18162
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
18219
|
+
return process.env.CLAUDE_CONFIG_DIR || resolve6(homedir9(), ".claude");
|
|
18163
18220
|
}
|
|
18164
18221
|
function hasClaudeCredentials(configDir2) {
|
|
18165
|
-
if (
|
|
18222
|
+
if (existsSync26(resolve6(configDir2, ".credentials.json"))) {
|
|
18166
18223
|
return true;
|
|
18167
18224
|
}
|
|
18168
18225
|
if (process.platform === "darwin") {
|
|
@@ -18199,7 +18256,7 @@ async function ensureClaudeCodeReady() {
|
|
|
18199
18256
|
console.log(" Flyspace spawns Claude Code as a subprocess. Without credentials, the bot");
|
|
18200
18257
|
console.log(" will prompt /login inside Slack at runtime and you cannot complete the");
|
|
18201
18258
|
console.log(" browser sign-in from there.\n");
|
|
18202
|
-
if (claudeConfigDir !==
|
|
18259
|
+
if (claudeConfigDir !== resolve6(homedir9(), ".claude")) {
|
|
18203
18260
|
console.log(" Tip: run with the same CLAUDE_CONFIG_DIR you use for this Claude Code install:");
|
|
18204
18261
|
console.log(` \x1B[1mCLAUDE_CONFIG_DIR=${claudeConfigDir} claude\x1B[0m
|
|
18205
18262
|
`);
|
|
@@ -18207,7 +18264,7 @@ async function ensureClaudeCodeReady() {
|
|
|
18207
18264
|
console.log(" Open a new terminal and run \x1B[1mclaude\x1B[0m to complete the sign-in flow,");
|
|
18208
18265
|
console.log(" then come back here.\n");
|
|
18209
18266
|
}
|
|
18210
|
-
const proceed = await
|
|
18267
|
+
const proceed = await confirm4({
|
|
18211
18268
|
message: "Continue setup anyway?",
|
|
18212
18269
|
default: false
|
|
18213
18270
|
});
|
|
@@ -18218,8 +18275,8 @@ async function ensureClaudeCodeReady() {
|
|
|
18218
18275
|
return { claudeConfigDir };
|
|
18219
18276
|
}
|
|
18220
18277
|
function getManifestYaml(botName) {
|
|
18221
|
-
const manifestPath =
|
|
18222
|
-
const raw =
|
|
18278
|
+
const manifestPath = resolve6(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
|
|
18279
|
+
const raw = readFileSync18(manifestPath, "utf-8");
|
|
18223
18280
|
return raw.replace(/name: Custie/g, `name: ${botName}`);
|
|
18224
18281
|
}
|
|
18225
18282
|
async function stepPersonalise() {
|
|
@@ -18432,7 +18489,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18432
18489
|
})
|
|
18433
18490
|
));
|
|
18434
18491
|
const DEBUG = process.argv.includes("--debug");
|
|
18435
|
-
const USER_DATA_DIR =
|
|
18492
|
+
const USER_DATA_DIR = resolve6(tmpdir4(), "custie-playwright-profile");
|
|
18436
18493
|
let context;
|
|
18437
18494
|
try {
|
|
18438
18495
|
log4("Opening browser \u2014 a Slack login page will appear.\n");
|
|
@@ -18464,14 +18521,14 @@ async function stepBrowserTokens(botName) {
|
|
|
18464
18521
|
const appLink = page2.locator('a[href*="/apps/A"]').first();
|
|
18465
18522
|
const hasExistingApp = await appLink.isVisible({ timeout: 3e3 }).catch(() => false);
|
|
18466
18523
|
if (hasExistingApp) {
|
|
18467
|
-
const tokenPromise = new Promise((
|
|
18524
|
+
const tokenPromise = new Promise((resolve7) => {
|
|
18468
18525
|
page2.on("request", (req) => {
|
|
18469
18526
|
if (token) return;
|
|
18470
18527
|
const postData = req.postData() || "";
|
|
18471
18528
|
const match = postData.match(/xoxc-[a-f0-9-]+/);
|
|
18472
18529
|
if (match) {
|
|
18473
18530
|
token = match[0];
|
|
18474
|
-
|
|
18531
|
+
resolve7(token);
|
|
18475
18532
|
}
|
|
18476
18533
|
});
|
|
18477
18534
|
});
|
|
@@ -18522,7 +18579,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18522
18579
|
log4("Almost done! The browser will now open an install page.\n");
|
|
18523
18580
|
console.log(' \x1B[1mPlease click "Install to Workspace", then click "Allow".\x1B[0m\n');
|
|
18524
18581
|
await page2.goto(installUrl);
|
|
18525
|
-
const installPage = await new Promise((
|
|
18582
|
+
const installPage = await new Promise((resolve7, reject) => {
|
|
18526
18583
|
let settled = false;
|
|
18527
18584
|
let interval;
|
|
18528
18585
|
let timeout;
|
|
@@ -18535,7 +18592,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18535
18592
|
};
|
|
18536
18593
|
const checkUrl = (p) => {
|
|
18537
18594
|
if (p.url().includes("install-on-team?success=1")) {
|
|
18538
|
-
finish(() =>
|
|
18595
|
+
finish(() => resolve7(p));
|
|
18539
18596
|
}
|
|
18540
18597
|
};
|
|
18541
18598
|
context.on("page", (newPage) => {
|
|
@@ -18594,7 +18651,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18594
18651
|
try {
|
|
18595
18652
|
const pages = context?.pages();
|
|
18596
18653
|
if (pages?.[0]) {
|
|
18597
|
-
const screenshotPath =
|
|
18654
|
+
const screenshotPath = resolve6(paths.PACKAGE_ROOT, "setup-error.png");
|
|
18598
18655
|
await pages[0].screenshot({ path: screenshotPath, fullPage: true });
|
|
18599
18656
|
warn4(`Screenshot saved to: ${screenshotPath}`);
|
|
18600
18657
|
}
|
|
@@ -18636,25 +18693,23 @@ function writeConfigFile(config) {
|
|
|
18636
18693
|
`OWNER_USER_ID=${config.ownerUserId}`,
|
|
18637
18694
|
``
|
|
18638
18695
|
].join("\n");
|
|
18639
|
-
|
|
18696
|
+
writeFileSync11(paths.CONFIG_FILE, envContent);
|
|
18640
18697
|
success4(`Config written to ${paths.CONFIG_FILE}`);
|
|
18641
18698
|
}
|
|
18642
18699
|
function printNextSteps() {
|
|
18643
18700
|
log4("Setup complete! Next steps:\n");
|
|
18644
|
-
console.log(" 1. Customise the system prompt (optional)
|
|
18645
|
-
console.log(` Run \x1B[1mflyspace prompt\x1B[0m to edit ${paths.PROMPT_FILE}
|
|
18646
|
-
`);
|
|
18701
|
+
console.log(" 1. Customise the system prompt (optional) in Settings \u2192 Prompt.\n");
|
|
18647
18702
|
console.log(" 2. Start the bot:");
|
|
18648
18703
|
console.log(" Run \x1B[1mflyspace start\x1B[0m\n");
|
|
18649
18704
|
console.log(" 3. Install as a background service (optional):");
|
|
18650
|
-
console.log(" Run \x1B[1mflyspace install\x1B[0m\n");
|
|
18705
|
+
console.log(" Run \x1B[1mflyspace service install\x1B[0m\n");
|
|
18651
18706
|
}
|
|
18652
18707
|
function copyDefaultPrompt(botName) {
|
|
18653
|
-
if (!
|
|
18654
|
-
const defaultPrompt =
|
|
18655
|
-
if (
|
|
18656
|
-
const content =
|
|
18657
|
-
|
|
18708
|
+
if (!existsSync26(paths.PROMPT_FILE)) {
|
|
18709
|
+
const defaultPrompt = resolve6(paths.PACKAGE_ROOT, "system.default.md");
|
|
18710
|
+
if (existsSync26(defaultPrompt)) {
|
|
18711
|
+
const content = readFileSync18(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
|
|
18712
|
+
writeFileSync11(paths.PROMPT_FILE, content);
|
|
18658
18713
|
success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
|
|
18659
18714
|
}
|
|
18660
18715
|
}
|
|
@@ -18666,8 +18721,8 @@ async function runSlackSetup(args) {
|
|
|
18666
18721
|
const prereq = await ensureClaudeCodeReady();
|
|
18667
18722
|
if (!prereq) return;
|
|
18668
18723
|
const { claudeConfigDir } = prereq;
|
|
18669
|
-
if (
|
|
18670
|
-
const reconfigure = await
|
|
18724
|
+
if (existsSync26(paths.CONFIG_FILE)) {
|
|
18725
|
+
const reconfigure = await confirm4({
|
|
18671
18726
|
message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
|
|
18672
18727
|
default: false
|
|
18673
18728
|
});
|
|
@@ -18976,7 +19031,85 @@ async function runChatCmd(args) {
|
|
|
18976
19031
|
}
|
|
18977
19032
|
|
|
18978
19033
|
// src/commands/memory.ts
|
|
18979
|
-
import { readFileSync as
|
|
19034
|
+
import { readFileSync as readFileSync20 } from "fs";
|
|
19035
|
+
|
|
19036
|
+
// src/store/claude-memory-import.ts
|
|
19037
|
+
import { existsSync as existsSync27, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "fs";
|
|
19038
|
+
import { homedir as homedir10 } from "os";
|
|
19039
|
+
import { basename as basename8, join as join24 } from "path";
|
|
19040
|
+
import dotenv2 from "dotenv";
|
|
19041
|
+
function claudeMemoryDir(cwd, configDir2) {
|
|
19042
|
+
return join24(configDir2, "projects", cwd.replaceAll(/[^a-zA-Z0-9]/g, "-"), "memory");
|
|
19043
|
+
}
|
|
19044
|
+
function profileClaudeConfigDirFor(profile) {
|
|
19045
|
+
const dir = profile === "default" ? paths.BASE_CONFIG_DIR : join24(paths.PROFILES_DIR, profile);
|
|
19046
|
+
const file = join24(dir, "config.env");
|
|
19047
|
+
const env = existsSync27(file) ? dotenv2.parse(readFileSync19(file)) : {};
|
|
19048
|
+
return env["CLAUDE_CONFIG_DIR"]?.trim() || join24(homedir10(), ".claude");
|
|
19049
|
+
}
|
|
19050
|
+
var TYPE_MAP = {
|
|
19051
|
+
user: "preference",
|
|
19052
|
+
feedback: "preference",
|
|
19053
|
+
project: "fact",
|
|
19054
|
+
reference: "reference"
|
|
19055
|
+
};
|
|
19056
|
+
var DESCRIPTION_MAX2 = 200;
|
|
19057
|
+
function unquote2(value) {
|
|
19058
|
+
const v = value.trim();
|
|
19059
|
+
return /^(["']).*\1$/.test(v) ? v.slice(1, -1) : v;
|
|
19060
|
+
}
|
|
19061
|
+
function slugify(value) {
|
|
19062
|
+
return value.toLowerCase().replaceAll(/[^a-z0-9]+/g, "-").replaceAll(/^-+|-+$/g, "");
|
|
19063
|
+
}
|
|
19064
|
+
function parseClaudeMemory(fileName, text, channelId) {
|
|
19065
|
+
const match = /^---\n([\s\S]*?)\n---\n?([\s\S]*)$/.exec(text.replaceAll("\r\n", "\n"));
|
|
19066
|
+
if (!match) return { skip: "no frontmatter" };
|
|
19067
|
+
const fields = {};
|
|
19068
|
+
for (const line of match[1].split("\n")) {
|
|
19069
|
+
const kv = /^\s*([A-Za-z_]+):\s*(.*)$/.exec(line);
|
|
19070
|
+
if (kv && kv[2] && !(kv[1] in fields)) fields[kv[1]] = unquote2(kv[2]);
|
|
19071
|
+
}
|
|
19072
|
+
const name = slugify(fields["name"] || basename8(fileName, ".md"));
|
|
19073
|
+
let description = (fields["description"] ?? "").replaceAll(/\s+/g, " ").trim();
|
|
19074
|
+
if (description.length > DESCRIPTION_MAX2) {
|
|
19075
|
+
description = `${description.slice(0, DESCRIPTION_MAX2 - 3).trimEnd()}...`;
|
|
19076
|
+
}
|
|
19077
|
+
const input2 = {
|
|
19078
|
+
channelId,
|
|
19079
|
+
name,
|
|
19080
|
+
description,
|
|
19081
|
+
type: TYPE_MAP[fields["type"] ?? ""] ?? "fact",
|
|
19082
|
+
body: match[2].trim(),
|
|
19083
|
+
createdBy: "agent"
|
|
19084
|
+
};
|
|
19085
|
+
try {
|
|
19086
|
+
validateMemoryInput(input2);
|
|
19087
|
+
} catch (err) {
|
|
19088
|
+
return { skip: err.message };
|
|
19089
|
+
}
|
|
19090
|
+
return input2;
|
|
19091
|
+
}
|
|
19092
|
+
async function importClaudeMemories(dir, channelId, opts = {}) {
|
|
19093
|
+
if (!existsSync27(dir)) throw new Error(`No Claude memory directory at ${dir}`);
|
|
19094
|
+
const files = readdirSync5(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
19095
|
+
const results = [];
|
|
19096
|
+
for (const file of files) {
|
|
19097
|
+
const parsed = parseClaudeMemory(file, readFileSync19(join24(dir, file), "utf-8"), channelId);
|
|
19098
|
+
if ("skip" in parsed) {
|
|
19099
|
+
results.push({ file, outcome: "skipped", reason: parsed.skip });
|
|
19100
|
+
continue;
|
|
19101
|
+
}
|
|
19102
|
+
if (await getChannelMemory(channelId, parsed.name)) {
|
|
19103
|
+
results.push({ file, name: parsed.name, outcome: "exists" });
|
|
19104
|
+
continue;
|
|
19105
|
+
}
|
|
19106
|
+
if (!opts.dryRun) await saveChannelMemory(parsed, "agent");
|
|
19107
|
+
results.push({ file, name: parsed.name, outcome: "imported" });
|
|
19108
|
+
}
|
|
19109
|
+
return results;
|
|
19110
|
+
}
|
|
19111
|
+
|
|
19112
|
+
// src/commands/memory.ts
|
|
18980
19113
|
var USAGE5 = `
|
|
18981
19114
|
Usage: flyspace memory <subcommand> --channel <name-or-id> [options]
|
|
18982
19115
|
|
|
@@ -18991,6 +19124,10 @@ var USAGE5 = `
|
|
|
18991
19124
|
--body <text | -> "-" reads the body from stdin
|
|
18992
19125
|
[--thread <id>] The thread the memory came from
|
|
18993
19126
|
delete <name-or-id> [--thread <id>] Delete one memory
|
|
19127
|
+
import [--dir <path>] [--dry-run] Copy Claude Code's native auto memory for the
|
|
19128
|
+
channel's folder in; names already present are kept.
|
|
19129
|
+
Defaults to the channel profile's CLAUDE_CONFIG_DIR
|
|
19130
|
+
(or ~/.claude)/projects/<cwd slug>/memory
|
|
18994
19131
|
|
|
18995
19132
|
Types: ${MEMORY_TYPES.join(", ")}
|
|
18996
19133
|
`;
|
|
@@ -19056,7 +19193,7 @@ ${memory.body}`);
|
|
|
19056
19193
|
);
|
|
19057
19194
|
}
|
|
19058
19195
|
if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
19059
|
-
const body = bodyArg === "-" ?
|
|
19196
|
+
const body = bodyArg === "-" ? readFileSync20(0, "utf-8") : bodyArg;
|
|
19060
19197
|
const thread = getArg4(rest, "--thread");
|
|
19061
19198
|
const { created, changed } = await saveChannelMemory(
|
|
19062
19199
|
{
|
|
@@ -19082,6 +19219,20 @@ ${memory.body}`);
|
|
|
19082
19219
|
console.log(`Deleted memory ${ref}.`);
|
|
19083
19220
|
break;
|
|
19084
19221
|
}
|
|
19222
|
+
case "import": {
|
|
19223
|
+
const dir = getArg4(rest, "--dir") ?? claudeMemoryDir(channel.cwd, profileClaudeConfigDirFor(channel.profile));
|
|
19224
|
+
const dryRun = rest.includes("--dry-run");
|
|
19225
|
+
const results = await importClaudeMemories(dir, channel.id, { dryRun });
|
|
19226
|
+
for (const r of results) {
|
|
19227
|
+
const detail = r.outcome === "skipped" ? r.reason : r.name;
|
|
19228
|
+
console.log(`${r.outcome.padEnd(8)} ${r.file} ${detail}`);
|
|
19229
|
+
}
|
|
19230
|
+
const count = (o) => results.filter((r) => r.outcome === o).length;
|
|
19231
|
+
console.log(
|
|
19232
|
+
`${dryRun ? "Would import" : "Imported"} ${count("imported")}, ${count("exists")} already present, ${count("skipped")} skipped, from ${dir}.`
|
|
19233
|
+
);
|
|
19234
|
+
break;
|
|
19235
|
+
}
|
|
19085
19236
|
default:
|
|
19086
19237
|
console.error(`Unknown subcommand: ${subcommand}`);
|
|
19087
19238
|
console.log(USAGE5);
|
|
@@ -19126,10 +19277,10 @@ async function runMcpCmd(args) {
|
|
|
19126
19277
|
}
|
|
19127
19278
|
|
|
19128
19279
|
// src/commands/dashboard.ts
|
|
19129
|
-
import { spawn as
|
|
19280
|
+
import { spawn as spawn5, spawnSync } from "child_process";
|
|
19130
19281
|
import { existsSync as existsSync28 } from "fs";
|
|
19131
19282
|
import { platform as platform7 } from "os";
|
|
19132
|
-
import { join as
|
|
19283
|
+
import { join as join25 } from "path";
|
|
19133
19284
|
function flagValue(args, name) {
|
|
19134
19285
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
19135
19286
|
if (eq) return eq.slice(`--${name}=`.length);
|
|
@@ -19139,13 +19290,13 @@ function flagValue(args, name) {
|
|
|
19139
19290
|
function openBrowser2(url) {
|
|
19140
19291
|
const cmd = platform7() === "darwin" ? "open" : platform7() === "win32" ? "start" : "xdg-open";
|
|
19141
19292
|
try {
|
|
19142
|
-
|
|
19293
|
+
spawn5(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
19143
19294
|
} catch {
|
|
19144
19295
|
}
|
|
19145
19296
|
}
|
|
19146
19297
|
function ensureWebBuild() {
|
|
19147
|
-
if (existsSync28(
|
|
19148
|
-
if (!existsSync28(
|
|
19298
|
+
if (existsSync28(join25(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
|
|
19299
|
+
if (!existsSync28(join25(paths.PACKAGE_ROOT, "web", "package.json"))) return;
|
|
19149
19300
|
console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
|
|
19150
19301
|
const result = spawnSync("pnpm", ["--dir", "web", "build"], {
|
|
19151
19302
|
cwd: paths.PACKAGE_ROOT,
|
|
@@ -19195,7 +19346,7 @@ async function runDashboard(args) {
|
|
|
19195
19346
|
}
|
|
19196
19347
|
|
|
19197
19348
|
// src/commands/workspace.ts
|
|
19198
|
-
import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as
|
|
19349
|
+
import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as writeFileSync12 } from "fs";
|
|
19199
19350
|
var USAGE7 = `
|
|
19200
19351
|
Usage: flyspace workspace <subcommand> [options]
|
|
19201
19352
|
|
|
@@ -19284,7 +19435,7 @@ function provisionLocal(botName) {
|
|
|
19284
19435
|
if (existsSync29(paths.CONFIG_FILE)) {
|
|
19285
19436
|
console.log(" config.env exists");
|
|
19286
19437
|
} else {
|
|
19287
|
-
|
|
19438
|
+
writeFileSync12(paths.CONFIG_FILE, `BOT_NAME=${botName}
|
|
19288
19439
|
`);
|
|
19289
19440
|
console.log(` config.env written (${paths.CONFIG_FILE})`);
|
|
19290
19441
|
}
|
|
@@ -19384,18 +19535,16 @@ var USAGE9 = `
|
|
|
19384
19535
|
setup Set up this machine as a Host (login, agent CLIs, ~/Flyspace)
|
|
19385
19536
|
auth Log in, check status, or log out this device
|
|
19386
19537
|
doctor Check this Host end to end (login, agent CLIs, services)
|
|
19387
|
-
start Start the agent host
|
|
19388
|
-
|
|
19389
|
-
uninstall Remove the system service
|
|
19538
|
+
start Start the agent host in the foreground
|
|
19539
|
+
service Run the host in the background (install / uninstall)
|
|
19390
19540
|
stop Stop the service (without uninstalling)
|
|
19391
19541
|
restart Restart the service
|
|
19392
19542
|
logs Tail the service log (-e for error log)
|
|
19393
19543
|
upgrade Upgrade Flyspace to the latest version
|
|
19394
|
-
|
|
19544
|
+
uninstall Remove Flyspace from this machine (CLI, services, config)
|
|
19395
19545
|
config Show resolved config (--edit to edit, --path for file path)
|
|
19396
19546
|
automation Manage scheduled tasks and triggers
|
|
19397
19547
|
usage Show agent token usage and cost for this workspace
|
|
19398
|
-
slack Query Slack (channels, users, post messages)
|
|
19399
19548
|
chat Inspect dashboard chat channels and move threads between them
|
|
19400
19549
|
memory Read and write a channel's persistent agent memory
|
|
19401
19550
|
profiles List local profiles (bound here? scheduler?) or remove one
|
|
@@ -19453,11 +19602,20 @@ async function main() {
|
|
|
19453
19602
|
case "reconcile":
|
|
19454
19603
|
await runReconcileCmd(args.slice(1));
|
|
19455
19604
|
break;
|
|
19605
|
+
case "service":
|
|
19606
|
+
if (args[1] === "install") await runInstall();
|
|
19607
|
+
else if (args[1] === "uninstall") await runServiceUninstall();
|
|
19608
|
+
else {
|
|
19609
|
+
printRaw("Usage: flyspace service <install|uninstall>");
|
|
19610
|
+
process.exit(1);
|
|
19611
|
+
}
|
|
19612
|
+
break;
|
|
19613
|
+
// Pre-rename spelling, kept so existing docs and scripts keep working.
|
|
19456
19614
|
case "install":
|
|
19457
19615
|
await runInstall();
|
|
19458
19616
|
break;
|
|
19459
19617
|
case "uninstall":
|
|
19460
|
-
await runUninstall();
|
|
19618
|
+
await runUninstall(args.slice(1));
|
|
19461
19619
|
break;
|
|
19462
19620
|
case "stop":
|
|
19463
19621
|
await runStop();
|
|
@@ -19471,9 +19629,6 @@ async function main() {
|
|
|
19471
19629
|
case "upgrade":
|
|
19472
19630
|
await runUpgrade();
|
|
19473
19631
|
break;
|
|
19474
|
-
case "prompt":
|
|
19475
|
-
await runPrompt();
|
|
19476
|
-
break;
|
|
19477
19632
|
case "config":
|
|
19478
19633
|
await runConfig(args.slice(1));
|
|
19479
19634
|
break;
|
|
@@ -19504,7 +19659,7 @@ async function main() {
|
|
|
19504
19659
|
break;
|
|
19505
19660
|
case "-v":
|
|
19506
19661
|
case "--version":
|
|
19507
|
-
printRaw("0.1.
|
|
19662
|
+
printRaw("0.1.12");
|
|
19508
19663
|
break;
|
|
19509
19664
|
case "-h":
|
|
19510
19665
|
case "--help":
|