skydive-cli 0.2.0-beta.577 → 0.2.0-beta.587

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/CHANGELOG.md CHANGED
@@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
11
11
 
12
12
  - Chat TUI sets the terminal title (OSC) to the live conversation title, so a terminal multiplexer (cmux, tmux, zellij) or window names its tab after the conversation. Follows renames from autoname, web, and Slack, and resets to a neutral default on exit.
13
13
  - Every TUI screen (pickers included), not just chat, now drives the title, and inside cmux the workspace (sidebar entry) is renamed too via the cmux control socket. A workspace title you set by hand is left untouched. The tab and workspace title read title-first (title then agent name), while the in-app status bar stays agent-first.
14
+ - Notification and sidebar previews use the last text block of the reply (the answer, not the opening preamble) and render as plain text, stripping inline markdown so markers like asterisks and backticks no longer leak into the preview.
14
15
 
15
16
  ## [0.2.0] - 2026-07-29
16
17
 
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.577";
23
+ var version = "0.2.0-beta.587";
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--whEK1H-.mjs");
1203
+ const { runChat } = await import("./boot-BZUWp0ve.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--whEK1H-.mjs");
1514
+ const { runChat } = await import("./boot-BZUWp0ve.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--whEK1H-.mjs");
1813
+ const { runWorkspacePicker } = await import("./boot-BZUWp0ve.mjs");
1814
1814
  await runWorkspacePicker(session);
1815
1815
  return;
1816
1816
  }
