@agentsky/sky 2.12.0 → 2.14.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-7OM2B5SE.js} +989 -37
- package/dist/{chunk-7LUCHBX6.js → chunk-FOJP33RS.js} +2 -2
- package/dist/{chunk-RF5JICNX.js → chunk-VJEE2ALN.js} +1 -1
- package/dist/index.js +1002 -172
- package/dist/products.js +1 -1
- package/dist/{src-S777KCS5.js → src-EJU5U2GO.js} +1 -1
- package/dist/{trace-UJTFSXNM.js → trace-YKEGRVJN.js} +2 -2
- package/dist/{trace-library-474QJTAZ.js → trace-library-6BK5GJVD.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-FOJP33RS.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-VJEE2ALN.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-7OM2B5SE.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 existsSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
48
|
+
import { basename as basename6, 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";
|
|
@@ -581,7 +699,7 @@ async function createClone(local, opts) {
|
|
|
581
699
|
console.log("Aborted \u2014 nothing sent.");
|
|
582
700
|
return;
|
|
583
701
|
}
|
|
584
|
-
const { servers:
|
|
702
|
+
const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
|
|
585
703
|
if (skipped.length) {
|
|
586
704
|
console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
|
|
587
705
|
}
|
|
@@ -589,7 +707,7 @@ async function createClone(local, opts) {
|
|
|
589
707
|
agentType: local.harness,
|
|
590
708
|
...local.model ? { llm: local.model } : {},
|
|
591
709
|
...local.prompt ? { prompt: local.prompt } : {},
|
|
592
|
-
...
|
|
710
|
+
...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
|
|
593
711
|
};
|
|
594
712
|
let sentLlm = local.model ?? null;
|
|
595
713
|
let created;
|
|
@@ -702,10 +820,495 @@ 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 existsSync5, openSync, readdirSync as readdirSync2, readFileSync as readFileSync6, readSync, statSync as statSync3 } from "fs";
|
|
824
|
+
import { homedir as homedir4 } from "os";
|
|
825
|
+
import { join as join4 } from "path";
|
|
708
826
|
import { createHash } from "crypto";
|
|
827
|
+
|
|
828
|
+
// src/import-files.ts
|
|
829
|
+
import { isUtf8 } from "buffer";
|
|
830
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
831
|
+
import { homedir as homedir3 } from "os";
|
|
832
|
+
import { dirname as dirname3, extname as extname2, isAbsolute, join as join3, relative as relative2, resolve as resolve3, sep as sep2, basename as basename4 } from "path";
|
|
833
|
+
|
|
834
|
+
// src/import-workspace.ts
|
|
835
|
+
import { spawnSync } from "child_process";
|
|
836
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, realpathSync, statSync } from "fs";
|
|
837
|
+
import { homedir as homedir2 } from "os";
|
|
838
|
+
import { basename as basename3, dirname as dirname2, join as join2, relative, sep } from "path";
|
|
839
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
840
|
+
var MAX_TREE = 5e3;
|
|
841
|
+
var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
|
|
842
|
+
function git(cwd, ...args) {
|
|
843
|
+
const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
|
|
844
|
+
return run2.status === 0 ? run2.stdout : null;
|
|
845
|
+
}
|
|
846
|
+
var isFile = (path) => {
|
|
847
|
+
try {
|
|
848
|
+
return statSync(path).isFile();
|
|
849
|
+
} catch {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
var isDir = (path) => {
|
|
854
|
+
try {
|
|
855
|
+
return statSync(path).isDirectory();
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
function sharedInstructionPaths(home = homedir2()) {
|
|
861
|
+
const rules = join2(home, ".agents", "rules");
|
|
862
|
+
let ruleFiles = [];
|
|
863
|
+
try {
|
|
864
|
+
ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join2(rules, name));
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
return [join2(home, "AGENTS.md"), join2(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
868
|
+
}
|
|
869
|
+
function strings(value, out, depth = 0) {
|
|
870
|
+
if (depth > 12) return;
|
|
871
|
+
if (typeof value === "string") out.push(value);
|
|
872
|
+
else if (Array.isArray(value)) for (const item of value) strings(item, out, depth + 1);
|
|
873
|
+
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
874
|
+
}
|
|
875
|
+
function skillRoots(cwd, home) {
|
|
876
|
+
const claude = process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude");
|
|
877
|
+
const codex = process.env.CODEX_HOME ?? join2(home, ".codex");
|
|
878
|
+
return [
|
|
879
|
+
...cwd ? [join2(cwd, ".claude", "skills"), join2(cwd, ".agents", "skills")] : [],
|
|
880
|
+
join2(home, ".agents", "skills"),
|
|
881
|
+
join2(claude, "skills"),
|
|
882
|
+
join2(codex, "skills"),
|
|
883
|
+
join2(home, ".hermes", "skills")
|
|
884
|
+
];
|
|
885
|
+
}
|
|
886
|
+
function pluginSkill(plugin, skill, home) {
|
|
887
|
+
const base = join2(process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude"), "plugins");
|
|
888
|
+
const walk = (dir, depth) => {
|
|
889
|
+
if (depth > 7) return null;
|
|
890
|
+
let entries;
|
|
891
|
+
try {
|
|
892
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
893
|
+
} catch {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
for (const entry2 of entries) {
|
|
897
|
+
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
898
|
+
const path = join2(dir, entry2.name);
|
|
899
|
+
if (entry2.name === skill && basename3(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join2(path, "SKILL.md"))) return path;
|
|
900
|
+
const found = walk(path, depth + 1);
|
|
901
|
+
if (found) return found;
|
|
902
|
+
}
|
|
903
|
+
return null;
|
|
904
|
+
};
|
|
905
|
+
return walk(base, 0);
|
|
906
|
+
}
|
|
907
|
+
function findSkill(name, cwd, home) {
|
|
908
|
+
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
909
|
+
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
910
|
+
for (const root of skillRoots(cwd, home)) {
|
|
911
|
+
const direct = join2(root, name);
|
|
912
|
+
if (isFile(join2(direct, "SKILL.md"))) return realpathSync(direct);
|
|
913
|
+
}
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
917
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
918
|
+
const missing = /* @__PURE__ */ new Set();
|
|
919
|
+
const add = (dir, name = basename3(dir)) => {
|
|
920
|
+
if (!isFile(join2(dir, "SKILL.md"))) return false;
|
|
921
|
+
const real = realpathSync(dir);
|
|
922
|
+
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
923
|
+
return true;
|
|
924
|
+
};
|
|
925
|
+
for (const record of records) {
|
|
926
|
+
const texts = [];
|
|
927
|
+
strings(record, texts);
|
|
928
|
+
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].replace(/[.,;:)]+$/, ""));
|
|
929
|
+
const content = record?.message?.content;
|
|
930
|
+
if (!Array.isArray(content)) continue;
|
|
931
|
+
for (const block of content) {
|
|
932
|
+
const b = block;
|
|
933
|
+
if (b?.type !== "tool_use" || b.name !== "Skill" || typeof b.input?.skill !== "string") continue;
|
|
934
|
+
const name = b.input.skill.replace(/^\//, "");
|
|
935
|
+
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
936
|
+
const dir = findSkill(name, cwd, home);
|
|
937
|
+
if (!dir || !add(dir, name)) missing.add(name);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
for (const path of touched) if (basename3(path) === "SKILL.md") add(dirname2(path));
|
|
941
|
+
for (const name of dirs.keys()) missing.delete(name);
|
|
942
|
+
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
943
|
+
}
|
|
944
|
+
function readJson2(path) {
|
|
945
|
+
try {
|
|
946
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
947
|
+
return value && typeof value === "object" ? value : null;
|
|
948
|
+
} catch {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function names(value) {
|
|
953
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
954
|
+
const keys2 = Object.keys(value).sort();
|
|
955
|
+
return keys2.length ? keys2 : void 0;
|
|
956
|
+
}
|
|
957
|
+
function safeUrl(raw) {
|
|
958
|
+
try {
|
|
959
|
+
const url = new URL(raw);
|
|
960
|
+
if (!/^https?:$/.test(url.protocol)) return null;
|
|
961
|
+
const query = [.../* @__PURE__ */ new Set([...url.searchParams.keys()])].sort();
|
|
962
|
+
return { url: `${url.protocol}//${url.host}${url.pathname}`, ...query.length ? { query } : {} };
|
|
963
|
+
} catch {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function serverEntry(name, def, scope) {
|
|
968
|
+
if (!def || typeof def !== "object") return null;
|
|
969
|
+
const env = names(def.env);
|
|
970
|
+
const headers = names(def.headers ?? def.http_headers);
|
|
971
|
+
if (typeof def.url === "string") {
|
|
972
|
+
const url = safeUrl(def.url);
|
|
973
|
+
if (!url) return null;
|
|
974
|
+
const type = def.type === "sse" ? "sse" : "http";
|
|
975
|
+
return { name, scope, transport: type, ...url, ...headers ? { headers } : {}, ...env ? { env } : {} };
|
|
976
|
+
}
|
|
977
|
+
if (typeof def.command !== "string" || !def.command) return null;
|
|
978
|
+
const args = Array.isArray(def.args) ? def.args.filter((arg) => typeof arg === "string") : [];
|
|
979
|
+
return { name, scope, transport: "stdio", command: def.command, ...args.length ? { args } : {}, ...env ? { env } : {} };
|
|
980
|
+
}
|
|
981
|
+
function mcpServers(harness2, cwd, home = homedir2()) {
|
|
982
|
+
const out = [];
|
|
983
|
+
const push = (servers, scope) => {
|
|
984
|
+
if (!servers || typeof servers !== "object") return;
|
|
985
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
986
|
+
const entry2 = serverEntry(name, def, scope);
|
|
987
|
+
if (entry2 && !out.some((existing) => existing.name === name)) out.push(entry2);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
if (harness2 === "claude_code") {
|
|
991
|
+
const config = readJson2(join2(process.env.CLAUDE_CONFIG_DIR ? dirname2(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
992
|
+
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
993
|
+
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
994
|
+
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
995
|
+
if (root) push(readJson2(join2(root, ".mcp.json"))?.mcpServers, "project");
|
|
996
|
+
push(config?.mcpServers, "user");
|
|
997
|
+
}
|
|
998
|
+
if (harness2 === "codex") {
|
|
999
|
+
try {
|
|
1000
|
+
const config = parseToml2(readFileSync4(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "config.toml"), "utf8"));
|
|
1001
|
+
push(config.mcp_servers, "user");
|
|
1002
|
+
} catch {
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return out;
|
|
1006
|
+
}
|
|
1007
|
+
function remoteWithoutCredentials(remote) {
|
|
1008
|
+
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
1009
|
+
}
|
|
1010
|
+
function workspaceState(cwd, harness2, skills2, home = homedir2()) {
|
|
1011
|
+
const present = !!cwd && isDir(cwd);
|
|
1012
|
+
const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
|
|
1013
|
+
let gitState = null;
|
|
1014
|
+
let tree = [];
|
|
1015
|
+
let patch = null;
|
|
1016
|
+
if (root) {
|
|
1017
|
+
const dirty = (git(root, "status", "--porcelain=v1", "--untracked-files=normal") ?? "").split("\n").filter(Boolean).map((line) => line.slice(3));
|
|
1018
|
+
const ahead = git(root, "rev-list", "--count", "@{upstream}..HEAD");
|
|
1019
|
+
const remote = git(root, "remote", "get-url", "origin");
|
|
1020
|
+
const branch = git(root, "rev-parse", "--abbrev-ref", "HEAD")?.trim() || null;
|
|
1021
|
+
gitState = {
|
|
1022
|
+
remote: remote ? remoteWithoutCredentials(remote) : null,
|
|
1023
|
+
branch: branch === "HEAD" ? null : branch,
|
|
1024
|
+
commit: git(root, "rev-parse", "HEAD")?.trim() || null,
|
|
1025
|
+
dirty: dirty.slice(0, 500),
|
|
1026
|
+
ahead: ahead === null ? null : Number(ahead.trim()) || 0
|
|
1027
|
+
};
|
|
1028
|
+
tree = (git(cwd, "-c", "core.quotePath=false", "ls-files") ?? "").split("\n").filter(Boolean);
|
|
1029
|
+
if (dirty.length) patch = git(cwd, "diff", "HEAD", "--no-color", "--no-ext-diff", "--relative", "--", ".") || null;
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
manifest: {
|
|
1033
|
+
format: "agentsky.import-workspace",
|
|
1034
|
+
format_version: 1,
|
|
1035
|
+
harness: harness2,
|
|
1036
|
+
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
1037
|
+
project: cwd ? basename3(root ?? cwd) : null,
|
|
1038
|
+
git: gitState,
|
|
1039
|
+
tree: tree.slice(0, MAX_TREE),
|
|
1040
|
+
tree_truncated: tree.length > MAX_TREE,
|
|
1041
|
+
skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
|
|
1042
|
+
mcp_servers: mcpServers(harness2, cwd, home)
|
|
1043
|
+
},
|
|
1044
|
+
patch
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function skillFiles(skill) {
|
|
1048
|
+
const out = [];
|
|
1049
|
+
const walk = (dir, depth) => {
|
|
1050
|
+
if (depth > 8 || out.length > 400) return;
|
|
1051
|
+
let entries;
|
|
1052
|
+
try {
|
|
1053
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1054
|
+
} catch {
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
for (const entry2 of entries) {
|
|
1058
|
+
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1059
|
+
const path = join2(dir, entry2.name);
|
|
1060
|
+
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1061
|
+
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
if (existsSync3(skill.dir)) walk(skill.dir, 0);
|
|
1065
|
+
return out;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/import-files.ts
|
|
1069
|
+
var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
|
|
1070
|
+
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;
|
|
1071
|
+
var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
|
|
1072
|
+
var INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
|
|
1073
|
+
var DOCUMENTS = {
|
|
1074
|
+
".pdf": "application/pdf",
|
|
1075
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1076
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1077
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1078
|
+
".doc": "application/msword",
|
|
1079
|
+
".rtf": "application/rtf",
|
|
1080
|
+
".odt": "application/vnd.oasis.opendocument.text"
|
|
1081
|
+
};
|
|
1082
|
+
var IMAGES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
1083
|
+
var TEXT = {
|
|
1084
|
+
".md": "text/markdown",
|
|
1085
|
+
".mdx": "text/markdown",
|
|
1086
|
+
".txt": "text/plain",
|
|
1087
|
+
".csv": "text/csv",
|
|
1088
|
+
".tsv": "text/tab-separated-values",
|
|
1089
|
+
".html": "text/html",
|
|
1090
|
+
".htm": "text/html",
|
|
1091
|
+
".css": "text/css",
|
|
1092
|
+
".scss": "text/css",
|
|
1093
|
+
".json": "application/json",
|
|
1094
|
+
".jsonl": "application/x-ndjson",
|
|
1095
|
+
".ndjson": "application/x-ndjson",
|
|
1096
|
+
".yaml": "application/yaml",
|
|
1097
|
+
".yml": "application/yaml",
|
|
1098
|
+
".toml": "application/toml",
|
|
1099
|
+
".xml": "application/xml",
|
|
1100
|
+
".sql": "application/sql",
|
|
1101
|
+
".svg": "image/svg+xml",
|
|
1102
|
+
".js": "application/javascript",
|
|
1103
|
+
".mjs": "application/javascript",
|
|
1104
|
+
".cjs": "application/javascript",
|
|
1105
|
+
".py": "text/x-python",
|
|
1106
|
+
".sh": "text/x-shellscript",
|
|
1107
|
+
".bash": "text/x-shellscript",
|
|
1108
|
+
".zsh": "text/x-shellscript"
|
|
1109
|
+
};
|
|
1110
|
+
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"]);
|
|
1111
|
+
var PLAIN_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Makefile", "Procfile", "Gemfile", "Rakefile", "LICENSE", "README", ".cursorrules", ".gitignore", ".dockerignore", ".editorconfig", ".prettierrc", ".eslintrc"]);
|
|
1112
|
+
function classify(path) {
|
|
1113
|
+
const ext = extname2(path).toLowerCase();
|
|
1114
|
+
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
1115
|
+
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
1116
|
+
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
1117
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
|
|
1118
|
+
return null;
|
|
1119
|
+
}
|
|
1120
|
+
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;
|
|
1121
|
+
var PATH_KEYS = ["file_path", "path", "notebook_path", "filePath", "target_file"];
|
|
1122
|
+
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1123
|
+
var str = (value) => typeof value === "string" ? value : "";
|
|
1124
|
+
function shellText(input) {
|
|
1125
|
+
if (typeof input === "string") return input;
|
|
1126
|
+
const i = obj(input);
|
|
1127
|
+
if (!i) return "";
|
|
1128
|
+
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
1129
|
+
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
1130
|
+
}
|
|
1131
|
+
function touchedPaths(records, cwd, home = homedir3()) {
|
|
1132
|
+
const out = /* @__PURE__ */ new Set();
|
|
1133
|
+
const add = (raw, base) => {
|
|
1134
|
+
if (!raw || raw.length > 1e3) return;
|
|
1135
|
+
const path = raw.replace(/^~(?=\/)/, home);
|
|
1136
|
+
if (isAbsolute(path)) out.add(resolve3(path));
|
|
1137
|
+
else if (base) out.add(resolve3(base, path));
|
|
1138
|
+
};
|
|
1139
|
+
const call = (name, input) => {
|
|
1140
|
+
const i = obj(input);
|
|
1141
|
+
const base = str(i?.workdir) || str(i?.cwd) || cwd;
|
|
1142
|
+
if (i) for (const key of PATH_KEYS) add(str(i[key]), base);
|
|
1143
|
+
const patch = typeof input === "string" ? input : str(i?.input) || str(i?.patch);
|
|
1144
|
+
for (const match of patch.matchAll(/^\*\*\* (?:Add|Update) File: (.+)$/gm)) add(match[1].trim(), base);
|
|
1145
|
+
if (!/bash|shell|exec|terminal|command|run/i.test(name)) return;
|
|
1146
|
+
for (const match of shellText(input).replace(/\\n/g, "\n").matchAll(SHELL_READ)) add(match[1], base);
|
|
1147
|
+
};
|
|
1148
|
+
for (const raw of records) {
|
|
1149
|
+
const record = obj(raw);
|
|
1150
|
+
if (!record) continue;
|
|
1151
|
+
const content = obj(record.message)?.content;
|
|
1152
|
+
if (record.type === "assistant" && Array.isArray(content)) {
|
|
1153
|
+
for (const block of content) {
|
|
1154
|
+
const b = obj(block);
|
|
1155
|
+
if (b?.type === "tool_use") call(str(b.name), b.input);
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
const payload = obj(record.payload);
|
|
1159
|
+
if (record.type === "response_item" && (payload?.type === "function_call" || payload?.type === "custom_tool_call")) {
|
|
1160
|
+
let input = payload.arguments ?? payload.input ?? "";
|
|
1161
|
+
if (typeof input === "string") {
|
|
1162
|
+
try {
|
|
1163
|
+
input = JSON.parse(input);
|
|
1164
|
+
} catch {
|
|
1165
|
+
input = payload.arguments ?? payload.input;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
call(str(payload.name), input);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
return [...out];
|
|
1172
|
+
}
|
|
1173
|
+
function gitRoot(dir) {
|
|
1174
|
+
for (let d = dir; ; d = dirname3(d)) {
|
|
1175
|
+
if (existsSync4(join3(d, ".git"))) return d;
|
|
1176
|
+
if (d === dirname3(d)) return null;
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
1180
|
+
const out = [];
|
|
1181
|
+
if (cwd && existsSync4(cwd)) {
|
|
1182
|
+
const root = gitRoot(cwd) ?? cwd;
|
|
1183
|
+
for (let d = resolve3(cwd); ; d = dirname3(d)) {
|
|
1184
|
+
for (const name of INSTRUCTION_FILES) out.push(join3(d, name));
|
|
1185
|
+
if (d === root || d === dirname3(d)) break;
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
if (harness2 === "claude_code") out.push(join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "CLAUDE.md"));
|
|
1189
|
+
if (harness2 === "codex") out.push(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "AGENTS.md"));
|
|
1190
|
+
out.push(...sharedInstructionPaths(home));
|
|
1191
|
+
return out.filter((path) => {
|
|
1192
|
+
try {
|
|
1193
|
+
return statSync2(path).isFile();
|
|
1194
|
+
} catch {
|
|
1195
|
+
return false;
|
|
1196
|
+
}
|
|
1197
|
+
});
|
|
1198
|
+
}
|
|
1199
|
+
function resourceName(path, cwd, home = homedir3()) {
|
|
1200
|
+
const inside = cwd ? relative2(resolve3(cwd), path) : "";
|
|
1201
|
+
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
1202
|
+
const underHome = relative2(home, path);
|
|
1203
|
+
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
1204
|
+
return `files/${shown.split(sep2).join("/")}`;
|
|
1205
|
+
}
|
|
1206
|
+
var ORDER = { text: 0, document: 1, image: 2 };
|
|
1207
|
+
function resourceLine(name, mediaType, data) {
|
|
1208
|
+
return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
1209
|
+
}
|
|
1210
|
+
function collectSessionFiles(records, opts) {
|
|
1211
|
+
const home = opts.home ?? homedir3();
|
|
1212
|
+
const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
|
|
1213
|
+
const leftOut = [];
|
|
1214
|
+
const candidates = [];
|
|
1215
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1216
|
+
const touched = touchedPaths(records, opts.cwd, home);
|
|
1217
|
+
const skills2 = loadedSkills(records, touched, opts.cwd, home);
|
|
1218
|
+
const consider = (path, name) => {
|
|
1219
|
+
if (seen.has(path) || exclude.has(path)) return;
|
|
1220
|
+
seen.add(path);
|
|
1221
|
+
if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
|
|
1222
|
+
leftOut.push({ path, reason: "credential or build file" });
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
let size;
|
|
1226
|
+
try {
|
|
1227
|
+
const stat = statSync2(path);
|
|
1228
|
+
if (!stat.isFile()) return;
|
|
1229
|
+
size = stat.size;
|
|
1230
|
+
} catch {
|
|
1231
|
+
leftOut.push({ path, reason: "missing" });
|
|
1232
|
+
return;
|
|
1233
|
+
}
|
|
1234
|
+
const type = classify(path);
|
|
1235
|
+
if (!type) {
|
|
1236
|
+
leftOut.push({ path, reason: "unsupported type" });
|
|
1237
|
+
return;
|
|
1238
|
+
}
|
|
1239
|
+
if (size > MAX_ATTACHED_FILE_BYTES) {
|
|
1240
|
+
leftOut.push({ path, reason: "too large" });
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
candidates.push({ path, name, ...type, size });
|
|
1244
|
+
};
|
|
1245
|
+
for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
|
|
1246
|
+
for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
|
|
1247
|
+
candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
|
|
1248
|
+
const lines = [];
|
|
1249
|
+
const attached = [];
|
|
1250
|
+
let bytes = 0;
|
|
1251
|
+
const state = workspaceState(opts.cwd, opts.harness, skills2, home);
|
|
1252
|
+
const described = !!opts.cwd || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
1253
|
+
const generated = [
|
|
1254
|
+
...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
|
|
1255
|
+
...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
|
|
1256
|
+
];
|
|
1257
|
+
for (const file of generated) {
|
|
1258
|
+
let line;
|
|
1259
|
+
try {
|
|
1260
|
+
line = resourceLine(file.name, file.mediaType, file.data);
|
|
1261
|
+
} catch {
|
|
1262
|
+
leftOut.push({ path: file.name, reason: "could not be redacted safely" });
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const size = Buffer.byteLength(line);
|
|
1266
|
+
if (bytes + size > opts.budget) {
|
|
1267
|
+
leftOut.push({ path: file.name, reason: "over the upload budget" });
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
bytes += size;
|
|
1271
|
+
lines.push(line);
|
|
1272
|
+
attached.push({ name: file.name, kind: "text", bytes: file.data.length });
|
|
1273
|
+
}
|
|
1274
|
+
for (const file of candidates) {
|
|
1275
|
+
let data;
|
|
1276
|
+
try {
|
|
1277
|
+
data = readFileSync5(file.path);
|
|
1278
|
+
} catch {
|
|
1279
|
+
leftOut.push({ path: file.path, reason: "missing" });
|
|
1280
|
+
continue;
|
|
1281
|
+
}
|
|
1282
|
+
if (file.kind === "text" && data.includes(0)) {
|
|
1283
|
+
leftOut.push({ path: file.path, reason: "not valid UTF-8 text" });
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
let line;
|
|
1287
|
+
try {
|
|
1288
|
+
line = resourceLine(file.name, file.mediaType, data);
|
|
1289
|
+
} catch {
|
|
1290
|
+
leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
|
|
1291
|
+
continue;
|
|
1292
|
+
}
|
|
1293
|
+
const size = Buffer.byteLength(line);
|
|
1294
|
+
if (bytes + size > opts.budget) {
|
|
1295
|
+
leftOut.push({ path: file.path, reason: "over the upload budget" });
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
bytes += size;
|
|
1299
|
+
lines.push(line);
|
|
1300
|
+
attached.push({ name: file.name, kind: file.kind, bytes: data.length });
|
|
1301
|
+
}
|
|
1302
|
+
const setup = {
|
|
1303
|
+
skills: skills2.found.map((skill) => skill.name),
|
|
1304
|
+
missingSkills: skills2.missing,
|
|
1305
|
+
mcpServers: state.manifest.mcp_servers.map((server) => server.name),
|
|
1306
|
+
git: state.manifest.git !== null
|
|
1307
|
+
};
|
|
1308
|
+
return { lines, bytes, attached, leftOut, setup };
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
// src/import-local.ts
|
|
709
1312
|
function redactLine2(raw) {
|
|
710
1313
|
return redactLine(raw).line;
|
|
711
1314
|
}
|
|
@@ -777,16 +1380,16 @@ function sniffTitle(filePath) {
|
|
|
777
1380
|
}
|
|
778
1381
|
}
|
|
779
1382
|
function listClaudeCodeSessions(limit) {
|
|
780
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
781
|
-
const projectsDir =
|
|
782
|
-
if (!
|
|
1383
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir4(), ".claude");
|
|
1384
|
+
const projectsDir = join4(root, "projects");
|
|
1385
|
+
if (!existsSync5(projectsDir)) return [];
|
|
783
1386
|
const sessions = [];
|
|
784
1387
|
try {
|
|
785
|
-
for (const encoded of
|
|
786
|
-
const projectDir =
|
|
1388
|
+
for (const encoded of readdirSync2(projectsDir)) {
|
|
1389
|
+
const projectDir = join4(projectsDir, encoded);
|
|
787
1390
|
let entries;
|
|
788
1391
|
try {
|
|
789
|
-
entries =
|
|
1392
|
+
entries = readdirSync2(projectDir);
|
|
790
1393
|
} catch {
|
|
791
1394
|
continue;
|
|
792
1395
|
}
|
|
@@ -794,22 +1397,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
794
1397
|
for (const entry2 of entries) {
|
|
795
1398
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
796
1399
|
const sessionId = entry2.slice(0, -6);
|
|
797
|
-
const mainFile =
|
|
1400
|
+
const mainFile = join4(projectDir, entry2);
|
|
798
1401
|
let stat;
|
|
799
1402
|
try {
|
|
800
|
-
stat =
|
|
1403
|
+
stat = statSync3(mainFile);
|
|
801
1404
|
} catch {
|
|
802
1405
|
continue;
|
|
803
1406
|
}
|
|
804
1407
|
if (!stat.isFile()) continue;
|
|
805
1408
|
let discoveryError;
|
|
806
1409
|
const subagentFiles = [];
|
|
807
|
-
const subagentDir =
|
|
808
|
-
if (
|
|
1410
|
+
const subagentDir = join4(projectDir, sessionId, "subagents");
|
|
1411
|
+
if (existsSync5(subagentDir)) {
|
|
809
1412
|
try {
|
|
810
|
-
const subFiles =
|
|
1413
|
+
const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
811
1414
|
for (const f of subFiles) {
|
|
812
|
-
subagentFiles.push(
|
|
1415
|
+
subagentFiles.push(join4(subagentDir, f));
|
|
813
1416
|
}
|
|
814
1417
|
} catch {
|
|
815
1418
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -831,17 +1434,17 @@ function listClaudeCodeSessions(limit) {
|
|
|
831
1434
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
832
1435
|
}
|
|
833
1436
|
function listCodexSessions(limit) {
|
|
834
|
-
const sessionsDir =
|
|
1437
|
+
const sessionsDir = join4(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join4(homedir4(), ".codex"), "sessions");
|
|
835
1438
|
const records = [];
|
|
836
1439
|
function walk(dir) {
|
|
837
1440
|
let entries;
|
|
838
1441
|
try {
|
|
839
|
-
entries =
|
|
1442
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
840
1443
|
} catch {
|
|
841
1444
|
return;
|
|
842
1445
|
}
|
|
843
1446
|
for (const entry2 of entries) {
|
|
844
|
-
const full =
|
|
1447
|
+
const full = join4(dir, entry2.name);
|
|
845
1448
|
if (entry2.isDirectory()) {
|
|
846
1449
|
walk(full);
|
|
847
1450
|
continue;
|
|
@@ -859,7 +1462,7 @@ function listCodexSessions(limit) {
|
|
|
859
1462
|
sessionId: data.id,
|
|
860
1463
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
861
1464
|
title: sniffTitle(full),
|
|
862
|
-
mtimeMs:
|
|
1465
|
+
mtimeMs: statSync3(full).mtimeMs,
|
|
863
1466
|
files: [full]
|
|
864
1467
|
},
|
|
865
1468
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -928,7 +1531,7 @@ async function mintTrace(base, harness2) {
|
|
|
928
1531
|
function loadSummaryFile(summaryPath) {
|
|
929
1532
|
let raw;
|
|
930
1533
|
try {
|
|
931
|
-
raw =
|
|
1534
|
+
raw = readFileSync6(summaryPath, "utf8");
|
|
932
1535
|
} catch {
|
|
933
1536
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
934
1537
|
}
|
|
@@ -942,10 +1545,19 @@ function loadSummaryFile(summaryPath) {
|
|
|
942
1545
|
const p = pkg;
|
|
943
1546
|
if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
|
|
944
1547
|
if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
|
|
945
|
-
const
|
|
946
|
-
return
|
|
947
|
-
}
|
|
948
|
-
|
|
1548
|
+
const redacted2 = redactLine2(raw.trimEnd());
|
|
1549
|
+
return redacted2;
|
|
1550
|
+
}
|
|
1551
|
+
var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
|
|
1552
|
+
var SUMMARY_RESERVE_BYTES = 128 * 1024;
|
|
1553
|
+
var MAX_LISTED_ARTIFACTS = 99;
|
|
1554
|
+
function leftOutLine(left) {
|
|
1555
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1556
|
+
for (const file of left) counts.set(file.reason, (counts.get(file.reason) ?? 0) + 1);
|
|
1557
|
+
const files2 = [...counts].map(([reason, n]) => `${n} ${n === 1 ? "file" : "files"} (${reason})`).join(", ");
|
|
1558
|
+
return `Not transferred: ${files2 ? `${files2}; ` : ""}runtime state such as running processes, installed tools and environment variables; and reasoning the source harness stored unreadably.`;
|
|
1559
|
+
}
|
|
1560
|
+
function prepareImport(session2, summaryPath, options = {}) {
|
|
949
1561
|
if (session2.discoveryError) throw new Error(session2.discoveryError);
|
|
950
1562
|
const maximum = 16 * 1024 * 1024;
|
|
951
1563
|
let inputBytes = 0;
|
|
@@ -957,10 +1569,12 @@ function prepareImport(session2, summaryPath) {
|
|
|
957
1569
|
let encryptedReasoningCount = 0;
|
|
958
1570
|
let compactionCount = 0;
|
|
959
1571
|
let unreadableCount = 0;
|
|
1572
|
+
const source = new SessionSourceCollector();
|
|
1573
|
+
const records = [];
|
|
960
1574
|
for (const path of session2.files) {
|
|
961
|
-
const stat =
|
|
1575
|
+
const stat = statSync3(path);
|
|
962
1576
|
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 =
|
|
1577
|
+
const text = readFileSync6(path, "utf8");
|
|
964
1578
|
for (const raw of text.split("\n")) {
|
|
965
1579
|
if (!raw.trim()) continue;
|
|
966
1580
|
let record;
|
|
@@ -973,6 +1587,8 @@ function prepareImport(session2, summaryPath) {
|
|
|
973
1587
|
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
|
|
974
1588
|
const rec = record;
|
|
975
1589
|
if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
|
|
1590
|
+
if (path === session2.files[0]) source.push(rec);
|
|
1591
|
+
records.push(rec);
|
|
976
1592
|
if (rec.type === "response_item") {
|
|
977
1593
|
const payload = rec.payload;
|
|
978
1594
|
if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
|
|
@@ -996,6 +1612,9 @@ function prepareImport(session2, summaryPath) {
|
|
|
996
1612
|
if (!lines.length) throw new Error("The selected transcript is empty.");
|
|
997
1613
|
const sourceId = nativeId ?? session2.sessionId;
|
|
998
1614
|
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.");
|
|
1615
|
+
const meta = source.result();
|
|
1616
|
+
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : 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 });
|
|
1617
|
+
const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under, the skills it loaded and its workspace description (repository state and tool servers, agentsky/workspace.json) are attached as well (listed in artifacts).` : "";
|
|
999
1618
|
let summaryJson;
|
|
1000
1619
|
if (summaryPath) {
|
|
1001
1620
|
summaryJson = loadSummaryFile(summaryPath);
|
|
@@ -1004,10 +1623,10 @@ function prepareImport(session2, summaryPath) {
|
|
|
1004
1623
|
format: "asteroids.session-summary",
|
|
1005
1624
|
format_version: 1,
|
|
1006
1625
|
source_harness: session2.harness,
|
|
1007
|
-
source_harness_version: null,
|
|
1626
|
+
source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
|
|
1008
1627
|
source_session_id: sourceId,
|
|
1009
1628
|
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1010
|
-
summary:
|
|
1629
|
+
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
1630
|
context: {
|
|
1012
1631
|
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
1013
1632
|
constraints: [],
|
|
@@ -1015,19 +1634,19 @@ function prepareImport(session2, summaryPath) {
|
|
|
1015
1634
|
completed: [],
|
|
1016
1635
|
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1017
1636
|
verification: [],
|
|
1018
|
-
blockers: [
|
|
1019
|
-
artifacts:
|
|
1637
|
+
blockers: [leftOutLine(files2.leftOut), ...files2.setup.missingSkills.length ? [`Skills it loaded that are not on this computer any more: ${files2.setup.missingSkills.slice(0, 20).join(", ")}.`] : []],
|
|
1638
|
+
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
1639
|
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1021
1640
|
}
|
|
1022
1641
|
};
|
|
1023
1642
|
summaryJson = JSON.stringify(summary);
|
|
1024
1643
|
}
|
|
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";
|
|
1644
|
+
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
1645
|
const gaps = [];
|
|
1027
1646
|
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1028
1647
|
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1029
1648
|
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1030
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps };
|
|
1649
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
|
|
1031
1650
|
}
|
|
1032
1651
|
async function importSession(opts) {
|
|
1033
1652
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1036,7 +1655,7 @@ async function importSession(opts) {
|
|
|
1036
1655
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1037
1656
|
}
|
|
1038
1657
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1039
|
-
const { body, transcriptLines, outputBytes, redactionCount, gaps } = prepareImport(opts.session, opts.summaryPath);
|
|
1658
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1040
1659
|
let traceId;
|
|
1041
1660
|
let key;
|
|
1042
1661
|
if (opts.importKey && opts.grantTraceId) {
|
|
@@ -1068,7 +1687,10 @@ async function importSession(opts) {
|
|
|
1068
1687
|
transcriptFiles: opts.session.files.length,
|
|
1069
1688
|
bytes: outputBytes,
|
|
1070
1689
|
redactionCount,
|
|
1071
|
-
gaps
|
|
1690
|
+
gaps,
|
|
1691
|
+
attachedFiles,
|
|
1692
|
+
leftOutFiles,
|
|
1693
|
+
setup
|
|
1072
1694
|
};
|
|
1073
1695
|
}
|
|
1074
1696
|
|
|
@@ -1105,7 +1727,7 @@ async function login(opts) {
|
|
|
1105
1727
|
}
|
|
1106
1728
|
async function browserLogin(verifier, challenge) {
|
|
1107
1729
|
const state = b64url(randomBytes(16));
|
|
1108
|
-
const code = await new Promise((
|
|
1730
|
+
const code = await new Promise((resolve8, reject) => {
|
|
1109
1731
|
const server = createServer((req, res) => {
|
|
1110
1732
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1111
1733
|
if (url.pathname !== "/callback") {
|
|
@@ -1118,7 +1740,7 @@ async function browserLogin(verifier, challenge) {
|
|
|
1118
1740
|
if (url.searchParams.get("state") !== state || !got) {
|
|
1119
1741
|
reject(new Error("login callback state mismatch"));
|
|
1120
1742
|
} else {
|
|
1121
|
-
|
|
1743
|
+
resolve8(got);
|
|
1122
1744
|
}
|
|
1123
1745
|
server.close();
|
|
1124
1746
|
});
|
|
@@ -1172,23 +1794,23 @@ async function headlessLogin(verifier, challenge) {
|
|
|
1172
1794
|
}
|
|
1173
1795
|
|
|
1174
1796
|
// 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 =
|
|
1797
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1798
|
+
import { homedir as homedir5 } from "os";
|
|
1799
|
+
import { join as join5 } from "path";
|
|
1800
|
+
function localAgents(env = process.env, home = homedir5()) {
|
|
1801
|
+
const claude = env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
|
|
1802
|
+
const codex = env.CODEX_HOME || join5(home, ".codex");
|
|
1803
|
+
const cursor = join5(home, ".cursor");
|
|
1182
1804
|
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:
|
|
1805
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join5(claude, "skills") },
|
|
1806
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join5(codex, "skills") },
|
|
1807
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join5(cursor, "skills") }
|
|
1186
1808
|
];
|
|
1187
1809
|
}
|
|
1188
|
-
function detectLocalAgents(env = process.env, home =
|
|
1189
|
-
return localAgents(env, home).filter((agent2) =>
|
|
1810
|
+
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
1811
|
+
return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
|
|
1190
1812
|
}
|
|
1191
|
-
function selectLocalAgents(ids, env = process.env, home =
|
|
1813
|
+
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
1192
1814
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
1193
1815
|
const known = localAgents(env, home);
|
|
1194
1816
|
return ids.map((id) => {
|
|
@@ -1282,21 +1904,21 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
1282
1904
|
for (; ; ) {
|
|
1283
1905
|
const reading = latestTurn(await history(sessionId));
|
|
1284
1906
|
if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
|
|
1285
|
-
await new Promise((
|
|
1907
|
+
await new Promise((resolve8) => setTimeout(resolve8, opts.intervalMs ?? 3e3));
|
|
1286
1908
|
}
|
|
1287
1909
|
}
|
|
1288
1910
|
|
|
1289
1911
|
// src/setup.ts
|
|
1290
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1291
|
-
import { dirname as
|
|
1912
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
1913
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
1292
1914
|
import { createInterface as createInterface3 } from "readline";
|
|
1293
1915
|
|
|
1294
1916
|
// src/mcp-register.ts
|
|
1295
|
-
import { spawnSync } from "child_process";
|
|
1917
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1296
1918
|
|
|
1297
1919
|
// src/skill.ts
|
|
1298
|
-
import { mkdirSync, readFileSync as
|
|
1299
|
-
import { join as
|
|
1920
|
+
import { mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1921
|
+
import { join as join6 } from "path";
|
|
1300
1922
|
|
|
1301
1923
|
// src/catalog-file.ts
|
|
1302
1924
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -1313,8 +1935,8 @@ function entry(agent2) {
|
|
|
1313
1935
|
const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
|
|
1314
1936
|
if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
|
|
1315
1937
|
if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
|
|
1316
|
-
const { mcp_servers:
|
|
1317
|
-
if (typeof
|
|
1938
|
+
const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
|
|
1939
|
+
if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
|
|
1318
1940
|
const details = describe(rest);
|
|
1319
1941
|
if (details) lines.push(`Details: ${details}`);
|
|
1320
1942
|
const pricing = describe(agent2.pricing);
|
|
@@ -1737,7 +2359,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
1737
2359
|
}
|
|
1738
2360
|
function installedVersion(dir) {
|
|
1739
2361
|
try {
|
|
1740
|
-
const marker = JSON.parse(
|
|
2362
|
+
const marker = JSON.parse(readFileSync7(join6(dir, SKILL_MARKER), "utf8"));
|
|
1741
2363
|
return typeof marker.version === "string" ? marker.version : null;
|
|
1742
2364
|
} catch {
|
|
1743
2365
|
return null;
|
|
@@ -1747,25 +2369,25 @@ function isInstalledSkill(dir) {
|
|
|
1747
2369
|
return installedVersion(dir) !== null;
|
|
1748
2370
|
}
|
|
1749
2371
|
function installSkill(agent2, opts = {}) {
|
|
1750
|
-
const dir =
|
|
2372
|
+
const dir = join6(agent2.skillsDir, SKILL_NAME);
|
|
1751
2373
|
const previous = installedVersion(dir);
|
|
1752
2374
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
1753
2375
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
1754
2376
|
if (outcome !== "current") {
|
|
1755
2377
|
mkdirSync(dir, { recursive: true });
|
|
1756
|
-
writeFileSync3(
|
|
1757
|
-
writeFileSync3(
|
|
2378
|
+
writeFileSync3(join6(dir, "SKILL.md"), skillDocument());
|
|
2379
|
+
writeFileSync3(join6(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
1758
2380
|
`);
|
|
1759
2381
|
}
|
|
1760
2382
|
if (opts.catalog) {
|
|
1761
2383
|
mkdirSync(dir, { recursive: true });
|
|
1762
|
-
writeFileSync3(
|
|
2384
|
+
writeFileSync3(join6(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
1763
2385
|
}
|
|
1764
|
-
const refsDir =
|
|
2386
|
+
const refsDir = join6(dir, REFERENCES_DIR);
|
|
1765
2387
|
mkdirSync(refsDir, { recursive: true });
|
|
1766
|
-
writeFileSync3(
|
|
1767
|
-
writeFileSync3(
|
|
1768
|
-
writeFileSync3(
|
|
2388
|
+
writeFileSync3(join6(refsDir, "import.md"), importReferenceDoc());
|
|
2389
|
+
writeFileSync3(join6(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2390
|
+
writeFileSync3(join6(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
1769
2391
|
return { agent: agent2.id, path: dir, outcome };
|
|
1770
2392
|
}
|
|
1771
2393
|
|
|
@@ -1774,9 +2396,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
|
|
|
1774
2396
|
var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
1775
2397
|
function systemCommandRunner() {
|
|
1776
2398
|
return {
|
|
1777
|
-
has: (command) =>
|
|
2399
|
+
has: (command) => spawnSync2(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
1778
2400
|
run: (command, args) => {
|
|
1779
|
-
const result =
|
|
2401
|
+
const result = spawnSync2(command, args, { encoding: "utf8" });
|
|
1780
2402
|
return { status: result.status, stderr: result.stderr ?? "" };
|
|
1781
2403
|
}
|
|
1782
2404
|
};
|
|
@@ -1816,10 +2438,11 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
1816
2438
|
}
|
|
1817
2439
|
|
|
1818
2440
|
// src/setup-local.ts
|
|
1819
|
-
import {
|
|
1820
|
-
import {
|
|
1821
|
-
import {
|
|
1822
|
-
import {
|
|
2441
|
+
import { isUtf8 as isUtf82 } from "buffer";
|
|
2442
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
2443
|
+
import { homedir as homedir6 } from "os";
|
|
2444
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
2445
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
1823
2446
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
1824
2447
|
var MAX_IMPORT_DEPTH = 5;
|
|
1825
2448
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
@@ -1831,14 +2454,24 @@ function frontmatterName(skillMd) {
|
|
|
1831
2454
|
function sensitive(name) {
|
|
1832
2455
|
return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
|
|
1833
2456
|
}
|
|
1834
|
-
function
|
|
2457
|
+
function redacted(data) {
|
|
2458
|
+
if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
|
|
2459
|
+
try {
|
|
2460
|
+
const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
|
|
2461
|
+
return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
|
|
2462
|
+
} catch {
|
|
2463
|
+
return null;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
function skillFiles2(root, prefix) {
|
|
1835
2467
|
const files2 = [];
|
|
1836
|
-
for (const entry2 of
|
|
1837
|
-
const path =
|
|
2468
|
+
for (const entry2 of readdirSync3(root, { withFileTypes: true })) {
|
|
2469
|
+
const path = join7(root, entry2.name);
|
|
1838
2470
|
if (entry2.isDirectory()) {
|
|
1839
|
-
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...
|
|
1840
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
1841
|
-
|
|
2471
|
+
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
2472
|
+
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync4(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
2473
|
+
const bytes = redacted(readFileSync8(path));
|
|
2474
|
+
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
1842
2475
|
}
|
|
1843
2476
|
}
|
|
1844
2477
|
return files2;
|
|
@@ -1847,19 +2480,19 @@ function readLocalSkills(agents) {
|
|
|
1847
2480
|
const skills2 = [];
|
|
1848
2481
|
const ignored = [];
|
|
1849
2482
|
for (const agent2 of agents) {
|
|
1850
|
-
if (!
|
|
1851
|
-
for (const entry2 of
|
|
1852
|
-
const dir =
|
|
1853
|
-
let
|
|
2483
|
+
if (!existsSync7(agent2.skillsDir)) continue;
|
|
2484
|
+
for (const entry2 of readdirSync3(agent2.skillsDir, { withFileTypes: true })) {
|
|
2485
|
+
const dir = join7(agent2.skillsDir, entry2.name);
|
|
2486
|
+
let isDir2 = entry2.isDirectory();
|
|
1854
2487
|
if (entry2.isSymbolicLink()) {
|
|
1855
2488
|
try {
|
|
1856
|
-
|
|
2489
|
+
isDir2 = statSync4(dir).isDirectory();
|
|
1857
2490
|
} catch {
|
|
1858
|
-
|
|
2491
|
+
isDir2 = false;
|
|
1859
2492
|
}
|
|
1860
2493
|
}
|
|
1861
|
-
if (!
|
|
1862
|
-
const name = frontmatterName(
|
|
2494
|
+
if (!isDir2 || !existsSync7(join7(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
2495
|
+
const name = frontmatterName(readFileSync8(join7(dir, "SKILL.md"), "utf8"));
|
|
1863
2496
|
if (!name) {
|
|
1864
2497
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
1865
2498
|
continue;
|
|
@@ -1869,13 +2502,13 @@ function readLocalSkills(agents) {
|
|
|
1869
2502
|
ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
|
|
1870
2503
|
continue;
|
|
1871
2504
|
}
|
|
1872
|
-
const files2 =
|
|
2505
|
+
const files2 = skillFiles2(dir, entry2.name);
|
|
1873
2506
|
skills2.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
|
|
1874
2507
|
}
|
|
1875
2508
|
}
|
|
1876
2509
|
return { skills: skills2, ignored };
|
|
1877
2510
|
}
|
|
1878
|
-
var
|
|
2511
|
+
var INSTRUCTION_FILES2 = { claude_code: "CLAUDE.md", codex: "AGENTS.md" };
|
|
1879
2512
|
function inlineImports(content, from, home, depth, seen) {
|
|
1880
2513
|
let fence;
|
|
1881
2514
|
return content.split(/\r?\n/).map((line) => {
|
|
@@ -1887,11 +2520,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
1887
2520
|
}
|
|
1888
2521
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
1889
2522
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
1890
|
-
const path = target.startsWith("~/") ?
|
|
2523
|
+
const path = target.startsWith("~/") ? join7(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname4(from), target);
|
|
1891
2524
|
if (seen.includes(path)) return "";
|
|
1892
2525
|
let imported;
|
|
1893
2526
|
try {
|
|
1894
|
-
imported =
|
|
2527
|
+
imported = readFileSync8(path, "utf8");
|
|
1895
2528
|
} catch {
|
|
1896
2529
|
return line;
|
|
1897
2530
|
}
|
|
@@ -1899,15 +2532,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
1899
2532
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
1900
2533
|
}).join("\n");
|
|
1901
2534
|
}
|
|
1902
|
-
function readGlobalInstructions(agents, home =
|
|
2535
|
+
function readGlobalInstructions(agents, home = homedir6()) {
|
|
1903
2536
|
const found = [];
|
|
1904
2537
|
for (const agent2 of agents) {
|
|
1905
|
-
const file =
|
|
2538
|
+
const file = INSTRUCTION_FILES2[agent2.id];
|
|
1906
2539
|
if (!file) continue;
|
|
1907
|
-
const path =
|
|
2540
|
+
const path = join7(agent2.root, file);
|
|
1908
2541
|
let raw;
|
|
1909
2542
|
try {
|
|
1910
|
-
raw =
|
|
2543
|
+
raw = readFileSync8(path, "utf8");
|
|
1911
2544
|
} catch {
|
|
1912
2545
|
continue;
|
|
1913
2546
|
}
|
|
@@ -1924,9 +2557,9 @@ function combineInstructions(found) {
|
|
|
1924
2557
|
|
|
1925
2558
|
${item.content.trim()}`).join("\n\n");
|
|
1926
2559
|
}
|
|
1927
|
-
function
|
|
2560
|
+
function readJson3(path) {
|
|
1928
2561
|
try {
|
|
1929
|
-
return JSON.parse(
|
|
2562
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
1930
2563
|
} catch {
|
|
1931
2564
|
return void 0;
|
|
1932
2565
|
}
|
|
@@ -1940,15 +2573,15 @@ function bearerOf(def) {
|
|
|
1940
2573
|
return match?.[1] ?? null;
|
|
1941
2574
|
}
|
|
1942
2575
|
function serversOf(agent2, home) {
|
|
1943
|
-
if (agent2.id === "claude_code") return
|
|
1944
|
-
if (agent2.id === "cursor") return
|
|
2576
|
+
if (agent2.id === "claude_code") return readJson3(join7(home, ".claude.json"))?.mcpServers ?? {};
|
|
2577
|
+
if (agent2.id === "cursor") return readJson3(join7(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
1945
2578
|
try {
|
|
1946
|
-
return
|
|
2579
|
+
return parseToml3(readFileSync8(join7(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
1947
2580
|
} catch {
|
|
1948
2581
|
return {};
|
|
1949
2582
|
}
|
|
1950
2583
|
}
|
|
1951
|
-
function findLocalSecrets(agents, home =
|
|
2584
|
+
function findLocalSecrets(agents, home = homedir6()) {
|
|
1952
2585
|
const secrets2 = [];
|
|
1953
2586
|
for (const agent2 of agents) {
|
|
1954
2587
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
@@ -1964,9 +2597,9 @@ function findLocalSecrets(agents, home = homedir4()) {
|
|
|
1964
2597
|
|
|
1965
2598
|
// src/subscriptions.ts
|
|
1966
2599
|
import { spawn as spawn3 } from "child_process";
|
|
1967
|
-
import { existsSync as
|
|
1968
|
-
import { homedir as
|
|
1969
|
-
import { join as
|
|
2600
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
2601
|
+
import { homedir as homedir7 } from "os";
|
|
2602
|
+
import { join as join8 } from "path";
|
|
1970
2603
|
import { createInterface as createInterface2 } from "readline";
|
|
1971
2604
|
var ALIASES = {
|
|
1972
2605
|
claude: "anthropic",
|
|
@@ -2051,12 +2684,12 @@ async function connectClaude() {
|
|
|
2051
2684
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
2052
2685
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
2053
2686
|
}
|
|
2054
|
-
function codexSignIn(env = process.env, home =
|
|
2055
|
-
const path =
|
|
2056
|
-
if (!
|
|
2687
|
+
function codexSignIn(env = process.env, home = homedir7()) {
|
|
2688
|
+
const path = join8(env.CODEX_HOME || join8(home, ".codex"), "auth.json");
|
|
2689
|
+
if (!existsSync8(path)) {
|
|
2057
2690
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
2058
2691
|
}
|
|
2059
|
-
const raw =
|
|
2692
|
+
const raw = readFileSync9(path, "utf8");
|
|
2060
2693
|
try {
|
|
2061
2694
|
const parsed = JSON.parse(raw);
|
|
2062
2695
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -2064,12 +2697,12 @@ function codexSignIn(env = process.env, home = homedir5()) {
|
|
|
2064
2697
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
2065
2698
|
}
|
|
2066
2699
|
}
|
|
2067
|
-
function cursorSignIn(env = process.env, home =
|
|
2068
|
-
const path =
|
|
2069
|
-
if (!
|
|
2700
|
+
function cursorSignIn(env = process.env, home = homedir7()) {
|
|
2701
|
+
const path = join8(env.XDG_CONFIG_HOME || join8(home, ".config"), "cursor", "auth.json");
|
|
2702
|
+
if (!existsSync8(path)) {
|
|
2070
2703
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
2071
2704
|
}
|
|
2072
|
-
const raw =
|
|
2705
|
+
const raw = readFileSync9(path, "utf8");
|
|
2073
2706
|
try {
|
|
2074
2707
|
const parsed = JSON.parse(raw);
|
|
2075
2708
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -2139,20 +2772,20 @@ async function disconnect(name, opts) {
|
|
|
2139
2772
|
// src/setup.ts
|
|
2140
2773
|
var SETUP_STEPS = ["skill", "logins", "import", "secrets"];
|
|
2141
2774
|
var PLAN_PROVIDERS = { claude: "anthropic", anthropic: "anthropic", chatgpt: "openai", codex: "openai", openai: "openai", cursor: "cursor" };
|
|
2142
|
-
function planProviders(
|
|
2143
|
-
if (!
|
|
2144
|
-
return new Set(
|
|
2775
|
+
function planProviders(names2) {
|
|
2776
|
+
if (!names2?.length) return null;
|
|
2777
|
+
return new Set(names2.map((name) => {
|
|
2145
2778
|
const provider = PLAN_PROVIDERS[name.toLowerCase()];
|
|
2146
2779
|
if (!provider) throw new ApiError(400, "unknown_provider", `unknown plan "${name}" \u2014 use claude, chatgpt or cursor`);
|
|
2147
2780
|
return provider;
|
|
2148
2781
|
}));
|
|
2149
2782
|
}
|
|
2150
2783
|
function reportPath() {
|
|
2151
|
-
return
|
|
2784
|
+
return join9(dirname5(configPath()), "setup.json");
|
|
2152
2785
|
}
|
|
2153
2786
|
function loadReport() {
|
|
2154
2787
|
try {
|
|
2155
|
-
const saved = JSON.parse(
|
|
2788
|
+
const saved = JSON.parse(readFileSync10(reportPath(), "utf8"));
|
|
2156
2789
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
2157
2790
|
} catch {
|
|
2158
2791
|
return { agents: [], plans: [] };
|
|
@@ -2165,7 +2798,7 @@ function mergeReport(base, next) {
|
|
|
2165
2798
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
2166
2799
|
}
|
|
2167
2800
|
function saveReport(report) {
|
|
2168
|
-
mkdirSync2(
|
|
2801
|
+
mkdirSync2(dirname5(reportPath()), { recursive: true });
|
|
2169
2802
|
writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
2170
2803
|
`, { mode: 384 });
|
|
2171
2804
|
}
|
|
@@ -2515,17 +3148,17 @@ function renderArtifact(artifact2) {
|
|
|
2515
3148
|
}
|
|
2516
3149
|
|
|
2517
3150
|
// src/skills-store.ts
|
|
2518
|
-
import { readdirSync as
|
|
2519
|
-
import { basename as
|
|
3151
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
|
|
3152
|
+
import { basename as basename5, join as join10, relative as relative3, resolve as resolve5 } from "path";
|
|
2520
3153
|
function skillBundle(dir) {
|
|
2521
|
-
const root =
|
|
2522
|
-
const top =
|
|
3154
|
+
const root = resolve5(dir);
|
|
3155
|
+
const top = basename5(root);
|
|
2523
3156
|
const files2 = [];
|
|
2524
3157
|
const walk = (path) => {
|
|
2525
|
-
for (const name of
|
|
2526
|
-
const full =
|
|
2527
|
-
if (
|
|
2528
|
-
else files2.push({ path: `${top}/${
|
|
3158
|
+
for (const name of readdirSync4(path).sort()) {
|
|
3159
|
+
const full = join10(path, name);
|
|
3160
|
+
if (statSync5(full).isDirectory()) walk(full);
|
|
3161
|
+
else files2.push({ path: `${top}/${relative3(root, full)}`, bytes: readFileSync11(full) });
|
|
2529
3162
|
}
|
|
2530
3163
|
};
|
|
2531
3164
|
walk(root);
|
|
@@ -2535,7 +3168,7 @@ function skillBundle(dir) {
|
|
|
2535
3168
|
async function uploadSkill(dir, displayName) {
|
|
2536
3169
|
const form = new FormData();
|
|
2537
3170
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
2538
|
-
form.append("displayName", displayName ??
|
|
3171
|
+
form.append("displayName", displayName ?? basename5(resolve5(dir)));
|
|
2539
3172
|
return apiForm("POST", "/skills", form);
|
|
2540
3173
|
}
|
|
2541
3174
|
async function listSkills() {
|
|
@@ -2653,19 +3286,19 @@ function renderPolicy(rule) {
|
|
|
2653
3286
|
}
|
|
2654
3287
|
|
|
2655
3288
|
// src/harness.ts
|
|
2656
|
-
import { existsSync as
|
|
2657
|
-
import { dirname as
|
|
3289
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
3290
|
+
import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
|
|
2658
3291
|
var HARNESS_FILE = "harness.json";
|
|
2659
3292
|
function harnessFilePath(dir) {
|
|
2660
|
-
return
|
|
3293
|
+
return join11(resolve6(dir), HARNESS_FILE);
|
|
2661
3294
|
}
|
|
2662
3295
|
function readHarnessFile(dir) {
|
|
2663
3296
|
const path = harnessFilePath(dir);
|
|
2664
|
-
if (!
|
|
2665
|
-
throw new Error(`no ${HARNESS_FILE} found in ${
|
|
3297
|
+
if (!existsSync9(path)) {
|
|
3298
|
+
throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
|
|
2666
3299
|
}
|
|
2667
3300
|
try {
|
|
2668
|
-
return JSON.parse(
|
|
3301
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
2669
3302
|
} catch (err) {
|
|
2670
3303
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2671
3304
|
}
|
|
@@ -2692,10 +3325,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
2692
3325
|
`;
|
|
2693
3326
|
function harnessInit(dir) {
|
|
2694
3327
|
const path = harnessFilePath(dir);
|
|
2695
|
-
if (
|
|
3328
|
+
if (existsSync9(path)) {
|
|
2696
3329
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
2697
3330
|
}
|
|
2698
|
-
mkdirSync3(
|
|
3331
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
2699
3332
|
writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
2700
3333
|
`);
|
|
2701
3334
|
console.log(`Created ${path}`);
|
|
@@ -3200,6 +3833,38 @@ session.command("read <ref>").description("what the agent has said since the las
|
|
|
3200
3833
|
if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
|
|
3201
3834
|
})
|
|
3202
3835
|
);
|
|
3836
|
+
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(
|
|
3837
|
+
run(async (ref, opts) => {
|
|
3838
|
+
const id = await resolveSessionRef(ref);
|
|
3839
|
+
const path = `/sessions/${encodeURIComponent(id)}/share`;
|
|
3840
|
+
if (opts.off) {
|
|
3841
|
+
await api("DELETE", path);
|
|
3842
|
+
console.log(`Sharing is off for ${id}`);
|
|
3843
|
+
return;
|
|
3844
|
+
}
|
|
3845
|
+
const link = await api("POST", path, {
|
|
3846
|
+
access: opts.view ? "view" : "prompt",
|
|
3847
|
+
...opts.rotate ? { rotate: true } : {}
|
|
3848
|
+
});
|
|
3849
|
+
if (link.shareUrl) console.log(link.shareUrl);
|
|
3850
|
+
else console.log(`view-only token: ${link.token} \u2014 read with GET /links/${link.token}/events`);
|
|
3851
|
+
})
|
|
3852
|
+
);
|
|
3853
|
+
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(
|
|
3854
|
+
run(async (ref, opts) => {
|
|
3855
|
+
const id = await resolveSessionRef(ref);
|
|
3856
|
+
const usage = await api("GET", `/sessions/${encodeURIComponent(id)}/usage?limit=${encodeURIComponent(opts.limit ?? "20")}`);
|
|
3857
|
+
if (opts.json) {
|
|
3858
|
+
console.log(JSON.stringify(usage, null, 2));
|
|
3859
|
+
return;
|
|
3860
|
+
}
|
|
3861
|
+
const t = usage.totals;
|
|
3862
|
+
console.log(`${id}: $${t.billedCostUsd.toFixed(4)} billed, ${t.turns} model calls, ${t.inputTokens} in / ${t.outputTokens} out tokens`);
|
|
3863
|
+
for (const entry2 of usage.entries) {
|
|
3864
|
+
console.log(` ${entry2.at} ${entry2.kind.padEnd(16)} ${(entry2.model ?? "").padEnd(24)} $${entry2.billedCostUsd.toFixed(4)}`);
|
|
3865
|
+
}
|
|
3866
|
+
})
|
|
3867
|
+
);
|
|
3203
3868
|
var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
|
|
3204
3869
|
var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
|
|
3205
3870
|
sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
|
|
@@ -3416,9 +4081,9 @@ secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the a
|
|
|
3416
4081
|
);
|
|
3417
4082
|
secrets.command("list <id>").action(
|
|
3418
4083
|
run(async (id) => {
|
|
3419
|
-
const { secrets:
|
|
3420
|
-
if (!
|
|
3421
|
-
for (const s of
|
|
4084
|
+
const { secrets: keys2 } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
|
|
4085
|
+
if (!keys2.length) console.log("no secrets set");
|
|
4086
|
+
for (const s of keys2) {
|
|
3422
4087
|
console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
3423
4088
|
}
|
|
3424
4089
|
})
|
|
@@ -3434,9 +4099,9 @@ secrets.command("unset <id> <key>").action(
|
|
|
3434
4099
|
);
|
|
3435
4100
|
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
4101
|
run(async (opts) => {
|
|
3437
|
-
const path =
|
|
3438
|
-
if (!
|
|
3439
|
-
const name =
|
|
4102
|
+
const path = resolve7(opts.file);
|
|
4103
|
+
if (!existsSync10(path)) {
|
|
4104
|
+
const name = basename6(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
3440
4105
|
scaffoldAgentFile(path, name);
|
|
3441
4106
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
3442
4107
|
}
|
|
@@ -3467,7 +4132,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
|
|
|
3467
4132
|
);
|
|
3468
4133
|
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
3469
4134
|
run(async (opts) => {
|
|
3470
|
-
const path =
|
|
4135
|
+
const path = resolve7(opts.file);
|
|
3471
4136
|
const { file, spec } = readAgentFile(path);
|
|
3472
4137
|
if (!file.id) {
|
|
3473
4138
|
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
@@ -3510,7 +4175,7 @@ function formatRelative(msAgo) {
|
|
|
3510
4175
|
if (hours < 48) return `${hours}h ago`;
|
|
3511
4176
|
return `${Math.round(hours / 24)}d ago`;
|
|
3512
4177
|
}
|
|
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(
|
|
4178
|
+
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 its local files, skills, repository state or tool-server setup").action(
|
|
3514
4179
|
run(
|
|
3515
4180
|
async (opts) => {
|
|
3516
4181
|
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
@@ -3570,11 +4235,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3570
4235
|
console.log();
|
|
3571
4236
|
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3572
4237
|
const answer = await new Promise(
|
|
3573
|
-
(
|
|
4238
|
+
(resolve8) => rl.question(
|
|
3574
4239
|
`Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
|
|
3575
4240
|
(a) => {
|
|
3576
4241
|
rl.close();
|
|
3577
|
-
|
|
4242
|
+
resolve8(a.trim());
|
|
3578
4243
|
}
|
|
3579
4244
|
)
|
|
3580
4245
|
);
|
|
@@ -3592,14 +4257,14 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3592
4257
|
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
3593
4258
|
if (!opts.json) {
|
|
3594
4259
|
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.");
|
|
4260
|
+
console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation, the local files it read, wrote or ran under, the skills it loaded, its repository state and the names of its tool servers are attached; text is redacted locally. Credential files, tool-server keys and login credentials are never transferred. Use --no-files to send the conversation only.");
|
|
3596
4261
|
}
|
|
3597
4262
|
if (!autoConfirm) {
|
|
3598
4263
|
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
3599
4264
|
const prompt = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3600
|
-
const answer = await new Promise((
|
|
4265
|
+
const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
3601
4266
|
prompt.close();
|
|
3602
|
-
|
|
4267
|
+
resolve8(value);
|
|
3603
4268
|
}));
|
|
3604
4269
|
if (!/^y(?:es)?$/i.test(answer.trim())) {
|
|
3605
4270
|
console.log("Cancelled. Nothing uploaded.");
|
|
@@ -3617,6 +4282,7 @@ Importing ${label} \u2026
|
|
|
3617
4282
|
summaryPath: opts.summary,
|
|
3618
4283
|
importKey: opts.key,
|
|
3619
4284
|
grantTraceId: opts.trace,
|
|
4285
|
+
files: opts.files,
|
|
3620
4286
|
onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
|
|
3621
4287
|
`)
|
|
3622
4288
|
});
|
|
@@ -3631,9 +4297,16 @@ Importing ${label} \u2026
|
|
|
3631
4297
|
bytes: result.bytes,
|
|
3632
4298
|
redactionCount: result.redactionCount,
|
|
3633
4299
|
gaps: result.gaps,
|
|
4300
|
+
attachedFiles: result.attachedFiles,
|
|
4301
|
+
leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
|
|
4302
|
+
skills: result.setup.skills,
|
|
4303
|
+
missingSkills: result.setup.missingSkills,
|
|
4304
|
+
mcpServers: result.setup.mcpServers,
|
|
3634
4305
|
continueUrl: result.url
|
|
3635
4306
|
}));
|
|
3636
4307
|
} else {
|
|
4308
|
+
if (result.setup.skills.length || result.setup.mcpServers.length) console.log(` Carried ${result.setup.skills.length} ${result.setup.skills.length === 1 ? "skill" : "skills"} and the setup of ${result.setup.mcpServers.length} tool ${result.setup.mcpServers.length === 1 ? "server" : "servers"} (their keys stay on this computer).`);
|
|
4309
|
+
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
4310
|
console.log(` \u2713 Imported. Continue here:
|
|
3638
4311
|
${result.url}
|
|
3639
4312
|
`);
|
|
@@ -3700,7 +4373,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
3700
4373
|
);
|
|
3701
4374
|
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
4375
|
run(async (taskId, path, local) => {
|
|
3703
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
4376
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync13(local), mediaTypeFor(local));
|
|
3704
4377
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
3705
4378
|
})
|
|
3706
4379
|
);
|
|
@@ -3865,6 +4538,19 @@ for (const [verb, blurb] of [
|
|
|
3865
4538
|
})
|
|
3866
4539
|
);
|
|
3867
4540
|
}
|
|
4541
|
+
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(
|
|
4542
|
+
run(async (id, opts) => {
|
|
4543
|
+
const params = new URLSearchParams({ limit: opts.limit ?? "20" });
|
|
4544
|
+
if (id) params.set("routine", id);
|
|
4545
|
+
if (opts.failed) params.set("has_error", "true");
|
|
4546
|
+
const { items } = await api("GET", `/routine-runs?${params}`);
|
|
4547
|
+
if (!items.length) console.log("no runs");
|
|
4548
|
+
for (const runRow of items) {
|
|
4549
|
+
const outcome = runRow.error ? `FAILED ${runRow.error.type}: ${runRow.error.message}` : runRow.session_id ?? "ok";
|
|
4550
|
+
console.log(`${runRow.id} ${runRow.created_at} ${runRow.trigger_context.type.padEnd(8)} ${outcome}`);
|
|
4551
|
+
}
|
|
4552
|
+
})
|
|
4553
|
+
);
|
|
3868
4554
|
var collect = (value, previous = []) => [...previous, value];
|
|
3869
4555
|
function goalPath(sessionId, suffix = "") {
|
|
3870
4556
|
return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
|
|
@@ -3959,6 +4645,147 @@ program.command("config").description("show the CLI config location and current
|
|
|
3959
4645
|
console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
|
|
3960
4646
|
console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
|
|
3961
4647
|
});
|
|
4648
|
+
var billing = program.command("billing").description("your balance, auto top-up, usage across sessions and API keys, and buying credit");
|
|
4649
|
+
billing.command("show").description("balance, whether a card is saved, and auto top-up").action(
|
|
4650
|
+
run(async () => {
|
|
4651
|
+
const [balance, autoTopup] = await Promise.all([
|
|
4652
|
+
api("GET", "/billing"),
|
|
4653
|
+
api("GET", "/billing/auto-topup")
|
|
4654
|
+
]);
|
|
4655
|
+
console.log(renderBalance(balance));
|
|
4656
|
+
console.log(renderAutoTopup(autoTopup));
|
|
4657
|
+
})
|
|
4658
|
+
);
|
|
4659
|
+
billing.command("card").description("print the address of a card form to save a card; saving moves no money").action(
|
|
4660
|
+
run(async () => {
|
|
4661
|
+
const { url } = await api("POST", "/billing/card-setup");
|
|
4662
|
+
console.log(url);
|
|
4663
|
+
})
|
|
4664
|
+
);
|
|
4665
|
+
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(
|
|
4666
|
+
run(async (dollars2, opts) => {
|
|
4667
|
+
const result = await api("POST", "/billing/topups", {
|
|
4668
|
+
amount: { amount: dollarsToCents("<dollars>", dollars2), currency: "USD" },
|
|
4669
|
+
request_id: opts.requestId ?? `sky-${crypto.randomUUID()}`
|
|
4670
|
+
});
|
|
4671
|
+
console.log(renderTopup(result));
|
|
4672
|
+
})
|
|
4673
|
+
);
|
|
4674
|
+
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(
|
|
4675
|
+
run(async (opts) => {
|
|
4676
|
+
const body = autoTopupBody(opts);
|
|
4677
|
+
const settings = body ? await api("PATCH", "/billing/auto-topup", body) : await api("GET", "/billing/auto-topup");
|
|
4678
|
+
console.log(renderAutoTopup(settings));
|
|
4679
|
+
})
|
|
4680
|
+
);
|
|
4681
|
+
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");
|
|
4682
|
+
usageOptions(
|
|
4683
|
+
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")
|
|
4684
|
+
).action(
|
|
4685
|
+
run(async (opts) => {
|
|
4686
|
+
const summary = await api("GET", `/billing/usage${usageQuery({ ...opts, groupBy: opts.by })}`);
|
|
4687
|
+
console.log(opts.json ? JSON.stringify(summary, null, 2) : renderUsage(summary));
|
|
4688
|
+
})
|
|
4689
|
+
);
|
|
4690
|
+
usageOptions(
|
|
4691
|
+
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")
|
|
4692
|
+
).action(
|
|
4693
|
+
run(async (opts) => {
|
|
4694
|
+
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
4695
|
+
if (opts.out) {
|
|
4696
|
+
writeFileSync6(opts.out, bytes);
|
|
4697
|
+
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
4698
|
+
} else {
|
|
4699
|
+
process.stdout.write(bytes);
|
|
4700
|
+
}
|
|
4701
|
+
})
|
|
4702
|
+
);
|
|
4703
|
+
var keys = program.command("keys").description("your own API keys: list, create, revoke, restore (admin scope)");
|
|
4704
|
+
keys.command("list").action(
|
|
4705
|
+
run(async () => {
|
|
4706
|
+
const { api_keys: list } = await api("GET", "/api_keys");
|
|
4707
|
+
if (!list.length) console.log("no API keys");
|
|
4708
|
+
for (const key of list) console.log(renderApiKey(key));
|
|
4709
|
+
})
|
|
4710
|
+
);
|
|
4711
|
+
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(
|
|
4712
|
+
run(async (name, opts) => {
|
|
4713
|
+
const { api_key: key, secret } = await api("POST", "/api_keys", apiKeyBody(name, opts));
|
|
4714
|
+
console.error(renderApiKey(key));
|
|
4715
|
+
console.log(secret);
|
|
4716
|
+
})
|
|
4717
|
+
);
|
|
4718
|
+
keys.command("reveal <id>").description("print a key's secret again").action(
|
|
4719
|
+
run(async (id) => {
|
|
4720
|
+
const { secret } = await api("POST", `/api_keys/${encodeURIComponent(id)}/reveal`);
|
|
4721
|
+
console.log(secret);
|
|
4722
|
+
})
|
|
4723
|
+
);
|
|
4724
|
+
for (const [verb, blurb] of [
|
|
4725
|
+
["revoke", "the key stops working at once"],
|
|
4726
|
+
["restore", "a revoked key works again"]
|
|
4727
|
+
]) {
|
|
4728
|
+
keys.command(`${verb} <id>`).description(blurb).action(
|
|
4729
|
+
run(async (id) => {
|
|
4730
|
+
const { api_key: key } = await api("POST", `/api_keys/${encodeURIComponent(id)}/${verb}`);
|
|
4731
|
+
console.log(renderApiKey(key));
|
|
4732
|
+
})
|
|
4733
|
+
);
|
|
4734
|
+
}
|
|
4735
|
+
var memory = program.command("memory").description("your personal memory: what every session you start is told about you");
|
|
4736
|
+
async function writeMemory(operation) {
|
|
4737
|
+
const current = await api("GET", "/memory");
|
|
4738
|
+
console.log(renderMemory(await api("POST", "/memory", memoryBody(operation, current.version))));
|
|
4739
|
+
}
|
|
4740
|
+
memory.command("list").action(run(async () => console.log(renderMemory(await api("GET", "/memory")))));
|
|
4741
|
+
memory.command("save <text...>").description("add an entry").action(run(async (text) => writeMemory({ op: "save", text: text.join(" ") })));
|
|
4742
|
+
memory.command("edit <index> <text...>").description("replace entry <index>").action(run(async (index, text) => writeMemory({ op: "update", index: memoryIndex(index), text: text.join(" ") })));
|
|
4743
|
+
memory.command("rm <index>").description("remove entry <index>").action(run(async (index) => writeMemory({ op: "remove", index: memoryIndex(index) })));
|
|
4744
|
+
memory.command("clear").description("remove every entry").action(run(async () => writeMemory({ op: "clear" })));
|
|
4745
|
+
var context = program.command("context").description("search your conversations, memory and the workspace trace library");
|
|
4746
|
+
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(
|
|
4747
|
+
run(async (words, opts) => {
|
|
4748
|
+
const page = await api("GET", `/context/search${searchQuery(words.join(" "), opts)}`);
|
|
4749
|
+
console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
|
|
4750
|
+
})
|
|
4751
|
+
);
|
|
4752
|
+
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(
|
|
4753
|
+
run(async (ref, opts) => {
|
|
4754
|
+
const params = new URLSearchParams({ ref });
|
|
4755
|
+
if (opts.offset) params.set("offset", opts.offset);
|
|
4756
|
+
const read = await api("GET", `/context/read?${params}`);
|
|
4757
|
+
console.log(read.text);
|
|
4758
|
+
if (read.nextOffset !== null) console.error(`more: --offset ${read.nextOffset}`);
|
|
4759
|
+
})
|
|
4760
|
+
);
|
|
4761
|
+
var webhookLine = (w) => `${w.id} ${w.status.padEnd(8)} ${w.url} ${w.events.join(",")}${w.failure_count ? ` (${w.failure_count} failures)` : ""}`;
|
|
4762
|
+
var webhook = program.command("webhook").description("your HTTPS endpoints for platform events, signed with a whsec_ secret");
|
|
4763
|
+
webhook.command("list").action(
|
|
4764
|
+
run(async () => {
|
|
4765
|
+
const { webhooks } = await api("GET", "/webhooks");
|
|
4766
|
+
if (!webhooks.length) console.log("no webhooks");
|
|
4767
|
+
for (const w of webhooks) console.log(webhookLine(w));
|
|
4768
|
+
})
|
|
4769
|
+
);
|
|
4770
|
+
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(
|
|
4771
|
+
run(async (url, opts) => {
|
|
4772
|
+
const { webhook: created } = await api("POST", "/webhooks", { url, events: opts.event });
|
|
4773
|
+
console.error(webhookLine(created));
|
|
4774
|
+
if (created.secret) console.log(created.secret);
|
|
4775
|
+
})
|
|
4776
|
+
);
|
|
4777
|
+
webhook.command("enable <id>").description("re-enable an endpoint that was disabled after failed deliveries").action(
|
|
4778
|
+
run(async (id) => {
|
|
4779
|
+
const { webhook: updated } = await api("PATCH", `/webhooks/${encodeURIComponent(id)}`, { status: "active" });
|
|
4780
|
+
console.log(webhookLine(updated));
|
|
4781
|
+
})
|
|
4782
|
+
);
|
|
4783
|
+
webhook.command("rm <id>").description("delete an endpoint").action(
|
|
4784
|
+
run(async (id) => {
|
|
4785
|
+
await api("DELETE", `/webhooks/${encodeURIComponent(id)}`);
|
|
4786
|
+
console.log(`Deleted ${id}`);
|
|
4787
|
+
})
|
|
4788
|
+
);
|
|
3962
4789
|
var harness = program.command("harness").description("workspace harnesses: init, verify, push");
|
|
3963
4790
|
harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
|
|
3964
4791
|
harnessInit(dir ?? ".");
|
|
@@ -3982,7 +4809,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
3982
4809
|
}));
|
|
3983
4810
|
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
4811
|
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-
|
|
4812
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-YKEGRVJN.js");
|
|
3986
4813
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
3987
4814
|
const scopes = parseContentScopes(opts.capture);
|
|
3988
4815
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -4011,13 +4838,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
4011
4838
|
]);
|
|
4012
4839
|
console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
|
|
4013
4840
|
console.log(scopes.length ? ` content: ${scopes.join(", ")} (redacted here before it leaves)` : " metadata only");
|
|
4014
|
-
if (opts.hooks) console.log(` hooks: ${installClaudeHooks(
|
|
4841
|
+
if (opts.hooks) console.log(` hooks: ${installClaudeHooks(resolve7(typeof opts.hooks === "string" ? opts.hooks : ".claude/settings.local.json"))}`);
|
|
4015
4842
|
console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
|
|
4016
4843
|
console.log(` sky trace show ${trace.id}`);
|
|
4017
4844
|
}));
|
|
4018
4845
|
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
4846
|
try {
|
|
4020
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
4847
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-YKEGRVJN.js");
|
|
4021
4848
|
const chunks = [];
|
|
4022
4849
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4023
4850
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -4030,14 +4857,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
4030
4857
|
}
|
|
4031
4858
|
});
|
|
4032
4859
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
4033
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
4860
|
+
const { readState, flush, grantSender } = await import("./trace-YKEGRVJN.js");
|
|
4034
4861
|
const state = readState();
|
|
4035
4862
|
if (!state) throw new Error("no trace is running here");
|
|
4036
4863
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
4037
4864
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
4038
4865
|
}));
|
|
4039
4866
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
4040
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
4867
|
+
const { readState, spoolStatus } = await import("./trace-YKEGRVJN.js");
|
|
4041
4868
|
const state = readState();
|
|
4042
4869
|
if (!state) {
|
|
4043
4870
|
console.log("No trace is running here.");
|
|
@@ -4047,7 +4874,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
4047
4874
|
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
4875
|
}));
|
|
4049
4876
|
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-
|
|
4877
|
+
const { readState } = await import("./trace-YKEGRVJN.js");
|
|
4051
4878
|
const ref = id ?? readState()?.trace_id;
|
|
4052
4879
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
4053
4880
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -4074,7 +4901,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
4074
4901
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
4075
4902
|
}));
|
|
4076
4903
|
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-
|
|
4904
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-YKEGRVJN.js");
|
|
4078
4905
|
const state = readState();
|
|
4079
4906
|
if (!state) throw new Error("no trace is running here");
|
|
4080
4907
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -4084,7 +4911,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
4084
4911
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
4085
4912
|
}));
|
|
4086
4913
|
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-
|
|
4914
|
+
const lib = await import("./trace-library-6BK5GJVD.js");
|
|
4088
4915
|
if (opts.hook) {
|
|
4089
4916
|
try {
|
|
4090
4917
|
let raw = payload ?? "";
|
|
@@ -4132,10 +4959,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4132
4959
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
4133
4960
|
}
|
|
4134
4961
|
if (opts.installSchedule) {
|
|
4135
|
-
const { join:
|
|
4136
|
-
const { homedir:
|
|
4962
|
+
const { join: join12 } = await import("path");
|
|
4963
|
+
const { homedir: homedir8 } = await import("os");
|
|
4137
4964
|
const { execFileSync } = await import("child_process");
|
|
4138
|
-
const log =
|
|
4965
|
+
const log = join12(homedir8(), ".config", "sky", "trace-sync.log");
|
|
4139
4966
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
4140
4967
|
try {
|
|
4141
4968
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -4145,11 +4972,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4145
4972
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
4146
4973
|
}
|
|
4147
4974
|
if (opts.status) {
|
|
4148
|
-
const { readFileSync:
|
|
4149
|
-
const { join:
|
|
4975
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
4976
|
+
const { join: join12 } = await import("path");
|
|
4150
4977
|
let state = { sessions: {} };
|
|
4151
4978
|
try {
|
|
4152
|
-
state = JSON.parse(
|
|
4979
|
+
state = JSON.parse(readFileSync14(join12(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
4153
4980
|
} catch {
|
|
4154
4981
|
}
|
|
4155
4982
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -4182,7 +5009,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4182
5009
|
})();
|
|
4183
5010
|
});
|
|
4184
5011
|
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-
|
|
5012
|
+
const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
|
|
4186
5013
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4187
5014
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4188
5015
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -4199,7 +5026,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
4199
5026
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
4200
5027
|
}));
|
|
4201
5028
|
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-
|
|
5029
|
+
const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
|
|
4203
5030
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4204
5031
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4205
5032
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|
|
@@ -4209,7 +5036,10 @@ traceCmd.command("library").description("list the sessions in a workspace's trac
|
|
|
4209
5036
|
}
|
|
4210
5037
|
console.log(`${page.total} sessions in ${workspace2}'s trace library`);
|
|
4211
5038
|
for (const trace of page.traces) {
|
|
4212
|
-
|
|
5039
|
+
const models = trace.models?.length ? `${trace.models[0]}${trace.models.length > 1 ? ` +${trace.models.length - 1}` : ""}` : "";
|
|
5040
|
+
const output = trace.usage?.output_tokens ? `${trace.usage.output_tokens.toLocaleString("en-US")} out` : "";
|
|
5041
|
+
const source = [models, output].filter(Boolean).join(" \xB7 ");
|
|
5042
|
+
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
5043
|
}
|
|
4214
5044
|
}));
|
|
4215
5045
|
var workerCmd = program.command("worker").description("run a pod on this machine and connect it to a worker pool");
|