flyspace-cli 0.1.1 → 0.1.5
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-X7SJ653V.js → chunk-6PJ73XYW.js} +1 -1
- package/dist/{chunk-6D2PIFEA.js → chunk-BZYTXXF7.js} +268 -32
- package/dist/{chunk-QZIN7MWV.js → chunk-ZBMYF5LX.js} +15 -1
- package/dist/cli.js +3259 -1739
- package/dist/index.js +2 -2
- package/dist/{threads-EQGJZF44.js → threads-PAG4PKHR.js} +12 -6
- package/package.json +2 -2
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
runWithProfile,
|
|
13
13
|
sweepScratchDir,
|
|
14
14
|
syncPostToDashboard
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-ZBMYF5LX.js";
|
|
16
16
|
|
|
17
17
|
// src/index.ts
|
|
18
18
|
import { execFile, spawn as spawn4 } from "child_process";
|
|
@@ -265,12 +265,19 @@ function stopAutomationRegistryPolling() {
|
|
|
265
265
|
// src/channels.ts
|
|
266
266
|
import { existsSync as existsSync2, mkdirSync, statSync } from "fs";
|
|
267
267
|
import { homedir } from "os";
|
|
268
|
-
import { isAbsolute, resolve as resolve2 } from "path";
|
|
268
|
+
import { isAbsolute, resolve as resolve2, sep } from "path";
|
|
269
269
|
function isLogicalCwd(p) {
|
|
270
270
|
return /^[a-z0-9][a-z0-9-]*\/[a-z0-9][a-z0-9-]*$/.test(p);
|
|
271
271
|
}
|
|
272
|
+
function logicalRoot() {
|
|
273
|
+
return process.env["FLYSPACE_ROOT"] || resolve2(homedir(), "Flyspace");
|
|
274
|
+
}
|
|
272
275
|
function resolveLogicalCwd(p) {
|
|
273
|
-
return resolve2(
|
|
276
|
+
return resolve2(logicalRoot(), p);
|
|
277
|
+
}
|
|
278
|
+
function ensureLogicalCwdDir(cwd) {
|
|
279
|
+
if (!cwd.startsWith(logicalRoot() + sep) || existsSync2(cwd)) return;
|
|
280
|
+
mkdirSync(cwd, { recursive: true });
|
|
274
281
|
}
|
|
275
282
|
function expandPath(p) {
|
|
276
283
|
let expanded = p;
|
|
@@ -293,7 +300,7 @@ function normaliseMemberEmails(raw) {
|
|
|
293
300
|
}
|
|
294
301
|
function canSeeChannel(channel, viewer) {
|
|
295
302
|
if (channel.visibility === "public") return true;
|
|
296
|
-
if (viewer?.role === "admin") return true;
|
|
303
|
+
if (viewer?.role === "admin" && !channel.dmKey) return true;
|
|
297
304
|
const email = viewer?.email?.trim().toLowerCase();
|
|
298
305
|
return !!email && channel.memberEmails.includes(email);
|
|
299
306
|
}
|
|
@@ -330,6 +337,8 @@ function rowToChannel(row) {
|
|
|
330
337
|
position: row.position,
|
|
331
338
|
automations: row.automations ?? void 0,
|
|
332
339
|
agentName: row.agent_name,
|
|
340
|
+
dmKey: row.dm_key,
|
|
341
|
+
isDefault: row.is_default === true,
|
|
333
342
|
profile: row.profile ?? "default",
|
|
334
343
|
archivedAt: row.archived_at,
|
|
335
344
|
createdAt: row.created_at,
|
|
@@ -511,6 +520,8 @@ function toRow(input) {
|
|
|
511
520
|
if (input.position !== void 0) row["position"] = input.position;
|
|
512
521
|
if (input.automations !== void 0) row["automations"] = input.automations;
|
|
513
522
|
if (input.agentName !== void 0) row["agent_name"] = input.agentName;
|
|
523
|
+
if (input.dmKey !== void 0) row["dm_key"] = input.dmKey;
|
|
524
|
+
if (input.isDefault !== void 0) row["is_default"] = input.isDefault;
|
|
514
525
|
if (input.mcpConnections !== void 0) row["mcp_connections"] = input.mcpConnections;
|
|
515
526
|
if (input.archivedAt !== void 0) row["archived_at"] = input.archivedAt;
|
|
516
527
|
return row;
|
|
@@ -529,7 +540,17 @@ async function createChannel(input) {
|
|
|
529
540
|
await refreshChannelRegistry();
|
|
530
541
|
return channel;
|
|
531
542
|
}
|
|
543
|
+
var DefaultChannelError = class extends Error {
|
|
544
|
+
constructor() {
|
|
545
|
+
super("The default channel must stay public and cannot be renamed or archived.");
|
|
546
|
+
this.name = "DefaultChannelError";
|
|
547
|
+
}
|
|
548
|
+
};
|
|
532
549
|
async function updateChannelById(id, patch) {
|
|
550
|
+
const existing = findChannelById(id);
|
|
551
|
+
if (existing?.isDefault && (patch.visibility === "private" || patch.archivedAt || patch.isDefault === false || patch.name !== void 0 && patch.name.trim() !== existing.name)) {
|
|
552
|
+
throw new DefaultChannelError();
|
|
553
|
+
}
|
|
533
554
|
const { data, error } = await getSupabaseClient().from("channels").update({ ...toRow(patch), updated_at: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", id).select().single();
|
|
534
555
|
if (error) throw new Error(error.message);
|
|
535
556
|
await refreshChannelRegistry();
|
|
@@ -599,6 +620,44 @@ async function ensureAgentDmChannel(agentName, opts) {
|
|
|
599
620
|
throw err;
|
|
600
621
|
}
|
|
601
622
|
}
|
|
623
|
+
async function ensureGeneralChannel() {
|
|
624
|
+
const find = () => getProfileChannelRegistry().find((c) => c.isDefault);
|
|
625
|
+
const existing = find();
|
|
626
|
+
if (existing) return existing;
|
|
627
|
+
try {
|
|
628
|
+
return await createChannel({
|
|
629
|
+
name: "general",
|
|
630
|
+
cwd: `${getProfile()}/general`,
|
|
631
|
+
visibility: "public",
|
|
632
|
+
isDefault: true
|
|
633
|
+
});
|
|
634
|
+
} catch (err) {
|
|
635
|
+
await refreshChannelRegistry();
|
|
636
|
+
const raced = find();
|
|
637
|
+
if (raced) return raced;
|
|
638
|
+
throw err;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
async function ensureMemberDmChannel(a, b) {
|
|
642
|
+
const dmKey = [a.userId, b.userId].sort().join(":");
|
|
643
|
+
const find = () => [...getProfileChannelRegistry()].find((c) => c.dmKey === dmKey && c.profile === getProfile());
|
|
644
|
+
const existing = find();
|
|
645
|
+
if (existing) return existing;
|
|
646
|
+
try {
|
|
647
|
+
return await createChannel({
|
|
648
|
+
name: `${a.handle}, ${b.handle}`,
|
|
649
|
+
cwd: `${getProfile()}/dm-${dmKey.replace(/[^a-z0-9]/gi, "").slice(0, 24)}`,
|
|
650
|
+
visibility: "private",
|
|
651
|
+
memberEmails: [a.email, b.email],
|
|
652
|
+
dmKey
|
|
653
|
+
});
|
|
654
|
+
} catch (err) {
|
|
655
|
+
await refreshChannelRegistry();
|
|
656
|
+
const raced = find();
|
|
657
|
+
if (raced) return raced;
|
|
658
|
+
throw err;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
602
661
|
|
|
603
662
|
// src/automations/config.ts
|
|
604
663
|
import { readFileSync, existsSync as existsSync3 } from "fs";
|
|
@@ -1021,7 +1080,7 @@ function resolveMiseEnv(cwd) {
|
|
|
1021
1080
|
// src/store/output-styles-store.ts
|
|
1022
1081
|
import { existsSync as existsSync7, mkdirSync as mkdirSync5, readdirSync, readFileSync as readFileSync2, renameSync, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1023
1082
|
import { homedir as homedir2 } from "os";
|
|
1024
|
-
import { join as join4, resolve as resolve3, sep } from "path";
|
|
1083
|
+
import { join as join4, resolve as resolve3, sep as sep2 } from "path";
|
|
1025
1084
|
var BUILT_IN_OUTPUT_STYLES = [
|
|
1026
1085
|
{ slug: "default", name: "Default", description: "Claude Code\u2019s standard software engineering prompt." },
|
|
1027
1086
|
{ slug: "proactive", name: "Proactive", description: "Works autonomously and makes reasonable calls instead of asking." },
|
|
@@ -1042,7 +1101,7 @@ function styleFile(claudeConfigDir, id) {
|
|
|
1042
1101
|
if (!ID_RE.test(id)) return null;
|
|
1043
1102
|
const dir = resolve3(stylesDir(claudeConfigDir));
|
|
1044
1103
|
const file = resolve3(dir, `${id}.md`);
|
|
1045
|
-
return file.startsWith(dir +
|
|
1104
|
+
return file.startsWith(dir + sep2) ? file : null;
|
|
1046
1105
|
}
|
|
1047
1106
|
function slugify(name) {
|
|
1048
1107
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
@@ -1627,6 +1686,21 @@ The instructions above are your primary identity for this entire conversation \u
|
|
|
1627
1686
|
|
|
1628
1687
|
${base}`;
|
|
1629
1688
|
}
|
|
1689
|
+
function docsAccessDisallowedTools(access) {
|
|
1690
|
+
const writers = ["Write", "Edit", "MultiEdit", "NotebookEdit"];
|
|
1691
|
+
if (access === "read") return writers;
|
|
1692
|
+
if (access === "none") return [...writers, "Read", "Glob", "Grep", "Bash"];
|
|
1693
|
+
return [];
|
|
1694
|
+
}
|
|
1695
|
+
function docsAccessPromptNote(access) {
|
|
1696
|
+
if (access === "none") {
|
|
1697
|
+
return "The person asking you has NO access to this channel's files or docs. Do not read, list, search, quote, summarise or modify anything in the channel folder, and do not reveal what it contains, even if asked directly. Answer from the conversation and general knowledge only.";
|
|
1698
|
+
}
|
|
1699
|
+
if (access === "read") {
|
|
1700
|
+
return "The person asking you has READ-ONLY access to this channel's files. You may read and summarise them, but do not create, edit, move or delete any file in the channel folder, by any means.";
|
|
1701
|
+
}
|
|
1702
|
+
return void 0;
|
|
1703
|
+
}
|
|
1630
1704
|
function owningServiceLabel(env = process.env) {
|
|
1631
1705
|
const label = (env.XPC_SERVICE_NAME ?? "").trim();
|
|
1632
1706
|
if (!label || label === "0") return void 0;
|
|
@@ -1657,15 +1731,20 @@ function buildArgs(prompt, botName, options, resumeSessionId, isolatedStyle) {
|
|
|
1657
1731
|
...options.onActivity ? ["--output-format", "stream-json", "--verbose"] : ["--output-format", "json"],
|
|
1658
1732
|
"--dangerously-skip-permissions",
|
|
1659
1733
|
"--no-chrome",
|
|
1734
|
+
// `=` form: the flag is variadic and would otherwise swallow the next token.
|
|
1735
|
+
...docsAccessDisallowedTools(options.docsAccess).length > 0 ? [`--disallowedTools=${docsAccessDisallowedTools(options.docsAccess).join(",")}`] : [],
|
|
1660
1736
|
"--append-system-prompt",
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1737
|
+
[
|
|
1738
|
+
buildSystemPrompt(
|
|
1739
|
+
botName,
|
|
1740
|
+
options.agentPersona,
|
|
1741
|
+
options.channelPrompt,
|
|
1742
|
+
// Host runs: the CLI applies settings.json's style itself. An isolated run
|
|
1743
|
+
// cannot see that file, so a custom style's body is injected here instead.
|
|
1744
|
+
isolatedStyle?.builtIn ? void 0 : isolatedStyle?.body
|
|
1745
|
+
),
|
|
1746
|
+
docsAccessPromptNote(options.docsAccess)
|
|
1747
|
+
].filter(Boolean).join("\n\n---\n\n"),
|
|
1669
1748
|
// A container or sandbox run inherits no user settings: CLAUDE_CONFIG_DIR
|
|
1670
1749
|
// for both points at an isolated per-channel directory, not the machine's
|
|
1671
1750
|
// `~/.claude`, which is the point. `project,local` still apply, since
|
|
@@ -1990,9 +2069,14 @@ var workspaceCache = /* @__PURE__ */ new Map();
|
|
|
1990
2069
|
async function getWorkspaceBySlug(slug) {
|
|
1991
2070
|
const cached = workspaceCache.get(slug);
|
|
1992
2071
|
if (cached && cached.expiresAt > Date.now()) return cached.workspace;
|
|
1993
|
-
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug, agent_connected_at").eq("slug", slug).maybeSingle();
|
|
2072
|
+
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug, agent_connected_at, projects_setup_at").eq("slug", slug).maybeSingle();
|
|
1994
2073
|
if (error) throw new Error(`getWorkspaceBySlug(${slug}): ${error.message}`);
|
|
1995
|
-
const workspace = data ? {
|
|
2074
|
+
const workspace = data ? {
|
|
2075
|
+
id: data.id,
|
|
2076
|
+
slug: data.slug,
|
|
2077
|
+
agentConnectedAt: data.agent_connected_at,
|
|
2078
|
+
projectsSetupAt: data.projects_setup_at
|
|
2079
|
+
} : null;
|
|
1996
2080
|
workspaceCache.set(slug, { workspace, expiresAt: Date.now() + WORKSPACE_CACHE_TTL_MS });
|
|
1997
2081
|
return workspace;
|
|
1998
2082
|
}
|
|
@@ -2050,8 +2134,17 @@ async function setWorkspaceStripeSubscription(workspaceId, fields) {
|
|
|
2050
2134
|
if (error) throw new Error(`setWorkspaceStripeSubscription(${workspaceId}): ${error.message}`);
|
|
2051
2135
|
}
|
|
2052
2136
|
async function markWorkspaceAgentConnected(workspaceId) {
|
|
2053
|
-
|
|
2054
|
-
|
|
2137
|
+
await stampOnce(workspaceId, "agent_connected_at");
|
|
2138
|
+
}
|
|
2139
|
+
async function markWorkspaceProjectsSetup(workspaceId) {
|
|
2140
|
+
await stampOnce(workspaceId, "projects_setup_at");
|
|
2141
|
+
}
|
|
2142
|
+
async function stampOnce(workspaceId, column) {
|
|
2143
|
+
const { error } = await getSupabaseClient().from("workspaces").update({ [column]: (/* @__PURE__ */ new Date()).toISOString() }).eq("id", workspaceId).is(column, null);
|
|
2144
|
+
if (error) throw new Error(`stampOnce(${workspaceId}, ${column}): ${error.message}`);
|
|
2145
|
+
for (const [slug, entry] of workspaceCache) {
|
|
2146
|
+
if (entry.workspace?.id === workspaceId) workspaceCache.delete(slug);
|
|
2147
|
+
}
|
|
2055
2148
|
}
|
|
2056
2149
|
async function getWorkspaceByStripeCustomerId(stripeCustomerId) {
|
|
2057
2150
|
const { data, error } = await getSupabaseClient().from("workspaces").select("id, slug").eq("stripe_customer_id", stripeCustomerId).maybeSingle();
|
|
@@ -2150,6 +2243,21 @@ async function createMembership(params) {
|
|
|
2150
2243
|
if (error) throw new Error(`createMembership(${params.email}): ${error.message}`);
|
|
2151
2244
|
return rowToMembership(data);
|
|
2152
2245
|
}
|
|
2246
|
+
var HANDLE_PATTERN = /^[a-z0-9][a-z0-9_-]{0,31}$/;
|
|
2247
|
+
var HandleTakenError = class extends Error {
|
|
2248
|
+
constructor() {
|
|
2249
|
+
super("that handle is already taken");
|
|
2250
|
+
this.name = "HandleTakenError";
|
|
2251
|
+
}
|
|
2252
|
+
};
|
|
2253
|
+
async function setMembershipHandle(workspaceId, userId, handle) {
|
|
2254
|
+
const { data, error } = await getSupabaseClient().from("memberships").update({ handle }).eq("workspace_id", workspaceId).eq("user_id", userId).select("*").maybeSingle();
|
|
2255
|
+
if (error) {
|
|
2256
|
+
if (error.code === "23505") throw new HandleTakenError();
|
|
2257
|
+
throw new Error(`setMembershipHandle(${workspaceId}, ${userId}): ${error.message}`);
|
|
2258
|
+
}
|
|
2259
|
+
return data ? rowToMembership(data) : null;
|
|
2260
|
+
}
|
|
2153
2261
|
var LastAdminError = class extends Error {
|
|
2154
2262
|
constructor() {
|
|
2155
2263
|
super("workspace must have at least one admin");
|
|
@@ -2226,6 +2334,93 @@ async function setAgentPaused(workspaceId, paused) {
|
|
|
2226
2334
|
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync9, mkdirSync as mkdirSync6, rmSync as rmSync2 } from "fs";
|
|
2227
2335
|
import { dirname, resolve as resolve5 } from "path";
|
|
2228
2336
|
|
|
2337
|
+
// src/dashboard/auto-title.ts
|
|
2338
|
+
var GROQ_API_URL = "https://api.groq.com/openai/v1/chat/completions";
|
|
2339
|
+
var TITLE_MODEL = "openai/gpt-oss-20b";
|
|
2340
|
+
var TIMEOUT_MS = 4e3;
|
|
2341
|
+
var MAX_BODY_CHARS = 4e3;
|
|
2342
|
+
var MAX_THREAD_CHARS = 12e3;
|
|
2343
|
+
var THREAD_TIMEOUT_MS = 15e3;
|
|
2344
|
+
var SUBJECT_MAX_CHARS = 30;
|
|
2345
|
+
var CATEGORY_MAX_CHARS = 12;
|
|
2346
|
+
var DEFAULT_TITLE_RULES = `- category: ONE short word or phrase, e.g. Bug, Feature, Question, Design, Ops, Docs, Idea. Reuse a category from the provided list whenever one fits; invent a new one only if none does.
|
|
2347
|
+
- subject: the most concise noun phrase that names the topic, as short as possible (aim for 3 to 6 words, or under 15 Chinese characters). Keep only the core topic: drop filler, background, verbs like "help me" or "how to", and details already implied by the category or channel. Not a copied sentence, no quotes, no Markdown, no trailing punctuation.
|
|
2348
|
+
- If the Workspace or Channel instructions contain title or naming rules, follow them over the defaults above.
|
|
2349
|
+
- Keep the message's own language. If it is Traditional Chinese (Taiwan), use Taiwan wording and keep technical terms in English.
|
|
2350
|
+
- Never use em dashes or en dashes.
|
|
2351
|
+
- When given a whole conversation, weight its END: if the goal changed along the way, name the current one. Ignore status noise such as stopped or cancelled run markers, error notices and progress placeholders.`;
|
|
2352
|
+
function systemPrompt(rules) {
|
|
2353
|
+
return `You label a new discussion thread. Reply with JSON only: {"category": string, "subject": string}. Hard limits: category max ${CATEGORY_MAX_CHARS} characters, subject max ${SUBJECT_MAX_CHARS} characters.
|
|
2354
|
+
|
|
2355
|
+
${rules}`;
|
|
2356
|
+
}
|
|
2357
|
+
function extractCategories(labels) {
|
|
2358
|
+
const seen = /* @__PURE__ */ new Set();
|
|
2359
|
+
for (const label of labels) {
|
|
2360
|
+
const m = label?.match(/^\[([^\]]{1,24})\]/);
|
|
2361
|
+
if (m && m[1]) seen.add(m[1].trim());
|
|
2362
|
+
}
|
|
2363
|
+
return [...seen].slice(0, 15);
|
|
2364
|
+
}
|
|
2365
|
+
function clean(s, max) {
|
|
2366
|
+
if (typeof s !== "string") return "";
|
|
2367
|
+
const t = s.replace(/[`*_#"'“”「」]/g, "").replace(/[–—]/g, ",").replace(/\s+/g, " ").trim();
|
|
2368
|
+
return t.length > max ? `${t.slice(0, max - 1)}\u2026` : t;
|
|
2369
|
+
}
|
|
2370
|
+
function formatAutoTitle(category, subject) {
|
|
2371
|
+
const cat = clean(category, CATEGORY_MAX_CHARS).replace(/[\[\]]/g, "");
|
|
2372
|
+
const sub = clean(subject, SUBJECT_MAX_CHARS);
|
|
2373
|
+
if (!cat || !sub) return null;
|
|
2374
|
+
return `[${cat}] ${sub}`;
|
|
2375
|
+
}
|
|
2376
|
+
async function buildAutoTitle(input) {
|
|
2377
|
+
const apiKey = process.env["GROQ_API_KEY"];
|
|
2378
|
+
if (!apiKey || !input.body.trim()) return null;
|
|
2379
|
+
try {
|
|
2380
|
+
const whole = input.scope === "thread";
|
|
2381
|
+
const context = [
|
|
2382
|
+
`Channel: #${input.channelName}`,
|
|
2383
|
+
input.workspacePrompt ? `Workspace instructions:
|
|
2384
|
+
${input.workspacePrompt.slice(0, 1500)}` : "",
|
|
2385
|
+
input.channelPrompt ? `Channel instructions:
|
|
2386
|
+
${input.channelPrompt.slice(0, 1500)}` : "",
|
|
2387
|
+
`Existing categories: ${input.existingCategories.length ? input.existingCategories.join(", ") : "(none yet)"}`,
|
|
2388
|
+
whole ? `Conversation (latest last):
|
|
2389
|
+
${input.body.slice(-MAX_THREAD_CHARS)}` : `Opening message:
|
|
2390
|
+
${input.body.slice(0, MAX_BODY_CHARS)}`
|
|
2391
|
+
].filter(Boolean).join("\n\n");
|
|
2392
|
+
const res = await fetch(GROQ_API_URL, {
|
|
2393
|
+
method: "POST",
|
|
2394
|
+
signal: AbortSignal.timeout(whole ? THREAD_TIMEOUT_MS : TIMEOUT_MS),
|
|
2395
|
+
headers: { "content-type": "application/json", authorization: `Bearer ${apiKey}` },
|
|
2396
|
+
body: JSON.stringify({
|
|
2397
|
+
model: TITLE_MODEL,
|
|
2398
|
+
temperature: 0.2,
|
|
2399
|
+
// Reasoning model: the cap covers reasoning tokens too, so keep it roomy.
|
|
2400
|
+
reasoning_effort: "low",
|
|
2401
|
+
max_tokens: 400,
|
|
2402
|
+
response_format: { type: "json_object" },
|
|
2403
|
+
messages: [
|
|
2404
|
+
{
|
|
2405
|
+
role: "system",
|
|
2406
|
+
content: systemPrompt(input.titlePrompt?.trim().slice(0, 2e3) || DEFAULT_TITLE_RULES)
|
|
2407
|
+
},
|
|
2408
|
+
{ role: "user", content: context }
|
|
2409
|
+
]
|
|
2410
|
+
})
|
|
2411
|
+
});
|
|
2412
|
+
if (!res.ok) throw new Error(`Groq request failed (${res.status})`);
|
|
2413
|
+
const data = await res.json();
|
|
2414
|
+
const raw = data.choices?.[0]?.message?.content;
|
|
2415
|
+
if (!raw) return null;
|
|
2416
|
+
const parsed = JSON.parse(raw);
|
|
2417
|
+
return formatAutoTitle(parsed.category, parsed.subject);
|
|
2418
|
+
} catch (err) {
|
|
2419
|
+
console.error("Auto title failed, keeping first-line fallback:", err);
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
}
|
|
2423
|
+
|
|
2229
2424
|
// src/store/workspace-settings-store.ts
|
|
2230
2425
|
var BRANDING_DEFAULTS = {
|
|
2231
2426
|
theme: "default",
|
|
@@ -2234,7 +2429,9 @@ var BRANDING_DEFAULTS = {
|
|
|
2234
2429
|
displayName: null,
|
|
2235
2430
|
gaMeasurementId: null,
|
|
2236
2431
|
botName: null,
|
|
2237
|
-
botAvatar: null
|
|
2432
|
+
botAvatar: null,
|
|
2433
|
+
autoTitleEnabled: true,
|
|
2434
|
+
autoTitlePrompt: null
|
|
2238
2435
|
};
|
|
2239
2436
|
function rowToBranding(row) {
|
|
2240
2437
|
return {
|
|
@@ -2246,7 +2443,9 @@ function rowToBranding(row) {
|
|
|
2246
2443
|
displayName: row["display_name"] ?? null,
|
|
2247
2444
|
gaMeasurementId: row["ga_measurement_id"] ?? null,
|
|
2248
2445
|
botName: row["bot_name"] ?? null,
|
|
2249
|
-
botAvatar: row["bot_avatar"] ?? null
|
|
2446
|
+
botAvatar: row["bot_avatar"] ?? null,
|
|
2447
|
+
autoTitleEnabled: row["auto_title_enabled"] !== false,
|
|
2448
|
+
autoTitlePrompt: row["auto_title_prompt"] ?? null
|
|
2250
2449
|
};
|
|
2251
2450
|
}
|
|
2252
2451
|
var CACHE_TTL_MS = 3e4;
|
|
@@ -2254,7 +2453,7 @@ var cache = /* @__PURE__ */ new Map();
|
|
|
2254
2453
|
async function getWorkspaceBranding(profile) {
|
|
2255
2454
|
const hit = cache.get(profile);
|
|
2256
2455
|
if (hit && Date.now() - hit.readAt < CACHE_TTL_MS) return hit.value;
|
|
2257
|
-
const { data, error } = await getSupabaseClient().from("workspace_settings").select("theme, font, logo, display_name, ga_measurement_id, bot_name, bot_avatar").eq("profile", profile).maybeSingle();
|
|
2456
|
+
const { data, error } = await getSupabaseClient().from("workspace_settings").select("theme, font, logo, display_name, ga_measurement_id, bot_name, bot_avatar, auto_title_enabled, auto_title_prompt").eq("profile", profile).maybeSingle();
|
|
2258
2457
|
if (error) throw new Error(`failed to read workspace settings: ${error.message}`);
|
|
2259
2458
|
const value = data ? rowToBranding(data) : null;
|
|
2260
2459
|
cache.set(profile, { value, readAt: Date.now() });
|
|
@@ -2269,7 +2468,9 @@ async function upsertWorkspaceBranding(profile, patch) {
|
|
|
2269
2468
|
displayName: patch.displayName === void 0 ? current.displayName : patch.displayName,
|
|
2270
2469
|
gaMeasurementId: patch.gaMeasurementId === void 0 ? current.gaMeasurementId : patch.gaMeasurementId,
|
|
2271
2470
|
botName: patch.botName === void 0 ? current.botName : patch.botName,
|
|
2272
|
-
botAvatar: patch.botAvatar === void 0 ? current.botAvatar : patch.botAvatar
|
|
2471
|
+
botAvatar: patch.botAvatar === void 0 ? current.botAvatar : patch.botAvatar,
|
|
2472
|
+
autoTitleEnabled: patch.autoTitleEnabled ?? current.autoTitleEnabled,
|
|
2473
|
+
autoTitlePrompt: patch.autoTitlePrompt === void 0 ? current.autoTitlePrompt : patch.autoTitlePrompt
|
|
2273
2474
|
};
|
|
2274
2475
|
const { error } = await getSupabaseClient().from("workspace_settings").upsert(
|
|
2275
2476
|
{
|
|
@@ -2281,6 +2482,8 @@ async function upsertWorkspaceBranding(profile, patch) {
|
|
|
2281
2482
|
ga_measurement_id: next.gaMeasurementId,
|
|
2282
2483
|
bot_name: next.botName,
|
|
2283
2484
|
bot_avatar: next.botAvatar,
|
|
2485
|
+
auto_title_enabled: next.autoTitleEnabled,
|
|
2486
|
+
auto_title_prompt: next.autoTitlePrompt,
|
|
2284
2487
|
updated_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2285
2488
|
},
|
|
2286
2489
|
{ onConflict: "profile" }
|
|
@@ -2291,7 +2494,15 @@ async function upsertWorkspaceBranding(profile, patch) {
|
|
|
2291
2494
|
}
|
|
2292
2495
|
|
|
2293
2496
|
// src/dashboard/settings.ts
|
|
2294
|
-
var VALID_THEMES = [
|
|
2497
|
+
var VALID_THEMES = [
|
|
2498
|
+
"default",
|
|
2499
|
+
"classic",
|
|
2500
|
+
"sanctuary",
|
|
2501
|
+
"light",
|
|
2502
|
+
"sanctuary-light",
|
|
2503
|
+
"folio",
|
|
2504
|
+
"cheer"
|
|
2505
|
+
];
|
|
2295
2506
|
var VALID_FONTS = [
|
|
2296
2507
|
"default",
|
|
2297
2508
|
"system",
|
|
@@ -2299,7 +2510,8 @@ var VALID_FONTS = [
|
|
|
2299
2510
|
"exo2",
|
|
2300
2511
|
"lato",
|
|
2301
2512
|
"jakarta",
|
|
2302
|
-
"playfair"
|
|
2513
|
+
"playfair",
|
|
2514
|
+
"inter"
|
|
2303
2515
|
];
|
|
2304
2516
|
var LEGACY_AGENT_TO_MODEL = {
|
|
2305
2517
|
claude: "sonnet",
|
|
@@ -2339,7 +2551,8 @@ function normalizeProviders(entries) {
|
|
|
2339
2551
|
for (const runner of KNOWN_RUNNERS) {
|
|
2340
2552
|
if (!seen.has(runner)) chain.push({ runner });
|
|
2341
2553
|
}
|
|
2342
|
-
if (!chain[0].model)
|
|
2554
|
+
if (!chain[0].model)
|
|
2555
|
+
chain[0] = { runner: chain[0].runner, model: firstModelFor(chain[0].runner) };
|
|
2343
2556
|
return chain;
|
|
2344
2557
|
}
|
|
2345
2558
|
function parseProviders(raw, legacy) {
|
|
@@ -2392,6 +2605,7 @@ function loadDashboardSettings() {
|
|
|
2392
2605
|
return localDefaults();
|
|
2393
2606
|
}
|
|
2394
2607
|
}
|
|
2608
|
+
var AUTO_TITLE_PROMPT_MAX = 2e3;
|
|
2395
2609
|
function withDefaultModel(providers, model) {
|
|
2396
2610
|
const runner = runnerForModel(model);
|
|
2397
2611
|
const rest = providers.filter((p) => p.runner !== runner);
|
|
@@ -2446,7 +2660,9 @@ function brandingToSettings(branding) {
|
|
|
2446
2660
|
font: branding.font,
|
|
2447
2661
|
logo: branding.logo ?? void 0,
|
|
2448
2662
|
displayName: branding.displayName ?? void 0,
|
|
2449
|
-
gaMeasurementId: branding.gaMeasurementId ?? void 0
|
|
2663
|
+
gaMeasurementId: branding.gaMeasurementId ?? void 0,
|
|
2664
|
+
autoTitleEnabled: branding.autoTitleEnabled,
|
|
2665
|
+
autoTitlePrompt: branding.autoTitlePrompt ?? void 0
|
|
2450
2666
|
};
|
|
2451
2667
|
}
|
|
2452
2668
|
function legacyBranding(raw) {
|
|
@@ -2472,12 +2688,18 @@ async function saveWorkspaceBranding(patch, profile = getProfile()) {
|
|
|
2472
2688
|
font: patch.font && VALID_FONTS.includes(patch.font) ? patch.font : current.font,
|
|
2473
2689
|
logo: patch.logo === null ? null : typeof patch.logo === "string" ? patch.logo : void 0,
|
|
2474
2690
|
displayName: patch.displayName === null || patch.displayName === "" ? null : typeof patch.displayName === "string" ? patch.displayName.trim() : void 0,
|
|
2475
|
-
gaMeasurementId: patch.gaMeasurementId === null || patch.gaMeasurementId === "" ? null : typeof patch.gaMeasurementId === "string" ? parseGaMeasurementId(patch.gaMeasurementId) : void 0
|
|
2691
|
+
gaMeasurementId: patch.gaMeasurementId === null || patch.gaMeasurementId === "" ? null : typeof patch.gaMeasurementId === "string" ? parseGaMeasurementId(patch.gaMeasurementId) : void 0,
|
|
2692
|
+
autoTitleEnabled: typeof patch.autoTitleEnabled === "boolean" ? patch.autoTitleEnabled : void 0,
|
|
2693
|
+
autoTitlePrompt: patch.autoTitlePrompt === null || patch.autoTitlePrompt === "" ? null : typeof patch.autoTitlePrompt === "string" ? patch.autoTitlePrompt.trim().slice(0, AUTO_TITLE_PROMPT_MAX) || null : void 0
|
|
2476
2694
|
};
|
|
2477
2695
|
return brandingToSettings(await upsertWorkspaceBranding(profile, next));
|
|
2478
2696
|
}
|
|
2479
2697
|
async function loadFullDashboardSettings(profile = getProfile()) {
|
|
2480
|
-
return {
|
|
2698
|
+
return {
|
|
2699
|
+
...await loadWorkspaceBranding(profile),
|
|
2700
|
+
...loadDashboardSettings(),
|
|
2701
|
+
autoTitleDefaultPrompt: DEFAULT_TITLE_RULES
|
|
2702
|
+
};
|
|
2481
2703
|
}
|
|
2482
2704
|
function defaultPromptPath() {
|
|
2483
2705
|
return resolve5(paths.PACKAGE_ROOT, "system.default.md");
|
|
@@ -3308,8 +3530,8 @@ function renderAsciiTable(t) {
|
|
|
3308
3530
|
(h, i) => Math.max(h.length, ...rows2.map((r) => (r[i] || "").length))
|
|
3309
3531
|
);
|
|
3310
3532
|
const fmt = (cells) => "| " + cells.map((c, i) => c.padEnd(widths[i])).join(" | ") + " |";
|
|
3311
|
-
const
|
|
3312
|
-
return [fmt(headers),
|
|
3533
|
+
const sep4 = "|" + widths.map((w) => "-".repeat(w + 2)).join("|") + "|";
|
|
3534
|
+
return [fmt(headers), sep4, ...rows2.map(fmt)].join("\n");
|
|
3313
3535
|
}
|
|
3314
3536
|
|
|
3315
3537
|
// src/store/channel-cache.ts
|
|
@@ -4880,7 +5102,7 @@ import {
|
|
|
4880
5102
|
writeFileSync as writeFileSync4
|
|
4881
5103
|
} from "fs";
|
|
4882
5104
|
import { homedir as homedir3 } from "os";
|
|
4883
|
-
import { join as join8, resolve as resolve6, sep as
|
|
5105
|
+
import { join as join8, resolve as resolve6, sep as sep3 } from "path";
|
|
4884
5106
|
import yaml2 from "js-yaml";
|
|
4885
5107
|
var MAX_SKILL_BYTES = 2 * 1024 * 1024;
|
|
4886
5108
|
var SKILL_NAME = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
|
|
@@ -4901,7 +5123,7 @@ function isValidSkillName(name) {
|
|
|
4901
5123
|
function resolveSkillPath(dir, name) {
|
|
4902
5124
|
if (!isValidSkillName(name)) return null;
|
|
4903
5125
|
const abs = resolve6(dir, name, "SKILL.md");
|
|
4904
|
-
if (!abs.startsWith(resolve6(dir) +
|
|
5126
|
+
if (!abs.startsWith(resolve6(dir) + sep3)) return null;
|
|
4905
5127
|
return abs;
|
|
4906
5128
|
}
|
|
4907
5129
|
function splitFrontmatter(raw) {
|
|
@@ -8093,6 +8315,9 @@ export {
|
|
|
8093
8315
|
loadConfig,
|
|
8094
8316
|
refreshAutomationRegistry,
|
|
8095
8317
|
startAutomationRegistryPolling,
|
|
8318
|
+
isLogicalCwd,
|
|
8319
|
+
resolveLogicalCwd,
|
|
8320
|
+
ensureLogicalCwdDir,
|
|
8096
8321
|
expandPath,
|
|
8097
8322
|
canSeeChannel,
|
|
8098
8323
|
refreshChannelRegistry,
|
|
@@ -8107,9 +8332,12 @@ export {
|
|
|
8107
8332
|
resolveCwd,
|
|
8108
8333
|
resolveChannelPrompt,
|
|
8109
8334
|
createChannel,
|
|
8335
|
+
DefaultChannelError,
|
|
8110
8336
|
updateChannelById,
|
|
8111
8337
|
updateChannelsMcpConnections,
|
|
8112
8338
|
ensureAgentDmChannel,
|
|
8339
|
+
ensureGeneralChannel,
|
|
8340
|
+
ensureMemberDmChannel,
|
|
8113
8341
|
DEFAULT_TIMEZONE,
|
|
8114
8342
|
loadAutomations,
|
|
8115
8343
|
loadAutomationsFile,
|
|
@@ -8131,6 +8359,7 @@ export {
|
|
|
8131
8359
|
usageLimitedUntil,
|
|
8132
8360
|
usageLimitedModels,
|
|
8133
8361
|
buildSystemPrompt,
|
|
8362
|
+
docsAccessPromptNote,
|
|
8134
8363
|
askClaude,
|
|
8135
8364
|
getWorkspaceBySlug,
|
|
8136
8365
|
getWorkspaceById,
|
|
@@ -8140,6 +8369,7 @@ export {
|
|
|
8140
8369
|
deleteWorkspace,
|
|
8141
8370
|
setWorkspaceStripeSubscription,
|
|
8142
8371
|
markWorkspaceAgentConnected,
|
|
8372
|
+
markWorkspaceProjectsSetup,
|
|
8143
8373
|
getWorkspaceByStripeCustomerId,
|
|
8144
8374
|
getWorkspaceBilling,
|
|
8145
8375
|
updateWorkspacePlanStatus,
|
|
@@ -8150,6 +8380,9 @@ export {
|
|
|
8150
8380
|
listMembershipsByEmail,
|
|
8151
8381
|
listMemberships,
|
|
8152
8382
|
createMembership,
|
|
8383
|
+
HANDLE_PATTERN,
|
|
8384
|
+
HandleTakenError,
|
|
8385
|
+
setMembershipHandle,
|
|
8153
8386
|
LastAdminError,
|
|
8154
8387
|
updateMembershipRole,
|
|
8155
8388
|
markInviteResent,
|
|
@@ -8161,6 +8394,9 @@ export {
|
|
|
8161
8394
|
setAllowGuestAccess,
|
|
8162
8395
|
getAgentPaused,
|
|
8163
8396
|
setAgentPaused,
|
|
8397
|
+
DEFAULT_TITLE_RULES,
|
|
8398
|
+
extractCategories,
|
|
8399
|
+
buildAutoTitle,
|
|
8164
8400
|
BRANDING_DEFAULTS,
|
|
8165
8401
|
getWorkspaceBranding,
|
|
8166
8402
|
localDefaults,
|
|
@@ -149,7 +149,14 @@ function sweepScratchDir(now = Date.now()) {
|
|
|
149
149
|
|
|
150
150
|
// src/store/supabase-client.ts
|
|
151
151
|
import { createClient } from "@supabase/supabase-js";
|
|
152
|
+
var REQUEST_TIMEOUT_MS = 15e3;
|
|
152
153
|
var client;
|
|
154
|
+
function setSupabaseClient(replacement) {
|
|
155
|
+
client = replacement;
|
|
156
|
+
}
|
|
157
|
+
function hasSupabaseCredentials() {
|
|
158
|
+
return !!process.env["SUPABASE_URL"] && !!process.env["SUPABASE_SECRET_KEY"];
|
|
159
|
+
}
|
|
153
160
|
function getSupabaseClient() {
|
|
154
161
|
if (client) return client;
|
|
155
162
|
const url = process.env["SUPABASE_URL"];
|
|
@@ -157,7 +164,12 @@ function getSupabaseClient() {
|
|
|
157
164
|
if (!url || !key) {
|
|
158
165
|
throw new Error("SUPABASE_URL / SUPABASE_SECRET_KEY not set \u2014 check .env");
|
|
159
166
|
}
|
|
160
|
-
client = createClient(url, key, {
|
|
167
|
+
client = createClient(url, key, {
|
|
168
|
+
auth: { persistSession: false },
|
|
169
|
+
global: {
|
|
170
|
+
fetch: (input, init) => fetch(input, { ...init, signal: init?.signal ?? AbortSignal.timeout(REQUEST_TIMEOUT_MS) })
|
|
171
|
+
}
|
|
172
|
+
});
|
|
161
173
|
return client;
|
|
162
174
|
}
|
|
163
175
|
|
|
@@ -416,6 +428,8 @@ export {
|
|
|
416
428
|
paths,
|
|
417
429
|
ensureDirs,
|
|
418
430
|
sweepScratchDir,
|
|
431
|
+
setSupabaseClient,
|
|
432
|
+
hasSupabaseCredentials,
|
|
419
433
|
getSupabaseClient,
|
|
420
434
|
AGENT_AUTHOR,
|
|
421
435
|
SYNCED_BOT_AUTHOR,
|