@agentsky/sky 2.12.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-XXDQKVPS.js → chunk-2IKEJ74C.js} +989 -37
- package/dist/{chunk-7LUCHBX6.js → chunk-XJ5JLLXM.js} +2 -2
- package/dist/{chunk-RF5JICNX.js → chunk-Z3FPPGWX.js} +1 -1
- package/dist/index.js +686 -145
- package/dist/products.js +1 -1
- package/dist/{src-S777KCS5.js → src-3AI7XR44.js} +1 -1
- package/dist/{trace-UJTFSXNM.js → trace-DS35CFKV.js} +2 -2
- package/dist/{trace-library-474QJTAZ.js → trace-library-B3NZ4OQM.js} +32 -12
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveSessionRef,
|
|
10
10
|
streamEvents,
|
|
11
11
|
uploadFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-XJ5JLLXM.js";
|
|
13
13
|
import {
|
|
14
14
|
CLI_VERSION,
|
|
15
15
|
USER_AGENT,
|
|
@@ -20,12 +20,13 @@ import {
|
|
|
20
20
|
currentUniverse,
|
|
21
21
|
loadConfig,
|
|
22
22
|
saveConfig
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-Z3FPPGWX.js";
|
|
24
24
|
import {
|
|
25
25
|
AGENT_TYPES,
|
|
26
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
27
27
|
MODEL_SUBSCRIPTION_PROVIDER_NAME,
|
|
28
28
|
REASONING_EFFORT_VALUES,
|
|
29
|
+
SessionSourceCollector,
|
|
29
30
|
capabilitiesSchema,
|
|
30
31
|
customDataSchema,
|
|
31
32
|
defaultLlm,
|
|
@@ -39,12 +40,12 @@ import {
|
|
|
39
40
|
skillRefSchema,
|
|
40
41
|
splitMcpServers,
|
|
41
42
|
supersededBy
|
|
42
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-2IKEJ74C.js";
|
|
43
44
|
import "./chunk-MLKGABMK.js";
|
|
44
45
|
|
|
45
46
|
// src/index.ts
|
|
46
|
-
import { existsSync as
|
|
47
|
-
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";
|
|
48
49
|
import { createInterface as createInterface4 } from "readline";
|
|
49
50
|
import { Command } from "commander";
|
|
50
51
|
|
|
@@ -167,6 +168,123 @@ function renderGoal(goal2) {
|
|
|
167
168
|
return lines.join("\n");
|
|
168
169
|
}
|
|
169
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
|
+
|
|
170
288
|
// src/agent-file.ts
|
|
171
289
|
import { existsSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
172
290
|
import { dirname, resolve } from "path";
|
|
@@ -702,10 +820,222 @@ async function clone(opts) {
|
|
|
702
820
|
}
|
|
703
821
|
|
|
704
822
|
// src/import-local.ts
|
|
705
|
-
import { closeSync, existsSync as
|
|
706
|
-
import { homedir as
|
|
707
|
-
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";
|
|
708
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
|
|
709
1039
|
function redactLine2(raw) {
|
|
710
1040
|
return redactLine(raw).line;
|
|
711
1041
|
}
|
|
@@ -777,13 +1107,13 @@ function sniffTitle(filePath) {
|
|
|
777
1107
|
}
|
|
778
1108
|
}
|
|
779
1109
|
function listClaudeCodeSessions(limit) {
|
|
780
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
781
|
-
const projectsDir =
|
|
782
|
-
if (!
|
|
1110
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
|
|
1111
|
+
const projectsDir = join3(root, "projects");
|
|
1112
|
+
if (!existsSync4(projectsDir)) return [];
|
|
783
1113
|
const sessions = [];
|
|
784
1114
|
try {
|
|
785
1115
|
for (const encoded of readdirSync(projectsDir)) {
|
|
786
|
-
const projectDir =
|
|
1116
|
+
const projectDir = join3(projectsDir, encoded);
|
|
787
1117
|
let entries;
|
|
788
1118
|
try {
|
|
789
1119
|
entries = readdirSync(projectDir);
|
|
@@ -794,22 +1124,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
794
1124
|
for (const entry2 of entries) {
|
|
795
1125
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
796
1126
|
const sessionId = entry2.slice(0, -6);
|
|
797
|
-
const mainFile =
|
|
1127
|
+
const mainFile = join3(projectDir, entry2);
|
|
798
1128
|
let stat;
|
|
799
1129
|
try {
|
|
800
|
-
stat =
|
|
1130
|
+
stat = statSync2(mainFile);
|
|
801
1131
|
} catch {
|
|
802
1132
|
continue;
|
|
803
1133
|
}
|
|
804
1134
|
if (!stat.isFile()) continue;
|
|
805
1135
|
let discoveryError;
|
|
806
1136
|
const subagentFiles = [];
|
|
807
|
-
const subagentDir =
|
|
808
|
-
if (
|
|
1137
|
+
const subagentDir = join3(projectDir, sessionId, "subagents");
|
|
1138
|
+
if (existsSync4(subagentDir)) {
|
|
809
1139
|
try {
|
|
810
1140
|
const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
811
1141
|
for (const f of subFiles) {
|
|
812
|
-
subagentFiles.push(
|
|
1142
|
+
subagentFiles.push(join3(subagentDir, f));
|
|
813
1143
|
}
|
|
814
1144
|
} catch {
|
|
815
1145
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -831,7 +1161,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
831
1161
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
832
1162
|
}
|
|
833
1163
|
function listCodexSessions(limit) {
|
|
834
|
-
const sessionsDir =
|
|
1164
|
+
const sessionsDir = join3(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join3(homedir3(), ".codex"), "sessions");
|
|
835
1165
|
const records = [];
|
|
836
1166
|
function walk(dir) {
|
|
837
1167
|
let entries;
|
|
@@ -841,7 +1171,7 @@ function listCodexSessions(limit) {
|
|
|
841
1171
|
return;
|
|
842
1172
|
}
|
|
843
1173
|
for (const entry2 of entries) {
|
|
844
|
-
const full =
|
|
1174
|
+
const full = join3(dir, entry2.name);
|
|
845
1175
|
if (entry2.isDirectory()) {
|
|
846
1176
|
walk(full);
|
|
847
1177
|
continue;
|
|
@@ -859,7 +1189,7 @@ function listCodexSessions(limit) {
|
|
|
859
1189
|
sessionId: data.id,
|
|
860
1190
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
861
1191
|
title: sniffTitle(full),
|
|
862
|
-
mtimeMs:
|
|
1192
|
+
mtimeMs: statSync2(full).mtimeMs,
|
|
863
1193
|
files: [full]
|
|
864
1194
|
},
|
|
865
1195
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -928,7 +1258,7 @@ async function mintTrace(base, harness2) {
|
|
|
928
1258
|
function loadSummaryFile(summaryPath) {
|
|
929
1259
|
let raw;
|
|
930
1260
|
try {
|
|
931
|
-
raw =
|
|
1261
|
+
raw = readFileSync5(summaryPath, "utf8");
|
|
932
1262
|
} catch {
|
|
933
1263
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
934
1264
|
}
|
|
@@ -945,7 +1275,16 @@ function loadSummaryFile(summaryPath) {
|
|
|
945
1275
|
const redacted = redactLine2(raw.trimEnd());
|
|
946
1276
|
return redacted;
|
|
947
1277
|
}
|
|
948
|
-
|
|
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 = {}) {
|
|
949
1288
|
if (session2.discoveryError) throw new Error(session2.discoveryError);
|
|
950
1289
|
const maximum = 16 * 1024 * 1024;
|
|
951
1290
|
let inputBytes = 0;
|
|
@@ -957,10 +1296,12 @@ function prepareImport(session2, summaryPath) {
|
|
|
957
1296
|
let encryptedReasoningCount = 0;
|
|
958
1297
|
let compactionCount = 0;
|
|
959
1298
|
let unreadableCount = 0;
|
|
1299
|
+
const source = new SessionSourceCollector();
|
|
1300
|
+
const records = [];
|
|
960
1301
|
for (const path of session2.files) {
|
|
961
|
-
const stat =
|
|
1302
|
+
const stat = statSync2(path);
|
|
962
1303
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
963
|
-
const text =
|
|
1304
|
+
const text = readFileSync5(path, "utf8");
|
|
964
1305
|
for (const raw of text.split("\n")) {
|
|
965
1306
|
if (!raw.trim()) continue;
|
|
966
1307
|
let record;
|
|
@@ -973,6 +1314,8 @@ function prepareImport(session2, summaryPath) {
|
|
|
973
1314
|
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
|
|
974
1315
|
const rec = record;
|
|
975
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);
|
|
976
1319
|
if (rec.type === "response_item") {
|
|
977
1320
|
const payload = rec.payload;
|
|
978
1321
|
if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
|
|
@@ -996,6 +1339,9 @@ function prepareImport(session2, summaryPath) {
|
|
|
996
1339
|
if (!lines.length) throw new Error("The selected transcript is empty.");
|
|
997
1340
|
const sourceId = nativeId ?? session2.sessionId;
|
|
998
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).` : "";
|
|
999
1345
|
let summaryJson;
|
|
1000
1346
|
if (summaryPath) {
|
|
1001
1347
|
summaryJson = loadSummaryFile(summaryPath);
|
|
@@ -1004,10 +1350,10 @@ function prepareImport(session2, summaryPath) {
|
|
|
1004
1350
|
format: "asteroids.session-summary",
|
|
1005
1351
|
format_version: 1,
|
|
1006
1352
|
source_harness: session2.harness,
|
|
1007
|
-
source_harness_version: null,
|
|
1353
|
+
source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
|
|
1008
1354
|
source_session_id: sourceId,
|
|
1009
1355
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1010
|
-
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.`,
|
|
1011
1357
|
context: {
|
|
1012
1358
|
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
1013
1359
|
constraints: [],
|
|
@@ -1015,19 +1361,19 @@ function prepareImport(session2, summaryPath) {
|
|
|
1015
1361
|
completed: [],
|
|
1016
1362
|
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1017
1363
|
verification: [],
|
|
1018
|
-
blockers: [
|
|
1019
|
-
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).` })),
|
|
1020
1366
|
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1021
1367
|
}
|
|
1022
1368
|
};
|
|
1023
1369
|
summaryJson = JSON.stringify(summary);
|
|
1024
1370
|
}
|
|
1025
|
-
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";
|
|
1026
1372
|
const gaps = [];
|
|
1027
1373
|
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1028
1374
|
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1029
1375
|
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1030
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
|
|
1376
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
|
|
1031
1377
|
}
|
|
1032
1378
|
async function importSession(opts) {
|
|
1033
1379
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1036,7 +1382,7 @@ async function importSession(opts) {
|
|
|
1036
1382
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1037
1383
|
}
|
|
1038
1384
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1039
|
-
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 });
|
|
1040
1386
|
let traceId;
|
|
1041
1387
|
let key;
|
|
1042
1388
|
if (opts.importKey && opts.grantTraceId) {
|
|
@@ -1068,7 +1414,9 @@ async function importSession(opts) {
|
|
|
1068
1414
|
transcriptFiles: opts.session.files.length,
|
|
1069
1415
|
bytes: outputBytes,
|
|
1070
1416
|
redactionCount,
|
|
1071
|
-
gaps
|
|
1417
|
+
gaps,
|
|
1418
|
+
attachedFiles,
|
|
1419
|
+
leftOutFiles
|
|
1072
1420
|
};
|
|
1073
1421
|
}
|
|
1074
1422
|
|
|
@@ -1105,7 +1453,7 @@ async function login(opts) {
|
|
|
1105
1453
|
}
|
|
1106
1454
|
async function browserLogin(verifier, challenge) {
|
|
1107
1455
|
const state = b64url(randomBytes(16));
|
|
1108
|
-
const code = await new Promise((
|
|
1456
|
+
const code = await new Promise((resolve8, reject) => {
|
|
1109
1457
|
const server = createServer((req, res) => {
|
|
1110
1458
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1111
1459
|
if (url.pathname !== "/callback") {
|
|
@@ -1118,7 +1466,7 @@ async function browserLogin(verifier, challenge) {
|
|
|
1118
1466
|
if (url.searchParams.get("state") !== state || !got) {
|
|
1119
1467
|
reject(new Error("login callback state mismatch"));
|
|
1120
1468
|
} else {
|
|
1121
|
-
|
|
1469
|
+
resolve8(got);
|
|
1122
1470
|
}
|
|
1123
1471
|
server.close();
|
|
1124
1472
|
});
|
|
@@ -1172,23 +1520,23 @@ async function headlessLogin(verifier, challenge) {
|
|
|
1172
1520
|
}
|
|
1173
1521
|
|
|
1174
1522
|
// src/local-agents.ts
|
|
1175
|
-
import { existsSync as
|
|
1176
|
-
import { homedir as
|
|
1177
|
-
import { join as
|
|
1178
|
-
function localAgents(env = process.env, home =
|
|
1179
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
1180
|
-
const codex = env.CODEX_HOME ||
|
|
1181
|
-
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");
|
|
1182
1530
|
return [
|
|
1183
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir:
|
|
1184
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
1185
|
-
{ 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") }
|
|
1186
1534
|
];
|
|
1187
1535
|
}
|
|
1188
|
-
function detectLocalAgents(env = process.env, home =
|
|
1189
|
-
return localAgents(env, home).filter((agent2) =>
|
|
1536
|
+
function detectLocalAgents(env = process.env, home = homedir4()) {
|
|
1537
|
+
return localAgents(env, home).filter((agent2) => existsSync5(agent2.root));
|
|
1190
1538
|
}
|
|
1191
|
-
function selectLocalAgents(ids, env = process.env, home =
|
|
1539
|
+
function selectLocalAgents(ids, env = process.env, home = homedir4()) {
|
|
1192
1540
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
1193
1541
|
const known = localAgents(env, home);
|
|
1194
1542
|
return ids.map((id) => {
|
|
@@ -1282,21 +1630,21 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
1282
1630
|
for (; ; ) {
|
|
1283
1631
|
const reading = latestTurn(await history(sessionId));
|
|
1284
1632
|
if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
|
|
1285
|
-
await new Promise((
|
|
1633
|
+
await new Promise((resolve8) => setTimeout(resolve8, opts.intervalMs ?? 3e3));
|
|
1286
1634
|
}
|
|
1287
1635
|
}
|
|
1288
1636
|
|
|
1289
1637
|
// src/setup.ts
|
|
1290
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1291
|
-
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";
|
|
1292
1640
|
import { createInterface as createInterface3 } from "readline";
|
|
1293
1641
|
|
|
1294
1642
|
// src/mcp-register.ts
|
|
1295
1643
|
import { spawnSync } from "child_process";
|
|
1296
1644
|
|
|
1297
1645
|
// src/skill.ts
|
|
1298
|
-
import { mkdirSync, readFileSync as
|
|
1299
|
-
import { join as
|
|
1646
|
+
import { mkdirSync, readFileSync as readFileSync6, writeFileSync as writeFileSync3 } from "fs";
|
|
1647
|
+
import { join as join5 } from "path";
|
|
1300
1648
|
|
|
1301
1649
|
// src/catalog-file.ts
|
|
1302
1650
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -1737,7 +2085,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
1737
2085
|
}
|
|
1738
2086
|
function installedVersion(dir) {
|
|
1739
2087
|
try {
|
|
1740
|
-
const marker = JSON.parse(
|
|
2088
|
+
const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
|
|
1741
2089
|
return typeof marker.version === "string" ? marker.version : null;
|
|
1742
2090
|
} catch {
|
|
1743
2091
|
return null;
|
|
@@ -1747,25 +2095,25 @@ function isInstalledSkill(dir) {
|
|
|
1747
2095
|
return installedVersion(dir) !== null;
|
|
1748
2096
|
}
|
|
1749
2097
|
function installSkill(agent2, opts = {}) {
|
|
1750
|
-
const dir =
|
|
2098
|
+
const dir = join5(agent2.skillsDir, SKILL_NAME);
|
|
1751
2099
|
const previous = installedVersion(dir);
|
|
1752
2100
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
1753
2101
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
1754
2102
|
if (outcome !== "current") {
|
|
1755
2103
|
mkdirSync(dir, { recursive: true });
|
|
1756
|
-
writeFileSync3(
|
|
1757
|
-
writeFileSync3(
|
|
2104
|
+
writeFileSync3(join5(dir, "SKILL.md"), skillDocument());
|
|
2105
|
+
writeFileSync3(join5(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
1758
2106
|
`);
|
|
1759
2107
|
}
|
|
1760
2108
|
if (opts.catalog) {
|
|
1761
2109
|
mkdirSync(dir, { recursive: true });
|
|
1762
|
-
writeFileSync3(
|
|
2110
|
+
writeFileSync3(join5(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
1763
2111
|
}
|
|
1764
|
-
const refsDir =
|
|
2112
|
+
const refsDir = join5(dir, REFERENCES_DIR);
|
|
1765
2113
|
mkdirSync(refsDir, { recursive: true });
|
|
1766
|
-
writeFileSync3(
|
|
1767
|
-
writeFileSync3(
|
|
1768
|
-
writeFileSync3(
|
|
2114
|
+
writeFileSync3(join5(refsDir, "import.md"), importReferenceDoc());
|
|
2115
|
+
writeFileSync3(join5(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2116
|
+
writeFileSync3(join5(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
1769
2117
|
return { agent: agent2.id, path: dir, outcome };
|
|
1770
2118
|
}
|
|
1771
2119
|
|
|
@@ -1816,9 +2164,9 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
1816
2164
|
}
|
|
1817
2165
|
|
|
1818
2166
|
// src/setup-local.ts
|
|
1819
|
-
import { existsSync as
|
|
1820
|
-
import { homedir as
|
|
1821
|
-
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";
|
|
1822
2170
|
import { parse as parseToml2 } from "smol-toml";
|
|
1823
2171
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
1824
2172
|
var MAX_IMPORT_DEPTH = 5;
|
|
@@ -1834,11 +2182,11 @@ function sensitive(name) {
|
|
|
1834
2182
|
function skillFiles(root, prefix) {
|
|
1835
2183
|
const files2 = [];
|
|
1836
2184
|
for (const entry2 of readdirSync2(root, { withFileTypes: true })) {
|
|
1837
|
-
const path =
|
|
2185
|
+
const path = join6(root, entry2.name);
|
|
1838
2186
|
if (entry2.isDirectory()) {
|
|
1839
2187
|
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles(path, `${prefix}/${entry2.name}`));
|
|
1840
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
1841
|
-
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)) });
|
|
1842
2190
|
}
|
|
1843
2191
|
}
|
|
1844
2192
|
return files2;
|
|
@@ -1847,19 +2195,19 @@ function readLocalSkills(agents) {
|
|
|
1847
2195
|
const skills2 = [];
|
|
1848
2196
|
const ignored = [];
|
|
1849
2197
|
for (const agent2 of agents) {
|
|
1850
|
-
if (!
|
|
2198
|
+
if (!existsSync6(agent2.skillsDir)) continue;
|
|
1851
2199
|
for (const entry2 of readdirSync2(agent2.skillsDir, { withFileTypes: true })) {
|
|
1852
|
-
const dir =
|
|
2200
|
+
const dir = join6(agent2.skillsDir, entry2.name);
|
|
1853
2201
|
let isDir = entry2.isDirectory();
|
|
1854
2202
|
if (entry2.isSymbolicLink()) {
|
|
1855
2203
|
try {
|
|
1856
|
-
isDir =
|
|
2204
|
+
isDir = statSync3(dir).isDirectory();
|
|
1857
2205
|
} catch {
|
|
1858
2206
|
isDir = false;
|
|
1859
2207
|
}
|
|
1860
2208
|
}
|
|
1861
|
-
if (!isDir || !
|
|
1862
|
-
const name = frontmatterName(
|
|
2209
|
+
if (!isDir || !existsSync6(join6(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
2210
|
+
const name = frontmatterName(readFileSync7(join6(dir, "SKILL.md"), "utf8"));
|
|
1863
2211
|
if (!name) {
|
|
1864
2212
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
1865
2213
|
continue;
|
|
@@ -1875,7 +2223,7 @@ function readLocalSkills(agents) {
|
|
|
1875
2223
|
}
|
|
1876
2224
|
return { skills: skills2, ignored };
|
|
1877
2225
|
}
|
|
1878
|
-
var
|
|
2226
|
+
var INSTRUCTION_FILES2 = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
|
|
1879
2227
|
function inlineImports(content, from, home, depth, seen) {
|
|
1880
2228
|
let fence;
|
|
1881
2229
|
return content.split(/\r?\n/).map((line) => {
|
|
@@ -1887,11 +2235,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
1887
2235
|
}
|
|
1888
2236
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
1889
2237
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
1890
|
-
const path = target.startsWith("~/") ?
|
|
2238
|
+
const path = target.startsWith("~/") ? join6(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname3(from), target);
|
|
1891
2239
|
if (seen.includes(path)) return "";
|
|
1892
2240
|
let imported;
|
|
1893
2241
|
try {
|
|
1894
|
-
imported =
|
|
2242
|
+
imported = readFileSync7(path, "utf8");
|
|
1895
2243
|
} catch {
|
|
1896
2244
|
return line;
|
|
1897
2245
|
}
|
|
@@ -1899,15 +2247,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
1899
2247
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
1900
2248
|
}).join("\n");
|
|
1901
2249
|
}
|
|
1902
|
-
function readGlobalInstructions(agents, home =
|
|
2250
|
+
function readGlobalInstructions(agents, home = homedir5()) {
|
|
1903
2251
|
const found = [];
|
|
1904
2252
|
for (const agent2 of agents) {
|
|
1905
|
-
const file =
|
|
2253
|
+
const file = INSTRUCTION_FILES2[agent2.id];
|
|
1906
2254
|
if (!file) continue;
|
|
1907
|
-
const path =
|
|
2255
|
+
const path = join6(agent2.root, file);
|
|
1908
2256
|
let raw;
|
|
1909
2257
|
try {
|
|
1910
|
-
raw =
|
|
2258
|
+
raw = readFileSync7(path, "utf8");
|
|
1911
2259
|
} catch {
|
|
1912
2260
|
continue;
|
|
1913
2261
|
}
|
|
@@ -1926,7 +2274,7 @@ ${item.content.trim()}`).join("\n\n");
|
|
|
1926
2274
|
}
|
|
1927
2275
|
function readJson2(path) {
|
|
1928
2276
|
try {
|
|
1929
|
-
return JSON.parse(
|
|
2277
|
+
return JSON.parse(readFileSync7(path, "utf8"));
|
|
1930
2278
|
} catch {
|
|
1931
2279
|
return void 0;
|
|
1932
2280
|
}
|
|
@@ -1940,15 +2288,15 @@ function bearerOf(def) {
|
|
|
1940
2288
|
return match?.[1] ?? null;
|
|
1941
2289
|
}
|
|
1942
2290
|
function serversOf(agent2, home) {
|
|
1943
|
-
if (agent2.id === "claude_code") return readJson2(
|
|
1944
|
-
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 ?? {};
|
|
1945
2293
|
try {
|
|
1946
|
-
return parseToml2(
|
|
2294
|
+
return parseToml2(readFileSync7(join6(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
1947
2295
|
} catch {
|
|
1948
2296
|
return {};
|
|
1949
2297
|
}
|
|
1950
2298
|
}
|
|
1951
|
-
function findLocalSecrets(agents, home =
|
|
2299
|
+
function findLocalSecrets(agents, home = homedir5()) {
|
|
1952
2300
|
const secrets2 = [];
|
|
1953
2301
|
for (const agent2 of agents) {
|
|
1954
2302
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
@@ -1964,9 +2312,9 @@ function findLocalSecrets(agents, home = homedir4()) {
|
|
|
1964
2312
|
|
|
1965
2313
|
// src/subscriptions.ts
|
|
1966
2314
|
import { spawn as spawn3 } from "child_process";
|
|
1967
|
-
import { existsSync as
|
|
1968
|
-
import { homedir as
|
|
1969
|
-
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";
|
|
1970
2318
|
import { createInterface as createInterface2 } from "readline";
|
|
1971
2319
|
var ALIASES = {
|
|
1972
2320
|
claude: "anthropic",
|
|
@@ -2051,12 +2399,12 @@ async function connectClaude() {
|
|
|
2051
2399
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
2052
2400
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
2053
2401
|
}
|
|
2054
|
-
function codexSignIn(env = process.env, home =
|
|
2055
|
-
const path =
|
|
2056
|
-
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)) {
|
|
2057
2405
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
2058
2406
|
}
|
|
2059
|
-
const raw =
|
|
2407
|
+
const raw = readFileSync8(path, "utf8");
|
|
2060
2408
|
try {
|
|
2061
2409
|
const parsed = JSON.parse(raw);
|
|
2062
2410
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -2064,12 +2412,12 @@ function codexSignIn(env = process.env, home = homedir5()) {
|
|
|
2064
2412
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
2065
2413
|
}
|
|
2066
2414
|
}
|
|
2067
|
-
function cursorSignIn(env = process.env, home =
|
|
2068
|
-
const path =
|
|
2069
|
-
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)) {
|
|
2070
2418
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
2071
2419
|
}
|
|
2072
|
-
const raw =
|
|
2420
|
+
const raw = readFileSync8(path, "utf8");
|
|
2073
2421
|
try {
|
|
2074
2422
|
const parsed = JSON.parse(raw);
|
|
2075
2423
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -2148,11 +2496,11 @@ function planProviders(names) {
|
|
|
2148
2496
|
}));
|
|
2149
2497
|
}
|
|
2150
2498
|
function reportPath() {
|
|
2151
|
-
return
|
|
2499
|
+
return join8(dirname4(configPath()), "setup.json");
|
|
2152
2500
|
}
|
|
2153
2501
|
function loadReport() {
|
|
2154
2502
|
try {
|
|
2155
|
-
const saved = JSON.parse(
|
|
2503
|
+
const saved = JSON.parse(readFileSync9(reportPath(), "utf8"));
|
|
2156
2504
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
2157
2505
|
} catch {
|
|
2158
2506
|
return { agents: [], plans: [] };
|
|
@@ -2165,7 +2513,7 @@ function mergeReport(base, next) {
|
|
|
2165
2513
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
2166
2514
|
}
|
|
2167
2515
|
function saveReport(report) {
|
|
2168
|
-
mkdirSync2(
|
|
2516
|
+
mkdirSync2(dirname4(reportPath()), { recursive: true });
|
|
2169
2517
|
writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
2170
2518
|
`, { mode: 384 });
|
|
2171
2519
|
}
|
|
@@ -2515,17 +2863,17 @@ function renderArtifact(artifact2) {
|
|
|
2515
2863
|
}
|
|
2516
2864
|
|
|
2517
2865
|
// src/skills-store.ts
|
|
2518
|
-
import { readdirSync as readdirSync3, readFileSync as
|
|
2519
|
-
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";
|
|
2520
2868
|
function skillBundle(dir) {
|
|
2521
|
-
const root =
|
|
2522
|
-
const top =
|
|
2869
|
+
const root = resolve5(dir);
|
|
2870
|
+
const top = basename4(root);
|
|
2523
2871
|
const files2 = [];
|
|
2524
2872
|
const walk = (path) => {
|
|
2525
2873
|
for (const name of readdirSync3(path).sort()) {
|
|
2526
|
-
const full =
|
|
2527
|
-
if (
|
|
2528
|
-
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) });
|
|
2529
2877
|
}
|
|
2530
2878
|
};
|
|
2531
2879
|
walk(root);
|
|
@@ -2535,7 +2883,7 @@ function skillBundle(dir) {
|
|
|
2535
2883
|
async function uploadSkill(dir, displayName) {
|
|
2536
2884
|
const form = new FormData();
|
|
2537
2885
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
2538
|
-
form.append("displayName", displayName ??
|
|
2886
|
+
form.append("displayName", displayName ?? basename4(resolve5(dir)));
|
|
2539
2887
|
return apiForm("POST", "/skills", form);
|
|
2540
2888
|
}
|
|
2541
2889
|
async function listSkills() {
|
|
@@ -2653,19 +3001,19 @@ function renderPolicy(rule) {
|
|
|
2653
3001
|
}
|
|
2654
3002
|
|
|
2655
3003
|
// src/harness.ts
|
|
2656
|
-
import { existsSync as
|
|
2657
|
-
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";
|
|
2658
3006
|
var HARNESS_FILE = "harness.json";
|
|
2659
3007
|
function harnessFilePath(dir) {
|
|
2660
|
-
return
|
|
3008
|
+
return join10(resolve6(dir), HARNESS_FILE);
|
|
2661
3009
|
}
|
|
2662
3010
|
function readHarnessFile(dir) {
|
|
2663
3011
|
const path = harnessFilePath(dir);
|
|
2664
|
-
if (!
|
|
2665
|
-
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`);
|
|
2666
3014
|
}
|
|
2667
3015
|
try {
|
|
2668
|
-
return JSON.parse(
|
|
3016
|
+
return JSON.parse(readFileSync11(path, "utf8"));
|
|
2669
3017
|
} catch (err) {
|
|
2670
3018
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2671
3019
|
}
|
|
@@ -2692,10 +3040,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
2692
3040
|
`;
|
|
2693
3041
|
function harnessInit(dir) {
|
|
2694
3042
|
const path = harnessFilePath(dir);
|
|
2695
|
-
if (
|
|
3043
|
+
if (existsSync8(path)) {
|
|
2696
3044
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
2697
3045
|
}
|
|
2698
|
-
mkdirSync3(
|
|
3046
|
+
mkdirSync3(dirname5(path), { recursive: true });
|
|
2699
3047
|
writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
2700
3048
|
`);
|
|
2701
3049
|
console.log(`Created ${path}`);
|
|
@@ -3200,6 +3548,38 @@ session.command("read <ref>").description("what the agent has said since the las
|
|
|
3200
3548
|
if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
|
|
3201
3549
|
})
|
|
3202
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
|
+
);
|
|
3203
3583
|
var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
|
|
3204
3584
|
var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
|
|
3205
3585
|
sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
|
|
@@ -3416,9 +3796,9 @@ secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the a
|
|
|
3416
3796
|
);
|
|
3417
3797
|
secrets.command("list <id>").action(
|
|
3418
3798
|
run(async (id) => {
|
|
3419
|
-
const { secrets:
|
|
3420
|
-
if (!
|
|
3421
|
-
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) {
|
|
3422
3802
|
console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
3423
3803
|
}
|
|
3424
3804
|
})
|
|
@@ -3434,9 +3814,9 @@ secrets.command("unset <id> <key>").action(
|
|
|
3434
3814
|
);
|
|
3435
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(
|
|
3436
3816
|
run(async (opts) => {
|
|
3437
|
-
const path =
|
|
3438
|
-
if (!
|
|
3439
|
-
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";
|
|
3440
3820
|
scaffoldAgentFile(path, name);
|
|
3441
3821
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
3442
3822
|
}
|
|
@@ -3467,7 +3847,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
|
|
|
3467
3847
|
);
|
|
3468
3848
|
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
3469
3849
|
run(async (opts) => {
|
|
3470
|
-
const path =
|
|
3850
|
+
const path = resolve7(opts.file);
|
|
3471
3851
|
const { file, spec } = readAgentFile(path);
|
|
3472
3852
|
if (!file.id) {
|
|
3473
3853
|
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
@@ -3510,7 +3890,7 @@ function formatRelative(msAgo) {
|
|
|
3510
3890
|
if (hours < 48) return `${hours}h ago`;
|
|
3511
3891
|
return `${Math.round(hours / 24)}d ago`;
|
|
3512
3892
|
}
|
|
3513
|
-
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(
|
|
3514
3894
|
run(
|
|
3515
3895
|
async (opts) => {
|
|
3516
3896
|
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
@@ -3570,11 +3950,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3570
3950
|
console.log();
|
|
3571
3951
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3572
3952
|
const answer = await new Promise(
|
|
3573
|
-
(
|
|
3953
|
+
(resolve8) => rl.question(
|
|
3574
3954
|
`Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
|
|
3575
3955
|
(a) => {
|
|
3576
3956
|
rl.close();
|
|
3577
|
-
|
|
3957
|
+
resolve8(a.trim());
|
|
3578
3958
|
}
|
|
3579
3959
|
)
|
|
3580
3960
|
);
|
|
@@ -3592,14 +3972,14 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3592
3972
|
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
3593
3973
|
if (!opts.json) {
|
|
3594
3974
|
console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
|
|
3595
|
-
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.");
|
|
3596
3976
|
}
|
|
3597
3977
|
if (!autoConfirm) {
|
|
3598
3978
|
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
3599
3979
|
const prompt = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3600
|
-
const answer = await new Promise((
|
|
3980
|
+
const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
3601
3981
|
prompt.close();
|
|
3602
|
-
|
|
3982
|
+
resolve8(value);
|
|
3603
3983
|
}));
|
|
3604
3984
|
if (!/^y(?:es)?$/i.test(answer.trim())) {
|
|
3605
3985
|
console.log("Cancelled. Nothing uploaded.");
|
|
@@ -3617,6 +3997,7 @@ Importing ${label} \u2026
|
|
|
3617
3997
|
summaryPath: opts.summary,
|
|
3618
3998
|
importKey: opts.key,
|
|
3619
3999
|
grantTraceId: opts.trace,
|
|
4000
|
+
files: opts.files,
|
|
3620
4001
|
onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
|
|
3621
4002
|
`)
|
|
3622
4003
|
});
|
|
@@ -3631,9 +4012,12 @@ Importing ${label} \u2026
|
|
|
3631
4012
|
bytes: result.bytes,
|
|
3632
4013
|
redactionCount: result.redactionCount,
|
|
3633
4014
|
gaps: result.gaps,
|
|
4015
|
+
attachedFiles: result.attachedFiles,
|
|
4016
|
+
leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
|
|
3634
4017
|
continueUrl: result.url
|
|
3635
4018
|
}));
|
|
3636
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)` : ""}.`);
|
|
3637
4021
|
console.log(` \u2713 Imported. Continue here:
|
|
3638
4022
|
${result.url}
|
|
3639
4023
|
`);
|
|
@@ -3700,7 +4084,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
3700
4084
|
);
|
|
3701
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(
|
|
3702
4086
|
run(async (taskId, path, local) => {
|
|
3703
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
4087
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync12(local), mediaTypeFor(local));
|
|
3704
4088
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
3705
4089
|
})
|
|
3706
4090
|
);
|
|
@@ -3865,6 +4249,19 @@ for (const [verb, blurb] of [
|
|
|
3865
4249
|
})
|
|
3866
4250
|
);
|
|
3867
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
|
+
);
|
|
3868
4265
|
var collect = (value, previous = []) => [...previous, value];
|
|
3869
4266
|
function goalPath(sessionId, suffix = "") {
|
|
3870
4267
|
return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
|
|
@@ -3959,6 +4356,147 @@ program.command("config").description("show the CLI config location and current
|
|
|
3959
4356
|
console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
|
|
3960
4357
|
console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
|
|
3961
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
|
+
);
|
|
3962
4500
|
var harness = program.command("harness").description("workspace harnesses: init, verify, push");
|
|
3963
4501
|
harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
|
|
3964
4502
|
harnessInit(dir ?? ".");
|
|
@@ -3982,7 +4520,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
3982
4520
|
}));
|
|
3983
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)");
|
|
3984
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) => {
|
|
3985
|
-
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");
|
|
3986
4524
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
3987
4525
|
const scopes = parseContentScopes(opts.capture);
|
|
3988
4526
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -4011,13 +4549,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
4011
4549
|
]);
|
|
4012
4550
|
console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
|
|
4013
4551
|
console.log(scopes.length ? ` content: ${scopes.join(", ")} (redacted here before it leaves)` : " metadata only");
|
|
4014
|
-
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"))}`);
|
|
4015
4553
|
console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
|
|
4016
4554
|
console.log(` sky trace show ${trace.id}`);
|
|
4017
4555
|
}));
|
|
4018
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 () => {
|
|
4019
4557
|
try {
|
|
4020
|
-
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");
|
|
4021
4559
|
const chunks = [];
|
|
4022
4560
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4023
4561
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -4030,14 +4568,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
4030
4568
|
}
|
|
4031
4569
|
});
|
|
4032
4570
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
4033
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
4571
|
+
const { readState, flush, grantSender } = await import("./trace-DS35CFKV.js");
|
|
4034
4572
|
const state = readState();
|
|
4035
4573
|
if (!state) throw new Error("no trace is running here");
|
|
4036
4574
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
4037
4575
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
4038
4576
|
}));
|
|
4039
4577
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
4040
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
4578
|
+
const { readState, spoolStatus } = await import("./trace-DS35CFKV.js");
|
|
4041
4579
|
const state = readState();
|
|
4042
4580
|
if (!state) {
|
|
4043
4581
|
console.log("No trace is running here.");
|
|
@@ -4047,7 +4585,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
4047
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`);
|
|
4048
4586
|
}));
|
|
4049
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) => {
|
|
4050
|
-
const { readState } = await import("./trace-
|
|
4588
|
+
const { readState } = await import("./trace-DS35CFKV.js");
|
|
4051
4589
|
const ref = id ?? readState()?.trace_id;
|
|
4052
4590
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
4053
4591
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -4074,7 +4612,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
4074
4612
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
4075
4613
|
}));
|
|
4076
4614
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
4077
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
4615
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-DS35CFKV.js");
|
|
4078
4616
|
const state = readState();
|
|
4079
4617
|
if (!state) throw new Error("no trace is running here");
|
|
4080
4618
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -4084,7 +4622,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
4084
4622
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
4085
4623
|
}));
|
|
4086
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) => {
|
|
4087
|
-
const lib = await import("./trace-library-
|
|
4625
|
+
const lib = await import("./trace-library-B3NZ4OQM.js");
|
|
4088
4626
|
if (opts.hook) {
|
|
4089
4627
|
try {
|
|
4090
4628
|
let raw = payload ?? "";
|
|
@@ -4132,10 +4670,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4132
4670
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
4133
4671
|
}
|
|
4134
4672
|
if (opts.installSchedule) {
|
|
4135
|
-
const { join:
|
|
4136
|
-
const { homedir:
|
|
4673
|
+
const { join: join11 } = await import("path");
|
|
4674
|
+
const { homedir: homedir7 } = await import("os");
|
|
4137
4675
|
const { execFileSync } = await import("child_process");
|
|
4138
|
-
const log =
|
|
4676
|
+
const log = join11(homedir7(), ".config", "sky", "trace-sync.log");
|
|
4139
4677
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
4140
4678
|
try {
|
|
4141
4679
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -4145,11 +4683,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4145
4683
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
4146
4684
|
}
|
|
4147
4685
|
if (opts.status) {
|
|
4148
|
-
const { readFileSync:
|
|
4149
|
-
const { join:
|
|
4686
|
+
const { readFileSync: readFileSync13 } = await import("fs");
|
|
4687
|
+
const { join: join11 } = await import("path");
|
|
4150
4688
|
let state = { sessions: {} };
|
|
4151
4689
|
try {
|
|
4152
|
-
state = JSON.parse(
|
|
4690
|
+
state = JSON.parse(readFileSync13(join11(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
4153
4691
|
} catch {
|
|
4154
4692
|
}
|
|
4155
4693
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -4182,7 +4720,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4182
4720
|
})();
|
|
4183
4721
|
});
|
|
4184
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) => {
|
|
4185
|
-
const { libraryRequest } = await import("./trace-library-
|
|
4723
|
+
const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
|
|
4186
4724
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4187
4725
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4188
4726
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -4199,7 +4737,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
4199
4737
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
4200
4738
|
}));
|
|
4201
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) => {
|
|
4202
|
-
const { libraryRequest } = await import("./trace-library-
|
|
4740
|
+
const { libraryRequest } = await import("./trace-library-B3NZ4OQM.js");
|
|
4203
4741
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4204
4742
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4205
4743
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|
|
@@ -4209,7 +4747,10 @@ traceCmd.command("library").description("list the sessions in a workspace's trac
|
|
|
4209
4747
|
}
|
|
4210
4748
|
console.log(`${page.total} sessions in ${workspace2}'s trace library`);
|
|
4211
4749
|
for (const trace of page.traces) {
|
|
4212
|
-
|
|
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})` : ""}`);
|
|
4213
4754
|
}
|
|
4214
4755
|
}));
|
|
4215
4756
|
var workerCmd = program.command("worker").description("run a pod on this machine and connect it to a worker pool");
|