@agentsky/sky 2.16.0 → 2.18.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-5JHQH7WH.js → chunk-MUHFB3LC.js} +1 -1
- package/dist/{chunk-MQECNHD2.js → chunk-PL5KNPCX.js} +2 -2
- package/dist/{chunk-RHRREQMF.js → chunk-ZJVFWFOY.js} +13 -0
- package/dist/index.js +653 -172
- package/dist/products.js +1 -1
- package/dist/{src-KLP774OA.js → src-TJJKOFXZ.js} +1 -1
- package/dist/{trace-IOARVWJ6.js → trace-QLJH7VK6.js} +2 -2
- package/dist/{trace-library-K3NE26GH.js → trace-library-FS5PCBJC.js} +3 -3
- 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-PL5KNPCX.js";
|
|
13
13
|
import {
|
|
14
14
|
CLI_VERSION,
|
|
15
15
|
USER_AGENT,
|
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
currentUniverse,
|
|
21
21
|
loadConfig,
|
|
22
22
|
saveConfig
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-MUHFB3LC.js";
|
|
24
24
|
import {
|
|
25
25
|
AGENT_TYPES,
|
|
26
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -37,21 +37,22 @@ import {
|
|
|
37
37
|
modelSubscriptionProviderFor,
|
|
38
38
|
reasoningEffortDocLine,
|
|
39
39
|
redactLine,
|
|
40
|
+
redactSecretValues,
|
|
40
41
|
skillRefSchema,
|
|
41
42
|
splitMcpServers,
|
|
42
43
|
supersededBy
|
|
43
|
-
} from "./chunk-
|
|
44
|
+
} from "./chunk-ZJVFWFOY.js";
|
|
44
45
|
import "./chunk-MLKGABMK.js";
|
|
45
46
|
|
|
46
47
|
// src/index.ts
|
|
47
|
-
import { existsSync as existsSync10, readFileSync as
|
|
48
|
+
import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
48
49
|
import { basename as basename8, resolve as resolve9 } from "path";
|
|
49
50
|
import { createInterface as createInterface6 } from "readline";
|
|
50
51
|
import { Command } from "commander";
|
|
51
52
|
|
|
52
53
|
// src/files.ts
|
|
53
|
-
import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
54
|
-
import { basename as basename2, extname } from "path";
|
|
54
|
+
import { readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
55
|
+
import { basename as basename2, extname, join as join2 } from "path";
|
|
55
56
|
|
|
56
57
|
// src/resumable-upload.ts
|
|
57
58
|
import { createHash } from "crypto";
|
|
@@ -183,6 +184,58 @@ async function uploadLocalFile(path, expiresInSeconds, onProgress = () => {
|
|
|
183
184
|
const bytes = readFileSync2(path);
|
|
184
185
|
return uploadFile(new Uint8Array(bytes), basename2(path), mediaTypeFor(path), expiresInSeconds);
|
|
185
186
|
}
|
|
187
|
+
var IGNORED_NAMES = /* @__PURE__ */ new Set([".DS_Store", "Thumbs.db", "desktop.ini"]);
|
|
188
|
+
function filesUnder(paths) {
|
|
189
|
+
const found = [];
|
|
190
|
+
const walk = (path) => {
|
|
191
|
+
const stat = statSync2(path);
|
|
192
|
+
if (stat.isFile()) {
|
|
193
|
+
if (!IGNORED_NAMES.has(basename2(path))) found.push(path);
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
if (!stat.isDirectory()) return;
|
|
197
|
+
for (const name of readdirSync(path).sort()) walk(join2(path, name));
|
|
198
|
+
};
|
|
199
|
+
for (const path of paths) walk(path);
|
|
200
|
+
return found;
|
|
201
|
+
}
|
|
202
|
+
function transient(error) {
|
|
203
|
+
return !(error instanceof ApiError) || error.status === 429 || error.status >= 500;
|
|
204
|
+
}
|
|
205
|
+
async function uploadLocalFiles(paths, {
|
|
206
|
+
concurrency = 4,
|
|
207
|
+
attempts = 4,
|
|
208
|
+
expiresInSeconds,
|
|
209
|
+
onUploaded,
|
|
210
|
+
onFailed,
|
|
211
|
+
wait = (ms) => new Promise((done) => setTimeout(done, ms))
|
|
212
|
+
}) {
|
|
213
|
+
let next = 0;
|
|
214
|
+
let uploaded = 0;
|
|
215
|
+
let failed = 0;
|
|
216
|
+
const worker = async () => {
|
|
217
|
+
while (next < paths.length) {
|
|
218
|
+
const path = paths[next++];
|
|
219
|
+
for (let attempt = 1; ; attempt++) {
|
|
220
|
+
try {
|
|
221
|
+
onUploaded(path, await uploadLocalFile(path, expiresInSeconds));
|
|
222
|
+
uploaded += 1;
|
|
223
|
+
break;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
if (attempt < attempts && transient(error)) {
|
|
226
|
+
await wait(1e3 * 2 ** (attempt - 1));
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
onFailed(path, error);
|
|
230
|
+
failed += 1;
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
};
|
|
236
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, paths.length) }, worker));
|
|
237
|
+
return { uploaded, failed };
|
|
238
|
+
}
|
|
186
239
|
function fileBlock(file) {
|
|
187
240
|
const source = { type: "file", file_id: file.id };
|
|
188
241
|
return file.mime_type.startsWith("image/") ? { type: "image", source } : { type: "document", source, title: file.filename };
|
|
@@ -564,12 +617,12 @@ function renderCatalogAgent(agent2) {
|
|
|
564
617
|
import { spawn } from "child_process";
|
|
565
618
|
import { existsSync as existsSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
566
619
|
import { homedir } from "os";
|
|
567
|
-
import { basename as basename3, join as
|
|
620
|
+
import { basename as basename3, join as join3 } from "path";
|
|
568
621
|
import { createInterface } from "readline";
|
|
569
622
|
var PROMPT_CAP = 1e5;
|
|
570
623
|
var HARNESSES = ["claude_code", "codex", "openclaw"];
|
|
571
624
|
function claudeConfigRoot() {
|
|
572
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
625
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join3(homedir(), ".claude");
|
|
573
626
|
}
|
|
574
627
|
function readIfExists(path) {
|
|
575
628
|
try {
|
|
@@ -589,9 +642,9 @@ function readJson(path) {
|
|
|
589
642
|
}
|
|
590
643
|
function claudeModel(configRoot) {
|
|
591
644
|
const candidates = [
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
645
|
+
join3(process.cwd(), ".claude", "settings.local.json"),
|
|
646
|
+
join3(process.cwd(), ".claude", "settings.json"),
|
|
647
|
+
join3(configRoot, "settings.json")
|
|
595
648
|
];
|
|
596
649
|
for (const path of candidates) {
|
|
597
650
|
const model = readJson(path)?.model;
|
|
@@ -601,11 +654,11 @@ function claudeModel(configRoot) {
|
|
|
601
654
|
}
|
|
602
655
|
function claudeMcpServers() {
|
|
603
656
|
const merged = {};
|
|
604
|
-
const userConfig = readJson(
|
|
657
|
+
const userConfig = readJson(join3(homedir(), ".claude.json"));
|
|
605
658
|
Object.assign(merged, userConfig?.mcpServers ?? {});
|
|
606
659
|
Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
|
|
607
660
|
const projectConfig = readJson(
|
|
608
|
-
|
|
661
|
+
join3(process.cwd(), ".mcp.json")
|
|
609
662
|
);
|
|
610
663
|
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
611
664
|
return merged;
|
|
@@ -622,13 +675,13 @@ function readClaudeCode() {
|
|
|
622
675
|
const configRoot = claudeConfigRoot();
|
|
623
676
|
const parts = [];
|
|
624
677
|
const sources = [];
|
|
625
|
-
const global = readIfExists(
|
|
678
|
+
const global = readIfExists(join3(configRoot, "CLAUDE.md"));
|
|
626
679
|
if (global?.trim()) {
|
|
627
680
|
parts.push({ label: "Global CLAUDE.md", content: global });
|
|
628
681
|
sources.push("global CLAUDE.md");
|
|
629
682
|
}
|
|
630
683
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
631
|
-
const project = readIfExists(
|
|
684
|
+
const project = readIfExists(join3(process.cwd(), name));
|
|
632
685
|
if (project?.trim()) {
|
|
633
686
|
parts.push({ label: `Project ${name}`, content: project });
|
|
634
687
|
sources.push(`project ${name}`);
|
|
@@ -669,8 +722,8 @@ function detectHarness(explicit) {
|
|
|
669
722
|
return explicit;
|
|
670
723
|
}
|
|
671
724
|
if (existsSync2(claudeConfigRoot())) return "claude_code";
|
|
672
|
-
if (existsSync2(
|
|
673
|
-
if (existsSync2(
|
|
725
|
+
if (existsSync2(join3(homedir(), ".codex"))) return "codex";
|
|
726
|
+
if (existsSync2(join3(homedir(), ".openclaw"))) return "openclaw";
|
|
674
727
|
throw new Error(
|
|
675
728
|
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
676
729
|
);
|
|
@@ -682,7 +735,7 @@ function readLocal(harness2) {
|
|
|
682
735
|
);
|
|
683
736
|
}
|
|
684
737
|
function linkPath(configRoot) {
|
|
685
|
-
return
|
|
738
|
+
return join3(configRoot, "agentsky-link.json");
|
|
686
739
|
}
|
|
687
740
|
function readLink(configRoot) {
|
|
688
741
|
const link = readJson(linkPath(configRoot));
|
|
@@ -928,22 +981,22 @@ async function clone(opts) {
|
|
|
928
981
|
}
|
|
929
982
|
|
|
930
983
|
// src/import-local.ts
|
|
931
|
-
import { closeSync as
|
|
984
|
+
import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
|
|
932
985
|
import { homedir as homedir4 } from "os";
|
|
933
|
-
import { join as
|
|
934
|
-
import { createHash as
|
|
986
|
+
import { join as join7 } from "path";
|
|
987
|
+
import { createHash as createHash3 } from "crypto";
|
|
935
988
|
|
|
936
989
|
// src/import-files.ts
|
|
937
990
|
import { isUtf8 } from "buffer";
|
|
938
991
|
import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
939
992
|
import { homedir as homedir3 } from "os";
|
|
940
|
-
import { dirname as dirname4, extname as extname2, isAbsolute, join as
|
|
993
|
+
import { dirname as dirname4, extname as extname2, isAbsolute, join as join5, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
|
|
941
994
|
|
|
942
995
|
// src/import-workspace.ts
|
|
943
996
|
import { spawnSync } from "child_process";
|
|
944
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
997
|
+
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
945
998
|
import { homedir as homedir2 } from "os";
|
|
946
|
-
import { basename as basename4, dirname as dirname3, join as
|
|
999
|
+
import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
|
|
947
1000
|
import { parse as parseToml2 } from "smol-toml";
|
|
948
1001
|
var MAX_TREE = 5e3;
|
|
949
1002
|
var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
|
|
@@ -966,13 +1019,13 @@ var isDir = (path) => {
|
|
|
966
1019
|
}
|
|
967
1020
|
};
|
|
968
1021
|
function sharedInstructionPaths(home = homedir2()) {
|
|
969
|
-
const rules =
|
|
1022
|
+
const rules = join4(home, ".agents", "rules");
|
|
970
1023
|
let ruleFiles = [];
|
|
971
1024
|
try {
|
|
972
|
-
ruleFiles =
|
|
1025
|
+
ruleFiles = readdirSync2(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join4(rules, name));
|
|
973
1026
|
} catch {
|
|
974
1027
|
}
|
|
975
|
-
return [
|
|
1028
|
+
return [join4(home, "AGENTS.md"), join4(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
976
1029
|
}
|
|
977
1030
|
function strings(value, out, depth = 0) {
|
|
978
1031
|
if (depth > 12) return;
|
|
@@ -981,30 +1034,30 @@ function strings(value, out, depth = 0) {
|
|
|
981
1034
|
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
982
1035
|
}
|
|
983
1036
|
function skillRoots(cwd, home) {
|
|
984
|
-
const claude = process.env.CLAUDE_CONFIG_DIR ??
|
|
985
|
-
const codex = process.env.CODEX_HOME ??
|
|
1037
|
+
const claude = process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude");
|
|
1038
|
+
const codex = process.env.CODEX_HOME ?? join4(home, ".codex");
|
|
986
1039
|
return [
|
|
987
|
-
...cwd ? [
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
1040
|
+
...cwd ? [join4(cwd, ".claude", "skills"), join4(cwd, ".agents", "skills")] : [],
|
|
1041
|
+
join4(home, ".agents", "skills"),
|
|
1042
|
+
join4(claude, "skills"),
|
|
1043
|
+
join4(codex, "skills"),
|
|
1044
|
+
join4(home, ".hermes", "skills")
|
|
992
1045
|
];
|
|
993
1046
|
}
|
|
994
1047
|
function pluginSkill(plugin, skill, home) {
|
|
995
|
-
const base =
|
|
1048
|
+
const base = join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins");
|
|
996
1049
|
const walk = (dir, depth) => {
|
|
997
1050
|
if (depth > 7) return null;
|
|
998
1051
|
let entries;
|
|
999
1052
|
try {
|
|
1000
|
-
entries =
|
|
1053
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1001
1054
|
} catch {
|
|
1002
1055
|
return null;
|
|
1003
1056
|
}
|
|
1004
1057
|
for (const entry2 of entries) {
|
|
1005
1058
|
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
1006
|
-
const path =
|
|
1007
|
-
if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(
|
|
1059
|
+
const path = join4(dir, entry2.name);
|
|
1060
|
+
if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join4(path, "SKILL.md"))) return path;
|
|
1008
1061
|
const found = walk(path, depth + 1);
|
|
1009
1062
|
if (found) return found;
|
|
1010
1063
|
}
|
|
@@ -1016,8 +1069,8 @@ function findSkill(name, cwd, home) {
|
|
|
1016
1069
|
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
1017
1070
|
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
1018
1071
|
for (const root of skillRoots(cwd, home)) {
|
|
1019
|
-
const direct =
|
|
1020
|
-
if (isFile(
|
|
1072
|
+
const direct = join4(root, name);
|
|
1073
|
+
if (isFile(join4(direct, "SKILL.md"))) return realpathSync(direct);
|
|
1021
1074
|
}
|
|
1022
1075
|
return null;
|
|
1023
1076
|
}
|
|
@@ -1025,7 +1078,7 @@ function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
|
1025
1078
|
const dirs = /* @__PURE__ */ new Map();
|
|
1026
1079
|
const missing = /* @__PURE__ */ new Set();
|
|
1027
1080
|
const add = (dir, name = basename4(dir)) => {
|
|
1028
|
-
if (!isFile(
|
|
1081
|
+
if (!isFile(join4(dir, "SKILL.md"))) return false;
|
|
1029
1082
|
const real = realpathSync(dir);
|
|
1030
1083
|
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
1031
1084
|
return true;
|
|
@@ -1096,16 +1149,16 @@ function mcpServers(harness2, cwd, home = homedir2()) {
|
|
|
1096
1149
|
}
|
|
1097
1150
|
};
|
|
1098
1151
|
if (harness2 === "claude_code") {
|
|
1099
|
-
const config = readJson2(
|
|
1152
|
+
const config = readJson2(join4(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
1100
1153
|
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
1101
1154
|
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
1102
1155
|
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
1103
|
-
if (root) push(readJson2(
|
|
1156
|
+
if (root) push(readJson2(join4(root, ".mcp.json"))?.mcpServers, "project");
|
|
1104
1157
|
push(config?.mcpServers, "user");
|
|
1105
1158
|
}
|
|
1106
1159
|
if (harness2 === "codex") {
|
|
1107
1160
|
try {
|
|
1108
|
-
const config = parseToml2(readFileSync5(
|
|
1161
|
+
const config = parseToml2(readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "config.toml"), "utf8"));
|
|
1109
1162
|
push(config.mcp_servers, "user");
|
|
1110
1163
|
} catch {
|
|
1111
1164
|
}
|
|
@@ -1159,13 +1212,13 @@ function skillFiles(skill) {
|
|
|
1159
1212
|
if (depth > 8 || out.length > 400) return;
|
|
1160
1213
|
let entries;
|
|
1161
1214
|
try {
|
|
1162
|
-
entries =
|
|
1215
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1163
1216
|
} catch {
|
|
1164
1217
|
return;
|
|
1165
1218
|
}
|
|
1166
1219
|
for (const entry2 of entries) {
|
|
1167
1220
|
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1168
|
-
const path =
|
|
1221
|
+
const path = join4(dir, entry2.name);
|
|
1169
1222
|
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1170
1223
|
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1171
1224
|
}
|
|
@@ -1176,7 +1229,7 @@ function skillFiles(skill) {
|
|
|
1176
1229
|
function codexThreadName(sessionId, home = homedir2()) {
|
|
1177
1230
|
let name = null;
|
|
1178
1231
|
try {
|
|
1179
|
-
for (const line of readFileSync5(
|
|
1232
|
+
for (const line of readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
|
|
1180
1233
|
if (!line.includes(sessionId)) continue;
|
|
1181
1234
|
try {
|
|
1182
1235
|
const entry2 = JSON.parse(line);
|
|
@@ -1296,7 +1349,7 @@ function touchedPaths(records, cwd, home = homedir3()) {
|
|
|
1296
1349
|
}
|
|
1297
1350
|
function gitRoot(dir) {
|
|
1298
1351
|
for (let d = dir; ; d = dirname4(d)) {
|
|
1299
|
-
if (existsSync4(
|
|
1352
|
+
if (existsSync4(join5(d, ".git"))) return d;
|
|
1300
1353
|
if (d === dirname4(d)) return null;
|
|
1301
1354
|
}
|
|
1302
1355
|
}
|
|
@@ -1305,12 +1358,12 @@ function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
|
1305
1358
|
if (cwd && existsSync4(cwd)) {
|
|
1306
1359
|
const root = gitRoot(cwd) ?? cwd;
|
|
1307
1360
|
for (let d = resolve4(cwd); ; d = dirname4(d)) {
|
|
1308
|
-
for (const name of INSTRUCTION_FILES) out.push(
|
|
1361
|
+
for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
|
|
1309
1362
|
if (d === root || d === dirname4(d)) break;
|
|
1310
1363
|
}
|
|
1311
1364
|
}
|
|
1312
|
-
if (harness2 === "claude_code") out.push(
|
|
1313
|
-
if (harness2 === "codex") out.push(
|
|
1365
|
+
if (harness2 === "claude_code") out.push(join5(process.env.CLAUDE_CONFIG_DIR ?? join5(home, ".claude"), "CLAUDE.md"));
|
|
1366
|
+
if (harness2 === "codex") out.push(join5(process.env.CODEX_HOME ?? join5(home, ".codex"), "AGENTS.md"));
|
|
1314
1367
|
out.push(...sharedInstructionPaths(home));
|
|
1315
1368
|
return out.filter((path) => {
|
|
1316
1369
|
try {
|
|
@@ -1432,6 +1485,325 @@ function collectSessionFiles(records, opts) {
|
|
|
1432
1485
|
return { lines, bytes, attached, leftOut, setup };
|
|
1433
1486
|
}
|
|
1434
1487
|
|
|
1488
|
+
// src/import-environment.ts
|
|
1489
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1490
|
+
import { createHash as createHash2 } from "crypto";
|
|
1491
|
+
import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync7, readSync as readSync2, rmSync as rmSync2 } from "fs";
|
|
1492
|
+
import { tmpdir } from "os";
|
|
1493
|
+
import { join as join6 } from "path";
|
|
1494
|
+
import { isUtf8 as isUtf82 } from "buffer";
|
|
1495
|
+
import { createGzip } from "zlib";
|
|
1496
|
+
|
|
1497
|
+
// ../../packages/spec/src/import-layout.ts
|
|
1498
|
+
import { gzipSync } from "zlib";
|
|
1499
|
+
function folderName(workspace2, sourceCwd) {
|
|
1500
|
+
const raw = workspace2?.project || (sourceCwd ? sourceCwd.split("/").filter(Boolean).pop() : "") || "";
|
|
1501
|
+
const cleaned = raw.replace(/[^A-Za-z0-9._ -]/g, "-").replace(/^[^A-Za-z0-9_]+/, "").replace(/\.\.+/g, ".").slice(0, 80).trim();
|
|
1502
|
+
return cleaned || "imported-session";
|
|
1503
|
+
}
|
|
1504
|
+
function envName(server, field) {
|
|
1505
|
+
return `${server}_${field}`.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
1506
|
+
}
|
|
1507
|
+
function ownServer(server, platform) {
|
|
1508
|
+
try {
|
|
1509
|
+
return server.transport !== "stdio" && platform.ownHost.test(new URL(server.url ?? "").hostname);
|
|
1510
|
+
} catch {
|
|
1511
|
+
return false;
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
function mcpJson(servers, platform) {
|
|
1515
|
+
const entries = servers.filter((server) => !ownServer(server, platform)).map((server) => {
|
|
1516
|
+
if (server.transport === "stdio") {
|
|
1517
|
+
return [server.name, {
|
|
1518
|
+
command: server.command,
|
|
1519
|
+
...server.args?.length ? { args: server.args } : {},
|
|
1520
|
+
...server.env?.length ? { env: Object.fromEntries(server.env.map((name) => [name, `\${${name}:-}`])) } : {}
|
|
1521
|
+
}];
|
|
1522
|
+
}
|
|
1523
|
+
const query = server.query?.length ? `?${server.query.map((name) => `${encodeURIComponent(name)}=\${${envName(server.name, name)}:-}`).join("&")}` : "";
|
|
1524
|
+
return [server.name, {
|
|
1525
|
+
type: server.transport,
|
|
1526
|
+
url: `${server.url}${query}`,
|
|
1527
|
+
...server.headers?.length ? { headers: Object.fromEntries(server.headers.map((name) => [name, `\${${envName(server.name, name)}:-}`])) } : {}
|
|
1528
|
+
}];
|
|
1529
|
+
});
|
|
1530
|
+
return JSON.stringify({ mcpServers: Object.fromEntries(entries) }, null, 2) + "\n";
|
|
1531
|
+
}
|
|
1532
|
+
function bullet(items, none) {
|
|
1533
|
+
return items.length ? items.map((item) => `- ${item}`).join("\n") : `- ${none}`;
|
|
1534
|
+
}
|
|
1535
|
+
function readme(folder, workspace2, counts, skills2, mcpWritten, platform) {
|
|
1536
|
+
const git2 = workspace2?.git;
|
|
1537
|
+
const servers = workspace2?.mcp_servers ?? [];
|
|
1538
|
+
const missingSkills = (workspace2?.skills ?? []).filter((skill) => !skill.found).map((skill) => skill.name);
|
|
1539
|
+
const secrets2 = servers.filter((server) => !ownServer(server, platform)).flatMap((server) => [
|
|
1540
|
+
...server.env ?? [],
|
|
1541
|
+
...(server.headers ?? []).map((name) => envName(server.name, name)),
|
|
1542
|
+
...(server.query ?? []).map((name) => envName(server.name, name))
|
|
1543
|
+
]);
|
|
1544
|
+
const where = workspace2?.cwd ? ` (${workspace2.cwd})` : "";
|
|
1545
|
+
return [
|
|
1546
|
+
"# Imported working copy",
|
|
1547
|
+
"",
|
|
1548
|
+
`This folder (/workspace/${folder}) holds what the imported conversation worked with on the person's computer.`,
|
|
1549
|
+
"It is historical context: verify a path before acting on it, and follow the person's current message.",
|
|
1550
|
+
"",
|
|
1551
|
+
"## Files",
|
|
1552
|
+
counts.wholeFolder ? `- The whole working directory${where}, ${counts.project} files at the same relative paths: its tracked files and new files git does not ignore (every file when it is not a repository), without version-control data, dependency and build output, or credential files.` : `- ${counts.project} files from the original working directory${where}, at the same relative paths: the files the conversation read, wrote or edited and its instruction files. The rest of the working tree did not travel${workspace2?.tree?.length ? `; its full file list is in ${platform.notesDir}/workspace.json (\`tree\`)` : ""}.`,
|
|
1553
|
+
...counts.outside ? [`- ${counts.outside} files from elsewhere on that computer (shared instructions such as ~/AGENTS.md, and files outside the working directory) under ${platform.notesDir}/files/, with ~ as home/.`] : [],
|
|
1554
|
+
...counts.attachments ? [`- ${counts.attachments} attachments of the conversation under ${platform.notesDir}/attachments/.`] : [],
|
|
1555
|
+
"- Text was redacted on that computer before upload: a masked value (\u25CF\u25CF\u25CF\u25CF\u25CF) is a secret that was removed, not the real content.",
|
|
1556
|
+
"",
|
|
1557
|
+
"## Repository",
|
|
1558
|
+
...git2 ? [
|
|
1559
|
+
`- Remote: ${git2.remote ?? "none"}`,
|
|
1560
|
+
`- Branch: ${git2.branch ?? "detached"} at commit ${git2.commit ?? "unknown"}${git2.ahead ? ` (${git2.ahead} commits not pushed)` : ""}`,
|
|
1561
|
+
...git2.dirty?.length ? [`- Uncommitted changes in ${git2.dirty.length} paths${counts.wholeFolder ? " are already in these files" : ""}; the change against that commit is ${platform.notesDir}/uncommitted.patch (apply with \`git apply\` on a checkout of that commit).`] : ["- No uncommitted changes."],
|
|
1562
|
+
counts.wholeFolder ? "- This folder's own git history starts with this copy; the project's history is on the remote." : `- To get the whole repository, clone the remote at that commit (connect GitHub in ${platform.name} if it is private), apply the patch, then use the files here.`
|
|
1563
|
+
] : ["- The working directory was not a git repository."],
|
|
1564
|
+
"",
|
|
1565
|
+
"## Skills",
|
|
1566
|
+
bullet(skills2.map((name) => `${name}: .claude/skills/${name}/SKILL.md (also .agents/skills/${name}/)`), "No skills were loaded."),
|
|
1567
|
+
...missingSkills.length ? ["", `Loaded but no longer on that computer, so not carried: ${missingSkills.join(", ")}.`] : [],
|
|
1568
|
+
"",
|
|
1569
|
+
"## Tool servers (MCP)",
|
|
1570
|
+
bullet(servers.map((server) => `${server.name} (${server.transport}${server.scope ? `, ${server.scope}` : ""}): ${server.transport === "stdio" ? [server.command, ...server.args ?? []].join(" ") : server.url}`), "None were configured."),
|
|
1571
|
+
...mcpWritten ? ["", `They are written to .mcp.json in this folder with every key as \${NAME:-}, except ${platform.name}'s own server, whose tools this session already has. Their values never leave the person's computer${secrets2.length ? `; these must be supplied to use them: ${[...new Set(secrets2)].join(", ")}` : ""}. A stdio server runs only if its command is installed here, and Claude Code connects a project server only once it is approved.`] : [],
|
|
1572
|
+
""
|
|
1573
|
+
].join("\n");
|
|
1574
|
+
}
|
|
1575
|
+
function parseWorkspace(file) {
|
|
1576
|
+
if (!file) return null;
|
|
1577
|
+
try {
|
|
1578
|
+
const value = JSON.parse(file.data.toString("utf8"));
|
|
1579
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1580
|
+
} catch {
|
|
1581
|
+
return null;
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
function importLayout(files2, sourceCwd, platform, wholeFolder) {
|
|
1585
|
+
if (!files2.length && !wholeFolder) return null;
|
|
1586
|
+
const workspace2 = parseWorkspace(files2.find((file) => file.name === "agentsky/workspace.json"));
|
|
1587
|
+
const folder = folderName(workspace2, sourceCwd);
|
|
1588
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1589
|
+
const skills2 = /* @__PURE__ */ new Set();
|
|
1590
|
+
const counts = { project: wholeFolder?.files ?? 0, outside: 0, attachments: 0, wholeFolder: Boolean(wholeFolder) };
|
|
1591
|
+
for (const file of files2) {
|
|
1592
|
+
const [kind, ...rest] = file.name.split("/");
|
|
1593
|
+
const path = rest.join("/");
|
|
1594
|
+
if (kind === "workspace") {
|
|
1595
|
+
if (wholeFolder) continue;
|
|
1596
|
+
entries.set(path, file.data);
|
|
1597
|
+
counts.project += 1;
|
|
1598
|
+
} else if (kind === "files") {
|
|
1599
|
+
entries.set(`${platform.notesDir}/files/${path.replace(/^~\//, "home/")}`, file.data);
|
|
1600
|
+
counts.outside += 1;
|
|
1601
|
+
} else if (kind === "imported-attachments") {
|
|
1602
|
+
entries.set(`${platform.notesDir}/attachments/${path}`, file.data);
|
|
1603
|
+
counts.attachments += 1;
|
|
1604
|
+
} else if (kind === "agentsky") entries.set(`${platform.notesDir}/${path}`, file.data);
|
|
1605
|
+
else if (kind === "skills" && rest.length > 1) {
|
|
1606
|
+
const skill = rest[0].split(":").pop();
|
|
1607
|
+
const inside = rest.slice(1).join("/");
|
|
1608
|
+
skills2.add(skill);
|
|
1609
|
+
entries.set(`.claude/skills/${skill}/${inside}`, file.data);
|
|
1610
|
+
entries.set(`.agents/skills/${skill}/${inside}`, file.data);
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
1613
|
+
const servers = (workspace2?.mcp_servers ?? []).filter((server) => server && typeof server.name === "string" && (server.transport === "stdio" ? typeof server.command === "string" : typeof server.url === "string"));
|
|
1614
|
+
const mcpWritten = servers.length > 0 && !entries.has(".mcp.json") && !wholeFolder?.paths.has(".mcp.json");
|
|
1615
|
+
if (mcpWritten) entries.set(".mcp.json", Buffer.from(mcpJson(servers, platform)));
|
|
1616
|
+
entries.set(`${platform.notesDir}/README.md`, Buffer.from(readme(folder, workspace2 ? { ...workspace2, mcp_servers: servers } : null, counts, [...skills2], mcpWritten, platform)));
|
|
1617
|
+
if (wholeFolder) for (const path of wholeFolder.paths) entries.delete(path);
|
|
1618
|
+
return { folder, entries, skills: [...skills2], mcpServers: servers.map((server) => server.name) };
|
|
1619
|
+
}
|
|
1620
|
+
var TAR_BLOCK = 512;
|
|
1621
|
+
var TAR_END = Buffer.alloc(TAR_BLOCK * 2);
|
|
1622
|
+
function header(name, size, type) {
|
|
1623
|
+
const block = Buffer.alloc(TAR_BLOCK);
|
|
1624
|
+
block.write(name, 0, 100, "utf8");
|
|
1625
|
+
block.write("0000644\0", 100);
|
|
1626
|
+
block.write("0000000\0", 108);
|
|
1627
|
+
block.write("0000000\0", 116);
|
|
1628
|
+
block.write(size.toString(8).padStart(11, "0") + "\0", 124);
|
|
1629
|
+
block.write("00000000000\0", 136);
|
|
1630
|
+
block.write(" ", 148);
|
|
1631
|
+
block.write(type, 156);
|
|
1632
|
+
block.write("ustar\x0000", 257);
|
|
1633
|
+
let sum = 0;
|
|
1634
|
+
for (const byte of block) sum += byte;
|
|
1635
|
+
block.write(sum.toString(8).padStart(6, "0") + "\0 ", 148);
|
|
1636
|
+
return block;
|
|
1637
|
+
}
|
|
1638
|
+
function paxRecord(key, value) {
|
|
1639
|
+
const body = ` ${key}=${value}
|
|
1640
|
+
`;
|
|
1641
|
+
let length = Buffer.byteLength(body);
|
|
1642
|
+
for (; ; ) {
|
|
1643
|
+
const total = String(length).length + length;
|
|
1644
|
+
if (String(total).length + length === total) return Buffer.from(`${total}${body}`);
|
|
1645
|
+
length += 1;
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
function tarPadding(size) {
|
|
1649
|
+
const rest = size % TAR_BLOCK;
|
|
1650
|
+
return Buffer.alloc(rest ? TAR_BLOCK - rest : 0);
|
|
1651
|
+
}
|
|
1652
|
+
function tarHeader(path, size) {
|
|
1653
|
+
if (Buffer.byteLength(path) <= 100) return header(path, size, "0");
|
|
1654
|
+
const pax = paxRecord("path", path);
|
|
1655
|
+
return Buffer.concat([header("PaxHeader", pax.length, "x"), pax, tarPadding(pax.length), header(path, size, "0")]);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/import-environment.ts
|
|
1659
|
+
var IMPORT_PLATFORM = { name: "AgentSky", ownHost: /(^|\.)(agentsky\.dev|asteroidbelt\.ai)$/i, notesDir: ".agentsky-import" };
|
|
1660
|
+
var MAX_ENVIRONMENT_FILES = 2e5;
|
|
1661
|
+
var MAX_ENVIRONMENT_BYTES = 2 * 1024 ** 3;
|
|
1662
|
+
var MAX_REDACTED_TEXT_BYTES = 10 * 1024 * 1024;
|
|
1663
|
+
var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules", ".venv", "venv", "__pycache__", ".next", "dist", "build", "target", ".DS_Store", ".cache", ".turbo"]);
|
|
1664
|
+
var SECRET_FILE2 = /(^|\/)(\.env(\..*)?|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\.?credentials(\.json)?|auth\.json|\.claude\.json|id_(rsa|ed25519|ecdsa)[^/]*|.*\.(pem|key|p12|pfx|keystore|jks|kdbx))$|(^|\/)\.ssh\/|(^|\/)\.aws\/|(^|\/)\.gnupg\/|(^|\/)\.docker\/config\.json$/i;
|
|
1665
|
+
function listFiles(cwd) {
|
|
1666
|
+
const git2 = spawnSync2("git", ["-C", cwd, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8", maxBuffer: 512 * 1024 * 1024 });
|
|
1667
|
+
if (git2.status === 0) return git2.stdout.split("\0").filter(Boolean).filter((path) => !path.split("/").some((part) => part === ".git"));
|
|
1668
|
+
const out = [];
|
|
1669
|
+
const walk = (dir, prefix) => {
|
|
1670
|
+
let entries;
|
|
1671
|
+
try {
|
|
1672
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1673
|
+
} catch {
|
|
1674
|
+
return;
|
|
1675
|
+
}
|
|
1676
|
+
for (const entry2 of entries) {
|
|
1677
|
+
if (SKIPPED.has(entry2.name)) continue;
|
|
1678
|
+
const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
1679
|
+
if (entry2.isDirectory()) walk(join6(dir, entry2.name), rel);
|
|
1680
|
+
else out.push(rel);
|
|
1681
|
+
if (out.length > MAX_ENVIRONMENT_FILES) return;
|
|
1682
|
+
}
|
|
1683
|
+
};
|
|
1684
|
+
walk(cwd, "");
|
|
1685
|
+
return out;
|
|
1686
|
+
}
|
|
1687
|
+
function textOf(path, size) {
|
|
1688
|
+
if (size > MAX_REDACTED_TEXT_BYTES) return null;
|
|
1689
|
+
const data = readFileSync7(path);
|
|
1690
|
+
if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
|
|
1691
|
+
return data;
|
|
1692
|
+
}
|
|
1693
|
+
async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
1694
|
+
const listed = listFiles(cwd);
|
|
1695
|
+
if (listed.length > MAX_ENVIRONMENT_FILES) throw new Error(`The working folder has more than ${MAX_ENVIRONMENT_FILES} files; import it with --no-environment.`);
|
|
1696
|
+
const scratch = mkdtempSync(join6(tmpdir(), "sky-environment-"));
|
|
1697
|
+
const path = join6(scratch, "environment.tar.gz");
|
|
1698
|
+
const leftOut = [];
|
|
1699
|
+
const written = /* @__PURE__ */ new Set();
|
|
1700
|
+
const hash = createHash2("sha256");
|
|
1701
|
+
let bytes = 0;
|
|
1702
|
+
let redacted2 = 0;
|
|
1703
|
+
const gzip = createGzip({ level: 6 });
|
|
1704
|
+
const sink = createWriteStream(path);
|
|
1705
|
+
gzip.on("data", (chunk) => {
|
|
1706
|
+
bytes += chunk.length;
|
|
1707
|
+
hash.update(chunk);
|
|
1708
|
+
});
|
|
1709
|
+
gzip.pipe(sink);
|
|
1710
|
+
const done = new Promise((resolve10, reject) => {
|
|
1711
|
+
sink.on("finish", () => resolve10());
|
|
1712
|
+
sink.on("error", reject);
|
|
1713
|
+
gzip.on("error", reject);
|
|
1714
|
+
});
|
|
1715
|
+
const write = (chunk) => new Promise((resolve10, reject) => {
|
|
1716
|
+
if (bytes > MAX_ENVIRONMENT_BYTES) {
|
|
1717
|
+
reject(new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`));
|
|
1718
|
+
return;
|
|
1719
|
+
}
|
|
1720
|
+
if (gzip.write(chunk)) resolve10();
|
|
1721
|
+
else gzip.once("drain", resolve10);
|
|
1722
|
+
});
|
|
1723
|
+
try {
|
|
1724
|
+
for (const rel of listed) {
|
|
1725
|
+
const full = join6(cwd, ...rel.split("/"));
|
|
1726
|
+
if (SECRET_FILE2.test(rel)) {
|
|
1727
|
+
leftOut.push({ path: rel, reason: "credential file" });
|
|
1728
|
+
continue;
|
|
1729
|
+
}
|
|
1730
|
+
let stat;
|
|
1731
|
+
try {
|
|
1732
|
+
stat = lstatSync(full);
|
|
1733
|
+
} catch {
|
|
1734
|
+
continue;
|
|
1735
|
+
}
|
|
1736
|
+
if (!stat.isFile()) {
|
|
1737
|
+
leftOut.push({ path: rel, reason: "not a regular file" });
|
|
1738
|
+
continue;
|
|
1739
|
+
}
|
|
1740
|
+
const text = textOf(full, stat.size);
|
|
1741
|
+
if (text) {
|
|
1742
|
+
const masked = redactSecretValues(text.toString("utf8"));
|
|
1743
|
+
if (Object.keys(masked.hits).length) redacted2 += 1;
|
|
1744
|
+
const data = Buffer.from(masked.text, "utf8");
|
|
1745
|
+
await write(tarHeader(rel, data.length));
|
|
1746
|
+
await write(data);
|
|
1747
|
+
await write(tarPadding(data.length));
|
|
1748
|
+
} else {
|
|
1749
|
+
await write(tarHeader(rel, stat.size));
|
|
1750
|
+
const fd = openSync2(full, "r");
|
|
1751
|
+
try {
|
|
1752
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
1753
|
+
let left = stat.size;
|
|
1754
|
+
for (let offset = 0; left > 0; ) {
|
|
1755
|
+
const read = readSync2(fd, chunk, 0, Math.min(chunk.length, left), offset);
|
|
1756
|
+
if (read <= 0) throw new Error(`${rel} changed while it was packed; import again.`);
|
|
1757
|
+
await write(Buffer.from(chunk.subarray(0, read)));
|
|
1758
|
+
offset += read;
|
|
1759
|
+
left -= read;
|
|
1760
|
+
}
|
|
1761
|
+
} finally {
|
|
1762
|
+
closeSync2(fd);
|
|
1763
|
+
}
|
|
1764
|
+
await write(tarPadding(stat.size));
|
|
1765
|
+
}
|
|
1766
|
+
written.add(rel);
|
|
1767
|
+
}
|
|
1768
|
+
const layout = importLayout(resources, cwd, platform, { files: written.size, paths: written });
|
|
1769
|
+
for (const [entry2, data] of layout?.entries ?? []) {
|
|
1770
|
+
await write(tarHeader(entry2, data.length));
|
|
1771
|
+
await write(data);
|
|
1772
|
+
await write(tarPadding(data.length));
|
|
1773
|
+
}
|
|
1774
|
+
await write(TAR_END);
|
|
1775
|
+
gzip.end();
|
|
1776
|
+
await done;
|
|
1777
|
+
if (bytes > MAX_ENVIRONMENT_BYTES) throw new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`);
|
|
1778
|
+
return {
|
|
1779
|
+
path,
|
|
1780
|
+
folder: layout?.folder ?? "imported-session",
|
|
1781
|
+
bytes,
|
|
1782
|
+
sha256: hash.digest("hex"),
|
|
1783
|
+
files: written.size + (layout?.entries.size ?? 0),
|
|
1784
|
+
redacted: redacted2,
|
|
1785
|
+
leftOut,
|
|
1786
|
+
dispose: () => rmSync2(scratch, { recursive: true, force: true })
|
|
1787
|
+
};
|
|
1788
|
+
} catch (error) {
|
|
1789
|
+
gzip.destroy();
|
|
1790
|
+
sink.destroy();
|
|
1791
|
+
rmSync2(scratch, { recursive: true, force: true });
|
|
1792
|
+
throw error;
|
|
1793
|
+
}
|
|
1794
|
+
}
|
|
1795
|
+
function resourcesOf(lines) {
|
|
1796
|
+
return lines.flatMap((line) => {
|
|
1797
|
+
try {
|
|
1798
|
+
const record = JSON.parse(line);
|
|
1799
|
+
if (record.type !== "import.resource" || typeof record.name !== "string" || typeof record.media_type !== "string" || typeof record.data !== "string") return [];
|
|
1800
|
+
return [{ name: record.name, mediaType: record.media_type, data: Buffer.from(record.data, "base64") }];
|
|
1801
|
+
} catch {
|
|
1802
|
+
return [];
|
|
1803
|
+
}
|
|
1804
|
+
});
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1435
1807
|
// src/import-local.ts
|
|
1436
1808
|
function redactLine2(raw) {
|
|
1437
1809
|
return redactLine(raw).line;
|
|
@@ -1444,12 +1816,12 @@ function decodeWorkspace(encoded) {
|
|
|
1444
1816
|
}
|
|
1445
1817
|
}
|
|
1446
1818
|
function readPrefix(filePath) {
|
|
1447
|
-
const fd =
|
|
1819
|
+
const fd = openSync3(filePath, "r");
|
|
1448
1820
|
try {
|
|
1449
1821
|
const bytes = Buffer.alloc(256 * 1024);
|
|
1450
|
-
return bytes.subarray(0,
|
|
1822
|
+
return bytes.subarray(0, readSync3(fd, bytes, 0, bytes.length, 0)).toString("utf8");
|
|
1451
1823
|
} finally {
|
|
1452
|
-
|
|
1824
|
+
closeSync3(fd);
|
|
1453
1825
|
}
|
|
1454
1826
|
}
|
|
1455
1827
|
function sniffTitle(filePath) {
|
|
@@ -1504,16 +1876,16 @@ function sniffTitle(filePath) {
|
|
|
1504
1876
|
}
|
|
1505
1877
|
}
|
|
1506
1878
|
function listClaudeCodeSessions(limit) {
|
|
1507
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
1508
|
-
const projectsDir =
|
|
1879
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir4(), ".claude");
|
|
1880
|
+
const projectsDir = join7(root, "projects");
|
|
1509
1881
|
if (!existsSync5(projectsDir)) return [];
|
|
1510
1882
|
const sessions = [];
|
|
1511
1883
|
try {
|
|
1512
|
-
for (const encoded of
|
|
1513
|
-
const projectDir =
|
|
1884
|
+
for (const encoded of readdirSync4(projectsDir)) {
|
|
1885
|
+
const projectDir = join7(projectsDir, encoded);
|
|
1514
1886
|
let entries;
|
|
1515
1887
|
try {
|
|
1516
|
-
entries =
|
|
1888
|
+
entries = readdirSync4(projectDir);
|
|
1517
1889
|
} catch {
|
|
1518
1890
|
continue;
|
|
1519
1891
|
}
|
|
@@ -1521,7 +1893,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
1521
1893
|
for (const entry2 of entries) {
|
|
1522
1894
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
1523
1895
|
const sessionId = entry2.slice(0, -6);
|
|
1524
|
-
const mainFile =
|
|
1896
|
+
const mainFile = join7(projectDir, entry2);
|
|
1525
1897
|
let stat;
|
|
1526
1898
|
try {
|
|
1527
1899
|
stat = statSync5(mainFile);
|
|
@@ -1531,12 +1903,12 @@ function listClaudeCodeSessions(limit) {
|
|
|
1531
1903
|
if (!stat.isFile()) continue;
|
|
1532
1904
|
let discoveryError;
|
|
1533
1905
|
const subagentFiles = [];
|
|
1534
|
-
const subagentDir =
|
|
1906
|
+
const subagentDir = join7(projectDir, sessionId, "subagents");
|
|
1535
1907
|
if (existsSync5(subagentDir)) {
|
|
1536
1908
|
try {
|
|
1537
|
-
const subFiles =
|
|
1909
|
+
const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1538
1910
|
for (const f of subFiles) {
|
|
1539
|
-
subagentFiles.push(
|
|
1911
|
+
subagentFiles.push(join7(subagentDir, f));
|
|
1540
1912
|
}
|
|
1541
1913
|
} catch {
|
|
1542
1914
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -1558,17 +1930,17 @@ function listClaudeCodeSessions(limit) {
|
|
|
1558
1930
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1559
1931
|
}
|
|
1560
1932
|
function listCodexSessions(limit) {
|
|
1561
|
-
const sessionsDir =
|
|
1933
|
+
const sessionsDir = join7(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join7(homedir4(), ".codex"), "sessions");
|
|
1562
1934
|
const records = [];
|
|
1563
1935
|
function walk(dir) {
|
|
1564
1936
|
let entries;
|
|
1565
1937
|
try {
|
|
1566
|
-
entries =
|
|
1938
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
1567
1939
|
} catch {
|
|
1568
1940
|
return;
|
|
1569
1941
|
}
|
|
1570
1942
|
for (const entry2 of entries) {
|
|
1571
|
-
const full =
|
|
1943
|
+
const full = join7(dir, entry2.name);
|
|
1572
1944
|
if (entry2.isDirectory()) {
|
|
1573
1945
|
walk(full);
|
|
1574
1946
|
continue;
|
|
@@ -1655,7 +2027,7 @@ async function mintTrace(base, harness2) {
|
|
|
1655
2027
|
function loadSummaryFile(summaryPath) {
|
|
1656
2028
|
let raw;
|
|
1657
2029
|
try {
|
|
1658
|
-
raw =
|
|
2030
|
+
raw = readFileSync8(summaryPath, "utf8");
|
|
1659
2031
|
} catch {
|
|
1660
2032
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1661
2033
|
}
|
|
@@ -1687,7 +2059,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1687
2059
|
let inputBytes = 0;
|
|
1688
2060
|
let outputBytes = 0;
|
|
1689
2061
|
const lines = [];
|
|
1690
|
-
const digest =
|
|
2062
|
+
const digest = createHash3("sha256");
|
|
1691
2063
|
let nativeId;
|
|
1692
2064
|
let redactionCount = 0;
|
|
1693
2065
|
let encryptedReasoningCount = 0;
|
|
@@ -1698,7 +2070,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1698
2070
|
for (const path of session2.files) {
|
|
1699
2071
|
const stat = statSync5(path);
|
|
1700
2072
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
1701
|
-
const text =
|
|
2073
|
+
const text = readFileSync8(path, "utf8");
|
|
1702
2074
|
for (const raw of text.split("\n")) {
|
|
1703
2075
|
if (!raw.trim()) continue;
|
|
1704
2076
|
let record;
|
|
@@ -1776,7 +2148,8 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1776
2148
|
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1777
2149
|
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1778
2150
|
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1779
|
-
|
|
2151
|
+
const cwd = meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : "");
|
|
2152
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup, cwd, resourceLines: files2.lines };
|
|
1780
2153
|
}
|
|
1781
2154
|
async function importSession(opts) {
|
|
1782
2155
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1785,7 +2158,7 @@ async function importSession(opts) {
|
|
|
1785
2158
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1786
2159
|
}
|
|
1787
2160
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1788
|
-
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
2161
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup, cwd, resourceLines } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1789
2162
|
let traceId;
|
|
1790
2163
|
let key;
|
|
1791
2164
|
if (opts.importKey && opts.grantTraceId) {
|
|
@@ -1809,6 +2182,7 @@ async function importSession(opts) {
|
|
|
1809
2182
|
}
|
|
1810
2183
|
const result = await response.json();
|
|
1811
2184
|
const finalTraceId = result.traceId ?? traceId;
|
|
2185
|
+
const environment = opts.files === false || opts.environment === false || !cwd || !existsSync5(cwd) ? null : await uploadEnvironment(base, key, finalTraceId, cwd, resourceLines, opts.onProgress).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
|
|
1812
2186
|
return {
|
|
1813
2187
|
traceId: finalTraceId,
|
|
1814
2188
|
url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
|
|
@@ -1820,9 +2194,42 @@ async function importSession(opts) {
|
|
|
1820
2194
|
gaps,
|
|
1821
2195
|
attachedFiles,
|
|
1822
2196
|
leftOutFiles,
|
|
1823
|
-
setup
|
|
2197
|
+
setup,
|
|
2198
|
+
environment
|
|
1824
2199
|
};
|
|
1825
2200
|
}
|
|
2201
|
+
async function grantCall(base, key, path, body) {
|
|
2202
|
+
const response = await fetch(`${base}${path}`, {
|
|
2203
|
+
method: "POST",
|
|
2204
|
+
redirect: "error",
|
|
2205
|
+
signal: AbortSignal.timeout(3e5),
|
|
2206
|
+
headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "user-agent": USER_AGENT },
|
|
2207
|
+
body: JSON.stringify(body)
|
|
2208
|
+
});
|
|
2209
|
+
const answer = await response.json().catch(() => null);
|
|
2210
|
+
if (!response.ok) throw new Error(typeof answer?.error === "string" ? answer.error : answer?.error?.message ?? `The environment upload failed (${response.status}).`);
|
|
2211
|
+
return answer;
|
|
2212
|
+
}
|
|
2213
|
+
async function uploadEnvironment(base, key, traceId, cwd, resourceLines, onProgress) {
|
|
2214
|
+
onProgress?.("Packing the working folder (secrets redacted locally)\u2026");
|
|
2215
|
+
const packed = await packEnvironment(cwd, resourcesOf(resourceLines));
|
|
2216
|
+
try {
|
|
2217
|
+
const path = `/api/traces/${encodeURIComponent(traceId)}/environment`;
|
|
2218
|
+
const { upload } = await grantCall(base, key, path, { name: packed.folder, size: packed.bytes, sha256: packed.sha256, files: packed.files });
|
|
2219
|
+
let shown = -1;
|
|
2220
|
+
await transfer(upload, packed.path, (sent, total) => {
|
|
2221
|
+
const percent = Math.floor(sent / total * 100);
|
|
2222
|
+
if (percent >= shown + 10 || sent === total) {
|
|
2223
|
+
shown = percent;
|
|
2224
|
+
onProgress?.(`Uploading the working folder\u2026 ${percent}% of ${(total / 1024 / 1024).toFixed(1)} MiB`);
|
|
2225
|
+
}
|
|
2226
|
+
});
|
|
2227
|
+
await grantCall(base, key, `${path}/complete`, {});
|
|
2228
|
+
return { folder: packed.folder, files: packed.files, bytes: packed.bytes, redacted: packed.redacted, leftOut: packed.leftOut.length };
|
|
2229
|
+
} finally {
|
|
2230
|
+
packed.dispose();
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
1826
2233
|
|
|
1827
2234
|
// src/channels.ts
|
|
1828
2235
|
function connectionLine(c) {
|
|
@@ -1876,7 +2283,7 @@ async function send(threadId, text) {
|
|
|
1876
2283
|
import { createServer as createServer2 } from "http";
|
|
1877
2284
|
|
|
1878
2285
|
// src/login.ts
|
|
1879
|
-
import { createHash as
|
|
2286
|
+
import { createHash as createHash4, randomBytes } from "crypto";
|
|
1880
2287
|
import { createServer } from "http";
|
|
1881
2288
|
import { hostname } from "os";
|
|
1882
2289
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -1892,7 +2299,7 @@ function openBrowser2(url) {
|
|
|
1892
2299
|
}
|
|
1893
2300
|
async function login(opts) {
|
|
1894
2301
|
const verifier = b64url(randomBytes(32));
|
|
1895
|
-
const challenge = b64url(
|
|
2302
|
+
const challenge = b64url(createHash4("sha256").update(verifier).digest());
|
|
1896
2303
|
const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
|
|
1897
2304
|
saveConfig({
|
|
1898
2305
|
...loadConfig(),
|
|
@@ -2083,15 +2490,15 @@ async function triggerRequest(id, resource, change) {
|
|
|
2083
2490
|
}
|
|
2084
2491
|
|
|
2085
2492
|
// src/session-workspace.ts
|
|
2086
|
-
import { spawnSync as
|
|
2087
|
-
import { createHash as
|
|
2088
|
-
import { readdirSync as
|
|
2089
|
-
import { basename as basename6, join as
|
|
2493
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
2494
|
+
import { createHash as createHash5, randomUUID } from "crypto";
|
|
2495
|
+
import { readdirSync as readdirSync5, statSync as statSync6 } from "fs";
|
|
2496
|
+
import { basename as basename6, join as join8, relative as relative3, resolve as resolve5 } from "path";
|
|
2090
2497
|
var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
|
|
2091
2498
|
var PART_SIZE = 8 * 1024 * 1024;
|
|
2092
|
-
var
|
|
2499
|
+
var SKIPPED2 = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2093
2500
|
var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
|
|
2094
|
-
async function
|
|
2501
|
+
async function listFiles2(id, path, hidden) {
|
|
2095
2502
|
const query = new URLSearchParams();
|
|
2096
2503
|
if (path) query.set("path", path);
|
|
2097
2504
|
if (hidden) query.set("hidden", "true");
|
|
@@ -2117,16 +2524,16 @@ async function workspacePatch(id, repo) {
|
|
|
2117
2524
|
return { meta, patch: Buffer.from(patch, "base64") };
|
|
2118
2525
|
}
|
|
2119
2526
|
function folderFiles(dir) {
|
|
2120
|
-
const git2 =
|
|
2121
|
-
const top =
|
|
2527
|
+
const git2 = spawnSync3("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
|
|
2528
|
+
const top = spawnSync3("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
|
|
2122
2529
|
if (git2.status === 0 && top.status === 0) {
|
|
2123
|
-
return git2.stdout.split("\0").filter((path) => path && statSync6(
|
|
2530
|
+
return git2.stdout.split("\0").filter((path) => path && statSync6(join8(dir, path), { throwIfNoEntry: false })?.isFile());
|
|
2124
2531
|
}
|
|
2125
2532
|
const files2 = [];
|
|
2126
2533
|
const walk = (at) => {
|
|
2127
|
-
for (const entry2 of
|
|
2128
|
-
if (
|
|
2129
|
-
const full =
|
|
2534
|
+
for (const entry2 of readdirSync5(at, { withFileTypes: true })) {
|
|
2535
|
+
if (SKIPPED2.has(entry2.name)) continue;
|
|
2536
|
+
const full = join8(at, entry2.name);
|
|
2130
2537
|
if (entry2.isDirectory()) walk(full);
|
|
2131
2538
|
else if (entry2.isFile()) files2.push(relative3(dir, full));
|
|
2132
2539
|
}
|
|
@@ -2135,7 +2542,7 @@ function folderFiles(dir) {
|
|
|
2135
2542
|
return files2;
|
|
2136
2543
|
}
|
|
2137
2544
|
function folderArchive(dir, files2) {
|
|
2138
|
-
const tar =
|
|
2545
|
+
const tar = spawnSync3("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
|
|
2139
2546
|
input: files2.join("\n"),
|
|
2140
2547
|
env: { ...process.env, COPYFILE_DISABLE: "1" },
|
|
2141
2548
|
maxBuffer: 2 * 1024 * 1024 * 1024
|
|
@@ -2151,7 +2558,7 @@ async function importFolder(id, dir, opts = {}) {
|
|
|
2151
2558
|
const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
|
|
2152
2559
|
return res.json();
|
|
2153
2560
|
}
|
|
2154
|
-
const sha256 =
|
|
2561
|
+
const sha256 = createHash5("sha256").update(archive).digest("hex");
|
|
2155
2562
|
let upload = await api("POST", `${workspacePath(id)}/imports`, { name, size: archive.length, sha256, part_size: PART_SIZE });
|
|
2156
2563
|
const uploadPath = `${workspacePath(id)}/imports/${encodeURIComponent(upload.id)}`;
|
|
2157
2564
|
for (let part = 0; part < upload.parts; part += 1) {
|
|
@@ -2184,6 +2591,13 @@ function accessPatch(opts) {
|
|
|
2184
2591
|
...opts.removeViewer?.length ? { remove_viewers: opts.removeViewer } : {}
|
|
2185
2592
|
};
|
|
2186
2593
|
}
|
|
2594
|
+
function engineChange(model, thinking) {
|
|
2595
|
+
if (!model && !thinking) return null;
|
|
2596
|
+
return {
|
|
2597
|
+
...model ? { llm: model } : {},
|
|
2598
|
+
...thinking ? { reasoningEffort: thinking === "default" ? null : thinking } : {}
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2187
2601
|
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
2188
2602
|
async function handoff(id, targetAgent, opts = {}) {
|
|
2189
2603
|
let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
|
|
@@ -2203,15 +2617,15 @@ async function handoff(id, targetAgent, opts = {}) {
|
|
|
2203
2617
|
// src/local-agents.ts
|
|
2204
2618
|
import { existsSync as existsSync6 } from "fs";
|
|
2205
2619
|
import { homedir as homedir5 } from "os";
|
|
2206
|
-
import { join as
|
|
2620
|
+
import { join as join9 } from "path";
|
|
2207
2621
|
function localAgents(env = process.env, home = homedir5()) {
|
|
2208
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
2209
|
-
const codex = env.CODEX_HOME ||
|
|
2210
|
-
const cursor =
|
|
2622
|
+
const claude = env.CLAUDE_CONFIG_DIR || join9(home, ".claude");
|
|
2623
|
+
const codex = env.CODEX_HOME || join9(home, ".codex");
|
|
2624
|
+
const cursor = join9(home, ".cursor");
|
|
2211
2625
|
return [
|
|
2212
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir:
|
|
2213
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
2214
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir:
|
|
2626
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join9(claude, "skills") },
|
|
2627
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join9(codex, "skills") },
|
|
2628
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join9(cursor, "skills") }
|
|
2215
2629
|
];
|
|
2216
2630
|
}
|
|
2217
2631
|
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
@@ -2316,16 +2730,16 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
2316
2730
|
}
|
|
2317
2731
|
|
|
2318
2732
|
// src/setup.ts
|
|
2319
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
2320
|
-
import { dirname as dirname6, join as
|
|
2733
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
2734
|
+
import { dirname as dirname6, join as join13 } from "path";
|
|
2321
2735
|
import { createInterface as createInterface3 } from "readline";
|
|
2322
2736
|
|
|
2323
2737
|
// src/mcp-register.ts
|
|
2324
|
-
import { spawnSync as
|
|
2738
|
+
import { spawnSync as spawnSync4 } from "child_process";
|
|
2325
2739
|
|
|
2326
2740
|
// src/skill.ts
|
|
2327
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
2328
|
-
import { join as
|
|
2741
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync4 } from "fs";
|
|
2742
|
+
import { join as join10 } from "path";
|
|
2329
2743
|
|
|
2330
2744
|
// src/catalog-file.ts
|
|
2331
2745
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -2351,13 +2765,13 @@ function entry(agent2) {
|
|
|
2351
2765
|
return lines.join("\n");
|
|
2352
2766
|
}
|
|
2353
2767
|
function renderCatalog(agents, generatedAt) {
|
|
2354
|
-
const
|
|
2768
|
+
const header2 = [
|
|
2355
2769
|
"# AgentSky agent catalog",
|
|
2356
2770
|
"",
|
|
2357
2771
|
`Every agent you can hire, as of ${generatedAt.toISOString()}. Start one with \`start_session\` and its handle, written with the @.`,
|
|
2358
2772
|
"Run `npx @agentsky/sky setup` again to refresh this file; `search_catalog` always has the current list."
|
|
2359
2773
|
];
|
|
2360
|
-
if (!agents.length) return [...
|
|
2774
|
+
if (!agents.length) return [...header2, "", "The catalog was empty when this file was written. Use `search_catalog`.", ""].join("\n");
|
|
2361
2775
|
const own = agents.filter((agent2) => agent2.visibility === "workspace");
|
|
2362
2776
|
const shared = agents.filter((agent2) => agent2.visibility !== "workspace");
|
|
2363
2777
|
const sections = [];
|
|
@@ -2372,7 +2786,7 @@ function renderCatalog(agents, generatedAt) {
|
|
|
2372
2786
|
const title = category.charAt(0).toUpperCase() + category.slice(1);
|
|
2373
2787
|
sections.push([`## ${title}`, ...groups.get(category).map(entry)].join("\n\n"));
|
|
2374
2788
|
}
|
|
2375
|
-
return [...
|
|
2789
|
+
return [...header2, "", sections.join("\n\n"), ""].join("\n");
|
|
2376
2790
|
}
|
|
2377
2791
|
|
|
2378
2792
|
// src/skill.ts
|
|
@@ -2766,7 +3180,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
2766
3180
|
}
|
|
2767
3181
|
function installedVersion(dir) {
|
|
2768
3182
|
try {
|
|
2769
|
-
const marker = JSON.parse(
|
|
3183
|
+
const marker = JSON.parse(readFileSync9(join10(dir, SKILL_MARKER), "utf8"));
|
|
2770
3184
|
return typeof marker.version === "string" ? marker.version : null;
|
|
2771
3185
|
} catch {
|
|
2772
3186
|
return null;
|
|
@@ -2776,25 +3190,25 @@ function isInstalledSkill(dir) {
|
|
|
2776
3190
|
return installedVersion(dir) !== null;
|
|
2777
3191
|
}
|
|
2778
3192
|
function installSkill(agent2, opts = {}) {
|
|
2779
|
-
const dir =
|
|
3193
|
+
const dir = join10(agent2.skillsDir, SKILL_NAME);
|
|
2780
3194
|
const previous = installedVersion(dir);
|
|
2781
3195
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
2782
3196
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
2783
3197
|
if (outcome !== "current") {
|
|
2784
3198
|
mkdirSync2(dir, { recursive: true });
|
|
2785
|
-
writeFileSync4(
|
|
2786
|
-
writeFileSync4(
|
|
3199
|
+
writeFileSync4(join10(dir, "SKILL.md"), skillDocument());
|
|
3200
|
+
writeFileSync4(join10(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
2787
3201
|
`);
|
|
2788
3202
|
}
|
|
2789
3203
|
if (opts.catalog) {
|
|
2790
3204
|
mkdirSync2(dir, { recursive: true });
|
|
2791
|
-
writeFileSync4(
|
|
3205
|
+
writeFileSync4(join10(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2792
3206
|
}
|
|
2793
|
-
const refsDir =
|
|
3207
|
+
const refsDir = join10(dir, REFERENCES_DIR);
|
|
2794
3208
|
mkdirSync2(refsDir, { recursive: true });
|
|
2795
|
-
writeFileSync4(
|
|
2796
|
-
writeFileSync4(
|
|
2797
|
-
writeFileSync4(
|
|
3209
|
+
writeFileSync4(join10(refsDir, "import.md"), importReferenceDoc());
|
|
3210
|
+
writeFileSync4(join10(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
3211
|
+
writeFileSync4(join10(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
2798
3212
|
return { agent: agent2.id, path: dir, outcome };
|
|
2799
3213
|
}
|
|
2800
3214
|
|
|
@@ -2803,9 +3217,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
|
|
|
2803
3217
|
var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
2804
3218
|
function systemCommandRunner() {
|
|
2805
3219
|
return {
|
|
2806
|
-
has: (command) =>
|
|
3220
|
+
has: (command) => spawnSync4(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
2807
3221
|
run: (command, args) => {
|
|
2808
|
-
const result =
|
|
3222
|
+
const result = spawnSync4(command, args, { encoding: "utf8" });
|
|
2809
3223
|
return { status: result.status, stderr: result.stderr ?? "" };
|
|
2810
3224
|
}
|
|
2811
3225
|
};
|
|
@@ -2845,10 +3259,10 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
2845
3259
|
}
|
|
2846
3260
|
|
|
2847
3261
|
// src/setup-local.ts
|
|
2848
|
-
import { isUtf8 as
|
|
2849
|
-
import { existsSync as existsSync7, readdirSync as
|
|
3262
|
+
import { isUtf8 as isUtf83 } from "buffer";
|
|
3263
|
+
import { existsSync as existsSync7, readdirSync as readdirSync6, readFileSync as readFileSync10, statSync as statSync7 } from "fs";
|
|
2850
3264
|
import { homedir as homedir6 } from "os";
|
|
2851
|
-
import { dirname as dirname5, isAbsolute as isAbsolute2, join as
|
|
3265
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join11, resolve as resolve6 } from "path";
|
|
2852
3266
|
import { parse as parseToml3 } from "smol-toml";
|
|
2853
3267
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
2854
3268
|
var MAX_IMPORT_DEPTH = 5;
|
|
@@ -2862,7 +3276,7 @@ function sensitive(name) {
|
|
|
2862
3276
|
return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
|
|
2863
3277
|
}
|
|
2864
3278
|
function redacted(data) {
|
|
2865
|
-
if (data.includes(0) || !
|
|
3279
|
+
if (data.includes(0) || !isUtf83(data)) return new Uint8Array(data);
|
|
2866
3280
|
try {
|
|
2867
3281
|
const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
|
|
2868
3282
|
return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
|
|
@@ -2872,12 +3286,12 @@ function redacted(data) {
|
|
|
2872
3286
|
}
|
|
2873
3287
|
function skillFiles2(root, prefix) {
|
|
2874
3288
|
const files2 = [];
|
|
2875
|
-
for (const entry2 of
|
|
2876
|
-
const path =
|
|
3289
|
+
for (const entry2 of readdirSync6(root, { withFileTypes: true })) {
|
|
3290
|
+
const path = join11(root, entry2.name);
|
|
2877
3291
|
if (entry2.isDirectory()) {
|
|
2878
3292
|
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
2879
3293
|
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
2880
|
-
const bytes = redacted(
|
|
3294
|
+
const bytes = redacted(readFileSync10(path));
|
|
2881
3295
|
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
2882
3296
|
}
|
|
2883
3297
|
}
|
|
@@ -2888,8 +3302,8 @@ function readLocalSkills(agents) {
|
|
|
2888
3302
|
const ignored = [];
|
|
2889
3303
|
for (const agent2 of agents) {
|
|
2890
3304
|
if (!existsSync7(agent2.skillsDir)) continue;
|
|
2891
|
-
for (const entry2 of
|
|
2892
|
-
const dir =
|
|
3305
|
+
for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
|
|
3306
|
+
const dir = join11(agent2.skillsDir, entry2.name);
|
|
2893
3307
|
let isDir2 = entry2.isDirectory();
|
|
2894
3308
|
if (entry2.isSymbolicLink()) {
|
|
2895
3309
|
try {
|
|
@@ -2898,8 +3312,8 @@ function readLocalSkills(agents) {
|
|
|
2898
3312
|
isDir2 = false;
|
|
2899
3313
|
}
|
|
2900
3314
|
}
|
|
2901
|
-
if (!isDir2 || !existsSync7(
|
|
2902
|
-
const name = frontmatterName(
|
|
3315
|
+
if (!isDir2 || !existsSync7(join11(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
3316
|
+
const name = frontmatterName(readFileSync10(join11(dir, "SKILL.md"), "utf8"));
|
|
2903
3317
|
if (!name) {
|
|
2904
3318
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
2905
3319
|
continue;
|
|
@@ -2927,11 +3341,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2927
3341
|
}
|
|
2928
3342
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
2929
3343
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
2930
|
-
const path = target.startsWith("~/") ?
|
|
3344
|
+
const path = target.startsWith("~/") ? join11(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
|
|
2931
3345
|
if (seen.includes(path)) return "";
|
|
2932
3346
|
let imported;
|
|
2933
3347
|
try {
|
|
2934
|
-
imported =
|
|
3348
|
+
imported = readFileSync10(path, "utf8");
|
|
2935
3349
|
} catch {
|
|
2936
3350
|
return line;
|
|
2937
3351
|
}
|
|
@@ -2944,10 +3358,10 @@ function readGlobalInstructions(agents, home = homedir6()) {
|
|
|
2944
3358
|
for (const agent2 of agents) {
|
|
2945
3359
|
const file = INSTRUCTION_FILES2[agent2.id];
|
|
2946
3360
|
if (!file) continue;
|
|
2947
|
-
const path =
|
|
3361
|
+
const path = join11(agent2.root, file);
|
|
2948
3362
|
let raw;
|
|
2949
3363
|
try {
|
|
2950
|
-
raw =
|
|
3364
|
+
raw = readFileSync10(path, "utf8");
|
|
2951
3365
|
} catch {
|
|
2952
3366
|
continue;
|
|
2953
3367
|
}
|
|
@@ -2966,7 +3380,7 @@ ${item.content.trim()}`).join("\n\n");
|
|
|
2966
3380
|
}
|
|
2967
3381
|
function readJson3(path) {
|
|
2968
3382
|
try {
|
|
2969
|
-
return JSON.parse(
|
|
3383
|
+
return JSON.parse(readFileSync10(path, "utf8"));
|
|
2970
3384
|
} catch {
|
|
2971
3385
|
return void 0;
|
|
2972
3386
|
}
|
|
@@ -2980,10 +3394,10 @@ function bearerOf(def) {
|
|
|
2980
3394
|
return match?.[1] ?? null;
|
|
2981
3395
|
}
|
|
2982
3396
|
function serversOf(agent2, home) {
|
|
2983
|
-
if (agent2.id === "claude_code") return readJson3(
|
|
2984
|
-
if (agent2.id === "cursor") return readJson3(
|
|
3397
|
+
if (agent2.id === "claude_code") return readJson3(join11(home, ".claude.json"))?.mcpServers ?? {};
|
|
3398
|
+
if (agent2.id === "cursor") return readJson3(join11(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
2985
3399
|
try {
|
|
2986
|
-
return parseToml3(
|
|
3400
|
+
return parseToml3(readFileSync10(join11(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
2987
3401
|
} catch {
|
|
2988
3402
|
return {};
|
|
2989
3403
|
}
|
|
@@ -3004,9 +3418,9 @@ function findLocalSecrets(agents, home = homedir6()) {
|
|
|
3004
3418
|
|
|
3005
3419
|
// src/subscriptions.ts
|
|
3006
3420
|
import { spawn as spawn3 } from "child_process";
|
|
3007
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3421
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
3008
3422
|
import { homedir as homedir7 } from "os";
|
|
3009
|
-
import { join as
|
|
3423
|
+
import { join as join12 } from "path";
|
|
3010
3424
|
import { createInterface as createInterface2 } from "readline";
|
|
3011
3425
|
var ALIASES = {
|
|
3012
3426
|
claude: "anthropic",
|
|
@@ -3102,11 +3516,11 @@ async function connectClaude() {
|
|
|
3102
3516
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
3103
3517
|
}
|
|
3104
3518
|
function codexSignIn(env = process.env, home = homedir7()) {
|
|
3105
|
-
const path =
|
|
3519
|
+
const path = join12(env.CODEX_HOME || join12(home, ".codex"), "auth.json");
|
|
3106
3520
|
if (!existsSync8(path)) {
|
|
3107
3521
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
3108
3522
|
}
|
|
3109
|
-
const raw =
|
|
3523
|
+
const raw = readFileSync11(path, "utf8");
|
|
3110
3524
|
try {
|
|
3111
3525
|
const parsed = JSON.parse(raw);
|
|
3112
3526
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -3115,11 +3529,11 @@ function codexSignIn(env = process.env, home = homedir7()) {
|
|
|
3115
3529
|
}
|
|
3116
3530
|
}
|
|
3117
3531
|
function cursorSignIn(env = process.env, home = homedir7()) {
|
|
3118
|
-
const path =
|
|
3532
|
+
const path = join12(env.XDG_CONFIG_HOME || join12(home, ".config"), "cursor", "auth.json");
|
|
3119
3533
|
if (!existsSync8(path)) {
|
|
3120
3534
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
3121
3535
|
}
|
|
3122
|
-
const raw =
|
|
3536
|
+
const raw = readFileSync11(path, "utf8");
|
|
3123
3537
|
try {
|
|
3124
3538
|
const parsed = JSON.parse(raw);
|
|
3125
3539
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -3278,11 +3692,11 @@ function planProviders(names2) {
|
|
|
3278
3692
|
}));
|
|
3279
3693
|
}
|
|
3280
3694
|
function reportPath() {
|
|
3281
|
-
return
|
|
3695
|
+
return join13(dirname6(configPath()), "setup.json");
|
|
3282
3696
|
}
|
|
3283
3697
|
function loadReport() {
|
|
3284
3698
|
try {
|
|
3285
|
-
const saved = JSON.parse(
|
|
3699
|
+
const saved = JSON.parse(readFileSync12(reportPath(), "utf8"));
|
|
3286
3700
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
3287
3701
|
} catch {
|
|
3288
3702
|
return { agents: [], plans: [] };
|
|
@@ -3645,17 +4059,17 @@ function renderArtifact(artifact2) {
|
|
|
3645
4059
|
}
|
|
3646
4060
|
|
|
3647
4061
|
// src/skills-store.ts
|
|
3648
|
-
import { readdirSync as
|
|
3649
|
-
import { basename as basename7, join as
|
|
4062
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync13, statSync as statSync8 } from "fs";
|
|
4063
|
+
import { basename as basename7, join as join14, relative as relative4, resolve as resolve7 } from "path";
|
|
3650
4064
|
function skillBundle(dir) {
|
|
3651
4065
|
const root = resolve7(dir);
|
|
3652
4066
|
const top = basename7(root);
|
|
3653
4067
|
const files2 = [];
|
|
3654
4068
|
const walk = (path) => {
|
|
3655
|
-
for (const name of
|
|
3656
|
-
const full =
|
|
4069
|
+
for (const name of readdirSync7(path).sort()) {
|
|
4070
|
+
const full = join14(path, name);
|
|
3657
4071
|
if (statSync8(full).isDirectory()) walk(full);
|
|
3658
|
-
else files2.push({ path: `${top}/${relative4(root, full)}`, bytes:
|
|
4072
|
+
else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync13(full) });
|
|
3659
4073
|
}
|
|
3660
4074
|
};
|
|
3661
4075
|
walk(root);
|
|
@@ -3908,11 +4322,11 @@ function confirmPrompt(question) {
|
|
|
3908
4322
|
}
|
|
3909
4323
|
|
|
3910
4324
|
// src/harness.ts
|
|
3911
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as
|
|
3912
|
-
import { dirname as dirname7, join as
|
|
4325
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
4326
|
+
import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
|
|
3913
4327
|
var HARNESS_FILE = "harness.json";
|
|
3914
4328
|
function harnessFilePath(dir) {
|
|
3915
|
-
return
|
|
4329
|
+
return join15(resolve8(dir), HARNESS_FILE);
|
|
3916
4330
|
}
|
|
3917
4331
|
function readHarnessFile(dir) {
|
|
3918
4332
|
const path = harnessFilePath(dir);
|
|
@@ -3920,7 +4334,7 @@ function readHarnessFile(dir) {
|
|
|
3920
4334
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
3921
4335
|
}
|
|
3922
4336
|
try {
|
|
3923
|
-
return JSON.parse(
|
|
4337
|
+
return JSON.parse(readFileSync14(path, "utf8"));
|
|
3924
4338
|
} catch (err) {
|
|
3925
4339
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3926
4340
|
}
|
|
@@ -4621,22 +5035,33 @@ session.command("shared").description("sessions other people shared with you to
|
|
|
4621
5035
|
for (const s of sessions) console.log(`${s.id} ${s.agent_type.padEnd(12)} ${s.title ?? "(untitled)"} from ${s.owner.name ?? s.owner.email ?? "unknown"} ${s.shared_at.slice(0, 10)}`);
|
|
4622
5036
|
})
|
|
4623
5037
|
);
|
|
4624
|
-
session.command("
|
|
5038
|
+
session.command("model <ref> [model]").description("show the session's model and thinking level, or switch them; the next turn uses the new ones").option("--thinking <level>", `thinking level for the next turns; "default" returns to the agent's own`).action(
|
|
5039
|
+
run(async (ref, model, opts) => {
|
|
5040
|
+
const id = await resolveSessionRef(ref);
|
|
5041
|
+
const change = engineChange(model, opts.thinking);
|
|
5042
|
+
const { session: s } = change ? await api("PATCH", `/sessions/${encodeURIComponent(id)}`, change) : await api("GET", `/sessions/${encodeURIComponent(id)}`);
|
|
5043
|
+
console.log(`${id}: ${s.llm}${s.reasoningEffort ? ` \xB7 thinking ${s.reasoningEffort}` : ""}${change ? " (from the next turn)" : ""}`);
|
|
5044
|
+
})
|
|
5045
|
+
);
|
|
5046
|
+
session.command("handoff <ref> <targetAgent>").description("hand the session's context and workspace to another agent (agent id or catalog handle); prints the new session").option("--from-message <id>", "carry the conversation up to this message only").option("--model <id>", "the model the new session runs").option("--thinking <level>", "the new session's thinking level").option("--no-wait", "return once the handoff is accepted").action(
|
|
4625
5047
|
run(async (ref, targetAgent, opts) => {
|
|
4626
5048
|
const id = await resolveSessionRef(ref);
|
|
5049
|
+
const change = engineChange(opts.model, opts.thinking);
|
|
5050
|
+
if (change && opts.wait === false) throw new ApiError(400, "invalid_request", "--model and --thinking need the handoff to finish; drop --no-wait");
|
|
4627
5051
|
const done = await handoff(id, targetAgent, {
|
|
4628
5052
|
fromMessage: opts.fromMessage,
|
|
4629
5053
|
wait: opts.wait !== false,
|
|
4630
5054
|
onStatus: (h) => console.error(`${h.id}: ${h.status}`)
|
|
4631
5055
|
});
|
|
4632
5056
|
if (done.status === "failed") throw new ApiError(422, done.error?.code ?? "handoff_failed", done.error?.message ?? "the handoff failed");
|
|
5057
|
+
if (done.target_session && change) await api("PATCH", `/sessions/${encodeURIComponent(done.target_session)}`, change);
|
|
4633
5058
|
if (done.target_session) console.log(`${done.target_session} (${done.resolved_mode === "native_fork" ? "forked" : "from a written summary"})`);
|
|
4634
5059
|
})
|
|
4635
5060
|
);
|
|
4636
5061
|
var sessionWorkspace = session.command("workspace").description("the session's workspace: list and read files, see changes, take a patch, copy a folder in");
|
|
4637
5062
|
sessionWorkspace.command("ls <ref> [path]").description("list a directory (default: the workspace root)").option("--hidden", "include dotfiles").action(
|
|
4638
5063
|
run(async (ref, path, opts) => {
|
|
4639
|
-
const listing = await
|
|
5064
|
+
const listing = await listFiles2(await resolveSessionRef(ref), path, Boolean(opts.hidden));
|
|
4640
5065
|
for (const e of listing.entries) console.log(entryLine(e));
|
|
4641
5066
|
if (listing.truncated) console.error(`(only the first ${listing.entries.length} entries)`);
|
|
4642
5067
|
})
|
|
@@ -4909,7 +5334,7 @@ function formatRelative(msAgo) {
|
|
|
4909
5334
|
if (hours < 48) return `${hours}h ago`;
|
|
4910
5335
|
return `${Math.round(hours / 24)}d ago`;
|
|
4911
5336
|
}
|
|
4912
|
-
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(
|
|
5337
|
+
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").option("--no-environment", "keep the working folder on this computer: send only the files the conversation touched").action(
|
|
4913
5338
|
run(
|
|
4914
5339
|
async (opts) => {
|
|
4915
5340
|
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
@@ -5017,6 +5442,7 @@ Importing ${label} \u2026
|
|
|
5017
5442
|
importKey: opts.key,
|
|
5018
5443
|
grantTraceId: opts.trace,
|
|
5019
5444
|
files: opts.files,
|
|
5445
|
+
environment: opts.environment,
|
|
5020
5446
|
onProgress: opts.json ? void 0 : (msg) => process.stdout.write(` ${msg}
|
|
5021
5447
|
`)
|
|
5022
5448
|
});
|
|
@@ -5036,11 +5462,14 @@ Importing ${label} \u2026
|
|
|
5036
5462
|
skills: result.setup.skills,
|
|
5037
5463
|
missingSkills: result.setup.missingSkills,
|
|
5038
5464
|
mcpServers: result.setup.mcpServers,
|
|
5465
|
+
environment: result.environment,
|
|
5039
5466
|
continueUrl: result.url
|
|
5040
5467
|
}));
|
|
5041
5468
|
} else {
|
|
5042
5469
|
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).`);
|
|
5043
5470
|
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)` : ""}.`);
|
|
5471
|
+
if (result.environment && "folder" in result.environment) console.log(` Uploaded the working folder ${result.environment.folder}: ${result.environment.files} files, ${(result.environment.bytes / 1024 / 1024).toFixed(1)} MiB packed${result.environment.leftOut ? `, ${result.environment.leftOut} credential or special files left on this computer` : ""}.`);
|
|
5472
|
+
else if (result.environment) console.log(` The working folder did not upload (${result.environment.error}); the conversation and the files it touched did.`);
|
|
5044
5473
|
console.log(` \u2713 Imported. Continue here:
|
|
5045
5474
|
${result.url}
|
|
5046
5475
|
`);
|
|
@@ -5050,10 +5479,40 @@ Importing ${label} \u2026
|
|
|
5050
5479
|
)
|
|
5051
5480
|
);
|
|
5052
5481
|
var files = program.command("files").description("files: upload once, reference by id from any message");
|
|
5053
|
-
files.command("upload <
|
|
5054
|
-
run(async (
|
|
5055
|
-
const
|
|
5056
|
-
|
|
5482
|
+
files.command("upload <paths...>").description("upload local files and folders, up to 50 GiB each; prints each file id to reference from a message").option("--expires <seconds>", "expire the uploads after this many seconds (3600\u20137776000)").action(
|
|
5483
|
+
run(async (paths, opts) => {
|
|
5484
|
+
const expires = opts.expires ? Number(opts.expires) : void 0;
|
|
5485
|
+
const chosen = filesUnder(paths);
|
|
5486
|
+
if (chosen.length === 0) throw new Error("no files to upload");
|
|
5487
|
+
const line = (file) => `${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`;
|
|
5488
|
+
if (chosen.length === 1) {
|
|
5489
|
+
console.log(line(await uploadLocalFile(chosen[0], expires, uploadProgress)));
|
|
5490
|
+
return;
|
|
5491
|
+
}
|
|
5492
|
+
let done = 0;
|
|
5493
|
+
const tally = () => process.stderr.write(`${process.stderr.isTTY ? "\r" : ""}uploaded ${++done} / ${chosen.length}${process.stderr.isTTY && done < chosen.length ? "" : "\n"}`);
|
|
5494
|
+
const result = await uploadLocalFiles(chosen, {
|
|
5495
|
+
expiresInSeconds: expires,
|
|
5496
|
+
onUploaded: (_path, file) => {
|
|
5497
|
+
console.log(line(file));
|
|
5498
|
+
tally();
|
|
5499
|
+
},
|
|
5500
|
+
onFailed: (path, error) => {
|
|
5501
|
+
console.error(`sky: ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
5502
|
+
tally();
|
|
5503
|
+
}
|
|
5504
|
+
});
|
|
5505
|
+
if (result.failed) {
|
|
5506
|
+
console.error(`sky: ${result.failed} of ${chosen.length} files failed; run the same command for those paths to retry`);
|
|
5507
|
+
process.exitCode = 1;
|
|
5508
|
+
}
|
|
5509
|
+
})
|
|
5510
|
+
);
|
|
5511
|
+
files.command("usage").description("how many files your workspace holds and the space they take (there is no storage limit)").action(
|
|
5512
|
+
run(async () => {
|
|
5513
|
+
const usage = await api("GET", "/files/usage");
|
|
5514
|
+
const gib = (bytes) => `${(bytes / 1024 ** 3).toFixed(2)} GiB`;
|
|
5515
|
+
console.log(`${usage.file_count} files ${gib(usage.size_bytes)} used ${usage.storage_limit_bytes === null ? "no storage limit" : `${gib(usage.storage_limit_bytes)} limit`} up to ${gib(usage.max_file_bytes)} per file`);
|
|
5057
5516
|
})
|
|
5058
5517
|
);
|
|
5059
5518
|
files.command("list").description("list your uploads, or one session's files with --session").option("--session <ref>", "list the files of this session (its message copies and outputs)").option("--limit <n>", "page size (default 20)").option("--page <cursor>", "continue from a previous page").action(
|
|
@@ -5106,7 +5565,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
5106
5565
|
);
|
|
5107
5566
|
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(
|
|
5108
5567
|
run(async (taskId, path, local) => {
|
|
5109
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
5568
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync15(local), mediaTypeFor(local));
|
|
5110
5569
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
5111
5570
|
})
|
|
5112
5571
|
);
|
|
@@ -5155,6 +5614,28 @@ skills.command("list").description("the workspace's stored skills").action(
|
|
|
5155
5614
|
for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
|
|
5156
5615
|
})
|
|
5157
5616
|
);
|
|
5617
|
+
async function decideReflection(id, verb) {
|
|
5618
|
+
const { reflection_proposal: p } = await api("POST", `/reflection_proposals/${encodeURIComponent(id)}/${verb}`);
|
|
5619
|
+
console.log(`${p.id} ${p.status}`);
|
|
5620
|
+
}
|
|
5621
|
+
var reflections = program.command("reflections").description("skill and lesson changes proposed after finished sessions; an owner or admin approves each one");
|
|
5622
|
+
reflections.command("list").description("the workspace's proposals, newest first (default: pending)").option("--status <status>", "pending, approved, rejected or superseded", "pending").option("--json", "print JSON").action(
|
|
5623
|
+
run(async (opts) => {
|
|
5624
|
+
const { reflection_proposals: list } = await api(
|
|
5625
|
+
"GET",
|
|
5626
|
+
`/reflection_proposals?status=${encodeURIComponent(opts.status)}`
|
|
5627
|
+
);
|
|
5628
|
+
if (opts.json) return void console.log(JSON.stringify(list, null, 2));
|
|
5629
|
+
if (!list.length) return void console.log(`no ${opts.status} proposals`);
|
|
5630
|
+
for (const p of list) {
|
|
5631
|
+
const target = p.kind === "save_lesson" ? p.path : p.kind === "create_skill" ? `new skill ${p.payload.name}` : `skill ${p.target_skill_id}`;
|
|
5632
|
+
console.log(`${p.id} ${p.kind} ${target} from ${p.session_id}
|
|
5633
|
+
${p.payload.why ?? ""}`);
|
|
5634
|
+
}
|
|
5635
|
+
})
|
|
5636
|
+
);
|
|
5637
|
+
reflections.command("approve <id>").description("write the proposal through the skill or memory store").action(run(async (id) => decideReflection(id, "approve")));
|
|
5638
|
+
reflections.command("reject <id>").description("decline the proposal; nothing is written").action(run(async (id) => decideReflection(id, "reject")));
|
|
5158
5639
|
var artifact = program.command("artifact").description("artifacts: publish a task's files as a named, shareable result");
|
|
5159
5640
|
artifact.command("list").description("the workspace's artifacts, or one task's with --task").option("--task <task>", "only this task's artifacts").action(
|
|
5160
5641
|
run(async (opts) => {
|
|
@@ -5769,7 +6250,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
5769
6250
|
}));
|
|
5770
6251
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
5771
6252
|
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) => {
|
|
5772
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
6253
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-QLJH7VK6.js");
|
|
5773
6254
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
5774
6255
|
const scopes = parseContentScopes(opts.capture);
|
|
5775
6256
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -5804,7 +6285,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
5804
6285
|
}));
|
|
5805
6286
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
5806
6287
|
try {
|
|
5807
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
6288
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-QLJH7VK6.js");
|
|
5808
6289
|
const chunks = [];
|
|
5809
6290
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
5810
6291
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -5817,14 +6298,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
5817
6298
|
}
|
|
5818
6299
|
});
|
|
5819
6300
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
5820
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
6301
|
+
const { readState, flush, grantSender } = await import("./trace-QLJH7VK6.js");
|
|
5821
6302
|
const state = readState();
|
|
5822
6303
|
if (!state) throw new Error("no trace is running here");
|
|
5823
6304
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
5824
6305
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
5825
6306
|
}));
|
|
5826
6307
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
5827
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
6308
|
+
const { readState, spoolStatus } = await import("./trace-QLJH7VK6.js");
|
|
5828
6309
|
const state = readState();
|
|
5829
6310
|
if (!state) {
|
|
5830
6311
|
console.log("No trace is running here.");
|
|
@@ -5834,7 +6315,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
5834
6315
|
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`);
|
|
5835
6316
|
}));
|
|
5836
6317
|
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) => {
|
|
5837
|
-
const { readState } = await import("./trace-
|
|
6318
|
+
const { readState } = await import("./trace-QLJH7VK6.js");
|
|
5838
6319
|
const ref = id ?? readState()?.trace_id;
|
|
5839
6320
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
5840
6321
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -5861,7 +6342,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
5861
6342
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
5862
6343
|
}));
|
|
5863
6344
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
5864
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
6345
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-QLJH7VK6.js");
|
|
5865
6346
|
const state = readState();
|
|
5866
6347
|
if (!state) throw new Error("no trace is running here");
|
|
5867
6348
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -5871,7 +6352,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
5871
6352
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
5872
6353
|
}));
|
|
5873
6354
|
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) => {
|
|
5874
|
-
const lib = await import("./trace-library-
|
|
6355
|
+
const lib = await import("./trace-library-FS5PCBJC.js");
|
|
5875
6356
|
if (opts.hook) {
|
|
5876
6357
|
try {
|
|
5877
6358
|
let raw = payload ?? "";
|
|
@@ -5919,10 +6400,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
5919
6400
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
5920
6401
|
}
|
|
5921
6402
|
if (opts.installSchedule) {
|
|
5922
|
-
const { join:
|
|
6403
|
+
const { join: join16 } = await import("path");
|
|
5923
6404
|
const { homedir: homedir8 } = await import("os");
|
|
5924
6405
|
const { execFileSync } = await import("child_process");
|
|
5925
|
-
const log =
|
|
6406
|
+
const log = join16(homedir8(), ".config", "sky", "trace-sync.log");
|
|
5926
6407
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
5927
6408
|
try {
|
|
5928
6409
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -5932,11 +6413,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
5932
6413
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
5933
6414
|
}
|
|
5934
6415
|
if (opts.status) {
|
|
5935
|
-
const { readFileSync:
|
|
5936
|
-
const { join:
|
|
6416
|
+
const { readFileSync: readFileSync16 } = await import("fs");
|
|
6417
|
+
const { join: join16 } = await import("path");
|
|
5937
6418
|
let state = { sessions: {} };
|
|
5938
6419
|
try {
|
|
5939
|
-
state = JSON.parse(
|
|
6420
|
+
state = JSON.parse(readFileSync16(join16(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
5940
6421
|
} catch {
|
|
5941
6422
|
}
|
|
5942
6423
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -5969,7 +6450,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
5969
6450
|
})();
|
|
5970
6451
|
});
|
|
5971
6452
|
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) => {
|
|
5972
|
-
const { libraryRequest } = await import("./trace-library-
|
|
6453
|
+
const { libraryRequest } = await import("./trace-library-FS5PCBJC.js");
|
|
5973
6454
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
5974
6455
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
5975
6456
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -5986,7 +6467,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
5986
6467
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
5987
6468
|
}));
|
|
5988
6469
|
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) => {
|
|
5989
|
-
const { libraryRequest } = await import("./trace-library-
|
|
6470
|
+
const { libraryRequest } = await import("./trace-library-FS5PCBJC.js");
|
|
5990
6471
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
5991
6472
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
5992
6473
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|