@@ -2466,7 +2466,7 @@ function setupUpdateCheck(argv) {
2466
2466
 
2467
2467
  //#endregion
2468
2468
  //#region src/changelog.generated.ts
2469
- const CHANGELOG_MD = "# Changelog\n\nAll notable changes to the Skydive CLI are documented here.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n\n- Chat TUI sets the terminal title (OSC) to the live conversation title, so a terminal multiplexer (cmux, tmux, zellij) or window names its tab after the conversation. Follows renames from autoname, web, and Slack, and resets to a neutral default on exit.\n- Every TUI screen (pickers included), not just chat, now drives the title, and inside cmux the workspace (sidebar entry) is renamed too via the cmux control socket. A workspace title you set by hand is left untouched. The tab and workspace title read title-first (title then agent name), while the in-app status bar stays agent-first.\n\n## [0.2.0] - 2026-07-29\n\n### Added\n\n**Sandbox & machine access**\n\n- Standalone `skydive sandbox` command for direct access to an agent's sandbox.\n- `/sandbox` in the chat TUI — live PTY session and one-shot command execution.\n- Headless machine sharing via `skydive portal`, with self-registering portal grants.\n- `shareMachineDefault` config key for always-on portal sharing.\n\n**Chat TUI**\n\n- File pane (`ctrl+g`) with Changes and Files tabs: a shared file tree plus source viewer and a workspace browser, mouse-resizable and responsive to terminal size.\n- Agent todo list rendered in the chat TUI, above the composer, mirroring the web chat's todo card.\n- Fuzzy finder in the conversation and agent pickers.\n- Slash-command autocomplete menu.\n- Paste or drag-and-drop any file into the composer; paste clipboard images with Cmd+V.\n- Run local shell commands with `!` in the composer.\n- Conversation recaps, streamed live title updates, and per-agent attribution on assistant turns.\n- Working timer rolls up into minutes and hours.\n- Conversation picker paginates past 50 conversations and shows only your own conversations.\n- Esc leaves a live run; typing `exit` quits the chat.\n- Cursor Dark theme.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n\n**Headless & scripting**\n\n- Resume a conversation by id.\n- `conversations list` and `conversations show` for transcript reads.\n- `messages get`, with run recovery keyed on message id.\n- Connect cards surface in headless `-p` mode so a driving agent never gets stuck.\n\n**Authentication**\n\n- `skydive auth login` via the browser now auto-mints an API key, so one login yields both a chat session and a usable management credential. Management commands announce the key's pinned workspace when it drives them, so a workspace mismatch is visible at use time.\n- Workspace picker on the device authorization page.\n- Account and workspace identity shown in `auth status`.\n\n**Platform**\n\n- Standalone binary builds compiling the CLI into a per-target executable.\n- Interactive workspace switcher; management commands follow the active workspace.\n- Terminal host integrations and agent notifications.\n\n### Fixed\n\n- Transcript errors collapse to one line, click to expand (REST and portal errors keep their full body).\n- Dragged/pasted image file paths attach the file instead of inserting path text, including macOS paths with literal parentheses.\n- Bare URLs in chat markdown are hyperlinked so they survive text wrap.\n- Composer draft is preserved across TUI overlays.\n- Relative connect links resolve before opening the browser.\n- Numbered markdown headings render colored in the TUI.\n- Chat transcript pages by 75% of a screen; picker rows stay on one line.\n- Run starts push to the TUI over the conversation stream.\n- Security: remediated high-severity dependency findings and cleared tar/shell-quote CVEs.\n\n## [0.1.0] - 2026-07-21\n\nInitial public release: `skydive chat` TUI, agent and conversation management,\ndevice authorization, and headless `-p` mode.\n";
2469
+ const CHANGELOG_MD = "# Changelog\n\nAll notable changes to the Skydive CLI are documented here.\n\nThe format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),\nand this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).\n\n## [Unreleased]\n\n### Added\n\n- Chat TUI sets the terminal title (OSC) to the live conversation title, so a terminal multiplexer (cmux, tmux, zellij) or window names its tab after the conversation. Follows renames from autoname, web, and Slack, and resets to a neutral default on exit.\n- Every TUI screen (pickers included), not just chat, now drives the title, and inside cmux the workspace (sidebar entry) is renamed too via the cmux control socket. A workspace title you set by hand is left untouched. The tab and workspace title read title-first (title then agent name), while the in-app status bar stays agent-first.\n- Notification and sidebar previews use the last text block of the reply (the answer, not the opening preamble) and render as plain text, stripping inline markdown so markers like asterisks and backticks no longer leak into the preview.\n\n## [0.2.0] - 2026-07-29\n\n### Added\n\n**Sandbox & machine access**\n\n- Standalone `skydive sandbox` command for direct access to an agent's sandbox.\n- `/sandbox` in the chat TUI — live PTY session and one-shot command execution.\n- Headless machine sharing via `skydive portal`, with self-registering portal grants.\n- `shareMachineDefault` config key for always-on portal sharing.\n\n**Chat TUI**\n\n- File pane (`ctrl+g`) with Changes and Files tabs: a shared file tree plus source viewer and a workspace browser, mouse-resizable and responsive to terminal size.\n- Agent todo list rendered in the chat TUI, above the composer, mirroring the web chat's todo card.\n- Fuzzy finder in the conversation and agent pickers.\n- Slash-command autocomplete menu.\n- Paste or drag-and-drop any file into the composer; paste clipboard images with Cmd+V.\n- Run local shell commands with `!` in the composer.\n- Conversation recaps, streamed live title updates, and per-agent attribution on assistant turns.\n- Working timer rolls up into minutes and hours.\n- Conversation picker paginates past 50 conversations and shows only your own conversations.\n- Esc leaves a live run; typing `exit` quits the chat.\n- Cursor Dark theme.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n\n**Headless & scripting**\n\n- Resume a conversation by id.\n- `conversations list` and `conversations show` for transcript reads.\n- `messages get`, with run recovery keyed on message id.\n- Connect cards surface in headless `-p` mode so a driving agent never gets stuck.\n\n**Authentication**\n\n- `skydive auth login` via the browser now auto-mints an API key, so one login yields both a chat session and a usable management credential. Management commands announce the key's pinned workspace when it drives them, so a workspace mismatch is visible at use time.\n- Workspace picker on the device authorization page.\n- Account and workspace identity shown in `auth status`.\n\n**Platform**\n\n- Standalone binary builds compiling the CLI into a per-target executable.\n- Interactive workspace switcher; management commands follow the active workspace.\n- Terminal host integrations and agent notifications.\n\n### Fixed\n\n- Transcript errors collapse to one line, click to expand (REST and portal errors keep their full body).\n- Dragged/pasted image file paths attach the file instead of inserting path text, including macOS paths with literal parentheses.\n- Bare URLs in chat markdown are hyperlinked so they survive text wrap.\n- Composer draft is preserved across TUI overlays.\n- Relative connect links resolve before opening the browser.\n- Numbered markdown headings render colored in the TUI.\n- Chat transcript pages by 75% of a screen; picker rows stay on one line.\n- Run starts push to the TUI over the conversation stream.\n- Security: remediated high-severity dependency findings and cleared tar/shell-quote CVEs.\n\n## [0.1.0] - 2026-07-21\n\nInitial public release: `skydive chat` TUI, agent and conversation management,\ndevice authorization, and headless `-p` mode.\n";
2470
2470
 
2471
2471
  //#endregion
2472
2472
  //#region src/whats-new.ts
@@ -4368,9 +4368,29 @@ function formatShare(share) {
4368
4368
  //#region src/chat/notification-preview.ts
4369
4369
  const MAX_NOTIFICATION_PREVIEW_CHARS = 240;
4370
4370
  /**
4371
+ * Flattens common inline markdown to plain text for a notification / sidebar
4372
+ * preview, which renders as raw text (no markdown). Without this the preview
4373
+ * shows literal `**bold**` and `*italic*` markers. Intentionally lightweight —
4374
+ * not a full parser, just the inline markers that show up in a one-line
4375
+ * preview: emphasis, inline code, links/images, headings, blockquotes, and
4376
+ * list bullets. Block structure is already gone by the time we preview (we
4377
+ * collapse whitespace to a single line).
4378
+ */
4379
+ function stripInlineMarkdown(input) {
4380
+ return input.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/`([^`]+)`/g, "$1").replace(/(\*\*\*|\*\*|\*|___|__|_)(?=\S)(.*?\S)\1/g, "$2").replace(/(\*\*\*|\*\*|\*|___|__|_)(?=\S)(.*?\S)\1/g, "$2").replace(/~~(?=\S)(.*?\S)~~/g, "$1").replace(/^\s{0,3}#{1,6}\s+/gm, "").replace(/^\s{0,3}>\s?/gm, "").replace(/^\s{0,3}(?:[-*+]|\d+\.)\s+/gm, "");
4381
+ }
4382
+ /**
4371
4383
  * Collects assistant `text-delta` chunks for one run and turns them into a
4372
- * desktop-notification-sized preview. Reasoning, tool output, cards, errors,
4384
+ * notification/side-panel-sized preview. Reasoning, tool output, cards, errors,
4373
4385
  * and old transcript items are intentionally excluded.
4386
+ *
4387
+ * A run is a sequence of text parts split by tool calls and reasoning
4388
+ * (text → tool → text → … → final text). The meaningful summary of a run is
4389
+ * its **last** text part: the closing answer the agent lands on after all its
4390
+ * tool work, not the "let me look into this…" preamble it opened with. So the
4391
+ * preview keeps only the final part's text, and when that part is longer than
4392
+ * the cap it keeps the **beginning** with a trailing ellipsis (the
4393
+ * conventional truncation) — the block already opens on the answer.
4374
4394
  */
4375
4395
  function createResponsePreview() {
4376
4396
  let text = "";
@@ -4380,12 +4400,12 @@ function createResponsePreview() {
4380
4400
  if (chunk.type !== "text-delta") return;
4381
4401
  const delta = typeof chunk.delta === "string" ? chunk.delta : typeof chunk.text === "string" ? chunk.text : "";
4382
4402
  const partId = typeof chunk.id === "string" ? chunk.id : void 0;
4383
- if (text && partId && lastPartId && partId !== lastPartId) text += " ";
4403
+ if (partId && lastPartId && partId !== lastPartId) text = "";
4384
4404
  text += delta;
4385
4405
  lastPartId = partId ?? lastPartId;
4386
4406
  },
4387
4407
  get() {
4388
- const normalized = text.replace(/\s+/g, " ").trim();
4408
+ const normalized = stripInlineMarkdown(text).replace(/\s+/g, " ").trim();
4389
4409
  if (!normalized) return void 0;
4390
4410
  const characters = Array.from(normalized);
4391
4411
  if (characters.length <= MAX_NOTIFICATION_PREVIEW_CHARS) return normalized;
@@ -4677,6 +4697,14 @@ const keybindGroups = [
4677
4697
  keys: "pgup / pgdn",
4678
4698
  action: "scroll transcript"
4679
4699
  },
4700
+ {
4701
+ keys: "end",
4702
+ action: "jump to latest (when scrolled up)"
4703
+ },
4704
+ {
4705
+ keys: "/plan [show|hide]",
4706
+ action: "show / hide the pinned plan card"
4707
+ },
4680
4708
  {
4681
4709
  keys: "/",
4682
4710
  action: "open the command menu (start of message)"
@@ -5143,6 +5171,16 @@ const slashCommands = [
5143
5171
  description: "show connection and session status",
5144
5172
  action: { kind: "status" }
5145
5173
  },
5174
+ {
5175
+ name: "plan",
5176
+ aliases: ["todos", "todo"],
5177
+ argsHint: "[show|hide]",
5178
+ description: "show or hide the pinned plan card",
5179
+ action: {
5180
+ kind: "plan",
5181
+ mode: "toggle"
5182
+ }
5183
+ },
5146
5184
  {
5147
5185
  name: "share",
5148
5186
  aliases: [],
@@ -5164,6 +5202,13 @@ const slashCommands = [
5164
5202
  description: "act on a waiting connect card (auth cards)",
5165
5203
  action: { kind: "connect" }
5166
5204
  },
5205
+ {
5206
+ name: "copy",
5207
+ aliases: ["cp"],
5208
+ argsHint: null,
5209
+ description: "copy the agent's last message to the clipboard",
5210
+ action: { kind: "copy" }
5211
+ },
5167
5212
  {
5168
5213
  name: "help",
5169
5214
  aliases: [],
@@ -8208,6 +8253,13 @@ function routeInput(raw) {
8208
8253
  kind: "open-conversation",
8209
8254
  ref: match.rest
8210
8255
  } : { kind: "conversation-picker" };
8256
+ case "plan": {
8257
+ const arg = match.rest.toLowerCase();
8258
+ return {
8259
+ kind: "plan",
8260
+ mode: arg === "show" ? "show" : arg === "hide" ? "hide" : "toggle"
8261
+ };
8262
+ }
8211
8263
  case "new-conversation":
8212
8264
  case "model-picker":
8213
8265
  case "theme-picker":
@@ -8215,6 +8267,7 @@ function routeInput(raw) {
8215
8267
  case "share":
8216
8268
  case "browser":
8217
8269
  case "connect":
8270
+ case "copy":
8218
8271
  case "help":
8219
8272
  case "quit": return { kind: action.kind };
8220
8273
  default: {
@@ -8409,6 +8462,14 @@ function ChatScreen({ agent, conversation }) {
8409
8462
  const [conversationId, setConversationId] = useState(initialConversationId);
8410
8463
  const [items, setItems] = useState([]);
8411
8464
  const [todos, setTodos] = useState(null);
8465
+ const [planHidden, setPlanHidden] = useState(false);
8466
+ const [scrolledUp, setScrolledUp] = useState(false);
8467
+ const hadTodosRef = useRef(false);
8468
+ useEffect(() => {
8469
+ const hasTodos = !!todos && todos.length > 0;
8470
+ if (hasTodos && !hadTodosRef.current) setPlanHidden(false);
8471
+ hadTodosRef.current = hasTodos;
8472
+ }, [todos]);
8412
8473
  const [historyLoaded, setHistoryLoaded] = useState(initialConversationId === null);
8413
8474
  const [input, setInput] = useState("");
8414
8475
  const [run, setRun] = useState({ kind: "idle" });
@@ -8475,6 +8536,25 @@ function ChatScreen({ agent, conversation }) {
8475
8536
  }, [agentHost, conversationId]);
8476
8537
  const scrollRef = useRef(null);
8477
8538
  const composerRef = useRef(null);
8539
+ const jumpToBottom = useCallback(() => {
8540
+ const box = scrollRef.current;
8541
+ if (!box) return;
8542
+ box.scrollTo({
8543
+ x: box.scrollLeft,
8544
+ y: box.scrollHeight
8545
+ });
8546
+ setScrolledUp(false);
8547
+ }, []);
8548
+ useEffect(() => {
8549
+ const id = setInterval(() => {
8550
+ const box = scrollRef.current;
8551
+ if (!box) return;
8552
+ const viewport = box.viewport.height;
8553
+ const atBottom = box.scrollTop + viewport >= box.scrollHeight - 1;
8554
+ setScrolledUp((prev) => prev === !atBottom ? prev : !atBottom);
8555
+ }, 150);
8556
+ return () => clearInterval(id);
8557
+ }, []);
8478
8558
  const reviewSideBoxRef = useRef(null);
8479
8559
  const reviewBelowBoxRef = useRef(null);
8480
8560
  const paneDragRef = useRef(null);
@@ -8498,9 +8578,11 @@ function ChatScreen({ agent, conversation }) {
8498
8578
  const chatHeight = bodyHeight;
8499
8579
  const inlineReviewHeight = reviewOpen && reviewPlacement === "below" ? reviewHeight : 0;
8500
8580
  const menuRows = menuOpen ? completionMenuHeight(menuCommands.length) : 0;
8501
- const todoCardVisible = !credPrompt && !!todos && todos.length > 0;
8581
+ const todoCardVisible = !credPrompt && !planHidden && !!todos && todos.length > 0;
8502
8582
  const todoRows = todoCardVisible ? todoCardHeight(todos) : 0;
8503
- const scrollHeight = Math.max(3, chatHeight - 2 - bottomBoxHeight - pendingRows - inlineReviewHeight - menuRows - todoRows);
8583
+ const jumpHintVisible = scrolledUp && !credPrompt;
8584
+ const jumpHintRows = jumpHintVisible ? 1 : 0;
8585
+ const scrollHeight = Math.max(3, chatHeight - 2 - bottomBoxHeight - pendingRows - inlineReviewHeight - menuRows - jumpHintRows - todoRows);
8504
8586
  useEffect(() => {
8505
8587
  if (reviewOpen && reviewFocused) {
8506
8588
  const composer = composerRef.current;
@@ -9208,6 +9290,17 @@ function ChatScreen({ agent, conversation }) {
9208
9290
  } : m));
9209
9291
  useStore.getState().showToast({ message: "run cancelled" });
9210
9292
  }, [rest]);
9293
+ const copyLastMessage = useCallback(() => {
9294
+ const last = [...items].reverse().find((m) => m.kind === "assistant-text" && m.done && m.text.trim() !== "");
9295
+ if (!last) {
9296
+ useStore.getState().showToast({ message: "no agent message to copy yet" });
9297
+ return;
9298
+ }
9299
+ writeClipboardText(last.text).then((ok) => useStore.getState().showToast(ok ? {
9300
+ variant: "success",
9301
+ message: "copied last message to clipboard"
9302
+ } : { message: "clipboard unavailable" }));
9303
+ }, [items]);
9211
9304
  const openInBrowser = useCallback(() => {
9212
9305
  if (!appUrl) return;
9213
9306
  if (!conversationId) {
@@ -9419,12 +9512,25 @@ function ChatScreen({ agent, conversation }) {
9419
9512
  })
9420
9513
  }]);
9421
9514
  break;
9515
+ case "plan": {
9516
+ if (!(!!todos && todos.length > 0)) {
9517
+ useStore.getState().showToast({ message: "no plan to show yet" });
9518
+ break;
9519
+ }
9520
+ const nextHidden = routed.mode === "toggle" ? !planHidden : routed.mode === "hide";
9521
+ setPlanHidden(nextHidden);
9522
+ useStore.getState().showToast({ message: nextHidden ? "plan hidden (/plan show)" : "plan shown" });
9523
+ break;
9524
+ }
9422
9525
  case "share":
9423
9526
  toggleShare();
9424
9527
  break;
9425
9528
  case "browser":
9426
9529
  openInBrowser();
9427
9530
  break;
9531
+ case "copy":
9532
+ copyLastMessage();
9533
+ break;
9428
9534
  case "connect":
9429
9535
  if (!promptForCards()) useStore.getState().showToast({ message: "no connect card waiting for action" });
9430
9536
  break;
@@ -9457,6 +9563,7 @@ function ChatScreen({ agent, conversation }) {
9457
9563
  openConversationRef,
9458
9564
  toggleShare,
9459
9565
  openInBrowser,
9566
+ copyLastMessage,
9460
9567
  quit,
9461
9568
  sendContent,
9462
9569
  goTo,
@@ -9467,7 +9574,9 @@ function ChatScreen({ agent, conversation }) {
9467
9574
  model,
9468
9575
  portal.status,
9469
9576
  portal.grantedAgentIds,
9470
- promptForCards
9577
+ promptForCards,
9578
+ todos,
9579
+ planHidden
9471
9580
  ]);
9472
9581
  const submit = useCallback(() => {
9473
9582
  const staged = attachments.filter((a) => a.status !== "error");
@@ -9659,6 +9768,10 @@ function ChatScreen({ agent, conversation }) {
9659
9768
  scrollRef.current?.scrollBy(pageScrollFraction, "viewport");
9660
9769
  return;
9661
9770
  }
9771
+ if (key.name === "end" && scrolledUp) {
9772
+ jumpToBottom();
9773
+ return;
9774
+ }
9662
9775
  if (key.name === "c" && key.ctrl) {
9663
9776
  if (runRef.current.kind === "streaming") {
9664
9777
  cancelRun();
@@ -9855,6 +9968,23 @@ function ChatScreen({ agent, conversation }) {
9855
9968
  }) : null]
9856
9969
  })
9857
9970
  }),
9971
+ jumpHintVisible ? /* @__PURE__ */ jsxs("box", {
9972
+ onMouseDown: jumpToBottom,
9973
+ style: {
9974
+ flexDirection: "row",
9975
+ paddingLeft: 1,
9976
+ paddingRight: 1,
9977
+ height: 1,
9978
+ flexShrink: 0
9979
+ },
9980
+ children: [/* @__PURE__ */ jsx("text", {
9981
+ fg: theme.accent,
9982
+ children: "↓ jump to latest"
9983
+ }), /* @__PURE__ */ jsxs("text", {
9984
+ fg: theme.dim,
9985
+ children: [" ", "end"]
9986
+ })]
9987
+ }) : null,
9858
9988
  todoCardVisible && todos ? /* @__PURE__ */ jsx(TodoCardView, {
9859
9989
  todos,
9860
9990
  isActive: run.kind !== "idle"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.2.0-beta.577",
3
+ "version": "0.2.0-beta.587",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",