skydive-cli 0.5.0-beta.8 → 0.5.0
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 +13 -0
- package/README.md +61 -13
- package/dist/js/api-BFQ4PQDA.mjs +315 -0
- package/dist/js/{billing-blocked-Dgu5-oDy.mjs → billing-blocked-SE6tySLd.mjs} +1 -1
- package/dist/js/bin.mjs +674 -307
- package/dist/js/{boot-BCClEpCM.mjs → boot-DD4T-61U.mjs} +4558 -930
- package/dist/js/chunk-BbwQpWto.mjs +33 -0
- package/dist/js/{client-DabRpc_T.mjs → client--k9cjfkX.mjs} +437 -39
- package/dist/js/{client-c4c5MmgN.mjs → client-Btq6bMzX.mjs} +108 -2
- package/dist/js/client-Ct0-JZSS.mjs +5 -0
- package/dist/js/daemon-CCgNLD0H.mjs +7 -0
- package/dist/js/{daemon-k2kVkJ8D.mjs → daemon-Do1jU2UF.mjs} +123 -43
- package/dist/js/daemon-client-C7nE-lLK.mjs +8 -0
- package/dist/js/{daemon-client-fxf1A25Z.mjs → daemon-client-Dvad009G.mjs} +1 -1
- package/dist/js/dist-CRtjM7ba.mjs +1750 -0
- package/dist/js/forward-C-f04uyE.mjs +208 -0
- package/dist/js/{profiler-DawY0V0Z.mjs → install-CtAVvERm.mjs} +545 -215
- package/dist/js/launcher.mjs +49 -0
- package/dist/js/localhost-cert-Bn-UBUmj.mjs +67 -0
- package/dist/js/{print-Wakr3GJd.mjs → print-Bx8qUC9U.mjs} +3 -3
- package/dist/js/{print-BpuyEfWX.mjs → print-D_UEjdSw.mjs} +257 -35
- package/dist/js/{print-share-CKLPmsg0.mjs → print-share-Cz0EO2RK.mjs} +9 -3
- package/dist/js/raw-pty-Ci2qFR9F.mjs +5 -0
- package/dist/js/{raw-pty-DY4KelZW.mjs → raw-pty-D5PhKZSl.mjs} +1 -1
- package/dist/js/{rest-I3imNduB.mjs → rest-B9__Zsuk.mjs} +144 -19
- package/dist/js/rest-Dc0EEok3.mjs +6 -0
- package/dist/js/tls-cert-BpCaD5AT.mjs +4 -0
- package/dist/js/tls-cert-Rua2oV7n.mjs +67 -0
- package/package.json +12 -4
- package/dist/js/api-DG5W6iwx.mjs +0 -131
- package/dist/js/client-BuU34IVE.mjs +0 -5
- package/dist/js/daemon-BxU59xie.mjs +0 -6
- package/dist/js/daemon-client-C2BvZgKO.mjs +0 -7
- package/dist/js/forward-18QoL5dO.mjs +0 -68
- package/dist/js/raw-pty-DmdUf4_w.mjs +0 -5
- package/dist/js/rest-D29qNkto.mjs +0 -6
- /package/dist/js/{billing-blocked-2wju4gC_.mjs → billing-blocked-D3l5kJlX.mjs} +0 -0
- /package/dist/js/{http-error-DzyrsLAZ.mjs → http-error-BF2NZZE3.mjs} +0 -0
- /package/dist/js/{output-DYzzdXYV.mjs → output-C9mb3sUB.mjs} +0 -0
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as HttpError } from "./http-error-
|
|
3
|
-
import { a as billingBlockedOutcomeSchema } from "./billing-blocked-
|
|
2
|
+
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
3
|
+
import { a as billingBlockedOutcomeSchema } from "./billing-blocked-D3l5kJlX.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { createParser } from "eventsource-parser";
|
|
6
6
|
|
|
7
|
+
//#region package.json
|
|
8
|
+
var name = "skydive-cli";
|
|
9
|
+
var version = "0.5.0";
|
|
10
|
+
|
|
11
|
+
//#endregion
|
|
7
12
|
//#region src/chat/util.ts
|
|
8
13
|
/** Narrowing helper for the many `unknown` payloads the chat stream and
|
|
9
14
|
* tool inputs/outputs carry. A type predicate (not an `as` cast), so call
|
|
@@ -18,8 +23,24 @@ function errorMessage(err) {
|
|
|
18
23
|
|
|
19
24
|
//#endregion
|
|
20
25
|
//#region src/chat/api/rest.ts
|
|
26
|
+
const CLI_VERSION_HEADER = "x-skydive-cli-version";
|
|
21
27
|
const ERROR_DETAIL_MAX_BODY = 2e3;
|
|
22
28
|
/**
|
|
29
|
+
* Pull the filename out of a `Content-Disposition` header. Handles the
|
|
30
|
+
* RFC 5987 `filename*=UTF-8''...` form (percent-decoded) and the plain
|
|
31
|
+
* quoted/bare `filename=` form, preferring the extended form when both are
|
|
32
|
+
* present (the API emits both for non-ASCII names). Returns '' when the header
|
|
33
|
+
* is absent or carries no filename, so callers fall back to a cached name.
|
|
34
|
+
*/
|
|
35
|
+
function filenameFromContentDisposition(header) {
|
|
36
|
+
if (!header) return "";
|
|
37
|
+
const extended = /filename\*=(?:UTF-8'')?([^;]+)/i.exec(header);
|
|
38
|
+
if (extended?.[1]) try {
|
|
39
|
+
return decodeURIComponent(extended[1].trim().replace(/^"|"$/g, ""));
|
|
40
|
+
} catch (_error) {}
|
|
41
|
+
return /filename="?([^";]+)"?/i.exec(header)?.[1]?.trim() ?? "";
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
23
44
|
* Fullest renderable text for a thrown value. `HttpError.message` clips the
|
|
24
45
|
* response body to 200 chars (it flows into logs and one-line UIs); the
|
|
25
46
|
* transcript renders errors collapsed to a single line, so it can afford the
|
|
@@ -104,7 +125,8 @@ const MAX_STREAM_RECONNECTS = 5;
|
|
|
104
125
|
function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
105
126
|
const baseHeaders = {
|
|
106
127
|
authorization: `Bearer ${sessionToken}`,
|
|
107
|
-
accept: "application/json"
|
|
128
|
+
accept: "application/json",
|
|
129
|
+
[CLI_VERSION_HEADER]: version
|
|
108
130
|
};
|
|
109
131
|
if (workspaceId) baseHeaders["x-workspace-id"] = workspaceId;
|
|
110
132
|
async function get(path, schema) {
|
|
@@ -124,6 +146,13 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
124
146
|
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
125
147
|
return schema.parse(await res.json());
|
|
126
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* The agent PATCH is a merge: only the keys present are written, so each
|
|
151
|
+
* caller sends exactly the settings it means to change.
|
|
152
|
+
*/
|
|
153
|
+
function patchAgent(agentId, body) {
|
|
154
|
+
return post(`/api/v1/agents/${encodeURIComponent(agentId)}`, body, updateAgentResponseSchema, "PATCH");
|
|
155
|
+
}
|
|
127
156
|
async function del(path) {
|
|
128
157
|
const res = await fetch(`${appUrl}${path}`, {
|
|
129
158
|
method: "DELETE",
|
|
@@ -140,7 +169,8 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
140
169
|
try {
|
|
141
170
|
const headers = {
|
|
142
171
|
authorization: `Bearer ${sessionToken}`,
|
|
143
|
-
accept: "text/event-stream"
|
|
172
|
+
accept: "text/event-stream",
|
|
173
|
+
[CLI_VERSION_HEADER]: version
|
|
144
174
|
};
|
|
145
175
|
if (workspaceId) headers["x-workspace-id"] = workspaceId;
|
|
146
176
|
if (lastEventId) headers["last-event-id"] = lastEventId;
|
|
@@ -214,9 +244,19 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
214
244
|
return all;
|
|
215
245
|
},
|
|
216
246
|
createAgent: async ({ name }) => {
|
|
217
|
-
const { agent } = await post("/api/v1/agents", { name }, createAgentResponseSchema);
|
|
247
|
+
const { agent } = await post("/api/v1/agents", name == null ? {} : { name }, createAgentResponseSchema);
|
|
218
248
|
return agent;
|
|
219
249
|
},
|
|
250
|
+
createOnboardingConversation: async ({ agentId, projectDir, os }) => {
|
|
251
|
+
const result = await post(`/api/v1/agents/${encodeURIComponent(agentId)}/onboarding-conversation`, {
|
|
252
|
+
projectDir,
|
|
253
|
+
os
|
|
254
|
+
}, onboardingConversationResponseSchema);
|
|
255
|
+
return {
|
|
256
|
+
conversationId: result.conversationId,
|
|
257
|
+
runId: result.runId
|
|
258
|
+
};
|
|
259
|
+
},
|
|
220
260
|
getAgent: async ({ agentId }) => {
|
|
221
261
|
const { agent } = await get(`/api/v1/agents/${encodeURIComponent(agentId)}`, getAgentResponseSchema);
|
|
222
262
|
return agent;
|
|
@@ -233,9 +273,31 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
233
273
|
const { models } = await get("/api/v1/models", listModelsResponseSchema);
|
|
234
274
|
return models;
|
|
235
275
|
},
|
|
236
|
-
|
|
237
|
-
const {
|
|
238
|
-
|
|
276
|
+
listModelEfforts: async () => {
|
|
277
|
+
const { modelConfigOverrides } = await get("/api/v1/models/preferred", modelEffortPreferencesResponseSchema);
|
|
278
|
+
const efforts = {};
|
|
279
|
+
for (const [modelId, config] of Object.entries(modelConfigOverrides ?? {})) if (config.thinkingLevel) efforts[modelId] = config.thinkingLevel;
|
|
280
|
+
return efforts;
|
|
281
|
+
},
|
|
282
|
+
setModelEffort: async ({ modelId, thinkingLevel }) => {
|
|
283
|
+
await post("/api/v1/models/config", {
|
|
284
|
+
modelId,
|
|
285
|
+
config: { thinkingLevel }
|
|
286
|
+
}, z.unknown(), "PATCH");
|
|
287
|
+
},
|
|
288
|
+
updateAgentModel: async ({ agentId, model, thinkingLevel }) => {
|
|
289
|
+
const { agent } = await patchAgent(agentId, {
|
|
290
|
+
model,
|
|
291
|
+
thinkingLevel
|
|
292
|
+
});
|
|
293
|
+
return {
|
|
294
|
+
model: agent.model ?? null,
|
|
295
|
+
thinkingLevel: agent.thinkingLevel ?? null
|
|
296
|
+
};
|
|
297
|
+
},
|
|
298
|
+
updateAgentEffort: async ({ agentId, thinkingLevel }) => {
|
|
299
|
+
const { agent } = await patchAgent(agentId, { thinkingLevel });
|
|
300
|
+
return { thinkingLevel: agent.thinkingLevel ?? null };
|
|
239
301
|
},
|
|
240
302
|
listConversations: async ({ agentId, limit, channels, archived, query, onPage }) => {
|
|
241
303
|
const trimmedQuery = query?.trim();
|
|
@@ -260,9 +322,17 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
260
322
|
} while (cursor && all.length < maxConversations);
|
|
261
323
|
return limit ? all.slice(0, limit) : all;
|
|
262
324
|
},
|
|
263
|
-
listMessages: async ({ conversationId }) => {
|
|
264
|
-
const
|
|
265
|
-
|
|
325
|
+
listMessages: async ({ conversationId, limit, before }) => {
|
|
326
|
+
const params = new URLSearchParams();
|
|
327
|
+
if (limit !== void 0) params.set("limit", String(limit));
|
|
328
|
+
if (before !== void 0) params.set("before", before);
|
|
329
|
+
const query = params.toString();
|
|
330
|
+
const { messages, hasMore, nextBefore } = await get(`/api/v1/conversations/${encodeURIComponent(conversationId)}/messages${query ? `?${query}` : ""}`, listMessagesResponseSchema);
|
|
331
|
+
return {
|
|
332
|
+
messages,
|
|
333
|
+
hasMore: hasMore ?? false,
|
|
334
|
+
nextBefore: nextBefore ?? null
|
|
335
|
+
};
|
|
266
336
|
},
|
|
267
337
|
listWorkspaceFiles: async ({ agentId }) => {
|
|
268
338
|
const { files } = await get(`/api/v1/workspace-files?${new URLSearchParams({ agentId }).toString()}`, listWorkspaceFilesResponseSchema);
|
|
@@ -289,6 +359,13 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
289
359
|
const { result } = await get(`/api/v1/trpc/v2.subagentTasks.list?input=${encodeURIComponent(JSON.stringify({ conversationId }))}`, subagentTasksResponseSchema);
|
|
290
360
|
return new Map(result.data.subagentTasks.map((task) => [task.id, task]));
|
|
291
361
|
},
|
|
362
|
+
computeLiveStats: async (agentId) => {
|
|
363
|
+
const { stats } = await get(`/api/v1/compute/live-stats?agentId=${encodeURIComponent(agentId)}`, liveStatsResponseSchema);
|
|
364
|
+
return stats;
|
|
365
|
+
},
|
|
366
|
+
restartAgent: async (agentId) => {
|
|
367
|
+
await post(`/api/v1/agents/${encodeURIComponent(agentId)}/restart`, {}, z.object({ ok: z.boolean() }));
|
|
368
|
+
},
|
|
292
369
|
uploadAttachment: async ({ agentId, fileName, mediaType, data }) => {
|
|
293
370
|
const size = data.byteLength;
|
|
294
371
|
const presign = await post("/api/v1/attachments/presign", {
|
|
@@ -316,6 +393,17 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
316
393
|
sizeBytes: finalized.sizeBytes ?? size
|
|
317
394
|
};
|
|
318
395
|
},
|
|
396
|
+
downloadAttachment: async ({ attachmentId }) => {
|
|
397
|
+
const res = await fetch(`${appUrl}/api/v1/attachments/${encodeURIComponent(attachmentId)}/download?disposition=inline`, { headers: baseHeaders });
|
|
398
|
+
if (!res.ok) throw new HttpError(res.status, await res.text().catch(() => ""));
|
|
399
|
+
const data = new Uint8Array(await res.arrayBuffer());
|
|
400
|
+
const mediaType = res.headers.get("content-type") ?? "application/octet-stream";
|
|
401
|
+
return {
|
|
402
|
+
data,
|
|
403
|
+
fileName: filenameFromContentDisposition(res.headers.get("content-disposition")),
|
|
404
|
+
mediaType
|
|
405
|
+
};
|
|
406
|
+
},
|
|
319
407
|
forkConversation: async ({ conversationId }) => {
|
|
320
408
|
const { conversation } = await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/fork`, {}, forkConversationResponseSchema);
|
|
321
409
|
return conversation;
|
|
@@ -329,6 +417,9 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
329
417
|
setConversationArchived: async ({ conversationId, archived }) => {
|
|
330
418
|
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/archive`, { archived }, z.object({ archived: z.boolean() }));
|
|
331
419
|
},
|
|
420
|
+
markConversationRead: async ({ conversationId }) => {
|
|
421
|
+
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}/read`, {}, z.object({ read: z.boolean() }));
|
|
422
|
+
},
|
|
332
423
|
renameConversation: async ({ conversationId, title }) => {
|
|
333
424
|
await post(`/api/v1/conversations/${encodeURIComponent(conversationId)}`, { title }, z.object({ conversation: z.object({ id: z.string() }) }), "PATCH");
|
|
334
425
|
},
|
|
@@ -350,6 +441,7 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
350
441
|
const { authorizationUrl } = await post("/api/v1/external-oauth/connect", input, externalOauthConnectResponseSchema);
|
|
351
442
|
return { authorizationUrl: authorizationUrl ?? null };
|
|
352
443
|
},
|
|
444
|
+
decideComputeRequest: async ({ requestId, decision }) => post(`/api/v1/compute-requests/${encodeURIComponent(requestId)}/decision`, { decision }, computeDecisionResponseSchema),
|
|
353
445
|
fulfillCredential: async ({ url, body }) => {
|
|
354
446
|
const target = new URL(url, appUrl).toString();
|
|
355
447
|
const res = await fetch(target, {
|
|
@@ -383,6 +475,7 @@ function createRestClient({ appUrl, sessionToken, workspaceId }) {
|
|
|
383
475
|
headers: {
|
|
384
476
|
authorization: `Bearer ${sessionToken}`,
|
|
385
477
|
accept: "text/event-stream",
|
|
478
|
+
[CLI_VERSION_HEADER]: version,
|
|
386
479
|
...workspaceId ? { "x-workspace-id": workspaceId } : {}
|
|
387
480
|
},
|
|
388
481
|
signal
|
|
@@ -434,23 +527,34 @@ const agentSummarySchema = z.object({
|
|
|
434
527
|
createdAt: z.string(),
|
|
435
528
|
creatorName: z.string().nullable().optional(),
|
|
436
529
|
model: z.string().nullable().optional(),
|
|
437
|
-
modelLocked: z.boolean().optional()
|
|
530
|
+
modelLocked: z.boolean().optional(),
|
|
531
|
+
thinkingLevel: z.string().nullable().optional()
|
|
438
532
|
});
|
|
439
533
|
const platformModelSchema = z.object({
|
|
440
534
|
id: z.string(),
|
|
441
535
|
displayName: z.string(),
|
|
442
536
|
providerDisplay: z.string().optional(),
|
|
443
537
|
reasoning: z.boolean().optional(),
|
|
444
|
-
compliant: z.boolean().optional()
|
|
538
|
+
compliant: z.boolean().optional(),
|
|
539
|
+
thinkingLevels: z.array(z.string()).optional(),
|
|
540
|
+
thinkingLevelLabels: z.record(z.string(), z.string()).optional()
|
|
445
541
|
}).passthrough();
|
|
446
542
|
const listModelsResponseSchema = z.object({ models: z.array(platformModelSchema) });
|
|
447
|
-
const
|
|
543
|
+
const modelEffortPreferencesResponseSchema = z.object({ modelConfigOverrides: z.record(z.string(), z.object({ thinkingLevel: z.string().optional() })).optional() });
|
|
544
|
+
const updateAgentResponseSchema = z.object({ agent: z.object({
|
|
545
|
+
model: z.string().nullable().optional(),
|
|
546
|
+
thinkingLevel: z.string().nullable().optional()
|
|
547
|
+
}).passthrough() });
|
|
448
548
|
const listAgentsResponseSchema = z.object({
|
|
449
549
|
agents: z.array(agentSummarySchema),
|
|
450
550
|
nextCursor: z.string().nullable().optional(),
|
|
451
551
|
totalCount: z.number().nullable().optional()
|
|
452
552
|
});
|
|
453
553
|
const createAgentResponseSchema = z.object({ agent: agentSummarySchema });
|
|
554
|
+
const onboardingConversationResponseSchema = z.object({
|
|
555
|
+
conversationId: z.string(),
|
|
556
|
+
runId: z.string().nullable()
|
|
557
|
+
});
|
|
454
558
|
const getAgentResponseSchema = z.object({ agent: agentSummarySchema });
|
|
455
559
|
const agentSuggestionSchema = z.object({ name: z.string() });
|
|
456
560
|
const suggestAgentResponseSchema = z.object({ suggestion: agentSuggestionSchema.nullable() });
|
|
@@ -469,6 +573,7 @@ const conversationSummarySchema = z.object({
|
|
|
469
573
|
channel: z.string().nullable(),
|
|
470
574
|
channelLabel: z.string().nullable(),
|
|
471
575
|
viewerArchivedAt: z.string().nullable().optional(),
|
|
576
|
+
unread: z.boolean().optional(),
|
|
472
577
|
agent: conversationAgentSchema,
|
|
473
578
|
agents: z.array(conversationAgentSchema).optional()
|
|
474
579
|
});
|
|
@@ -479,7 +584,8 @@ const conversationDetailSchema = z.object({
|
|
|
479
584
|
createdAt: z.string(),
|
|
480
585
|
updatedAt: z.string(),
|
|
481
586
|
channel: z.string().nullable(),
|
|
482
|
-
channelLabel: z.string().nullable()
|
|
587
|
+
channelLabel: z.string().nullable(),
|
|
588
|
+
parentConversationId: z.string().uuid().nullable().optional()
|
|
483
589
|
});
|
|
484
590
|
const conversationTitleSchema = z.object({
|
|
485
591
|
id: z.string().uuid(),
|
|
@@ -527,10 +633,27 @@ const subagentTaskSnapshotSchema = z.object({
|
|
|
527
633
|
status: z.string(),
|
|
528
634
|
title: z.string().nullable(),
|
|
529
635
|
createdAt: z.string(),
|
|
530
|
-
completedAt: z.string().nullable()
|
|
636
|
+
completedAt: z.string().nullable(),
|
|
637
|
+
childConversationId: z.string().nullish(),
|
|
638
|
+
childRunStartedAt: z.string().nullish(),
|
|
639
|
+
contextTokens: z.number().nullish(),
|
|
640
|
+
latestActivity: z.string().nullish()
|
|
531
641
|
});
|
|
532
642
|
const subagentTasksResponseSchema = z.object({ result: z.object({ data: z.object({ subagentTasks: z.array(subagentTaskSnapshotSchema) }) }) });
|
|
533
|
-
const
|
|
643
|
+
const liveStatsResponseSchema = z.object({ stats: z.object({
|
|
644
|
+
sampledAt: z.number(),
|
|
645
|
+
cpuUsedPct: z.number(),
|
|
646
|
+
cpuCount: z.number(),
|
|
647
|
+
memUsedBytes: z.number(),
|
|
648
|
+
memTotalBytes: z.number(),
|
|
649
|
+
diskUsedBytes: z.number(),
|
|
650
|
+
diskTotalBytes: z.number()
|
|
651
|
+
}).nullable() });
|
|
652
|
+
const listMessagesResponseSchema = z.object({
|
|
653
|
+
messages: z.array(uiMessageSchema),
|
|
654
|
+
hasMore: z.boolean().optional(),
|
|
655
|
+
nextBefore: z.string().nullish()
|
|
656
|
+
});
|
|
534
657
|
const workspaceFileSchema = z.object({
|
|
535
658
|
id: z.string(),
|
|
536
659
|
path: z.string(),
|
|
@@ -564,6 +687,7 @@ const finalizeResponseSchema = z.object({
|
|
|
564
687
|
});
|
|
565
688
|
const oauthConnectResponseSchema = z.object({ connectLink: z.string() }).passthrough();
|
|
566
689
|
const externalOauthConnectResponseSchema = z.object({ authorizationUrl: z.string().optional() }).passthrough();
|
|
690
|
+
const computeDecisionResponseSchema = z.object({ status: z.string() }).passthrough();
|
|
567
691
|
const runStreamEventSchema = z.union([z.object({
|
|
568
692
|
kind: z.literal("chunk"),
|
|
569
693
|
chunk: z.record(z.unknown())
|
|
@@ -584,7 +708,8 @@ const conversationStreamEventSchema = z.discriminatedUnion("kind", [
|
|
|
584
708
|
kind: z.literal("agent"),
|
|
585
709
|
id: z.string(),
|
|
586
710
|
model: z.string().nullable(),
|
|
587
|
-
modelLocked: z.boolean()
|
|
711
|
+
modelLocked: z.boolean(),
|
|
712
|
+
thinkingLevel: z.string().nullish()
|
|
588
713
|
}),
|
|
589
714
|
z.object({
|
|
590
715
|
kind: z.literal("run"),
|
|
@@ -602,4 +727,4 @@ const conversationStreamEventSchema = z.discriminatedUnion("kind", [
|
|
|
602
727
|
]);
|
|
603
728
|
|
|
604
729
|
//#endregion
|
|
605
|
-
export {
|
|
730
|
+
export { errorMessage as a, version as c, sendErrorMessage as i, errorDetail as n, isRecord as o, filenameFromContentDisposition as r, name as s, createRestClient as t };
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as sendErrorMessage, n as errorDetail, r as filenameFromContentDisposition, t as createRestClient } from "./rest-B9__Zsuk.mjs";
|
|
3
|
+
import { t as HttpError } from "./http-error-BF2NZZE3.mjs";
|
|
4
|
+
import "./billing-blocked-D3l5kJlX.mjs";
|
|
5
|
+
|
|
6
|
+
export { createRestClient };
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import dns from "node:dns";
|
|
4
|
+
|
|
5
|
+
//#region ../portal-daemon/src/tls-cert.ts
|
|
6
|
+
/**
|
|
7
|
+
* File-backed cert source, configured by environment:
|
|
8
|
+
* SKYDIVE_PORTAL_TLS_KEY / SKYDIVE_PORTAL_TLS_CERT (PEM file paths) and
|
|
9
|
+
* SKYDIVE_PORTAL_TLS_HOST (the name the cert covers). All three or nothing —
|
|
10
|
+
* a partial configuration is reported once and treated as absent, so a typo
|
|
11
|
+
* degrades to today's behavior instead of a crash loop.
|
|
12
|
+
*/
|
|
13
|
+
function envTlsCertSource(env, log) {
|
|
14
|
+
return async () => {
|
|
15
|
+
const keyPath = env.SKYDIVE_PORTAL_TLS_KEY;
|
|
16
|
+
const certPath = env.SKYDIVE_PORTAL_TLS_CERT;
|
|
17
|
+
const hostname = env.SKYDIVE_PORTAL_TLS_HOST;
|
|
18
|
+
if (!keyPath && !certPath && !hostname) return null;
|
|
19
|
+
if (!keyPath || !certPath || !hostname) {
|
|
20
|
+
log("forward: SKYDIVE_PORTAL_TLS_KEY, SKYDIVE_PORTAL_TLS_CERT and SKYDIVE_PORTAL_TLS_HOST must all be set to enable the TLS origin; continuing with http only");
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const [key, cert] = await Promise.all([readFile(keyPath, "utf8"), readFile(certPath, "utf8")]);
|
|
25
|
+
return {
|
|
26
|
+
key,
|
|
27
|
+
cert,
|
|
28
|
+
hostname
|
|
29
|
+
};
|
|
30
|
+
} catch (err) {
|
|
31
|
+
log(`forward: could not read TLS key/cert (${err instanceof Error ? err.message : String(err)}); continuing with http only`);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The default cert source for both tunnel surfaces: an explicit env/file
|
|
38
|
+
* configuration wins (BYO cert — also the escape hatch for machines where the
|
|
39
|
+
* trust prompt is unwanted, e.g. CI), otherwise the fully automatic
|
|
40
|
+
* devcert-backed localhost source.
|
|
41
|
+
*/
|
|
42
|
+
function defaultTlsCertSource(env, log) {
|
|
43
|
+
const fromEnv = envTlsCertSource(env, log);
|
|
44
|
+
return async () => {
|
|
45
|
+
if (env.SKYDIVE_PORTAL_TLS_KEY || env.SKYDIVE_PORTAL_TLS_CERT || env.SKYDIVE_PORTAL_TLS_HOST) return fromEnv();
|
|
46
|
+
const { localhostCertSource } = await import("./localhost-cert-Bn-UBUmj.mjs");
|
|
47
|
+
return localhostCertSource(log)();
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Detect resolver-level DNS rebind protection: some routers/resolvers refuse
|
|
52
|
+
* public names that answer with loopback addresses (Plex documents this exact
|
|
53
|
+
* failure for *.plex.direct). The https origin simply won't resolve for such
|
|
54
|
+
* users — warn with the cause, and leave the http origin (unaffected) as the
|
|
55
|
+
* fallback. Never fatal: the browser may use a different resolver than the
|
|
56
|
+
* OS, so we start the listener regardless.
|
|
57
|
+
*/
|
|
58
|
+
async function warnOnRebindProtection(hostname, log) {
|
|
59
|
+
try {
|
|
60
|
+
if (!(await dns.promises.lookup(hostname, { all: true })).some((a) => a.address === "127.0.0.1" || a.address === "::1")) log(`forward: ${hostname} did not resolve to 127.0.0.1 — a resolver on this network may block public names pointing at loopback (DNS rebind protection). The https origin may not work here; http://localhost is unaffected.`);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
log(`forward: ${hostname} did not resolve (${error instanceof Error ? error.message : String(error)}) — a resolver on this network may block public names pointing at loopback (DNS rebind protection). The https origin may not work here; http://localhost is unaffected.`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
//#endregion
|
|
67
|
+
export { envTlsCertSource as n, warnOnRebindProtection as r, defaultTlsCertSource as t };
|
package/package.json
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skydive-cli",
|
|
3
|
-
"version": "0.5.0
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Skydive CLI — cloud agents from the command line",
|
|
5
5
|
"homepage": "https://skydive.com",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"bin": {
|
|
8
|
-
"skydive": "./dist/js/
|
|
8
|
+
"skydive": "./dist/js/launcher.mjs"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist"
|
|
12
12
|
],
|
|
13
13
|
"type": "module",
|
|
14
14
|
"exports": {
|
|
15
|
-
"./bin": "./dist/js/bin.mjs"
|
|
15
|
+
"./bin": "./dist/js/bin.mjs",
|
|
16
|
+
"./launcher": "./dist/js/launcher.mjs"
|
|
16
17
|
},
|
|
17
18
|
"publishConfig": {
|
|
18
19
|
"access": "public",
|
|
@@ -41,7 +42,7 @@
|
|
|
41
42
|
"react-devtools-core": "^7.0.1",
|
|
42
43
|
"safe-stable-stringify": "^2.3.1",
|
|
43
44
|
"semver": "^7.7.4",
|
|
44
|
-
"
|
|
45
|
+
"tar": "7.5.21",
|
|
45
46
|
"web-tree-sitter": "0.25.10",
|
|
46
47
|
"ws": "^8.21.0",
|
|
47
48
|
"yargs": "^17.7.2",
|
|
@@ -66,5 +67,12 @@
|
|
|
66
67
|
"engines": {
|
|
67
68
|
"bun": ">=1.3.14",
|
|
68
69
|
"node": ">=20.0.0"
|
|
70
|
+
},
|
|
71
|
+
"//optionalDependencies": "The per-platform binary packages (skydive-cli-<os>-<arch>) are INTENTIONALLY not committed here. They are published by release-skydive-cli-binaries.yml and injected — pinned to the exact version being published — into the published launcher's optionalDependencies at release time (scripts/prepare-platform-packages.mjs --pin-version, run in release-skydive-cli.yml). Committing them would make `yarn install` try to resolve versions that only exist post-publish, breaking local dev and CI. npm installs of a published skydive-cli still get them (os/cpu-gated); the launcher (src/launcher.ts) resolves and execs the matching one. Local dev never needs them: it runs the JS bundle or `build:binary` directly.",
|
|
72
|
+
"optionalDependencies": {
|
|
73
|
+
"skydive-cli-darwin-arm64": "0.5.0",
|
|
74
|
+
"skydive-cli-darwin-x64": "0.5.0",
|
|
75
|
+
"skydive-cli-linux-x64": "0.5.0",
|
|
76
|
+
"skydive-cli-linux-arm64": "0.5.0"
|
|
69
77
|
}
|
|
70
78
|
}
|
package/dist/js/api-DG5W6iwx.mjs
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
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 };
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import "./client-DabRpc_T.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-k2kVkJ8D.mjs";
|
|
4
|
-
import "./api-DG5W6iwx.mjs";
|
|
5
|
-
|
|
6
|
-
export { runPortalDaemon };
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { t as fetchForwardTarget } from "./api-DG5W6iwx.mjs";
|
|
3
|
-
import net from "node:net";
|
|
4
|
-
import { WebSocket, createWebSocketStream } from "ws";
|
|
5
|
-
|
|
6
|
-
//#region src/chat/portal/forward.ts
|
|
7
|
-
const TARGET_REFRESH_SAFETY_MS = 12e4;
|
|
8
|
-
/**
|
|
9
|
-
* The reverse portal's client half: listen on the local machine's loopback and
|
|
10
|
-
* pipe each TCP connection to the agent sandbox's daemon (`/portal/tcp`),
|
|
11
|
-
* which pipes to the sandbox's own loopback. The daemon is reached through the
|
|
12
|
-
* agent-webserver edge Worker (sandboxes have public ingress disabled; the
|
|
13
|
-
* Worker owns boot-resolution and injects the sandbox edge-auth token), so a
|
|
14
|
-
* sandbox recycle just costs the next connection a cold-start wait rather
|
|
15
|
-
* than invalidating the forward.
|
|
16
|
-
*/
|
|
17
|
-
async function startForward({ auth, agentId, localPort, targetPort, log }) {
|
|
18
|
-
let target = await fetchForwardTarget(auth, agentId);
|
|
19
|
-
let mintedAt = Date.now();
|
|
20
|
-
async function freshTarget() {
|
|
21
|
-
const ttlMs = target.expiresInSeconds * 1e3;
|
|
22
|
-
if (Date.now() - mintedAt > ttlMs - TARGET_REFRESH_SAFETY_MS) {
|
|
23
|
-
target = await fetchForwardTarget(auth, agentId);
|
|
24
|
-
mintedAt = Date.now();
|
|
25
|
-
}
|
|
26
|
-
return target;
|
|
27
|
-
}
|
|
28
|
-
const server = net.createServer((sock) => {
|
|
29
|
-
sock.pause();
|
|
30
|
-
(async () => {
|
|
31
|
-
let resolved;
|
|
32
|
-
try {
|
|
33
|
-
resolved = await freshTarget();
|
|
34
|
-
} catch (err) {
|
|
35
|
-
log(`forward: token refresh failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
36
|
-
sock.destroy();
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const ws = new WebSocket(`${resolved.daemonOrigin.replace(/^http/, "ws")}/portal/tcp?port=${targetPort}`, { headers: { authorization: `Bearer ${resolved.token}` } });
|
|
40
|
-
ws.on("open", () => {
|
|
41
|
-
const stream = createWebSocketStream(ws);
|
|
42
|
-
stream.on("error", () => sock.destroy());
|
|
43
|
-
sock.on("error", () => stream.destroy());
|
|
44
|
-
sock.pipe(stream).pipe(sock);
|
|
45
|
-
sock.resume();
|
|
46
|
-
});
|
|
47
|
-
ws.on("error", (err) => {
|
|
48
|
-
log(`forward: tunnel connect failed: ${err.message}`);
|
|
49
|
-
sock.destroy();
|
|
50
|
-
});
|
|
51
|
-
})();
|
|
52
|
-
});
|
|
53
|
-
await new Promise((resolve, reject) => {
|
|
54
|
-
server.once("error", reject);
|
|
55
|
-
server.listen(localPort, "127.0.0.1", () => {
|
|
56
|
-
server.removeListener("error", reject);
|
|
57
|
-
resolve();
|
|
58
|
-
});
|
|
59
|
-
});
|
|
60
|
-
const addr = server.address();
|
|
61
|
-
return {
|
|
62
|
-
port: addr && typeof addr === "object" ? addr.port : localPort,
|
|
63
|
-
close: () => new Promise((resolve) => server.close(() => resolve()))
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
//#endregion
|
|
68
|
-
export { startForward };
|