skydive-cli 0.5.0-beta.3 → 0.5.0-beta.31
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 +3 -0
- package/README.md +15 -4
- package/dist/js/api-DG5W6iwx.mjs +131 -0
- package/dist/js/bin.mjs +251 -84
- package/dist/js/{boot-BVVbrf2k.mjs → boot-iaVSnd7h.mjs} +1800 -605
- package/dist/js/{client-Dd5sMXPv.mjs → client-DbqRBquD.mjs} +206 -133
- package/dist/js/client-hH2PJL8y.mjs +5 -0
- package/dist/js/{daemon-Bq93vOIk.mjs → daemon-BTyg329O.mjs} +93 -7
- package/dist/js/{daemon-DDBIumf1.mjs → daemon-SPQgtsbW.mjs} +3 -2
- package/dist/js/{daemon-client-Bewt98dE.mjs → daemon-client--A_yMKq6.mjs} +12 -8
- package/dist/js/daemon-client-C7emKKlj.mjs +7 -0
- package/dist/js/forward-DL6DYqyc.mjs +187 -0
- package/dist/js/{print-CbayCa87.mjs → print-C_TnQSPg.mjs} +211 -35
- package/dist/js/{print-ba_0hiV9.mjs → print-DACzRUwQ.mjs} +2 -2
- package/dist/js/{print-share-C1LPsgTP.mjs → print-share-BH9gvCls.mjs} +3 -1
- package/dist/js/{profiler-DtuXkIkR.mjs → profiler-BVbYs_Yg.mjs} +258 -4
- package/dist/js/{raw-pty-B6mAroiI.mjs → raw-pty-DY4KelZW.mjs} +1 -1
- package/dist/js/raw-pty-DmdUf4_w.mjs +5 -0
- package/dist/js/{rest-BY2nADw5.mjs → rest-CgfbKXst.mjs} +40 -12
- package/dist/js/{rest-DADJh0bi.mjs → rest-VV5nc-Mn.mjs} +1 -1
- package/package.json +1 -1
- package/dist/js/client-CKBQ8pft.mjs +0 -4
- package/dist/js/daemon-client-srAit8Fz.mjs +0 -6
- package/dist/js/raw-pty-3EkG-jjH.mjs +0 -5
- /package/dist/js/{client-Cn2af31H.mjs → client-c4c5MmgN.mjs} +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
### Added
|
|
11
11
|
|
|
12
|
+
- `/search` (alias `/find`) in the chat TUI — opens the conversation picker with its search field focused, so "find a conversation" has a discoverable command instead of being a feature you had to know the bare picker already had. Pass a query inline (`/search billing deep dive`) and the picker opens already narrowed to the matches; bare `/search` opens it ready for you to type. The search runs server-side across conversation titles, message bodies, and participant names, the same search the picker exposes.
|
|
13
|
+
- 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.
|
|
14
|
+
- `/computer` (alias `/stats`) in the chat TUI — a snapshot of the agent's sandbox drawn as colored bar graphs: CPU, memory, and disk usage. Bars turn amber past 75%% and red past 90%% so a glance tells you whether the box is under pressure. The numbers come from the Skydive API (the same live resource sample the web computer view reads), not by connecting to the sandbox — so the card shows instantly, never waking or hanging on a box that isn't running: when the agent has no live sandbox it simply reads "no sandbox running". 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.
|
|
12
15
|
- `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.)
|
|
13
16
|
|
|
14
17
|
## [0.4.0] - 2026-08-07
|
package/README.md
CHANGED
|
@@ -261,6 +261,16 @@ internal API. On launch it walks you through:
|
|
|
261
261
|
formatting, collapsed reasoning, and rich rendering of tool calls
|
|
262
262
|
(`bash`, `edit`/`write` diffs, `read`, `grep`/`glob`).
|
|
263
263
|
|
|
264
|
+
The chat TUI remembers the last agent you talked to: every conversation it
|
|
265
|
+
opens records that agent as your default, and the next bare `skydive chat`
|
|
266
|
+
drops straight into a new conversation with it — no picker. An explicit
|
|
267
|
+
`--agent` or `--resume` still wins for that invocation, and the conversation
|
|
268
|
+
it opens sets the default like any other. You can also seed or override the
|
|
269
|
+
default by hand with `skydive config set defaultAgent <id|slug|name>`. A
|
|
270
|
+
stored value that no longer matches (renamed or archived agent) falls back
|
|
271
|
+
to the agent picker with a notice, and picking an agent there heals it
|
|
272
|
+
automatically.
|
|
273
|
+
|
|
264
274
|
You can change conversation without leaving chat. `/conversation` (aliases
|
|
265
275
|
`/conversations`, `/conv`) reopens the conversation list, and
|
|
266
276
|
`/conversation <id>` — an id, its `/c/<id>` link, or a pasted
|
|
@@ -463,10 +473,11 @@ skydive config path # print the config.json location
|
|
|
463
473
|
|
|
464
474
|
Editable keys:
|
|
465
475
|
|
|
466
|
-
| Key | Type | Effect
|
|
467
|
-
| --------------------- | ------- |
|
|
468
|
-
| `shareMachineDefault` | boolean | When `true`, `skydive chat` (TUI and `-p`) shares this machine over the portal on launch, as if `--share-machine` were passed. An explicit `--share-machine`/`--no-share-machine` overrides it per invocation. Default `false`.
|
|
469
|
-
| `updateCheck` | boolean | When `false`, disables the daily background update check and its "Update available" notice. The persistent equivalent of setting `SKYDIVE_NO_UPDATE_CHECK` (or `NO_UPDATE_NOTIFIER`) in the environment. Default `true`.
|
|
476
|
+
| Key | Type | Effect |
|
|
477
|
+
| --------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
478
|
+
| `shareMachineDefault` | boolean | When `true`, `skydive chat` (TUI and `-p`) shares this machine over the portal on launch, as if `--share-machine` were passed. An explicit `--share-machine`/`--no-share-machine` overrides it per invocation. Default `false`. |
|
|
479
|
+
| `updateCheck` | boolean | When `false`, disables the daily background update check and its "Update available" notice. The persistent equivalent of setting `SKYDIVE_NO_UPDATE_CHECK` (or `NO_UPDATE_NOTIFIER`) in the environment. Default `true`. |
|
|
480
|
+
| `defaultAgent` | string | Agent (id, slug, or name) a bare `skydive chat` opens a new conversation with, skipping the agent picker. Maintained automatically: every conversation the chat TUI opens records its agent here (last used wins). `skydive config set defaultAgent` seeds or overrides it; an explicit `--agent`/`--resume` wins per invocation; a value that no longer matches falls back to the picker. |
|
|
470
481
|
|
|
471
482
|
The remaining keys (`apiKey`, `apiUrl`, `sessionToken`, `appUrl`, `themeDark`,
|
|
472
483
|
`themeLight`, …) are credentials or flow-managed state — leave them to the
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { t as HttpError } from "./http-error-DzyrsLAZ.mjs";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
|
|
5
|
+
//#region ../portal-daemon/src/api.ts
|
|
6
|
+
/**
|
|
7
|
+
* The portal's session-authed REST surface, shared by `PortalClient` (the
|
|
8
|
+
* TUI/`portal open` connection) and the `skydive portal` management
|
|
9
|
+
* commands, so the endpoint contracts and response schemas live in exactly
|
|
10
|
+
* one place.
|
|
11
|
+
*/
|
|
12
|
+
const deviceSchema = z.object({
|
|
13
|
+
id: z.string(),
|
|
14
|
+
machineName: z.string(),
|
|
15
|
+
friendlyName: z.string(),
|
|
16
|
+
connected: z.boolean(),
|
|
17
|
+
lastSeen: z.string().nullable(),
|
|
18
|
+
grantedAgentIds: z.array(z.string())
|
|
19
|
+
});
|
|
20
|
+
const devicesResponseSchema = z.object({
|
|
21
|
+
devices: z.array(deviceSchema),
|
|
22
|
+
agents: z.array(z.object({
|
|
23
|
+
id: z.string(),
|
|
24
|
+
name: z.string()
|
|
25
|
+
}))
|
|
26
|
+
});
|
|
27
|
+
const deviceTokenSchema = z.object({ token: z.string().min(1) });
|
|
28
|
+
async function portalFetch(auth, path, init) {
|
|
29
|
+
const res = await fetch(`${auth.appUrl}${path}`, {
|
|
30
|
+
method: init.method,
|
|
31
|
+
headers: {
|
|
32
|
+
authorization: `Bearer ${auth.sessionToken}`,
|
|
33
|
+
accept: "application/json",
|
|
34
|
+
...init.body ? { "content-type": "application/json" } : {}
|
|
35
|
+
},
|
|
36
|
+
...init.body ? { body: init.body } : {}
|
|
37
|
+
});
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const body = await res.text().catch(() => "");
|
|
40
|
+
throw new HttpError(res.status, body);
|
|
41
|
+
}
|
|
42
|
+
return res.json();
|
|
43
|
+
}
|
|
44
|
+
async function fetchPortalDevices(auth) {
|
|
45
|
+
const json = await portalFetch(auth, "/api/v1/portal/devices", { method: "GET" });
|
|
46
|
+
return devicesResponseSchema.parse(json);
|
|
47
|
+
}
|
|
48
|
+
const registerResponseSchema = z.object({ device: z.object({ id: z.string() }) });
|
|
49
|
+
/**
|
|
50
|
+
* Register this machine's device row without connecting. Connecting registers
|
|
51
|
+
* as a side effect; this covers granting an agent on a machine that has never
|
|
52
|
+
* shared yet (the grant references the device row).
|
|
53
|
+
*/
|
|
54
|
+
async function registerPortalDevice(auth, { machineName, friendlyName }) {
|
|
55
|
+
const json = await portalFetch(auth, "/api/v1/portal/devices", {
|
|
56
|
+
method: "POST",
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
machineName,
|
|
59
|
+
friendlyName
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
return registerResponseSchema.parse(json).device;
|
|
63
|
+
}
|
|
64
|
+
/** Short-lived token the machine presents when dialing the portal WebSocket. */
|
|
65
|
+
async function mintPortalDeviceToken(auth) {
|
|
66
|
+
const json = await portalFetch(auth, "/api/v1/portal/device-token", { method: "POST" });
|
|
67
|
+
return deviceTokenSchema.parse(json).token;
|
|
68
|
+
}
|
|
69
|
+
const forwardTargetSchema = z.object({
|
|
70
|
+
daemonOrigin: z.string().min(1),
|
|
71
|
+
token: z.string().min(1),
|
|
72
|
+
expiresInSeconds: z.number()
|
|
73
|
+
});
|
|
74
|
+
/**
|
|
75
|
+
* Everything `portal forward` needs to dial an agent's sandbox daemon through
|
|
76
|
+
* the agent-webserver edge Worker: the daemon's stable public origin and a
|
|
77
|
+
* daemon auth token (canUse-gated server-side).
|
|
78
|
+
*/
|
|
79
|
+
async function fetchForwardTarget(auth, agentId) {
|
|
80
|
+
const json = await portalFetch(auth, "/api/v1/portal/forward-target", {
|
|
81
|
+
method: "POST",
|
|
82
|
+
body: JSON.stringify({ agentId })
|
|
83
|
+
});
|
|
84
|
+
return forwardTargetSchema.parse(json);
|
|
85
|
+
}
|
|
86
|
+
async function grantPortalAccess(auth, { deviceId, agentId, conversationId }) {
|
|
87
|
+
await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants`, {
|
|
88
|
+
method: "POST",
|
|
89
|
+
body: JSON.stringify({
|
|
90
|
+
agentId,
|
|
91
|
+
conversationId
|
|
92
|
+
})
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
async function revokePortalAccess(auth, { deviceId, agentId }) {
|
|
96
|
+
await portalFetch(auth, `/api/v1/portal/devices/${encodeURIComponent(deviceId)}/grants/${encodeURIComponent(agentId)}`, { method: "DELETE" });
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The device row for a given machine identity. Matching is by `machineName`
|
|
100
|
+
* equality — the stable handle the machine registers under, not the display
|
|
101
|
+
* label.
|
|
102
|
+
*/
|
|
103
|
+
function findThisDevice(devices, machineName) {
|
|
104
|
+
return devices.find((device) => device.machineName === machineName) ?? null;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* One-time grant migration onto the merged device. Earlier CLI builds
|
|
108
|
+
* registered a separate `<machineName>-cli` device, so a user's existing
|
|
109
|
+
* approvals hang off that row; the merged device would start with zero grants
|
|
110
|
+
* and every already-authorized agent would ask again. Copy any grant the
|
|
111
|
+
* merged device is missing (the grant endpoint upserts, so re-runs are
|
|
112
|
+
* no-ops). The legacy row is left in place — an old CLI build may still
|
|
113
|
+
* connect under it. Returns how many grants were copied.
|
|
114
|
+
*/
|
|
115
|
+
async function unifyLegacyCliGrants(auth, machineName) {
|
|
116
|
+
const { devices } = await fetchPortalDevices(auth);
|
|
117
|
+
const merged = findThisDevice(devices, machineName);
|
|
118
|
+
const legacy = findThisDevice(devices, `${machineName}-cli`);
|
|
119
|
+
if (!merged || !legacy) return 0;
|
|
120
|
+
const have = new Set(merged.grantedAgentIds);
|
|
121
|
+
const missing = legacy.grantedAgentIds.filter((id) => !have.has(id));
|
|
122
|
+
for (const agentId of missing) await grantPortalAccess(auth, {
|
|
123
|
+
deviceId: merged.id,
|
|
124
|
+
agentId,
|
|
125
|
+
conversationId: null
|
|
126
|
+
});
|
|
127
|
+
return missing.length;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
//#endregion
|
|
131
|
+
export { mintPortalDeviceToken as a, unifyLegacyCliGrants as c, grantPortalAccess as i, fetchPortalDevices as n, registerPortalDevice as o, findThisDevice as r, revokePortalAccess as s, fetchForwardTarget as t };
|