skydive-cli 0.2.0-beta.457 → 0.2.0-beta.482

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { t as HttpError } from "./rest-BBQrPIwq.mjs";
2
+ import { t as HttpError } from "./rest-DZnAT7BG.mjs";
3
3
  import os from "node:os";
4
4
  import { z } from "zod";
5
5
 
package/dist/js/bin.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { A as getPromptHistoryPath, B as resolveSession, C as API_KEY_PREFIX, D as deleteConfig, E as DEFAULT_WEB_URL, F as getStoredApiKeyWorkspaceName, G as setLastSeenVersion, H as saveConfig, I as getUpdateCheckDisabled, L as resolveAppUrl, N as getShareMachineDefault, O as getConfigPath, P as getStoredApiKeyId, R as resolveConfig, S as API_KEY_FAMILY_PREFIX, U as saveSession, _ as getActiveWorkspaceId, b as setActiveWorkspace, g as ensureActiveOrganization, k as getLastSeenVersion, m as themes, r as brandHelpArt, v as getSessionIdentity, w as DEFAULT_API_URL, x as API_KEYS_URL, y as listWorkspaces, z as resolveManagementAuth } from "./brand-BVALAEf1.mjs";
3
3
  import { n as printError, r as printTable, t as output } from "./output-B4cW10Ph.mjs";
4
- import { n as createRestClient } from "./rest-BBQrPIwq.mjs";
5
- import { i as resolveAgent } from "./print-YqNIRwdf.mjs";
6
- import { a as registerPortalDevice, c as machineIdentity, n as findThisDevice, o as revokePortalAccess, r as grantPortalAccess, t as fetchPortalDevices } from "./api-zUf_Vrzh.mjs";
4
+ import { n as createRestClient } from "./rest-DZnAT7BG.mjs";
5
+ import { i as resolveAgent } from "./print-XvAHDKqu.mjs";
6
+ import { a as registerPortalDevice, c as machineIdentity, n as findThisDevice, o as revokePortalAccess, r as grantPortalAccess, t as fetchPortalDevices } from "./api-BHq-5eW_.mjs";
7
7
  import { t as SandboxStream } from "./client-CpEvH2Pq.mjs";
8
8
  import { hideBin } from "yargs/helpers";
9
9
  import yargs from "yargs";
@@ -20,7 +20,7 @@ import semver from "semver";
20
20
 
21
21
  //#region package.json
22
22
  var name = "skydive-cli";
23
- var version = "0.2.0-beta.457";
23
+ var version = "0.2.0-beta.482";
24
24
 
25
25
  //#endregion
26
26
  //#region src/types.ts
@@ -1200,7 +1200,7 @@ const importCommand = {
1200
1200
  process.exit(1);
1201
1201
  }
1202
1202
  }
1203
- const { runChat } = await import("./boot-D_H3xGxt.mjs");
1203
+ const { runChat } = await import("./boot-B8ownO3n.mjs");
1204
1204
  await runChat({
1205
1205
  appUrl,
1206
1206
  sessionToken: session.value.sessionToken,
@@ -1226,7 +1226,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1226
1226
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login --web\` first, or set SKYDIVE_SESSION_TOKEN.`);
1227
1227
  process.exit(1);
1228
1228
  }
