flyspace-cli 0.1.0 → 0.1.1
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-DCCECKHH.js → chunk-6D2PIFEA.js} +326 -230
- package/dist/cli.js +744 -374
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -4,11 +4,13 @@ import {
|
|
|
4
4
|
AutomationRunStore,
|
|
5
5
|
BOT_PROFILE_KEY,
|
|
6
6
|
BRANDING_DEFAULTS,
|
|
7
|
+
BUILT_IN_OUTPUT_STYLES,
|
|
7
8
|
DEFAULT_CODEX_MODEL,
|
|
8
9
|
DEFAULT_TIMEZONE,
|
|
9
10
|
InvalidTransferError,
|
|
10
11
|
LastAdminError,
|
|
11
12
|
MessageQueue,
|
|
13
|
+
OutputStyleConflictError,
|
|
12
14
|
SKIP_SUBTYPES,
|
|
13
15
|
SLACK_ENV,
|
|
14
16
|
SessionStore,
|
|
@@ -28,6 +30,7 @@ import {
|
|
|
28
30
|
codexAuthAvailable,
|
|
29
31
|
createChannel,
|
|
30
32
|
createMembership,
|
|
33
|
+
createOutputStyle,
|
|
31
34
|
createSkillFile,
|
|
32
35
|
createWorkspace,
|
|
33
36
|
deleteMembership,
|
|
@@ -46,16 +49,14 @@ import {
|
|
|
46
49
|
getChannelRegistry,
|
|
47
50
|
getMembershipByEmail,
|
|
48
51
|
getMembershipByUserId,
|
|
49
|
-
|
|
52
|
+
getOutputStyleDefault,
|
|
50
53
|
getProfileChannelRegistry,
|
|
51
54
|
getWorkspaceBilling,
|
|
52
55
|
getWorkspaceBranding,
|
|
53
56
|
getWorkspaceById,
|
|
54
57
|
getWorkspaceBySlug,
|
|
55
58
|
getWorkspaceByStripeCustomerId,
|
|
56
|
-
getWorkspaceOutputStyleDefault,
|
|
57
59
|
getWorkspacePlanStatus,
|
|
58
|
-
insertOutputStyle,
|
|
59
60
|
installUsageRecording,
|
|
60
61
|
isAllowedModel,
|
|
61
62
|
isCodexModel,
|
|
@@ -104,7 +105,7 @@ import {
|
|
|
104
105
|
resolveChannelId,
|
|
105
106
|
resolveChannelPrompt,
|
|
106
107
|
resolveCwd,
|
|
107
|
-
|
|
108
|
+
resolveSelectedOutputStyle,
|
|
108
109
|
resolveUserId,
|
|
109
110
|
runAutomation,
|
|
110
111
|
runScriptAutomation,
|
|
@@ -116,7 +117,7 @@ import {
|
|
|
116
117
|
saveWorkspacePrompt,
|
|
117
118
|
setAgentPaused,
|
|
118
119
|
setAllowGuestAccess,
|
|
119
|
-
|
|
120
|
+
setOutputStyleDefault,
|
|
120
121
|
setWorkspaceStripeSubscription,
|
|
121
122
|
slackConfigured,
|
|
122
123
|
startAutomationRegistryPolling,
|
|
@@ -135,7 +136,7 @@ import {
|
|
|
135
136
|
warnOnBrokenChannelCwds,
|
|
136
137
|
workspaceSkillsDir,
|
|
137
138
|
writeSkillFile
|
|
138
|
-
} from "./chunk-
|
|
139
|
+
} from "./chunk-6D2PIFEA.js";
|
|
139
140
|
import {
|
|
140
141
|
firstReplyBody,
|
|
141
142
|
getThread,
|
|
@@ -312,10 +313,10 @@ async function runPrompt() {
|
|
|
312
313
|
const editor = process.env["EDITOR"] || "vi";
|
|
313
314
|
console.log(`[flyspace] Opening ${paths.PROMPT_FILE} in ${editor}...`);
|
|
314
315
|
const child = spawn(editor, [paths.PROMPT_FILE], { stdio: "inherit" });
|
|
315
|
-
return new Promise((
|
|
316
|
+
return new Promise((resolve8, reject) => {
|
|
316
317
|
child.on("close", (code) => {
|
|
317
318
|
if (code === 0) {
|
|
318
|
-
|
|
319
|
+
resolve8();
|
|
319
320
|
} else {
|
|
320
321
|
reject(new Error(`Editor exited with code ${code}`));
|
|
321
322
|
}
|
|
@@ -387,10 +388,10 @@ function editConfig() {
|
|
|
387
388
|
const editor = process.env["EDITOR"] || "vi";
|
|
388
389
|
console.log(`[flyspace] Opening ${paths.CONFIG_FILE} in ${editor}...`);
|
|
389
390
|
const child = spawn2(editor, [paths.CONFIG_FILE], { stdio: "inherit" });
|
|
390
|
-
return new Promise((
|
|
391
|
+
return new Promise((resolve8, reject) => {
|
|
391
392
|
child.on("close", (code) => {
|
|
392
393
|
if (code === 0) {
|
|
393
|
-
|
|
394
|
+
resolve8();
|
|
394
395
|
} else {
|
|
395
396
|
reject(new Error(`Editor exited with code ${code}`));
|
|
396
397
|
}
|
|
@@ -851,7 +852,7 @@ Restarting ${installed.length} instance(s)...`);
|
|
|
851
852
|
}
|
|
852
853
|
}
|
|
853
854
|
async function runUpgrade() {
|
|
854
|
-
const current = "0.1.
|
|
855
|
+
const current = "0.1.1";
|
|
855
856
|
console.log(`Current version: ${current}`);
|
|
856
857
|
console.log("Checking for updates...\n");
|
|
857
858
|
try {
|
|
@@ -1490,10 +1491,10 @@ async function runLogs(args) {
|
|
|
1490
1491
|
}
|
|
1491
1492
|
|
|
1492
1493
|
// src/commands/slack.ts
|
|
1493
|
-
import { execFileSync as
|
|
1494
|
-
import { existsSync as
|
|
1495
|
-
import { resolve as
|
|
1496
|
-
import { tmpdir as tmpdir3, homedir as
|
|
1494
|
+
import { execFileSync as execFileSync3 } from "child_process";
|
|
1495
|
+
import { existsSync as existsSync28, readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
|
|
1496
|
+
import { resolve as resolve7 } from "path";
|
|
1497
|
+
import { tmpdir as tmpdir3, homedir as homedir8 } from "os";
|
|
1497
1498
|
import { input, confirm, select, checkbox } from "@inquirer/prompts";
|
|
1498
1499
|
import { WebClient as WebClient3 } from "@slack/web-api";
|
|
1499
1500
|
|
|
@@ -1931,13 +1932,14 @@ function inviteEmail({
|
|
|
1931
1932
|
}
|
|
1932
1933
|
function workspaceSetupEmail({
|
|
1933
1934
|
actionLink,
|
|
1935
|
+
logoUrl,
|
|
1934
1936
|
invitedBy,
|
|
1935
1937
|
message
|
|
1936
1938
|
}) {
|
|
1937
1939
|
const who = invitedBy?.trim();
|
|
1938
1940
|
const note = message?.trim();
|
|
1939
1941
|
return render({
|
|
1940
|
-
brand: { name: "Flyspace" },
|
|
1942
|
+
brand: { name: "Flyspace", ...logoUrl ? { logoUrl } : {} },
|
|
1941
1943
|
subject: who ? `${who} invited you to create a Flyspace workspace` : "Create your Flyspace workspace",
|
|
1942
1944
|
heading: "Create your workspace",
|
|
1943
1945
|
paragraphs: [
|
|
@@ -2178,6 +2180,7 @@ var RESERVED = /* @__PURE__ */ new Set([
|
|
|
2178
2180
|
// Platform hosts and routes (`app`, `www`, `admin` are also host-profile.ts's RESERVED_LABELS).
|
|
2179
2181
|
"app",
|
|
2180
2182
|
"www",
|
|
2183
|
+
"get",
|
|
2181
2184
|
"admin",
|
|
2182
2185
|
"api",
|
|
2183
2186
|
"auth",
|
|
@@ -2532,7 +2535,7 @@ var COPY = {
|
|
|
2532
2535
|
cta: "Continue"
|
|
2533
2536
|
}
|
|
2534
2537
|
};
|
|
2535
|
-
function page(body, font, theme, dark = false, maxWidth = 420) {
|
|
2538
|
+
function page(body, font, theme, dark = false, maxWidth = 420, title = "Confirm sign-in") {
|
|
2536
2539
|
const base = dark ? DARK : LIGHT;
|
|
2537
2540
|
return `<!doctype html>
|
|
2538
2541
|
<html lang="en">
|
|
@@ -2544,7 +2547,10 @@ function page(body, font, theme, dark = false, maxWidth = 420) {
|
|
|
2544
2547
|
<!-- The token sits in this page's URL, so it must not travel to Supabase (or
|
|
2545
2548
|
anywhere else) in a Referer header. -->
|
|
2546
2549
|
<meta name="referrer" content="no-referrer">
|
|
2547
|
-
<title
|
|
2550
|
+
<title>${title}</title>
|
|
2551
|
+
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png">
|
|
2552
|
+
<link rel="icon" type="image/png" sizes="64x64" href="/icons/favicon-64.png">
|
|
2553
|
+
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
|
|
2548
2554
|
${fontLink(font)}
|
|
2549
2555
|
<style>${dark ? "" : darkModeCss(accentOf(theme))}
|
|
2550
2556
|
body { margin:0; }
|
|
@@ -2587,14 +2593,18 @@ async function findInvite(token) {
|
|
|
2587
2593
|
}
|
|
2588
2594
|
var PASSWORD_MIN = 8;
|
|
2589
2595
|
function passwordField(name, label2, autocomplete, display, invalid) {
|
|
2590
|
-
return `<label for="${name}" class="fs-ink" style="display:block;font:600 13px/1.4 ${display};color:${DARK.ink};margin-bottom:6px;">${label2}</label><input id="${name}" name="${name}" type="password" required${name === "password" ? " autofocus" : ""} minlength="${PASSWORD_MIN}" autocomplete="${autocomplete}" style="display:block;box-sizing:border-box;width:100%;
|
|
2596
|
+
return `<label for="${name}" class="fs-ink" style="display:block;font:600 13px/1.4 ${display};color:${DARK.ink};margin-bottom:6px;">${label2}</label><div style="position:relative;margin-bottom:16px;"><input id="${name}" name="${name}" type="password" required${name === "password" ? " autofocus" : ""} minlength="${PASSWORD_MIN}" autocomplete="${autocomplete}" style="display:block;box-sizing:border-box;width:100%;border:1px solid ${invalid ? "#d33" : DARK.line};border-radius:8px;background:transparent;padding:12px 44px 12px 12px;font:500 15px/1.2 ${display};color:inherit;outline:0;"><button type="button" data-toggle-password="${name}" aria-label="Show password" aria-pressed="false" class="fs-dim" style="position:absolute;top:0;right:0;height:100%;width:44px;display:flex;align-items:center;justify-content:center;border:0;background:transparent;color:${DARK.dim};cursor:pointer;"><svg data-eye viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7S2 12 2 12z"/><circle cx="12" cy="12" r="3"/></svg><svg data-eye-off viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="display:none"><path d="M17.94 17.94A10.9 10.9 0 0 1 12 19c-6.5 0-10-7-10-7a18.5 18.5 0 0 1 5.06-5.94M9.9 4.24A10.9 10.9 0 0 1 12 5c6.5 0 10 7 10 7a18.5 18.5 0 0 1-2.16 3.19M1 1l22 22"/><path d="M14.12 14.12a3 3 0 1 1-4.24-4.24"/></svg></button></div>`;
|
|
2591
2597
|
}
|
|
2592
2598
|
var CLAUDE_PATH = "m4.7144 15.9555 4.7174-2.6471.079-.2307-.079-.1275h-.2307l-.7893-.0486-2.6956-.0729-2.3375-.0971-2.2646-.1214-.5707-.1215-.5343-.7042.0546-.3522.4797-.3218.686.0608 1.5179.1032 2.2767.1578 1.6514.0972 2.4468.255h.3886l.0546-.1579-.1336-.0971-.1032-.0972L6.973 9.8356l-2.55-1.6879-1.3356-.9714-.7225-.4918-.3643-.4614-.1578-1.0078.6557-.7225.8803.0607.2246.0607.8925.686 1.9064 1.4754 2.4893 1.8336.3643.3035.1457-.1032.0182-.0728-.164-.2733-1.3539-2.4467-1.445-2.4893-.6435-1.032-.17-.6194c-.0607-.255-.1032-.4674-.1032-.7285L6.287.1335 6.6997 0l.9957.1336.419.3642.6192 1.4147 1.0018 2.2282 1.5543 3.0296.4553.8985.2429.8318.091.255h.1579v-.1457l.1275-1.706.2368-2.0947.2307-2.6957.0789-.7589.3764-.9107.7468-.4918.5828.2793.4797.686-.0668.4433-.2853 1.8517-.5586 2.9021-.3643 1.9429h.2125l.2429-.2429.9835-1.3053 1.6514-2.0643.7286-.8196.85-.9046.5464-.4311h1.0321l.759 1.1293-.34 1.1657-1.0625 1.3478-.8804 1.1414-1.2628 1.7-.7893 1.36.0729.1093.1882-.0183 2.8535-.607 1.5421-.2794 1.8396-.3157.8318.3886.091.3946-.3278.8075-1.967.4857-2.3072.4614-3.4364.8136-.0425.0304.0486.0607 1.5482.1457.6618.0364h1.621l3.0175.2247.7892.522.4736.6376-.079.4857-1.2142.6193-1.6393-.3886-3.825-.9107-1.3113-.3279h-.1822v.1093l1.0929 1.0686 2.0035 1.8092 2.5075 2.3314.1275.5768-.3218.4554-.34-.0486-2.2039-1.6575-.85-.7468-1.9246-1.621h-.1275v.17l.4432.6496 2.3436 3.5214.1214 1.0807-.17.3521-.6071.2125-.6679-.1214-1.3721-1.9246L14.38 17.959l-1.1414-1.9428-.1397.079-.674 7.2552-.3156.3703-.7286.2793-.6071-.4614-.3218-.7468.3218-1.4753.3886-1.9246.3157-1.53.2853-1.9004.17-.6314-.0121-.0425-.1397.0182-1.4328 1.9672-2.1796 2.9446-1.7243 1.8456-.4128.164-.7164-.3704.0667-.6618.4008-.5889 2.386-3.0357 1.4389-1.882.929-1.0868-.0062-.1579h-.0546l-6.3385 4.1164-1.1293.1457-.4857-.4554.0608-.7467.2307-.2429 1.9064-1.3114Z";
|
|
2593
2599
|
var CODEX_PATH = "M8.086.457a6.105 6.105 0 013.046-.415c1.333.153 2.521.72 3.564 1.7a.117.117 0 00.107.029c1.408-.346 2.762-.224 4.061.366l.063.03.154.076c1.357.703 2.33 1.77 2.918 3.198.278.679.418 1.388.421 2.126a5.655 5.655 0 01-.18 1.631.167.167 0 00.04.155 5.982 5.982 0 011.578 2.891c.385 1.901-.01 3.615-1.183 5.14l-.182.22a6.063 6.063 0 01-2.934 1.851.162.162 0 00-.108.102c-.255.736-.511 1.364-.987 1.992-1.199 1.582-2.962 2.462-4.948 2.451-1.583-.008-2.986-.587-4.21-1.736a.145.145 0 00-.14-.032c-.518.167-1.04.191-1.604.185a5.924 5.924 0 01-2.595-.622 6.058 6.058 0 01-2.146-1.781c-.203-.269-.404-.522-.551-.821a7.74 7.74 0 01-.495-1.283 6.11 6.11 0 01-.017-3.064.166.166 0 00.008-.074.115.115 0 00-.037-.064 5.958 5.958 0 01-1.38-2.202 5.196 5.196 0 01-.333-1.589 6.915 6.915 0 01.188-2.132c.45-1.484 1.309-2.648 2.577-3.493.282-.188.55-.334.802-.438.286-.12.573-.22.861-.304a.129.129 0 00.087-.087A6.016 6.016 0 015.635 2.31C6.315 1.464 7.132.846 8.086.457zm-.804 7.85a.848.848 0 00-1.473.842l1.694 2.965-1.688 2.848a.849.849 0 001.46.864l1.94-3.272a.849.849 0 00.007-.854l-1.94-3.393zm5.446 6.24a.849.849 0 000 1.695h4.848a.849.849 0 000-1.696h-4.848z";
|
|
2594
2600
|
function hostDiagram(display, accent) {
|
|
2595
|
-
const box = (y, title) => `<rect x="20" y="${y}" width="280" height="38" rx="8" fill="${DARK.bg}" stroke="${DARK.line}"/><text x="
|
|
2601
|
+
const box = (y, title) => `<rect x="20" y="${y}" width="280" height="38" rx="8" fill="${DARK.bg}" stroke="${DARK.line}"/><text x="160" y="${y + 19}" text-anchor="middle" dominant-baseline="central" fill="${DARK.ink}" font-size="13" font-weight="600">${title}</text>`;
|
|
2596
2602
|
const arrow = (from, to, x = 160) => `<line x1="${x}" y1="${from}" x2="${x}" y2="${to}" stroke="${accent}" stroke-width="1.5" marker-end="url(#fs-arrow)"/>`;
|
|
2597
|
-
const agent = (x, title, path, fill) =>
|
|
2603
|
+
const agent = (x, title, path, fill) => {
|
|
2604
|
+
const unit = 16 + 8 + title.length * 7;
|
|
2605
|
+
const start = x + 67 - unit / 2;
|
|
2606
|
+
return `<rect x="${x}" y="182" width="134" height="38" rx="8" fill="${DARK.bg}" stroke="${DARK.line}"/><svg x="${start}" y="193" width="16" height="16" viewBox="0 0 24 24"><path d="${path}" fill="${fill}" fill-rule="evenodd"/></svg><text x="${start + 24}" y="201" dominant-baseline="central" fill="${DARK.ink}" font-size="13" font-weight="600">${title}</text>`;
|
|
2607
|
+
};
|
|
2598
2608
|
return `<svg viewBox="0 0 320 232" width="100%" role="img" aria-labelledby="fs-host-title" style="display:block;max-width:360px;margin:20px 0 0;font-family:${display};"><title id="fs-host-title">The Flyspace Host on your machine connects to your workspace over a WebSocket and runs Claude Code or Codex CLI.</title><defs><marker id="fs-arrow" viewBox="0 0 10 10" refX="8" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse"><path d="M0 0L10 5L0 10z" fill="${accent}"/></marker></defs>` + box(10, "Your Workspace") + arrow(88, 51) + `<text x="172" y="73" fill="${DARK.dim}" font-size="11">WebSocket</text><rect x="8" y="90" width="304" height="136" rx="10" fill="none" stroke="${DARK.dim}" stroke-dasharray="4 4"/><text x="20" y="108" fill="${DARK.dim}" font-size="10" font-weight="600" letter-spacing="0.06em">YOUR MACHINE</text>` + box(116, "Flyspace Host") + arrow(156, 180, 87) + arrow(156, 180, 233) + agent(20, "Claude Code", CLAUDE_PATH, "#D97757") + agent(166, "Codex CLI", CODEX_PATH, DARK.ink) + `</svg>`;
|
|
2599
2609
|
}
|
|
2600
2610
|
function setupPage(c, token, invite, form = {}) {
|
|
@@ -2603,11 +2613,12 @@ function setupPage(c, token, invite, form = {}) {
|
|
|
2603
2613
|
const host = trustedHost(c) ?? "";
|
|
2604
2614
|
const suffix = `.${host.replace(/:\d+$/, "").split(".").slice(1).join(".")}`;
|
|
2605
2615
|
return page(
|
|
2606
|
-
`<div class="fs-split"><div>` + masthead2("Flyspace", FLYSPACE_MARK, display, true) + `<div class="fs-ink" style="font:600 20px/1.3 ${display};color:${DARK.ink};margin-bottom:10px;">Create your workspace</div><p class="fs-ink" style="margin:0 0 8px;font:500 15px/1.5 ${display};color:${DARK.ink};">Welcome to the early access cohort.</p><p class="fs-muted" style="margin:0;font:400 14px/1.6 ${display};color:${DARK.muted};">Your agents run on your own machine. The Flyspace Host connects out to this workspace, so Claude Code or Codex works in each channel's real folder, with your own tools and keys.</p>` + hostDiagram(display, accent) + `</div><div><form method="post" action="/auth/new-workspace"><input type="hidden" name="token" value="${escapeHtml(token)}"><div class="fs-dim" style="margin:0 0 14px;font:600 11px/1.4 ${display};letter-spacing:0.06em;text-transform:uppercase;color:${DARK.dim};">Your account</div><label for="username" class="fs-muted" style="display:block;font:400 13px/1.4 ${display};color:${DARK.muted};margin-bottom:4px;">You'll be its Owner, signed in as</label><input id="username" name="username" type="email" autocomplete="username" readonly tabindex="-1" value="${escapeHtml(invite.email)}" style="display:block;width:100%;box-sizing:border-box;border:0;outline:0;background:transparent;padding:0;margin-bottom:20px;font:600 15px/1.4 ${display};color:${DARK.ink};text-overflow:ellipsis;"><div class="fs-pair"><div>` + passwordField("password", "Password", "new-password", display, !!form.passwordError) + `</div><div>` + passwordField("confirm", "Confirm password", "new-password", display, !!form.passwordError) + `</div></div>` + (form.passwordError ? `<p style="margin:-8px 0 16px;font:500 13px/1.5 ${display};color:#d33;">${escapeHtml(form.passwordError)}</p>` : `<p class="fs-dim" style="margin:-8px 0 20px;font:400 12px/1.6 ${display};color:${DARK.dim};">At least ${PASSWORD_MIN} characters. You'll use it to sign in to your workspace.</p>`) + `<div style="border-top:1px solid ${DARK.line};margin:4px 0 24px;"></div><div class="fs-dim" style="margin:0 0 14px;font:600 11px/1.4 ${display};letter-spacing:0.06em;text-transform:uppercase;color:${DARK.dim};">Your workspace</div><label for="name" class="fs-ink" style="display:block;font:600 13px/1.4 ${display};color:${DARK.ink};margin-bottom:6px;">Workspace name</label><input id="name" name="name" required autocomplete="organization" maxlength="${DISPLAY_NAME_MAX}" value="${escapeHtml(form.name ?? "")}" placeholder="My Team" class="fs-card" style="display:block;width:100%;box-sizing:border-box;border:1px solid ${DARK.line};border-radius:8px;padding:12px;outline:0;background:transparent;font:500 15px/1.2 ${display};color:inherit;"><p class="fs-dim" style="margin:8px 0 20px;font:400 12px/1.6 ${display};color:${DARK.dim};">Shown across your workspace. You can change it later.</p><label for="slug" class="fs-ink" style="display:block;font:600 13px/1.4 ${display};color:${DARK.ink};margin-bottom:6px;">Workspace URL</label><div class="fs-card" style="display:flex;align-items:center;border:1px solid ${form.error ? "#d33" : DARK.line};border-radius:8px;padding:0 12px;"><input id="slug" name="slug" required autocomplete="off" autocapitalize="none" spellcheck="false" minlength="${SLUG_MIN}" maxlength="${SLUG_MAX}" pattern="[a-z](?:[a-z0-9]|-(?!-))*[a-z0-9]" value="${escapeHtml(form.slug ?? "")}" placeholder="my-team" style="flex:1;min-width:0;border:0;outline:0;background:transparent;padding:12px 0;font:500 15px/1.2 ${display};color:inherit;"><span class="fs-dim" style="font:400 15px/1 ${display};color:${DARK.dim};white-space:nowrap;">${escapeHtml(suffix)}</span></div>` + (form.error ? `<p style="margin:8px 0 0;font:500 13px/1.5 ${display};color:#d33;">${escapeHtml(form.error)}</p>` : "") + `<p class="fs-dim" style="margin:8px 0 20px;font:400 12px/1.6 ${display};color:${DARK.dim};">Lowercase letters, digits and hyphens, ${SLUG_MIN} to ${SLUG_MAX} characters. <strong style="font-weight:600;color:${DARK.ink};">It can't be changed later.</strong></p><button class="fs-btn" type="submit" style="background:${accent};color:${DARK.onAccent};border-radius:8px;padding:13px 24px;width:100%;font:600 15px/1 ${display};">Create workspace</button></form><div style="margin-top:20px;border-left:2px solid ${accent};padding:2px 0 2px 12px;"><p class="fs-muted" style="margin:0;font:400 13px/1.6 ${display};color:${DARK.muted};"><strong class="fs-ink" style="font-weight:600;color:${DARK.ink};">Next:</strong> we'll help you connect Claude Code or Codex CLI.</p></div><p class="fs-dim" style="margin:16px 0 0;font:400 12px/1.6 ${display};color:${DARK.dim};">By creating a workspace, you agree to the <a href="https://flyspace.ai/terms/" target="_blank" rel="noreferrer" style="color:inherit;text-decoration:underline;text-underline-offset:2px;">Terms</a> and <a href="https://flyspace.ai/privacy/" target="_blank" rel="noreferrer" style="color:inherit;text-decoration:underline;text-underline-offset:2px;">Privacy Policy</a>.</p></div></div><script>(()=>{const n=document.getElementById('name'),s=document.getElementById('slug');let touched=!!s.value;s.addEventListener('input',()=>{touched=true});n.addEventListener('input',()=>{if(touched)return;s.value=n.value.normalize('NFKD').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').replace(/^[^a-z]+/,'').slice(0,${SLUG_MAX}).replace(/-+$/,'')})})()</script>`,
|
|
2616
|
+
`<div class="fs-split"><div>` + masthead2("Flyspace", FLYSPACE_MARK, display, true) + `<div class="fs-ink" style="font:600 20px/1.3 ${display};color:${DARK.ink};margin-bottom:10px;">Create your workspace</div><p class="fs-ink" style="margin:0 0 8px;font:500 15px/1.5 ${display};color:${DARK.ink};">Welcome to the early access cohort.</p><p class="fs-muted" style="margin:0;font:400 14px/1.6 ${display};color:${DARK.muted};">Your agents run on your own machine. The Flyspace Host connects out to this workspace, so Claude Code or Codex works in each channel's real folder, with your own tools and keys.</p>` + hostDiagram(display, accent) + `</div><div><form id="setup" method="post" action="/auth/new-workspace"><input type="hidden" name="token" value="${escapeHtml(token)}"><div class="fs-dim" style="margin:0 0 14px;font:600 11px/1.4 ${display};letter-spacing:0.06em;text-transform:uppercase;color:${DARK.dim};">Your account</div><label for="username" class="fs-muted" style="display:block;font:400 13px/1.4 ${display};color:${DARK.muted};margin-bottom:4px;">You'll be its Owner, signed in as</label><input id="username" name="username" type="email" autocomplete="username" readonly tabindex="-1" value="${escapeHtml(invite.email)}" style="display:block;width:100%;box-sizing:border-box;border:0;outline:0;background:transparent;padding:0;margin-bottom:20px;font:600 15px/1.4 ${display};color:${DARK.ink};text-overflow:ellipsis;"><div class="fs-pair"><div>` + passwordField("password", "Password", "new-password", display, !!form.passwordError) + `</div><div>` + passwordField("confirm", "Confirm password", "new-password", display, !!form.passwordError) + `</div></div>` + (form.passwordError ? `<p style="margin:-8px 0 16px;font:500 13px/1.5 ${display};color:#d33;">${escapeHtml(form.passwordError)}</p>` : `<p class="fs-dim" style="margin:-8px 0 20px;font:400 12px/1.6 ${display};color:${DARK.dim};">At least ${PASSWORD_MIN} characters. You'll use it to sign in to your workspace.</p>`) + `<div style="border-top:1px solid ${DARK.line};margin:4px 0 24px;"></div><div class="fs-dim" style="margin:0 0 14px;font:600 11px/1.4 ${display};letter-spacing:0.06em;text-transform:uppercase;color:${DARK.dim};">Your workspace</div><label for="name" class="fs-ink" style="display:block;font:600 13px/1.4 ${display};color:${DARK.ink};margin-bottom:6px;">Workspace name</label><input id="name" name="name" required autocomplete="organization" maxlength="${DISPLAY_NAME_MAX}" value="${escapeHtml(form.name ?? "")}" placeholder="My Team" class="fs-card" style="display:block;width:100%;box-sizing:border-box;border:1px solid ${DARK.line};border-radius:8px;padding:12px;outline:0;background:transparent;font:500 15px/1.2 ${display};color:inherit;"><p class="fs-dim" style="margin:8px 0 20px;font:400 12px/1.6 ${display};color:${DARK.dim};">Shown across your workspace. You can change it later.</p><label for="slug" class="fs-ink" style="display:block;font:600 13px/1.4 ${display};color:${DARK.ink};margin-bottom:6px;">Workspace URL</label><div class="fs-card" style="display:flex;align-items:center;border:1px solid ${form.error ? "#d33" : DARK.line};border-radius:8px;padding:0 12px;"><input id="slug" name="slug" required autocomplete="off" autocapitalize="none" spellcheck="false" minlength="${SLUG_MIN}" maxlength="${SLUG_MAX}" pattern="[a-z](?:[a-z0-9]|-(?!-))*[a-z0-9]" value="${escapeHtml(form.slug ?? "")}" placeholder="my-team" style="flex:1;min-width:0;border:0;outline:0;background:transparent;padding:12px 0;font:500 15px/1.2 ${display};color:inherit;"><span class="fs-dim" style="font:400 15px/1 ${display};color:${DARK.dim};white-space:nowrap;">${escapeHtml(suffix)}</span></div>` + (form.error ? `<p style="margin:8px 0 0;font:500 13px/1.5 ${display};color:#d33;">${escapeHtml(form.error)}</p>` : "") + `<p class="fs-dim" style="margin:8px 0 20px;font:400 12px/1.6 ${display};color:${DARK.dim};">Lowercase letters, digits and hyphens, ${SLUG_MIN} to ${SLUG_MAX} characters. <strong style="font-weight:600;color:${DARK.ink};">It can't be changed later.</strong></p><button id="setup-submit" class="fs-btn" type="submit" style="background:${accent};color:${DARK.onAccent};border-radius:8px;padding:13px 24px;width:100%;font:600 15px/1 ${display};">Create workspace</button></form><div style="margin-top:20px;border-left:2px solid ${accent};padding:2px 0 2px 12px;"><p class="fs-muted" style="margin:0;font:400 13px/1.6 ${display};color:${DARK.muted};"><strong class="fs-ink" style="font-weight:600;color:${DARK.ink};">Next:</strong> we'll help you connect Claude Code or Codex CLI.</p></div><p class="fs-dim" style="margin:16px 0 0;font:400 12px/1.6 ${display};color:${DARK.dim};">By creating a workspace, you agree to the <a href="https://flyspace.ai/terms/" target="_blank" rel="noreferrer" style="color:inherit;text-decoration:underline;text-underline-offset:2px;">Terms</a> and <a href="https://flyspace.ai/privacy/" target="_blank" rel="noreferrer" style="color:inherit;text-decoration:underline;text-underline-offset:2px;">Privacy Policy</a>.</p></div></div><script>(()=>{const n=document.getElementById('name'),s=document.getElementById('slug');let touched=!!s.value;s.addEventListener('input',()=>{touched=true});n.addEventListener('input',()=>{if(touched)return;s.value=n.value.normalize('NFKD').toLowerCase().replace(/[^a-z0-9]+/g,'-').replace(/^-+|-+$/g,'').replace(/^[^a-z]+/,'').slice(0,${SLUG_MAX}).replace(/-+$/,'')})})()</script><script>document.getElementById('setup').addEventListener('submit',()=>{const b=document.getElementById('setup-submit');b.disabled=true;b.style.opacity='0.7';b.style.cursor='progress';b.textContent='Creating workspace\u2026'})</script><script>document.querySelectorAll('[data-toggle-password]').forEach(b=>b.addEventListener('click',()=>{const i=document.getElementById(b.dataset.togglePassword),show=i.type==='password';i.type=show?'text':'password';b.setAttribute('aria-pressed',String(show));b.setAttribute('aria-label',show?'Hide password':'Show password');b.querySelector('[data-eye]').style.display=show?'none':'';b.querySelector('[data-eye-off]').style.display=show?'':'none'}))</script>`,
|
|
2607
2617
|
void 0,
|
|
2608
2618
|
void 0,
|
|
2609
2619
|
true,
|
|
2610
|
-
960
|
|
2620
|
+
960,
|
|
2621
|
+
"Create your workspace \xB7 Flyspace"
|
|
2611
2622
|
);
|
|
2612
2623
|
}
|
|
2613
2624
|
function createAuthConfirmRouter() {
|
|
@@ -2773,6 +2784,35 @@ function createAuthConfirmRouter() {
|
|
|
2773
2784
|
const passwordError = password.length < PASSWORD_MIN ? `Password must be at least ${PASSWORD_MIN} characters.` : password !== form["confirm"] ? "Passwords do not match." : null;
|
|
2774
2785
|
if (passwordError)
|
|
2775
2786
|
return c.html(setupPage(c, token, invite, { name, slug, passwordError }), 400);
|
|
2787
|
+
try {
|
|
2788
|
+
const { userId } = await ensureInviteUser(
|
|
2789
|
+
invite.email,
|
|
2790
|
+
invite.ownerName ? { name: invite.ownerName } : {}
|
|
2791
|
+
);
|
|
2792
|
+
const { error: error2 } = await getSupabaseClient().auth.admin.updateUserById(userId, { password });
|
|
2793
|
+
if (error2) {
|
|
2794
|
+
const weak = error2.code === "weak_password";
|
|
2795
|
+
if (!weak) console.error(`[new-workspace] setting the password for ${invite.email} failed:`, error2);
|
|
2796
|
+
return c.html(
|
|
2797
|
+
setupPage(c, token, invite, {
|
|
2798
|
+
name,
|
|
2799
|
+
slug,
|
|
2800
|
+
passwordError: weak ? "This password is too easy to guess or has appeared in a data breach. Please choose another." : "We could not set your password. Please try again in a moment."
|
|
2801
|
+
}),
|
|
2802
|
+
weak ? 400 : 500
|
|
2803
|
+
);
|
|
2804
|
+
}
|
|
2805
|
+
} catch (err) {
|
|
2806
|
+
console.error(`[new-workspace] preparing the owner account for ${invite.email} failed:`, err);
|
|
2807
|
+
return c.html(
|
|
2808
|
+
setupPage(c, token, invite, {
|
|
2809
|
+
name,
|
|
2810
|
+
slug,
|
|
2811
|
+
error: "Something went wrong creating your workspace. Please try again in a moment."
|
|
2812
|
+
}),
|
|
2813
|
+
500
|
|
2814
|
+
);
|
|
2815
|
+
}
|
|
2776
2816
|
if (!await claimSetupInvite(invite.id)) return c.redirect(back, 303);
|
|
2777
2817
|
let workspace;
|
|
2778
2818
|
try {
|
|
@@ -2804,16 +2844,6 @@ function createAuthConfirmRouter() {
|
|
|
2804
2844
|
);
|
|
2805
2845
|
}
|
|
2806
2846
|
const origin = workspaceOrigin(c, workspace.slug);
|
|
2807
|
-
let passwordSet = false;
|
|
2808
|
-
try {
|
|
2809
|
-
const userId = await authUserIdForEmail(invite.email);
|
|
2810
|
-
if (!userId) throw new Error("owner has no auth user");
|
|
2811
|
-
const { error: error2 } = await getSupabaseClient().auth.admin.updateUserById(userId, { password });
|
|
2812
|
-
if (error2) throw new Error(error2.message);
|
|
2813
|
-
passwordSet = true;
|
|
2814
|
-
} catch (err) {
|
|
2815
|
-
console.error(`[new-workspace] setting the password for ${workspace.slug} failed:`, err);
|
|
2816
|
-
}
|
|
2817
2847
|
try {
|
|
2818
2848
|
const session = await mintSession(invite.email);
|
|
2819
2849
|
const fragment = new URLSearchParams({
|
|
@@ -2827,8 +2857,7 @@ function createAuthConfirmRouter() {
|
|
|
2827
2857
|
} catch (err) {
|
|
2828
2858
|
console.error(`[new-workspace] signing in the owner of ${workspace.slug} failed:`, err);
|
|
2829
2859
|
}
|
|
2830
|
-
|
|
2831
|
-
return c.redirect(`${origin}/login${login}`, 303);
|
|
2860
|
+
return c.redirect(`${origin}/login?email=${encodeURIComponent(invite.email)}`, 303);
|
|
2832
2861
|
});
|
|
2833
2862
|
return confirm3;
|
|
2834
2863
|
}
|
|
@@ -2887,10 +2916,20 @@ async function deliverInvite({
|
|
|
2887
2916
|
});
|
|
2888
2917
|
await sendEmail({ to: email, ...content });
|
|
2889
2918
|
}
|
|
2919
|
+
function flyspaceLogo(origin) {
|
|
2920
|
+
try {
|
|
2921
|
+
const url = new URL(origin);
|
|
2922
|
+
const local = url.hostname === "localhost" || url.hostname === "127.0.0.1";
|
|
2923
|
+
return url.protocol === "https:" && !local ? { logoUrl: `${url.origin}/icons/icon-192.png` } : {};
|
|
2924
|
+
} catch {
|
|
2925
|
+
return {};
|
|
2926
|
+
}
|
|
2927
|
+
}
|
|
2890
2928
|
async function deliverWorkspaceSetupInvite(params) {
|
|
2891
2929
|
if (!isMailConfigured()) throw new MailNotConfiguredError();
|
|
2892
2930
|
const content = workspaceSetupEmail({
|
|
2893
2931
|
actionLink: params.inviteLink,
|
|
2932
|
+
...flyspaceLogo(originOf(params.inviteLink)),
|
|
2894
2933
|
...params.invitedBy ? { invitedBy: params.invitedBy } : {},
|
|
2895
2934
|
...params.message?.trim() ? { message: params.message } : {}
|
|
2896
2935
|
});
|
|
@@ -2916,10 +2955,10 @@ async function deliverSignInLink(email, redirectTo) {
|
|
|
2916
2955
|
}
|
|
2917
2956
|
|
|
2918
2957
|
// src/dashboard/server.ts
|
|
2919
|
-
import { existsSync as
|
|
2958
|
+
import { existsSync as existsSync27 } from "fs";
|
|
2920
2959
|
import { createServer } from "net";
|
|
2921
|
-
import { join as
|
|
2922
|
-
import { Hono as
|
|
2960
|
+
import { join as join19 } from "path";
|
|
2961
|
+
import { Hono as Hono30 } from "hono";
|
|
2923
2962
|
import { serve } from "@hono/node-server";
|
|
2924
2963
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
2925
2964
|
|
|
@@ -2965,7 +3004,7 @@ async function reconcileAll() {
|
|
|
2965
3004
|
}
|
|
2966
3005
|
|
|
2967
3006
|
// src/dashboard/api.ts
|
|
2968
|
-
import { Hono as
|
|
3007
|
+
import { Hono as Hono27 } from "hono";
|
|
2969
3008
|
|
|
2970
3009
|
// src/dashboard/public-share-routes.ts
|
|
2971
3010
|
import { existsSync as existsSync11, readFileSync as readFileSync6, statSync as statSync2 } from "fs";
|
|
@@ -4022,7 +4061,7 @@ function truncationNotice(lang, voice) {
|
|
|
4022
4061
|
if (lang === "en" || lang.startsWith("en-")) return TRUNCATION_NOTICE.en;
|
|
4023
4062
|
return null;
|
|
4024
4063
|
}
|
|
4025
|
-
var sleep = (ms) => new Promise((
|
|
4064
|
+
var sleep = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
4026
4065
|
async function synthesizeWithRetry(script, voice) {
|
|
4027
4066
|
let lastError;
|
|
4028
4067
|
for (let attempt = 1; attempt <= SYNTH_ATTEMPTS; attempt += 1) {
|
|
@@ -5035,6 +5074,9 @@ import { isAuthApiError, isAuthRetryableFetchError } from "@supabase/supabase-js
|
|
|
5035
5074
|
var inFlight3 = /* @__PURE__ */ new Map();
|
|
5036
5075
|
var CACHE_TTL_MS = 3e4;
|
|
5037
5076
|
var resolved = /* @__PURE__ */ new Map();
|
|
5077
|
+
function invalidateAuthContextCache() {
|
|
5078
|
+
resolved.clear();
|
|
5079
|
+
}
|
|
5038
5080
|
function pruneExpired(now) {
|
|
5039
5081
|
for (const [key, entry] of resolved) {
|
|
5040
5082
|
if (entry.expires <= now) resolved.delete(key);
|
|
@@ -5138,7 +5180,10 @@ function siteverify(secret, token) {
|
|
|
5138
5180
|
async function verifyTurnstileToken(token) {
|
|
5139
5181
|
const secret = profileEnv("TURNSTILE_SECRET_KEY")?.trim();
|
|
5140
5182
|
if (!secret) return true;
|
|
5141
|
-
if (!token)
|
|
5183
|
+
if (!token) {
|
|
5184
|
+
console.warn("[turnstile] no token sent (widget failed to load or errored in the browser)");
|
|
5185
|
+
return false;
|
|
5186
|
+
}
|
|
5142
5187
|
let res;
|
|
5143
5188
|
try {
|
|
5144
5189
|
res = await siteverify(secret, token);
|
|
@@ -5149,9 +5194,15 @@ async function verifyTurnstileToken(token) {
|
|
|
5149
5194
|
return false;
|
|
5150
5195
|
}
|
|
5151
5196
|
}
|
|
5152
|
-
if (!res.ok)
|
|
5197
|
+
if (!res.ok) {
|
|
5198
|
+
console.warn("[turnstile] siteverify HTTP", res.status);
|
|
5199
|
+
return false;
|
|
5200
|
+
}
|
|
5153
5201
|
const data = await res.json().catch(() => ({}));
|
|
5154
|
-
if (data.success !== true)
|
|
5202
|
+
if (data.success !== true) {
|
|
5203
|
+
console.warn("[turnstile] rejected", data["error-codes"] ?? [], data.hostname ?? "");
|
|
5204
|
+
return false;
|
|
5205
|
+
}
|
|
5155
5206
|
return data.action === void 0 || data.action === "login";
|
|
5156
5207
|
}
|
|
5157
5208
|
|
|
@@ -6413,7 +6464,7 @@ function needsRefresh(expiresAt, now = Date.now()) {
|
|
|
6413
6464
|
if (!expiresAt) return false;
|
|
6414
6465
|
return new Date(expiresAt).getTime() - now < REFRESH_SKEW_MS;
|
|
6415
6466
|
}
|
|
6416
|
-
var sleep2 = (ms) => new Promise((
|
|
6467
|
+
var sleep2 = (ms) => new Promise((resolve8) => setTimeout(resolve8, ms));
|
|
6417
6468
|
async function refreshConnection(params) {
|
|
6418
6469
|
const { workspaceId, connection, provider } = params;
|
|
6419
6470
|
const deps = params.deps ?? {};
|
|
@@ -6657,8 +6708,8 @@ function collectFrom(root, source) {
|
|
|
6657
6708
|
return { name, description: (fm.description ?? "").trim(), source, path };
|
|
6658
6709
|
});
|
|
6659
6710
|
}
|
|
6660
|
-
function listAgents(
|
|
6661
|
-
const base =
|
|
6711
|
+
function listAgents(configDir2, cwd) {
|
|
6712
|
+
const base = configDir2 ? expandHome(configDir2) : defaultConfigDir();
|
|
6662
6713
|
const collected = [
|
|
6663
6714
|
...collectFrom(join11(cwd, ".claude", "agents"), "project"),
|
|
6664
6715
|
...collectFrom(join11(base, "agents"), "user")
|
|
@@ -6669,8 +6720,8 @@ function listAgents(configDir, cwd) {
|
|
|
6669
6720
|
}
|
|
6670
6721
|
return [...byName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
6671
6722
|
}
|
|
6672
|
-
function readAgentPersona(
|
|
6673
|
-
const agent = listAgents(
|
|
6723
|
+
function readAgentPersona(configDir2, cwd, name) {
|
|
6724
|
+
const agent = listAgents(configDir2, cwd).find((a) => a.name === name);
|
|
6674
6725
|
if (!agent) return void 0;
|
|
6675
6726
|
try {
|
|
6676
6727
|
const content = readFileSync7(agent.path, "utf-8");
|
|
@@ -6722,7 +6773,7 @@ Rules:
|
|
|
6722
6773
|
- No preamble like "Summary:" \u2014 start directly with the summary text.`;
|
|
6723
6774
|
var HAIKU_TIMEOUT_MS = 6e4;
|
|
6724
6775
|
function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
6725
|
-
return new Promise((
|
|
6776
|
+
return new Promise((resolve8, reject) => {
|
|
6726
6777
|
const args = [
|
|
6727
6778
|
"--print",
|
|
6728
6779
|
"--model",
|
|
@@ -6773,7 +6824,7 @@ function runHaikuPrint(input2, claudeConfigDir, claudeCodeOauthToken) {
|
|
|
6773
6824
|
reject(new Error("claude -p --model haiku returned empty output"));
|
|
6774
6825
|
return;
|
|
6775
6826
|
}
|
|
6776
|
-
|
|
6827
|
+
resolve8(text);
|
|
6777
6828
|
});
|
|
6778
6829
|
});
|
|
6779
6830
|
}
|
|
@@ -7672,22 +7723,22 @@ var ChatAgent = class {
|
|
|
7672
7723
|
let ready;
|
|
7673
7724
|
try {
|
|
7674
7725
|
channel = this.supabase().channel(`agent-thinking:${threadId}`);
|
|
7675
|
-
ready = new Promise((
|
|
7726
|
+
ready = new Promise((resolve8) => {
|
|
7676
7727
|
const timeout = setTimeout(() => {
|
|
7677
7728
|
console.error(`[chat-agent] thinking-channel ${threadId}: subscribe timed out after 10s`);
|
|
7678
|
-
|
|
7729
|
+
resolve8(false);
|
|
7679
7730
|
}, 1e4);
|
|
7680
7731
|
channel.subscribe((status, err) => {
|
|
7681
7732
|
if (status === "SUBSCRIBED") {
|
|
7682
7733
|
clearTimeout(timeout);
|
|
7683
|
-
|
|
7734
|
+
resolve8(true);
|
|
7684
7735
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
7685
7736
|
console.error(
|
|
7686
7737
|
`[chat-agent] thinking-channel ${threadId}: ${status}`,
|
|
7687
7738
|
err?.message ?? ""
|
|
7688
7739
|
);
|
|
7689
7740
|
clearTimeout(timeout);
|
|
7690
|
-
|
|
7741
|
+
resolve8(false);
|
|
7691
7742
|
}
|
|
7692
7743
|
});
|
|
7693
7744
|
});
|
|
@@ -7734,19 +7785,19 @@ var ChatAgent = class {
|
|
|
7734
7785
|
let ready;
|
|
7735
7786
|
try {
|
|
7736
7787
|
channel = this.supabase().channel("channel-activity");
|
|
7737
|
-
ready = new Promise((
|
|
7788
|
+
ready = new Promise((resolve8) => {
|
|
7738
7789
|
const timeout = setTimeout(() => {
|
|
7739
7790
|
console.error("[chat-agent] channel-activity: subscribe timed out after 10s");
|
|
7740
|
-
|
|
7791
|
+
resolve8(false);
|
|
7741
7792
|
}, 1e4);
|
|
7742
7793
|
channel.subscribe((status, err) => {
|
|
7743
7794
|
if (status === "SUBSCRIBED") {
|
|
7744
7795
|
clearTimeout(timeout);
|
|
7745
|
-
|
|
7796
|
+
resolve8(true);
|
|
7746
7797
|
} else if (status === "CHANNEL_ERROR" || status === "TIMED_OUT" || status === "CLOSED") {
|
|
7747
7798
|
console.error(`[chat-agent] channel-activity: ${status}`, err?.message ?? "");
|
|
7748
7799
|
clearTimeout(timeout);
|
|
7749
|
-
|
|
7800
|
+
resolve8(false);
|
|
7750
7801
|
}
|
|
7751
7802
|
});
|
|
7752
7803
|
});
|
|
@@ -7773,7 +7824,6 @@ var ChatAgent = class {
|
|
|
7773
7824
|
async tryAsk(channelConfig, prompt, sessionId, onActivity, signal, usageContext) {
|
|
7774
7825
|
const name = subAgentName(channelConfig.agentName);
|
|
7775
7826
|
const agentPersona = name ? readAgentPersona(channelConfig.claudeConfigDir, channelConfig.cwd, name) : void 0;
|
|
7776
|
-
const outputStyle = await resolveOutputStyleForProfile(channelConfig.profile);
|
|
7777
7827
|
try {
|
|
7778
7828
|
return await withMcpConfig(
|
|
7779
7829
|
channelConfig.profile,
|
|
@@ -7790,7 +7840,6 @@ var ChatAgent = class {
|
|
|
7790
7840
|
maxBudgetUsd: this.config.maxBudgetUsd,
|
|
7791
7841
|
agentPersona,
|
|
7792
7842
|
channelPrompt: channelConfig.prompt,
|
|
7793
|
-
outputStyle,
|
|
7794
7843
|
onActivity,
|
|
7795
7844
|
signal,
|
|
7796
7845
|
mcpConfigPath,
|
|
@@ -7809,7 +7858,6 @@ var ChatAgent = class {
|
|
|
7809
7858
|
async tryAskCodex(channelConfig, prompt, sessionId, signal, onActivity, usageContext) {
|
|
7810
7859
|
const name = subAgentName(channelConfig.agentName);
|
|
7811
7860
|
const agentPersona = name ? readAgentPersona(channelConfig.claudeConfigDir, channelConfig.cwd, name) : void 0;
|
|
7812
|
-
const outputStyle = await resolveOutputStyleForProfile(channelConfig.profile);
|
|
7813
7861
|
try {
|
|
7814
7862
|
return await askCodex(
|
|
7815
7863
|
prompt,
|
|
@@ -7829,7 +7877,7 @@ var ChatAgent = class {
|
|
|
7829
7877
|
this.config.botName,
|
|
7830
7878
|
agentPersona,
|
|
7831
7879
|
channelConfig.prompt,
|
|
7832
|
-
|
|
7880
|
+
resolveSelectedOutputStyle(channelConfig.claudeConfigDir)?.body
|
|
7833
7881
|
),
|
|
7834
7882
|
...onActivity ? { onActivity } : {},
|
|
7835
7883
|
...signal ? { signal } : {},
|
|
@@ -8096,10 +8144,10 @@ ${lines}
|
|
|
8096
8144
|
// src/dashboard/artifact-export.ts
|
|
8097
8145
|
async function exportDocAsArtifact(channelId, docPath) {
|
|
8098
8146
|
const owningProfile = findOwningProfile(channelId);
|
|
8099
|
-
const
|
|
8100
|
-
|
|
8101
|
-
|
|
8102
|
-
|
|
8147
|
+
const channelConfig = await runWithProfile(
|
|
8148
|
+
owningProfile,
|
|
8149
|
+
async () => resolveChannelConfig(channelId)
|
|
8150
|
+
);
|
|
8103
8151
|
const abs = resolveDocPath(channelConfig.cwd, docPath);
|
|
8104
8152
|
if (!abs) throw new Error(`doc not found: ${docPath}`);
|
|
8105
8153
|
const content = readFileSync8(abs, "utf8");
|
|
@@ -8133,7 +8181,6 @@ ${content}`;
|
|
|
8133
8181
|
runtime: channelConfig.runtime,
|
|
8134
8182
|
mounts: channelConfig.mounts,
|
|
8135
8183
|
channelPrompt: resolveChannelPrompt(channelId),
|
|
8136
|
-
outputStyle,
|
|
8137
8184
|
usageContext: { source: "automation", channelId, label: "export-artifact" }
|
|
8138
8185
|
},
|
|
8139
8186
|
channelConfig.claudeConfigDir,
|
|
@@ -8980,10 +9027,123 @@ function createDocsRouter() {
|
|
|
8980
9027
|
return app;
|
|
8981
9028
|
}
|
|
8982
9029
|
|
|
9030
|
+
// src/dashboard/output-styles-routes.ts
|
|
9031
|
+
import { Hono as Hono10 } from "hono";
|
|
9032
|
+
function requireAdmin(auth) {
|
|
9033
|
+
return auth?.role === "admin" ? null : "admin access required";
|
|
9034
|
+
}
|
|
9035
|
+
function toResponse3(style) {
|
|
9036
|
+
return {
|
|
9037
|
+
id: style.id,
|
|
9038
|
+
name: style.name,
|
|
9039
|
+
description: style.description,
|
|
9040
|
+
body: style.body,
|
|
9041
|
+
createdAt: style.createdAt,
|
|
9042
|
+
updatedAt: style.updatedAt
|
|
9043
|
+
};
|
|
9044
|
+
}
|
|
9045
|
+
var configDir = () => profileEnv("CLAUDE_CONFIG_DIR");
|
|
9046
|
+
function failure(c, err) {
|
|
9047
|
+
const message = err.message;
|
|
9048
|
+
return c.json({ error: message }, err instanceof OutputStyleConflictError ? 409 : 500);
|
|
9049
|
+
}
|
|
9050
|
+
function createOutputStylesRouter() {
|
|
9051
|
+
const app = new Hono10();
|
|
9052
|
+
app.get("/", (c) => {
|
|
9053
|
+
const auth = c.get("auth");
|
|
9054
|
+
const denied = requireAdmin(auth);
|
|
9055
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
9056
|
+
try {
|
|
9057
|
+
return c.json({
|
|
9058
|
+
builtIn: BUILT_IN_OUTPUT_STYLES,
|
|
9059
|
+
custom: listOutputStyles(configDir()).map(toResponse3),
|
|
9060
|
+
defaultId: getOutputStyleDefault(configDir())
|
|
9061
|
+
});
|
|
9062
|
+
} catch (err) {
|
|
9063
|
+
return failure(c, err);
|
|
9064
|
+
}
|
|
9065
|
+
});
|
|
9066
|
+
app.post("/", async (c) => {
|
|
9067
|
+
const auth = c.get("auth");
|
|
9068
|
+
const denied = requireAdmin(auth);
|
|
9069
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
9070
|
+
const body = await c.req.json().catch(() => null);
|
|
9071
|
+
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
9072
|
+
if (!name) return c.json({ error: "name is required" }, 400);
|
|
9073
|
+
const description = typeof body?.description === "string" ? body.description.trim() : "";
|
|
9074
|
+
const content = typeof body?.body === "string" ? body.body : "";
|
|
9075
|
+
try {
|
|
9076
|
+
return c.json(
|
|
9077
|
+
toResponse3(createOutputStyle(configDir(), { name, description, body: content })),
|
|
9078
|
+
201
|
|
9079
|
+
);
|
|
9080
|
+
} catch (err) {
|
|
9081
|
+
return failure(c, err);
|
|
9082
|
+
}
|
|
9083
|
+
});
|
|
9084
|
+
app.put("/default", async (c) => {
|
|
9085
|
+
const auth = c.get("auth");
|
|
9086
|
+
const denied = requireAdmin(auth);
|
|
9087
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
9088
|
+
const body = await c.req.json().catch(() => null);
|
|
9089
|
+
const id = typeof body?.id === "string" ? body.id : "";
|
|
9090
|
+
if (!id) return c.json({ error: "id is required" }, 400);
|
|
9091
|
+
try {
|
|
9092
|
+
if (!setOutputStyleDefault(configDir(), id)) {
|
|
9093
|
+
return c.json({ error: "output style not found" }, 404);
|
|
9094
|
+
}
|
|
9095
|
+
} catch (err) {
|
|
9096
|
+
return failure(c, err);
|
|
9097
|
+
}
|
|
9098
|
+
return c.json({ defaultId: id });
|
|
9099
|
+
});
|
|
9100
|
+
app.put("/:id", async (c) => {
|
|
9101
|
+
const auth = c.get("auth");
|
|
9102
|
+
const denied = requireAdmin(auth);
|
|
9103
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
9104
|
+
const body = await c.req.json().catch(() => null);
|
|
9105
|
+
if (!body) return c.json({ error: "a body is required" }, 400);
|
|
9106
|
+
const patch = {};
|
|
9107
|
+
if (body.name !== void 0) {
|
|
9108
|
+
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
9109
|
+
if (!name) return c.json({ error: "name cannot be empty" }, 400);
|
|
9110
|
+
patch.name = name;
|
|
9111
|
+
}
|
|
9112
|
+
if (body.description !== void 0) {
|
|
9113
|
+
patch.description = typeof body.description === "string" ? body.description.trim() : "";
|
|
9114
|
+
}
|
|
9115
|
+
if (body.body !== void 0) {
|
|
9116
|
+
patch.body = typeof body.body === "string" ? body.body : "";
|
|
9117
|
+
}
|
|
9118
|
+
if (!Object.keys(patch).length) return c.json({ error: "nothing to update" }, 400);
|
|
9119
|
+
try {
|
|
9120
|
+
const updated = updateOutputStyle(configDir(), c.req.param("id"), patch);
|
|
9121
|
+
if (!updated) return c.json({ error: "output style not found" }, 404);
|
|
9122
|
+
return c.json(toResponse3(updated));
|
|
9123
|
+
} catch (err) {
|
|
9124
|
+
return failure(c, err);
|
|
9125
|
+
}
|
|
9126
|
+
});
|
|
9127
|
+
app.delete("/:id", async (c) => {
|
|
9128
|
+
const auth = c.get("auth");
|
|
9129
|
+
const denied = requireAdmin(auth);
|
|
9130
|
+
if (denied) return c.json({ error: denied }, 403);
|
|
9131
|
+
try {
|
|
9132
|
+
if (!deleteOutputStyle(configDir(), c.req.param("id"))) {
|
|
9133
|
+
return c.json({ error: "output style not found" }, 404);
|
|
9134
|
+
}
|
|
9135
|
+
} catch (err) {
|
|
9136
|
+
return failure(c, err);
|
|
9137
|
+
}
|
|
9138
|
+
return c.json({ ok: true });
|
|
9139
|
+
});
|
|
9140
|
+
return app;
|
|
9141
|
+
}
|
|
9142
|
+
|
|
8983
9143
|
// src/dashboard/skills-routes.ts
|
|
8984
9144
|
import { existsSync as existsSync18, statSync as statSync6 } from "fs";
|
|
8985
9145
|
import { dirname as dirname4 } from "path";
|
|
8986
|
-
import { Hono as
|
|
9146
|
+
import { Hono as Hono11 } from "hono";
|
|
8987
9147
|
|
|
8988
9148
|
// src/dashboard/skill-mirror.ts
|
|
8989
9149
|
import { createHash as createHash8 } from "crypto";
|
|
@@ -9010,11 +9170,11 @@ function ownSkills(scope, dir) {
|
|
|
9010
9170
|
}
|
|
9011
9171
|
function snapshot(scopes) {
|
|
9012
9172
|
const wants = (scope) => !scopes || scopes.has(scope);
|
|
9013
|
-
const
|
|
9173
|
+
const configDir2 = profileEnv("CLAUDE_CONFIG_DIR");
|
|
9014
9174
|
const rows = [];
|
|
9015
|
-
if (wants("workspace")) rows.push(...ownSkills("workspace", workspaceSkillsDir(
|
|
9175
|
+
if (wants("workspace")) rows.push(...ownSkills("workspace", workspaceSkillsDir(configDir2)));
|
|
9016
9176
|
if (wants("plugin")) {
|
|
9017
|
-
for (const s of listPluginSkillDetails(
|
|
9177
|
+
for (const s of listPluginSkillDetails(configDir2, defaultClaudeCwd())) {
|
|
9018
9178
|
rows.push({
|
|
9019
9179
|
scope: "plugin",
|
|
9020
9180
|
name: s.name,
|
|
@@ -9131,7 +9291,7 @@ function channelScope(channelId) {
|
|
|
9131
9291
|
function inheritedSkills(scope, own) {
|
|
9132
9292
|
return listSkills(scope.claudeConfigDir, scope.cwd).filter((s) => scope.inheritFrom.includes(s.source) && !own.has(s.name)).sort((a, b) => a.name.localeCompare(b.name));
|
|
9133
9293
|
}
|
|
9134
|
-
function
|
|
9294
|
+
function requireAdmin2(auth) {
|
|
9135
9295
|
return auth?.role === "admin" ? null : "admin access required";
|
|
9136
9296
|
}
|
|
9137
9297
|
function rootExists(dir) {
|
|
@@ -9271,7 +9431,7 @@ function mountSkillRoutes(app, base, refOf, backend) {
|
|
|
9271
9431
|
return detail ? c.json(detail) : c.json({ error: "not found" }, 404);
|
|
9272
9432
|
});
|
|
9273
9433
|
app.post(base, async (c) => {
|
|
9274
|
-
const denied =
|
|
9434
|
+
const denied = requireAdmin2(c.get("auth"));
|
|
9275
9435
|
if (denied) return c.json({ error: denied }, 403);
|
|
9276
9436
|
const body = await c.req.json().catch(() => null);
|
|
9277
9437
|
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
@@ -9283,7 +9443,7 @@ function mountSkillRoutes(app, base, refOf, backend) {
|
|
|
9283
9443
|
return c.json(result.body, result.status);
|
|
9284
9444
|
});
|
|
9285
9445
|
app.put(`${base}/:name`, async (c) => {
|
|
9286
|
-
const denied =
|
|
9446
|
+
const denied = requireAdmin2(c.get("auth"));
|
|
9287
9447
|
if (denied) return c.json({ error: denied }, 403);
|
|
9288
9448
|
const body = await c.req.json().catch(() => null);
|
|
9289
9449
|
if (!body || typeof body.body !== "string" || typeof body.description !== "string") {
|
|
@@ -9299,7 +9459,7 @@ function mountSkillRoutes(app, base, refOf, backend) {
|
|
|
9299
9459
|
return c.json(result.body, result.status);
|
|
9300
9460
|
});
|
|
9301
9461
|
app.delete(`${base}/:name`, async (c) => {
|
|
9302
|
-
const denied =
|
|
9462
|
+
const denied = requireAdmin2(c.get("auth"));
|
|
9303
9463
|
if (denied) return c.json({ error: denied }, 403);
|
|
9304
9464
|
const result = await backend.write(refOf(c), { op: "delete", name: c.req.param("name") });
|
|
9305
9465
|
return c.json(result.body, result.status);
|
|
@@ -9308,12 +9468,12 @@ function mountSkillRoutes(app, base, refOf, backend) {
|
|
|
9308
9468
|
var workspaceRef = () => ({ kind: "workspace" });
|
|
9309
9469
|
var channelRef = (c) => ({ kind: "channel", channelId: c.req.param("id") });
|
|
9310
9470
|
function createWorkspaceSkillsRouter() {
|
|
9311
|
-
const app = new
|
|
9471
|
+
const app = new Hono11();
|
|
9312
9472
|
mountSkillRoutes(app, "/skills", workspaceRef, diskBackend);
|
|
9313
9473
|
return app;
|
|
9314
9474
|
}
|
|
9315
9475
|
function createChannelSkillsRouter() {
|
|
9316
|
-
const app = new
|
|
9476
|
+
const app = new Hono11();
|
|
9317
9477
|
mountSkillRoutes(app, "/:id/skill-files", channelRef, diskBackend);
|
|
9318
9478
|
return app;
|
|
9319
9479
|
}
|
|
@@ -9370,8 +9530,8 @@ function binaryOnPath(bin) {
|
|
|
9370
9530
|
}
|
|
9371
9531
|
function claudeAuthAvailable() {
|
|
9372
9532
|
if (profileEnv("CLAUDE_CODE_OAUTH_TOKEN")?.trim()) return true;
|
|
9373
|
-
const
|
|
9374
|
-
if (existsSync19(join14(
|
|
9533
|
+
const configDir2 = profileEnv("CLAUDE_CONFIG_DIR")?.trim() || resolve5(homedir6(), ".claude");
|
|
9534
|
+
if (existsSync19(join14(configDir2, ".credentials.json"))) return true;
|
|
9375
9535
|
if (process.platform !== "darwin") return false;
|
|
9376
9536
|
try {
|
|
9377
9537
|
execFileSync("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
|
|
@@ -9420,6 +9580,7 @@ function registerHostRoutes(api) {
|
|
|
9420
9580
|
});
|
|
9421
9581
|
api.route("/channels", createChannelSkillsRouter());
|
|
9422
9582
|
api.route("/", createWorkspaceSkillsRouter());
|
|
9583
|
+
api.route("/output-styles", createOutputStylesRouter());
|
|
9423
9584
|
api.route("/channels", createDocsRouter());
|
|
9424
9585
|
api.route("/channels", createChannelWriteRouter());
|
|
9425
9586
|
api.get("/settings", async (c) => c.json(await loadFullDashboardSettings()));
|
|
@@ -9529,11 +9690,11 @@ function tailFile(file, limit) {
|
|
|
9529
9690
|
}
|
|
9530
9691
|
|
|
9531
9692
|
// src/dashboard/data-api.ts
|
|
9532
|
-
import { existsSync as
|
|
9693
|
+
import { existsSync as existsSync23 } from "fs";
|
|
9533
9694
|
import sharp5 from "sharp";
|
|
9534
9695
|
|
|
9535
9696
|
// src/dashboard/automations-routes.ts
|
|
9536
|
-
import { Hono as
|
|
9697
|
+
import { Hono as Hono12 } from "hono";
|
|
9537
9698
|
var TYPES2 = ["schedule", "trigger", "mention-trigger"];
|
|
9538
9699
|
function isType(t) {
|
|
9539
9700
|
return TYPES2.includes(t);
|
|
@@ -9550,7 +9711,7 @@ function statusFor(message) {
|
|
|
9550
9711
|
return 400;
|
|
9551
9712
|
}
|
|
9552
9713
|
function createAutomationWriteRouter() {
|
|
9553
|
-
const app = new
|
|
9714
|
+
const app = new Hono12();
|
|
9554
9715
|
const manager = () => new AutomationManager();
|
|
9555
9716
|
app.get("/:type/:name", (c) => {
|
|
9556
9717
|
const item = manager().get(c.req.param("name"));
|
|
@@ -9606,9 +9767,9 @@ function createAutomationWriteRouter() {
|
|
|
9606
9767
|
}
|
|
9607
9768
|
|
|
9608
9769
|
// src/dashboard/categories-routes.ts
|
|
9609
|
-
import { Hono as
|
|
9770
|
+
import { Hono as Hono13 } from "hono";
|
|
9610
9771
|
function createCategoryWriteRouter() {
|
|
9611
|
-
const app = new
|
|
9772
|
+
const app = new Hono13();
|
|
9612
9773
|
app.get("/", async (c) => {
|
|
9613
9774
|
const { data, error: error2 } = await getSupabaseClient().from("categories").select("*").eq("profile", getProfile()).order("position", { ascending: true });
|
|
9614
9775
|
if (error2) return c.json({ error: error2.message }, 500);
|
|
@@ -9662,7 +9823,7 @@ function createCategoryWriteRouter() {
|
|
|
9662
9823
|
}
|
|
9663
9824
|
|
|
9664
9825
|
// src/dashboard/chat-routes.ts
|
|
9665
|
-
import { Hono as
|
|
9826
|
+
import { Hono as Hono14 } from "hono";
|
|
9666
9827
|
|
|
9667
9828
|
// src/dashboard/fork-summary.ts
|
|
9668
9829
|
var GROQ_API_URL5 = "https://api.groq.com/openai/v1/chat/completions";
|
|
@@ -10106,7 +10267,7 @@ async function withPreviews(supabase, threads) {
|
|
|
10106
10267
|
});
|
|
10107
10268
|
}
|
|
10108
10269
|
function createChatRouter(agent = {}) {
|
|
10109
|
-
const app = new
|
|
10270
|
+
const app = new Hono14();
|
|
10110
10271
|
const {
|
|
10111
10272
|
onChatMessage: onMessage,
|
|
10112
10273
|
isAgentThinking,
|
|
@@ -10596,7 +10757,7 @@ function createChatRouter(agent = {}) {
|
|
|
10596
10757
|
}
|
|
10597
10758
|
|
|
10598
10759
|
// src/dashboard/doc-shares-routes.ts
|
|
10599
|
-
import { Hono as
|
|
10760
|
+
import { Hono as Hono15 } from "hono";
|
|
10600
10761
|
|
|
10601
10762
|
// src/dashboard/doc-share-features.ts
|
|
10602
10763
|
import { DeleteObjectCommand as DeleteObjectCommand3 } from "@aws-sdk/client-s3";
|
|
@@ -10756,7 +10917,7 @@ function resolveChannel2(c) {
|
|
|
10756
10917
|
}
|
|
10757
10918
|
var LIST_LIMIT = 500;
|
|
10758
10919
|
function createDocShareListRouter() {
|
|
10759
|
-
const app = new
|
|
10920
|
+
const app = new Hono15();
|
|
10760
10921
|
app.get("/shares", async (c) => {
|
|
10761
10922
|
const viewer = c.get("auth");
|
|
10762
10923
|
const channels = new Map(
|
|
@@ -10792,7 +10953,7 @@ function createDocShareListRouter() {
|
|
|
10792
10953
|
return app;
|
|
10793
10954
|
}
|
|
10794
10955
|
function createDocShareWriteRouter(agent = {}) {
|
|
10795
|
-
const app = new
|
|
10956
|
+
const app = new Hono15();
|
|
10796
10957
|
app.get("/:id/docs/share", async (c) => {
|
|
10797
10958
|
const resolved2 = resolveChannel2(c);
|
|
10798
10959
|
if ("error" in resolved2) return resolved2.error;
|
|
@@ -10939,7 +11100,7 @@ function createDocShareWriteRouter(agent = {}) {
|
|
|
10939
11100
|
}
|
|
10940
11101
|
|
|
10941
11102
|
// src/dashboard/search-routes.ts
|
|
10942
|
-
import { Hono as
|
|
11103
|
+
import { Hono as Hono16 } from "hono";
|
|
10943
11104
|
function matchScore(q, fields) {
|
|
10944
11105
|
const terms = q.toLowerCase().split(/\s+/).filter(Boolean);
|
|
10945
11106
|
if (terms.length === 0) return 0;
|
|
@@ -11058,7 +11219,7 @@ function searchAutomations(q) {
|
|
|
11058
11219
|
}
|
|
11059
11220
|
var RESULT_LIMIT = 30;
|
|
11060
11221
|
function createSearchRouter() {
|
|
11061
|
-
const app = new
|
|
11222
|
+
const app = new Hono16();
|
|
11062
11223
|
app.get("/", async (c) => {
|
|
11063
11224
|
const q = c.req.query("q")?.trim();
|
|
11064
11225
|
if (!q) return c.json({ results: [] });
|
|
@@ -11075,7 +11236,7 @@ function createSearchRouter() {
|
|
|
11075
11236
|
|
|
11076
11237
|
// src/dashboard/uploads-routes.ts
|
|
11077
11238
|
import { randomUUID } from "crypto";
|
|
11078
|
-
import { Hono as
|
|
11239
|
+
import { Hono as Hono17 } from "hono";
|
|
11079
11240
|
import { PutObjectCommand as PutObjectCommand3, GetObjectCommand as GetObjectCommand5 } from "@aws-sdk/client-s3";
|
|
11080
11241
|
import { getSignedUrl as getSignedUrl4 } from "@aws-sdk/s3-request-presigner";
|
|
11081
11242
|
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -11090,7 +11251,7 @@ function encodeKeyForUrl(key) {
|
|
|
11090
11251
|
return key.split("/").map(encodeURIComponent).join("/");
|
|
11091
11252
|
}
|
|
11092
11253
|
function createUploadsRouter() {
|
|
11093
|
-
const app = new
|
|
11254
|
+
const app = new Hono17();
|
|
11094
11255
|
app.post("/uploads", async (c) => {
|
|
11095
11256
|
const body = await c.req.parseBody().catch(() => null);
|
|
11096
11257
|
const file = body?.["file"];
|
|
@@ -11136,7 +11297,7 @@ function createUploadsRouter() {
|
|
|
11136
11297
|
|
|
11137
11298
|
// src/dashboard/avatars-routes.ts
|
|
11138
11299
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
11139
|
-
import { Hono as
|
|
11300
|
+
import { Hono as Hono18 } from "hono";
|
|
11140
11301
|
import sharp4 from "sharp";
|
|
11141
11302
|
import { PutObjectCommand as PutObjectCommand4, DeleteObjectCommand as DeleteObjectCommand4 } from "@aws-sdk/client-s3";
|
|
11142
11303
|
var AVATAR_SIZE = 192;
|
|
@@ -11155,10 +11316,12 @@ async function saveAvatarKey(userId, avatarKey) {
|
|
|
11155
11316
|
user_metadata: metadata
|
|
11156
11317
|
});
|
|
11157
11318
|
if (error2) throw new Error(`saveAvatarKey(${userId}): ${error2.message}`);
|
|
11319
|
+
invalidateAuthContextCache();
|
|
11158
11320
|
}
|
|
11159
11321
|
async function storeAvatar(file, owner) {
|
|
11160
11322
|
if (!(file instanceof File)) return { error: "missing file" };
|
|
11161
|
-
if (!AVATAR_MIME_TYPES.includes(file.type))
|
|
11323
|
+
if (!AVATAR_MIME_TYPES.includes(file.type))
|
|
11324
|
+
return { error: "avatar must be a PNG or JPEG image" };
|
|
11162
11325
|
if (file.size > MAX_AVATAR_BYTES) return { error: "avatar must be 2MB or smaller" };
|
|
11163
11326
|
const original = Buffer.from(await file.arrayBuffer());
|
|
11164
11327
|
const resized = await sharp4(original).resize({ width: AVATAR_SIZE, height: AVATAR_SIZE, fit: "cover" }).png().toBuffer();
|
|
@@ -11177,7 +11340,7 @@ function avatarKeyPrefix() {
|
|
|
11177
11340
|
return `${KEY_PREFIX3}/${getProfile()}/`;
|
|
11178
11341
|
}
|
|
11179
11342
|
function createAvatarsRouter() {
|
|
11180
|
-
const app = new
|
|
11343
|
+
const app = new Hono18();
|
|
11181
11344
|
app.get("/avatars/slack", async (c) => {
|
|
11182
11345
|
const ctx = await resolveAuthContext(c);
|
|
11183
11346
|
if (!ctx) return c.json({ error: "not signed in" }, 401);
|
|
@@ -11215,7 +11378,7 @@ function createAvatarsRouter() {
|
|
|
11215
11378
|
}
|
|
11216
11379
|
|
|
11217
11380
|
// src/dashboard/members-routes.ts
|
|
11218
|
-
import { Hono as
|
|
11381
|
+
import { Hono as Hono19 } from "hono";
|
|
11219
11382
|
function inviteLinkFor(c, token) {
|
|
11220
11383
|
const url = new URL("/auth/invite", callbackUrl(c));
|
|
11221
11384
|
url.searchParams.set("token", token);
|
|
@@ -11223,7 +11386,7 @@ function inviteLinkFor(c, token) {
|
|
|
11223
11386
|
}
|
|
11224
11387
|
var EMAIL_RE2 = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
11225
11388
|
function createMembersRouter() {
|
|
11226
|
-
const members = new
|
|
11389
|
+
const members = new Hono19();
|
|
11227
11390
|
members.get("/", async (c) => {
|
|
11228
11391
|
const ctx = await resolveAuthContext(c);
|
|
11229
11392
|
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
@@ -11407,7 +11570,7 @@ function createMembersRouter() {
|
|
|
11407
11570
|
}
|
|
11408
11571
|
|
|
11409
11572
|
// src/dashboard/notifications-routes.ts
|
|
11410
|
-
import { Hono as
|
|
11573
|
+
import { Hono as Hono20 } from "hono";
|
|
11411
11574
|
|
|
11412
11575
|
// src/store/notification-store.ts
|
|
11413
11576
|
function rowToNotification(row) {
|
|
@@ -11475,7 +11638,7 @@ async function markAllRead(channelIds, userId) {
|
|
|
11475
11638
|
|
|
11476
11639
|
// src/dashboard/notifications-routes.ts
|
|
11477
11640
|
function createNotificationsRouter() {
|
|
11478
|
-
const notifications = new
|
|
11641
|
+
const notifications = new Hono20();
|
|
11479
11642
|
notifications.get("/", async (c) => {
|
|
11480
11643
|
const auth = c.get("auth");
|
|
11481
11644
|
const channelIds = getProfileChannelRegistry().filter((ch) => canSeeChannel(ch, auth)).map((ch) => ch.id);
|
|
@@ -11502,7 +11665,7 @@ function createNotificationsRouter() {
|
|
|
11502
11665
|
return notifications;
|
|
11503
11666
|
}
|
|
11504
11667
|
function createThreadReadRouter() {
|
|
11505
|
-
const threads = new
|
|
11668
|
+
const threads = new Hono20();
|
|
11506
11669
|
threads.post("/threads/:threadId/read", async (c) => {
|
|
11507
11670
|
const { userId } = c.get("auth");
|
|
11508
11671
|
const threadId = c.req.param("threadId");
|
|
@@ -11514,7 +11677,7 @@ function createThreadReadRouter() {
|
|
|
11514
11677
|
}
|
|
11515
11678
|
|
|
11516
11679
|
// src/dashboard/push-routes.ts
|
|
11517
|
-
import { Hono as
|
|
11680
|
+
import { Hono as Hono21 } from "hono";
|
|
11518
11681
|
|
|
11519
11682
|
// src/push/send.ts
|
|
11520
11683
|
import webpush from "web-push";
|
|
@@ -11617,7 +11780,7 @@ async function pushToUser(userId, payload) {
|
|
|
11617
11780
|
|
|
11618
11781
|
// src/dashboard/push-routes.ts
|
|
11619
11782
|
function createPushRouter() {
|
|
11620
|
-
const push = new
|
|
11783
|
+
const push = new Hono21();
|
|
11621
11784
|
push.get("/key", (c) => c.json({ publicKey: getVapidPublicKey() }));
|
|
11622
11785
|
push.post("/subscribe", async (c) => {
|
|
11623
11786
|
const { userId } = c.get("auth");
|
|
@@ -11648,7 +11811,7 @@ function createPushRouter() {
|
|
|
11648
11811
|
}
|
|
11649
11812
|
|
|
11650
11813
|
// src/dashboard/mcp-oauth-routes.ts
|
|
11651
|
-
import { Hono as
|
|
11814
|
+
import { Hono as Hono22 } from "hono";
|
|
11652
11815
|
var CONNECTIONS_PAGE = "/settings/connections";
|
|
11653
11816
|
function callbackUri(c) {
|
|
11654
11817
|
const proto = c.req.header("x-forwarded-proto") ?? "http";
|
|
@@ -11663,7 +11826,7 @@ function backToDashboard(c, to, params) {
|
|
|
11663
11826
|
return c.redirect(`${target.pathname}${target.search}`);
|
|
11664
11827
|
}
|
|
11665
11828
|
function createMcpOAuthRouter() {
|
|
11666
|
-
const app = new
|
|
11829
|
+
const app = new Hono22();
|
|
11667
11830
|
app.get("/oauth/:provider/start", async (c) => {
|
|
11668
11831
|
const auth = c.get("auth");
|
|
11669
11832
|
if (auth.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
@@ -11790,7 +11953,7 @@ function createMcpOAuthRouter() {
|
|
|
11790
11953
|
}
|
|
11791
11954
|
|
|
11792
11955
|
// src/dashboard/mcp-routes.ts
|
|
11793
|
-
import { Hono as
|
|
11956
|
+
import { Hono as Hono23 } from "hono";
|
|
11794
11957
|
|
|
11795
11958
|
// src/mcp/transport.ts
|
|
11796
11959
|
var KINDS = ["inherit", "npx", "local", "http", "sse"];
|
|
@@ -11884,10 +12047,10 @@ function fromServerCommand(command) {
|
|
|
11884
12047
|
|
|
11885
12048
|
// src/dashboard/mcp-routes.ts
|
|
11886
12049
|
var CUSTOM_PROVIDER = "custom";
|
|
11887
|
-
function
|
|
12050
|
+
function requireAdmin3(auth) {
|
|
11888
12051
|
return auth?.role === "admin" ? null : "admin access required";
|
|
11889
12052
|
}
|
|
11890
|
-
function
|
|
12053
|
+
function toResponse4(connection, channelIds, providerCommand = null) {
|
|
11891
12054
|
return {
|
|
11892
12055
|
id: connection.id,
|
|
11893
12056
|
providerSlug: connection.providerSlug,
|
|
@@ -11920,9 +12083,9 @@ function bindingsByConnection() {
|
|
|
11920
12083
|
return map;
|
|
11921
12084
|
}
|
|
11922
12085
|
function createMcpRouter() {
|
|
11923
|
-
const app = new
|
|
12086
|
+
const app = new Hono23();
|
|
11924
12087
|
app.get("/providers", async (c) => {
|
|
11925
|
-
const denied =
|
|
12088
|
+
const denied = requireAdmin3(c.get("auth"));
|
|
11926
12089
|
if (denied) return c.json({ error: denied }, 403);
|
|
11927
12090
|
const providers = await listMcpProviders();
|
|
11928
12091
|
return c.json({
|
|
@@ -11940,7 +12103,7 @@ function createMcpRouter() {
|
|
|
11940
12103
|
});
|
|
11941
12104
|
app.get("/connections", async (c) => {
|
|
11942
12105
|
const auth = c.get("auth");
|
|
11943
|
-
const denied =
|
|
12106
|
+
const denied = requireAdmin3(auth);
|
|
11944
12107
|
if (denied) return c.json({ error: denied }, 403);
|
|
11945
12108
|
const connections = await listMcpConnections(auth.workspaceId);
|
|
11946
12109
|
const commands = new Map(
|
|
@@ -11949,13 +12112,13 @@ function createMcpRouter() {
|
|
|
11949
12112
|
const bindings = bindingsByConnection();
|
|
11950
12113
|
return c.json({
|
|
11951
12114
|
connections: connections.map(
|
|
11952
|
-
(conn) =>
|
|
12115
|
+
(conn) => toResponse4(conn, bindings.get(conn.id) ?? [], commands.get(conn.providerSlug) ?? null)
|
|
11953
12116
|
)
|
|
11954
12117
|
});
|
|
11955
12118
|
});
|
|
11956
12119
|
app.post("/connections", async (c) => {
|
|
11957
12120
|
const auth = c.get("auth");
|
|
11958
|
-
const denied =
|
|
12121
|
+
const denied = requireAdmin3(auth);
|
|
11959
12122
|
if (denied) return c.json({ error: denied }, 403);
|
|
11960
12123
|
const body = await c.req.json().catch(() => null);
|
|
11961
12124
|
const label2 = typeof body?.label === "string" ? body.label.trim() : "";
|
|
@@ -11981,7 +12144,7 @@ function createMcpRouter() {
|
|
|
11981
12144
|
serverCommand: toServerCommand(parsed.transport),
|
|
11982
12145
|
createdBy: auth.userId ?? null
|
|
11983
12146
|
});
|
|
11984
|
-
return c.json(
|
|
12147
|
+
return c.json(toResponse4(created, []), 201);
|
|
11985
12148
|
} catch (err) {
|
|
11986
12149
|
const message = err.message;
|
|
11987
12150
|
if (/duplicate key|unique/i.test(message)) {
|
|
@@ -11992,7 +12155,7 @@ function createMcpRouter() {
|
|
|
11992
12155
|
});
|
|
11993
12156
|
app.patch("/connections/:id", async (c) => {
|
|
11994
12157
|
const auth = c.get("auth");
|
|
11995
|
-
const denied =
|
|
12158
|
+
const denied = requireAdmin3(auth);
|
|
11996
12159
|
if (denied) return c.json({ error: denied }, 403);
|
|
11997
12160
|
const body = await c.req.json().catch(() => null);
|
|
11998
12161
|
if (!body) return c.json({ error: "a body is required" }, 400);
|
|
@@ -12018,12 +12181,12 @@ function createMcpRouter() {
|
|
|
12018
12181
|
const provider = await getMcpProvider(updated.providerSlug);
|
|
12019
12182
|
const bindings = bindingsByConnection();
|
|
12020
12183
|
return c.json(
|
|
12021
|
-
|
|
12184
|
+
toResponse4(updated, bindings.get(updated.id) ?? [], provider?.serverCommand ?? null)
|
|
12022
12185
|
);
|
|
12023
12186
|
});
|
|
12024
12187
|
app.put("/connections/:id/credential", async (c) => {
|
|
12025
12188
|
const auth = c.get("auth");
|
|
12026
|
-
const denied =
|
|
12189
|
+
const denied = requireAdmin3(auth);
|
|
12027
12190
|
if (denied) return c.json({ error: denied }, 403);
|
|
12028
12191
|
const body = await c.req.json().catch(() => null);
|
|
12029
12192
|
const raw = body?.apiKey;
|
|
@@ -12036,12 +12199,12 @@ function createMcpRouter() {
|
|
|
12036
12199
|
const provider = await getMcpProvider(updated.providerSlug);
|
|
12037
12200
|
const bindings = bindingsByConnection();
|
|
12038
12201
|
return c.json(
|
|
12039
|
-
|
|
12202
|
+
toResponse4(updated, bindings.get(updated.id) ?? [], provider?.serverCommand ?? null)
|
|
12040
12203
|
);
|
|
12041
12204
|
});
|
|
12042
12205
|
app.put("/connections/:id/channels", async (c) => {
|
|
12043
12206
|
const auth = c.get("auth");
|
|
12044
|
-
const denied =
|
|
12207
|
+
const denied = requireAdmin3(auth);
|
|
12045
12208
|
if (denied) return c.json({ error: denied }, 403);
|
|
12046
12209
|
const id = c.req.param("id");
|
|
12047
12210
|
const connection = await getMcpConnection(auth.workspaceId, id);
|
|
@@ -12064,11 +12227,11 @@ function createMcpRouter() {
|
|
|
12064
12227
|
} catch (err) {
|
|
12065
12228
|
return c.json({ error: err.message }, 500);
|
|
12066
12229
|
}
|
|
12067
|
-
return c.json(
|
|
12230
|
+
return c.json(toResponse4(connection, [...wanted]));
|
|
12068
12231
|
});
|
|
12069
12232
|
app.delete("/connections/:id", async (c) => {
|
|
12070
12233
|
const auth = c.get("auth");
|
|
12071
|
-
const denied =
|
|
12234
|
+
const denied = requireAdmin3(auth);
|
|
12072
12235
|
if (denied) return c.json({ error: denied }, 403);
|
|
12073
12236
|
const deleted = await deleteMcpConnection(auth.workspaceId, c.req.param("id"));
|
|
12074
12237
|
if (!deleted) return c.json({ error: "connection not found" }, 404);
|
|
@@ -12078,143 +12241,6 @@ function createMcpRouter() {
|
|
|
12078
12241
|
return app;
|
|
12079
12242
|
}
|
|
12080
12243
|
|
|
12081
|
-
// src/dashboard/output-styles-routes.ts
|
|
12082
|
-
import { Hono as Hono23 } from "hono";
|
|
12083
|
-
var BUILT_IN_OUTPUT_STYLES = [
|
|
12084
|
-
{
|
|
12085
|
-
slug: "default",
|
|
12086
|
-
name: "Default",
|
|
12087
|
-
description: "Balanced detail: enough context to act on, without padding."
|
|
12088
|
-
},
|
|
12089
|
-
{
|
|
12090
|
-
slug: "concise",
|
|
12091
|
-
name: "Concise",
|
|
12092
|
-
description: "The shortest answer that fully addresses the question."
|
|
12093
|
-
},
|
|
12094
|
-
{
|
|
12095
|
-
slug: "explanatory",
|
|
12096
|
-
name: "Explanatory",
|
|
12097
|
-
description: "Walks through the reasoning and trade-offs, not just the result."
|
|
12098
|
-
},
|
|
12099
|
-
{
|
|
12100
|
-
slug: "formal",
|
|
12101
|
-
name: "Formal",
|
|
12102
|
-
description: "Structured, businesslike phrasing with no casual asides."
|
|
12103
|
-
}
|
|
12104
|
-
];
|
|
12105
|
-
function requireAdmin3(auth) {
|
|
12106
|
-
return auth?.role === "admin" ? null : "admin access required";
|
|
12107
|
-
}
|
|
12108
|
-
function toResponse4(style) {
|
|
12109
|
-
return {
|
|
12110
|
-
id: style.id,
|
|
12111
|
-
name: style.name,
|
|
12112
|
-
description: style.description,
|
|
12113
|
-
body: style.body,
|
|
12114
|
-
createdAt: style.createdAt,
|
|
12115
|
-
updatedAt: style.updatedAt
|
|
12116
|
-
};
|
|
12117
|
-
}
|
|
12118
|
-
function createOutputStylesRouter() {
|
|
12119
|
-
const app = new Hono23();
|
|
12120
|
-
app.get("/", async (c) => {
|
|
12121
|
-
const auth = c.get("auth");
|
|
12122
|
-
const denied = requireAdmin3(auth);
|
|
12123
|
-
if (denied) return c.json({ error: denied }, 403);
|
|
12124
|
-
const [custom, defaultId] = await Promise.all([
|
|
12125
|
-
listOutputStyles(auth.workspaceId),
|
|
12126
|
-
getWorkspaceOutputStyleDefault(auth.workspaceId)
|
|
12127
|
-
]);
|
|
12128
|
-
return c.json({
|
|
12129
|
-
builtIn: BUILT_IN_OUTPUT_STYLES,
|
|
12130
|
-
custom: custom.map(toResponse4),
|
|
12131
|
-
defaultId
|
|
12132
|
-
});
|
|
12133
|
-
});
|
|
12134
|
-
app.post("/", async (c) => {
|
|
12135
|
-
const auth = c.get("auth");
|
|
12136
|
-
const denied = requireAdmin3(auth);
|
|
12137
|
-
if (denied) return c.json({ error: denied }, 403);
|
|
12138
|
-
const body = await c.req.json().catch(() => null);
|
|
12139
|
-
const name = typeof body?.name === "string" ? body.name.trim() : "";
|
|
12140
|
-
if (!name) return c.json({ error: "name is required" }, 400);
|
|
12141
|
-
const description = typeof body?.description === "string" ? body.description.trim() : "";
|
|
12142
|
-
const content = typeof body?.body === "string" ? body.body : "";
|
|
12143
|
-
try {
|
|
12144
|
-
const created = await insertOutputStyle({
|
|
12145
|
-
workspaceId: auth.workspaceId,
|
|
12146
|
-
name,
|
|
12147
|
-
description,
|
|
12148
|
-
body: content
|
|
12149
|
-
});
|
|
12150
|
-
return c.json(toResponse4(created), 201);
|
|
12151
|
-
} catch (err) {
|
|
12152
|
-
const message = err.message;
|
|
12153
|
-
if (/duplicate key|unique/i.test(message)) {
|
|
12154
|
-
return c.json({ error: `an output style named "${name}" already exists` }, 409);
|
|
12155
|
-
}
|
|
12156
|
-
return c.json({ error: message }, 500);
|
|
12157
|
-
}
|
|
12158
|
-
});
|
|
12159
|
-
app.put("/default", async (c) => {
|
|
12160
|
-
const auth = c.get("auth");
|
|
12161
|
-
const denied = requireAdmin3(auth);
|
|
12162
|
-
if (denied) return c.json({ error: denied }, 403);
|
|
12163
|
-
const body = await c.req.json().catch(() => null);
|
|
12164
|
-
const id = typeof body?.id === "string" ? body.id : "";
|
|
12165
|
-
if (!id) return c.json({ error: "id is required" }, 400);
|
|
12166
|
-
const isBuiltIn = BUILT_IN_OUTPUT_STYLES.some((s) => s.slug === id);
|
|
12167
|
-
if (!isBuiltIn && !await getOutputStyle(auth.workspaceId, id)) {
|
|
12168
|
-
return c.json({ error: "output style not found" }, 404);
|
|
12169
|
-
}
|
|
12170
|
-
await setWorkspaceOutputStyleDefault(auth.workspaceId, id);
|
|
12171
|
-
return c.json({ defaultId: id });
|
|
12172
|
-
});
|
|
12173
|
-
app.put("/:id", async (c) => {
|
|
12174
|
-
const auth = c.get("auth");
|
|
12175
|
-
const denied = requireAdmin3(auth);
|
|
12176
|
-
if (denied) return c.json({ error: denied }, 403);
|
|
12177
|
-
const body = await c.req.json().catch(() => null);
|
|
12178
|
-
if (!body) return c.json({ error: "a body is required" }, 400);
|
|
12179
|
-
const patch = {};
|
|
12180
|
-
if (body.name !== void 0) {
|
|
12181
|
-
const name = typeof body.name === "string" ? body.name.trim() : "";
|
|
12182
|
-
if (!name) return c.json({ error: "name cannot be empty" }, 400);
|
|
12183
|
-
patch.name = name;
|
|
12184
|
-
}
|
|
12185
|
-
if (body.description !== void 0) {
|
|
12186
|
-
patch.description = typeof body.description === "string" ? body.description.trim() : "";
|
|
12187
|
-
}
|
|
12188
|
-
if (body.body !== void 0) {
|
|
12189
|
-
patch.body = typeof body.body === "string" ? body.body : "";
|
|
12190
|
-
}
|
|
12191
|
-
if (!Object.keys(patch).length) return c.json({ error: "nothing to update" }, 400);
|
|
12192
|
-
try {
|
|
12193
|
-
const updated = await updateOutputStyle(auth.workspaceId, c.req.param("id"), patch);
|
|
12194
|
-
if (!updated) return c.json({ error: "output style not found" }, 404);
|
|
12195
|
-
return c.json(toResponse4(updated));
|
|
12196
|
-
} catch (err) {
|
|
12197
|
-
const message = err.message;
|
|
12198
|
-
if (/duplicate key|unique/i.test(message)) {
|
|
12199
|
-
return c.json({ error: `an output style named "${patch.name}" already exists` }, 409);
|
|
12200
|
-
}
|
|
12201
|
-
return c.json({ error: message }, 500);
|
|
12202
|
-
}
|
|
12203
|
-
});
|
|
12204
|
-
app.delete("/:id", async (c) => {
|
|
12205
|
-
const auth = c.get("auth");
|
|
12206
|
-
const denied = requireAdmin3(auth);
|
|
12207
|
-
if (denied) return c.json({ error: denied }, 403);
|
|
12208
|
-
const id = c.req.param("id");
|
|
12209
|
-
const currentDefault = await getWorkspaceOutputStyleDefault(auth.workspaceId);
|
|
12210
|
-
const deleted = await deleteOutputStyle(auth.workspaceId, id);
|
|
12211
|
-
if (!deleted) return c.json({ error: "output style not found" }, 404);
|
|
12212
|
-
if (currentDefault === id) await setWorkspaceOutputStyleDefault(auth.workspaceId, "default");
|
|
12213
|
-
return c.json({ ok: true });
|
|
12214
|
-
});
|
|
12215
|
-
return app;
|
|
12216
|
-
}
|
|
12217
|
-
|
|
12218
12244
|
// src/store/usage-query.ts
|
|
12219
12245
|
var COLUMNS = "ran_at, channel_id, thread_ts, user_id, source, model, provider, label, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens, cost_usd, num_turns, is_error";
|
|
12220
12246
|
var EMPTY_TOTALS = {
|
|
@@ -12522,14 +12548,320 @@ function createChannelMembersRouter() {
|
|
|
12522
12548
|
return router;
|
|
12523
12549
|
}
|
|
12524
12550
|
|
|
12551
|
+
// src/dashboard/onboarding-routes.ts
|
|
12552
|
+
import { Hono as Hono25 } from "hono";
|
|
12553
|
+
|
|
12554
|
+
// src/dashboard/onboarding-scan.ts
|
|
12555
|
+
import { execFileSync as execFileSync2 } from "child_process";
|
|
12556
|
+
import {
|
|
12557
|
+
existsSync as existsSync22,
|
|
12558
|
+
lstatSync as lstatSync2,
|
|
12559
|
+
mkdirSync as mkdirSync6,
|
|
12560
|
+
readdirSync as readdirSync4,
|
|
12561
|
+
readFileSync as readFileSync11,
|
|
12562
|
+
statSync as statSync8,
|
|
12563
|
+
symlinkSync
|
|
12564
|
+
} from "fs";
|
|
12565
|
+
import { homedir as homedir7 } from "os";
|
|
12566
|
+
import { basename as basename5, join as join16, resolve as resolve6 } from "path";
|
|
12567
|
+
var MAX_JSONL_HEAD_BYTES = 64 * 1024;
|
|
12568
|
+
var GIT_SCAN_ROOTS = ["Workspaces", "Projects", "code"];
|
|
12569
|
+
function flyspaceRoot() {
|
|
12570
|
+
return process.env["FLYSPACE_ROOT"] || join16(homedir7(), "Flyspace");
|
|
12571
|
+
}
|
|
12572
|
+
function onPath(cmd) {
|
|
12573
|
+
try {
|
|
12574
|
+
execFileSync2(process.platform === "win32" ? "where" : "which", [cmd], { stdio: "ignore" });
|
|
12575
|
+
return true;
|
|
12576
|
+
} catch {
|
|
12577
|
+
return false;
|
|
12578
|
+
}
|
|
12579
|
+
}
|
|
12580
|
+
function claudeDir() {
|
|
12581
|
+
return process.env["CLAUDE_CONFIG_DIR"] || join16(homedir7(), ".claude");
|
|
12582
|
+
}
|
|
12583
|
+
function codexDir() {
|
|
12584
|
+
return process.env["CODEX_HOME"] || join16(homedir7(), ".codex");
|
|
12585
|
+
}
|
|
12586
|
+
function claudeLoggedIn() {
|
|
12587
|
+
if (process.env["ANTHROPIC_API_KEY"]) return true;
|
|
12588
|
+
if (existsSync22(join16(claudeDir(), ".credentials.json"))) return true;
|
|
12589
|
+
try {
|
|
12590
|
+
const raw = JSON.parse(readFileSync11(join16(homedir7(), ".claude.json"), "utf-8"));
|
|
12591
|
+
if (raw.oauthAccount) return true;
|
|
12592
|
+
} catch {
|
|
12593
|
+
}
|
|
12594
|
+
if (process.platform !== "darwin") return false;
|
|
12595
|
+
try {
|
|
12596
|
+
execFileSync2("security", ["find-generic-password", "-s", "Claude Code-credentials"], {
|
|
12597
|
+
stdio: "ignore"
|
|
12598
|
+
});
|
|
12599
|
+
return true;
|
|
12600
|
+
} catch {
|
|
12601
|
+
return false;
|
|
12602
|
+
}
|
|
12603
|
+
}
|
|
12604
|
+
function codexLoggedIn() {
|
|
12605
|
+
return !!process.env["OPENAI_API_KEY"] || existsSync22(join16(codexDir(), "auth.json"));
|
|
12606
|
+
}
|
|
12607
|
+
function jsonlFiles(dir, depth) {
|
|
12608
|
+
const out = [];
|
|
12609
|
+
let entries2;
|
|
12610
|
+
try {
|
|
12611
|
+
entries2 = readdirSync4(dir, { withFileTypes: true });
|
|
12612
|
+
} catch {
|
|
12613
|
+
return out;
|
|
12614
|
+
}
|
|
12615
|
+
for (const e of entries2) {
|
|
12616
|
+
const full = join16(dir, e.name);
|
|
12617
|
+
if (e.isDirectory()) {
|
|
12618
|
+
if (depth > 0) out.push(...jsonlFiles(full, depth - 1));
|
|
12619
|
+
} else if (e.name.endsWith(".jsonl")) {
|
|
12620
|
+
try {
|
|
12621
|
+
out.push({ file: full, mtime: statSync8(full).mtimeMs });
|
|
12622
|
+
} catch {
|
|
12623
|
+
}
|
|
12624
|
+
}
|
|
12625
|
+
}
|
|
12626
|
+
return out;
|
|
12627
|
+
}
|
|
12628
|
+
function cwdFromTranscript(file) {
|
|
12629
|
+
let head;
|
|
12630
|
+
try {
|
|
12631
|
+
head = readFileSync11(file, { encoding: "utf-8", flag: "r" }).slice(0, MAX_JSONL_HEAD_BYTES);
|
|
12632
|
+
} catch {
|
|
12633
|
+
return null;
|
|
12634
|
+
}
|
|
12635
|
+
for (const line of head.split("\n")) {
|
|
12636
|
+
if (!line.includes('"cwd"')) continue;
|
|
12637
|
+
try {
|
|
12638
|
+
const obj = JSON.parse(line);
|
|
12639
|
+
const cwd = obj.cwd ?? obj.payload?.cwd;
|
|
12640
|
+
if (typeof cwd === "string" && cwd) return cwd;
|
|
12641
|
+
} catch {
|
|
12642
|
+
}
|
|
12643
|
+
}
|
|
12644
|
+
return null;
|
|
12645
|
+
}
|
|
12646
|
+
function codexTrustedPaths() {
|
|
12647
|
+
try {
|
|
12648
|
+
const toml = readFileSync11(join16(codexDir(), "config.toml"), "utf-8");
|
|
12649
|
+
return [...toml.matchAll(/^\[projects\.(?:"([^"]+)"|'([^']+)')\]/gm)].map((m) => m[1] ?? m[2]);
|
|
12650
|
+
} catch {
|
|
12651
|
+
return [];
|
|
12652
|
+
}
|
|
12653
|
+
}
|
|
12654
|
+
function isExcluded(path) {
|
|
12655
|
+
const home = homedir7();
|
|
12656
|
+
if (path === home || path === "/") return true;
|
|
12657
|
+
if (path === "/tmp" || path.startsWith("/tmp/")) return true;
|
|
12658
|
+
if (path === "/private/tmp" || path.startsWith("/private/tmp/")) return true;
|
|
12659
|
+
const root = flyspaceRoot();
|
|
12660
|
+
if (path === root || path.startsWith(`${root}/`)) return true;
|
|
12661
|
+
try {
|
|
12662
|
+
return !statSync8(path).isDirectory();
|
|
12663
|
+
} catch {
|
|
12664
|
+
return true;
|
|
12665
|
+
}
|
|
12666
|
+
}
|
|
12667
|
+
function gitRepos() {
|
|
12668
|
+
const home = homedir7();
|
|
12669
|
+
const found = [];
|
|
12670
|
+
for (const rootName of GIT_SCAN_ROOTS) {
|
|
12671
|
+
const root = join16(home, rootName);
|
|
12672
|
+
let entries2;
|
|
12673
|
+
try {
|
|
12674
|
+
entries2 = readdirSync4(root, { withFileTypes: true });
|
|
12675
|
+
} catch {
|
|
12676
|
+
continue;
|
|
12677
|
+
}
|
|
12678
|
+
for (const e of entries2) {
|
|
12679
|
+
if (e.isDirectory() && existsSync22(join16(root, e.name, ".git"))) found.push(join16(root, e.name));
|
|
12680
|
+
}
|
|
12681
|
+
}
|
|
12682
|
+
return found;
|
|
12683
|
+
}
|
|
12684
|
+
function detectRunners(history = { claude: false, codex: false }) {
|
|
12685
|
+
return [
|
|
12686
|
+
{
|
|
12687
|
+
id: "claude",
|
|
12688
|
+
installed: onPath("claude"),
|
|
12689
|
+
loggedIn: claudeLoggedIn(),
|
|
12690
|
+
hasHistory: history.claude
|
|
12691
|
+
},
|
|
12692
|
+
{
|
|
12693
|
+
id: "codex",
|
|
12694
|
+
installed: onPath("codex"),
|
|
12695
|
+
loggedIn: codexLoggedIn(),
|
|
12696
|
+
hasHistory: history.codex
|
|
12697
|
+
}
|
|
12698
|
+
];
|
|
12699
|
+
}
|
|
12700
|
+
function scanOnboarding() {
|
|
12701
|
+
const byPath = /* @__PURE__ */ new Map();
|
|
12702
|
+
const history = { claude: false, codex: false };
|
|
12703
|
+
const add = (rawPath, source, mtime, sessions, trusted = false) => {
|
|
12704
|
+
const path = resolve6(rawPath);
|
|
12705
|
+
if (isExcluded(path)) return;
|
|
12706
|
+
const existing = byPath.get(path);
|
|
12707
|
+
if (existing) {
|
|
12708
|
+
if (!existing.sources.includes(source)) existing.sources.push(source);
|
|
12709
|
+
existing.lastUsed = Math.max(existing.lastUsed, mtime);
|
|
12710
|
+
existing.sessions += sessions;
|
|
12711
|
+
existing.trusted ||= trusted;
|
|
12712
|
+
} else {
|
|
12713
|
+
byPath.set(path, {
|
|
12714
|
+
path,
|
|
12715
|
+
name: basename5(path),
|
|
12716
|
+
sources: [source],
|
|
12717
|
+
lastUsed: mtime,
|
|
12718
|
+
sessions,
|
|
12719
|
+
trusted
|
|
12720
|
+
});
|
|
12721
|
+
}
|
|
12722
|
+
};
|
|
12723
|
+
const claudeProjects = join16(claudeDir(), "projects");
|
|
12724
|
+
try {
|
|
12725
|
+
for (const e of readdirSync4(claudeProjects, { withFileTypes: true })) {
|
|
12726
|
+
if (!e.isDirectory()) continue;
|
|
12727
|
+
const files = jsonlFiles(join16(claudeProjects, e.name), 0);
|
|
12728
|
+
if (!files.length) continue;
|
|
12729
|
+
history.claude = true;
|
|
12730
|
+
files.sort((a, b) => b.mtime - a.mtime);
|
|
12731
|
+
let cwd = null;
|
|
12732
|
+
for (const f of files.slice(0, 3)) {
|
|
12733
|
+
cwd = cwdFromTranscript(f.file);
|
|
12734
|
+
if (cwd) break;
|
|
12735
|
+
}
|
|
12736
|
+
if (cwd) add(cwd, "claude", files[0].mtime, files.length);
|
|
12737
|
+
}
|
|
12738
|
+
} catch {
|
|
12739
|
+
}
|
|
12740
|
+
const codexSessions = jsonlFiles(join16(codexDir(), "sessions"), 4);
|
|
12741
|
+
if (codexSessions.length) history.codex = true;
|
|
12742
|
+
const perCwd = /* @__PURE__ */ new Map();
|
|
12743
|
+
for (const f of codexSessions) {
|
|
12744
|
+
const cwd = cwdFromTranscript(f.file);
|
|
12745
|
+
if (!cwd) continue;
|
|
12746
|
+
const cur = perCwd.get(cwd) ?? { mtime: 0, count: 0 };
|
|
12747
|
+
cur.mtime = Math.max(cur.mtime, f.mtime);
|
|
12748
|
+
cur.count += 1;
|
|
12749
|
+
perCwd.set(cwd, cur);
|
|
12750
|
+
}
|
|
12751
|
+
for (const [cwd, v] of perCwd) add(cwd, "codex", v.mtime, v.count);
|
|
12752
|
+
for (const p of codexTrustedPaths()) add(p, "codex", 0, 0, true);
|
|
12753
|
+
let projects = [...byPath.values()];
|
|
12754
|
+
let fromGitScan = false;
|
|
12755
|
+
if (!projects.length) {
|
|
12756
|
+
fromGitScan = true;
|
|
12757
|
+
for (const repo of gitRepos()) {
|
|
12758
|
+
let mtime = 0;
|
|
12759
|
+
try {
|
|
12760
|
+
mtime = statSync8(repo).mtimeMs;
|
|
12761
|
+
} catch {
|
|
12762
|
+
}
|
|
12763
|
+
add(repo, "git", mtime, 0);
|
|
12764
|
+
}
|
|
12765
|
+
projects = [...byPath.values()];
|
|
12766
|
+
}
|
|
12767
|
+
projects.sort(
|
|
12768
|
+
(a, b) => Number(b.sources.length > 1) - Number(a.sources.length > 1) || Number(b.trusted) - Number(a.trusted) || b.lastUsed - a.lastUsed || b.sessions - a.sessions
|
|
12769
|
+
);
|
|
12770
|
+
return {
|
|
12771
|
+
root: flyspaceRoot(),
|
|
12772
|
+
runners: detectRunners(history),
|
|
12773
|
+
projects,
|
|
12774
|
+
fromGitScan
|
|
12775
|
+
};
|
|
12776
|
+
}
|
|
12777
|
+
function uniqueChannelName(raw, taken) {
|
|
12778
|
+
const base = raw.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
|
|
12779
|
+
let name = base;
|
|
12780
|
+
for (let i = 2; taken.has(name); i += 1) name = `${base}-${i}`;
|
|
12781
|
+
taken.add(name);
|
|
12782
|
+
return name;
|
|
12783
|
+
}
|
|
12784
|
+
function makeLink(target, linkPath) {
|
|
12785
|
+
symlinkSync(target, linkPath, process.platform === "win32" ? "junction" : "dir");
|
|
12786
|
+
}
|
|
12787
|
+
function linkProjectFolders(slug, items) {
|
|
12788
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(slug)) throw new Error(`invalid workspace slug: ${slug}`);
|
|
12789
|
+
const dir = join16(flyspaceRoot(), slug);
|
|
12790
|
+
mkdirSync6(dir, { recursive: true });
|
|
12791
|
+
const results = [];
|
|
12792
|
+
for (const item of items) {
|
|
12793
|
+
if (!/^[a-z0-9][a-z0-9-]*$/.test(item.name)) throw new Error(`invalid channel name: ${item.name}`);
|
|
12794
|
+
const dest = join16(dir, item.name);
|
|
12795
|
+
let exists = true;
|
|
12796
|
+
try {
|
|
12797
|
+
lstatSync2(dest);
|
|
12798
|
+
} catch {
|
|
12799
|
+
exists = false;
|
|
12800
|
+
}
|
|
12801
|
+
if (!exists) {
|
|
12802
|
+
if (item.path) {
|
|
12803
|
+
const target = resolve6(item.path);
|
|
12804
|
+
if (!statSync8(target).isDirectory()) throw new Error(`not a directory: ${target}`);
|
|
12805
|
+
makeLink(target, dest);
|
|
12806
|
+
} else {
|
|
12807
|
+
mkdirSync6(dest, { recursive: true });
|
|
12808
|
+
}
|
|
12809
|
+
}
|
|
12810
|
+
results.push({ name: item.name, cwd: `${slug}/${item.name}` });
|
|
12811
|
+
}
|
|
12812
|
+
return results;
|
|
12813
|
+
}
|
|
12814
|
+
|
|
12815
|
+
// src/dashboard/onboarding-routes.ts
|
|
12816
|
+
var MAX_FOLDERS = 20;
|
|
12817
|
+
function createOnboardingRouter(agent) {
|
|
12818
|
+
const app = new Hono25();
|
|
12819
|
+
app.get("/scan", async (c) => {
|
|
12820
|
+
const ctx = await resolveAuthContext(c);
|
|
12821
|
+
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
12822
|
+
const scan = await agent.scanOnboarding?.();
|
|
12823
|
+
if (!scan) return c.json({ error: "host offline" }, 503);
|
|
12824
|
+
return c.json(scan);
|
|
12825
|
+
});
|
|
12826
|
+
app.post("/channels", async (c) => {
|
|
12827
|
+
const ctx = await resolveAuthContext(c);
|
|
12828
|
+
if (ctx?.role !== "admin") return c.json({ error: "admin access required" }, 403);
|
|
12829
|
+
const body = await c.req.json().catch(() => ({}));
|
|
12830
|
+
const runner = body["agent"];
|
|
12831
|
+
const folders = Array.isArray(body["folders"]) ? body["folders"].filter((f) => typeof f === "string" && f.length > 0) : [];
|
|
12832
|
+
if (folders.length > MAX_FOLDERS) return c.json({ error: `at most ${MAX_FOLDERS} folders` }, 400);
|
|
12833
|
+
const scan = await agent.scanOnboarding?.();
|
|
12834
|
+
if (!scan) return c.json({ error: "host offline" }, 503);
|
|
12835
|
+
const offered = new Map(scan.projects.map((p) => [p.path, p]));
|
|
12836
|
+
const unknown = folders.find((f) => !offered.has(f));
|
|
12837
|
+
if (unknown) return c.json({ error: "folder was not offered by the scan" }, 400);
|
|
12838
|
+
const taken = new Set(getProfileChannelRegistry().map((ch) => ch.name));
|
|
12839
|
+
const items = [];
|
|
12840
|
+
if (!taken.has("general")) {
|
|
12841
|
+
taken.add("general");
|
|
12842
|
+
items.push({ name: "general" });
|
|
12843
|
+
}
|
|
12844
|
+
for (const path of folders) items.push({ name: uniqueChannelName(offered.get(path).name, taken), path });
|
|
12845
|
+
const linked = await agent.linkProjectFolders?.(getProfile(), items);
|
|
12846
|
+
if (!linked) return c.json({ error: "host offline" }, 503);
|
|
12847
|
+
if (runner === "claude" || runner === "codex") {
|
|
12848
|
+
await agent.saveLocalDashboardSettings?.({ providers: [{ runner }] });
|
|
12849
|
+
}
|
|
12850
|
+
const created = [];
|
|
12851
|
+
for (const l of linked) created.push(await createChannel({ name: l.name, cwd: l.cwd }));
|
|
12852
|
+
return c.json({ channels: created.map((ch) => ({ id: ch.id, name: ch.name })) }, 201);
|
|
12853
|
+
});
|
|
12854
|
+
return app;
|
|
12855
|
+
}
|
|
12856
|
+
|
|
12525
12857
|
// src/dashboard/host-status-signal.ts
|
|
12526
12858
|
var HOST_STATUS_CHANNEL = "host-status";
|
|
12527
12859
|
var channelReady = null;
|
|
12528
12860
|
function getHostStatusChannel() {
|
|
12529
|
-
channelReady ??= new Promise((
|
|
12861
|
+
channelReady ??= new Promise((resolve8) => {
|
|
12530
12862
|
const channel = getSupabaseClient().channel(HOST_STATUS_CHANNEL);
|
|
12531
12863
|
channel.subscribe((state) => {
|
|
12532
|
-
if (state === "SUBSCRIBED")
|
|
12864
|
+
if (state === "SUBSCRIBED") resolve8(channel);
|
|
12533
12865
|
});
|
|
12534
12866
|
});
|
|
12535
12867
|
return channelReady;
|
|
@@ -12630,10 +12962,10 @@ async function resolveThreadLinks(sessions) {
|
|
|
12630
12962
|
function registerDataRoutes(api, agent = {}) {
|
|
12631
12963
|
api.get("/profile", (c) => c.json({ profile: getProfile() }));
|
|
12632
12964
|
api.get("/host/status", async (c) => {
|
|
12633
|
-
const online = agent.isHostOnline?.() ?? false;
|
|
12965
|
+
const online = agent.isHostOnline?.(getProfile()) ?? false;
|
|
12634
12966
|
if (!online) {
|
|
12635
|
-
const
|
|
12636
|
-
return c.json({ online: false, reason:
|
|
12967
|
+
const failure2 = agent.getHostOfflineReason?.() ?? null;
|
|
12968
|
+
return c.json({ online: false, reason: failure2?.reason ?? null, since: failure2?.since ?? null });
|
|
12637
12969
|
}
|
|
12638
12970
|
const auth = c.get("auth");
|
|
12639
12971
|
const paused = await getAgentPaused(auth.workspaceId);
|
|
@@ -12678,6 +13010,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
12678
13010
|
return c.json(await buildChannelUsageReport(c.req.param("id"), days, channelLabel));
|
|
12679
13011
|
});
|
|
12680
13012
|
api.route("/channels", createDocShareWriteRouter(agent));
|
|
13013
|
+
api.route("/onboarding", createOnboardingRouter(agent));
|
|
12681
13014
|
api.route("/channels", createChannelMembersRouter());
|
|
12682
13015
|
api.route("/docs", createDocShareListRouter());
|
|
12683
13016
|
api.get("/users", (c) => c.json({ users: listActiveUsers() }));
|
|
@@ -12690,7 +13023,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
12690
13023
|
runs.set(name, { lastRun: run3.lastRunAt.toISOString(), lastStatus: run3.lastStatus });
|
|
12691
13024
|
}
|
|
12692
13025
|
} catch {
|
|
12693
|
-
if (
|
|
13026
|
+
if (existsSync23(paths.DB_FILE)) {
|
|
12694
13027
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
12695
13028
|
try {
|
|
12696
13029
|
for (const run3 of runStore.listLastRuns()) {
|
|
@@ -12752,7 +13085,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
12752
13085
|
const runs = (await recentRunsForWorkspace(getProfile(), 200)).map(toResponse5);
|
|
12753
13086
|
return c.json({ runs });
|
|
12754
13087
|
} catch {
|
|
12755
|
-
if (!
|
|
13088
|
+
if (!existsSync23(paths.DB_FILE)) return c.json({ runs: [] });
|
|
12756
13089
|
const runStore = new AutomationRunStore(paths.DB_FILE);
|
|
12757
13090
|
try {
|
|
12758
13091
|
const runs = runStore.listRuns(200).map(toResponse5);
|
|
@@ -12799,7 +13132,6 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
12799
13132
|
});
|
|
12800
13133
|
api.route("/mcp", createMcpOAuthRouter());
|
|
12801
13134
|
api.route("/mcp", createMcpRouter());
|
|
12802
|
-
api.route("/output-styles", createOutputStylesRouter());
|
|
12803
13135
|
api.get("/agent-pause", async (c) => {
|
|
12804
13136
|
const auth = c.get("auth");
|
|
12805
13137
|
return c.json({ paused: await getAgentPaused(auth.workspaceId) });
|
|
@@ -12868,7 +13200,7 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
12868
13200
|
} catch {
|
|
12869
13201
|
latest = /* @__PURE__ */ new Map();
|
|
12870
13202
|
totals = /* @__PURE__ */ new Map();
|
|
12871
|
-
if (
|
|
13203
|
+
if (existsSync23(paths.DB_FILE)) {
|
|
12872
13204
|
const usageStore = new UsageStore(paths.DB_FILE);
|
|
12873
13205
|
try {
|
|
12874
13206
|
latest = usageStore.latestByThread(threadTsList);
|
|
@@ -12923,28 +13255,28 @@ function registerDataRoutes(api, agent = {}) {
|
|
|
12923
13255
|
// src/dashboard/device-auth-routes.ts
|
|
12924
13256
|
import { verify } from "crypto";
|
|
12925
13257
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
12926
|
-
import { Hono as
|
|
13258
|
+
import { Hono as Hono26 } from "hono";
|
|
12927
13259
|
|
|
12928
13260
|
// src/dashboard/agent-bridge.ts
|
|
12929
13261
|
import { randomBytes as randomBytes7, randomUUID as randomUUID3, sign as signRaw, verify as verifyRaw } from "crypto";
|
|
12930
|
-
import { existsSync as
|
|
13262
|
+
import { existsSync as existsSync26, readFileSync as readFileSync15 } from "fs";
|
|
12931
13263
|
import dotenv from "dotenv";
|
|
12932
13264
|
import { WebSocket, WebSocketServer } from "ws";
|
|
12933
13265
|
|
|
12934
13266
|
// src/dashboard/host-identity.ts
|
|
12935
13267
|
import { createPrivateKey, createPublicKey, generateKeyPairSync } from "crypto";
|
|
12936
|
-
import { existsSync as
|
|
12937
|
-
import { dirname as dirname5, join as
|
|
13268
|
+
import { existsSync as existsSync24, mkdirSync as mkdirSync7, readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
13269
|
+
import { dirname as dirname5, join as join17 } from "path";
|
|
12938
13270
|
function hostPrivateKeyPath() {
|
|
12939
|
-
return process.env["FLYSPACE_HOST_KEY_PATH"] ||
|
|
13271
|
+
return process.env["FLYSPACE_HOST_KEY_PATH"] || join17(paths.BASE_CONFIG_DIR, "host.key");
|
|
12940
13272
|
}
|
|
12941
13273
|
function loadHostPrivateKey(path = hostPrivateKeyPath()) {
|
|
12942
|
-
if (!
|
|
13274
|
+
if (!existsSync24(path)) {
|
|
12943
13275
|
throw new Error(
|
|
12944
13276
|
`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.`
|
|
12945
13277
|
);
|
|
12946
13278
|
}
|
|
12947
|
-
return createPrivateKey(
|
|
13279
|
+
return createPrivateKey(readFileSync12(path, "utf8"));
|
|
12948
13280
|
}
|
|
12949
13281
|
function loadHostPublicKey() {
|
|
12950
13282
|
const b64 = process.env["FLYSPACE_HOST_PUBLIC_KEY"];
|
|
@@ -13146,8 +13478,8 @@ async function userEmail(userId) {
|
|
|
13146
13478
|
|
|
13147
13479
|
// src/auth/device-auth.ts
|
|
13148
13480
|
import { createPrivateKey as createPrivateKey2, createPublicKey as createPublicKey3, generateKeyPairSync as generateKeyPairSync2, sign } from "crypto";
|
|
13149
|
-
import { existsSync as
|
|
13150
|
-
import { dirname as dirname6, join as
|
|
13481
|
+
import { existsSync as existsSync25, mkdirSync as mkdirSync8, readFileSync as readFileSync13, rmSync as rmSync4, writeFileSync as writeFileSync8 } from "fs";
|
|
13482
|
+
import { dirname as dirname6, join as join18 } from "path";
|
|
13151
13483
|
var DEFAULT_API_URL = "https://app.flyspace.ai";
|
|
13152
13484
|
var EXPIRY_SKEW_MS = 6e4;
|
|
13153
13485
|
var AuthApiError = class extends Error {
|
|
@@ -13161,35 +13493,35 @@ function apiUrl() {
|
|
|
13161
13493
|
return (process.env["FLYSPACE_API_URL"] || DEFAULT_API_URL).replace(/\/+$/, "");
|
|
13162
13494
|
}
|
|
13163
13495
|
function deviceKeyPath() {
|
|
13164
|
-
return
|
|
13496
|
+
return join18(paths.BASE_CONFIG_DIR, "device.key");
|
|
13165
13497
|
}
|
|
13166
13498
|
function credentialsPath() {
|
|
13167
|
-
return
|
|
13499
|
+
return join18(paths.BASE_CONFIG_DIR, "credentials.json");
|
|
13168
13500
|
}
|
|
13169
13501
|
function ensureDeviceKey(path = deviceKeyPath()) {
|
|
13170
13502
|
let created = false;
|
|
13171
|
-
if (!
|
|
13503
|
+
if (!existsSync25(path)) {
|
|
13172
13504
|
const { privateKey: privateKey2 } = generateKeyPairSync2("ed25519");
|
|
13173
|
-
|
|
13505
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
13174
13506
|
writeFileSync8(path, privateKey2.export({ type: "pkcs8", format: "pem" }), { mode: 384 });
|
|
13175
13507
|
created = true;
|
|
13176
13508
|
}
|
|
13177
|
-
const privateKey = createPrivateKey2(
|
|
13509
|
+
const privateKey = createPrivateKey2(readFileSync13(path, "utf8"));
|
|
13178
13510
|
const publicKeyBase64 = createPublicKey3(privateKey).export({ type: "spki", format: "der" }).toString(
|
|
13179
13511
|
"base64"
|
|
13180
13512
|
);
|
|
13181
13513
|
return { privateKey, publicKeyBase64, created };
|
|
13182
13514
|
}
|
|
13183
13515
|
function loadCredentials(path = credentialsPath()) {
|
|
13184
|
-
if (!
|
|
13516
|
+
if (!existsSync25(path)) return null;
|
|
13185
13517
|
try {
|
|
13186
|
-
return JSON.parse(
|
|
13518
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
13187
13519
|
} catch {
|
|
13188
13520
|
return null;
|
|
13189
13521
|
}
|
|
13190
13522
|
}
|
|
13191
13523
|
function saveCredentials(creds, path = credentialsPath()) {
|
|
13192
|
-
|
|
13524
|
+
mkdirSync8(dirname6(path), { recursive: true });
|
|
13193
13525
|
writeFileSync8(path, `${JSON.stringify(creds, null, 2)}
|
|
13194
13526
|
`, { mode: 384 });
|
|
13195
13527
|
}
|
|
@@ -13313,7 +13645,7 @@ async function getAccessToken() {
|
|
|
13313
13645
|
|
|
13314
13646
|
// src/dashboard/doc-share-publisher.ts
|
|
13315
13647
|
import { randomBytes as randomBytes6 } from "crypto";
|
|
13316
|
-
import { readFileSync as
|
|
13648
|
+
import { readFileSync as readFileSync14, statSync as statSync9 } from "fs";
|
|
13317
13649
|
import { extname as extname2, posix } from "path";
|
|
13318
13650
|
import { CopyObjectCommand, PutObjectCommand as PutObjectCommand5 } from "@aws-sdk/client-s3";
|
|
13319
13651
|
|
|
@@ -13493,13 +13825,13 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
13493
13825
|
if (!kind) return { status: "unsupported" };
|
|
13494
13826
|
let stat;
|
|
13495
13827
|
try {
|
|
13496
|
-
stat =
|
|
13828
|
+
stat = statSync9(abs);
|
|
13497
13829
|
} catch {
|
|
13498
13830
|
return { status: "not-found" };
|
|
13499
13831
|
}
|
|
13500
13832
|
if (!stat.isFile()) return { status: "not-found" };
|
|
13501
13833
|
if (stat.size > MAX_CONTENT_BYTES2) return { status: "too-large" };
|
|
13502
|
-
const source =
|
|
13834
|
+
const source = readFileSync14(abs, "utf8");
|
|
13503
13835
|
const title = extractTitle(abs, kind === "html" ? "html" : "md");
|
|
13504
13836
|
const docDir = sourcePath.includes("/") ? sourcePath.slice(0, sourcePath.lastIndexOf("/")) : "";
|
|
13505
13837
|
const client2 = getR2Client();
|
|
@@ -13569,8 +13901,8 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
13569
13901
|
const budget = Math.min(MAX_INLINE_BYTES, MAX_TOTAL_INLINE_BYTES - inlinedBytes);
|
|
13570
13902
|
let text = null;
|
|
13571
13903
|
try {
|
|
13572
|
-
const size =
|
|
13573
|
-
if (size <= budget) text =
|
|
13904
|
+
const size = statSync9(docAbs).size;
|
|
13905
|
+
if (size <= budget) text = readFileSync14(docAbs, "utf8");
|
|
13574
13906
|
} catch {
|
|
13575
13907
|
continue;
|
|
13576
13908
|
}
|
|
@@ -13586,8 +13918,8 @@ async function buildSnapshot(cwd, sourcePath, shareId) {
|
|
|
13586
13918
|
if (!resolved2) continue;
|
|
13587
13919
|
let body;
|
|
13588
13920
|
try {
|
|
13589
|
-
if (
|
|
13590
|
-
body =
|
|
13921
|
+
if (statSync9(resolved2.abs).size > MAX_ASSET_BYTES) continue;
|
|
13922
|
+
body = readFileSync14(resolved2.abs);
|
|
13591
13923
|
} catch {
|
|
13592
13924
|
continue;
|
|
13593
13925
|
}
|
|
@@ -13661,11 +13993,15 @@ async function publishDocShare(input2) {
|
|
|
13661
13993
|
// src/dashboard/agent-bridge.ts
|
|
13662
13994
|
var HOST_SOCKET_PATH = "/internal/host-socket";
|
|
13663
13995
|
var DEFAULT_DATA_API_PORT = 4757;
|
|
13996
|
+
var HOSTED_DATA_SOCKET_ORIGIN = "wss://flyspace-api.fly.dev";
|
|
13664
13997
|
function dataSocketUrl() {
|
|
13665
|
-
|
|
13998
|
+
const explicit = process.env["FLYSPACE_DATA_SOCKET_URL"];
|
|
13999
|
+
if (explicit) return explicit;
|
|
14000
|
+
if (!process.env["FLYSPACE_DATA_API_PORT"] && loadCredentials()) return `${HOSTED_DATA_SOCKET_ORIGIN}${HOST_SOCKET_PATH}`;
|
|
14001
|
+
return `ws://127.0.0.1:${process.env["FLYSPACE_DATA_API_PORT"] ?? DEFAULT_DATA_API_PORT}${HOST_SOCKET_PATH}`;
|
|
13666
14002
|
}
|
|
13667
14003
|
function listLocalSessions() {
|
|
13668
|
-
if (!
|
|
14004
|
+
if (!existsSync26(paths.DB_FILE)) return [];
|
|
13669
14005
|
const store = new SessionStore(paths.DB_FILE);
|
|
13670
14006
|
try {
|
|
13671
14007
|
return store.listSessions();
|
|
@@ -13691,6 +14027,8 @@ function localAgentCallbacks(chatAgent) {
|
|
|
13691
14027
|
saveLocalDashboardSettings: (patch) => saveDashboardSettings(patch),
|
|
13692
14028
|
writeSkill: (ref, write) => writeSkillAndSync(ref, write),
|
|
13693
14029
|
listSessions: () => listLocalSessions(),
|
|
14030
|
+
scanOnboarding: () => scanOnboarding(),
|
|
14031
|
+
linkProjectFolders: (slug, items) => linkProjectFolders(slug, items),
|
|
13694
14032
|
isHostOnline: () => true
|
|
13695
14033
|
};
|
|
13696
14034
|
}
|
|
@@ -13699,7 +14037,7 @@ function buildHostConfigSnapshot() {
|
|
|
13699
14037
|
const snapshot2 = {};
|
|
13700
14038
|
for (const profile of listProfiles()) {
|
|
13701
14039
|
snapshot2[profile] = runWithProfile(profile, () => ({
|
|
13702
|
-
env:
|
|
14040
|
+
env: existsSync26(paths.CONFIG_FILE) ? dotenv.parse(readFileSync15(paths.CONFIG_FILE, "utf-8")) : {},
|
|
13703
14041
|
agentModel: loadDashboardSettings().agent
|
|
13704
14042
|
}));
|
|
13705
14043
|
}
|
|
@@ -13715,13 +14053,24 @@ var AUTH_TIMEOUT_MS = 5e3;
|
|
|
13715
14053
|
var HEARTBEAT_INTERVAL_MS = 15e3;
|
|
13716
14054
|
var HEARTBEAT_TIMEOUT_MS = HEARTBEAT_INTERVAL_MS * 2.5;
|
|
13717
14055
|
var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13718
|
-
var
|
|
13719
|
-
var activeHostScope = null;
|
|
14056
|
+
var hosts = /* @__PURE__ */ new Map();
|
|
13720
14057
|
var pending = /* @__PURE__ */ new Map();
|
|
13721
14058
|
var hostStatusListeners = /* @__PURE__ */ new Set();
|
|
13722
14059
|
var lastAuthFailure = null;
|
|
13723
|
-
function
|
|
13724
|
-
|
|
14060
|
+
function hostFor(profile) {
|
|
14061
|
+
let legacy = null;
|
|
14062
|
+
let scoped = null;
|
|
14063
|
+
for (const [ws, scope] of hosts) {
|
|
14064
|
+
if (ws.readyState !== WebSocket.OPEN) continue;
|
|
14065
|
+
if (scope.workspaces === null) legacy = ws;
|
|
14066
|
+
else if (scope.workspaces.includes(profile)) scoped = ws;
|
|
14067
|
+
}
|
|
14068
|
+
return scoped ?? legacy;
|
|
14069
|
+
}
|
|
14070
|
+
function isHostConnected(profile) {
|
|
14071
|
+
if (profile) return hostFor(profile) !== null;
|
|
14072
|
+
for (const ws of hosts.keys()) if (ws.readyState === WebSocket.OPEN) return true;
|
|
14073
|
+
return false;
|
|
13725
14074
|
}
|
|
13726
14075
|
function getHostOfflineReason() {
|
|
13727
14076
|
return lastAuthFailure;
|
|
@@ -13730,11 +14079,26 @@ function onHostStatusChange(listener) {
|
|
|
13730
14079
|
hostStatusListeners.add(listener);
|
|
13731
14080
|
return () => hostStatusListeners.delete(listener);
|
|
13732
14081
|
}
|
|
13733
|
-
function
|
|
13734
|
-
const
|
|
13735
|
-
|
|
13736
|
-
|
|
13737
|
-
|
|
14082
|
+
function notifyHostStatus() {
|
|
14083
|
+
const online = hosts.size > 0;
|
|
14084
|
+
for (const l of hostStatusListeners) l(online);
|
|
14085
|
+
}
|
|
14086
|
+
function addHost(ws, scope) {
|
|
14087
|
+
hosts.set(ws, scope);
|
|
14088
|
+
notifyHostStatus();
|
|
14089
|
+
}
|
|
14090
|
+
function removeHost(ws) {
|
|
14091
|
+
if (hosts.delete(ws)) notifyHostStatus();
|
|
14092
|
+
}
|
|
14093
|
+
function supersedeSameIdentity(scope, except) {
|
|
14094
|
+
for (const [ws, other] of hosts) {
|
|
14095
|
+
if (ws === except) continue;
|
|
14096
|
+
const same = scope.deviceId ? other.deviceId === scope.deviceId : other.workspaces === null;
|
|
14097
|
+
if (!same) continue;
|
|
14098
|
+
console.log("[host-socket] new Host connection replacing the previous one");
|
|
14099
|
+
removeHost(ws);
|
|
14100
|
+
ws.close(1e3, "superseded");
|
|
14101
|
+
}
|
|
13738
14102
|
}
|
|
13739
14103
|
async function markAgentConnected(slugs) {
|
|
13740
14104
|
for (const slug of slugs) {
|
|
@@ -13747,20 +14111,27 @@ async function markAgentConnected(slugs) {
|
|
|
13747
14111
|
}
|
|
13748
14112
|
}
|
|
13749
14113
|
function disconnectHostDevice(deviceId) {
|
|
13750
|
-
|
|
14114
|
+
for (const [ws, scope] of hosts) {
|
|
14115
|
+
if (scope.deviceId !== deviceId) continue;
|
|
13751
14116
|
console.log(`[host-socket] closing connection for revoked device ${deviceId}`);
|
|
13752
|
-
|
|
13753
|
-
|
|
14117
|
+
removeHost(ws);
|
|
14118
|
+
ws.close(1008, "device revoked");
|
|
13754
14119
|
}
|
|
13755
14120
|
}
|
|
13756
|
-
async function
|
|
13757
|
-
|
|
13758
|
-
const
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
14121
|
+
async function revalidateDeviceScopes() {
|
|
14122
|
+
const deviceIds = /* @__PURE__ */ new Set();
|
|
14123
|
+
for (const scope of hosts.values()) {
|
|
14124
|
+
if (scope.authPath === "device" && scope.deviceId) deviceIds.add(scope.deviceId);
|
|
14125
|
+
}
|
|
14126
|
+
for (const deviceId of deviceIds) {
|
|
14127
|
+
const device = await getDevice(deviceId);
|
|
14128
|
+
if (!device || device.revokedAt) {
|
|
14129
|
+
disconnectHostDevice(deviceId);
|
|
14130
|
+
continue;
|
|
14131
|
+
}
|
|
14132
|
+
const workspaces = (await listDeviceWorkspaces(device)).map((w) => w.slug);
|
|
14133
|
+
for (const scope of hosts.values()) if (scope.deviceId === deviceId) scope.workspaces = workspaces;
|
|
13762
14134
|
}
|
|
13763
|
-
activeHostScope.workspaces = (await listDeviceWorkspaces(device)).map((w) => w.slug);
|
|
13764
14135
|
}
|
|
13765
14136
|
function attachHostSocket(server) {
|
|
13766
14137
|
const wss = new WebSocketServer({ server, path: HOST_SOCKET_PATH });
|
|
@@ -13775,7 +14146,7 @@ function attachHostSocket(server) {
|
|
|
13775
14146
|
alive.delete(ws);
|
|
13776
14147
|
ws.ping();
|
|
13777
14148
|
}
|
|
13778
|
-
void
|
|
14149
|
+
void revalidateDeviceScopes();
|
|
13779
14150
|
}, HEARTBEAT_INTERVAL_MS);
|
|
13780
14151
|
heartbeat.unref();
|
|
13781
14152
|
server.on("close", () => clearInterval(heartbeat));
|
|
@@ -13802,12 +14173,9 @@ function attachHostSocket(server) {
|
|
|
13802
14173
|
ws.close(1008, "bad signature");
|
|
13803
14174
|
return;
|
|
13804
14175
|
}
|
|
13805
|
-
|
|
13806
|
-
console.log("[host-socket] new Host connection replacing the previous one");
|
|
13807
|
-
activeHost.close(1e3, "superseded");
|
|
13808
|
-
}
|
|
14176
|
+
supersedeSameIdentity(scope, ws);
|
|
13809
14177
|
lastAuthFailure = null;
|
|
13810
|
-
|
|
14178
|
+
addHost(ws, scope);
|
|
13811
14179
|
send(ws, { type: "auth-ok" });
|
|
13812
14180
|
console.log(`[host-socket] Host authenticated (authPath: ${scope.authPath})`);
|
|
13813
14181
|
if (scope.workspaces) void markAgentConnected(scope.workspaces);
|
|
@@ -13827,34 +14195,32 @@ function attachHostSocket(server) {
|
|
|
13827
14195
|
});
|
|
13828
14196
|
ws.on("close", () => {
|
|
13829
14197
|
clearTimeout(authTimer);
|
|
13830
|
-
|
|
14198
|
+
removeHost(ws);
|
|
13831
14199
|
});
|
|
13832
14200
|
ws.on("error", (err) => console.error("[host-socket] connection error:", err.message));
|
|
13833
14201
|
});
|
|
13834
14202
|
return wss;
|
|
13835
14203
|
}
|
|
13836
14204
|
function callHost(method, args) {
|
|
13837
|
-
if (!activeHost || activeHost.readyState !== WebSocket.OPEN) {
|
|
13838
|
-
console.error(`[host-socket] ${method} -> no Host connected`);
|
|
13839
|
-
return Promise.resolve(null);
|
|
13840
|
-
}
|
|
13841
14205
|
const profile = getProfile();
|
|
13842
|
-
|
|
13843
|
-
|
|
14206
|
+
const ws = hostFor(profile);
|
|
14207
|
+
if (!ws) {
|
|
14208
|
+
console.error(
|
|
14209
|
+
isHostConnected() ? `[host-socket] ${method} -> not scoped to workspace "${profile}"` : `[host-socket] ${method} -> no Host connected`
|
|
14210
|
+
);
|
|
13844
14211
|
return Promise.resolve(null);
|
|
13845
14212
|
}
|
|
13846
14213
|
const id = randomUUID3();
|
|
13847
|
-
|
|
13848
|
-
return new Promise((resolve7) => {
|
|
14214
|
+
return new Promise((resolve8) => {
|
|
13849
14215
|
const timer2 = setTimeout(() => {
|
|
13850
14216
|
pending.delete(id);
|
|
13851
14217
|
console.error(`[host-socket] ${method} -> timed out`);
|
|
13852
|
-
|
|
14218
|
+
resolve8(null);
|
|
13853
14219
|
}, CALL_TIMEOUT_MS);
|
|
13854
14220
|
pending.set(id, {
|
|
13855
14221
|
resolve: (value) => {
|
|
13856
14222
|
clearTimeout(timer2);
|
|
13857
|
-
|
|
14223
|
+
resolve8(value);
|
|
13858
14224
|
}
|
|
13859
14225
|
});
|
|
13860
14226
|
send(ws, { type: "call", id, method, profile, args });
|
|
@@ -13905,7 +14271,11 @@ function remoteAgentCallbacks() {
|
|
|
13905
14271
|
// `null` (no Host connected) is passed through rather than defaulting to
|
|
13906
14272
|
// `[]` — see listSessions's doc comment on AgentCallbacks.
|
|
13907
14273
|
listSessions: () => callHost("listSessions", []),
|
|
13908
|
-
|
|
14274
|
+
// Both need this machine's disk and CLI history, so `null` (no Host)
|
|
14275
|
+
// surfaces as a 503 rather than an empty scan or a fake success.
|
|
14276
|
+
scanOnboarding: () => callHost("scanOnboarding", []),
|
|
14277
|
+
linkProjectFolders: (slug, items) => callHost("linkProjectFolders", [slug, items]),
|
|
14278
|
+
isHostOnline: (profile) => isHostConnected(profile),
|
|
13909
14279
|
getHostOfflineReason
|
|
13910
14280
|
};
|
|
13911
14281
|
}
|
|
@@ -14084,7 +14454,7 @@ async function bearerDevice(c) {
|
|
|
14084
14454
|
return token ? deviceForAccessToken(token) : null;
|
|
14085
14455
|
}
|
|
14086
14456
|
function createDeviceAuthRouter() {
|
|
14087
|
-
const app = new
|
|
14457
|
+
const app = new Hono26();
|
|
14088
14458
|
app.post("/code", async (c) => {
|
|
14089
14459
|
const body = await readBody(c);
|
|
14090
14460
|
const publicKey = typeof body["public_key"] === "string" ? body["public_key"] : "";
|
|
@@ -14234,7 +14604,7 @@ function createDeviceAuthRouter() {
|
|
|
14234
14604
|
|
|
14235
14605
|
// src/dashboard/api.ts
|
|
14236
14606
|
function createApiRouter(agent = {}, mode = "all") {
|
|
14237
|
-
const api = new
|
|
14607
|
+
const api = new Hono27();
|
|
14238
14608
|
api.use("*", async (c, next) => {
|
|
14239
14609
|
const known = await listKnownWorkspaces();
|
|
14240
14610
|
const fromHost = profileFromHost(trustedHost(c), known);
|
|
@@ -14341,7 +14711,7 @@ function createApiRouter(agent = {}, mode = "all") {
|
|
|
14341
14711
|
}
|
|
14342
14712
|
|
|
14343
14713
|
// src/dashboard/app-icon-routes.ts
|
|
14344
|
-
import { Hono as
|
|
14714
|
+
import { Hono as Hono28 } from "hono";
|
|
14345
14715
|
var NEUTRAL_MANIFEST = {
|
|
14346
14716
|
id: "/",
|
|
14347
14717
|
name: "Flyspace",
|
|
@@ -14379,7 +14749,7 @@ async function withResolvedProfile2(c, next) {
|
|
|
14379
14749
|
return profile ? runWithProfile(profile, () => next()) : next();
|
|
14380
14750
|
}
|
|
14381
14751
|
function createAppIconRouter() {
|
|
14382
|
-
const app = new
|
|
14752
|
+
const app = new Hono28();
|
|
14383
14753
|
app.get("/manifest.json", withResolvedProfile2, async (c) => {
|
|
14384
14754
|
if (!await resolvedProfileHost(c)) return c.json(NEUTRAL_MANIFEST);
|
|
14385
14755
|
const profile = getProfile();
|
|
@@ -14419,7 +14789,7 @@ function pngResponse(c, png) {
|
|
|
14419
14789
|
}
|
|
14420
14790
|
|
|
14421
14791
|
// src/dashboard/stripe-routes.ts
|
|
14422
|
-
import { Hono as
|
|
14792
|
+
import { Hono as Hono29 } from "hono";
|
|
14423
14793
|
function planStatusFor(status) {
|
|
14424
14794
|
const known = ["trialing", "active", "paused", "canceled"];
|
|
14425
14795
|
return known.includes(status) ? status : null;
|
|
@@ -14462,7 +14832,7 @@ async function handleCardAdded(session) {
|
|
|
14462
14832
|
}
|
|
14463
14833
|
}
|
|
14464
14834
|
function createStripeWebhookRouter() {
|
|
14465
|
-
const stripeRouter = new
|
|
14835
|
+
const stripeRouter = new Hono29();
|
|
14466
14836
|
stripeRouter.post("/webhook", async (c) => {
|
|
14467
14837
|
const secret = process.env["STRIPE_WEBHOOK_SECRET"];
|
|
14468
14838
|
const signature = c.req.header("stripe-signature");
|
|
@@ -14573,14 +14943,14 @@ function startHostStatusBroadcast() {
|
|
|
14573
14943
|
|
|
14574
14944
|
// src/dashboard/server.ts
|
|
14575
14945
|
var DEFAULT_DASHBOARD_PORT = 4747;
|
|
14576
|
-
var WEB_DIST =
|
|
14946
|
+
var WEB_DIST = join19(paths.PACKAGE_ROOT, "web", "dist");
|
|
14577
14947
|
var DEFAULT_DASHBOARD_HOST = "127.0.0.1";
|
|
14578
14948
|
async function findFreePort(start, host = DEFAULT_DASHBOARD_HOST, attempts = 50) {
|
|
14579
14949
|
for (let port = start; port < start + attempts; port++) {
|
|
14580
|
-
const free = await new Promise((
|
|
14950
|
+
const free = await new Promise((resolve8) => {
|
|
14581
14951
|
const probe2 = createServer();
|
|
14582
|
-
probe2.once("error", () =>
|
|
14583
|
-
probe2.once("listening", () => probe2.close(() =>
|
|
14952
|
+
probe2.once("error", () => resolve8(false));
|
|
14953
|
+
probe2.once("listening", () => probe2.close(() => resolve8(true)));
|
|
14584
14954
|
probe2.listen(port, host);
|
|
14585
14955
|
});
|
|
14586
14956
|
if (free) return port;
|
|
@@ -14607,7 +14977,7 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
14607
14977
|
if (servesHost) installUsageRecording();
|
|
14608
14978
|
const stopSkillSync = servesHost ? installSkillSync(listProfiles()) : () => {
|
|
14609
14979
|
};
|
|
14610
|
-
const app = new
|
|
14980
|
+
const app = new Hono30();
|
|
14611
14981
|
app.use("*", async (c, next) => {
|
|
14612
14982
|
await next();
|
|
14613
14983
|
if (c.req.path.startsWith("/assets/")) {
|
|
@@ -14630,9 +15000,9 @@ async function startDashboardServer(preferredPort = DEFAULT_DASHBOARD_PORT, host
|
|
|
14630
15000
|
app.route("/", createAppIconRouter());
|
|
14631
15001
|
app.use("/*", serveStatic({ root: WEB_DIST }));
|
|
14632
15002
|
app.get("/assets/*", (c) => c.text("stale asset \u2014 reload for the current build", 404));
|
|
14633
|
-
const spaFallback = serveStatic({ path:
|
|
15003
|
+
const spaFallback = serveStatic({ path: join19(WEB_DIST, "index.html") });
|
|
14634
15004
|
app.get("*", (c, next) => {
|
|
14635
|
-
if (!
|
|
15005
|
+
if (!existsSync27(join19(WEB_DIST, "index.html"))) {
|
|
14636
15006
|
return c.text(
|
|
14637
15007
|
`Dashboard UI build missing (${WEB_DIST}).
|
|
14638
15008
|
Run \`pnpm --dir web build\` (or \`pnpm --dir web dev\`).
|
|
@@ -14669,7 +15039,7 @@ The JSON API is live under /api.`,
|
|
|
14669
15039
|
|
|
14670
15040
|
// src/slack/image-uploader.ts
|
|
14671
15041
|
import { readFile } from "fs/promises";
|
|
14672
|
-
import { basename as
|
|
15042
|
+
import { basename as basename6 } from "path";
|
|
14673
15043
|
import sharp6 from "sharp";
|
|
14674
15044
|
var MAX_WIDTH = 800;
|
|
14675
15045
|
async function uploadImage(client2, { channel, filePath, threadTs, text }) {
|
|
@@ -14678,7 +15048,7 @@ async function uploadImage(client2, { channel, filePath, threadTs, text }) {
|
|
|
14678
15048
|
const base = {
|
|
14679
15049
|
channel_id: channel,
|
|
14680
15050
|
file: resized,
|
|
14681
|
-
filename:
|
|
15051
|
+
filename: basename6(filePath),
|
|
14682
15052
|
...text ? { initial_comment: text } : {}
|
|
14683
15053
|
};
|
|
14684
15054
|
const result = await client2.filesUploadV2(threadTs ? { ...base, thread_ts: threadTs } : base);
|
|
@@ -15050,7 +15420,7 @@ async function threadReplies(client2, nameOrId, ts2) {
|
|
|
15050
15420
|
async function postMessage(client2, args) {
|
|
15051
15421
|
const channelArg = getArg2(args, "--channel");
|
|
15052
15422
|
const textFilePath = getArg2(args, "--text-file");
|
|
15053
|
-
const text = textFilePath ?
|
|
15423
|
+
const text = textFilePath ? readFileSync16(textFilePath, "utf8") : getArg2(args, "--text");
|
|
15054
15424
|
const threadTs = getArg2(args, "--thread");
|
|
15055
15425
|
const markdown = args.includes("--markdown");
|
|
15056
15426
|
const broadcast = args.includes("--broadcast") && Boolean(threadTs);
|
|
@@ -15140,22 +15510,22 @@ function mask(token) {
|
|
|
15140
15510
|
}
|
|
15141
15511
|
function hasClaudeBinary() {
|
|
15142
15512
|
try {
|
|
15143
|
-
|
|
15513
|
+
execFileSync3("claude", ["--version"], { stdio: "ignore" });
|
|
15144
15514
|
return true;
|
|
15145
15515
|
} catch {
|
|
15146
15516
|
return false;
|
|
15147
15517
|
}
|
|
15148
15518
|
}
|
|
15149
15519
|
function defaultClaudeConfigDir() {
|
|
15150
|
-
return process.env.CLAUDE_CONFIG_DIR ||
|
|
15520
|
+
return process.env.CLAUDE_CONFIG_DIR || resolve7(homedir8(), ".claude");
|
|
15151
15521
|
}
|
|
15152
|
-
function hasClaudeCredentials(
|
|
15153
|
-
if (
|
|
15522
|
+
function hasClaudeCredentials(configDir2) {
|
|
15523
|
+
if (existsSync28(resolve7(configDir2, ".credentials.json"))) {
|
|
15154
15524
|
return true;
|
|
15155
15525
|
}
|
|
15156
15526
|
if (process.platform === "darwin") {
|
|
15157
15527
|
try {
|
|
15158
|
-
|
|
15528
|
+
execFileSync3(
|
|
15159
15529
|
"security",
|
|
15160
15530
|
["find-generic-password", "-s", "Claude Code-credentials"],
|
|
15161
15531
|
{ stdio: "ignore" }
|
|
@@ -15187,7 +15557,7 @@ async function ensureClaudeCodeReady() {
|
|
|
15187
15557
|
console.log(" Flyspace spawns Claude Code as a subprocess. Without credentials, the bot");
|
|
15188
15558
|
console.log(" will prompt /login inside Slack at runtime and you cannot complete the");
|
|
15189
15559
|
console.log(" browser sign-in from there.\n");
|
|
15190
|
-
if (claudeConfigDir !==
|
|
15560
|
+
if (claudeConfigDir !== resolve7(homedir8(), ".claude")) {
|
|
15191
15561
|
console.log(" Tip: run with the same CLAUDE_CONFIG_DIR you use for this Claude Code install:");
|
|
15192
15562
|
console.log(` \x1B[1mCLAUDE_CONFIG_DIR=${claudeConfigDir} claude\x1B[0m
|
|
15193
15563
|
`);
|
|
@@ -15206,8 +15576,8 @@ async function ensureClaudeCodeReady() {
|
|
|
15206
15576
|
return { claudeConfigDir };
|
|
15207
15577
|
}
|
|
15208
15578
|
function getManifestYaml(botName) {
|
|
15209
|
-
const manifestPath =
|
|
15210
|
-
const raw =
|
|
15579
|
+
const manifestPath = resolve7(paths.PACKAGE_ROOT, "slack-app-manifest.yml");
|
|
15580
|
+
const raw = readFileSync16(manifestPath, "utf-8");
|
|
15211
15581
|
return raw.replace(/name: Custie/g, `name: ${botName}`);
|
|
15212
15582
|
}
|
|
15213
15583
|
async function stepPersonalise() {
|
|
@@ -15420,7 +15790,7 @@ async function stepBrowserTokens(botName) {
|
|
|
15420
15790
|
})
|
|
15421
15791
|
));
|
|
15422
15792
|
const DEBUG = process.argv.includes("--debug");
|
|
15423
|
-
const USER_DATA_DIR =
|
|
15793
|
+
const USER_DATA_DIR = resolve7(tmpdir3(), "custie-playwright-profile");
|
|
15424
15794
|
let context;
|
|
15425
15795
|
try {
|
|
15426
15796
|
log4("Opening browser \u2014 a Slack login page will appear.\n");
|
|
@@ -15452,14 +15822,14 @@ async function stepBrowserTokens(botName) {
|
|
|
15452
15822
|
const appLink = page2.locator('a[href*="/apps/A"]').first();
|
|
15453
15823
|
const hasExistingApp = await appLink.isVisible({ timeout: 3e3 }).catch(() => false);
|
|
15454
15824
|
if (hasExistingApp) {
|
|
15455
|
-
const tokenPromise = new Promise((
|
|
15825
|
+
const tokenPromise = new Promise((resolve8) => {
|
|
15456
15826
|
page2.on("request", (req) => {
|
|
15457
15827
|
if (token) return;
|
|
15458
15828
|
const postData = req.postData() || "";
|
|
15459
15829
|
const match = postData.match(/xoxc-[a-f0-9-]+/);
|
|
15460
15830
|
if (match) {
|
|
15461
15831
|
token = match[0];
|
|
15462
|
-
|
|
15832
|
+
resolve8(token);
|
|
15463
15833
|
}
|
|
15464
15834
|
});
|
|
15465
15835
|
});
|
|
@@ -15510,7 +15880,7 @@ async function stepBrowserTokens(botName) {
|
|
|
15510
15880
|
log4("Almost done! The browser will now open an install page.\n");
|
|
15511
15881
|
console.log(' \x1B[1mPlease click "Install to Workspace", then click "Allow".\x1B[0m\n');
|
|
15512
15882
|
await page2.goto(installUrl);
|
|
15513
|
-
const installPage = await new Promise((
|
|
15883
|
+
const installPage = await new Promise((resolve8, reject) => {
|
|
15514
15884
|
let settled = false;
|
|
15515
15885
|
let interval;
|
|
15516
15886
|
let timeout;
|
|
@@ -15523,7 +15893,7 @@ async function stepBrowserTokens(botName) {
|
|
|
15523
15893
|
};
|
|
15524
15894
|
const checkUrl = (p) => {
|
|
15525
15895
|
if (p.url().includes("install-on-team?success=1")) {
|
|
15526
|
-
finish(() =>
|
|
15896
|
+
finish(() => resolve8(p));
|
|
15527
15897
|
}
|
|
15528
15898
|
};
|
|
15529
15899
|
context.on("page", (newPage) => {
|
|
@@ -15582,7 +15952,7 @@ async function stepBrowserTokens(botName) {
|
|
|
15582
15952
|
try {
|
|
15583
15953
|
const pages = context?.pages();
|
|
15584
15954
|
if (pages?.[0]) {
|
|
15585
|
-
const screenshotPath =
|
|
15955
|
+
const screenshotPath = resolve7(paths.PACKAGE_ROOT, "setup-error.png");
|
|
15586
15956
|
await pages[0].screenshot({ path: screenshotPath, fullPage: true });
|
|
15587
15957
|
warn4(`Screenshot saved to: ${screenshotPath}`);
|
|
15588
15958
|
}
|
|
@@ -15638,10 +16008,10 @@ function printNextSteps() {
|
|
|
15638
16008
|
console.log(" Run \x1B[1mflyspace install\x1B[0m\n");
|
|
15639
16009
|
}
|
|
15640
16010
|
function copyDefaultPrompt(botName) {
|
|
15641
|
-
if (!
|
|
15642
|
-
const defaultPrompt =
|
|
15643
|
-
if (
|
|
15644
|
-
const content =
|
|
16011
|
+
if (!existsSync28(paths.PROMPT_FILE)) {
|
|
16012
|
+
const defaultPrompt = resolve7(paths.PACKAGE_ROOT, "system.default.md");
|
|
16013
|
+
if (existsSync28(defaultPrompt)) {
|
|
16014
|
+
const content = readFileSync16(defaultPrompt, "utf-8").replace(/\{\{botName\}\}/g, botName);
|
|
15645
16015
|
writeFileSync9(paths.PROMPT_FILE, content);
|
|
15646
16016
|
success4(`Default prompt copied to ${paths.PROMPT_FILE}`);
|
|
15647
16017
|
}
|
|
@@ -15654,7 +16024,7 @@ async function runSlackSetup(args) {
|
|
|
15654
16024
|
const prereq = await ensureClaudeCodeReady();
|
|
15655
16025
|
if (!prereq) return;
|
|
15656
16026
|
const { claudeConfigDir } = prereq;
|
|
15657
|
-
if (
|
|
16027
|
+
if (existsSync28(paths.CONFIG_FILE)) {
|
|
15658
16028
|
const reconfigure = await confirm({
|
|
15659
16029
|
message: `Config already exists at ${paths.CONFIG_FILE}. Reconfigure?`,
|
|
15660
16030
|
default: false
|
|
@@ -16001,9 +16371,9 @@ async function runMcpCmd(args) {
|
|
|
16001
16371
|
|
|
16002
16372
|
// src/commands/dashboard.ts
|
|
16003
16373
|
import { spawn as spawn5, spawnSync } from "child_process";
|
|
16004
|
-
import { existsSync as
|
|
16374
|
+
import { existsSync as existsSync29 } from "fs";
|
|
16005
16375
|
import { platform as platform6 } from "os";
|
|
16006
|
-
import { join as
|
|
16376
|
+
import { join as join20 } from "path";
|
|
16007
16377
|
function flagValue(args, name) {
|
|
16008
16378
|
const eq = args.find((a) => a.startsWith(`--${name}=`));
|
|
16009
16379
|
if (eq) return eq.slice(`--${name}=`.length);
|
|
@@ -16018,7 +16388,7 @@ function openBrowser(url) {
|
|
|
16018
16388
|
}
|
|
16019
16389
|
}
|
|
16020
16390
|
function ensureWebBuild() {
|
|
16021
|
-
if (
|
|
16391
|
+
if (existsSync29(join20(paths.PACKAGE_ROOT, "web", "dist", "index.html"))) return;
|
|
16022
16392
|
console.log(" web/dist missing \u2014 building the dashboard UI (pnpm --dir web build)...");
|
|
16023
16393
|
const result = spawnSync("pnpm", ["--dir", "web", "build"], {
|
|
16024
16394
|
cwd: paths.PACKAGE_ROOT,
|
|
@@ -16066,7 +16436,7 @@ async function runDashboard(args) {
|
|
|
16066
16436
|
}
|
|
16067
16437
|
|
|
16068
16438
|
// src/commands/workspace.ts
|
|
16069
|
-
import { existsSync as
|
|
16439
|
+
import { existsSync as existsSync30, mkdirSync as mkdirSync9, writeFileSync as writeFileSync10 } from "fs";
|
|
16070
16440
|
var USAGE5 = `
|
|
16071
16441
|
Usage: flyspace workspace <subcommand> [options]
|
|
16072
16442
|
|
|
@@ -16141,7 +16511,7 @@ function provisionLocal(botName) {
|
|
|
16141
16511
|
ensureDirs();
|
|
16142
16512
|
console.log(` config dir ready (${paths.CONFIG_DIR})`);
|
|
16143
16513
|
console.log(` data dir ready (${paths.DATA_DIR})`);
|
|
16144
|
-
if (
|
|
16514
|
+
if (existsSync30(paths.CONFIG_FILE)) {
|
|
16145
16515
|
console.log(" config.env exists");
|
|
16146
16516
|
} else {
|
|
16147
16517
|
writeFileSync10(paths.CONFIG_FILE, `BOT_NAME=${botName}
|
|
@@ -16158,8 +16528,8 @@ async function provisionChannels(specs) {
|
|
|
16158
16528
|
continue;
|
|
16159
16529
|
}
|
|
16160
16530
|
const cwd = expandPath(spec.cwd);
|
|
16161
|
-
if (!
|
|
16162
|
-
|
|
16531
|
+
if (!existsSync30(cwd)) {
|
|
16532
|
+
mkdirSync9(cwd, { recursive: true });
|
|
16163
16533
|
console.log(` created cwd ${cwd}`);
|
|
16164
16534
|
}
|
|
16165
16535
|
await createChannel({ name: spec.name, cwd, position: index });
|
|
@@ -16664,7 +17034,7 @@ async function main() {
|
|
|
16664
17034
|
break;
|
|
16665
17035
|
case "-v":
|
|
16666
17036
|
case "--version":
|
|
16667
|
-
printRaw("0.1.
|
|
17037
|
+
printRaw("0.1.1");
|
|
16668
17038
|
break;
|
|
16669
17039
|
case "-h":
|
|
16670
17040
|
case "--help":
|