@agentsky/sky 2.13.0 → 2.15.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-Z3FPPGWX.js → chunk-R5FKQQMG.js} +1 -1
- package/dist/{chunk-2IKEJ74C.js → chunk-RHRREQMF.js} +15 -4
- package/dist/{chunk-XJ5JLLXM.js → chunk-Y3NTKJLA.js} +2 -2
- package/dist/index.js +559 -188
- package/dist/products.js +1 -1
- package/dist/{src-3AI7XR44.js → src-KLP774OA.js} +1 -1
- package/dist/{trace-DS35CFKV.js → trace-PMUETYJS.js} +2 -2
- package/dist/{trace-library-B3NZ4OQM.js → trace-library-FIMBRED6.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-Y3NTKJLA.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-R5FKQQMG.js";
|
|
24
24
|
import {
|
|
25
25
|
AGENT_TYPES,
|
|
26
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -40,13 +40,13 @@ import {
|
|
|
40
40
|
skillRefSchema,
|
|
41
41
|
splitMcpServers,
|
|
42
42
|
supersededBy
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-RHRREQMF.js";
|
|
44
44
|
import "./chunk-MLKGABMK.js";
|
|
45
45
|
|
|
46
46
|
// src/index.ts
|
|
47
|
-
import { existsSync as
|
|
48
|
-
import { basename as
|
|
49
|
-
import { createInterface 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
|
+
import { createInterface as createInterface5 } from "readline";
|
|
50
50
|
import { Command } from "commander";
|
|
51
51
|
|
|
52
52
|
// src/files.ts
|
|
@@ -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,268 @@ 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(), title = null) {
|
|
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
|
+
title: title?.trim().slice(0, 120) || null,
|
|
1037
|
+
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
1038
|
+
project: cwd ? basename3(root ?? cwd) : null,
|
|
1039
|
+
git: gitState,
|
|
1040
|
+
tree: tree.slice(0, MAX_TREE),
|
|
1041
|
+
tree_truncated: tree.length > MAX_TREE,
|
|
1042
|
+
skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
|
|
1043
|
+
mcp_servers: mcpServers(harness2, cwd, home)
|
|
1044
|
+
},
|
|
1045
|
+
patch
|
|
1046
|
+
};
|
|
1047
|
+
}
|
|
1048
|
+
function skillFiles(skill) {
|
|
1049
|
+
const out = [];
|
|
1050
|
+
const walk = (dir, depth) => {
|
|
1051
|
+
if (depth > 8 || out.length > 400) return;
|
|
1052
|
+
let entries;
|
|
1053
|
+
try {
|
|
1054
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
1055
|
+
} catch {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
for (const entry2 of entries) {
|
|
1059
|
+
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1060
|
+
const path = join2(dir, entry2.name);
|
|
1061
|
+
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1062
|
+
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
if (existsSync3(skill.dir)) walk(skill.dir, 0);
|
|
1066
|
+
return out;
|
|
1067
|
+
}
|
|
1068
|
+
function codexThreadName(sessionId, home = homedir2()) {
|
|
1069
|
+
let name = null;
|
|
1070
|
+
try {
|
|
1071
|
+
for (const line of readFileSync4(join2(process.env.CODEX_HOME ?? join2(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
|
|
1072
|
+
if (!line.includes(sessionId)) continue;
|
|
1073
|
+
try {
|
|
1074
|
+
const entry2 = JSON.parse(line);
|
|
1075
|
+
if (entry2.id === sessionId && typeof entry2.thread_name === "string" && entry2.thread_name.trim()) name = entry2.thread_name.trim();
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
} catch {
|
|
1080
|
+
}
|
|
1081
|
+
return name;
|
|
1082
|
+
}
|
|
1083
|
+
|
|
1084
|
+
// src/import-files.ts
|
|
833
1085
|
var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
|
|
834
1086
|
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
1087
|
var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
|
|
@@ -878,7 +1130,7 @@ function classify(path) {
|
|
|
878
1130
|
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
879
1131
|
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
880
1132
|
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
881
|
-
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(
|
|
1133
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
|
|
882
1134
|
return null;
|
|
883
1135
|
}
|
|
884
1136
|
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 +1144,7 @@ function shellText(input) {
|
|
|
892
1144
|
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
893
1145
|
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
894
1146
|
}
|
|
895
|
-
function touchedPaths(records, cwd, home =
|
|
1147
|
+
function touchedPaths(records, cwd, home = homedir3()) {
|
|
896
1148
|
const out = /* @__PURE__ */ new Set();
|
|
897
1149
|
const add = (raw, base) => {
|
|
898
1150
|
if (!raw || raw.length > 1e3) return;
|
|
@@ -935,79 +1187,110 @@ function touchedPaths(records, cwd, home = homedir2()) {
|
|
|
935
1187
|
return [...out];
|
|
936
1188
|
}
|
|
937
1189
|
function gitRoot(dir) {
|
|
938
|
-
for (let d = dir; ; d =
|
|
939
|
-
if (
|
|
940
|
-
if (d ===
|
|
1190
|
+
for (let d = dir; ; d = dirname3(d)) {
|
|
1191
|
+
if (existsSync4(join3(d, ".git"))) return d;
|
|
1192
|
+
if (d === dirname3(d)) return null;
|
|
941
1193
|
}
|
|
942
1194
|
}
|
|
943
|
-
function instructionPaths(cwd, harness2, home =
|
|
1195
|
+
function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
944
1196
|
const out = [];
|
|
945
|
-
if (cwd &&
|
|
1197
|
+
if (cwd && existsSync4(cwd)) {
|
|
946
1198
|
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 ===
|
|
1199
|
+
for (let d = resolve3(cwd); ; d = dirname3(d)) {
|
|
1200
|
+
for (const name of INSTRUCTION_FILES) out.push(join3(d, name));
|
|
1201
|
+
if (d === root || d === dirname3(d)) break;
|
|
950
1202
|
}
|
|
951
1203
|
}
|
|
952
|
-
if (harness2 === "claude_code") out.push(
|
|
953
|
-
if (harness2 === "codex") out.push(
|
|
1204
|
+
if (harness2 === "claude_code") out.push(join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "CLAUDE.md"));
|
|
1205
|
+
if (harness2 === "codex") out.push(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "AGENTS.md"));
|
|
1206
|
+
out.push(...sharedInstructionPaths(home));
|
|
954
1207
|
return out.filter((path) => {
|
|
955
1208
|
try {
|
|
956
|
-
return
|
|
1209
|
+
return statSync2(path).isFile();
|
|
957
1210
|
} catch {
|
|
958
1211
|
return false;
|
|
959
1212
|
}
|
|
960
1213
|
});
|
|
961
1214
|
}
|
|
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 =
|
|
1215
|
+
function resourceName(path, cwd, home = homedir3()) {
|
|
1216
|
+
const inside = cwd ? relative2(resolve3(cwd), path) : "";
|
|
1217
|
+
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
1218
|
+
const underHome = relative2(home, path);
|
|
966
1219
|
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
967
|
-
return `files/${shown.split(
|
|
1220
|
+
return `files/${shown.split(sep2).join("/")}`;
|
|
968
1221
|
}
|
|
969
1222
|
var ORDER = { text: 0, document: 1, image: 2 };
|
|
1223
|
+
function resourceLine(name, mediaType, data) {
|
|
1224
|
+
return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
1225
|
+
}
|
|
970
1226
|
function collectSessionFiles(records, opts) {
|
|
971
|
-
const home = opts.home ??
|
|
1227
|
+
const home = opts.home ?? homedir3();
|
|
972
1228
|
const exclude = new Set((opts.exclude ?? []).map((path) => resolve3(path)));
|
|
973
1229
|
const leftOut = [];
|
|
974
1230
|
const candidates = [];
|
|
975
1231
|
const seen = /* @__PURE__ */ new Set();
|
|
976
|
-
|
|
977
|
-
|
|
1232
|
+
const touched = touchedPaths(records, opts.cwd, home);
|
|
1233
|
+
const skills2 = loadedSkills(records, touched, opts.cwd, home);
|
|
1234
|
+
const consider = (path, name) => {
|
|
1235
|
+
if (seen.has(path) || exclude.has(path)) return;
|
|
978
1236
|
seen.add(path);
|
|
979
1237
|
if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
|
|
980
1238
|
leftOut.push({ path, reason: "credential or build file" });
|
|
981
|
-
|
|
1239
|
+
return;
|
|
982
1240
|
}
|
|
983
1241
|
let size;
|
|
984
1242
|
try {
|
|
985
|
-
const stat =
|
|
986
|
-
if (!stat.isFile())
|
|
1243
|
+
const stat = statSync2(path);
|
|
1244
|
+
if (!stat.isFile()) return;
|
|
987
1245
|
size = stat.size;
|
|
988
1246
|
} catch {
|
|
989
1247
|
leftOut.push({ path, reason: "missing" });
|
|
990
|
-
|
|
1248
|
+
return;
|
|
991
1249
|
}
|
|
992
1250
|
const type = classify(path);
|
|
993
1251
|
if (!type) {
|
|
994
1252
|
leftOut.push({ path, reason: "unsupported type" });
|
|
995
|
-
|
|
1253
|
+
return;
|
|
996
1254
|
}
|
|
997
1255
|
if (size > MAX_ATTACHED_FILE_BYTES) {
|
|
998
1256
|
leftOut.push({ path, reason: "too large" });
|
|
999
|
-
|
|
1257
|
+
return;
|
|
1000
1258
|
}
|
|
1001
|
-
candidates.push({ path, name
|
|
1002
|
-
}
|
|
1259
|
+
candidates.push({ path, name, ...type, size });
|
|
1260
|
+
};
|
|
1261
|
+
for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
|
|
1262
|
+
for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
|
|
1003
1263
|
candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
|
|
1004
1264
|
const lines = [];
|
|
1005
1265
|
const attached = [];
|
|
1006
1266
|
let bytes = 0;
|
|
1267
|
+
const state = workspaceState(opts.cwd, opts.harness, skills2, home, opts.title ?? null);
|
|
1268
|
+
const described = !!opts.cwd || !!state.manifest.title || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
1269
|
+
const generated = [
|
|
1270
|
+
...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
|
|
1271
|
+
...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
|
|
1272
|
+
];
|
|
1273
|
+
for (const file of generated) {
|
|
1274
|
+
let line;
|
|
1275
|
+
try {
|
|
1276
|
+
line = resourceLine(file.name, file.mediaType, file.data);
|
|
1277
|
+
} catch {
|
|
1278
|
+
leftOut.push({ path: file.name, reason: "could not be redacted safely" });
|
|
1279
|
+
continue;
|
|
1280
|
+
}
|
|
1281
|
+
const size = Buffer.byteLength(line);
|
|
1282
|
+
if (bytes + size > opts.budget) {
|
|
1283
|
+
leftOut.push({ path: file.name, reason: "over the upload budget" });
|
|
1284
|
+
continue;
|
|
1285
|
+
}
|
|
1286
|
+
bytes += size;
|
|
1287
|
+
lines.push(line);
|
|
1288
|
+
attached.push({ name: file.name, kind: "text", bytes: file.data.length });
|
|
1289
|
+
}
|
|
1007
1290
|
for (const file of candidates) {
|
|
1008
1291
|
let data;
|
|
1009
1292
|
try {
|
|
1010
|
-
data =
|
|
1293
|
+
data = readFileSync5(file.path);
|
|
1011
1294
|
} catch {
|
|
1012
1295
|
leftOut.push({ path: file.path, reason: "missing" });
|
|
1013
1296
|
continue;
|
|
@@ -1018,7 +1301,7 @@ function collectSessionFiles(records, opts) {
|
|
|
1018
1301
|
}
|
|
1019
1302
|
let line;
|
|
1020
1303
|
try {
|
|
1021
|
-
line =
|
|
1304
|
+
line = resourceLine(file.name, file.mediaType, data);
|
|
1022
1305
|
} catch {
|
|
1023
1306
|
leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
|
|
1024
1307
|
continue;
|
|
@@ -1032,7 +1315,13 @@ function collectSessionFiles(records, opts) {
|
|
|
1032
1315
|
lines.push(line);
|
|
1033
1316
|
attached.push({ name: file.name, kind: file.kind, bytes: data.length });
|
|
1034
1317
|
}
|
|
1035
|
-
|
|
1318
|
+
const setup = {
|
|
1319
|
+
skills: skills2.found.map((skill) => skill.name),
|
|
1320
|
+
missingSkills: skills2.missing,
|
|
1321
|
+
mcpServers: state.manifest.mcp_servers.map((server) => server.name),
|
|
1322
|
+
git: state.manifest.git !== null
|
|
1323
|
+
};
|
|
1324
|
+
return { lines, bytes, attached, leftOut, setup };
|
|
1036
1325
|
}
|
|
1037
1326
|
|
|
1038
1327
|
// src/import-local.ts
|
|
@@ -1107,16 +1396,16 @@ function sniffTitle(filePath) {
|
|
|
1107
1396
|
}
|
|
1108
1397
|
}
|
|
1109
1398
|
function listClaudeCodeSessions(limit) {
|
|
1110
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
1111
|
-
const projectsDir =
|
|
1112
|
-
if (!
|
|
1399
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join4(homedir4(), ".claude");
|
|
1400
|
+
const projectsDir = join4(root, "projects");
|
|
1401
|
+
if (!existsSync5(projectsDir)) return [];
|
|
1113
1402
|
const sessions = [];
|
|
1114
1403
|
try {
|
|
1115
|
-
for (const encoded of
|
|
1116
|
-
const projectDir =
|
|
1404
|
+
for (const encoded of readdirSync2(projectsDir)) {
|
|
1405
|
+
const projectDir = join4(projectsDir, encoded);
|
|
1117
1406
|
let entries;
|
|
1118
1407
|
try {
|
|
1119
|
-
entries =
|
|
1408
|
+
entries = readdirSync2(projectDir);
|
|
1120
1409
|
} catch {
|
|
1121
1410
|
continue;
|
|
1122
1411
|
}
|
|
@@ -1124,22 +1413,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
1124
1413
|
for (const entry2 of entries) {
|
|
1125
1414
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
1126
1415
|
const sessionId = entry2.slice(0, -6);
|
|
1127
|
-
const mainFile =
|
|
1416
|
+
const mainFile = join4(projectDir, entry2);
|
|
1128
1417
|
let stat;
|
|
1129
1418
|
try {
|
|
1130
|
-
stat =
|
|
1419
|
+
stat = statSync3(mainFile);
|
|
1131
1420
|
} catch {
|
|
1132
1421
|
continue;
|
|
1133
1422
|
}
|
|
1134
1423
|
if (!stat.isFile()) continue;
|
|
1135
1424
|
let discoveryError;
|
|
1136
1425
|
const subagentFiles = [];
|
|
1137
|
-
const subagentDir =
|
|
1138
|
-
if (
|
|
1426
|
+
const subagentDir = join4(projectDir, sessionId, "subagents");
|
|
1427
|
+
if (existsSync5(subagentDir)) {
|
|
1139
1428
|
try {
|
|
1140
|
-
const subFiles =
|
|
1429
|
+
const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1141
1430
|
for (const f of subFiles) {
|
|
1142
|
-
subagentFiles.push(
|
|
1431
|
+
subagentFiles.push(join4(subagentDir, f));
|
|
1143
1432
|
}
|
|
1144
1433
|
} catch {
|
|
1145
1434
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -1161,17 +1450,17 @@ function listClaudeCodeSessions(limit) {
|
|
|
1161
1450
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1162
1451
|
}
|
|
1163
1452
|
function listCodexSessions(limit) {
|
|
1164
|
-
const sessionsDir =
|
|
1453
|
+
const sessionsDir = join4(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join4(homedir4(), ".codex"), "sessions");
|
|
1165
1454
|
const records = [];
|
|
1166
1455
|
function walk(dir) {
|
|
1167
1456
|
let entries;
|
|
1168
1457
|
try {
|
|
1169
|
-
entries =
|
|
1458
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1170
1459
|
} catch {
|
|
1171
1460
|
return;
|
|
1172
1461
|
}
|
|
1173
1462
|
for (const entry2 of entries) {
|
|
1174
|
-
const full =
|
|
1463
|
+
const full = join4(dir, entry2.name);
|
|
1175
1464
|
if (entry2.isDirectory()) {
|
|
1176
1465
|
walk(full);
|
|
1177
1466
|
continue;
|
|
@@ -1189,7 +1478,7 @@ function listCodexSessions(limit) {
|
|
|
1189
1478
|
sessionId: data.id,
|
|
1190
1479
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
1191
1480
|
title: sniffTitle(full),
|
|
1192
|
-
mtimeMs:
|
|
1481
|
+
mtimeMs: statSync3(full).mtimeMs,
|
|
1193
1482
|
files: [full]
|
|
1194
1483
|
},
|
|
1195
1484
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -1258,7 +1547,7 @@ async function mintTrace(base, harness2) {
|
|
|
1258
1547
|
function loadSummaryFile(summaryPath) {
|
|
1259
1548
|
let raw;
|
|
1260
1549
|
try {
|
|
1261
|
-
raw =
|
|
1550
|
+
raw = readFileSync6(summaryPath, "utf8");
|
|
1262
1551
|
} catch {
|
|
1263
1552
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1264
1553
|
}
|
|
@@ -1272,8 +1561,8 @@ function loadSummaryFile(summaryPath) {
|
|
|
1272
1561
|
const p = pkg;
|
|
1273
1562
|
if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
|
|
1274
1563
|
if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
|
|
1275
|
-
const
|
|
1276
|
-
return
|
|
1564
|
+
const redacted2 = redactLine2(raw.trimEnd());
|
|
1565
|
+
return redacted2;
|
|
1277
1566
|
}
|
|
1278
1567
|
var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
|
|
1279
1568
|
var SUMMARY_RESERVE_BYTES = 128 * 1024;
|
|
@@ -1299,9 +1588,9 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1299
1588
|
const source = new SessionSourceCollector();
|
|
1300
1589
|
const records = [];
|
|
1301
1590
|
for (const path of session2.files) {
|
|
1302
|
-
const stat =
|
|
1591
|
+
const stat = statSync3(path);
|
|
1303
1592
|
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 =
|
|
1593
|
+
const text = readFileSync6(path, "utf8");
|
|
1305
1594
|
for (const raw of text.split("\n")) {
|
|
1306
1595
|
if (!raw.trim()) continue;
|
|
1307
1596
|
let record;
|
|
@@ -1340,8 +1629,14 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1340
1629
|
const sourceId = nativeId ?? session2.sessionId;
|
|
1341
1630
|
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
1631
|
const meta = source.result();
|
|
1343
|
-
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: []
|
|
1344
|
-
|
|
1632
|
+
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : collectSessionFiles(records, {
|
|
1633
|
+
harness: session2.harness,
|
|
1634
|
+
cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""),
|
|
1635
|
+
budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES,
|
|
1636
|
+
exclude: session2.files,
|
|
1637
|
+
title: meta.title ?? (session2.harness === "codex" ? codexThreadName(session2.sessionId) : null)
|
|
1638
|
+
});
|
|
1639
|
+
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
1640
|
let summaryJson;
|
|
1346
1641
|
if (summaryPath) {
|
|
1347
1642
|
summaryJson = loadSummaryFile(summaryPath);
|
|
@@ -1361,7 +1656,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1361
1656
|
completed: [],
|
|
1362
1657
|
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1363
1658
|
verification: [],
|
|
1364
|
-
blockers: [leftOutLine(files2.leftOut)],
|
|
1659
|
+
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
1660
|
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
1661
|
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1367
1662
|
}
|
|
@@ -1373,7 +1668,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1373
1668
|
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1374
1669
|
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1375
1670
|
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1376
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut };
|
|
1671
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup };
|
|
1377
1672
|
}
|
|
1378
1673
|
async function importSession(opts) {
|
|
1379
1674
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
@@ -1382,7 +1677,7 @@ async function importSession(opts) {
|
|
|
1382
1677
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1383
1678
|
}
|
|
1384
1679
|
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 });
|
|
1680
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1386
1681
|
let traceId;
|
|
1387
1682
|
let key;
|
|
1388
1683
|
if (opts.importKey && opts.grantTraceId) {
|
|
@@ -1416,7 +1711,8 @@ async function importSession(opts) {
|
|
|
1416
1711
|
redactionCount,
|
|
1417
1712
|
gaps,
|
|
1418
1713
|
attachedFiles,
|
|
1419
|
-
leftOutFiles
|
|
1714
|
+
leftOutFiles,
|
|
1715
|
+
setup
|
|
1420
1716
|
};
|
|
1421
1717
|
}
|
|
1422
1718
|
|
|
@@ -1520,23 +1816,23 @@ async function headlessLogin(verifier, challenge) {
|
|
|
1520
1816
|
}
|
|
1521
1817
|
|
|
1522
1818
|
// 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 =
|
|
1819
|
+
import { existsSync as existsSync6 } from "fs";
|
|
1820
|
+
import { homedir as homedir5 } from "os";
|
|
1821
|
+
import { join as join5 } from "path";
|
|
1822
|
+
function localAgents(env = process.env, home = homedir5()) {
|
|
1823
|
+
const claude = env.CLAUDE_CONFIG_DIR || join5(home, ".claude");
|
|
1824
|
+
const codex = env.CODEX_HOME || join5(home, ".codex");
|
|
1825
|
+
const cursor = join5(home, ".cursor");
|
|
1530
1826
|
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:
|
|
1827
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join5(claude, "skills") },
|
|
1828
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join5(codex, "skills") },
|
|
1829
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join5(cursor, "skills") }
|
|
1534
1830
|
];
|
|
1535
1831
|
}
|
|
1536
|
-
function detectLocalAgents(env = process.env, home =
|
|
1537
|
-
return localAgents(env, home).filter((agent2) =>
|
|
1832
|
+
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
1833
|
+
return localAgents(env, home).filter((agent2) => existsSync6(agent2.root));
|
|
1538
1834
|
}
|
|
1539
|
-
function selectLocalAgents(ids, env = process.env, home =
|
|
1835
|
+
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
1540
1836
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
1541
1837
|
const known = localAgents(env, home);
|
|
1542
1838
|
return ids.map((id) => {
|
|
@@ -1635,16 +1931,16 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
1635
1931
|
}
|
|
1636
1932
|
|
|
1637
1933
|
// src/setup.ts
|
|
1638
|
-
import { mkdirSync as mkdirSync2, readFileSync as
|
|
1639
|
-
import { dirname as
|
|
1934
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
|
|
1935
|
+
import { dirname as dirname5, join as join9 } from "path";
|
|
1640
1936
|
import { createInterface as createInterface3 } from "readline";
|
|
1641
1937
|
|
|
1642
1938
|
// src/mcp-register.ts
|
|
1643
|
-
import { spawnSync } from "child_process";
|
|
1939
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
1644
1940
|
|
|
1645
1941
|
// src/skill.ts
|
|
1646
|
-
import { mkdirSync, readFileSync as
|
|
1647
|
-
import { join as
|
|
1942
|
+
import { mkdirSync, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
1943
|
+
import { join as join6 } from "path";
|
|
1648
1944
|
|
|
1649
1945
|
// src/catalog-file.ts
|
|
1650
1946
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -1661,8 +1957,8 @@ function entry(agent2) {
|
|
|
1661
1957
|
const lines = [`### @${agent2.handle} \u2014 ${agent2.name}${engine}`, "", agent2.tagline];
|
|
1662
1958
|
if (agent2.does.length) lines.push("", `Does: ${agent2.does.join("; ")}`);
|
|
1663
1959
|
if (agent2.does_not.length) lines.push(`Does not: ${agent2.does_not.join("; ")}`);
|
|
1664
|
-
const { mcp_servers:
|
|
1665
|
-
if (typeof
|
|
1960
|
+
const { mcp_servers: mcpServers2, ...rest } = agent2.requirements;
|
|
1961
|
+
if (typeof mcpServers2 === "boolean") lines.push(mcpServers2 ? "Can use MCP servers." : "Cannot use MCP servers.");
|
|
1666
1962
|
const details = describe(rest);
|
|
1667
1963
|
if (details) lines.push(`Details: ${details}`);
|
|
1668
1964
|
const pricing = describe(agent2.pricing);
|
|
@@ -2085,7 +2381,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
2085
2381
|
}
|
|
2086
2382
|
function installedVersion(dir) {
|
|
2087
2383
|
try {
|
|
2088
|
-
const marker = JSON.parse(
|
|
2384
|
+
const marker = JSON.parse(readFileSync7(join6(dir, SKILL_MARKER), "utf8"));
|
|
2089
2385
|
return typeof marker.version === "string" ? marker.version : null;
|
|
2090
2386
|
} catch {
|
|
2091
2387
|
return null;
|
|
@@ -2095,25 +2391,25 @@ function isInstalledSkill(dir) {
|
|
|
2095
2391
|
return installedVersion(dir) !== null;
|
|
2096
2392
|
}
|
|
2097
2393
|
function installSkill(agent2, opts = {}) {
|
|
2098
|
-
const dir =
|
|
2394
|
+
const dir = join6(agent2.skillsDir, SKILL_NAME);
|
|
2099
2395
|
const previous = installedVersion(dir);
|
|
2100
2396
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
2101
2397
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
2102
2398
|
if (outcome !== "current") {
|
|
2103
2399
|
mkdirSync(dir, { recursive: true });
|
|
2104
|
-
writeFileSync3(
|
|
2105
|
-
writeFileSync3(
|
|
2400
|
+
writeFileSync3(join6(dir, "SKILL.md"), skillDocument());
|
|
2401
|
+
writeFileSync3(join6(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
2106
2402
|
`);
|
|
2107
2403
|
}
|
|
2108
2404
|
if (opts.catalog) {
|
|
2109
2405
|
mkdirSync(dir, { recursive: true });
|
|
2110
|
-
writeFileSync3(
|
|
2406
|
+
writeFileSync3(join6(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2111
2407
|
}
|
|
2112
|
-
const refsDir =
|
|
2408
|
+
const refsDir = join6(dir, REFERENCES_DIR);
|
|
2113
2409
|
mkdirSync(refsDir, { recursive: true });
|
|
2114
|
-
writeFileSync3(
|
|
2115
|
-
writeFileSync3(
|
|
2116
|
-
writeFileSync3(
|
|
2410
|
+
writeFileSync3(join6(refsDir, "import.md"), importReferenceDoc());
|
|
2411
|
+
writeFileSync3(join6(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2412
|
+
writeFileSync3(join6(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
2117
2413
|
return { agent: agent2.id, path: dir, outcome };
|
|
2118
2414
|
}
|
|
2119
2415
|
|
|
@@ -2122,9 +2418,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
|
|
|
2122
2418
|
var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
2123
2419
|
function systemCommandRunner() {
|
|
2124
2420
|
return {
|
|
2125
|
-
has: (command) =>
|
|
2421
|
+
has: (command) => spawnSync2(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
2126
2422
|
run: (command, args) => {
|
|
2127
|
-
const result =
|
|
2423
|
+
const result = spawnSync2(command, args, { encoding: "utf8" });
|
|
2128
2424
|
return { status: result.status, stderr: result.stderr ?? "" };
|
|
2129
2425
|
}
|
|
2130
2426
|
};
|
|
@@ -2164,10 +2460,11 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
2164
2460
|
}
|
|
2165
2461
|
|
|
2166
2462
|
// src/setup-local.ts
|
|
2167
|
-
import {
|
|
2168
|
-
import {
|
|
2169
|
-
import {
|
|
2170
|
-
import {
|
|
2463
|
+
import { isUtf8 as isUtf82 } from "buffer";
|
|
2464
|
+
import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync8, statSync as statSync4 } from "fs";
|
|
2465
|
+
import { homedir as homedir6 } from "os";
|
|
2466
|
+
import { dirname as dirname4, isAbsolute as isAbsolute2, join as join7, resolve as resolve4 } from "path";
|
|
2467
|
+
import { parse as parseToml3 } from "smol-toml";
|
|
2171
2468
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
2172
2469
|
var MAX_IMPORT_DEPTH = 5;
|
|
2173
2470
|
var SKIPPED_DIRS = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
@@ -2179,14 +2476,24 @@ function frontmatterName(skillMd) {
|
|
|
2179
2476
|
function sensitive(name) {
|
|
2180
2477
|
return name.startsWith(".env") || name.endsWith(".pem") || name.endsWith(".key");
|
|
2181
2478
|
}
|
|
2182
|
-
function
|
|
2479
|
+
function redacted(data) {
|
|
2480
|
+
if (data.includes(0) || !isUtf82(data)) return new Uint8Array(data);
|
|
2481
|
+
try {
|
|
2482
|
+
const line = redactLine(JSON.stringify({ type: "import.resource", name: "skill", media_type: "text/plain", data: data.toString("base64") }), { requireStructured: true }).line;
|
|
2483
|
+
return new Uint8Array(Buffer.from(JSON.parse(line).data, "base64"));
|
|
2484
|
+
} catch {
|
|
2485
|
+
return null;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
2488
|
+
function skillFiles2(root, prefix) {
|
|
2183
2489
|
const files2 = [];
|
|
2184
|
-
for (const entry2 of
|
|
2185
|
-
const path =
|
|
2490
|
+
for (const entry2 of readdirSync3(root, { withFileTypes: true })) {
|
|
2491
|
+
const path = join7(root, entry2.name);
|
|
2186
2492
|
if (entry2.isDirectory()) {
|
|
2187
|
-
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...
|
|
2188
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
2189
|
-
|
|
2493
|
+
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
2494
|
+
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync4(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
2495
|
+
const bytes = redacted(readFileSync8(path));
|
|
2496
|
+
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
2190
2497
|
}
|
|
2191
2498
|
}
|
|
2192
2499
|
return files2;
|
|
@@ -2195,19 +2502,19 @@ function readLocalSkills(agents) {
|
|
|
2195
2502
|
const skills2 = [];
|
|
2196
2503
|
const ignored = [];
|
|
2197
2504
|
for (const agent2 of agents) {
|
|
2198
|
-
if (!
|
|
2199
|
-
for (const entry2 of
|
|
2200
|
-
const dir =
|
|
2201
|
-
let
|
|
2505
|
+
if (!existsSync7(agent2.skillsDir)) continue;
|
|
2506
|
+
for (const entry2 of readdirSync3(agent2.skillsDir, { withFileTypes: true })) {
|
|
2507
|
+
const dir = join7(agent2.skillsDir, entry2.name);
|
|
2508
|
+
let isDir2 = entry2.isDirectory();
|
|
2202
2509
|
if (entry2.isSymbolicLink()) {
|
|
2203
2510
|
try {
|
|
2204
|
-
|
|
2511
|
+
isDir2 = statSync4(dir).isDirectory();
|
|
2205
2512
|
} catch {
|
|
2206
|
-
|
|
2513
|
+
isDir2 = false;
|
|
2207
2514
|
}
|
|
2208
2515
|
}
|
|
2209
|
-
if (!
|
|
2210
|
-
const name = frontmatterName(
|
|
2516
|
+
if (!isDir2 || !existsSync7(join7(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
2517
|
+
const name = frontmatterName(readFileSync8(join7(dir, "SKILL.md"), "utf8"));
|
|
2211
2518
|
if (!name) {
|
|
2212
2519
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
2213
2520
|
continue;
|
|
@@ -2217,7 +2524,7 @@ function readLocalSkills(agents) {
|
|
|
2217
2524
|
ignored.push({ agent: agent2.id, dir, reason: `${taken.agent} already has a skill named ${name}` });
|
|
2218
2525
|
continue;
|
|
2219
2526
|
}
|
|
2220
|
-
const files2 =
|
|
2527
|
+
const files2 = skillFiles2(dir, entry2.name);
|
|
2221
2528
|
skills2.push({ name, agent: agent2.id, dir, files: files2, bytes: files2.reduce((sum, file) => sum + file.bytes.length, 0) });
|
|
2222
2529
|
}
|
|
2223
2530
|
}
|
|
@@ -2235,11 +2542,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2235
2542
|
}
|
|
2236
2543
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
2237
2544
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
2238
|
-
const path = target.startsWith("~/") ?
|
|
2545
|
+
const path = target.startsWith("~/") ? join7(home, target.slice(2)) : isAbsolute2(target) ? target : resolve4(dirname4(from), target);
|
|
2239
2546
|
if (seen.includes(path)) return "";
|
|
2240
2547
|
let imported;
|
|
2241
2548
|
try {
|
|
2242
|
-
imported =
|
|
2549
|
+
imported = readFileSync8(path, "utf8");
|
|
2243
2550
|
} catch {
|
|
2244
2551
|
return line;
|
|
2245
2552
|
}
|
|
@@ -2247,15 +2554,15 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2247
2554
|
return inlineImports(imported, path, home, depth + 1, seen).trim();
|
|
2248
2555
|
}).join("\n");
|
|
2249
2556
|
}
|
|
2250
|
-
function readGlobalInstructions(agents, home =
|
|
2557
|
+
function readGlobalInstructions(agents, home = homedir6()) {
|
|
2251
2558
|
const found = [];
|
|
2252
2559
|
for (const agent2 of agents) {
|
|
2253
2560
|
const file = INSTRUCTION_FILES2[agent2.id];
|
|
2254
2561
|
if (!file) continue;
|
|
2255
|
-
const path =
|
|
2562
|
+
const path = join7(agent2.root, file);
|
|
2256
2563
|
let raw;
|
|
2257
2564
|
try {
|
|
2258
|
-
raw =
|
|
2565
|
+
raw = readFileSync8(path, "utf8");
|
|
2259
2566
|
} catch {
|
|
2260
2567
|
continue;
|
|
2261
2568
|
}
|
|
@@ -2272,9 +2579,9 @@ function combineInstructions(found) {
|
|
|
2272
2579
|
|
|
2273
2580
|
${item.content.trim()}`).join("\n\n");
|
|
2274
2581
|
}
|
|
2275
|
-
function
|
|
2582
|
+
function readJson3(path) {
|
|
2276
2583
|
try {
|
|
2277
|
-
return JSON.parse(
|
|
2584
|
+
return JSON.parse(readFileSync8(path, "utf8"));
|
|
2278
2585
|
} catch {
|
|
2279
2586
|
return void 0;
|
|
2280
2587
|
}
|
|
@@ -2288,15 +2595,15 @@ function bearerOf(def) {
|
|
|
2288
2595
|
return match?.[1] ?? null;
|
|
2289
2596
|
}
|
|
2290
2597
|
function serversOf(agent2, home) {
|
|
2291
|
-
if (agent2.id === "claude_code") return
|
|
2292
|
-
if (agent2.id === "cursor") return
|
|
2598
|
+
if (agent2.id === "claude_code") return readJson3(join7(home, ".claude.json"))?.mcpServers ?? {};
|
|
2599
|
+
if (agent2.id === "cursor") return readJson3(join7(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
2293
2600
|
try {
|
|
2294
|
-
return
|
|
2601
|
+
return parseToml3(readFileSync8(join7(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
2295
2602
|
} catch {
|
|
2296
2603
|
return {};
|
|
2297
2604
|
}
|
|
2298
2605
|
}
|
|
2299
|
-
function findLocalSecrets(agents, home =
|
|
2606
|
+
function findLocalSecrets(agents, home = homedir6()) {
|
|
2300
2607
|
const secrets2 = [];
|
|
2301
2608
|
for (const agent2 of agents) {
|
|
2302
2609
|
for (const [name, def] of Object.entries(serversOf(agent2, home))) {
|
|
@@ -2312,9 +2619,9 @@ function findLocalSecrets(agents, home = homedir5()) {
|
|
|
2312
2619
|
|
|
2313
2620
|
// src/subscriptions.ts
|
|
2314
2621
|
import { spawn as spawn3 } from "child_process";
|
|
2315
|
-
import { existsSync as
|
|
2316
|
-
import { homedir as
|
|
2317
|
-
import { join as
|
|
2622
|
+
import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
|
|
2623
|
+
import { homedir as homedir7 } from "os";
|
|
2624
|
+
import { join as join8 } from "path";
|
|
2318
2625
|
import { createInterface as createInterface2 } from "readline";
|
|
2319
2626
|
var ALIASES = {
|
|
2320
2627
|
claude: "anthropic",
|
|
@@ -2399,12 +2706,12 @@ async function connectClaude() {
|
|
|
2399
2706
|
const expiry = sub.expiresAt ? ` (expires ~${sub.expiresAt.slice(0, 10)})` : "";
|
|
2400
2707
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
2401
2708
|
}
|
|
2402
|
-
function codexSignIn(env = process.env, home =
|
|
2403
|
-
const path =
|
|
2404
|
-
if (!
|
|
2709
|
+
function codexSignIn(env = process.env, home = homedir7()) {
|
|
2710
|
+
const path = join8(env.CODEX_HOME || join8(home, ".codex"), "auth.json");
|
|
2711
|
+
if (!existsSync8(path)) {
|
|
2405
2712
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
2406
2713
|
}
|
|
2407
|
-
const raw =
|
|
2714
|
+
const raw = readFileSync9(path, "utf8");
|
|
2408
2715
|
try {
|
|
2409
2716
|
const parsed = JSON.parse(raw);
|
|
2410
2717
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -2412,12 +2719,12 @@ function codexSignIn(env = process.env, home = homedir6()) {
|
|
|
2412
2719
|
throw new ApiError(400, "invalid_codex_auth", `${path} isn't valid JSON \u2014 re-run \`codex login\``);
|
|
2413
2720
|
}
|
|
2414
2721
|
}
|
|
2415
|
-
function cursorSignIn(env = process.env, home =
|
|
2416
|
-
const path =
|
|
2417
|
-
if (!
|
|
2722
|
+
function cursorSignIn(env = process.env, home = homedir7()) {
|
|
2723
|
+
const path = join8(env.XDG_CONFIG_HOME || join8(home, ".config"), "cursor", "auth.json");
|
|
2724
|
+
if (!existsSync8(path)) {
|
|
2418
2725
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
2419
2726
|
}
|
|
2420
|
-
const raw =
|
|
2727
|
+
const raw = readFileSync9(path, "utf8");
|
|
2421
2728
|
try {
|
|
2422
2729
|
const parsed = JSON.parse(raw);
|
|
2423
2730
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -2487,20 +2794,20 @@ async function disconnect(name, opts) {
|
|
|
2487
2794
|
// src/setup.ts
|
|
2488
2795
|
var SETUP_STEPS = ["skill", "logins", "import", "secrets"];
|
|
2489
2796
|
var PLAN_PROVIDERS = { claude: "anthropic", anthropic: "anthropic", chatgpt: "openai", codex: "openai", openai: "openai", cursor: "cursor" };
|
|
2490
|
-
function planProviders(
|
|
2491
|
-
if (!
|
|
2492
|
-
return new Set(
|
|
2797
|
+
function planProviders(names2) {
|
|
2798
|
+
if (!names2?.length) return null;
|
|
2799
|
+
return new Set(names2.map((name) => {
|
|
2493
2800
|
const provider = PLAN_PROVIDERS[name.toLowerCase()];
|
|
2494
2801
|
if (!provider) throw new ApiError(400, "unknown_provider", `unknown plan "${name}" \u2014 use claude, chatgpt or cursor`);
|
|
2495
2802
|
return provider;
|
|
2496
2803
|
}));
|
|
2497
2804
|
}
|
|
2498
2805
|
function reportPath() {
|
|
2499
|
-
return
|
|
2806
|
+
return join9(dirname5(configPath()), "setup.json");
|
|
2500
2807
|
}
|
|
2501
2808
|
function loadReport() {
|
|
2502
2809
|
try {
|
|
2503
|
-
const saved = JSON.parse(
|
|
2810
|
+
const saved = JSON.parse(readFileSync10(reportPath(), "utf8"));
|
|
2504
2811
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
2505
2812
|
} catch {
|
|
2506
2813
|
return { agents: [], plans: [] };
|
|
@@ -2513,7 +2820,7 @@ function mergeReport(base, next) {
|
|
|
2513
2820
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
2514
2821
|
}
|
|
2515
2822
|
function saveReport(report) {
|
|
2516
|
-
mkdirSync2(
|
|
2823
|
+
mkdirSync2(dirname5(reportPath()), { recursive: true });
|
|
2517
2824
|
writeFileSync4(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
2518
2825
|
`, { mode: 384 });
|
|
2519
2826
|
}
|
|
@@ -2863,17 +3170,17 @@ function renderArtifact(artifact2) {
|
|
|
2863
3170
|
}
|
|
2864
3171
|
|
|
2865
3172
|
// src/skills-store.ts
|
|
2866
|
-
import { readdirSync as
|
|
2867
|
-
import { basename as
|
|
3173
|
+
import { readdirSync as readdirSync4, readFileSync as readFileSync11, statSync as statSync5 } from "fs";
|
|
3174
|
+
import { basename as basename5, join as join10, relative as relative3, resolve as resolve5 } from "path";
|
|
2868
3175
|
function skillBundle(dir) {
|
|
2869
3176
|
const root = resolve5(dir);
|
|
2870
|
-
const top =
|
|
3177
|
+
const top = basename5(root);
|
|
2871
3178
|
const files2 = [];
|
|
2872
3179
|
const walk = (path) => {
|
|
2873
|
-
for (const name of
|
|
2874
|
-
const full =
|
|
2875
|
-
if (
|
|
2876
|
-
else files2.push({ path: `${top}/${
|
|
3180
|
+
for (const name of readdirSync4(path).sort()) {
|
|
3181
|
+
const full = join10(path, name);
|
|
3182
|
+
if (statSync5(full).isDirectory()) walk(full);
|
|
3183
|
+
else files2.push({ path: `${top}/${relative3(root, full)}`, bytes: readFileSync11(full) });
|
|
2877
3184
|
}
|
|
2878
3185
|
};
|
|
2879
3186
|
walk(root);
|
|
@@ -2883,7 +3190,7 @@ function skillBundle(dir) {
|
|
|
2883
3190
|
async function uploadSkill(dir, displayName) {
|
|
2884
3191
|
const form = new FormData();
|
|
2885
3192
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
2886
|
-
form.append("displayName", displayName ??
|
|
3193
|
+
form.append("displayName", displayName ?? basename5(resolve5(dir)));
|
|
2887
3194
|
return apiForm("POST", "/skills", form);
|
|
2888
3195
|
}
|
|
2889
3196
|
async function listSkills() {
|
|
@@ -2907,6 +3214,7 @@ function renderRequest(request) {
|
|
|
2907
3214
|
}
|
|
2908
3215
|
|
|
2909
3216
|
// src/workspace.ts
|
|
3217
|
+
import { createInterface as createInterface4 } from "readline";
|
|
2910
3218
|
var KINDS = ["TOOL_CALL", "SPEND_CAP", "CALL_COUNT", "CAPABILITY"];
|
|
2911
3219
|
async function workspaceSlug(explicit) {
|
|
2912
3220
|
if (explicit) return explicit;
|
|
@@ -2999,21 +3307,66 @@ function renderPolicy(rule) {
|
|
|
2999
3307
|
const state = rule.enabled ? "on " : "off";
|
|
3000
3308
|
return `${state} ${rule.layer.padEnd(9)} ${rule.kind.padEnd(10)} ${rule.name} ${JSON.stringify(rule.config)} ${rule.id}`;
|
|
3001
3309
|
}
|
|
3310
|
+
var REMOVED_KINDS = [
|
|
3311
|
+
["sessions", "sessions and their transcripts"],
|
|
3312
|
+
["agents", "agents"],
|
|
3313
|
+
["artifacts", "artifacts"],
|
|
3314
|
+
["files", "task files"],
|
|
3315
|
+
["memory_stores", "memory stores"],
|
|
3316
|
+
["routines", "routines"],
|
|
3317
|
+
["channels", "channel connections"],
|
|
3318
|
+
["webhooks", "webhooks"],
|
|
3319
|
+
["api_keys", "API keys"],
|
|
3320
|
+
["vaults", "vaults"],
|
|
3321
|
+
["skills", "skills"],
|
|
3322
|
+
["environments", "environments"],
|
|
3323
|
+
["connections", "connected apps"],
|
|
3324
|
+
["traces", "uploaded traces"],
|
|
3325
|
+
["members", "other members"],
|
|
3326
|
+
["invites", "pending invitations"]
|
|
3327
|
+
];
|
|
3328
|
+
async function deletionPreview(slug) {
|
|
3329
|
+
return api("GET", `/universes/${encodeURIComponent(slug)}/deletion`);
|
|
3330
|
+
}
|
|
3331
|
+
function renderDeletionPreview(preview) {
|
|
3332
|
+
if (!preview.can_delete) return [`${preview.workspace.slug} cannot be deleted:`, ...preview.blockers.map((b) => ` ${b.message}`)];
|
|
3333
|
+
const lines = [`Deleting ${preview.workspace.slug} (${preview.workspace.name}) permanently removes:`];
|
|
3334
|
+
const removed = REMOVED_KINDS.filter(([key]) => (preview.removes[key] ?? 0) > 0);
|
|
3335
|
+
for (const [key, label] of removed) lines.push(` ${preview.removes[key]} ${label}`);
|
|
3336
|
+
if (!removed.length) lines.push(" nothing: the workspace is empty");
|
|
3337
|
+
const running = preview.removes.running_sessions ?? 0;
|
|
3338
|
+
if (running) lines.push(`${running} running session${running === 1 ? " is" : "s are"} stopped first.`);
|
|
3339
|
+
if (preview.wallet) lines.push(`$${(preview.wallet.balance_micros / 1e6).toFixed(2)} in its wallet goes back to ${preview.wallet.returns_to ?? "you"}.`);
|
|
3340
|
+
lines.push("This cannot be undone.");
|
|
3341
|
+
return lines;
|
|
3342
|
+
}
|
|
3343
|
+
async function deleteWorkspace(slug) {
|
|
3344
|
+
return (await api("DELETE", `/universes/${encodeURIComponent(slug)}`)).deleted;
|
|
3345
|
+
}
|
|
3346
|
+
function typedConfirmation(slug) {
|
|
3347
|
+
return new Promise((done) => {
|
|
3348
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3349
|
+
rl.question(`Type ${slug} to delete it: `, (answer) => {
|
|
3350
|
+
rl.close();
|
|
3351
|
+
done(answer.trim() === slug);
|
|
3352
|
+
});
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
3002
3355
|
|
|
3003
3356
|
// src/harness.ts
|
|
3004
|
-
import { existsSync as
|
|
3005
|
-
import { dirname as
|
|
3357
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync5 } from "fs";
|
|
3358
|
+
import { dirname as dirname6, join as join11, resolve as resolve6 } from "path";
|
|
3006
3359
|
var HARNESS_FILE = "harness.json";
|
|
3007
3360
|
function harnessFilePath(dir) {
|
|
3008
|
-
return
|
|
3361
|
+
return join11(resolve6(dir), HARNESS_FILE);
|
|
3009
3362
|
}
|
|
3010
3363
|
function readHarnessFile(dir) {
|
|
3011
3364
|
const path = harnessFilePath(dir);
|
|
3012
|
-
if (!
|
|
3365
|
+
if (!existsSync9(path)) {
|
|
3013
3366
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve6(dir)} \u2014 run \`sky harness init\` first`);
|
|
3014
3367
|
}
|
|
3015
3368
|
try {
|
|
3016
|
-
return JSON.parse(
|
|
3369
|
+
return JSON.parse(readFileSync12(path, "utf8"));
|
|
3017
3370
|
} catch (err) {
|
|
3018
3371
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3019
3372
|
}
|
|
@@ -3040,10 +3393,10 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
3040
3393
|
`;
|
|
3041
3394
|
function harnessInit(dir) {
|
|
3042
3395
|
const path = harnessFilePath(dir);
|
|
3043
|
-
if (
|
|
3396
|
+
if (existsSync9(path)) {
|
|
3044
3397
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
3045
3398
|
}
|
|
3046
|
-
mkdirSync3(
|
|
3399
|
+
mkdirSync3(dirname6(path), { recursive: true });
|
|
3047
3400
|
writeFileSync5(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
3048
3401
|
`);
|
|
3049
3402
|
console.log(`Created ${path}`);
|
|
@@ -3280,6 +3633,19 @@ universe.command("remove <member>").description("remove a member (user id or ema
|
|
|
3280
3633
|
console.log(`Removed ${member.email ?? member.user_id}`);
|
|
3281
3634
|
})
|
|
3282
3635
|
);
|
|
3636
|
+
async function deleteWorkspaceCommand(slug, opts) {
|
|
3637
|
+
const preview = await deletionPreview(slug);
|
|
3638
|
+
for (const line of renderDeletionPreview(preview)) console.log(line);
|
|
3639
|
+
if (!preview.can_delete) process.exit(1);
|
|
3640
|
+
if (!opts.yes && !await typedConfirmation(slug)) {
|
|
3641
|
+
console.log("Aborted.");
|
|
3642
|
+
return;
|
|
3643
|
+
}
|
|
3644
|
+
const deleted = await deleteWorkspace(slug);
|
|
3645
|
+
if (currentUniverse() === slug) saveConfig({ ...loadConfig(), currentUniverse: void 0 });
|
|
3646
|
+
console.log(`Deleted ${deleted.slug}${deleted.sessions_stopped ? ` \xB7 stopped ${deleted.sessions_stopped} running session${deleted.sessions_stopped === 1 ? "" : "s"}` : ""}`);
|
|
3647
|
+
}
|
|
3648
|
+
universe.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
|
|
3283
3649
|
universe.command("join <token>").description("accept an invitation sent to your verified email").action(
|
|
3284
3650
|
run(async (token) => {
|
|
3285
3651
|
const workspace2 = await joinWorkspace(token);
|
|
@@ -3297,6 +3663,7 @@ workspace.command("list").action(
|
|
|
3297
3663
|
}
|
|
3298
3664
|
})
|
|
3299
3665
|
);
|
|
3666
|
+
workspace.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
|
|
3300
3667
|
var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
|
|
3301
3668
|
async function policyScope(opts) {
|
|
3302
3669
|
return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
|
|
@@ -3700,7 +4067,7 @@ async function sendAndFollow(sessionId, text, opts) {
|
|
|
3700
4067
|
async function chatLoop(sessionId, opts = {}) {
|
|
3701
4068
|
console.log(`Chatting with ${sessionId} \u2014 Ctrl-C to leave.`);
|
|
3702
4069
|
const controller = new AbortController();
|
|
3703
|
-
const rl =
|
|
4070
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout, prompt: "> " });
|
|
3704
4071
|
let system = opts.system;
|
|
3705
4072
|
void (async () => {
|
|
3706
4073
|
try {
|
|
@@ -3815,8 +4182,8 @@ secrets.command("unset <id> <key>").action(
|
|
|
3815
4182
|
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
4183
|
run(async (opts) => {
|
|
3817
4184
|
const path = resolve7(opts.file);
|
|
3818
|
-
if (!
|
|
3819
|
-
const name =
|
|
4185
|
+
if (!existsSync10(path)) {
|
|
4186
|
+
const name = basename6(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
3820
4187
|
scaffoldAgentFile(path, name);
|
|
3821
4188
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
3822
4189
|
}
|
|
@@ -3890,7 +4257,7 @@ function formatRelative(msAgo) {
|
|
|
3890
4257
|
if (hours < 48) return `${hours}h ago`;
|
|
3891
4258
|
return `${Math.round(hours / 24)}d ago`;
|
|
3892
4259
|
}
|
|
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
|
|
4260
|
+
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
4261
|
run(
|
|
3895
4262
|
async (opts) => {
|
|
3896
4263
|
if (opts.key && !opts.trace) throw new Error("--key requires --trace <traceId>");
|
|
@@ -3948,7 +4315,7 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3948
4315
|
console.log(` ${ws}`);
|
|
3949
4316
|
});
|
|
3950
4317
|
console.log();
|
|
3951
|
-
const rl =
|
|
4318
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
3952
4319
|
const answer = await new Promise(
|
|
3953
4320
|
(resolve8) => rl.question(
|
|
3954
4321
|
`Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
|
|
@@ -3972,11 +4339,11 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
3972
4339
|
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
3973
4340
|
if (!opts.json) {
|
|
3974
4341
|
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
|
|
4342
|
+
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
4343
|
}
|
|
3977
4344
|
if (!autoConfirm) {
|
|
3978
4345
|
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
3979
|
-
const prompt =
|
|
4346
|
+
const prompt = createInterface5({ input: process.stdin, output: process.stdout });
|
|
3980
4347
|
const answer = await new Promise((resolve8) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
3981
4348
|
prompt.close();
|
|
3982
4349
|
resolve8(value);
|
|
@@ -4014,9 +4381,13 @@ Importing ${label} \u2026
|
|
|
4014
4381
|
gaps: result.gaps,
|
|
4015
4382
|
attachedFiles: result.attachedFiles,
|
|
4016
4383
|
leftOutFiles: result.leftOutFiles.map((file) => ({ reason: file.reason })),
|
|
4384
|
+
skills: result.setup.skills,
|
|
4385
|
+
missingSkills: result.setup.missingSkills,
|
|
4386
|
+
mcpServers: result.setup.mcpServers,
|
|
4017
4387
|
continueUrl: result.url
|
|
4018
4388
|
}));
|
|
4019
4389
|
} else {
|
|
4390
|
+
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
4391
|
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
4392
|
console.log(` \u2713 Imported. Continue here:
|
|
4022
4393
|
${result.url}
|
|
@@ -4084,7 +4455,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
4084
4455
|
);
|
|
4085
4456
|
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
4457
|
run(async (taskId, path, local) => {
|
|
4087
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
4458
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync13(local), mediaTypeFor(local));
|
|
4088
4459
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
4089
4460
|
})
|
|
4090
4461
|
);
|
|
@@ -4520,7 +4891,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
4520
4891
|
}));
|
|
4521
4892
|
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
4893
|
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-
|
|
4894
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-PMUETYJS.js");
|
|
4524
4895
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
4525
4896
|
const scopes = parseContentScopes(opts.capture);
|
|
4526
4897
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -4555,7 +4926,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
4555
4926
|
}));
|
|
4556
4927
|
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
4928
|
try {
|
|
4558
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
4929
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-PMUETYJS.js");
|
|
4559
4930
|
const chunks = [];
|
|
4560
4931
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4561
4932
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -4568,14 +4939,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
4568
4939
|
}
|
|
4569
4940
|
});
|
|
4570
4941
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
4571
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
4942
|
+
const { readState, flush, grantSender } = await import("./trace-PMUETYJS.js");
|
|
4572
4943
|
const state = readState();
|
|
4573
4944
|
if (!state) throw new Error("no trace is running here");
|
|
4574
4945
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
4575
4946
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
4576
4947
|
}));
|
|
4577
4948
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
4578
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
4949
|
+
const { readState, spoolStatus } = await import("./trace-PMUETYJS.js");
|
|
4579
4950
|
const state = readState();
|
|
4580
4951
|
if (!state) {
|
|
4581
4952
|
console.log("No trace is running here.");
|
|
@@ -4585,7 +4956,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
4585
4956
|
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
4957
|
}));
|
|
4587
4958
|
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-
|
|
4959
|
+
const { readState } = await import("./trace-PMUETYJS.js");
|
|
4589
4960
|
const ref = id ?? readState()?.trace_id;
|
|
4590
4961
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
4591
4962
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -4612,7 +4983,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
4612
4983
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
4613
4984
|
}));
|
|
4614
4985
|
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-
|
|
4986
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-PMUETYJS.js");
|
|
4616
4987
|
const state = readState();
|
|
4617
4988
|
if (!state) throw new Error("no trace is running here");
|
|
4618
4989
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -4622,7 +4993,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
4622
4993
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
4623
4994
|
}));
|
|
4624
4995
|
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-
|
|
4996
|
+
const lib = await import("./trace-library-FIMBRED6.js");
|
|
4626
4997
|
if (opts.hook) {
|
|
4627
4998
|
try {
|
|
4628
4999
|
let raw = payload ?? "";
|
|
@@ -4670,10 +5041,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4670
5041
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
4671
5042
|
}
|
|
4672
5043
|
if (opts.installSchedule) {
|
|
4673
|
-
const { join:
|
|
4674
|
-
const { homedir:
|
|
5044
|
+
const { join: join12 } = await import("path");
|
|
5045
|
+
const { homedir: homedir8 } = await import("os");
|
|
4675
5046
|
const { execFileSync } = await import("child_process");
|
|
4676
|
-
const log =
|
|
5047
|
+
const log = join12(homedir8(), ".config", "sky", "trace-sync.log");
|
|
4677
5048
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
4678
5049
|
try {
|
|
4679
5050
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -4683,11 +5054,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4683
5054
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
4684
5055
|
}
|
|
4685
5056
|
if (opts.status) {
|
|
4686
|
-
const { readFileSync:
|
|
4687
|
-
const { join:
|
|
5057
|
+
const { readFileSync: readFileSync14 } = await import("fs");
|
|
5058
|
+
const { join: join12 } = await import("path");
|
|
4688
5059
|
let state = { sessions: {} };
|
|
4689
5060
|
try {
|
|
4690
|
-
state = JSON.parse(
|
|
5061
|
+
state = JSON.parse(readFileSync14(join12(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
4691
5062
|
} catch {
|
|
4692
5063
|
}
|
|
4693
5064
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -4720,7 +5091,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4720
5091
|
})();
|
|
4721
5092
|
});
|
|
4722
5093
|
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-
|
|
5094
|
+
const { libraryRequest } = await import("./trace-library-FIMBRED6.js");
|
|
4724
5095
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4725
5096
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4726
5097
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -4737,7 +5108,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
4737
5108
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
4738
5109
|
}));
|
|
4739
5110
|
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-
|
|
5111
|
+
const { libraryRequest } = await import("./trace-library-FIMBRED6.js");
|
|
4741
5112
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
4742
5113
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
4743
5114
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|