flyspace-cli 0.1.35 → 0.1.36

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.
@@ -343,6 +343,7 @@ function rowToChannel(row) {
343
343
  model: row.model,
344
344
  effort: row.effort && isEffort(row.effort) ? row.effort : null,
345
345
  prompt: row.prompt?.trim() || null,
346
+ topicCategories: normaliseTopicCategories(row.topic_categories),
346
347
  // A uuid[] arrives as a real array; anything else (an older row read
347
348
  // through a cached client, a hand-edited value) reads as "no MCP", which
348
349
  // is the safe direction for a list that decides which credentials a run
@@ -528,6 +529,15 @@ function resolveEffort(explicitEffort, channelId, fallback) {
528
529
  if (explicitEffort) return explicitEffort;
529
530
  return resolveChannelEffort(channelId) ?? fallback;
530
531
  }
532
+ function normaliseTopicCategories(value) {
533
+ if (!Array.isArray(value)) return [];
534
+ const out = [];
535
+ for (const v of value) {
536
+ const t = typeof v === "string" ? v.trim() : "";
537
+ if (t && !out.includes(t)) out.push(t);
538
+ }
539
+ return out;
540
+ }
531
541
  function toRow(input) {
532
542
  const row = {};
533
543
  if (input.name !== void 0) row["name"] = input.name;
@@ -537,6 +547,10 @@ function toRow(input) {
537
547
  if (input.model !== void 0) row["model"] = input.model;
538
548
  if (input.effort !== void 0) row["effort"] = input.effort;
539
549
  if (input.prompt !== void 0) row["prompt"] = input.prompt?.trim() || null;
550
+ if (input.topicCategories !== void 0) {
551
+ const list = normaliseTopicCategories(input.topicCategories);
552
+ row["topic_categories"] = list.length ? list : null;
553
+ }
540
554
  if (input.access !== void 0) row["access"] = input.access;
541
555
  if (input.visibility !== void 0) row["visibility"] = input.visibility;
542
556
  if (input.memberEmails !== void 0) {
@@ -2625,7 +2639,7 @@ async function listWorkspaceSummaries() {
2625
2639
  const [workspaces, settings, channels, memberships, activity] = await Promise.all([
2626
2640
  db.from("workspaces").select("id, slug, created_at, agent_connected_at, plan, plan_status, archived_at"),
2627
2641
  db.from("workspace_settings").select("profile, display_name, logo"),
2628
- db.from("channels").select("profile").is("archived_at", null),
2642
+ db.from("channels").select("profile, is_default").is("archived_at", null),
2629
2643
  db.from("memberships").select("workspace_id, user_id, email, handle, role, accepted_at").eq("status", "active").eq("is_support", false).order("invited_at", { ascending: true }),
2630
2644
  db.rpc("workspace_last_activity")
2631
2645
  ]);
@@ -2646,6 +2660,9 @@ async function listWorkspaceSummaries() {
2646
2660
  );
2647
2661
  const tally = (keys) => keys.reduce((m, k) => m.set(k, (m.get(k) ?? 0) + 1), /* @__PURE__ */ new Map());
2648
2662
  const channelCounts = tally((channels.data ?? []).map((c) => c.profile));
2663
+ const ownChannelCounts = tally(
2664
+ (channels.data ?? []).filter((c) => !c.is_default).map((c) => c.profile)
2665
+ );
2649
2666
  const memberCounts = tally((memberships.data ?? []).map((m) => m.workspace_id));
2650
2667
  const owners = /* @__PURE__ */ new Map();
2651
2668
  for (const m of memberships.data ?? []) {
@@ -2672,6 +2689,7 @@ async function listWorkspaceSummaries() {
2672
2689
  displayName: displayNames.get(w.slug) || null,
2673
2690
  logo: logos.get(w.slug) || null,
2674
2691
  channelCount: channelCounts.get(w.slug) ?? 0,
2692
+ ownChannelCount: ownChannelCounts.get(w.slug) ?? 0,
2675
2693
  memberCount: memberCounts.get(w.id) ?? 0,
2676
2694
  lastActivityAt: lastActivity.get(w.slug) ?? null,
2677
2695
  owner: owners.get(w.id) ?? null
@@ -2961,8 +2979,8 @@ var MAX_BODY_CHARS = 4e3;
2961
2979
  var MAX_THREAD_CHARS = 12e3;
2962
2980
  var THREAD_TIMEOUT_MS = 15e3;
2963
2981
  var SUBJECT_MAX_CHARS = 30;
2964
- var CATEGORY_MAX_CHARS = 12;
2965
- 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.
2982
+ var CATEGORY_MAX_CHARS = 16;
2983
+ var DEFAULT_TITLE_RULES = `- category: ONE short phrase naming the component or feature area the thread is about (e.g. Docs pane, Auto Topic, Login, Billing), not the kind of request. Pick from Channel categories whenever one fits, otherwise reuse one of the Existing categories; invent a new area name only if none fits. Generic types such as Bug, Feature, Idea or Question are a last resort, only when no area can be named.
2966
2984
  - 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.
2967
2985
  - If the Workspace or Channel instructions contain title or naming rules, follow them over the defaults above.
2968
2986
  - Keep the message's own language. If it is Traditional Chinese (Taiwan), use Taiwan wording and keep technical terms in English.
@@ -3003,6 +3021,7 @@ async function buildAutoTitle(input) {
3003
3021
  ${input.workspacePrompt.slice(0, 1500)}` : "",
3004
3022
  input.channelPrompt ? `Channel instructions:
3005
3023
  ${input.channelPrompt.slice(0, 1500)}` : "",
3024
+ input.channelCategories.length ? `Channel categories (pick one of these whenever it fits): ${input.channelCategories.join(", ")}` : "",
3006
3025
  `Existing categories: ${input.existingCategories.length ? input.existingCategories.join(", ") : "(none yet)"}`,
3007
3026
  whole ? `Conversation (latest last):
3008
3027
  ${input.body.slice(-MAX_THREAD_CHARS)}` : `Opening message:
package/dist/cli.js CHANGED
@@ -187,7 +187,7 @@ import {
187
187
  warnOnBrokenChannelCwds,
188
188
  workspaceSkillsDir,
189
189
  writeSkillFile
190
- } from "./chunk-KTMAS4TP.js";
190
+ } from "./chunk-HG5Z6QVF.js";
191
191
  import {
192
192
  firstReplyBody,
193
193
  getThread,
@@ -1861,7 +1861,7 @@ async function runAuthCmd(args) {
1861
1861
  }
1862
1862
 
1863
1863
  // ../api/src/version.ts
1864
- var CLI_VERSION = true ? "0.1.35" : null;
1864
+ var CLI_VERSION = true ? "0.1.36" : null;
1865
1865
  function isOlderVersion(a, b) {
1866
1866
  const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
1867
1867
  const [pa, pb] = [parse(a), parse(b)];
@@ -2471,7 +2471,7 @@ function npmEnv(target) {
2471
2471
  return nodeBin ? { ...process.env, PATH: `${nodeBin}:${process.env.PATH ?? ""}` } : process.env;
2472
2472
  }
2473
2473
  async function runUpgrade() {
2474
- const current = "0.1.35";
2474
+ const current = "0.1.36";
2475
2475
  console.log(`Current version: ${current}`);
2476
2476
  console.log("Checking for updates...\n");
2477
2477
  try {
@@ -3331,19 +3331,31 @@ function hashInviteToken(token) {
3331
3331
  }
3332
3332
  async function issueInvite(membershipId, message) {
3333
3333
  const db = getSupabaseClient();
3334
- const { error: clearError } = await db.from("workspace_invites").delete().eq("membership_id", membershipId).is("accepted_at", null);
3335
- if (clearError) throw new Error(`issueInvite(${membershipId}): ${clearError.message}`);
3336
3334
  const token = randomBytes2(32).toString("hex");
3337
- const expiresAt = new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1e3);
3338
- const { error: error2 } = await db.from("workspace_invites").insert({
3335
+ const expiresAt = new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1e3).toISOString();
3336
+ const { data: pending2, error: pendingError } = await db.from("workspace_invites").select("id, token_hash, previous_token_hashes").eq("membership_id", membershipId).is("accepted_at", null).order("created_at", { ascending: false }).limit(1).maybeSingle();
3337
+ if (pendingError) throw new Error(`issueInvite(${membershipId}): ${pendingError.message}`);
3338
+ const { error: error2 } = pending2 ? await db.from("workspace_invites").update({
3339
+ token_hash: hashInviteToken(token),
3340
+ previous_token_hashes: [
3341
+ ...pending2.previous_token_hashes ?? [],
3342
+ pending2.token_hash
3343
+ ],
3344
+ message,
3345
+ expires_at: expiresAt
3346
+ }).eq("id", pending2.id) : await db.from("workspace_invites").insert({
3339
3347
  membership_id: membershipId,
3340
3348
  token_hash: hashInviteToken(token),
3341
3349
  message,
3342
- expires_at: expiresAt.toISOString()
3350
+ expires_at: expiresAt
3343
3351
  });
3344
3352
  if (error2) throw new Error(`issueInvite(${membershipId}): ${error2.message}`);
3345
3353
  return token;
3346
3354
  }
3355
+ function tokenHashFilter(token) {
3356
+ const hash = hashInviteToken(token);
3357
+ return `token_hash.eq.${hash},previous_token_hashes.cs.{${hash}}`;
3358
+ }
3347
3359
  async function latestInviteMessage(membershipId) {
3348
3360
  const { data, error: error2 } = await getSupabaseClient().from("workspace_invites").select("message").eq("membership_id", membershipId).order("created_at", { ascending: false }).limit(1).maybeSingle();
3349
3361
  if (error2) throw new Error(`latestInviteMessage(${membershipId}): ${error2.message}`);
@@ -3352,7 +3364,7 @@ async function latestInviteMessage(membershipId) {
3352
3364
  async function lookupInviteByToken(token) {
3353
3365
  const { data, error: error2 } = await getSupabaseClient().from("workspace_invites").select(
3354
3366
  "id, message, expires_at, accepted_at, memberships!inner(workspace_id, user_id, email)"
3355
- ).eq("token_hash", hashInviteToken(token)).maybeSingle();
3367
+ ).or(tokenHashFilter(token)).maybeSingle();
3356
3368
  if (error2) throw new Error(`lookupInviteByToken: ${error2.message}`);
3357
3369
  return toInviteLookup(data);
3358
3370
  }
@@ -3817,12 +3829,19 @@ async function revokeSetupInvite(id) {
3817
3829
  }
3818
3830
  async function reissueSetupInvite(id) {
3819
3831
  const db = getSupabaseClient();
3820
- const { data, error: error2 } = await db.from("workspace_setup_invites").select("email, owner_name, message").eq("id", id).is("accepted_at", null).maybeSingle();
3832
+ const { data, error: error2 } = await db.from("workspace_setup_invites").select("email, owner_name, message, token_hash, previous_token_hashes").eq("id", id).is("accepted_at", null).maybeSingle();
3821
3833
  if (error2) throw new Error(`reissueSetupInvite(${id}): ${error2.message}`);
3822
3834
  if (!data) throw new Error("no pending invite with that id");
3823
3835
  const token = randomBytes3(32).toString("hex");
3824
3836
  const expiresAt = new Date(Date.now() + INVITE_TTL_DAYS * 24 * 60 * 60 * 1e3);
3825
- const { error: updateError } = await db.from("workspace_setup_invites").update({ token_hash: hashInviteToken(token), expires_at: expiresAt.toISOString() }).eq("id", id);
3837
+ const { error: updateError } = await db.from("workspace_setup_invites").update({
3838
+ token_hash: hashInviteToken(token),
3839
+ previous_token_hashes: [
3840
+ ...data.previous_token_hashes ?? [],
3841
+ data.token_hash
3842
+ ],
3843
+ expires_at: expiresAt.toISOString()
3844
+ }).eq("id", id);
3826
3845
  if (updateError) throw new Error(`reissueSetupInvite(${id}): ${updateError.message}`);
3827
3846
  return {
3828
3847
  token,
@@ -3834,7 +3853,7 @@ async function reissueSetupInvite(id) {
3834
3853
  };
3835
3854
  }
3836
3855
  async function lookupSetupInvite(token) {
3837
- const { data, error: error2 } = await getSupabaseClient().from("workspace_setup_invites").select("id, email, owner_name, plan, message, expires_at, accepted_at, workspace_id").eq("token_hash", hashInviteToken(token)).maybeSingle();
3856
+ const { data, error: error2 } = await getSupabaseClient().from("workspace_setup_invites").select("id, email, owner_name, plan, message, expires_at, accepted_at, workspace_id").or(tokenHashFilter(token)).maybeSingle();
3838
3857
  if (error2) throw new Error(`lookupSetupInvite: ${error2.message}`);
3839
3858
  if (!data) return { state: "unknown" };
3840
3859
  if (data.accepted_at) return { state: "used", workspaceId: data.workspace_id ?? null };
@@ -10596,6 +10615,7 @@ async function labelThread(channelId, body, scope) {
10596
10615
  return buildAutoTitle({
10597
10616
  channelName: channel.name,
10598
10617
  channelPrompt: channel.prompt,
10618
+ channelCategories: channel.topicCategories,
10599
10619
  workspacePrompt: loadWorkspacePrompt().prompt,
10600
10620
  titlePrompt: (await loadWorkspaceBranding()).autoTitlePrompt,
10601
10621
  existingCategories,
@@ -14374,6 +14394,7 @@ function registerDataRoutes(api, agent = {}) {
14374
14394
  model: ch.model ?? void 0,
14375
14395
  effort: ch.effort ?? void 0,
14376
14396
  prompt: ch.prompt ?? void 0,
14397
+ topicCategories: ch.topicCategories,
14377
14398
  access: ch.access,
14378
14399
  visibility: ch.visibility,
14379
14400
  memberEmails: ch.memberEmails,
@@ -15222,6 +15243,9 @@ function coerceRuntime(raw) {
15222
15243
  function coerceEffort(raw) {
15223
15244
  return typeof raw === "string" && isEffort(raw) ? raw : void 0;
15224
15245
  }
15246
+ function coerceTopicCategories(raw) {
15247
+ return Array.isArray(raw) ? raw.filter((v) => typeof v === "string") : void 0;
15248
+ }
15225
15249
  function coerceMounts(raw) {
15226
15250
  const list = typeof raw === "string" ? raw.split("\n") : raw;
15227
15251
  if (!Array.isArray(list)) return void 0;
@@ -15258,6 +15282,7 @@ function toResponse3(channel) {
15258
15282
  model: channel.model ?? void 0,
15259
15283
  effort: channel.effort ?? void 0,
15260
15284
  prompt: channel.prompt ?? void 0,
15285
+ topicCategories: channel.topicCategories,
15261
15286
  access: channel.access,
15262
15287
  visibility: channel.visibility,
15263
15288
  memberEmails: channel.memberEmails,
@@ -15289,6 +15314,7 @@ function createChannelWriteRouter() {
15289
15314
  model: model || null,
15290
15315
  effort: coerceEffort(body["effort"]) ?? null,
15291
15316
  prompt: typeof body["prompt"] === "string" ? body["prompt"] : null,
15317
+ topicCategories: coerceTopicCategories(body["topicCategories"]),
15292
15318
  access: coerceAccess(body["access"]),
15293
15319
  visibility: coerceVisibility(body["visibility"]),
15294
15320
  memberEmails: coerceMemberEmails(body["memberEmails"]),
@@ -15318,6 +15344,7 @@ function createChannelWriteRouter() {
15318
15344
  model: model || null,
15319
15345
  effort: coerceEffort(body["effort"]) ?? null,
15320
15346
  prompt: typeof body["prompt"] === "string" ? body["prompt"] : null,
15347
+ topicCategories: coerceTopicCategories(body["topicCategories"]),
15321
15348
  access: coerceAccess(body["access"]),
15322
15349
  visibility: coerceVisibility(body["visibility"]),
15323
15350
  memberEmails: coerceMemberEmails(body["memberEmails"]),
@@ -15360,6 +15387,7 @@ function createChannelWriteRouter() {
15360
15387
  if (typeof body["position"] === "number") patch.position = body["position"];
15361
15388
  if (typeof body["model"] === "string") patch.model = body["model"] || null;
15362
15389
  if ("effort" in body) patch.effort = coerceEffort(body["effort"]) ?? null;
15390
+ if ("topicCategories" in body) patch.topicCategories = coerceTopicCategories(body["topicCategories"]);
15363
15391
  if ("prompt" in body) {
15364
15392
  patch.prompt = typeof body["prompt"] === "string" ? body["prompt"] : null;
15365
15393
  }
@@ -19993,7 +20021,7 @@ var MAX_AGE_MINUTES = 30;
19993
20021
  var GRACE_MS = 6e4;
19994
20022
  var BODY_LIMIT = 140;
19995
20023
  function preview(body) {
19996
- const flat = body.replace(/```[\s\S]*?```/g, "[code]").replace(/\s+/g, " ").trim();
20024
+ const flat = body.replace(/```[\s\S]*?```/g, "[code]").replace(/!\[([^\]]*)\]\([^)]*\)/g, (_, alt) => alt || "[image]").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/<(https?:[^|>]+)\|([^>]+)>/g, "$2").replace(/^\s{0,3}(?:#{1,6}\s+|>\s?|[-*+]\s+(?:\[[ xX]\]\s+)?|\d+[.)]\s+)/gm, "").replace(/^\s*(?:[-*_]\s*){3,}$/gm, "").replace(/(\*\*|__|~~)(.+?)\1/g, "$2").replace(/(^|[^\w*])[*_](\S(?:.*?\S)?)[*_](?=[^\w*]|$)/g, "$1$2").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
19997
20025
  return flat.length > BODY_LIMIT ? `${flat.slice(0, BODY_LIMIT - 1)}\u2026` : flat;
19998
20026
  }
19999
20027
  async function channelNames(ids) {
@@ -20679,7 +20707,7 @@ async function main() {
20679
20707
  break;
20680
20708
  case "-v":
20681
20709
  case "--version":
20682
- printRaw("0.1.35");
20710
+ printRaw("0.1.36");
20683
20711
  break;
20684
20712
  case "-h":
20685
20713
  case "--help":
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startServer
4
- } from "./chunk-KTMAS4TP.js";
4
+ } from "./chunk-HG5Z6QVF.js";
5
5
  import "./chunk-GYHCSGMJ.js";
6
6
  export {
7
7
  startServer
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flyspace-cli",
3
- "version": "0.1.35",
3
+ "version": "0.1.36",
4
4
  "description": "AI-native headless community OS",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,