skydive-cli 0.2.0-beta.530 → 0.2.0-beta.576
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
|
@@ -5,6 +5,12 @@ All notable changes to the Skydive CLI are documented here.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
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
|
+
|
|
8
14
|
## [0.2.0] - 2026-07-29
|
|
9
15
|
|
|
10
16
|
### Added
|
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.
|
|
23
|
+
var version = "0.2.0-beta.576";
|
|
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-
|
|
1203
|
+
const { runChat } = await import("./boot-DodmZA55.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-
|
|
1514
|
+
const { runChat } = await import("./boot-DodmZA55.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-
|
|
1813
|
+
const { runWorkspacePicker } = await import("./boot-DodmZA55.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## [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\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
|
|
@@ -17,7 +17,7 @@ import { createHash } from "node:crypto";
|
|
|
17
17
|
import { constants } from "node:fs";
|
|
18
18
|
import { MarkdownRenderable, RenderableEvents, SyntaxStyle, createCliRenderer, decodePasteBytes, detectLinks } from "@opentui/core";
|
|
19
19
|
import { createRoot, extend, useKeyboard, usePaste, useRenderer, useTerminalDimensions } from "@opentui/react";
|
|
20
|
-
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
20
|
+
import { createContext, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
21
21
|
import { create } from "zustand";
|
|
22
22
|
import { createConnection } from "node:net";
|
|
23
23
|
import { access, appendFile, mkdir, mkdtemp, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
@@ -272,6 +272,7 @@ function usePortal({ appUrl, sessionToken, shareMachine }) {
|
|
|
272
272
|
const noopAgentHost = {
|
|
273
273
|
transition() {},
|
|
274
274
|
setSession() {},
|
|
275
|
+
setTitle() {},
|
|
275
276
|
dispose() {}
|
|
276
277
|
};
|
|
277
278
|
const AgentHostContext = createContext(noopAgentHost);
|
|
@@ -293,6 +294,10 @@ function composeAgentHost(adapters) {
|
|
|
293
294
|
if (disposed) return;
|
|
294
295
|
for (const adapter of adapters) adapter.setSession?.(sessionId);
|
|
295
296
|
},
|
|
297
|
+
setTitle(title) {
|
|
298
|
+
if (disposed) return;
|
|
299
|
+
for (const adapter of adapters) adapter.setTitle?.(title);
|
|
300
|
+
},
|
|
296
301
|
dispose() {
|
|
297
302
|
if (disposed) return;
|
|
298
303
|
disposed = true;
|
|
@@ -354,6 +359,54 @@ function createTerminalNotificationsAdapter({ renderer, enabled }) {
|
|
|
354
359
|
};
|
|
355
360
|
}
|
|
356
361
|
|
|
362
|
+
//#endregion
|
|
363
|
+
//#region src/chat/agent-host/adapters/terminal-title.ts
|
|
364
|
+
/**
|
|
365
|
+
* The title shown when there is no conversation title yet, or after the chat
|
|
366
|
+
* closes. Keeps the tab identifiable as this CLI rather than leaving a stale
|
|
367
|
+
* conversation name behind.
|
|
368
|
+
*/
|
|
369
|
+
const DEFAULT_TERMINAL_TITLE = "skydive";
|
|
370
|
+
/**
|
|
371
|
+
* Collapses a conversation title to a single terminal-title line. Titles arrive
|
|
372
|
+
* from autoname / web / Slack and can carry newlines, tabs, or other control
|
|
373
|
+
* characters (the same reason picker rows sanitize them); a raw newline in an
|
|
374
|
+
* OSC title terminates the sequence early and corrupts the tab, so flatten
|
|
375
|
+
* whitespace and drop C0 controls. Empty or whitespace-only input falls back to
|
|
376
|
+
* the default so the tab is never blanked.
|
|
377
|
+
*/
|
|
378
|
+
function terminalTitleFor(title) {
|
|
379
|
+
if (!title) return DEFAULT_TERMINAL_TITLE;
|
|
380
|
+
return title.replace(/[\u0000-\u001f\u007f]+/g, " ").trim() || DEFAULT_TERMINAL_TITLE;
|
|
381
|
+
}
|
|
382
|
+
/**
|
|
383
|
+
* Mirrors the live conversation title onto the terminal title (OSC), so a
|
|
384
|
+
* multiplexer tab or window carries the conversation name instead of a bare
|
|
385
|
+
* shell/binary label. OpenTUI owns the escape transport via
|
|
386
|
+
* `setTerminalTitle`, so this never interleaves with the rendered frame.
|
|
387
|
+
*
|
|
388
|
+
* Writes are deduped, and the title is reset to a neutral default on dispose so
|
|
389
|
+
* leaving the chat doesn't strand the last conversation's name on the tab.
|
|
390
|
+
*/
|
|
391
|
+
function createTerminalTitleAdapter({ renderer }) {
|
|
392
|
+
let last;
|
|
393
|
+
const apply = (next) => {
|
|
394
|
+
if (next === last) return;
|
|
395
|
+
last = next;
|
|
396
|
+
try {
|
|
397
|
+
renderer.setTerminalTitle(next);
|
|
398
|
+
} catch (_error) {}
|
|
399
|
+
};
|
|
400
|
+
return {
|
|
401
|
+
setTitle(title) {
|
|
402
|
+
apply(terminalTitleFor(title));
|
|
403
|
+
},
|
|
404
|
+
dispose() {
|
|
405
|
+
apply(DEFAULT_TERMINAL_TITLE);
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
}
|
|
409
|
+
|
|
357
410
|
//#endregion
|
|
358
411
|
//#region src/chat/agent-host/adapters/herdr/adapter.ts
|
|
359
412
|
/** Maps one complete semantic transition to one herdr state report. */
|
|
@@ -523,7 +576,7 @@ function createAgentHost({ renderer, notifications }) {
|
|
|
523
576
|
const adapters = [createTerminalNotificationsAdapter({
|
|
524
577
|
renderer,
|
|
525
578
|
enabled: notifications
|
|
526
|
-
})];
|
|
579
|
+
}), createTerminalTitleAdapter({ renderer })];
|
|
527
580
|
const herdr = createHerdrReporter();
|
|
528
581
|
if (herdr) adapters.push(createHerdrAdapter(herdr));
|
|
529
582
|
return composeAgentHost(adapters);
|
|
@@ -8427,6 +8480,7 @@ function ChatScreen({ agent, conversation }) {
|
|
|
8427
8480
|
agentHost
|
|
8428
8481
|
]);
|
|
8429
8482
|
const multiAgent = useMemo(() => conversationAgentNames(items, agent.name), [items, agent.name]).size > 1;
|
|
8483
|
+
const themeGeneration = themeVersion();
|
|
8430
8484
|
useEffect(() => {
|
|
8431
8485
|
if (!rest || !conversationId) return;
|
|
8432
8486
|
const abort = new AbortController();
|
|
@@ -8440,7 +8494,10 @@ function ChatScreen({ agent, conversation }) {
|
|
|
8440
8494
|
attachToRun(event.runId, null, parseStampMs(event.createdAt));
|
|
8441
8495
|
return;
|
|
8442
8496
|
}
|
|
8443
|
-
if (event.title)
|
|
8497
|
+
if (event.title) {
|
|
8498
|
+
setChatTitle(event.title);
|
|
8499
|
+
agentHost.setTitle(event.title);
|
|
8500
|
+
}
|
|
8444
8501
|
}
|
|
8445
8502
|
}).catch(() => {});
|
|
8446
8503
|
return () => abort.abort();
|
|
@@ -8448,7 +8505,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
8448
8505
|
rest,
|
|
8449
8506
|
conversationId,
|
|
8450
8507
|
setChatTitle,
|
|
8451
|
-
attachToRun
|
|
8508
|
+
attachToRun,
|
|
8509
|
+
agentHost
|
|
8452
8510
|
]);
|
|
8453
8511
|
const sendContent = useCallback(async (content, staged, opts) => {
|
|
8454
8512
|
if (!rest) return;
|
|
@@ -9643,22 +9701,11 @@ function ChatScreen({ agent, conversation }) {
|
|
|
9643
9701
|
agent.name,
|
|
9644
9702
|
" below."
|
|
9645
9703
|
]
|
|
9646
|
-
}) :
|
|
9647
|
-
|
|
9648
|
-
|
|
9649
|
-
|
|
9650
|
-
|
|
9651
|
-
style: {
|
|
9652
|
-
paddingLeft: 3,
|
|
9653
|
-
paddingRight: 1
|
|
9654
|
-
},
|
|
9655
|
-
children: /* @__PURE__ */ jsx("text", {
|
|
9656
|
-
fg: theme.muted,
|
|
9657
|
-
children: /* @__PURE__ */ jsx("b", { children: label })
|
|
9658
|
-
})
|
|
9659
|
-
}) : null, /* @__PURE__ */ jsx(RenderItem, { item })]
|
|
9660
|
-
}, item.id);
|
|
9661
|
-
}) }), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, {
|
|
9704
|
+
}) : items.map((item, i) => /* @__PURE__ */ jsx(TranscriptRow, {
|
|
9705
|
+
item,
|
|
9706
|
+
label: multiAgent ? agentLabelFor(items, i) : null,
|
|
9707
|
+
themeVersion: themeGeneration
|
|
9708
|
+
}, item.id)), run.kind !== "idle" ? /* @__PURE__ */ jsx(ActivityRow, {
|
|
9662
9709
|
label: run.kind === "sending" ? "sending" : "working",
|
|
9663
9710
|
startedAtMs: run.startedAtMs
|
|
9664
9711
|
}) : null]
|
|
@@ -9933,6 +9980,34 @@ function CredentialPromptBox({ prompt, height, onInput, onSubmit }) {
|
|
|
9933
9980
|
});
|
|
9934
9981
|
}
|
|
9935
9982
|
/**
|
|
9983
|
+
* One transcript row: the optional agent-attribution label plus the item's own
|
|
9984
|
+
* rendering. Memoized on the item because the reducer rebuilds the `items`
|
|
9985
|
+
* array on every stream chunk while leaving finished items' objects untouched.
|
|
9986
|
+
* Without the memo, one text delta re-rendered every row in the scrollback, and
|
|
9987
|
+
* each re-render pushes content through that row's native OpenTUI text buffers,
|
|
9988
|
+
* which never hand their memory back — so a long session grew O(turns^2)
|
|
9989
|
+
* resident memory (~1GB after 6k deltas in a soak; ~190MB with the memo).
|
|
9990
|
+
*
|
|
9991
|
+
* `themeVersion` is a prop rather than a read because rows resolve colors from
|
|
9992
|
+
* the mutable `theme` binding during render: a swap mutates that object in
|
|
9993
|
+
* place, so the memo needs the version bump to know its output is stale.
|
|
9994
|
+
*/
|
|
9995
|
+
const TranscriptRow = memo(function TranscriptRow({ item, label }) {
|
|
9996
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
9997
|
+
style: { flexDirection: "column" },
|
|
9998
|
+
children: [label ? /* @__PURE__ */ jsx("box", {
|
|
9999
|
+
style: {
|
|
10000
|
+
paddingLeft: 3,
|
|
10001
|
+
paddingRight: 1
|
|
10002
|
+
},
|
|
10003
|
+
children: /* @__PURE__ */ jsx("text", {
|
|
10004
|
+
fg: theme.muted,
|
|
10005
|
+
children: /* @__PURE__ */ jsx("b", { children: label })
|
|
10006
|
+
})
|
|
10007
|
+
}) : null, /* @__PURE__ */ jsx(RenderItem, { item })]
|
|
10008
|
+
});
|
|
10009
|
+
});
|
|
10010
|
+
/**
|
|
9936
10011
|
* A live indicator at the foot of the transcript while a run is in flight: an
|
|
9937
10012
|
* animated spinner plus a label and a seconds counter, so it's obvious the
|
|
9938
10013
|
* agent is still working even during long gaps between chunks. The counter
|