flyspace-cli 0.1.7 → 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-BZYTXXF7.js → chunk-JG6LNX5D.js} +21 -17
- package/dist/cli.js +806 -184
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -52,6 +52,7 @@ import {
|
|
|
52
52
|
expandPath,
|
|
53
53
|
extractCategories,
|
|
54
54
|
findChannel,
|
|
55
|
+
findChannelByAgentName,
|
|
55
56
|
findChannelById,
|
|
56
57
|
findSkillDetail,
|
|
57
58
|
getAgentPaused,
|
|
@@ -106,6 +107,7 @@ import {
|
|
|
106
107
|
mcpStrictConfigEnabled,
|
|
107
108
|
mimetypeSizeLimit,
|
|
108
109
|
modelLabel,
|
|
110
|
+
pinnedClaudeConfigDir,
|
|
109
111
|
readSkillFile,
|
|
110
112
|
recentRunsForWorkspace,
|
|
111
113
|
refreshAutomationRegistry,
|
|
@@ -150,7 +152,7 @@ import {
|
|
|
150
152
|
warnOnBrokenChannelCwds,
|
|
151
153
|
workspaceSkillsDir,
|
|
152
154
|
writeSkillFile
|
|
153
|
-
} from "./chunk-
|
|
155
|
+
} from "./chunk-JG6LNX5D.js";
|
|
154
156
|
import {
|
|
155
157
|
firstReplyBody,
|
|
156
158
|
getThread,
|
|
@@ -437,7 +439,7 @@ import { confirm as confirm2 } from "@inquirer/prompts";
|
|
|
437
439
|
|
|
438
440
|
// src/service.ts
|
|
439
441
|
import { execSync } from "child_process";
|
|
440
|
-
import { existsSync as existsSync3 } from "fs";
|
|
442
|
+
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
|
|
441
443
|
import { join as join2 } from "path";
|
|
442
444
|
import { homedir, platform } from "os";
|
|
443
445
|
function serviceLabel(profile) {
|
|
@@ -466,6 +468,22 @@ function isServiceInstalled(profile) {
|
|
|
466
468
|
if (platform() === "linux") return existsSync3(systemdUnitPath(profile));
|
|
467
469
|
return false;
|
|
468
470
|
}
|
|
471
|
+
function serviceBinaryMissing(file) {
|
|
472
|
+
let text;
|
|
473
|
+
try {
|
|
474
|
+
text = readFileSync3(file, "utf-8");
|
|
475
|
+
} catch {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
const bin = text.match(/<key>ProgramArguments<\/key>\s*<array>\s*<string>([^<]+)<\/string>/)?.[1] ?? text.match(/^ExecStart=(\S+)/m)?.[1];
|
|
479
|
+
return !!bin && !existsSync3(bin);
|
|
480
|
+
}
|
|
481
|
+
function profileServiceFile(profile) {
|
|
482
|
+
return platform() === "darwin" ? plistPath(profile) : systemdUnitPath(profile);
|
|
483
|
+
}
|
|
484
|
+
function hostServiceFile() {
|
|
485
|
+
return platform() === "darwin" ? hostPlistPath() : join2(homedir(), ".config", "systemd", "user", `${HOST_SYSTEMD_UNIT}.service`);
|
|
486
|
+
}
|
|
469
487
|
function restartService(profile) {
|
|
470
488
|
const os = platform();
|
|
471
489
|
if (os === "darwin") {
|
|
@@ -497,10 +515,7 @@ function hostPlistPath() {
|
|
|
497
515
|
return join2(homedir(), "Library", "LaunchAgents", `${HOST_SERVICE_LABEL}.plist`);
|
|
498
516
|
}
|
|
499
517
|
function isHostServiceInstalled() {
|
|
500
|
-
if (platform() === "darwin") return existsSync3(
|
|
501
|
-
if (platform() === "linux") {
|
|
502
|
-
return existsSync3(join2(homedir(), ".config", "systemd", "user", `${HOST_SYSTEMD_UNIT}.service`));
|
|
503
|
-
}
|
|
518
|
+
if (platform() === "darwin" || platform() === "linux") return existsSync3(hostServiceFile());
|
|
504
519
|
return false;
|
|
505
520
|
}
|
|
506
521
|
function restartHostService() {
|
|
@@ -518,9 +533,29 @@ function restartHostService() {
|
|
|
518
533
|
}
|
|
519
534
|
return false;
|
|
520
535
|
}
|
|
536
|
+
function serviceActive(label2, unit) {
|
|
537
|
+
try {
|
|
538
|
+
if (platform() === "darwin") {
|
|
539
|
+
const out = execSync(`launchctl print gui/${process.getuid?.()}/${label2}`, { encoding: "utf-8", stdio: "pipe" });
|
|
540
|
+
return /^\s*state = running$/m.test(out);
|
|
541
|
+
}
|
|
542
|
+
if (platform() === "linux") {
|
|
543
|
+
execSync(`systemctl --user is-active --quiet ${unit}`, { stdio: "pipe" });
|
|
544
|
+
return true;
|
|
545
|
+
}
|
|
546
|
+
} catch {
|
|
547
|
+
}
|
|
548
|
+
return false;
|
|
549
|
+
}
|
|
550
|
+
function isServiceRunning(profile) {
|
|
551
|
+
return serviceActive(serviceLabel(profile), systemdUnitName(profile));
|
|
552
|
+
}
|
|
553
|
+
function isHostServiceRunning() {
|
|
554
|
+
return serviceActive(HOST_SERVICE_LABEL, HOST_SYSTEMD_UNIT);
|
|
555
|
+
}
|
|
521
556
|
|
|
522
557
|
// src/commands/install.ts
|
|
523
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as
|
|
558
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync } from "fs";
|
|
524
559
|
import { execSync as execSync2 } from "child_process";
|
|
525
560
|
import { dirname as dirname2, join as join3, resolve as resolve3 } from "path";
|
|
526
561
|
import { homedir as homedir2, platform as platform2 } from "os";
|
|
@@ -548,6 +583,11 @@ function detectClaudeBinDir() {
|
|
|
548
583
|
return void 0;
|
|
549
584
|
}
|
|
550
585
|
}
|
|
586
|
+
function nodeShimDirs() {
|
|
587
|
+
return [join3(homedir2(), ".local", "share", "mise", "shims"), join3(homedir2(), ".asdf", "shims")].filter(
|
|
588
|
+
(d) => existsSync4(d)
|
|
589
|
+
);
|
|
590
|
+
}
|
|
551
591
|
function detectCustieBin() {
|
|
552
592
|
const argv1 = process.argv[1];
|
|
553
593
|
if (argv1) {
|
|
@@ -562,7 +602,7 @@ function detectCustieBin() {
|
|
|
562
602
|
function readEnvFile() {
|
|
563
603
|
if (!existsSync4(paths.CONFIG_FILE)) return {};
|
|
564
604
|
const vars = {};
|
|
565
|
-
for (const line of
|
|
605
|
+
for (const line of readFileSync4(paths.CONFIG_FILE, "utf-8").split("\n")) {
|
|
566
606
|
const trimmed = line.trim();
|
|
567
607
|
if (!trimmed || trimmed.startsWith("#")) continue;
|
|
568
608
|
const eq = trimmed.indexOf("=");
|
|
@@ -610,12 +650,10 @@ async function installMacOS(spec) {
|
|
|
610
650
|
mkdirSync2(logDir, { recursive: true });
|
|
611
651
|
const envVars = spec.env;
|
|
612
652
|
const homebrewBin = existsSync4("/opt/homebrew/bin") ? "/opt/homebrew/bin" : void 0;
|
|
613
|
-
const asdfShims = join3(homedir2(), ".asdf", "shims");
|
|
614
|
-
const asdfBin = existsSync4(asdfShims) ? asdfShims : void 0;
|
|
615
653
|
const pathParts = [
|
|
616
654
|
dirname2(nodePath),
|
|
617
655
|
claudeBinDir,
|
|
618
|
-
|
|
656
|
+
...nodeShimDirs(),
|
|
619
657
|
homebrewBin,
|
|
620
658
|
"/usr/local/bin",
|
|
621
659
|
"/usr/bin",
|
|
@@ -684,6 +722,7 @@ async function installLinux(spec) {
|
|
|
684
722
|
const pathParts = [
|
|
685
723
|
dirname2(nodePath),
|
|
686
724
|
claudeBinDir,
|
|
725
|
+
...nodeShimDirs(),
|
|
687
726
|
linuxbrewBin,
|
|
688
727
|
"/usr/local/bin",
|
|
689
728
|
"/usr/bin",
|
|
@@ -757,7 +796,7 @@ function listProfiles() {
|
|
|
757
796
|
}
|
|
758
797
|
return profiles;
|
|
759
798
|
}
|
|
760
|
-
function
|
|
799
|
+
function isServiceRunning2(profile) {
|
|
761
800
|
try {
|
|
762
801
|
if (platform3() === "darwin") {
|
|
763
802
|
const out = execSync3("launchctl list", { encoding: "utf-8", stdio: "pipe" });
|
|
@@ -774,7 +813,7 @@ function isServiceRunning(profile) {
|
|
|
774
813
|
|
|
775
814
|
// src/auth/device-auth.ts
|
|
776
815
|
import { createPrivateKey, createPublicKey, generateKeyPairSync, sign } from "crypto";
|
|
777
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as
|
|
816
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync3, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
778
817
|
import { dirname as dirname3, join as join5 } from "path";
|
|
779
818
|
var DEFAULT_API_URL = "https://app.flyspace.ai";
|
|
780
819
|
var EXPIRY_SKEW_MS = 6e4;
|
|
@@ -802,7 +841,7 @@ function ensureDeviceKey(path = deviceKeyPath()) {
|
|
|
802
841
|
writeFileSync4(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
|
|
803
842
|
created = true;
|
|
804
843
|
}
|
|
805
|
-
const privateKey = createPrivateKey(
|
|
844
|
+
const privateKey = createPrivateKey(readFileSync5(path, "utf8"));
|
|
806
845
|
const publicKeyBase64 = createPublicKey(privateKey).export({ type: "spki", format: "der" }).toString(
|
|
807
846
|
"base64"
|
|
808
847
|
);
|
|
@@ -811,7 +850,7 @@ function ensureDeviceKey(path = deviceKeyPath()) {
|
|
|
811
850
|
function loadCredentials(path = credentialsPath()) {
|
|
812
851
|
if (!existsSync6(path)) return null;
|
|
813
852
|
try {
|
|
814
|
-
return JSON.parse(
|
|
853
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
815
854
|
} catch {
|
|
816
855
|
return null;
|
|
817
856
|
}
|
|
@@ -957,7 +996,7 @@ import {
|
|
|
957
996
|
lstatSync,
|
|
958
997
|
mkdirSync as mkdirSync4,
|
|
959
998
|
readdirSync as readdirSync2,
|
|
960
|
-
readFileSync as
|
|
999
|
+
readFileSync as readFileSync6,
|
|
961
1000
|
statSync as statSync2,
|
|
962
1001
|
symlinkSync
|
|
963
1002
|
} from "fs";
|
|
@@ -986,7 +1025,7 @@ function claudeLoggedIn() {
|
|
|
986
1025
|
if (process.env["ANTHROPIC_API_KEY"]) return true;
|
|
987
1026
|
if (existsSync7(join6(claudeDir(), ".credentials.json"))) return true;
|
|
988
1027
|
try {
|
|
989
|
-
const raw = JSON.parse(
|
|
1028
|
+
const raw = JSON.parse(readFileSync6(join6(homedir3(), ".claude.json"), "utf-8"));
|
|
990
1029
|
if (raw.oauthAccount) return true;
|
|
991
1030
|
} catch {
|
|
992
1031
|
}
|
|
@@ -1027,7 +1066,7 @@ function jsonlFiles(dir, depth) {
|
|
|
1027
1066
|
function cwdFromTranscript(file) {
|
|
1028
1067
|
let head;
|
|
1029
1068
|
try {
|
|
1030
|
-
head =
|
|
1069
|
+
head = readFileSync6(file, { encoding: "utf-8", flag: "r" }).slice(0, MAX_JSONL_HEAD_BYTES);
|
|
1031
1070
|
} catch {
|
|
1032
1071
|
return null;
|
|
1033
1072
|
}
|
|
@@ -1044,7 +1083,7 @@ function cwdFromTranscript(file) {
|
|
|
1044
1083
|
}
|
|
1045
1084
|
function codexTrustedPaths() {
|
|
1046
1085
|
try {
|
|
1047
|
-
const toml =
|
|
1086
|
+
const toml = readFileSync6(join6(codexDir(), "config.toml"), "utf-8");
|
|
1048
1087
|
return [...toml.matchAll(/^\[projects\.(?:"([^"]+)"|'([^']+)')\]/gm)].map((m) => m[1] ?? m[2]);
|
|
1049
1088
|
} catch {
|
|
1050
1089
|
return [];
|
|
@@ -1514,7 +1553,7 @@ async function runAuthCmd(args) {
|
|
|
1514
1553
|
}
|
|
1515
1554
|
|
|
1516
1555
|
// src/version.ts
|
|
1517
|
-
var CLI_VERSION = true ? "0.1.
|
|
1556
|
+
var CLI_VERSION = true ? "0.1.9" : null;
|
|
1518
1557
|
function isOlderVersion(a, b) {
|
|
1519
1558
|
const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1520
1559
|
const [pa, pb] = [parse2(a), parse2(b)];
|
|
@@ -1559,15 +1598,16 @@ async function servedWorkspaces() {
|
|
|
1559
1598
|
return bound.workspaces;
|
|
1560
1599
|
}
|
|
1561
1600
|
function checkLatestVersion() {
|
|
1562
|
-
if (!CLI_VERSION) return;
|
|
1601
|
+
if (!CLI_VERSION) return true;
|
|
1563
1602
|
let latest = "";
|
|
1564
1603
|
try {
|
|
1565
1604
|
latest = execSync4(`npm view ${"flyspace-cli"} version`, { encoding: "utf-8", stdio: "pipe", timeout: 1e4 }).trim();
|
|
1566
1605
|
} catch {
|
|
1567
|
-
return;
|
|
1606
|
+
return true;
|
|
1568
1607
|
}
|
|
1569
|
-
if (!latest || !isOlderVersion(CLI_VERSION, latest)) return;
|
|
1608
|
+
if (!latest || !isOlderVersion(CLI_VERSION, latest)) return true;
|
|
1570
1609
|
bad(`flyspace ${CLI_VERSION} is out of date (latest ${latest}) ${DIM2}run: flyspace upgrade${RESET2}`);
|
|
1610
|
+
return false;
|
|
1571
1611
|
}
|
|
1572
1612
|
function loginArgs(args) {
|
|
1573
1613
|
const out = [];
|
|
@@ -1633,7 +1673,12 @@ ${BOLD2}Flyspace Setup${RESET2}
|
|
|
1633
1673
|
}
|
|
1634
1674
|
let hostRunning = false;
|
|
1635
1675
|
if (args.includes("--start-host") && workspaces.length) {
|
|
1636
|
-
|
|
1676
|
+
for (const slug of workspaces) {
|
|
1677
|
+
if (!serviceBinaryMissing(profileServiceFile(slug))) continue;
|
|
1678
|
+
await runWithProfile(slug, runInstall);
|
|
1679
|
+
ok(`${slug} scheduler reinstalled on the new CLI path`);
|
|
1680
|
+
}
|
|
1681
|
+
if (!isHostServiceInstalled() || serviceBinaryMissing(hostServiceFile())) {
|
|
1637
1682
|
hostRunning = await installHostService();
|
|
1638
1683
|
if (hostRunning) ok(`Host running in the background ${DIM2}logs: ${tilde2(paths.LOG_DIR)}/host.log${RESET2}`);
|
|
1639
1684
|
} else {
|
|
@@ -1657,6 +1702,134 @@ ${BOLD2}Flyspace Setup${RESET2}
|
|
|
1657
1702
|
if (!workspaces.length) process.exitCode = 1;
|
|
1658
1703
|
}
|
|
1659
1704
|
|
|
1705
|
+
// src/commands/doctor.ts
|
|
1706
|
+
import { execFile } from "child_process";
|
|
1707
|
+
import { tmpdir } from "os";
|
|
1708
|
+
import { promisify } from "util";
|
|
1709
|
+
var execAsync = promisify(execFile);
|
|
1710
|
+
var exec = (cmd, args, opts) => {
|
|
1711
|
+
const run3 = execAsync(cmd, args, opts);
|
|
1712
|
+
run3.child.stdin?.end();
|
|
1713
|
+
return run3;
|
|
1714
|
+
};
|
|
1715
|
+
var BOLD3 = "\x1B[1m";
|
|
1716
|
+
var DIM3 = "\x1B[2m";
|
|
1717
|
+
var GREEN3 = "\x1B[32m";
|
|
1718
|
+
var YELLOW3 = "\x1B[33m";
|
|
1719
|
+
var RESET3 = "\x1B[0m";
|
|
1720
|
+
var HELP3 = `
|
|
1721
|
+
Usage: flyspace doctor
|
|
1722
|
+
|
|
1723
|
+
Checks this Host end to end: CLI version, server reachability, device login,
|
|
1724
|
+
a live turn with each installed agent CLI, and the background services.
|
|
1725
|
+
Exits non-zero when any check fails.
|
|
1726
|
+
`;
|
|
1727
|
+
var LOGIN_HINTS = {
|
|
1728
|
+
claude: "claude auth login",
|
|
1729
|
+
codex: "codex login"
|
|
1730
|
+
};
|
|
1731
|
+
var tilde3 = (p) => p.replace(process.env["HOME"] ?? "\0", "~");
|
|
1732
|
+
var failed = false;
|
|
1733
|
+
var ok2 = (msg) => console.log(` ${GREEN3}\u2713${RESET3} ${msg}`);
|
|
1734
|
+
var bad2 = (msg, hint) => {
|
|
1735
|
+
failed = true;
|
|
1736
|
+
console.log(` ${YELLOW3}\u2717${RESET3} ${msg}${hint ? ` ${DIM3}${hint}${RESET3}` : ""}`);
|
|
1737
|
+
};
|
|
1738
|
+
function errorLine(err) {
|
|
1739
|
+
const e = err;
|
|
1740
|
+
if (e.killed) return "timed out after 90s";
|
|
1741
|
+
const lines = `${e.stdout ?? ""}
|
|
1742
|
+
${e.stderr ?? ""}`.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1743
|
+
const last = (re) => [...lines].reverse().find((l) => re.test(l));
|
|
1744
|
+
const line = last(/expired|unauthori[sz]ed|\b401\b|not logged in|authenticate/i) ?? last(/error|fail/i) ?? lines.at(-1) ?? e.message ?? "failed";
|
|
1745
|
+
return line.length > 160 ? `${line.slice(0, 157)}...` : line;
|
|
1746
|
+
}
|
|
1747
|
+
async function probe(id) {
|
|
1748
|
+
const env = { ...process.env };
|
|
1749
|
+
const opts = { cwd: tmpdir(), timeout: 9e4, encoding: "utf-8" };
|
|
1750
|
+
try {
|
|
1751
|
+
if (id === "claude") {
|
|
1752
|
+
const dir = pinnedClaudeConfigDir(process.env["CLAUDE_CONFIG_DIR"]);
|
|
1753
|
+
if (dir) env["CLAUDE_CONFIG_DIR"] = dir;
|
|
1754
|
+
else delete env["CLAUDE_CONFIG_DIR"];
|
|
1755
|
+
await exec("claude", ["-p", "Reply with OK", "--model", "haiku", "--max-turns", "1"], { ...opts, env });
|
|
1756
|
+
} else {
|
|
1757
|
+
await exec("codex", ["exec", "--skip-git-repo-check", "-s", "read-only", "Reply with OK"], { ...opts, env });
|
|
1758
|
+
}
|
|
1759
|
+
return null;
|
|
1760
|
+
} catch (err) {
|
|
1761
|
+
return errorLine(err);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
async function serverReachable() {
|
|
1765
|
+
try {
|
|
1766
|
+
const res = await fetch(`${apiUrl()}/api/health`, { signal: AbortSignal.timeout(1e4) });
|
|
1767
|
+
return res.ok ? null : `HTTP ${res.status}`;
|
|
1768
|
+
} catch (err) {
|
|
1769
|
+
return err.cause ? String(err.cause) : err.message;
|
|
1770
|
+
}
|
|
1771
|
+
}
|
|
1772
|
+
function checkService(name, installed, file, running, fix) {
|
|
1773
|
+
if (!installed) bad2(`${name} not installed`, `run: ${fix}`);
|
|
1774
|
+
else if (serviceBinaryMissing(file)) bad2(`${name} points at a CLI that no longer exists`, `run: ${fix}`);
|
|
1775
|
+
else if (!running) bad2(`${name} not running`, `logs: ${tilde3(paths.LOG_DIR)}`);
|
|
1776
|
+
else ok2(`${name} running`);
|
|
1777
|
+
}
|
|
1778
|
+
async function runDoctor(args) {
|
|
1779
|
+
if (args.includes("-h") || args.includes("--help")) return void console.log(HELP3);
|
|
1780
|
+
failed = false;
|
|
1781
|
+
console.log(`
|
|
1782
|
+
${BOLD3}Flyspace Doctor${RESET3}
|
|
1783
|
+
`);
|
|
1784
|
+
if (checkLatestVersion()) ok2(`flyspace ${CLI_VERSION || "(dev build)"}`);
|
|
1785
|
+
else failed = true;
|
|
1786
|
+
const serverError = await serverReachable();
|
|
1787
|
+
if (serverError) bad2(`Can't reach ${apiUrl()}: ${serverError}`, "check the network or DNS");
|
|
1788
|
+
else ok2(`Server ${apiUrl()} reachable`);
|
|
1789
|
+
const creds = loadCredentials();
|
|
1790
|
+
let workspaces = [];
|
|
1791
|
+
if (!creds) {
|
|
1792
|
+
bad2("This device is not logged in", "run: flyspace setup");
|
|
1793
|
+
} else {
|
|
1794
|
+
const bound = await boundWorkspaces();
|
|
1795
|
+
workspaces = bound.workspaces;
|
|
1796
|
+
ok2(`Logged in as ${creds.userEmail} (device ${creds.deviceName})`);
|
|
1797
|
+
if (!bound.fresh) bad2("Couldn't refresh the device session; showing the workspaces saved at login");
|
|
1798
|
+
if (workspaces.length) ok2(`Serving ${workspaces.join(", ")}`);
|
|
1799
|
+
else bad2("Not serving any workspace", "a workspace admin has to add this Host");
|
|
1800
|
+
}
|
|
1801
|
+
const runners = detectRunners().filter((r) => r.installed);
|
|
1802
|
+
if (!runners.length) bad2("Neither claude nor codex is installed", "npm install -g @anthropic-ai/claude-code");
|
|
1803
|
+
const results = await Promise.all(runners.map((r) => probe(r.id)));
|
|
1804
|
+
runners.forEach((r, i) => {
|
|
1805
|
+
const error2 = results[i];
|
|
1806
|
+
if (error2) bad2(`${r.id} can't run a turn: ${error2}`, `run: ${LOGIN_HINTS[r.id]}`);
|
|
1807
|
+
else ok2(`${r.id} answered a test turn`);
|
|
1808
|
+
});
|
|
1809
|
+
if (workspaces.length) {
|
|
1810
|
+
checkService(
|
|
1811
|
+
"Host service",
|
|
1812
|
+
isHostServiceInstalled(),
|
|
1813
|
+
hostServiceFile(),
|
|
1814
|
+
isHostServiceRunning(),
|
|
1815
|
+
"flyspace setup --start-host"
|
|
1816
|
+
);
|
|
1817
|
+
for (const slug of workspaces) {
|
|
1818
|
+
checkService(
|
|
1819
|
+
`${slug} scheduler`,
|
|
1820
|
+
isServiceInstalled(slug),
|
|
1821
|
+
profileServiceFile(slug),
|
|
1822
|
+
isServiceRunning(slug),
|
|
1823
|
+
"flyspace setup --start-host"
|
|
1824
|
+
);
|
|
1825
|
+
}
|
|
1826
|
+
}
|
|
1827
|
+
console.log(failed ? "" : `
|
|
1828
|
+
${GREEN3}All good.${RESET3}
|
|
1829
|
+
`);
|
|
1830
|
+
if (failed) process.exitCode = 1;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1660
1833
|
// src/commands/uninstall.ts
|
|
1661
1834
|
import { existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
|
|
1662
1835
|
import { execSync as execSync5 } from "child_process";
|
|
@@ -1722,7 +1895,6 @@ async function runUninstall() {
|
|
|
1722
1895
|
|
|
1723
1896
|
// src/commands/profiles-cmd.ts
|
|
1724
1897
|
import { rmSync as rmSync3 } from "fs";
|
|
1725
|
-
var OPERATOR_NOTE = "This machine is an operator Host (it has Supabase credentials), so it serves every workspace, bound or not.";
|
|
1726
1898
|
var USAGE = `
|
|
1727
1899
|
Usage: flyspace profiles [list]
|
|
1728
1900
|
flyspace profiles remove <name>
|
|
@@ -1743,7 +1915,7 @@ async function listCmd() {
|
|
|
1743
1915
|
console.log("\n Profiles:\n");
|
|
1744
1916
|
console.log(` ${"NAME".padEnd(24)} ${"BOUND HERE".padEnd(12)} SCHEDULER`);
|
|
1745
1917
|
for (const profile of listProfiles()) {
|
|
1746
|
-
const scheduler = !isServiceInstalled(profile) ? "\x1B[90mnot installed\x1B[0m" :
|
|
1918
|
+
const scheduler = !isServiceInstalled(profile) ? "\x1B[90mnot installed\x1B[0m" : isServiceRunning2(profile) ? "\x1B[32mrunning\x1B[0m" : "\x1B[90mstopped\x1B[0m";
|
|
1747
1919
|
const here = bound === null ? "-".padEnd(12) : bound.includes(profile) ? "yes".padEnd(12) : `\x1B[33m${"no".padEnd(12)}\x1B[0m`;
|
|
1748
1920
|
console.log(` ${profile.padEnd(24)} ${here} ${scheduler}`);
|
|
1749
1921
|
}
|
|
@@ -1752,10 +1924,7 @@ async function listCmd() {
|
|
|
1752
1924
|
if (bound?.length === 0) {
|
|
1753
1925
|
console.log("\n This Host is not bound to any workspace, so no scheduler runs here. Run: flyspace setup");
|
|
1754
1926
|
}
|
|
1755
|
-
if (
|
|
1756
|
-
console.log(`
|
|
1757
|
-
${OPERATOR_NOTE}`);
|
|
1758
|
-
} else if (bound && listProfiles().some((p) => p !== DEFAULT_PROFILE && !bound.includes(p))) {
|
|
1927
|
+
if (bound && listProfiles().some((p) => p !== DEFAULT_PROFILE && !bound.includes(p))) {
|
|
1759
1928
|
console.log("\n Profiles not bound here run nothing on this machine. Remove one with: flyspace profiles remove <name>");
|
|
1760
1929
|
}
|
|
1761
1930
|
console.log("");
|
|
@@ -1770,10 +1939,6 @@ async function removeCmd(name) {
|
|
|
1770
1939
|
console.error(` No local profile named "${name}".`);
|
|
1771
1940
|
process.exit(1);
|
|
1772
1941
|
}
|
|
1773
|
-
if (hasSupabaseCredentials()) {
|
|
1774
|
-
console.error(` ${OPERATOR_NOTE} Removing a profile would stop that workspace's schedules.`);
|
|
1775
|
-
process.exit(1);
|
|
1776
|
-
}
|
|
1777
1942
|
const bound = await boundHere();
|
|
1778
1943
|
if (bound === null) {
|
|
1779
1944
|
console.error(" This machine has no device login, so it serves every workspace; removing a profile would be undone.");
|
|
@@ -1803,6 +1968,7 @@ async function runProfiles(args = []) {
|
|
|
1803
1968
|
|
|
1804
1969
|
// src/commands/upgrade.ts
|
|
1805
1970
|
import { execSync as execSync6 } from "child_process";
|
|
1971
|
+
import { realpathSync } from "fs";
|
|
1806
1972
|
function restartInstalledProfiles() {
|
|
1807
1973
|
if (isHostServiceInstalled()) {
|
|
1808
1974
|
console.log(restartHostService() ? "\nHost restarted." : "\nHost restart failed; run: flyspace setup --start-host --restart");
|
|
@@ -1822,8 +1988,18 @@ Restarting ${installed.length} instance(s)...`);
|
|
|
1822
1988
|
}
|
|
1823
1989
|
}
|
|
1824
1990
|
}
|
|
1991
|
+
function npmInstallCommand() {
|
|
1992
|
+
let prefix;
|
|
1993
|
+
try {
|
|
1994
|
+
const entry = realpathSync(process.argv[1] ?? "");
|
|
1995
|
+
const at = entry.indexOf(`/lib/node_modules/${"flyspace-cli"}/`);
|
|
1996
|
+
if (at > 0) prefix = entry.slice(0, at);
|
|
1997
|
+
} catch {
|
|
1998
|
+
}
|
|
1999
|
+
return `npm install -g${prefix ? ` --prefix "${prefix}"` : ""} ${"flyspace-cli"}@latest`;
|
|
2000
|
+
}
|
|
1825
2001
|
async function runUpgrade() {
|
|
1826
|
-
const current = "0.1.
|
|
2002
|
+
const current = "0.1.9";
|
|
1827
2003
|
console.log(`Current version: ${current}`);
|
|
1828
2004
|
console.log("Checking for updates...\n");
|
|
1829
2005
|
try {
|
|
@@ -1834,7 +2010,7 @@ async function runUpgrade() {
|
|
|
1834
2010
|
}
|
|
1835
2011
|
console.log(`New version available: ${latest}`);
|
|
1836
2012
|
console.log("Upgrading...\n");
|
|
1837
|
-
execSync6(
|
|
2013
|
+
execSync6(npmInstallCommand(), { stdio: "inherit" });
|
|
1838
2014
|
console.log(`
|
|
1839
2015
|
Upgraded from ${current} to ${latest}.`);
|
|
1840
2016
|
restartInstalledProfiles();
|
|
@@ -1843,7 +2019,7 @@ Upgraded from ${current} to ${latest}.`);
|
|
|
1843
2019
|
} catch (err) {
|
|
1844
2020
|
console.error("Upgrade failed:", err.message);
|
|
1845
2021
|
console.error(`
|
|
1846
|
-
You can upgrade manually with:
|
|
2022
|
+
You can upgrade manually with: ${npmInstallCommand()}`);
|
|
1847
2023
|
process.exit(1);
|
|
1848
2024
|
}
|
|
1849
2025
|
}
|
|
@@ -2195,7 +2371,7 @@ async function runAutomationCmd(args) {
|
|
|
2195
2371
|
|
|
2196
2372
|
// src/commands/usage.ts
|
|
2197
2373
|
import { existsSync as existsSync9 } from "fs";
|
|
2198
|
-
var
|
|
2374
|
+
var HELP4 = `
|
|
2199
2375
|
Usage: flyspace usage [options]
|
|
2200
2376
|
|
|
2201
2377
|
Show agent token usage and notional cost for this workspace.
|
|
@@ -2317,7 +2493,7 @@ function printExplanation(explained) {
|
|
|
2317
2493
|
async function runUsage(args) {
|
|
2318
2494
|
const parsed = parseArgs(args);
|
|
2319
2495
|
if (parsed === "help") {
|
|
2320
|
-
console.log(
|
|
2496
|
+
console.log(HELP4);
|
|
2321
2497
|
return;
|
|
2322
2498
|
}
|
|
2323
2499
|
const { days, by, explain, json } = parsed;
|
|
@@ -2463,9 +2639,9 @@ async function runLogs(args) {
|
|
|
2463
2639
|
|
|
2464
2640
|
// src/commands/slack.ts
|
|
2465
2641
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2466
|
-
import { existsSync as existsSync27, readFileSync as
|
|
2642
|
+
import { existsSync as existsSync27, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
|
|
2467
2643
|
import { resolve as resolve7 } from "path";
|
|
2468
|
-
import { tmpdir as
|
|
2644
|
+
import { tmpdir as tmpdir4, homedir as homedir9 } from "os";
|
|
2469
2645
|
import { input, confirm as confirm3, select, checkbox } from "@inquirer/prompts";
|
|
2470
2646
|
import { WebClient as WebClient3 } from "@slack/web-api";
|
|
2471
2647
|
|
|
@@ -3994,7 +4170,7 @@ async function deliverPasswordResetLink(email, redirectTo) {
|
|
|
3994
4170
|
import { existsSync as existsSync26 } from "fs";
|
|
3995
4171
|
import { createServer } from "net";
|
|
3996
4172
|
import { join as join22 } from "path";
|
|
3997
|
-
import { Hono as
|
|
4173
|
+
import { Hono as Hono33 } from "hono";
|
|
3998
4174
|
import { serve } from "@hono/node-server";
|
|
3999
4175
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4000
4176
|
|
|
@@ -4003,6 +4179,10 @@ import { existsSync as existsSync11, mkdirSync as mkdirSync6, writeFileSync as w
|
|
|
4003
4179
|
async function boundWorkspaces2() {
|
|
4004
4180
|
return (await boundWorkspaces())?.workspaces ?? null;
|
|
4005
4181
|
}
|
|
4182
|
+
async function servedWorkspaces2() {
|
|
4183
|
+
const bound = await boundWorkspaces2();
|
|
4184
|
+
return new Set(bound ?? [...(await listWorkspaces()).map((w) => w.slug), ...listProfiles()]);
|
|
4185
|
+
}
|
|
4006
4186
|
async function reconcileProfile(profile) {
|
|
4007
4187
|
return runWithProfile(profile, async () => {
|
|
4008
4188
|
const log5 = [];
|
|
@@ -4028,8 +4208,7 @@ async function reconcileProfile(profile) {
|
|
|
4028
4208
|
});
|
|
4029
4209
|
}
|
|
4030
4210
|
async function reconcileAll() {
|
|
4031
|
-
const
|
|
4032
|
-
const slugs = new Set(bound ?? [...(await listWorkspaces()).map((w) => w.slug), ...listProfiles()]);
|
|
4211
|
+
const slugs = await servedWorkspaces2();
|
|
4033
4212
|
const results = /* @__PURE__ */ new Map();
|
|
4034
4213
|
for (const slug of slugs) {
|
|
4035
4214
|
try {
|
|
@@ -4042,8 +4221,7 @@ async function reconcileAll() {
|
|
|
4042
4221
|
return results;
|
|
4043
4222
|
}
|
|
4044
4223
|
async function ensureServices() {
|
|
4045
|
-
const
|
|
4046
|
-
const slugs = new Set(bound ?? [...(await listWorkspaces()).map((w) => w.slug), ...listProfiles()]);
|
|
4224
|
+
const slugs = await servedWorkspaces2();
|
|
4047
4225
|
slugs.delete(DEFAULT_PROFILE);
|
|
4048
4226
|
const installed = [];
|
|
4049
4227
|
for (const slug of slugs) {
|
|
@@ -4192,10 +4370,10 @@ var requireDocsAccess = async (c, next) => {
|
|
|
4192
4370
|
};
|
|
4193
4371
|
|
|
4194
4372
|
// src/dashboard/api.ts
|
|
4195
|
-
import { Hono as
|
|
4373
|
+
import { Hono as Hono30 } from "hono";
|
|
4196
4374
|
|
|
4197
4375
|
// src/dashboard/public-share-routes.ts
|
|
4198
|
-
import { existsSync as existsSync12, readFileSync as
|
|
4376
|
+
import { existsSync as existsSync12, readFileSync as readFileSync8, statSync as statSync3 } from "fs";
|
|
4199
4377
|
import { join as join12 } from "path";
|
|
4200
4378
|
import { Hono as Hono3 } from "hono";
|
|
4201
4379
|
import { GetObjectCommand } from "@aws-sdk/client-s3";
|
|
@@ -4336,15 +4514,15 @@ async function createDocShare(input2) {
|
|
|
4336
4514
|
if (error2) throw new Error(`createDocShare(${input2.sourcePath}): ${error2.message}`);
|
|
4337
4515
|
return rowToShare(data);
|
|
4338
4516
|
}
|
|
4339
|
-
async function updateDocShareSnapshot(id,
|
|
4517
|
+
async function updateDocShareSnapshot(id, snapshot3, features) {
|
|
4340
4518
|
const { data, error: error2 } = await getSupabaseClient().from("doc_shares").update({
|
|
4341
|
-
title:
|
|
4342
|
-
content:
|
|
4343
|
-
asset_keys:
|
|
4519
|
+
title: snapshot3.title,
|
|
4520
|
+
content: snapshot3.content,
|
|
4521
|
+
asset_keys: snapshot3.assetKeys,
|
|
4344
4522
|
// Re-detected with the snapshot: a re-publish can carry a rewritten
|
|
4345
4523
|
// article, and one written in another language than last time is exactly
|
|
4346
4524
|
// the case a stale value would get wrong.
|
|
4347
|
-
source_lang:
|
|
4525
|
+
source_lang: snapshot3.sourceLang,
|
|
4348
4526
|
audio_enabled: features.audioEnabled,
|
|
4349
4527
|
ask_enabled: features.askEnabled,
|
|
4350
4528
|
social_enabled: features.socialEnabled,
|
|
@@ -4661,7 +4839,7 @@ function injectShareHead(html, head) {
|
|
|
4661
4839
|
|
|
4662
4840
|
// src/dashboard/app-icon.ts
|
|
4663
4841
|
import { createHash as createHash2 } from "crypto";
|
|
4664
|
-
import { readFileSync as
|
|
4842
|
+
import { readFileSync as readFileSync7 } from "fs";
|
|
4665
4843
|
import { join as join11 } from "path";
|
|
4666
4844
|
import sharp from "sharp";
|
|
4667
4845
|
var PLATE = "#0A0A0F";
|
|
@@ -4689,7 +4867,7 @@ function roundedPlate(size, fill = PLATE) {
|
|
|
4689
4867
|
}
|
|
4690
4868
|
async function renderBadge(box) {
|
|
4691
4869
|
const transparent = { r: 0, g: 0, b: 0, alpha: 0 };
|
|
4692
|
-
const raster = await sharp(
|
|
4870
|
+
const raster = await sharp(readFileSync7(MARK_SVG_FILE)).resize(512, 512, { fit: "contain", background: transparent }).png().toBuffer();
|
|
4693
4871
|
return sharp(raster).trim().resize(box, box, { fit: "contain", background: transparent }).png().toBuffer();
|
|
4694
4872
|
}
|
|
4695
4873
|
async function sampleBackground(logo) {
|
|
@@ -4744,7 +4922,7 @@ async function renderAppIcon({
|
|
|
4744
4922
|
if (hit) return hit;
|
|
4745
4923
|
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,
|
|
4746
4924
|
// either on its usual dark plate or, for a favicon, trimmed and bare.
|
|
4747
|
-
(transparent ? await renderBadge(size) : await sharp(
|
|
4925
|
+
(transparent ? await renderBadge(size) : await sharp(readFileSync7(MARK_FILE)).resize(size, size, { fit: "cover" }).png().toBuffer());
|
|
4748
4926
|
cache3.set(key, out);
|
|
4749
4927
|
return out;
|
|
4750
4928
|
}
|
|
@@ -5962,7 +6140,7 @@ var shellCache = null;
|
|
|
5962
6140
|
function readShell(path) {
|
|
5963
6141
|
const { mtimeMs } = statSync3(path);
|
|
5964
6142
|
if (shellCache?.mtimeMs === mtimeMs) return shellCache.html;
|
|
5965
|
-
const html =
|
|
6143
|
+
const html = readFileSync8(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`);
|
|
5966
6144
|
shellCache = { mtimeMs, html };
|
|
5967
6145
|
return html;
|
|
5968
6146
|
}
|
|
@@ -5987,12 +6165,12 @@ function serveBuildFile(c, webDist, dir, name) {
|
|
|
5987
6165
|
c.header("Content-Type", type);
|
|
5988
6166
|
c.header("Cache-Control", "public, max-age=31536000, immutable");
|
|
5989
6167
|
if (ext === ".css") {
|
|
5990
|
-
return c.body(
|
|
6168
|
+
return c.body(readFileSync8(path, "utf8").replaceAll("url(/fonts/", `url(${BUILD_PREFIX}/fonts/`));
|
|
5991
6169
|
}
|
|
5992
6170
|
if (ext === ".js") {
|
|
5993
|
-
return c.body(
|
|
6171
|
+
return c.body(readFileSync8(path, "utf8").replaceAll('"/assets/', `"${BUILD_PREFIX}/`));
|
|
5994
6172
|
}
|
|
5995
|
-
return c.body(new Uint8Array(
|
|
6173
|
+
return c.body(new Uint8Array(readFileSync8(path)));
|
|
5996
6174
|
}
|
|
5997
6175
|
var HTML_SHARE_CSP = "sandbox allow-scripts allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox";
|
|
5998
6176
|
function servePublishedHtml(c, share) {
|
|
@@ -6754,8 +6932,48 @@ function createAdminRouter() {
|
|
|
6754
6932
|
return admin;
|
|
6755
6933
|
}
|
|
6756
6934
|
|
|
6935
|
+
// src/dashboard/agent-identities.ts
|
|
6936
|
+
var BOT_LABEL = "Workspace Agent";
|
|
6937
|
+
var BOT_DM_KEY = "claude";
|
|
6938
|
+
async function resolveAgentIdentities() {
|
|
6939
|
+
const settings = loadDashboardSettings();
|
|
6940
|
+
const { cwd: defaultCwd } = resolveChannelConfig("");
|
|
6941
|
+
const main2 = [
|
|
6942
|
+
{
|
|
6943
|
+
name: BOT_DM_KEY,
|
|
6944
|
+
label: BOT_LABEL,
|
|
6945
|
+
kind: "main",
|
|
6946
|
+
cwd: defaultCwd,
|
|
6947
|
+
model: settings.agentProfiles?.[BOT_DM_KEY]?.model,
|
|
6948
|
+
...await loadBotIdentity()
|
|
6949
|
+
}
|
|
6950
|
+
];
|
|
6951
|
+
return Promise.all(
|
|
6952
|
+
main2.map(async ({ cwd, ...agent }) => {
|
|
6953
|
+
const dm = await ensureAgentDmChannel(agent.name, {
|
|
6954
|
+
name: agent.displayName ?? agent.label,
|
|
6955
|
+
cwd
|
|
6956
|
+
});
|
|
6957
|
+
return { ...agent, channelId: dm.id };
|
|
6958
|
+
})
|
|
6959
|
+
);
|
|
6960
|
+
}
|
|
6961
|
+
async function offlineAgentIdentities() {
|
|
6962
|
+
const dm = findChannelByAgentName(BOT_DM_KEY);
|
|
6963
|
+
if (!dm) return [];
|
|
6964
|
+
return [
|
|
6965
|
+
{
|
|
6966
|
+
name: BOT_DM_KEY,
|
|
6967
|
+
label: BOT_LABEL,
|
|
6968
|
+
kind: "main",
|
|
6969
|
+
channelId: dm.id,
|
|
6970
|
+
...await loadBotIdentity()
|
|
6971
|
+
}
|
|
6972
|
+
];
|
|
6973
|
+
}
|
|
6974
|
+
|
|
6757
6975
|
// src/dashboard/host-api.ts
|
|
6758
|
-
import { existsSync as existsSync21, readFileSync as
|
|
6976
|
+
import { existsSync as existsSync21, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
6759
6977
|
import { join as join19 } from "path";
|
|
6760
6978
|
import sharp3 from "sharp";
|
|
6761
6979
|
|
|
@@ -6994,8 +7212,8 @@ function createChannelWriteRouter() {
|
|
|
6994
7212
|
(id, position) => supabase.from("channels").update({ position, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id)
|
|
6995
7213
|
)
|
|
6996
7214
|
);
|
|
6997
|
-
const
|
|
6998
|
-
if (
|
|
7215
|
+
const failed2 = results.find((r) => r.error);
|
|
7216
|
+
if (failed2?.error) return c.json({ error: failed2.error.message }, 500);
|
|
6999
7217
|
await refreshChannelRegistry();
|
|
7000
7218
|
return c.json({ ok: true });
|
|
7001
7219
|
});
|
|
@@ -7053,8 +7271,8 @@ import {
|
|
|
7053
7271
|
openSync,
|
|
7054
7272
|
readSync,
|
|
7055
7273
|
readdirSync as readdirSync4,
|
|
7056
|
-
readFileSync as
|
|
7057
|
-
realpathSync,
|
|
7274
|
+
readFileSync as readFileSync11,
|
|
7275
|
+
realpathSync as realpathSync2,
|
|
7058
7276
|
renameSync,
|
|
7059
7277
|
rmSync as rmSync5,
|
|
7060
7278
|
statSync as statSync5,
|
|
@@ -7344,14 +7562,14 @@ async function readOrQueueDocAudio(input2) {
|
|
|
7344
7562
|
}
|
|
7345
7563
|
|
|
7346
7564
|
// src/dashboard/artifact-export.ts
|
|
7347
|
-
import { readFileSync as
|
|
7565
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
7348
7566
|
|
|
7349
7567
|
// src/dashboard/chat-agent.ts
|
|
7350
7568
|
import { homedir as homedir7 } from "os";
|
|
7351
7569
|
|
|
7352
7570
|
// src/mcp/config.ts
|
|
7353
7571
|
import { mkdtempSync, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
|
|
7354
|
-
import { tmpdir } from "os";
|
|
7572
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
7355
7573
|
import { join as join14 } from "path";
|
|
7356
7574
|
|
|
7357
7575
|
// src/mcp/crypto.ts
|
|
@@ -8038,7 +8256,7 @@ async function buildMcpConfig(profile, connectionIds) {
|
|
|
8038
8256
|
return { config, skipped };
|
|
8039
8257
|
}
|
|
8040
8258
|
function writeMcpConfigFile(config) {
|
|
8041
|
-
const dir = mkdtempSync(join14(
|
|
8259
|
+
const dir = mkdtempSync(join14(tmpdir2(), "flyspace-mcp-"));
|
|
8042
8260
|
const path = join14(dir, "mcp-config.json");
|
|
8043
8261
|
writeFileSync6(path, JSON.stringify(config), { mode: 384 });
|
|
8044
8262
|
return {
|
|
@@ -8060,7 +8278,7 @@ async function withMcpConfig(profile, connectionIds, fn) {
|
|
|
8060
8278
|
}
|
|
8061
8279
|
|
|
8062
8280
|
// src/claude/agents.ts
|
|
8063
|
-
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as
|
|
8281
|
+
import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync9 } from "fs";
|
|
8064
8282
|
import { homedir as homedir6 } from "os";
|
|
8065
8283
|
import { basename as basename4, join as join15 } from "path";
|
|
8066
8284
|
import yaml from "js-yaml";
|
|
@@ -8075,7 +8293,7 @@ function expandHome(p) {
|
|
|
8075
8293
|
function parseFrontmatter(file) {
|
|
8076
8294
|
let content;
|
|
8077
8295
|
try {
|
|
8078
|
-
content =
|
|
8296
|
+
content = readFileSync9(file, "utf-8");
|
|
8079
8297
|
} catch {
|
|
8080
8298
|
return {};
|
|
8081
8299
|
}
|
|
@@ -8121,16 +8339,241 @@ function readAgentPersona(configDir2, cwd, name) {
|
|
|
8121
8339
|
const agent = listAgents(configDir2, cwd).find((a) => a.name === name);
|
|
8122
8340
|
if (!agent) return void 0;
|
|
8123
8341
|
try {
|
|
8124
|
-
const content =
|
|
8342
|
+
const content = readFileSync9(agent.path, "utf-8");
|
|
8125
8343
|
return content.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").trim();
|
|
8126
8344
|
} catch {
|
|
8127
8345
|
return void 0;
|
|
8128
8346
|
}
|
|
8129
8347
|
}
|
|
8130
8348
|
|
|
8349
|
+
// src/store/memory-store.ts
|
|
8350
|
+
var MEMORY_TYPES = ["preference", "decision", "fact", "reference"];
|
|
8351
|
+
var NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
8352
|
+
var DESCRIPTION_MAX = 200;
|
|
8353
|
+
var BODY_MAX = 4e3;
|
|
8354
|
+
var MEMORY_INDEX_MAX_CHARS = 8e3;
|
|
8355
|
+
function isMemoryType(value) {
|
|
8356
|
+
return MEMORY_TYPES.includes(value);
|
|
8357
|
+
}
|
|
8358
|
+
function validateMemoryInput(input2) {
|
|
8359
|
+
if (!NAME_RE.test(input2.name))
|
|
8360
|
+
throw new Error("name must be a kebab-case slug, e.g. deploy-flow");
|
|
8361
|
+
if (!isMemoryType(input2.type)) throw new Error(`type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
8362
|
+
const description = input2.description.trim();
|
|
8363
|
+
if (!description || description.includes("\n") || description.length > DESCRIPTION_MAX) {
|
|
8364
|
+
throw new Error(`description must be one line of at most ${DESCRIPTION_MAX} characters`);
|
|
8365
|
+
}
|
|
8366
|
+
const body = input2.body.trim();
|
|
8367
|
+
if (!body || body.length > BODY_MAX) {
|
|
8368
|
+
throw new Error(`body must be 1 to ${BODY_MAX} characters`);
|
|
8369
|
+
}
|
|
8370
|
+
}
|
|
8371
|
+
function rowToMemory(row) {
|
|
8372
|
+
return {
|
|
8373
|
+
id: row["id"],
|
|
8374
|
+
channelId: row["channel_id"],
|
|
8375
|
+
name: row["name"],
|
|
8376
|
+
description: row["description"],
|
|
8377
|
+
type: row["type"],
|
|
8378
|
+
body: row["body"],
|
|
8379
|
+
sourceThreadId: row["source_thread_id"] ?? null,
|
|
8380
|
+
createdBy: row["created_by"],
|
|
8381
|
+
createdAt: row["created_at"],
|
|
8382
|
+
updatedAt: row["updated_at"]
|
|
8383
|
+
};
|
|
8384
|
+
}
|
|
8385
|
+
async function listChannelMemories(channelId) {
|
|
8386
|
+
const { data, error: error2 } = await getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).order("updated_at", { ascending: false });
|
|
8387
|
+
if (error2) throw new Error(`listChannelMemories(${channelId}): ${error2.message}`);
|
|
8388
|
+
return (data ?? []).map(rowToMemory);
|
|
8389
|
+
}
|
|
8390
|
+
async function getChannelMemory(channelId, name) {
|
|
8391
|
+
const { data, error: error2 } = await getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId).eq("name", name).maybeSingle();
|
|
8392
|
+
if (error2) throw new Error(`getChannelMemory(${channelId}, ${name}): ${error2.message}`);
|
|
8393
|
+
return data ? rowToMemory(data) : null;
|
|
8394
|
+
}
|
|
8395
|
+
async function saveChannelMemory(input2, actor) {
|
|
8396
|
+
validateMemoryInput(input2);
|
|
8397
|
+
const supabase = getSupabaseClient();
|
|
8398
|
+
const existing = await getChannelMemory(input2.channelId, input2.name);
|
|
8399
|
+
const fields = {
|
|
8400
|
+
description: input2.description.trim(),
|
|
8401
|
+
type: input2.type,
|
|
8402
|
+
body: input2.body.trim(),
|
|
8403
|
+
source_thread_id: input2.sourceThreadId ?? existing?.sourceThreadId ?? null,
|
|
8404
|
+
created_by: input2.createdBy
|
|
8405
|
+
};
|
|
8406
|
+
if (existing && sameContent(existing, fields)) {
|
|
8407
|
+
return { memory: existing, created: false, changed: false };
|
|
8408
|
+
}
|
|
8409
|
+
const { data, error: error2 } = existing ? await supabase.from("memories").update({ ...fields, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", existing.id).select("*").single() : await supabase.from("memories").insert({ ...fields, scope: "channel", channel_id: input2.channelId, name: input2.name }).select("*").single();
|
|
8410
|
+
if (error2)
|
|
8411
|
+
throw new Error(`saveChannelMemory(${input2.channelId}, ${input2.name}): ${error2.message}`);
|
|
8412
|
+
const memory = rowToMemory(data);
|
|
8413
|
+
await logMemoryEvent({
|
|
8414
|
+
memory,
|
|
8415
|
+
action: existing ? "update" : "create",
|
|
8416
|
+
before: existing ? snapshot(existing) : null,
|
|
8417
|
+
after: snapshot(memory),
|
|
8418
|
+
actor,
|
|
8419
|
+
threadId: input2.sourceThreadId ?? null
|
|
8420
|
+
});
|
|
8421
|
+
return { memory, created: !existing, changed: true };
|
|
8422
|
+
}
|
|
8423
|
+
async function updateChannelMemory(channelId, memoryId, patch, actor) {
|
|
8424
|
+
const existing = (await listChannelMemories(channelId)).find((m) => m.id === memoryId);
|
|
8425
|
+
if (!existing) return null;
|
|
8426
|
+
const { memory } = await saveChannelMemory(
|
|
8427
|
+
{
|
|
8428
|
+
channelId,
|
|
8429
|
+
name: existing.name,
|
|
8430
|
+
description: patch.description ?? existing.description,
|
|
8431
|
+
type: patch.type ?? existing.type,
|
|
8432
|
+
body: patch.body ?? existing.body,
|
|
8433
|
+
createdBy: existing.createdBy
|
|
8434
|
+
},
|
|
8435
|
+
actor
|
|
8436
|
+
);
|
|
8437
|
+
return memory;
|
|
8438
|
+
}
|
|
8439
|
+
async function searchChannelMemories(channelId, query) {
|
|
8440
|
+
const words = query.replaceAll(/[,()*%\\]/g, " ").split(/\s+/).filter(Boolean);
|
|
8441
|
+
if (!words.length) return [];
|
|
8442
|
+
let request2 = getSupabaseClient().from("memories").select("*").eq("scope", "channel").eq("channel_id", channelId);
|
|
8443
|
+
for (const word of words) {
|
|
8444
|
+
request2 = request2.or(`name.ilike.*${word}*,description.ilike.*${word}*,body.ilike.*${word}*`);
|
|
8445
|
+
}
|
|
8446
|
+
const { data, error: error2 } = await request2.order("updated_at", { ascending: false }).limit(20);
|
|
8447
|
+
if (error2) throw new Error(`searchChannelMemories(${channelId}): ${error2.message}`);
|
|
8448
|
+
return (data ?? []).map(rowToMemory);
|
|
8449
|
+
}
|
|
8450
|
+
async function deleteChannelMemory(channelId, idOrName, actor, threadId) {
|
|
8451
|
+
const { data, error: error2 } = await getSupabaseClient().from("memories").delete().eq("scope", "channel").eq("channel_id", channelId).eq(isUuid(idOrName) ? "id" : "name", idOrName).select("*");
|
|
8452
|
+
if (error2) throw new Error(`deleteChannelMemory(${channelId}, ${idOrName}): ${error2.message}`);
|
|
8453
|
+
const deleted = (data ?? []).map(rowToMemory);
|
|
8454
|
+
for (const memory of deleted) {
|
|
8455
|
+
await logMemoryEvent({
|
|
8456
|
+
memory,
|
|
8457
|
+
action: "delete",
|
|
8458
|
+
before: snapshot(memory),
|
|
8459
|
+
after: null,
|
|
8460
|
+
actor,
|
|
8461
|
+
threadId: threadId ?? null
|
|
8462
|
+
});
|
|
8463
|
+
}
|
|
8464
|
+
return deleted.length > 0;
|
|
8465
|
+
}
|
|
8466
|
+
function snapshot(m) {
|
|
8467
|
+
return {
|
|
8468
|
+
description: m.description,
|
|
8469
|
+
type: m.type,
|
|
8470
|
+
body: m.body,
|
|
8471
|
+
sourceThreadId: m.sourceThreadId,
|
|
8472
|
+
createdBy: m.createdBy
|
|
8473
|
+
};
|
|
8474
|
+
}
|
|
8475
|
+
function sameContent(m, other) {
|
|
8476
|
+
return m.description === other.description && m.type === other.type && m.body === other.body;
|
|
8477
|
+
}
|
|
8478
|
+
async function logMemoryEvent(e) {
|
|
8479
|
+
const { error: error2 } = await getSupabaseClient().from("memory_events").insert({
|
|
8480
|
+
channel_id: e.memory.channelId,
|
|
8481
|
+
memory_id: e.action === "delete" ? null : e.memory.id,
|
|
8482
|
+
thread_id: e.threadId,
|
|
8483
|
+
name: e.memory.name,
|
|
8484
|
+
action: e.action,
|
|
8485
|
+
before: e.before,
|
|
8486
|
+
after: e.after,
|
|
8487
|
+
actor: e.actor
|
|
8488
|
+
});
|
|
8489
|
+
if (error2) console.error(`[memory] event log failed for ${e.memory.name}:`, error2.message);
|
|
8490
|
+
}
|
|
8491
|
+
function rowToEvent(row) {
|
|
8492
|
+
return {
|
|
8493
|
+
id: row["id"],
|
|
8494
|
+
channelId: row["channel_id"],
|
|
8495
|
+
memoryId: row["memory_id"] ?? null,
|
|
8496
|
+
threadId: row["thread_id"] ?? null,
|
|
8497
|
+
name: row["name"],
|
|
8498
|
+
action: row["action"],
|
|
8499
|
+
before: row["before"] ?? null,
|
|
8500
|
+
after: row["after"] ?? null,
|
|
8501
|
+
actor: row["actor"],
|
|
8502
|
+
createdAt: row["created_at"],
|
|
8503
|
+
undoneAt: row["undone_at"] ?? null
|
|
8504
|
+
};
|
|
8505
|
+
}
|
|
8506
|
+
async function listThreadMemoryEvents(channelId, threadId) {
|
|
8507
|
+
const { data, error: error2 } = await getSupabaseClient().from("memory_events").select("*").eq("channel_id", channelId).eq("thread_id", threadId).order("created_at", { ascending: false }).limit(50);
|
|
8508
|
+
if (error2) throw new Error(`listThreadMemoryEvents(${threadId}): ${error2.message}`);
|
|
8509
|
+
return (data ?? []).map(rowToEvent);
|
|
8510
|
+
}
|
|
8511
|
+
async function undoMemoryEvent(channelId, eventId) {
|
|
8512
|
+
const supabase = getSupabaseClient();
|
|
8513
|
+
const { data, error: error2 } = await supabase.from("memory_events").select("*").eq("id", eventId).eq("channel_id", channelId).maybeSingle();
|
|
8514
|
+
if (error2) throw new Error(`undoMemoryEvent(${eventId}): ${error2.message}`);
|
|
8515
|
+
if (!data) return "not-found";
|
|
8516
|
+
const event = rowToEvent(data);
|
|
8517
|
+
if (event.undoneAt) return "already-undone";
|
|
8518
|
+
const current = await getChannelMemory(channelId, event.name);
|
|
8519
|
+
const unchangedSince = current && event.after ? sameContent(current, event.after) : !current && !event.after;
|
|
8520
|
+
if (!unchangedSince) return "conflict";
|
|
8521
|
+
const claimed = await supabase.from("memory_events").update({ undone_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", eventId).is("undone_at", null).select("id");
|
|
8522
|
+
if (claimed.error) throw new Error(`undoMemoryEvent(${eventId}): ${claimed.error.message}`);
|
|
8523
|
+
if (!(claimed.data ?? []).length) return "already-undone";
|
|
8524
|
+
const before = event.before;
|
|
8525
|
+
const restore = before ? {
|
|
8526
|
+
description: before.description,
|
|
8527
|
+
type: before.type,
|
|
8528
|
+
body: before.body,
|
|
8529
|
+
source_thread_id: before.sourceThreadId,
|
|
8530
|
+
created_by: before.createdBy
|
|
8531
|
+
} : null;
|
|
8532
|
+
const { error: writeError } = !restore ? await supabase.from("memories").delete().eq("id", current.id) : current ? await supabase.from("memories").update({ ...restore, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", current.id) : await supabase.from("memories").insert({ ...restore, scope: "channel", channel_id: channelId, name: event.name });
|
|
8533
|
+
if (writeError) {
|
|
8534
|
+
await supabase.from("memory_events").update({ undone_at: null }).eq("id", eventId);
|
|
8535
|
+
throw new Error(`undoMemoryEvent(${eventId}): ${writeError.message}`);
|
|
8536
|
+
}
|
|
8537
|
+
return "undone";
|
|
8538
|
+
}
|
|
8539
|
+
function isUuid(value) {
|
|
8540
|
+
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
8541
|
+
}
|
|
8542
|
+
function formatMemoryPrompt(channelId, memories) {
|
|
8543
|
+
const lines = [];
|
|
8544
|
+
let used = 0;
|
|
8545
|
+
let omitted = 0;
|
|
8546
|
+
for (const m of memories) {
|
|
8547
|
+
const line = `- \`${m.name}\` (${m.type}): ${m.description}`;
|
|
8548
|
+
if (used + line.length > MEMORY_INDEX_MAX_CHARS) {
|
|
8549
|
+
omitted++;
|
|
8550
|
+
continue;
|
|
8551
|
+
}
|
|
8552
|
+
lines.push(line);
|
|
8553
|
+
used += line.length + 1;
|
|
8554
|
+
}
|
|
8555
|
+
const index = lines.length ? lines.join("\n") + (omitted ? `
|
|
8556
|
+
- (${omitted} older memories not listed; find them with \`memory search\`)` : "") : "(none yet)";
|
|
8557
|
+
return [
|
|
8558
|
+
`## Channel memory`,
|
|
8559
|
+
``,
|
|
8560
|
+
`This channel has a persistent memory shared by every thread in it and by every agent runner. Use it instead of any built-in memory directory of your own CLI.`,
|
|
8561
|
+
``,
|
|
8562
|
+
`- Read a memory: \`flyspace memory get --channel ${channelId} <name>\``,
|
|
8563
|
+
`- Search: \`flyspace memory search --channel ${channelId} <words>\``,
|
|
8564
|
+
`- Save or overwrite: \`flyspace memory save --channel ${channelId} --name <kebab-slug> --type <${MEMORY_TYPES.join("|")}> --description "<one line>" --body "<text>" [--thread <thread-id>]\``,
|
|
8565
|
+
`- Delete one that turned out wrong: \`flyspace memory delete --channel ${channelId} <name> [--thread <thread-id>]\``,
|
|
8566
|
+
``,
|
|
8567
|
+
`Save when someone asks you to remember something, corrects you, or a decision is made that later threads will need. Save what is not derivable from the code, git history or docs: decisions and why, preferences, corrections, where external things live. Do not save one-off details of this conversation. Check the index first and update an existing memory rather than adding a near-duplicate. Write memories in English. Pass \`--thread\` with the thread id from the \`[context: ...]\` header, so the thread shows what changed and it can be undone there.`,
|
|
8568
|
+
``,
|
|
8569
|
+
`Index:`,
|
|
8570
|
+
index
|
|
8571
|
+
].join("\n");
|
|
8572
|
+
}
|
|
8573
|
+
|
|
8131
8574
|
// src/dashboard/rollover-summary.ts
|
|
8132
8575
|
import { spawn as spawn5 } from "child_process";
|
|
8133
|
-
import { tmpdir as
|
|
8576
|
+
import { tmpdir as tmpdir3 } from "os";
|
|
8134
8577
|
var HAIKU_MODEL = "haiku";
|
|
8135
8578
|
var CJK_PATTERN = /[ -〿-ヿ㐀-䶿一-鿿豈--]/;
|
|
8136
8579
|
function estimateTokens(text) {
|
|
@@ -8193,7 +8636,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
|
8193
8636
|
// A stateless rewrite touches no files — tmpdir keeps it that way and
|
|
8194
8637
|
// avoids picking up an unrelated project's own CLAUDE.md/AGENTS.md,
|
|
8195
8638
|
// unlike the channel's own cwd the main turn runs in.
|
|
8196
|
-
cwd:
|
|
8639
|
+
cwd: tmpdir3(),
|
|
8197
8640
|
env,
|
|
8198
8641
|
stdio: ["ignore", "pipe", "pipe"]
|
|
8199
8642
|
});
|
|
@@ -8989,6 +9432,13 @@ var ChatAgent = class {
|
|
|
8989
9432
|
thread.channel_id
|
|
8990
9433
|
);
|
|
8991
9434
|
if (docsAccess && docsAccess !== "write") channelConfig = { ...channelConfig, docsAccess };
|
|
9435
|
+
if (docsAccess !== "none" && isUuid(thread.channel_id)) {
|
|
9436
|
+
const memoryPrompt = await listChannelMemories(thread.channel_id).then((memories) => formatMemoryPrompt(thread.channel_id, memories)).catch((err) => {
|
|
9437
|
+
console.error("[chat-agent] memory lookup failed:", err.message);
|
|
9438
|
+
return void 0;
|
|
9439
|
+
});
|
|
9440
|
+
if (memoryPrompt) channelConfig = { ...channelConfig, memoryPrompt };
|
|
9441
|
+
}
|
|
8992
9442
|
const store = this.openSessionStore();
|
|
8993
9443
|
try {
|
|
8994
9444
|
const channelKey = `chat:${thread.channel_id}`;
|
|
@@ -9396,7 +9846,8 @@ ${msg.body}` : msg.body;
|
|
|
9396
9846
|
signal,
|
|
9397
9847
|
mcpConfigPath,
|
|
9398
9848
|
...usageContext ? { usageContext } : {},
|
|
9399
|
-
...channelConfig.docsAccess ? { docsAccess: channelConfig.docsAccess } : {}
|
|
9849
|
+
...channelConfig.docsAccess ? { docsAccess: channelConfig.docsAccess } : {},
|
|
9850
|
+
...channelConfig.memoryPrompt ? { memoryPrompt: channelConfig.memoryPrompt } : {}
|
|
9400
9851
|
},
|
|
9401
9852
|
channelConfig.claudeConfigDir,
|
|
9402
9853
|
sessionId,
|
|
@@ -9433,6 +9884,7 @@ ${msg.body}` : msg.body;
|
|
|
9433
9884
|
channelConfig.prompt,
|
|
9434
9885
|
resolveSelectedOutputStyle(channelConfig.claudeConfigDir)?.body
|
|
9435
9886
|
),
|
|
9887
|
+
channelConfig.memoryPrompt,
|
|
9436
9888
|
docsAccessPromptNote(channelConfig.docsAccess)
|
|
9437
9889
|
].filter(Boolean).join("\n\n---\n\n"),
|
|
9438
9890
|
// Codex has no per-tool deny list, so any restricted requester gets
|
|
@@ -9710,7 +10162,7 @@ async function exportDocAsArtifact(channelId, docPath) {
|
|
|
9710
10162
|
);
|
|
9711
10163
|
const abs = resolveDocPath(channelConfig.cwd, docPath);
|
|
9712
10164
|
if (!abs) throw new Error(`doc not found: ${docPath}`);
|
|
9713
|
-
const content =
|
|
10165
|
+
const content = readFileSync10(abs, "utf8");
|
|
9714
10166
|
const title = extractTitle(abs, "md");
|
|
9715
10167
|
const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
9716
10168
|
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).
|
|
@@ -9989,12 +10441,12 @@ function readGitBranch(dir) {
|
|
|
9989
10441
|
try {
|
|
9990
10442
|
let gitDir = join17(dir, ".git");
|
|
9991
10443
|
if (statSync5(gitDir).isFile()) {
|
|
9992
|
-
const pointer =
|
|
10444
|
+
const pointer = readFileSync11(gitDir, "utf8").trim();
|
|
9993
10445
|
const match = pointer.match(/^gitdir:\s*(.+)$/);
|
|
9994
10446
|
if (!match) return void 0;
|
|
9995
10447
|
gitDir = resolve5(dir, match[1]);
|
|
9996
10448
|
}
|
|
9997
|
-
const head =
|
|
10449
|
+
const head = readFileSync11(join17(gitDir, "HEAD"), "utf8").trim();
|
|
9998
10450
|
const branchMatch = head.match(/^ref:\s*refs\/heads\/(.+)$/);
|
|
9999
10451
|
if (branchMatch) return branchMatch[1];
|
|
10000
10452
|
return /^[0-9a-f]{7,40}$/i.test(head) ? head.slice(0, 7) : void 0;
|
|
@@ -10021,8 +10473,8 @@ function listDocLevel(root, relDir = "", sort = "newest", showHidden = false) {
|
|
|
10021
10473
|
const kind = entryKind(entry, abs);
|
|
10022
10474
|
if (kind === "dir") {
|
|
10023
10475
|
if (SKIP_DIRS.has(entry.name)) continue;
|
|
10024
|
-
const
|
|
10025
|
-
if (!
|
|
10476
|
+
const probe3 = probeDir(abs, showHidden);
|
|
10477
|
+
if (!probe3.hasDocs && probe3.hasOther) continue;
|
|
10026
10478
|
const isRepo = existsSync17(join17(abs, ".git"));
|
|
10027
10479
|
dirs.push({
|
|
10028
10480
|
path,
|
|
@@ -10241,7 +10693,7 @@ function moveDocEntry(root, from, to) {
|
|
|
10241
10693
|
return { status: "into-self" };
|
|
10242
10694
|
}
|
|
10243
10695
|
try {
|
|
10244
|
-
if (
|
|
10696
|
+
if (realpathSync2(finalAbs) !== realpathSync2(absFrom)) return { status: "exists" };
|
|
10245
10697
|
} catch {
|
|
10246
10698
|
}
|
|
10247
10699
|
renameSync(absFrom, finalAbs);
|
|
@@ -10298,7 +10750,7 @@ function createDocsRouter() {
|
|
|
10298
10750
|
}
|
|
10299
10751
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
10300
10752
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
10301
|
-
const data =
|
|
10753
|
+
const data = readFileSync11(resolved2.abs);
|
|
10302
10754
|
const headers = { "Content-Type": resolved2.contentType };
|
|
10303
10755
|
if (download) {
|
|
10304
10756
|
const filename = basename5(resolved2.abs).replace(/"/g, "");
|
|
@@ -10318,7 +10770,7 @@ function createDocsRouter() {
|
|
|
10318
10770
|
}
|
|
10319
10771
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
10320
10772
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
10321
|
-
return c.body(new Uint8Array(
|
|
10773
|
+
return c.body(new Uint8Array(readFileSync11(abs)), 200, {
|
|
10322
10774
|
"Content-Type": "text/html; charset=utf-8",
|
|
10323
10775
|
"Content-Security-Policy": HTML_SHARE_CSP,
|
|
10324
10776
|
"X-Content-Type-Options": "nosniff",
|
|
@@ -10334,10 +10786,10 @@ function createDocsRouter() {
|
|
|
10334
10786
|
const files = form.getAll("file").filter((f) => f instanceof File);
|
|
10335
10787
|
if (files.length === 0) return c.json({ error: "missing file" }, 400);
|
|
10336
10788
|
const uploaded = [];
|
|
10337
|
-
const
|
|
10789
|
+
const failed2 = [];
|
|
10338
10790
|
for (const file of files) {
|
|
10339
10791
|
if (file.size > MAX_UPLOAD_BYTES) {
|
|
10340
|
-
|
|
10792
|
+
failed2.push({
|
|
10341
10793
|
name: file.name,
|
|
10342
10794
|
error: `larger than ${MAX_UPLOAD_BYTES / (1024 * 1024)}MB`
|
|
10343
10795
|
});
|
|
@@ -10351,16 +10803,16 @@ function createDocsRouter() {
|
|
|
10351
10803
|
case "no-dir":
|
|
10352
10804
|
return c.json({ error: "folder not found" }, 404);
|
|
10353
10805
|
case "bad-type":
|
|
10354
|
-
|
|
10806
|
+
failed2.push({ name: file.name, error: "file type not allowed" });
|
|
10355
10807
|
break;
|
|
10356
10808
|
case "too-large":
|
|
10357
|
-
|
|
10809
|
+
failed2.push({ name: file.name, error: "file too large" });
|
|
10358
10810
|
break;
|
|
10359
10811
|
default:
|
|
10360
|
-
|
|
10812
|
+
failed2.push({ name: file.name, error: "invalid filename" });
|
|
10361
10813
|
}
|
|
10362
10814
|
}
|
|
10363
|
-
return c.json({ path: dir, uploaded, failed }, uploaded.length > 0 ? 201 : 400);
|
|
10815
|
+
return c.json({ path: dir, uploaded, failed: failed2 }, uploaded.length > 0 ? 201 : 400);
|
|
10364
10816
|
});
|
|
10365
10817
|
app.post("/:id/docs/folder", async (c) => {
|
|
10366
10818
|
const { cwd } = resolveChannelConfig(c.req.param("id"));
|
|
@@ -10503,7 +10955,7 @@ function createDocsRouter() {
|
|
|
10503
10955
|
title: kind ? extractTitle(abs, kind) : basename5(abs),
|
|
10504
10956
|
mtime: stat.mtime.toISOString(),
|
|
10505
10957
|
tooLarge: tooLarge || void 0,
|
|
10506
|
-
content: tooLarge ? "" :
|
|
10958
|
+
content: tooLarge ? "" : readFileSync11(abs, "utf8")
|
|
10507
10959
|
});
|
|
10508
10960
|
});
|
|
10509
10961
|
app.put("/:id/docs/content", async (c) => {
|
|
@@ -10546,7 +10998,7 @@ function createDocsRouter() {
|
|
|
10546
10998
|
channel,
|
|
10547
10999
|
rel,
|
|
10548
11000
|
title: extractTitle(abs, "md"),
|
|
10549
|
-
content:
|
|
11001
|
+
content: readFileSync11(abs, "utf8")
|
|
10550
11002
|
};
|
|
10551
11003
|
};
|
|
10552
11004
|
app.get("/:id/docs/translation/:lang", async (c) => {
|
|
@@ -10769,7 +11221,7 @@ function ownSkills(scope, dir) {
|
|
|
10769
11221
|
return [{ scope, name, description, body, path, plugin: null, mtime }];
|
|
10770
11222
|
});
|
|
10771
11223
|
}
|
|
10772
|
-
function
|
|
11224
|
+
function snapshot2(scopes) {
|
|
10773
11225
|
const wants = (scope) => !scopes || scopes.has(scope);
|
|
10774
11226
|
const configDir2 = profileClaudeConfigDir();
|
|
10775
11227
|
const rows = [];
|
|
@@ -10804,7 +11256,7 @@ async function pushSkillScopes(scopes) {
|
|
|
10804
11256
|
if (!mirrorEnabled()) return;
|
|
10805
11257
|
const workspace = getProfile();
|
|
10806
11258
|
const supabase = getSupabaseClient();
|
|
10807
|
-
const rows =
|
|
11259
|
+
const rows = snapshot2(scopes ? new Set(scopes) : void 0);
|
|
10808
11260
|
let query = supabase.from("skill_files").select("scope, name, hash").eq("workspace", workspace);
|
|
10809
11261
|
if (scopes) query = query.in("scope", scopes);
|
|
10810
11262
|
const { data, error: error2 } = await query;
|
|
@@ -11084,32 +11536,6 @@ function registerMirroredSkillRoutes(api, agent) {
|
|
|
11084
11536
|
mountSkillRoutes(api, "/channels/:id/skill-files", channelRef, backend);
|
|
11085
11537
|
}
|
|
11086
11538
|
|
|
11087
|
-
// src/dashboard/agent-identities.ts
|
|
11088
|
-
var BOT_DM_KEY = "claude";
|
|
11089
|
-
async function resolveAgentIdentities() {
|
|
11090
|
-
const settings = loadDashboardSettings();
|
|
11091
|
-
const { cwd: defaultCwd } = resolveChannelConfig("");
|
|
11092
|
-
const main2 = [
|
|
11093
|
-
{
|
|
11094
|
-
name: BOT_DM_KEY,
|
|
11095
|
-
label: "Workspace Agent",
|
|
11096
|
-
kind: "main",
|
|
11097
|
-
cwd: defaultCwd,
|
|
11098
|
-
model: settings.agentProfiles?.[BOT_DM_KEY]?.model,
|
|
11099
|
-
...await loadBotIdentity()
|
|
11100
|
-
}
|
|
11101
|
-
];
|
|
11102
|
-
return Promise.all(
|
|
11103
|
-
main2.map(async ({ cwd, ...agent }) => {
|
|
11104
|
-
const dm = await ensureAgentDmChannel(agent.name, {
|
|
11105
|
-
name: agent.displayName ?? agent.label,
|
|
11106
|
-
cwd
|
|
11107
|
-
});
|
|
11108
|
-
return { ...agent, channelId: dm.id };
|
|
11109
|
-
})
|
|
11110
|
-
);
|
|
11111
|
-
}
|
|
11112
|
-
|
|
11113
11539
|
// src/dashboard/providers.ts
|
|
11114
11540
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
11115
11541
|
import { existsSync as existsSync20 } from "fs";
|
|
@@ -11143,7 +11569,7 @@ function claudeAuthAvailable() {
|
|
|
11143
11569
|
return false;
|
|
11144
11570
|
}
|
|
11145
11571
|
}
|
|
11146
|
-
function
|
|
11572
|
+
function probe2(runner) {
|
|
11147
11573
|
const label2 = PROVIDER_LABELS[runner];
|
|
11148
11574
|
const bin = runner === "codex" ? "codex" : "claude";
|
|
11149
11575
|
if (!binaryOnPath(bin)) {
|
|
@@ -11164,7 +11590,7 @@ function providerStatuses() {
|
|
|
11164
11590
|
const key = getProfile();
|
|
11165
11591
|
const hit = cache6.get(key);
|
|
11166
11592
|
if (hit && Date.now() - hit.at < TTL_MS2) return hit.statuses;
|
|
11167
|
-
const statuses = ["claude", "codex"].map(
|
|
11593
|
+
const statuses = ["claude", "codex"].map(probe2);
|
|
11168
11594
|
cache6.set(key, { at: Date.now(), statuses });
|
|
11169
11595
|
return statuses;
|
|
11170
11596
|
}
|
|
@@ -11287,7 +11713,7 @@ function registerHostRoutes(api) {
|
|
|
11287
11713
|
}
|
|
11288
11714
|
function tailFile(file, limit) {
|
|
11289
11715
|
if (!existsSync21(file)) return [];
|
|
11290
|
-
const lines =
|
|
11716
|
+
const lines = readFileSync12(file, "utf-8").split("\n");
|
|
11291
11717
|
if (lines.at(-1) === "") lines.pop();
|
|
11292
11718
|
return lines.slice(-limit);
|
|
11293
11719
|
}
|
|
@@ -11399,8 +11825,8 @@ function createCategoryWriteRouter() {
|
|
|
11399
11825
|
(id, position) => supabase.from("categories").update({ position, updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id).eq("profile", getProfile())
|
|
11400
11826
|
)
|
|
11401
11827
|
);
|
|
11402
|
-
const
|
|
11403
|
-
if (
|
|
11828
|
+
const failed2 = results.find((r) => r.error);
|
|
11829
|
+
if (failed2?.error) return c.json({ error: failed2.error.message }, 500);
|
|
11404
11830
|
return c.json({ ok: true });
|
|
11405
11831
|
});
|
|
11406
11832
|
app.put("/:id", async (c) => {
|
|
@@ -14354,8 +14780,80 @@ function createChannelMembersRouter() {
|
|
|
14354
14780
|
return router;
|
|
14355
14781
|
}
|
|
14356
14782
|
|
|
14357
|
-
// src/dashboard/
|
|
14783
|
+
// src/dashboard/memories-routes.ts
|
|
14358
14784
|
import { Hono as Hono27 } from "hono";
|
|
14785
|
+
var requireChannelVisible = async (c, next) => {
|
|
14786
|
+
const channel = findChannel(c.req.param("id"));
|
|
14787
|
+
if (!channel || !canSeeChannel(channel, c.get("auth"))) {
|
|
14788
|
+
return c.json({ error: "channel not found" }, 404);
|
|
14789
|
+
}
|
|
14790
|
+
c.set("memoryChannelId", channel.id);
|
|
14791
|
+
await next();
|
|
14792
|
+
};
|
|
14793
|
+
function createMemoriesRouter() {
|
|
14794
|
+
const router = new Hono27();
|
|
14795
|
+
for (const path of [
|
|
14796
|
+
"/:id/memories",
|
|
14797
|
+
"/:id/memories/*",
|
|
14798
|
+
"/:id/memory-events",
|
|
14799
|
+
"/:id/memory-events/*"
|
|
14800
|
+
]) {
|
|
14801
|
+
router.use(path, requireChannelVisible, requireDocsAccess);
|
|
14802
|
+
}
|
|
14803
|
+
router.get("/:id/memories", async (c) => {
|
|
14804
|
+
return c.json({ memories: await listChannelMemories(c.get("memoryChannelId")) });
|
|
14805
|
+
});
|
|
14806
|
+
router.patch("/:id/memories/:memoryId", async (c) => {
|
|
14807
|
+
const memoryId = c.req.param("memoryId");
|
|
14808
|
+
if (!isUuid(memoryId)) return c.json({ error: "invalid memory id" }, 400);
|
|
14809
|
+
const body = await c.req.json().catch(() => ({}));
|
|
14810
|
+
const patch = {};
|
|
14811
|
+
if (typeof body["description"] === "string") patch.description = body["description"];
|
|
14812
|
+
if (typeof body["body"] === "string") patch.body = body["body"];
|
|
14813
|
+
const type = body["type"];
|
|
14814
|
+
if (type !== void 0) {
|
|
14815
|
+
if (!isMemoryType(type)) return c.json({ error: "invalid type" }, 400);
|
|
14816
|
+
patch.type = type;
|
|
14817
|
+
}
|
|
14818
|
+
try {
|
|
14819
|
+
const memory = await updateChannelMemory(c.get("memoryChannelId"), memoryId, patch, "user");
|
|
14820
|
+
if (!memory) return c.json({ error: "memory not found" }, 404);
|
|
14821
|
+
return c.json({ memory });
|
|
14822
|
+
} catch (err) {
|
|
14823
|
+
return c.json({ error: err.message }, 400);
|
|
14824
|
+
}
|
|
14825
|
+
});
|
|
14826
|
+
router.delete("/:id/memories/:memoryId", async (c) => {
|
|
14827
|
+
const memoryId = c.req.param("memoryId");
|
|
14828
|
+
if (!isUuid(memoryId)) return c.json({ error: "invalid memory id" }, 400);
|
|
14829
|
+
const deleted = await deleteChannelMemory(c.get("memoryChannelId"), memoryId, "user");
|
|
14830
|
+
if (!deleted) return c.json({ error: "memory not found" }, 404);
|
|
14831
|
+
return c.json({ ok: true });
|
|
14832
|
+
});
|
|
14833
|
+
router.get("/:id/memory-events", async (c) => {
|
|
14834
|
+
const threadId = c.req.query("thread") ?? "";
|
|
14835
|
+
if (!isUuid(threadId)) return c.json({ error: "thread query parameter required" }, 400);
|
|
14836
|
+
return c.json({ events: await listThreadMemoryEvents(c.get("memoryChannelId"), threadId) });
|
|
14837
|
+
});
|
|
14838
|
+
router.post("/:id/memory-events/:eventId/undo", async (c) => {
|
|
14839
|
+
const eventId = c.req.param("eventId");
|
|
14840
|
+
if (!isUuid(eventId)) return c.json({ error: "invalid event id" }, 400);
|
|
14841
|
+
const result = await undoMemoryEvent(c.get("memoryChannelId"), eventId);
|
|
14842
|
+
if (result === "not-found") return c.json({ error: "event not found" }, 404);
|
|
14843
|
+
if (result === "already-undone") return c.json({ error: "already undone" }, 409);
|
|
14844
|
+
if (result === "conflict") {
|
|
14845
|
+
return c.json(
|
|
14846
|
+
{ error: "The memory has changed since, so this can no longer be undone." },
|
|
14847
|
+
409
|
|
14848
|
+
);
|
|
14849
|
+
}
|
|
14850
|
+
return c.json({ ok: true });
|
|
14851
|
+
});
|
|
14852
|
+
return router;
|
|
14853
|
+
}
|
|
14854
|
+
|
|
14855
|
+
// src/dashboard/onboarding-routes.ts
|
|
14856
|
+
import { Hono as Hono28 } from "hono";
|
|
14359
14857
|
|
|
14360
14858
|
// src/dashboard/unique-channel-name.ts
|
|
14361
14859
|
function uniqueChannelName(raw, taken) {
|
|
@@ -14444,7 +14942,7 @@ async function buildGreeting(input2) {
|
|
|
14444
14942
|
// src/dashboard/onboarding-routes.ts
|
|
14445
14943
|
var MAX_FOLDERS = 20;
|
|
14446
14944
|
function createOnboardingRouter(agent) {
|
|
14447
|
-
const app = new
|
|
14945
|
+
const app = new Hono28();
|
|
14448
14946
|
app.get("/scan", async (c) => {
|
|
14449
14947
|
const ctx = await resolveAuthContext(c);
|
|
14450
14948
|
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
@@ -14689,6 +15187,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
14689
15187
|
api.route("/channels", createDocShareWriteRouter(agent));
|
|
14690
15188
|
api.route("/onboarding", createOnboardingRouter(agent));
|
|
14691
15189
|
api.route("/channels", createChannelMembersRouter());
|
|
15190
|
+
api.route("/channels", createMemoriesRouter());
|
|
14692
15191
|
api.route("/docs", createDocShareListRouter());
|
|
14693
15192
|
api.get("/users", (c) => c.json({ users: listActiveUsers() }));
|
|
14694
15193
|
api.route("/categories", createCategoryWriteRouter());
|
|
@@ -14939,17 +15438,17 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
14939
15438
|
// src/dashboard/device-auth-routes.ts
|
|
14940
15439
|
import { verify } from "crypto";
|
|
14941
15440
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
14942
|
-
import { Hono as
|
|
15441
|
+
import { Hono as Hono29 } from "hono";
|
|
14943
15442
|
|
|
14944
15443
|
// src/dashboard/agent-bridge.ts
|
|
14945
15444
|
import { randomBytes as randomBytes8, randomUUID as randomUUID4, sign as signRaw, timingSafeEqual as timingSafeEqual2, verify as verifyRaw } from "crypto";
|
|
14946
|
-
import { existsSync as existsSync25, readFileSync as
|
|
15445
|
+
import { existsSync as existsSync25, readFileSync as readFileSync16 } from "fs";
|
|
14947
15446
|
import dotenv from "dotenv";
|
|
14948
15447
|
import { WebSocket, WebSocketServer } from "ws";
|
|
14949
15448
|
|
|
14950
15449
|
// src/dashboard/host-identity.ts
|
|
14951
15450
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
|
|
14952
|
-
import { existsSync as existsSync24, mkdirSync as mkdirSync9, readFileSync as
|
|
15451
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync9, readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
14953
15452
|
import { dirname as dirname6, join as join20 } from "path";
|
|
14954
15453
|
function hostPrivateKeyPath() {
|
|
14955
15454
|
return process.env["FLYSPACE_HOST_KEY_PATH"] || join20(paths.BASE_CONFIG_DIR, "host.key");
|
|
@@ -14960,7 +15459,7 @@ function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
|
14960
15459
|
`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.`
|
|
14961
15460
|
);
|
|
14962
15461
|
}
|
|
14963
|
-
return createPrivateKey2(
|
|
15462
|
+
return createPrivateKey2(readFileSync13(path, "utf8"));
|
|
14964
15463
|
}
|
|
14965
15464
|
function loadHostPublicKey() {
|
|
14966
15465
|
const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
|
|
@@ -14976,7 +15475,7 @@ function loadHostPublicKey() {
|
|
|
14976
15475
|
// src/dashboard/host-lock.ts
|
|
14977
15476
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
14978
15477
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
14979
|
-
import { mkdirSync as mkdirSync10, readFileSync as
|
|
15478
|
+
import { mkdirSync as mkdirSync10, readFileSync as readFileSync14, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
14980
15479
|
import { join as join21 } from "path";
|
|
14981
15480
|
function hostLockPath() {
|
|
14982
15481
|
return join21(paths.BASE_DATA_DIR, "host.lock");
|
|
@@ -14994,7 +15493,7 @@ function isLiveFlyspace(pid) {
|
|
|
14994
15493
|
}
|
|
14995
15494
|
function readLockPid(path) {
|
|
14996
15495
|
try {
|
|
14997
|
-
const pid = Number(JSON.parse(
|
|
15496
|
+
const pid = Number(JSON.parse(readFileSync14(path, "utf8")).pid);
|
|
14998
15497
|
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
14999
15498
|
} catch {
|
|
15000
15499
|
return null;
|
|
@@ -15035,13 +15534,13 @@ function publishHostEndpoint(port) {
|
|
|
15035
15534
|
const path = hostLockPath();
|
|
15036
15535
|
if (readLockPid(path) !== process.pid) return null;
|
|
15037
15536
|
const token = randomBytes5(32).toString("hex");
|
|
15038
|
-
const lock = JSON.parse(
|
|
15537
|
+
const lock = JSON.parse(readFileSync14(path, "utf8"));
|
|
15039
15538
|
writeFileSync9(path, JSON.stringify({ ...lock, port, token }), { mode: 384 });
|
|
15040
15539
|
return token;
|
|
15041
15540
|
}
|
|
15042
15541
|
function readHostEndpoint() {
|
|
15043
15542
|
try {
|
|
15044
|
-
const { pid, port, token } = JSON.parse(
|
|
15543
|
+
const { pid, port, token } = JSON.parse(readFileSync14(hostLockPath(), "utf8"));
|
|
15045
15544
|
if (pid === process.pid || !Number.isInteger(port) || typeof token !== "string") return null;
|
|
15046
15545
|
return { port, token };
|
|
15047
15546
|
} catch {
|
|
@@ -15281,7 +15780,7 @@ async function userEmail(userId) {
|
|
|
15281
15780
|
|
|
15282
15781
|
// src/dashboard/doc-share-publisher.ts
|
|
15283
15782
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
15284
|
-
import { readFileSync as
|
|
15783
|
+
import { readFileSync as readFileSync15, statSync as statSync9 } from "fs";
|
|
15285
15784
|
import { extname as extname2, posix } from "path";
|
|
15286
15785
|
import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
|
|
15287
15786
|
|
|
@@ -15468,7 +15967,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
15468
15967
|
}
|
|
15469
15968
|
if (!stat.isFile()) return { status: "not-found" };
|
|
15470
15969
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
15471
|
-
const source =
|
|
15970
|
+
const source = readFileSync15(abs, "utf8");
|
|
15472
15971
|
const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
|
|
15473
15972
|
if (kind === "excalidraw") {
|
|
15474
15973
|
try {
|
|
@@ -15548,7 +16047,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
15548
16047
|
let text = null;
|
|
15549
16048
|
try {
|
|
15550
16049
|
const size = statSync9(docAbs).size;
|
|
15551
|
-
if (size <= budget) text =
|
|
16050
|
+
if (size <= budget) text = readFileSync15(docAbs, "utf8");
|
|
15552
16051
|
} catch {
|
|
15553
16052
|
continue;
|
|
15554
16053
|
}
|
|
@@ -15565,7 +16064,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
15565
16064
|
let body;
|
|
15566
16065
|
try {
|
|
15567
16066
|
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
15568
|
-
body =
|
|
16067
|
+
body = readFileSync15(resolved2.abs);
|
|
15569
16068
|
} catch {
|
|
15570
16069
|
continue;
|
|
15571
16070
|
}
|
|
@@ -15607,7 +16106,7 @@ async function publishDocShare(input2) {
|
|
|
15607
16106
|
...HTML_FEATURES,
|
|
15608
16107
|
brandingEnabled: resolveFeatures(input2.features, existing).brandingEnabled
|
|
15609
16108
|
} : html ? HTML_FEATURES : resolveFeatures(input2.features, existing);
|
|
15610
|
-
const
|
|
16109
|
+
const snapshot3 = {
|
|
15611
16110
|
...built,
|
|
15612
16111
|
sourceLang: html ? null : await detectSourceLanguage(built.title, built.content)
|
|
15613
16112
|
};
|
|
@@ -15617,8 +16116,8 @@ async function publishDocShare(input2) {
|
|
|
15617
16116
|
const renamed = await setDocShareSlug(existing.id, slug);
|
|
15618
16117
|
if (!renamed) return { status: "not-found" };
|
|
15619
16118
|
}
|
|
15620
|
-
const share2 = await updateDocShareSnapshot(existing.id,
|
|
15621
|
-
const stale = existing.assetKeys.filter((key) => !
|
|
16119
|
+
const share2 = await updateDocShareSnapshot(existing.id, snapshot3, features);
|
|
16120
|
+
const stale = existing.assetKeys.filter((key) => !snapshot3.assetKeys.includes(key));
|
|
15622
16121
|
await deleteShareAssets(stale);
|
|
15623
16122
|
if (!features.audioEnabled && existing.audioEnabled) await dropShareAudio(existing);
|
|
15624
16123
|
void queueShareAudio(share2);
|
|
@@ -15632,7 +16131,7 @@ async function publishDocShare(input2) {
|
|
|
15632
16131
|
channelId: input2.channelId,
|
|
15633
16132
|
sourcePath: input2.sourcePath,
|
|
15634
16133
|
createdBy: input2.createdBy,
|
|
15635
|
-
...
|
|
16134
|
+
...snapshot3,
|
|
15636
16135
|
...features
|
|
15637
16136
|
});
|
|
15638
16137
|
void queueShareAudio(share);
|
|
@@ -15747,8 +16246,8 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
15747
16246
|
const scope = table === "skill_files" && isWrite ? assigned : workspaces;
|
|
15748
16247
|
if (scope.length === 0) return deny(`This Host is not the assigned Host for any workspace it may write ${table} to.`);
|
|
15749
16248
|
if (method === "POST") {
|
|
15750
|
-
const
|
|
15751
|
-
if (!
|
|
16249
|
+
const ok3 = rows.length > 0 && rows.every((r) => typeof r[slugColumn] === "string" && scope.includes(r[slugColumn]));
|
|
16250
|
+
if (!ok3) return deny(`Every ${table} row must belong to a workspace this Host is assigned.`);
|
|
15752
16251
|
}
|
|
15753
16252
|
if (method === "PATCH") {
|
|
15754
16253
|
const patch = bodyRows(req.body);
|
|
@@ -15762,8 +16261,8 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
15762
16261
|
if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
|
|
15763
16262
|
if (table === "threads") {
|
|
15764
16263
|
if (method === "POST") {
|
|
15765
|
-
const
|
|
15766
|
-
if (!
|
|
16264
|
+
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
16265
|
+
if (!ok3) return deny("A thread must belong to a channel of the device workspaces.");
|
|
15767
16266
|
}
|
|
15768
16267
|
if (method === "PATCH") {
|
|
15769
16268
|
const patch = bodyRows(req.body);
|
|
@@ -15942,7 +16441,7 @@ function localAgentCallbacks(chatAgent) {
|
|
|
15942
16441
|
regenerateAgentRun: (threadId) => chatAgent.regenerate(threadId),
|
|
15943
16442
|
listAgentRuns: () => chatAgent.runningThreadIds(),
|
|
15944
16443
|
listMissingCwds: () => auditChannelCwds().map((ch) => ch.id),
|
|
15945
|
-
listRunningProfiles: () => listProfiles().filter(
|
|
16444
|
+
listRunningProfiles: () => listProfiles().filter(isServiceRunning2),
|
|
15946
16445
|
getAgentIdentities: () => resolveAgentIdentities(),
|
|
15947
16446
|
publishDocShare: (input2) => publishDocShare(input2),
|
|
15948
16447
|
listChannelSkills: (args) => listSkills(args.claudeConfigDir, args.cwd),
|
|
@@ -15959,14 +16458,14 @@ function localAgentCallbacks(chatAgent) {
|
|
|
15959
16458
|
}
|
|
15960
16459
|
var HOST_CONFIG_PUSH_INTERVAL_MS = 6e4;
|
|
15961
16460
|
function buildHostConfigSnapshot() {
|
|
15962
|
-
const
|
|
16461
|
+
const snapshot3 = {};
|
|
15963
16462
|
for (const profile of listProfiles()) {
|
|
15964
|
-
|
|
15965
|
-
env: existsSync25(paths.CONFIG_FILE) ? dotenv.parse(
|
|
16463
|
+
snapshot3[profile] = runWithProfile(profile, () => ({
|
|
16464
|
+
env: existsSync25(paths.CONFIG_FILE) ? dotenv.parse(readFileSync16(paths.CONFIG_FILE, "utf-8")) : {},
|
|
15966
16465
|
agentModel: loadDashboardSettings().agent
|
|
15967
16466
|
}));
|
|
15968
16467
|
}
|
|
15969
|
-
return
|
|
16468
|
+
return snapshot3;
|
|
15970
16469
|
}
|
|
15971
16470
|
function startLocalProfileConfigHydration() {
|
|
15972
16471
|
setProfileConfigCache(buildHostConfigSnapshot());
|
|
@@ -16208,7 +16707,8 @@ function remoteAgentCallbacks() {
|
|
|
16208
16707
|
// Host connected, showing nobody as running is a reasonable default
|
|
16209
16708
|
// rather than a false claim of certainty either way.
|
|
16210
16709
|
listRunningProfiles: async () => await callHost("listRunningProfiles", []) ?? [],
|
|
16211
|
-
|
|
16710
|
+
// `null` (no Host) falls back to `offlineAgentIdentities` in api.ts.
|
|
16711
|
+
getAgentIdentities: () => callHost("getAgentIdentities", []),
|
|
16212
16712
|
// Reading the doc and copying its assets can only happen where the
|
|
16213
16713
|
// channel's real folder is, so this always goes to the Host. `null` (no
|
|
16214
16714
|
// Host connected) surfaces as `host-unavailable` — see
|
|
@@ -16492,7 +16992,7 @@ async function boundWorkspaceIds(publicKey, userId) {
|
|
|
16492
16992
|
return new Set(existing ? (await listDeviceWorkspaces(existing)).map((w) => w.id) : []);
|
|
16493
16993
|
}
|
|
16494
16994
|
function createDeviceAuthRouter() {
|
|
16495
|
-
const app = new
|
|
16995
|
+
const app = new Hono29();
|
|
16496
16996
|
app.post("/code", async (c) => {
|
|
16497
16997
|
const body = await readBody(c);
|
|
16498
16998
|
const publicKey = typeof body["public_key"] === "string" ? body["public_key"] : "";
|
|
@@ -16555,8 +17055,8 @@ function createDeviceAuthRouter() {
|
|
|
16555
17055
|
if (!device || device.revokedAt || !nonce || !signature) return c.json({ error: "invalid_grant" }, 401);
|
|
16556
17056
|
if (!await consumeChallenge(device.id, nonce)) return c.json({ error: "invalid_grant" }, 401);
|
|
16557
17057
|
const key = parseEd25519PublicKey(device.publicKey);
|
|
16558
|
-
const
|
|
16559
|
-
if (!
|
|
17058
|
+
const ok3 = key && verify(null, Buffer.from(nonce), key, Buffer.from(signature, "base64"));
|
|
17059
|
+
if (!ok3) return c.json({ error: "invalid_grant" }, 401);
|
|
16560
17060
|
const { token, expiresInS } = await issueAccessToken(device.id);
|
|
16561
17061
|
return c.json({ access_token: token, token_type: "Bearer", expires_in: expiresInS });
|
|
16562
17062
|
});
|
|
@@ -16656,7 +17156,7 @@ function createDeviceAuthRouter() {
|
|
|
16656
17156
|
|
|
16657
17157
|
// src/dashboard/api.ts
|
|
16658
17158
|
function createApiRouter(agent = {}, mode = "all") {
|
|
16659
|
-
const api = new
|
|
17159
|
+
const api = new Hono30();
|
|
16660
17160
|
api.use("*", async (c, next) => {
|
|
16661
17161
|
const known = await listKnownWorkspaces();
|
|
16662
17162
|
const fromHost = profileFromHost(trustedHost(c), known);
|
|
@@ -16703,7 +17203,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
16703
17203
|
if (mode === "data") {
|
|
16704
17204
|
api.get(
|
|
16705
17205
|
"/settings/agents",
|
|
16706
|
-
async (c) => c.json({ agents: await agent.getAgentIdentities?.() ??
|
|
17206
|
+
async (c) => c.json({ agents: await agent.getAgentIdentities?.() ?? await offlineAgentIdentities() })
|
|
16707
17207
|
);
|
|
16708
17208
|
api.get("/channels/:id/skills", async (c) => {
|
|
16709
17209
|
const { cwd, claudeConfigDir } = resolveChannelConfig(c.req.param("id"));
|
|
@@ -16767,7 +17267,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
16767
17267
|
}
|
|
16768
17268
|
|
|
16769
17269
|
// src/dashboard/app-icon-routes.ts
|
|
16770
|
-
import { Hono as
|
|
17270
|
+
import { Hono as Hono31 } from "hono";
|
|
16771
17271
|
var NEUTRAL_MANIFEST = {
|
|
16772
17272
|
id: "/",
|
|
16773
17273
|
name: "Flyspace",
|
|
@@ -16805,7 +17305,7 @@ async function withResolvedProfile2(c, next) {
|
|
|
16805
17305
|
return profile ? runWithProfile(profile, () => next()) : next();
|
|
16806
17306
|
}
|
|
16807
17307
|
function createAppIconRouter() {
|
|
16808
|
-
const app = new
|
|
17308
|
+
const app = new Hono31();
|
|
16809
17309
|
app.get("/manifest.json", withResolvedProfile2, async (c) => {
|
|
16810
17310
|
if (!await resolvedProfileHost(c)) return c.json(NEUTRAL_MANIFEST);
|
|
16811
17311
|
const profile = getProfile();
|
|
@@ -16845,7 +17345,7 @@ function pngResponse(c, png) {
|
|
|
16845
17345
|
}
|
|
16846
17346
|
|
|
16847
17347
|
// src/dashboard/stripe-routes.ts
|
|
16848
|
-
import { Hono as
|
|
17348
|
+
import { Hono as Hono32 } from "hono";
|
|
16849
17349
|
function planStatusFor(status) {
|
|
16850
17350
|
const known = ["trialing", "active", "paused", "canceled"];
|
|
16851
17351
|
return known.includes(status) ? status : null;
|
|
@@ -16888,7 +17388,7 @@ async function handleCardAdded(session) {
|
|
|
16888
17388
|
}
|
|
16889
17389
|
}
|
|
16890
17390
|
function createStripeWebhookRouter() {
|
|
16891
|
-
const stripeRouter = new
|
|
17391
|
+
const stripeRouter = new Hono32();
|
|
16892
17392
|
stripeRouter.post("/webhook", async (c) => {
|
|
16893
17393
|
const secret = process.env["STRIPE_WEBHOOK_SECRET"];
|
|
16894
17394
|
const signature = c.req.header("stripe-signature");
|
|
@@ -17004,10 +17504,10 @@ var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
|
17004
17504
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
17005
17505
|
for (let port = start; port < start + attempts; port++) {
|
|
17006
17506
|
const free = await new Promise((resolve8) => {
|
|
17007
|
-
const
|
|
17008
|
-
|
|
17009
|
-
|
|
17010
|
-
|
|
17507
|
+
const probe3 = createServer();
|
|
17508
|
+
probe3.once("error", () => resolve8(false));
|
|
17509
|
+
probe3.once("listening", () => probe3.close(() => resolve8(true)));
|
|
17510
|
+
probe3.listen(port, host);
|
|
17011
17511
|
});
|
|
17012
17512
|
if (free) return port;
|
|
17013
17513
|
}
|
|
@@ -17048,7 +17548,7 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17048
17548
|
const mirrorsSkills = mode === "host" || process.env["FLYSPACE_SKILL_SYNC"] === "1";
|
|
17049
17549
|
const stopSkillSync = mirrorsSkills ? installSkillSync(listProfiles()) : () => {
|
|
17050
17550
|
};
|
|
17051
|
-
const app = new
|
|
17551
|
+
const app = new Hono33();
|
|
17052
17552
|
app.use("*", async (c, next) => {
|
|
17053
17553
|
await next();
|
|
17054
17554
|
if (c.req.path.startsWith("/assets/")) {
|
|
@@ -17518,7 +18018,7 @@ async function threadReplies(client2, nameOrId, ts2) {
|
|
|
17518
18018
|
async function postMessage(client2, args) {
|
|
17519
18019
|
const channelArg = getArg2(args, "--channel");
|
|
17520
18020
|
const textFilePath = getArg2(args, "--text-file");
|
|
17521
|
-
const text = textFilePath ?
|
|
18021
|
+
const text = textFilePath ? readFileSync17(textFilePath, "utf8") : getArg2(args, "--text");
|
|
17522
18022
|
const threadTs = getArg2(args, "--thread");
|
|
17523
18023
|
const markdown = args.includes("--markdown");
|
|
17524
18024
|
const broadcast = args.includes("--broadcast") && Boolean(threadTs);
|
|
@@ -17673,7 +18173,7 @@ async function ensureClaudeCodeReady() {
|
|
|
17673
18173
|
}
|
|
17674
18174
|
function getManifestYaml(botName) {
|
|
17675
18175
|
const manifestPath = resolve7(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
|
|
17676
|
-
const raw =
|
|
18176
|
+
const raw = readFileSync17(manifestPath, "utf-8");
|
|
17677
18177
|
return raw.replace(/name: Custie/g, `name: ${botName}`);
|
|
17678
18178
|
}
|
|
17679
18179
|
async function stepPersonalise() {
|
|
@@ -17886,7 +18386,7 @@ async function stepBrowserTokens(botName) {
|
|
|
17886
18386
|
})
|
|
17887
18387
|
));
|
|
17888
18388
|
const DEBUG = process.argv.includes("--debug");
|
|
17889
|
-
const USER_DATA_DIR = resolve7(
|
|
18389
|
+
const USER_DATA_DIR = resolve7(tmpdir4(), "custie-playwright-profile");
|
|
17890
18390
|
let context;
|
|
17891
18391
|
try {
|
|
17892
18392
|
log4("Opening browser \u2014 a Slack login page will appear.\n");
|
|
@@ -18107,7 +18607,7 @@ function copyDefaultPrompt(botName) {
|
|
|
18107
18607
|
if (!existsSync27(paths.PROMPT_FILE)) {
|
|
18108
18608
|
const defaultPrompt = resolve7(paths.PACKAGE_ROOT, "system.default.md");
|
|
18109
18609
|
if (existsSync27(defaultPrompt)) {
|
|
18110
|
-
const content =
|
|
18610
|
+
const content = readFileSync17(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
|
|
18111
18611
|
writeFileSync10(paths.PROMPT_FILE, content);
|
|
18112
18612
|
success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
|
|
18113
18613
|
}
|
|
@@ -18429,8 +18929,122 @@ async function runChatCmd(args) {
|
|
|
18429
18929
|
}
|
|
18430
18930
|
}
|
|
18431
18931
|
|
|
18432
|
-
// src/commands/
|
|
18932
|
+
// src/commands/memory.ts
|
|
18933
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
18433
18934
|
var USAGE5 = `
|
|
18935
|
+
Usage: flyspace memory <subcommand> --channel <name-or-id> [options]
|
|
18936
|
+
|
|
18937
|
+
A channel's persistent agent memory (docs/features/workspace-memory.md).
|
|
18938
|
+
|
|
18939
|
+
Subcommands:
|
|
18940
|
+
list Every memory in the channel (index form)
|
|
18941
|
+
get <name> One memory in full
|
|
18942
|
+
search <words...> Memories whose name, description or body match
|
|
18943
|
+
save --name <slug> --type <type> Create, or overwrite the memory with that name
|
|
18944
|
+
--description <one line>
|
|
18945
|
+
--body <text | -> "-" reads the body from stdin
|
|
18946
|
+
[--thread <id>] The thread the memory came from
|
|
18947
|
+
delete <name-or-id> [--thread <id>] Delete one memory
|
|
18948
|
+
|
|
18949
|
+
Types: ${MEMORY_TYPES.join(", ")}
|
|
18950
|
+
`;
|
|
18951
|
+
function getArg4(args, flag) {
|
|
18952
|
+
const idx = args.indexOf(flag);
|
|
18953
|
+
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
|
|
18954
|
+
}
|
|
18955
|
+
function positionals(args) {
|
|
18956
|
+
const out = [];
|
|
18957
|
+
for (let i = 0; i < args.length; i++) {
|
|
18958
|
+
if (args[i].startsWith("--")) i++;
|
|
18959
|
+
else out.push(args[i]);
|
|
18960
|
+
}
|
|
18961
|
+
return out;
|
|
18962
|
+
}
|
|
18963
|
+
function indexLine(m) {
|
|
18964
|
+
return `${m.name} (${m.type}, ${m.updatedAt.slice(0, 10)}) ${m.description}`;
|
|
18965
|
+
}
|
|
18966
|
+
async function runMemoryCmd(args) {
|
|
18967
|
+
loadEnvFiles();
|
|
18968
|
+
const subcommand = args[0];
|
|
18969
|
+
const rest = args.slice(1);
|
|
18970
|
+
if (!subcommand || subcommand === "-h" || subcommand === "--help") {
|
|
18971
|
+
console.log(USAGE5);
|
|
18972
|
+
return;
|
|
18973
|
+
}
|
|
18974
|
+
const channelArg = getArg4(rest, "--channel");
|
|
18975
|
+
if (!channelArg) throw new Error("--channel <name-or-id> is required");
|
|
18976
|
+
const channel = await resolveChannel(channelArg, getArg4(rest, "--profile-name"));
|
|
18977
|
+
const words = positionals(rest);
|
|
18978
|
+
switch (subcommand) {
|
|
18979
|
+
case "list": {
|
|
18980
|
+
const memories = await listChannelMemories(channel.id);
|
|
18981
|
+
console.log(memories.length ? memories.map(indexLine).join("\n") : "No memories yet.");
|
|
18982
|
+
break;
|
|
18983
|
+
}
|
|
18984
|
+
case "get": {
|
|
18985
|
+
const name = words[0];
|
|
18986
|
+
if (!name) throw new Error("Usage: flyspace memory get --channel <c> <name>");
|
|
18987
|
+
const memory = await getChannelMemory(channel.id, name);
|
|
18988
|
+
if (!memory) throw new Error(`No memory named ${name} in ${channel.name}.`);
|
|
18989
|
+
console.log(`# ${memory.name} (${memory.type})
|
|
18990
|
+
|
|
18991
|
+
${memory.description}
|
|
18992
|
+
|
|
18993
|
+
${memory.body}`);
|
|
18994
|
+
break;
|
|
18995
|
+
}
|
|
18996
|
+
case "search": {
|
|
18997
|
+
if (!words.length) throw new Error("Usage: flyspace memory search --channel <c> <words...>");
|
|
18998
|
+
const hits = await searchChannelMemories(channel.id, words.join(" "));
|
|
18999
|
+
console.log(hits.length ? hits.map(indexLine).join("\n") : "No matching memories.");
|
|
19000
|
+
break;
|
|
19001
|
+
}
|
|
19002
|
+
case "save": {
|
|
19003
|
+
const name = getArg4(rest, "--name");
|
|
19004
|
+
const type = getArg4(rest, "--type");
|
|
19005
|
+
const description = getArg4(rest, "--description");
|
|
19006
|
+
const bodyArg = getArg4(rest, "--body");
|
|
19007
|
+
if (!name || !type || !description || bodyArg === void 0) {
|
|
19008
|
+
throw new Error(
|
|
19009
|
+
"Usage: flyspace memory save --channel <c> --name <slug> --type <type> --description <one line> --body <text | -> [--thread <id>]"
|
|
19010
|
+
);
|
|
19011
|
+
}
|
|
19012
|
+
if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
19013
|
+
const body = bodyArg === "-" ? readFileSync18(0, "utf-8") : bodyArg;
|
|
19014
|
+
const thread = getArg4(rest, "--thread");
|
|
19015
|
+
const { created, changed } = await saveChannelMemory(
|
|
19016
|
+
{
|
|
19017
|
+
channelId: channel.id,
|
|
19018
|
+
name,
|
|
19019
|
+
type,
|
|
19020
|
+
description,
|
|
19021
|
+
body,
|
|
19022
|
+
createdBy: "agent",
|
|
19023
|
+
...thread ? { sourceThreadId: thread } : {}
|
|
19024
|
+
},
|
|
19025
|
+
"agent"
|
|
19026
|
+
);
|
|
19027
|
+
const verb = created ? "Saved" : changed ? "Updated" : "Unchanged:";
|
|
19028
|
+
console.log(`${verb} memory ${name} in ${channel.name}.`);
|
|
19029
|
+
break;
|
|
19030
|
+
}
|
|
19031
|
+
case "delete": {
|
|
19032
|
+
const ref = words[0];
|
|
19033
|
+
if (!ref) throw new Error("Usage: flyspace memory delete --channel <c> <name-or-id>");
|
|
19034
|
+
const deleted = await deleteChannelMemory(channel.id, ref, "agent", getArg4(rest, "--thread"));
|
|
19035
|
+
if (!deleted) throw new Error(`No memory ${ref} in ${channel.name}.`);
|
|
19036
|
+
console.log(`Deleted memory ${ref}.`);
|
|
19037
|
+
break;
|
|
19038
|
+
}
|
|
19039
|
+
default:
|
|
19040
|
+
console.error(`Unknown subcommand: ${subcommand}`);
|
|
19041
|
+
console.log(USAGE5);
|
|
19042
|
+
process.exit(1);
|
|
19043
|
+
}
|
|
19044
|
+
}
|
|
19045
|
+
|
|
19046
|
+
// src/commands/mcp.ts
|
|
19047
|
+
var USAGE6 = `
|
|
18434
19048
|
Usage: flyspace mcp <server>
|
|
18435
19049
|
|
|
18436
19050
|
Run one of Flyspace's own MCP servers on stdio. Spawned by the agent runtime
|
|
@@ -18447,7 +19061,7 @@ Usage: flyspace mcp <server>
|
|
|
18447
19061
|
async function runMcpCmd(args) {
|
|
18448
19062
|
const server = args[0];
|
|
18449
19063
|
if (!server || server === "-h" || server === "--help") {
|
|
18450
|
-
console.log(
|
|
19064
|
+
console.log(USAGE6.trim());
|
|
18451
19065
|
return;
|
|
18452
19066
|
}
|
|
18453
19067
|
restoreStdout();
|
|
@@ -18460,7 +19074,7 @@ async function runMcpCmd(args) {
|
|
|
18460
19074
|
}
|
|
18461
19075
|
default:
|
|
18462
19076
|
console.error(`Unknown MCP server: ${server}`);
|
|
18463
|
-
console.error(
|
|
19077
|
+
console.error(USAGE6.trim());
|
|
18464
19078
|
process.exit(1);
|
|
18465
19079
|
}
|
|
18466
19080
|
}
|
|
@@ -18536,7 +19150,7 @@ async function runDashboard(args) {
|
|
|
18536
19150
|
|
|
18537
19151
|
// src/commands/workspace.ts
|
|
18538
19152
|
import { existsSync as existsSync29, mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
18539
|
-
var
|
|
19153
|
+
var USAGE7 = `
|
|
18540
19154
|
Usage: flyspace workspace <subcommand> [options]
|
|
18541
19155
|
|
|
18542
19156
|
Subcommands:
|
|
@@ -18553,7 +19167,7 @@ var USAGE6 = `
|
|
|
18553
19167
|
--no-invite Never send an invite email. Fails if the admin
|
|
18554
19168
|
has no existing account.
|
|
18555
19169
|
`;
|
|
18556
|
-
function
|
|
19170
|
+
function getArg5(args, flag) {
|
|
18557
19171
|
const idx = args.indexOf(flag);
|
|
18558
19172
|
return idx !== -1 && idx + 1 < args.length ? args[idx + 1] : void 0;
|
|
18559
19173
|
}
|
|
@@ -18649,10 +19263,10 @@ async function provisionChannels(specs) {
|
|
|
18649
19263
|
async function handleCreate(args) {
|
|
18650
19264
|
const slug = args[0];
|
|
18651
19265
|
if (!slug || slug.startsWith("-")) fail2("Usage: flyspace workspace create <slug> --admin-email <email>");
|
|
18652
|
-
const adminEmail =
|
|
19266
|
+
const adminEmail = getArg5(args, "--admin-email");
|
|
18653
19267
|
if (!adminEmail?.includes("@")) fail2("--admin-email is required and must be an email address");
|
|
18654
|
-
const botName =
|
|
18655
|
-
const baseDomain =
|
|
19268
|
+
const botName = getArg5(args, "--bot-name") ?? slug;
|
|
19269
|
+
const baseDomain = getArg5(args, "--base-domain") ?? "flyspace.ai";
|
|
18656
19270
|
const allowInvite = !args.includes("--no-invite");
|
|
18657
19271
|
const channels = getAllArgs(args, "--channel").map(parseChannelSpec);
|
|
18658
19272
|
loadEnvFiles();
|
|
@@ -18675,13 +19289,13 @@ async function runWorkspaceCmd(args) {
|
|
|
18675
19289
|
await handleCreate(args.slice(1));
|
|
18676
19290
|
break;
|
|
18677
19291
|
default:
|
|
18678
|
-
console.log(
|
|
19292
|
+
console.log(USAGE7);
|
|
18679
19293
|
process.exit(args[0] ? 1 : 0);
|
|
18680
19294
|
}
|
|
18681
19295
|
}
|
|
18682
19296
|
|
|
18683
19297
|
// src/commands/reconcile.ts
|
|
18684
|
-
var
|
|
19298
|
+
var USAGE8 = `
|
|
18685
19299
|
Usage: flyspace reconcile [<workspace> | --all]
|
|
18686
19300
|
|
|
18687
19301
|
Repair a workspace's local filesystem state (profile dir, config.env,
|
|
@@ -18701,7 +19315,7 @@ function printLog(name, log5) {
|
|
|
18701
19315
|
}
|
|
18702
19316
|
async function runReconcileCmd(args) {
|
|
18703
19317
|
if (args.includes("-h") || args.includes("--help")) {
|
|
18704
|
-
console.log(
|
|
19318
|
+
console.log(USAGE8);
|
|
18705
19319
|
return;
|
|
18706
19320
|
}
|
|
18707
19321
|
loadEnvFiles();
|
|
@@ -18717,12 +19331,13 @@ async function runReconcileCmd(args) {
|
|
|
18717
19331
|
|
|
18718
19332
|
// src/cli.ts
|
|
18719
19333
|
var XERO_USAGE = false ? " xero Connect to Xero and expose it as an MCP server\n" : "";
|
|
18720
|
-
var
|
|
19334
|
+
var USAGE9 = `
|
|
18721
19335
|
Usage: flyspace <command> [options]
|
|
18722
19336
|
|
|
18723
19337
|
Commands:
|
|
18724
19338
|
setup Set up this machine as a Host (login, agent CLIs, ~/Flyspace)
|
|
18725
19339
|
auth Log in, check status, or log out this device
|
|
19340
|
+
doctor Check this Host end to end (login, agent CLIs, services)
|
|
18726
19341
|
start Start the agent host (Slack only if configured)
|
|
18727
19342
|
install Install as a system service (launchd / systemd)
|
|
18728
19343
|
uninstall Remove the system service
|
|
@@ -18736,6 +19351,7 @@ var USAGE8 = `
|
|
|
18736
19351
|
usage Show agent token usage and cost for this workspace
|
|
18737
19352
|
slack Query Slack (channels, users, post messages)
|
|
18738
19353
|
chat Inspect dashboard chat channels and move threads between them
|
|
19354
|
+
memory Read and write a channel's persistent agent memory
|
|
18739
19355
|
profiles List local profiles (bound here? scheduler?) or remove one
|
|
18740
19356
|
workspace Provision a workspace (Supabase rows + local profile dirs)
|
|
18741
19357
|
reconcile Repair a workspace's local files against Supabase
|
|
@@ -18776,6 +19392,9 @@ async function main() {
|
|
|
18776
19392
|
case "auth":
|
|
18777
19393
|
await runAuthCmd(args.slice(1));
|
|
18778
19394
|
break;
|
|
19395
|
+
case "doctor":
|
|
19396
|
+
await runDoctor(args.slice(1));
|
|
19397
|
+
break;
|
|
18779
19398
|
case "start":
|
|
18780
19399
|
await runStart();
|
|
18781
19400
|
break;
|
|
@@ -18824,6 +19443,9 @@ async function main() {
|
|
|
18824
19443
|
case "chat":
|
|
18825
19444
|
await runChatCmd(args.slice(1));
|
|
18826
19445
|
break;
|
|
19446
|
+
case "memory":
|
|
19447
|
+
await runMemoryCmd(args.slice(1));
|
|
19448
|
+
break;
|
|
18827
19449
|
case "profile":
|
|
18828
19450
|
case "profiles":
|
|
18829
19451
|
await runProfiles(args.slice(1));
|
|
@@ -18836,12 +19458,12 @@ async function main() {
|
|
|
18836
19458
|
break;
|
|
18837
19459
|
case "-v":
|
|
18838
19460
|
case "--version":
|
|
18839
|
-
printRaw("0.1.
|
|
19461
|
+
printRaw("0.1.9");
|
|
18840
19462
|
break;
|
|
18841
19463
|
case "-h":
|
|
18842
19464
|
case "--help":
|
|
18843
19465
|
case void 0:
|
|
18844
|
-
printRaw(
|
|
19466
|
+
printRaw(USAGE9);
|
|
18845
19467
|
break;
|
|
18846
19468
|
case "xero":
|
|
18847
19469
|
if (false) {
|
|
@@ -18852,7 +19474,7 @@ async function main() {
|
|
|
18852
19474
|
// falls through
|
|
18853
19475
|
default:
|
|
18854
19476
|
printRaw(`Unknown command: ${command}`);
|
|
18855
|
-
printRaw(
|
|
19477
|
+
printRaw(USAGE9);
|
|
18856
19478
|
process.exit(1);
|
|
18857
19479
|
}
|
|
18858
19480
|
}
|