1229
- const { connectMachineShare } = await import("./print-share-B8sZ0w-M.mjs");
1229
+ const { connectMachineShare } = await import("./print-share-DT89ADa6.mjs");
1230
1230
  const machineShare = await connectMachineShare({
1231
1231
  appUrl,
1232
1232
  sessionToken: session.value.sessionToken,
@@ -1234,7 +1234,7 @@ async function runImportPrintMode({ argv, appUrl }) {
1234
1234
  });
1235
1235
  const extra = (argv.print ?? "").trim();
1236
1236
  const prompt = buildImportSeedPrompt(process.cwd()) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
1237
- const { runPrint } = await import("./print-DUsH9Dlh.mjs");
1237
+ const { runPrint } = await import("./print-Chg635Ye.mjs");
1238
1238
  try {
1239
1239
  const result = await runPrint({
1240
1240
  appUrl,
@@ -1511,7 +1511,7 @@ const chatCommand = {
1511
1511
  printError(`Unknown theme "${themeId}". Valid themes: ${themes.map((t) => t.id).join(", ")}`);
1512
1512
  process.exit(1);
1513
1513
  }
1514
- const { runChat } = await import("./boot-D_H3xGxt.mjs");
1514
+ const { runChat } = await import("./boot-B8ownO3n.mjs");
1515
1515
  await runChat({
1516
1516
  appUrl,
1517
1517
  sessionToken: session.value.sessionToken,
@@ -1539,7 +1539,7 @@ async function runPrintMode({ argv, appUrl }) {
1539
1539
  printError(`${session.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
1540
1540
  process.exit(1);
1541
1541
  }
1542
- const { runPrint, readStdin } = await import("./print-DUsH9Dlh.mjs");
1542
+ const { runPrint, readStdin } = await import("./print-Chg635Ye.mjs");
1543
1543
  let prompt = (argv.print ?? "").trim();
1544
1544
  if (!prompt) {
1545
1545
  if (process.stdin.isTTY) {
@@ -1554,7 +1554,7 @@ async function runPrintMode({ argv, appUrl }) {
1554
1554
  }
1555
1555
  let machineShare = null;
1556
1556
  if (resolveShareMachine(argv)) {
1557
- const { connectMachineShare } = await import("./print-share-B8sZ0w-M.mjs");
1557
+ const { connectMachineShare } = await import("./print-share-DT89ADa6.mjs");
1558
1558
  machineShare = await connectMachineShare({
1559
1559
  appUrl,
1560
1560
  sessionToken: session.value.sessionToken,
@@ -1613,7 +1613,7 @@ const getCommand = {
1613
1613
  printError(`${session.error.message} Run \`skydive auth login\` first, or set SKYDIVE_SESSION_TOKEN.`);
1614
1614
  process.exit(1);
1615
1615
  }
1616
- const { messageGet } = await import("./print-DUsH9Dlh.mjs");
1616
+ const { messageGet } = await import("./print-Chg635Ye.mjs");
1617
1617
  try {
1618
1618
  const result = await messageGet({
1619
1619
  appUrl,
@@ -1810,7 +1810,7 @@ const switchCommand = {
1810
1810
  printError("The workspace picker needs the Bun runtime and it could not be set up automatically. Pass a workspace slug instead, or install Bun and retry.");
1811
1811
  process.exit(1);
1812
1812
  }
1813
- const { runWorkspacePicker } = await import("./boot-D_H3xGxt.mjs");
1813
+ const { runWorkspacePicker } = await import("./boot-B8ownO3n.mjs");
1814
1814
  await runWorkspacePicker(session);
1815
1815
  return;
1816
1816
  }
@@ -1888,7 +1888,7 @@ const openCommand = {
1888
1888
  const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
1889
1889
  const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
1890
1890
  const { machineName } = machineIdentity();
1891
- const { PortalClient } = await import("./client-A4m7lNDg.mjs");
1891
+ const { PortalClient } = await import("./client-CRAGDqp0.mjs");
1892
1892
  let lastLine = "";
1893
1893
  let signalConnected;
1894
1894
  const connected = new Promise((resolve) => {
@@ -2060,8 +2060,8 @@ const sandboxCommand = {
2060
2060
  }).example("skydive sandbox --agent grace", "Live terminal (Ctrl-] detaches)").example("skydive sandbox --agent grace -- tail -n 50 /tmp/harness.log", "One-shot command (use `--` so its flags reach the sandbox)").example("skydive sandbox --agent grace -- sh -c 'ls /tmp | wc -l'", "Shell features go through an explicit `sh -c`"),
2061
2061
  handler: async (argv) => {
2062
2062
  const session = requireSession(argv);
2063
- const { createRestClient } = await import("./rest-Cz0VLhmi.mjs");
2064
- const { resolveAgent } = await import("./print-DUsH9Dlh.mjs");
2063
+ const { createRestClient } = await import("./rest-Ci_yRwoi.mjs");
2064
+ const { resolveAgent } = await import("./print-Chg635Ye.mjs");
2065
2065
  const client = createRestClient({
2066
2066
  appUrl: session.appUrl,
2067
2067
  sessionToken: session.sessionToken
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import { M as getSavedTheme, O as getConfigPath, T as DEFAULT_APP_URL, V as resolveWebUrl, W as saveTheme, _ as getActiveWorkspaceId, a as applyTheme, b as setActiveWorkspace, c as noColorRequested, d as themeMode, f as themeModeFromColorFgBg, h as themesForMode, i as DEFAULT_THEME_ID, j as getReviewStateDir, l as theme, n as WORDMARK, o as findTheme, p as themeVersion, s as monoTheme, t as MARK_CELLS, u as themeForMode, w as DEFAULT_API_URL, y as listWorkspaces } from "./brand-BVALAEf1.mjs";
3
- import { n as createRestClient, r as errorDetail, t as HttpError } from "./rest-BBQrPIwq.mjs";
3
+ import { n as createRestClient, r as errorDetail, t as HttpError } from "./rest-DZnAT7BG.mjs";
4
4
  import { n as isRecord, t as errorMessage } from "./util-CeisaZVY.mjs";
5
- import { c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams } from "./print-YqNIRwdf.mjs";
6
- import "./api-zUf_Vrzh.mjs";
5
+ import { c as cardActionErrorMessage, d as reconcileMaskedInput, f as resolveConnectUrl, i as resolveAgent, l as parseExternalOauthConnectParams, m as specKeyFor, p as parseConnectCard, s as MASK_CHAR, u as parseOauthConnectParams } from "./print-XvAHDKqu.mjs";
6
+ import "./api-BHq-5eW_.mjs";
7
7
  import { t as SandboxStream } from "./client-CpEvH2Pq.mjs";
8
- import { t as PortalClient } from "./client-DuPxkSoH.mjs";
8
+ import { t as PortalClient } from "./client-t6GmE8TI.mjs";
9
9
  import { t as runRawPtyPassthrough } from "./raw-pty-GAvxm2ol.mjs";
10
10
  import * as os$1 from "node:os";
11
11
  import { homedir, platform, release, tmpdir } from "node:os";
@@ -183,8 +183,8 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
183
183
  createdAt: conversation.createdAt,
184
184
  updatedAt: conversation.updatedAt,
185
185
  preview: null,
186
- channel: null,
187
- channelLabel: null,
186
+ channel: conversation.channel,
187
+ channelLabel: conversation.channelLabel,
188
188
  agent: {
189
189
  id: agent.id,
190
190
  name: agent.name,
@@ -1605,27 +1605,45 @@ function singleLine(text) {
1605
1605
  return out;
1606
1606
  }
1607
1607
  /**
1608
- * Fit a row's title and trailing detail into `width` columns. The title
1609
- * identifies the row so it gets the space first; the detail takes what is
1610
- * left and is dropped when nothing is.
1608
+ * Fit a row's title, an optional mid-row `tag` and a trailing detail into
1609
+ * `width` columns, each rendered separated by one space. The title identifies
1610
+ * the row so it gets the space first; the tag comes next because it carries
1611
+ * information the detail does not; the detail takes what is left and is
1612
+ * dropped when nothing is.
1611
1613
  */
1612
- function fitRowText(title, detail, width) {
1614
+ function fitRowText(title, detail, width, tag = "") {
1613
1615
  if (width <= 0) return {
1614
1616
  title: "",
1615
- detail: ""
1617
+ detail: "",
1618
+ tag: ""
1616
1619
  };
1617
- if ((detail ? title.length + 1 + detail.length : title.length) <= width) return {
1620
+ const segment = (text) => text ? text.length + 1 : 0;
1621
+ if (title.length + segment(tag) + segment(detail) <= width) return {
1618
1622
  title,
1619
- detail
1623
+ detail,
1624
+ tag
1620
1625
  };
1621
1626
  const room = width - title.length - 1;
1622
1627
  if (room < 2) return {
1623
1628
  title: truncate(title, width),
1624
- detail: ""
1629
+ detail: "",
1630
+ tag: ""
1631
+ };
1632
+ if (title.length + segment(tag) > width) return {
1633
+ title,
1634
+ detail: "",
1635
+ tag: truncate(tag, room)
1636
+ };
1637
+ const detailRoom = room - segment(tag);
1638
+ if (detailRoom < 2) return {
1639
+ title,
1640
+ detail: "",
1641
+ tag
1625
1642
  };
1626
1643
  return {
1627
1644
  title,
1628
- detail: truncate(detail, room)
1645
+ detail: truncate(detail, detailRoom),
1646
+ tag
1629
1647
  };
1630
1648
  }
1631
1649
  /**
@@ -1926,7 +1944,7 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
1926
1944
  children: [marker, truncate("+ new conversation", rowWidth)]
1927
1945
  }, "new");
1928
1946
  const archived = isArchived(row.hit.item);
1929
- const { title, detail } = fitRowText(singleLine(row.hit.item.title ?? "(untitled)"), `${archived ? "archived · " : ""}${previewLine(row.hit.item)}`, rowWidth);
1947
+ const { title, detail, tag } = fitRowText(singleLine(row.hit.item.title ?? "(untitled)"), `${archived ? "archived · " : ""}${previewLine(row.hit.item)}`, rowWidth, coAgentsTag(row.hit.item, agent.id));
1930
1948
  return /* @__PURE__ */ jsxs("text", {
1931
1949
  fg: archived && !selected ? theme.dim : fg,
1932
1950
  children: [
@@ -1935,6 +1953,10 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
1935
1953
  text: title,
1936
1954
  indexes: row.hit.item.title ? row.hit.highlights[0] : null
1937
1955
  }),
1956
+ tag ? /* @__PURE__ */ jsxs("span", {
1957
+ fg: theme.muted,
1958
+ children: [" ", tag]
1959
+ }) : null,
1938
1960
  detail ? /* @__PURE__ */ jsxs("span", {
1939
1961
  fg: theme.dim,
1940
1962
  children: [" ", detail]
@@ -1946,7 +1968,27 @@ function ConversationList({ agent, conversations, complete, showAutomated, onTog
1946
1968
  ]
1947
1969
  });
1948
1970
  }
1949
- const conversationKeys = [(c) => singleLine(c.title ?? ""), (c) => c.preview ?? ""];
1971
+ /**
1972
+ * Tags a row with the thread's other agents (`+Grace, Ada`). The picker is
1973
+ * entered per agent, so that agent stays implicit and only its co-participants
1974
+ * are named — this tag is the only thing in the list distinguishing a
1975
+ * multi-agent thread from a solo one. Empty for a solo thread.
1976
+ *
1977
+ * A string rather than a component because the row's one-line budget has to
1978
+ * account for it (see list-line.ts).
1979
+ */
1980
+ function coAgentsTag(conv, viewerAgentId) {
1981
+ const names = conversationAgents(conv).filter((a) => a.id !== viewerAgentId).map((a) => a.name);
1982
+ return names.length > 0 ? `+${names.join(", ")}` : "";
1983
+ }
1984
+ function conversationAgents(c) {
1985
+ return c.agents && c.agents.length > 0 ? c.agents : [c.agent];
1986
+ }
1987
+ const conversationKeys = [
1988
+ (c) => singleLine(c.title ?? ""),
1989
+ (c) => c.preview ?? "",
1990
+ (c) => conversationAgents(c).map((a) => a.name).join(" ")
1991
+ ];
1950
1992
  function previewLine(c) {
1951
1993
  const when = relativeTime(c.updatedAt);
1952
1994
  const prefix = c.preview ? c.preview.replace(/\s+/g, " ").trim() : "";
@@ -4053,10 +4095,13 @@ function TodoCardView({ todos, isActive }) {
4053
4095
  //#endregion
4054
4096
  //#region src/chat/tui/chat/footer-hints.ts
4055
4097
  /** Build the chat footer copy independently of its colors and layout. */
4056
- function formatChatFooterStatus({ runKind, ctrlCArmed, isShared, hasPendingSteer, hasActionableCard }) {
4098
+ function formatChatFooterStatus({ runKind, ctrlCArmed, isShared, hasPendingSteer, hasActionableCard, nonWebChannel }) {
4057
4099
  const connectHint = hasActionableCard ? "ctrl+r connect · " : "";
4058
4100
  switch (runKind) {
4059
- case "idle": return ctrlCArmed ? "press ctrl+c again to quit" : `${connectHint}↵ send · / commands · esc back · ctrl+c quit · ctrl+t ${isShared ? "revoke access" : "local access"} · ? keybinds`;
4101
+ case "idle":
4102
+ if (ctrlCArmed) return "press ctrl+c again to quit";
4103
+ if (nonWebChannel) return `esc back · ctrl+c quit · ctrl+t ${isShared ? "revoke access" : "local access"} · ? keybinds`;
4104
+ return `${connectHint}↵ send · / commands · esc back · ctrl+c quit · ctrl+t ${isShared ? "revoke access" : "local access"} · ? keybinds`;
4060
4105
  case "sending": return "sending…";
4061
4106
  case "streaming": return hasPendingSteer ? `${connectHint}↵ steer · esc cancel steer · ctrl+c cancel run` : `${connectHint}↵ steer · esc leave (keeps running) · ctrl+c cancel`;
4062
4107
  }
@@ -4392,6 +4437,10 @@ const keybindGroups = [
4392
4437
  keys: "ctrl+l",
4393
4438
  action: "open this conversation in the browser"
4394
4439
  },
4440
+ {
4441
+ keys: "ctrl+f",
4442
+ action: "fork a channel conversation into a chat you can continue here"
4443
+ },
4395
4444
  {
4396
4445
  keys: "ctrl+r",
4397
4446
  action: "run the newest connect card (auth cards)"
@@ -7901,6 +7950,47 @@ async function runPtySession(opts) {
7901
7950
  }
7902
7951
  }
7903
7952
 
7953
+ //#endregion
7954
+ //#region src/chat/tui/screens/conversation-channel.ts
7955
+ function isNewConversation(c) {
7956
+ return "kind" in c && c.kind === "new";
7957
+ }
7958
+ /**
7959
+ * The channel a conversation belongs to when it isn't the web channel, else
7960
+ * null. A brand-new conversation is always web, and the list route reports the
7961
+ * web channel as either `'web'` or (legacy) null — both mean "post from here".
7962
+ * Anything else (slack, email, imessage, webhook, cron) is owned by that
7963
+ * channel: the message has to go through it, so the TUI can't send here.
7964
+ * Mirrors the web app's `channel !== null && channel !== 'web'` gate.
7965
+ */
7966
+ function nonWebChannelLabel(c) {
7967
+ if (isNewConversation(c)) return null;
7968
+ if (c.channel === null || c.channel === "web") return null;
7969
+ return c.channelLabel ?? c.channel;
7970
+ }
7971
+ /**
7972
+ * The list-row shape for a conversation the TUI just forked, so the chat
7973
+ * screen can reopen on the copy without waiting for a list refetch. A fork
7974
+ * carries none of the source's channel binding — that's what makes it
7975
+ * sendable from here — so it is always a web conversation.
7976
+ */
7977
+ function forkedConversationRef({ agent, forked }) {
7978
+ const now = (/* @__PURE__ */ new Date()).toISOString();
7979
+ return {
7980
+ id: forked.id,
7981
+ title: forked.title,
7982
+ createdAt: now,
7983
+ updatedAt: now,
7984
+ preview: null,
7985
+ channel: "web",
7986
+ channelLabel: null,
7987
+ agent: {
7988
+ id: agent.id,
7989
+ name: agent.name
7990
+ }
7991
+ };
7992
+ }
7993
+
7904
7994
  //#endregion
7905
7995
  //#region src/chat/tui/screens/chat.tsx
7906
7996
  /**
@@ -7937,9 +8027,6 @@ const pageScrollFraction = .75;
7937
8027
  /** Cap composer growth; past this the textarea scrolls its content instead. */
7938
8028
  const maxComposerRows = 8;
7939
8029
  const maxAttachments = 10;
7940
- function isNewConversation(c) {
7941
- return "kind" in c && c.kind === "new";
7942
- }
7943
8030
  /**
7944
8031
  * A server timestamp as local epoch ms, or null when it is missing or
7945
8032
  * unparseable so the caller can fall back to its own clock.
@@ -7992,6 +8079,7 @@ function ChatScreen({ agent, conversation }) {
7992
8079
  const isShared = portal.status !== "off";
7993
8080
  const initialConversationId = isNewConversation(conversation) ? null : conversation.id;
7994
8081
  const agentHost = useAgentHost();
8082
+ const nonWebChannel = nonWebChannelLabel(conversation);
7995
8083
  const [conversationId, setConversationId] = useState(initialConversationId);
7996
8084
  const [items, setItems] = useState([]);
7997
8085
  const [todos, setTodos] = useState(null);
@@ -8024,6 +8112,8 @@ function ChatScreen({ agent, conversation }) {
8024
8112
  const [composerRows, setComposerRows] = useState(1);
8025
8113
  const [attachments, setAttachments] = useState([]);
8026
8114
  const uploadsRef = useRef(/* @__PURE__ */ new Map());
8115
+ const [forking, setForking] = useState(false);
8116
+ const forkingRef = useRef(false);
8027
8117
  const [credPrompt, setCredPrompt] = useState(null);
8028
8118
  const credPromptOpen = credPrompt !== null;
8029
8119
  const [menuDismissed, setMenuDismissed] = useState(false);
@@ -8062,10 +8152,11 @@ function ChatScreen({ agent, conversation }) {
8062
8152
  const reviewBelowBoxRef = useRef(null);
8063
8153
  const paneDragRef = useRef(null);
8064
8154
  const composerBoxHeight = composerRows + 2;
8065
- const pendingVisible = !grantPrompt && !credPrompt && attachments.length > 0;
8155
+ const pendingVisible = !grantPrompt && !credPrompt && !nonWebChannel && attachments.length > 0;
8066
8156
  const pendingRows = pendingVisible ? attachments.length + 2 : 0;
8067
8157
  const credPromptHeight = 4 + (credPrompt && (credPrompt.error || credPrompt.submitting) ? 1 : 0);
8068
- const bottomBoxHeight = credPrompt ? credPromptHeight : composerBoxHeight;
8158
+ const nonWebNoticeHeight = 5;
8159
+ const bottomBoxHeight = credPrompt ? credPromptHeight : nonWebChannel ? nonWebNoticeHeight : composerBoxHeight;
8069
8160
  const bodyWidth = Math.max(1, width - 2);
8070
8161
  const bodyHeight = Math.max(1, height - 3);
8071
8162
  const reviewPlacement = filePanePlacement({
@@ -8712,7 +8803,7 @@ function ChatScreen({ agent, conversation }) {
8712
8803
  stageAttachment(image);
8713
8804
  }, [stageAttachment]);
8714
8805
  usePaste((event) => {
8715
- if (modelPickerOpen || themePickerOpen || helpOpen || credPromptOpen || grantPrompt) return;
8806
+ if (modelPickerOpen || themePickerOpen || helpOpen || credPromptOpen || grantPrompt || nonWebChannel) return;
8716
8807
  const text = event.metadata?.kind === "binary" ? "" : decodePasteBytes(event.bytes);
8717
8808
  const route = routePaste({
8718
8809
  kind: event.metadata?.kind,
@@ -8798,6 +8889,39 @@ function ChatScreen({ agent, conversation }) {
8798
8889
  text: `couldn't open a browser — visit ${url} (${errorMessage(err)})`
8799
8890
  }]));
8800
8891
  }, [appUrl, conversationId]);
8892
+ const forkAndContinue = useCallback(() => {
8893
+ if (!rest || !conversationId || forkingRef.current) return;
8894
+ forkingRef.current = true;
8895
+ setForking(true);
8896
+ const stillHere = () => {
8897
+ const screen = useStore.getState().screen;
8898
+ return screen.kind === "chat" && !isNewConversation(screen.conversation) && screen.conversation.id === conversationId;
8899
+ };
8900
+ rest.forkConversation({ conversationId }).then((forked) => {
8901
+ if (!stillHere()) return;
8902
+ goTo({
8903
+ kind: "chat",
8904
+ agent,
8905
+ conversation: forkedConversationRef({
8906
+ agent,
8907
+ forked
8908
+ })
8909
+ });
8910
+ }).catch((err) => {
8911
+ forkingRef.current = false;
8912
+ setForking(false);
8913
+ if (!stillHere()) return;
8914
+ useStore.getState().showToast({
8915
+ variant: "error",
8916
+ message: `couldn't fork this conversation: ${errorMessage(err)}`
8917
+ });
8918
+ });
8919
+ }, [
8920
+ rest,
8921
+ conversationId,
8922
+ agent,
8923
+ goTo
8924
+ ]);
8801
8925
  const toggleShare = useCallback(() => {
8802
8926
  if (!portalClient) return;
8803
8927
  if (portal.status === "off") {
@@ -9093,11 +9217,17 @@ function ChatScreen({ agent, conversation }) {
9093
9217
  toggleShare();
9094
9218
  return;
9095
9219
  }
9220
+ if (nonWebChannel && key.name === "f" && key.ctrl) {
9221
+ forkAndContinue();
9222
+ return;
9223
+ }
9096
9224
  if (key.name === "v" && key.ctrl) {
9225
+ if (nonWebChannel) return;
9097
9226
  pasteImage();
9098
9227
  return;
9099
9228
  }
9100
9229
  if (key.name === "x" && key.ctrl || key.name === "backspace" && attachmentsRef.current.length > 0 && !composerRef.current?.plainText) {
9230
+ if (nonWebChannel) return;
9101
9231
  const last = attachmentsRef.current.at(-1);
9102
9232
  if (last) {
9103
9233
  uploadsRef.current.delete(last.tempId);
@@ -9162,13 +9292,15 @@ function ChatScreen({ agent, conversation }) {
9162
9292
  ctrlCArmed,
9163
9293
  isShared,
9164
9294
  hasPendingSteer,
9165
- hasActionableCard
9295
+ hasActionableCard,
9296
+ nonWebChannel: nonWebChannel !== null
9166
9297
  }), [
9167
9298
  run.kind,
9168
9299
  ctrlCArmed,
9169
9300
  isShared,
9170
9301
  hasPendingSteer,
9171
- hasActionableCard
9302
+ hasActionableCard,
9303
+ nonWebChannel
9172
9304
  ]);
9173
9305
  const modelHint = formatModelHint(model);
9174
9306
  if (modelPickerOpen) return /* @__PURE__ */ jsx(ModelPicker, {
@@ -9397,6 +9529,35 @@ function ChatScreen({ agent, conversation }) {
9397
9529
  children: "n"
9398
9530
  })
9399
9531
  ]
9532
+ }) : nonWebChannel ? /* @__PURE__ */ jsxs("box", {
9533
+ style: {
9534
+ border: true,
9535
+ borderColor: theme.faint,
9536
+ paddingLeft: 1,
9537
+ paddingRight: 1,
9538
+ height: nonWebNoticeHeight,
9539
+ flexShrink: 0,
9540
+ marginTop: 1,
9541
+ flexDirection: "column"
9542
+ },
9543
+ children: [
9544
+ /* @__PURE__ */ jsxs("text", {
9545
+ fg: theme.muted,
9546
+ children: [
9547
+ "this conversation is on ",
9548
+ nonWebChannel,
9549
+ "."
9550
+ ]
9551
+ }),
9552
+ /* @__PURE__ */ jsx("text", {
9553
+ fg: theme.dim,
9554
+ children: "reply there to continue it."
9555
+ }),
9556
+ /* @__PURE__ */ jsx("text", {
9557
+ fg: theme.dim,
9558
+ children: forking ? "forking into a new chat…" : "ctrl+f fork it into a chat you can continue here."
9559
+ })
9560
+ ]
9400
9561
  }) : /* @__PURE__ */ jsx("box", {
9401
9562
  style: {
9402
9563
  border: true,
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import "./rest-DZnAT7BG.mjs";
3
+ import "./api-BHq-5eW_.mjs";
4
+ import { t as PortalClient } from "./client-t6GmE8TI.mjs";
5
+
6
+ export { PortalClient };
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as errorMessage } from "./util-CeisaZVY.mjs";
3
- import { c as machineIdentity, i as mintPortalDeviceToken, l as portalWsUrl, n as findThisDevice, r as grantPortalAccess, s as buildEnv, t as fetchPortalDevices } from "./api-zUf_Vrzh.mjs";
3
+ import { c as machineIdentity, i as mintPortalDeviceToken, l as portalWsUrl, n as findThisDevice, r as grantPortalAccess, s as buildEnv, t as fetchPortalDevices } from "./api-BHq-5eW_.mjs";
4
4
  import { z } from "zod";
5
5
  import { spawn } from "node:child_process";
6
6
  import { WebSocket } from "ws";
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import "./rest-BBQrPIwq.mjs";
3
- import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-YqNIRwdf.mjs";
2
+ import "./rest-DZnAT7BG.mjs";
3
+ import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-XvAHDKqu.mjs";
4
4
 
5
5
  export { messageGet, readStdin, resolveAgent, runPrint };
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { n as createRestClient, t as HttpError } from "./rest-BBQrPIwq.mjs";
2
+ import { n as createRestClient, t as HttpError } from "./rest-DZnAT7BG.mjs";
3
3
  import { n as isRecord, t as errorMessage } from "./util-CeisaZVY.mjs";
4
4
  import stableStringify from "safe-stable-stringify";
5
5
 
@@ -14,7 +14,7 @@ import { n as printError } from "./output-B4cW10Ph.mjs";
14
14
  * the reply (and to --json).
15
15
  */
16
16
  async function connectMachineShare({ appUrl, sessionToken, timeoutHint }) {
17
- const { PortalClient } = await import("./client-A4m7lNDg.mjs");
17
+ const { PortalClient } = await import("./client-CRAGDqp0.mjs");
18
18
  let signalConnected;
19
19
  const connected = new Promise((resolve) => {
20
20
  signalConnected = resolve;
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { n as createRestClient, r as errorDetail, t as HttpError } from "./rest-BBQrPIwq.mjs";
2
+ import { n as createRestClient, r as errorDetail, t as HttpError } from "./rest-DZnAT7BG.mjs";
3
3
 
4
4
  export { createRestClient };
@@ -223,6 +223,10 @@ function createRestClient({ appUrl, sessionToken }) {
223
223
  sizeBytes: finalized.sizeBytes ?? size
224
224
  };
225
225
  },
226
+ forkConversation: async ({ conversationId }) => {
227
+ const { conversation } = await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/fork`, {}, forkConversationResponseSchema);
228
+ return conversation;
229
+ },
226
230
  deleteConversation: async ({ conversationId }) => {
227
231
  await del(`/api/v1/conversations/${encodeURIComponent(conversationId)}`);
228
232
  },
@@ -347,6 +351,12 @@ const listAgentsResponseSchema = z.object({
347
351
  totalCount: z.number().nullable().optional()
348
352
  });
349
353
  const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
354
+ const conversationAgentSchema = z.object({
355
+ id: z.string().uuid(),
356
+ name: z.string(),
357
+ slug: z.string().nullable().optional(),
358
+ title: z.string().nullable().optional()
359
+ });
350
360
  const conversationSummarySchema = z.object({
351
361
  id: z.string().uuid(),
352
362
  title: z.string().nullable(),
@@ -356,20 +366,23 @@ const conversationSummarySchema = z.object({
356
366
  channel: z.string().nullable(),
357
367
  channelLabel: z.string().nullable(),
358
368
  viewerArchivedAt: z.string().nullable().optional(),
359
- agent: z.object({
360
- id: z.string().uuid(),
361
- name: z.string(),
362
- slug: z.string().nullable().optional(),
363
- title: z.string().nullable().optional()
364
- })
369
+ agent: conversationAgentSchema,
370
+ agents: z.array(conversationAgentSchema).optional()
365
371
  });
366
372
  const conversationDetailSchema = z.object({
367
373
  id: z.string().uuid(),
368
374
  title: z.string().nullable(),
369
375
  agentId: z.string().uuid(),
370
376
  createdAt: z.string(),
371
- updatedAt: z.string()
377
+ updatedAt: z.string(),
378
+ channel: z.string().nullable(),
379
+ channelLabel: z.string().nullable()
380
+ });
381
+ const conversationTitleSchema = z.object({
382
+ id: z.string().uuid(),
383
+ title: z.string().nullable()
372
384
  });
385
+ const forkConversationResponseSchema = z.object({ conversation: conversationTitleSchema });
373
386
  const getConversationResponseSchema = z.object({ conversation: conversationDetailSchema });
374
387
  const listConversationsResponseSchema = z.object({
375
388
  conversations: z.array(conversationSummarySchema),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.2.0-beta.457",
3
+ "version": "0.2.0-beta.482",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -1,6 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./rest-BBQrPIwq.mjs";
3
- import "./api-zUf_Vrzh.mjs";
4
- import { t as PortalClient } from "./client-DuPxkSoH.mjs";
5
-
6
- export { PortalClient };