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