flyspace-cli 0.1.41 → 0.1.42

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.
Files changed (2) hide show
  1. package/dist/cli.js +129 -69
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -19487,7 +19487,7 @@ async function runAuthCmd(args) {
19487
19487
 
19488
19488
  // ../api/src/version.ts
19489
19489
  import { readFileSync as readFileSync8 } from "fs";
19490
- var CLI_VERSION = true ? "0.1.41" : devVersion();
19490
+ var CLI_VERSION = true ? "0.1.42" : devVersion();
19491
19491
  function isOlderVersion(a, b) {
19492
19492
  const parse = (v) => v.replace(/^v/, "").split("-")[0].split(".").map(Number);
19493
19493
  const [pa, pb] = [parse(a), parse(b)];
@@ -19672,9 +19672,9 @@ import { tmpdir } from "os";
19672
19672
  import { promisify } from "util";
19673
19673
  var execAsync = promisify(execFile);
19674
19674
  var exec = (cmd, args, opts) => {
19675
- const run4 = execAsync(cmd, args, opts);
19676
- run4.child.stdin?.end();
19677
- return run4;
19675
+ const run5 = execAsync(cmd, args, opts);
19676
+ run5.child.stdin?.end();
19677
+ return run5;
19678
19678
  };
19679
19679
  var BOLD3 = "\x1B[1m";
19680
19680
  var DIM3 = "\x1B[2m";
@@ -20111,7 +20111,7 @@ function npmEnv(target) {
20111
20111
  return nodeBin ? { ...process.env, PATH: `${nodeBin}:${process.env.PATH ?? ""}` } : process.env;
20112
20112
  }
20113
20113
  async function runUpgrade() {
20114
- const current = "0.1.41";
20114
+ const current = "0.1.42";
20115
20115
  console.log(`Current version: ${current}`);
20116
20116
  console.log("Checking for updates...\n");
20117
20117
  try {
@@ -25331,9 +25331,9 @@ async function synthesizeWithRetry(script, voice) {
25331
25331
  }
25332
25332
  var renderQueue = Promise.resolve();
25333
25333
  function queued(job) {
25334
- const run4 = renderQueue.then(job, job);
25335
- renderQueue = run4.catch(() => void 0);
25336
- return run4;
25334
+ const run5 = renderQueue.then(job, job);
25335
+ renderQueue = run5.catch(() => void 0);
25336
+ return run5;
25337
25337
  }
25338
25338
  function renderArticleAudio(title, content, lang) {
25339
25339
  return queued(() => renderArticleAudioNow(title, content, lang));
@@ -25407,12 +25407,12 @@ function queueShareAudio(share) {
25407
25407
  }
25408
25408
  const current = inFlight2.get(share.id);
25409
25409
  if (current?.hash === hash) return current.done;
25410
- const run4 = () => runShareAudioJob(share, hash).catch((err) => {
25410
+ const run5 = () => runShareAudioJob(share, hash).catch((err) => {
25411
25411
  console.error(`share audio job crashed (${share.id}):`, err);
25412
25412
  }).finally(() => {
25413
25413
  if (inFlight2.get(share.id)?.hash === hash) inFlight2.delete(share.id);
25414
25414
  });
25415
- const done = current ? current.done.then(run4) : run4();
25415
+ const done = current ? current.done.then(run5) : run5();
25416
25416
  inFlight2.set(share.id, { hash, done });
25417
25417
  return done;
25418
25418
  }
@@ -29973,22 +29973,13 @@ function createTranscribeRouter() {
29973
29973
  import { Hono as Hono19 } from "hono";
29974
29974
  var GROQ_API_URL8 = "https://api.groq.com/openai/v1/chat/completions";
29975
29975
  var MODEL2 = "openai/gpt-oss-120b";
29976
- var MAX_BEFORE_CHARS = 2e3;
29977
- var MAX_THREAD_CHARS = 1500;
29976
+ var MAX_REPLY_CHARS = 4e3;
29977
+ var MAX_EARLIER_CHARS = 1500;
29978
29978
  var THREAD_TAIL_REPLIES = 5;
29979
- var MIN_CJK_CHARS = 8;
29980
- var MIN_WORDS = 4;
29981
- var RULES2 = `You suggest the rest of a message a user is writing to an AI coding agent or teammates, like Claude Code's prompt suggestions. The user usually asks a question or gives an instruction.
29982
- Infer the concrete thing they are about to ask or request from the conversation (the agent's last reply, files, features, errors, names mentioned) and complete THAT, using specific nouns from the context.
29983
- Never add greetings, thanks, pleasantries, filler, or generic chat phrases. Never repeat the draft.
29984
- Output ONLY the continuation text (one sentence at most), no quotes or explanation, in the draft's language. Start with a space if the continuation needs one.
29985
- If you cannot predict a specific, useful continuation, output nothing.`;
29986
- function hasIntent(before) {
29987
- const paragraph = before.split("\n").pop() ?? "";
29988
- const cjk = paragraph.match(/[\u3400-\u9fff]/g)?.length ?? 0;
29989
- const words = paragraph.match(/[A-Za-z0-9]+/g)?.length ?? 0;
29990
- return cjk >= MIN_CJK_CHARS || words >= MIN_WORDS || cjk + words * 2 >= MIN_CJK_CHARS;
29991
- }
29979
+ var RULES2 = `You predict the next message a user will send in a chat thread, right after the AI agent's last reply, like Claude Code's prompt suggestions. The composer is empty; you suggest the whole message.
29980
+ Base it on the agent's last reply: answer a question it asked, approve or pick an option it offered, or ask for the obvious next step (commit, deploy, run the tests, check a specific file or feature it named). Use specific nouns from the reply.
29981
+ Keep it short (under 15 words), in the language the user writes in. No greetings, thanks or filler.
29982
+ Output ONLY the message text, no quotes or explanation. If there is no clear next step, output nothing.`;
29992
29983
  function createAutocompleteRouter() {
29993
29984
  const app = new Hono19();
29994
29985
  app.post("/autocomplete", async (c) => {
@@ -29997,15 +29988,14 @@ function createAutocompleteRouter() {
29997
29988
  const apiKey = process.env["GROQ_API_KEY"];
29998
29989
  if (!apiKey) return c.json({ error: "autocomplete is not configured" }, 503);
29999
29990
  const body = await c.req.json().catch(() => null);
30000
- const before = typeof body?.before === "string" ? body.before.slice(-MAX_BEFORE_CHARS) : "";
30001
- if (!hasIntent(before)) return c.json({ text: "" });
30002
29991
  const channel = typeof body?.channelId === "string" ? getChannelRegistry().find((ch) => ch.id === body.channelId) : void 0;
30003
- const visible = channel && canSeeChannel(channel, c.get("auth")) ? channel : void 0;
30004
- const thread = visible && typeof body?.threadId === "string" ? await threadContext(visible, body.threadId) : "";
30005
- const context = visible ? `Channel: #${visible.name}${visible.prompt ? `
30006
- Channel instructions: ${visible.prompt.slice(0, 500)}` : ""}
30007
- ${thread}
30008
- ` : "";
29992
+ if (!channel || !canSeeChannel(channel, c.get("auth")) || typeof body?.threadId !== "string")
29993
+ return c.json({ text: "" });
29994
+ const thread = await threadContext(channel, body.threadId);
29995
+ if (!thread) return c.json({ text: "" });
29996
+ const context = `Channel: #${channel.name}${channel.prompt ? `
29997
+ Channel instructions: ${channel.prompt.slice(0, 500)}` : ""}
29998
+ ${thread}`;
30009
29999
  const res = await fetch(GROQ_API_URL8, {
30010
30000
  method: "POST",
30011
30001
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
@@ -30019,8 +30009,7 @@ ${thread}
30019
30009
  temperature: 0.2,
30020
30010
  messages: [
30021
30011
  { role: "system", content: RULES2 },
30022
- { role: "user", content: `${context}Draft so far:
30023
- ${before}` }
30012
+ { role: "user", content: context }
30024
30013
  ]
30025
30014
  })
30026
30015
  }).catch(() => null);
@@ -30041,15 +30030,18 @@ async function threadContext(channel, threadId) {
30041
30030
  ]);
30042
30031
  const channelId = thread.data?.channel_id;
30043
30032
  if (!channelId || channelId !== channel.id && channelId !== channel.externalChannelId) return "";
30033
+ const [last, ...older] = tail.data ?? [];
30034
+ if (!last || last.author !== AGENT_AUTHOR) return "";
30044
30035
  const rows = [...opener.data ?? []];
30045
- for (const r of (tail.data ?? []).reverse())
30046
- if (!rows.some((x) => x.id === r.id)) rows.push(r);
30047
- const text = rows.map((r) => `${r.author}: ${r.body}`).join("\n\n").slice(-MAX_THREAD_CHARS);
30036
+ for (const r of older.reverse()) if (!rows.some((x) => x.id === r.id)) rows.push(r);
30037
+ const earlier = rows.filter((r) => r.id !== last.id).map((r) => `${r.author}: ${r.body}`).join("\n\n").slice(-MAX_EARLIER_CHARS);
30048
30038
  const title = thread.data?.title ? `Thread title: ${thread.data.title}
30049
30039
  ` : "";
30050
- return text ? `${title}Conversation so far:
30051
- ${text}
30052
- ` : title;
30040
+ return `${title}Earlier messages:
30041
+ ${earlier}
30042
+
30043
+ Agent's last reply:
30044
+ ${last.body.slice(-MAX_REPLY_CHARS)}`;
30053
30045
  }
30054
30046
 
30055
30047
  // ../api/src/dashboard/avatars-routes.ts
@@ -31782,10 +31774,10 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
31782
31774
  }
31783
31775
  }
31784
31776
  var COMPACT_THRESHOLD_TOKENS = Number(process.env["SESSION_COMPACT_THRESHOLD_TOKENS"]) || 14e4;
31785
- function contextOf(run4) {
31786
- if (!run4) return void 0;
31787
- const total = run4.inputTokens + run4.cacheReadTokens + run4.cacheCreationTokens;
31788
- const turns = run4.numTurns ?? 1;
31777
+ function contextOf(run5) {
31778
+ if (!run5) return void 0;
31779
+ const total = run5.inputTokens + run5.cacheReadTokens + run5.cacheCreationTokens;
31780
+ const turns = run5.numTurns ?? 1;
31789
31781
  return Math.round(turns > 0 ? total / turns : total);
31790
31782
  }
31791
31783
  async function buildThreadUsage(threadTs) {
@@ -32587,6 +32579,12 @@ function registerDataRoutes(api, agent = {}) {
32587
32579
  if (!res) return c.json({ error: "Host is offline" }, 503);
32588
32580
  return c.json(res, 202);
32589
32581
  });
32582
+ api.get("/mcp-servers", async (c) => {
32583
+ const auth = c.get("auth");
32584
+ if (!await isHostOwner(auth)) return c.json({ error: "only the Host owner can view MCP servers" }, 403);
32585
+ const res = await agent.listMcpServers?.();
32586
+ return c.json(res ?? { servers: null });
32587
+ });
32590
32588
  api.get("/channels", async (c) => {
32591
32589
  const auth = c.get("auth");
32592
32590
  const channels = getProfileChannelRegistry().filter((ch) => canSeeChannel(ch, auth));
@@ -32645,17 +32643,17 @@ function registerDataRoutes(api, agent = {}) {
32645
32643
  const auto = loadEffectiveAutomations();
32646
32644
  const runs2 = /* @__PURE__ */ new Map();
32647
32645
  try {
32648
- for (const [name, run4] of await lastRunsForWorkspace(getProfile())) {
32649
- runs2.set(name, { lastRun: run4.lastRunAt.toISOString(), lastStatus: run4.lastStatus });
32646
+ for (const [name, run5] of await lastRunsForWorkspace(getProfile())) {
32647
+ runs2.set(name, { lastRun: run5.lastRunAt.toISOString(), lastStatus: run5.lastStatus });
32650
32648
  }
32651
32649
  } catch {
32652
32650
  const runStore = existsSync20(paths.DB_FILE) ? getLocalCache().openAutomationRunStore(paths.DB_FILE) : null;
32653
32651
  if (runStore) {
32654
32652
  try {
32655
- for (const run4 of runStore.listLastRuns()) {
32656
- runs2.set(run4.name, {
32657
- lastRun: run4.lastRunAt.toISOString(),
32658
- lastStatus: run4.lastStatus
32653
+ for (const run5 of runStore.listLastRuns()) {
32654
+ runs2.set(run5.name, {
32655
+ lastRun: run5.lastRunAt.toISOString(),
32656
+ lastStatus: run5.lastStatus
32659
32657
  });
32660
32658
  }
32661
32659
  } finally {
@@ -32846,17 +32844,17 @@ function registerDataRoutes(api, agent = {}) {
32846
32844
  }
32847
32845
  const threadLinks = await resolveThreadLinks(raw);
32848
32846
  const sessions2 = raw.map((s) => {
32849
- const run4 = latest2.get(s.threadTs);
32850
- const turns = run4?.numTurns ?? 1;
32851
- const total = run4 ? run4.inputTokens + run4.cacheReadTokens + run4.cacheCreationTokens : void 0;
32847
+ const run5 = latest2.get(s.threadTs);
32848
+ const turns = run5?.numTurns ?? 1;
32849
+ const total = run5 ? run5.inputTokens + run5.cacheReadTokens + run5.cacheCreationTokens : void 0;
32852
32850
  const link = s.channelId.startsWith("chat:") ? threadLinks.chat.get(s.threadTs) : threadLinks.external.get(`${s.channelId}:${s.threadTs}`);
32853
32851
  return {
32854
32852
  ...s,
32855
32853
  channelLabel: channelLabel(s.channelId),
32856
- model: run4?.model,
32857
- numTurns: run4?.numTurns,
32854
+ model: run5?.model,
32855
+ numTurns: run5?.numTurns,
32858
32856
  contextTokens: total !== void 0 ? Math.round(total / (turns > 0 ? turns : 1)) : void 0,
32859
- rolledOver: run4?.label === "rollover",
32857
+ rolledOver: run5?.label === "rollover",
32860
32858
  threadTitle: link?.title ?? void 0,
32861
32859
  chatChannelId: link?.chatChannelId,
32862
32860
  chatThreadId: link?.chatThreadId,
@@ -34880,21 +34878,21 @@ var ChatAgent = class {
34880
34878
  * kill.
34881
34879
  */
34882
34880
  async maybeSupersede(msg) {
34883
- const run4 = this.activeRuns.get(msg.threadId);
34884
- if (!run4 || run4.msg.author !== msg.author || run4.msg.replyId === msg.replyId) return;
34881
+ const run5 = this.activeRuns.get(msg.threadId);
34882
+ if (!run5 || run5.msg.author !== msg.author || run5.msg.replyId === msg.replyId) return;
34885
34883
  try {
34886
- if (!await this.shouldInterrupt(run4.msg.body, msg.body)) return;
34884
+ if (!await this.shouldInterrupt(run5.msg.body, msg.body)) return;
34887
34885
  } catch (err) {
34888
34886
  console.error("[chat-agent] interrupt classifier failed:", err.message);
34889
34887
  return;
34890
34888
  }
34891
- if (this.activeRuns.get(msg.threadId) !== run4) return;
34889
+ if (this.activeRuns.get(msg.threadId) !== run5) return;
34892
34890
  console.log(
34893
34891
  `[chat-agent] follow-up supersedes in-flight run \u2014 aborting. thread=${msg.threadId}`
34894
34892
  );
34895
- run4.superseded = true;
34893
+ run5.superseded = true;
34896
34894
  this.supersedingReplies.add(msg.replyId);
34897
- run4.abort.abort();
34895
+ run5.abort.abort();
34898
34896
  }
34899
34897
  /**
34900
34898
  * Picks up messages nobody answered. `handleMessage` is fire-and-forget and
@@ -35102,11 +35100,11 @@ var ChatAgent = class {
35102
35100
  * continue with `--resume`. Returns whether a run was actually in flight —
35103
35101
  * a queued-but-not-started message is not affected. */
35104
35102
  cancel(threadId) {
35105
- const run4 = this.activeRuns.get(threadId);
35106
- if (!run4) return false;
35107
- run4.superseded = true;
35108
- this.stoppedReplies.set(threadId, run4.msg.replyId);
35109
- run4.abort.abort();
35103
+ const run5 = this.activeRuns.get(threadId);
35104
+ if (!run5) return false;
35105
+ run5.superseded = true;
35106
+ this.stoppedReplies.set(threadId, run5.msg.replyId);
35107
+ run5.abort.abort();
35110
35108
  return true;
35111
35109
  }
35112
35110
  /**
@@ -38023,6 +38021,67 @@ function startHostUpgrade() {
38023
38021
  return { started: true };
38024
38022
  }
38025
38023
 
38024
+ // src/dashboard/mcp-servers-host.ts
38025
+ import { execFile as execFile2 } from "child_process";
38026
+ import { homedir as homedir11 } from "os";
38027
+ import { promisify as promisify2 } from "util";
38028
+
38029
+ // ../api/src/dashboard/mcp-servers.ts
38030
+ var LINE = /^(.+?): (.+) - [✔✘!✓✗] ?(.*)$/u;
38031
+ function redactTarget(raw) {
38032
+ const typed = raw.match(/^(.*) \((HTTP|SSE)\)$/);
38033
+ const value = typed ? typed[1] : raw;
38034
+ if (/^https?:\/\//.test(value)) {
38035
+ try {
38036
+ const url = new URL(value);
38037
+ return {
38038
+ transport: typed?.[2] === "SSE" ? "sse" : "http",
38039
+ target: `${url.origin}${url.pathname}`
38040
+ };
38041
+ } catch {
38042
+ return { transport: "http", target: "" };
38043
+ }
38044
+ }
38045
+ const command = value.split(/\s+/)[0] ?? "";
38046
+ return { transport: "stdio", target: command.split("/").pop() ?? "" };
38047
+ }
38048
+ function statusOf(text) {
38049
+ if (/^connected/i.test(text)) return "connected";
38050
+ if (/auth/i.test(text)) return "needs-auth";
38051
+ if (/fail|error|closed/i.test(text)) return "failed";
38052
+ return "unknown";
38053
+ }
38054
+ function parseMcpList(output) {
38055
+ const servers = [];
38056
+ for (const line of output.split("\n")) {
38057
+ const m = line.trim().match(LINE);
38058
+ if (!m) continue;
38059
+ const name = m[1].trim();
38060
+ const detail = m[3].trim();
38061
+ const source = name.startsWith("claude.ai ") ? "claude.ai" : name.startsWith("plugin:") ? "plugin" : "local";
38062
+ servers.push({
38063
+ name: source === "claude.ai" ? name.slice("claude.ai ".length) : name,
38064
+ ...redactTarget(m[2].trim()),
38065
+ status: statusOf(detail),
38066
+ detail,
38067
+ source
38068
+ });
38069
+ }
38070
+ return servers;
38071
+ }
38072
+
38073
+ // src/dashboard/mcp-servers-host.ts
38074
+ var run4 = promisify2(execFile2);
38075
+ async function listMcpServers() {
38076
+ try {
38077
+ const { stdout } = await run4("claude", ["mcp", "list"], { cwd: homedir11(), timeout: 45e3 });
38078
+ return { servers: parseMcpList(stdout) };
38079
+ } catch (err) {
38080
+ console.error(`[mcp-servers] claude mcp list failed: ${err.message}`);
38081
+ return { servers: [], error: "Could not run `claude mcp list` on the Host." };
38082
+ }
38083
+ }
38084
+
38026
38085
  // src/dashboard/agent-bridge-host.ts
38027
38086
  var HOSTED_DATA_SOCKET_ORIGIN = "wss://flyspace-api.fly.dev";
38028
38087
  function dataSocketUrl() {
@@ -38067,6 +38126,7 @@ function localAgentCallbacks(chatAgent) {
38067
38126
  docsRequest: (req) => handleDocsRequest(req),
38068
38127
  hostRequest: (req) => handleHostRequest(req),
38069
38128
  listSessions: () => listLocalSessions(),
38129
+ listMcpServers: () => listMcpServers(),
38070
38130
  upgradeHost: () => startHostUpgrade(),
38071
38131
  scanOnboarding: (slug) => scanOnboarding(slug),
38072
38132
  linkProjectFolders: (slug, items) => linkProjectFolders(slug, items),
@@ -38961,7 +39021,7 @@ async function main() {
38961
39021
  break;
38962
39022
  case "-v":
38963
39023
  case "--version":
38964
- printRaw("0.1.41");
39024
+ printRaw("0.1.42");
38965
39025
  break;
38966
39026
  case "-h":
38967
39027
  case "--help":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flyspace-cli",
3
- "version": "0.1.41",
3
+ "version": "0.1.42",
4
4
  "description": "AI-native headless community OS",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,