flyspace-cli 0.1.41 → 0.1.43

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 +148 -70
  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.43" : 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";
@@ -20079,6 +20079,10 @@ function installTarget(entry) {
20079
20079
  if (at > 0) return { prefix: real.slice(0, at), nodeBin: join11(real.slice(0, at), "bin") };
20080
20080
  } catch {
20081
20081
  }
20082
+ const prefix = entry.endsWith("/bin/flyspace") ? dirname5(dirname5(entry)) : void 0;
20083
+ if (prefix && existsSync9(join11(prefix, "lib", "node_modules", "flyspace-cli"))) {
20084
+ return { prefix, nodeBin: join11(prefix, "bin") };
20085
+ }
20082
20086
  return void 0;
20083
20087
  }
20084
20088
  function installTargets() {
@@ -20111,12 +20115,15 @@ function npmEnv(target) {
20111
20115
  return nodeBin ? { ...process.env, PATH: `${nodeBin}:${process.env.PATH ?? ""}` } : process.env;
20112
20116
  }
20113
20117
  async function runUpgrade() {
20114
- const current = "0.1.41";
20118
+ const current = "0.1.43";
20115
20119
  console.log(`Current version: ${current}`);
20116
20120
  console.log("Checking for updates...\n");
20117
20121
  try {
20118
20122
  const latest2 = execSync6(`npm view ${"flyspace-cli"} version`, { encoding: "utf-8", env: npmEnv() }).trim();
20119
- const stale = installTargets().filter((t) => installedVersion(t) !== latest2);
20123
+ const own = installTarget(process.argv[1] ?? "")?.prefix;
20124
+ const stale = installTargets().filter(
20125
+ (t) => installedVersion(t) !== latest2 || t.prefix === own && current !== latest2
20126
+ );
20120
20127
  if (stale.length === 0) {
20121
20128
  if (current === latest2) {
20122
20129
  console.log(`Already on the latest version (${current}).`);
@@ -25331,9 +25338,9 @@ async function synthesizeWithRetry(script, voice) {
25331
25338
  }
25332
25339
  var renderQueue = Promise.resolve();
25333
25340
  function queued(job) {
25334
- const run4 = renderQueue.then(job, job);
25335
- renderQueue = run4.catch(() => void 0);
25336
- return run4;
25341
+ const run5 = renderQueue.then(job, job);
25342
+ renderQueue = run5.catch(() => void 0);
25343
+ return run5;
25337
25344
  }
25338
25345
  function renderArticleAudio(title, content, lang) {
25339
25346
  return queued(() => renderArticleAudioNow(title, content, lang));
@@ -25407,12 +25414,12 @@ function queueShareAudio(share) {
25407
25414
  }
25408
25415
  const current = inFlight2.get(share.id);
25409
25416
  if (current?.hash === hash) return current.done;
25410
- const run4 = () => runShareAudioJob(share, hash).catch((err) => {
25417
+ const run5 = () => runShareAudioJob(share, hash).catch((err) => {
25411
25418
  console.error(`share audio job crashed (${share.id}):`, err);
25412
25419
  }).finally(() => {
25413
25420
  if (inFlight2.get(share.id)?.hash === hash) inFlight2.delete(share.id);
25414
25421
  });
25415
- const done = current ? current.done.then(run4) : run4();
25422
+ const done = current ? current.done.then(run5) : run5();
25416
25423
  inFlight2.set(share.id, { hash, done });
25417
25424
  return done;
25418
25425
  }
@@ -29973,22 +29980,13 @@ function createTranscribeRouter() {
29973
29980
  import { Hono as Hono19 } from "hono";
29974
29981
  var GROQ_API_URL8 = "https://api.groq.com/openai/v1/chat/completions";
29975
29982
  var MODEL2 = "openai/gpt-oss-120b";
29976
- var MAX_BEFORE_CHARS = 2e3;
29977
- var MAX_THREAD_CHARS = 1500;
29983
+ var MAX_REPLY_CHARS = 4e3;
29984
+ var MAX_EARLIER_CHARS = 1500;
29978
29985
  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
- }
29986
+ 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.
29987
+ 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.
29988
+ Keep it short (under 15 words), in the language the user writes in. No greetings, thanks or filler.
29989
+ Output ONLY the message text, no quotes or explanation. If there is no clear next step, output nothing.`;
29992
29990
  function createAutocompleteRouter() {
29993
29991
  const app = new Hono19();
29994
29992
  app.post("/autocomplete", async (c) => {
@@ -29997,15 +29995,14 @@ function createAutocompleteRouter() {
29997
29995
  const apiKey = process.env["GROQ_API_KEY"];
29998
29996
  if (!apiKey) return c.json({ error: "autocomplete is not configured" }, 503);
29999
29997
  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
29998
  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
- ` : "";
29999
+ if (!channel || !canSeeChannel(channel, c.get("auth")) || typeof body?.threadId !== "string")
30000
+ return c.json({ text: "" });
30001
+ const thread = await threadContext(channel, body.threadId);
30002
+ if (!thread) return c.json({ text: "" });
30003
+ const context = `Channel: #${channel.name}${channel.prompt ? `
30004
+ Channel instructions: ${channel.prompt.slice(0, 500)}` : ""}
30005
+ ${thread}`;
30009
30006
  const res = await fetch(GROQ_API_URL8, {
30010
30007
  method: "POST",
30011
30008
  headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
@@ -30019,8 +30016,7 @@ ${thread}
30019
30016
  temperature: 0.2,
30020
30017
  messages: [
30021
30018
  { role: "system", content: RULES2 },
30022
- { role: "user", content: `${context}Draft so far:
30023
- ${before}` }
30019
+ { role: "user", content: context }
30024
30020
  ]
30025
30021
  })
30026
30022
  }).catch(() => null);
@@ -30041,15 +30037,18 @@ async function threadContext(channel, threadId) {
30041
30037
  ]);
30042
30038
  const channelId = thread.data?.channel_id;
30043
30039
  if (!channelId || channelId !== channel.id && channelId !== channel.externalChannelId) return "";
30040
+ const [last, ...older] = tail.data ?? [];
30041
+ if (!last || last.author !== AGENT_AUTHOR) return "";
30044
30042
  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);
30043
+ for (const r of older.reverse()) if (!rows.some((x) => x.id === r.id)) rows.push(r);
30044
+ const earlier = rows.filter((r) => r.id !== last.id).map((r) => `${r.author}: ${r.body}`).join("\n\n").slice(-MAX_EARLIER_CHARS);
30048
30045
  const title = thread.data?.title ? `Thread title: ${thread.data.title}
30049
30046
  ` : "";
30050
- return text ? `${title}Conversation so far:
30051
- ${text}
30052
- ` : title;
30047
+ return `${title}Earlier messages:
30048
+ ${earlier}
30049
+
30050
+ Agent's last reply:
30051
+ ${last.body.slice(-MAX_REPLY_CHARS)}`;
30053
30052
  }
30054
30053
 
30055
30054
  // ../api/src/dashboard/avatars-routes.ts
@@ -31782,10 +31781,10 @@ async function buildChannelUsageReport(channelId, days, labelChannel) {
31782
31781
  }
31783
31782
  }
31784
31783
  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;
31784
+ function contextOf(run5) {
31785
+ if (!run5) return void 0;
31786
+ const total = run5.inputTokens + run5.cacheReadTokens + run5.cacheCreationTokens;
31787
+ const turns = run5.numTurns ?? 1;
31789
31788
  return Math.round(turns > 0 ? total / turns : total);
31790
31789
  }
31791
31790
  async function buildThreadUsage(threadTs) {
@@ -32587,6 +32586,12 @@ function registerDataRoutes(api, agent = {}) {
32587
32586
  if (!res) return c.json({ error: "Host is offline" }, 503);
32588
32587
  return c.json(res, 202);
32589
32588
  });
32589
+ api.get("/mcp-servers", async (c) => {
32590
+ const auth = c.get("auth");
32591
+ if (!await isHostOwner(auth)) return c.json({ error: "only the Host owner can view MCP servers" }, 403);
32592
+ const res = await agent.listMcpServers?.();
32593
+ return c.json(res ?? { servers: null });
32594
+ });
32590
32595
  api.get("/channels", async (c) => {
32591
32596
  const auth = c.get("auth");
32592
32597
  const channels = getProfileChannelRegistry().filter((ch) => canSeeChannel(ch, auth));
@@ -32645,17 +32650,17 @@ function registerDataRoutes(api, agent = {}) {
32645
32650
  const auto = loadEffectiveAutomations();
32646
32651
  const runs2 = /* @__PURE__ */ new Map();
32647
32652
  try {
32648
- for (const [name, run4] of await lastRunsForWorkspace(getProfile())) {
32649
- runs2.set(name, { lastRun: run4.lastRunAt.toISOString(), lastStatus: run4.lastStatus });
32653
+ for (const [name, run5] of await lastRunsForWorkspace(getProfile())) {
32654
+ runs2.set(name, { lastRun: run5.lastRunAt.toISOString(), lastStatus: run5.lastStatus });
32650
32655
  }
32651
32656
  } catch {
32652
32657
  const runStore = existsSync20(paths.DB_FILE) ? getLocalCache().openAutomationRunStore(paths.DB_FILE) : null;
32653
32658
  if (runStore) {
32654
32659
  try {
32655
- for (const run4 of runStore.listLastRuns()) {
32656
- runs2.set(run4.name, {
32657
- lastRun: run4.lastRunAt.toISOString(),
32658
- lastStatus: run4.lastStatus
32660
+ for (const run5 of runStore.listLastRuns()) {
32661
+ runs2.set(run5.name, {
32662
+ lastRun: run5.lastRunAt.toISOString(),
32663
+ lastStatus: run5.lastStatus
32659
32664
  });
32660
32665
  }
32661
32666
  } finally {
@@ -32846,17 +32851,17 @@ function registerDataRoutes(api, agent = {}) {
32846
32851
  }
32847
32852
  const threadLinks = await resolveThreadLinks(raw);
32848
32853
  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;
32854
+ const run5 = latest2.get(s.threadTs);
32855
+ const turns = run5?.numTurns ?? 1;
32856
+ const total = run5 ? run5.inputTokens + run5.cacheReadTokens + run5.cacheCreationTokens : void 0;
32852
32857
  const link = s.channelId.startsWith("chat:") ? threadLinks.chat.get(s.threadTs) : threadLinks.external.get(`${s.channelId}:${s.threadTs}`);
32853
32858
  return {
32854
32859
  ...s,
32855
32860
  channelLabel: channelLabel(s.channelId),
32856
- model: run4?.model,
32857
- numTurns: run4?.numTurns,
32861
+ model: run5?.model,
32862
+ numTurns: run5?.numTurns,
32858
32863
  contextTokens: total !== void 0 ? Math.round(total / (turns > 0 ? turns : 1)) : void 0,
32859
- rolledOver: run4?.label === "rollover",
32864
+ rolledOver: run5?.label === "rollover",
32860
32865
  threadTitle: link?.title ?? void 0,
32861
32866
  chatChannelId: link?.chatChannelId,
32862
32867
  chatThreadId: link?.chatThreadId,
@@ -34880,21 +34885,21 @@ var ChatAgent = class {
34880
34885
  * kill.
34881
34886
  */
34882
34887
  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;
34888
+ const run5 = this.activeRuns.get(msg.threadId);
34889
+ if (!run5 || run5.msg.author !== msg.author || run5.msg.replyId === msg.replyId) return;
34885
34890
  try {
34886
- if (!await this.shouldInterrupt(run4.msg.body, msg.body)) return;
34891
+ if (!await this.shouldInterrupt(run5.msg.body, msg.body)) return;
34887
34892
  } catch (err) {
34888
34893
  console.error("[chat-agent] interrupt classifier failed:", err.message);
34889
34894
  return;
34890
34895
  }
34891
- if (this.activeRuns.get(msg.threadId) !== run4) return;
34896
+ if (this.activeRuns.get(msg.threadId) !== run5) return;
34892
34897
  console.log(
34893
34898
  `[chat-agent] follow-up supersedes in-flight run \u2014 aborting. thread=${msg.threadId}`
34894
34899
  );
34895
- run4.superseded = true;
34900
+ run5.superseded = true;
34896
34901
  this.supersedingReplies.add(msg.replyId);
34897
- run4.abort.abort();
34902
+ run5.abort.abort();
34898
34903
  }
34899
34904
  /**
34900
34905
  * Picks up messages nobody answered. `handleMessage` is fire-and-forget and
@@ -35102,11 +35107,11 @@ var ChatAgent = class {
35102
35107
  * continue with `--resume`. Returns whether a run was actually in flight —
35103
35108
  * a queued-but-not-started message is not affected. */
35104
35109
  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();
35110
+ const run5 = this.activeRuns.get(threadId);
35111
+ if (!run5) return false;
35112
+ run5.superseded = true;
35113
+ this.stoppedReplies.set(threadId, run5.msg.replyId);
35114
+ run5.abort.abort();
35110
35115
  return true;
35111
35116
  }
35112
35117
  /**
@@ -38023,6 +38028,78 @@ function startHostUpgrade() {
38023
38028
  return { started: true };
38024
38029
  }
38025
38030
 
38031
+ // src/dashboard/mcp-servers-host.ts
38032
+ import { execFile as execFile2 } from "child_process";
38033
+ import { homedir as homedir11 } from "os";
38034
+ import { promisify as promisify2 } from "util";
38035
+
38036
+ // ../api/src/dashboard/mcp-servers.ts
38037
+ var LINE = /^(.+?): (.+) - [✔✘!✓✗] ?(.*)$/u;
38038
+ function redactTarget(raw) {
38039
+ const typed = raw.match(/^(.*) \((HTTP|SSE)\)$/);
38040
+ const value = typed ? typed[1] : raw;
38041
+ if (/^https?:\/\//.test(value)) {
38042
+ try {
38043
+ const url = new URL(value);
38044
+ return {
38045
+ transport: typed?.[2] === "SSE" ? "sse" : "http",
38046
+ target: `${url.origin}${url.pathname}`
38047
+ };
38048
+ } catch {
38049
+ return { transport: "http", target: "" };
38050
+ }
38051
+ }
38052
+ const command = value.split(/\s+/)[0] ?? "";
38053
+ return { transport: "stdio", target: command.split("/").pop() ?? "" };
38054
+ }
38055
+ function statusOf(text) {
38056
+ if (/^connected/i.test(text)) return "connected";
38057
+ if (/auth/i.test(text)) return "needs-auth";
38058
+ if (/fail|error|closed/i.test(text)) return "failed";
38059
+ return "unknown";
38060
+ }
38061
+ function parseMcpList(output) {
38062
+ const servers = [];
38063
+ for (const line of output.split("\n")) {
38064
+ const m = line.trim().match(LINE);
38065
+ if (!m) continue;
38066
+ const name = m[1].trim();
38067
+ const detail = m[3].trim();
38068
+ const source = name.startsWith("claude.ai ") ? "claude.ai" : name.startsWith("plugin:") ? "plugin" : "local";
38069
+ servers.push({
38070
+ name: source === "claude.ai" ? name.slice("claude.ai ".length) : name,
38071
+ ...redactTarget(m[2].trim()),
38072
+ status: statusOf(detail),
38073
+ detail,
38074
+ source
38075
+ });
38076
+ }
38077
+ return servers;
38078
+ }
38079
+
38080
+ // src/dashboard/mcp-servers-host.ts
38081
+ var run4 = promisify2(execFile2);
38082
+ async function listMcpServers() {
38083
+ const env = { ...process.env };
38084
+ const pinned = pinnedClaudeConfigDir(profileClaudeConfigDir());
38085
+ if (pinned) env["CLAUDE_CONFIG_DIR"] = pinned;
38086
+ else delete env["CLAUDE_CONFIG_DIR"];
38087
+ const token = profileOauthToken();
38088
+ if (token) env["CLAUDE_CODE_OAUTH_TOKEN"] = token;
38089
+ else delete env["CLAUDE_CODE_OAUTH_TOKEN"];
38090
+ try {
38091
+ const { stdout } = await run4("claude", ["mcp", "list"], {
38092
+ cwd: homedir11(),
38093
+ env,
38094
+ timeout: 45e3
38095
+ });
38096
+ return { servers: parseMcpList(stdout) };
38097
+ } catch (err) {
38098
+ console.error(`[mcp-servers] claude mcp list failed: ${err.message}`);
38099
+ return { servers: [], error: "Could not run `claude mcp list` on the Host." };
38100
+ }
38101
+ }
38102
+
38026
38103
  // src/dashboard/agent-bridge-host.ts
38027
38104
  var HOSTED_DATA_SOCKET_ORIGIN = "wss://flyspace-api.fly.dev";
38028
38105
  function dataSocketUrl() {
@@ -38067,6 +38144,7 @@ function localAgentCallbacks(chatAgent) {
38067
38144
  docsRequest: (req) => handleDocsRequest(req),
38068
38145
  hostRequest: (req) => handleHostRequest(req),
38069
38146
  listSessions: () => listLocalSessions(),
38147
+ listMcpServers: () => listMcpServers(),
38070
38148
  upgradeHost: () => startHostUpgrade(),
38071
38149
  scanOnboarding: (slug) => scanOnboarding(slug),
38072
38150
  linkProjectFolders: (slug, items) => linkProjectFolders(slug, items),
@@ -38961,7 +39039,7 @@ async function main() {
38961
39039
  break;
38962
39040
  case "-v":
38963
39041
  case "--version":
38964
- printRaw("0.1.41");
39042
+ printRaw("0.1.43");
38965
39043
  break;
38966
39044
  case "-h":
38967
39045
  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.43",
4
4
  "description": "AI-native headless community OS",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "private": false,