skydive-cli 0.5.0-beta.19 → 0.5.0-beta.20
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 +1 -0
- package/dist/js/bin.mjs +39 -32
- package/dist/js/{boot-C831RDoa.mjs → boot-DX50EPUe.mjs} +230 -13
- package/dist/js/{daemon-BX8OBLBs.mjs → daemon-BGPE3evT.mjs} +1 -1
- package/dist/js/{daemon-DWw9AjB4.mjs → daemon-WOEEiYWf.mjs} +1 -1
- package/dist/js/daemon-client-BL3EohF6.mjs +7 -0
- package/dist/js/{daemon-client-C_lgQr4E.mjs → daemon-client-CX4qctxA.mjs} +1 -1
- package/dist/js/{print-D8L3DAbD.mjs → print-C8DHKrJu.mjs} +2 -2
- package/dist/js/{print-CPRgbo2N.mjs → print-OvYyj6Uk.mjs} +1 -1
- package/dist/js/{profiler-DlxtXXGp.mjs → profiler-C44QO8Op.mjs} +27 -3
- package/dist/js/{rest-DsdPAE8K.mjs → rest-DW05dMMc.mjs} +1 -1
- package/dist/js/{rest-CLrHVkxh.mjs → rest-Dx6b-Nq_.mjs} +15 -1
- package/package.json +1 -1
- package/dist/js/daemon-client-CMsS0Ayk.mjs +0 -7
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- First run in a workspace with no agents now sets you up automatically: instead of dropping you on an empty agent picker, `skydive` creates your first agent for you (with a suggested name and avatar, mirroring the web onboarding flow) and then offers to have it learn how you already work — reading the other coding agents set up on your machine (Claude Code, Cursor, Codex, and more), the same import that `skydive import` runs, with a plan shown first and credentials never copied. Decline and you go straight into the new agent's chat. You can replay this flow anytime with `skydive --onboarding` (or `SKYDIVE_FORCE_ONBOARDING=1`), even in a workspace that already has agents.
|
|
12
13
|
- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU load (scaled to core count), memory, swap (when present), and disk usage, plus uptime and process count. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The card keeps itself current quietly (a slow background refresh, no flicker or "updating" chrome); only the most recent card refreshes, and it silently stops while you're scrolled up (no wasted polls on a card you're not looking at) and resumes at the bottom. Stats are read over the same relay `skydive sandbox` uses, so it's gated on EDIT access to the agent like the other sandbox commands.
|
|
13
14
|
- `skydive fs` — a namespace for working with the files on an agent's computer (its sandbox) from your own machine. Its first subcommand, `skydive fs edit <path>`, reads a remote file down over the same relay `skydive sandbox` uses, opens it in your local editor (`$VISUAL`/`$EDITOR`, override with `--editor`, defaults to `vi`), and writes your changes back atomically when you save and close — so you can edit a remote file as if it were local without opening the chat TUI. A path that doesn't exist yet is created on save; no changes means nothing is written. Access is gated on EDIT permission for the agent, exactly like `skydive sandbox`. (A first-class `fs` relay channel and `skydive fs mount` land in follow-ups.)
|
|
14
15
|
|
package/dist/js/bin.mjs
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { A as
|
|
3
|
-
import { A as getStoredApiKeyId, B as saveConfig, C as getDefaultAgent, E as getPromptHistoryPath, F as resolveChatAuth, I as resolveConfig, L as resolveManagementAuth, M as getUpdateCheckDisabled, P as resolveAppUrl, R as resolveSession, S as getConfigPath, T as getPreference, U as setLastSeenVersion, V as saveSession, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getStoredApiKeyWorkspaceName, k as getShareMachineDefault, v as DEFAULT_API_URL, w as getLastSeenVersion, x as deleteConfig } from "./print-
|
|
2
|
+
import { A as setActiveWorkspace, D as getActiveWorkspaceId, E as ensureActiveOrganization, M as version, O as getSessionIdentity, S as themes, T as machineOsFromPlatform, a as installCrashHandler, j as name, k as listWorkspaces, t as maybeStartProfiling, u as brandHelpArt, w as buildImportSeedPrompt } from "./profiler-C44QO8Op.mjs";
|
|
3
|
+
import { A as getStoredApiKeyId, B as saveConfig, C as getDefaultAgent, E as getPromptHistoryPath, F as resolveChatAuth, I as resolveConfig, L as resolveManagementAuth, M as getUpdateCheckDisabled, P as resolveAppUrl, R as resolveSession, S as getConfigPath, T as getPreference, U as setLastSeenVersion, V as saveSession, _ as API_KEY_PREFIX, b as PREFERENCES, g as API_KEY_FAMILY_PREFIX, h as API_KEYS_URL, i as resolveAgent, j as getStoredApiKeyWorkspaceName, k as getShareMachineDefault, v as DEFAULT_API_URL, w as getLastSeenVersion, x as deleteConfig } from "./print-OvYyj6Uk.mjs";
|
|
4
4
|
import { n as printError, r as printTable, t as output } from "./output-DYzzdXYV.mjs";
|
|
5
5
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
6
|
-
import { t as createRestClient } from "./rest-
|
|
6
|
+
import { t as createRestClient } from "./rest-Dx6b-Nq_.mjs";
|
|
7
7
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
8
8
|
import { r as resolveMachineIdentity } from "./client-DbqRBquD.mjs";
|
|
9
|
-
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-
|
|
9
|
+
import { i as queryDaemonStatus, l as PORTAL_DAEMON_FLAG, n as ensureDaemonRunning, s as stopDaemon, u as daemonPaths } from "./daemon-WOEEiYWf.mjs";
|
|
10
10
|
import { i as grantPortalAccess, n as fetchPortalDevices, o as registerPortalDevice, r as findThisDevice, s as revokePortalAccess } from "./api-DG5W6iwx.mjs";
|
|
11
11
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
12
12
|
import { hideBin } from "yargs/helpers";
|
|
@@ -1463,19 +1463,6 @@ function ensureBunAndReexec(onProgress) {
|
|
|
1463
1463
|
|
|
1464
1464
|
//#endregion
|
|
1465
1465
|
//#region src/commands/import.ts
|
|
1466
|
-
/**
|
|
1467
|
-
* The first message of the import conversation, sent as the user. It hands
|
|
1468
|
-
* the real work to the agent's `import-config` skill; this text only needs
|
|
1469
|
-
* to establish intent, anchor the starting directory, and set the two
|
|
1470
|
-
* expectations the user cares about most (plan first, no credentials).
|
|
1471
|
-
*/
|
|
1472
|
-
function buildImportSeedPrompt(projectDir) {
|
|
1473
|
-
return [
|
|
1474
|
-
"I'm migrating from another coding agent. Import my setup from this machine.",
|
|
1475
|
-
"",
|
|
1476
|
-
`Use your import-config skill. I ran this from \`${projectDir}\`, so start there and in my home directory. Don't assume one tool — do the discovery sweep so you catch whatever I actually use (Claude Code, Cursor, Codex, Gemini CLI, Copilot, Windsurf, Cline, OpenCode, Aider, and any nested AGENTS.md). Show me the plan first: everything you found, what you'll bring over, where it lands in you, and anything you're leaving out (credentials especially). Then wait for my OK before committing anything.`
|
|
1477
|
-
].join("\n");
|
|
1478
|
-
}
|
|
1479
1466
|
const importCommand = {
|
|
1480
1467
|
command: "import",
|
|
1481
1468
|
describe: "Import your existing coding-agent setup (Claude Code, Cursor, Codex, Gemini, Copilot, and more) into an agent (opens a machine-shared chat)",
|
|
@@ -1517,7 +1504,7 @@ const importCommand = {
|
|
|
1517
1504
|
process.exit(1);
|
|
1518
1505
|
}
|
|
1519
1506
|
}
|
|
1520
|
-
const { runChat } = await import("./boot-
|
|
1507
|
+
const { runChat } = await import("./boot-DX50EPUe.mjs");
|
|
1521
1508
|
await runChat({
|
|
1522
1509
|
appUrl,
|
|
1523
1510
|
sessionToken: session.value.sessionToken,
|
|
@@ -1528,7 +1515,8 @@ const importCommand = {
|
|
|
1528
1515
|
conversationId: null,
|
|
1529
1516
|
newConversation: false,
|
|
1530
1517
|
defaultAgentSelector: getDefaultAgent(),
|
|
1531
|
-
|
|
1518
|
+
forceOnboarding: false,
|
|
1519
|
+
seedPrompt: buildImportSeedPrompt(process.cwd(), machineOsFromPlatform(process.platform))
|
|
1532
1520
|
});
|
|
1533
1521
|
}
|
|
1534
1522
|
};
|
|
@@ -1552,8 +1540,8 @@ async function runImportPrintMode({ argv, appUrl }) {
|
|
|
1552
1540
|
timeoutHint: "Check `skydive portal status`, then re-run."
|
|
1553
1541
|
});
|
|
1554
1542
|
const extra = (argv.print ?? "").trim();
|
|
1555
|
-
const prompt = buildImportSeedPrompt(process.cwd()) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
|
|
1556
|
-
const { runPrint } = await import("./print-
|
|
1543
|
+
const prompt = buildImportSeedPrompt(process.cwd(), machineOsFromPlatform(process.platform)) + (extra ? `\n\nAdditional instructions: ${extra}` : "");
|
|
1544
|
+
const { runPrint } = await import("./print-C8DHKrJu.mjs");
|
|
1557
1545
|
try {
|
|
1558
1546
|
const result = await runPrint({
|
|
1559
1547
|
appUrl,
|
|
@@ -1879,6 +1867,10 @@ const chatCommand = {
|
|
|
1879
1867
|
type: "boolean",
|
|
1880
1868
|
default: false,
|
|
1881
1869
|
describe: "Start a fresh conversation instead of opening the conversation list. With --agent, opens a new conversation with that agent directly. Without --agent, opens a new conversation once you pick an agent (auto-picked if the account has only one). Ignored with --resume, which always continues the named conversation."
|
|
1870
|
+
}).option("onboarding", {
|
|
1871
|
+
type: "boolean",
|
|
1872
|
+
default: false,
|
|
1873
|
+
describe: "Force the first-run onboarding flow (auto-create a first agent, then offer the machine import) even when the workspace already has agents. Also settable via SKYDIVE_FORCE_ONBOARDING=1. Handy for replaying the new-user experience; ignored with -p, --resume, or --agent."
|
|
1882
1874
|
}).option("share-machine", {
|
|
1883
1875
|
type: "boolean",
|
|
1884
1876
|
describe: "Share this machine with the agent over the portal so it can run commands here (default-deny; in the TUI you approve per agent, with -p the flag grants the target agent for the run). Defaults to `shareMachineDefault` in the CLI config file; --no-share-machine disables for this invocation."
|
|
@@ -1930,7 +1922,7 @@ const chatCommand = {
|
|
|
1930
1922
|
sessionToken: auth.value.token,
|
|
1931
1923
|
agentSelector: argv.agent ?? null
|
|
1932
1924
|
});
|
|
1933
|
-
const { runChat } = await import("./boot-
|
|
1925
|
+
const { runChat } = await import("./boot-DX50EPUe.mjs");
|
|
1934
1926
|
await runChat({
|
|
1935
1927
|
appUrl: auth.value.appUrl,
|
|
1936
1928
|
sessionToken: auth.value.token,
|
|
@@ -1942,6 +1934,7 @@ const chatCommand = {
|
|
|
1942
1934
|
agentSelector: argv.agent ?? null,
|
|
1943
1935
|
conversationId: argv.conversation ?? null,
|
|
1944
1936
|
newConversation: resolveNewConversation(argv),
|
|
1937
|
+
forceOnboarding: resolveForceOnboarding(argv),
|
|
1945
1938
|
defaultAgentSelector: getDefaultAgent(),
|
|
1946
1939
|
seedPrompt: null
|
|
1947
1940
|
});
|
|
@@ -1964,6 +1957,20 @@ function resolveNewConversation(argv) {
|
|
|
1964
1957
|
return argv.new === true && !argv.conversation;
|
|
1965
1958
|
}
|
|
1966
1959
|
/**
|
|
1960
|
+
* Whether to force the first-run onboarding flow: the `--onboarding` flag or
|
|
1961
|
+
* `SKYDIVE_FORCE_ONBOARDING` (any non-empty, non-"0"/"false" value). Lets the
|
|
1962
|
+
* new-user experience be replayed in a workspace that already has agents. The
|
|
1963
|
+
* TUI applies it only to a bare interactive launch — an explicit
|
|
1964
|
+
* `--agent`/`--resume`/`--new` still takes precedence.
|
|
1965
|
+
*/
|
|
1966
|
+
function resolveForceOnboarding(argv, env = process.env) {
|
|
1967
|
+
if (argv.onboarding === true) return true;
|
|
1968
|
+
const raw = env["SKYDIVE_FORCE_ONBOARDING"];
|
|
1969
|
+
if (raw === void 0) return false;
|
|
1970
|
+
const value = raw.trim().toLowerCase();
|
|
1971
|
+
return value !== "" && value !== "0" && value !== "false";
|
|
1972
|
+
}
|
|
1973
|
+
/**
|
|
1967
1974
|
* Cross-workspace `--agent <uuid>` handling shared by the TUI and -p paths:
|
|
1968
1975
|
* on a hit in another workspace, switch to it and say so on stderr (stdout
|
|
1969
1976
|
* may carry the -p reply/JSON envelope); when the id matches no workspace on
|
|
@@ -1998,7 +2005,7 @@ async function runPrintMode({ argv, appUrl }) {
|
|
|
1998
2005
|
printError(`${auth.error.message} For non-interactive use, run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
|
|
1999
2006
|
process.exit(1);
|
|
2000
2007
|
}
|
|
2001
|
-
const { runPrint, readStdin } = await import("./print-
|
|
2008
|
+
const { runPrint, readStdin } = await import("./print-C8DHKrJu.mjs");
|
|
2002
2009
|
await ensureAgentWorkspace({
|
|
2003
2010
|
appUrl,
|
|
2004
2011
|
sessionToken: auth.value.token,
|
|
@@ -2087,7 +2094,7 @@ const getCommand$1 = {
|
|
|
2087
2094
|
printError(`${auth.error.message} Run \`skydive auth login\` first, or set SKYDIVE_API_KEY / SKYDIVE_SESSION_TOKEN.`);
|
|
2088
2095
|
process.exit(1);
|
|
2089
2096
|
}
|
|
2090
|
-
const { messageGet } = await import("./print-
|
|
2097
|
+
const { messageGet } = await import("./print-C8DHKrJu.mjs");
|
|
2091
2098
|
try {
|
|
2092
2099
|
const result = await messageGet({
|
|
2093
2100
|
appUrl: auth.value.appUrl,
|
|
@@ -2290,7 +2297,7 @@ const switchCommand = {
|
|
|
2290
2297
|
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.");
|
|
2291
2298
|
process.exit(1);
|
|
2292
2299
|
}
|
|
2293
|
-
const { runWorkspacePicker } = await import("./boot-
|
|
2300
|
+
const { runWorkspacePicker } = await import("./boot-DX50EPUe.mjs");
|
|
2294
2301
|
await runWorkspacePicker(session);
|
|
2295
2302
|
return;
|
|
2296
2303
|
}
|
|
@@ -2369,7 +2376,7 @@ const openCommand = {
|
|
|
2369
2376
|
const agent = argv.agent ? resolveAgent((await fetchPortalDevices(session)).agents, argv.agent) : null;
|
|
2370
2377
|
const cwd = argv.cwd ? path.resolve(argv.cwd) : process.cwd();
|
|
2371
2378
|
const { machineName } = await resolveMachineIdentity(null);
|
|
2372
|
-
const { PortalDaemonClient } = await import("./daemon-client-
|
|
2379
|
+
const { PortalDaemonClient } = await import("./daemon-client-BL3EohF6.mjs");
|
|
2373
2380
|
let lastLine = "";
|
|
2374
2381
|
let signalConnected;
|
|
2375
2382
|
const connected = new Promise((resolve) => {
|
|
@@ -2679,8 +2686,8 @@ const sandboxCommand = {
|
|
|
2679
2686
|
}).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`"),
|
|
2680
2687
|
handler: async (argv) => {
|
|
2681
2688
|
const session = requireSession(argv);
|
|
2682
|
-
const { createRestClient } = await import("./rest-
|
|
2683
|
-
const { resolveAgent } = await import("./print-
|
|
2689
|
+
const { createRestClient } = await import("./rest-DW05dMMc.mjs");
|
|
2690
|
+
const { resolveAgent } = await import("./print-C8DHKrJu.mjs");
|
|
2684
2691
|
const client = createRestClient({
|
|
2685
2692
|
appUrl: session.appUrl,
|
|
2686
2693
|
sessionToken: session.sessionToken
|
|
@@ -2837,8 +2844,8 @@ const fsEditCommand = {
|
|
|
2837
2844
|
handler: async (argv) => {
|
|
2838
2845
|
const session = requireSession(argv);
|
|
2839
2846
|
const remotePath = argv.path;
|
|
2840
|
-
const { createRestClient } = await import("./rest-
|
|
2841
|
-
const { resolveAgent } = await import("./print-
|
|
2847
|
+
const { createRestClient } = await import("./rest-DW05dMMc.mjs");
|
|
2848
|
+
const { resolveAgent } = await import("./print-C8DHKrJu.mjs");
|
|
2842
2849
|
const client = createRestClient({
|
|
2843
2850
|
appUrl: session.appUrl,
|
|
2844
2851
|
sessionToken: session.sessionToken
|
|
@@ -4072,7 +4079,7 @@ function setupUpdateCheck(argv) {
|
|
|
4072
4079
|
|
|
4073
4080
|
//#endregion
|
|
4074
4081
|
//#region src/changelog.generated.ts
|
|
4075
|
-
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- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU load (scaled to core count), memory, swap (when present), and disk usage, plus uptime and process count. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The card keeps itself current quietly (a slow background refresh, no flicker or \"updating\" chrome); only the most recent card refreshes, and it silently stops while you're scrolled up (no wasted polls on a card you're not looking at) and resumes at the bottom. Stats are read over the same relay `skydive sandbox` uses, so it's gated on EDIT access to the agent like the other sandbox commands.\n- `skydive fs` — a namespace for working with the files on an agent's computer (its sandbox) from your own machine. Its first subcommand, `skydive fs edit <path>`, reads a remote file down over the same relay `skydive sandbox` uses, opens it in your local editor (`$VISUAL`/`$EDITOR`, override with `--editor`, defaults to `vi`), and writes your changes back atomically when you save and close — so you can edit a remote file as if it were local without opening the chat TUI. A path that doesn't exist yet is created on save; no changes means nothing is written. Access is gated on EDIT permission for the agent, exactly like `skydive sandbox`. (A first-class `fs` relay channel and `skydive fs mount` land in follow-ups.)\n\n## [0.4.0] - 2026-08-07\n\n### Added\n\n- `skydive chat` works with API key auth: an API key alone now opens the chat TUI (and headless `chat -p`) without a browser login, using the key's pinned workspace.\n- `skydive config` command to view and set the safe, hand-editable CLI preferences (`shareMachineDefault`, `updateCheck`) without opening `config.json`. `config`/`config list` shows each key, its effective value, and whether it comes from the file or the built-in default; `config get`/`set`/`unset` read and change one key; `config path` prints the config file location. It only ever touches those preference keys, never credentials or flow-managed state.\n- Billing-blocked sends and runs now surface the server-authored recovery\n guidance from the typed billing outcome. The chat TUI renders it as a\n warning-toned system-notice row — live when a send is refused or a run is\n stopped at the billing boundary, and again when reopening a conversation\n that holds a persisted billing notice — instead of a generic error.\n Headless `chat -p` and `messages get` print the guidance (state, recovery\n step, and manage-billing URL) on stderr, keep any partial output already\n streamed on stdout, and exit with code `5` (distinct from the generic `1`)\n so scripted callers can tell a billing pause from a failure.\n- Syntax highlighting for 14 more languages in the chat transcript and file/review panes (python, bash, go, rust, c, cpp, java, ruby, css, html, json, yaml, and more) — previously only JavaScript/TypeScript rendered colored.\n\n### Changed\n\n- `skydive agent list` lists the full agent roster by default instead of a truncated page.\n\n### Fixed\n\n- Long multi-line bash tool-call commands (e.g. heredocs) are clamped with a `+N more · click to expand` tail instead of dumping their whole body into the scrollback.\n- Tool error output is clipped the same way as ok output, so a failing command no longer floods the transcript.\n- The TUI toast overlay has an opaque background instead of letting the transcript bleed through.\n\n## [0.3.0] - 2026-08-04\n\n### Added\n\n**Chat TUI**\n\n- `/fork` slash command to fork a conversation at any point and continue in a new thread.\n- `/conversation` switches threads from inside a live session.\n- `/compact` triggers manual conversation compaction.\n- `/portal` and `/copy` slash commands.\n- Archive the current conversation from the chat TUI.\n- `--new` flag on `skydive chat` to start a fresh conversation.\n- Debounced server-side conversation search in the picker, with title matches ranked first.\n- File review pane orientation toggle, and review comments in the file pane.\n- Pinned plan card controls and a jump-to-latest hint.\n- Terminal title (OSC) follows the live conversation title on every screen; inside cmux the workspace is renamed too, and linked PRs are pushed to the cmux sidebar.\n- Every participating agent shows in the conversation list; connect cards each have their own identity, and ctrl+r asks which connect card to act on.\n- Colored Skydive splash art in `skydive --help` and the TUI pickers.\n- New-agent name prefilled from the server's suggestion.\n\n**CLI & platform**\n\n- `skydive update` self-updater, with an update notice when a new version is available.\n- Shell completions for bash, zsh, and fish via `completion install`.\n- Portal daemon: one connection per host with per-conversation working directories.\n- Agent-led coding-agent import (`skydive import`).\n- Per-workspace scoping on the REST client, and cross-org `--agent <uuid>` auto-switches workspace.\n- `skydive-beta` wrapper for the canary channel.\n\n### Fixed\n\n- `skydive update` no longer claims an update is available on every npm install.\n- Chat memory is bounded (payload clamps + windowed rows) with a memory watchdog and OOM diagnostic report; transcript rows are memoized so streaming stops re-rendering the whole chat.\n- Chat-send failures map to real error messages.\n- Notification and sidebar previews use the last text block of the reply and strip inline markdown.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n- Sending to non-web channel conversations is blocked in the TUI.\n- GitHub bot connect opens on the web front door, not api.skydive.com.\n- TUI activity counter times from the run, not the screen.\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";
|
|
4082
|
+
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- First run in a workspace with no agents now sets you up automatically: instead of dropping you on an empty agent picker, `skydive` creates your first agent for you (with a suggested name and avatar, mirroring the web onboarding flow) and then offers to have it learn how you already work — reading the other coding agents set up on your machine (Claude Code, Cursor, Codex, and more), the same import that `skydive import` runs, with a plan shown first and credentials never copied. Decline and you go straight into the new agent's chat. You can replay this flow anytime with `skydive --onboarding` (or `SKYDIVE_FORCE_ONBOARDING=1`), even in a workspace that already has agents.\n- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU load (scaled to core count), memory, swap (when present), and disk usage, plus uptime and process count. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The card keeps itself current quietly (a slow background refresh, no flicker or \"updating\" chrome); only the most recent card refreshes, and it silently stops while you're scrolled up (no wasted polls on a card you're not looking at) and resumes at the bottom. Stats are read over the same relay `skydive sandbox` uses, so it's gated on EDIT access to the agent like the other sandbox commands.\n- `skydive fs` — a namespace for working with the files on an agent's computer (its sandbox) from your own machine. Its first subcommand, `skydive fs edit <path>`, reads a remote file down over the same relay `skydive sandbox` uses, opens it in your local editor (`$VISUAL`/`$EDITOR`, override with `--editor`, defaults to `vi`), and writes your changes back atomically when you save and close — so you can edit a remote file as if it were local without opening the chat TUI. A path that doesn't exist yet is created on save; no changes means nothing is written. Access is gated on EDIT permission for the agent, exactly like `skydive sandbox`. (A first-class `fs` relay channel and `skydive fs mount` land in follow-ups.)\n\n## [0.4.0] - 2026-08-07\n\n### Added\n\n- `skydive chat` works with API key auth: an API key alone now opens the chat TUI (and headless `chat -p`) without a browser login, using the key's pinned workspace.\n- `skydive config` command to view and set the safe, hand-editable CLI preferences (`shareMachineDefault`, `updateCheck`) without opening `config.json`. `config`/`config list` shows each key, its effective value, and whether it comes from the file or the built-in default; `config get`/`set`/`unset` read and change one key; `config path` prints the config file location. It only ever touches those preference keys, never credentials or flow-managed state.\n- Billing-blocked sends and runs now surface the server-authored recovery\n guidance from the typed billing outcome. The chat TUI renders it as a\n warning-toned system-notice row — live when a send is refused or a run is\n stopped at the billing boundary, and again when reopening a conversation\n that holds a persisted billing notice — instead of a generic error.\n Headless `chat -p` and `messages get` print the guidance (state, recovery\n step, and manage-billing URL) on stderr, keep any partial output already\n streamed on stdout, and exit with code `5` (distinct from the generic `1`)\n so scripted callers can tell a billing pause from a failure.\n- Syntax highlighting for 14 more languages in the chat transcript and file/review panes (python, bash, go, rust, c, cpp, java, ruby, css, html, json, yaml, and more) — previously only JavaScript/TypeScript rendered colored.\n\n### Changed\n\n- `skydive agent list` lists the full agent roster by default instead of a truncated page.\n\n### Fixed\n\n- Long multi-line bash tool-call commands (e.g. heredocs) are clamped with a `+N more · click to expand` tail instead of dumping their whole body into the scrollback.\n- Tool error output is clipped the same way as ok output, so a failing command no longer floods the transcript.\n- The TUI toast overlay has an opaque background instead of letting the transcript bleed through.\n\n## [0.3.0] - 2026-08-04\n\n### Added\n\n**Chat TUI**\n\n- `/fork` slash command to fork a conversation at any point and continue in a new thread.\n- `/conversation` switches threads from inside a live session.\n- `/compact` triggers manual conversation compaction.\n- `/portal` and `/copy` slash commands.\n- Archive the current conversation from the chat TUI.\n- `--new` flag on `skydive chat` to start a fresh conversation.\n- Debounced server-side conversation search in the picker, with title matches ranked first.\n- File review pane orientation toggle, and review comments in the file pane.\n- Pinned plan card controls and a jump-to-latest hint.\n- Terminal title (OSC) follows the live conversation title on every screen; inside cmux the workspace is renamed too, and linked PRs are pushed to the cmux sidebar.\n- Every participating agent shows in the conversation list; connect cards each have their own identity, and ctrl+r asks which connect card to act on.\n- Colored Skydive splash art in `skydive --help` and the TUI pickers.\n- New-agent name prefilled from the server's suggestion.\n\n**CLI & platform**\n\n- `skydive update` self-updater, with an update notice when a new version is available.\n- Shell completions for bash, zsh, and fish via `completion install`.\n- Portal daemon: one connection per host with per-conversation working directories.\n- Agent-led coding-agent import (`skydive import`).\n- Per-workspace scoping on the REST client, and cross-org `--agent <uuid>` auto-switches workspace.\n- `skydive-beta` wrapper for the canary channel.\n\n### Fixed\n\n- `skydive update` no longer claims an update is available on every npm install.\n- Chat memory is bounded (payload clamps + windowed rows) with a memory watchdog and OOM diagnostic report; transcript rows are memoized so streaming stops re-rendering the whole chat.\n- Chat-send failures map to real error messages.\n- Notification and sidebar previews use the last text block of the reply and strip inline markdown.\n- Picker rows no longer overlap when a conversation title contains a newline, tab, or control character.\n- Sending to non-web channel conversations is blocked in the TUI.\n- GitHub bot connect opens on the web front door, not api.skydive.com.\n- TUI activity counter times from the run, not the screen.\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";
|
|
4076
4083
|
|
|
4077
4084
|
//#endregion
|
|
4078
4085
|
//#region src/whats-new.ts
|
|
@@ -4215,7 +4222,7 @@ if (process.argv.includes(UPDATE_WORKER_FLAG)) {
|
|
|
4215
4222
|
process.exit(0);
|
|
4216
4223
|
}
|
|
4217
4224
|
if (process.argv.includes(PORTAL_DAEMON_FLAG)) {
|
|
4218
|
-
const { runPortalDaemon } = await import("./daemon-
|
|
4225
|
+
const { runPortalDaemon } = await import("./daemon-BGPE3evT.mjs");
|
|
4219
4226
|
runPortalDaemon(process.argv);
|
|
4220
4227
|
} else runCli();
|
|
4221
4228
|
function runCli() {
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import { D as getReviewStateDir, H as saveTheme, N as recordDefaultAgent, O as getSavedTheme, S as getConfigPath, 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, v as DEFAULT_API_URL, y as DEFAULT_APP_URL, z as resolveWebUrl } from "./print-
|
|
2
|
+
import { A as setActiveWorkspace, C as themesForMode, D as getActiveWorkspaceId, T as machineOsFromPlatform, _ as theme, a as installCrashHandler, b as themeModeFromColorFgBg, c as MARK_CELLS, d as splashFitsWidth, f as DEFAULT_THEME_ID, g as noColorRequested, h as monoTheme, i as writeArtifact, k as listWorkspaces, l as WORDMARK, m as findTheme, n as profilingEnabled, o as buildCrashReport, p as applyTheme, r as record, s as writeCrashReport, v as themeForMode, x as themeVersion, y as themeMode } from "./profiler-C44QO8Op.mjs";
|
|
3
|
+
import { D as getReviewStateDir, H as saveTheme, N as recordDefaultAgent, O as getSavedTheme, S as getConfigPath, 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, v as DEFAULT_API_URL, y as DEFAULT_APP_URL, z as resolveWebUrl } from "./print-OvYyj6Uk.mjs";
|
|
4
4
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
5
|
-
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
5
|
+
import { a as isRecord, i as errorMessage, n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-Dx6b-Nq_.mjs";
|
|
6
6
|
import { i as billingBlockedOutcomeFromSendResponse } from "./billing-blocked-2wju4gC_.mjs";
|
|
7
7
|
import { t as PortalClient } from "./client-DbqRBquD.mjs";
|
|
8
|
-
import "./daemon-
|
|
8
|
+
import "./daemon-WOEEiYWf.mjs";
|
|
9
9
|
import "./api-DG5W6iwx.mjs";
|
|
10
10
|
import { t as SandboxStream } from "./client-c4c5MmgN.mjs";
|
|
11
|
-
import { t as PortalDaemonClient } from "./daemon-client-
|
|
11
|
+
import { t as PortalDaemonClient } from "./daemon-client-CX4qctxA.mjs";
|
|
12
12
|
import { t as runRawPtyPassthrough } from "./raw-pty-DY4KelZW.mjs";
|
|
13
13
|
import * as os$1 from "node:os";
|
|
14
14
|
import { homedir, platform, release, tmpdir } from "node:os";
|
|
@@ -137,6 +137,7 @@ const useStore = create((set, get) => ({
|
|
|
137
137
|
seedPrompt: null,
|
|
138
138
|
autoGrantMachine: false,
|
|
139
139
|
newConversationIntent: false,
|
|
140
|
+
forcedOnboarding: false,
|
|
140
141
|
earlyInput: "",
|
|
141
142
|
goTo: (screen) => set({
|
|
142
143
|
screen,
|
|
@@ -254,7 +255,7 @@ const useStore = create((set, get) => ({
|
|
|
254
255
|
* org roster. A name/slug selector is the only case that can be ambiguous and
|
|
255
256
|
* therefore still needs the full candidate list to disambiguate. */
|
|
256
257
|
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
257
|
-
async function resolveInitialScreen({ rest, agentSelector, conversationId, newConversation, defaultAgentSelector }) {
|
|
258
|
+
async function resolveInitialScreen({ rest, agentSelector, conversationId, newConversation, defaultAgentSelector, forceOnboarding }) {
|
|
258
259
|
if (conversationId) {
|
|
259
260
|
const conversation = await rest.getConversation({ conversationId }).catch((err) => {
|
|
260
261
|
if (err instanceof HttpError && err.status === 404) throw new Error(`No conversation with id ${conversationId}. It may have been deleted, or belong to another workspace.`);
|
|
@@ -284,7 +285,14 @@ async function resolveInitialScreen({ rest, agentSelector, conversationId, newCo
|
|
|
284
285
|
}
|
|
285
286
|
};
|
|
286
287
|
}
|
|
287
|
-
if (
|
|
288
|
+
if (forceOnboarding && !agentSelector) return { kind: "first-run" };
|
|
289
|
+
if (!agentSelector && !newConversation && !defaultAgentSelector) {
|
|
290
|
+
if ((await rest.listAgents({
|
|
291
|
+
scope: "org",
|
|
292
|
+
onPage: null
|
|
293
|
+
})).length === 0) return { kind: "first-run" };
|
|
294
|
+
return { kind: "agent-picker" };
|
|
295
|
+
}
|
|
288
296
|
if (!agentSelector && defaultAgentSelector) {
|
|
289
297
|
const agent = UUID_RE.test(defaultAgentSelector) ? await rest.getAgent({ agentId: defaultAgentSelector }).catch((err) => {
|
|
290
298
|
if (err instanceof HttpError && (err.status === 404 || err.status === 403)) return null;
|
|
@@ -1640,6 +1648,7 @@ function screenLabelParts(screen, chatTitle) {
|
|
|
1640
1648
|
case "error": return { title: "" };
|
|
1641
1649
|
case "agent-picker": return { title: "pick an agent" };
|
|
1642
1650
|
case "agent-create": return { title: "create an agent" };
|
|
1651
|
+
case "first-run": return { title: "welcome" };
|
|
1643
1652
|
case "workspace-picker": return { title: "switch workspace" };
|
|
1644
1653
|
case "conversation-picker": return {
|
|
1645
1654
|
title: "pick a conversation",
|
|
@@ -2249,6 +2258,208 @@ function AgentCreateScreen() {
|
|
|
2249
2258
|
});
|
|
2250
2259
|
}
|
|
2251
2260
|
|
|
2261
|
+
//#endregion
|
|
2262
|
+
//#region src/chat/tui/screens/first-run.tsx
|
|
2263
|
+
/** Rows the mark lockup needs before it's worth painting (matches the
|
|
2264
|
+
* new-chat splash): the 6-row mark, a spacer, and a couple of content rows.
|
|
2265
|
+
* Below this the mark is dropped and only the message renders. */
|
|
2266
|
+
const MARK_MIN_ROWS$1 = 12;
|
|
2267
|
+
/** Columns the full mark + wordmark lockup spans; below this the pinwheel
|
|
2268
|
+
* mark renders alone instead of wrapping the wordmark. */
|
|
2269
|
+
const FULL_MARK_MIN_COLS$1 = 70;
|
|
2270
|
+
function FirstRunScreen() {
|
|
2271
|
+
const rest = useStore((s) => s.rest);
|
|
2272
|
+
const goTo = useStore((s) => s.goTo);
|
|
2273
|
+
const fail = useStore((s) => s.fail);
|
|
2274
|
+
const authKind = useStore((s) => s.authKind);
|
|
2275
|
+
const forced = useStore((s) => s.forcedOnboarding);
|
|
2276
|
+
const [state, setState] = useState({ kind: "creating" });
|
|
2277
|
+
const canShareMachine = authKind === "session";
|
|
2278
|
+
useEffect(() => {
|
|
2279
|
+
if (!rest) return;
|
|
2280
|
+
let cancelled = false;
|
|
2281
|
+
(async () => {
|
|
2282
|
+
try {
|
|
2283
|
+
const agent = await rest.createAgent({});
|
|
2284
|
+
if (cancelled) return;
|
|
2285
|
+
setState({
|
|
2286
|
+
kind: "offer",
|
|
2287
|
+
agent
|
|
2288
|
+
});
|
|
2289
|
+
} catch (err) {
|
|
2290
|
+
if (cancelled) return;
|
|
2291
|
+
if (err instanceof HttpError && err.status === 401) {
|
|
2292
|
+
fail("Session expired. Run `skydive auth login --web`, then retry.");
|
|
2293
|
+
return;
|
|
2294
|
+
}
|
|
2295
|
+
setState({
|
|
2296
|
+
kind: "error",
|
|
2297
|
+
message: createErrorMessage(err)
|
|
2298
|
+
});
|
|
2299
|
+
}
|
|
2300
|
+
})();
|
|
2301
|
+
return () => {
|
|
2302
|
+
cancelled = true;
|
|
2303
|
+
};
|
|
2304
|
+
}, [rest, fail]);
|
|
2305
|
+
const openNewChat = (agent) => {
|
|
2306
|
+
goTo({
|
|
2307
|
+
kind: "chat",
|
|
2308
|
+
agent,
|
|
2309
|
+
conversation: {
|
|
2310
|
+
kind: "new",
|
|
2311
|
+
agentId: agent.id,
|
|
2312
|
+
nonce: crypto.randomUUID()
|
|
2313
|
+
}
|
|
2314
|
+
});
|
|
2315
|
+
};
|
|
2316
|
+
const openOnboardingChat = (agent, conversationId, title) => {
|
|
2317
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2318
|
+
goTo({
|
|
2319
|
+
kind: "chat",
|
|
2320
|
+
agent,
|
|
2321
|
+
conversation: {
|
|
2322
|
+
id: conversationId,
|
|
2323
|
+
title,
|
|
2324
|
+
createdAt: now,
|
|
2325
|
+
updatedAt: now,
|
|
2326
|
+
preview: null,
|
|
2327
|
+
channel: "web",
|
|
2328
|
+
channelLabel: null,
|
|
2329
|
+
agent: {
|
|
2330
|
+
id: agent.id,
|
|
2331
|
+
name: agent.name,
|
|
2332
|
+
slug: agent.slug ?? null,
|
|
2333
|
+
title: agent.title ?? null
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
});
|
|
2337
|
+
};
|
|
2338
|
+
const acceptImport = async (agent) => {
|
|
2339
|
+
if (!rest) return;
|
|
2340
|
+
setState({ kind: "starting" });
|
|
2341
|
+
const { portalClient } = useStore.getState();
|
|
2342
|
+
try {
|
|
2343
|
+
const os = machineOsFromPlatform(process.platform);
|
|
2344
|
+
const { conversationId } = await rest.createOnboardingConversation({
|
|
2345
|
+
agentId: agent.id,
|
|
2346
|
+
projectDir: process.cwd(),
|
|
2347
|
+
os
|
|
2348
|
+
});
|
|
2349
|
+
if (portalClient) {
|
|
2350
|
+
portalClient.enable();
|
|
2351
|
+
portalClient.grantAgent(agent.id, conversationId).catch(() => {
|
|
2352
|
+
useStore.getState().showToast({
|
|
2353
|
+
message: `Couldn't share this machine automatically. ${agent.name} will ask in chat.`,
|
|
2354
|
+
variant: "warning"
|
|
2355
|
+
});
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
openOnboardingChat(agent, conversationId, `Onboarding ${agent.name}`);
|
|
2359
|
+
} catch (err) {
|
|
2360
|
+
if (err instanceof HttpError && err.status === 401) {
|
|
2361
|
+
fail("Session expired. Run `skydive auth login --web`, then retry.");
|
|
2362
|
+
return;
|
|
2363
|
+
}
|
|
2364
|
+
useStore.getState().showToast({
|
|
2365
|
+
message: "Couldn't start onboarding automatically. Opening a chat instead.",
|
|
2366
|
+
variant: "warning"
|
|
2367
|
+
});
|
|
2368
|
+
openNewChat(agent);
|
|
2369
|
+
}
|
|
2370
|
+
};
|
|
2371
|
+
const declineImport = (agent) => {
|
|
2372
|
+
setState({ kind: "starting" });
|
|
2373
|
+
openNewChat(agent);
|
|
2374
|
+
};
|
|
2375
|
+
useKeyboard((key) => {
|
|
2376
|
+
if (state.kind !== "offer") return;
|
|
2377
|
+
if (!canShareMachine) {
|
|
2378
|
+
declineImport(state.agent);
|
|
2379
|
+
return;
|
|
2380
|
+
}
|
|
2381
|
+
if (key.name === "return" || key.name === "y") acceptImport(state.agent);
|
|
2382
|
+
else if (key.name === "n" || key.name === "escape") declineImport(state.agent);
|
|
2383
|
+
});
|
|
2384
|
+
return /* @__PURE__ */ jsx(Centered, { children: renderBody({
|
|
2385
|
+
state,
|
|
2386
|
+
forced,
|
|
2387
|
+
canShareMachine
|
|
2388
|
+
}) });
|
|
2389
|
+
}
|
|
2390
|
+
/** The Skydive mark centered above the screen's content, matching the
|
|
2391
|
+
* new-chat splash lockup and degrading by available space. */
|
|
2392
|
+
function Centered({ children }) {
|
|
2393
|
+
const { width, height } = useTerminalDimensions();
|
|
2394
|
+
return /* @__PURE__ */ jsxs("box", {
|
|
2395
|
+
style: {
|
|
2396
|
+
flexGrow: 1,
|
|
2397
|
+
flexDirection: "column",
|
|
2398
|
+
justifyContent: "center",
|
|
2399
|
+
alignItems: "center"
|
|
2400
|
+
},
|
|
2401
|
+
children: [height >= MARK_MIN_ROWS$1 ? /* @__PURE__ */ jsx("box", {
|
|
2402
|
+
style: {
|
|
2403
|
+
flexDirection: "column",
|
|
2404
|
+
alignItems: "center",
|
|
2405
|
+
marginBottom: 1
|
|
2406
|
+
},
|
|
2407
|
+
children: /* @__PURE__ */ jsx(SkydiveMark, { variant: width >= FULL_MARK_MIN_COLS$1 ? "full" : "mark" })
|
|
2408
|
+
}) : null, /* @__PURE__ */ jsx("box", {
|
|
2409
|
+
style: {
|
|
2410
|
+
flexDirection: "column",
|
|
2411
|
+
alignItems: "center",
|
|
2412
|
+
gap: 1
|
|
2413
|
+
},
|
|
2414
|
+
children
|
|
2415
|
+
})]
|
|
2416
|
+
});
|
|
2417
|
+
}
|
|
2418
|
+
function renderBody({ state, forced, canShareMachine }) {
|
|
2419
|
+
if (state.kind === "error") return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
|
|
2420
|
+
fg: theme.error,
|
|
2421
|
+
children: state.message
|
|
2422
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
2423
|
+
fg: theme.muted,
|
|
2424
|
+
children: "press ctrl+c to quit, then run `skydive` again to retry."
|
|
2425
|
+
})] });
|
|
2426
|
+
if (state.kind === "creating") return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
|
|
2427
|
+
fg: theme.fg,
|
|
2428
|
+
children: "Welcome to Skydive."
|
|
2429
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
2430
|
+
fg: theme.muted,
|
|
2431
|
+
children: forced ? "creating an agent…" : "setting up your first agent…"
|
|
2432
|
+
})] });
|
|
2433
|
+
if (state.kind === "starting") return /* @__PURE__ */ jsx("text", {
|
|
2434
|
+
fg: theme.muted,
|
|
2435
|
+
children: "opening your agent…"
|
|
2436
|
+
});
|
|
2437
|
+
const { agent } = state;
|
|
2438
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("text", {
|
|
2439
|
+
fg: theme.fg,
|
|
2440
|
+
children: forced ? `${agent.name} is ready.` : `Your first agent, ${agent.name}, is ready.`
|
|
2441
|
+
}), canShareMachine ? /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("text", {
|
|
2442
|
+
fg: theme.muted,
|
|
2443
|
+
children: [
|
|
2444
|
+
"Want ",
|
|
2445
|
+
agent.name,
|
|
2446
|
+
" to learn how you already work? It can look at the coding agents set up on this machine to get up to speed fast. It will connect to your computer to read your setup, show you a plan first, and never copy credentials."
|
|
2447
|
+
]
|
|
2448
|
+
}), /* @__PURE__ */ jsx("text", {
|
|
2449
|
+
fg: theme.dim,
|
|
2450
|
+
children: "↵/y yes, learn from my setup · n skip"
|
|
2451
|
+
})] }) : /* @__PURE__ */ jsx("text", {
|
|
2452
|
+
fg: theme.dim,
|
|
2453
|
+
children: "press any key to open the chat"
|
|
2454
|
+
})] });
|
|
2455
|
+
}
|
|
2456
|
+
function createErrorMessage(err) {
|
|
2457
|
+
if (err instanceof HttpError && err.status === 403) return "You don't have permission to create an agent in this workspace.";
|
|
2458
|
+
if (err instanceof HttpError && err.status === 429) return "Too many requests. Wait a moment, then run `skydive` again.";
|
|
2459
|
+
if (err instanceof HttpError) return "Skydive could not create your agent. Try again in a moment.";
|
|
2460
|
+
return "Could not reach Skydive. Check your connection and try again.";
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2252
2463
|
//#endregion
|
|
2253
2464
|
//#region src/chat/tui/screens/workspace-picker.tsx
|
|
2254
2465
|
function WorkspacePicker({ appUrl, sessionToken, onSelect, onCancel }) {
|
|
@@ -11966,7 +12177,8 @@ function ChatScreen({ agent, conversation }) {
|
|
|
11966
12177
|
agentSelector: null,
|
|
11967
12178
|
conversationId: target,
|
|
11968
12179
|
newConversation: false,
|
|
11969
|
-
defaultAgentSelector: null
|
|
12180
|
+
defaultAgentSelector: null,
|
|
12181
|
+
forceOnboarding: false
|
|
11970
12182
|
}));
|
|
11971
12183
|
} catch (err) {
|
|
11972
12184
|
setItems((prev) => [...prev, {
|
|
@@ -13125,7 +13337,7 @@ function ToastView() {
|
|
|
13125
13337
|
|
|
13126
13338
|
//#endregion
|
|
13127
13339
|
//#region src/chat/tui/app.tsx
|
|
13128
|
-
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector }) {
|
|
13340
|
+
function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector, forceOnboarding }) {
|
|
13129
13341
|
const renderer = useRenderer();
|
|
13130
13342
|
const screen = useStore((s) => s.screen);
|
|
13131
13343
|
useEffect(() => {
|
|
@@ -13214,7 +13426,8 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
13214
13426
|
agentSelector,
|
|
13215
13427
|
conversationId,
|
|
13216
13428
|
newConversation: newConversation || useStore.getState().seedPrompt !== null,
|
|
13217
|
-
defaultAgentSelector
|
|
13429
|
+
defaultAgentSelector,
|
|
13430
|
+
forceOnboarding
|
|
13218
13431
|
});
|
|
13219
13432
|
if (cancelled) return;
|
|
13220
13433
|
goTo(screen);
|
|
@@ -13238,6 +13451,7 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
13238
13451
|
conversationId,
|
|
13239
13452
|
newConversation,
|
|
13240
13453
|
defaultAgentSelector,
|
|
13454
|
+
forceOnboarding,
|
|
13241
13455
|
goTo,
|
|
13242
13456
|
setClients,
|
|
13243
13457
|
fail
|
|
@@ -13278,6 +13492,7 @@ function App({ appUrl, sessionToken, authKind, shareMachine, promptHistoryPath,
|
|
|
13278
13492
|
screen.kind === "error" && /* @__PURE__ */ jsx(ErrorScreen, { message: screen.message }),
|
|
13279
13493
|
screen.kind === "agent-picker" && rest && /* @__PURE__ */ jsx(AgentPickerScreen, {}),
|
|
13280
13494
|
screen.kind === "agent-create" && rest && /* @__PURE__ */ jsx(AgentCreateScreen, {}),
|
|
13495
|
+
screen.kind === "first-run" && rest && /* @__PURE__ */ jsx(FirstRunScreen, {}),
|
|
13281
13496
|
screen.kind === "workspace-picker" && /* @__PURE__ */ jsx(WorkspacePicker, {
|
|
13282
13497
|
appUrl,
|
|
13283
13498
|
sessionToken,
|
|
@@ -13816,7 +14031,7 @@ function onRenderCommit(id, phase, actualDuration, baseDuration, startTime, comm
|
|
|
13816
14031
|
* module — and the heavy OpenTUI dependency graph it pulls in — is
|
|
13817
14032
|
* dynamically imported and never loaded on the Node management path.
|
|
13818
14033
|
*/
|
|
13819
|
-
async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector, seedPrompt }) {
|
|
14034
|
+
async function runChat({ appUrl, sessionToken, authKind = "session", shareMachine, promptHistoryPath, theme: themeOverride, notifications, agentSelector, conversationId, newConversation, defaultAgentSelector, forceOnboarding, seedPrompt }) {
|
|
13820
14035
|
await registerGrammars();
|
|
13821
14036
|
const watchdog = startMemoryWatchdog();
|
|
13822
14037
|
installCrashHandler({ memoryProvider: () => watchdog.snapshot() });
|
|
@@ -13831,7 +14046,8 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
13831
14046
|
authKind,
|
|
13832
14047
|
seedPrompt: seedPrompt ?? null,
|
|
13833
14048
|
autoGrantMachine: Boolean(seedPrompt && shareMachine),
|
|
13834
|
-
newConversationIntent: newConversation
|
|
14049
|
+
newConversationIntent: newConversation,
|
|
14050
|
+
forcedOnboarding: forceOnboarding
|
|
13835
14051
|
});
|
|
13836
14052
|
const store = useStore.getState();
|
|
13837
14053
|
if (noColorRequested()) {
|
|
@@ -13858,7 +14074,8 @@ async function runChat({ appUrl, sessionToken, authKind = "session", shareMachin
|
|
|
13858
14074
|
agentSelector: agentSelector ?? null,
|
|
13859
14075
|
conversationId: conversationId ?? null,
|
|
13860
14076
|
newConversation,
|
|
13861
|
-
defaultAgentSelector: defaultAgentSelector ?? null
|
|
14077
|
+
defaultAgentSelector: defaultAgentSelector ?? null,
|
|
14078
|
+
forceOnboarding
|
|
13862
14079
|
})
|
|
13863
14080
|
}));
|
|
13864
14081
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./client-DbqRBquD.mjs";
|
|
3
|
-
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-
|
|
3
|
+
import { a as runPortalDaemon, i as queryDaemonStatus, n as ensureDaemonRunning, o as startPortalDaemon, r as isDaemonListening, s as stopDaemon, t as PortalDaemon } from "./daemon-WOEEiYWf.mjs";
|
|
4
4
|
import "./api-DG5W6iwx.mjs";
|
|
5
5
|
|
|
6
6
|
export { runPortalDaemon };
|
|
@@ -20,7 +20,7 @@ import { access, appendFile, mkdir, readFile, unlink, writeFile } from "node:fs/
|
|
|
20
20
|
* the commit time of the built tree is one monotonic clock they share.
|
|
21
21
|
*/
|
|
22
22
|
function portalDaemonBuild() {
|
|
23
|
-
return "
|
|
23
|
+
return "1786651284";
|
|
24
24
|
}
|
|
25
25
|
/**
|
|
26
26
|
* Whether a client carrying `mine` should replace a running daemon carrying
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-
|
|
2
|
+
import { c as LOCAL_PROTOCOL_VERSION, d as encodeLine, f as makeLineParser, n as ensureDaemonRunning, p as parseDaemonMessage, u as daemonPaths } from "./daemon-WOEEiYWf.mjs";
|
|
3
3
|
import { randomUUID } from "node:crypto";
|
|
4
4
|
import { connect } from "node:net";
|
|
5
5
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-
|
|
3
|
-
import "./rest-
|
|
2
|
+
import { a as runPrint, i as resolveAgent, n as messageGet, o as toPrintError, r as readStdin, t as collectRunText } from "./print-OvYyj6Uk.mjs";
|
|
3
|
+
import "./rest-Dx6b-Nq_.mjs";
|
|
4
4
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
5
5
|
|
|
6
6
|
export { messageGet, readStdin, resolveAgent, runPrint };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
3
|
-
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
3
|
+
import { a as isRecord, i as errorMessage, r as sendErrorMessage, t as createRestClient } from "./rest-Dx6b-Nq_.mjs";
|
|
4
4
|
import { i as billingBlockedOutcomeFromSendResponse, n as BillingBlockedError } from "./billing-blocked-2wju4gC_.mjs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import Conf from "conf";
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { S as getConfigPath } from "./print-
|
|
2
|
+
import { S as getConfigPath } from "./print-OvYyj6Uk.mjs";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { err, ok } from "neverthrow";
|
|
@@ -8,7 +8,7 @@ import fs from "node:fs";
|
|
|
8
8
|
|
|
9
9
|
//#region package.json
|
|
10
10
|
var name = "skydive-cli";
|
|
11
|
-
var version$1 = "0.5.0-beta.
|
|
11
|
+
var version$1 = "0.5.0-beta.20";
|
|
12
12
|
|
|
13
13
|
//#endregion
|
|
14
14
|
//#region src/auth/organization.ts
|
|
@@ -125,6 +125,30 @@ async function ensureActiveOrganization({ appUrl, sessionToken }) {
|
|
|
125
125
|
});
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/chat/import-seed.ts
|
|
130
|
+
/** Map `process.platform` to the OS family used by the seed prompts. Every
|
|
131
|
+
* non-`win32` platform Node runs on (darwin, linux, the BSDs) is POSIX. */
|
|
132
|
+
function machineOsFromPlatform(platform) {
|
|
133
|
+
return platform === "win32" ? "windows" : "posix";
|
|
134
|
+
}
|
|
135
|
+
/** One clause describing the machine's shell/paths so the agent's discovery
|
|
136
|
+
* sweep uses the right conventions instead of guessing. */
|
|
137
|
+
function osHint(os) {
|
|
138
|
+
return os === "windows" ? "This machine is Windows, so use PowerShell and Windows paths (%USERPROFILE%, backslashes)." : "This machine is POSIX (macOS/Linux), so use a POSIX shell and paths (~, forward slashes).";
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* The first message of an explicit `skydive import` conversation, sent as the
|
|
142
|
+
* user. Frames the migration and points the agent at its import-config skill.
|
|
143
|
+
*/
|
|
144
|
+
function buildImportSeedPrompt(projectDir, os) {
|
|
145
|
+
return [
|
|
146
|
+
"I'm migrating from another coding agent. Import my setup from this machine.",
|
|
147
|
+
"",
|
|
148
|
+
`Use your import-config skill. I ran this from \`${projectDir}\`, so start there and in my home directory. ${osHint(os)} Don't assume one tool — do the discovery sweep so you catch whatever I actually use (Claude Code, Cursor, Codex, Gemini CLI, Copilot, Windsurf, Cline, OpenCode, Aider, and any nested AGENTS.md). Show me the plan first: everything you found, what you'll bring over, where it lands in you, and anything you're leaving out (credentials especially). Then wait for my OK before committing anything.`
|
|
149
|
+
].join("\n");
|
|
150
|
+
}
|
|
151
|
+
|
|
128
152
|
//#endregion
|
|
129
153
|
//#region src/chat/tui/theme.ts
|
|
130
154
|
const tokyonight = {
|
|
@@ -1275,4 +1299,4 @@ function maybeStartProfiling(argv, cliVersion) {
|
|
|
1275
1299
|
}
|
|
1276
1300
|
|
|
1277
1301
|
//#endregion
|
|
1278
|
-
export {
|
|
1302
|
+
export { setActiveWorkspace as A, themesForMode as C, getActiveWorkspaceId as D, ensureActiveOrganization as E, version$1 as M, getSessionIdentity as O, themes as S, machineOsFromPlatform as T, theme as _, installCrashHandler as a, themeModeFromColorFgBg as b, MARK_CELLS as c, splashFitsWidth as d, DEFAULT_THEME_ID as f, noColorRequested as g, monoTheme as h, writeArtifact as i, name as j, listWorkspaces as k, WORDMARK as l, findTheme as m, profilingEnabled as n, buildCrashReport as o, applyTheme as p, record as r, writeCrashReport as s, maybeStartProfiling as t, brandHelpArt as u, themeForMode as v, buildImportSeedPrompt as w, themeVersion as x, themeMode as y };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
3
|
-
import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-
|
|
3
|
+
import { n as errorDetail, r as sendErrorMessage, t as createRestClient } from "./rest-Dx6b-Nq_.mjs";
|
|
4
4
|
import "./billing-blocked-2wju4gC_.mjs";
|
|
5
5
|
|
|
6
6
|
export { createRestClient };
|
|
@@ -214,9 +214,19 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
214
214
|
return all;
|
|
215
215
|
},
|
|
216
216
|
createAgent: async ({ name }) => {
|
|
217
|
-
const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
|
|
217
|
+
const { agent } = await post("/api/v1/agents", name == null ? {} : { name }, createAgentResponseSchema);
|
|
218
218
|
return agent;
|
|
219
219
|
},
|
|
220
|
+
createOnboardingConversation: async ({ agentId, projectDir, os }) => {
|
|
221
|
+
const result = await post(`/api/v1/agents/${encodeURIComponent(agentId)}/onboarding-conversation`, {
|
|
222
|
+
projectDir,
|
|
223
|
+
os
|
|
224
|
+
}, onboardingConversationResponseSchema);
|
|
225
|
+
return {
|
|
226
|
+
conversationId: result.conversationId,
|
|
227
|
+
runId: result.runId
|
|
228
|
+
};
|
|
229
|
+
},
|
|
220
230
|
getAgent: async ({ agentId }) => {
|
|
221
231
|
const { agent } = await get(`/api/v1/agents/${encodeURIComponent(agentId)}`, getAgentResponseSchema);
|
|
222
232
|
return agent;
|
|
@@ -454,6 +464,10 @@ const listAgentsResponseSchema = z.object({
|
|
|
454
464
|
totalCount: z.number().nullable().optional()
|
|
455
465
|
});
|
|
456
466
|
const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
|
|
467
|
+
const onboardingConversationResponseSchema = z.object({
|
|
468
|
+
conversationId: z.string(),
|
|
469
|
+
runId: z.string().nullable()
|
|
470
|
+
});
|
|
457
471
|
const getAgentResponseSchema = z.object({ agent: agentSummarySchema });
|
|
458
472
|
const agentSuggestionSchema = z.object({ name: z.string() });
|
|
459
473
|
const suggestAgentResponseSchema = z.object({ suggestion: agentSuggestionSchema.nullable() });
|
package/package.json
CHANGED