flyspace-cli 0.1.15 → 0.1.16
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-KFT2YP5W.js → chunk-2OGLCT7I.js} +236 -63
- package/dist/cli.js +637 -271
- package/dist/index.js +1 -1
- package/package.json +6 -4
package/dist/cli.js
CHANGED
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
BOT_PROFILE_KEY,
|
|
6
6
|
BRANDING_DEFAULTS,
|
|
7
7
|
BUILT_IN_OUTPUT_STYLES,
|
|
8
|
+
ChannelCwdError,
|
|
8
9
|
DEFAULT_CODEX_MODEL,
|
|
9
10
|
DEFAULT_TIMEZONE,
|
|
10
11
|
DEFAULT_TITLE_RULES,
|
|
@@ -34,6 +35,7 @@ import {
|
|
|
34
35
|
canSeeChannel,
|
|
35
36
|
channelSkillsDir,
|
|
36
37
|
claudeAuthFailed,
|
|
38
|
+
clearClaudeAuthFailed,
|
|
37
39
|
codexAuthAvailable,
|
|
38
40
|
createChannel,
|
|
39
41
|
createMembership,
|
|
@@ -42,6 +44,7 @@ import {
|
|
|
42
44
|
createWorkspace,
|
|
43
45
|
deleteChannelMemory,
|
|
44
46
|
deleteMembership,
|
|
47
|
+
deleteMemory,
|
|
45
48
|
deleteOutputStyle,
|
|
46
49
|
deleteSkillFile,
|
|
47
50
|
deleteWorkspace,
|
|
@@ -68,6 +71,7 @@ import {
|
|
|
68
71
|
getChannelRegistry,
|
|
69
72
|
getMembershipByEmail,
|
|
70
73
|
getMembershipByUserId,
|
|
74
|
+
getMemory,
|
|
71
75
|
getOutputStyleDefault,
|
|
72
76
|
getProfileChannelRegistry,
|
|
73
77
|
getWorkspaceBilling,
|
|
@@ -96,6 +100,8 @@ import {
|
|
|
96
100
|
listMemberChannels,
|
|
97
101
|
listMemberships,
|
|
98
102
|
listMembershipsByEmail,
|
|
103
|
+
listMemories,
|
|
104
|
+
listMemoryEvents,
|
|
99
105
|
listOutputStyles,
|
|
100
106
|
listPluginSkillDetails,
|
|
101
107
|
listSkillFiles,
|
|
@@ -118,6 +124,7 @@ import {
|
|
|
118
124
|
localDefaults,
|
|
119
125
|
lookupById,
|
|
120
126
|
markInviteResent,
|
|
127
|
+
markMemoryUsed,
|
|
121
128
|
markWorkspaceAgentConnected,
|
|
122
129
|
markWorkspaceGreeted,
|
|
123
130
|
markWorkspaceProjectsSetup,
|
|
@@ -126,6 +133,7 @@ import {
|
|
|
126
133
|
mimetypeSizeLimit,
|
|
127
134
|
modelLabel,
|
|
128
135
|
pinnedClaudeConfigDir,
|
|
136
|
+
promoteChannelMemory,
|
|
129
137
|
readSkillFile,
|
|
130
138
|
recentRunsForWorkspace,
|
|
131
139
|
refreshAutomationRegistry,
|
|
@@ -148,12 +156,14 @@ import {
|
|
|
148
156
|
saveBotIdentity,
|
|
149
157
|
saveChannelMemory,
|
|
150
158
|
saveDashboardSettings,
|
|
159
|
+
saveMemory,
|
|
151
160
|
saveWorkspaceBranding,
|
|
152
161
|
saveWorkspacePrompt,
|
|
153
|
-
|
|
162
|
+
searchMemories,
|
|
154
163
|
setAgentPaused,
|
|
155
164
|
setAllowGuestAccess,
|
|
156
165
|
setMembershipHandle,
|
|
166
|
+
setMemoryAutoPaused,
|
|
157
167
|
setOutputStyleDefault,
|
|
158
168
|
setWorkspaceStripeSubscription,
|
|
159
169
|
slackConfigured,
|
|
@@ -168,6 +178,7 @@ import {
|
|
|
168
178
|
updateChannelMemory,
|
|
169
179
|
updateChannelsMcpConnections,
|
|
170
180
|
updateMembershipRole,
|
|
181
|
+
updateMemory,
|
|
171
182
|
updateOutputStyle,
|
|
172
183
|
updateWorkspacePlanStatus,
|
|
173
184
|
usageLimitedModels,
|
|
@@ -176,7 +187,7 @@ import {
|
|
|
176
187
|
warnOnBrokenChannelCwds,
|
|
177
188
|
workspaceSkillsDir,
|
|
178
189
|
writeSkillFile
|
|
179
|
-
} from "./chunk-
|
|
190
|
+
} from "./chunk-2OGLCT7I.js";
|
|
180
191
|
import {
|
|
181
192
|
firstReplyBody,
|
|
182
193
|
getThread,
|
|
@@ -1348,14 +1359,14 @@ function detectRunners(history = { claude: false, codex: false }) {
|
|
|
1348
1359
|
function scanOnboarding() {
|
|
1349
1360
|
const byPath = /* @__PURE__ */ new Map();
|
|
1350
1361
|
const history = { claude: false, codex: false };
|
|
1351
|
-
const add = (rawPath, source, mtime,
|
|
1362
|
+
const add = (rawPath, source, mtime, sessions2, trusted = false) => {
|
|
1352
1363
|
const path = resolve3(rawPath);
|
|
1353
1364
|
if (isExcluded(path)) return;
|
|
1354
1365
|
const existing = byPath.get(path);
|
|
1355
1366
|
if (existing) {
|
|
1356
1367
|
if (!existing.sources.includes(source)) existing.sources.push(source);
|
|
1357
1368
|
existing.lastUsed = Math.max(existing.lastUsed, mtime);
|
|
1358
|
-
existing.sessions +=
|
|
1369
|
+
existing.sessions += sessions2;
|
|
1359
1370
|
existing.trusted ||= trusted;
|
|
1360
1371
|
} else {
|
|
1361
1372
|
byPath.set(path, {
|
|
@@ -1363,7 +1374,7 @@ function scanOnboarding() {
|
|
|
1363
1374
|
name: basename2(path),
|
|
1364
1375
|
sources: [source],
|
|
1365
1376
|
lastUsed: mtime,
|
|
1366
|
-
sessions,
|
|
1377
|
+
sessions: sessions2,
|
|
1367
1378
|
trusted
|
|
1368
1379
|
});
|
|
1369
1380
|
}
|
|
@@ -1765,7 +1776,7 @@ async function runAuthCmd(args) {
|
|
|
1765
1776
|
}
|
|
1766
1777
|
|
|
1767
1778
|
// src/version.ts
|
|
1768
|
-
var CLI_VERSION = true ? "0.1.
|
|
1779
|
+
var CLI_VERSION = true ? "0.1.16" : null;
|
|
1769
1780
|
function isOlderVersion(a, b) {
|
|
1770
1781
|
const parse2 = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1771
1782
|
const [pa, pb] = [parse2(a), parse2(b)];
|
|
@@ -2043,7 +2054,7 @@ ${BOLD3}Flyspace Doctor${RESET3}
|
|
|
2043
2054
|
}
|
|
2044
2055
|
|
|
2045
2056
|
// src/commands/uninstall.ts
|
|
2046
|
-
import { existsSync as existsSync7, readFileSync as readFileSync8, rmSync as rmSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
2057
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync7, readFileSync as readFileSync8, rmSync as rmSync4, unlinkSync as unlinkSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
2047
2058
|
import { confirm as confirm3 } from "@inquirer/prompts";
|
|
2048
2059
|
import { execSync as execSync5 } from "child_process";
|
|
2049
2060
|
import { basename as basename3, join as join9 } from "path";
|
|
@@ -2110,12 +2121,31 @@ async function runUninstall(args = []) {
|
|
|
2110
2121
|
console.log(" - revokes this device and removes every background service");
|
|
2111
2122
|
for (const t of targets) console.log(` - deletes ${t}`);
|
|
2112
2123
|
console.log(" Your channel folders (~/Flyspace) are kept.\n");
|
|
2113
|
-
|
|
2124
|
+
const backupSources = [paths.BASE_CONFIG_DIR, paths.BASE_DATA_DIR].filter(existsSync7);
|
|
2125
|
+
const interactive = !args.includes("--yes") && !args.includes("-y");
|
|
2126
|
+
let backup = !args.includes("--no-backup") && backupSources.length > 0;
|
|
2127
|
+
if (interactive) {
|
|
2128
|
+
if (backup) backup = await confirm3({ message: "Back up local data first?", default: true });
|
|
2114
2129
|
if (!await confirm3({ message: "Continue?", default: false })) {
|
|
2115
2130
|
console.log(" Cancelled.");
|
|
2116
2131
|
return;
|
|
2117
2132
|
}
|
|
2118
2133
|
}
|
|
2134
|
+
let backupFile;
|
|
2135
|
+
if (backup) {
|
|
2136
|
+
const dir = join9(homedir4(), "Flyspace", ".uninstall-backups");
|
|
2137
|
+
mkdirSync7(dir, { recursive: true });
|
|
2138
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
2139
|
+
backupFile = join9(dir, `flyspace-${stamp}.tar.gz`);
|
|
2140
|
+
const rel = backupSources.map((s) => `"${s.replace(/^\//, "")}"`).join(" ");
|
|
2141
|
+
try {
|
|
2142
|
+
run2(`tar -czf "${backupFile}" -C / ${rel}`);
|
|
2143
|
+
success2(`Backup saved to ${backupFile}`);
|
|
2144
|
+
} catch (err) {
|
|
2145
|
+
warn2(`Backup failed, nothing was removed: ${err.message}`);
|
|
2146
|
+
return;
|
|
2147
|
+
}
|
|
2148
|
+
}
|
|
2119
2149
|
const creds = loadCredentials();
|
|
2120
2150
|
if (creds) {
|
|
2121
2151
|
try {
|
|
@@ -2142,7 +2172,11 @@ async function runUninstall(args = []) {
|
|
|
2142
2172
|
success2(`Removed PATH entry from ${rc}`);
|
|
2143
2173
|
}
|
|
2144
2174
|
}
|
|
2145
|
-
console.log("\n Flyspace is uninstalled. Open a new terminal to finish
|
|
2175
|
+
console.log("\n Flyspace is uninstalled. Open a new terminal to finish.");
|
|
2176
|
+
if (backupFile) {
|
|
2177
|
+
console.log(` To restore your local data after reinstalling: tar -xzf "${backupFile}" -C /`);
|
|
2178
|
+
}
|
|
2179
|
+
console.log("");
|
|
2146
2180
|
}
|
|
2147
2181
|
|
|
2148
2182
|
// src/commands/profiles-cmd.ts
|
|
@@ -2257,7 +2291,7 @@ function npmEnv() {
|
|
|
2257
2291
|
return home ? { ...process.env, PATH: `${join10(home, "node", "bin")}:${process.env.PATH ?? ""}` } : process.env;
|
|
2258
2292
|
}
|
|
2259
2293
|
async function runUpgrade() {
|
|
2260
|
-
const current = "0.1.
|
|
2294
|
+
const current = "0.1.16";
|
|
2261
2295
|
console.log(`Current version: ${current}`);
|
|
2262
2296
|
console.log("Checking for updates...\n");
|
|
2263
2297
|
try {
|
|
@@ -2898,7 +2932,7 @@ async function runLogs(args) {
|
|
|
2898
2932
|
|
|
2899
2933
|
// src/commands/slack.ts
|
|
2900
2934
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
2901
|
-
import { existsSync as
|
|
2935
|
+
import { existsSync as existsSync27, readFileSync as readFileSync20, writeFileSync as writeFileSync12 } from "fs";
|
|
2902
2936
|
import { resolve as resolve6 } from "path";
|
|
2903
2937
|
import { tmpdir as tmpdir5, homedir as homedir9 } from "os";
|
|
2904
2938
|
import { input, confirm as confirm4, select, checkbox } from "@inquirer/prompts";
|
|
@@ -4436,10 +4470,10 @@ async function deliverPasswordResetLink(email, redirectTo) {
|
|
|
4436
4470
|
}
|
|
4437
4471
|
|
|
4438
4472
|
// src/dashboard/server.ts
|
|
4439
|
-
import { existsSync as
|
|
4473
|
+
import { existsSync as existsSync26 } from "fs";
|
|
4440
4474
|
import { createServer } from "net";
|
|
4441
|
-
import { join as
|
|
4442
|
-
import { Hono as
|
|
4475
|
+
import { join as join25 } from "path";
|
|
4476
|
+
import { Hono as Hono35 } from "hono";
|
|
4443
4477
|
|
|
4444
4478
|
// src/dashboard/sentry.ts
|
|
4445
4479
|
import * as Sentry from "@sentry/node";
|
|
@@ -4456,7 +4490,7 @@ import { serve } from "@hono/node-server";
|
|
|
4456
4490
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
4457
4491
|
|
|
4458
4492
|
// src/reconcile.ts
|
|
4459
|
-
import { existsSync as existsSync10, mkdirSync as
|
|
4493
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
4460
4494
|
async function boundWorkspaces2() {
|
|
4461
4495
|
return (await boundWorkspaces())?.workspaces ?? null;
|
|
4462
4496
|
}
|
|
@@ -4482,7 +4516,7 @@ async function reconcileProfile(profile) {
|
|
|
4482
4516
|
if (!channel.cwd) continue;
|
|
4483
4517
|
const cwd = expandPath(channel.cwd);
|
|
4484
4518
|
if (isCwdUsable(cwd)) continue;
|
|
4485
|
-
|
|
4519
|
+
mkdirSync8(cwd, { recursive: true });
|
|
4486
4520
|
log5.push(`channel "${channel.name}" cwd created (${cwd})`);
|
|
4487
4521
|
}
|
|
4488
4522
|
return log5;
|
|
@@ -4556,45 +4590,18 @@ var RANK = { none: 0, read: 1, write: 2 };
|
|
|
4556
4590
|
function docsAccessAtLeast(have, need) {
|
|
4557
4591
|
return RANK[have] >= RANK[need];
|
|
4558
4592
|
}
|
|
4559
|
-
function
|
|
4560
|
-
return value === "read" || value === "write";
|
|
4593
|
+
function isDocsAccess(value) {
|
|
4594
|
+
return value === "none" || value === "read" || value === "write";
|
|
4561
4595
|
}
|
|
4562
|
-
function
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
access: row["access"],
|
|
4568
|
-
grantedAt: row["granted_at"]
|
|
4569
|
-
};
|
|
4570
|
-
}
|
|
4571
|
-
async function listChannelDocsAccess(channelId) {
|
|
4572
|
-
const { data, error: error2 } = await getSupabaseClient().from("channel_docs_access").select("*").eq("channel_id", channelId).order("granted_at", { ascending: true });
|
|
4573
|
-
if (error2) throw new Error(`listChannelDocsAccess(${channelId}): ${error2.message}`);
|
|
4574
|
-
return (data ?? []).map(rowToGrant);
|
|
4596
|
+
async function getChannelDocsMemberAccess(channelId) {
|
|
4597
|
+
const { data, error: error2 } = await getSupabaseClient().from("channels").select("docs_member_access").eq("id", channelId).maybeSingle();
|
|
4598
|
+
if (error2) throw new Error(`getChannelDocsMemberAccess(${channelId}): ${error2.message}`);
|
|
4599
|
+
const level = data?.docs_member_access;
|
|
4600
|
+
return isDocsAccess(level) ? level : "none";
|
|
4575
4601
|
}
|
|
4576
|
-
async function
|
|
4577
|
-
const { data, error: error2 } = await getSupabaseClient().from("
|
|
4578
|
-
if (error2) throw new Error(`
|
|
4579
|
-
return data ? rowToGrant(data) : null;
|
|
4580
|
-
}
|
|
4581
|
-
async function upsertChannelDocsAccess(params) {
|
|
4582
|
-
const { data, error: error2 } = await getSupabaseClient().from("channel_docs_access").upsert(
|
|
4583
|
-
{ channel_id: params.channelId, membership_id: params.membershipId, access: params.access },
|
|
4584
|
-
{ onConflict: "channel_id,membership_id" }
|
|
4585
|
-
).select("*").single();
|
|
4586
|
-
if (error2) {
|
|
4587
|
-
throw new Error(
|
|
4588
|
-
`upsertChannelDocsAccess(${params.channelId}, ${params.membershipId}): ${error2.message}`
|
|
4589
|
-
);
|
|
4590
|
-
}
|
|
4591
|
-
return rowToGrant(data);
|
|
4592
|
-
}
|
|
4593
|
-
async function removeChannelDocsAccess(channelId, membershipId) {
|
|
4594
|
-
const { data, error: error2 } = await getSupabaseClient().from("channel_docs_access").delete().eq("channel_id", channelId).eq("membership_id", membershipId).select("id");
|
|
4595
|
-
if (error2) {
|
|
4596
|
-
throw new Error(`removeChannelDocsAccess(${channelId}, ${membershipId}): ${error2.message}`);
|
|
4597
|
-
}
|
|
4602
|
+
async function setChannelDocsMemberAccess(channelId, access) {
|
|
4603
|
+
const { data, error: error2 } = await getSupabaseClient().from("channels").update({ docs_member_access: access }).eq("id", channelId).select("id");
|
|
4604
|
+
if (error2) throw new Error(`setChannelDocsMemberAccess(${channelId}): ${error2.message}`);
|
|
4598
4605
|
return data?.length ? true : false;
|
|
4599
4606
|
}
|
|
4600
4607
|
async function resolveForMembership(membership, channelId) {
|
|
@@ -4603,8 +4610,7 @@ async function resolveForMembership(membership, channelId) {
|
|
|
4603
4610
|
if (members.some((m) => m.membershipId === membership.id && m.role === "channel_admin")) {
|
|
4604
4611
|
return "write";
|
|
4605
4612
|
}
|
|
4606
|
-
|
|
4607
|
-
return grant?.access ?? "none";
|
|
4613
|
+
return getChannelDocsMemberAccess(channelId);
|
|
4608
4614
|
}
|
|
4609
4615
|
async function resolveDocsAccess(ctx, channelId) {
|
|
4610
4616
|
if (ctx.role === "admin") return "write";
|
|
@@ -4626,15 +4632,16 @@ async function filterReadableDocsChannels(ctx, channelIds) {
|
|
|
4626
4632
|
if (ctx.role === "admin") return new Set(channelIds);
|
|
4627
4633
|
const membership = await getMembershipByUserId(ctx.workspaceId, ctx.userId);
|
|
4628
4634
|
if (!membership) return /* @__PURE__ */ new Set();
|
|
4635
|
+
if (channelIds.length === 0) return /* @__PURE__ */ new Set();
|
|
4629
4636
|
const db = getSupabaseClient();
|
|
4630
|
-
const [
|
|
4631
|
-
db.from("
|
|
4637
|
+
const [open, roles] = await Promise.all([
|
|
4638
|
+
db.from("channels").select("id").in("id", channelIds).neq("docs_member_access", "none"),
|
|
4632
4639
|
db.from("channel_members").select("channel_id, role").eq("membership_id", membership.id)
|
|
4633
4640
|
]);
|
|
4634
|
-
if (
|
|
4641
|
+
if (open.error) throw new Error(`filterReadableDocsChannels: ${open.error.message}`);
|
|
4635
4642
|
if (roles.error) throw new Error(`filterReadableDocsChannels: ${roles.error.message}`);
|
|
4636
4643
|
const allowed = /* @__PURE__ */ new Set();
|
|
4637
|
-
for (const r of
|
|
4644
|
+
for (const r of open.data ?? []) allowed.add(r.id);
|
|
4638
4645
|
for (const r of roles.data ?? []) {
|
|
4639
4646
|
if (r.role === "channel_admin") allowed.add(r.channel_id);
|
|
4640
4647
|
}
|
|
@@ -4653,7 +4660,7 @@ var requireDocsAccess = async (c, next) => {
|
|
|
4653
4660
|
};
|
|
4654
4661
|
|
|
4655
4662
|
// src/dashboard/api.ts
|
|
4656
|
-
import { Hono as
|
|
4663
|
+
import { Hono as Hono32 } from "hono";
|
|
4657
4664
|
|
|
4658
4665
|
// src/dashboard/public-share-routes.ts
|
|
4659
4666
|
import { existsSync as existsSync11, readFileSync as readFileSync10, statSync as statSync3 } from "fs";
|
|
@@ -7251,12 +7258,12 @@ async function offlineAgentIdentities() {
|
|
|
7251
7258
|
}
|
|
7252
7259
|
|
|
7253
7260
|
// src/dashboard/host-api.ts
|
|
7254
|
-
import { existsSync as
|
|
7255
|
-
import { join as
|
|
7261
|
+
import { existsSync as existsSync21, readFileSync as readFileSync16, statSync as statSync8 } from "fs";
|
|
7262
|
+
import { join as join23 } from "path";
|
|
7256
7263
|
import sharp3 from "sharp";
|
|
7257
7264
|
|
|
7258
7265
|
// src/dashboard/channels-routes.ts
|
|
7259
|
-
import { existsSync as existsSync12, mkdirSync as
|
|
7266
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync9 } from "fs";
|
|
7260
7267
|
import { Hono as Hono9 } from "hono";
|
|
7261
7268
|
|
|
7262
7269
|
// src/dashboard/feature-flags-routes.ts
|
|
@@ -7329,7 +7336,7 @@ function coerceMemberEmails(raw) {
|
|
|
7329
7336
|
}
|
|
7330
7337
|
function ensureCwdDir(cwd) {
|
|
7331
7338
|
const expanded = isLogicalCwd(cwd) ? resolveLogicalCwd(cwd) : expandPath(cwd);
|
|
7332
|
-
if (!existsSync12(expanded))
|
|
7339
|
+
if (!existsSync12(expanded)) mkdirSync9(expanded, { recursive: true });
|
|
7333
7340
|
}
|
|
7334
7341
|
function toResponse(channel) {
|
|
7335
7342
|
return {
|
|
@@ -7367,7 +7374,6 @@ function createChannelWriteRouter() {
|
|
|
7367
7374
|
const externalChannelId = typeof body["externalChannelId"] === "string" && body["externalChannelId"].trim() ? body["externalChannelId"].trim() : null;
|
|
7368
7375
|
const categoryId = typeof body["categoryId"] === "string" && body["categoryId"].trim() ? body["categoryId"].trim() : null;
|
|
7369
7376
|
try {
|
|
7370
|
-
ensureCwdDir(cwd);
|
|
7371
7377
|
const channel = await createChannel({
|
|
7372
7378
|
name,
|
|
7373
7379
|
cwd,
|
|
@@ -7380,9 +7386,10 @@ function createChannelWriteRouter() {
|
|
|
7380
7386
|
externalChannelId,
|
|
7381
7387
|
categoryId
|
|
7382
7388
|
});
|
|
7389
|
+
ensureCwdDir(cwd);
|
|
7383
7390
|
return c.json(toResponse(channel), 201);
|
|
7384
7391
|
} catch (err) {
|
|
7385
|
-
return c.json({ error: err.message }, err instanceof DefaultChannelError ? 400 : 500);
|
|
7392
|
+
return c.json({ error: err.message }, err instanceof DefaultChannelError || err instanceof ChannelCwdError ? 400 : 500);
|
|
7386
7393
|
}
|
|
7387
7394
|
});
|
|
7388
7395
|
app.put("/:id", async (c) => {
|
|
@@ -7396,7 +7403,6 @@ function createChannelWriteRouter() {
|
|
|
7396
7403
|
if (coerceRuntime(body["runtime"]) === "sandbox" && existing.runtime !== "sandbox" && !await isFeatureEnabled(c, "sandbox"))
|
|
7397
7404
|
return c.json({ error: "feature not enabled" }, 403);
|
|
7398
7405
|
try {
|
|
7399
|
-
ensureCwdDir(cwd);
|
|
7400
7406
|
const channel = await updateChannelById(id, {
|
|
7401
7407
|
name: typeof body["name"] === "string" ? body["name"] : void 0,
|
|
7402
7408
|
cwd,
|
|
@@ -7409,10 +7415,11 @@ function createChannelWriteRouter() {
|
|
|
7409
7415
|
runtime: coerceRuntime(body["runtime"]),
|
|
7410
7416
|
mounts: coerceMounts(body["mounts"])
|
|
7411
7417
|
});
|
|
7418
|
+
ensureCwdDir(cwd);
|
|
7412
7419
|
dropSessionsIfRuntimeChanged(id, existing.runtime, channel.runtime);
|
|
7413
7420
|
return c.json(toResponse(channel), 200);
|
|
7414
7421
|
} catch (err) {
|
|
7415
|
-
return c.json({ error: err.message }, err instanceof DefaultChannelError ? 400 : 500);
|
|
7422
|
+
return c.json({ error: err.message }, err instanceof DefaultChannelError || err instanceof ChannelCwdError ? 400 : 500);
|
|
7416
7423
|
}
|
|
7417
7424
|
});
|
|
7418
7425
|
app.patch("/reorder", async (c) => {
|
|
@@ -7464,12 +7471,12 @@ function createChannelWriteRouter() {
|
|
|
7464
7471
|
patch.archivedAt = body["archived"] ? (/* @__PURE__ */ new Date()).toISOString() : null;
|
|
7465
7472
|
}
|
|
7466
7473
|
try {
|
|
7467
|
-
if (patch.cwd) ensureCwdDir(patch.cwd);
|
|
7468
7474
|
const channel = await updateChannelById(id, patch);
|
|
7475
|
+
if (patch.cwd) ensureCwdDir(patch.cwd);
|
|
7469
7476
|
dropSessionsIfRuntimeChanged(id, existing.runtime, channel.runtime);
|
|
7470
7477
|
return c.json(toResponse(channel), 200);
|
|
7471
7478
|
} catch (err) {
|
|
7472
|
-
return c.json({ error: err.message }, err instanceof DefaultChannelError ? 400 : 500);
|
|
7479
|
+
return c.json({ error: err.message }, err instanceof DefaultChannelError || err instanceof ChannelCwdError ? 400 : 500);
|
|
7473
7480
|
}
|
|
7474
7481
|
});
|
|
7475
7482
|
return app;
|
|
@@ -7478,9 +7485,10 @@ function createChannelWriteRouter() {
|
|
|
7478
7485
|
// src/dashboard/docs-routes.ts
|
|
7479
7486
|
import {
|
|
7480
7487
|
closeSync,
|
|
7488
|
+
createReadStream,
|
|
7481
7489
|
existsSync as existsSync16,
|
|
7482
7490
|
lstatSync as lstatSync2,
|
|
7483
|
-
mkdirSync as
|
|
7491
|
+
mkdirSync as mkdirSync10,
|
|
7484
7492
|
openSync,
|
|
7485
7493
|
readSync,
|
|
7486
7494
|
readdirSync as readdirSync4,
|
|
@@ -7491,6 +7499,7 @@ import {
|
|
|
7491
7499
|
statSync as statSync5,
|
|
7492
7500
|
writeFileSync as writeFileSync9
|
|
7493
7501
|
} from "fs";
|
|
7502
|
+
import { Readable } from "stream";
|
|
7494
7503
|
import { basename as basename6, dirname as dirname5, extname, join as join20, relative, resolve as resolve4, sep } from "path";
|
|
7495
7504
|
import { Hono as Hono10 } from "hono";
|
|
7496
7505
|
import { streamSSE } from "hono/streaming";
|
|
@@ -9547,7 +9556,8 @@ var ChatAgent = class {
|
|
|
9547
9556
|
};
|
|
9548
9557
|
}
|
|
9549
9558
|
const memory = await loadAgentMemory(owningProfile, thread.channel_id, {
|
|
9550
|
-
readable: docsAccess !== "none"
|
|
9559
|
+
readable: docsAccess !== "none",
|
|
9560
|
+
...isHumanAuthor(msg.author) ? { senderHandle: msg.author } : {}
|
|
9551
9561
|
});
|
|
9552
9562
|
if (memory.enabled) channelConfig = { ...channelConfig, disableAutoMemory: true };
|
|
9553
9563
|
if (memory.memoryPrompt) channelConfig = { ...channelConfig, memoryPrompt: memory.memoryPrompt };
|
|
@@ -10346,7 +10356,7 @@ var MEDIA_EXTENSIONS = {
|
|
|
10346
10356
|
".csv": "text/csv; charset=utf-8",
|
|
10347
10357
|
".json": "application/json"
|
|
10348
10358
|
};
|
|
10349
|
-
var MAX_RAW_BYTES =
|
|
10359
|
+
var MAX_RAW_BYTES = 100 * 1024 * 1024;
|
|
10350
10360
|
var ASSET_LIST_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
10351
10361
|
".png",
|
|
10352
10362
|
".jpg",
|
|
@@ -10362,7 +10372,7 @@ var ASSET_LIST_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
10362
10372
|
".txt",
|
|
10363
10373
|
".csv"
|
|
10364
10374
|
]);
|
|
10365
|
-
var MAX_UPLOAD_BYTES =
|
|
10375
|
+
var MAX_UPLOAD_BYTES = 20 * 1024 * 1024;
|
|
10366
10376
|
var CODE_EXTENSIONS = {
|
|
10367
10377
|
".js": "javascript",
|
|
10368
10378
|
".jsx": "javascript",
|
|
@@ -10749,7 +10759,7 @@ function createDocFolder(root, relDir, name) {
|
|
|
10749
10759
|
return { status: "exists" };
|
|
10750
10760
|
} catch {
|
|
10751
10761
|
}
|
|
10752
|
-
|
|
10762
|
+
mkdirSync10(abs);
|
|
10753
10763
|
return { status: "ok", path: relative(root, abs).split(sep).join("/"), name: safe };
|
|
10754
10764
|
}
|
|
10755
10765
|
function createDocFile(root, relDir, name, content) {
|
|
@@ -10866,13 +10876,15 @@ function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
|
10866
10876
|
}
|
|
10867
10877
|
if (!stat.isFile()) return c.json({ error: "not found" }, 404);
|
|
10868
10878
|
if (stat.size > MAX_RAW_BYTES) return c.json({ error: "file too large" }, 413);
|
|
10869
|
-
const
|
|
10870
|
-
const
|
|
10871
|
-
|
|
10872
|
-
|
|
10873
|
-
|
|
10874
|
-
|
|
10875
|
-
|
|
10879
|
+
const name = basename6(resolved2.abs);
|
|
10880
|
+
const asciiName = name.replace(/[^\x20-\x7e]/g, "_").replace(/["\\]/g, "_");
|
|
10881
|
+
const disposition = download ? "attachment" : "inline";
|
|
10882
|
+
const stream = Readable.toWeb(createReadStream(resolved2.abs));
|
|
10883
|
+
return c.body(stream, 200, {
|
|
10884
|
+
"Content-Type": resolved2.contentType,
|
|
10885
|
+
"Content-Length": String(stat.size),
|
|
10886
|
+
"Content-Disposition": `${disposition}; filename="${asciiName}"; filename*=UTF-8''${encodeURIComponent(name)}`
|
|
10887
|
+
});
|
|
10876
10888
|
});
|
|
10877
10889
|
app.get("/:id/docs/html", (c) => {
|
|
10878
10890
|
const { cwd } = resolveChannelConfig(c.req.param("id"));
|
|
@@ -11663,6 +11675,9 @@ var PROVIDER_LABELS = {
|
|
|
11663
11675
|
};
|
|
11664
11676
|
var TTL_MS2 = 6e4;
|
|
11665
11677
|
var cache6 = /* @__PURE__ */ new Map();
|
|
11678
|
+
function clearProviderStatusCache() {
|
|
11679
|
+
cache6.clear();
|
|
11680
|
+
}
|
|
11666
11681
|
function binaryOnPath(bin) {
|
|
11667
11682
|
try {
|
|
11668
11683
|
execFileSync3(bin, ["--version"], { stdio: "ignore" });
|
|
@@ -11697,7 +11712,7 @@ function probe2(runner) {
|
|
|
11697
11712
|
runner,
|
|
11698
11713
|
label: label2,
|
|
11699
11714
|
connected: false,
|
|
11700
|
-
reason: `${label2} is installed but signed out
|
|
11715
|
+
reason: `${label2} is installed but signed out. Use Sign in below.`
|
|
11701
11716
|
};
|
|
11702
11717
|
}
|
|
11703
11718
|
return { runner, label: label2, connected: true };
|
|
@@ -11707,7 +11722,7 @@ function withAuthFailure(status) {
|
|
|
11707
11722
|
return {
|
|
11708
11723
|
...status,
|
|
11709
11724
|
connected: false,
|
|
11710
|
-
reason:
|
|
11725
|
+
reason: "Sign-in expired and could not be refreshed. Use Sign in again below."
|
|
11711
11726
|
};
|
|
11712
11727
|
}
|
|
11713
11728
|
function providerStatuses() {
|
|
@@ -11721,6 +11736,232 @@ function providerStatuses() {
|
|
|
11721
11736
|
return statuses.map((st) => st.runner === "claude" && st.connected ? withAuthFailure(st) : st);
|
|
11722
11737
|
}
|
|
11723
11738
|
|
|
11739
|
+
// src/dashboard/provider-login.ts
|
|
11740
|
+
import { spawn as spawn6 } from "child_process";
|
|
11741
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
11742
|
+
import { chmodSync, existsSync as existsSync20, mkdirSync as mkdirSync11, readFileSync as readFileSync15, writeFileSync as writeFileSync10 } from "fs";
|
|
11743
|
+
import { createRequire } from "module";
|
|
11744
|
+
import { dirname as dirname7, join as join22 } from "path";
|
|
11745
|
+
import { Hono as Hono13 } from "hono";
|
|
11746
|
+
var SESSION_TTL_MS = 10 * 6e4;
|
|
11747
|
+
var URL_WAIT_MS = 3e4;
|
|
11748
|
+
var TOKEN_WAIT_MS = 6e4;
|
|
11749
|
+
var sessions = /* @__PURE__ */ new Map();
|
|
11750
|
+
function stripAnsi(raw) {
|
|
11751
|
+
return raw.replace(/\x1b\][^\x07]*\x07/g, "").replace(/\x1b\[\d*G/g, " ").replace(/\x1b\[[0-9;?<>=]*[A-Za-z~]/g, "").replace(/\x1b[78=>]/g, "");
|
|
11752
|
+
}
|
|
11753
|
+
function parseClaudeAuthUrl(raw) {
|
|
11754
|
+
return stripAnsi(raw).match(/https:\/\/\S+\/oauth\/authorize\?\S+/)?.[0];
|
|
11755
|
+
}
|
|
11756
|
+
function parseClaudeToken(raw) {
|
|
11757
|
+
return stripAnsi(raw).match(/sk-ant-oat[A-Za-z0-9_-]+/)?.[0];
|
|
11758
|
+
}
|
|
11759
|
+
function parseCodexDeviceAuth(raw) {
|
|
11760
|
+
const text = stripAnsi(raw);
|
|
11761
|
+
const url = text.match(/https:\/\/\S+\/device\b\S*/)?.[0];
|
|
11762
|
+
const code = text.match(/\b([A-Z0-9]{4,}-[A-Z0-9]{4,})\b/)?.[1];
|
|
11763
|
+
return url && code ? { url, code } : void 0;
|
|
11764
|
+
}
|
|
11765
|
+
function upsertEnvLine(content, key, value) {
|
|
11766
|
+
const line = `${key}=${value}`;
|
|
11767
|
+
const re = new RegExp(`^\\s*(?:export\\s+)?${key}=.*$`, "m");
|
|
11768
|
+
if (re.test(content)) return content.replace(re, line);
|
|
11769
|
+
return `${content}${content && !content.endsWith("\n") ? "\n" : ""}${line}
|
|
11770
|
+
`;
|
|
11771
|
+
}
|
|
11772
|
+
function finish(id, status, error2) {
|
|
11773
|
+
const s = sessions.get(id);
|
|
11774
|
+
if (!s || s.status !== "pending") return;
|
|
11775
|
+
s.status = status;
|
|
11776
|
+
s.error = error2;
|
|
11777
|
+
s.kill();
|
|
11778
|
+
clearTimeout(s.timer);
|
|
11779
|
+
s.timer = setTimeout(() => sessions.delete(id), 6e4);
|
|
11780
|
+
clearProviderStatusCache();
|
|
11781
|
+
}
|
|
11782
|
+
function track(session) {
|
|
11783
|
+
const id = randomUUID2();
|
|
11784
|
+
const s = {
|
|
11785
|
+
...session,
|
|
11786
|
+
status: "pending",
|
|
11787
|
+
output: "",
|
|
11788
|
+
timer: setTimeout(() => finish(id, "failed", "Sign-in timed out. Start again."), SESSION_TTL_MS)
|
|
11789
|
+
};
|
|
11790
|
+
sessions.set(id, s);
|
|
11791
|
+
return [id, s];
|
|
11792
|
+
}
|
|
11793
|
+
function waitFor(s, parse2, ms) {
|
|
11794
|
+
return new Promise((resolve7) => {
|
|
11795
|
+
const started = Date.now();
|
|
11796
|
+
const tick = setInterval(() => {
|
|
11797
|
+
const v = parse2();
|
|
11798
|
+
if (v !== void 0 || s.status !== "pending" || Date.now() - started > ms) {
|
|
11799
|
+
clearInterval(tick);
|
|
11800
|
+
resolve7(v);
|
|
11801
|
+
}
|
|
11802
|
+
}, 200);
|
|
11803
|
+
});
|
|
11804
|
+
}
|
|
11805
|
+
function loadPty() {
|
|
11806
|
+
const require2 = createRequire(import.meta.url);
|
|
11807
|
+
const root = dirname7(require2.resolve("node-pty/package.json"));
|
|
11808
|
+
const helper = join22(root, "prebuilds", `${process.platform}-${process.arch}`, "spawn-helper");
|
|
11809
|
+
try {
|
|
11810
|
+
if (existsSync20(helper)) chmodSync(helper, 493);
|
|
11811
|
+
} catch {
|
|
11812
|
+
}
|
|
11813
|
+
return require2("node-pty");
|
|
11814
|
+
}
|
|
11815
|
+
function cliEnv() {
|
|
11816
|
+
const configDir2 = profileClaudeConfigDir();
|
|
11817
|
+
const codexHome = profileEnv("CODEX_HOME");
|
|
11818
|
+
return {
|
|
11819
|
+
...process.env,
|
|
11820
|
+
// Never pop a browser on the Host: the person signing in isn't there.
|
|
11821
|
+
BROWSER: "true",
|
|
11822
|
+
...configDir2 ? { CLAUDE_CONFIG_DIR: configDir2 } : {},
|
|
11823
|
+
...codexHome ? { CODEX_HOME: codexHome } : {}
|
|
11824
|
+
};
|
|
11825
|
+
}
|
|
11826
|
+
async function startClaude() {
|
|
11827
|
+
const pty = loadPty().spawn("claude", ["setup-token"], {
|
|
11828
|
+
// Wide enough that the URL and token are never wrapped.
|
|
11829
|
+
cols: 2e3,
|
|
11830
|
+
rows: 50,
|
|
11831
|
+
env: cliEnv()
|
|
11832
|
+
});
|
|
11833
|
+
const [id, s] = track({ runner: "claude", kill: () => pty.kill(), configFile: paths.CONFIG_FILE, pty });
|
|
11834
|
+
pty.onData((d) => {
|
|
11835
|
+
s.output += d;
|
|
11836
|
+
});
|
|
11837
|
+
pty.onExit(() => {
|
|
11838
|
+
if (s.status === "pending" && !parseClaudeToken(s.output)) {
|
|
11839
|
+
finish(id, "failed", "The claude CLI exited before sign-in finished.");
|
|
11840
|
+
}
|
|
11841
|
+
});
|
|
11842
|
+
const url = await waitFor(s, () => parseClaudeAuthUrl(s.output), URL_WAIT_MS);
|
|
11843
|
+
if (!url) {
|
|
11844
|
+
finish(id, "failed");
|
|
11845
|
+
throw new Error("claude setup-token did not print a sign-in URL.");
|
|
11846
|
+
}
|
|
11847
|
+
return { id, url };
|
|
11848
|
+
}
|
|
11849
|
+
async function submitClaudeCode(id, code) {
|
|
11850
|
+
const s = sessions.get(id);
|
|
11851
|
+
if (!s || s.runner !== "claude" || !s.pty) throw new Error("Sign-in session not found. Start again.");
|
|
11852
|
+
if (s.status !== "pending") throw new Error(s.error ?? "Sign-in already finished.");
|
|
11853
|
+
s.output = "";
|
|
11854
|
+
s.pty.write(`${code.trim()}\r`);
|
|
11855
|
+
const token = await waitFor(s, () => parseClaudeToken(s.output), TOKEN_WAIT_MS);
|
|
11856
|
+
if (!token) {
|
|
11857
|
+
finish(id, "failed", "Claude did not accept that code. Start again and paste the full code.");
|
|
11858
|
+
throw new Error("Claude did not accept that code. Start again and paste the full code.");
|
|
11859
|
+
}
|
|
11860
|
+
mkdirSync11(dirname7(s.configFile), { recursive: true });
|
|
11861
|
+
const current = existsSync20(s.configFile) ? readFileSync15(s.configFile, "utf-8") : "";
|
|
11862
|
+
writeFileSync10(s.configFile, upsertEnvLine(current, "CLAUDE_CODE_OAUTH_TOKEN", token), { mode: 384 });
|
|
11863
|
+
const cached = cachedProfileConfig();
|
|
11864
|
+
if (cached) cached.env["CLAUDE_CODE_OAUTH_TOKEN"] = token;
|
|
11865
|
+
clearClaudeAuthFailed();
|
|
11866
|
+
finish(id, "done");
|
|
11867
|
+
}
|
|
11868
|
+
async function startCodex() {
|
|
11869
|
+
const child = spawn6("codex", ["login", "--device-auth"], {
|
|
11870
|
+
env: cliEnv(),
|
|
11871
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
11872
|
+
});
|
|
11873
|
+
const [id, s] = track({ runner: "codex", kill: () => child.kill(), configFile: paths.CONFIG_FILE });
|
|
11874
|
+
const onData = (d) => {
|
|
11875
|
+
s.output += d.toString();
|
|
11876
|
+
};
|
|
11877
|
+
child.stdout?.on("data", onData);
|
|
11878
|
+
child.stderr?.on("data", onData);
|
|
11879
|
+
child.on("error", () => finish(id, "failed", "Could not run the codex CLI on the Host."));
|
|
11880
|
+
child.on(
|
|
11881
|
+
"exit",
|
|
11882
|
+
(code) => finish(id, code === 0 ? "done" : "failed", code === 0 ? void 0 : "Codex sign-in did not complete.")
|
|
11883
|
+
);
|
|
11884
|
+
const auth = await waitFor(s, () => parseCodexDeviceAuth(s.output), URL_WAIT_MS);
|
|
11885
|
+
if (!auth) {
|
|
11886
|
+
finish(id, "failed");
|
|
11887
|
+
throw new Error("codex login did not print a device code.");
|
|
11888
|
+
}
|
|
11889
|
+
return { id, ...auth };
|
|
11890
|
+
}
|
|
11891
|
+
function requireAdmin3(auth) {
|
|
11892
|
+
return auth?.role === "admin" ? null : "admin access required";
|
|
11893
|
+
}
|
|
11894
|
+
function failure2(c, err) {
|
|
11895
|
+
return c.json({ error: err.message }, 500);
|
|
11896
|
+
}
|
|
11897
|
+
function createProviderLoginRouter() {
|
|
11898
|
+
const app = new Hono13();
|
|
11899
|
+
app.use("*", async (c, next) => {
|
|
11900
|
+
const denied = requireAdmin3(c.get("auth"));
|
|
11901
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
11902
|
+
await next();
|
|
11903
|
+
});
|
|
11904
|
+
app.post("/:runner/login", async (c) => {
|
|
11905
|
+
const runner = c.req.param("runner");
|
|
11906
|
+
try {
|
|
11907
|
+
if (runner === "claude") return c.json(await startClaude());
|
|
11908
|
+
if (runner === "codex") return c.json(await startCodex());
|
|
11909
|
+
} catch (err) {
|
|
11910
|
+
return failure2(c, err);
|
|
11911
|
+
}
|
|
11912
|
+
return c.json({ error: "unknown provider" }, 404);
|
|
11913
|
+
});
|
|
11914
|
+
app.post("/claude/login/:id/code", async (c) => {
|
|
11915
|
+
const body = await c.req.json().catch(() => ({}));
|
|
11916
|
+
const code = body["code"];
|
|
11917
|
+
if (typeof code !== "string" || !code.trim()) return c.json({ error: "code is required" }, 400);
|
|
11918
|
+
try {
|
|
11919
|
+
await submitClaudeCode(c.req.param("id"), code);
|
|
11920
|
+
} catch (err) {
|
|
11921
|
+
return c.json({ error: err.message }, 400);
|
|
11922
|
+
}
|
|
11923
|
+
return c.json({ status: "done" });
|
|
11924
|
+
});
|
|
11925
|
+
app.get("/:runner/login/:id", (c) => {
|
|
11926
|
+
const s = sessions.get(c.req.param("id"));
|
|
11927
|
+
if (!s || s.runner !== c.req.param("runner")) return c.json({ error: "session not found" }, 404);
|
|
11928
|
+
return c.json({ status: s.status, ...s.error ? { error: s.error } : {} });
|
|
11929
|
+
});
|
|
11930
|
+
return app;
|
|
11931
|
+
}
|
|
11932
|
+
async function handleProviderLoginRequest(req) {
|
|
11933
|
+
const app = new Hono13();
|
|
11934
|
+
app.use("*", async (c, next) => {
|
|
11935
|
+
c.set("auth", { role: "admin" });
|
|
11936
|
+
await next();
|
|
11937
|
+
});
|
|
11938
|
+
app.route("/", createProviderLoginRouter());
|
|
11939
|
+
const res = await app.request(req.path, {
|
|
11940
|
+
method: req.method,
|
|
11941
|
+
...req.body !== void 0 && {
|
|
11942
|
+
body: JSON.stringify(req.body),
|
|
11943
|
+
headers: { "content-type": "application/json" }
|
|
11944
|
+
}
|
|
11945
|
+
});
|
|
11946
|
+
return { status: res.status, body: await res.json().catch(() => null) };
|
|
11947
|
+
}
|
|
11948
|
+
function registerRelayedProviderLoginRoutes(api, agent) {
|
|
11949
|
+
const relay3 = async (c) => {
|
|
11950
|
+
const denied = requireAdmin3(c.get("auth"));
|
|
11951
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
11952
|
+
const method = c.req.method;
|
|
11953
|
+
const body = method === "GET" ? void 0 : await c.req.json().catch(() => null);
|
|
11954
|
+
const path = c.req.path.slice(c.req.path.indexOf("/settings/providers") + "/settings/providers".length);
|
|
11955
|
+
const result = await agent.providerLoginRequest?.({ method, path, body });
|
|
11956
|
+
if (!result) {
|
|
11957
|
+
return c.json({ error: "Signing in needs the Host machine, which is not connected right now." }, 503);
|
|
11958
|
+
}
|
|
11959
|
+
return c.json(result.body, result.status);
|
|
11960
|
+
};
|
|
11961
|
+
api.all("/settings/providers/:runner/login", relay3);
|
|
11962
|
+
api.all("/settings/providers/:runner/login/*", relay3);
|
|
11963
|
+
}
|
|
11964
|
+
|
|
11724
11965
|
// src/dashboard/host-api.ts
|
|
11725
11966
|
function registerHostRoutes(api) {
|
|
11726
11967
|
api.get("/channels/:id/skills", (c) => {
|
|
@@ -11769,6 +12010,7 @@ function registerHostRoutes(api) {
|
|
|
11769
12010
|
return c.json({ ...await saveWorkspaceBranding(patch), ...local });
|
|
11770
12011
|
});
|
|
11771
12012
|
api.get("/settings/providers", (c) => c.json({ providers: providerStatuses() }));
|
|
12013
|
+
api.route("/settings/providers", createProviderLoginRouter());
|
|
11772
12014
|
api.get(
|
|
11773
12015
|
"/settings/usage-limit",
|
|
11774
12016
|
(c) => c.json({
|
|
@@ -11827,8 +12069,8 @@ function registerHostRoutes(api) {
|
|
|
11827
12069
|
const errors = c.req.query("error") === "true" || c.req.query("error") === "1";
|
|
11828
12070
|
const limit = Math.min(Number(c.req.query("limit")) || 200, 2e3);
|
|
11829
12071
|
const filename = errors ? "custie-error.log" : "custie.log";
|
|
11830
|
-
const file =
|
|
11831
|
-
const exists =
|
|
12072
|
+
const file = join23(paths.LOG_DIR, filename);
|
|
12073
|
+
const exists = existsSync21(file);
|
|
11832
12074
|
return c.json({
|
|
11833
12075
|
file,
|
|
11834
12076
|
exists,
|
|
@@ -11838,18 +12080,18 @@ function registerHostRoutes(api) {
|
|
|
11838
12080
|
});
|
|
11839
12081
|
}
|
|
11840
12082
|
function tailFile(file, limit) {
|
|
11841
|
-
if (!
|
|
11842
|
-
const lines =
|
|
12083
|
+
if (!existsSync21(file)) return [];
|
|
12084
|
+
const lines = readFileSync16(file, "utf-8").split("\n");
|
|
11843
12085
|
if (lines.at(-1) === "") lines.pop();
|
|
11844
12086
|
return lines.slice(-limit);
|
|
11845
12087
|
}
|
|
11846
12088
|
|
|
11847
12089
|
// src/dashboard/data-api.ts
|
|
11848
|
-
import { existsSync as
|
|
12090
|
+
import { existsSync as existsSync23 } from "fs";
|
|
11849
12091
|
import sharp5 from "sharp";
|
|
11850
12092
|
|
|
11851
12093
|
// src/dashboard/automations-routes.ts
|
|
11852
|
-
import { Hono as
|
|
12094
|
+
import { Hono as Hono14 } from "hono";
|
|
11853
12095
|
var TYPES2 = ["schedule", "trigger", "mention-trigger"];
|
|
11854
12096
|
function isType(t) {
|
|
11855
12097
|
return TYPES2.includes(t);
|
|
@@ -11866,7 +12108,7 @@ function statusFor(message) {
|
|
|
11866
12108
|
return 400;
|
|
11867
12109
|
}
|
|
11868
12110
|
function createAutomationWriteRouter() {
|
|
11869
|
-
const app = new
|
|
12111
|
+
const app = new Hono14();
|
|
11870
12112
|
const manager = () => new AutomationManager();
|
|
11871
12113
|
app.get("/:type/:name", (c) => {
|
|
11872
12114
|
const item = manager().get(c.req.param("name"));
|
|
@@ -11922,9 +12164,9 @@ function createAutomationWriteRouter() {
|
|
|
11922
12164
|
}
|
|
11923
12165
|
|
|
11924
12166
|
// src/dashboard/categories-routes.ts
|
|
11925
|
-
import { Hono as
|
|
12167
|
+
import { Hono as Hono15 } from "hono";
|
|
11926
12168
|
function createCategoryWriteRouter() {
|
|
11927
|
-
const app = new
|
|
12169
|
+
const app = new Hono15();
|
|
11928
12170
|
app.get("/", async (c) => {
|
|
11929
12171
|
const { data, error: error2 } = await getSupabaseClient().from("categories").select("*").eq("profile", getProfile()).order("position", { ascending: true });
|
|
11930
12172
|
if (error2) return c.json({ error: error2.message }, 500);
|
|
@@ -11978,7 +12220,7 @@ function createCategoryWriteRouter() {
|
|
|
11978
12220
|
}
|
|
11979
12221
|
|
|
11980
12222
|
// src/dashboard/chat-routes.ts
|
|
11981
|
-
import { Hono as
|
|
12223
|
+
import { Hono as Hono16 } from "hono";
|
|
11982
12224
|
|
|
11983
12225
|
// src/dashboard/fork-summary.ts
|
|
11984
12226
|
var GROQ_API_URL6 = "https://api.groq.com/openai/v1/chat/completions";
|
|
@@ -12398,7 +12640,7 @@ async function withPreviews(supabase, threads) {
|
|
|
12398
12640
|
threads.map((t) => t.id),
|
|
12399
12641
|
THREAD_CHUNK_SIZE
|
|
12400
12642
|
).map(
|
|
12401
|
-
(ids) => supabase.from("thread_replies").select("thread_id, body, author").in("thread_id", ids).is("archived_at", null).order("created_at", { ascending: true })
|
|
12643
|
+
(ids) => supabase.from("thread_replies").select("thread_id, body, author, external_user_id").in("thread_id", ids).is("archived_at", null).order("created_at", { ascending: true })
|
|
12402
12644
|
)
|
|
12403
12645
|
);
|
|
12404
12646
|
const previewByThread = /* @__PURE__ */ new Map();
|
|
@@ -12408,8 +12650,15 @@ async function withPreviews(supabase, threads) {
|
|
|
12408
12650
|
if (entry) {
|
|
12409
12651
|
entry.count += 1;
|
|
12410
12652
|
entry.lastAuthor = r.author;
|
|
12653
|
+
entry.participants.delete(r.author);
|
|
12654
|
+
entry.participants.set(r.author, r.external_user_id ?? null);
|
|
12411
12655
|
} else
|
|
12412
|
-
previewByThread.set(r.thread_id, {
|
|
12656
|
+
previewByThread.set(r.thread_id, {
|
|
12657
|
+
count: 1,
|
|
12658
|
+
firstBody: r.body,
|
|
12659
|
+
lastAuthor: r.author,
|
|
12660
|
+
participants: /* @__PURE__ */ new Map()
|
|
12661
|
+
});
|
|
12413
12662
|
}
|
|
12414
12663
|
}
|
|
12415
12664
|
return threads.map((t) => {
|
|
@@ -12421,7 +12670,9 @@ async function withPreviews(supabase, threads) {
|
|
|
12421
12670
|
// Who spoke last, counting only replies AFTER the opening message —
|
|
12422
12671
|
// the reader's own handle here means there is nothing new for them to
|
|
12423
12672
|
// read, on this browser or any other (see web/src/lib/thread-unread).
|
|
12424
|
-
last_reply_author: entry && entry.count > 1 ? entry.lastAuthor : null
|
|
12673
|
+
last_reply_author: entry && entry.count > 1 ? entry.lastAuthor : null,
|
|
12674
|
+
// Everyone who replied, most recent first, for the row's avatar stack.
|
|
12675
|
+
participants: [...entry?.participants ?? []].reverse().map(([author, external_user_id]) => ({ author, external_user_id }))
|
|
12425
12676
|
};
|
|
12426
12677
|
});
|
|
12427
12678
|
}
|
|
@@ -12456,7 +12707,7 @@ async function generateAutoTitle(threadId, channelId, body) {
|
|
|
12456
12707
|
await supabase.from("threads").update({ auto_title: autoTitle }).eq("id", threadId).is("title", null);
|
|
12457
12708
|
}
|
|
12458
12709
|
function createChatRouter(agent = {}) {
|
|
12459
|
-
const app = new
|
|
12710
|
+
const app = new Hono16();
|
|
12460
12711
|
const {
|
|
12461
12712
|
onChatMessage: onMessage,
|
|
12462
12713
|
isAgentThinking,
|
|
@@ -12963,7 +13214,7 @@ function createChatRouter(agent = {}) {
|
|
|
12963
13214
|
}
|
|
12964
13215
|
|
|
12965
13216
|
// src/dashboard/doc-shares-routes.ts
|
|
12966
|
-
import { Hono as
|
|
13217
|
+
import { Hono as Hono17 } from "hono";
|
|
12967
13218
|
|
|
12968
13219
|
// src/dashboard/doc-share-features.ts
|
|
12969
13220
|
import { DeleteObjectCommand as DeleteObjectCommand3 } from "@aws-sdk/client-s3";
|
|
@@ -13147,7 +13398,7 @@ function resolveChannel2(c) {
|
|
|
13147
13398
|
}
|
|
13148
13399
|
var LIST_LIMIT = 500;
|
|
13149
13400
|
function createDocShareListRouter() {
|
|
13150
|
-
const app = new
|
|
13401
|
+
const app = new Hono17();
|
|
13151
13402
|
app.get("/shares", async (c) => {
|
|
13152
13403
|
const viewer = c.get("auth");
|
|
13153
13404
|
const readable = await filterReadableDocsChannels(
|
|
@@ -13191,7 +13442,7 @@ function createDocShareListRouter() {
|
|
|
13191
13442
|
return app;
|
|
13192
13443
|
}
|
|
13193
13444
|
function createDocShareWriteRouter(agent = {}) {
|
|
13194
|
-
const app = new
|
|
13445
|
+
const app = new Hono17();
|
|
13195
13446
|
app.use("/:id/docs/share", requireDocsAccess);
|
|
13196
13447
|
app.use("/:id/docs/share/*", requireDocsAccess);
|
|
13197
13448
|
app.use("/:id/docs/shares", requireDocsAccess);
|
|
@@ -13348,7 +13599,7 @@ function createDocShareWriteRouter(agent = {}) {
|
|
|
13348
13599
|
}
|
|
13349
13600
|
|
|
13350
13601
|
// src/dashboard/search-routes.ts
|
|
13351
|
-
import { Hono as
|
|
13602
|
+
import { Hono as Hono18 } from "hono";
|
|
13352
13603
|
function matchScore(q, fields) {
|
|
13353
13604
|
const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
|
|
13354
13605
|
if (terms.length === 0) return 0;
|
|
@@ -13496,7 +13747,7 @@ function searchAutomations(q) {
|
|
|
13496
13747
|
}
|
|
13497
13748
|
var RESULT_LIMIT = 30;
|
|
13498
13749
|
function createSearchRouter() {
|
|
13499
|
-
const app = new
|
|
13750
|
+
const app = new Hono18();
|
|
13500
13751
|
app.get("/", async (c) => {
|
|
13501
13752
|
const q = c.req.query("q")?.trim();
|
|
13502
13753
|
if (!q) return c.json({ results: [] });
|
|
@@ -13512,8 +13763,8 @@ function createSearchRouter() {
|
|
|
13512
13763
|
}
|
|
13513
13764
|
|
|
13514
13765
|
// src/dashboard/uploads-routes.ts
|
|
13515
|
-
import { randomUUID as
|
|
13516
|
-
import { Hono as
|
|
13766
|
+
import { randomUUID as randomUUID3 } from "crypto";
|
|
13767
|
+
import { Hono as Hono19 } from "hono";
|
|
13517
13768
|
import { PutObjectCommand as PutObjectCommand3, GetObjectCommand as GetObjectCommand5 } from "@aws-sdk/client-s3";
|
|
13518
13769
|
import { getSignedUrl as getSignedUrl4 } from "@aws-sdk/s3-request-presigner";
|
|
13519
13770
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -13528,7 +13779,7 @@ function encodeKeyForUrl(key) {
|
|
|
13528
13779
|
return key.split("/").map(encodeURIComponent).join("/");
|
|
13529
13780
|
}
|
|
13530
13781
|
function createUploadsRouter() {
|
|
13531
|
-
const app = new
|
|
13782
|
+
const app = new Hono19();
|
|
13532
13783
|
app.post("/uploads", async (c) => {
|
|
13533
13784
|
const body = await c.req.parseBody().catch(() => null);
|
|
13534
13785
|
const file = body?.["file"];
|
|
@@ -13536,7 +13787,7 @@ function createUploadsRouter() {
|
|
|
13536
13787
|
if (file.size > MAX_FILE_BYTES) {
|
|
13537
13788
|
return c.json({ error: `file must be ${MAX_FILE_BYTES / (1024 * 1024)}MB or smaller` }, 400);
|
|
13538
13789
|
}
|
|
13539
|
-
const key = `${KEY_PREFIX2}/${getProfile()}/${
|
|
13790
|
+
const key = `${KEY_PREFIX2}/${getProfile()}/${randomUUID3()}-${sanitizeFilename(file.name)}`;
|
|
13540
13791
|
const buffer = Buffer.from(await file.arrayBuffer());
|
|
13541
13792
|
await getR2Client().send(
|
|
13542
13793
|
new PutObjectCommand3({
|
|
@@ -13573,14 +13824,14 @@ function createUploadsRouter() {
|
|
|
13573
13824
|
}
|
|
13574
13825
|
|
|
13575
13826
|
// src/dashboard/transcribe-routes.ts
|
|
13576
|
-
import { Hono as
|
|
13827
|
+
import { Hono as Hono20 } from "hono";
|
|
13577
13828
|
var GROQ_TRANSCRIBE_URL = "https://api.groq.com/openai/v1/audio/transcriptions";
|
|
13578
13829
|
var MODEL = "whisper-large-v3-turbo";
|
|
13579
13830
|
var MAX_AUDIO_BYTES = 5 * 1024 * 1024;
|
|
13580
13831
|
var MAX_PROMPT_CHARS = 400;
|
|
13581
13832
|
var NO_SPEECH_THRESHOLD = 0.6;
|
|
13582
13833
|
function createTranscribeRouter() {
|
|
13583
|
-
const app = new
|
|
13834
|
+
const app = new Hono20();
|
|
13584
13835
|
app.post("/transcribe", async (c) => {
|
|
13585
13836
|
if (!await isFeatureEnabled(c, "dictation"))
|
|
13586
13837
|
return c.json({ error: "feature not enabled" }, 403);
|
|
@@ -13619,8 +13870,8 @@ function createTranscribeRouter() {
|
|
|
13619
13870
|
}
|
|
13620
13871
|
|
|
13621
13872
|
// src/dashboard/avatars-routes.ts
|
|
13622
|
-
import { createHash as createHash10, randomUUID as
|
|
13623
|
-
import { Hono as
|
|
13873
|
+
import { createHash as createHash10, randomUUID as randomUUID4 } from "crypto";
|
|
13874
|
+
import { Hono as Hono21 } from "hono";
|
|
13624
13875
|
import sharp4 from "sharp";
|
|
13625
13876
|
import { PutObjectCommand as PutObjectCommand4, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
|
|
13626
13877
|
var AVATAR_SIZE = 192;
|
|
@@ -13658,7 +13909,7 @@ async function storeAvatar(file, owner) {
|
|
|
13658
13909
|
if (file.size > MAX_AVATAR_BYTES) return { error: "avatar must be 2MB or smaller" };
|
|
13659
13910
|
const original = Buffer.from(await file.arrayBuffer());
|
|
13660
13911
|
const resized = await sharp4(original).resize({ width: AVATAR_SIZE, height: AVATAR_SIZE, fit: "cover" }).png().toBuffer();
|
|
13661
|
-
const key = `${avatarKeyPrefix()}${owner}-${
|
|
13912
|
+
const key = `${avatarKeyPrefix()}${owner}-${randomUUID4()}.png`;
|
|
13662
13913
|
await getR2Client().send(
|
|
13663
13914
|
new PutObjectCommand4({
|
|
13664
13915
|
Bucket: getR2Bucket(),
|
|
@@ -13673,7 +13924,7 @@ function avatarKeyPrefix() {
|
|
|
13673
13924
|
return `${KEY_PREFIX3}/${getProfile()}/`;
|
|
13674
13925
|
}
|
|
13675
13926
|
function createAvatarsRouter() {
|
|
13676
|
-
const app = new
|
|
13927
|
+
const app = new Hono21();
|
|
13677
13928
|
app.get("/avatars/slack", async (c) => {
|
|
13678
13929
|
const ctx = await resolveAuthContext(c);
|
|
13679
13930
|
if (!ctx) return c.json({ error: "not signed in" }, 401);
|
|
@@ -13722,7 +13973,7 @@ function createAvatarsRouter() {
|
|
|
13722
13973
|
}
|
|
13723
13974
|
|
|
13724
13975
|
// src/dashboard/members-routes.ts
|
|
13725
|
-
import { Hono as
|
|
13976
|
+
import { Hono as Hono22 } from "hono";
|
|
13726
13977
|
function inviteLinkFor(c, token) {
|
|
13727
13978
|
const url = new URL("/auth/invite", callbackUrl(c));
|
|
13728
13979
|
url.searchParams.set("token", token);
|
|
@@ -13730,7 +13981,7 @@ function inviteLinkFor(c, token) {
|
|
|
13730
13981
|
}
|
|
13731
13982
|
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
13732
13983
|
function createMembersRouter() {
|
|
13733
|
-
const members = new
|
|
13984
|
+
const members = new Hono22();
|
|
13734
13985
|
members.get("/", async (c) => {
|
|
13735
13986
|
const ctx = await resolveAuthContext(c);
|
|
13736
13987
|
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
@@ -13771,7 +14022,7 @@ function createMembersRouter() {
|
|
|
13771
14022
|
return { userId: m.userId, handle: m.handle, role: m.role, avatarUrl };
|
|
13772
14023
|
})
|
|
13773
14024
|
);
|
|
13774
|
-
return c.json({ members: directory });
|
|
14025
|
+
return c.json({ workspaceId: ctx.workspaceId, members: directory });
|
|
13775
14026
|
});
|
|
13776
14027
|
members.post("/:userId/dm", async (c) => {
|
|
13777
14028
|
const ctx = await resolveAuthContext(c);
|
|
@@ -13945,7 +14196,7 @@ function createMembersRouter() {
|
|
|
13945
14196
|
}
|
|
13946
14197
|
|
|
13947
14198
|
// src/dashboard/notifications-routes.ts
|
|
13948
|
-
import { Hono as
|
|
14199
|
+
import { Hono as Hono23 } from "hono";
|
|
13949
14200
|
|
|
13950
14201
|
// src/store/notification-store.ts
|
|
13951
14202
|
function rowToNotification(row) {
|
|
@@ -14013,7 +14264,7 @@ async function markAllRead(channelIds, userId) {
|
|
|
14013
14264
|
|
|
14014
14265
|
// src/dashboard/notifications-routes.ts
|
|
14015
14266
|
function createNotificationsRouter() {
|
|
14016
|
-
const notifications = new
|
|
14267
|
+
const notifications = new Hono23();
|
|
14017
14268
|
notifications.get("/", async (c) => {
|
|
14018
14269
|
const auth = c.get("auth");
|
|
14019
14270
|
const channelIds = getProfileChannelRegistry().filter((ch) => canSeeChannel(ch, auth)).map((ch) => ch.id);
|
|
@@ -14040,7 +14291,7 @@ function createNotificationsRouter() {
|
|
|
14040
14291
|
return notifications;
|
|
14041
14292
|
}
|
|
14042
14293
|
function createThreadReadRouter() {
|
|
14043
|
-
const threads = new
|
|
14294
|
+
const threads = new Hono23();
|
|
14044
14295
|
threads.post("/threads/:threadId/read", async (c) => {
|
|
14045
14296
|
const { userId } = c.get("auth");
|
|
14046
14297
|
const threadId = c.req.param("threadId");
|
|
@@ -14052,7 +14303,7 @@ function createThreadReadRouter() {
|
|
|
14052
14303
|
}
|
|
14053
14304
|
|
|
14054
14305
|
// src/dashboard/push-routes.ts
|
|
14055
|
-
import { Hono as
|
|
14306
|
+
import { Hono as Hono24 } from "hono";
|
|
14056
14307
|
|
|
14057
14308
|
// src/push/send.ts
|
|
14058
14309
|
import webpush from "web-push";
|
|
@@ -14155,7 +14406,7 @@ async function pushToUser(userId, payload) {
|
|
|
14155
14406
|
|
|
14156
14407
|
// src/dashboard/push-routes.ts
|
|
14157
14408
|
function createPushRouter() {
|
|
14158
|
-
const push = new
|
|
14409
|
+
const push = new Hono24();
|
|
14159
14410
|
push.get("/key", (c) => c.json({ publicKey: getVapidPublicKey() }));
|
|
14160
14411
|
push.post("/subscribe", async (c) => {
|
|
14161
14412
|
const { userId } = c.get("auth");
|
|
@@ -14186,7 +14437,7 @@ function createPushRouter() {
|
|
|
14186
14437
|
}
|
|
14187
14438
|
|
|
14188
14439
|
// src/dashboard/mcp-oauth-routes.ts
|
|
14189
|
-
import { Hono as
|
|
14440
|
+
import { Hono as Hono25 } from "hono";
|
|
14190
14441
|
var CONNECTIONS_PAGE = "/settings/connections";
|
|
14191
14442
|
function callbackUri(c) {
|
|
14192
14443
|
const proto = c.req.header("x-forwarded-proto") ?? "http";
|
|
@@ -14201,7 +14452,7 @@ function backToDashboard(c, to, params) {
|
|
|
14201
14452
|
return c.redirect(`${target.pathname}${target.search}`);
|
|
14202
14453
|
}
|
|
14203
14454
|
function createMcpOAuthRouter() {
|
|
14204
|
-
const app = new
|
|
14455
|
+
const app = new Hono25();
|
|
14205
14456
|
app.get("/oauth/:provider/start", async (c) => {
|
|
14206
14457
|
const auth = c.get("auth");
|
|
14207
14458
|
if (auth.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
@@ -14328,7 +14579,7 @@ function createMcpOAuthRouter() {
|
|
|
14328
14579
|
}
|
|
14329
14580
|
|
|
14330
14581
|
// src/dashboard/mcp-routes.ts
|
|
14331
|
-
import { Hono as
|
|
14582
|
+
import { Hono as Hono26 } from "hono";
|
|
14332
14583
|
|
|
14333
14584
|
// src/mcp/transport.ts
|
|
14334
14585
|
var KINDS = ["inherit", "npx", "local", "http", "sse"];
|
|
@@ -14422,7 +14673,7 @@ function fromServerCommand(command) {
|
|
|
14422
14673
|
|
|
14423
14674
|
// src/dashboard/mcp-routes.ts
|
|
14424
14675
|
var CUSTOM_PROVIDER = "custom";
|
|
14425
|
-
function
|
|
14676
|
+
function requireAdmin4(auth) {
|
|
14426
14677
|
return auth?.role === "admin" ? null : "admin access required";
|
|
14427
14678
|
}
|
|
14428
14679
|
function toResponse4(connection, channelIds, providerCommand = null) {
|
|
@@ -14458,9 +14709,9 @@ function bindingsByConnection() {
|
|
|
14458
14709
|
return map;
|
|
14459
14710
|
}
|
|
14460
14711
|
function createMcpRouter() {
|
|
14461
|
-
const app = new
|
|
14712
|
+
const app = new Hono26();
|
|
14462
14713
|
app.get("/providers", async (c) => {
|
|
14463
|
-
const denied =
|
|
14714
|
+
const denied = requireAdmin4(c.get("auth"));
|
|
14464
14715
|
if (denied) return c.json({ error: denied }, 403);
|
|
14465
14716
|
const providers = await listMcpProviders();
|
|
14466
14717
|
return c.json({
|
|
@@ -14478,7 +14729,7 @@ function createMcpRouter() {
|
|
|
14478
14729
|
});
|
|
14479
14730
|
app.get("/connections", async (c) => {
|
|
14480
14731
|
const auth = c.get("auth");
|
|
14481
|
-
const denied =
|
|
14732
|
+
const denied = requireAdmin4(auth);
|
|
14482
14733
|
if (denied) return c.json({ error: denied }, 403);
|
|
14483
14734
|
const connections = await listMcpConnections(auth.workspaceId);
|
|
14484
14735
|
const commands = new Map(
|
|
@@ -14493,7 +14744,7 @@ function createMcpRouter() {
|
|
|
14493
14744
|
});
|
|
14494
14745
|
app.post("/connections", async (c) => {
|
|
14495
14746
|
const auth = c.get("auth");
|
|
14496
|
-
const denied =
|
|
14747
|
+
const denied = requireAdmin4(auth);
|
|
14497
14748
|
if (denied) return c.json({ error: denied }, 403);
|
|
14498
14749
|
const body = await c.req.json().catch(() => null);
|
|
14499
14750
|
const label2 = typeof body?.label === "string" ? body.label.trim() : "";
|
|
@@ -14530,7 +14781,7 @@ function createMcpRouter() {
|
|
|
14530
14781
|
});
|
|
14531
14782
|
app.patch("/connections/:id", async (c) => {
|
|
14532
14783
|
const auth = c.get("auth");
|
|
14533
|
-
const denied =
|
|
14784
|
+
const denied = requireAdmin4(auth);
|
|
14534
14785
|
if (denied) return c.json({ error: denied }, 403);
|
|
14535
14786
|
const body = await c.req.json().catch(() => null);
|
|
14536
14787
|
if (!body) return c.json({ error: "a body is required" }, 400);
|
|
@@ -14561,7 +14812,7 @@ function createMcpRouter() {
|
|
|
14561
14812
|
});
|
|
14562
14813
|
app.put("/connections/:id/credential", async (c) => {
|
|
14563
14814
|
const auth = c.get("auth");
|
|
14564
|
-
const denied =
|
|
14815
|
+
const denied = requireAdmin4(auth);
|
|
14565
14816
|
if (denied) return c.json({ error: denied }, 403);
|
|
14566
14817
|
const body = await c.req.json().catch(() => null);
|
|
14567
14818
|
const raw = body?.apiKey;
|
|
@@ -14579,7 +14830,7 @@ function createMcpRouter() {
|
|
|
14579
14830
|
});
|
|
14580
14831
|
app.put("/connections/:id/channels", async (c) => {
|
|
14581
14832
|
const auth = c.get("auth");
|
|
14582
|
-
const denied =
|
|
14833
|
+
const denied = requireAdmin4(auth);
|
|
14583
14834
|
if (denied) return c.json({ error: denied }, 403);
|
|
14584
14835
|
const id = c.req.param("id");
|
|
14585
14836
|
const connection = await getMcpConnection(auth.workspaceId, id);
|
|
@@ -14606,7 +14857,7 @@ function createMcpRouter() {
|
|
|
14606
14857
|
});
|
|
14607
14858
|
app.delete("/connections/:id", async (c) => {
|
|
14608
14859
|
const auth = c.get("auth");
|
|
14609
|
-
const denied =
|
|
14860
|
+
const denied = requireAdmin4(auth);
|
|
14610
14861
|
if (denied) return c.json({ error: denied }, 403);
|
|
14611
14862
|
const deleted = await deleteMcpConnection(auth.workspaceId, c.req.param("id"));
|
|
14612
14863
|
if (!deleted) return c.json({ error: "connection not found" }, 404);
|
|
@@ -14717,7 +14968,7 @@ async function sessionUsageForThreads(workspace, threadTsList) {
|
|
|
14717
14968
|
}
|
|
14718
14969
|
|
|
14719
14970
|
// src/dashboard/usage-report.ts
|
|
14720
|
-
import { existsSync as
|
|
14971
|
+
import { existsSync as existsSync22 } from "fs";
|
|
14721
14972
|
function mergeByLabel(buckets) {
|
|
14722
14973
|
const merged = /* @__PURE__ */ new Map();
|
|
14723
14974
|
for (const bucket of buckets) {
|
|
@@ -14763,7 +15014,7 @@ function toReport(days, rows, labelChannel) {
|
|
|
14763
15014
|
};
|
|
14764
15015
|
}
|
|
14765
15016
|
function fromLocalStore(days, labelChannel) {
|
|
14766
|
-
if (!
|
|
15017
|
+
if (!existsSync22(paths.DB_FILE)) return { ...EMPTY, days };
|
|
14767
15018
|
const window = days === 0 ? void 0 : days;
|
|
14768
15019
|
const store = new UsageStore(paths.DB_FILE);
|
|
14769
15020
|
try {
|
|
@@ -14805,7 +15056,7 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
|
|
|
14805
15056
|
const { totals, byDay } = await channelUsageForWorkspace(workspace, aliasIds, window);
|
|
14806
15057
|
return { days, totals: totals.runs > 0 ? totals : null, byDay };
|
|
14807
15058
|
} catch {
|
|
14808
|
-
if (!
|
|
15059
|
+
if (!existsSync22(paths.DB_FILE)) return { days, totals: null, byDay: [] };
|
|
14809
15060
|
const store = new UsageStore(paths.DB_FILE);
|
|
14810
15061
|
try {
|
|
14811
15062
|
const targetLabel = labelChannel(channelId);
|
|
@@ -14827,7 +15078,7 @@ async function buildThreadUsage(threadTs) {
|
|
|
14827
15078
|
if (totals.runs === 0) return { threadTs, totals: null, byModel: [] };
|
|
14828
15079
|
return { threadTs, totals, byModel };
|
|
14829
15080
|
} catch {
|
|
14830
|
-
if (!
|
|
15081
|
+
if (!existsSync22(paths.DB_FILE)) return { threadTs, totals: null, byModel: [] };
|
|
14831
15082
|
const store = new UsageStore(paths.DB_FILE);
|
|
14832
15083
|
try {
|
|
14833
15084
|
const totals = store.threadTotals(threadTs);
|
|
@@ -14840,7 +15091,7 @@ async function buildThreadUsage(threadTs) {
|
|
|
14840
15091
|
}
|
|
14841
15092
|
|
|
14842
15093
|
// src/dashboard/channel-members-routes.ts
|
|
14843
|
-
import { Hono as
|
|
15094
|
+
import { Hono as Hono27 } from "hono";
|
|
14844
15095
|
async function isChannelAdmin(ctx, channelId) {
|
|
14845
15096
|
if (ctx.role === "admin") return true;
|
|
14846
15097
|
const membership = await getMembershipByUserId(ctx.workspaceId, ctx.userId);
|
|
@@ -14849,7 +15100,7 @@ async function isChannelAdmin(ctx, channelId) {
|
|
|
14849
15100
|
return members.some((m) => m.membershipId === membership.id && m.role === "channel_admin");
|
|
14850
15101
|
}
|
|
14851
15102
|
function createChannelMembersRouter() {
|
|
14852
|
-
const router = new
|
|
15103
|
+
const router = new Hono27();
|
|
14853
15104
|
router.get("/:id/members", async (c) => {
|
|
14854
15105
|
const ctx = await resolveAuthContext(c);
|
|
14855
15106
|
if (!ctx) return c.json({ error: "workspace access required" }, 403);
|
|
@@ -14897,9 +15148,9 @@ function createChannelMembersRouter() {
|
|
|
14897
15148
|
if (!await isChannelAdmin(ctx, channelId)) {
|
|
14898
15149
|
return c.json({ error: "channel admin access required" }, 403);
|
|
14899
15150
|
}
|
|
14900
|
-
return c.json({
|
|
15151
|
+
return c.json({ memberAccess: await getChannelDocsMemberAccess(channelId) });
|
|
14901
15152
|
});
|
|
14902
|
-
router.put("/:id/docs-access
|
|
15153
|
+
router.put("/:id/docs-access", async (c) => {
|
|
14903
15154
|
const ctx = await resolveAuthContext(c);
|
|
14904
15155
|
if (!ctx) return c.json({ error: "workspace access required" }, 403);
|
|
14905
15156
|
const channelId = c.req.param("id");
|
|
@@ -14907,32 +15158,31 @@ function createChannelMembersRouter() {
|
|
|
14907
15158
|
return c.json({ error: "channel admin access required" }, 403);
|
|
14908
15159
|
}
|
|
14909
15160
|
const body = await c.req.json().catch(() => ({}));
|
|
14910
|
-
if (!
|
|
14911
|
-
return c.json({ error: "
|
|
15161
|
+
if (!isDocsAccess(body.memberAccess)) {
|
|
15162
|
+
return c.json({ error: "memberAccess must be 'none', 'read' or 'write'" }, 400);
|
|
14912
15163
|
}
|
|
14913
|
-
|
|
14914
|
-
|
|
14915
|
-
membershipId: c.req.param("membershipId"),
|
|
14916
|
-
access: body.access
|
|
14917
|
-
});
|
|
14918
|
-
return c.json({ grant });
|
|
14919
|
-
});
|
|
14920
|
-
router.delete("/:id/docs-access/:membershipId", async (c) => {
|
|
14921
|
-
const ctx = await resolveAuthContext(c);
|
|
14922
|
-
if (!ctx) return c.json({ error: "workspace access required" }, 403);
|
|
14923
|
-
const channelId = c.req.param("id");
|
|
14924
|
-
if (!await isChannelAdmin(ctx, channelId)) {
|
|
14925
|
-
return c.json({ error: "channel admin access required" }, 403);
|
|
15164
|
+
if (!await setChannelDocsMemberAccess(channelId, body.memberAccess)) {
|
|
15165
|
+
return c.json({ error: "no such channel" }, 404);
|
|
14926
15166
|
}
|
|
14927
|
-
|
|
14928
|
-
if (!removed) return c.json({ error: "no such docs grant" }, 404);
|
|
14929
|
-
return c.body(null, 204);
|
|
15167
|
+
return c.json({ memberAccess: body.memberAccess });
|
|
14930
15168
|
});
|
|
14931
15169
|
return router;
|
|
14932
15170
|
}
|
|
14933
15171
|
|
|
14934
15172
|
// src/dashboard/memories-routes.ts
|
|
14935
|
-
import { Hono as
|
|
15173
|
+
import { Hono as Hono28 } from "hono";
|
|
15174
|
+
async function readPatch(c) {
|
|
15175
|
+
const body = await c.req.json().catch(() => ({}));
|
|
15176
|
+
const patch = {};
|
|
15177
|
+
if (typeof body["description"] === "string") patch.description = body["description"];
|
|
15178
|
+
if (typeof body["body"] === "string") patch.body = body["body"];
|
|
15179
|
+
const type = body["type"];
|
|
15180
|
+
if (type !== void 0) {
|
|
15181
|
+
if (!isMemoryType(type)) return "invalid type";
|
|
15182
|
+
patch.type = type;
|
|
15183
|
+
}
|
|
15184
|
+
return patch;
|
|
15185
|
+
}
|
|
14936
15186
|
var requireChannelVisible = async (c, next) => {
|
|
14937
15187
|
const channel = findChannel(c.req.param("id"));
|
|
14938
15188
|
if (!channel || !canSeeChannel(channel, c.get("auth"))) {
|
|
@@ -14948,7 +15198,7 @@ var requireAgentMemory = async (c, next) => {
|
|
|
14948
15198
|
await next();
|
|
14949
15199
|
};
|
|
14950
15200
|
function createMemoriesRouter() {
|
|
14951
|
-
const router = new
|
|
15201
|
+
const router = new Hono28();
|
|
14952
15202
|
for (const path of [
|
|
14953
15203
|
"/:id/memories",
|
|
14954
15204
|
"/:id/memories/*",
|
|
@@ -14963,15 +15213,8 @@ function createMemoriesRouter() {
|
|
|
14963
15213
|
router.patch("/:id/memories/:memoryId", async (c) => {
|
|
14964
15214
|
const memoryId = c.req.param("memoryId");
|
|
14965
15215
|
if (!isUuid(memoryId)) return c.json({ error: "invalid memory id" }, 400);
|
|
14966
|
-
const
|
|
14967
|
-
|
|
14968
|
-
if (typeof body["description"] === "string") patch.description = body["description"];
|
|
14969
|
-
if (typeof body["body"] === "string") patch.body = body["body"];
|
|
14970
|
-
const type = body["type"];
|
|
14971
|
-
if (type !== void 0) {
|
|
14972
|
-
if (!isMemoryType(type)) return c.json({ error: "invalid type" }, 400);
|
|
14973
|
-
patch.type = type;
|
|
14974
|
-
}
|
|
15216
|
+
const patch = await readPatch(c);
|
|
15217
|
+
if (typeof patch === "string") return c.json({ error: patch }, 400);
|
|
14975
15218
|
try {
|
|
14976
15219
|
const memory = await updateChannelMemory(c.get("memoryChannelId"), memoryId, patch, "user");
|
|
14977
15220
|
if (!memory) return c.json({ error: "memory not found" }, 404);
|
|
@@ -14987,9 +15230,28 @@ function createMemoriesRouter() {
|
|
|
14987
15230
|
if (!deleted) return c.json({ error: "memory not found" }, 404);
|
|
14988
15231
|
return c.json({ ok: true });
|
|
14989
15232
|
});
|
|
15233
|
+
router.post("/:id/memories/:memoryId/promote", async (c) => {
|
|
15234
|
+
const memoryId = c.req.param("memoryId");
|
|
15235
|
+
if (!isUuid(memoryId)) return c.json({ error: "invalid memory id" }, 400);
|
|
15236
|
+
if (c.get("auth").role !== "admin") {
|
|
15237
|
+
return c.json({ error: "Only a Workspace Admin can move a memory to the workspace." }, 403);
|
|
15238
|
+
}
|
|
15239
|
+
if (findChannel(c.get("memoryChannelId"))?.visibility !== "public") {
|
|
15240
|
+
return c.json({ error: "Memories of a private channel stay in that channel." }, 403);
|
|
15241
|
+
}
|
|
15242
|
+
const result = await promoteChannelMemory(c.get("memoryChannelId"), memoryId);
|
|
15243
|
+
if (!result.ok && result.reason === "not-found") return c.json({ error: "memory not found" }, 404);
|
|
15244
|
+
if (!result.ok) {
|
|
15245
|
+
return c.json({ error: "The workspace already has a memory with this name." }, 409);
|
|
15246
|
+
}
|
|
15247
|
+
return c.json({ memory: result.memory });
|
|
15248
|
+
});
|
|
14990
15249
|
router.get("/:id/memory-events", async (c) => {
|
|
14991
|
-
const threadId = c.req.query("thread")
|
|
14992
|
-
if (
|
|
15250
|
+
const threadId = c.req.query("thread");
|
|
15251
|
+
if (threadId === void 0) {
|
|
15252
|
+
return c.json({ events: await listMemoryEvents(c.get("memoryChannelId"), { limit: 20 }) });
|
|
15253
|
+
}
|
|
15254
|
+
if (!isUuid(threadId)) return c.json({ error: "invalid thread id" }, 400);
|
|
14993
15255
|
return c.json({ events: await listThreadMemoryEvents(c.get("memoryChannelId"), threadId) });
|
|
14994
15256
|
});
|
|
14995
15257
|
router.post("/:id/memory-events/:eventId/undo", async (c) => {
|
|
@@ -15008,9 +15270,68 @@ function createMemoriesRouter() {
|
|
|
15008
15270
|
});
|
|
15009
15271
|
return router;
|
|
15010
15272
|
}
|
|
15273
|
+
function createScopedMemoriesRouter() {
|
|
15274
|
+
const router = new Hono28();
|
|
15275
|
+
router.use("*", requireAgentMemory);
|
|
15276
|
+
const workspace = (c) => ({
|
|
15277
|
+
scope: "workspace",
|
|
15278
|
+
workspaceId: c.get("auth").workspaceId
|
|
15279
|
+
});
|
|
15280
|
+
const mine = (c) => ({
|
|
15281
|
+
scope: "user",
|
|
15282
|
+
workspaceId: c.get("auth").workspaceId,
|
|
15283
|
+
userId: c.get("auth").userId
|
|
15284
|
+
});
|
|
15285
|
+
router.on(["PATCH", "DELETE"], "/workspace/*", async (c, next) => {
|
|
15286
|
+
if (c.get("auth").role !== "admin") {
|
|
15287
|
+
return c.json({ error: "Only a Workspace Admin can change workspace memory." }, 403);
|
|
15288
|
+
}
|
|
15289
|
+
await next();
|
|
15290
|
+
});
|
|
15291
|
+
for (const [path, target] of [
|
|
15292
|
+
["/workspace", workspace],
|
|
15293
|
+
["/me", mine]
|
|
15294
|
+
]) {
|
|
15295
|
+
router.get(path, async (c) => c.json({ memories: await listMemories(target(c)) }));
|
|
15296
|
+
router.patch(`${path}/:memoryId`, async (c) => {
|
|
15297
|
+
const memoryId = c.req.param("memoryId") ?? "";
|
|
15298
|
+
if (!isUuid(memoryId)) return c.json({ error: "invalid memory id" }, 400);
|
|
15299
|
+
const patch = await readPatch(c);
|
|
15300
|
+
if (typeof patch === "string") return c.json({ error: patch }, 400);
|
|
15301
|
+
try {
|
|
15302
|
+
const memory = await updateMemory(target(c), memoryId, patch, "user");
|
|
15303
|
+
if (!memory) return c.json({ error: "memory not found" }, 404);
|
|
15304
|
+
return c.json({ memory });
|
|
15305
|
+
} catch (err) {
|
|
15306
|
+
return c.json({ error: err.message }, 400);
|
|
15307
|
+
}
|
|
15308
|
+
});
|
|
15309
|
+
router.delete(`${path}/:memoryId`, async (c) => {
|
|
15310
|
+
const memoryId = c.req.param("memoryId") ?? "";
|
|
15311
|
+
if (!isUuid(memoryId)) return c.json({ error: "invalid memory id" }, 400);
|
|
15312
|
+
if (!await deleteMemory(target(c), memoryId, "user")) {
|
|
15313
|
+
return c.json({ error: "memory not found" }, 404);
|
|
15314
|
+
}
|
|
15315
|
+
return c.json({ ok: true });
|
|
15316
|
+
});
|
|
15317
|
+
}
|
|
15318
|
+
router.get("/me-settings", async (c) => {
|
|
15319
|
+
const auth = c.get("auth");
|
|
15320
|
+
const membership = await getMembershipByUserId(auth.workspaceId, auth.userId);
|
|
15321
|
+
return c.json({ autoPaused: membership?.memoryAutoPaused ?? false });
|
|
15322
|
+
});
|
|
15323
|
+
router.put("/me-settings", async (c) => {
|
|
15324
|
+
const body = await c.req.json().catch(() => ({}));
|
|
15325
|
+
if (typeof body["autoPaused"] !== "boolean") return c.json({ error: "autoPaused must be a boolean" }, 400);
|
|
15326
|
+
const auth = c.get("auth");
|
|
15327
|
+
await setMemoryAutoPaused(auth.workspaceId, auth.userId, body["autoPaused"]);
|
|
15328
|
+
return c.json({ autoPaused: body["autoPaused"] });
|
|
15329
|
+
});
|
|
15330
|
+
return router;
|
|
15331
|
+
}
|
|
15011
15332
|
|
|
15012
15333
|
// src/dashboard/onboarding-routes.ts
|
|
15013
|
-
import { Hono as
|
|
15334
|
+
import { Hono as Hono29 } from "hono";
|
|
15014
15335
|
|
|
15015
15336
|
// src/dashboard/unique-channel-name.ts
|
|
15016
15337
|
function uniqueChannelName(raw, taken) {
|
|
@@ -15111,7 +15432,7 @@ async function buildGreeting(input2) {
|
|
|
15111
15432
|
// src/dashboard/onboarding-routes.ts
|
|
15112
15433
|
var MAX_FOLDERS = 20;
|
|
15113
15434
|
function createOnboardingRouter(agent) {
|
|
15114
|
-
const app = new
|
|
15435
|
+
const app = new Hono29();
|
|
15115
15436
|
app.get("/scan", async (c) => {
|
|
15116
15437
|
const ctx = await resolveAuthContext(c);
|
|
15117
15438
|
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
@@ -15277,13 +15598,13 @@ function firstSentence(body) {
|
|
|
15277
15598
|
function threadLabel(row) {
|
|
15278
15599
|
return row.title?.trim() || firstSentence(row.thread_replies?.[0]?.body);
|
|
15279
15600
|
}
|
|
15280
|
-
async function resolveThreadLinks(
|
|
15601
|
+
async function resolveThreadLinks(sessions2) {
|
|
15281
15602
|
const chatIds = [
|
|
15282
|
-
...new Set(
|
|
15603
|
+
...new Set(sessions2.filter((s) => s.channelId.startsWith("chat:")).map((s) => s.threadTs))
|
|
15283
15604
|
];
|
|
15284
15605
|
const externalKeys = [
|
|
15285
15606
|
...new Set(
|
|
15286
|
-
|
|
15607
|
+
sessions2.filter((s) => !s.channelId.startsWith("chat:")).map((s) => `${s.channelId}:${s.threadTs}`)
|
|
15287
15608
|
)
|
|
15288
15609
|
];
|
|
15289
15610
|
const chat = /* @__PURE__ */ new Map();
|
|
@@ -15316,8 +15637,8 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15316
15637
|
api.get("/host/status", async (c) => {
|
|
15317
15638
|
const online = agent.isHostOnline?.(getProfile()) ?? false;
|
|
15318
15639
|
if (!online) {
|
|
15319
|
-
const
|
|
15320
|
-
return c.json({ online: false, reason:
|
|
15640
|
+
const failure3 = agent.getHostOfflineReason?.() ?? null;
|
|
15641
|
+
return c.json({ online: false, reason: failure3?.reason ?? null, since: failure3?.since ?? null });
|
|
15321
15642
|
}
|
|
15322
15643
|
const auth = c.get("auth");
|
|
15323
15644
|
const paused = await getAgentPaused(auth.workspaceId);
|
|
@@ -15368,6 +15689,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15368
15689
|
api.route("/onboarding", createOnboardingRouter(agent));
|
|
15369
15690
|
api.route("/channels", createChannelMembersRouter());
|
|
15370
15691
|
api.route("/channels", createMemoriesRouter());
|
|
15692
|
+
api.route("/memories", createScopedMemoriesRouter());
|
|
15371
15693
|
api.route("/docs", createDocShareListRouter());
|
|
15372
15694
|
api.get("/users", (c) => c.json({ users: listActiveUsers() }));
|
|
15373
15695
|
api.route("/categories", createCategoryWriteRouter());
|
|
@@ -15379,7 +15701,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15379
15701
|
runs.set(name, { lastRun: run4.lastRunAt.toISOString(), lastStatus: run4.lastStatus });
|
|
15380
15702
|
}
|
|
15381
15703
|
} catch {
|
|
15382
|
-
if (
|
|
15704
|
+
if (existsSync23(paths.DB_FILE)) {
|
|
15383
15705
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
15384
15706
|
try {
|
|
15385
15707
|
for (const run4 of runStore.listLastRuns()) {
|
|
@@ -15441,7 +15763,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15441
15763
|
const runs = (await recentRunsForWorkspace(getProfile(), 200)).map(toResponse5);
|
|
15442
15764
|
return c.json({ runs });
|
|
15443
15765
|
} catch {
|
|
15444
|
-
if (!
|
|
15766
|
+
if (!existsSync23(paths.DB_FILE)) return c.json({ runs: [] });
|
|
15445
15767
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
15446
15768
|
try {
|
|
15447
15769
|
const runs = runStore.listRuns(200).map(toResponse5);
|
|
@@ -15563,7 +15885,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15563
15885
|
} catch {
|
|
15564
15886
|
latest = /* @__PURE__ */ new Map();
|
|
15565
15887
|
totals = /* @__PURE__ */ new Map();
|
|
15566
|
-
if (
|
|
15888
|
+
if (existsSync23(paths.DB_FILE)) {
|
|
15567
15889
|
const usageStore = new UsageStore(paths.DB_FILE);
|
|
15568
15890
|
try {
|
|
15569
15891
|
latest = usageStore.latestByThread(threadTsList);
|
|
@@ -15574,7 +15896,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15574
15896
|
}
|
|
15575
15897
|
}
|
|
15576
15898
|
const threadLinks = await resolveThreadLinks(raw);
|
|
15577
|
-
const
|
|
15899
|
+
const sessions2 = raw.map((s) => {
|
|
15578
15900
|
const run4 = latest.get(s.threadTs);
|
|
15579
15901
|
const turns = run4?.numTurns ?? 1;
|
|
15580
15902
|
const total = run4 ? run4.inputTokens + run4.cacheReadTokens + run4.cacheCreationTokens : void 0;
|
|
@@ -15592,7 +15914,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15592
15914
|
costUsd: totals.get(s.threadTs)?.costUsd
|
|
15593
15915
|
};
|
|
15594
15916
|
});
|
|
15595
|
-
return c.json({ sessions });
|
|
15917
|
+
return c.json({ sessions: sessions2 });
|
|
15596
15918
|
});
|
|
15597
15919
|
const LOGO_SIZE = 256;
|
|
15598
15920
|
const MAX_LOGO_BYTES = 2 * 1024 * 1024;
|
|
@@ -15618,18 +15940,18 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
15618
15940
|
// src/dashboard/device-auth-routes.ts
|
|
15619
15941
|
import { verify } from "crypto";
|
|
15620
15942
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
15621
|
-
import { Hono as
|
|
15943
|
+
import { Hono as Hono31 } from "hono";
|
|
15622
15944
|
|
|
15623
15945
|
// src/dashboard/agent-bridge.ts
|
|
15624
|
-
import { randomBytes as randomBytes8, randomUUID as
|
|
15946
|
+
import { randomBytes as randomBytes8, randomUUID as randomUUID5, sign as signRaw, timingSafeEqual as timingSafeEqual2, verify as verifyRaw } from "crypto";
|
|
15625
15947
|
|
|
15626
15948
|
// src/dashboard/docs-relay.ts
|
|
15627
|
-
import { Hono as
|
|
15949
|
+
import { Hono as Hono30 } from "hono";
|
|
15628
15950
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
15629
15951
|
var FORWARDED_HEADERS = ["content-type", "host", "accept-language"];
|
|
15630
15952
|
var RETURNED_HEADERS = ["content-type", "content-disposition", "content-security-policy", "cache-control"];
|
|
15631
15953
|
async function handleDocsRequest(req) {
|
|
15632
|
-
const app = new
|
|
15954
|
+
const app = new Hono30();
|
|
15633
15955
|
app.use("*", async (c, next) => {
|
|
15634
15956
|
c.set("auth", req.auth);
|
|
15635
15957
|
await next();
|
|
@@ -15688,24 +16010,24 @@ function relay2(agent) {
|
|
|
15688
16010
|
}
|
|
15689
16011
|
|
|
15690
16012
|
// src/dashboard/agent-bridge.ts
|
|
15691
|
-
import { existsSync as
|
|
16013
|
+
import { existsSync as existsSync25, readFileSync as readFileSync19 } from "fs";
|
|
15692
16014
|
import dotenv from "dotenv";
|
|
15693
16015
|
import { WebSocket, WebSocketServer } from "ws";
|
|
15694
16016
|
|
|
15695
16017
|
// src/dashboard/host-identity.ts
|
|
15696
16018
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey2, generateKeyPairSync as generateKeyPairSync2 } from "crypto";
|
|
15697
|
-
import { existsSync as
|
|
15698
|
-
import { dirname as
|
|
16019
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
16020
|
+
import { dirname as dirname8, join as join24 } from "path";
|
|
15699
16021
|
function hostPrivateKeyPath() {
|
|
15700
|
-
return process.env["FLYSPACE_HOST_KEY_PATH"] ||
|
|
16022
|
+
return process.env["FLYSPACE_HOST_KEY_PATH"] || join24(paths.BASE_CONFIG_DIR, "host.key");
|
|
15701
16023
|
}
|
|
15702
16024
|
function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
15703
|
-
if (!
|
|
16025
|
+
if (!existsSync24(path)) {
|
|
15704
16026
|
throw new Error(
|
|
15705
16027
|
`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.`
|
|
15706
16028
|
);
|
|
15707
16029
|
}
|
|
15708
|
-
return createPrivateKey2(
|
|
16030
|
+
return createPrivateKey2(readFileSync17(path, "utf8"));
|
|
15709
16031
|
}
|
|
15710
16032
|
function loadHostPublicKey() {
|
|
15711
16033
|
const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
|
|
@@ -15950,7 +16272,7 @@ async function userEmail(userId) {
|
|
|
15950
16272
|
|
|
15951
16273
|
// src/dashboard/doc-share-publisher.ts
|
|
15952
16274
|
import { randomBytes as randomBytes7 } from "crypto";
|
|
15953
|
-
import { readFileSync as
|
|
16275
|
+
import { readFileSync as readFileSync18, statSync as statSync9 } from "fs";
|
|
15954
16276
|
import { extname as extname2, posix } from "path";
|
|
15955
16277
|
import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
|
|
15956
16278
|
|
|
@@ -16137,7 +16459,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16137
16459
|
}
|
|
16138
16460
|
if (!stat.isFile()) return { status: "not-found" };
|
|
16139
16461
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
16140
|
-
const source =
|
|
16462
|
+
const source = readFileSync18(abs, "utf8");
|
|
16141
16463
|
const title = extractTitle(abs, kind === "markdown" ? "md" : kind);
|
|
16142
16464
|
if (kind === "excalidraw") {
|
|
16143
16465
|
try {
|
|
@@ -16217,7 +16539,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16217
16539
|
let text = null;
|
|
16218
16540
|
try {
|
|
16219
16541
|
const size = statSync9(docAbs).size;
|
|
16220
|
-
if (size <= budget) text =
|
|
16542
|
+
if (size <= budget) text = readFileSync18(docAbs, "utf8");
|
|
16221
16543
|
} catch {
|
|
16222
16544
|
continue;
|
|
16223
16545
|
}
|
|
@@ -16234,7 +16556,7 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
16234
16556
|
let body;
|
|
16235
16557
|
try {
|
|
16236
16558
|
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
16237
|
-
body =
|
|
16559
|
+
body = readFileSync18(resolved2.abs);
|
|
16238
16560
|
} catch {
|
|
16239
16561
|
continue;
|
|
16240
16562
|
}
|
|
@@ -16327,13 +16649,14 @@ var METHODS = {
|
|
|
16327
16649
|
threads: ["GET", "HEAD", "POST", "PATCH"],
|
|
16328
16650
|
thread_replies: ["GET", "HEAD", "POST", "PATCH"],
|
|
16329
16651
|
memberships: ["GET", "HEAD"],
|
|
16652
|
+
feature_flags: ["GET", "HEAD"],
|
|
16330
16653
|
channel_members: ["GET", "HEAD"],
|
|
16331
|
-
channel_docs_access: ["GET", "HEAD"],
|
|
16332
16654
|
memories: ALL,
|
|
16333
16655
|
memory_events: ["GET", "HEAD", "POST", "PATCH"]
|
|
16334
16656
|
};
|
|
16335
|
-
var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members"
|
|
16336
|
-
var
|
|
16657
|
+
var CHANNEL_SCOPED = /* @__PURE__ */ new Set(["channel_members"]);
|
|
16658
|
+
var MEMORY_TABLES = /* @__PURE__ */ new Set(["memories", "memory_events"]);
|
|
16659
|
+
var MEMORY_KEYS = ["channel_id", "workspace_id", "user_id", "scope"];
|
|
16337
16660
|
var FORWARDED_HEADERS2 = ["accept", "content-type", "prefer", "range"];
|
|
16338
16661
|
var deny = (message, status = 403) => ({
|
|
16339
16662
|
status,
|
|
@@ -16440,7 +16763,30 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
16440
16763
|
}
|
|
16441
16764
|
}
|
|
16442
16765
|
params.append(slugColumn, inList(scope));
|
|
16443
|
-
} else if (table
|
|
16766
|
+
} else if (MEMORY_TABLES.has(table)) {
|
|
16767
|
+
const [workspaceIds, channelIds] = await Promise.all([
|
|
16768
|
+
ownedWorkspaceIds(forward, workspaces),
|
|
16769
|
+
ownedChannelIds(forward, workspaces)
|
|
16770
|
+
]);
|
|
16771
|
+
if (!workspaceIds || !channelIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16772
|
+
if (method === "POST") {
|
|
16773
|
+
const ok3 = rows.length > 0 && rows.every((r) => {
|
|
16774
|
+
const channel = r["channel_id"];
|
|
16775
|
+
if (channel !== void 0 && channel !== null) {
|
|
16776
|
+
return typeof channel === "string" && channelIds.includes(channel);
|
|
16777
|
+
}
|
|
16778
|
+
return typeof r["workspace_id"] === "string" && workspaceIds.includes(r["workspace_id"]);
|
|
16779
|
+
});
|
|
16780
|
+
if (!ok3) return deny(`Every ${table} row must belong to a channel or workspace of the device.`);
|
|
16781
|
+
}
|
|
16782
|
+
if (method === "PATCH") {
|
|
16783
|
+
const patch = bodyRows(req.body);
|
|
16784
|
+
if (!patch || patch.some((r) => MEMORY_KEYS.some((k) => k in r))) {
|
|
16785
|
+
return deny(`A ${table} row cannot move to another channel, workspace or person.`);
|
|
16786
|
+
}
|
|
16787
|
+
}
|
|
16788
|
+
params.append("workspace_id", inList(workspaceIds));
|
|
16789
|
+
} else if (table === "memberships" || table === "feature_flags") {
|
|
16444
16790
|
const workspaceIds = await ownedWorkspaceIds(forward, workspaces);
|
|
16445
16791
|
if (!workspaceIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16446
16792
|
params.append("workspace_id", inList(workspaceIds));
|
|
@@ -16449,16 +16795,6 @@ async function proxyHostDb(workspaces, req, forward, assigned = workspaces ?? []
|
|
|
16449
16795
|
if (!channelIds) return deny("Could not resolve the device workspaces.", 502);
|
|
16450
16796
|
if (CHANNEL_SCOPED.has(table)) {
|
|
16451
16797
|
params.append("channel_id", inList(channelIds));
|
|
16452
|
-
} else if (CHANNEL_OWNED.has(table)) {
|
|
16453
|
-
if (method === "POST") {
|
|
16454
|
-
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
16455
|
-
if (!ok3) return deny(`Every ${table} row must belong to a channel of the device workspaces.`);
|
|
16456
|
-
}
|
|
16457
|
-
if (method === "PATCH") {
|
|
16458
|
-
const patch = bodyRows(req.body);
|
|
16459
|
-
if (!patch || patch.some((r) => "channel_id" in r)) return deny(`A ${table} row cannot move channels.`);
|
|
16460
|
-
}
|
|
16461
|
-
params.append("channel_id", inList(channelIds));
|
|
16462
16798
|
} else if (table === "threads") {
|
|
16463
16799
|
if (method === "POST") {
|
|
16464
16800
|
const ok3 = rows.length > 0 && rows.every((r) => typeof r["channel_id"] === "string" && channelIds.includes(r["channel_id"]));
|
|
@@ -16532,7 +16868,7 @@ function dataSocketUrl() {
|
|
|
16532
16868
|
return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
|
|
16533
16869
|
}
|
|
16534
16870
|
function listLocalSessions() {
|
|
16535
|
-
if (!
|
|
16871
|
+
if (!existsSync25(paths.DB_FILE)) return [];
|
|
16536
16872
|
const store = new SessionStore(paths.DB_FILE);
|
|
16537
16873
|
try {
|
|
16538
16874
|
return store.listSessions();
|
|
@@ -16559,6 +16895,7 @@ function localAgentCallbacks(chatAgent) {
|
|
|
16559
16895
|
saveLocalDashboardSettings: (patch) => saveDashboardSettings(patch),
|
|
16560
16896
|
writeSkill: (ref, write) => writeSkillAndSync(ref, write),
|
|
16561
16897
|
outputStylesRequest: (req) => handleOutputStylesRequest(req),
|
|
16898
|
+
providerLoginRequest: (req) => handleProviderLoginRequest(req),
|
|
16562
16899
|
docsRequest: (req) => handleDocsRequest(req),
|
|
16563
16900
|
listSessions: () => listLocalSessions(),
|
|
16564
16901
|
scanOnboarding: () => scanOnboarding(),
|
|
@@ -16571,7 +16908,7 @@ function buildHostConfigSnapshot() {
|
|
|
16571
16908
|
const snapshot2 = {};
|
|
16572
16909
|
for (const profile of listProfiles()) {
|
|
16573
16910
|
snapshot2[profile] = runWithProfile(profile, () => ({
|
|
16574
|
-
env:
|
|
16911
|
+
env: existsSync25(paths.CONFIG_FILE) ? dotenv.parse(readFileSync19(paths.CONFIG_FILE, "utf-8")) : {},
|
|
16575
16912
|
agentModel: loadDashboardSettings().agent
|
|
16576
16913
|
}));
|
|
16577
16914
|
}
|
|
@@ -16784,7 +17121,7 @@ function callHost(method, args, timeoutMs = CALL_TIMEOUT_MS) {
|
|
|
16784
17121
|
return Promise.resolve(null);
|
|
16785
17122
|
}
|
|
16786
17123
|
if (method === "docsRequest") console.log(`[host-socket] docsRequest for "${profile}" -> device ${hosts.get(ws)?.deviceId ?? "legacy"}`);
|
|
16787
|
-
const id =
|
|
17124
|
+
const id = randomUUID5();
|
|
16788
17125
|
return new Promise((resolve7) => {
|
|
16789
17126
|
const timer2 = setTimeout(() => {
|
|
16790
17127
|
pending2.delete(id);
|
|
@@ -16848,6 +17185,8 @@ function remoteAgentCallbacks() {
|
|
|
16848
17185
|
writeSkill: (ref, write) => callHost("writeSkill", [ref, write]),
|
|
16849
17186
|
// Same posture as writeSkill: `null` becomes a 503.
|
|
16850
17187
|
outputStylesRequest: (req) => callHost("outputStylesRequest", [req]),
|
|
17188
|
+
// Submitting a Claude code waits on the CLI to mint the token.
|
|
17189
|
+
providerLoginRequest: (req) => callHost("providerLoginRequest", [req], DOCS_CALL_TIMEOUT_MS),
|
|
16851
17190
|
// Same posture again. Longer timeout: a first translation of a doc runs a
|
|
16852
17191
|
// model call on the Host before it answers.
|
|
16853
17192
|
docsRequest: (req) => callHost("docsRequest", [req], DOCS_CALL_TIMEOUT_MS),
|
|
@@ -17112,7 +17451,7 @@ async function boundWorkspaceIds(publicKey, userId) {
|
|
|
17112
17451
|
return new Set(existing ? (await listDeviceWorkspaces(existing)).map((w) => w.id) : []);
|
|
17113
17452
|
}
|
|
17114
17453
|
function createDeviceAuthRouter() {
|
|
17115
|
-
const app = new
|
|
17454
|
+
const app = new Hono31();
|
|
17116
17455
|
app.post("/code", async (c) => {
|
|
17117
17456
|
const body = await readBody(c);
|
|
17118
17457
|
const publicKey = typeof body["public_key"] === "string" ? body["public_key"] : "";
|
|
@@ -17276,7 +17615,7 @@ function createDeviceAuthRouter() {
|
|
|
17276
17615
|
|
|
17277
17616
|
// src/dashboard/api.ts
|
|
17278
17617
|
function createApiRouter(agent = {}, mode = "all") {
|
|
17279
|
-
const api = new
|
|
17618
|
+
const api = new Hono32();
|
|
17280
17619
|
api.use("*", async (c, next) => {
|
|
17281
17620
|
const known = await listKnownWorkspaces();
|
|
17282
17621
|
const fromHost = profileFromHost(trustedHost(c), known);
|
|
@@ -17332,6 +17671,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
17332
17671
|
});
|
|
17333
17672
|
registerMirroredSkillRoutes(api, agent);
|
|
17334
17673
|
registerRelayedOutputStylesRoutes(api, agent);
|
|
17674
|
+
registerRelayedProviderLoginRoutes(api, agent);
|
|
17335
17675
|
api.get("/channels/:id/docs/flat", requireDocsAccess, async (c) => {
|
|
17336
17676
|
const { cwd } = resolveChannelConfig(c.req.param("id"));
|
|
17337
17677
|
const query = c.req.query("q") ?? "";
|
|
@@ -17389,7 +17729,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
17389
17729
|
}
|
|
17390
17730
|
|
|
17391
17731
|
// src/dashboard/app-icon-routes.ts
|
|
17392
|
-
import { Hono as
|
|
17732
|
+
import { Hono as Hono33 } from "hono";
|
|
17393
17733
|
var NEUTRAL_MANIFEST = {
|
|
17394
17734
|
id: "/",
|
|
17395
17735
|
name: "Flyspace",
|
|
@@ -17427,7 +17767,7 @@ async function withResolvedProfile2(c, next) {
|
|
|
17427
17767
|
return profile ? runWithProfile(profile, () => next()) : next();
|
|
17428
17768
|
}
|
|
17429
17769
|
function createAppIconRouter() {
|
|
17430
|
-
const app = new
|
|
17770
|
+
const app = new Hono33();
|
|
17431
17771
|
app.get("/manifest.json", withResolvedProfile2, async (c) => {
|
|
17432
17772
|
if (!await resolvedProfileHost(c)) return c.json(NEUTRAL_MANIFEST);
|
|
17433
17773
|
const profile = getProfile();
|
|
@@ -17467,7 +17807,7 @@ function pngResponse(c, png) {
|
|
|
17467
17807
|
}
|
|
17468
17808
|
|
|
17469
17809
|
// src/dashboard/stripe-routes.ts
|
|
17470
|
-
import { Hono as
|
|
17810
|
+
import { Hono as Hono34 } from "hono";
|
|
17471
17811
|
function planStatusFor(status) {
|
|
17472
17812
|
const known = ["trialing", "active", "paused", "canceled"];
|
|
17473
17813
|
return known.includes(status) ? status : null;
|
|
@@ -17510,7 +17850,7 @@ async function handleCardAdded(session) {
|
|
|
17510
17850
|
}
|
|
17511
17851
|
}
|
|
17512
17852
|
function createStripeWebhookRouter() {
|
|
17513
|
-
const stripeRouter = new
|
|
17853
|
+
const stripeRouter = new Hono34();
|
|
17514
17854
|
stripeRouter.post("/webhook", async (c) => {
|
|
17515
17855
|
const secret = process.env["STRIPE_WEBHOOK_SECRET"];
|
|
17516
17856
|
const signature = c.req.header("stripe-signature");
|
|
@@ -17621,7 +17961,7 @@ function startHostStatusBroadcast() {
|
|
|
17621
17961
|
|
|
17622
17962
|
// src/dashboard/server.ts
|
|
17623
17963
|
var DEFAULT_DASHBOARD_PORT = 4747;
|
|
17624
|
-
var WEB_DIST =
|
|
17964
|
+
var WEB_DIST = join25(paths.PACKAGE_ROOT, "web", "dist");
|
|
17625
17965
|
var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
17626
17966
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
17627
17967
|
for (let port = start; port < start + attempts; port++) {
|
|
@@ -17670,7 +18010,7 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17670
18010
|
const mirrorsSkills = mode === "host" || process.env["FLYSPACE_SKILL_SYNC"] === "1";
|
|
17671
18011
|
const stopSkillSync = mirrorsSkills ? installSkillSync(listProfiles()) : () => {
|
|
17672
18012
|
};
|
|
17673
|
-
const app = new
|
|
18013
|
+
const app = new Hono35();
|
|
17674
18014
|
app.onError(reportingErrorHandler);
|
|
17675
18015
|
app.use("*", async (c, next) => {
|
|
17676
18016
|
await next();
|
|
@@ -17710,14 +18050,14 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
17710
18050
|
} else {
|
|
17711
18051
|
app.route("/share", createPublicShareAssetRouter(WEB_DIST));
|
|
17712
18052
|
app.route("/", createAppIconRouter());
|
|
17713
|
-
if (
|
|
18053
|
+
if (existsSync26(WEB_DIST) || existsSync26(join25(paths.PACKAGE_ROOT, "web", "package.json"))) {
|
|
17714
18054
|
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
17715
18055
|
}
|
|
17716
18056
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
17717
|
-
const spaFallback = serveStatic({ path:
|
|
18057
|
+
const spaFallback = serveStatic({ path: join25(WEB_DIST, "index.html") });
|
|
17718
18058
|
app.get("*", async (c, next) => {
|
|
17719
|
-
if (!
|
|
17720
|
-
const isCheckout =
|
|
18059
|
+
if (!existsSync26(join25(WEB_DIST, "index.html"))) {
|
|
18060
|
+
const isCheckout = existsSync26(join25(paths.PACKAGE_ROOT, "web", "package.json"));
|
|
17721
18061
|
return c.text(
|
|
17722
18062
|
isCheckout ? `Workspace UI build missing (${WEB_DIST}).
|
|
17723
18063
|
Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
|
|
@@ -18155,7 +18495,7 @@ async function threadReplies(client2, nameOrId, ts2) {
|
|
|
18155
18495
|
async function postMessage(client2, args) {
|
|
18156
18496
|
const channelArg = getArg2(args, "--channel");
|
|
18157
18497
|
const textFilePath = getArg2(args, "--text-file");
|
|
18158
|
-
const text = textFilePath ?
|
|
18498
|
+
const text = textFilePath ? readFileSync20(textFilePath, "utf8") : getArg2(args, "--text");
|
|
18159
18499
|
const threadTs = getArg2(args, "--thread");
|
|
18160
18500
|
const markdown = args.includes("--markdown");
|
|
18161
18501
|
const broadcast = args.includes("--broadcast") && Boolean(threadTs);
|
|
@@ -18253,7 +18593,7 @@ function defaultClaudeConfigDir() {
|
|
|
18253
18593
|
return process.env.CLAUDE_CONFIG_DIR || resolve6(homedir9(), ".claude");
|
|
18254
18594
|
}
|
|
18255
18595
|
function hasClaudeCredentials(configDir2) {
|
|
18256
|
-
if (
|
|
18596
|
+
if (existsSync27(resolve6(configDir2, ".credentials.json"))) {
|
|
18257
18597
|
return true;
|
|
18258
18598
|
}
|
|
18259
18599
|
if (process.platform === "darwin") {
|
|
@@ -18310,7 +18650,7 @@ async function ensureClaudeCodeReady() {
|
|
|
18310
18650
|
}
|
|
18311
18651
|
function getManifestYaml(botName) {
|
|
18312
18652
|
const manifestPath = resolve6(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
|
|
18313
|
-
const raw =
|
|
18653
|
+
const raw = readFileSync20(manifestPath, "utf-8");
|
|
18314
18654
|
return raw.replace(/name: Custie/g, `name: ${botName}`);
|
|
18315
18655
|
}
|
|
18316
18656
|
async function stepPersonalise() {
|
|
@@ -18617,7 +18957,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18617
18957
|
let settled = false;
|
|
18618
18958
|
let interval;
|
|
18619
18959
|
let timeout;
|
|
18620
|
-
const
|
|
18960
|
+
const finish2 = (action) => {
|
|
18621
18961
|
if (settled) return;
|
|
18622
18962
|
settled = true;
|
|
18623
18963
|
clearInterval(interval);
|
|
@@ -18626,7 +18966,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18626
18966
|
};
|
|
18627
18967
|
const checkUrl = (p) => {
|
|
18628
18968
|
if (p.url().includes("install-on-team?success=1")) {
|
|
18629
|
-
|
|
18969
|
+
finish2(() => resolve7(p));
|
|
18630
18970
|
}
|
|
18631
18971
|
};
|
|
18632
18972
|
context.on("page", (newPage) => {
|
|
@@ -18637,7 +18977,7 @@ async function stepBrowserTokens(botName) {
|
|
|
18637
18977
|
for (const p of context.pages()) checkUrl(p);
|
|
18638
18978
|
}, 1e3);
|
|
18639
18979
|
timeout = setTimeout(() => {
|
|
18640
|
-
|
|
18980
|
+
finish2(() => reject(new Error("Timed out waiting for the app to be installed.")));
|
|
18641
18981
|
}, 12e4);
|
|
18642
18982
|
});
|
|
18643
18983
|
success4("App installed! Extracting Bot Token...");
|
|
@@ -18727,7 +19067,7 @@ function writeConfigFile(config) {
|
|
|
18727
19067
|
`OWNER_USER_ID=${config.ownerUserId}`,
|
|
18728
19068
|
``
|
|
18729
19069
|
].join("\n");
|
|
18730
|
-
|
|
19070
|
+
writeFileSync12(paths.CONFIG_FILE, envContent);
|
|
18731
19071
|
success4(`Config written to ${paths.CONFIG_FILE}`);
|
|
18732
19072
|
}
|
|
18733
19073
|
function printNextSteps() {
|
|
@@ -18739,11 +19079,11 @@ function printNextSteps() {
|
|
|
18739
19079
|
console.log(" Run \x1B[1mflyspace service install\x1B[0m\n");
|
|
18740
19080
|
}
|
|
18741
19081
|
function copyDefaultPrompt(botName) {
|
|
18742
|
-
if (!
|
|
19082
|
+
if (!existsSync27(paths.PROMPT_FILE)) {
|
|
18743
19083
|
const defaultPrompt = resolve6(paths.PACKAGE_ROOT, "system.default.md");
|
|
18744
|
-
if (
|
|
18745
|
-
const content =
|
|
18746
|
-
|
|
19084
|
+
if (existsSync27(defaultPrompt)) {
|
|
19085
|
+
const content = readFileSync20(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
|
|
19086
|
+
writeFileSync12(paths.PROMPT_FILE, content);
|
|
18747
19087
|
success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
|
|
18748
19088
|
}
|
|
18749
19089
|
}
|
|
@@ -18755,7 +19095,7 @@ async function runSlackSetup(args) {
|
|
|
18755
19095
|
const prereq = await ensureClaudeCodeReady();
|
|
18756
19096
|
if (!prereq) return;
|
|
18757
19097
|
const { claudeConfigDir } = prereq;
|
|
18758
|
-
if (
|
|
19098
|
+
if (existsSync27(paths.CONFIG_FILE)) {
|
|
18759
19099
|
const reconfigure = await confirm4({
|
|
18760
19100
|
message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
|
|
18761
19101
|
default: false
|
|
@@ -19066,21 +19406,21 @@ async function runChatCmd(args) {
|
|
|
19066
19406
|
}
|
|
19067
19407
|
|
|
19068
19408
|
// src/commands/memory.ts
|
|
19069
|
-
import { readFileSync as
|
|
19409
|
+
import { readFileSync as readFileSync22 } from "fs";
|
|
19070
19410
|
|
|
19071
19411
|
// src/store/claude-memory-import.ts
|
|
19072
|
-
import { existsSync as
|
|
19412
|
+
import { existsSync as existsSync28, readdirSync as readdirSync5, readFileSync as readFileSync21 } from "fs";
|
|
19073
19413
|
import { homedir as homedir10 } from "os";
|
|
19074
|
-
import { basename as basename8, join as
|
|
19414
|
+
import { basename as basename8, join as join26 } from "path";
|
|
19075
19415
|
import dotenv2 from "dotenv";
|
|
19076
19416
|
function claudeMemoryDir(cwd, configDir2) {
|
|
19077
|
-
return
|
|
19417
|
+
return join26(configDir2, "projects", cwd.replaceAll(/[^a-zA-Z0-9]/g, "-"), "memory");
|
|
19078
19418
|
}
|
|
19079
19419
|
function profileClaudeConfigDirFor(profile) {
|
|
19080
|
-
const dir = profile === "default" ? paths.BASE_CONFIG_DIR :
|
|
19081
|
-
const file =
|
|
19082
|
-
const env =
|
|
19083
|
-
return env["CLAUDE_CONFIG_DIR"]?.trim() ||
|
|
19420
|
+
const dir = profile === "default" ? paths.BASE_CONFIG_DIR : join26(paths.PROFILES_DIR, profile);
|
|
19421
|
+
const file = join26(dir, "config.env");
|
|
19422
|
+
const env = existsSync28(file) ? dotenv2.parse(readFileSync21(file)) : {};
|
|
19423
|
+
return env["CLAUDE_CONFIG_DIR"]?.trim() || join26(homedir10(), ".claude");
|
|
19084
19424
|
}
|
|
19085
19425
|
var TYPE_MAP = {
|
|
19086
19426
|
user: "preference",
|
|
@@ -19125,11 +19465,11 @@ function parseClaudeMemory(fileName, text, channelId) {
|
|
|
19125
19465
|
return input2;
|
|
19126
19466
|
}
|
|
19127
19467
|
async function importClaudeMemories(dir, channelId, opts = {}) {
|
|
19128
|
-
if (!
|
|
19468
|
+
if (!existsSync28(dir)) throw new Error(`No Claude memory directory at ${dir}`);
|
|
19129
19469
|
const files = readdirSync5(dir).filter((f) => f.endsWith(".md") && f !== "MEMORY.md").sort();
|
|
19130
19470
|
const results = [];
|
|
19131
19471
|
for (const file of files) {
|
|
19132
|
-
const parsed = parseClaudeMemory(file,
|
|
19472
|
+
const parsed = parseClaudeMemory(file, readFileSync21(join26(dir, file), "utf-8"), channelId);
|
|
19133
19473
|
if ("skip" in parsed) {
|
|
19134
19474
|
results.push({ file, outcome: "skipped", reason: parsed.skip });
|
|
19135
19475
|
continue;
|
|
@@ -19164,6 +19504,12 @@ var USAGE5 = `
|
|
|
19164
19504
|
Defaults to the channel profile's CLAUDE_CONFIG_DIR
|
|
19165
19505
|
(or ~/.claude)/projects/<cwd slug>/memory
|
|
19166
19506
|
|
|
19507
|
+
Scope (list, get, search, save, delete; default: the channel's memory):
|
|
19508
|
+
--scope user --user <user-id> That member's personal memory, in the channel's
|
|
19509
|
+
workspace. Only ever the current sender's.
|
|
19510
|
+
--scope workspace The workspace's memory. Read-only here: only a
|
|
19511
|
+
Workspace Admin changes it, from Settings.
|
|
19512
|
+
|
|
19167
19513
|
Types: ${MEMORY_TYPES.join(", ")}
|
|
19168
19514
|
`;
|
|
19169
19515
|
function getArg4(args, flag) {
|
|
@@ -19194,17 +19540,23 @@ async function runMemoryCmd(args) {
|
|
|
19194
19540
|
if (!channelArg) throw new Error("--channel <name-or-id> is required");
|
|
19195
19541
|
const channel = await resolveChannel(channelArg, getArg4(rest, "--profile-name"));
|
|
19196
19542
|
const words = positionals(rest);
|
|
19543
|
+
const target = await resolveTarget(rest, channel);
|
|
19544
|
+
const where = target.scope === "channel" ? channel.name : `${target.scope} memory`;
|
|
19545
|
+
if (target.scope === "workspace" && (subcommand === "save" || subcommand === "delete")) {
|
|
19546
|
+
throw new Error("Workspace memory is read-only to the agent; a Workspace Admin manages it.");
|
|
19547
|
+
}
|
|
19197
19548
|
switch (subcommand) {
|
|
19198
19549
|
case "list": {
|
|
19199
|
-
const memories = await
|
|
19550
|
+
const memories = await listMemories(target);
|
|
19200
19551
|
console.log(memories.length ? memories.map(indexLine).join("\n") : "No memories yet.");
|
|
19201
19552
|
break;
|
|
19202
19553
|
}
|
|
19203
19554
|
case "get": {
|
|
19204
19555
|
const name = words[0];
|
|
19205
19556
|
if (!name) throw new Error("Usage: flyspace memory get --channel <c> <name>");
|
|
19206
|
-
const memory = await
|
|
19207
|
-
if (!memory) throw new Error(`No memory named ${name} in ${
|
|
19557
|
+
const memory = await getMemory(target, name);
|
|
19558
|
+
if (!memory) throw new Error(`No memory named ${name} in ${where}.`);
|
|
19559
|
+
await markMemoryUsed(memory);
|
|
19208
19560
|
console.log(`# ${memory.name} (${memory.type})
|
|
19209
19561
|
|
|
19210
19562
|
${memory.description}
|
|
@@ -19214,7 +19566,7 @@ ${memory.body}`);
|
|
|
19214
19566
|
}
|
|
19215
19567
|
case "search": {
|
|
19216
19568
|
if (!words.length) throw new Error("Usage: flyspace memory search --channel <c> <words...>");
|
|
19217
|
-
const hits = await
|
|
19569
|
+
const hits = await searchMemories(target, words.join(" "));
|
|
19218
19570
|
console.log(hits.length ? hits.map(indexLine).join("\n") : "No matching memories.");
|
|
19219
19571
|
break;
|
|
19220
19572
|
}
|
|
@@ -19229,11 +19581,11 @@ ${memory.body}`);
|
|
|
19229
19581
|
);
|
|
19230
19582
|
}
|
|
19231
19583
|
if (!isMemoryType(type)) throw new Error(`--type must be one of ${MEMORY_TYPES.join(", ")}`);
|
|
19232
|
-
const body = bodyArg === "-" ?
|
|
19584
|
+
const body = bodyArg === "-" ? readFileSync22(0, "utf-8") : bodyArg;
|
|
19233
19585
|
const thread = getArg4(rest, "--thread");
|
|
19234
|
-
const { created, changed } = await
|
|
19586
|
+
const { created, changed } = await saveMemory(
|
|
19587
|
+
target,
|
|
19235
19588
|
{
|
|
19236
|
-
channelId: channel.id,
|
|
19237
19589
|
name,
|
|
19238
19590
|
type,
|
|
19239
19591
|
description,
|
|
@@ -19244,18 +19596,19 @@ ${memory.body}`);
|
|
|
19244
19596
|
"agent"
|
|
19245
19597
|
);
|
|
19246
19598
|
const verb = created ? "Saved" : changed ? "Updated" : "Unchanged:";
|
|
19247
|
-
console.log(`${verb} memory ${name} in ${
|
|
19599
|
+
console.log(`${verb} memory ${name} in ${where}.`);
|
|
19248
19600
|
break;
|
|
19249
19601
|
}
|
|
19250
19602
|
case "delete": {
|
|
19251
19603
|
const ref = words[0];
|
|
19252
19604
|
if (!ref) throw new Error("Usage: flyspace memory delete --channel <c> <name-or-id>");
|
|
19253
|
-
const deleted = await
|
|
19254
|
-
if (!deleted) throw new Error(`No memory ${ref} in ${
|
|
19605
|
+
const deleted = await deleteMemory(target, ref, "agent", getArg4(rest, "--thread"));
|
|
19606
|
+
if (!deleted) throw new Error(`No memory ${ref} in ${where}.`);
|
|
19255
19607
|
console.log(`Deleted memory ${ref}.`);
|
|
19256
19608
|
break;
|
|
19257
19609
|
}
|
|
19258
19610
|
case "import": {
|
|
19611
|
+
if (target.scope !== "channel") throw new Error("import only fills a channel memory");
|
|
19259
19612
|
const dir = getArg4(rest, "--dir") ?? claudeMemoryDir(channel.cwd, profileClaudeConfigDirFor(channel.profile));
|
|
19260
19613
|
const dryRun = rest.includes("--dry-run");
|
|
19261
19614
|
const results = await importClaudeMemories(dir, channel.id, { dryRun });
|
|
@@ -19275,6 +19628,19 @@ ${memory.body}`);
|
|
|
19275
19628
|
process.exit(1);
|
|
19276
19629
|
}
|
|
19277
19630
|
}
|
|
19631
|
+
async function resolveTarget(args, channel) {
|
|
19632
|
+
const scope = getArg4(args, "--scope") ?? "channel";
|
|
19633
|
+
if (scope === "channel") return { scope: "channel", channelId: channel.id };
|
|
19634
|
+
if (scope !== "user" && scope !== "workspace") {
|
|
19635
|
+
throw new Error("--scope must be channel, user or workspace");
|
|
19636
|
+
}
|
|
19637
|
+
const workspace = await getWorkspaceBySlug(channel.profile);
|
|
19638
|
+
if (!workspace) throw new Error(`No workspace for ${channel.profile}.`);
|
|
19639
|
+
if (scope === "workspace") return { scope, workspaceId: workspace.id };
|
|
19640
|
+
const userId = getArg4(args, "--user");
|
|
19641
|
+
if (!userId) throw new Error("--scope user needs --user <user-id>");
|
|
19642
|
+
return { scope, workspaceId: workspace.id, userId };
|
|
19643
|
+
}
|
|
19278
19644
|
|
|
19279
19645
|
// src/commands/mcp.ts
|
|
19280
19646
|
var USAGE6 = `
|
|
@@ -19313,10 +19679,10 @@ async function runMcpCmd(args) {
|
|
|
19313
19679
|
}
|
|
19314
19680
|
|
|
19315
19681
|
// src/commands/dashboard.ts
|
|
19316
|
-
import { spawn as
|
|
19317
|
-
import { existsSync as
|
|
19682
|
+
import { spawn as spawn7, spawnSync } from "child_process";
|
|
19683
|
+
import { existsSync as existsSync29 } from "fs";
|
|
19318
19684
|
import { platform as platform7 } from "os";
|
|
19319
|
-
import { join as
|
|
19685
|
+
import { join as join27 } from "path";
|
|
19320
19686
|
function flagValue(args, name) {
|
|
19321
19687
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
19322
19688
|
if (eq) return eq.slice(`--${name}=`.length);
|
|
@@ -19326,13 +19692,13 @@ function flagValue(args, name) {
|
|
|
19326
19692
|
function openBrowser2(url) {
|
|
19327
19693
|
const cmd = platform7() === "darwin" ? "open" : platform7() === "win32" ? "start" : "xdg-open";
|
|
19328
19694
|
try {
|
|
19329
|
-
|
|
19695
|
+
spawn7(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
19330
19696
|
} catch {
|
|
19331
19697
|
}
|
|
19332
19698
|
}
|
|
19333
19699
|
function ensureWebBuild() {
|
|
19334
|
-
if (
|
|
19335
|
-
if (!
|
|
19700
|
+
if (existsSync29(join27(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
|
|
19701
|
+
if (!existsSync29(join27(paths.PACKAGE_ROOT, "web", "package.json"))) return;
|
|
19336
19702
|
console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
|
|
19337
19703
|
const result = spawnSync("pnpm", ["--dir", "web", "build"], {
|
|
19338
19704
|
cwd: paths.PACKAGE_ROOT,
|
|
@@ -19382,7 +19748,7 @@ async function runDashboard(args) {
|
|
|
19382
19748
|
}
|
|
19383
19749
|
|
|
19384
19750
|
// src/commands/workspace.ts
|
|
19385
|
-
import { existsSync as
|
|
19751
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
19386
19752
|
var USAGE7 = `
|
|
19387
19753
|
Usage: flyspace workspace <subcommand> [options]
|
|
19388
19754
|
|
|
@@ -19468,10 +19834,10 @@ function provisionLocal(botName) {
|
|
|
19468
19834
|
ensureDirs();
|
|
19469
19835
|
console.log(` config dir ready (${paths.CONFIG_DIR})`);
|
|
19470
19836
|
console.log(` data dir ready (${paths.DATA_DIR})`);
|
|
19471
|
-
if (
|
|
19837
|
+
if (existsSync30(paths.CONFIG_FILE)) {
|
|
19472
19838
|
console.log(" config.env exists");
|
|
19473
19839
|
} else {
|
|
19474
|
-
|
|
19840
|
+
writeFileSync13(paths.CONFIG_FILE, `BOT_NAME=${botName}
|
|
19475
19841
|
`);
|
|
19476
19842
|
console.log(` config.env written (${paths.CONFIG_FILE})`);
|
|
19477
19843
|
}
|
|
@@ -19485,8 +19851,8 @@ async function provisionChannels(specs) {
|
|
|
19485
19851
|
continue;
|
|
19486
19852
|
}
|
|
19487
19853
|
const cwd = expandPath(spec.cwd);
|
|
19488
|
-
if (!
|
|
19489
|
-
|
|
19854
|
+
if (!existsSync30(cwd)) {
|
|
19855
|
+
mkdirSync13(cwd, { recursive: true });
|
|
19490
19856
|
console.log(` created cwd ${cwd}`);
|
|
19491
19857
|
}
|
|
19492
19858
|
await createChannel({ name: spec.name, cwd, position: index });
|
|
@@ -19695,7 +20061,7 @@ async function main() {
|
|
|
19695
20061
|
break;
|
|
19696
20062
|
case "-v":
|
|
19697
20063
|
case "--version":
|
|
19698
|
-
printRaw("0.1.
|
|
20064
|
+
printRaw("0.1.16");
|
|
19699
20065
|
break;
|
|
19700
20066
|
case "-h":
|
|
19701
20067
|
case "--help":
|