@agentsky/sky 2.11.0 → 2.13.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/dist/{chunk-WC7GJSAJ.js → chunk-2IKEJ74C.js} +1013 -39
- package/dist/chunk-XJ5JLLXM.js +168 -0
- package/dist/{chunk-RWPPIEHH.js → chunk-Z3FPPGWX.js} +1 -1
- package/dist/index.js +821 -290
- package/dist/products.d.ts +6 -0
- package/dist/products.js +1 -1
- package/dist/{src-HVCP3PPM.js → src-3AI7XR44.js} +20 -2
- package/dist/{trace-MPGQYXNR.js → trace-DS35CFKV.js} +2 -2
- package/dist/trace-library-B3NZ4OQM.js +730 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
ApiError,
|
|
4
|
+
api,
|
|
5
|
+
apiBytes,
|
|
6
|
+
apiForm,
|
|
7
|
+
downloadFile,
|
|
8
|
+
postEvents,
|
|
9
|
+
resolveSessionRef,
|
|
10
|
+
streamEvents,
|
|
11
|
+
uploadFile
|
|
12
|
+
} from "./chunk-XJ5JLLXM.js";
|
|
2
13
|
import {
|
|
3
14
|
CLI_VERSION,
|
|
4
15
|
USER_AGENT,
|
|
@@ -9,12 +20,13 @@ import {
|
|
|
9
20
|
currentUniverse,
|
|
10
21
|
loadConfig,
|
|
11
22
|
saveConfig
|
|
12
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-Z3FPPGWX.js";
|
|
13
24
|
import {
|
|
14
25
|
AGENT_TYPES,
|
|
15
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
16
27
|
MODEL_SUBSCRIPTION_PROVIDER_NAME,
|
|
17
28
|
REASONING_EFFORT_VALUES,
|
|
29
|
+
SessionSourceCollector,
|
|
18
30
|
capabilitiesSchema,
|
|
19
31
|
customDataSchema,
|
|
20
32
|
defaultLlm,
|
|
@@ -28,165 +40,15 @@ import {
|
|
|
28
40
|
skillRefSchema,
|
|
29
41
|
splitMcpServers,
|
|
30
42
|
supersededBy
|
|
31
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-2IKEJ74C.js";
|
|
32
44
|
import "./chunk-MLKGABMK.js";
|
|
33
45
|
|
|
34
46
|
// src/index.ts
|
|
35
|
-
import { existsSync as
|
|
36
|
-
import { basename as
|
|
47
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12, writeFileSync as writeFileSync6 } from "fs";
|
|
48
|
+
import { basename as basename5, resolve as resolve7 } from "path";
|
|
37
49
|
import { createInterface as createInterface4 } from "readline";
|
|
38
50
|
import { Command } from "commander";
|
|
39
51
|
|
|
40
|
-
// src/api.ts
|
|
41
|
-
var ApiError = class extends Error {
|
|
42
|
-
constructor(status, code, message2) {
|
|
43
|
-
super(message2);
|
|
44
|
-
this.status = status;
|
|
45
|
-
this.code = code;
|
|
46
|
-
}
|
|
47
|
-
status;
|
|
48
|
-
code;
|
|
49
|
-
};
|
|
50
|
-
function authHeaders() {
|
|
51
|
-
const token = apiToken();
|
|
52
|
-
if (!token) {
|
|
53
|
-
throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
|
|
54
|
-
}
|
|
55
|
-
const headers = { authorization: `Bearer ${token}` };
|
|
56
|
-
const universe2 = currentUniverse();
|
|
57
|
-
if (universe2) headers["x-universe"] = universe2;
|
|
58
|
-
return headers;
|
|
59
|
-
}
|
|
60
|
-
async function parseError(res) {
|
|
61
|
-
let code = "http_error";
|
|
62
|
-
let message2 = `${res.status} ${res.statusText}`;
|
|
63
|
-
try {
|
|
64
|
-
const body = await res.json();
|
|
65
|
-
if (body.error?.message) {
|
|
66
|
-
code = body.error.code ?? code;
|
|
67
|
-
message2 = body.error.message;
|
|
68
|
-
}
|
|
69
|
-
} catch {
|
|
70
|
-
}
|
|
71
|
-
throw new ApiError(res.status, code, message2);
|
|
72
|
-
}
|
|
73
|
-
async function api(method, path, body, opts = {}) {
|
|
74
|
-
const res = await fetch(`${apiBase()}${path}`, {
|
|
75
|
-
method,
|
|
76
|
-
headers: {
|
|
77
|
-
"user-agent": USER_AGENT,
|
|
78
|
-
...opts.auth === false ? {} : authHeaders(),
|
|
79
|
-
...body !== void 0 ? { "content-type": "application/json" } : {},
|
|
80
|
-
...opts.idempotencyKey ? { "idempotency-key": opts.idempotencyKey } : {}
|
|
81
|
-
},
|
|
82
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
83
|
-
});
|
|
84
|
-
if (!res.ok) await parseError(res);
|
|
85
|
-
if (res.status === 204) return void 0;
|
|
86
|
-
return await res.json();
|
|
87
|
-
}
|
|
88
|
-
async function postEvents(sessionId, events, idempotencyKey) {
|
|
89
|
-
const res = await fetch(`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/events`, {
|
|
90
|
-
method: "POST",
|
|
91
|
-
headers: {
|
|
92
|
-
"user-agent": USER_AGENT,
|
|
93
|
-
...authHeaders(),
|
|
94
|
-
"content-type": "application/json",
|
|
95
|
-
...idempotencyKey ? { "idempotency-key": idempotencyKey } : {}
|
|
96
|
-
},
|
|
97
|
-
body: JSON.stringify({ events })
|
|
98
|
-
});
|
|
99
|
-
if (!res.ok) await parseError(res);
|
|
100
|
-
return await res.json();
|
|
101
|
-
}
|
|
102
|
-
async function uploadFile(bytes, filename, mediaType, expiresInSeconds) {
|
|
103
|
-
const form = new FormData();
|
|
104
|
-
form.append("file", new Blob([new Uint8Array(bytes)], { type: mediaType }), filename);
|
|
105
|
-
if (expiresInSeconds) form.append("expires_in_seconds", String(expiresInSeconds));
|
|
106
|
-
const res = await fetch(`${apiBase()}/files`, {
|
|
107
|
-
method: "POST",
|
|
108
|
-
headers: { "user-agent": USER_AGENT, ...authHeaders() },
|
|
109
|
-
body: form
|
|
110
|
-
});
|
|
111
|
-
if (!res.ok) await parseError(res);
|
|
112
|
-
return await res.json();
|
|
113
|
-
}
|
|
114
|
-
async function apiForm(method, path, form) {
|
|
115
|
-
const res = await fetch(`${apiBase()}${path}`, {
|
|
116
|
-
method,
|
|
117
|
-
headers: { "user-agent": USER_AGENT, ...authHeaders() },
|
|
118
|
-
body: form
|
|
119
|
-
});
|
|
120
|
-
if (!res.ok) await parseError(res);
|
|
121
|
-
return await res.json();
|
|
122
|
-
}
|
|
123
|
-
async function downloadFile(fileId) {
|
|
124
|
-
const res = await fetch(`${apiBase()}/files/${encodeURIComponent(fileId)}/content`, {
|
|
125
|
-
headers: { "user-agent": USER_AGENT, ...authHeaders() }
|
|
126
|
-
});
|
|
127
|
-
if (!res.ok) await parseError(res);
|
|
128
|
-
return { bytes: new Uint8Array(await res.arrayBuffer()), mediaType: res.headers.get("content-type") ?? "application/octet-stream" };
|
|
129
|
-
}
|
|
130
|
-
async function apiBytes(method, path, body) {
|
|
131
|
-
const res = await fetch(`${apiBase()}${path}`, {
|
|
132
|
-
method,
|
|
133
|
-
headers: {
|
|
134
|
-
"user-agent": USER_AGENT,
|
|
135
|
-
...authHeaders(),
|
|
136
|
-
...body ? { "content-type": body.mediaType } : {}
|
|
137
|
-
},
|
|
138
|
-
body: body ? new Uint8Array(body.bytes) : void 0
|
|
139
|
-
});
|
|
140
|
-
if (!res.ok) await parseError(res);
|
|
141
|
-
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
142
|
-
return {
|
|
143
|
-
bytes,
|
|
144
|
-
mediaType: res.headers.get("content-type") ?? "application/octet-stream",
|
|
145
|
-
json: () => JSON.parse(new TextDecoder().decode(bytes))
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
async function resolveSessionRef(ref) {
|
|
149
|
-
if (ref.startsWith("sess-")) return ref;
|
|
150
|
-
const { agent: agent2 } = await api(
|
|
151
|
-
"GET",
|
|
152
|
-
`/agents/${encodeURIComponent(ref)}`
|
|
153
|
-
).catch(async () => {
|
|
154
|
-
await api("GET", `/sessions/${encodeURIComponent(ref)}`);
|
|
155
|
-
return { agent: { id: ref, defaultSessionId: ref } };
|
|
156
|
-
});
|
|
157
|
-
if (!agent2.defaultSessionId) {
|
|
158
|
-
throw new ApiError(
|
|
159
|
-
404,
|
|
160
|
-
"no_sessions",
|
|
161
|
-
`agent ${agent2.id} has no sessions \u2014 create one: sky session create ${agent2.id}`
|
|
162
|
-
);
|
|
163
|
-
}
|
|
164
|
-
return agent2.defaultSessionId;
|
|
165
|
-
}
|
|
166
|
-
async function* streamEvents(sessionId, signal) {
|
|
167
|
-
const { parseSseStream } = await import("./src-HVCP3PPM.js");
|
|
168
|
-
const res = await fetch(
|
|
169
|
-
`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
|
|
170
|
-
{
|
|
171
|
-
headers: { "user-agent": USER_AGENT, ...authHeaders(), accept: "text/event-stream" },
|
|
172
|
-
signal
|
|
173
|
-
}
|
|
174
|
-
);
|
|
175
|
-
if (!res.ok) await parseError(res);
|
|
176
|
-
if (!res.body) throw new ApiError(502, "no_body", "stream returned no body");
|
|
177
|
-
try {
|
|
178
|
-
for await (const frame of parseSseStream(res.body)) {
|
|
179
|
-
if (!frame.data) continue;
|
|
180
|
-
try {
|
|
181
|
-
yield { id: frame.id, event: frame.event, data: JSON.parse(frame.data) };
|
|
182
|
-
} catch {
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
} catch (error) {
|
|
186
|
-
if (!(error instanceof DOMException && error.name === "AbortError")) throw error;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
52
|
// src/files.ts
|
|
191
53
|
import { readFileSync } from "fs";
|
|
192
54
|
import { basename, extname } from "path";
|
|
@@ -306,6 +168,123 @@ function renderGoal(goal2) {
|
|
|
306
168
|
return lines.join("\n");
|
|
307
169
|
}
|
|
308
170
|
|
|
171
|
+
// src/account.ts
|
|
172
|
+
var DOLLARS = /^\d+(?:\.\d{1,2})?$/;
|
|
173
|
+
function dollarsToCents(flag, value) {
|
|
174
|
+
const trimmed = value.trim().replace(/^\$/, "");
|
|
175
|
+
if (!DOLLARS.test(trimmed)) throw new Error(`${flag} must be a dollar amount like 20 or 20.00, got ${JSON.stringify(value)}`);
|
|
176
|
+
const [whole, fraction = ""] = trimmed.split(".");
|
|
177
|
+
const cents = Number(whole) * 100 + Number(fraction.padEnd(2, "0"));
|
|
178
|
+
if (cents <= 0) throw new Error(`${flag} must be more than $0`);
|
|
179
|
+
return String(cents);
|
|
180
|
+
}
|
|
181
|
+
function wholeDollars(flag, value) {
|
|
182
|
+
const trimmed = value.trim().replace(/^\$/, "");
|
|
183
|
+
if (!/^\d+$/.test(trimmed)) throw new Error(`${flag} must be whole dollars, got ${JSON.stringify(value)}`);
|
|
184
|
+
return Number(trimmed);
|
|
185
|
+
}
|
|
186
|
+
function autoTopupBody(opts) {
|
|
187
|
+
if (opts.on && opts.off) throw new Error("pass --on or --off, not both");
|
|
188
|
+
const body = {};
|
|
189
|
+
if (opts.on) body.enabled = true;
|
|
190
|
+
if (opts.off) body.enabled = false;
|
|
191
|
+
if (opts.threshold !== void 0) body.threshold_usd = wholeDollars("--threshold", opts.threshold);
|
|
192
|
+
if (opts.amount !== void 0) body.amount_usd = wholeDollars("--amount", opts.amount);
|
|
193
|
+
return Object.keys(body).length ? body : null;
|
|
194
|
+
}
|
|
195
|
+
var usd = (value) => `$${value.toFixed(2)}`;
|
|
196
|
+
var centsUsd = (money) => `$${(Number(money.amount) / 100).toFixed(2)}`;
|
|
197
|
+
function renderAutoTopup(settings) {
|
|
198
|
+
const state = settings.enabled ? `on \u2014 below ${usd(settings.threshold_usd)}, the saved card adds ${usd(settings.amount_usd)}` : `off${settings.opted_out ? " (you turned it off)" : ""} \u2014 would add ${usd(settings.amount_usd)} below ${usd(settings.threshold_usd)}`;
|
|
199
|
+
const lines = [`auto top-up: ${state}`];
|
|
200
|
+
if (settings.card_on_file === false) lines.push(" no saved card \u2014 `sky billing card` adds one");
|
|
201
|
+
if (settings.failure) lines.push(` last charge failed${settings.failure.paused ? " (paused)" : ""}: ${settings.failure.reason}`);
|
|
202
|
+
return lines.join("\n");
|
|
203
|
+
}
|
|
204
|
+
function renderBalance(balance) {
|
|
205
|
+
return `balance: ${usd(balance.balance_usd)} card on file: ${balance.card_on_file ? "yes" : "no"}`;
|
|
206
|
+
}
|
|
207
|
+
function renderTopup(result) {
|
|
208
|
+
return result.charged ? `Charged your saved card. Balance: ${centsUsd(result.balance)}` : `Already bought with this request id \u2014 not charged again. Balance: ${centsUsd(result.balance)}`;
|
|
209
|
+
}
|
|
210
|
+
function measureLine(m) {
|
|
211
|
+
return `${String(m.calls).padStart(6)} calls ${String(m.inputTokens).padStart(10)} in ${String(m.outputTokens).padStart(9)} out ${`$${m.billedCostUsd.toFixed(4)}`.padStart(10)}`;
|
|
212
|
+
}
|
|
213
|
+
function renderUsage(summary) {
|
|
214
|
+
const lines = [`usage ${summary.range.from} \u2192 ${summary.range.to} (end exclusive), by ${summary.groupBy}`];
|
|
215
|
+
for (const group of summary.groups) {
|
|
216
|
+
lines.push(` ${group.label.padEnd(28).slice(0, 28)} ${group.source.padEnd(8)} ${measureLine(group)}`);
|
|
217
|
+
}
|
|
218
|
+
if (!summary.groups.length) lines.push(" no usage in this range");
|
|
219
|
+
lines.push(` ${"total".padEnd(37)} ${measureLine(summary.totals)}`);
|
|
220
|
+
return lines.join("\n");
|
|
221
|
+
}
|
|
222
|
+
function usageQuery(opts) {
|
|
223
|
+
const params = new URLSearchParams();
|
|
224
|
+
if (opts.from) params.set("from", opts.from);
|
|
225
|
+
if (opts.to) params.set("to", opts.to);
|
|
226
|
+
if (opts.groupBy) params.set("group_by", opts.groupBy);
|
|
227
|
+
if (opts.source) params.set("source", opts.source);
|
|
228
|
+
if (opts.key) params.set("api_key_id", opts.key);
|
|
229
|
+
if (opts.format) params.set("format", opts.format);
|
|
230
|
+
const query = params.toString();
|
|
231
|
+
return query ? `?${query}` : "";
|
|
232
|
+
}
|
|
233
|
+
function apiKeyBody(name, opts) {
|
|
234
|
+
const scopes = (opts.scopes ?? "read,write").split(",").map((scope) => scope.trim()).filter(Boolean);
|
|
235
|
+
const unknown = scopes.find((scope) => !["read", "write", "admin"].includes(scope));
|
|
236
|
+
if (unknown) throw new Error(`--scopes takes read, write and admin, got ${JSON.stringify(unknown)}`);
|
|
237
|
+
if (!scopes.length) throw new Error("--scopes needs at least one scope");
|
|
238
|
+
return {
|
|
239
|
+
name: name.trim(),
|
|
240
|
+
scopes,
|
|
241
|
+
...opts.workspace !== void 0 ? { universe: opts.workspace === "personal" ? null : opts.workspace } : {},
|
|
242
|
+
...opts.expires !== void 0 ? { expires_at: opts.expires } : {}
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
function renderApiKey(key) {
|
|
246
|
+
const where = key.universe ? `workspace ${key.universe}` : "any workspace";
|
|
247
|
+
const used = key.last_used_at ? `used ${key.last_used_at.slice(0, 10)}` : "never used";
|
|
248
|
+
return `${key.id} ${key.token_prefix}\u2026 ${key.status.padEnd(7)} ${key.scopes.join(",").padEnd(16)} ${where} ${used} ${key.name}${key.current ? " (this key)" : ""}`;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// src/context.ts
|
|
252
|
+
function memoryBody(operation, version) {
|
|
253
|
+
return { ...operation, expectedVersion: version };
|
|
254
|
+
}
|
|
255
|
+
function memoryIndex(value) {
|
|
256
|
+
if (!/^\d+$/.test(value.trim())) throw new Error(`the entry number must be a whole number, got ${JSON.stringify(value)}`);
|
|
257
|
+
return Number(value.trim());
|
|
258
|
+
}
|
|
259
|
+
function renderMemory(document) {
|
|
260
|
+
if (!document.entries.length) return "no saved memory \u2014 `sky memory save <text>` adds an entry";
|
|
261
|
+
return document.entries.map((entry2) => `${String(entry2.index).padStart(3)} ${entry2.text}`).join("\n");
|
|
262
|
+
}
|
|
263
|
+
function searchQuery(query, opts) {
|
|
264
|
+
const params = new URLSearchParams({ q: query });
|
|
265
|
+
params.set("scope", opts.scope ?? "sessions");
|
|
266
|
+
if (opts.limit) params.set("limit", opts.limit);
|
|
267
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
268
|
+
if (opts.since) params.set("since", opts.since);
|
|
269
|
+
if (opts.until) params.set("until", opts.until);
|
|
270
|
+
return `?${params.toString()}`;
|
|
271
|
+
}
|
|
272
|
+
function refArgument(ref) {
|
|
273
|
+
return `'${JSON.stringify(ref).replace(/'/g, "'\\''")}'`;
|
|
274
|
+
}
|
|
275
|
+
function renderContextPage(page) {
|
|
276
|
+
const lines = [];
|
|
277
|
+
for (const { hit } of page.hits) {
|
|
278
|
+
lines.push(`${hit.source.padEnd(8)} ${hit.occurredAt?.slice(0, 10) ?? " "} ${hit.title}`);
|
|
279
|
+
lines.push(` ${hit.snippet.replace(/\s+/g, " ").slice(0, 160)}`);
|
|
280
|
+
lines.push(` sky context read ${refArgument(hit.ref)}`);
|
|
281
|
+
}
|
|
282
|
+
if (!page.hits.length) lines.push("no matches on this page");
|
|
283
|
+
if (page.partial) lines.push(`partial: ${page.coverage ?? "not every source was searched"}`);
|
|
284
|
+
if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
|
|
285
|
+
return lines.join("\n");
|
|
286
|
+
}
|
|
287
|
+
|
|
309
288
|
// src/agent-file.ts
|
|
310
289
|
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
311
290
|
import { dirname, resolve } from "path";
|
|
@@ -841,10 +820,222 @@ async function clone(opts) {
|
|
|
841
820
|
}
|
|
842
821
|
|
|
843
822
|
// src/import-local.ts
|
|
844
|
-
import { closeSync, existsSync as
|
|
845
|
-
import { homedir as
|
|
846
|
-
import { join as
|
|
823
|
+
import { closeSync, existsSync as existsSync4, openSync, readdirSync, readFileSync as readFileSync5, readSync, statSync as statSync2 } from "fs";
|
|
824
|
+
import { homedir as homedir3 } from "os";
|
|
825
|
+
import { join as join3 } from "path";
|
|
847
826
|
import { createHash } from "crypto";
|
|
827
|
+
|
|
828
|
+
// src/import-files.ts
|
|
829
|
+
import { isUtf8 } from "buffer";
|
|
830
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync } from "fs";
|
|
831
|
+
import { homedir as homedir2 } from "os";
|
|
832
|
+
import { dirname as dirname2, extname as extname2, isAbsolute, join as join2, relative, resolve as resolve3, sep, basename as basename3 } from "path";
|
|
833
|
+
var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
|
|
834
|
+
var SECRET_FILE = /(^|\/)(\.env(\..*)?|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\.?credentials(\.json)?|auth\.json|\.claude\.json|id_(rsa|ed25519|ecdsa)[^/]*|.*\.(pem|key|p12|pfx|keystore|jks|kdbx|sqlite|db))$|\/\.ssh\/|\/\.aws\/|\/\.gnupg\/|\/\.docker\/config\.json$|\/Keychains\//i;
|
|
835
|
+
var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
|
|
836
|
+
var INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
|
|
837
|
+
var DOCUMENTS = {
|
|
838
|
+
".pdf": "application/pdf",
|
|
839
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
840
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
841
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
842
|
+
".doc": "application/msword",
|
|
843
|
+
".rtf": "application/rtf",
|
|
844
|
+
".odt": "application/vnd.oasis.opendocument.text"
|
|
845
|
+
};
|
|
846
|
+
var IMAGES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
847
|
+
var TEXT = {
|
|
848
|
+
".md": "text/markdown",
|
|
849
|
+
".mdx": "text/markdown",
|
|
850
|
+
".txt": "text/plain",
|
|
851
|
+
".csv": "text/csv",
|
|
852
|
+
".tsv": "text/tab-separated-values",
|
|
853
|
+
".html": "text/html",
|
|
854
|
+
".htm": "text/html",
|
|
855
|
+
".css": "text/css",
|
|
856
|
+
".scss": "text/css",
|
|
857
|
+
".json": "application/json",
|
|
858
|
+
".jsonl": "application/x-ndjson",
|
|
859
|
+
".ndjson": "application/x-ndjson",
|
|
860
|
+
".yaml": "application/yaml",
|
|
861
|
+
".yml": "application/yaml",
|
|
862
|
+
".toml": "application/toml",
|
|
863
|
+
".xml": "application/xml",
|
|
864
|
+
".sql": "application/sql",
|
|
865
|
+
".svg": "image/svg+xml",
|
|
866
|
+
".js": "application/javascript",
|
|
867
|
+
".mjs": "application/javascript",
|
|
868
|
+
".cjs": "application/javascript",
|
|
869
|
+
".py": "text/x-python",
|
|
870
|
+
".sh": "text/x-shellscript",
|
|
871
|
+
".bash": "text/x-shellscript",
|
|
872
|
+
".zsh": "text/x-shellscript"
|
|
873
|
+
};
|
|
874
|
+
var PLAIN_TEXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts", ".go", ".rs", ".java", ".kt", ".swift", ".rb", ".php", ".c", ".h", ".cc", ".cpp", ".hpp", ".cs", ".scala", ".lua", ".r", ".m", ".tex", ".bib", ".ipynb", ".prisma", ".graphql", ".proto", ".ini", ".cfg", ".conf", ".log", ".rst", ".vue", ".svelte", ".astro", ".tf", ".hcl", ".gradle", ".lock", ".patch", ".diff"]);
|
|
875
|
+
var PLAIN_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Makefile", "Procfile", "Gemfile", "Rakefile", "LICENSE", "README", ".cursorrules", ".gitignore", ".dockerignore", ".editorconfig", ".prettierrc", ".eslintrc"]);
|
|
876
|
+
function classify(path) {
|
|
877
|
+
const ext = extname2(path).toLowerCase();
|
|
878
|
+
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
879
|
+
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
880
|
+
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
881
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename3(path))) return { kind: "text", mediaType: "text/plain" };
|
|
882
|
+
return null;
|
|
883
|
+
}
|
|
884
|
+
var SHELL_READ = /(?:^|[\s;&|(`"'])(?:cat|head|tail|nl|less|more|wc|bat|sed\s+-n\s+(?:'[^']*'|"[^"]*"|\S+))(?:\s+-[\w-]+(?:\s+\d+)?)*\s+((?:~|\.{0,2}\/)?[\w@%+./~-]*[\w-]\.[\w]{1,10}|\/[\w@%+./-]+)/g;
|
|
885
|
+
var PATH_KEYS = ["file_path", "path", "notebook_path", "filePath", "target_file"];
|
|
886
|
+
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
887
|
+
var str = (value) => typeof value === "string" ? value : "";
|
|
888
|
+
function shellText(input) {
|
|
889
|
+
if (typeof input === "string") return input;
|
|
890
|
+
const i = obj(input);
|
|
891
|
+
if (!i) return "";
|
|
892
|
+
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
893
|
+
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
894
|
+
}
|
|
895
|
+
function touchedPaths(records, cwd, home = homedir2()) {
|
|
896
|
+
const out = /* @__PURE__ */ new Set();
|
|
897
|
+
const add = (raw, base) => {
|
|
898
|
+
if (!raw || raw.length > 1e3) return;
|
|
899
|
+
const path = raw.replace(/^~(?=\/)/, home);
|
|
900
|
+
if (isAbsolute(path)) out.add(resolve3(path));
|
|
901
|
+
else if (base) out.add(resolve3(base, path));
|
|
902
|
+
};
|
|
903
|
+
const call = (name, input) => {
|
|
904
|
+
const i = obj(input);
|
|
905
|
+
const base = str(i?.workdir) || str(i?.cwd) || cwd;
|
|
906
|
+
if (i) for (const key of PATH_KEYS) add(str(i[key]), base);
|
|
907
|
+
const patch = typeof input === "string" ? input : str(i?.input) || str(i?.patch);
|
|
908
|
+
for (const match of patch.matchAll(/^\*\*\* (?:Add|Update) File: (.+)$/gm)) add(match[1].trim(), base);
|
|
909
|
+
if (!/bash|shell|exec|terminal|command|run/i.test(name)) return;
|
|
910
|
+
for (const match of shellText(input).replace(/\\n/g, "\n").matchAll(SHELL_READ)) add(match[1], base);
|
|
911
|
+
};
|
|
912
|
+
for (const raw of records) {
|
|
913
|
+
const record = obj(raw);
|
|
914
|
+
if (!record) continue;
|
|
915
|
+
const content = obj(record.message)?.content;
|
|
916
|
+
if (record.type === "assistant" && Array.isArray(content)) {
|
|
917
|
+
for (const block of content) {
|
|
918
|
+
const b = obj(block);
|
|
919
|
+
if (b?.type === "tool_use") call(str(b.name), b.input);
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
const payload = obj(record.payload);
|
|
923
|
+
if (record.type === "response_item" && (payload?.type === "function_call" || payload?.type === "custom_tool_call")) {
|
|
924
|
+
let input = payload.arguments ?? payload.input ?? "";
|
|
925
|
+
if (typeof input === "string") {
|
|
926
|
+
try {
|
|
927
|
+
input = JSON.parse(input);
|
|
928
|
+
} catch {
|
|
929
|
+
input = payload.arguments ?? payload.input;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
call(str(payload.name), input);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return [...out];
|
|
936
|
+
}
|
|
937
|
+
function gitRoot(dir) {
|
|
938
|
+
for (let d = dir; ; d = dirname2(d)) {
|
|
939
|
+
if (existsSync3(join2(d, ".git"))) return d;
|
|
940
|
+
if (d === dirname2(d)) return null;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
function instructionPaths(cwd, harness2, home = homedir2()) {
|
|
944
|
+
const out = [];
|
|
945
|
+
if (cwd && existsSync3(cwd)) {
|
|
946
|
+
const root = gitRoot(cwd) ?? cwd;
|
|
947
|
+
for (let d = resolve3(cwd); ; d = dirname2(d)) {
|
|
948
|
+
for (const name of INSTRUCTION_FILES) out.push(join2(d, name));
|
|
949
|
+
if (d === root || d === dirname2(d)) break;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
if (harness2 === "claude_code") out.push(join2(process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude"), "CLAUDE.md"));
|
|
953
|
+
if (harness2 === "codex") out.push(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "AGENTS.md"));
|
|
954
|
+
return out.filter((path) => {
|
|
955
|
+
try {
|
|
956
|
+
return statSync(path).isFile();
|
|
957
|
+
} catch {
|
|
958
|
+
return false;
|
|
959
|
+
}
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
function resourceName(path, cwd, home = homedir2()) {
|
|
963
|
+
const inside = cwd ? relative(resolve3(cwd), path) : "";
|
|
964
|
+
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep).join("/")}`;
|
|
965
|
+
const underHome = relative(home, path);
|
|
966
|
+
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
967
|
+
return `files/${shown.split(sep).join("/")}`;
|
|
968
|
+
}
|
|
969
|
+
var ORDER = { text: 0, document: 1, image: 2 };
|
|
970
|
+
function collectSessionFiles(records, opts) {
|
|
971
|
+
const home = opts.home ?? homedir2();
|
|
972
|
+
const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
|
|
973
|
+
const leftOut = [];
|
|
974
|
+
const candidates = [];
|
|
975
|
+
const seen = /* @__PURE__ */ new Set();
|
|
976
|
+
for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touchedPaths(records, opts.cwd, home)]) {
|
|
977
|
+
if (seen.has(path) || exclude.has(path)) continue;
|
|
978
|
+
seen.add(path);
|
|
979
|
+
if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
|
|
980
|
+
leftOut.push({ path, reason: "credential or build file" });
|
|
981
|
+
continue;
|
|
982
|
+
}
|
|
983
|
+
let size;
|
|
984
|
+
try {
|
|
985
|
+
const stat = statSync(path);
|
|
986
|
+
if (!stat.isFile()) continue;
|
|
987
|
+
size = stat.size;
|
|
988
|
+
} catch {
|
|
989
|
+
leftOut.push({ path, reason: "missing" });
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
const type = classify(path);
|
|
993
|
+
if (!type) {
|
|
994
|
+
leftOut.push({ path, reason: "unsupported type" });
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
if (size > MAX_ATTACHED_FILE_BYTES) {
|
|
998
|
+
leftOut.push({ path, reason: "too large" });
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
candidates.push({ path, name: resourceName(path, opts.cwd, home), ...type, size });
|
|
1002
|
+
}
|
|
1003
|
+
candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
|
|
1004
|
+
const lines = [];
|
|
1005
|
+
const attached = [];
|
|
1006
|
+
let bytes = 0;
|
|
1007
|
+
for (const file of candidates) {
|
|
1008
|
+
let data;
|
|
1009
|
+
try {
|
|
1010
|
+
data = readFileSync4(file.path);
|
|
1011
|
+
} catch {
|
|
1012
|
+
leftOut.push({ path: file.path, reason: "missing" });
|
|
1013
|
+
continue;
|
|
1014
|
+
}
|
|
1015
|
+
if (file.kind === "text" && data.includes(0)) {
|
|
1016
|
+
leftOut.push({ path: file.path, reason: "not valid UTF-8 text" });
|
|
1017
|
+
continue;
|
|
1018
|
+
}
|
|
1019
|
+
let line;
|
|
1020
|
+
try {
|
|
1021
|
+
line = redactLine(JSON.stringify({ type: "import.resource", name: file.name, media_type: file.mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
1022
|
+
} catch {
|
|
1023
|
+
leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
|
|
1024
|
+
continue;
|
|
1025
|
+
}
|
|
1026
|
+
const size = Buffer.byteLength(line);
|
|
1027
|
+
if (bytes + size > opts.budget) {
|
|
1028
|
+
leftOut.push({ path: file.path, reason: "over the upload budget" });
|
|
1029
|
+
continue;
|
|
1030
|
+
}
|
|
1031
|
+
bytes += size;
|
|
1032
|
+
lines.push(line);
|
|
1033
|
+
attached.push({ name: file.name, kind: file.kind, bytes: data.length });
|
|
1034
|
+
}
|
|
1035
|
+
return { lines, bytes, attached, leftOut };
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/import-local.ts
|
|
848
1039
|
function redactLine2(raw) {
|
|
849
1040
|
return redactLine(raw).line;
|
|
850
1041
|
}
|
|
@@ -916,13 +1107,13 @@ function sniffTitle(filePath) {
|
|
|
916
1107
|
}
|
|
917
1108
|
}
|
|
918
1109
|
function listClaudeCodeSessions(limit) {
|
|
919
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
920
|
-
const projectsDir =
|
|
921
|
-
if (!
|
|
1110
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
|
|
1111
|
+
const projectsDir = join3(root, "projects");
|
|
1112
|
+
if (!existsSync4(projectsDir)) return [];
|
|
922
1113
|
const sessions = [];
|
|
923
1114
|
try {
|
|
924
1115
|
for (const encoded of readdirSync(projectsDir)) {
|
|
925
|
-
const projectDir =
|
|
1116
|
+
const projectDir = join3(projectsDir, encoded);
|
|
926
1117
|
let entries;
|
|
927
1118
|
try {
|
|
928
1119
|
entries = readdirSync(projectDir);
|
|
@@ -933,22 +1124,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
933
1124
|
for (const entry2 of entries) {
|
|
934
1125
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
935
1126
|
const sessionId = entry2.slice(0, -6);
|
|
936
|
-
const mainFile =
|
|
1127
|
+
const mainFile = join3(projectDir, entry2);
|
|
937
1128
|
let stat;
|
|
938
1129
|
try {
|
|
939
|
-
stat =
|
|
1130
|
+
stat = statSync2(mainFile);
|
|
940
1131
|
} catch {
|
|
941
1132
|
continue;
|
|
942
1133
|
}
|
|
943
1134
|
if (!stat.isFile()) continue;
|
|
944
1135
|
let discoveryError;
|
|
945
1136
|
const subagentFiles = [];
|
|
946
|
-
const subagentDir =
|
|
947
|
-
if (
|
|
1137
|
+
const subagentDir = join3(projectDir, sessionId, "subagents");
|
|
1138
|
+
if (existsSync4(subagentDir)) {
|
|
948
1139
|
try {
|
|
949
1140
|
const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
950
1141
|
for (const f of subFiles) {
|
|
951
|
-
subagentFiles.push(
|
|
1142
|
+
subagentFiles.push(join3(subagentDir, f));
|
|
952
1143
|
}
|
|
953
1144
|
} catch {
|
|
954
1145
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -970,7 +1161,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
970
1161
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
971
1162
|
}
|
|
972
1163
|
function listCodexSessions(limit) {
|
|
973
|
-
const sessionsDir =
|
|
1164
|
+
const sessionsDir = join3(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join3(homedir3(), ".codex"), "sessions");
|
|
974
1165
|
const records = [];
|
|
975
1166
|
function walk(dir) {
|
|
976
1167
|
let entries;
|
|
@@ -980,7 +1171,7 @@ function listCodexSessions(limit) {
|
|
|
980
1171
|
return;
|
|
981
1172
|
}
|
|
982
1173
|
for (const entry2 of entries) {
|
|
983
|
-
const full =
|
|
1174
|
+
const full = join3(dir, entry2.name);
|
|
984
1175
|
if (entry2.isDirectory()) {
|
|
985
1176
|
walk(full);
|
|
986
1177
|
continue;
|
|
@@ -998,7 +1189,7 @@ function listCodexSessions(limit) {
|
|
|
998
1189
|
sessionId: data.id,
|
|
999
1190
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
1000
1191
|
title: sniffTitle(full),
|
|
1001
|
-
mtimeMs:
|
|
1192
|
+
mtimeMs: statSync2(full).mtimeMs,
|
|
1002
1193
|
files: [full]
|
|
1003
1194
|
},
|
|
1004
1195
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -1067,7 +1258,7 @@ async function mintTrace(base, harness2) {
|
|
|
1067
1258
|
function loadSummaryFile(summaryPath) {
|
|
1068
1259
|
let raw;
|
|
1069
1260
|
try {
|
|
1070
|
-
raw =
|
|
1261
|
+
raw = readFileSync5(summaryPath, "utf8");
|
|
1071
1262
|
} catch {
|
|
1072
1263
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1073
1264
|
}
|
|
@@ -1084,7 +1275,16 @@ function loadSummaryFile(summaryPath) {
|
|
|
1084
1275
|
const redacted = redactLine2(raw.trimEnd());
|
|
1085
1276
|
return redacted;
|
|
1086
1277
|
}
|
|
1087
|
-
|
|
1278
|
+
var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
|
|
1279
|
+
var SUMMARY_RESERVE_BYTES = 128 * 1024;
|
|
1280
|
+
var MAX_LISTED_ARTIFACTS = 99;
|
|
1281
|
+
function leftOutLine(left) {
|
|
1282
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1283
|
+
for (const file of left) counts.set(file.reason, (counts.get(file.reason) ?? 0) + 1);
|
|
1284
|
+
const files2 = [...counts].map(([reason, n]) => `${n} ${n === 1 ? "file" : "files"} (${reason})`).join(", ");
|
|
1285
|
+
return `Not transferred: ${files2 ? `${files2}; ` : ""}runtime state such as running processes, installed tools and environment variables; and reasoning the source harness stored unreadably.`;
|
|
1286
|
+
}
|
|
1287
|
+
function prepareImport(session2, summaryPath, options = {}) {
|
|
1088
1288
|
if (session2.discoveryError) throw new Error(session2.discoveryError);
|
|
1089
1289
|
const maximum = 16 * 1024 * 1024;
|
|
1090
1290
|
let inputBytes = 0;
|
|
@@ -1096,10 +1296,12 @@ function prepareImport(session2, summaryPath) {
|
|
|
1096
1296
|
let encryptedReasoningCount = 0;
|
|
1097
1297
|
let compactionCount = 0;
|
|
1098
1298
|
let unreadableCount = 0;
|
|
1299
|
+
const source = new SessionSourceCollector();
|
|
1300
|
+
const records = [];
|
|
1099
1301
|
for (const path of session2.files) {
|
|
1100
|
-
const stat =
|
|
1302
|
+
const stat = statSync2(path);
|
|
1101
1303
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
1102
|
-
const text =
|
|
1304
|
+
const text = readFileSync5(path, "utf8");
|
|
1103
1305
|
for (const raw of text.split("\n")) {
|
|
1104
1306
|
if (!raw.trim()) continue;
|
|
1105
1307
|
let record;
|
|
@@ -1112,6 +1314,8 @@ function prepareImport(session2, summaryPath) {
|
|
|
1112
1314
|
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
|
|
1113
1315
|
const rec = record;
|
|
1114
1316
|
if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
|
|
1317
|
+
if (path === session2.files[0]) source.push(rec);
|
|
1318
|
+
records.push(rec);
|
|
1115
1319
|
if (rec.type === "response_item") {
|
|
1116
1320
|
const payload = rec.payload;
|
|
1117
1321
|
if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
|
|
@@ -1135,6 +1339,9 @@ function prepareImport(session2, summaryPath) {
|
|
|
1135
1339
|
if (!lines.length) throw new Error("The selected transcript is empty.");
|
|
1136
1340
|
const sourceId = nativeId ?? session2.sessionId;
|
|
1137
1341
|
if (!/^[A-Za-z0-9_-]{1,200}$/.test(sourceId) || redactLine2(JSON.stringify(sourceId)) !== JSON.stringify(sourceId)) throw new Error("The source conversation has an invalid identifier.");
|
|
1342
|
+
const meta = source.result();
|
|
1343
|
+
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [] } : collectSessionFiles(records, { harness: session2.harness, cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""), budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES, exclude: session2.files });
|
|
1344
|
+
const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under are attached as well (listed in artifacts).` : "";
|
|
1138
1345
|
let summaryJson;
|
|
1139
1346
|
if (summaryPath) {
|
|
1140
1347
|
summaryJson = loadSummaryFile(summaryPath);
|
|
@@ -1143,10 +1350,10 @@ function prepareImport(session2, summaryPath) {
|
|
|
1143
1350
|
format: "asteroids.session-summary",
|
|
1144
1351
|
format_version: 1,
|
|
1145
1352
|
source_harness: session2.harness,
|
|
1146
|
-
source_harness_version: null,
|
|
1353
|
+
source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
|
|
1147
1354
|
source_session_id: sourceId,
|
|
1148
1355
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1149
|
-
summary:
|
|
1356
|
+
summary: `The complete selected local conversation is attached as a transcript.${attachedNote} Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.`,
|
|
1150
1357
|
context: {
|
|
1151
1358
|
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
1152
1359
|
constraints: [],
|
|
@@ -1154,19 +1361,19 @@ function prepareImport(session2, summaryPath) {
|
|
|
1154
1361
|
completed: [],
|
|
1155
1362
|
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1156
1363
|
verification: [],
|
|
1157
|
-
blockers: [
|
|
1158
|
-
artifacts:
|
|
1364
|
+
blockers: [leftOutLine(files2.leftOut)],
|
|
1365
|
+
artifacts: files2.attached.slice(0, MAX_LISTED_ARTIFACTS).map((file) => ({ path: file.name, description: `Attached from the local ${file.kind === "text" ? "workspace" : file.kind} the conversation used (${file.bytes} bytes).` })),
|
|
1159
1366
|
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1160
1367
|
}
|
|
1161
1368
|
};
|
|
1162
1369
|
summaryJson = JSON.stringify(summary);
|
|
1163
1370
|
}
|
|
1164
|
-
const body = lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + 1 }) + "\n";
|
|
1371
|
+
const body = lines.join("") + files2.lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + files2.lines.length + 1 }) + "\n";
|
|
1165
1372
|
const gaps = [];
|
|
1166
1373
|
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1167
1374
|
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1168
1375
|
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1169
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
|
|
1376
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
|
|
1170
1377
|
}
|
|
1171
1378
|
async function importSession(opts) {
|
|
1172
1379
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1175,7 +1382,7 @@ async function importSession(opts) {
|
|
|
1175
1382
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1176
1383
|
}
|
|
1177
1384
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1178
|
-
const { body, transcriptLines, outputBytes, redactionCount, gaps } = prepareImport(opts.session, opts.summaryPath);
|
|
1385
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1179
1386
|
let traceId;
|
|
1180
1387
|
let key;
|
|
1181
1388
|
if (opts.importKey && opts.grantTraceId) {
|
|
@@ -1207,7 +1414,9 @@ async function importSession(opts) {
|
|
|
1207
1414
|
transcriptFiles: opts.session.files.length,
|
|
1208
1415
|
bytes: outputBytes,
|
|
1209
1416
|
redactionCount,
|
|
1210
|
-
gaps
|
|
1417
|
+
gaps,
|
|
1418
|
+
attachedFiles,
|
|
1419
|
+
leftOutFiles
|
|
1211
1420
|
};
|
|
1212
1421
|
}
|
|
1213
1422
|
|
|
@@ -1244,7 +1453,7 @@ async function login(opts) {
|
|
|
1244
1453
|
}
|
|
1245
1454
|
async function browserLogin(verifier, challenge) {
|
|
1246
1455
|
const state = b64url(randomBytes(16));
|
|
1247
|
-
const code = await new Promise((
|
|
1456
|
+
const code = await new Promise((resolve8, reject) => {
|
|
1248
1457
|
const server = createServer((req, res) => {
|
|
1249
1458
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1250
1459
|
if (url.pathname !== "/callback") {
|
|
@@ -1257,7 +1466,7 @@ async function browserLogin(verifier, challenge) {
|
|
|
1257
1466
|
if (url.searchParams.get("state") !== state || !got) {
|
|
1258
1467
|
reject(new Error("login callback state mismatch"));
|
|
1259
1468
|
} else {
|
|
1260
|
-
|
|
1469
|
+
resolve8(got);
|
|
1261
1470
|
}
|
|
1262
1471
|
server.close();
|
|
1263
1472
|
});
|
|
@@ -1311,23 +1520,23 @@ async function headlessLogin(verifier, challenge) {
|
|
|
1311
1520
|
}
|
|
1312
1521
|
|
|
1313
1522
|
// src/local-agents.ts
|
|
1314
|
-
import { existsSync as
|
|
1315
|
-
import { homedir as
|
|
1316
|
-
import { join as
|
|
1317
|
-
function localAgents(env = process.env, home =
|
|
1318
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
1319
|
-
const codex = env.CODEX_HOME ||
|
|
1320
|
-
const cursor =
|
|
1523
|
+
import { existsSync as existsSync5 } from "fs";
|
|
1524
|
+
import { homedir as homedir4 } from "os";
|
|
1525
|
+
import { join as join4 } from "path";
|
|
1526
|
+
function localAgents(env = process.env, home = homedir4()) {
|
|
1527
|
+
const claude = env.CLAUDE_CONFIG_DIR || join4(home, ".claude");
|
|
1528
|
+
const codex = env.CODEX_HOME || join4(home, ".codex");
|
|
1529
|
+
const cursor = join4(home, ".cursor");
|
|
1321
1530
|
return [
|
|
1322
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir:
|
|
1323
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
1324
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir:
|
|
1531
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join4(claude, "skills") },
|
|
1532
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join4(codex, "skills") },
|
|
1533
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join4(cursor, "skills") }
|
|
1325
1534
|
];
|
|
1326
1535
|
}
|
|
1327
|
-
function detectLocalAgents(env = process.env, home =
|
|
1328
|
-
return localAgents(env, home).filter((agent2) =>
|
|
1536
|
+
function detectLocalAgents(env = process.env, home = homedir4()) {
|
|
1537
|
+
return localAgents(env, home).filter((agent2) => existsSync5(agent2.root));
|
|
1329
1538
|
}
|
|
1330
|
-
function selectLocalAgents(ids, env = process.env, home =
|
|
1539
|
+
function selectLocalAgents(ids, env = process.env, home = homedir4()) {
|
|
1331
1540
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
1332
1541
|
const known = localAgents(env, home);
|
|
1333
1542
|
return ids.map((id) => {
|
|
@@ -1421,21 +1630,21 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
1421
1630
|
for (; ; ) {
|
|
1422
1631
|
const reading = latestTurn(await history(sessionId));
|
|
1423
1632
|
if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
|
|
1424
|
-
await new Promise((
|
|
1633
|
+
await new Promise((resolve8) => setTimeout(resolve8, opts.intervalMs ?? 3e3));
|
|
1425
1634
|
}
|
|
1426
1635
|
}
|
|
1427
1636
|
|
|
1428
1637
|
// src/setup.ts
|
|
1429
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1430
|
-
import { dirname as
|
|
1638
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
1639
|
+
import { dirname as dirname4, join as join8 } from "path";
|
|
1431
1640
|
import { createInterface as createInterface3 } from "readline";
|
|
1432
1641
|
|
|
1433
1642
|
// src/mcp-register.ts
|
|
1434
1643
|
import { spawnSync } from "child_process";
|
|
1435
1644
|
|
|
1436
1645
|
// src/skill.ts
|
|
1437
|
-
import { mkdirSync, readFileSync as
|
|
1438
|
-
import { join as
|
|
1646
|
+
import { mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1647
|
+
import { join as join5 } from "path";
|
|
1439
1648
|
|
|
1440
1649
|
// src/catalog-file.ts
|
|
1441
1650
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -1511,7 +1720,7 @@ at the end; they do the same things, one agent at a time.
|
|
|
1511
1720
|
|
|
1512
1721
|
Use the workspace the person named. If they named none, check the git root for
|
|
1513
1722
|
\`.agentsky.json\` \u2014 if it has a \`"workspace"\` key, use that. Otherwise use the
|
|
1514
|
-
account default
|
|
1723
|
+
account default workspace. Never ask. Pass the chosen slug as \`workspace\` on
|
|
1515
1724
|
every MCP tool that accepts it, or as \`--workspace <slug>\` on sky commands.
|
|
1516
1725
|
Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
|
|
1517
1726
|
|
|
@@ -1876,7 +2085,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
1876
2085
|
}
|
|
1877
2086
|
function installedVersion(dir) {
|
|
1878
2087
|
try {
|
|
1879
|
-
const marker = JSON.parse(
|
|
2088
|
+
const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
|
|
1880
2089
|
return typeof marker.version === "string" ? marker.version : null;
|
|
1881
2090
|
} catch {
|
|
1882
2091
|
return null;
|
|
@@ -1886,25 +2095,25 @@ function isInstalledSkill(dir) {
|
|
|
1886
2095
|
return installedVersion(dir) !== null;
|
|
1887
2096
|
}
|
|
1888
2097
|
function installSkill(agent2, opts = {}) {
|
|
1889
|
-
const dir =
|
|
2098
|
+
const dir = join5(agent2.skillsDir, SKILL_NAME);
|
|
1890
2099
|
const previous = installedVersion(dir);
|
|
1891
2100
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
1892
2101
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
1893
2102
|
if (outcome !== "current") {
|
|
1894
2103
|
mkdirSync(dir, { recursive: true });
|
|
1895
|
-
writeFileSync3(
|
|
1896
|
-
writeFileSync3(
|
|
2104
|
+
writeFileSync3(join5(dir, "SKILL.md"), skillDocument());
|
|
2105
|
+
writeFileSync3(join5(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
1897
2106
|
`);
|
|
1898
2107
|
}
|
|
1899
2108
|
if (opts.catalog) {
|
|
1900
2109
|
mkdirSync(dir, { recursive: true });
|
|
1901
|
-
writeFileSync3(
|
|
2110
|
+
writeFileSync3(join5(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
1902
2111
|
}
|
|
1903
|
-
const refsDir =
|
|
2112
|
+
const refsDir = join5(dir, REFERENCES_DIR);
|
|
1904
2113
|
mkdirSync(refsDir, { recursive: true });
|
|
1905
|
-
writeFileSync3(
|
|
1906
|
-
writeFileSync3(
|
|
1907
|
-
writeFileSync3(
|
|
2114
|
+
writeFileSync3(join5(refsDir, "import.md"), importReferenceDoc());
|
|
2115
|
+
writeFileSync3(join5(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2116
|
+
writeFileSync3(join5(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
1908
2117
|
return { agent: agent2.id, path: dir, outcome };
|
|
1909
2118
|
}
|
|
1910
2119
|
|
|
@@ -1955,9 +2164,9 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
1955
2164
|
}
|
|
1956
2165
|
|
|
1957
2166
|
// src/setup-local.ts
|
|
1958
|
-
import { existsSync as
|
|
1959
|
-
import { homedir as
|
|
1960
|
-
import { dirname as
|
|
2167
|
+
import { existsSync as existsSync6, readdirSync as readdirSync2, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
2168
|
+
import { homedir as homedir5 } from "os";
|
|
2169
|
+
import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6, resolve as resolve4 } from "path";
|
|
1961
2170
|
import { parse as parseToml2 } from "smol-toml";
|
|
1962
2171
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
1963
2172
|
var MAX_IMPORT_DEPTH = 5;
|
|
@@ -1973,11 +2182,11 @@ function sensitive(name) {
|
|
|
1973
2182
|
function skillFiles(root, prefix) {
|
|
1974
2183
|
const files2 = [];
|
|
1975
2184
|
for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
|
|
1976
|
-
const path =
|
|
2185
|
+
const path = join6(root, entry2.name);
|
|
1977
2186
|
if (entry2.isDirectory()) {
|
|
1978
2187
|
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles(path, `${prefix}/${entry2.name}`));
|
|
1979
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
1980
|
-
files2.push({ path: `${prefix}/${entry2.name}`, bytes: new Uint8Array(
|
|
2188
|
+
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync3(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
2189
|
+
files2.push({ path: `${prefix}/${entry2.name}`, bytes: new Uint8Array(readFileSync7(path)) });
|
|
1981
2190
|
}
|
|
1982
2191
|
}
|
|
1983
2192
|
return files2;
|
|
@@ -1986,19 +2195,19 @@ function readLocalSkills(agents) {
|
|
|
1986
2195
|
const skills2 = [];
|
|
1987
2196
|
const ignored = [];
|
|
1988
2197
|
for (const agent2 of agents) {
|
|
1989
|
-
if (!
|
|
2198
|
+
if (!existsSync6(agent2.skillsDir)) continue;
|
|
1990
2199
|
for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
|
|
1991
|
-
const dir =
|
|
2200
|
+
const dir = join6(agent2.skillsDir, entry2.name);
|
|
1992
2201
|
let isDir = entry2.isDirectory();
|
|
1993
2202
|
if (entry2.isSymbolicLink()) {
|
|
1994
2203
|
try {
|
|
1995
|
-
isDir =
|
|
2204
|
+
isDir = statSync3(dir).isDirectory();
|
|
1996
2205
|
} catch {
|
|
1997
2206
|
isDir = false;
|
|
1998
2207
|
}
|
|
1999
2208
|
}
|
|
2000
|
-
if (!isDir || !
|
|
2001
|
-
const name = frontmatterName(
|
|
2209
|
+
if (!isDir || !existsSync6(join6(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
2210
|
+
const name = frontmatterName(readFileSync7(join6(dir, "SKILL.md"), "utf8"));
|
|
2002
2211
|
if (!name) {
|
|
2003
2212
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
2004
2213
|
continue;
|
|
@@ -2014,7 +2223,7 @@ function readLocalSkills(agents) {
|
|
|
2014
2223
|
}
|
|
2015
2224
|
return { skills: skills2, ignored };
|
|
2016
2225
|
}
|
|
2017
|
-
var
|
|
2226
|
+
var INSTRUCTION_FILES2 = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
|
|
2018
2227
|
function inlineImports(content, from, home, depth, seen) {
|
|
2019
2228
|
let fence;
|
|
2020
2229
|
return content.split(/\r?\n/).map((line) => {
|
|
@@ -2026,11 +2235,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2026
2235
|
}
|
|
2027
2236
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
2028
2237
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
2029
|
-
const path = target.startsWith("~/") ?
|
|
2238
|
+
const path = target.startsWith("~/") ? join6(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname3(from), target);
|
|
2030
2239
|
if (seen.includes(path)) return "";
|
|
2031
2240
|
let imported;
|
|
2032
2241
|
try {
|
|
2033
|
-
imported =
|
|
2242
|
+
imported = readFileSync7(path, "utf8");
|
|
2034
2243
|
} catch {
|
|
2035
2244
|
return line;
|
|
2036
2245
|
}
|
|
@@ -2038,15 +2247,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2038
2247
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
2039
2248
|
}).join("\n");
|
|
2040
2249
|
}
|
|
2041
|
-
function readGlobalInstructions(agents, home =
|
|
2250
|
+
function readGlobalInstructions(agents, home = homedir5()) {
|
|
2042
2251
|
const found = [];
|
|
2043
2252
|
for (const agent2 of agents) {
|
|
2044
|
-
const file =
|
|
2253
|
+
const file = INSTRUCTION_FILES2[agent2.id];
|
|
2045
2254
|
if (!file) continue;
|
|
2046
|
-
const path =
|
|
2255
|
+
const path = join6(agent2.root, file);
|
|
2047
2256
|
let raw;
|
|
2048
2257
|
try {
|
|
2049
|
-
raw =
|
|
2258
|
+
raw = readFileSync7(path, "utf8");
|
|
2050
2259
|
} catch {
|
|
2051
2260
|
continue;
|
|
2052
2261
|
}
|
|
@@ -2065,7 +2274,7 @@ ${item.content.trim()}`).join("\n\n");
|
|
|
2065
2274
|
}
|
|
2066
2275
|
function readJson2(path) {
|
|
2067
2276
|
try {
|
|
2068
|
-
return JSON.parse(
|
|
2277
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
2069
2278
|
} catch {
|
|
2070
2279
|
return void 0;
|
|
2071
2280
|
}
|
|
@@ -2079,15 +2288,15 @@ function bearerOf(def) {
|
|
|
2079
2288
|
return match?.[1] ?? null;
|
|
2080
2289
|
}
|
|
2081
2290
|
function serversOf(agent2, home) {
|
|
2082
|
-
if (agent2.id === "claude_code") return readJson2(
|
|
2083
|
-
if (agent2.id === "cursor") return readJson2(
|
|
2291
|
+
if (agent2.id === "claude_code") return readJson2(join6(home, ".claude.json"))?.mcpServers ?? {};
|
|
2292
|
+
if (agent2.id === "cursor") return readJson2(join6(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
2084
2293
|
try {
|
|
2085
|
-
return parseToml2(
|
|
2294
|
+
return parseToml2(readFileSync7(join6(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
2086
2295
|
} catch {
|
|
2087
2296
|
return {};
|
|
2088
2297
|
}
|
|
2089
2298
|
}
|
|
2090
|
-
function findLocalSecrets(agents, home =
|
|
2299
|
+
function findLocalSecrets(agents, home = homedir5()) {
|
|
2091
2300
|
const secrets2 = [];
|
|
2092
2301
|
for (const agent2 of agents) {
|
|
2093
2302
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
@@ -2103,9 +2312,9 @@ function findLocalSecrets(agents, home = homedir4()) {
|
|
|
2103
2312
|
|
|
2104
2313
|
// src/subscriptions.ts
|
|
2105
2314
|
import { spawn as spawn3 } from "child_process";
|
|
2106
|
-
import { existsSync as
|
|
2107
|
-
import { homedir as
|
|
2108
|
-
import { join as
|
|
2315
|
+
import { existsSync as existsSync7, readFileSync as readFileSync8 } from "fs";
|
|
2316
|
+
import { homedir as homedir6 } from "os";
|
|
2317
|
+
import { join as join7 } from "path";
|
|
2109
2318
|
import { createInterface as createInterface2 } from "readline";
|
|
2110
2319
|
var ALIASES = {
|
|
2111
2320
|
claude: "anthropic",
|
|
@@ -2190,12 +2399,12 @@ async function connectClaude() {
|
|
|
2190
2399
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
2191
2400
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
2192
2401
|
}
|
|
2193
|
-
function codexSignIn(env = process.env, home =
|
|
2194
|
-
const path =
|
|
2195
|
-
if (!
|
|
2402
|
+
function codexSignIn(env = process.env, home = homedir6()) {
|
|
2403
|
+
const path = join7(env.CODEX_HOME || join7(home, ".codex"), "auth.json");
|
|
2404
|
+
if (!existsSync7(path)) {
|
|
2196
2405
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
2197
2406
|
}
|
|
2198
|
-
const raw =
|
|
2407
|
+
const raw = readFileSync8(path, "utf8");
|
|
2199
2408
|
try {
|
|
2200
2409
|
const parsed = JSON.parse(raw);
|
|
2201
2410
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -2203,12 +2412,12 @@ function codexSignIn(env = process.env, home = homedir5()) {
|
|
|
2203
2412
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
2204
2413
|
}
|
|
2205
2414
|
}
|
|
2206
|
-
function cursorSignIn(env = process.env, home =
|
|
2207
|
-
const path =
|
|
2208
|
-
if (!
|
|
2415
|
+
function cursorSignIn(env = process.env, home = homedir6()) {
|
|
2416
|
+
const path = join7(env.XDG_CONFIG_HOME || join7(home, ".config"), "cursor", "auth.json");
|
|
2417
|
+
if (!existsSync7(path)) {
|
|
2209
2418
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
2210
2419
|
}
|
|
2211
|
-
const raw =
|
|
2420
|
+
const raw = readFileSync8(path, "utf8");
|
|
2212
2421
|
try {
|
|
2213
2422
|
const parsed = JSON.parse(raw);
|
|
2214
2423
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -2287,11 +2496,11 @@ function planProviders(names) {
|
|
|
2287
2496
|
}));
|
|
2288
2497
|
}
|
|
2289
2498
|
function reportPath() {
|
|
2290
|
-
return
|
|
2499
|
+
return join8(dirname4(configPath()), "setup.json");
|
|
2291
2500
|
}
|
|
2292
2501
|
function loadReport() {
|
|
2293
2502
|
try {
|
|
2294
|
-
const saved = JSON.parse(
|
|
2503
|
+
const saved = JSON.parse(readFileSync9(reportPath(), "utf8"));
|
|
2295
2504
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
2296
2505
|
} catch {
|
|
2297
2506
|
return { agents: [], plans: [] };
|
|
@@ -2304,7 +2513,7 @@ function mergeReport(base, next) {
|
|
|
2304
2513
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
2305
2514
|
}
|
|
2306
2515
|
function saveReport(report) {
|
|
2307
|
-
mkdirSync2(
|
|
2516
|
+
mkdirSync2(dirname4(reportPath()), { recursive: true });
|
|
2308
2517
|
writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
2309
2518
|
`, { mode: 384 });
|
|
2310
2519
|
}
|
|
@@ -2654,17 +2863,17 @@ function renderArtifact(artifact2) {
|
|
|
2654
2863
|
}
|
|
2655
2864
|
|
|
2656
2865
|
// src/skills-store.ts
|
|
2657
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2658
|
-
import { basename as
|
|
2866
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync10, statSync as statSync4 } from "fs";
|
|
2867
|
+
import { basename as basename4, join as join9, relative as relative2, resolve as resolve5 } from "path";
|
|
2659
2868
|
function skillBundle(dir) {
|
|
2660
|
-
const root =
|
|
2661
|
-
const top =
|
|
2869
|
+
const root = resolve5(dir);
|
|
2870
|
+
const top = basename4(root);
|
|
2662
2871
|
const files2 = [];
|
|
2663
2872
|
const walk = (path) => {
|
|
2664
2873
|
for (const name of readdirSync3(path).sort()) {
|
|
2665
|
-
const full =
|
|
2666
|
-
if (
|
|
2667
|
-
else files2.push({ path: `${top}/${
|
|
2874
|
+
const full = join9(path, name);
|
|
2875
|
+
if (statSync4(full).isDirectory()) walk(full);
|
|
2876
|
+
else files2.push({ path: `${top}/${relative2(root, full)}`, bytes: readFileSync10(full) });
|
|
2668
2877
|
}
|
|
2669
2878
|
};
|
|
2670
2879
|
walk(root);
|
|
@@ -2674,7 +2883,7 @@ function skillBundle(dir) {
|
|
|
2674
2883
|
async function uploadSkill(dir, displayName) {
|
|
2675
2884
|
const form = new FormData();
|
|
2676
2885
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
2677
|
-
form.append("displayName", displayName ??
|
|
2886
|
+
form.append("displayName", displayName ?? basename4(resolve5(dir)));
|
|
2678
2887
|
return apiForm("POST", "/skills", form);
|
|
2679
2888
|
}
|
|
2680
2889
|
async function listSkills() {
|
|
@@ -2792,19 +3001,19 @@ function renderPolicy(rule) {
|
|
|
2792
3001
|
}
|
|
2793
3002
|
|
|
2794
3003
|
// src/harness.ts
|
|
2795
|
-
import { existsSync as
|
|
2796
|
-
import { dirname as
|
|
3004
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
3005
|
+
import { dirname as dirname5, join as join10, resolve as resolve6 } from "path";
|
|
2797
3006
|
var HARNESS_FILE = "harness.json";
|
|
2798
3007
|
function harnessFilePath(dir) {
|
|
2799
|
-
return
|
|
3008
|
+
return join10(resolve6(dir), HARNESS_FILE);
|
|
2800
3009
|
}
|
|
2801
3010
|
function readHarnessFile(dir) {
|
|
2802
3011
|
const path = harnessFilePath(dir);
|
|
2803
|
-
if (!
|
|
2804
|
-
throw new Error(`no ${HARNESS_FILE} found in ${
|
|
3012
|
+
if (!existsSync8(path)) {
|
|
3013
|
+
throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
|
|
2805
3014
|
}
|
|
2806
3015
|
try {
|
|
2807
|
-
return JSON.parse(
|
|
3016
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2808
3017
|
} catch (err) {
|
|
2809
3018
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2810
3019
|
}
|
|
@@ -2831,10 +3040,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
2831
3040
|
`;
|
|
2832
3041
|
function harnessInit(dir) {
|
|
2833
3042
|
const path = harnessFilePath(dir);
|
|
2834
|
-
if (
|
|
3043
|
+
if (existsSync8(path)) {
|
|
2835
3044
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
2836
3045
|
}
|
|
2837
|
-
mkdirSync3(
|
|
3046
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
2838
3047
|
writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
2839
3048
|
`);
|
|
2840
3049
|
console.log(`Created ${path}`);
|
|
@@ -2990,7 +3199,7 @@ auth.command("whoami").description("who the current token acts as").action(
|
|
|
2990
3199
|
run(async () => {
|
|
2991
3200
|
const who = await api("GET", "/whoami");
|
|
2992
3201
|
console.log(
|
|
2993
|
-
`${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (
|
|
3202
|
+
`${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (default)" : ""} \xB7 scopes ${who.scopes.join(",")}`
|
|
2994
3203
|
);
|
|
2995
3204
|
})
|
|
2996
3205
|
);
|
|
@@ -3010,7 +3219,7 @@ universe.command("list").action(
|
|
|
3010
3219
|
const current = currentUniverse();
|
|
3011
3220
|
for (const u of universes) {
|
|
3012
3221
|
const marker = u.slug === current || !current && u.isPersonal ? "*" : " ";
|
|
3013
|
-
console.log(`${marker} ${u.slug}${u.isPersonal ? " (
|
|
3222
|
+
console.log(`${marker} ${u.slug}${u.isPersonal ? " (default)" : ""}`);
|
|
3014
3223
|
}
|
|
3015
3224
|
})
|
|
3016
3225
|
);
|
|
@@ -3036,7 +3245,7 @@ universe.command("show").action(
|
|
|
3036
3245
|
console.log(`${u.slug} \u2014 ${u.name} \xB7 ${u.agents} agents \xB7 ${u.members} members`);
|
|
3037
3246
|
} else {
|
|
3038
3247
|
const who = await api("GET", path);
|
|
3039
|
-
console.log(`${who.universe.slug} (
|
|
3248
|
+
console.log(`${who.universe.slug} (default)`);
|
|
3040
3249
|
}
|
|
3041
3250
|
})
|
|
3042
3251
|
);
|
|
@@ -3084,7 +3293,7 @@ workspace.command("list").action(
|
|
|
3084
3293
|
const current = currentUniverse();
|
|
3085
3294
|
for (const u of universes) {
|
|
3086
3295
|
const marker = u.slug === current || !current && u.isPersonal ? "*" : " ";
|
|
3087
|
-
console.log(`${marker} ${u.slug}${u.isPersonal ? " (
|
|
3296
|
+
console.log(`${marker} ${u.slug}${u.isPersonal ? " (default)" : ""}`);
|
|
3088
3297
|
}
|
|
3089
3298
|
})
|
|
3090
3299
|
);
|
|
@@ -3339,6 +3548,38 @@ session.command("read <ref>").description("what the agent has said since the las
|
|
|
3339
3548
|
if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
|
|
3340
3549
|
})
|
|
3341
3550
|
);
|
|
3551
|
+
session.command("share <ref>").description("print the session's share link, turning sharing on; --view makes it read-only, --off turns it off").option("--view", "a view-only link: holders read the conversation and can do nothing else").option("--rotate", "replace the link; the old one stops working").option("--off", "turn sharing off; the link stops working at once").action(
|
|
3552
|
+
run(async (ref, opts) => {
|
|
3553
|
+
const id = await resolveSessionRef(ref);
|
|
3554
|
+
const path = `/sessions/${encodeURIComponent(id)}/share`;
|
|
3555
|
+
if (opts.off) {
|
|
3556
|
+
await api("DELETE", path);
|
|
3557
|
+
console.log(`Sharing is off for ${id}`);
|
|
3558
|
+
return;
|
|
3559
|
+
}
|
|
3560
|
+
const link = await api("POST", path, {
|
|
3561
|
+
access: opts.view ? "view" : "prompt",
|
|
3562
|
+
...opts.rotate ? { rotate: true } : {}
|
|
3563
|
+
});
|
|
3564
|
+
if (link.shareUrl) console.log(link.shareUrl);
|
|
3565
|
+
else console.log(`view-only token: ${link.token} \u2014 read with GET /links/${link.token}/events`);
|
|
3566
|
+
})
|
|
3567
|
+
);
|
|
3568
|
+
session.command("usage <ref>").description("what the session has cost so far, and its newest billed entries").option("--limit <n>", "entries to list (default 20)").option("--json", "print the API's answer as JSON").action(
|
|
3569
|
+
run(async (ref, opts) => {
|
|
3570
|
+
const id = await resolveSessionRef(ref);
|
|
3571
|
+
const usage = await api("GET", `/sessions/${encodeURIComponent(id)}/usage?limit=${encodeURIComponent(opts.limit ?? "20")}`);
|
|
3572
|
+
if (opts.json) {
|
|
3573
|
+
console.log(JSON.stringify(usage, null, 2));
|
|
3574
|
+
return;
|
|
3575
|
+
}
|
|
3576
|
+
const t = usage.totals;
|
|
3577
|
+
console.log(`${id}: $${t.billedCostUsd.toFixed(4)} billed, ${t.turns} model calls, ${t.inputTokens} in / ${t.outputTokens} out tokens`);
|
|
3578
|
+
for (const entry2 of usage.entries) {
|
|
3579
|
+
console.log(` ${entry2.at} ${entry2.kind.padEnd(16)} ${(entry2.model ?? "").padEnd(24)} $${entry2.billedCostUsd.toFixed(4)}`);
|
|
3580
|
+
}
|
|
3581
|
+
})
|
|
3582
|
+
);
|
|
3342
3583
|
var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
|
|
3343
3584
|
var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
|
|
3344
3585
|
sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
|
|
@@ -3555,9 +3796,9 @@ secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the a
|
|
|
3555
3796
|
);
|
|
3556
3797
|
secrets.command("list <id>").action(
|
|
3557
3798
|
run(async (id) => {
|
|
3558
|
-
const { secrets:
|
|
3559
|
-
if (!
|
|
3560
|
-
for (const s of
|
|
3799
|
+
const { secrets: keys2 } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
|
|
3800
|
+
if (!keys2.length) console.log("no secrets set");
|
|
3801
|
+
for (const s of keys2) {
|
|
3561
3802
|
console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
3562
3803
|
}
|
|
3563
3804
|
})
|
|
@@ -3573,9 +3814,9 @@ secrets.command("unset <id> <key>").action(
|
|
|
3573
3814
|
);
|
|
3574
3815
|
program.command("launch").description("scaffold ./agent.toml if absent, create the agent, print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").action(
|
|
3575
3816
|
run(async (opts) => {
|
|
3576
|
-
const path =
|
|
3577
|
-
if (!
|
|
3578
|
-
const name =
|
|
3817
|
+
const path = resolve7(opts.file);
|
|
3818
|
+
if (!existsSync9(path)) {
|
|
3819
|
+
const name = basename5(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
3579
3820
|
scaffoldAgentFile(path, name);
|
|
3580
3821
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
3581
3822
|
}
|
|
@@ -3606,7 +3847,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
|
|
|
3606
3847
|
);
|
|
3607
3848
|
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
3608
3849
|
run(async (opts) => {
|
|
3609
|
-
const path =
|
|
3850
|
+
const path = resolve7(opts.file);
|
|
3610
3851
|
const { file, spec } = readAgentFile(path);
|
|
3611
3852
|
if (!file.id) {
|
|
3612
3853
|
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
@@ -3649,7 +3890,7 @@ function formatRelative(msAgo) {
|
|
|
3649
3890
|
if (hours < 48) return `${hours}h ago`;
|
|
3650
3891
|
return `${Math.round(hours / 24)}d ago`;
|
|
3651
3892
|
}
|
|
3652
|
-
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").action(
|
|
3893
|
+
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").option("--no-files", "send the conversation only, without the local files it read, wrote or ran under").action(
|
|
3653
3894
|
run(
|
|
3654
3895
|
async (opts) => {
|
|
3655
3896
|
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
@@ -3709,11 +3950,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3709
3950
|
console.log();
|
|
3710
3951
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3711
3952
|
const answer = await new Promise(
|
|
3712
|
-
(
|
|
3953
|
+
(resolve8) => rl.question(
|
|
3713
3954
|
`Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
|
|
3714
3955
|
(a) => {
|
|
3715
3956
|
rl.close();
|
|
3716
|
-
|
|
3957
|
+
resolve8(a.trim());
|
|
3717
3958
|
}
|
|
3718
3959
|
)
|
|
3719
3960
|
);
|
|
@@ -3731,14 +3972,14 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3731
3972
|
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
3732
3973
|
if (!opts.json) {
|
|
3733
3974
|
console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
|
|
3734
|
-
console.log("The recorded conversation is redacted locally. Local project files and login credentials are not transferred.");
|
|
3975
|
+
console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation and the local files it read, wrote or ran under are attached; text is redacted locally. Credential files and login credentials are never transferred. Use --no-files to send the conversation only.");
|
|
3735
3976
|
}
|
|
3736
3977
|
if (!autoConfirm) {
|
|
3737
3978
|
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
3738
3979
|
const prompt = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3739
|
-
const answer = await new Promise((
|
|
3980
|
+
const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
3740
3981
|
prompt.close();
|
|
3741
|
-
|
|
3982
|
+
resolve8(value);
|
|
3742
3983
|
}));
|
|
3743
3984
|
if (!/^y(?:es)?$/i.test(answer.trim())) {
|
|
3744
3985
|
console.log("Cancelled. Nothing uploaded.");
|
|
@@ -3756,6 +3997,7 @@ Importing ${label} \u2026
|
|
|
3756
3997
|
summaryPath: opts.summary,
|
|
3757
3998
|
importKey: opts.key,
|
|
3758
3999
|
grantTraceId: opts.trace,
|
|
4000
|
+
files: opts.files,
|
|
3759
4001
|
onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
|
|
3760
4002
|
`)
|
|
3761
4003
|
});
|
|
@@ -3770,9 +4012,12 @@ Importing ${label} \u2026
|
|
|
3770
4012
|
bytes: result.bytes,
|
|
3771
4013
|
redactionCount: result.redactionCount,
|
|
3772
4014
|
gaps: result.gaps,
|
|
4015
|
+
attachedFiles: result.attachedFiles,
|
|
4016
|
+
leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
|
|
3773
4017
|
continueUrl: result.url
|
|
3774
4018
|
}));
|
|
3775
4019
|
} else {
|
|
4020
|
+
if (result.attachedFiles.length || result.leftOutFiles.length) console.log(` Attached ${result.attachedFiles.length} local ${result.attachedFiles.length === 1 ? "file" : "files"}${result.leftOutFiles.length ? `; left out ${result.leftOutFiles.length} (credential, missing, unsupported or over the 30 MiB upload)` : ""}.`);
|
|
3776
4021
|
console.log(` \u2713 Imported. Continue here:
|
|
3777
4022
|
${result.url}
|
|
3778
4023
|
`);
|
|
@@ -3839,7 +4084,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
3839
4084
|
);
|
|
3840
4085
|
task.command("put <task> <path> <local>").description("write a local file to <path> in the task's files (every session of the task sees it)").action(
|
|
3841
4086
|
run(async (taskId, path, local) => {
|
|
3842
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
4087
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync12(local), mediaTypeFor(local));
|
|
3843
4088
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
3844
4089
|
})
|
|
3845
4090
|
);
|
|
@@ -4004,6 +4249,19 @@ for (const [verb, blurb] of [
|
|
|
4004
4249
|
})
|
|
4005
4250
|
);
|
|
4006
4251
|
}
|
|
4252
|
+
routine.command("runs [id]").description("every fire attempt, newest first \u2014 of one routine, or all of them").option("--failed", "only runs that failed to start").option("--limit <n>", "how many (default 20)").action(
|
|
4253
|
+
run(async (id, opts) => {
|
|
4254
|
+
const params = new URLSearchParams({ limit: opts.limit ?? "20" });
|
|
4255
|
+
if (id) params.set("routine", id);
|
|
4256
|
+
if (opts.failed) params.set("has_error", "true");
|
|
4257
|
+
const { items } = await api("GET", `/routine-runs?${params}`);
|
|
4258
|
+
if (!items.length) console.log("no runs");
|
|
4259
|
+
for (const runRow of items) {
|
|
4260
|
+
const outcome = runRow.error ? `FAILED ${runRow.error.type}: ${runRow.error.message}` : runRow.session_id ?? "ok";
|
|
4261
|
+
console.log(`${runRow.id} ${runRow.created_at} ${runRow.trigger_context.type.padEnd(8)} ${outcome}`);
|
|
4262
|
+
}
|
|
4263
|
+
})
|
|
4264
|
+
);
|
|
4007
4265
|
var collect = (value, previous = []) => [...previous, value];
|
|
4008
4266
|
function goalPath(sessionId, suffix = "") {
|
|
4009
4267
|
return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
|
|
@@ -4096,8 +4354,149 @@ program.command("config").description("show the CLI config location and current
|
|
|
4096
4354
|
console.log(`config: ${configPath()}`);
|
|
4097
4355
|
console.log(` apiBase: ${config.apiBase ?? "(default)"}`);
|
|
4098
4356
|
console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
|
|
4099
|
-
console.log(` universe: ${config.currentUniverse ?? "(
|
|
4357
|
+
console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
|
|
4100
4358
|
});
|
|
4359
|
+
var billing = program.command("billing").description("your balance, auto top-up, usage across sessions and API keys, and buying credit");
|
|
4360
|
+
billing.command("show").description("balance, whether a card is saved, and auto top-up").action(
|
|
4361
|
+
run(async () => {
|
|
4362
|
+
const [balance, autoTopup] = await Promise.all([
|
|
4363
|
+
api("GET", "/billing"),
|
|
4364
|
+
api("GET", "/billing/auto-topup")
|
|
4365
|
+
]);
|
|
4366
|
+
console.log(renderBalance(balance));
|
|
4367
|
+
console.log(renderAutoTopup(autoTopup));
|
|
4368
|
+
})
|
|
4369
|
+
);
|
|
4370
|
+
billing.command("card").description("print the address of a card form to save a card; saving moves no money").action(
|
|
4371
|
+
run(async () => {
|
|
4372
|
+
const { url } = await api("POST", "/billing/card-setup");
|
|
4373
|
+
console.log(url);
|
|
4374
|
+
})
|
|
4375
|
+
);
|
|
4376
|
+
billing.command("topup <dollars>").description("buy credit with your saved card: 10, 20, 50 or 100 dollars").option("--request-id <id>", "repeat a request without charging twice (default: a new id)").action(
|
|
4377
|
+
run(async (dollars2, opts) => {
|
|
4378
|
+
const result = await api("POST", "/billing/topups", {
|
|
4379
|
+
amount: { amount: dollarsToCents("<dollars>", dollars2), currency: "USD" },
|
|
4380
|
+
request_id: opts.requestId ?? `sky-${crypto.randomUUID()}`
|
|
4381
|
+
});
|
|
4382
|
+
console.log(renderTopup(result));
|
|
4383
|
+
})
|
|
4384
|
+
);
|
|
4385
|
+
billing.command("auto-topup").description("show auto top-up, or change it: --on/--off, --threshold <5|10|20>, --amount <10|20|50|100>").option("--on", "charge the saved card when the balance falls under the threshold").option("--off", "stop charging automatically").option("--threshold <dollars>", "the balance that triggers a charge").option("--amount <dollars>", "the credit each charge adds").action(
|
|
4386
|
+
run(async (opts) => {
|
|
4387
|
+
const body = autoTopupBody(opts);
|
|
4388
|
+
const settings = body ? await api("PATCH", "/billing/auto-topup", body) : await api("GET", "/billing/auto-topup");
|
|
4389
|
+
console.log(renderAutoTopup(settings));
|
|
4390
|
+
})
|
|
4391
|
+
);
|
|
4392
|
+
var usageOptions = (command) => command.option("--from <day>", "first UTC day, YYYY-MM-DD (default: 29 days ago)").option("--to <day>", "first UTC day not included (default: tomorrow)").option("--source <source>", "session or api_key (default: both)").option("--key <id>", "only this API key");
|
|
4393
|
+
usageOptions(
|
|
4394
|
+
billing.command("usage").description("what you spent over a range of days, across sessions and API keys").option("--by <dimension>", "day, kind, session or api_key (default day)").option("--json", "print the API's answer as JSON")
|
|
4395
|
+
).action(
|
|
4396
|
+
run(async (opts) => {
|
|
4397
|
+
const summary = await api("GET", `/billing/usage${usageQuery({ ...opts, groupBy: opts.by })}`);
|
|
4398
|
+
console.log(opts.json ? JSON.stringify(summary, null, 2) : renderUsage(summary));
|
|
4399
|
+
})
|
|
4400
|
+
);
|
|
4401
|
+
usageOptions(
|
|
4402
|
+
billing.command("export").description("every usage record in the range, as CSV or JSONL").option("--format <format>", "csv or jsonl (default csv)").option("-o, --out <path>", "write to a file instead of printing")
|
|
4403
|
+
).action(
|
|
4404
|
+
run(async (opts) => {
|
|
4405
|
+
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
4406
|
+
if (opts.out) {
|
|
4407
|
+
writeFileSync6(opts.out, bytes);
|
|
4408
|
+
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
4409
|
+
} else {
|
|
4410
|
+
process.stdout.write(bytes);
|
|
4411
|
+
}
|
|
4412
|
+
})
|
|
4413
|
+
);
|
|
4414
|
+
var keys = program.command("keys").description("your own API keys: list, create, revoke, restore (admin scope)");
|
|
4415
|
+
keys.command("list").action(
|
|
4416
|
+
run(async () => {
|
|
4417
|
+
const { api_keys: list } = await api("GET", "/api_keys");
|
|
4418
|
+
if (!list.length) console.log("no API keys");
|
|
4419
|
+
for (const key of list) console.log(renderApiKey(key));
|
|
4420
|
+
})
|
|
4421
|
+
);
|
|
4422
|
+
keys.command("create <name>").description("make a key; its secret is printed once (`sky keys reveal` reads it again where allowed)").option("--scopes <list>", "comma-separated: read, write, admin (default read,write)").option("--workspace <slug>", 'fix the key to one workspace, or "personal" for none').option("--expires <iso>", "when the key stops working, e.g. 2027-01-01T00:00:00Z").action(
|
|
4423
|
+
run(async (name, opts) => {
|
|
4424
|
+
const { api_key: key, secret } = await api("POST", "/api_keys", apiKeyBody(name, opts));
|
|
4425
|
+
console.error(renderApiKey(key));
|
|
4426
|
+
console.log(secret);
|
|
4427
|
+
})
|
|
4428
|
+
);
|
|
4429
|
+
keys.command("reveal <id>").description("print a key's secret again").action(
|
|
4430
|
+
run(async (id) => {
|
|
4431
|
+
const { secret } = await api("POST", `/api_keys/${encodeURIComponent(id)}/reveal`);
|
|
4432
|
+
console.log(secret);
|
|
4433
|
+
})
|
|
4434
|
+
);
|
|
4435
|
+
for (const [verb, blurb] of [
|
|
4436
|
+
["revoke", "the key stops working at once"],
|
|
4437
|
+
["restore", "a revoked key works again"]
|
|
4438
|
+
]) {
|
|
4439
|
+
keys.command(`${verb} <id>`).description(blurb).action(
|
|
4440
|
+
run(async (id) => {
|
|
4441
|
+
const { api_key: key } = await api("POST", `/api_keys/${encodeURIComponent(id)}/${verb}`);
|
|
4442
|
+
console.log(renderApiKey(key));
|
|
4443
|
+
})
|
|
4444
|
+
);
|
|
4445
|
+
}
|
|
4446
|
+
var memory = program.command("memory").description("your personal memory: what every session you start is told about you");
|
|
4447
|
+
async function writeMemory(operation) {
|
|
4448
|
+
const current = await api("GET", "/memory");
|
|
4449
|
+
console.log(renderMemory(await api("POST", "/memory", memoryBody(operation, current.version))));
|
|
4450
|
+
}
|
|
4451
|
+
memory.command("list").action(run(async () => console.log(renderMemory(await api("GET", "/memory")))));
|
|
4452
|
+
memory.command("save <text...>").description("add an entry").action(run(async (text) => writeMemory({ op: "save", text: text.join(" ") })));
|
|
4453
|
+
memory.command("edit <index> <text...>").description("replace entry <index>").action(run(async (index, text) => writeMemory({ op: "update", index: memoryIndex(index), text: text.join(" ") })));
|
|
4454
|
+
memory.command("rm <index>").description("remove entry <index>").action(run(async (index) => writeMemory({ op: "remove", index: memoryIndex(index) })));
|
|
4455
|
+
memory.command("clear").description("remove every entry").action(run(async () => writeMemory({ op: "clear" })));
|
|
4456
|
+
var context = program.command("context").description("search your conversations, memory and the workspace trace library");
|
|
4457
|
+
context.command("search <words...>").description("literal matches, in source order; follow --cursor for more").option("--scope <scope>", "sessions, memory or traces (default sessions)").option("--limit <n>", "hits per page (default 20)").option("--cursor <cursor>", "continue a previous search").option("--since <iso>", "inclusive lower time bound").option("--until <iso>", "exclusive upper time bound").option("--json", "print the API's answer as JSON").action(
|
|
4458
|
+
run(async (words, opts) => {
|
|
4459
|
+
const page = await api("GET", `/context/search${searchQuery(words.join(" "), opts)}`);
|
|
4460
|
+
console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
|
|
4461
|
+
})
|
|
4462
|
+
);
|
|
4463
|
+
context.command("read <ref>").description("read a search hit in full: <ref> is the JSON reference the search printed").option("--offset <n>", "continue a long message from this offset").action(
|
|
4464
|
+
run(async (ref, opts) => {
|
|
4465
|
+
const params = new URLSearchParams({ ref });
|
|
4466
|
+
if (opts.offset) params.set("offset", opts.offset);
|
|
4467
|
+
const read = await api("GET", `/context/read?${params}`);
|
|
4468
|
+
console.log(read.text);
|
|
4469
|
+
if (read.nextOffset !== null) console.error(`more: --offset ${read.nextOffset}`);
|
|
4470
|
+
})
|
|
4471
|
+
);
|
|
4472
|
+
var webhookLine = (w) => `${w.id} ${w.status.padEnd(8)} ${w.url} ${w.events.join(",")}${w.failure_count ? ` (${w.failure_count} failures)` : ""}`;
|
|
4473
|
+
var webhook = program.command("webhook").description("your HTTPS endpoints for platform events, signed with a whsec_ secret");
|
|
4474
|
+
webhook.command("list").action(
|
|
4475
|
+
run(async () => {
|
|
4476
|
+
const { webhooks } = await api("GET", "/webhooks");
|
|
4477
|
+
if (!webhooks.length) console.log("no webhooks");
|
|
4478
|
+
for (const w of webhooks) console.log(webhookLine(w));
|
|
4479
|
+
})
|
|
4480
|
+
);
|
|
4481
|
+
webhook.command("create <url>").description("register an endpoint; its signing secret is printed once").requiredOption("--event <type...>", "event types to deliver, e.g. goal.completed routine_run.failed").action(
|
|
4482
|
+
run(async (url, opts) => {
|
|
4483
|
+
const { webhook: created } = await api("POST", "/webhooks", { url, events: opts.event });
|
|
4484
|
+
console.error(webhookLine(created));
|
|
4485
|
+
if (created.secret) console.log(created.secret);
|
|
4486
|
+
})
|
|
4487
|
+
);
|
|
4488
|
+
webhook.command("enable <id>").description("re-enable an endpoint that was disabled after failed deliveries").action(
|
|
4489
|
+
run(async (id) => {
|
|
4490
|
+
const { webhook: updated } = await api("PATCH", `/webhooks/${encodeURIComponent(id)}`, { status: "active" });
|
|
4491
|
+
console.log(webhookLine(updated));
|
|
4492
|
+
})
|
|
4493
|
+
);
|
|
4494
|
+
webhook.command("rm <id>").description("delete an endpoint").action(
|
|
4495
|
+
run(async (id) => {
|
|
4496
|
+
await api("DELETE", `/webhooks/${encodeURIComponent(id)}`);
|
|
4497
|
+
console.log(`Deleted ${id}`);
|
|
4498
|
+
})
|
|
4499
|
+
);
|
|
4101
4500
|
var harness = program.command("harness").description("workspace harnesses: init, verify, push");
|
|
4102
4501
|
harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
|
|
4103
4502
|
harnessInit(dir ?? ".");
|
|
@@ -4121,7 +4520,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
4121
4520
|
}));
|
|
4122
4521
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
4123
4522
|
traceCmd.command("start").description("start a trace: a grant for this machine, a root span, and the spool the hooks write to").option("--name <name>", "a name for the task").option("--source <source>", "how this machine reports itself", "claude-code").option("--hooks [settings]", "also install the Claude Code hooks (default: ./.claude/settings.local.json)").option("--capture <scopes>", "also send content, redacted on this machine first: any of prompts,tool_output,files").option("--file <path>", "a file the files scope may send (repeatable)", collect).option("--dir <path>", "a folder whose files the files scope may send when a file tool touches them (repeatable)", collect).action(run(async (opts) => {
|
|
4124
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
4523
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-DS35CFKV.js");
|
|
4125
4524
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
4126
4525
|
const scopes = parseContentScopes(opts.capture);
|
|
4127
4526
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -4150,13 +4549,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
4150
4549
|
]);
|
|
4151
4550
|
console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
|
|
4152
4551
|
console.log(scopes.length ? ` content: ${scopes.join(", ")} (redacted here before it leaves)` : " metadata only");
|
|
4153
|
-
if (opts.hooks) console.log(` hooks: ${installClaudeHooks(
|
|
4552
|
+
if (opts.hooks) console.log(` hooks: ${installClaudeHooks(resolve7(typeof opts.hooks === "string" ? opts.hooks : ".claude/settings.local.json"))}`);
|
|
4154
4553
|
console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
|
|
4155
4554
|
console.log(` sky trace show ${trace.id}`);
|
|
4156
4555
|
}));
|
|
4157
4556
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
4158
4557
|
try {
|
|
4159
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
4558
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DS35CFKV.js");
|
|
4160
4559
|
const chunks = [];
|
|
4161
4560
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4162
4561
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -4169,14 +4568,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
4169
4568
|
}
|
|
4170
4569
|
});
|
|
4171
4570
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
4172
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
4571
|
+
const { readState, flush, grantSender } = await import("./trace-DS35CFKV.js");
|
|
4173
4572
|
const state = readState();
|
|
4174
4573
|
if (!state) throw new Error("no trace is running here");
|
|
4175
4574
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
4176
4575
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
4177
4576
|
}));
|
|
4178
4577
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
4179
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
4578
|
+
const { readState, spoolStatus } = await import("./trace-DS35CFKV.js");
|
|
4180
4579
|
const state = readState();
|
|
4181
4580
|
if (!state) {
|
|
4182
4581
|
console.log("No trace is running here.");
|
|
@@ -4186,7 +4585,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
4186
4585
|
console.log(`${state.trace_id} as ${state.source}: ${spool.pending} pending, ${spool.dropped_full} dropped (spool full), ${spool.dropped_expired} given up, ${spool.refused} refused`);
|
|
4187
4586
|
}));
|
|
4188
4587
|
traceCmd.command("show [id]").description("the trace bundle: spans, the sessions they reach, cost counted once per session").option("--json", "print the bundle as JSON").action(run(async (id, opts) => {
|
|
4189
|
-
const { readState } = await import("./trace-
|
|
4588
|
+
const { readState } = await import("./trace-DS35CFKV.js");
|
|
4190
4589
|
const ref = id ?? readState()?.trace_id;
|
|
4191
4590
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
4192
4591
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -4213,7 +4612,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
4213
4612
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
4214
4613
|
}));
|
|
4215
4614
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
4216
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
4615
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-DS35CFKV.js");
|
|
4217
4616
|
const state = readState();
|
|
4218
4617
|
if (!state) throw new Error("no trace is running here");
|
|
4219
4618
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -4222,6 +4621,138 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
4222
4621
|
clearState();
|
|
4223
4622
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
4224
4623
|
}));
|
|
4624
|
+
traceCmd.command("sync [payload]").description("upload this machine's Claude Code and Codex sessions to the matching workspace's trace library, redacted here first").option("--enable", "turn sync on for this machine").option("--disable", "turn sync off; nothing more is uploaded").option("--route <match=workspace>", "send sessions whose folder path contains <match> to <workspace> (repeatable; replaces the saved rules)", collect).option("--default <workspace>", "where a session no rule matches goes").option("--keep-local", "keep sessions no rule matches on this machine (the default)").option("--session <ref>", "only this session: its id or transcript path (repeatable)", collect).option("--full", "re-read every session, not only files that changed").option("--limit <n>", "at most this many changed sessions this run", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("--hook", "run from a Claude Code SessionEnd hook or Codex notify: upload that session in the background and return at once").option("--install-schedule", "also sweep every 15 minutes (macOS launchd)").option("--status", "what sync has done on this machine").option("--quiet", "print only failures").action(async (payload, opts) => {
|
|
4625
|
+
const lib = await import("./trace-library-B3NZ4OQM.js");
|
|
4626
|
+
if (opts.hook) {
|
|
4627
|
+
try {
|
|
4628
|
+
let raw = payload ?? "";
|
|
4629
|
+
if (!raw && !process.stdin.isTTY) {
|
|
4630
|
+
const chunks = [];
|
|
4631
|
+
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4632
|
+
raw = Buffer.concat(chunks).toString("utf8");
|
|
4633
|
+
}
|
|
4634
|
+
if (!lib.loadSyncConfig().enabled) return;
|
|
4635
|
+
const ref = lib.hookSessionRef(raw);
|
|
4636
|
+
const { spawn: spawn4 } = await import("child_process");
|
|
4637
|
+
spawn4(process.execPath, [process.argv[1], "trace", "sync", "--quiet", ...ref ? ["--session", ref] : []], { detached: true, stdio: "ignore" }).unref();
|
|
4638
|
+
} catch {
|
|
4639
|
+
}
|
|
4640
|
+
return;
|
|
4641
|
+
}
|
|
4642
|
+
await run(async () => {
|
|
4643
|
+
const config = lib.loadSyncConfig();
|
|
4644
|
+
if (opts.disable) {
|
|
4645
|
+
lib.saveSyncConfig({ ...config, enabled: false });
|
|
4646
|
+
console.log("Trace sync is off on this machine. Uploaded traces stay in their workspaces until deleted there.");
|
|
4647
|
+
return;
|
|
4648
|
+
}
|
|
4649
|
+
const changedSettings = opts.enable || opts.route || opts.default || opts.keepLocal;
|
|
4650
|
+
if (changedSettings) {
|
|
4651
|
+
if (opts.enable && !apiToken()) throw new Error("not logged in \u2014 run `sky auth login` first");
|
|
4652
|
+
const rules = opts.route ? opts.route.map((spec) => {
|
|
4653
|
+
const at = spec.lastIndexOf("=");
|
|
4654
|
+
if (at <= 0 || at === spec.length - 1) throw new Error(`--route takes match=workspace, got "${spec}"`);
|
|
4655
|
+
return { match: spec.slice(0, at), workspace: spec.slice(at + 1) };
|
|
4656
|
+
}) : config.rules;
|
|
4657
|
+
const next = {
|
|
4658
|
+
...config,
|
|
4659
|
+
rules,
|
|
4660
|
+
enabled: opts.enable ? true : config.enabled,
|
|
4661
|
+
enabledAt: opts.enable ? (/* @__PURE__ */ new Date()).toISOString() : config.enabledAt,
|
|
4662
|
+
fallback: opts.keepLocal ? void 0 : opts.default ?? config.fallback
|
|
4663
|
+
};
|
|
4664
|
+
lib.saveSyncConfig(next);
|
|
4665
|
+
console.log(`Trace sync is ${next.enabled ? "on" : "off"} on this machine. It reads only:`);
|
|
4666
|
+
for (const folder of lib.sourceFolders()) console.log(` ${folder}`);
|
|
4667
|
+
console.log("and sends each session, redacted here first, to:");
|
|
4668
|
+
for (const rule of next.rules) console.log(` folder path contains "${rule.match}" \u2192 ${rule.workspace}`);
|
|
4669
|
+
console.log(` anything else \u2192 ${next.fallback ?? "stays on this machine"}`);
|
|
4670
|
+
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
4671
|
+
}
|
|
4672
|
+
if (opts.installSchedule) {
|
|
4673
|
+
const { join: join11 } = await import("path");
|
|
4674
|
+
const { homedir: homedir7 } = await import("os");
|
|
4675
|
+
const { execFileSync } = await import("child_process");
|
|
4676
|
+
const log = join11(homedir7(), ".config", "sky", "trace-sync.log");
|
|
4677
|
+
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
4678
|
+
try {
|
|
4679
|
+
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
4680
|
+
} catch {
|
|
4681
|
+
}
|
|
4682
|
+
execFileSync("launchctl", ["load", plist]);
|
|
4683
|
+
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
4684
|
+
}
|
|
4685
|
+
if (opts.status) {
|
|
4686
|
+
const { readFileSync: readFileSync13 } = await import("fs");
|
|
4687
|
+
const { join: join11 } = await import("path");
|
|
4688
|
+
let state = { sessions: {} };
|
|
4689
|
+
try {
|
|
4690
|
+
state = JSON.parse(readFileSync13(join11(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
4691
|
+
} catch {
|
|
4692
|
+
}
|
|
4693
|
+
const counts = /* @__PURE__ */ new Map();
|
|
4694
|
+
for (const entry2 of Object.values(state.sessions)) {
|
|
4695
|
+
const label = entry2.status === "stored" ? `stored in ${entry2.workspace}` : entry2.status;
|
|
4696
|
+
counts.set(label, (counts.get(label) ?? 0) + 1);
|
|
4697
|
+
}
|
|
4698
|
+
console.log(`Trace sync is ${config.enabled ? "on" : "off"}${config.enabledAt ? ` (since ${config.enabledAt})` : ""}. Last full sweep: ${state.lastFullAt ?? "never"}.`);
|
|
4699
|
+
for (const [label, count] of [...counts].sort((a, b) => b[1] - a[1])) console.log(` ${count} ${label}`);
|
|
4700
|
+
return;
|
|
4701
|
+
}
|
|
4702
|
+
if (changedSettings && !opts.full && !opts.session) return;
|
|
4703
|
+
const report = await lib.syncTraces({
|
|
4704
|
+
full: opts.full,
|
|
4705
|
+
only: opts.session,
|
|
4706
|
+
dryRun: opts.dryRun,
|
|
4707
|
+
limit: opts.limit,
|
|
4708
|
+
onProgress: opts.quiet ? void 0 : (line) => console.log(line)
|
|
4709
|
+
});
|
|
4710
|
+
if (!report) {
|
|
4711
|
+
if (!opts.quiet) console.log("Another sync is running on this machine; it will pick these up.");
|
|
4712
|
+
return;
|
|
4713
|
+
}
|
|
4714
|
+
if (!opts.quiet || report.failed) {
|
|
4715
|
+
const where = Object.entries(report.byWorkspace).map(([workspace2, count]) => `${count} \u2192 ${workspace2}`).join(", ");
|
|
4716
|
+
console.log(`${opts.dryRun ? "Would upload" : "Uploaded"} ${report.uploaded}${where ? ` (${where})` : ""}; unchanged ${report.unchanged}; kept local ${report.local}; empty ${report.empty}; another member's ${report.owned}; failed ${report.failed}. Scanned ${report.scanned}, ${report.changed} changed.`);
|
|
4717
|
+
for (const error of report.errors.slice(0, 20)) console.error(` ${error}`);
|
|
4718
|
+
}
|
|
4719
|
+
if (report.failed) process.exitCode = 1;
|
|
4720
|
+
})();
|
|
4721
|
+
});
|
|
4722
|
+
traceCmd.command("search <query...>").description("search every session in a workspace's trace library (literal substring)").option("--workspace <slug>", "the workspace to search (default: the current one)").option("--limit <n>", "hits per page", (v) => Number.parseInt(v, 10), 20).option("--cursor <cursor>", "the next page of an earlier search").option("--json", "print the page as JSON").action(run(async (words, opts) => {
|
|
4723
|
+
const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
|
|
4724
|
+
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4725
|
+
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4726
|
+
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
4727
|
+
const page = await libraryRequest("GET", `/context/search?${params}`, workspace2);
|
|
4728
|
+
if (opts.json) {
|
|
4729
|
+
console.log(JSON.stringify(page, null, 2));
|
|
4730
|
+
return;
|
|
4731
|
+
}
|
|
4732
|
+
if (!page.hits.length) console.log(page.partial ? `No hits, but the search was cut short (${page.coverage}).` : "No hits.");
|
|
4733
|
+
for (const { hit } of page.hits) {
|
|
4734
|
+
console.log(`${hit.occurredAt?.slice(0, 16).replace("T", " ") ?? " "} ${hit.title}`);
|
|
4735
|
+
console.log(` ${hit.snippet.replace(/\s+/g, " ")}`);
|
|
4736
|
+
}
|
|
4737
|
+
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
4738
|
+
}));
|
|
4739
|
+
traceCmd.command("library").description("list the sessions in a workspace's trace library, most recently updated first").option("--workspace <slug>", "the workspace (default: the current one)").option("--limit <n>", "how many", (v) => Number.parseInt(v, 10), 20).option("--json", "print the page as JSON").action(run(async (opts) => {
|
|
4740
|
+
const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
|
|
4741
|
+
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4742
|
+
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4743
|
+
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|
|
4744
|
+
if (opts.json) {
|
|
4745
|
+
console.log(JSON.stringify(page, null, 2));
|
|
4746
|
+
return;
|
|
4747
|
+
}
|
|
4748
|
+
console.log(`${page.total} sessions in ${workspace2}'s trace library`);
|
|
4749
|
+
for (const trace of page.traces) {
|
|
4750
|
+
const models = trace.models?.length ? `${trace.models[0]}${trace.models.length > 1 ? ` +${trace.models.length - 1}` : ""}` : "";
|
|
4751
|
+
const output = trace.usage?.output_tokens ? `${trace.usage.output_tokens.toLocaleString("en-US")} out` : "";
|
|
4752
|
+
const source = [models, output].filter(Boolean).join(" \xB7 ");
|
|
4753
|
+
console.log(`${trace.updated_at.slice(0, 16).replace("T", " ")} ${trace.harness.padEnd(11)} ${(trace.project ?? "").padEnd(16)} ${String(trace.messages).padStart(5)} msgs ${trace.title ?? trace.session_id}${source ? ` (${source})` : ""}`);
|
|
4754
|
+
}
|
|
4755
|
+
}));
|
|
4225
4756
|
var workerCmd = program.command("worker").description("run a pod on this machine and connect it to a worker pool");
|
|
4226
4757
|
workerCmd.command("start <pool>").description("start a worker connected to the given pool (wpool_\u2026 id)").option("--token <token>", "bearer token (default: $SKY_WORKER_TOKEN)").option("--label <label>", "a human-readable name for this machine", "cli").action(async (poolId, options) => {
|
|
4227
4758
|
const { startWorker } = await import("./worker-6Q4K4HZW.js");
|