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 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 version, D as listWorkspaces, E as getSessionIdentity, O as setActiveWorkspace, S as themes, T as getActiveWorkspaceId, a as installCrashHandler, k as name, t as maybeStartProfiling, u as brandHelpArt, w as ensureActiveOrganization } from "./profiler-DlxtXXGp.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-CPRgbo2N.mjs";
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-CLrHVkxh.mjs";
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-DWw9AjB4.mjs";
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-C831RDoa.mjs");
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
- seedPrompt: buildImportSeedPrompt(process.cwd())
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-D8L3DAbD.mjs");
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-C831RDoa.mjs");
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-D8L3DAbD.mjs");
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-D8L3DAbD.mjs");
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-C831RDoa.mjs");
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-CMsS0Ayk.mjs");
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-DsdPAE8K.mjs");
2683
- const { resolveAgent } = await import("./print-D8L3DAbD.mjs");
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-DsdPAE8K.mjs");
2841
- const { resolveAgent } = await import("./print-D8L3DAbD.mjs");
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-BX8OBLBs.mjs");
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 { C as themesForMode, D as listWorkspaces, O as setActiveWorkspace, T as getActiveWorkspaceId, _ 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, 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-DlxtXXGp.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-CPRgbo2N.mjs";
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-CLrHVkxh.mjs";
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-DWw9AjB4.mjs";
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-C_lgQr4E.mjs";
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 (!agentSelector && !newConversation && !defaultAgentSelector) return { kind: "agent-picker" };
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-DWw9AjB4.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-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 "1786649704";
23
+ return "1786651284";
24
24
  }
25
25
  /**
26
26
  * Whether a client carrying `mine` should replace a running daemon carrying
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env node
2
+ import "./client-DbqRBquD.mjs";
3
+ import "./daemon-WOEEiYWf.mjs";
4
+ import "./api-DG5W6iwx.mjs";
5
+ import { t as PortalDaemonClient } from "./daemon-client-CX4qctxA.mjs";
6
+
7
+ export { PortalDaemonClient };
@@ -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-DWw9AjB4.mjs";
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-CPRgbo2N.mjs";
3
- import "./rest-CLrHVkxh.mjs";
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-CLrHVkxh.mjs";
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-CPRgbo2N.mjs";
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.19";
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 { version$1 as A, themesForMode as C, listWorkspaces as D, getSessionIdentity as E, setActiveWorkspace as O, themes as S, getActiveWorkspaceId 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 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, ensureActiveOrganization as w, themeVersion as x, themeMode as y };
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-CLrHVkxh.mjs";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skydive-cli",
3
- "version": "0.5.0-beta.19",
3
+ "version": "0.5.0-beta.20",
4
4
  "description": "Skydive CLI — cloud agents from the command line",
5
5
  "homepage": "https://skydive.com",
6
6
  "license": "MIT",
@@ -1,7 +0,0 @@
1
- #!/usr/bin/env node
2
- import "./client-DbqRBquD.mjs";
3
- import "./daemon-DWw9AjB4.mjs";
4
- import "./api-DG5W6iwx.mjs";
5
- import { t as PortalDaemonClient } from "./daemon-client-C_lgQr4E.mjs";
6
-
7
- export { PortalDaemonClient };