@agentsky/sky 2.13.0 → 2.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-2IKEJ74C.js → chunk-7OM2B5SE.js} +4 -4
- package/dist/{chunk-XJ5JLLXM.js → chunk-FOJP33RS.js} +2 -2
- package/dist/{chunk-Z3FPPGWX.js → chunk-VJEE2ALN.js} +1 -1
- package/dist/index.js +473 -184
- package/dist/products.js +1 -1
- package/dist/{src-3AI7XR44.js → src-EJU5U2GO.js} +1 -1
- package/dist/{trace-DS35CFKV.js → trace-YKEGRVJN.js} +2 -2
- package/dist/{trace-library-B3NZ4OQM.js → trace-library-6BK5GJVD.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-FOJP33RS.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-VJEE2ALN.js";
|
|
24
24
|
import {
|
|
25
25
|
AGENT_TYPES,
|
|
26
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -40,12 +40,12 @@ import {
|
|
|
40
40
|
skillRefSchema,
|
|
41
41
|
splitMcpServers,
|
|
42
42
|
supersededBy
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-7OM2B5SE.js";
|
|
44
44
|
import "./chunk-MLKGABMK.js";
|
|
45
45
|
|
|
46
46
|
// src/index.ts
|
|
47
|
-
import { existsSync as
|
|
48
|
-
import { basename as
|
|
47
|
+
import { existsSync as existsSync10, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
48
|
+
import { basename as basename6, resolve as resolve7 } from "path";
|
|
49
49
|
import { createInterface as createInterface4 } from "readline";
|
|
50
50
|
import { Command } from "commander";
|
|
51
51
|
|
|
@@ -699,7 +699,7 @@ async function createClone(local, opts) {
|
|
|
699
699
|
console.log("Aborted \u2014 nothing sent.");
|
|
700
700
|
return;
|
|
701
701
|
}
|
|
702
|
-
const { servers:
|
|
702
|
+
const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
|
|
703
703
|
if (skipped.length) {
|
|
704
704
|
console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
|
|
705
705
|
}
|
|
@@ -707,7 +707,7 @@ async function createClone(local, opts) {
|
|
|
707
707
|
agentType: local.harness,
|
|
708
708
|
...local.model ? { llm: local.model } : {},
|
|
709
709
|
...local.prompt ? { prompt: local.prompt } : {},
|
|
710
|
-
...
|
|
710
|
+
...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
|
|
711
711
|
};
|
|
712
712
|
let sentLlm = local.model ?? null;
|
|
713
713
|
let created;
|
|
@@ -820,16 +820,252 @@ async function clone(opts) {
|
|
|
820
820
|
}
|
|
821
821
|
|
|
822
822
|
// src/import-local.ts
|
|
823
|
-
import { closeSync, existsSync as
|
|
824
|
-
import { homedir as
|
|
825
|
-
import { join as
|
|
823
|
+
import { closeSync, existsSync as existsSync5, openSync, readdirSync as readdirSync2, readFileSync as readFileSync6, readSync, statSync as statSync3 } from "fs";
|
|
824
|
+
import { homedir as homedir4 } from "os";
|
|
825
|
+
import { join as join4 } from "path";
|
|
826
826
|
import { createHash } from "crypto";
|
|
827
827
|
|
|
828
828
|
// src/import-files.ts
|
|
829
829
|
import { isUtf8 } from "buffer";
|
|
830
|
-
import { existsSync as
|
|
830
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
|
|
831
|
+
import { homedir as homedir3 } from "os";
|
|
832
|
+
import { dirname as dirname3, extname as extname2, isAbsolute, join as join3, relative as relative2, resolve as resolve3, sep as sep2, basename as basename4 } from "path";
|
|
833
|
+
|
|
834
|
+
// src/import-workspace.ts
|
|
835
|
+
import { spawnSync } from "child_process";
|
|
836
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync4, realpathSync, statSync } from "fs";
|
|
831
837
|
import { homedir as homedir2 } from "os";
|
|
832
|
-
import {
|
|
838
|
+
import { basename as basename3, dirname as dirname2, join as join2, relative, sep } from "path";
|
|
839
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
840
|
+
var MAX_TREE = 5e3;
|
|
841
|
+
var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
|
|
842
|
+
function git(cwd, ...args) {
|
|
843
|
+
const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
|
|
844
|
+
return run2.status === 0 ? run2.stdout : null;
|
|
845
|
+
}
|
|
846
|
+
var isFile = (path) => {
|
|
847
|
+
try {
|
|
848
|
+
return statSync(path).isFile();
|
|
849
|
+
} catch {
|
|
850
|
+
return false;
|
|
851
|
+
}
|
|
852
|
+
};
|
|
853
|
+
var isDir = (path) => {
|
|
854
|
+
try {
|
|
855
|
+
return statSync(path).isDirectory();
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
function sharedInstructionPaths(home = homedir2()) {
|
|
861
|
+
const rules = join2(home, ".agents", "rules");
|
|
862
|
+
let ruleFiles = [];
|
|
863
|
+
try {
|
|
864
|
+
ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join2(rules, name));
|
|
865
|
+
} catch {
|
|
866
|
+
}
|
|
867
|
+
return [join2(home, "AGENTS.md"), join2(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
868
|
+
}
|
|
869
|
+
function strings(value, out, depth = 0) {
|
|
870
|
+
if (depth > 12) return;
|
|
871
|
+
if (typeof value === "string") out.push(value);
|
|
872
|
+
else if (Array.isArray(value)) for (const item of value) strings(item, out, depth + 1);
|
|
873
|
+
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
874
|
+
}
|
|
875
|
+
function skillRoots(cwd, home) {
|
|
876
|
+
const claude = process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude");
|
|
877
|
+
const codex = process.env.CODEX_HOME ?? join2(home, ".codex");
|
|
878
|
+
return [
|
|
879
|
+
...cwd ? [join2(cwd, ".claude", "skills"), join2(cwd, ".agents", "skills")] : [],
|
|
880
|
+
join2(home, ".agents", "skills"),
|
|
881
|
+
join2(claude, "skills"),
|
|
882
|
+
join2(codex, "skills"),
|
|
883
|
+
join2(home, ".hermes", "skills")
|
|
884
|
+
];
|
|
885
|
+
}
|
|
886
|
+
function pluginSkill(plugin, skill, home) {
|
|
887
|
+
const base = join2(process.env.CLAUDE_CONFIG_DIR ?? join2(home, ".claude"), "plugins");
|
|
888
|
+
const walk = (dir, depth) => {
|
|
889
|
+
if (depth > 7) return null;
|
|
890
|
+
let entries;
|
|
891
|
+
try {
|
|
892
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
893
|
+
} catch {
|
|
894
|
+
return null;
|
|
895
|
+
}
|
|
896
|
+
for (const entry2 of entries) {
|
|
897
|
+
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
898
|
+
const path = join2(dir, entry2.name);
|
|
899
|
+
if (entry2.name === skill && basename3(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join2(path, "SKILL.md"))) return path;
|
|
900
|
+
const found = walk(path, depth + 1);
|
|
901
|
+
if (found) return found;
|
|
902
|
+
}
|
|
903
|
+
return null;
|
|
904
|
+
};
|
|
905
|
+
return walk(base, 0);
|
|
906
|
+
}
|
|
907
|
+
function findSkill(name, cwd, home) {
|
|
908
|
+
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
909
|
+
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
910
|
+
for (const root of skillRoots(cwd, home)) {
|
|
911
|
+
const direct = join2(root, name);
|
|
912
|
+
if (isFile(join2(direct, "SKILL.md"))) return realpathSync(direct);
|
|
913
|
+
}
|
|
914
|
+
return null;
|
|
915
|
+
}
|
|
916
|
+
function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
917
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
918
|
+
const missing = /* @__PURE__ */ new Set();
|
|
919
|
+
const add = (dir, name = basename3(dir)) => {
|
|
920
|
+
if (!isFile(join2(dir, "SKILL.md"))) return false;
|
|
921
|
+
const real = realpathSync(dir);
|
|
922
|
+
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
923
|
+
return true;
|
|
924
|
+
};
|
|
925
|
+
for (const record of records) {
|
|
926
|
+
const texts = [];
|
|
927
|
+
strings(record, texts);
|
|
928
|
+
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].replace(/[.,;:)]+$/, ""));
|
|
929
|
+
const content = record?.message?.content;
|
|
930
|
+
if (!Array.isArray(content)) continue;
|
|
931
|
+
for (const block of content) {
|
|
932
|
+
const b = block;
|
|
933
|
+
if (b?.type !== "tool_use" || b.name !== "Skill" || typeof b.input?.skill !== "string") continue;
|
|
934
|
+
const name = b.input.skill.replace(/^\//, "");
|
|
935
|
+
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
936
|
+
const dir = findSkill(name, cwd, home);
|
|
937
|
+
if (!dir || !add(dir, name)) missing.add(name);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
for (const path of touched) if (basename3(path) === "SKILL.md") add(dirname2(path));
|
|
941
|
+
for (const name of dirs.keys()) missing.delete(name);
|
|
942
|
+
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
943
|
+
}
|
|
944
|
+
function readJson2(path) {
|
|
945
|
+
try {
|
|
946
|
+
const value = JSON.parse(readFileSync4(path, "utf8"));
|
|
947
|
+
return value && typeof value === "object" ? value : null;
|
|
948
|
+
} catch {
|
|
949
|
+
return null;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
function names(value) {
|
|
953
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
954
|
+
const keys2 = Object.keys(value).sort();
|
|
955
|
+
return keys2.length ? keys2 : void 0;
|
|
956
|
+
}
|
|
957
|
+
function safeUrl(raw) {
|
|
958
|
+
try {
|
|
959
|
+
const url = new URL(raw);
|
|
960
|
+
if (!/^https?:$/.test(url.protocol)) return null;
|
|
961
|
+
const query = [.../* @__PURE__ */ new Set([...url.searchParams.keys()])].sort();
|
|
962
|
+
return { url: `${url.protocol}//${url.host}${url.pathname}`, ...query.length ? { query } : {} };
|
|
963
|
+
} catch {
|
|
964
|
+
return null;
|
|
965
|
+
}
|
|
966
|
+
}
|
|
967
|
+
function serverEntry(name, def, scope) {
|
|
968
|
+
if (!def || typeof def !== "object") return null;
|
|
969
|
+
const env = names(def.env);
|
|
970
|
+
const headers = names(def.headers ?? def.http_headers);
|
|
971
|
+
if (typeof def.url === "string") {
|
|
972
|
+
const url = safeUrl(def.url);
|
|
973
|
+
if (!url) return null;
|
|
974
|
+
const type = def.type === "sse" ? "sse" : "http";
|
|
975
|
+
return { name, scope, transport: type, ...url, ...headers ? { headers } : {}, ...env ? { env } : {} };
|
|
976
|
+
}
|
|
977
|
+
if (typeof def.command !== "string" || !def.command) return null;
|
|
978
|
+
const args = Array.isArray(def.args) ? def.args.filter((arg) => typeof arg === "string") : [];
|
|
979
|
+
return { name, scope, transport: "stdio", command: def.command, ...args.length ? { args } : {}, ...env ? { env } : {} };
|
|
980
|
+
}
|
|
981
|
+
function mcpServers(harness2, cwd, home = homedir2()) {
|
|
982
|
+
const out = [];
|
|
983
|
+
const push = (servers, scope) => {
|
|
984
|
+
if (!servers || typeof servers !== "object") return;
|
|
985
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
986
|
+
const entry2 = serverEntry(name, def, scope);
|
|
987
|
+
if (entry2 && !out.some((existing) => existing.name === name)) out.push(entry2);
|
|
988
|
+
}
|
|
989
|
+
};
|
|
990
|
+
if (harness2 === "claude_code") {
|
|
991
|
+
const config = readJson2(join2(process.env.CLAUDE_CONFIG_DIR ? dirname2(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
992
|
+
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
993
|
+
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
994
|
+
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
995
|
+
if (root) push(readJson2(join2(root, ".mcp.json"))?.mcpServers, "project");
|
|
996
|
+
push(config?.mcpServers, "user");
|
|
997
|
+
}
|
|
998
|
+
if (harness2 === "codex") {
|
|
999
|
+
try {
|
|
1000
|
+
const config = parseToml2(readFileSync4(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "config.toml"), "utf8"));
|
|
1001
|
+
push(config.mcp_servers, "user");
|
|
1002
|
+
} catch {
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
return out;
|
|
1006
|
+
}
|
|
1007
|
+
function remoteWithoutCredentials(remote) {
|
|
1008
|
+
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
1009
|
+
}
|
|
1010
|
+
function workspaceState(cwd, harness2, skills2, home = homedir2()) {
|
|
1011
|
+
const present = !!cwd && isDir(cwd);
|
|
1012
|
+
const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
|
|
1013
|
+
let gitState = null;
|
|
1014
|
+
let tree = [];
|
|
1015
|
+
let patch = null;
|
|
1016
|
+
if (root) {
|
|
1017
|
+
const dirty = (git(root, "status", "--porcelain=v1", "--untracked-files=normal") ?? "").split("\n").filter(Boolean).map((line) => line.slice(3));
|
|
1018
|
+
const ahead = git(root, "rev-list", "--count", "@{upstream}..HEAD");
|
|
1019
|
+
const remote = git(root, "remote", "get-url", "origin");
|
|
1020
|
+
const branch = git(root, "rev-parse", "--abbrev-ref", "HEAD")?.trim() || null;
|
|
1021
|
+
gitState = {
|
|
1022
|
+
remote: remote ? remoteWithoutCredentials(remote) : null,
|
|
1023
|
+
branch: branch === "HEAD" ? null : branch,
|
|
1024
|
+
commit: git(root, "rev-parse", "HEAD")?.trim() || null,
|
|
1025
|
+
dirty: dirty.slice(0, 500),
|
|
1026
|
+
ahead: ahead === null ? null : Number(ahead.trim()) || 0
|
|
1027
|
+
};
|
|
1028
|
+
tree = (git(cwd, "-c", "core.quotePath=false", "ls-files") ?? "").split("\n").filter(Boolean);
|
|
1029
|
+
if (dirty.length) patch = git(cwd, "diff", "HEAD", "--no-color", "--no-ext-diff", "--relative", "--", ".") || null;
|
|
1030
|
+
}
|
|
1031
|
+
return {
|
|
1032
|
+
manifest: {
|
|
1033
|
+
format: "agentsky.import-workspace",
|
|
1034
|
+
format_version: 1,
|
|
1035
|
+
harness: harness2,
|
|
1036
|
+
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
1037
|
+
project: cwd ? basename3(root ?? cwd) : null,
|
|
1038
|
+
git: gitState,
|
|
1039
|
+
tree: tree.slice(0, MAX_TREE),
|
|
1040
|
+
tree_truncated: tree.length > MAX_TREE,
|
|
1041
|
+
skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
|
|
1042
|
+
mcp_servers: mcpServers(harness2, cwd, home)
|
|
1043
|
+
},
|
|
1044
|
+
patch
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
function skillFiles(skill) {
|
|
1048
|
+
const out = [];
|
|
1049
|
+
const walk = (dir, depth) => {
|
|
1050
|
+
if (depth > 8 || out.length > 400) return;
|
|
1051
|
+
let entries;
|
|
1052
|
+
try {
|
|
1053
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1054
|
+
} catch {
|
|
1055
|
+
return;
|
|
1056
|
+
}
|
|
1057
|
+
for (const entry2 of entries) {
|
|
1058
|
+
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1059
|
+
const path = join2(dir, entry2.name);
|
|
1060
|
+
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1061
|
+
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1062
|
+
}
|
|
1063
|
+
};
|
|
1064
|
+
if (existsSync3(skill.dir)) walk(skill.dir, 0);
|
|
1065
|
+
return out;
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
// src/import-files.ts
|
|
833
1069
|
var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
|
|
834
1070
|
var SECRET_FILE = /(^|\/)(\.env(\..*)?|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\.?credentials(\.json)?|auth\.json|\.claude\.json|id_(rsa|ed25519|ecdsa)[^/]*|.*\.(pem|key|p12|pfx|keystore|jks|kdbx|sqlite|db))$|\/\.ssh\/|\/\.aws\/|\/\.gnupg\/|\/\.docker\/config\.json$|\/Keychains\//i;
|
|
835
1071
|
var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
|
|
@@ -878,7 +1114,7 @@ function classify(path) {
|
|
|
878
1114
|
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
879
1115
|
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
880
1116
|
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
881
|
-
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(
|
|
1117
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
|
|
882
1118
|
return null;
|
|
883
1119
|
}
|
|
884
1120
|
var SHELL_READ = /(?:^|[\s;&|(`"'])(?:cat|head|tail|nl|less|more|wc|bat|sed\s+-n\s+(?:'[^']*'|"[^"]*"|\S+))(?:\s+-[\w-]+(?:\s+\d+)?)*\s+((?:~|\.{0,2}\/)?[\w@%+./~-]*[\w-]\.[\w]{1,10}|\/[\w@%+./-]+)/g;
|
|
@@ -892,7 +1128,7 @@ function shellText(input) {
|
|
|
892
1128
|
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
893
1129
|
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
894
1130
|
}
|
|
895
|
-
function touchedPaths(records, cwd, home =
|
|
1131
|
+
function touchedPaths(records, cwd, home = homedir3()) {
|
|
896
1132
|
const out = /* @__PURE__ */ new Set();
|
|
897
1133
|
const add = (raw, base) => {
|
|
898
1134
|
if (!raw || raw.length > 1e3) return;
|
|
@@ -935,79 +1171,110 @@ function touchedPaths(records, cwd, home = homedir2()) {
|
|
|
935
1171
|
return [...out];
|
|
936
1172
|
}
|
|
937
1173
|
function gitRoot(dir) {
|
|
938
|
-
for (let d = dir; ; d =
|
|
939
|
-
if (
|
|
940
|
-
if (d ===
|
|
1174
|
+
for (let d = dir; ; d = dirname3(d)) {
|
|
1175
|
+
if (existsSync4(join3(d, ".git"))) return d;
|
|
1176
|
+
if (d === dirname3(d)) return null;
|
|
941
1177
|
}
|
|
942
1178
|
}
|
|
943
|
-
function instructionPaths(cwd, harness2, home =
|
|
1179
|
+
function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
944
1180
|
const out = [];
|
|
945
|
-
if (cwd &&
|
|
1181
|
+
if (cwd && existsSync4(cwd)) {
|
|
946
1182
|
const root = gitRoot(cwd) ?? cwd;
|
|
947
|
-
for (let d = resolve3(cwd); ; d =
|
|
948
|
-
for (const name of INSTRUCTION_FILES) out.push(
|
|
949
|
-
if (d === root || d ===
|
|
1183
|
+
for (let d = resolve3(cwd); ; d = dirname3(d)) {
|
|
1184
|
+
for (const name of INSTRUCTION_FILES) out.push(join3(d, name));
|
|
1185
|
+
if (d === root || d === dirname3(d)) break;
|
|
950
1186
|
}
|
|
951
1187
|
}
|
|
952
|
-
if (harness2 === "claude_code") out.push(
|
|
953
|
-
if (harness2 === "codex") out.push(
|
|
1188
|
+
if (harness2 === "claude_code") out.push(join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "CLAUDE.md"));
|
|
1189
|
+
if (harness2 === "codex") out.push(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "AGENTS.md"));
|
|
1190
|
+
out.push(...sharedInstructionPaths(home));
|
|
954
1191
|
return out.filter((path) => {
|
|
955
1192
|
try {
|
|
956
|
-
return
|
|
1193
|
+
return statSync2(path).isFile();
|
|
957
1194
|
} catch {
|
|
958
1195
|
return false;
|
|
959
1196
|
}
|
|
960
1197
|
});
|
|
961
1198
|
}
|
|
962
|
-
function resourceName(path, cwd, home =
|
|
963
|
-
const inside = cwd ?
|
|
964
|
-
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(
|
|
965
|
-
const underHome =
|
|
1199
|
+
function resourceName(path, cwd, home = homedir3()) {
|
|
1200
|
+
const inside = cwd ? relative2(resolve3(cwd), path) : "";
|
|
1201
|
+
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
1202
|
+
const underHome = relative2(home, path);
|
|
966
1203
|
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
967
|
-
return `files/${shown.split(
|
|
1204
|
+
return `files/${shown.split(sep2).join("/")}`;
|
|
968
1205
|
}
|
|
969
1206
|
var ORDER = { text: 0, document: 1, image: 2 };
|
|
1207
|
+
function resourceLine(name, mediaType, data) {
|
|
1208
|
+
return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
1209
|
+
}
|
|
970
1210
|
function collectSessionFiles(records, opts) {
|
|
971
|
-
const home = opts.home ??
|
|
1211
|
+
const home = opts.home ?? homedir3();
|
|
972
1212
|
const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
|
|
973
1213
|
const leftOut = [];
|
|
974
1214
|
const candidates = [];
|
|
975
1215
|
const seen = /* @__PURE__ */ new Set();
|
|
976
|
-
|
|
977
|
-
|
|
1216
|
+
const touched = touchedPaths(records, opts.cwd, home);
|
|
1217
|
+
const skills2 = loadedSkills(records, touched, opts.cwd, home);
|
|
1218
|
+
const consider = (path, name) => {
|
|
1219
|
+
if (seen.has(path) || exclude.has(path)) return;
|
|
978
1220
|
seen.add(path);
|
|
979
1221
|
if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
|
|
980
1222
|
leftOut.push({ path, reason: "credential or build file" });
|
|
981
|
-
|
|
1223
|
+
return;
|
|
982
1224
|
}
|
|
983
1225
|
let size;
|
|
984
1226
|
try {
|
|
985
|
-
const stat =
|
|
986
|
-
if (!stat.isFile())
|
|
1227
|
+
const stat = statSync2(path);
|
|
1228
|
+
if (!stat.isFile()) return;
|
|
987
1229
|
size = stat.size;
|
|
988
1230
|
} catch {
|
|
989
1231
|
leftOut.push({ path, reason: "missing" });
|
|
990
|
-
|
|
1232
|
+
return;
|
|
991
1233
|
}
|
|
992
1234
|
const type = classify(path);
|
|
993
1235
|
if (!type) {
|
|
994
1236
|
leftOut.push({ path, reason: "unsupported type" });
|
|
995
|
-
|
|
1237
|
+
return;
|
|
996
1238
|
}
|
|
997
1239
|
if (size > MAX_ATTACHED_FILE_BYTES) {
|
|
998
1240
|
leftOut.push({ path, reason: "too large" });
|
|
999
|
-
|
|
1241
|
+
return;
|
|
1000
1242
|
}
|
|
1001
|
-
candidates.push({ path, name
|
|
1002
|
-
}
|
|
1243
|
+
candidates.push({ path, name, ...type, size });
|
|
1244
|
+
};
|
|
1245
|
+
for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
|
|
1246
|
+
for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
|
|
1003
1247
|
candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
|
|
1004
1248
|
const lines = [];
|
|
1005
1249
|
const attached = [];
|
|
1006
1250
|
let bytes = 0;
|
|
1251
|
+
const state = workspaceState(opts.cwd, opts.harness, skills2, home);
|
|
1252
|
+
const described = !!opts.cwd || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
1253
|
+
const generated = [
|
|
1254
|
+
...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
|
|
1255
|
+
...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
|
|
1256
|
+
];
|
|
1257
|
+
for (const file of generated) {
|
|
1258
|
+
let line;
|
|
1259
|
+
try {
|
|
1260
|
+
line = resourceLine(file.name, file.mediaType, file.data);
|
|
1261
|
+
} catch {
|
|
1262
|
+
leftOut.push({ path: file.name, reason: "could not be redacted safely" });
|
|
1263
|
+
continue;
|
|
1264
|
+
}
|
|
1265
|
+
const size = Buffer.byteLength(line);
|
|
1266
|
+
if (bytes + size > opts.budget) {
|
|
1267
|
+
leftOut.push({ path: file.name, reason: "over the upload budget" });
|
|
1268
|
+
continue;
|
|
1269
|
+
}
|
|
1270
|
+
bytes += size;
|
|
1271
|
+
lines.push(line);
|
|
1272
|
+
attached.push({ name: file.name, kind: "text", bytes: file.data.length });
|
|
1273
|
+
}
|
|
1007
1274
|
for (const file of candidates) {
|
|
1008
1275
|
let data;
|
|
1009
1276
|
try {
|
|
1010
|
-
data =
|
|
1277
|
+
data = readFileSync5(file.path);
|
|
1011
1278
|
} catch {
|
|
1012
1279
|
leftOut.push({ path: file.path, reason: "missing" });
|
|
1013
1280
|
continue;
|
|
@@ -1018,7 +1285,7 @@ function collectSessionFiles(records, opts) {
|
|
|
1018
1285
|
}
|
|
1019
1286
|
let line;
|
|
1020
1287
|
try {
|
|
1021
|
-
line =
|
|
1288
|
+
line = resourceLine(file.name, file.mediaType, data);
|
|
1022
1289
|
} catch {
|
|
1023
1290
|
leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
|
|
1024
1291
|
continue;
|
|
@@ -1032,7 +1299,13 @@ function collectSessionFiles(records, opts) {
|
|
|
1032
1299
|
lines.push(line);
|
|
1033
1300
|
attached.push({ name: file.name, kind: file.kind, bytes: data.length });
|
|
1034
1301
|
}
|
|
1035
|
-
|
|
1302
|
+
const setup = {
|
|
1303
|
+
skills: skills2.found.map((skill) => skill.name),
|
|
1304
|
+
missingSkills: skills2.missing,
|
|
1305
|
+
mcpServers: state.manifest.mcp_servers.map((server) => server.name),
|
|
1306
|
+
git: state.manifest.git !== null
|
|
1307
|
+
};
|
|
1308
|
+
return { lines, bytes, attached, leftOut, setup };
|
|
1036
1309
|
}
|
|
1037
1310
|
|
|
1038
1311
|
// src/import-local.ts
|
|
@@ -1107,16 +1380,16 @@ function sniffTitle(filePath) {
|
|
|
1107
1380
|
}
|
|
1108
1381
|
}
|
|
1109
1382
|
function listClaudeCodeSessions(limit) {
|
|
1110
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
1111
|
-
const projectsDir =
|
|
1112
|
-
if (!
|
|
1383
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir4(), ".claude");
|
|
1384
|
+
const projectsDir = join4(root, "projects");
|
|
1385
|
+
if (!existsSync5(projectsDir)) return [];
|
|
1113
1386
|
const sessions = [];
|
|
1114
1387
|
try {
|
|
1115
|
-
for (const encoded of
|
|
1116
|
-
const projectDir =
|
|
1388
|
+
for (const encoded of readdirSync2(projectsDir)) {
|
|
1389
|
+
const projectDir = join4(projectsDir, encoded);
|
|
1117
1390
|
let entries;
|
|
1118
1391
|
try {
|
|
1119
|
-
entries =
|
|
1392
|
+
entries = readdirSync2(projectDir);
|
|
1120
1393
|
} catch {
|
|
1121
1394
|
continue;
|
|
1122
1395
|
}
|
|
@@ -1124,22 +1397,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
1124
1397
|
for (const entry2 of entries) {
|
|
1125
1398
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
1126
1399
|
const sessionId = entry2.slice(0, -6);
|
|
1127
|
-
const mainFile =
|
|
1400
|
+
const mainFile = join4(projectDir, entry2);
|
|
1128
1401
|
let stat;
|
|
1129
1402
|
try {
|
|
1130
|
-
stat =
|
|
1403
|
+
stat = statSync3(mainFile);
|
|
1131
1404
|
} catch {
|
|
1132
1405
|
continue;
|
|
1133
1406
|
}
|
|
1134
1407
|
if (!stat.isFile()) continue;
|
|
1135
1408
|
let discoveryError;
|
|
1136
1409
|
const subagentFiles = [];
|
|
1137
|
-
const subagentDir =
|
|
1138
|
-
if (
|
|
1410
|
+
const subagentDir = join4(projectDir, sessionId, "subagents");
|
|
1411
|
+
if (existsSync5(subagentDir)) {
|
|
1139
1412
|
try {
|
|
1140
|
-
const subFiles =
|
|
1413
|
+
const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1141
1414
|
for (const f of subFiles) {
|
|
1142
|
-
subagentFiles.push(
|
|
1415
|
+
subagentFiles.push(join4(subagentDir, f));
|
|
1143
1416
|
}
|
|
1144
1417
|
} catch {
|
|
1145
1418
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -1161,17 +1434,17 @@ function listClaudeCodeSessions(limit) {
|
|
|
1161
1434
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1162
1435
|
}
|
|
1163
1436
|
function listCodexSessions(limit) {
|
|
1164
|
-
const sessionsDir =
|
|
1437
|
+
const sessionsDir = join4(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join4(homedir4(), ".codex"), "sessions");
|
|
1165
1438
|
const records = [];
|
|
1166
1439
|
function walk(dir) {
|
|
1167
1440
|
let entries;
|
|
1168
1441
|
try {
|
|
1169
|
-
entries =
|
|
1442
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1170
1443
|
} catch {
|
|
1171
1444
|
return;
|
|
1172
1445
|
}
|
|
1173
1446
|
for (const entry2 of entries) {
|
|
1174
|
-
const full =
|
|
1447
|
+
const full = join4(dir, entry2.name);
|
|
1175
1448
|
if (entry2.isDirectory()) {
|
|
1176
1449
|
walk(full);
|
|
1177
1450
|
continue;
|
|
@@ -1189,7 +1462,7 @@ function listCodexSessions(limit) {
|
|
|
1189
1462
|
sessionId: data.id,
|
|
1190
1463
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
1191
1464
|
title: sniffTitle(full),
|
|
1192
|
-
mtimeMs:
|
|
1465
|
+
mtimeMs: statSync3(full).mtimeMs,
|
|
1193
1466
|
files: [full]
|
|
1194
1467
|
},
|
|
1195
1468
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -1258,7 +1531,7 @@ async function mintTrace(base, harness2) {
|
|
|
1258
1531
|
function loadSummaryFile(summaryPath) {
|
|
1259
1532
|
let raw;
|
|
1260
1533
|
try {
|
|
1261
|
-
raw =
|
|
1534
|
+
raw = readFileSync6(summaryPath, "utf8");
|
|
1262
1535
|
} catch {
|
|
1263
1536
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1264
1537
|
}
|
|
@@ -1272,8 +1545,8 @@ function loadSummaryFile(summaryPath) {
|
|
|
1272
1545
|
const p = pkg;
|
|
1273
1546
|
if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
|
|
1274
1547
|
if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
|
|
1275
|
-
const
|
|
1276
|
-
return
|
|
1548
|
+
const redacted2 = redactLine2(raw.trimEnd());
|
|
1549
|
+
return redacted2;
|
|
1277
1550
|
}
|
|
1278
1551
|
var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
|
|
1279
1552
|
var SUMMARY_RESERVE_BYTES = 128 * 1024;
|
|
@@ -1299,9 +1572,9 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1299
1572
|
const source = new SessionSourceCollector();
|
|
1300
1573
|
const records = [];
|
|
1301
1574
|
for (const path of session2.files) {
|
|
1302
|
-
const stat =
|
|
1575
|
+
const stat = statSync3(path);
|
|
1303
1576
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
1304
|
-
const text =
|
|
1577
|
+
const text = readFileSync6(path, "utf8");
|
|
1305
1578
|
for (const raw of text.split("\n")) {
|
|
1306
1579
|
if (!raw.trim()) continue;
|
|
1307
1580
|
let record;
|
|
@@ -1340,8 +1613,8 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1340
1613
|
const sourceId = nativeId ?? session2.sessionId;
|
|
1341
1614
|
if (!/^[A-Za-z0-9_-]{1,200}$/.test(sourceId) || redactLine2(JSON.stringify(sourceId)) !== JSON.stringify(sourceId)) throw new Error("The source conversation has an invalid identifier.");
|
|
1342
1615
|
const meta = source.result();
|
|
1343
|
-
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [] } : collectSessionFiles(records, { harness: session2.harness, cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""), budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES, exclude: session2.files });
|
|
1344
|
-
const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under are attached as well (listed in artifacts).` : "";
|
|
1616
|
+
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : collectSessionFiles(records, { harness: session2.harness, cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""), budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES, exclude: session2.files });
|
|
1617
|
+
const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under, the skills it loaded and its workspace description (repository state and tool servers, agentsky/workspace.json) are attached as well (listed in artifacts).` : "";
|
|
1345
1618
|
let summaryJson;
|
|
1346
1619
|
if (summaryPath) {
|
|
1347
1620
|
summaryJson = loadSummaryFile(summaryPath);
|
|
@@ -1361,7 +1634,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1361
1634
|
completed: [],
|
|
1362
1635
|
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1363
1636
|
verification: [],
|
|
1364
|
-
blockers: [leftOutLine(files2.leftOut)],
|
|
1637
|
+
blockers: [leftOutLine(files2.leftOut), ...files2.setup.missingSkills.length ? [`Skills it loaded that are not on this computer any more: ${files2.setup.missingSkills.slice(0, 20).join(", ")}.`] : []],
|
|
1365
1638
|
artifacts: files2.attached.slice(0, MAX_LISTED_ARTIFACTS).map((file) => ({ path: file.name, description: `Attached from the local ${file.kind === "text" ? "workspace" : file.kind} the conversation used (${file.bytes} bytes).` })),
|
|
1366
1639
|
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1367
1640
|
}
|
|
@@ -1373,7 +1646,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1373
1646
|
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1374
1647
|
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1375
1648
|
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1376
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
|
|
1649
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
|
|
1377
1650
|
}
|
|
1378
1651
|
async function importSession(opts) {
|
|
1379
1652
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1382,7 +1655,7 @@ async function importSession(opts) {
|
|
|
1382
1655
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1383
1656
|
}
|
|
1384
1657
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1385
|
-
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1658
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1386
1659
|
let traceId;
|
|
1387
1660
|
let key;
|
|
1388
1661
|
if (opts.importKey && opts.grantTraceId) {
|
|
@@ -1416,7 +1689,8 @@ async function importSession(opts) {
|
|
|
1416
1689
|
redactionCount,
|
|
1417
1690
|
gaps,
|
|
1418
1691
|
attachedFiles,
|
|
1419
|
-
leftOutFiles
|
|
1692
|
+
leftOutFiles,
|
|
1693
|
+
setup
|
|
1420
1694
|
};
|
|
1421
1695
|
}
|
|
1422
1696
|
|
|
@@ -1520,23 +1794,23 @@ async function headlessLogin(verifier, challenge) {
|
|
|
1520
1794
|
}
|
|
1521
1795
|
|
|
1522
1796
|
// src/local-agents.ts
|
|
1523
|
-
import { existsSync as
|
|
1524
|
-
import { homedir as
|
|
1525
|
-
import { join as
|
|
1526
|
-
function localAgents(env = process.env, home =
|
|
1527
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
1528
|
-
const codex = env.CODEX_HOME ||
|
|
1529
|
-
const cursor =
|
|
1797
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1798
|
+
import { homedir as homedir5 } from "os";
|
|
1799
|
+
import { join as join5 } from "path";
|
|
1800
|
+
function localAgents(env = process.env, home = homedir5()) {
|
|
1801
|
+
const claude = env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
|
|
1802
|
+
const codex = env.CODEX_HOME || join5(home, ".codex");
|
|
1803
|
+
const cursor = join5(home, ".cursor");
|
|
1530
1804
|
return [
|
|
1531
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir:
|
|
1532
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
1533
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir:
|
|
1805
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join5(claude, "skills") },
|
|
1806
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join5(codex, "skills") },
|
|
1807
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join5(cursor, "skills") }
|
|
1534
1808
|
];
|
|
1535
1809
|
}
|
|
1536
|
-
function detectLocalAgents(env = process.env, home =
|
|
1537
|
-
return localAgents(env, home).filter((agent2) =>
|
|
1810
|
+
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
1811
|
+
return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
|
|
1538
1812
|
}
|
|
1539
|
-
function selectLocalAgents(ids, env = process.env, home =
|
|
1813
|
+
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
1540
1814
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
1541
1815
|
const known = localAgents(env, home);
|
|
1542
1816
|
return ids.map((id) => {
|
|
@@ -1635,16 +1909,16 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
1635
1909
|
}
|
|
1636
1910
|
|
|
1637
1911
|
// src/setup.ts
|
|
1638
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1639
|
-
import { dirname as
|
|
1912
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
1913
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
1640
1914
|
import { createInterface as createInterface3 } from "readline";
|
|
1641
1915
|
|
|
1642
1916
|
// src/mcp-register.ts
|
|
1643
|
-
import { spawnSync } from "child_process";
|
|
1917
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1644
1918
|
|
|
1645
1919
|
// src/skill.ts
|
|
1646
|
-
import { mkdirSync, readFileSync as
|
|
1647
|
-
import { join as
|
|
1920
|
+
import { mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1921
|
+
import { join as join6 } from "path";
|
|
1648
1922
|
|
|
1649
1923
|
// src/catalog-file.ts
|
|
1650
1924
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -1661,8 +1935,8 @@ function entry(agent2) {
|
|
|
1661
1935
|
const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
|
|
1662
1936
|
if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
|
|
1663
1937
|
if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
|
|
1664
|
-
const { mcp_servers:
|
|
1665
|
-
if (typeof
|
|
1938
|
+
const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
|
|
1939
|
+
if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
|
|
1666
1940
|
const details = describe(rest);
|
|
1667
1941
|
if (details) lines.push(`Details: ${details}`);
|
|
1668
1942
|
const pricing = describe(agent2.pricing);
|
|
@@ -2085,7 +2359,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
2085
2359
|
}
|
|
2086
2360
|
function installedVersion(dir) {
|
|
2087
2361
|
try {
|
|
2088
|
-
const marker = JSON.parse(
|
|
2362
|
+
const marker = JSON.parse(readFileSync7(join6(dir, SKILL_MARKER), "utf8"));
|
|
2089
2363
|
return typeof marker.version === "string" ? marker.version : null;
|
|
2090
2364
|
} catch {
|
|
2091
2365
|
return null;
|
|
@@ -2095,25 +2369,25 @@ function isInstalledSkill(dir) {
|
|
|
2095
2369
|
return installedVersion(dir) !== null;
|
|
2096
2370
|
}
|
|
2097
2371
|
function installSkill(agent2, opts = {}) {
|
|
2098
|
-
const dir =
|
|
2372
|
+
const dir = join6(agent2.skillsDir, SKILL_NAME);
|
|
2099
2373
|
const previous = installedVersion(dir);
|
|
2100
2374
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
2101
2375
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
2102
2376
|
if (outcome !== "current") {
|
|
2103
2377
|
mkdirSync(dir, { recursive: true });
|
|
2104
|
-
writeFileSync3(
|
|
2105
|
-
writeFileSync3(
|
|
2378
|
+
writeFileSync3(join6(dir, "SKILL.md"), skillDocument());
|
|
2379
|
+
writeFileSync3(join6(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
2106
2380
|
`);
|
|
2107
2381
|
}
|
|
2108
2382
|
if (opts.catalog) {
|
|
2109
2383
|
mkdirSync(dir, { recursive: true });
|
|
2110
|
-
writeFileSync3(
|
|
2384
|
+
writeFileSync3(join6(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2111
2385
|
}
|
|
2112
|
-
const refsDir =
|
|
2386
|
+
const refsDir = join6(dir, REFERENCES_DIR);
|
|
2113
2387
|
mkdirSync(refsDir, { recursive: true });
|
|
2114
|
-
writeFileSync3(
|
|
2115
|
-
writeFileSync3(
|
|
2116
|
-
writeFileSync3(
|
|
2388
|
+
writeFileSync3(join6(refsDir, "import.md"), importReferenceDoc());
|
|
2389
|
+
writeFileSync3(join6(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2390
|
+
writeFileSync3(join6(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
2117
2391
|
return { agent: agent2.id, path: dir, outcome };
|
|
2118
2392
|
}
|
|
2119
2393
|
|
|
@@ -2122,9 +2396,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
|
|
|
2122
2396
|
var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
2123
2397
|
function systemCommandRunner() {
|
|
2124
2398
|
return {
|
|
2125
|
-
has: (command) =>
|
|
2399
|
+
has: (command) => spawnSync2(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
2126
2400
|
run: (command, args) => {
|
|
2127
|
-
const result =
|
|
2401
|
+
const result = spawnSync2(command, args, { encoding: "utf8" });
|
|
2128
2402
|
return { status: result.status, stderr: result.stderr ?? "" };
|
|
2129
2403
|
}
|
|
2130
2404
|
};
|
|
@@ -2164,10 +2438,11 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
2164
2438
|
}
|
|
2165
2439
|
|
|
2166
2440
|
// src/setup-local.ts
|
|
2167
|
-
import {
|
|
2168
|
-
import {
|
|
2169
|
-
import {
|
|
2170
|
-
import {
|
|
2441
|
+
import { isUtf8 as isUtf82 } from "buffer";
|
|
2442
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
2443
|
+
import { homedir as homedir6 } from "os";
|
|
2444
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
2445
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
2171
2446
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
2172
2447
|
var MAX_IMPORT_DEPTH = 5;
|
|
2173
2448
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
@@ -2179,14 +2454,24 @@ function frontmatterName(skillMd) {
|
|
|
2179
2454
|
function sensitive(name) {
|
|
2180
2455
|
return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
|
|
2181
2456
|
}
|
|
2182
|
-
function
|
|
2457
|
+
function redacted(data) {
|
|
2458
|
+
if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
|
|
2459
|
+
try {
|
|
2460
|
+
const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
|
|
2461
|
+
return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
|
|
2462
|
+
} catch {
|
|
2463
|
+
return null;
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
function skillFiles2(root, prefix) {
|
|
2183
2467
|
const files2 = [];
|
|
2184
|
-
for (const entry2 of
|
|
2185
|
-
const path =
|
|
2468
|
+
for (const entry2 of readdirSync3(root, { withFileTypes: true })) {
|
|
2469
|
+
const path = join7(root, entry2.name);
|
|
2186
2470
|
if (entry2.isDirectory()) {
|
|
2187
|
-
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...
|
|
2188
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
2189
|
-
|
|
2471
|
+
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
2472
|
+
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync4(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
2473
|
+
const bytes = redacted(readFileSync8(path));
|
|
2474
|
+
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
2190
2475
|
}
|
|
2191
2476
|
}
|
|
2192
2477
|
return files2;
|
|
@@ -2195,19 +2480,19 @@ function readLocalSkills(agents) {
|
|
|
2195
2480
|
const skills2 = [];
|
|
2196
2481
|
const ignored = [];
|
|
2197
2482
|
for (const agent2 of agents) {
|
|
2198
|
-
if (!
|
|
2199
|
-
for (const entry2 of
|
|
2200
|
-
const dir =
|
|
2201
|
-
let
|
|
2483
|
+
if (!existsSync7(agent2.skillsDir)) continue;
|
|
2484
|
+
for (const entry2 of readdirSync3(agent2.skillsDir, { withFileTypes: true })) {
|
|
2485
|
+
const dir = join7(agent2.skillsDir, entry2.name);
|
|
2486
|
+
let isDir2 = entry2.isDirectory();
|
|
2202
2487
|
if (entry2.isSymbolicLink()) {
|
|
2203
2488
|
try {
|
|
2204
|
-
|
|
2489
|
+
isDir2 = statSync4(dir).isDirectory();
|
|
2205
2490
|
} catch {
|
|
2206
|
-
|
|
2491
|
+
isDir2 = false;
|
|
2207
2492
|
}
|
|
2208
2493
|
}
|
|
2209
|
-
if (!
|
|
2210
|
-
const name = frontmatterName(
|
|
2494
|
+
if (!isDir2 || !existsSync7(join7(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
2495
|
+
const name = frontmatterName(readFileSync8(join7(dir, "SKILL.md"), "utf8"));
|
|
2211
2496
|
if (!name) {
|
|
2212
2497
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
2213
2498
|
continue;
|
|
@@ -2217,7 +2502,7 @@ function readLocalSkills(agents) {
|
|
|
2217
2502
|
ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
|
|
2218
2503
|
continue;
|
|
2219
2504
|
}
|
|
2220
|
-
const files2 =
|
|
2505
|
+
const files2 = skillFiles2(dir, entry2.name);
|
|
2221
2506
|
skills2.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
|
|
2222
2507
|
}
|
|
2223
2508
|
}
|
|
@@ -2235,11 +2520,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2235
2520
|
}
|
|
2236
2521
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
2237
2522
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
2238
|
-
const path = target.startsWith("~/") ?
|
|
2523
|
+
const path = target.startsWith("~/") ? join7(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname4(from), target);
|
|
2239
2524
|
if (seen.includes(path)) return "";
|
|
2240
2525
|
let imported;
|
|
2241
2526
|
try {
|
|
2242
|
-
imported =
|
|
2527
|
+
imported = readFileSync8(path, "utf8");
|
|
2243
2528
|
} catch {
|
|
2244
2529
|
return line;
|
|
2245
2530
|
}
|
|
@@ -2247,15 +2532,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2247
2532
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
2248
2533
|
}).join("\n");
|
|
2249
2534
|
}
|
|
2250
|
-
function readGlobalInstructions(agents, home =
|
|
2535
|
+
function readGlobalInstructions(agents, home = homedir6()) {
|
|
2251
2536
|
const found = [];
|
|
2252
2537
|
for (const agent2 of agents) {
|
|
2253
2538
|
const file = INSTRUCTION_FILES2[agent2.id];
|
|
2254
2539
|
if (!file) continue;
|
|
2255
|
-
const path =
|
|
2540
|
+
const path = join7(agent2.root, file);
|
|
2256
2541
|
let raw;
|
|
2257
2542
|
try {
|
|
2258
|
-
raw =
|
|
2543
|
+
raw = readFileSync8(path, "utf8");
|
|
2259
2544
|
} catch {
|
|
2260
2545
|
continue;
|
|
2261
2546
|
}
|
|
@@ -2272,9 +2557,9 @@ function combineInstructions(found) {
|
|
|
2272
2557
|
|
|
2273
2558
|
${item.content.trim()}`).join("\n\n");
|
|
2274
2559
|
}
|
|
2275
|
-
function
|
|
2560
|
+
function readJson3(path) {
|
|
2276
2561
|
try {
|
|
2277
|
-
return JSON.parse(
|
|
2562
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
2278
2563
|
} catch {
|
|
2279
2564
|
return void 0;
|
|
2280
2565
|
}
|
|
@@ -2288,15 +2573,15 @@ function bearerOf(def) {
|
|
|
2288
2573
|
return match?.[1] ?? null;
|
|
2289
2574
|
}
|
|
2290
2575
|
function serversOf(agent2, home) {
|
|
2291
|
-
if (agent2.id === "claude_code") return
|
|
2292
|
-
if (agent2.id === "cursor") return
|
|
2576
|
+
if (agent2.id === "claude_code") return readJson3(join7(home, ".claude.json"))?.mcpServers ?? {};
|
|
2577
|
+
if (agent2.id === "cursor") return readJson3(join7(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
2293
2578
|
try {
|
|
2294
|
-
return
|
|
2579
|
+
return parseToml3(readFileSync8(join7(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
2295
2580
|
} catch {
|
|
2296
2581
|
return {};
|
|
2297
2582
|
}
|
|
2298
2583
|
}
|
|
2299
|
-
function findLocalSecrets(agents, home =
|
|
2584
|
+
function findLocalSecrets(agents, home = homedir6()) {
|
|
2300
2585
|
const secrets2 = [];
|
|
2301
2586
|
for (const agent2 of agents) {
|
|
2302
2587
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
@@ -2312,9 +2597,9 @@ function findLocalSecrets(agents, home = homedir5()) {
|
|
|
2312
2597
|
|
|
2313
2598
|
// src/subscriptions.ts
|
|
2314
2599
|
import { spawn as spawn3 } from "child_process";
|
|
2315
|
-
import { existsSync as
|
|
2316
|
-
import { homedir as
|
|
2317
|
-
import { join as
|
|
2600
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
2601
|
+
import { homedir as homedir7 } from "os";
|
|
2602
|
+
import { join as join8 } from "path";
|
|
2318
2603
|
import { createInterface as createInterface2 } from "readline";
|
|
2319
2604
|
var ALIASES = {
|
|
2320
2605
|
claude: "anthropic",
|
|
@@ -2399,12 +2684,12 @@ async function connectClaude() {
|
|
|
2399
2684
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
2400
2685
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
2401
2686
|
}
|
|
2402
|
-
function codexSignIn(env = process.env, home =
|
|
2403
|
-
const path =
|
|
2404
|
-
if (!
|
|
2687
|
+
function codexSignIn(env = process.env, home = homedir7()) {
|
|
2688
|
+
const path = join8(env.CODEX_HOME || join8(home, ".codex"), "auth.json");
|
|
2689
|
+
if (!existsSync8(path)) {
|
|
2405
2690
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
2406
2691
|
}
|
|
2407
|
-
const raw =
|
|
2692
|
+
const raw = readFileSync9(path, "utf8");
|
|
2408
2693
|
try {
|
|
2409
2694
|
const parsed = JSON.parse(raw);
|
|
2410
2695
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -2412,12 +2697,12 @@ function codexSignIn(env = process.env, home = homedir6()) {
|
|
|
2412
2697
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
2413
2698
|
}
|
|
2414
2699
|
}
|
|
2415
|
-
function cursorSignIn(env = process.env, home =
|
|
2416
|
-
const path =
|
|
2417
|
-
if (!
|
|
2700
|
+
function cursorSignIn(env = process.env, home = homedir7()) {
|
|
2701
|
+
const path = join8(env.XDG_CONFIG_HOME || join8(home, ".config"), "cursor", "auth.json");
|
|
2702
|
+
if (!existsSync8(path)) {
|
|
2418
2703
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
2419
2704
|
}
|
|
2420
|
-
const raw =
|
|
2705
|
+
const raw = readFileSync9(path, "utf8");
|
|
2421
2706
|
try {
|
|
2422
2707
|
const parsed = JSON.parse(raw);
|
|
2423
2708
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -2487,20 +2772,20 @@ async function disconnect(name, opts) {
|
|
|
2487
2772
|
// src/setup.ts
|
|
2488
2773
|
var SETUP_STEPS = ["skill", "logins", "import", "secrets"];
|
|
2489
2774
|
var PLAN_PROVIDERS = { claude: "anthropic", anthropic: "anthropic", chatgpt: "openai", codex: "openai", openai: "openai", cursor: "cursor" };
|
|
2490
|
-
function planProviders(
|
|
2491
|
-
if (!
|
|
2492
|
-
return new Set(
|
|
2775
|
+
function planProviders(names2) {
|
|
2776
|
+
if (!names2?.length) return null;
|
|
2777
|
+
return new Set(names2.map((name) => {
|
|
2493
2778
|
const provider = PLAN_PROVIDERS[name.toLowerCase()];
|
|
2494
2779
|
if (!provider) throw new ApiError(400, "unknown_provider", `unknown plan "${name}" \u2014 use claude, chatgpt or cursor`);
|
|
2495
2780
|
return provider;
|
|
2496
2781
|
}));
|
|
2497
2782
|
}
|
|
2498
2783
|
function reportPath() {
|
|
2499
|
-
return
|
|
2784
|
+
return join9(dirname5(configPath()), "setup.json");
|
|
2500
2785
|
}
|
|
2501
2786
|
function loadReport() {
|
|
2502
2787
|
try {
|
|
2503
|
-
const saved = JSON.parse(
|
|
2788
|
+
const saved = JSON.parse(readFileSync10(reportPath(), "utf8"));
|
|
2504
2789
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
2505
2790
|
} catch {
|
|
2506
2791
|
return { agents: [], plans: [] };
|
|
@@ -2513,7 +2798,7 @@ function mergeReport(base, next) {
|
|
|
2513
2798
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
2514
2799
|
}
|
|
2515
2800
|
function saveReport(report) {
|
|
2516
|
-
mkdirSync2(
|
|
2801
|
+
mkdirSync2(dirname5(reportPath()), { recursive: true });
|
|
2517
2802
|
writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
2518
2803
|
`, { mode: 384 });
|
|
2519
2804
|
}
|
|
@@ -2863,17 +3148,17 @@ function renderArtifact(artifact2) {
|
|
|
2863
3148
|
}
|
|
2864
3149
|
|
|
2865
3150
|
// src/skills-store.ts
|
|
2866
|
-
import { readdirSync as
|
|
2867
|
-
import { basename as
|
|
3151
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
|
|
3152
|
+
import { basename as basename5, join as join10, relative as relative3, resolve as resolve5 } from "path";
|
|
2868
3153
|
function skillBundle(dir) {
|
|
2869
3154
|
const root = resolve5(dir);
|
|
2870
|
-
const top =
|
|
3155
|
+
const top = basename5(root);
|
|
2871
3156
|
const files2 = [];
|
|
2872
3157
|
const walk = (path) => {
|
|
2873
|
-
for (const name of
|
|
2874
|
-
const full =
|
|
2875
|
-
if (
|
|
2876
|
-
else files2.push({ path: `${top}/${
|
|
3158
|
+
for (const name of readdirSync4(path).sort()) {
|
|
3159
|
+
const full = join10(path, name);
|
|
3160
|
+
if (statSync5(full).isDirectory()) walk(full);
|
|
3161
|
+
else files2.push({ path: `${top}/${relative3(root, full)}`, bytes: readFileSync11(full) });
|
|
2877
3162
|
}
|
|
2878
3163
|
};
|
|
2879
3164
|
walk(root);
|
|
@@ -2883,7 +3168,7 @@ function skillBundle(dir) {
|
|
|
2883
3168
|
async function uploadSkill(dir, displayName) {
|
|
2884
3169
|
const form = new FormData();
|
|
2885
3170
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
2886
|
-
form.append("displayName", displayName ??
|
|
3171
|
+
form.append("displayName", displayName ?? basename5(resolve5(dir)));
|
|
2887
3172
|
return apiForm("POST", "/skills", form);
|
|
2888
3173
|
}
|
|
2889
3174
|
async function listSkills() {
|
|
@@ -3001,19 +3286,19 @@ function renderPolicy(rule) {
|
|
|
3001
3286
|
}
|
|
3002
3287
|
|
|
3003
3288
|
// src/harness.ts
|
|
3004
|
-
import { existsSync as
|
|
3005
|
-
import { dirname as
|
|
3289
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
3290
|
+
import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
|
|
3006
3291
|
var HARNESS_FILE = "harness.json";
|
|
3007
3292
|
function harnessFilePath(dir) {
|
|
3008
|
-
return
|
|
3293
|
+
return join11(resolve6(dir), HARNESS_FILE);
|
|
3009
3294
|
}
|
|
3010
3295
|
function readHarnessFile(dir) {
|
|
3011
3296
|
const path = harnessFilePath(dir);
|
|
3012
|
-
if (!
|
|
3297
|
+
if (!existsSync9(path)) {
|
|
3013
3298
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
|
|
3014
3299
|
}
|
|
3015
3300
|
try {
|
|
3016
|
-
return JSON.parse(
|
|
3301
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3017
3302
|
} catch (err) {
|
|
3018
3303
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3019
3304
|
}
|
|
@@ -3040,10 +3325,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
3040
3325
|
`;
|
|
3041
3326
|
function harnessInit(dir) {
|
|
3042
3327
|
const path = harnessFilePath(dir);
|
|
3043
|
-
if (
|
|
3328
|
+
if (existsSync9(path)) {
|
|
3044
3329
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
3045
3330
|
}
|
|
3046
|
-
mkdirSync3(
|
|
3331
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
3047
3332
|
writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
3048
3333
|
`);
|
|
3049
3334
|
console.log(`Created ${path}`);
|
|
@@ -3815,8 +4100,8 @@ secrets.command("unset <id> <key>").action(
|
|
|
3815
4100
|
program.command("launch").description("scaffold ./agent.toml if absent, create the agent, print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").action(
|
|
3816
4101
|
run(async (opts) => {
|
|
3817
4102
|
const path = resolve7(opts.file);
|
|
3818
|
-
if (!
|
|
3819
|
-
const name =
|
|
4103
|
+
if (!existsSync10(path)) {
|
|
4104
|
+
const name = basename6(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
3820
4105
|
scaffoldAgentFile(path, name);
|
|
3821
4106
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
3822
4107
|
}
|
|
@@ -3890,7 +4175,7 @@ function formatRelative(msAgo) {
|
|
|
3890
4175
|
if (hours < 48) return `${hours}h ago`;
|
|
3891
4176
|
return `${Math.round(hours / 24)}d ago`;
|
|
3892
4177
|
}
|
|
3893
|
-
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").option("--no-files", "send the conversation only, without
|
|
4178
|
+
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--current", "import the session this agent is running in (reads CLAUDE_CODE_SESSION_ID or CODEX_THREAD_ID, then falls back to most recent session in the cwd)").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer without prompting (default when --current or --session is given in non-TTY)").option("--json", "print one JSON result object per import and nothing else to stdout").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--summary <path>", "attach a pre-written summary JSON (asteroids.session-summary v1) instead of the auto-generated one").option("--key <importKey>", "import grant key minted by the web page (skips sky auth login)").option("--trace <traceId>", "trace id paired with --key").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").option("--no-files", "send the conversation only, without its local files, skills, repository state or tool-server setup").action(
|
|
3894
4179
|
run(
|
|
3895
4180
|
async (opts) => {
|
|
3896
4181
|
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
@@ -3972,7 +4257,7 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3972
4257
|
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
3973
4258
|
if (!opts.json) {
|
|
3974
4259
|
console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
|
|
3975
|
-
console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation
|
|
4260
|
+
console.log(opts.files === false ? "The recorded conversation is redacted locally. Local project files and login credentials are not transferred." : "The recorded conversation, the local files it read, wrote or ran under, the skills it loaded, its repository state and the names of its tool servers are attached; text is redacted locally. Credential files, tool-server keys and login credentials are never transferred. Use --no-files to send the conversation only.");
|
|
3976
4261
|
}
|
|
3977
4262
|
if (!autoConfirm) {
|
|
3978
4263
|
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
@@ -4014,9 +4299,13 @@ Importing ${label} \u2026
|
|
|
4014
4299
|
gaps: result.gaps,
|
|
4015
4300
|
attachedFiles: result.attachedFiles,
|
|
4016
4301
|
leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
|
|
4302
|
+
skills: result.setup.skills,
|
|
4303
|
+
missingSkills: result.setup.missingSkills,
|
|
4304
|
+
mcpServers: result.setup.mcpServers,
|
|
4017
4305
|
continueUrl: result.url
|
|
4018
4306
|
}));
|
|
4019
4307
|
} else {
|
|
4308
|
+
if (result.setup.skills.length || result.setup.mcpServers.length) console.log(` Carried ${result.setup.skills.length} ${result.setup.skills.length === 1 ? "skill" : "skills"} and the setup of ${result.setup.mcpServers.length} tool ${result.setup.mcpServers.length === 1 ? "server" : "servers"} (their keys stay on this computer).`);
|
|
4020
4309
|
if (result.attachedFiles.length || result.leftOutFiles.length) console.log(` Attached ${result.attachedFiles.length} local ${result.attachedFiles.length === 1 ? "file" : "files"}${result.leftOutFiles.length ? `; left out ${result.leftOutFiles.length} (credential, missing, unsupported or over the 30 MiB upload)` : ""}.`);
|
|
4021
4310
|
console.log(` \u2713 Imported. Continue here:
|
|
4022
4311
|
${result.url}
|
|
@@ -4084,7 +4373,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
4084
4373
|
);
|
|
4085
4374
|
task.command("put <task> <path> <local>").description("write a local file to <path> in the task's files (every session of the task sees it)").action(
|
|
4086
4375
|
run(async (taskId, path, local) => {
|
|
4087
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
4376
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync13(local), mediaTypeFor(local));
|
|
4088
4377
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
4089
4378
|
})
|
|
4090
4379
|
);
|
|
@@ -4520,7 +4809,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
4520
4809
|
}));
|
|
4521
4810
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
4522
4811
|
traceCmd.command("start").description("start a trace: a grant for this machine, a root span, and the spool the hooks write to").option("--name <name>", "a name for the task").option("--source <source>", "how this machine reports itself", "claude-code").option("--hooks [settings]", "also install the Claude Code hooks (default: ./.claude/settings.local.json)").option("--capture <scopes>", "also send content, redacted on this machine first: any of prompts,tool_output,files").option("--file <path>", "a file the files scope may send (repeatable)", collect).option("--dir <path>", "a folder whose files the files scope may send when a file tool touches them (repeatable)", collect).action(run(async (opts) => {
|
|
4523
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
4812
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-YKEGRVJN.js");
|
|
4524
4813
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
4525
4814
|
const scopes = parseContentScopes(opts.capture);
|
|
4526
4815
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -4555,7 +4844,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
4555
4844
|
}));
|
|
4556
4845
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
4557
4846
|
try {
|
|
4558
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
4847
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-YKEGRVJN.js");
|
|
4559
4848
|
const chunks = [];
|
|
4560
4849
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4561
4850
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -4568,14 +4857,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
4568
4857
|
}
|
|
4569
4858
|
});
|
|
4570
4859
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
4571
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
4860
|
+
const { readState, flush, grantSender } = await import("./trace-YKEGRVJN.js");
|
|
4572
4861
|
const state = readState();
|
|
4573
4862
|
if (!state) throw new Error("no trace is running here");
|
|
4574
4863
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
4575
4864
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
4576
4865
|
}));
|
|
4577
4866
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
4578
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
4867
|
+
const { readState, spoolStatus } = await import("./trace-YKEGRVJN.js");
|
|
4579
4868
|
const state = readState();
|
|
4580
4869
|
if (!state) {
|
|
4581
4870
|
console.log("No trace is running here.");
|
|
@@ -4585,7 +4874,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
4585
4874
|
console.log(`${state.trace_id} as ${state.source}: ${spool.pending} pending, ${spool.dropped_full} dropped (spool full), ${spool.dropped_expired} given up, ${spool.refused} refused`);
|
|
4586
4875
|
}));
|
|
4587
4876
|
traceCmd.command("show [id]").description("the trace bundle: spans, the sessions they reach, cost counted once per session").option("--json", "print the bundle as JSON").action(run(async (id, opts) => {
|
|
4588
|
-
const { readState } = await import("./trace-
|
|
4877
|
+
const { readState } = await import("./trace-YKEGRVJN.js");
|
|
4589
4878
|
const ref = id ?? readState()?.trace_id;
|
|
4590
4879
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
4591
4880
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -4612,7 +4901,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
4612
4901
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
4613
4902
|
}));
|
|
4614
4903
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
4615
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
4904
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-YKEGRVJN.js");
|
|
4616
4905
|
const state = readState();
|
|
4617
4906
|
if (!state) throw new Error("no trace is running here");
|
|
4618
4907
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -4622,7 +4911,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
4622
4911
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
4623
4912
|
}));
|
|
4624
4913
|
traceCmd.command("sync [payload]").description("upload this machine's Claude Code and Codex sessions to the matching workspace's trace library, redacted here first").option("--enable", "turn sync on for this machine").option("--disable", "turn sync off; nothing more is uploaded").option("--route <match=workspace>", "send sessions whose folder path contains <match> to <workspace> (repeatable; replaces the saved rules)", collect).option("--default <workspace>", "where a session no rule matches goes").option("--keep-local", "keep sessions no rule matches on this machine (the default)").option("--session <ref>", "only this session: its id or transcript path (repeatable)", collect).option("--full", "re-read every session, not only files that changed").option("--limit <n>", "at most this many changed sessions this run", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("--hook", "run from a Claude Code SessionEnd hook or Codex notify: upload that session in the background and return at once").option("--install-schedule", "also sweep every 15 minutes (macOS launchd)").option("--status", "what sync has done on this machine").option("--quiet", "print only failures").action(async (payload, opts) => {
|
|
4625
|
-
const lib = await import("./trace-library-
|
|
4914
|
+
const lib = await import("./trace-library-6BK5GJVD.js");
|
|
4626
4915
|
if (opts.hook) {
|
|
4627
4916
|
try {
|
|
4628
4917
|
let raw = payload ?? "";
|
|
@@ -4670,10 +4959,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4670
4959
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
4671
4960
|
}
|
|
4672
4961
|
if (opts.installSchedule) {
|
|
4673
|
-
const { join:
|
|
4674
|
-
const { homedir:
|
|
4962
|
+
const { join: join12 } = await import("path");
|
|
4963
|
+
const { homedir: homedir8 } = await import("os");
|
|
4675
4964
|
const { execFileSync } = await import("child_process");
|
|
4676
|
-
const log =
|
|
4965
|
+
const log = join12(homedir8(), ".config", "sky", "trace-sync.log");
|
|
4677
4966
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
4678
4967
|
try {
|
|
4679
4968
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -4683,11 +4972,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4683
4972
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
4684
4973
|
}
|
|
4685
4974
|
if (opts.status) {
|
|
4686
|
-
const { readFileSync:
|
|
4687
|
-
const { join:
|
|
4975
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
4976
|
+
const { join: join12 } = await import("path");
|
|
4688
4977
|
let state = { sessions: {} };
|
|
4689
4978
|
try {
|
|
4690
|
-
state = JSON.parse(
|
|
4979
|
+
state = JSON.parse(readFileSync14(join12(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
4691
4980
|
} catch {
|
|
4692
4981
|
}
|
|
4693
4982
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -4720,7 +5009,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4720
5009
|
})();
|
|
4721
5010
|
});
|
|
4722
5011
|
traceCmd.command("search <query...>").description("search every session in a workspace's trace library (literal substring)").option("--workspace <slug>", "the workspace to search (default: the current one)").option("--limit <n>", "hits per page", (v) => Number.parseInt(v, 10), 20).option("--cursor <cursor>", "the next page of an earlier search").option("--json", "print the page as JSON").action(run(async (words, opts) => {
|
|
4723
|
-
const { libraryRequest } = await import("./trace-library-
|
|
5012
|
+
const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
|
|
4724
5013
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4725
5014
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4726
5015
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -4737,7 +5026,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
4737
5026
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
4738
5027
|
}));
|
|
4739
5028
|
traceCmd.command("library").description("list the sessions in a workspace's trace library, most recently updated first").option("--workspace <slug>", "the workspace (default: the current one)").option("--limit <n>", "how many", (v) => Number.parseInt(v, 10), 20).option("--json", "print the page as JSON").action(run(async (opts) => {
|
|
4740
|
-
const { libraryRequest } = await import("./trace-library-
|
|
5029
|
+
const { libraryRequest } = await import("./trace-library-6BK5GJVD.js");
|
|
4741
5030
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4742
5031
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4743
5032
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|