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

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/js/bin.mjs CHANGED
@@ -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.482";
23
+ var version = "0.2.0-beta.497";
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-B8ownO3n.mjs");
1203
+ const { runChat } = await import("./boot-DlptSLP6.mjs");
1204
1204
  await runChat({
1205
1205
  appUrl,
1206
1206
  sessionToken: session.value.sessionToken,
@@ -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-B8ownO3n.mjs");
1514
+ const { runChat } = await import("./boot-DlptSLP6.mjs");
1515
1515
  await runChat({
1516
1516
  appUrl,
1517
1517
  sessionToken: session.value.sessionToken,
@@ -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-B8ownO3n.mjs");
1813
+ const { runWorkspacePicker } = await import("./boot-DlptSLP6.mjs");
1814
1814
  await runWorkspacePicker(session);
1815
1815
  return;
1816
1816
  }
@@ -4095,8 +4095,8 @@ function TodoCardView({ todos, isActive }) {
4095
4095
  //#endregion
4096
4096
  //#region src/chat/tui/chat/footer-hints.ts
4097
4097
  /** Build the chat footer copy independently of its colors and layout. */
4098
- function formatChatFooterStatus({ runKind, ctrlCArmed, isShared, hasPendingSteer, hasActionableCard, nonWebChannel }) {
4099
- const connectHint = hasActionableCard ? "ctrl+r connect · " : "";
4098
+ function formatChatFooterStatus({ runKind, ctrlCArmed, isShared, hasPendingSteer, actionableCardCount, nonWebChannel }) {
4099
+ const connectHint = actionableCardCount > 1 ? `ctrl+r connect (${actionableCardCount}) · ` : actionableCardCount === 1 ? "ctrl+r connect · " : "";
4100
4100
  switch (runKind) {
4101
4101
  case "idle":
4102
4102
  if (ctrlCArmed) return "press ctrl+c again to quit";
@@ -4443,7 +4443,7 @@ const keybindGroups = [
4443
4443
  },
4444
4444
  {
4445
4445
  keys: "ctrl+r",
4446
- action: "run the newest connect card (auth cards)"
4446
+ action: "act on a waiting connect card (auth cards)"
4447
4447
  },
4448
4448
  {
4449
4449
  keys: "pgup / pgdn",
@@ -4926,7 +4926,7 @@ const slashCommands = [
4926
4926
  name: "connect",
4927
4927
  aliases: [],
4928
4928
  argsHint: null,
4929
- description: "run the newest connect card (auth cards)",
4929
+ description: "act on a waiting connect card (auth cards)",
4930
4930
  action: { kind: "connect" }
4931
4931
  },
4932
4932
  {
@@ -5024,6 +5024,93 @@ function printResumeHint(write = (text) => process.stdout.write(text)) {
5024
5024
  lastConversationId = null;
5025
5025
  }
5026
5026
 
5027
+ //#endregion
5028
+ //#region src/chat/tui/screens/card-picker.tsx
5029
+ /**
5030
+ * Asks which connect card ctrl+r should act on. A conversation can hold
5031
+ * several cards waiting at once (a batch of `platform auth configure` calls),
5032
+ * and the chord can't guess which one the user means — picking the newest
5033
+ * silently strands the rest, since a card left in `launched` or `error` stays
5034
+ * actionable forever. Rendered as an overlay in place of the transcript, like
5035
+ * the model and theme pickers, so the conversation and any live run survive.
5036
+ */
5037
+ function CardPicker({ cards, onSelect, onCancel }) {
5038
+ const { height } = useTerminalDimensions();
5039
+ const [highlight, setHighlight] = useState(0);
5040
+ const clamped = Math.min(highlight, Math.max(0, cards.length - 1));
5041
+ const visibleRows = Math.max(3, height - 6);
5042
+ const start = windowStart(clamped, cards.length, visibleRows);
5043
+ const windowed = cards.slice(start, start + visibleRows);
5044
+ useKeyboard((key) => {
5045
+ if (key.name === "escape") {
5046
+ onCancel();
5047
+ return;
5048
+ }
5049
+ if (key.name === "up") setHighlight(Math.max(0, clamped - 1));
5050
+ else if (key.name === "down") setHighlight(Math.min(Math.max(0, cards.length - 1), clamped + 1));
5051
+ else if (key.name === "return") {
5052
+ const card = cards[clamped];
5053
+ if (card) onSelect(card);
5054
+ }
5055
+ });
5056
+ useEffect(() => {
5057
+ if (cards.length === 0) onCancel();
5058
+ }, [cards.length, onCancel]);
5059
+ if (cards.length === 0) return null;
5060
+ return /* @__PURE__ */ jsxs("box", {
5061
+ style: {
5062
+ flexDirection: "column",
5063
+ flexGrow: 1
5064
+ },
5065
+ children: [
5066
+ /* @__PURE__ */ jsx("text", {
5067
+ fg: theme.accent,
5068
+ children: "connect"
5069
+ }),
5070
+ /* @__PURE__ */ jsx("box", {
5071
+ style: {
5072
+ flexDirection: "column",
5073
+ flexGrow: 1,
5074
+ marginTop: 1
5075
+ },
5076
+ children: windowed.map((card, i) => {
5077
+ const selected = start + i === clamped;
5078
+ const waiting = waitingOn(card);
5079
+ const label = card.card.button?.label ?? "";
5080
+ return /* @__PURE__ */ jsxs("text", {
5081
+ fg: selected ? theme.accent : theme.fg,
5082
+ children: [
5083
+ selected ? "› " : " ",
5084
+ "⚿ ",
5085
+ card.card.title,
5086
+ label ? /* @__PURE__ */ jsxs("span", {
5087
+ fg: theme.muted,
5088
+ children: [" · ", label]
5089
+ }) : null,
5090
+ waiting ? /* @__PURE__ */ jsxs("span", {
5091
+ fg: theme.dim,
5092
+ children: [" · ", waiting]
5093
+ }) : null
5094
+ ]
5095
+ }, card.id);
5096
+ })
5097
+ }),
5098
+ /* @__PURE__ */ jsxs("text", {
5099
+ fg: theme.dim,
5100
+ children: [cards.length, " waiting · ↑/↓ move · ↵ select · esc cancel"]
5101
+ })
5102
+ ]
5103
+ });
5104
+ }
5105
+ /** Why a card is still on the list, for the rows that aren't simply untouched. */
5106
+ function waitingOn(card) {
5107
+ switch (card.status) {
5108
+ case "launched": return "finish in your browser";
5109
+ case "error": return card.detail ?? "failed";
5110
+ default: return null;
5111
+ }
5112
+ }
5113
+
5027
5114
  //#endregion
5028
5115
  //#region src/chat/tui/screens/model-picker.tsx
5029
5116
  /**
@@ -8087,6 +8174,7 @@ function ChatScreen({ agent, conversation }) {
8087
8174
  const [input, setInput] = useState("");
8088
8175
  const [run, setRun] = useState({ kind: "idle" });
8089
8176
  const [model, setModel] = useState(agent.model ?? null);
8177
+ const [cardPickerOpen, setCardPickerOpen] = useState(false);
8090
8178
  const [modelPickerOpen, setModelPickerOpen] = useState(false);
8091
8179
  const [themePickerOpen, setThemePickerOpen] = useState(false);
8092
8180
  const [helpOpen, setHelpOpen] = useState(false);
@@ -8200,6 +8288,7 @@ function ChatScreen({ agent, conversation }) {
8200
8288
  };
8201
8289
  }, [
8202
8290
  grantPrompt,
8291
+ cardPickerOpen,
8203
8292
  modelPickerOpen,
8204
8293
  themePickerOpen,
8205
8294
  helpOpen,
@@ -8555,10 +8644,15 @@ function ChatScreen({ agent, conversation }) {
8555
8644
  portalClient,
8556
8645
  portal.status
8557
8646
  ]);
8558
- const runNewestCard = useCallback(() => {
8559
- const target = [...itemsRef.current].reverse().find(isActionableCard);
8560
- if (!target) return false;
8561
- runCardAction(target);
8647
+ const promptForCards = useCallback(() => {
8648
+ const targets = itemsRef.current.filter(isActionableCard);
8649
+ const [only] = targets;
8650
+ if (!only) return false;
8651
+ if (targets.length === 1) {
8652
+ runCardAction(only);
8653
+ return true;
8654
+ }
8655
+ setCardPickerOpen(true);
8562
8656
  return true;
8563
8657
  }, [runCardAction]);
8564
8658
  const handleCredInput = useCallback((displayed) => {
@@ -8803,7 +8897,7 @@ function ChatScreen({ agent, conversation }) {
8803
8897
  stageAttachment(image);
8804
8898
  }, [stageAttachment]);
8805
8899
  usePaste((event) => {
8806
- if (modelPickerOpen || themePickerOpen || helpOpen || credPromptOpen || grantPrompt || nonWebChannel) return;
8900
+ if (cardPickerOpen || modelPickerOpen || themePickerOpen || helpOpen || credPromptOpen || grantPrompt || nonWebChannel) return;
8807
8901
  const text = event.metadata?.kind === "binary" ? "" : decodePasteBytes(event.bytes);
8808
8902
  const route = routePaste({
8809
8903
  kind: event.metadata?.kind,
@@ -8852,6 +8946,7 @@ function ChatScreen({ agent, conversation }) {
8852
8946
  if (composer.plainText === "" && inputRef.current !== "") applyComposerText(inputRef.current, "end");
8853
8947
  }, [
8854
8948
  grantPrompt,
8949
+ cardPickerOpen,
8855
8950
  modelPickerOpen,
8856
8951
  themePickerOpen,
8857
8952
  helpOpen,
@@ -9024,7 +9119,7 @@ function ChatScreen({ agent, conversation }) {
9024
9119
  openInBrowser();
9025
9120
  break;
9026
9121
  case "connect":
9027
- if (!runNewestCard()) useStore.getState().showToast({ message: "no connect card waiting for action" });
9122
+ if (!promptForCards()) useStore.getState().showToast({ message: "no connect card waiting for action" });
9028
9123
  break;
9029
9124
  case "help":
9030
9125
  setHelpOpen(true);
@@ -9063,7 +9158,7 @@ function ChatScreen({ agent, conversation }) {
9063
9158
  model,
9064
9159
  portal.status,
9065
9160
  portal.grantedAgentIds,
9066
- runNewestCard
9161
+ promptForCards
9067
9162
  ]);
9068
9163
  const submit = useCallback(() => {
9069
9164
  const staged = attachments.filter((a) => a.status !== "error");
@@ -9118,7 +9213,7 @@ function ChatScreen({ agent, conversation }) {
9118
9213
  }
9119
9214
  if (reviewFocused) return;
9120
9215
  }
9121
- if (modelPickerOpen || themePickerOpen || helpOpen) return;
9216
+ if (cardPickerOpen || modelPickerOpen || themePickerOpen || helpOpen) return;
9122
9217
  if (grantPrompt) {
9123
9218
  if (key.name === "y") {
9124
9219
  confirmGrant();
@@ -9210,7 +9305,7 @@ function ChatScreen({ agent, conversation }) {
9210
9305
  return;
9211
9306
  }
9212
9307
  if (key.name === "r" && key.ctrl) {
9213
- runNewestCard();
9308
+ promptForCards();
9214
9309
  return;
9215
9310
  }
9216
9311
  if (key.name === "t" && key.ctrl) {
@@ -9286,23 +9381,31 @@ function ChatScreen({ agent, conversation }) {
9286
9381
  }
9287
9382
  });
9288
9383
  const hasPendingSteer = items.some((m) => m.kind === "pending-steer");
9289
- const hasActionableCard = items.some(isActionableCard);
9384
+ const actionableCards = useMemo(() => items.filter(isActionableCard), [items]);
9290
9385
  const statusText = useMemo(() => formatChatFooterStatus({
9291
9386
  runKind: run.kind,
9292
9387
  ctrlCArmed,
9293
9388
  isShared,
9294
9389
  hasPendingSteer,
9295
- hasActionableCard,
9390
+ actionableCardCount: actionableCards.length,
9296
9391
  nonWebChannel: nonWebChannel !== null
9297
9392
  }), [
9298
9393
  run.kind,
9299
9394
  ctrlCArmed,
9300
9395
  isShared,
9301
9396
  hasPendingSteer,
9302
- hasActionableCard,
9397
+ actionableCards.length,
9303
9398
  nonWebChannel
9304
9399
  ]);
9305
9400
  const modelHint = formatModelHint(model);
9401
+ if (cardPickerOpen) return /* @__PURE__ */ jsx(CardPicker, {
9402
+ cards: actionableCards,
9403
+ onSelect: (card) => {
9404
+ setCardPickerOpen(false);
9405
+ runCardAction(card);
9406
+ },
9407
+ onCancel: () => setCardPickerOpen(false)
9408
+ });
9306
9409
  if (modelPickerOpen) return /* @__PURE__ */ jsx(ModelPicker, {
9307
9410
  agentId: agent.id,
9308
9411
  currentModel: model,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.2.0-beta.482",
3
+ "version": "0.2.0-beta.497",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",