flyspace-cli 0.1.32 → 0.1.33
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-6YFDWUNF.js → chunk-KGX44L3V.js} +116 -17
- package/dist/cli.js +395 -64
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -102,6 +102,9 @@ function loadConfig() {
|
|
|
102
102
|
const autoRespondChannelIds = new Set(
|
|
103
103
|
(process.env["AUTO_RESPOND_CHANNEL_IDS"] ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
104
104
|
);
|
|
105
|
+
const slackEnglishOnlyExemptChannelIds = new Set(
|
|
106
|
+
(process.env["SLACK_ENGLISH_ONLY_EXEMPT_CHANNEL_IDS"] ?? "").split(",").map((s) => s.trim()).filter(Boolean)
|
|
107
|
+
);
|
|
105
108
|
const slack = slackConfigured();
|
|
106
109
|
return {
|
|
107
110
|
slackBotToken: slack ? requireEnv("SLACK_BOT_TOKEN") : void 0,
|
|
@@ -118,7 +121,9 @@ function loadConfig() {
|
|
|
118
121
|
maxBudgetUsd: parseBudget(process.env["MAX_BUDGET_USD"]),
|
|
119
122
|
ownerUserId,
|
|
120
123
|
autoRespondChannelIds,
|
|
121
|
-
codexModel: process.env["CUSTIE_CODEX_MODEL"]?.trim() || DEFAULT_CODEX_MODEL
|
|
124
|
+
codexModel: process.env["CUSTIE_CODEX_MODEL"]?.trim() || DEFAULT_CODEX_MODEL,
|
|
125
|
+
slackEnglishOnly: process.env["SLACK_ENGLISH_ONLY"] === "true",
|
|
126
|
+
slackEnglishOnlyExemptChannelIds
|
|
122
127
|
};
|
|
123
128
|
}
|
|
124
129
|
|
|
@@ -356,6 +361,7 @@ function rowToChannel(row) {
|
|
|
356
361
|
isDefault: row.is_default === true,
|
|
357
362
|
profile: row.profile ?? "default",
|
|
358
363
|
archivedAt: row.archived_at,
|
|
364
|
+
greetedAt: row.greeted_at,
|
|
359
365
|
createdAt: row.created_at,
|
|
360
366
|
updatedAt: row.updated_at
|
|
361
367
|
};
|
|
@@ -616,6 +622,12 @@ async function updateChannelById(id, patch) {
|
|
|
616
622
|
await refreshChannelRegistry();
|
|
617
623
|
return rowToChannel(data);
|
|
618
624
|
}
|
|
625
|
+
async function markChannelGreeted(id) {
|
|
626
|
+
const { data, error } = await getSupabaseClient().from("channels").update({ greeted_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id).is("greeted_at", null).select("id");
|
|
627
|
+
if (error) throw new Error(error.message);
|
|
628
|
+
await refreshChannelRegistry();
|
|
629
|
+
return (data ?? []).length > 0;
|
|
630
|
+
}
|
|
619
631
|
async function updateChannelsMcpConnections(updates) {
|
|
620
632
|
if (!updates.length) return;
|
|
621
633
|
const supabase = getSupabaseClient();
|
|
@@ -2575,14 +2587,15 @@ var workspaceCache = /* @__PURE__ */ new Map();
|
|
|
2575
2587
|
async function getWorkspaceBySlug(slug) {
|
|
2576
2588
|
const cached = workspaceCache.get(slug);
|
|
2577
2589
|
if (cached && cached.expiresAt > Date.now()) return cached.workspace;
|
|
2578
|
-
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug, agent_connected_at, projects_setup_at, plan").eq("slug", slug).maybeSingle();
|
|
2590
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug, agent_connected_at, projects_setup_at, plan, archived_at").eq("slug", slug).maybeSingle();
|
|
2579
2591
|
if (error) throw new Error(`getWorkspaceBySlug(${slug}): ${error.message}`);
|
|
2580
2592
|
const workspace = data ? {
|
|
2581
2593
|
id: data.id,
|
|
2582
2594
|
slug: data.slug,
|
|
2583
2595
|
agentConnectedAt: data.agent_connected_at,
|
|
2584
2596
|
projectsSetupAt: data.projects_setup_at,
|
|
2585
|
-
plan: data.plan ?? null
|
|
2597
|
+
plan: data.plan ?? null,
|
|
2598
|
+
archivedAt: data.archived_at ?? null
|
|
2586
2599
|
} : null;
|
|
2587
2600
|
workspaceCache.set(slug, { workspace, expiresAt: Date.now() + WORKSPACE_CACHE_TTL_MS });
|
|
2588
2601
|
return workspace;
|
|
@@ -2597,29 +2610,70 @@ async function listWorkspaces() {
|
|
|
2597
2610
|
if (error) throw new Error(`listWorkspaces: ${error.message}`);
|
|
2598
2611
|
return data ?? [];
|
|
2599
2612
|
}
|
|
2613
|
+
var TRIAL_DAYS = 14;
|
|
2600
2614
|
async function listWorkspaceSummaries() {
|
|
2601
2615
|
const db = getSupabaseClient();
|
|
2602
|
-
const [workspaces, settings, channels, memberships] = await Promise.all([
|
|
2603
|
-
db.from("workspaces").select("id, slug"),
|
|
2604
|
-
db.from("workspace_settings").select("profile, display_name"),
|
|
2616
|
+
const [workspaces, settings, channels, memberships, activity] = await Promise.all([
|
|
2617
|
+
db.from("workspaces").select("id, slug, created_at, agent_connected_at, plan, plan_status, archived_at"),
|
|
2618
|
+
db.from("workspace_settings").select("profile, display_name, logo"),
|
|
2605
2619
|
db.from("channels").select("profile").is("archived_at", null),
|
|
2606
|
-
db.from("memberships").select("workspace_id").eq("status", "active")
|
|
2620
|
+
db.from("memberships").select("workspace_id, user_id, email, handle, role, accepted_at").eq("status", "active").eq("is_support", false).order("invited_at", { ascending: true }),
|
|
2621
|
+
db.rpc("workspace_last_activity")
|
|
2607
2622
|
]);
|
|
2608
|
-
for (const r of [workspaces, settings, channels, memberships]) {
|
|
2623
|
+
for (const r of [workspaces, settings, channels, memberships, activity]) {
|
|
2609
2624
|
if (r.error) throw new Error(`listWorkspaceSummaries: ${r.error.message}`);
|
|
2610
2625
|
}
|
|
2611
2626
|
const displayNames = new Map(
|
|
2612
2627
|
(settings.data ?? []).map((s) => [s.profile, s.display_name])
|
|
2613
2628
|
);
|
|
2629
|
+
const logos = new Map(
|
|
2630
|
+
(settings.data ?? []).map((s) => [s.profile, s.logo])
|
|
2631
|
+
);
|
|
2632
|
+
const lastActivity = new Map(
|
|
2633
|
+
(activity.data ?? []).map((a) => [
|
|
2634
|
+
a.profile,
|
|
2635
|
+
a.last_activity_at
|
|
2636
|
+
])
|
|
2637
|
+
);
|
|
2614
2638
|
const tally = (keys) => keys.reduce((m, k) => m.set(k, (m.get(k) ?? 0) + 1), /* @__PURE__ */ new Map());
|
|
2615
2639
|
const channelCounts = tally((channels.data ?? []).map((c) => c.profile));
|
|
2616
2640
|
const memberCounts = tally((memberships.data ?? []).map((m) => m.workspace_id));
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2641
|
+
const owners = /* @__PURE__ */ new Map();
|
|
2642
|
+
for (const m of memberships.data ?? []) {
|
|
2643
|
+
if (m.role !== "admin" || owners.has(m.workspace_id)) continue;
|
|
2644
|
+
owners.set(m.workspace_id, {
|
|
2645
|
+
userId: m.user_id,
|
|
2646
|
+
email: m.email,
|
|
2647
|
+
handle: m.handle,
|
|
2648
|
+
joinedAt: m.accepted_at ?? null
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
return (workspaces.data ?? []).map((w) => {
|
|
2652
|
+
const createdAt = w.created_at;
|
|
2653
|
+
const planStatus = w.plan_status ?? null;
|
|
2654
|
+
return {
|
|
2655
|
+
id: w.id,
|
|
2656
|
+
slug: w.slug,
|
|
2657
|
+
createdAt,
|
|
2658
|
+
agentConnectedAt: w.agent_connected_at,
|
|
2659
|
+
plan: w.plan ?? null,
|
|
2660
|
+
planStatus,
|
|
2661
|
+
trialEndsAt: planStatus === "trialing" ? new Date(new Date(createdAt).getTime() + TRIAL_DAYS * 864e5).toISOString() : null,
|
|
2662
|
+
archivedAt: w.archived_at ?? null,
|
|
2663
|
+
displayName: displayNames.get(w.slug) || null,
|
|
2664
|
+
logo: logos.get(w.slug) || null,
|
|
2665
|
+
channelCount: channelCounts.get(w.slug) ?? 0,
|
|
2666
|
+
memberCount: memberCounts.get(w.id) ?? 0,
|
|
2667
|
+
lastActivityAt: lastActivity.get(w.slug) ?? null,
|
|
2668
|
+
owner: owners.get(w.id) ?? null
|
|
2669
|
+
};
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
async function setWorkspaceArchived(workspaceId, archived) {
|
|
2673
|
+
const { data, error } = await getSupabaseClient().from("workspaces").update({ archived_at: archived ? (/* @__PURE__ */ new Date()).toISOString() : null }).eq("id", workspaceId).select("slug");
|
|
2674
|
+
if (error) throw new Error(`setWorkspaceArchived(${workspaceId}): ${error.message}`);
|
|
2675
|
+
if (!data?.length) throw new Error("workspace not found");
|
|
2676
|
+
for (const row of data) workspaceCache.delete(row.slug);
|
|
2623
2677
|
}
|
|
2624
2678
|
async function createWorkspace(slug) {
|
|
2625
2679
|
const { data, error } = await getSupabaseClient().from("workspaces").insert({ slug }).select("id, slug").single();
|
|
@@ -5383,6 +5437,29 @@ async function forkThreadWithSummary(ctx, deps) {
|
|
|
5383
5437
|
return { forked: true, newThreadTs };
|
|
5384
5438
|
}
|
|
5385
5439
|
|
|
5440
|
+
// src/slack/reply-language.ts
|
|
5441
|
+
var CJK = /[-ヿ㐀-䶿一-鿿가-豈-]/g;
|
|
5442
|
+
var CJK_THRESHOLD = 10;
|
|
5443
|
+
var REWRITE_PROMPT = "[system: this Slack channel is English-only. Your previous reply was NOT posted because it was not in English. Rewrite that same reply in English (en-AU spelling): same content, same structure, and keep any [BUTTONS: ...] marker with its labels translated. Do not run any tools and do not mention this rewrite. Output only the rewritten reply.]";
|
|
5444
|
+
function isEnglishOnlyChannel(config, channelId) {
|
|
5445
|
+
return config.slackEnglishOnly && !config.slackEnglishOnlyExemptChannelIds.has(channelId);
|
|
5446
|
+
}
|
|
5447
|
+
function isNonEnglishReply(text) {
|
|
5448
|
+
const prose = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`\n]*`/g, "");
|
|
5449
|
+
return (prose.match(CJK)?.length ?? 0) >= CJK_THRESHOLD;
|
|
5450
|
+
}
|
|
5451
|
+
async function enforceEnglishReply(text, rewrite) {
|
|
5452
|
+
if (!isNonEnglishReply(text)) return text;
|
|
5453
|
+
try {
|
|
5454
|
+
const rewritten = await rewrite(REWRITE_PROMPT);
|
|
5455
|
+
if (rewritten?.trim()) return rewritten;
|
|
5456
|
+
console.error("[reply-language] rewrite returned nothing \u2014 posting the original");
|
|
5457
|
+
} catch (err) {
|
|
5458
|
+
console.error("[reply-language] rewrite failed \u2014 posting the original:", err);
|
|
5459
|
+
}
|
|
5460
|
+
return text;
|
|
5461
|
+
}
|
|
5462
|
+
|
|
5386
5463
|
// src/store/identity-map.ts
|
|
5387
5464
|
function identityHandleFor(slackUserId) {
|
|
5388
5465
|
const raw = process.env.SLACK_IDENTITY_MAP;
|
|
@@ -6549,7 +6626,12 @@ ${trimmedDetail.slice(0, 1500)}
|
|
|
6549
6626
|
if (codexResponse.sessionId) {
|
|
6550
6627
|
store.saveSession(channelId, sessionKey, codexResponse.sessionId, "codex");
|
|
6551
6628
|
}
|
|
6552
|
-
const
|
|
6629
|
+
const codexSessionId = codexResponse.sessionId;
|
|
6630
|
+
const codexText = codexSessionId && isEnglishOnlyChannel(config, channelId) ? await enforceEnglishReply(codexResponse.text, async (p) => {
|
|
6631
|
+
const r = await askCodex(p, cwd, codexOptions, codexSessionId);
|
|
6632
|
+
return r.isError ? null : r.text;
|
|
6633
|
+
}) : codexResponse.text;
|
|
6634
|
+
const { cleanedText: cleanedText2, buttons: buttons2 } = extractButtons(codexText);
|
|
6553
6635
|
const actionsBlock2 = buttons2 ? buildActionsBlock(buttons2) : null;
|
|
6554
6636
|
const rtBlocks2 = markdownToBlocks(cleanedText2);
|
|
6555
6637
|
const messages2 = rtBlocks2.length > 0 ? rtBlocks2.map((b) => ({ blocks: [b], text: blockToFallbackText(b) })) : splitMessage(toSlackMarkdown(cleanedText2)).map((text) => ({ text }));
|
|
@@ -6740,7 +6822,21 @@ retry: ${retryDetail}`);
|
|
|
6740
6822
|
} else {
|
|
6741
6823
|
store.saveSession(channelId, sessionKey, resolved.sessionId);
|
|
6742
6824
|
}
|
|
6743
|
-
const
|
|
6825
|
+
const replyText = !resolved.isError && resolved.sessionId && isEnglishOnlyChannel(config, channelId) ? await enforceEnglishReply(resolved.text, async (p) => {
|
|
6826
|
+
const r = await askClaude(
|
|
6827
|
+
p,
|
|
6828
|
+
cwd,
|
|
6829
|
+
botName,
|
|
6830
|
+
askOptions,
|
|
6831
|
+
claudeConfigDir,
|
|
6832
|
+
resolved.sessionId,
|
|
6833
|
+
claudeCodeOauthToken
|
|
6834
|
+
);
|
|
6835
|
+
if (r.isError) return null;
|
|
6836
|
+
store.saveSession(channelId, sessionKey, r.sessionId);
|
|
6837
|
+
return r.text;
|
|
6838
|
+
}) : resolved.text;
|
|
6839
|
+
const { cleanedText, buttons } = extractButtons(replyText);
|
|
6744
6840
|
const actionsBlock = buttons ? buildActionsBlock(buttons) : null;
|
|
6745
6841
|
const rtBlocks = markdownToBlocks(cleanedText);
|
|
6746
6842
|
const messages = rtBlocks.length > 0 ? rtBlocks.map((b) => ({ blocks: [b], text: blockToFallbackText(b) })) : splitMessage(toSlackMarkdown(cleanedText)).map((text) => ({ text }));
|
|
@@ -6757,7 +6853,7 @@ retry: ${retryDetail}`);
|
|
|
6757
6853
|
}
|
|
6758
6854
|
if (debug4) {
|
|
6759
6855
|
console.log(
|
|
6760
|
-
`[response] length=${
|
|
6856
|
+
`[response] length=${replyText.length} messages=${messages.length} buttons=${buttons?.length ?? 0}`
|
|
6761
6857
|
);
|
|
6762
6858
|
}
|
|
6763
6859
|
await clearReaction();
|
|
@@ -8975,6 +9071,7 @@ export {
|
|
|
8975
9071
|
createChannel,
|
|
8976
9072
|
DefaultChannelError,
|
|
8977
9073
|
updateChannelById,
|
|
9074
|
+
markChannelGreeted,
|
|
8978
9075
|
updateChannelsMcpConnections,
|
|
8979
9076
|
ensureAgentDmChannel,
|
|
8980
9077
|
ensureGeneralChannel,
|
|
@@ -9035,7 +9132,9 @@ export {
|
|
|
9035
9132
|
getWorkspaceBySlug,
|
|
9036
9133
|
getWorkspaceById,
|
|
9037
9134
|
listWorkspaces,
|
|
9135
|
+
TRIAL_DAYS,
|
|
9038
9136
|
listWorkspaceSummaries,
|
|
9137
|
+
setWorkspaceArchived,
|
|
9039
9138
|
createWorkspace,
|
|
9040
9139
|
deleteWorkspace,
|
|
9041
9140
|
setWorkspaceStripeSubscription,
|
package/dist/cli.js
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
SKIP_SUBTYPES,
|
|
19
19
|
SLACK_ENV,
|
|
20
20
|
SessionStore,
|
|
21
|
+
TRIAL_DAYS,
|
|
21
22
|
UsageStore,
|
|
22
23
|
activateMembership,
|
|
23
24
|
agentLabel,
|
|
@@ -118,6 +119,7 @@ import {
|
|
|
118
119
|
loadFullDashboardSettings,
|
|
119
120
|
loadWorkspaceBranding,
|
|
120
121
|
loadWorkspacePrompt,
|
|
122
|
+
markChannelGreeted,
|
|
121
123
|
markInviteResent,
|
|
122
124
|
markMemoryUsed,
|
|
123
125
|
markWorkspaceGreeted,
|
|
@@ -159,6 +161,7 @@ import {
|
|
|
159
161
|
setMembershipHandle,
|
|
160
162
|
setMemoryAutoPaused,
|
|
161
163
|
setOutputStyleDefault,
|
|
164
|
+
setWorkspaceArchived,
|
|
162
165
|
setWorkspaceStripeSubscription,
|
|
163
166
|
slackConfigured,
|
|
164
167
|
startAutomationRegistryPolling,
|
|
@@ -184,7 +187,7 @@ import {
|
|
|
184
187
|
warnOnBrokenChannelCwds,
|
|
185
188
|
workspaceSkillsDir,
|
|
186
189
|
writeSkillFile
|
|
187
|
-
} from "./chunk-
|
|
190
|
+
} from "./chunk-KGX44L3V.js";
|
|
188
191
|
import {
|
|
189
192
|
firstReplyBody,
|
|
190
193
|
getThread,
|
|
@@ -1858,7 +1861,7 @@ async function runAuthCmd(args) {
|
|
|
1858
1861
|
}
|
|
1859
1862
|
|
|
1860
1863
|
// ../api/src/version.ts
|
|
1861
|
-
var CLI_VERSION = true ? "0.1.
|
|
1864
|
+
var CLI_VERSION = true ? "0.1.33" : null;
|
|
1862
1865
|
function isOlderVersion(a, b) {
|
|
1863
1866
|
const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
|
|
1864
1867
|
const [pa, pb] = [parse(a), parse(b)];
|
|
@@ -2468,7 +2471,7 @@ function npmEnv(target) {
|
|
|
2468
2471
|
return nodeBin ? { ...process.env, PATH: `${nodeBin}:${process.env.PATH ?? ""}` } : process.env;
|
|
2469
2472
|
}
|
|
2470
2473
|
async function runUpgrade() {
|
|
2471
|
-
const current = "0.1.
|
|
2474
|
+
const current = "0.1.33";
|
|
2472
2475
|
console.log(`Current version: ${current}`);
|
|
2473
2476
|
console.log("Checking for updates...\n");
|
|
2474
2477
|
try {
|
|
@@ -4144,7 +4147,7 @@ async function startWorkspaceTrial(workspace, plan, ownerEmail) {
|
|
|
4144
4147
|
const subscription = await stripe.subscriptions.create({
|
|
4145
4148
|
customer: customer.id,
|
|
4146
4149
|
items: [{ price: priceId }],
|
|
4147
|
-
trial_period_days:
|
|
4150
|
+
trial_period_days: TRIAL_DAYS,
|
|
4148
4151
|
trial_settings: { end_behavior: { missing_payment_method: "pause" } },
|
|
4149
4152
|
metadata: { workspace_id: workspace.id, plan }
|
|
4150
4153
|
});
|
|
@@ -6583,7 +6586,7 @@ var requireDocsAccess = async (c, next) => {
|
|
|
6583
6586
|
};
|
|
6584
6587
|
|
|
6585
6588
|
// ../api/src/dashboard/api.ts
|
|
6586
|
-
import { Hono as
|
|
6589
|
+
import { Hono as Hono30 } from "hono";
|
|
6587
6590
|
|
|
6588
6591
|
// ../api/src/dashboard/public-share-routes.ts
|
|
6589
6592
|
import { existsSync as existsSync17, readFileSync as readFileSync16, statSync as statSync3 } from "fs";
|
|
@@ -8735,7 +8738,17 @@ function pruneExpired(now) {
|
|
|
8735
8738
|
}
|
|
8736
8739
|
var SUPPORT_NAME = "Flyspace Support";
|
|
8737
8740
|
var SUPPORT_AVATAR_URL = "/logo/flyspace-support.svg";
|
|
8741
|
+
function workspaceArchivedError() {
|
|
8742
|
+
return new HTTPException2(403, {
|
|
8743
|
+
res: Response.json({ error: "workspace archived", archived: true }, { status: 403 })
|
|
8744
|
+
});
|
|
8745
|
+
}
|
|
8738
8746
|
async function resolveAuthContext(c, opts = {}) {
|
|
8747
|
+
const ctx = await resolveAuthContextCached(c, opts);
|
|
8748
|
+
if (ctx?.archived && !ctx.isSupport) throw workspaceArchivedError();
|
|
8749
|
+
return ctx;
|
|
8750
|
+
}
|
|
8751
|
+
async function resolveAuthContextCached(c, opts) {
|
|
8739
8752
|
const cookie = c.req?.header("Cookie") ?? "";
|
|
8740
8753
|
if (!cookie) return resolveAuthContextUncached(c);
|
|
8741
8754
|
const key = `${getProfile()}\0${cookie}`;
|
|
@@ -8800,7 +8813,8 @@ async function resolveAuthContextUncached(c) {
|
|
|
8800
8813
|
passwordSet: Boolean(metadata.passwordSetAt),
|
|
8801
8814
|
agentConnectedAt: workspace.agentConnectedAt ?? null,
|
|
8802
8815
|
projectsSetupAt: workspace.projectsSetupAt ?? null,
|
|
8803
|
-
plan: workspace.plan ?? null
|
|
8816
|
+
plan: workspace.plan ?? null,
|
|
8817
|
+
...workspace.archivedAt ? { archived: true } : {}
|
|
8804
8818
|
};
|
|
8805
8819
|
}
|
|
8806
8820
|
|
|
@@ -9116,11 +9130,19 @@ function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
|
9116
9130
|
// ../api/src/dashboard/uploads-routes.ts
|
|
9117
9131
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
9118
9132
|
import { Hono as Hono7 } from "hono";
|
|
9133
|
+
import sharp4 from "sharp";
|
|
9119
9134
|
import { PutObjectCommand as PutObjectCommand2, GetObjectCommand as GetObjectCommand3 } from "@aws-sdk/client-s3";
|
|
9120
9135
|
import { getSignedUrl as getSignedUrl3 } from "@aws-sdk/s3-request-presigner";
|
|
9121
9136
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
9122
9137
|
var PRESIGN_TTL_SECONDS2 = 300;
|
|
9123
9138
|
var KEY_PREFIX = "chat";
|
|
9139
|
+
var RESIZABLE = /* @__PURE__ */ new Set(["image/jpeg", "image/png", "image/webp", "image/avif"]);
|
|
9140
|
+
var FULL_EDGE = 2560;
|
|
9141
|
+
var THUMB_EDGE = 480;
|
|
9142
|
+
var WEBP_QUALITY = 80;
|
|
9143
|
+
function toWebp(input2, edge) {
|
|
9144
|
+
return sharp4(input2).rotate().resize({ width: edge, height: edge, fit: "inside", withoutEnlargement: true }).webp({ quality: WEBP_QUALITY }).toBuffer();
|
|
9145
|
+
}
|
|
9124
9146
|
var READABLE_PREFIXES = [KEY_PREFIX, SCRATCH_PREFIX];
|
|
9125
9147
|
function sanitizeFilename(name) {
|
|
9126
9148
|
const cleaned = name.replace(/[/\\]/g, "_").replace(/\s+/g, "-").replace(/[^\w.-]/g, "").trim();
|
|
@@ -9144,22 +9166,33 @@ function createUploadsRouter() {
|
|
|
9144
9166
|
if (file.size > MAX_FILE_BYTES) {
|
|
9145
9167
|
return c.json({ error: `file must be ${MAX_FILE_BYTES / (1024 * 1024)}MB or smaller` }, 400);
|
|
9146
9168
|
}
|
|
9147
|
-
const
|
|
9148
|
-
const
|
|
9149
|
-
|
|
9150
|
-
|
|
9151
|
-
|
|
9152
|
-
|
|
9153
|
-
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
)
|
|
9169
|
+
const id = `${KEY_PREFIX}/${getProfile()}/${randomUUID2()}-${sanitizeFilename(file.name)}`;
|
|
9170
|
+
const original = Buffer.from(await file.arrayBuffer());
|
|
9171
|
+
const put = (Key, Body, ContentType) => getR2Client().send(new PutObjectCommand2({ Bucket: getR2Bucket(), Key, Body, ContentType }));
|
|
9172
|
+
let key = id;
|
|
9173
|
+
let size = file.size;
|
|
9174
|
+
let type = file.type;
|
|
9175
|
+
const renditions = RESIZABLE.has(file.type) ? await Promise.all([toWebp(original, FULL_EDGE), toWebp(original, THUMB_EDGE)]).catch(
|
|
9176
|
+
() => null
|
|
9177
|
+
) : null;
|
|
9178
|
+
if (renditions) {
|
|
9179
|
+
const [full, thumb] = renditions;
|
|
9180
|
+
key = `${id.replace(/\.[^./]*$/, "")}.webp`;
|
|
9181
|
+
size = full.length;
|
|
9182
|
+
type = "image/webp";
|
|
9183
|
+
await Promise.all([
|
|
9184
|
+
put(key, full, type),
|
|
9185
|
+
put(key.replace(/\.webp$/, ".thumb.webp"), thumb, type)
|
|
9186
|
+
]);
|
|
9187
|
+
} else {
|
|
9188
|
+
await put(key, original, file.type || "application/octet-stream");
|
|
9189
|
+
}
|
|
9157
9190
|
return c.json(
|
|
9158
9191
|
{
|
|
9159
9192
|
url: `/api/attachments/${encodeKeyForUrl(key)}`,
|
|
9160
9193
|
name: file.name,
|
|
9161
|
-
type
|
|
9162
|
-
size
|
|
9194
|
+
type,
|
|
9195
|
+
size
|
|
9163
9196
|
},
|
|
9164
9197
|
201
|
|
9165
9198
|
);
|
|
@@ -9544,12 +9577,94 @@ function createAuthRouter() {
|
|
|
9544
9577
|
return auth;
|
|
9545
9578
|
}
|
|
9546
9579
|
|
|
9580
|
+
// ../api/src/store/support-store.ts
|
|
9581
|
+
var SUPPORT_HANDLE = "flyspace-support";
|
|
9582
|
+
async function logSupportAction(entry) {
|
|
9583
|
+
const { error: error2 } = await getSupabaseClient().from("support_access_log").insert({
|
|
9584
|
+
workspace_id: entry.workspaceId,
|
|
9585
|
+
staff_user_id: entry.staffUserId,
|
|
9586
|
+
staff_email: entry.staffEmail,
|
|
9587
|
+
action: entry.action,
|
|
9588
|
+
detail: entry.detail ?? null
|
|
9589
|
+
});
|
|
9590
|
+
if (error2) throw new Error(`logSupportAction(${entry.action}): ${error2.message}`);
|
|
9591
|
+
}
|
|
9592
|
+
async function listSupportWorkspaceIds(userId) {
|
|
9593
|
+
const { data, error: error2 } = await getSupabaseClient().from("memberships").select("workspace_id").eq("user_id", userId).eq("is_support", true);
|
|
9594
|
+
if (error2) throw new Error(`listSupportWorkspaceIds: ${error2.message}`);
|
|
9595
|
+
return new Set((data ?? []).map((r) => r.workspace_id));
|
|
9596
|
+
}
|
|
9597
|
+
async function grantSupportMembership(params) {
|
|
9598
|
+
const db = getSupabaseClient();
|
|
9599
|
+
const { data: existing, error: readError } = await db.from("memberships").select("is_support").eq("workspace_id", params.workspaceId).eq("user_id", params.userId).maybeSingle();
|
|
9600
|
+
if (readError) throw new Error(`grantSupportMembership: ${readError.message}`);
|
|
9601
|
+
if (existing) {
|
|
9602
|
+
if (existing.is_support === true) return "existing";
|
|
9603
|
+
throw new Error("already a regular member of this workspace");
|
|
9604
|
+
}
|
|
9605
|
+
const { data: taken } = await db.from("memberships").select("id").eq("workspace_id", params.workspaceId).eq("handle", SUPPORT_HANDLE).maybeSingle();
|
|
9606
|
+
const handle = taken ? `${SUPPORT_HANDLE}-${params.userId.slice(0, 8)}` : SUPPORT_HANDLE;
|
|
9607
|
+
const { error: error2 } = await db.from("memberships").insert({
|
|
9608
|
+
workspace_id: params.workspaceId,
|
|
9609
|
+
user_id: params.userId,
|
|
9610
|
+
email: params.email,
|
|
9611
|
+
handle,
|
|
9612
|
+
role: "admin",
|
|
9613
|
+
status: "active",
|
|
9614
|
+
is_support: true,
|
|
9615
|
+
accepted_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
9616
|
+
});
|
|
9617
|
+
if (error2) throw new Error(`grantSupportMembership: ${error2.message}`);
|
|
9618
|
+
return "granted";
|
|
9619
|
+
}
|
|
9620
|
+
async function revokeSupportMembership(workspaceId, userId) {
|
|
9621
|
+
const { data, error: error2 } = await getSupabaseClient().from("memberships").delete().eq("workspace_id", workspaceId).eq("user_id", userId).eq("is_support", true).select("id");
|
|
9622
|
+
if (error2) throw new Error(`revokeSupportMembership: ${error2.message}`);
|
|
9623
|
+
return (data ?? []).length > 0;
|
|
9624
|
+
}
|
|
9625
|
+
async function listSupportLog(filter) {
|
|
9626
|
+
let query = getSupabaseClient().from("support_access_log").select("*").order("created_at", { ascending: false }).limit(Math.min(filter.limit ?? 200, 1e3));
|
|
9627
|
+
if (filter.workspaceId) query = query.eq("workspace_id", filter.workspaceId);
|
|
9628
|
+
const { data, error: error2 } = await query;
|
|
9629
|
+
if (error2) throw new Error(`listSupportLog: ${error2.message}`);
|
|
9630
|
+
return (data ?? []).map((r) => ({
|
|
9631
|
+
id: r.id,
|
|
9632
|
+
workspaceId: r.workspace_id,
|
|
9633
|
+
staffUserId: r.staff_user_id,
|
|
9634
|
+
staffEmail: r.staff_email,
|
|
9635
|
+
action: r.action,
|
|
9636
|
+
detail: r.detail ?? null,
|
|
9637
|
+
createdAt: r.created_at
|
|
9638
|
+
}));
|
|
9639
|
+
}
|
|
9640
|
+
|
|
9547
9641
|
// ../api/src/dashboard/auth-middleware.ts
|
|
9642
|
+
var ENTER_WINDOW_MS = 8 * 60 * 60 * 1e3;
|
|
9643
|
+
var lastEnter = /* @__PURE__ */ new Map();
|
|
9644
|
+
function auditSupport(ctx, action, detail) {
|
|
9645
|
+
logSupportAction({
|
|
9646
|
+
workspaceId: ctx.workspaceId,
|
|
9647
|
+
staffUserId: ctx.userId,
|
|
9648
|
+
staffEmail: ctx.email,
|
|
9649
|
+
action,
|
|
9650
|
+
detail
|
|
9651
|
+
}).catch((err) => console.error("[support-audit]", err.message));
|
|
9652
|
+
}
|
|
9548
9653
|
var requireWorkspaceMembership = async (c, next) => {
|
|
9549
9654
|
const ctx = await resolveAuthContext(c);
|
|
9550
9655
|
if (!ctx) return c.json({ error: "not signed in" }, 401);
|
|
9551
9656
|
c.set("auth", ctx);
|
|
9552
|
-
return next();
|
|
9657
|
+
if (!ctx.isSupport) return next();
|
|
9658
|
+
const key = `${ctx.userId}:${ctx.workspaceId}`;
|
|
9659
|
+
const now = Date.now();
|
|
9660
|
+
if ((lastEnter.get(key) ?? 0) + ENTER_WINDOW_MS < now) {
|
|
9661
|
+
lastEnter.set(key, now);
|
|
9662
|
+
auditSupport(ctx, "enter");
|
|
9663
|
+
}
|
|
9664
|
+
await next();
|
|
9665
|
+
if (!["GET", "HEAD", "OPTIONS"].includes(c.req.method)) {
|
|
9666
|
+
auditSupport(ctx, "write", { method: c.req.method, path: c.req.path, status: c.res.status });
|
|
9667
|
+
}
|
|
9553
9668
|
};
|
|
9554
9669
|
|
|
9555
9670
|
// ../api/src/dashboard/billing-routes.ts
|
|
@@ -9654,7 +9769,14 @@ function createAdminRouter() {
|
|
|
9654
9769
|
const admin = new Hono11();
|
|
9655
9770
|
admin.use("*", requirePlatformOperator);
|
|
9656
9771
|
admin.get("/workspaces", async (c) => {
|
|
9657
|
-
const
|
|
9772
|
+
const operator = c.get("platformOperator");
|
|
9773
|
+
const [summaries, supportIds, users] = await Promise.all([
|
|
9774
|
+
listWorkspaceSummaries(),
|
|
9775
|
+
listSupportWorkspaceIds(operator.userId),
|
|
9776
|
+
// One call for every owner's profile metadata instead of one per row.
|
|
9777
|
+
getSupabaseClient().auth.admin.listUsers({ perPage: 1e3 })
|
|
9778
|
+
]);
|
|
9779
|
+
const usersById = new Map((users.data?.users ?? []).map((u) => [u.id, u]));
|
|
9658
9780
|
const workspaces = await Promise.all(
|
|
9659
9781
|
summaries.map(async (w) => {
|
|
9660
9782
|
const agentModel = cachedProfileConfig(w.slug)?.agentModel;
|
|
@@ -9664,11 +9786,23 @@ function createAdminRouter() {
|
|
|
9664
9786
|
console.error(`[admin] R2 usage for ${w.slug}:`, err.message);
|
|
9665
9787
|
return null;
|
|
9666
9788
|
});
|
|
9789
|
+
const metadata = w.owner ? usersById.get(w.owner.userId)?.user_metadata : void 0;
|
|
9790
|
+
const owner = w.owner && {
|
|
9791
|
+
...w.owner,
|
|
9792
|
+
role: "admin",
|
|
9793
|
+
avatarUrl: metadata?.avatarKey ? await signAvatarUrl(metadata.avatarKey) : null,
|
|
9794
|
+
name: metadata?.name ?? null,
|
|
9795
|
+
title: metadata?.title ?? null,
|
|
9796
|
+
bio: metadata?.bio ?? null,
|
|
9797
|
+
timeZone: metadata?.timeZone ?? null
|
|
9798
|
+
};
|
|
9667
9799
|
return {
|
|
9668
9800
|
...w,
|
|
9801
|
+
owner,
|
|
9669
9802
|
agent: agentModel ? { runner: runnerForModel(agentModel), model: agentModel } : null,
|
|
9670
9803
|
r2Bytes,
|
|
9671
|
-
flyVolumeBytes: null
|
|
9804
|
+
flyVolumeBytes: null,
|
|
9805
|
+
supportAccess: supportIds.has(w.id)
|
|
9672
9806
|
};
|
|
9673
9807
|
})
|
|
9674
9808
|
);
|
|
@@ -9756,6 +9890,63 @@ function createAdminRouter() {
|
|
|
9756
9890
|
}
|
|
9757
9891
|
return c.json({ sent: true, inviteLink: inviteUrl.toString() });
|
|
9758
9892
|
});
|
|
9893
|
+
admin.post("/workspaces/:id/support-access", async (c) => {
|
|
9894
|
+
const operator = c.get("platformOperator");
|
|
9895
|
+
const workspaceId = c.req.param("id");
|
|
9896
|
+
let result;
|
|
9897
|
+
try {
|
|
9898
|
+
result = await grantSupportMembership({
|
|
9899
|
+
workspaceId,
|
|
9900
|
+
userId: operator.userId,
|
|
9901
|
+
email: operator.email
|
|
9902
|
+
});
|
|
9903
|
+
} catch (err) {
|
|
9904
|
+
return c.json({ error: err instanceof Error ? err.message : "grant failed" }, 409);
|
|
9905
|
+
}
|
|
9906
|
+
if (result === "granted") {
|
|
9907
|
+
await logSupportAction({
|
|
9908
|
+
workspaceId,
|
|
9909
|
+
staffUserId: operator.userId,
|
|
9910
|
+
staffEmail: operator.email,
|
|
9911
|
+
action: "grant"
|
|
9912
|
+
});
|
|
9913
|
+
}
|
|
9914
|
+
invalidateAuthContextCache();
|
|
9915
|
+
return c.json({ supportAccess: true });
|
|
9916
|
+
});
|
|
9917
|
+
admin.delete("/workspaces/:id/support-access", async (c) => {
|
|
9918
|
+
const operator = c.get("platformOperator");
|
|
9919
|
+
const workspaceId = c.req.param("id");
|
|
9920
|
+
if (await revokeSupportMembership(workspaceId, operator.userId)) {
|
|
9921
|
+
await logSupportAction({
|
|
9922
|
+
workspaceId,
|
|
9923
|
+
staffUserId: operator.userId,
|
|
9924
|
+
staffEmail: operator.email,
|
|
9925
|
+
action: "leave"
|
|
9926
|
+
});
|
|
9927
|
+
}
|
|
9928
|
+
invalidateAuthContextCache();
|
|
9929
|
+
return c.json({ supportAccess: false });
|
|
9930
|
+
});
|
|
9931
|
+
const archiveRoute = (archived) => async (c) => {
|
|
9932
|
+
try {
|
|
9933
|
+
await setWorkspaceArchived(c.req.param("id"), archived);
|
|
9934
|
+
} catch (err) {
|
|
9935
|
+
const message = err instanceof Error ? err.message : "archive failed";
|
|
9936
|
+
return c.json({ error: message }, message === "workspace not found" ? 404 : 500);
|
|
9937
|
+
}
|
|
9938
|
+
invalidateAuthContextCache();
|
|
9939
|
+
return c.json({ archived });
|
|
9940
|
+
};
|
|
9941
|
+
admin.post("/workspaces/:id/archive", archiveRoute(true));
|
|
9942
|
+
admin.delete("/workspaces/:id/archive", archiveRoute(false));
|
|
9943
|
+
admin.get("/support-log", async (c) => {
|
|
9944
|
+
const entries2 = await listSupportLog({
|
|
9945
|
+
workspaceId: c.req.query("workspace") || void 0,
|
|
9946
|
+
limit: Number(c.req.query("limit")) || void 0
|
|
9947
|
+
});
|
|
9948
|
+
return c.json({ entries: entries2 });
|
|
9949
|
+
});
|
|
9759
9950
|
return admin;
|
|
9760
9951
|
}
|
|
9761
9952
|
|
|
@@ -9765,7 +9956,7 @@ var BOT_DM_KEY = "claude";
|
|
|
9765
9956
|
|
|
9766
9957
|
// ../api/src/dashboard/data-api.ts
|
|
9767
9958
|
import { existsSync as existsSync20 } from "fs";
|
|
9768
|
-
import
|
|
9959
|
+
import sharp6 from "sharp";
|
|
9769
9960
|
|
|
9770
9961
|
// ../api/src/dashboard/automations-routes.ts
|
|
9771
9962
|
import { Hono as Hono12 } from "hono";
|
|
@@ -10259,6 +10450,25 @@ var BOARD_STATUSES = [
|
|
|
10259
10450
|
"cancelled"
|
|
10260
10451
|
];
|
|
10261
10452
|
var NO_STATUS_COLUMN = "none";
|
|
10453
|
+
function isBoardStatus(v) {
|
|
10454
|
+
return typeof v === "string" && BOARD_STATUSES.includes(v);
|
|
10455
|
+
}
|
|
10456
|
+
async function callerMembershipId(c) {
|
|
10457
|
+
const viewer = c.get("auth");
|
|
10458
|
+
if (!viewer) return null;
|
|
10459
|
+
return (await getMembershipByUserId(viewer.workspaceId, viewer.userId))?.id ?? null;
|
|
10460
|
+
}
|
|
10461
|
+
async function rememberComposerStatus(c, channelId, status) {
|
|
10462
|
+
const membershipId = await callerMembershipId(c);
|
|
10463
|
+
if (!membershipId) return;
|
|
10464
|
+
const { error: error2 } = await getSupabaseClient().from("channel_member_prefs").upsert({
|
|
10465
|
+
channel_id: channelId,
|
|
10466
|
+
membership_id: membershipId,
|
|
10467
|
+
last_board_status: status,
|
|
10468
|
+
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
10469
|
+
});
|
|
10470
|
+
if (error2) throw new Error(error2.message);
|
|
10471
|
+
}
|
|
10262
10472
|
function boardColumnParam(status) {
|
|
10263
10473
|
if (status === NO_STATUS_COLUMN) return NO_STATUS_COLUMN;
|
|
10264
10474
|
return BOARD_STATUSES.includes(status) ? status : null;
|
|
@@ -10324,7 +10534,9 @@ async function withPreviews(supabase, threads) {
|
|
|
10324
10534
|
for (const { data } of chunkResults) {
|
|
10325
10535
|
for (const r of data ?? []) {
|
|
10326
10536
|
const entry = previewByThread.get(r.thread_id);
|
|
10537
|
+
const images = r.body.includes("![") ? markdownImageUrls(slackMrkdwnToMarkdown(r.body)) : [];
|
|
10327
10538
|
if (entry) {
|
|
10539
|
+
entry.images.push(...images);
|
|
10328
10540
|
entry.count += 1;
|
|
10329
10541
|
entry.lastAuthor = r.author;
|
|
10330
10542
|
entry.participants.delete(r.author);
|
|
@@ -10334,7 +10546,8 @@ async function withPreviews(supabase, threads) {
|
|
|
10334
10546
|
count: 1,
|
|
10335
10547
|
firstBody: r.body,
|
|
10336
10548
|
lastAuthor: r.author,
|
|
10337
|
-
participants: /* @__PURE__ */ new Map()
|
|
10549
|
+
participants: /* @__PURE__ */ new Map(),
|
|
10550
|
+
images
|
|
10338
10551
|
});
|
|
10339
10552
|
}
|
|
10340
10553
|
}
|
|
@@ -10349,10 +10562,19 @@ async function withPreviews(supabase, threads) {
|
|
|
10349
10562
|
// read, on this browser or any other (see web/src/lib/thread-unread).
|
|
10350
10563
|
last_reply_author: entry && entry.count > 1 ? entry.lastAuthor : null,
|
|
10351
10564
|
// Everyone who replied, most recent first, for the row's avatar stack.
|
|
10352
|
-
participants: [...entry?.participants ?? []].reverse().map(([author, external_user_id]) => ({ author, external_user_id }))
|
|
10565
|
+
participants: [...entry?.participants ?? []].reverse().map(([author, external_user_id]) => ({ author, external_user_id })),
|
|
10566
|
+
// Every image in the thread, in order, so the row can show a thumbnail
|
|
10567
|
+
// strip; only the first few URLs are sent, the rest are just counted.
|
|
10568
|
+
images: (entry?.images ?? []).slice(0, ROW_IMAGE_LIMIT),
|
|
10569
|
+
image_count: entry?.images.length ?? 0
|
|
10353
10570
|
};
|
|
10354
10571
|
});
|
|
10355
10572
|
}
|
|
10573
|
+
var ROW_IMAGE_LIMIT = 3;
|
|
10574
|
+
var MARKDOWN_IMAGE_RE = /!\[[^\]]*\]\(\s*<?([^\s)>]+)>?(?:\s+"[^"]*")?\s*\)/g;
|
|
10575
|
+
function markdownImageUrls(markdown) {
|
|
10576
|
+
return [...markdown.matchAll(MARKDOWN_IMAGE_RE)].map((m) => m[1]);
|
|
10577
|
+
}
|
|
10356
10578
|
async function labelThread(channelId, body, scope) {
|
|
10357
10579
|
const channel = getProfileChannelRegistry().find(
|
|
10358
10580
|
(ch) => ch.id === channelId || ch.externalChannelId === channelId
|
|
@@ -10762,6 +10984,13 @@ function createChatRouter(agent = {}) {
|
|
|
10762
10984
|
const clientKey2 = isNonEmptyString(input2.clientKey) ? input2.clientKey : null;
|
|
10763
10985
|
const model = requestedModel(c, input2.model);
|
|
10764
10986
|
const effort = requestedEffort(c, input2.effort);
|
|
10987
|
+
const pickedStatus = input2.board_status;
|
|
10988
|
+
if (pickedStatus != null && !isBoardStatus(pickedStatus)) {
|
|
10989
|
+
return c.json(
|
|
10990
|
+
{ error: `board_status must be null or one of: ${BOARD_STATUSES.join(", ")}` },
|
|
10991
|
+
400
|
|
10992
|
+
);
|
|
10993
|
+
}
|
|
10765
10994
|
const supabase = getSupabaseClient();
|
|
10766
10995
|
let thread;
|
|
10767
10996
|
let replayed = false;
|
|
@@ -10772,7 +11001,8 @@ function createChatRouter(agent = {}) {
|
|
|
10772
11001
|
// opening message at display time (see threadCrumbLabel).
|
|
10773
11002
|
title: isNonEmptyString(input2.title) ? input2.title.trim() : null,
|
|
10774
11003
|
author: input2.author,
|
|
10775
|
-
client_key: clientKey2
|
|
11004
|
+
client_key: clientKey2,
|
|
11005
|
+
board_status: pickedStatus ?? null
|
|
10776
11006
|
}).select().single();
|
|
10777
11007
|
if (threadError) {
|
|
10778
11008
|
if (!clientKey2 || threadError.code !== "23505") {
|
|
@@ -10813,6 +11043,11 @@ function createChatRouter(agent = {}) {
|
|
|
10813
11043
|
effort: effortFromRow(reply["effort"])
|
|
10814
11044
|
});
|
|
10815
11045
|
}
|
|
11046
|
+
if (!replayed && pickedStatus !== void 0) {
|
|
11047
|
+
void rememberComposerStatus(c, channelId, pickedStatus).catch(
|
|
11048
|
+
(err) => console.error("Remember composer status failed:", err)
|
|
11049
|
+
);
|
|
11050
|
+
}
|
|
10816
11051
|
if (!replayed && thread["title"] == null) {
|
|
10817
11052
|
void generateAutoTitle(thread.id, channelId, input2.body).catch(
|
|
10818
11053
|
(err) => console.error("Auto title failed:", err)
|
|
@@ -10820,6 +11055,15 @@ function createChatRouter(agent = {}) {
|
|
|
10820
11055
|
}
|
|
10821
11056
|
return c.json({ ...thread, replies: [reply] }, replayed ? 200 : 201);
|
|
10822
11057
|
});
|
|
11058
|
+
app.get("/channels/:channelId/composer-status", async (c) => {
|
|
11059
|
+
const channelId = c.req.param("channelId");
|
|
11060
|
+
if (!ownChannelIds(c).has(channelId)) return c.json({ error: "unknown channel" }, 404);
|
|
11061
|
+
const membershipId = await callerMembershipId(c);
|
|
11062
|
+
if (!membershipId) return c.json({ lastStatus: null });
|
|
11063
|
+
const { data: pref, error: error2 } = await getSupabaseClient().from("channel_member_prefs").select("last_board_status").eq("channel_id", channelId).eq("membership_id", membershipId).maybeSingle();
|
|
11064
|
+
if (error2) return c.json({ error: error2.message }, 500);
|
|
11065
|
+
return c.json({ lastStatus: pref?.last_board_status ?? null });
|
|
11066
|
+
});
|
|
10823
11067
|
app.post("/replies/:replyId/fork", async (c) => {
|
|
10824
11068
|
const replyId = c.req.param("replyId");
|
|
10825
11069
|
const input2 = await c.req.json().catch(() => null);
|
|
@@ -11035,7 +11279,7 @@ function generateShareId() {
|
|
|
11035
11279
|
return randomBytes6(9).toString("base64url");
|
|
11036
11280
|
}
|
|
11037
11281
|
async function publishDocShare(input2, readBundle) {
|
|
11038
|
-
const bundle = await readBundle({ cwd: input2.cwd, sourcePath: input2.sourcePath });
|
|
11282
|
+
const bundle = await readBundle({ channelId: input2.channelId, cwd: input2.cwd, sourcePath: input2.sourcePath });
|
|
11039
11283
|
if (bundle.status !== "ok") return bundle;
|
|
11040
11284
|
const existing = await findDocShareBySource(input2.workspaceId, input2.channelId, input2.sourcePath);
|
|
11041
11285
|
const shareId = existing?.id ?? generateShareId();
|
|
@@ -11695,7 +11939,7 @@ function createTranscribeRouter() {
|
|
|
11695
11939
|
// ../api/src/dashboard/avatars-routes.ts
|
|
11696
11940
|
import { createHash as createHash9, randomUUID as randomUUID4 } from "crypto";
|
|
11697
11941
|
import { Hono as Hono19 } from "hono";
|
|
11698
|
-
import
|
|
11942
|
+
import sharp5 from "sharp";
|
|
11699
11943
|
import { PutObjectCommand as PutObjectCommand4, DeleteObjectCommand as DeleteObjectCommand3 } from "@aws-sdk/client-s3";
|
|
11700
11944
|
var AVATAR_SIZE = 192;
|
|
11701
11945
|
var MAX_AVATAR_BYTES = 2 * 1024 * 1024;
|
|
@@ -11731,7 +11975,7 @@ async function storeAvatar(file, owner) {
|
|
|
11731
11975
|
return { error: "avatar must be a PNG or JPEG image" };
|
|
11732
11976
|
if (file.size > MAX_AVATAR_BYTES) return { error: "avatar must be 2MB or smaller" };
|
|
11733
11977
|
const original = Buffer.from(await file.arrayBuffer());
|
|
11734
|
-
const resized = await
|
|
11978
|
+
const resized = await sharp5(original).resize({ width: AVATAR_SIZE, height: AVATAR_SIZE, fit: "cover" }).png().toBuffer();
|
|
11735
11979
|
const key = `${avatarKeyPrefix()}${owner}-${randomUUID4()}.png`;
|
|
11736
11980
|
await getR2Client().send(
|
|
11737
11981
|
new PutObjectCommand4({
|
|
@@ -11909,10 +12153,6 @@ function createMembersRouter() {
|
|
|
11909
12153
|
if (avatarKey && !avatarKey.startsWith(`${avatarKeyPrefix()}invite-${ctx.userId}-`)) {
|
|
11910
12154
|
return c.json({ error: "unknown avatar" }, 400);
|
|
11911
12155
|
}
|
|
11912
|
-
const { plan } = await getWorkspaceBilling(ctx.workspaceId);
|
|
11913
|
-
if (plan === "local") {
|
|
11914
|
-
return c.json({ error: "The Solo plan is for one person. Upgrade to invite members." }, 403);
|
|
11915
|
-
}
|
|
11916
12156
|
const existing = await getMembershipByEmail(ctx.workspaceId, email);
|
|
11917
12157
|
if (existing) return c.json({ error: "already a member of this workspace" }, 409);
|
|
11918
12158
|
let membership;
|
|
@@ -13425,8 +13665,95 @@ async function buildThreadUsage(threadTs) {
|
|
|
13425
13665
|
}
|
|
13426
13666
|
}
|
|
13427
13667
|
|
|
13428
|
-
// ../api/src/dashboard/channel-
|
|
13668
|
+
// ../api/src/dashboard/channel-greeting.ts
|
|
13429
13669
|
import { Hono as Hono25 } from "hono";
|
|
13670
|
+
function eligible(ch) {
|
|
13671
|
+
return !!ch && !ch.isDefault && !ch.dmKey && !ch.agentName && !ch.archivedAt;
|
|
13672
|
+
}
|
|
13673
|
+
function buildIntroPrompt(ch) {
|
|
13674
|
+
const automations = [
|
|
13675
|
+
...(ch.automations?.schedules ?? []).map((s) => s.name),
|
|
13676
|
+
...(ch.automations?.triggers ?? []).map((t) => t.name)
|
|
13677
|
+
].filter(Boolean);
|
|
13678
|
+
return [
|
|
13679
|
+
`[channel intro request] Write the one-time intro message for the #${ch.name} channel, into this thread. It is the first thing a visitor sees here, so it must be grounded in what this folder actually contains.`,
|
|
13680
|
+
"",
|
|
13681
|
+
"Read-only: inspect the folder (file listing, README.md, AGENTS.md / CLAUDE.md, docs/, git status, .claude/skills and any SKILL.md files), but do not create, modify or delete anything, and do not run anything with side effects.",
|
|
13682
|
+
"",
|
|
13683
|
+
"Language: match how this project is usually worked on, in this order of evidence: (1) the user messages in this folder's recent agent sessions (Claude Code: ~/.claude/projects/<encoded cwd>/*.jsonl; Codex: ~/.codex/sessions/**/*.jsonl with a matching cwd): read only enough to judge the language, never quote them; (2) the main language of README.md / AGENTS.md / docs/; (3) English. For Chinese use Traditional Chinese (Taiwan), full-width punctuation.",
|
|
13684
|
+
"",
|
|
13685
|
+
"Shape (pyramid, at most ~80 Chinese characters / ~60 English words in total):",
|
|
13686
|
+
"1. One sentence: what this channel/project is, and what you can best help with here.",
|
|
13687
|
+
`2. Three one-line bullets: what already exists in the folder (repo, docs); 1-2 skills that are really available and how to invoke them; one concrete automation suggestion with frequency and content (e.g. "every Monday 9am, summarise last week's commits here").`,
|
|
13688
|
+
"3. End with exactly one line `[BUTTONS: a | b | c]` whose three buttons correspond to the three bullets and read as directly executable requests.",
|
|
13689
|
+
"",
|
|
13690
|
+
"Rules: never invent skills or automations that are not actually there: skip that bullet instead. " + (automations.length ? `This channel already has automations (${automations.join(", ")}): introduce the existing one(s) instead of suggesting a new one. ` : "") + "Do not explain Flyspace itself. If the folder is empty, instead ask what this channel will be used for and offer `[BUTTONS: Help me set up this channel]`. Output only the message itself, nothing else."
|
|
13691
|
+
].join("\n");
|
|
13692
|
+
}
|
|
13693
|
+
var FALLBACK_BODY = "I could not write this channel's intro automatically. Ask me below and I'll take a look at the folder.\n\n[BUTTONS: Introduce this channel]";
|
|
13694
|
+
function introOpening(channelName) {
|
|
13695
|
+
return `Welcome to #${channelName}. I'm reading this project's folder, docs, skills and automations to work out how I can help here. Below you'll find a short intro and a few first steps you can click to try.`;
|
|
13696
|
+
}
|
|
13697
|
+
function createChannelGreetingRouter(agent) {
|
|
13698
|
+
const app = new Hono25();
|
|
13699
|
+
app.get("/:id/greeting", async (c) => {
|
|
13700
|
+
const ctx = await resolveAuthContext(c);
|
|
13701
|
+
const ch = findChannelById(c.req.param("id"));
|
|
13702
|
+
const pending2 = isWorkspaceOwner(ctx) && eligible(ch) && !ch.greetedAt && (agent.isHostOnline?.(ch.profile) ?? false);
|
|
13703
|
+
if (!pending2) return c.json({ pending: false, threadId: null });
|
|
13704
|
+
const threadId = await ensureIntroThread(ch);
|
|
13705
|
+
return c.json({ pending: !!threadId, threadId });
|
|
13706
|
+
});
|
|
13707
|
+
app.post("/:id/greeting", async (c) => {
|
|
13708
|
+
const ctx = await resolveAuthContext(c);
|
|
13709
|
+
if (!isWorkspaceOwner(ctx)) return c.json({ error: "workspace owner access required" }, 403);
|
|
13710
|
+
const ch = findChannelById(c.req.param("id"));
|
|
13711
|
+
if (!eligible(ch)) return c.json({ error: "channel has no intro" }, 404);
|
|
13712
|
+
const threadId = await ensureIntroThread(ch);
|
|
13713
|
+
if (!threadId) return c.json({ error: "could not create the intro thread" }, 500);
|
|
13714
|
+
if (!await markChannelGreeted(ch.id)) return c.json({ threadId, error: "already greeted" }, 409);
|
|
13715
|
+
try {
|
|
13716
|
+
if (!(agent.isHostOnline?.(ch.profile) ?? false)) throw new Error("host offline");
|
|
13717
|
+
await agent.onChatMessage?.({
|
|
13718
|
+
threadId,
|
|
13719
|
+
// Synthetic: no thread_replies row has this id, so the prompt is
|
|
13720
|
+
// never shown and never counted as thread context.
|
|
13721
|
+
replyId: crypto.randomUUID(),
|
|
13722
|
+
author: ctx.handle,
|
|
13723
|
+
body: buildIntroPrompt(ch)
|
|
13724
|
+
});
|
|
13725
|
+
} catch (err) {
|
|
13726
|
+
console.error(`[channel-greeting] dispatch failed for #${ch.name}:`, err);
|
|
13727
|
+
await getSupabaseClient().from("thread_replies").insert({ thread_id: threadId, author: AGENT_AUTHOR, body: FALLBACK_BODY });
|
|
13728
|
+
}
|
|
13729
|
+
return c.json({ threadId }, 201);
|
|
13730
|
+
});
|
|
13731
|
+
return app;
|
|
13732
|
+
}
|
|
13733
|
+
async function ensureIntroThread(ch) {
|
|
13734
|
+
const supabase = getSupabaseClient();
|
|
13735
|
+
const existing = () => supabase.from("threads").select("id").eq("client_key", ch.id).maybeSingle();
|
|
13736
|
+
const found = await existing();
|
|
13737
|
+
if (found.data) return found.data.id;
|
|
13738
|
+
const { data, error: error2 } = await supabase.from("threads").insert({
|
|
13739
|
+
channel_id: ch.id,
|
|
13740
|
+
title: `Getting started in #${ch.name}`,
|
|
13741
|
+
author: AGENT_AUTHOR,
|
|
13742
|
+
client_key: ch.id
|
|
13743
|
+
}).select("id").single();
|
|
13744
|
+
if (data) {
|
|
13745
|
+
await supabase.from("thread_replies").insert({
|
|
13746
|
+
thread_id: data.id,
|
|
13747
|
+
author: AGENT_AUTHOR,
|
|
13748
|
+
body: introOpening(ch.name)
|
|
13749
|
+
});
|
|
13750
|
+
return data.id;
|
|
13751
|
+
}
|
|
13752
|
+
return error2?.code === "23505" ? (await existing()).data?.id ?? null : null;
|
|
13753
|
+
}
|
|
13754
|
+
|
|
13755
|
+
// ../api/src/dashboard/channel-members-routes.ts
|
|
13756
|
+
import { Hono as Hono26 } from "hono";
|
|
13430
13757
|
async function isChannelAdmin(ctx, channelId) {
|
|
13431
13758
|
if (ctx.role === "admin") return true;
|
|
13432
13759
|
const membership = await getMembershipByUserId(ctx.workspaceId, ctx.userId);
|
|
@@ -13435,7 +13762,7 @@ async function isChannelAdmin(ctx, channelId) {
|
|
|
13435
13762
|
return members.some((m) => m.membershipId === membership.id && m.role === "channel_admin");
|
|
13436
13763
|
}
|
|
13437
13764
|
function createChannelMembersRouter() {
|
|
13438
|
-
const router = new
|
|
13765
|
+
const router = new Hono26();
|
|
13439
13766
|
router.get("/:id/members", async (c) => {
|
|
13440
13767
|
const ctx = await resolveAuthContext(c);
|
|
13441
13768
|
if (!ctx) return c.json({ error: "workspace access required" }, 403);
|
|
@@ -13505,7 +13832,7 @@ function createChannelMembersRouter() {
|
|
|
13505
13832
|
}
|
|
13506
13833
|
|
|
13507
13834
|
// ../api/src/dashboard/memories-routes.ts
|
|
13508
|
-
import { Hono as
|
|
13835
|
+
import { Hono as Hono27 } from "hono";
|
|
13509
13836
|
async function readPatch(c) {
|
|
13510
13837
|
const body = await c.req.json().catch(() => ({}));
|
|
13511
13838
|
const patch = {};
|
|
@@ -13533,7 +13860,7 @@ var requireAgentMemory = async (c, next) => {
|
|
|
13533
13860
|
await next();
|
|
13534
13861
|
};
|
|
13535
13862
|
function createMemoriesRouter() {
|
|
13536
|
-
const router = new
|
|
13863
|
+
const router = new Hono27();
|
|
13537
13864
|
for (const path of [
|
|
13538
13865
|
"/:id/memories",
|
|
13539
13866
|
"/:id/memories/*",
|
|
@@ -13606,7 +13933,7 @@ function createMemoriesRouter() {
|
|
|
13606
13933
|
return router;
|
|
13607
13934
|
}
|
|
13608
13935
|
function createScopedMemoriesRouter() {
|
|
13609
|
-
const router = new
|
|
13936
|
+
const router = new Hono27();
|
|
13610
13937
|
router.use("*", requireAgentMemory);
|
|
13611
13938
|
const workspace = (c) => ({
|
|
13612
13939
|
scope: "workspace",
|
|
@@ -13837,6 +14164,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
13837
14164
|
});
|
|
13838
14165
|
api.route("/channels", createDocShareWriteRouter(agent));
|
|
13839
14166
|
api.route("/onboarding", createOnboardingRouter(agent));
|
|
14167
|
+
api.route("/channels", createChannelGreetingRouter(agent));
|
|
13840
14168
|
api.route("/channels", createChannelMembersRouter());
|
|
13841
14169
|
api.route("/channels", createMemoriesRouter());
|
|
13842
14170
|
api.route("/memories", createScopedMemoriesRouter());
|
|
@@ -14080,7 +14408,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
14080
14408
|
return c.json({ error: "logo must be 2MB or smaller" }, 400);
|
|
14081
14409
|
}
|
|
14082
14410
|
const original = Buffer.from(await file.arrayBuffer());
|
|
14083
|
-
const resized = await
|
|
14411
|
+
const resized = await sharp6(original).resize({ width: LOGO_SIZE, height: LOGO_SIZE, fit: "cover" }).png().toBuffer();
|
|
14084
14412
|
const logo = `data:image/png;base64,${resized.toString("base64")}`;
|
|
14085
14413
|
return c.json(await saveWorkspaceBranding({ logo }));
|
|
14086
14414
|
});
|
|
@@ -14090,7 +14418,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
14090
14418
|
// ../api/src/dashboard/device-auth-routes.ts
|
|
14091
14419
|
import { verify } from "crypto";
|
|
14092
14420
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
14093
|
-
import { Hono as
|
|
14421
|
+
import { Hono as Hono28 } from "hono";
|
|
14094
14422
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
14095
14423
|
function cleanText(value, max) {
|
|
14096
14424
|
if (typeof value !== "string") return null;
|
|
@@ -14138,7 +14466,7 @@ async function boundWorkspaceIds(publicKey, userId) {
|
|
|
14138
14466
|
return new Set(existing ? (await listDeviceWorkspaces(existing)).map((w) => w.id) : []);
|
|
14139
14467
|
}
|
|
14140
14468
|
function createDeviceAuthRouter() {
|
|
14141
|
-
const app = new
|
|
14469
|
+
const app = new Hono28();
|
|
14142
14470
|
app.post("/code", async (c) => {
|
|
14143
14471
|
const body = await readBody(c);
|
|
14144
14472
|
const publicKey = typeof body["public_key"] === "string" ? body["public_key"] : "";
|
|
@@ -14457,7 +14785,7 @@ var workspaceRef = () => ({ kind: "workspace" });
|
|
|
14457
14785
|
var channelRef = (c) => ({ kind: "channel", channelId: c.req.param("id") });
|
|
14458
14786
|
|
|
14459
14787
|
// ../api/src/dashboard/output-styles-routes.ts
|
|
14460
|
-
import { Hono as
|
|
14788
|
+
import { Hono as Hono29 } from "hono";
|
|
14461
14789
|
function requireAdmin3(auth) {
|
|
14462
14790
|
return auth?.role === "admin" ? null : "admin access required";
|
|
14463
14791
|
}
|
|
@@ -14477,7 +14805,7 @@ function failure(c, err) {
|
|
|
14477
14805
|
return c.json({ error: message }, err instanceof OutputStyleConflictError ? 409 : 500);
|
|
14478
14806
|
}
|
|
14479
14807
|
function createOutputStylesRouter() {
|
|
14480
|
-
const app = new
|
|
14808
|
+
const app = new Hono29();
|
|
14481
14809
|
app.get("/", (c) => {
|
|
14482
14810
|
const auth = c.get("auth");
|
|
14483
14811
|
const denied = requireAdmin3(auth);
|
|
@@ -14569,7 +14897,7 @@ function createOutputStylesRouter() {
|
|
|
14569
14897
|
return app;
|
|
14570
14898
|
}
|
|
14571
14899
|
async function handleOutputStylesRequest(req) {
|
|
14572
|
-
const app = new
|
|
14900
|
+
const app = new Hono29();
|
|
14573
14901
|
app.use("*", async (c, next) => {
|
|
14574
14902
|
c.set("auth", { role: "admin" });
|
|
14575
14903
|
await next();
|
|
@@ -14590,7 +14918,7 @@ import { streamSSE } from "hono/streaming";
|
|
|
14590
14918
|
|
|
14591
14919
|
// ../api/src/dashboard/api.ts
|
|
14592
14920
|
function createGatedRouter(agent, register) {
|
|
14593
|
-
const api = new
|
|
14921
|
+
const api = new Hono30();
|
|
14594
14922
|
api.use("*", async (c, next) => {
|
|
14595
14923
|
const known = await listKnownWorkspaces();
|
|
14596
14924
|
const fromHost = profileFromHost(trustedHost(c), known);
|
|
@@ -14639,11 +14967,11 @@ function createGatedRouter(agent, register) {
|
|
|
14639
14967
|
|
|
14640
14968
|
// src/dashboard/host-api.ts
|
|
14641
14969
|
import { existsSync as existsSync30, readFileSync as readFileSync24, statSync as statSync8 } from "fs";
|
|
14642
|
-
import
|
|
14970
|
+
import sharp7 from "sharp";
|
|
14643
14971
|
|
|
14644
14972
|
// src/dashboard/channels-routes.ts
|
|
14645
14973
|
import { existsSync as existsSync22, mkdirSync as mkdirSync10 } from "fs";
|
|
14646
|
-
import { Hono as
|
|
14974
|
+
import { Hono as Hono31 } from "hono";
|
|
14647
14975
|
function coerceAccess(raw) {
|
|
14648
14976
|
if (raw === "open" || raw === "*" || raw === "all") return raw;
|
|
14649
14977
|
if (Array.isArray(raw) && raw.every((v) => typeof v === "string")) return raw;
|
|
@@ -14706,7 +15034,7 @@ function toResponse3(channel) {
|
|
|
14706
15034
|
};
|
|
14707
15035
|
}
|
|
14708
15036
|
function createChannelWriteRouter() {
|
|
14709
|
-
const app = new
|
|
15037
|
+
const app = new Hono31();
|
|
14710
15038
|
app.post("/", async (c) => {
|
|
14711
15039
|
const body = await c.req.json().catch(() => ({}));
|
|
14712
15040
|
const name = typeof body["name"] === "string" ? body["name"].trim() : "";
|
|
@@ -14846,7 +15174,7 @@ import {
|
|
|
14846
15174
|
} from "fs";
|
|
14847
15175
|
import { Readable } from "stream";
|
|
14848
15176
|
import { basename as basename8, dirname as dirname8, extname, join as join23, relative, resolve as resolve5, sep } from "path";
|
|
14849
|
-
import { Hono as
|
|
15177
|
+
import { Hono as Hono32 } from "hono";
|
|
14850
15178
|
import { streamSSE as streamSSE2 } from "hono/streaming";
|
|
14851
15179
|
|
|
14852
15180
|
// src/dashboard/docs-watcher.ts
|
|
@@ -17727,7 +18055,7 @@ function deleteDocEntry(root, path) {
|
|
|
17727
18055
|
};
|
|
17728
18056
|
}
|
|
17729
18057
|
function createDocsRouter({ gate = requireDocsAccess } = {}) {
|
|
17730
|
-
const app = new
|
|
18058
|
+
const app = new Hono32();
|
|
17731
18059
|
app.use("/:id/docs", gate);
|
|
17732
18060
|
app.use("/:id/docs/*", gate);
|
|
17733
18061
|
app.get("/:id/docs", (c) => {
|
|
@@ -18065,7 +18393,7 @@ function withDocLinkBridge(html) {
|
|
|
18065
18393
|
// src/dashboard/skills-routes-host.ts
|
|
18066
18394
|
import { existsSync as existsSync27, statSync as statSync7 } from "fs";
|
|
18067
18395
|
import { dirname as dirname9 } from "path";
|
|
18068
|
-
import { Hono as
|
|
18396
|
+
import { Hono as Hono33 } from "hono";
|
|
18069
18397
|
function workspaceScope() {
|
|
18070
18398
|
const claudeConfigDir = profileClaudeConfigDir();
|
|
18071
18399
|
const cwd = defaultClaudeCwd();
|
|
@@ -18171,12 +18499,12 @@ var diskBackend = {
|
|
|
18171
18499
|
write: writeSkillAndSync
|
|
18172
18500
|
};
|
|
18173
18501
|
function createWorkspaceSkillsRouter() {
|
|
18174
|
-
const app = new
|
|
18502
|
+
const app = new Hono33();
|
|
18175
18503
|
mountSkillRoutes(app, "/skills", workspaceRef, diskBackend);
|
|
18176
18504
|
return app;
|
|
18177
18505
|
}
|
|
18178
18506
|
function createChannelSkillsRouter() {
|
|
18179
|
-
const app = new
|
|
18507
|
+
const app = new Hono33();
|
|
18180
18508
|
mountSkillRoutes(app, "/:id/skill-files", channelRef, diskBackend);
|
|
18181
18509
|
return app;
|
|
18182
18510
|
}
|
|
@@ -18311,7 +18639,7 @@ import { randomUUID as randomUUID5 } from "crypto";
|
|
|
18311
18639
|
import { chmodSync, existsSync as existsSync29, mkdirSync as mkdirSync12, readFileSync as readFileSync23, writeFileSync as writeFileSync12 } from "fs";
|
|
18312
18640
|
import { createRequire } from "module";
|
|
18313
18641
|
import { dirname as dirname10, join as join25 } from "path";
|
|
18314
|
-
import { Hono as
|
|
18642
|
+
import { Hono as Hono34 } from "hono";
|
|
18315
18643
|
var SESSION_TTL_MS = 10 * 6e4;
|
|
18316
18644
|
var URL_WAIT_MS = 3e4;
|
|
18317
18645
|
var TOKEN_WAIT_MS = 6e4;
|
|
@@ -18468,7 +18796,7 @@ function failure2(c, err) {
|
|
|
18468
18796
|
return c.json({ error: err.message }, 500);
|
|
18469
18797
|
}
|
|
18470
18798
|
function createProviderLoginRouter() {
|
|
18471
|
-
const app = new
|
|
18799
|
+
const app = new Hono34();
|
|
18472
18800
|
app.use("*", async (c, next) => {
|
|
18473
18801
|
const denied = requireAdmin4(c.get("auth"));
|
|
18474
18802
|
if (denied) return c.json({ error: denied }, 403);
|
|
@@ -18512,7 +18840,7 @@ function createProviderLoginRouter() {
|
|
|
18512
18840
|
return app;
|
|
18513
18841
|
}
|
|
18514
18842
|
async function handleProviderLoginRequest(req) {
|
|
18515
|
-
const app = new
|
|
18843
|
+
const app = new Hono34();
|
|
18516
18844
|
app.use("*", async (c, next) => {
|
|
18517
18845
|
c.set("auth", { role: "admin" });
|
|
18518
18846
|
await next();
|
|
@@ -18620,7 +18948,7 @@ function registerHostRoutes(api) {
|
|
|
18620
18948
|
return c.json({ error: "avatar must be 2MB or smaller" }, 400);
|
|
18621
18949
|
}
|
|
18622
18950
|
const original = Buffer.from(await file.arrayBuffer());
|
|
18623
|
-
const resized = await
|
|
18951
|
+
const resized = await sharp7(original).resize({ width: LOGO_SIZE, height: LOGO_SIZE, fit: "cover" }).png().toBuffer();
|
|
18624
18952
|
const avatar = `data:image/png;base64,${resized.toString("base64")}`;
|
|
18625
18953
|
const name = c.req.param("name");
|
|
18626
18954
|
if (name === BOT_PROFILE_KEY) return c.json(await saveBotIdentity({ avatar }));
|
|
@@ -18669,13 +18997,13 @@ import dotenv2 from "dotenv";
|
|
|
18669
18997
|
import { WebSocket as WebSocket2 } from "ws";
|
|
18670
18998
|
|
|
18671
18999
|
// src/dashboard/docs-relay-host.ts
|
|
18672
|
-
import { Hono as
|
|
19000
|
+
import { Hono as Hono35 } from "hono";
|
|
18673
19001
|
var RETURNED_HEADERS = ["content-type", "content-disposition", "content-security-policy", "cache-control"];
|
|
18674
19002
|
async function handleDocsRequest(req) {
|
|
18675
19003
|
return runRelayed(req, (app) => app.route("/channels", createDocsRouter({ gate: async (_c, next) => next() })));
|
|
18676
19004
|
}
|
|
18677
19005
|
async function runRelayed(req, register) {
|
|
18678
|
-
const app = new
|
|
19006
|
+
const app = new Hono35();
|
|
18679
19007
|
app.use("*", async (c, next) => {
|
|
18680
19008
|
c.set("auth", req.auth);
|
|
18681
19009
|
await next();
|
|
@@ -18976,9 +19304,12 @@ function localAgentCallbacks(chatAgent) {
|
|
|
18976
19304
|
listMissingCwds: () => auditChannelCwds().map((ch) => ch.id),
|
|
18977
19305
|
listRunningProfiles: () => listProfiles().filter(isServiceRunning2),
|
|
18978
19306
|
getAgentIdentities: () => resolveAgentIdentities(),
|
|
18979
|
-
readDocShareBundle: (req) => readDocShareBundle(req),
|
|
18980
|
-
listChannelSkills: (args) =>
|
|
18981
|
-
|
|
19307
|
+
readDocShareBundle: (req) => readDocShareBundle(req.channelId ? { ...req, cwd: resolveChannelConfig(req.channelId).cwd } : req),
|
|
19308
|
+
listChannelSkills: (args) => {
|
|
19309
|
+
const { cwd, claudeConfigDir } = args.channelId ? resolveChannelConfig(args.channelId) : args;
|
|
19310
|
+
return listSkills(claudeConfigDir, cwd);
|
|
19311
|
+
},
|
|
19312
|
+
listFlatDocs: (args) => flatDocs(args.channelId ? resolveChannelConfig(args.channelId).cwd : args.cwd, { query: args.query, sort: args.sort, showHidden: args.showHidden }),
|
|
18982
19313
|
getLocalDashboardSettings: () => loadDashboardSettings(),
|
|
18983
19314
|
saveLocalDashboardSettings: (patch) => saveDashboardSettings(patch),
|
|
18984
19315
|
writeSkill: (ref, write) => writeSkillAndSync(ref, write),
|
|
@@ -19253,7 +19584,7 @@ import { join as join27 } from "path";
|
|
|
19253
19584
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
19254
19585
|
|
|
19255
19586
|
// src/dashboard/app-icon-routes.ts
|
|
19256
|
-
import { Hono as
|
|
19587
|
+
import { Hono as Hono36 } from "hono";
|
|
19257
19588
|
var NEUTRAL_MANIFEST = {
|
|
19258
19589
|
id: "/",
|
|
19259
19590
|
name: "Flyspace",
|
|
@@ -19291,7 +19622,7 @@ async function withResolvedProfile2(c, next) {
|
|
|
19291
19622
|
return profile ? runWithProfile(profile, () => next()) : next();
|
|
19292
19623
|
}
|
|
19293
19624
|
function createAppIconRouter() {
|
|
19294
|
-
const app = new
|
|
19625
|
+
const app = new Hono36();
|
|
19295
19626
|
app.get("/manifest.json", withResolvedProfile2, async (c) => {
|
|
19296
19627
|
if (!await resolvedProfileHost(c)) return c.json(NEUTRAL_MANIFEST);
|
|
19297
19628
|
const profile = getProfile();
|
|
@@ -19865,7 +20196,7 @@ async function main() {
|
|
|
19865
20196
|
break;
|
|
19866
20197
|
case "-v":
|
|
19867
20198
|
case "--version":
|
|
19868
|
-
printRaw("0.1.
|
|
20199
|
+
printRaw("0.1.33");
|
|
19869
20200
|
break;
|
|
19870
20201
|
case "-h":
|
|
19871
20202
|
case "--help":
|
package/dist/index.js
CHANGED