@agentsky/sky 2.24.0 → 2.24.1
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-277AZSSG.js → chunk-D3WG4BMK.js} +7 -2
- package/dist/{chunk-UHMKEYJN.js → chunk-KWLEADJQ.js} +3 -3
- package/dist/{chunk-RAFHOB5S.js → chunk-S4YG3TXA.js} +1 -1
- package/dist/index.js +1921 -1816
- package/dist/{src-FB5IETJP.js → src-BMXKAMOB.js} +1 -1
- package/dist/{trace-CFHCOGKB.js → trace-WLTD4C22.js} +2 -2
- package/dist/{trace-library-2VHTJCOY.js → trace-library-YMAVLH4Y.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
resolveSessionRef,
|
|
11
11
|
streamEvents,
|
|
12
12
|
uploadFile
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-KWLEADJQ.js";
|
|
14
14
|
import {
|
|
15
15
|
CLI_VERSION,
|
|
16
16
|
USER_AGENT,
|
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
currentUniverse,
|
|
22
22
|
loadConfig,
|
|
23
23
|
saveConfig
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-S4YG3TXA.js";
|
|
25
25
|
import {
|
|
26
26
|
AGENT_TYPES,
|
|
27
27
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -43,11 +43,11 @@ import {
|
|
|
43
43
|
skillRefSchema,
|
|
44
44
|
splitMcpServers,
|
|
45
45
|
supersededBy
|
|
46
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-D3WG4BMK.js";
|
|
47
47
|
import "./chunk-MLKGABMK.js";
|
|
48
48
|
|
|
49
49
|
// src/index.ts
|
|
50
|
-
import { existsSync as existsSync13, readFileSync as
|
|
50
|
+
import { existsSync as existsSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "fs";
|
|
51
51
|
import { basename as basename9, resolve as resolve9 } from "path";
|
|
52
52
|
import { createInterface as createInterface6 } from "readline";
|
|
53
53
|
import { Command } from "commander";
|
|
@@ -467,1886 +467,1898 @@ async function unsubscribeMarketing() {
|
|
|
467
467
|
return { marketing: notification_preferences.email.marketing };
|
|
468
468
|
}
|
|
469
469
|
|
|
470
|
-
// src/
|
|
471
|
-
|
|
472
|
-
|
|
470
|
+
// src/import-local.ts
|
|
471
|
+
import { closeSync as closeSync3, existsSync as existsSync4, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync6, readSync as readSync3, statSync as statSync5 } from "fs";
|
|
472
|
+
import { homedir as homedir3 } from "os";
|
|
473
|
+
import { join as join6 } from "path";
|
|
474
|
+
import { createHash as createHash3 } from "crypto";
|
|
475
|
+
|
|
476
|
+
// src/import-files.ts
|
|
477
|
+
import { isUtf8 } from "buffer";
|
|
478
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, statSync as statSync4 } from "fs";
|
|
479
|
+
import { homedir as homedir2 } from "os";
|
|
480
|
+
import { dirname as dirname3, extname as extname2, isAbsolute, join as join4, relative as relative2, resolve as resolve2, sep as sep2, basename as basename4 } from "path";
|
|
481
|
+
|
|
482
|
+
// src/import-workspace.ts
|
|
483
|
+
import { spawnSync } from "child_process";
|
|
484
|
+
import { existsSync as existsSync2, readdirSync as readdirSync2, readFileSync as readFileSync3, realpathSync, statSync as statSync3 } from "fs";
|
|
485
|
+
import { homedir } from "os";
|
|
486
|
+
import { basename as basename3, dirname as dirname2, join as join3, relative, sep } from "path";
|
|
487
|
+
import { parse as parseToml } from "smol-toml";
|
|
488
|
+
var MAX_TREE = 5e3;
|
|
489
|
+
var SKILL_LINE = /Base directory for this skill: (\/[^\n\r"'\\<>]+)/g;
|
|
490
|
+
function git(cwd, ...args) {
|
|
491
|
+
const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
|
|
492
|
+
return run2.status === 0 ? run2.stdout : null;
|
|
473
493
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
494
|
+
var isFile = (path) => {
|
|
495
|
+
try {
|
|
496
|
+
return statSync3(path).isFile();
|
|
497
|
+
} catch {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
};
|
|
501
|
+
var isDir = (path) => {
|
|
502
|
+
try {
|
|
503
|
+
return statSync3(path).isDirectory();
|
|
504
|
+
} catch {
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
function sharedInstructionPaths(home = homedir()) {
|
|
509
|
+
const rules = join3(home, ".agents", "rules");
|
|
510
|
+
let ruleFiles = [];
|
|
511
|
+
try {
|
|
512
|
+
ruleFiles = readdirSync2(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join3(rules, name));
|
|
513
|
+
} catch {
|
|
514
|
+
}
|
|
515
|
+
return [join3(home, "AGENTS.md"), join3(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
477
516
|
}
|
|
478
|
-
function
|
|
479
|
-
if (
|
|
480
|
-
|
|
517
|
+
function strings(value, out, depth = 0) {
|
|
518
|
+
if (depth > 12) return;
|
|
519
|
+
if (typeof value === "string") out.push(value);
|
|
520
|
+
else if (Array.isArray(value)) for (const item of value) strings(item, out, depth + 1);
|
|
521
|
+
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
481
522
|
}
|
|
482
|
-
function
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
523
|
+
function skillRoots(cwd, home) {
|
|
524
|
+
const claude = process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude");
|
|
525
|
+
const codex = process.env.CODEX_HOME ?? join3(home, ".codex");
|
|
526
|
+
return [
|
|
527
|
+
...cwd ? [join3(cwd, ".claude", "skills"), join3(cwd, ".agents", "skills")] : [],
|
|
528
|
+
join3(home, ".agents", "skills"),
|
|
529
|
+
join3(claude, "skills"),
|
|
530
|
+
join3(codex, "skills"),
|
|
531
|
+
join3(home, ".hermes", "skills")
|
|
532
|
+
];
|
|
490
533
|
}
|
|
491
|
-
function
|
|
492
|
-
|
|
534
|
+
function pluginSkill(plugin, skill, home) {
|
|
535
|
+
const walk = (dir, depth, underPlugin) => {
|
|
536
|
+
if (depth > 7) return null;
|
|
537
|
+
let entries;
|
|
538
|
+
try {
|
|
539
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
540
|
+
} catch {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
for (const entry2 of entries) {
|
|
544
|
+
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
545
|
+
const path = join3(dir, entry2.name);
|
|
546
|
+
if (entry2.name === skill && basename3(dir) === "skills" && (underPlugin || path.includes(`${sep}${plugin}${sep}`)) && isFile(join3(path, "SKILL.md"))) return realpathSync(path);
|
|
547
|
+
const found = walk(path, depth + 1, underPlugin);
|
|
548
|
+
if (found) return found;
|
|
549
|
+
}
|
|
550
|
+
return null;
|
|
551
|
+
};
|
|
552
|
+
return walk(join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "plugins"), 0, false) ?? walk(join3(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions", "skills-plugin"), 0, true);
|
|
493
553
|
}
|
|
494
|
-
function
|
|
495
|
-
const
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
554
|
+
function findSkill(name, cwd, home) {
|
|
555
|
+
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
556
|
+
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
557
|
+
for (const root of skillRoots(cwd, home)) {
|
|
558
|
+
const direct = join3(root, name);
|
|
559
|
+
if (isFile(join3(direct, "SKILL.md"))) return realpathSync(direct);
|
|
500
560
|
}
|
|
501
|
-
|
|
502
|
-
if (page.partial) lines.push(`partial: ${page.coverage ?? "not every source was searched"}`);
|
|
503
|
-
if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
|
|
504
|
-
return lines.join("\n");
|
|
561
|
+
return null;
|
|
505
562
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
capabilities: capabilitiesSchema.removeDefault().optional(),
|
|
527
|
-
// omitted = leave the agent's as they are
|
|
528
|
-
prompt: external_exports.string().optional(),
|
|
529
|
-
prompt_file: external_exports.string().optional(),
|
|
530
|
-
instructions: external_exports.array(fileInstructionSchema).optional(),
|
|
531
|
-
skills: external_exports.array(skillRefSchema).optional(),
|
|
532
|
-
customData: external_exports.array(customDataSchema).optional(),
|
|
533
|
-
/** "deny" makes the agent work alone: its sessions may not hire helpers. */
|
|
534
|
-
hiring_policy: external_exports.enum(["allow", "deny"]).optional(),
|
|
535
|
-
/** The environment its sessions boot in when they name none. */
|
|
536
|
-
environment: external_exports.string().min(1).optional(),
|
|
537
|
-
/** Advanced machine shape (create-only): 1|2|4|8 vCPUs, 256MB-step memory. */
|
|
538
|
-
vcpus: external_exports.number().int().optional(),
|
|
539
|
-
memory_mb: external_exports.number().int().optional()
|
|
540
|
-
});
|
|
541
|
-
function readAgentFile(path) {
|
|
542
|
-
const raw = readFileSync3(path, "utf8");
|
|
543
|
-
const parsed = agentFileSchema.safeParse(parseToml(raw));
|
|
544
|
-
if (!parsed.success) {
|
|
545
|
-
const issue = parsed.error.issues[0];
|
|
546
|
-
throw new Error(`${path}: ${issue?.path.join(".") ?? ""} ${issue?.message ?? "invalid"}`);
|
|
563
|
+
function loadedSkills(records, touched, cwd, home = homedir()) {
|
|
564
|
+
const dirs = /* @__PURE__ */ new Map();
|
|
565
|
+
const missing = /* @__PURE__ */ new Set();
|
|
566
|
+
const add = (dir, name = basename3(dir)) => {
|
|
567
|
+
if (!isFile(join3(dir, "SKILL.md"))) return false;
|
|
568
|
+
const real = realpathSync(dir);
|
|
569
|
+
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
570
|
+
return true;
|
|
571
|
+
};
|
|
572
|
+
const called = [];
|
|
573
|
+
for (const record of records) {
|
|
574
|
+
const texts = [];
|
|
575
|
+
strings(record, texts);
|
|
576
|
+
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].trim().replace(/[.,;:)]+$/, ""));
|
|
577
|
+
const content = record?.message?.content;
|
|
578
|
+
if (!Array.isArray(content)) continue;
|
|
579
|
+
for (const block of content) {
|
|
580
|
+
const b = block;
|
|
581
|
+
if (b?.type === "tool_use" && b.name === "Skill" && typeof b.input?.skill === "string") called.push(b.input.skill.replace(/^\//, ""));
|
|
582
|
+
}
|
|
547
583
|
}
|
|
548
|
-
const
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
);
|
|
584
|
+
for (const path of touched) if (basename3(path) === "SKILL.md") add(dirname2(path));
|
|
585
|
+
for (const name of called) {
|
|
586
|
+
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
587
|
+
const dir = findSkill(name, cwd, home);
|
|
588
|
+
if (!dir || !add(dir, name)) missing.add(name);
|
|
553
589
|
}
|
|
554
|
-
|
|
555
|
-
const inline = (ref, inlineContent) => {
|
|
556
|
-
if (inlineContent !== void 0) return inlineContent;
|
|
557
|
-
if (!ref) return void 0;
|
|
558
|
-
return readFileSync3(resolve2(base, ref), "utf8");
|
|
559
|
-
};
|
|
560
|
-
const spec = {
|
|
561
|
-
name: file.name,
|
|
562
|
-
description: file.description,
|
|
563
|
-
agentType: file.type,
|
|
564
|
-
llm: file.llm,
|
|
565
|
-
reasoningEffort: file.reasoning_effort,
|
|
566
|
-
displayName: file.name,
|
|
567
|
-
capabilities: file.capabilities,
|
|
568
|
-
prompt: inline(file.prompt_file, file.prompt),
|
|
569
|
-
instructions: file.instructions?.map((f) => {
|
|
570
|
-
const content = inline(f.file, f.content);
|
|
571
|
-
if (content === void 0) throw new Error(`instruction ${f.name}: content or file required`);
|
|
572
|
-
return { name: f.name, content };
|
|
573
|
-
}),
|
|
574
|
-
skills: file.skills,
|
|
575
|
-
customData: file.customData,
|
|
576
|
-
hiringPolicy: file.hiring_policy,
|
|
577
|
-
environmentId: file.environment,
|
|
578
|
-
vcpus: file.vcpus,
|
|
579
|
-
memoryMb: file.memory_mb
|
|
580
|
-
};
|
|
581
|
-
return { file, spec };
|
|
590
|
+
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
582
591
|
}
|
|
583
|
-
function
|
|
584
|
-
|
|
592
|
+
function readJson(path) {
|
|
593
|
+
try {
|
|
594
|
+
const value = JSON.parse(readFileSync3(path, "utf8"));
|
|
595
|
+
return value && typeof value === "object" ? value : null;
|
|
596
|
+
} catch {
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
585
599
|
}
|
|
586
|
-
function
|
|
600
|
+
function names(value) {
|
|
601
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
602
|
+
const keys2 = Object.keys(value).sort();
|
|
603
|
+
return keys2.length ? keys2 : void 0;
|
|
604
|
+
}
|
|
605
|
+
function safeUrl(raw) {
|
|
606
|
+
try {
|
|
607
|
+
const url = new URL(raw);
|
|
608
|
+
if (!/^https?:$/.test(url.protocol)) return null;
|
|
609
|
+
const query = [.../* @__PURE__ */ new Set([...url.searchParams.keys()])].sort();
|
|
610
|
+
return { url: `${url.protocol}//${url.host}${url.pathname}`, ...query.length ? { query } : {} };
|
|
611
|
+
} catch {
|
|
612
|
+
return null;
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
function serverEntry(name, def, scope) {
|
|
616
|
+
if (!def || typeof def !== "object") return null;
|
|
617
|
+
const env = names(def.env);
|
|
618
|
+
const headers = names(def.headers ?? def.http_headers);
|
|
619
|
+
if (typeof def.url === "string") {
|
|
620
|
+
const url = safeUrl(def.url);
|
|
621
|
+
if (!url) return null;
|
|
622
|
+
const type = def.type === "sse" ? "sse" : "http";
|
|
623
|
+
return { name, scope, transport: type, ...url, ...headers ? { headers } : {}, ...env ? { env } : {} };
|
|
624
|
+
}
|
|
625
|
+
if (typeof def.command !== "string" || !def.command) return null;
|
|
626
|
+
const args = Array.isArray(def.args) ? def.args.filter((arg) => typeof arg === "string") : [];
|
|
627
|
+
return { name, scope, transport: "stdio", command: def.command, ...args.length ? { args } : {}, ...env ? { env } : {} };
|
|
628
|
+
}
|
|
629
|
+
function mcpServers(harness2, cwd, home = homedir()) {
|
|
587
630
|
const out = [];
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
if (openString) {
|
|
594
|
-
if (line.includes(openString)) openString = null;
|
|
595
|
-
out.push(line);
|
|
596
|
-
continue;
|
|
631
|
+
const push = (servers, scope) => {
|
|
632
|
+
if (!servers || typeof servers !== "object") return;
|
|
633
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
634
|
+
const entry2 = serverEntry(name, def, scope);
|
|
635
|
+
if (entry2 && !out.some((existing) => existing.name === name)) out.push(entry2);
|
|
597
636
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
637
|
+
};
|
|
638
|
+
if (harness2 === "claude_code") {
|
|
639
|
+
const config = readJson(join3(process.env.CLAUDE_CONFIG_DIR ? dirname2(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
640
|
+
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
641
|
+
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
642
|
+
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
643
|
+
if (root) push(readJson(join3(root, ".mcp.json"))?.mcpServers, "project");
|
|
644
|
+
push(config?.mcpServers, "user");
|
|
645
|
+
}
|
|
646
|
+
if (harness2 === "codex") {
|
|
647
|
+
try {
|
|
648
|
+
const config = parseToml(readFileSync3(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "config.toml"), "utf8"));
|
|
649
|
+
push(config.mcp_servers, "user");
|
|
650
|
+
} catch {
|
|
605
651
|
}
|
|
606
|
-
if (topLevel && nameAt < 0 && /^\s*name\s*=/.test(line)) nameAt = out.length;
|
|
607
|
-
const opener = /=\s*("""|''')/.exec(line);
|
|
608
|
-
if (opener && !line.slice(opener.index + opener[0].length).includes(opener[1])) openString = opener[1];
|
|
609
|
-
out.push(line);
|
|
610
652
|
}
|
|
611
|
-
|
|
612
|
-
const next = out.join("\n");
|
|
613
|
-
if (parseToml(next).id !== id) throw new Error("could not stamp the agent id into the file");
|
|
614
|
-
return next;
|
|
653
|
+
return out;
|
|
615
654
|
}
|
|
616
|
-
function
|
|
617
|
-
|
|
618
|
-
writeFileSync2(
|
|
619
|
-
path,
|
|
620
|
-
`# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
|
|
621
|
-
name = "${name}"
|
|
622
|
-
# type = "pi" # pi (default) | hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode
|
|
623
|
-
# llm = "deepseek-flash" # must be compatible with type
|
|
624
|
-
# reasoning_effort = "high" # per harness \u2014 ${reasoningEffortDocLine()}
|
|
625
|
-
# capabilities = []
|
|
626
|
-
# hiring_policy = "deny" # its sessions work alone; "allow" (default) lets them hire helpers
|
|
627
|
-
# environment = "env_..." # the environment its sessions boot in when they name none
|
|
628
|
-
# [[skills]] # a skill from \`sky skills upload <dir>\`
|
|
629
|
-
# type = "skill"
|
|
630
|
-
# skillId = "skill_..."
|
|
631
|
-
|
|
632
|
-
prompt = """
|
|
633
|
-
You are ${name}, a helpful long-lived agent.
|
|
634
|
-
"""
|
|
635
|
-
`
|
|
636
|
-
);
|
|
655
|
+
function remoteWithoutCredentials(remote) {
|
|
656
|
+
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
637
657
|
}
|
|
638
|
-
function
|
|
658
|
+
function workspaceState(cwd, harness2, skills2, home = homedir(), title = null) {
|
|
659
|
+
const present = !!cwd && isDir(cwd);
|
|
660
|
+
const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
|
|
661
|
+
let gitState = null;
|
|
662
|
+
let tree = [];
|
|
663
|
+
let patch = null;
|
|
664
|
+
if (root) {
|
|
665
|
+
const dirty = (git(root, "status", "--porcelain=v1", "--untracked-files=normal") ?? "").split("\n").filter(Boolean).map((line) => line.slice(3));
|
|
666
|
+
const ahead = git(root, "rev-list", "--count", "@{upstream}..HEAD");
|
|
667
|
+
const remote = git(root, "remote", "get-url", "origin");
|
|
668
|
+
const branch = git(root, "rev-parse", "--abbrev-ref", "HEAD")?.trim() || null;
|
|
669
|
+
gitState = {
|
|
670
|
+
remote: remote ? remoteWithoutCredentials(remote) : null,
|
|
671
|
+
branch: branch === "HEAD" ? null : branch,
|
|
672
|
+
commit: git(root, "rev-parse", "HEAD")?.trim() || null,
|
|
673
|
+
dirty: dirty.slice(0, 500),
|
|
674
|
+
ahead: ahead === null ? null : Number(ahead.trim()) || 0
|
|
675
|
+
};
|
|
676
|
+
tree = (git(cwd, "-c", "core.quotePath=false", "ls-files") ?? "").split("\n").filter(Boolean);
|
|
677
|
+
if (dirty.length) patch = git(cwd, "diff", "HEAD", "--no-color", "--no-ext-diff", "--relative", "--", ".") || null;
|
|
678
|
+
}
|
|
639
679
|
return {
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
680
|
+
manifest: {
|
|
681
|
+
format: "agentsky.import-workspace",
|
|
682
|
+
format_version: 1,
|
|
683
|
+
harness: harness2,
|
|
684
|
+
title: title?.trim().slice(0, 120) || null,
|
|
685
|
+
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
686
|
+
project: cwd ? basename3(root ?? cwd) : null,
|
|
687
|
+
git: gitState,
|
|
688
|
+
tree: tree.slice(0, MAX_TREE),
|
|
689
|
+
tree_truncated: tree.length > MAX_TREE,
|
|
690
|
+
skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
|
|
691
|
+
mcp_servers: mcpServers(harness2, cwd, home)
|
|
692
|
+
},
|
|
693
|
+
patch
|
|
648
694
|
};
|
|
649
695
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
696
|
+
function skillFiles(skill) {
|
|
697
|
+
const out = [];
|
|
698
|
+
const walk = (dir, depth) => {
|
|
699
|
+
if (depth > 8 || out.length > 400) return;
|
|
700
|
+
let entries;
|
|
701
|
+
try {
|
|
702
|
+
entries = readdirSync2(dir, { withFileTypes: true });
|
|
703
|
+
} catch {
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
for (const entry2 of entries) {
|
|
707
|
+
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
708
|
+
const path = join3(dir, entry2.name);
|
|
709
|
+
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
710
|
+
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
711
|
+
}
|
|
712
|
+
};
|
|
713
|
+
if (existsSync2(skill.dir)) walk(skill.dir, 0);
|
|
714
|
+
return out;
|
|
715
|
+
}
|
|
716
|
+
function codexThreadName(sessionId, home = homedir()) {
|
|
717
|
+
let name = null;
|
|
718
|
+
try {
|
|
719
|
+
for (const line of readFileSync3(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
|
|
720
|
+
if (!line.includes(sessionId)) continue;
|
|
721
|
+
try {
|
|
722
|
+
const entry2 = JSON.parse(line);
|
|
723
|
+
if (entry2.id === sessionId && typeof entry2.thread_name === "string" && entry2.thread_name.trim()) name = entry2.thread_name.trim();
|
|
724
|
+
} catch {
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
} catch {
|
|
667
728
|
}
|
|
668
|
-
return
|
|
729
|
+
return name;
|
|
669
730
|
}
|
|
670
731
|
|
|
671
|
-
// src/
|
|
672
|
-
var
|
|
673
|
-
var
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
732
|
+
// src/import-files.ts
|
|
733
|
+
var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
|
|
734
|
+
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;
|
|
735
|
+
var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
|
|
736
|
+
var INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
|
|
737
|
+
var DOCUMENTS = {
|
|
738
|
+
".pdf": "application/pdf",
|
|
739
|
+
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
740
|
+
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
741
|
+
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
742
|
+
".doc": "application/msword",
|
|
743
|
+
".rtf": "application/rtf",
|
|
744
|
+
".odt": "application/vnd.oasis.opendocument.text"
|
|
745
|
+
};
|
|
746
|
+
var IMAGES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
747
|
+
var TEXT = {
|
|
748
|
+
".md": "text/markdown",
|
|
749
|
+
".mdx": "text/markdown",
|
|
750
|
+
".txt": "text/plain",
|
|
751
|
+
".csv": "text/csv",
|
|
752
|
+
".tsv": "text/tab-separated-values",
|
|
753
|
+
".html": "text/html",
|
|
754
|
+
".htm": "text/html",
|
|
755
|
+
".css": "text/css",
|
|
756
|
+
".scss": "text/css",
|
|
757
|
+
".json": "application/json",
|
|
758
|
+
".jsonl": "application/x-ndjson",
|
|
759
|
+
".ndjson": "application/x-ndjson",
|
|
760
|
+
".yaml": "application/yaml",
|
|
761
|
+
".yml": "application/yaml",
|
|
762
|
+
".toml": "application/toml",
|
|
763
|
+
".xml": "application/xml",
|
|
764
|
+
".sql": "application/sql",
|
|
765
|
+
".svg": "image/svg+xml",
|
|
766
|
+
".js": "application/javascript",
|
|
767
|
+
".mjs": "application/javascript",
|
|
768
|
+
".cjs": "application/javascript",
|
|
769
|
+
".py": "text/x-python",
|
|
770
|
+
".sh": "text/x-shellscript",
|
|
771
|
+
".bash": "text/x-shellscript",
|
|
772
|
+
".zsh": "text/x-shellscript"
|
|
773
|
+
};
|
|
774
|
+
var PLAIN_TEXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts", ".go", ".rs", ".java", ".kt", ".swift", ".rb", ".php", ".c", ".h", ".cc", ".cpp", ".hpp", ".cs", ".scala", ".lua", ".r", ".m", ".tex", ".bib", ".ipynb", ".prisma", ".graphql", ".proto", ".ini", ".cfg", ".conf", ".log", ".rst", ".vue", ".svelte", ".astro", ".tf", ".hcl", ".gradle", ".lock", ".patch", ".diff"]);
|
|
775
|
+
var PLAIN_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Makefile", "Procfile", "Gemfile", "Rakefile", "LICENSE", "README", ".cursorrules", ".gitignore", ".dockerignore", ".editorconfig", ".prettierrc", ".eslintrc"]);
|
|
776
|
+
function classify(path) {
|
|
777
|
+
const ext = extname2(path).toLowerCase();
|
|
778
|
+
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
779
|
+
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
780
|
+
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
781
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
|
|
782
|
+
return null;
|
|
680
783
|
}
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
784
|
+
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;
|
|
785
|
+
var PATH_KEYS = ["file_path", "path", "notebook_path", "filePath", "target_file"];
|
|
786
|
+
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
787
|
+
var str = (value) => typeof value === "string" ? value : "";
|
|
788
|
+
function shellText(input) {
|
|
789
|
+
if (typeof input === "string") return input;
|
|
790
|
+
const i = obj(input);
|
|
791
|
+
if (!i) return "";
|
|
792
|
+
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
793
|
+
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
794
|
+
}
|
|
795
|
+
function touchedPaths(records, cwd, home = homedir2()) {
|
|
796
|
+
const out = /* @__PURE__ */ new Set();
|
|
797
|
+
const add = (raw, base) => {
|
|
798
|
+
if (!raw || raw.length > 1e3) return;
|
|
799
|
+
const path = raw.replace(/^~(?=\/)/, home);
|
|
800
|
+
if (isAbsolute(path)) out.add(resolve2(path));
|
|
801
|
+
else if (base) out.add(resolve2(base, path));
|
|
802
|
+
};
|
|
803
|
+
const call = (name, input) => {
|
|
804
|
+
const i = obj(input);
|
|
805
|
+
const base = str(i?.workdir) || str(i?.cwd) || cwd;
|
|
806
|
+
if (i) for (const key of PATH_KEYS) add(str(i[key]), base);
|
|
807
|
+
const patch = typeof input === "string" ? input : str(i?.input) || str(i?.patch);
|
|
808
|
+
for (const match of patch.matchAll(/^\*\*\* (?:Add|Update) File: (.+)$/gm)) add(match[1].trim(), base);
|
|
809
|
+
if (!/bash|shell|exec|terminal|command|run/i.test(name)) return;
|
|
810
|
+
for (const match of shellText(input).replace(/\\n/g, "\n").matchAll(SHELL_READ)) add(match[1], base);
|
|
691
811
|
};
|
|
812
|
+
for (const raw of records) {
|
|
813
|
+
const record = obj(raw);
|
|
814
|
+
if (!record) continue;
|
|
815
|
+
const content = obj(record.message)?.content;
|
|
816
|
+
if (record.type === "assistant" && Array.isArray(content)) {
|
|
817
|
+
for (const block of content) {
|
|
818
|
+
const b = obj(block);
|
|
819
|
+
if (b?.type === "tool_use") call(str(b.name), b.input);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
const payload = obj(record.payload);
|
|
823
|
+
if (record.type === "response_item" && (payload?.type === "function_call" || payload?.type === "custom_tool_call")) {
|
|
824
|
+
let input = payload.arguments ?? payload.input ?? "";
|
|
825
|
+
if (typeof input === "string") {
|
|
826
|
+
try {
|
|
827
|
+
input = JSON.parse(input);
|
|
828
|
+
} catch {
|
|
829
|
+
input = payload.arguments ?? payload.input;
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
call(str(payload.name), input);
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
return [...out];
|
|
692
836
|
}
|
|
693
|
-
|
|
694
|
-
|
|
837
|
+
function gitRoot(dir) {
|
|
838
|
+
for (let d = dir; ; d = dirname3(d)) {
|
|
839
|
+
if (existsSync3(join4(d, ".git"))) return d;
|
|
840
|
+
if (d === dirname3(d)) return null;
|
|
841
|
+
}
|
|
695
842
|
}
|
|
696
|
-
|
|
697
|
-
|
|
843
|
+
function instructionPaths(cwd, harness2, home = homedir2()) {
|
|
844
|
+
const out = [];
|
|
845
|
+
if (cwd && existsSync3(cwd)) {
|
|
846
|
+
const root = gitRoot(cwd) ?? cwd;
|
|
847
|
+
for (let d = resolve2(cwd); ; d = dirname3(d)) {
|
|
848
|
+
for (const name of INSTRUCTION_FILES) out.push(join4(d, name));
|
|
849
|
+
if (d === root || d === dirname3(d)) break;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (harness2 === "claude_code") out.push(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "CLAUDE.md"));
|
|
853
|
+
if (harness2 === "codex") out.push(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "AGENTS.md"));
|
|
854
|
+
out.push(...sharedInstructionPaths(home));
|
|
855
|
+
return out.filter((path) => {
|
|
856
|
+
try {
|
|
857
|
+
return statSync4(path).isFile();
|
|
858
|
+
} catch {
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
});
|
|
698
862
|
}
|
|
699
|
-
|
|
700
|
-
|
|
863
|
+
function resourceName(path, cwd, home = homedir2()) {
|
|
864
|
+
const inside = cwd ? relative2(resolve2(cwd), path) : "";
|
|
865
|
+
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
866
|
+
const underHome = relative2(home, path);
|
|
867
|
+
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
868
|
+
return `files/${shown.split(sep2).join("/")}`;
|
|
701
869
|
}
|
|
702
|
-
|
|
703
|
-
|
|
870
|
+
var ORDER = { text: 0, document: 1, image: 2 };
|
|
871
|
+
function resourceLine(name, mediaType, data) {
|
|
872
|
+
return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
704
873
|
}
|
|
705
|
-
|
|
706
|
-
|
|
874
|
+
function collectSessionFiles(records, opts) {
|
|
875
|
+
const home = opts.home ?? homedir2();
|
|
876
|
+
const exclude = new Set((opts.exclude ?? []).map((path) => resolve2(path)));
|
|
877
|
+
const leftOut = [];
|
|
878
|
+
const candidates = [];
|
|
879
|
+
const seen = /* @__PURE__ */ new Set();
|
|
880
|
+
const touched = touchedPaths(records, opts.cwd, home);
|
|
881
|
+
const skills2 = loadedSkills(records, touched, opts.cwd, home);
|
|
882
|
+
const consider = (path, name) => {
|
|
883
|
+
if (seen.has(path) || exclude.has(path)) return;
|
|
884
|
+
seen.add(path);
|
|
885
|
+
if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
|
|
886
|
+
leftOut.push({ path, reason: "credential or build file" });
|
|
887
|
+
return;
|
|
888
|
+
}
|
|
889
|
+
let size;
|
|
890
|
+
try {
|
|
891
|
+
const stat = statSync4(path);
|
|
892
|
+
if (!stat.isFile()) return;
|
|
893
|
+
size = stat.size;
|
|
894
|
+
} catch {
|
|
895
|
+
leftOut.push({ path, reason: "missing" });
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const type = classify(path);
|
|
899
|
+
if (!type) {
|
|
900
|
+
leftOut.push({ path, reason: "unsupported type" });
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
if (size > MAX_ATTACHED_FILE_BYTES) {
|
|
904
|
+
leftOut.push({ path, reason: "too large" });
|
|
905
|
+
return;
|
|
906
|
+
}
|
|
907
|
+
candidates.push({ path, name, ...type, size });
|
|
908
|
+
};
|
|
909
|
+
for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
|
|
910
|
+
for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
|
|
911
|
+
candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
|
|
912
|
+
const lines = [];
|
|
913
|
+
const attached = [];
|
|
914
|
+
let bytes = 0;
|
|
915
|
+
const state = workspaceState(opts.cwd, opts.harness, skills2, home, opts.title ?? null);
|
|
916
|
+
const described = !!opts.cwd || !!state.manifest.title || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
917
|
+
const generated = [
|
|
918
|
+
...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
|
|
919
|
+
...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
|
|
920
|
+
];
|
|
921
|
+
for (const file of generated) {
|
|
922
|
+
let line;
|
|
923
|
+
try {
|
|
924
|
+
line = resourceLine(file.name, file.mediaType, file.data);
|
|
925
|
+
} catch {
|
|
926
|
+
leftOut.push({ path: file.name, reason: "could not be redacted safely" });
|
|
927
|
+
continue;
|
|
928
|
+
}
|
|
929
|
+
const size = Buffer.byteLength(line);
|
|
930
|
+
if (bytes + size > opts.budget) {
|
|
931
|
+
leftOut.push({ path: file.name, reason: "over the upload budget" });
|
|
932
|
+
continue;
|
|
933
|
+
}
|
|
934
|
+
bytes += size;
|
|
935
|
+
lines.push(line);
|
|
936
|
+
attached.push({ name: file.name, kind: "text", bytes: file.data.length });
|
|
937
|
+
}
|
|
938
|
+
for (const file of candidates) {
|
|
939
|
+
let data;
|
|
940
|
+
try {
|
|
941
|
+
data = readFileSync4(file.path);
|
|
942
|
+
} catch {
|
|
943
|
+
leftOut.push({ path: file.path, reason: "missing" });
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
if (file.kind === "text" && data.includes(0)) {
|
|
947
|
+
leftOut.push({ path: file.path, reason: "not valid UTF-8 text" });
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
let line;
|
|
951
|
+
try {
|
|
952
|
+
line = resourceLine(file.name, file.mediaType, data);
|
|
953
|
+
} catch {
|
|
954
|
+
leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
const size = Buffer.byteLength(line);
|
|
958
|
+
if (bytes + size > opts.budget) {
|
|
959
|
+
leftOut.push({ path: file.path, reason: "over the upload budget" });
|
|
960
|
+
continue;
|
|
961
|
+
}
|
|
962
|
+
bytes += size;
|
|
963
|
+
lines.push(line);
|
|
964
|
+
attached.push({ name: file.name, kind: file.kind, bytes: data.length });
|
|
965
|
+
}
|
|
966
|
+
const setup = {
|
|
967
|
+
skills: skills2.found.map((skill) => skill.name),
|
|
968
|
+
missingSkills: skills2.missing,
|
|
969
|
+
mcpServers: state.manifest.mcp_servers.map((server) => server.name),
|
|
970
|
+
git: state.manifest.git !== null
|
|
971
|
+
};
|
|
972
|
+
return { lines, bytes, attached, leftOut, setup };
|
|
707
973
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
974
|
+
|
|
975
|
+
// src/import-environment.ts
|
|
976
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
977
|
+
import { createHash as createHash2 } from "crypto";
|
|
978
|
+
import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, readSync as readSync2, rmSync as rmSync2 } from "fs";
|
|
979
|
+
import { tmpdir } from "os";
|
|
980
|
+
import { join as join5 } from "path";
|
|
981
|
+
import { isUtf8 as isUtf82 } from "buffer";
|
|
982
|
+
import { createGzip } from "zlib";
|
|
983
|
+
|
|
984
|
+
// ../../packages/spec/src/import-layout.ts
|
|
985
|
+
import { gzipSync } from "zlib";
|
|
986
|
+
function folderName(workspace2, sourceCwd) {
|
|
987
|
+
const raw = workspace2?.project || (sourceCwd ? sourceCwd.split("/").filter(Boolean).pop() : "") || "";
|
|
988
|
+
const cleaned = raw.replace(/[^A-Za-z0-9._ -]/g, "-").replace(/^[^A-Za-z0-9_]+/, "").replace(/\.\.+/g, ".").slice(0, 80).trim();
|
|
989
|
+
return cleaned || "imported-session";
|
|
990
|
+
}
|
|
991
|
+
function envName(server, field) {
|
|
992
|
+
return `${server}_${field}`.toUpperCase().replace(/[^A-Z0-9]+/g, "_").replace(/^_+|_+$/g, "");
|
|
993
|
+
}
|
|
994
|
+
function ownServer(server, platform) {
|
|
995
|
+
try {
|
|
996
|
+
return server.transport !== "stdio" && platform.ownHost.test(new URL(server.url ?? "").hostname);
|
|
997
|
+
} catch {
|
|
998
|
+
return false;
|
|
713
999
|
}
|
|
714
1000
|
}
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
1001
|
+
function mcpJson(servers, platform) {
|
|
1002
|
+
const entries = servers.filter((server) => !ownServer(server, platform)).map((server) => {
|
|
1003
|
+
if (server.transport === "stdio") {
|
|
1004
|
+
return [server.name, {
|
|
1005
|
+
command: server.command,
|
|
1006
|
+
...server.args?.length ? { args: server.args } : {},
|
|
1007
|
+
...server.env?.length ? { env: Object.fromEntries(server.env.map((name) => [name, `\${${name}:-}`])) } : {}
|
|
1008
|
+
}];
|
|
1009
|
+
}
|
|
1010
|
+
const query = server.query?.length ? `?${server.query.map((name) => `${encodeURIComponent(name)}=\${${envName(server.name, name)}:-}`).join("&")}` : "";
|
|
1011
|
+
return [server.name, {
|
|
1012
|
+
type: server.transport,
|
|
1013
|
+
url: `${server.url}${query}`,
|
|
1014
|
+
...server.headers?.length ? { headers: Object.fromEntries(server.headers.map((name) => [name, `\${${envName(server.name, name)}:-}`])) } : {}
|
|
1015
|
+
}];
|
|
1016
|
+
});
|
|
1017
|
+
return JSON.stringify({ mcpServers: Object.fromEntries(entries) }, null, 2) + "\n";
|
|
1018
|
+
}
|
|
1019
|
+
function bullet(items, none) {
|
|
1020
|
+
return items.length ? items.map((item) => `- ${item}`).join("\n") : `- ${none}`;
|
|
1021
|
+
}
|
|
1022
|
+
function readme(folder, workspace2, counts, skills2, mcpWritten, platform) {
|
|
1023
|
+
const git2 = workspace2?.git;
|
|
1024
|
+
const servers = workspace2?.mcp_servers ?? [];
|
|
1025
|
+
const missingSkills = (workspace2?.skills ?? []).filter((skill) => !skill.found).map((skill) => skill.name);
|
|
1026
|
+
const secrets2 = servers.filter((server) => !ownServer(server, platform)).flatMap((server) => [
|
|
1027
|
+
...server.env ?? [],
|
|
1028
|
+
...(server.headers ?? []).map((name) => envName(server.name, name)),
|
|
1029
|
+
...(server.query ?? []).map((name) => envName(server.name, name))
|
|
1030
|
+
]);
|
|
1031
|
+
const where = workspace2?.cwd ? ` (${workspace2.cwd})` : "";
|
|
719
1032
|
return [
|
|
720
|
-
|
|
721
|
-
agent2.tagline,
|
|
722
|
-
...agent2.description && agent2.description !== agent2.tagline ? ["", agent2.description] : [],
|
|
1033
|
+
"# Imported working copy",
|
|
723
1034
|
"",
|
|
724
|
-
`
|
|
725
|
-
|
|
726
|
-
...section("Does", agent2.does),
|
|
727
|
-
...section("Does not", agent2.does_not),
|
|
728
|
-
...section("Examples", agent2.examples),
|
|
1035
|
+
`This folder (/workspace/${folder}) holds what the imported conversation worked with on the person's computer.`,
|
|
1036
|
+
"It is historical context: verify a path before acting on it, and follow the person's current message.",
|
|
729
1037
|
"",
|
|
730
|
-
|
|
1038
|
+
"## Files",
|
|
1039
|
+
counts.wholeFolder ? `- The whole working directory${where}, ${counts.project} files at the same relative paths: its tracked files and new files git does not ignore (every file when it is not a repository), without version-control data, dependency and build output, or credential files.` : `- ${counts.project} files from the original working directory${where}, at the same relative paths: the files the conversation read, wrote or edited and its instruction files. The rest of the working tree did not travel${workspace2?.tree?.length ? `; its full file list is in ${platform.notesDir}/workspace.json (\`tree\`)` : ""}.`,
|
|
1040
|
+
...counts.outside ? [`- ${counts.outside} files from elsewhere on that computer (shared instructions such as ~/AGENTS.md, and files outside the working directory) under ${platform.notesDir}/files/, with ~ as home/.`] : [],
|
|
1041
|
+
...counts.attachments ? [`- ${counts.attachments} attachments of the conversation under ${platform.notesDir}/attachments/.`] : [],
|
|
1042
|
+
"- Text was redacted on that computer before upload: a masked value (\u25CF\u25CF\u25CF\u25CF\u25CF) is a secret that was removed, not the real content.",
|
|
1043
|
+
"",
|
|
1044
|
+
"## Repository",
|
|
1045
|
+
...git2 ? [
|
|
1046
|
+
`- Remote: ${git2.remote ?? "none"}`,
|
|
1047
|
+
`- Branch: ${git2.branch ?? "detached"} at commit ${git2.commit ?? "unknown"}${git2.ahead ? ` (${git2.ahead} commits not pushed)` : ""}`,
|
|
1048
|
+
...git2.dirty?.length ? [`- Uncommitted changes in ${git2.dirty.length} paths${counts.wholeFolder ? " are already in these files" : ""}; the change against that commit is ${platform.notesDir}/uncommitted.patch (apply with \`git apply\` on a checkout of that commit).`] : ["- No uncommitted changes."],
|
|
1049
|
+
counts.wholeFolder ? "- This folder's own git history starts with this copy; the project's history is on the remote." : `- To get the whole repository, clone the remote at that commit (connect GitHub in ${platform.name} if it is private), apply the patch, then use the files here.`
|
|
1050
|
+
] : ["- The working directory was not a git repository."],
|
|
1051
|
+
"",
|
|
1052
|
+
"## Skills",
|
|
1053
|
+
bullet(skills2.map((name) => `${name}: .claude/skills/${name}/SKILL.md (also .agents/skills/${name}/)`), "No skills were loaded."),
|
|
1054
|
+
...missingSkills.length ? ["", `Loaded but no longer on that computer, so not carried: ${missingSkills.join(", ")}.`] : [],
|
|
1055
|
+
"",
|
|
1056
|
+
"## Tool servers (MCP)",
|
|
1057
|
+
bullet(servers.map((server) => `${server.name} (${server.transport}${server.scope ? `, ${server.scope}` : ""}): ${server.transport === "stdio" ? [server.command, ...server.args ?? []].join(" ") : server.url}`), "None were configured."),
|
|
1058
|
+
...mcpWritten ? ["", `They are written to .mcp.json in this folder with every key as \${NAME:-}, except ${platform.name}'s own server, whose tools this session already has. Their values never leave the person's computer${secrets2.length ? `; these must be supplied to use them: ${[...new Set(secrets2)].join(", ")}` : ""}. A stdio server runs only if its command is installed here, and Claude Code connects a project server only once it is approved.`] : [],
|
|
1059
|
+
""
|
|
731
1060
|
].join("\n");
|
|
732
1061
|
}
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
import { spawn } from "child_process";
|
|
736
|
-
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
737
|
-
import { homedir } from "os";
|
|
738
|
-
import { basename as basename3, join as join3 } from "path";
|
|
739
|
-
import { createInterface } from "readline";
|
|
740
|
-
var PROMPT_CAP = 1e5;
|
|
741
|
-
var HARNESSES = ["claude_code", "codex", "openclaw"];
|
|
742
|
-
function claudeConfigRoot() {
|
|
743
|
-
return process.env.CLAUDE_CONFIG_DIR ?? join3(homedir(), ".claude");
|
|
744
|
-
}
|
|
745
|
-
function readIfExists(path) {
|
|
1062
|
+
function parseWorkspace(file) {
|
|
1063
|
+
if (!file) return null;
|
|
746
1064
|
try {
|
|
747
|
-
|
|
1065
|
+
const value = JSON.parse(file.data.toString("utf8"));
|
|
1066
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
748
1067
|
} catch {
|
|
749
|
-
return
|
|
1068
|
+
return null;
|
|
750
1069
|
}
|
|
751
1070
|
}
|
|
752
|
-
function
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1071
|
+
function importLayout(files2, sourceCwd, platform, wholeFolder) {
|
|
1072
|
+
if (!files2.length && !wholeFolder) return null;
|
|
1073
|
+
const workspace2 = parseWorkspace(files2.find((file) => file.name === "agentsky/workspace.json"));
|
|
1074
|
+
const folder = folderName(workspace2, sourceCwd);
|
|
1075
|
+
const entries = /* @__PURE__ */ new Map();
|
|
1076
|
+
const skills2 = /* @__PURE__ */ new Set();
|
|
1077
|
+
const counts = { project: wholeFolder?.files ?? 0, outside: 0, attachments: 0, wholeFolder: Boolean(wholeFolder) };
|
|
1078
|
+
for (const file of files2) {
|
|
1079
|
+
const [kind, ...rest] = file.name.split("/");
|
|
1080
|
+
const path = rest.join("/");
|
|
1081
|
+
if (kind === "workspace") {
|
|
1082
|
+
if (wholeFolder) continue;
|
|
1083
|
+
entries.set(path, file.data);
|
|
1084
|
+
counts.project += 1;
|
|
1085
|
+
} else if (kind === "files") {
|
|
1086
|
+
entries.set(`${platform.notesDir}/files/${path.replace(/^~\//, "home/")}`, file.data);
|
|
1087
|
+
counts.outside += 1;
|
|
1088
|
+
} else if (kind === "imported-attachments") {
|
|
1089
|
+
entries.set(`${platform.notesDir}/attachments/${path}`, file.data);
|
|
1090
|
+
counts.attachments += 1;
|
|
1091
|
+
} else if (kind === "agentsky") entries.set(`${platform.notesDir}/${path}`, file.data);
|
|
1092
|
+
else if (kind === "skills" && rest.length > 1) {
|
|
1093
|
+
const skill = rest[0].split(":").pop();
|
|
1094
|
+
const inside = rest.slice(1).join("/");
|
|
1095
|
+
skills2.add(skill);
|
|
1096
|
+
entries.set(`.claude/skills/${skill}/${inside}`, file.data);
|
|
1097
|
+
entries.set(`.agents/skills/${skill}/${inside}`, file.data);
|
|
1098
|
+
}
|
|
759
1099
|
}
|
|
1100
|
+
const servers = (workspace2?.mcp_servers ?? []).filter((server) => server && typeof server.name === "string" && (server.transport === "stdio" ? typeof server.command === "string" : typeof server.url === "string"));
|
|
1101
|
+
const mcpWritten = servers.length > 0 && !entries.has(".mcp.json") && !wholeFolder?.paths.has(".mcp.json");
|
|
1102
|
+
if (mcpWritten) entries.set(".mcp.json", Buffer.from(mcpJson(servers, platform)));
|
|
1103
|
+
entries.set(`${platform.notesDir}/README.md`, Buffer.from(readme(folder, workspace2 ? { ...workspace2, mcp_servers: servers } : null, counts, [...skills2], mcpWritten, platform)));
|
|
1104
|
+
if (wholeFolder) for (const path of wholeFolder.paths) entries.delete(path);
|
|
1105
|
+
return { folder, entries, skills: [...skills2], mcpServers: servers.map((server) => server.name) };
|
|
760
1106
|
}
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
1107
|
+
var TAR_BLOCK = 512;
|
|
1108
|
+
var TAR_END = Buffer.alloc(TAR_BLOCK * 2);
|
|
1109
|
+
function header(name, size, type) {
|
|
1110
|
+
const block = Buffer.alloc(TAR_BLOCK);
|
|
1111
|
+
block.write(name, 0, 100, "utf8");
|
|
1112
|
+
block.write("0000644\0", 100);
|
|
1113
|
+
block.write("0000000\0", 108);
|
|
1114
|
+
block.write("0000000\0", 116);
|
|
1115
|
+
block.write(size.toString(8).padStart(11, "0") + "\0", 124);
|
|
1116
|
+
block.write("00000000000\0", 136);
|
|
1117
|
+
block.write(" ", 148);
|
|
1118
|
+
block.write(type, 156);
|
|
1119
|
+
block.write("ustar\x0000", 257);
|
|
1120
|
+
let sum = 0;
|
|
1121
|
+
for (const byte of block) sum += byte;
|
|
1122
|
+
block.write(sum.toString(8).padStart(6, "0") + "\0 ", 148);
|
|
1123
|
+
return block;
|
|
1124
|
+
}
|
|
1125
|
+
function paxRecord(key, value) {
|
|
1126
|
+
const body = ` ${key}=${value}
|
|
1127
|
+
`;
|
|
1128
|
+
let length = Buffer.byteLength(body);
|
|
1129
|
+
for (; ; ) {
|
|
1130
|
+
const total = String(length).length + length;
|
|
1131
|
+
if (String(total).length + length === total) return Buffer.from(`${total}${body}`);
|
|
1132
|
+
length += 1;
|
|
770
1133
|
}
|
|
771
|
-
return void 0;
|
|
772
1134
|
}
|
|
773
|
-
function
|
|
774
|
-
const
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
);
|
|
781
|
-
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
782
|
-
return merged;
|
|
1135
|
+
function tarPadding(size) {
|
|
1136
|
+
const rest = size % TAR_BLOCK;
|
|
1137
|
+
return Buffer.alloc(rest ? TAR_BLOCK - rest : 0);
|
|
1138
|
+
}
|
|
1139
|
+
function tarHeader(path, size) {
|
|
1140
|
+
if (Buffer.byteLength(path) <= 100) return header(path, size, "0");
|
|
1141
|
+
const pax = paxRecord("path", path);
|
|
1142
|
+
return Buffer.concat([header("PaxHeader", pax.length, "x"), pax, tarPadding(pax.length), header(path, size, "0")]);
|
|
783
1143
|
}
|
|
784
|
-
function assemblePrompt(parts) {
|
|
785
|
-
const joined = parts.map((p) => `# \u2500\u2500 ${p.label} \u2500\u2500
|
|
786
1144
|
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
1145
|
+
// src/import-environment.ts
|
|
1146
|
+
var IMPORT_PLATFORM = { name: "AgentSky", ownHost: /(^|\.)(agentsky\.dev|asteroidbelt\.ai)$/i, notesDir: ".agentsky-import" };
|
|
1147
|
+
var MAX_ENVIRONMENT_FILES = 2e5;
|
|
1148
|
+
var MAX_ENVIRONMENT_BYTES = 2 * 1024 ** 3;
|
|
1149
|
+
var MAX_REDACTED_TEXT_BYTES = 10 * 1024 * 1024;
|
|
1150
|
+
var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules", ".venv", "venv", "__pycache__", ".next", "dist", "build", "target", ".DS_Store", ".cache", ".turbo"]);
|
|
1151
|
+
var SECRET_FILE2 = /(^|\/)(\.env(\..*)?|\.netrc|\.npmrc|\.pypirc|\.git-credentials|\.?credentials(\.json)?|auth\.json|\.claude\.json|id_(rsa|ed25519|ecdsa)[^/]*|.*\.(pem|key|p12|pfx|keystore|jks|kdbx))$|(^|\/)\.ssh\/|(^|\/)\.aws\/|(^|\/)\.gnupg\/|(^|\/)\.docker\/config\.json$/i;
|
|
1152
|
+
function listFiles(cwd) {
|
|
1153
|
+
const git2 = spawnSync2("git", ["-C", cwd, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8", maxBuffer: 512 * 1024 * 1024 });
|
|
1154
|
+
if (git2.status === 0) return git2.stdout.split("\0").filter(Boolean).filter((path) => !path.split("/").some((part) => part === ".git"));
|
|
1155
|
+
const out = [];
|
|
1156
|
+
const walk = (dir, prefix) => {
|
|
1157
|
+
let entries;
|
|
1158
|
+
try {
|
|
1159
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
1160
|
+
} catch {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
for (const entry2 of entries) {
|
|
1164
|
+
if (SKIPPED.has(entry2.name)) continue;
|
|
1165
|
+
const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
1166
|
+
if (entry2.isDirectory()) walk(join5(dir, entry2.name), rel);
|
|
1167
|
+
else out.push(rel);
|
|
1168
|
+
if (out.length > MAX_ENVIRONMENT_FILES) return;
|
|
1169
|
+
}
|
|
1170
|
+
};
|
|
1171
|
+
walk(cwd, "");
|
|
1172
|
+
return out;
|
|
791
1173
|
}
|
|
792
|
-
function
|
|
793
|
-
|
|
794
|
-
const
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
}
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
1174
|
+
function textOf(path, size) {
|
|
1175
|
+
if (size > MAX_REDACTED_TEXT_BYTES) return null;
|
|
1176
|
+
const data = readFileSync5(path);
|
|
1177
|
+
if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
|
|
1178
|
+
return data;
|
|
1179
|
+
}
|
|
1180
|
+
async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
1181
|
+
const listed = listFiles(cwd);
|
|
1182
|
+
if (listed.length > MAX_ENVIRONMENT_FILES) throw new Error(`The working folder has more than ${MAX_ENVIRONMENT_FILES} files; import it with --no-environment.`);
|
|
1183
|
+
const scratch = mkdtempSync(join5(tmpdir(), "sky-environment-"));
|
|
1184
|
+
const path = join5(scratch, "environment.tar.gz");
|
|
1185
|
+
const leftOut = [];
|
|
1186
|
+
const written2 = /* @__PURE__ */ new Set();
|
|
1187
|
+
const hash = createHash2("sha256");
|
|
1188
|
+
let bytes = 0;
|
|
1189
|
+
let redacted2 = 0;
|
|
1190
|
+
const gzip = createGzip({ level: 6 });
|
|
1191
|
+
const sink = createWriteStream(path);
|
|
1192
|
+
gzip.on("data", (chunk) => {
|
|
1193
|
+
bytes += chunk.length;
|
|
1194
|
+
hash.update(chunk);
|
|
1195
|
+
});
|
|
1196
|
+
gzip.pipe(sink);
|
|
1197
|
+
const done = new Promise((resolve10, reject) => {
|
|
1198
|
+
sink.on("finish", () => resolve10());
|
|
1199
|
+
sink.on("error", reject);
|
|
1200
|
+
gzip.on("error", reject);
|
|
1201
|
+
});
|
|
1202
|
+
const write = (chunk) => new Promise((resolve10, reject) => {
|
|
1203
|
+
if (bytes > MAX_ENVIRONMENT_BYTES) {
|
|
1204
|
+
reject(new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`));
|
|
1205
|
+
return;
|
|
807
1206
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
1207
|
+
if (gzip.write(chunk)) resolve10();
|
|
1208
|
+
else gzip.once("drain", resolve10);
|
|
1209
|
+
});
|
|
1210
|
+
try {
|
|
1211
|
+
for (const rel of listed) {
|
|
1212
|
+
const full = join5(cwd, ...rel.split("/"));
|
|
1213
|
+
if (SECRET_FILE2.test(rel)) {
|
|
1214
|
+
leftOut.push({ path: rel, reason: "credential file" });
|
|
1215
|
+
continue;
|
|
1216
|
+
}
|
|
1217
|
+
let stat;
|
|
1218
|
+
try {
|
|
1219
|
+
stat = lstatSync(full);
|
|
1220
|
+
} catch {
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
if (!stat.isFile()) {
|
|
1224
|
+
leftOut.push({ path: rel, reason: "not a regular file" });
|
|
1225
|
+
continue;
|
|
1226
|
+
}
|
|
1227
|
+
const text = textOf(full, stat.size);
|
|
1228
|
+
if (text) {
|
|
1229
|
+
const masked = redactSecretValues(text.toString("utf8"));
|
|
1230
|
+
if (Object.keys(masked.hits).length) redacted2 += 1;
|
|
1231
|
+
const data = Buffer.from(masked.text, "utf8");
|
|
1232
|
+
await write(tarHeader(rel, data.length));
|
|
1233
|
+
await write(data);
|
|
1234
|
+
await write(tarPadding(data.length));
|
|
1235
|
+
} else {
|
|
1236
|
+
await write(tarHeader(rel, stat.size));
|
|
1237
|
+
const fd = openSync2(full, "r");
|
|
1238
|
+
try {
|
|
1239
|
+
const chunk = Buffer.alloc(1024 * 1024);
|
|
1240
|
+
let left = stat.size;
|
|
1241
|
+
for (let offset = 0; left > 0; ) {
|
|
1242
|
+
const read = readSync2(fd, chunk, 0, Math.min(chunk.length, left), offset);
|
|
1243
|
+
if (read <= 0) throw new Error(`${rel} changed while it was packed; import again.`);
|
|
1244
|
+
await write(Buffer.from(chunk.subarray(0, read)));
|
|
1245
|
+
offset += read;
|
|
1246
|
+
left -= read;
|
|
1247
|
+
}
|
|
1248
|
+
} finally {
|
|
1249
|
+
closeSync2(fd);
|
|
1250
|
+
}
|
|
1251
|
+
await write(tarPadding(stat.size));
|
|
820
1252
|
}
|
|
1253
|
+
written2.add(rel);
|
|
1254
|
+
}
|
|
1255
|
+
const layout = importLayout(resources, cwd, platform, { files: written2.size, paths: written2 });
|
|
1256
|
+
for (const [entry2, data] of layout?.entries ?? []) {
|
|
1257
|
+
await write(tarHeader(entry2, data.length));
|
|
1258
|
+
await write(data);
|
|
1259
|
+
await write(tarPadding(data.length));
|
|
821
1260
|
}
|
|
1261
|
+
await write(TAR_END);
|
|
1262
|
+
gzip.end();
|
|
1263
|
+
await done;
|
|
1264
|
+
if (bytes > MAX_ENVIRONMENT_BYTES) throw new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`);
|
|
1265
|
+
return {
|
|
1266
|
+
path,
|
|
1267
|
+
folder: layout?.folder ?? "imported-session",
|
|
1268
|
+
bytes,
|
|
1269
|
+
sha256: hash.digest("hex"),
|
|
1270
|
+
files: written2.size + (layout?.entries.size ?? 0),
|
|
1271
|
+
redacted: redacted2,
|
|
1272
|
+
leftOut,
|
|
1273
|
+
dispose: () => rmSync2(scratch, { recursive: true, force: true })
|
|
1274
|
+
};
|
|
1275
|
+
} catch (error) {
|
|
1276
|
+
gzip.destroy();
|
|
1277
|
+
sink.destroy();
|
|
1278
|
+
rmSync2(scratch, { recursive: true, force: true });
|
|
1279
|
+
throw error;
|
|
822
1280
|
}
|
|
823
|
-
return {
|
|
824
|
-
harness: "claude_code",
|
|
825
|
-
configRoot,
|
|
826
|
-
prompt,
|
|
827
|
-
promptSources: sources,
|
|
828
|
-
truncated,
|
|
829
|
-
model: claudeModel(configRoot),
|
|
830
|
-
keptServers: kept,
|
|
831
|
-
droppedServers: dropped,
|
|
832
|
-
secretCandidates
|
|
833
|
-
};
|
|
834
1281
|
}
|
|
835
|
-
function
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
1282
|
+
function resourcesOf(lines) {
|
|
1283
|
+
return lines.flatMap((line) => {
|
|
1284
|
+
try {
|
|
1285
|
+
const record = JSON.parse(line);
|
|
1286
|
+
if (record.type !== "import.resource" || typeof record.name !== "string" || typeof record.media_type !== "string" || typeof record.data !== "string") return [];
|
|
1287
|
+
return [{ name: record.name, mediaType: record.media_type, data: Buffer.from(record.data, "base64") }];
|
|
1288
|
+
} catch {
|
|
1289
|
+
return [];
|
|
839
1290
|
}
|
|
840
|
-
|
|
841
|
-
}
|
|
842
|
-
if (existsSync3(claudeConfigRoot())) return "claude_code";
|
|
843
|
-
if (existsSync3(join3(homedir(), ".codex"))) return "codex";
|
|
844
|
-
if (existsSync3(join3(homedir(), ".openclaw"))) return "openclaw";
|
|
845
|
-
throw new Error(
|
|
846
|
-
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
847
|
-
);
|
|
1291
|
+
});
|
|
848
1292
|
}
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
);
|
|
1293
|
+
|
|
1294
|
+
// src/import-local.ts
|
|
1295
|
+
function redactLine2(raw) {
|
|
1296
|
+
return redactLine(raw).line;
|
|
854
1297
|
}
|
|
855
|
-
function
|
|
856
|
-
|
|
1298
|
+
function decodeWorkspace(encoded) {
|
|
1299
|
+
try {
|
|
1300
|
+
return decodeURIComponent(encoded.replace(/-/g, "%").replace(/_/g, "%5F"));
|
|
1301
|
+
} catch {
|
|
1302
|
+
return encoded;
|
|
1303
|
+
}
|
|
857
1304
|
}
|
|
858
|
-
function
|
|
859
|
-
const
|
|
860
|
-
|
|
1305
|
+
function readPrefix(filePath) {
|
|
1306
|
+
const fd = openSync3(filePath, "r");
|
|
1307
|
+
try {
|
|
1308
|
+
const bytes = Buffer.alloc(256 * 1024);
|
|
1309
|
+
return bytes.subarray(0, readSync3(fd, bytes, 0, bytes.length, 0)).toString("utf8");
|
|
1310
|
+
} finally {
|
|
1311
|
+
closeSync3(fd);
|
|
1312
|
+
}
|
|
861
1313
|
}
|
|
862
|
-
function
|
|
863
|
-
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link2, null, 2)}
|
|
864
|
-
`);
|
|
865
|
-
}
|
|
866
|
-
var DEFAULT_VAULT_NAME = "default";
|
|
867
|
-
async function fetchDefaultVaultId() {
|
|
868
|
-
const { vaults } = await api(
|
|
869
|
-
"GET",
|
|
870
|
-
"/vaults"
|
|
871
|
-
);
|
|
872
|
-
return vaults.find((v) => v.display_name === DEFAULT_VAULT_NAME)?.id ?? null;
|
|
873
|
-
}
|
|
874
|
-
async function ensureDefaultVaultId() {
|
|
875
|
-
const existing = await fetchDefaultVaultId();
|
|
876
|
-
if (existing) return existing;
|
|
1314
|
+
function sniffTitle(filePath) {
|
|
877
1315
|
try {
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
1316
|
+
const data = readPrefix(filePath);
|
|
1317
|
+
for (const raw of data.split("\n")) {
|
|
1318
|
+
if (!raw.trim()) continue;
|
|
1319
|
+
let line;
|
|
1320
|
+
try {
|
|
1321
|
+
line = JSON.parse(redactLine2(raw));
|
|
1322
|
+
} catch {
|
|
1323
|
+
continue;
|
|
1324
|
+
}
|
|
1325
|
+
if (!line || typeof line !== "object") continue;
|
|
1326
|
+
const rec = line;
|
|
1327
|
+
if (rec.type === "user" && rec.message && typeof rec.message === "object") {
|
|
1328
|
+
const msg = rec.message;
|
|
1329
|
+
if (Array.isArray(msg.content)) {
|
|
1330
|
+
for (const block of msg.content) {
|
|
1331
|
+
if (typeof block === "object" && block !== null) {
|
|
1332
|
+
const b = block;
|
|
1333
|
+
if (b.type === "text" && typeof b.text === "string" && b.text.trim()) {
|
|
1334
|
+
return b.text.trim().slice(0, 120);
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
1340
|
+
return msg.content.trim().slice(0, 120);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
if (rec.type === "event_msg" && rec.payload && typeof rec.payload === "object") {
|
|
1344
|
+
const payload = rec.payload;
|
|
1345
|
+
if (payload.type === "user_message" && typeof payload.message === "string") return redactLine2(payload.message).slice(0, 120);
|
|
1346
|
+
}
|
|
1347
|
+
if (rec.type === "response_item" && rec.payload && typeof rec.payload === "object") {
|
|
1348
|
+
const payload = rec.payload;
|
|
1349
|
+
if (payload.type === "message" && payload.role === "user" && Array.isArray(payload.content)) {
|
|
1350
|
+
const text = payload.content.find((part) => typeof part?.text === "string")?.text;
|
|
1351
|
+
if (typeof text === "string" && text.trim()) return redactLine2(text.trim()).slice(0, 120);
|
|
1352
|
+
}
|
|
1353
|
+
}
|
|
1354
|
+
if (rec.type === "session_meta") continue;
|
|
1355
|
+
if (rec.type === "user_turn" || rec.type === "message" && rec.role === "user") {
|
|
1356
|
+
const content = rec.content;
|
|
1357
|
+
if (typeof content === "string" && content.trim()) return content.trim().slice(0, 120);
|
|
1358
|
+
}
|
|
886
1359
|
}
|
|
887
|
-
|
|
1360
|
+
return null;
|
|
1361
|
+
} catch {
|
|
1362
|
+
return null;
|
|
888
1363
|
}
|
|
889
1364
|
}
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1365
|
+
function listClaudeCodeSessions(limit) {
|
|
1366
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join6(homedir3(), ".claude");
|
|
1367
|
+
const projectsDir = join6(root, "projects");
|
|
1368
|
+
if (!existsSync4(projectsDir)) return [];
|
|
1369
|
+
const sessions = [];
|
|
1370
|
+
try {
|
|
1371
|
+
for (const encoded of readdirSync4(projectsDir)) {
|
|
1372
|
+
const projectDir = join6(projectsDir, encoded);
|
|
1373
|
+
let entries;
|
|
1374
|
+
try {
|
|
1375
|
+
entries = readdirSync4(projectDir);
|
|
1376
|
+
} catch {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
const workspace2 = decodeWorkspace(encoded);
|
|
1380
|
+
for (const entry2 of entries) {
|
|
1381
|
+
if (!entry2.endsWith(".jsonl")) continue;
|
|
1382
|
+
const sessionId = entry2.slice(0, -6);
|
|
1383
|
+
const mainFile = join6(projectDir, entry2);
|
|
1384
|
+
let stat;
|
|
1385
|
+
try {
|
|
1386
|
+
stat = statSync5(mainFile);
|
|
1387
|
+
} catch {
|
|
1388
|
+
continue;
|
|
1389
|
+
}
|
|
1390
|
+
if (!stat.isFile()) continue;
|
|
1391
|
+
let discoveryError;
|
|
1392
|
+
const subagentFiles = [];
|
|
1393
|
+
const subagentDir = join6(projectDir, sessionId, "subagents");
|
|
1394
|
+
if (existsSync4(subagentDir)) {
|
|
1395
|
+
try {
|
|
1396
|
+
const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1397
|
+
for (const f of subFiles) {
|
|
1398
|
+
subagentFiles.push(join6(subagentDir, f));
|
|
1399
|
+
}
|
|
1400
|
+
} catch {
|
|
1401
|
+
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
1402
|
+
}
|
|
1403
|
+
}
|
|
1404
|
+
sessions.push({
|
|
1405
|
+
harness: "claude_code",
|
|
1406
|
+
sessionId,
|
|
1407
|
+
workspace: workspace2,
|
|
1408
|
+
title: sniffTitle(mainFile),
|
|
1409
|
+
mtimeMs: stat.mtimeMs,
|
|
1410
|
+
files: [mainFile, ...subagentFiles],
|
|
1411
|
+
...discoveryError ? { discoveryError } : {}
|
|
1412
|
+
});
|
|
1413
|
+
}
|
|
909
1414
|
}
|
|
1415
|
+
} catch {
|
|
1416
|
+
}
|
|
1417
|
+
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1418
|
+
}
|
|
1419
|
+
function listCodexSessions(limit) {
|
|
1420
|
+
const sessionsDir = join6(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join6(homedir3(), ".codex"), "sessions");
|
|
1421
|
+
const records = [];
|
|
1422
|
+
function walk(dir) {
|
|
1423
|
+
let entries;
|
|
910
1424
|
try {
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
const
|
|
928
|
-
|
|
1425
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
1426
|
+
} catch {
|
|
1427
|
+
return;
|
|
1428
|
+
}
|
|
1429
|
+
for (const entry2 of entries) {
|
|
1430
|
+
const full = join6(dir, entry2.name);
|
|
1431
|
+
if (entry2.isDirectory()) {
|
|
1432
|
+
walk(full);
|
|
1433
|
+
continue;
|
|
1434
|
+
}
|
|
1435
|
+
if (!entry2.isFile() || !entry2.name.startsWith("rollout-") || !entry2.name.endsWith(".jsonl")) continue;
|
|
1436
|
+
try {
|
|
1437
|
+
const first = readPrefix(full).split("\n", 1)[0];
|
|
1438
|
+
const meta = JSON.parse(first);
|
|
1439
|
+
if (meta.type !== "session_meta" || typeof meta.payload?.id !== "string") continue;
|
|
1440
|
+
const data = meta.payload;
|
|
1441
|
+
const parent = data.forked_from_id ?? data.source?.spawn?.parent_thread_id ?? data.source?.subagent?.spawn?.parent_thread_id;
|
|
1442
|
+
records.push({
|
|
1443
|
+
session: {
|
|
1444
|
+
harness: "codex",
|
|
1445
|
+
sessionId: data.id,
|
|
1446
|
+
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
1447
|
+
title: sniffTitle(full),
|
|
1448
|
+
mtimeMs: statSync5(full).mtimeMs,
|
|
1449
|
+
files: [full]
|
|
1450
|
+
},
|
|
1451
|
+
...typeof parent === "string" ? { parent } : {}
|
|
1452
|
+
});
|
|
1453
|
+
} catch {
|
|
1454
|
+
continue;
|
|
929
1455
|
}
|
|
930
1456
|
}
|
|
931
1457
|
}
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
)
|
|
1458
|
+
walk(sessionsDir);
|
|
1459
|
+
function descendants(id, visited) {
|
|
1460
|
+
if (visited.has(id)) return [];
|
|
1461
|
+
visited.add(id);
|
|
1462
|
+
return records.filter((item) => item.parent === id).flatMap((item) => [
|
|
1463
|
+
...item.session.files,
|
|
1464
|
+
...descendants(item.session.sessionId, visited)
|
|
1465
|
+
]);
|
|
937
1466
|
}
|
|
1467
|
+
return records.filter((item) => !item.parent).map(({ session: session2 }) => ({
|
|
1468
|
+
...session2,
|
|
1469
|
+
files: [.../* @__PURE__ */ new Set([...session2.files, ...descendants(session2.sessionId, /* @__PURE__ */ new Set())])]
|
|
1470
|
+
})).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
938
1471
|
}
|
|
939
|
-
function
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
1472
|
+
function listRecentSessions(harness2, limit) {
|
|
1473
|
+
const claudeSessions = harness2 === "codex" ? [] : listClaudeCodeSessions(limit);
|
|
1474
|
+
const codexSessions = harness2 === "claude_code" ? [] : listCodexSessions(limit);
|
|
1475
|
+
return [...claudeSessions, ...codexSessions].sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1476
|
+
}
|
|
1477
|
+
function agentUseBase() {
|
|
1478
|
+
const env = process.env.AGENT_USE_URL?.trim().replace(/\/$/, "");
|
|
1479
|
+
if (env) return env;
|
|
1480
|
+
const api2 = apiBase().replace(/\/$/, "");
|
|
1481
|
+
const derived = api2.replace(/\/api\/v1$/, "").replace(/\/v1$/, "").replace("api.agentsky.dev", "agentsky.dev");
|
|
1482
|
+
if (derived.includes("localhost:3000")) return derived.replace("localhost:3000", "localhost:3002");
|
|
1483
|
+
return derived;
|
|
1484
|
+
}
|
|
1485
|
+
function currentSessionEnv() {
|
|
1486
|
+
const claudeId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
1487
|
+
if (claudeId) return { harness: "claude_code", sessionId: claudeId };
|
|
1488
|
+
const codexId = process.env.CODEX_THREAD_ID;
|
|
1489
|
+
if (codexId) return { harness: "codex", sessionId: codexId };
|
|
1490
|
+
return void 0;
|
|
1491
|
+
}
|
|
1492
|
+
async function mintTrace(base, harness2) {
|
|
1493
|
+
const token = apiToken();
|
|
1494
|
+
if (!token) throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
|
|
1495
|
+
const res = await fetch(`${base}/api/traces`, {
|
|
1496
|
+
method: "POST",
|
|
1497
|
+
redirect: "error",
|
|
1498
|
+
signal: AbortSignal.timeout(15e3),
|
|
1499
|
+
headers: {
|
|
1500
|
+
"content-type": "application/json",
|
|
1501
|
+
authorization: `Bearer ${token}`,
|
|
1502
|
+
"user-agent": USER_AGENT
|
|
1503
|
+
},
|
|
1504
|
+
body: JSON.stringify({ harness: harness2 })
|
|
946
1505
|
});
|
|
1506
|
+
if (!res.ok) {
|
|
1507
|
+
const body = await res.json().catch(() => null);
|
|
1508
|
+
throw new Error(
|
|
1509
|
+
typeof body?.error === "string" ? body.error : `Failed to create import (${res.status}).`
|
|
1510
|
+
);
|
|
1511
|
+
}
|
|
1512
|
+
return await res.json();
|
|
947
1513
|
}
|
|
948
|
-
function
|
|
949
|
-
|
|
1514
|
+
function loadSummaryFile(summaryPath) {
|
|
1515
|
+
let raw;
|
|
950
1516
|
try {
|
|
951
|
-
|
|
1517
|
+
raw = readFileSync6(summaryPath, "utf8");
|
|
952
1518
|
} catch {
|
|
1519
|
+
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
953
1520
|
}
|
|
954
|
-
|
|
955
|
-
function kb(text) {
|
|
956
|
-
return `${Math.max(1, Math.round(text.length / 1024))} KB`;
|
|
957
|
-
}
|
|
958
|
-
async function createClone(local, opts) {
|
|
959
|
-
const keptNames = Object.keys(local.keptServers);
|
|
960
|
-
const summary = [
|
|
961
|
-
local.promptSources.length ? `${local.promptSources.join(" + ")} (${kb(local.prompt)})` : "no instructions found",
|
|
962
|
-
`model ${local.model ?? "(harness default)"}`,
|
|
963
|
-
`${keptNames.length} MCP server${keptNames.length === 1 ? "" : "s"}${keptNames.length ? ` (${keptNames.join(", ")})` : ""}`
|
|
964
|
-
].join(" \xB7 ");
|
|
965
|
-
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${basename3(process.cwd())})`);
|
|
966
|
-
console.log(`Will clone: ${summary}`);
|
|
967
|
-
if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
|
|
968
|
-
if (local.droppedServers.length) {
|
|
969
|
-
console.log(
|
|
970
|
-
`Left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
971
|
-
);
|
|
972
|
-
}
|
|
973
|
-
if (opts.dryRun) {
|
|
974
|
-
console.log("Dry run \u2014 nothing sent.");
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
if (!opts.yes && !await confirm("Launch this as a new cloud agent?")) {
|
|
978
|
-
console.log("Aborted \u2014 nothing sent.");
|
|
979
|
-
return;
|
|
980
|
-
}
|
|
981
|
-
const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
|
|
982
|
-
if (skipped.length) {
|
|
983
|
-
console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
|
|
984
|
-
}
|
|
985
|
-
const body = {
|
|
986
|
-
agentType: local.harness,
|
|
987
|
-
...local.model ? { llm: local.model } : {},
|
|
988
|
-
...local.prompt ? { prompt: local.prompt } : {},
|
|
989
|
-
...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
|
|
990
|
-
};
|
|
991
|
-
let sentLlm = local.model ?? null;
|
|
992
|
-
let created;
|
|
993
|
-
try {
|
|
994
|
-
({ agent: created } = await api("POST", "/agents", body));
|
|
995
|
-
} catch (error) {
|
|
996
|
-
if (error instanceof ApiError && error.status === 422 && local.model) {
|
|
997
|
-
const successor = supersededBy(local.model);
|
|
998
|
-
console.log(
|
|
999
|
-
successor ? ` note: ${local.model} is superseded \u2014 using ${successor}` : ` note: ${local.model} isn't available for ${local.harness} \u2014 using the harness default`
|
|
1000
|
-
);
|
|
1001
|
-
sentLlm = successor;
|
|
1002
|
-
({ agent: created } = await api("POST", "/agents", {
|
|
1003
|
-
...body,
|
|
1004
|
-
llm: successor ?? void 0
|
|
1005
|
-
}));
|
|
1006
|
-
} else {
|
|
1007
|
-
throw error;
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
1010
|
-
console.log("Starting its runtime\u2026");
|
|
1011
|
-
await api("POST", "/sessions", { agent: created.id });
|
|
1012
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1013
|
-
writeLink(local.configRoot, {
|
|
1014
|
-
baseUrl: apiBase(),
|
|
1015
|
-
agentId: created.id,
|
|
1016
|
-
clonedAt: now,
|
|
1017
|
-
lastSyncedAt: now,
|
|
1018
|
-
llm: sentLlm,
|
|
1019
|
-
mcpServers: keptNames
|
|
1020
|
-
});
|
|
1021
|
-
const url = `${apiBase()}/agents/${created.id}`;
|
|
1022
|
-
console.log(`Cloned \u2192 ${created.id} ${url}`);
|
|
1023
|
-
console.log(
|
|
1024
|
-
`Linked this machine (${linkPath(local.configRoot)}). Push prompt changes anytime by running \`sky clone\` again.`
|
|
1025
|
-
);
|
|
1026
|
-
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
1027
|
-
if (opts.open) openBrowser(url);
|
|
1028
|
-
}
|
|
1029
|
-
async function syncClone(local, link2, opts) {
|
|
1030
|
-
const since = link2.lastSyncedAt ?? link2.clonedAt;
|
|
1031
|
-
console.log(
|
|
1032
|
-
`Syncing ${link2.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
|
|
1033
|
-
);
|
|
1034
|
-
if (opts.dryRun) {
|
|
1035
|
-
console.log(
|
|
1036
|
-
` would push prompt (${kb(local.prompt)}) to ${link2.agentId}. Dry run \u2014 nothing sent.`
|
|
1037
|
-
);
|
|
1038
|
-
return;
|
|
1039
|
-
}
|
|
1040
|
-
let agent2;
|
|
1041
|
-
try {
|
|
1042
|
-
({ agent: agent2 } = await api(
|
|
1043
|
-
"GET",
|
|
1044
|
-
`/agents/${encodeURIComponent(link2.agentId)}`
|
|
1045
|
-
));
|
|
1046
|
-
} catch (error) {
|
|
1047
|
-
if (error instanceof ApiError && error.status === 404) {
|
|
1048
|
-
throw new Error(
|
|
1049
|
-
`linked agent ${link2.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
|
|
1050
|
-
);
|
|
1051
|
-
}
|
|
1052
|
-
throw error;
|
|
1053
|
-
}
|
|
1054
|
-
if (agent2.agentType !== local.harness) {
|
|
1055
|
-
throw new Error(
|
|
1056
|
-
`linked agent ${link2.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
|
|
1057
|
-
);
|
|
1058
|
-
}
|
|
1059
|
-
const result = await api(
|
|
1060
|
-
"PUT",
|
|
1061
|
-
`/agents/${encodeURIComponent(link2.agentId)}/prompt`,
|
|
1062
|
-
{ prompt: local.prompt }
|
|
1063
|
-
);
|
|
1064
|
-
console.log(
|
|
1065
|
-
result.unchanged ? " prompt unchanged" : ` prompt updated (v${result.version}${result.applied === "live" ? ", hot-reloaded" : ", applies on next boot"})`
|
|
1066
|
-
);
|
|
1067
|
-
const drift = [];
|
|
1068
|
-
const localModel = local.model ?? null;
|
|
1069
|
-
const linkedModel = link2.llm ?? agent2.llm;
|
|
1070
|
-
if (localModel && linkedModel && localModel !== linkedModel) {
|
|
1071
|
-
drift.push(`model changed ${linkedModel} \u2192 ${localModel}`);
|
|
1072
|
-
}
|
|
1073
|
-
const before = new Set(link2.mcpServers ?? []);
|
|
1074
|
-
const nowKept = Object.keys(local.keptServers);
|
|
1075
|
-
const newServers = nowKept.filter((n) => !before.has(n));
|
|
1076
|
-
const goneServers = [...before].filter((n) => !nowKept.includes(n));
|
|
1077
|
-
if (newServers.length) drift.push(`${newServers.length} new MCP server${newServers.length === 1 ? "" : "s"} (${newServers.join(", ")})`);
|
|
1078
|
-
if (goneServers.length) drift.push(`${goneServers.length} removed MCP server${goneServers.length === 1 ? "" : "s"} (${goneServers.join(", ")})`);
|
|
1079
|
-
if (drift.length) {
|
|
1080
|
-
console.log(` skipped (set at launch): ${drift.join("; ")}`);
|
|
1081
|
-
console.log(" \u2192 a running agent can't change these; run `sky clone --new` to launch a fresh agent.");
|
|
1082
|
-
}
|
|
1083
|
-
if (local.droppedServers.length) {
|
|
1084
|
-
console.log(
|
|
1085
|
-
` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
1086
|
-
);
|
|
1087
|
-
}
|
|
1088
|
-
writeLink(local.configRoot, { ...link2, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1089
|
-
console.log(`Synced ${link2.agentId}.`);
|
|
1090
|
-
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
1091
|
-
if (opts.open) openBrowser(`${apiBase()}/agents/${link2.agentId}`);
|
|
1092
|
-
}
|
|
1093
|
-
async function clone(opts) {
|
|
1094
|
-
const harness2 = detectHarness(opts.harness);
|
|
1095
|
-
const local = readLocal(harness2);
|
|
1096
|
-
const link2 = opts.new ? void 0 : readLink(local.configRoot);
|
|
1097
|
-
if (link2) await syncClone(local, link2, opts);
|
|
1098
|
-
else await createClone(local, opts);
|
|
1099
|
-
}
|
|
1100
|
-
|
|
1101
|
-
// src/import-local.ts
|
|
1102
|
-
import { closeSync as closeSync3, existsSync as existsSync6, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
|
|
1103
|
-
import { homedir as homedir4 } from "os";
|
|
1104
|
-
import { join as join7 } from "path";
|
|
1105
|
-
import { createHash as createHash3 } from "crypto";
|
|
1106
|
-
|
|
1107
|
-
// src/import-files.ts
|
|
1108
|
-
import { isUtf8 } from "buffer";
|
|
1109
|
-
import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
1110
|
-
import { homedir as homedir3 } from "os";
|
|
1111
|
-
import { dirname as dirname4, extname as extname2, isAbsolute, join as join5, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
|
|
1112
|
-
|
|
1113
|
-
// src/import-workspace.ts
|
|
1114
|
-
import { spawnSync } from "child_process";
|
|
1115
|
-
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
1116
|
-
import { homedir as homedir2 } from "os";
|
|
1117
|
-
import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
|
|
1118
|
-
import { parse as parseToml2 } from "smol-toml";
|
|
1119
|
-
var MAX_TREE = 5e3;
|
|
1120
|
-
var SKILL_LINE = /Base directory for this skill: (\/[^\n\r"'\\<>]+)/g;
|
|
1121
|
-
function git(cwd, ...args) {
|
|
1122
|
-
const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
|
|
1123
|
-
return run2.status === 0 ? run2.stdout : null;
|
|
1124
|
-
}
|
|
1125
|
-
var isFile = (path) => {
|
|
1126
|
-
try {
|
|
1127
|
-
return statSync3(path).isFile();
|
|
1128
|
-
} catch {
|
|
1129
|
-
return false;
|
|
1130
|
-
}
|
|
1131
|
-
};
|
|
1132
|
-
var isDir = (path) => {
|
|
1133
|
-
try {
|
|
1134
|
-
return statSync3(path).isDirectory();
|
|
1135
|
-
} catch {
|
|
1136
|
-
return false;
|
|
1137
|
-
}
|
|
1138
|
-
};
|
|
1139
|
-
function sharedInstructionPaths(home = homedir2()) {
|
|
1140
|
-
const rules = join4(home, ".agents", "rules");
|
|
1141
|
-
let ruleFiles = [];
|
|
1142
|
-
try {
|
|
1143
|
-
ruleFiles = readdirSync2(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join4(rules, name));
|
|
1144
|
-
} catch {
|
|
1145
|
-
}
|
|
1146
|
-
return [join4(home, "AGENTS.md"), join4(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
1147
|
-
}
|
|
1148
|
-
function strings(value, out, depth = 0) {
|
|
1149
|
-
if (depth > 12) return;
|
|
1150
|
-
if (typeof value === "string") out.push(value);
|
|
1151
|
-
else if (Array.isArray(value)) for (const item of value) strings(item, out, depth + 1);
|
|
1152
|
-
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
1153
|
-
}
|
|
1154
|
-
function skillRoots(cwd, home) {
|
|
1155
|
-
const claude = process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude");
|
|
1156
|
-
const codex = process.env.CODEX_HOME ?? join4(home, ".codex");
|
|
1157
|
-
return [
|
|
1158
|
-
...cwd ? [join4(cwd, ".claude", "skills"), join4(cwd, ".agents", "skills")] : [],
|
|
1159
|
-
join4(home, ".agents", "skills"),
|
|
1160
|
-
join4(claude, "skills"),
|
|
1161
|
-
join4(codex, "skills"),
|
|
1162
|
-
join4(home, ".hermes", "skills")
|
|
1163
|
-
];
|
|
1164
|
-
}
|
|
1165
|
-
function pluginSkill(plugin, skill, home) {
|
|
1166
|
-
const walk = (dir, depth, underPlugin) => {
|
|
1167
|
-
if (depth > 7) return null;
|
|
1168
|
-
let entries;
|
|
1169
|
-
try {
|
|
1170
|
-
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1171
|
-
} catch {
|
|
1172
|
-
return null;
|
|
1173
|
-
}
|
|
1174
|
-
for (const entry2 of entries) {
|
|
1175
|
-
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
1176
|
-
const path = join4(dir, entry2.name);
|
|
1177
|
-
if (entry2.name === skill && basename4(dir) === "skills" && (underPlugin || path.includes(`${sep}${plugin}${sep}`)) && isFile(join4(path, "SKILL.md"))) return realpathSync(path);
|
|
1178
|
-
const found = walk(path, depth + 1, underPlugin);
|
|
1179
|
-
if (found) return found;
|
|
1180
|
-
}
|
|
1181
|
-
return null;
|
|
1182
|
-
};
|
|
1183
|
-
return walk(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins"), 0, false) ?? walk(join4(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions", "skills-plugin"), 0, true);
|
|
1184
|
-
}
|
|
1185
|
-
function findSkill(name, cwd, home) {
|
|
1186
|
-
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
1187
|
-
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
1188
|
-
for (const root of skillRoots(cwd, home)) {
|
|
1189
|
-
const direct = join4(root, name);
|
|
1190
|
-
if (isFile(join4(direct, "SKILL.md"))) return realpathSync(direct);
|
|
1191
|
-
}
|
|
1192
|
-
return null;
|
|
1193
|
-
}
|
|
1194
|
-
function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
1195
|
-
const dirs = /* @__PURE__ */ new Map();
|
|
1196
|
-
const missing = /* @__PURE__ */ new Set();
|
|
1197
|
-
const add = (dir, name = basename4(dir)) => {
|
|
1198
|
-
if (!isFile(join4(dir, "SKILL.md"))) return false;
|
|
1199
|
-
const real = realpathSync(dir);
|
|
1200
|
-
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
1201
|
-
return true;
|
|
1202
|
-
};
|
|
1203
|
-
const called = [];
|
|
1204
|
-
for (const record of records) {
|
|
1205
|
-
const texts = [];
|
|
1206
|
-
strings(record, texts);
|
|
1207
|
-
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].trim().replace(/[.,;:)]+$/, ""));
|
|
1208
|
-
const content = record?.message?.content;
|
|
1209
|
-
if (!Array.isArray(content)) continue;
|
|
1210
|
-
for (const block of content) {
|
|
1211
|
-
const b = block;
|
|
1212
|
-
if (b?.type === "tool_use" && b.name === "Skill" && typeof b.input?.skill === "string") called.push(b.input.skill.replace(/^\//, ""));
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
for (const path of touched) if (basename4(path) === "SKILL.md") add(dirname3(path));
|
|
1216
|
-
for (const name of called) {
|
|
1217
|
-
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
1218
|
-
const dir = findSkill(name, cwd, home);
|
|
1219
|
-
if (!dir || !add(dir, name)) missing.add(name);
|
|
1220
|
-
}
|
|
1221
|
-
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
1222
|
-
}
|
|
1223
|
-
function readJson2(path) {
|
|
1224
|
-
try {
|
|
1225
|
-
const value = JSON.parse(readFileSync5(path, "utf8"));
|
|
1226
|
-
return value && typeof value === "object" ? value : null;
|
|
1227
|
-
} catch {
|
|
1228
|
-
return null;
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
function names(value) {
|
|
1232
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1233
|
-
const keys2 = Object.keys(value).sort();
|
|
1234
|
-
return keys2.length ? keys2 : void 0;
|
|
1235
|
-
}
|
|
1236
|
-
function safeUrl(raw) {
|
|
1237
|
-
try {
|
|
1238
|
-
const url = new URL(raw);
|
|
1239
|
-
if (!/^https?:$/.test(url.protocol)) return null;
|
|
1240
|
-
const query = [.../* @__PURE__ */ new Set([...url.searchParams.keys()])].sort();
|
|
1241
|
-
return { url: `${url.protocol}//${url.host}${url.pathname}`, ...query.length ? { query } : {} };
|
|
1242
|
-
} catch {
|
|
1243
|
-
return null;
|
|
1244
|
-
}
|
|
1245
|
-
}
|
|
1246
|
-
function serverEntry(name, def, scope) {
|
|
1247
|
-
if (!def || typeof def !== "object") return null;
|
|
1248
|
-
const env = names(def.env);
|
|
1249
|
-
const headers = names(def.headers ?? def.http_headers);
|
|
1250
|
-
if (typeof def.url === "string") {
|
|
1251
|
-
const url = safeUrl(def.url);
|
|
1252
|
-
if (!url) return null;
|
|
1253
|
-
const type = def.type === "sse" ? "sse" : "http";
|
|
1254
|
-
return { name, scope, transport: type, ...url, ...headers ? { headers } : {}, ...env ? { env } : {} };
|
|
1255
|
-
}
|
|
1256
|
-
if (typeof def.command !== "string" || !def.command) return null;
|
|
1257
|
-
const args = Array.isArray(def.args) ? def.args.filter((arg) => typeof arg === "string") : [];
|
|
1258
|
-
return { name, scope, transport: "stdio", command: def.command, ...args.length ? { args } : {}, ...env ? { env } : {} };
|
|
1259
|
-
}
|
|
1260
|
-
function mcpServers(harness2, cwd, home = homedir2()) {
|
|
1261
|
-
const out = [];
|
|
1262
|
-
const push = (servers, scope) => {
|
|
1263
|
-
if (!servers || typeof servers !== "object") return;
|
|
1264
|
-
for (const [name, def] of Object.entries(servers)) {
|
|
1265
|
-
const entry2 = serverEntry(name, def, scope);
|
|
1266
|
-
if (entry2 && !out.some((existing) => existing.name === name)) out.push(entry2);
|
|
1267
|
-
}
|
|
1268
|
-
};
|
|
1269
|
-
if (harness2 === "claude_code") {
|
|
1270
|
-
const config = readJson2(join4(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
1271
|
-
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
1272
|
-
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
1273
|
-
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
1274
|
-
if (root) push(readJson2(join4(root, ".mcp.json"))?.mcpServers, "project");
|
|
1275
|
-
push(config?.mcpServers, "user");
|
|
1276
|
-
}
|
|
1277
|
-
if (harness2 === "codex") {
|
|
1278
|
-
try {
|
|
1279
|
-
const config = parseToml2(readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "config.toml"), "utf8"));
|
|
1280
|
-
push(config.mcp_servers, "user");
|
|
1281
|
-
} catch {
|
|
1282
|
-
}
|
|
1283
|
-
}
|
|
1284
|
-
return out;
|
|
1285
|
-
}
|
|
1286
|
-
function remoteWithoutCredentials(remote) {
|
|
1287
|
-
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
1288
|
-
}
|
|
1289
|
-
function workspaceState(cwd, harness2, skills2, home = homedir2(), title = null) {
|
|
1290
|
-
const present = !!cwd && isDir(cwd);
|
|
1291
|
-
const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
|
|
1292
|
-
let gitState = null;
|
|
1293
|
-
let tree = [];
|
|
1294
|
-
let patch = null;
|
|
1295
|
-
if (root) {
|
|
1296
|
-
const dirty = (git(root, "status", "--porcelain=v1", "--untracked-files=normal") ?? "").split("\n").filter(Boolean).map((line) => line.slice(3));
|
|
1297
|
-
const ahead = git(root, "rev-list", "--count", "@{upstream}..HEAD");
|
|
1298
|
-
const remote = git(root, "remote", "get-url", "origin");
|
|
1299
|
-
const branch = git(root, "rev-parse", "--abbrev-ref", "HEAD")?.trim() || null;
|
|
1300
|
-
gitState = {
|
|
1301
|
-
remote: remote ? remoteWithoutCredentials(remote) : null,
|
|
1302
|
-
branch: branch === "HEAD" ? null : branch,
|
|
1303
|
-
commit: git(root, "rev-parse", "HEAD")?.trim() || null,
|
|
1304
|
-
dirty: dirty.slice(0, 500),
|
|
1305
|
-
ahead: ahead === null ? null : Number(ahead.trim()) || 0
|
|
1306
|
-
};
|
|
1307
|
-
tree = (git(cwd, "-c", "core.quotePath=false", "ls-files") ?? "").split("\n").filter(Boolean);
|
|
1308
|
-
if (dirty.length) patch = git(cwd, "diff", "HEAD", "--no-color", "--no-ext-diff", "--relative", "--", ".") || null;
|
|
1309
|
-
}
|
|
1310
|
-
return {
|
|
1311
|
-
manifest: {
|
|
1312
|
-
format: "agentsky.import-workspace",
|
|
1313
|
-
format_version: 1,
|
|
1314
|
-
harness: harness2,
|
|
1315
|
-
title: title?.trim().slice(0, 120) || null,
|
|
1316
|
-
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
1317
|
-
project: cwd ? basename4(root ?? cwd) : null,
|
|
1318
|
-
git: gitState,
|
|
1319
|
-
tree: tree.slice(0, MAX_TREE),
|
|
1320
|
-
tree_truncated: tree.length > MAX_TREE,
|
|
1321
|
-
skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
|
|
1322
|
-
mcp_servers: mcpServers(harness2, cwd, home)
|
|
1323
|
-
},
|
|
1324
|
-
patch
|
|
1325
|
-
};
|
|
1326
|
-
}
|
|
1327
|
-
function skillFiles(skill) {
|
|
1328
|
-
const out = [];
|
|
1329
|
-
const walk = (dir, depth) => {
|
|
1330
|
-
if (depth > 8 || out.length > 400) return;
|
|
1331
|
-
let entries;
|
|
1332
|
-
try {
|
|
1333
|
-
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1334
|
-
} catch {
|
|
1335
|
-
return;
|
|
1336
|
-
}
|
|
1337
|
-
for (const entry2 of entries) {
|
|
1338
|
-
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1339
|
-
const path = join4(dir, entry2.name);
|
|
1340
|
-
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1341
|
-
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1342
|
-
}
|
|
1343
|
-
};
|
|
1344
|
-
if (existsSync4(skill.dir)) walk(skill.dir, 0);
|
|
1345
|
-
return out;
|
|
1346
|
-
}
|
|
1347
|
-
function codexThreadName(sessionId, home = homedir2()) {
|
|
1348
|
-
let name = null;
|
|
1521
|
+
let pkg;
|
|
1349
1522
|
try {
|
|
1350
|
-
|
|
1351
|
-
if (!line.includes(sessionId)) continue;
|
|
1352
|
-
try {
|
|
1353
|
-
const entry2 = JSON.parse(line);
|
|
1354
|
-
if (entry2.id === sessionId && typeof entry2.thread_name === "string" && entry2.thread_name.trim()) name = entry2.thread_name.trim();
|
|
1355
|
-
} catch {
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1523
|
+
pkg = JSON.parse(raw);
|
|
1358
1524
|
} catch {
|
|
1525
|
+
throw new Error("Summary file is not valid JSON.");
|
|
1359
1526
|
}
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
var SKIP_DIR = /(^|\/)(node_modules|\.git|\.next|dist|build|__pycache__|\.venv)(\/|$)/;
|
|
1367
|
-
var INSTRUCTION_FILES = ["CLAUDE.md", "AGENTS.md", "HERMES.md", "GEMINI.md", ".cursorrules"];
|
|
1368
|
-
var DOCUMENTS = {
|
|
1369
|
-
".pdf": "application/pdf",
|
|
1370
|
-
".docx": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
1371
|
-
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
1372
|
-
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
1373
|
-
".doc": "application/msword",
|
|
1374
|
-
".rtf": "application/rtf",
|
|
1375
|
-
".odt": "application/vnd.oasis.opendocument.text"
|
|
1376
|
-
};
|
|
1377
|
-
var IMAGES = { ".png": "image/png", ".jpg": "image/jpeg", ".jpeg": "image/jpeg", ".gif": "image/gif", ".webp": "image/webp" };
|
|
1378
|
-
var TEXT = {
|
|
1379
|
-
".md": "text/markdown",
|
|
1380
|
-
".mdx": "text/markdown",
|
|
1381
|
-
".txt": "text/plain",
|
|
1382
|
-
".csv": "text/csv",
|
|
1383
|
-
".tsv": "text/tab-separated-values",
|
|
1384
|
-
".html": "text/html",
|
|
1385
|
-
".htm": "text/html",
|
|
1386
|
-
".css": "text/css",
|
|
1387
|
-
".scss": "text/css",
|
|
1388
|
-
".json": "application/json",
|
|
1389
|
-
".jsonl": "application/x-ndjson",
|
|
1390
|
-
".ndjson": "application/x-ndjson",
|
|
1391
|
-
".yaml": "application/yaml",
|
|
1392
|
-
".yml": "application/yaml",
|
|
1393
|
-
".toml": "application/toml",
|
|
1394
|
-
".xml": "application/xml",
|
|
1395
|
-
".sql": "application/sql",
|
|
1396
|
-
".svg": "image/svg+xml",
|
|
1397
|
-
".js": "application/javascript",
|
|
1398
|
-
".mjs": "application/javascript",
|
|
1399
|
-
".cjs": "application/javascript",
|
|
1400
|
-
".py": "text/x-python",
|
|
1401
|
-
".sh": "text/x-shellscript",
|
|
1402
|
-
".bash": "text/x-shellscript",
|
|
1403
|
-
".zsh": "text/x-shellscript"
|
|
1404
|
-
};
|
|
1405
|
-
var PLAIN_TEXT = /* @__PURE__ */ new Set([".ts", ".tsx", ".jsx", ".mts", ".cts", ".go", ".rs", ".java", ".kt", ".swift", ".rb", ".php", ".c", ".h", ".cc", ".cpp", ".hpp", ".cs", ".scala", ".lua", ".r", ".m", ".tex", ".bib", ".ipynb", ".prisma", ".graphql", ".proto", ".ini", ".cfg", ".conf", ".log", ".rst", ".vue", ".svelte", ".astro", ".tf", ".hcl", ".gradle", ".lock", ".patch", ".diff"]);
|
|
1406
|
-
var PLAIN_NAMES = /* @__PURE__ */ new Set(["Dockerfile", "Makefile", "Procfile", "Gemfile", "Rakefile", "LICENSE", "README", ".cursorrules", ".gitignore", ".dockerignore", ".editorconfig", ".prettierrc", ".eslintrc"]);
|
|
1407
|
-
function classify(path) {
|
|
1408
|
-
const ext = extname2(path).toLowerCase();
|
|
1409
|
-
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
1410
|
-
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
1411
|
-
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
1412
|
-
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename5(path))) return { kind: "text", mediaType: "text/plain" };
|
|
1413
|
-
return null;
|
|
1414
|
-
}
|
|
1415
|
-
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;
|
|
1416
|
-
var PATH_KEYS = ["file_path", "path", "notebook_path", "filePath", "target_file"];
|
|
1417
|
-
var obj = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
1418
|
-
var str = (value) => typeof value === "string" ? value : "";
|
|
1419
|
-
function shellText(input) {
|
|
1420
|
-
if (typeof input === "string") return input;
|
|
1421
|
-
const i = obj(input);
|
|
1422
|
-
if (!i) return "";
|
|
1423
|
-
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
1424
|
-
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
1527
|
+
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) throw new Error("Summary file must be a JSON object.");
|
|
1528
|
+
const p = pkg;
|
|
1529
|
+
if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
|
|
1530
|
+
if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
|
|
1531
|
+
const redacted2 = redactLine2(raw.trimEnd());
|
|
1532
|
+
return redacted2;
|
|
1425
1533
|
}
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
};
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1447
|
-
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1534
|
+
var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
|
|
1535
|
+
var SUMMARY_RESERVE_BYTES = 128 * 1024;
|
|
1536
|
+
var MAX_LISTED_ARTIFACTS = 99;
|
|
1537
|
+
function leftOutLine(left) {
|
|
1538
|
+
const counts = /* @__PURE__ */ new Map();
|
|
1539
|
+
for (const file of left) counts.set(file.reason, (counts.get(file.reason) ?? 0) + 1);
|
|
1540
|
+
const files2 = [...counts].map(([reason, n]) => `${n} ${n === 1 ? "file" : "files"} (${reason})`).join(", ");
|
|
1541
|
+
return `Not transferred: ${files2 ? `${files2}; ` : ""}runtime state such as running processes, installed tools and environment variables; and reasoning the source harness stored unreadably.`;
|
|
1542
|
+
}
|
|
1543
|
+
function prepareImport(session2, summaryPath, options = {}) {
|
|
1544
|
+
if (session2.discoveryError) throw new Error(session2.discoveryError);
|
|
1545
|
+
const maximum = 16 * 1024 * 1024;
|
|
1546
|
+
let inputBytes = 0;
|
|
1547
|
+
let outputBytes = 0;
|
|
1548
|
+
const lines = [];
|
|
1549
|
+
const digest = createHash3("sha256");
|
|
1550
|
+
let nativeId;
|
|
1551
|
+
let redactionCount = 0;
|
|
1552
|
+
let encryptedReasoningCount = 0;
|
|
1553
|
+
let compactionCount = 0;
|
|
1554
|
+
let unreadableCount = 0;
|
|
1555
|
+
const source = new SessionSourceCollector();
|
|
1556
|
+
const records = [];
|
|
1557
|
+
for (const path of session2.files) {
|
|
1558
|
+
const stat = statSync5(path);
|
|
1559
|
+
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
1560
|
+
const text = readFileSync6(path, "utf8");
|
|
1561
|
+
for (const raw of text.split("\n")) {
|
|
1562
|
+
if (!raw.trim()) continue;
|
|
1563
|
+
let record;
|
|
1564
|
+
try {
|
|
1565
|
+
record = JSON.parse(raw);
|
|
1566
|
+
} catch {
|
|
1567
|
+
unreadableCount++;
|
|
1568
|
+
continue;
|
|
1451
1569
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1570
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
|
|
1571
|
+
const rec = record;
|
|
1572
|
+
if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
|
|
1573
|
+
if (path === session2.files[0]) source.push(rec);
|
|
1574
|
+
records.push(rec);
|
|
1575
|
+
if (rec.type === "response_item") {
|
|
1576
|
+
const payload = rec.payload;
|
|
1577
|
+
if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
|
|
1578
|
+
encryptedReasoningCount++;
|
|
1461
1579
|
}
|
|
1462
1580
|
}
|
|
1463
|
-
|
|
1581
|
+
if (rec.type === "compacted") {
|
|
1582
|
+
compactionCount++;
|
|
1583
|
+
}
|
|
1584
|
+
const id = rec.type === "session_meta" ? rec.payload?.id : rec.sessionId;
|
|
1585
|
+
if (!nativeId && typeof id === "string") nativeId = id;
|
|
1586
|
+
const result = redactLine(raw.replace(/\r$/, ""), { requireStructured: true });
|
|
1587
|
+
if (Object.keys(result.hits).length > 0) redactionCount++;
|
|
1588
|
+
const line = result.line + "\n";
|
|
1589
|
+
outputBytes += Buffer.byteLength(line);
|
|
1590
|
+
if (outputBytes > maximum) throw new Error("Redacted transcript exceeds the 16 MiB limit.");
|
|
1591
|
+
digest.update(line);
|
|
1592
|
+
lines.push(line);
|
|
1464
1593
|
}
|
|
1465
1594
|
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1595
|
+
if (!lines.length) throw new Error("The selected transcript is empty.");
|
|
1596
|
+
const sourceId = nativeId ?? session2.sessionId;
|
|
1597
|
+
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.");
|
|
1598
|
+
const meta = source.result();
|
|
1599
|
+
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : collectSessionFiles(records, {
|
|
1600
|
+
harness: session2.harness,
|
|
1601
|
+
cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""),
|
|
1602
|
+
budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES,
|
|
1603
|
+
exclude: session2.files,
|
|
1604
|
+
title: meta.title ?? (session2.harness === "codex" ? codexThreadName(session2.sessionId) : null)
|
|
1605
|
+
});
|
|
1606
|
+
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).` : "";
|
|
1607
|
+
let summaryJson;
|
|
1608
|
+
if (summaryPath) {
|
|
1609
|
+
summaryJson = loadSummaryFile(summaryPath);
|
|
1610
|
+
} else {
|
|
1611
|
+
const summary = {
|
|
1612
|
+
format: "asteroids.session-summary",
|
|
1613
|
+
format_version: 1,
|
|
1614
|
+
source_harness: session2.harness,
|
|
1615
|
+
source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
|
|
1616
|
+
source_session_id: sourceId,
|
|
1617
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1618
|
+
summary: `The complete selected local conversation is attached as a transcript.${attachedNote} Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.`,
|
|
1619
|
+
context: {
|
|
1620
|
+
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
1621
|
+
constraints: [],
|
|
1622
|
+
decisions: [],
|
|
1623
|
+
completed: [],
|
|
1624
|
+
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
1625
|
+
verification: [],
|
|
1626
|
+
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(", ")}.`] : []],
|
|
1627
|
+
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).` })),
|
|
1628
|
+
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
1629
|
+
}
|
|
1630
|
+
};
|
|
1631
|
+
summaryJson = JSON.stringify(summary);
|
|
1472
1632
|
}
|
|
1633
|
+
const body = lines.join("") + files2.lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + files2.lines.length + 1 }) + "\n";
|
|
1634
|
+
const gaps = [];
|
|
1635
|
+
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
1636
|
+
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
1637
|
+
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
1638
|
+
const cwd = meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : "");
|
|
1639
|
+
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup, cwd, resourceLines: files2.lines };
|
|
1473
1640
|
}
|
|
1474
|
-
function
|
|
1475
|
-
const
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
|
|
1480
|
-
if (d === root || d === dirname4(d)) break;
|
|
1481
|
-
}
|
|
1641
|
+
async function importSession(opts) {
|
|
1642
|
+
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
1643
|
+
const destination2 = new URL(base);
|
|
1644
|
+
if (destination2.username || destination2.password || destination2.protocol !== "https:" && !(destination2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(destination2.hostname))) {
|
|
1645
|
+
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1482
1646
|
}
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
}
|
|
1494
|
-
function resourceName(path, cwd, home = homedir3()) {
|
|
1495
|
-
const inside = cwd ? relative2(resolve4(cwd), path) : "";
|
|
1496
|
-
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
1497
|
-
const underHome = relative2(home, path);
|
|
1498
|
-
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
1499
|
-
return `files/${shown.split(sep2).join("/")}`;
|
|
1500
|
-
}
|
|
1501
|
-
var ORDER = { text: 0, document: 1, image: 2 };
|
|
1502
|
-
function resourceLine(name, mediaType, data) {
|
|
1503
|
-
return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
1504
|
-
}
|
|
1505
|
-
function collectSessionFiles(records, opts) {
|
|
1506
|
-
const home = opts.home ?? homedir3();
|
|
1507
|
-
const exclude = new Set((opts.exclude ?? []).map((path) => resolve4(path)));
|
|
1508
|
-
const leftOut = [];
|
|
1509
|
-
const candidates = [];
|
|
1510
|
-
const seen = /* @__PURE__ */ new Set();
|
|
1511
|
-
const touched = touchedPaths(records, opts.cwd, home);
|
|
1512
|
-
const skills2 = loadedSkills(records, touched, opts.cwd, home);
|
|
1513
|
-
const consider = (path, name) => {
|
|
1514
|
-
if (seen.has(path) || exclude.has(path)) return;
|
|
1515
|
-
seen.add(path);
|
|
1516
|
-
if (SECRET_FILE.test(path) || SKIP_DIR.test(path)) {
|
|
1517
|
-
leftOut.push({ path, reason: "credential or build file" });
|
|
1518
|
-
return;
|
|
1519
|
-
}
|
|
1520
|
-
let size;
|
|
1521
|
-
try {
|
|
1522
|
-
const stat = statSync4(path);
|
|
1523
|
-
if (!stat.isFile()) return;
|
|
1524
|
-
size = stat.size;
|
|
1525
|
-
} catch {
|
|
1526
|
-
leftOut.push({ path, reason: "missing" });
|
|
1527
|
-
return;
|
|
1528
|
-
}
|
|
1529
|
-
const type = classify(path);
|
|
1530
|
-
if (!type) {
|
|
1531
|
-
leftOut.push({ path, reason: "unsupported type" });
|
|
1532
|
-
return;
|
|
1533
|
-
}
|
|
1534
|
-
if (size > MAX_ATTACHED_FILE_BYTES) {
|
|
1535
|
-
leftOut.push({ path, reason: "too large" });
|
|
1536
|
-
return;
|
|
1537
|
-
}
|
|
1538
|
-
candidates.push({ path, name, ...type, size });
|
|
1539
|
-
};
|
|
1540
|
-
for (const path of [...instructionPaths(opts.cwd, opts.harness, home), ...touched]) consider(path, resourceName(path, opts.cwd, home));
|
|
1541
|
-
for (const skill of skills2.found) for (const file of skillFiles(skill)) consider(file.path, file.name);
|
|
1542
|
-
candidates.sort((a, b) => ORDER[a.kind] - ORDER[b.kind] || a.size - b.size);
|
|
1543
|
-
const lines = [];
|
|
1544
|
-
const attached = [];
|
|
1545
|
-
let bytes = 0;
|
|
1546
|
-
const state = workspaceState(opts.cwd, opts.harness, skills2, home, opts.title ?? null);
|
|
1547
|
-
const described = !!opts.cwd || !!state.manifest.title || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
1548
|
-
const generated = [
|
|
1549
|
-
...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
|
|
1550
|
-
...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
|
|
1551
|
-
];
|
|
1552
|
-
for (const file of generated) {
|
|
1553
|
-
let line;
|
|
1554
|
-
try {
|
|
1555
|
-
line = resourceLine(file.name, file.mediaType, file.data);
|
|
1556
|
-
} catch {
|
|
1557
|
-
leftOut.push({ path: file.name, reason: "could not be redacted safely" });
|
|
1558
|
-
continue;
|
|
1559
|
-
}
|
|
1560
|
-
const size = Buffer.byteLength(line);
|
|
1561
|
-
if (bytes + size > opts.budget) {
|
|
1562
|
-
leftOut.push({ path: file.name, reason: "over the upload budget" });
|
|
1563
|
-
continue;
|
|
1564
|
-
}
|
|
1565
|
-
bytes += size;
|
|
1566
|
-
lines.push(line);
|
|
1567
|
-
attached.push({ name: file.name, kind: "text", bytes: file.data.length });
|
|
1647
|
+
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
1648
|
+
const { body, transcriptLines, outputBytes, redactionCount, gaps, attachedFiles, leftOutFiles, setup, cwd, resourceLines } = prepareImport(opts.session, opts.summaryPath, { files: opts.files });
|
|
1649
|
+
let traceId;
|
|
1650
|
+
let key;
|
|
1651
|
+
if (opts.importKey && opts.grantTraceId) {
|
|
1652
|
+
traceId = opts.grantTraceId;
|
|
1653
|
+
key = opts.importKey;
|
|
1654
|
+
} else {
|
|
1655
|
+
opts.onProgress?.("Minting import key\u2026");
|
|
1656
|
+
({ traceId, key } = await mintTrace(base, opts.session.harness));
|
|
1568
1657
|
}
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
}
|
|
1581
|
-
let line;
|
|
1582
|
-
try {
|
|
1583
|
-
line = resourceLine(file.name, file.mediaType, data);
|
|
1584
|
-
} catch {
|
|
1585
|
-
leftOut.push({ path: file.path, reason: file.kind === "text" && !isUtf8(data) ? "not valid UTF-8 text" : "could not be redacted safely" });
|
|
1586
|
-
continue;
|
|
1587
|
-
}
|
|
1588
|
-
const size = Buffer.byteLength(line);
|
|
1589
|
-
if (bytes + size > opts.budget) {
|
|
1590
|
-
leftOut.push({ path: file.path, reason: "over the upload budget" });
|
|
1591
|
-
continue;
|
|
1592
|
-
}
|
|
1593
|
-
bytes += size;
|
|
1594
|
-
lines.push(line);
|
|
1595
|
-
attached.push({ name: file.name, kind: file.kind, bytes: data.length });
|
|
1658
|
+
opts.onProgress?.("Uploading the complete transcript\u2026");
|
|
1659
|
+
const response = await fetch(`${base}/api/traces/ingest`, {
|
|
1660
|
+
method: "POST",
|
|
1661
|
+
redirect: "error",
|
|
1662
|
+
signal: AbortSignal.timeout(12e4),
|
|
1663
|
+
headers: { authorization: `Bearer ${key}`, "content-type": "application/x-ndjson", "x-source-harness": opts.session.harness, "user-agent": USER_AGENT },
|
|
1664
|
+
body
|
|
1665
|
+
});
|
|
1666
|
+
if (!response.ok) {
|
|
1667
|
+
const result2 = await response.json().catch(() => null);
|
|
1668
|
+
throw new Error(typeof result2?.error === "string" ? result2.error : result2?.error?.message ?? `Upload failed (${response.status}).`);
|
|
1596
1669
|
}
|
|
1597
|
-
const
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1670
|
+
const result = await response.json();
|
|
1671
|
+
const finalTraceId = result.traceId ?? traceId;
|
|
1672
|
+
const environment = opts.files === false || opts.environment === false || !cwd || !existsSync4(cwd) ? null : await uploadEnvironment(base, key, finalTraceId, cwd, resourceLines, opts.onProgress).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
|
|
1673
|
+
return {
|
|
1674
|
+
traceId: finalTraceId,
|
|
1675
|
+
url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
|
|
1676
|
+
harness: opts.session.harness,
|
|
1677
|
+
sessionId: opts.session.sessionId,
|
|
1678
|
+
transcriptFiles: opts.session.files.length,
|
|
1679
|
+
bytes: outputBytes,
|
|
1680
|
+
redactionCount,
|
|
1681
|
+
gaps,
|
|
1682
|
+
attachedFiles,
|
|
1683
|
+
leftOutFiles,
|
|
1684
|
+
setup,
|
|
1685
|
+
environment
|
|
1602
1686
|
};
|
|
1603
|
-
return { lines, bytes, attached, leftOut, setup };
|
|
1604
1687
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
import { gzipSync } from "zlib";
|
|
1617
|
-
function folderName(workspace2, sourceCwd) {
|
|
1618
|
-
const raw = workspace2?.project || (sourceCwd ? sourceCwd.split("/").filter(Boolean).pop() : "") || "";
|
|
1619
|
-
const cleaned = raw.replace(/[^A-Za-z0-9._ -]/g, "-").replace(/^[^A-Za-z0-9_]+/, "").replace(/\.\.+/g, ".").slice(0, 80).trim();
|
|
1620
|
-
return cleaned || "imported-session";
|
|
1688
|
+
async function grantCall(base, key, path, body) {
|
|
1689
|
+
const response = await fetch(`${base}${path}`, {
|
|
1690
|
+
method: "POST",
|
|
1691
|
+
redirect: "error",
|
|
1692
|
+
signal: AbortSignal.timeout(3e5),
|
|
1693
|
+
headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "user-agent": USER_AGENT },
|
|
1694
|
+
body: JSON.stringify(body)
|
|
1695
|
+
});
|
|
1696
|
+
const answer = await response.json().catch(() => null);
|
|
1697
|
+
if (!response.ok) throw new Error(typeof answer?.error === "string" ? answer.error : answer?.error?.message ?? `The environment upload failed (${response.status}).`);
|
|
1698
|
+
return answer;
|
|
1621
1699
|
}
|
|
1622
|
-
function
|
|
1623
|
-
|
|
1700
|
+
async function uploadEnvironment(base, key, traceId, cwd, resourceLines, onProgress) {
|
|
1701
|
+
onProgress?.("Packing the working folder (secrets redacted locally)\u2026");
|
|
1702
|
+
const packed = await packEnvironment(cwd, resourcesOf(resourceLines));
|
|
1703
|
+
try {
|
|
1704
|
+
const path = `/api/traces/${encodeURIComponent(traceId)}/environment`;
|
|
1705
|
+
const { upload } = await grantCall(base, key, path, { name: packed.folder, size: packed.bytes, sha256: packed.sha256, files: packed.files });
|
|
1706
|
+
let shown = -1;
|
|
1707
|
+
await transfer(upload, packed.path, (sent, total) => {
|
|
1708
|
+
const percent = Math.floor(sent / total * 100);
|
|
1709
|
+
if (percent >= shown + 10 || sent === total) {
|
|
1710
|
+
shown = percent;
|
|
1711
|
+
onProgress?.(`Uploading the working folder\u2026 ${percent}% of ${(total / 1024 / 1024).toFixed(1)} MiB`);
|
|
1712
|
+
}
|
|
1713
|
+
});
|
|
1714
|
+
await grantCall(base, key, `${path}/complete`, {});
|
|
1715
|
+
return { folder: packed.folder, files: packed.files, bytes: packed.bytes, redacted: packed.redacted, leftOut: packed.leftOut.length };
|
|
1716
|
+
} finally {
|
|
1717
|
+
packed.dispose();
|
|
1718
|
+
}
|
|
1624
1719
|
}
|
|
1625
|
-
|
|
1720
|
+
|
|
1721
|
+
// src/context.ts
|
|
1722
|
+
async function contextRequest(path, universe2) {
|
|
1626
1723
|
try {
|
|
1627
|
-
return
|
|
1628
|
-
} catch {
|
|
1629
|
-
|
|
1724
|
+
return await api("GET", `/api${path}`, void 0, { base: agentUseBase(), universe: universe2 });
|
|
1725
|
+
} catch (error) {
|
|
1726
|
+
if (!(error instanceof ApiError) || ![401, 403, 409].includes(error.status)) throw error;
|
|
1727
|
+
return api("GET", path, void 0, { universe: universe2 });
|
|
1630
1728
|
}
|
|
1631
1729
|
}
|
|
1632
|
-
function
|
|
1633
|
-
const
|
|
1634
|
-
|
|
1635
|
-
return [server.name, {
|
|
1636
|
-
command: server.command,
|
|
1637
|
-
...server.args?.length ? { args: server.args } : {},
|
|
1638
|
-
...server.env?.length ? { env: Object.fromEntries(server.env.map((name) => [name, `\${${name}:-}`])) } : {}
|
|
1639
|
-
}];
|
|
1640
|
-
}
|
|
1641
|
-
const query = server.query?.length ? `?${server.query.map((name) => `${encodeURIComponent(name)}=\${${envName(server.name, name)}:-}`).join("&")}` : "";
|
|
1642
|
-
return [server.name, {
|
|
1643
|
-
type: server.transport,
|
|
1644
|
-
url: `${server.url}${query}`,
|
|
1645
|
-
...server.headers?.length ? { headers: Object.fromEntries(server.headers.map((name) => [name, `\${${envName(server.name, name)}:-}`])) } : {}
|
|
1646
|
-
}];
|
|
1647
|
-
});
|
|
1648
|
-
return JSON.stringify({ mcpServers: Object.fromEntries(entries) }, null, 2) + "\n";
|
|
1730
|
+
async function searchContext(query, opts, universe2) {
|
|
1731
|
+
const page = await contextRequest(`/context/search${searchQuery(query, opts)}`, universe2);
|
|
1732
|
+
return { ...page, hits: page.hits.map((entry2) => "hit" in entry2 ? entry2.hit : entry2) };
|
|
1649
1733
|
}
|
|
1650
|
-
function
|
|
1651
|
-
return
|
|
1734
|
+
function memoryBody(operation, version) {
|
|
1735
|
+
return { ...operation, expectedVersion: version };
|
|
1652
1736
|
}
|
|
1653
|
-
function
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
const missingSkills = (workspace2?.skills ?? []).filter((skill) => !skill.found).map((skill) => skill.name);
|
|
1657
|
-
const secrets2 = servers.filter((server) => !ownServer(server, platform)).flatMap((server) => [
|
|
1658
|
-
...server.env ?? [],
|
|
1659
|
-
...(server.headers ?? []).map((name) => envName(server.name, name)),
|
|
1660
|
-
...(server.query ?? []).map((name) => envName(server.name, name))
|
|
1661
|
-
]);
|
|
1662
|
-
const where = workspace2?.cwd ? ` (${workspace2.cwd})` : "";
|
|
1663
|
-
return [
|
|
1664
|
-
"# Imported working copy",
|
|
1665
|
-
"",
|
|
1666
|
-
`This folder (/workspace/${folder}) holds what the imported conversation worked with on the person's computer.`,
|
|
1667
|
-
"It is historical context: verify a path before acting on it, and follow the person's current message.",
|
|
1668
|
-
"",
|
|
1669
|
-
"## Files",
|
|
1670
|
-
counts.wholeFolder ? `- The whole working directory${where}, ${counts.project} files at the same relative paths: its tracked files and new files git does not ignore (every file when it is not a repository), without version-control data, dependency and build output, or credential files.` : `- ${counts.project} files from the original working directory${where}, at the same relative paths: the files the conversation read, wrote or edited and its instruction files. The rest of the working tree did not travel${workspace2?.tree?.length ? `; its full file list is in ${platform.notesDir}/workspace.json (\`tree\`)` : ""}.`,
|
|
1671
|
-
...counts.outside ? [`- ${counts.outside} files from elsewhere on that computer (shared instructions such as ~/AGENTS.md, and files outside the working directory) under ${platform.notesDir}/files/, with ~ as home/.`] : [],
|
|
1672
|
-
...counts.attachments ? [`- ${counts.attachments} attachments of the conversation under ${platform.notesDir}/attachments/.`] : [],
|
|
1673
|
-
"- Text was redacted on that computer before upload: a masked value (\u25CF\u25CF\u25CF\u25CF\u25CF) is a secret that was removed, not the real content.",
|
|
1674
|
-
"",
|
|
1675
|
-
"## Repository",
|
|
1676
|
-
...git2 ? [
|
|
1677
|
-
`- Remote: ${git2.remote ?? "none"}`,
|
|
1678
|
-
`- Branch: ${git2.branch ?? "detached"} at commit ${git2.commit ?? "unknown"}${git2.ahead ? ` (${git2.ahead} commits not pushed)` : ""}`,
|
|
1679
|
-
...git2.dirty?.length ? [`- Uncommitted changes in ${git2.dirty.length} paths${counts.wholeFolder ? " are already in these files" : ""}; the change against that commit is ${platform.notesDir}/uncommitted.patch (apply with \`git apply\` on a checkout of that commit).`] : ["- No uncommitted changes."],
|
|
1680
|
-
counts.wholeFolder ? "- This folder's own git history starts with this copy; the project's history is on the remote." : `- To get the whole repository, clone the remote at that commit (connect GitHub in ${platform.name} if it is private), apply the patch, then use the files here.`
|
|
1681
|
-
] : ["- The working directory was not a git repository."],
|
|
1682
|
-
"",
|
|
1683
|
-
"## Skills",
|
|
1684
|
-
bullet(skills2.map((name) => `${name}: .claude/skills/${name}/SKILL.md (also .agents/skills/${name}/)`), "No skills were loaded."),
|
|
1685
|
-
...missingSkills.length ? ["", `Loaded but no longer on that computer, so not carried: ${missingSkills.join(", ")}.`] : [],
|
|
1686
|
-
"",
|
|
1687
|
-
"## Tool servers (MCP)",
|
|
1688
|
-
bullet(servers.map((server) => `${server.name} (${server.transport}${server.scope ? `, ${server.scope}` : ""}): ${server.transport === "stdio" ? [server.command, ...server.args ?? []].join(" ") : server.url}`), "None were configured."),
|
|
1689
|
-
...mcpWritten ? ["", `They are written to .mcp.json in this folder with every key as \${NAME:-}, except ${platform.name}'s own server, whose tools this session already has. Their values never leave the person's computer${secrets2.length ? `; these must be supplied to use them: ${[...new Set(secrets2)].join(", ")}` : ""}. A stdio server runs only if its command is installed here, and Claude Code connects a project server only once it is approved.`] : [],
|
|
1690
|
-
""
|
|
1691
|
-
].join("\n");
|
|
1737
|
+
function memoryIndex(value) {
|
|
1738
|
+
if (!/^\d+$/.test(value.trim())) throw new Error(`the entry number must be a whole number, got ${JSON.stringify(value)}`);
|
|
1739
|
+
return Number(value.trim());
|
|
1692
1740
|
}
|
|
1693
|
-
function
|
|
1694
|
-
if (!
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1741
|
+
function renderMemory(document) {
|
|
1742
|
+
if (!document.entries.length) return "no saved memory \u2014 `sky memory save <text>` adds an entry";
|
|
1743
|
+
return document.entries.map((entry2) => `${String(entry2.index).padStart(3)} ${entry2.text}`).join("\n");
|
|
1744
|
+
}
|
|
1745
|
+
function searchQuery(query, opts) {
|
|
1746
|
+
const params = new URLSearchParams({ q: query });
|
|
1747
|
+
params.set("scope", opts.scope ?? "everything");
|
|
1748
|
+
if (opts.limit) params.set("limit", opts.limit);
|
|
1749
|
+
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
1750
|
+
if (opts.since) params.set("since", opts.since);
|
|
1751
|
+
if (opts.until) params.set("until", opts.until);
|
|
1752
|
+
return `?${params.toString()}`;
|
|
1753
|
+
}
|
|
1754
|
+
function refArgument(ref) {
|
|
1755
|
+
return `'${JSON.stringify(ref).replace(/'/g, "'\\''")}'`;
|
|
1756
|
+
}
|
|
1757
|
+
function renderContextPage(page) {
|
|
1758
|
+
const lines = [];
|
|
1759
|
+
for (const hit of page.hits) {
|
|
1760
|
+
lines.push(`${hit.path ?? hit.title}${hit.line ? `:${hit.line}` : ""}: ${hit.snippet.replace(/\s+/g, " ").slice(0, 160)}`);
|
|
1761
|
+
lines.push(` sky context read ${refArgument(hit.ref)}`);
|
|
1700
1762
|
}
|
|
1763
|
+
if (!page.hits.length) lines.push("no matches on this page");
|
|
1764
|
+
const missed = (page.sources ?? []).filter((source) => source.status === "failed" || source.status === "partial").map((source) => source.source);
|
|
1765
|
+
if (page.partial) lines.push(`partial: ${missed.length ? `${missed.join(", ")} not fully searched` : "not every source was searched"}`);
|
|
1766
|
+
if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
|
|
1767
|
+
return lines.join("\n");
|
|
1701
1768
|
}
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1769
|
+
|
|
1770
|
+
// src/agent-file.ts
|
|
1771
|
+
import { existsSync as existsSync5, readFileSync as readFileSync7, writeFileSync as writeFileSync2 } from "fs";
|
|
1772
|
+
import { dirname as dirname4, resolve as resolve3 } from "path";
|
|
1773
|
+
import { parse as parseToml2 } from "smol-toml";
|
|
1774
|
+
var fileInstructionSchema = external_exports.object({
|
|
1775
|
+
name: external_exports.string(),
|
|
1776
|
+
content: external_exports.string().optional(),
|
|
1777
|
+
file: external_exports.string().optional()
|
|
1778
|
+
});
|
|
1779
|
+
var agentFileSchema = external_exports.object({
|
|
1780
|
+
name: external_exports.string().min(1),
|
|
1781
|
+
id: external_exports.string().optional(),
|
|
1782
|
+
// stamped by launch/deploy; identifies the live agent
|
|
1783
|
+
slug: external_exports.string().optional(),
|
|
1784
|
+
// pre-RFC-0079 stamp; ignored (agents are id-addressed)
|
|
1785
|
+
description: external_exports.string().optional(),
|
|
1786
|
+
type: external_exports.enum(AGENT_TYPES).optional(),
|
|
1787
|
+
llm: external_exports.string().optional(),
|
|
1788
|
+
reasoning_effort: external_exports.enum(REASONING_EFFORT_VALUES).optional(),
|
|
1789
|
+
capabilities: capabilitiesSchema.removeDefault().optional(),
|
|
1790
|
+
// omitted = leave the agent's as they are
|
|
1791
|
+
prompt: external_exports.string().optional(),
|
|
1792
|
+
prompt_file: external_exports.string().optional(),
|
|
1793
|
+
instructions: external_exports.array(fileInstructionSchema).optional(),
|
|
1794
|
+
skills: external_exports.array(skillRefSchema).optional(),
|
|
1795
|
+
customData: external_exports.array(customDataSchema).optional(),
|
|
1796
|
+
/** "deny" makes the agent work alone: its sessions may not hire helpers. */
|
|
1797
|
+
hiring_policy: external_exports.enum(["allow", "deny"]).optional(),
|
|
1798
|
+
/** The environment its sessions boot in when they name none. */
|
|
1799
|
+
environment: external_exports.string().min(1).optional(),
|
|
1800
|
+
/** Advanced machine shape (create-only): 1|2|4|8 vCPUs, 256MB-step memory. */
|
|
1801
|
+
vcpus: external_exports.number().int().optional(),
|
|
1802
|
+
memory_mb: external_exports.number().int().optional()
|
|
1803
|
+
});
|
|
1804
|
+
function readAgentFile(path) {
|
|
1805
|
+
const raw = readFileSync7(path, "utf8");
|
|
1806
|
+
const parsed = agentFileSchema.safeParse(parseToml2(raw));
|
|
1807
|
+
if (!parsed.success) {
|
|
1808
|
+
const issue = parsed.error.issues[0];
|
|
1809
|
+
throw new Error(`${path}: ${issue?.path.join(".") ?? ""} ${issue?.message ?? "invalid"}`);
|
|
1810
|
+
}
|
|
1811
|
+
const file = parsed.data;
|
|
1812
|
+
if (file.reasoning_effort && file.type && !isModelEffortSupported(file.type, file.llm ?? defaultLlm(file.type), file.reasoning_effort)) {
|
|
1813
|
+
throw new Error(
|
|
1814
|
+
`${path}: reasoning_effort '${file.reasoning_effort}' is not available for ${file.type} \u2014 ${reasoningEffortDocLine()}`
|
|
1815
|
+
);
|
|
1816
|
+
}
|
|
1817
|
+
const base = dirname4(resolve3(path));
|
|
1818
|
+
const inline = (ref, inlineContent) => {
|
|
1819
|
+
if (inlineContent !== void 0) return inlineContent;
|
|
1820
|
+
if (!ref) return void 0;
|
|
1821
|
+
return readFileSync7(resolve3(base, ref), "utf8");
|
|
1822
|
+
};
|
|
1823
|
+
const spec = {
|
|
1824
|
+
name: file.name,
|
|
1825
|
+
description: file.description,
|
|
1826
|
+
agentType: file.type,
|
|
1827
|
+
llm: file.llm,
|
|
1828
|
+
reasoningEffort: file.reasoning_effort,
|
|
1829
|
+
displayName: file.name,
|
|
1830
|
+
capabilities: file.capabilities,
|
|
1831
|
+
prompt: inline(file.prompt_file, file.prompt),
|
|
1832
|
+
instructions: file.instructions?.map((f) => {
|
|
1833
|
+
const content = inline(f.file, f.content);
|
|
1834
|
+
if (content === void 0) throw new Error(`instruction ${f.name}: content or file required`);
|
|
1835
|
+
return { name: f.name, content };
|
|
1836
|
+
}),
|
|
1837
|
+
skills: file.skills,
|
|
1838
|
+
customData: file.customData,
|
|
1839
|
+
hiringPolicy: file.hiring_policy,
|
|
1840
|
+
environmentId: file.environment,
|
|
1841
|
+
vcpus: file.vcpus,
|
|
1842
|
+
memoryMb: file.memory_mb
|
|
1843
|
+
};
|
|
1844
|
+
return { file, spec };
|
|
1845
|
+
}
|
|
1846
|
+
function stampId(path, id) {
|
|
1847
|
+
writeFileSync2(path, stampIdText(readFileSync7(path, "utf8"), id));
|
|
1848
|
+
}
|
|
1849
|
+
function stampIdText(text, id) {
|
|
1850
|
+
const out = [];
|
|
1851
|
+
let topLevel = true;
|
|
1852
|
+
let openString = null;
|
|
1853
|
+
let stamped = false;
|
|
1854
|
+
let nameAt = -1;
|
|
1855
|
+
for (const line of text.split("\n")) {
|
|
1856
|
+
if (openString) {
|
|
1857
|
+
if (line.includes(openString)) openString = null;
|
|
1858
|
+
out.push(line);
|
|
1859
|
+
continue;
|
|
1860
|
+
}
|
|
1861
|
+
if (/^\s*\[/.test(line)) topLevel = false;
|
|
1862
|
+
if (topLevel && /^\s*(id|slug)\s*=/.test(line)) {
|
|
1863
|
+
if (/^\s*id\s*=/.test(line) && !stamped) {
|
|
1864
|
+
out.push(`id = ${JSON.stringify(id)}`);
|
|
1865
|
+
stamped = true;
|
|
1866
|
+
}
|
|
1867
|
+
continue;
|
|
1729
1868
|
}
|
|
1869
|
+
if (topLevel && nameAt < 0 && /^\s*name\s*=/.test(line)) nameAt = out.length;
|
|
1870
|
+
const opener = /=\s*("""|''')/.exec(line);
|
|
1871
|
+
if (opener && !line.slice(opener.index + opener[0].length).includes(opener[1])) openString = opener[1];
|
|
1872
|
+
out.push(line);
|
|
1730
1873
|
}
|
|
1731
|
-
|
|
1732
|
-
const
|
|
1733
|
-
if (
|
|
1734
|
-
|
|
1735
|
-
if (wholeFolder) for (const path of wholeFolder.paths) entries.delete(path);
|
|
1736
|
-
return { folder, entries, skills: [...skills2], mcpServers: servers.map((server) => server.name) };
|
|
1737
|
-
}
|
|
1738
|
-
var TAR_BLOCK = 512;
|
|
1739
|
-
var TAR_END = Buffer.alloc(TAR_BLOCK * 2);
|
|
1740
|
-
function header(name, size, type) {
|
|
1741
|
-
const block = Buffer.alloc(TAR_BLOCK);
|
|
1742
|
-
block.write(name, 0, 100, "utf8");
|
|
1743
|
-
block.write("0000644\0", 100);
|
|
1744
|
-
block.write("0000000\0", 108);
|
|
1745
|
-
block.write("0000000\0", 116);
|
|
1746
|
-
block.write(size.toString(8).padStart(11, "0") + "\0", 124);
|
|
1747
|
-
block.write("00000000000\0", 136);
|
|
1748
|
-
block.write(" ", 148);
|
|
1749
|
-
block.write(type, 156);
|
|
1750
|
-
block.write("ustar\x0000", 257);
|
|
1751
|
-
let sum = 0;
|
|
1752
|
-
for (const byte of block) sum += byte;
|
|
1753
|
-
block.write(sum.toString(8).padStart(6, "0") + "\0 ", 148);
|
|
1754
|
-
return block;
|
|
1874
|
+
if (!stamped) out.splice(nameAt + 1, 0, `id = ${JSON.stringify(id)}`);
|
|
1875
|
+
const next = out.join("\n");
|
|
1876
|
+
if (parseToml2(next).id !== id) throw new Error("could not stamp the agent id into the file");
|
|
1877
|
+
return next;
|
|
1755
1878
|
}
|
|
1756
|
-
function
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1879
|
+
function scaffoldAgentFile(path, name) {
|
|
1880
|
+
if (existsSync5(path)) throw new Error(`${path} already exists`);
|
|
1881
|
+
writeFileSync2(
|
|
1882
|
+
path,
|
|
1883
|
+
`# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
|
|
1884
|
+
name = "${name}"
|
|
1885
|
+
# type = "pi" # pi (default) | hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode
|
|
1886
|
+
# llm = "deepseek-flash" # must be compatible with type
|
|
1887
|
+
# reasoning_effort = "high" # per harness \u2014 ${reasoningEffortDocLine()}
|
|
1888
|
+
# capabilities = []
|
|
1889
|
+
# hiring_policy = "deny" # its sessions work alone; "allow" (default) lets them hire helpers
|
|
1890
|
+
# environment = "env_..." # the environment its sessions boot in when they name none
|
|
1891
|
+
# [[skills]] # a skill from \`sky skills upload <dir>\`
|
|
1892
|
+
# type = "skill"
|
|
1893
|
+
# skillId = "skill_..."
|
|
1894
|
+
|
|
1895
|
+
prompt = """
|
|
1896
|
+
You are ${name}, a helpful long-lived agent.
|
|
1897
|
+
"""
|
|
1898
|
+
`
|
|
1899
|
+
);
|
|
1765
1900
|
}
|
|
1766
|
-
function
|
|
1767
|
-
|
|
1768
|
-
|
|
1901
|
+
function deployPatch(spec) {
|
|
1902
|
+
return {
|
|
1903
|
+
displayName: spec.displayName,
|
|
1904
|
+
...spec.agentType ? { agentType: spec.agentType } : {},
|
|
1905
|
+
...spec.llm ? { llm: spec.llm } : {},
|
|
1906
|
+
...spec.capabilities ? { capabilities: spec.capabilities } : {},
|
|
1907
|
+
...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
1908
|
+
...spec.skills ? { skills: spec.skills } : {},
|
|
1909
|
+
...spec.hiringPolicy ? { hiringPolicy: spec.hiringPolicy } : {},
|
|
1910
|
+
...spec.environmentId ? { environmentId: spec.environmentId } : {}
|
|
1911
|
+
};
|
|
1769
1912
|
}
|
|
1770
|
-
function
|
|
1771
|
-
if (
|
|
1772
|
-
|
|
1773
|
-
|
|
1913
|
+
function deployReport(id, spec, before, after, promptChanged) {
|
|
1914
|
+
if (spec.agentType && after.agentType !== spec.agentType) {
|
|
1915
|
+
throw new Error(`${id} is still ${after.agentType}: the API did not apply type = "${spec.agentType}"`);
|
|
1916
|
+
}
|
|
1917
|
+
if (spec.llm && after.llm !== spec.llm) {
|
|
1918
|
+
throw new Error(`${id} still runs ${after.llm}: the API did not apply llm = "${spec.llm}"`);
|
|
1919
|
+
}
|
|
1920
|
+
const changed = [];
|
|
1921
|
+
if (promptChanged) changed.push("prompt");
|
|
1922
|
+
if (after.version !== before.version) changed.push("settings");
|
|
1923
|
+
if (!changed.length) return [`${id} is up to date \u2014 nothing to deploy (version ${after.version})`];
|
|
1924
|
+
const lines = [`Deployed ${id}: ${changed.join(" and ")} updated (version ${before.version} \u2192 ${after.version})`];
|
|
1925
|
+
if (after.agentType !== before.agentType || after.llm !== before.llm) {
|
|
1926
|
+
lines.push(` now ${after.agentType} \xB7 ${after.llm} for new sessions (was ${before.agentType} \xB7 ${before.llm})`);
|
|
1927
|
+
}
|
|
1928
|
+
if (spec.skills && after.version !== before.version) {
|
|
1929
|
+
lines.push(` Skills apply to new sessions; running sessions keep the skills they started with \u2014 start one with \`sky session create ${id}\`.`);
|
|
1930
|
+
}
|
|
1931
|
+
return lines;
|
|
1774
1932
|
}
|
|
1775
1933
|
|
|
1776
|
-
// src/
|
|
1777
|
-
var
|
|
1778
|
-
var
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
}
|
|
1792
|
-
|
|
1793
|
-
}
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
1797
|
-
if (entry2.isDirectory()) walk(join6(dir, entry2.name), rel);
|
|
1798
|
-
else out.push(rel);
|
|
1799
|
-
if (out.length > MAX_ENVIRONMENT_FILES) return;
|
|
1800
|
-
}
|
|
1934
|
+
// src/catalog.ts
|
|
1935
|
+
var bare = (handle) => handle.replace(/^@/, "");
|
|
1936
|
+
var optionalAuth = () => ({ auth: Boolean(apiToken()) });
|
|
1937
|
+
function catalogQuery(filter) {
|
|
1938
|
+
const params = new URLSearchParams();
|
|
1939
|
+
if (filter.text?.trim()) params.set("q", filter.text.trim());
|
|
1940
|
+
if (filter.category) params.set("category", filter.category);
|
|
1941
|
+
const query = params.toString();
|
|
1942
|
+
return query ? `?${query}` : "";
|
|
1943
|
+
}
|
|
1944
|
+
function listingBody(agentId, options) {
|
|
1945
|
+
return {
|
|
1946
|
+
agent_id: agentId,
|
|
1947
|
+
handle: bare(options.handle),
|
|
1948
|
+
tagline: options.tagline,
|
|
1949
|
+
...options.description ? { description: options.description } : {},
|
|
1950
|
+
...options.category?.length ? { categories: options.category } : {},
|
|
1951
|
+
...options.does?.length ? { does: options.does } : {},
|
|
1952
|
+
...options.doesNot?.length ? { does_not: options.doesNot } : {},
|
|
1953
|
+
...options.example?.length ? { examples: options.example } : {}
|
|
1801
1954
|
};
|
|
1802
|
-
walk(cwd, "");
|
|
1803
|
-
return out;
|
|
1804
1955
|
}
|
|
1805
|
-
function
|
|
1806
|
-
|
|
1807
|
-
const data = readFileSync7(path);
|
|
1808
|
-
if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
|
|
1809
|
-
return data;
|
|
1956
|
+
async function listCatalog(filter = {}) {
|
|
1957
|
+
return (await api("GET", `/catalog/agents${catalogQuery(filter)}`, void 0, optionalAuth())).agents;
|
|
1810
1958
|
}
|
|
1811
|
-
async function
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
const done = new Promise((resolve10, reject) => {
|
|
1829
|
-
sink.on("finish", () => resolve10());
|
|
1830
|
-
sink.on("error", reject);
|
|
1831
|
-
gzip.on("error", reject);
|
|
1832
|
-
});
|
|
1833
|
-
const write = (chunk) => new Promise((resolve10, reject) => {
|
|
1834
|
-
if (bytes > MAX_ENVIRONMENT_BYTES) {
|
|
1835
|
-
reject(new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`));
|
|
1836
|
-
return;
|
|
1837
|
-
}
|
|
1838
|
-
if (gzip.write(chunk)) resolve10();
|
|
1839
|
-
else gzip.once("drain", resolve10);
|
|
1840
|
-
});
|
|
1841
|
-
try {
|
|
1842
|
-
for (const rel of listed) {
|
|
1843
|
-
const full = join6(cwd, ...rel.split("/"));
|
|
1844
|
-
if (SECRET_FILE2.test(rel)) {
|
|
1845
|
-
leftOut.push({ path: rel, reason: "credential file" });
|
|
1846
|
-
continue;
|
|
1847
|
-
}
|
|
1848
|
-
let stat;
|
|
1849
|
-
try {
|
|
1850
|
-
stat = lstatSync(full);
|
|
1851
|
-
} catch {
|
|
1852
|
-
continue;
|
|
1853
|
-
}
|
|
1854
|
-
if (!stat.isFile()) {
|
|
1855
|
-
leftOut.push({ path: rel, reason: "not a regular file" });
|
|
1856
|
-
continue;
|
|
1857
|
-
}
|
|
1858
|
-
const text = textOf(full, stat.size);
|
|
1859
|
-
if (text) {
|
|
1860
|
-
const masked = redactSecretValues(text.toString("utf8"));
|
|
1861
|
-
if (Object.keys(masked.hits).length) redacted2 += 1;
|
|
1862
|
-
const data = Buffer.from(masked.text, "utf8");
|
|
1863
|
-
await write(tarHeader(rel, data.length));
|
|
1864
|
-
await write(data);
|
|
1865
|
-
await write(tarPadding(data.length));
|
|
1866
|
-
} else {
|
|
1867
|
-
await write(tarHeader(rel, stat.size));
|
|
1868
|
-
const fd = openSync2(full, "r");
|
|
1869
|
-
try {
|
|
1870
|
-
const chunk = Buffer.alloc(1024 * 1024);
|
|
1871
|
-
let left = stat.size;
|
|
1872
|
-
for (let offset = 0; left > 0; ) {
|
|
1873
|
-
const read = readSync2(fd, chunk, 0, Math.min(chunk.length, left), offset);
|
|
1874
|
-
if (read <= 0) throw new Error(`${rel} changed while it was packed; import again.`);
|
|
1875
|
-
await write(Buffer.from(chunk.subarray(0, read)));
|
|
1876
|
-
offset += read;
|
|
1877
|
-
left -= read;
|
|
1878
|
-
}
|
|
1879
|
-
} finally {
|
|
1880
|
-
closeSync2(fd);
|
|
1881
|
-
}
|
|
1882
|
-
await write(tarPadding(stat.size));
|
|
1883
|
-
}
|
|
1884
|
-
written2.add(rel);
|
|
1885
|
-
}
|
|
1886
|
-
const layout = importLayout(resources, cwd, platform, { files: written2.size, paths: written2 });
|
|
1887
|
-
for (const [entry2, data] of layout?.entries ?? []) {
|
|
1888
|
-
await write(tarHeader(entry2, data.length));
|
|
1889
|
-
await write(data);
|
|
1890
|
-
await write(tarPadding(data.length));
|
|
1891
|
-
}
|
|
1892
|
-
await write(TAR_END);
|
|
1893
|
-
gzip.end();
|
|
1894
|
-
await done;
|
|
1895
|
-
if (bytes > MAX_ENVIRONMENT_BYTES) throw new Error(`The working folder packs to more than ${MAX_ENVIRONMENT_BYTES / 1024 ** 3} GiB; import it with --no-environment.`);
|
|
1896
|
-
return {
|
|
1897
|
-
path,
|
|
1898
|
-
folder: layout?.folder ?? "imported-session",
|
|
1899
|
-
bytes,
|
|
1900
|
-
sha256: hash.digest("hex"),
|
|
1901
|
-
files: written2.size + (layout?.entries.size ?? 0),
|
|
1902
|
-
redacted: redacted2,
|
|
1903
|
-
leftOut,
|
|
1904
|
-
dispose: () => rmSync2(scratch, { recursive: true, force: true })
|
|
1905
|
-
};
|
|
1906
|
-
} catch (error) {
|
|
1907
|
-
gzip.destroy();
|
|
1908
|
-
sink.destroy();
|
|
1909
|
-
rmSync2(scratch, { recursive: true, force: true });
|
|
1910
|
-
throw error;
|
|
1959
|
+
async function showCatalogAgent(handle) {
|
|
1960
|
+
return (await api("GET", `/catalog/agents/${encodeURIComponent(bare(handle))}`, void 0, optionalAuth())).agent;
|
|
1961
|
+
}
|
|
1962
|
+
async function listCategories() {
|
|
1963
|
+
return (await api("GET", "/catalog/categories", void 0, { auth: false })).categories;
|
|
1964
|
+
}
|
|
1965
|
+
async function publishListing(agentId, options) {
|
|
1966
|
+
return (await api("POST", "/catalog/listings", listingBody(agentId, options))).agent;
|
|
1967
|
+
}
|
|
1968
|
+
async function removeListing(handle) {
|
|
1969
|
+
await api("DELETE", `/catalog/listings/${encodeURIComponent(bare(handle))}`);
|
|
1970
|
+
}
|
|
1971
|
+
async function setEngineStatus(engine, status, reason) {
|
|
1972
|
+
await api("PATCH", `/catalog/engines/${encodeURIComponent(engine)}`, { status, reason });
|
|
1973
|
+
const state = await api("GET", "/catalog/engines", void 0, { auth: false });
|
|
1974
|
+
if (state.delisted.includes(engine) !== (status === "delisted")) {
|
|
1975
|
+
throw new Error("The catalog does not show the requested state; check whether another operator changed it.");
|
|
1911
1976
|
}
|
|
1912
1977
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1978
|
+
var uses = (agent2) => `${agent2.stats.uses} ${agent2.stats.uses === 1 ? "use" : "uses"}`;
|
|
1979
|
+
var mine = (agent2) => agent2.visibility === "workspace" ? " (yours)" : "";
|
|
1980
|
+
function renderCatalogAgent(agent2) {
|
|
1981
|
+
const section = (title, lines) => lines.length ? [`${title}:`, ...lines.map((line) => ` - ${line}`)] : [];
|
|
1982
|
+
return [
|
|
1983
|
+
`@${agent2.handle} ${agent2.name}${mine(agent2)}`,
|
|
1984
|
+
agent2.tagline,
|
|
1985
|
+
...agent2.description && agent2.description !== agent2.tagline ? ["", agent2.description] : [],
|
|
1986
|
+
"",
|
|
1987
|
+
`Categories: ${agent2.categories.join(", ") || "none"}`,
|
|
1988
|
+
`Used: ${uses(agent2)}`,
|
|
1989
|
+
...section("Does", agent2.does),
|
|
1990
|
+
...section("Does not", agent2.does_not),
|
|
1991
|
+
...section("Examples", agent2.examples),
|
|
1992
|
+
"",
|
|
1993
|
+
`Hire it: sky session create @${agent2.handle} --message "\u2026"`
|
|
1994
|
+
].join("\n");
|
|
1923
1995
|
}
|
|
1924
1996
|
|
|
1925
|
-
// src/
|
|
1926
|
-
|
|
1927
|
-
|
|
1997
|
+
// src/clone.ts
|
|
1998
|
+
import { spawn } from "child_process";
|
|
1999
|
+
import { existsSync as existsSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync3 } from "fs";
|
|
2000
|
+
import { homedir as homedir4 } from "os";
|
|
2001
|
+
import { basename as basename5, join as join7 } from "path";
|
|
2002
|
+
import { createInterface } from "readline";
|
|
2003
|
+
var PROMPT_CAP = 1e5;
|
|
2004
|
+
var HARNESSES = ["claude_code", "codex", "openclaw"];
|
|
2005
|
+
function claudeConfigRoot() {
|
|
2006
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join7(homedir4(), ".claude");
|
|
1928
2007
|
}
|
|
1929
|
-
function
|
|
2008
|
+
function readIfExists(path) {
|
|
1930
2009
|
try {
|
|
1931
|
-
return
|
|
2010
|
+
return readFileSync8(path, "utf8");
|
|
1932
2011
|
} catch {
|
|
1933
|
-
return
|
|
1934
|
-
}
|
|
1935
|
-
}
|
|
1936
|
-
function readPrefix(filePath) {
|
|
1937
|
-
const fd = openSync3(filePath, "r");
|
|
1938
|
-
try {
|
|
1939
|
-
const bytes = Buffer.alloc(256 * 1024);
|
|
1940
|
-
return bytes.subarray(0, readSync3(fd, bytes, 0, bytes.length, 0)).toString("utf8");
|
|
1941
|
-
} finally {
|
|
1942
|
-
closeSync3(fd);
|
|
2012
|
+
return void 0;
|
|
1943
2013
|
}
|
|
1944
2014
|
}
|
|
1945
|
-
function
|
|
2015
|
+
function readJson2(path) {
|
|
2016
|
+
const raw = readIfExists(path);
|
|
2017
|
+
if (raw === void 0) return void 0;
|
|
1946
2018
|
try {
|
|
1947
|
-
|
|
1948
|
-
for (const raw of data.split("\n")) {
|
|
1949
|
-
if (!raw.trim()) continue;
|
|
1950
|
-
let line;
|
|
1951
|
-
try {
|
|
1952
|
-
line = JSON.parse(redactLine2(raw));
|
|
1953
|
-
} catch {
|
|
1954
|
-
continue;
|
|
1955
|
-
}
|
|
1956
|
-
if (!line || typeof line !== "object") continue;
|
|
1957
|
-
const rec = line;
|
|
1958
|
-
if (rec.type === "user" && rec.message && typeof rec.message === "object") {
|
|
1959
|
-
const msg = rec.message;
|
|
1960
|
-
if (Array.isArray(msg.content)) {
|
|
1961
|
-
for (const block of msg.content) {
|
|
1962
|
-
if (typeof block === "object" && block !== null) {
|
|
1963
|
-
const b = block;
|
|
1964
|
-
if (b.type === "text" && typeof b.text === "string" && b.text.trim()) {
|
|
1965
|
-
return b.text.trim().slice(0, 120);
|
|
1966
|
-
}
|
|
1967
|
-
}
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
1971
|
-
return msg.content.trim().slice(0, 120);
|
|
1972
|
-
}
|
|
1973
|
-
}
|
|
1974
|
-
if (rec.type === "event_msg" && rec.payload && typeof rec.payload === "object") {
|
|
1975
|
-
const payload = rec.payload;
|
|
1976
|
-
if (payload.type === "user_message" && typeof payload.message === "string") return redactLine2(payload.message).slice(0, 120);
|
|
1977
|
-
}
|
|
1978
|
-
if (rec.type === "response_item" && rec.payload && typeof rec.payload === "object") {
|
|
1979
|
-
const payload = rec.payload;
|
|
1980
|
-
if (payload.type === "message" && payload.role === "user" && Array.isArray(payload.content)) {
|
|
1981
|
-
const text = payload.content.find((part) => typeof part?.text === "string")?.text;
|
|
1982
|
-
if (typeof text === "string" && text.trim()) return redactLine2(text.trim()).slice(0, 120);
|
|
1983
|
-
}
|
|
1984
|
-
}
|
|
1985
|
-
if (rec.type === "session_meta") continue;
|
|
1986
|
-
if (rec.type === "user_turn" || rec.type === "message" && rec.role === "user") {
|
|
1987
|
-
const content = rec.content;
|
|
1988
|
-
if (typeof content === "string" && content.trim()) return content.trim().slice(0, 120);
|
|
1989
|
-
}
|
|
1990
|
-
}
|
|
1991
|
-
return null;
|
|
2019
|
+
return JSON.parse(raw);
|
|
1992
2020
|
} catch {
|
|
1993
|
-
return
|
|
2021
|
+
return void 0;
|
|
1994
2022
|
}
|
|
1995
2023
|
}
|
|
1996
|
-
function
|
|
1997
|
-
const
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
try {
|
|
2006
|
-
entries = readdirSync4(projectDir);
|
|
2007
|
-
} catch {
|
|
2008
|
-
continue;
|
|
2009
|
-
}
|
|
2010
|
-
const workspace2 = decodeWorkspace(encoded);
|
|
2011
|
-
for (const entry2 of entries) {
|
|
2012
|
-
if (!entry2.endsWith(".jsonl")) continue;
|
|
2013
|
-
const sessionId = entry2.slice(0, -6);
|
|
2014
|
-
const mainFile = join7(projectDir, entry2);
|
|
2015
|
-
let stat;
|
|
2016
|
-
try {
|
|
2017
|
-
stat = statSync5(mainFile);
|
|
2018
|
-
} catch {
|
|
2019
|
-
continue;
|
|
2020
|
-
}
|
|
2021
|
-
if (!stat.isFile()) continue;
|
|
2022
|
-
let discoveryError;
|
|
2023
|
-
const subagentFiles = [];
|
|
2024
|
-
const subagentDir = join7(projectDir, sessionId, "subagents");
|
|
2025
|
-
if (existsSync6(subagentDir)) {
|
|
2026
|
-
try {
|
|
2027
|
-
const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
2028
|
-
for (const f of subFiles) {
|
|
2029
|
-
subagentFiles.push(join7(subagentDir, f));
|
|
2030
|
-
}
|
|
2031
|
-
} catch {
|
|
2032
|
-
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
2033
|
-
}
|
|
2034
|
-
}
|
|
2035
|
-
sessions.push({
|
|
2036
|
-
harness: "claude_code",
|
|
2037
|
-
sessionId,
|
|
2038
|
-
workspace: workspace2,
|
|
2039
|
-
title: sniffTitle(mainFile),
|
|
2040
|
-
mtimeMs: stat.mtimeMs,
|
|
2041
|
-
files: [mainFile, ...subagentFiles],
|
|
2042
|
-
...discoveryError ? { discoveryError } : {}
|
|
2043
|
-
});
|
|
2044
|
-
}
|
|
2045
|
-
}
|
|
2046
|
-
} catch {
|
|
2024
|
+
function claudeModel(configRoot) {
|
|
2025
|
+
const candidates = [
|
|
2026
|
+
join7(process.cwd(), ".claude", "settings.local.json"),
|
|
2027
|
+
join7(process.cwd(), ".claude", "settings.json"),
|
|
2028
|
+
join7(configRoot, "settings.json")
|
|
2029
|
+
];
|
|
2030
|
+
for (const path of candidates) {
|
|
2031
|
+
const model = readJson2(path)?.model;
|
|
2032
|
+
if (typeof model === "string" && model.trim()) return model.trim();
|
|
2047
2033
|
}
|
|
2048
|
-
return
|
|
2034
|
+
return void 0;
|
|
2049
2035
|
}
|
|
2050
|
-
function
|
|
2051
|
-
const
|
|
2052
|
-
const
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2036
|
+
function claudeMcpServers() {
|
|
2037
|
+
const merged = {};
|
|
2038
|
+
const userConfig = readJson2(join7(homedir4(), ".claude.json"));
|
|
2039
|
+
Object.assign(merged, userConfig?.mcpServers ?? {});
|
|
2040
|
+
Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
|
|
2041
|
+
const projectConfig = readJson2(
|
|
2042
|
+
join7(process.cwd(), ".mcp.json")
|
|
2043
|
+
);
|
|
2044
|
+
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
2045
|
+
return merged;
|
|
2046
|
+
}
|
|
2047
|
+
function assemblePrompt(parts) {
|
|
2048
|
+
const joined = parts.map((p) => `# \u2500\u2500 ${p.label} \u2500\u2500
|
|
2049
|
+
|
|
2050
|
+
${p.content.trim()}`).join("\n\n").trim();
|
|
2051
|
+
if (joined.length <= PROMPT_CAP) return { prompt: joined, truncated: false };
|
|
2052
|
+
const notice = "\n\n[truncated by sky clone: local instructions exceeded the 100k prompt cap]";
|
|
2053
|
+
return { prompt: joined.slice(0, PROMPT_CAP - notice.length) + notice, truncated: true };
|
|
2054
|
+
}
|
|
2055
|
+
function readClaudeCode() {
|
|
2056
|
+
const configRoot = claudeConfigRoot();
|
|
2057
|
+
const parts = [];
|
|
2058
|
+
const sources = [];
|
|
2059
|
+
const global = readIfExists(join7(configRoot, "CLAUDE.md"));
|
|
2060
|
+
if (global?.trim()) {
|
|
2061
|
+
parts.push({ label: "Global CLAUDE.md", content: global });
|
|
2062
|
+
sources.push("global CLAUDE.md");
|
|
2063
|
+
}
|
|
2064
|
+
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
2065
|
+
const project2 = readIfExists(join7(process.cwd(), name));
|
|
2066
|
+
if (project2?.trim()) {
|
|
2067
|
+
parts.push({ label: `Project ${name}`, content: project2 });
|
|
2068
|
+
sources.push(`project ${name}`);
|
|
2069
|
+
break;
|
|
2059
2070
|
}
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
const parent = data.forked_from_id ?? data.source?.spawn?.parent_thread_id ?? data.source?.subagent?.spawn?.parent_thread_id;
|
|
2073
|
-
records.push({
|
|
2074
|
-
session: {
|
|
2075
|
-
harness: "codex",
|
|
2076
|
-
sessionId: data.id,
|
|
2077
|
-
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
2078
|
-
title: sniffTitle(full),
|
|
2079
|
-
mtimeMs: statSync5(full).mtimeMs,
|
|
2080
|
-
files: [full]
|
|
2081
|
-
},
|
|
2082
|
-
...typeof parent === "string" ? { parent } : {}
|
|
2083
|
-
});
|
|
2084
|
-
} catch {
|
|
2085
|
-
continue;
|
|
2071
|
+
}
|
|
2072
|
+
const { prompt, truncated } = assemblePrompt(parts);
|
|
2073
|
+
const allServers = claudeMcpServers();
|
|
2074
|
+
const { kept, dropped } = splitMcpServers(allServers);
|
|
2075
|
+
const secretCandidates = [];
|
|
2076
|
+
for (const d of dropped) {
|
|
2077
|
+
const def = allServers[d.name];
|
|
2078
|
+
if (!def?.env) continue;
|
|
2079
|
+
for (const key of d.envKeys) {
|
|
2080
|
+
const value = def.env[key];
|
|
2081
|
+
if (value !== void 0 && value.trim()) {
|
|
2082
|
+
secretCandidates.push({ serverName: d.name, envKey: key, value, url: d.url });
|
|
2086
2083
|
}
|
|
2087
2084
|
}
|
|
2088
2085
|
}
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2086
|
+
return {
|
|
2087
|
+
harness: "claude_code",
|
|
2088
|
+
configRoot,
|
|
2089
|
+
prompt,
|
|
2090
|
+
promptSources: sources,
|
|
2091
|
+
truncated,
|
|
2092
|
+
model: claudeModel(configRoot),
|
|
2093
|
+
keptServers: kept,
|
|
2094
|
+
droppedServers: dropped,
|
|
2095
|
+
secretCandidates
|
|
2096
|
+
};
|
|
2097
|
+
}
|
|
2098
|
+
function detectHarness(explicit) {
|
|
2099
|
+
if (explicit) {
|
|
2100
|
+
if (!HARNESSES.includes(explicit)) {
|
|
2101
|
+
throw new Error(`unknown harness ${explicit} \u2014 expected ${HARNESSES.join(" | ")}`);
|
|
2102
|
+
}
|
|
2103
|
+
return explicit;
|
|
2104
|
+
}
|
|
2105
|
+
if (existsSync6(claudeConfigRoot())) return "claude_code";
|
|
2106
|
+
if (existsSync6(join7(homedir4(), ".codex"))) return "codex";
|
|
2107
|
+
if (existsSync6(join7(homedir4(), ".openclaw"))) return "openclaw";
|
|
2108
|
+
throw new Error(
|
|
2109
|
+
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
2110
|
+
);
|
|
2102
2111
|
}
|
|
2103
|
-
function
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2112
|
+
function readLocal(harness2) {
|
|
2113
|
+
if (harness2 === "claude_code") return readClaudeCode();
|
|
2114
|
+
throw new Error(
|
|
2115
|
+
`${harness2} cloning lands next \u2014 claude_code is the first supported harness`
|
|
2116
|
+
);
|
|
2107
2117
|
}
|
|
2108
|
-
function
|
|
2109
|
-
|
|
2110
|
-
if (env) return env;
|
|
2111
|
-
const api2 = apiBase().replace(/\/$/, "");
|
|
2112
|
-
const derived = api2.replace(/\/api\/v1$/, "").replace(/\/v1$/, "").replace("api.agentsky.dev", "agentsky.dev");
|
|
2113
|
-
if (derived.includes("localhost:3000")) return derived.replace("localhost:3000", "localhost:3002");
|
|
2114
|
-
return derived;
|
|
2118
|
+
function linkPath(configRoot) {
|
|
2119
|
+
return join7(configRoot, "agentsky-link.json");
|
|
2115
2120
|
}
|
|
2116
|
-
function
|
|
2117
|
-
const
|
|
2118
|
-
|
|
2119
|
-
const codexId = process.env.CODEX_THREAD_ID;
|
|
2120
|
-
if (codexId) return { harness: "codex", sessionId: codexId };
|
|
2121
|
-
return void 0;
|
|
2121
|
+
function readLink(configRoot) {
|
|
2122
|
+
const link2 = readJson2(linkPath(configRoot));
|
|
2123
|
+
return link2?.agentId ? link2 : void 0;
|
|
2122
2124
|
}
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
const res = await fetch(`${base}/api/traces`, {
|
|
2127
|
-
method: "POST",
|
|
2128
|
-
redirect: "error",
|
|
2129
|
-
signal: AbortSignal.timeout(15e3),
|
|
2130
|
-
headers: {
|
|
2131
|
-
"content-type": "application/json",
|
|
2132
|
-
authorization: `Bearer ${token}`,
|
|
2133
|
-
"user-agent": USER_AGENT
|
|
2134
|
-
},
|
|
2135
|
-
body: JSON.stringify({ harness: harness2 })
|
|
2136
|
-
});
|
|
2137
|
-
if (!res.ok) {
|
|
2138
|
-
const body = await res.json().catch(() => null);
|
|
2139
|
-
throw new Error(
|
|
2140
|
-
typeof body?.error === "string" ? body.error : `Failed to create import (${res.status}).`
|
|
2141
|
-
);
|
|
2142
|
-
}
|
|
2143
|
-
return await res.json();
|
|
2125
|
+
function writeLink(configRoot, link2) {
|
|
2126
|
+
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link2, null, 2)}
|
|
2127
|
+
`);
|
|
2144
2128
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2129
|
+
var DEFAULT_VAULT_NAME = "default";
|
|
2130
|
+
async function fetchDefaultVaultId() {
|
|
2131
|
+
const { vaults } = await api(
|
|
2132
|
+
"GET",
|
|
2133
|
+
"/vaults"
|
|
2134
|
+
);
|
|
2135
|
+
return vaults.find((v) => v.display_name === DEFAULT_VAULT_NAME)?.id ?? null;
|
|
2136
|
+
}
|
|
2137
|
+
async function ensureDefaultVaultId() {
|
|
2138
|
+
const existing = await fetchDefaultVaultId();
|
|
2139
|
+
if (existing) return existing;
|
|
2153
2140
|
try {
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2141
|
+
const { vault: vault2 } = await api("POST", "/vaults", {
|
|
2142
|
+
display_name: DEFAULT_VAULT_NAME
|
|
2143
|
+
});
|
|
2144
|
+
return vault2.id;
|
|
2145
|
+
} catch (error) {
|
|
2146
|
+
if (error instanceof ApiError && error.status === 409) {
|
|
2147
|
+
const raced = await fetchDefaultVaultId();
|
|
2148
|
+
if (raced) return raced;
|
|
2149
|
+
}
|
|
2150
|
+
throw error;
|
|
2157
2151
|
}
|
|
2158
|
-
if (!pkg || typeof pkg !== "object" || Array.isArray(pkg)) throw new Error("Summary file must be a JSON object.");
|
|
2159
|
-
const p = pkg;
|
|
2160
|
-
if (p.format !== "asteroids.session-summary") throw new Error('Summary file must have format "asteroids.session-summary".');
|
|
2161
|
-
if (p.format_version !== 1) throw new Error("Summary file must have format_version 1.");
|
|
2162
|
-
const redacted2 = redactLine2(raw.trimEnd());
|
|
2163
|
-
return redacted2;
|
|
2164
|
-
}
|
|
2165
|
-
var MAX_IMPORT_BYTES = 30 * 1024 * 1024;
|
|
2166
|
-
var SUMMARY_RESERVE_BYTES = 128 * 1024;
|
|
2167
|
-
var MAX_LISTED_ARTIFACTS = 99;
|
|
2168
|
-
function leftOutLine(left) {
|
|
2169
|
-
const counts = /* @__PURE__ */ new Map();
|
|
2170
|
-
for (const file of left) counts.set(file.reason, (counts.get(file.reason) ?? 0) + 1);
|
|
2171
|
-
const files2 = [...counts].map(([reason, n]) => `${n} ${n === 1 ? "file" : "files"} (${reason})`).join(", ");
|
|
2172
|
-
return `Not transferred: ${files2 ? `${files2}; ` : ""}runtime state such as running processes, installed tools and environment variables; and reasoning the source harness stored unreadably.`;
|
|
2173
2152
|
}
|
|
2174
|
-
function
|
|
2175
|
-
if (
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
let
|
|
2185
|
-
let
|
|
2186
|
-
|
|
2187
|
-
const
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2202
|
-
|
|
2203
|
-
if (String(rec.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
|
|
2204
|
-
if (path === session2.files[0]) source.push(rec);
|
|
2205
|
-
records.push(rec);
|
|
2206
|
-
if (rec.type === "response_item") {
|
|
2207
|
-
const payload = rec.payload;
|
|
2208
|
-
if (payload?.type === "reasoning" && typeof payload?.encrypted_content === "string" && payload.encrypted_content) {
|
|
2209
|
-
encryptedReasoningCount++;
|
|
2153
|
+
async function confirmAndVaultSecrets(candidates, yes) {
|
|
2154
|
+
if (!candidates.length) return;
|
|
2155
|
+
if (yes) {
|
|
2156
|
+
console.log(
|
|
2157
|
+
` ${candidates.length} secret${candidates.length === 1 ? "" : "s"} from dropped MCP servers not saved (--yes skips vault confirmation).`
|
|
2158
|
+
);
|
|
2159
|
+
return;
|
|
2160
|
+
}
|
|
2161
|
+
console.log("\nSecret-bearing MCP servers were left out of the clone payload.");
|
|
2162
|
+
console.log("You can save their keys to your workspace vault now (values are sent encrypted, never displayed).\n");
|
|
2163
|
+
let vaultId;
|
|
2164
|
+
let saved = 0;
|
|
2165
|
+
let skipped = 0;
|
|
2166
|
+
for (const c of candidates) {
|
|
2167
|
+
const chosen = await confirm(` Add ${c.envKey} (from ${c.serverName}) to your default vault?`);
|
|
2168
|
+
if (!chosen) {
|
|
2169
|
+
console.log(` \u2014 skipped ${c.envKey}`);
|
|
2170
|
+
skipped += 1;
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
try {
|
|
2174
|
+
vaultId ??= await ensureDefaultVaultId();
|
|
2175
|
+
await api("POST", `/vaults/${encodeURIComponent(vaultId)}/secrets`, {
|
|
2176
|
+
display_name: `${c.envKey} (${c.serverName})`,
|
|
2177
|
+
auth: {
|
|
2178
|
+
type: "environment_variable",
|
|
2179
|
+
secret_name: c.envKey,
|
|
2180
|
+
value: c.value,
|
|
2181
|
+
allowed_hosts: c.url ? [c.url] : []
|
|
2210
2182
|
}
|
|
2183
|
+
});
|
|
2184
|
+
console.log(` \u2713 saved ${c.envKey}`);
|
|
2185
|
+
saved += 1;
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
if (error instanceof ApiError && error.status === 409) {
|
|
2188
|
+
console.log(` \u2014 ${c.envKey} already in vault`);
|
|
2189
|
+
} else {
|
|
2190
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
2191
|
+
console.log(` \u2717 ${c.envKey} failed: ${msg}`);
|
|
2211
2192
|
}
|
|
2212
|
-
if (rec.type === "compacted") {
|
|
2213
|
-
compactionCount++;
|
|
2214
|
-
}
|
|
2215
|
-
const id = rec.type === "session_meta" ? rec.payload?.id : rec.sessionId;
|
|
2216
|
-
if (!nativeId && typeof id === "string") nativeId = id;
|
|
2217
|
-
const result = redactLine(raw.replace(/\r$/, ""), { requireStructured: true });
|
|
2218
|
-
if (Object.keys(result.hits).length > 0) redactionCount++;
|
|
2219
|
-
const line = result.line + "\n";
|
|
2220
|
-
outputBytes += Buffer.byteLength(line);
|
|
2221
|
-
if (outputBytes > maximum) throw new Error("Redacted transcript exceeds the 16 MiB limit.");
|
|
2222
|
-
digest.update(line);
|
|
2223
|
-
lines.push(line);
|
|
2224
2193
|
}
|
|
2225
2194
|
}
|
|
2226
|
-
if (
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2195
|
+
if (saved || skipped) {
|
|
2196
|
+
console.log(
|
|
2197
|
+
`
|
|
2198
|
+
Vault: ${saved} saved, ${skipped} skipped. Attach the "default" vault to a session to use these keys.`
|
|
2199
|
+
);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
function confirm(question) {
|
|
2203
|
+
return new Promise((done) => {
|
|
2204
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
2205
|
+
rl.question(`${question} [Y/n] `, (answer) => {
|
|
2206
|
+
rl.close();
|
|
2207
|
+
done(/^(y(es)?)?$/i.test(answer.trim()));
|
|
2208
|
+
});
|
|
2236
2209
|
});
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
format: "asteroids.session-summary",
|
|
2244
|
-
format_version: 1,
|
|
2245
|
-
source_harness: session2.harness,
|
|
2246
|
-
source_harness_version: meta.harness_version?.slice(0, 100) ?? null,
|
|
2247
|
-
source_session_id: sourceId,
|
|
2248
|
-
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2249
|
-
summary: `The complete selected local conversation is attached as a transcript.${attachedNote} Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.`,
|
|
2250
|
-
context: {
|
|
2251
|
-
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
2252
|
-
constraints: [],
|
|
2253
|
-
decisions: [],
|
|
2254
|
-
completed: [],
|
|
2255
|
-
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
2256
|
-
verification: [],
|
|
2257
|
-
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(", ")}.`] : []],
|
|
2258
|
-
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).` })),
|
|
2259
|
-
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
2260
|
-
}
|
|
2261
|
-
};
|
|
2262
|
-
summaryJson = JSON.stringify(summary);
|
|
2210
|
+
}
|
|
2211
|
+
function openBrowser(url) {
|
|
2212
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
2213
|
+
try {
|
|
2214
|
+
spawn(cmd, [url], { stdio: "ignore", detached: true }).unref();
|
|
2215
|
+
} catch {
|
|
2263
2216
|
}
|
|
2264
|
-
const body = lines.join("") + files2.lines.join("") + JSON.stringify({ type: "import.summary", package: JSON.parse(summaryJson) }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + files2.lines.length + 1 }) + "\n";
|
|
2265
|
-
const gaps = [];
|
|
2266
|
-
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
2267
|
-
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
2268
|
-
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
2269
|
-
const cwd = meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : "");
|
|
2270
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup, cwd, resourceLines: files2.lines };
|
|
2271
2217
|
}
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2218
|
+
function kb(text) {
|
|
2219
|
+
return `${Math.max(1, Math.round(text.length / 1024))} KB`;
|
|
2220
|
+
}
|
|
2221
|
+
async function createClone(local, opts) {
|
|
2222
|
+
const keptNames = Object.keys(local.keptServers);
|
|
2223
|
+
const summary = [
|
|
2224
|
+
local.promptSources.length ? `${local.promptSources.join(" + ")} (${kb(local.prompt)})` : "no instructions found",
|
|
2225
|
+
`model ${local.model ?? "(harness default)"}`,
|
|
2226
|
+
`${keptNames.length} MCP server${keptNames.length === 1 ? "" : "s"}${keptNames.length ? ` (${keptNames.join(", ")})` : ""}`
|
|
2227
|
+
].join(" \xB7 ");
|
|
2228
|
+
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${basename5(process.cwd())})`);
|
|
2229
|
+
console.log(`Will clone: ${summary}`);
|
|
2230
|
+
if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
|
|
2231
|
+
if (local.droppedServers.length) {
|
|
2232
|
+
console.log(
|
|
2233
|
+
`Left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
2234
|
+
);
|
|
2277
2235
|
}
|
|
2278
|
-
opts.
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
let key;
|
|
2282
|
-
if (opts.importKey && opts.grantTraceId) {
|
|
2283
|
-
traceId = opts.grantTraceId;
|
|
2284
|
-
key = opts.importKey;
|
|
2285
|
-
} else {
|
|
2286
|
-
opts.onProgress?.("Minting import key\u2026");
|
|
2287
|
-
({ traceId, key } = await mintTrace(base, opts.session.harness));
|
|
2236
|
+
if (opts.dryRun) {
|
|
2237
|
+
console.log("Dry run \u2014 nothing sent.");
|
|
2238
|
+
return;
|
|
2288
2239
|
}
|
|
2289
|
-
opts.
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
redirect: "error",
|
|
2293
|
-
signal: AbortSignal.timeout(12e4),
|
|
2294
|
-
headers: { authorization: `Bearer ${key}`, "content-type": "application/x-ndjson", "x-source-harness": opts.session.harness, "user-agent": USER_AGENT },
|
|
2295
|
-
body
|
|
2296
|
-
});
|
|
2297
|
-
if (!response.ok) {
|
|
2298
|
-
const result2 = await response.json().catch(() => null);
|
|
2299
|
-
throw new Error(typeof result2?.error === "string" ? result2.error : result2?.error?.message ?? `Upload failed (${response.status}).`);
|
|
2240
|
+
if (!opts.yes && !await confirm("Launch this as a new cloud agent?")) {
|
|
2241
|
+
console.log("Aborted \u2014 nothing sent.");
|
|
2242
|
+
return;
|
|
2300
2243
|
}
|
|
2301
|
-
const
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
bytes: outputBytes,
|
|
2311
|
-
redactionCount,
|
|
2312
|
-
gaps,
|
|
2313
|
-
attachedFiles,
|
|
2314
|
-
leftOutFiles,
|
|
2315
|
-
setup,
|
|
2316
|
-
environment
|
|
2244
|
+
const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
|
|
2245
|
+
if (skipped.length) {
|
|
2246
|
+
console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
|
|
2247
|
+
}
|
|
2248
|
+
const body = {
|
|
2249
|
+
agentType: local.harness,
|
|
2250
|
+
...local.model ? { llm: local.model } : {},
|
|
2251
|
+
...local.prompt ? { prompt: local.prompt } : {},
|
|
2252
|
+
...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
|
|
2317
2253
|
};
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2254
|
+
let sentLlm = local.model ?? null;
|
|
2255
|
+
let created;
|
|
2256
|
+
try {
|
|
2257
|
+
({ agent: created } = await api("POST", "/agents", body));
|
|
2258
|
+
} catch (error) {
|
|
2259
|
+
if (error instanceof ApiError && error.status === 422 && local.model) {
|
|
2260
|
+
const successor = supersededBy(local.model);
|
|
2261
|
+
console.log(
|
|
2262
|
+
successor ? ` note: ${local.model} is superseded \u2014 using ${successor}` : ` note: ${local.model} isn't available for ${local.harness} \u2014 using the harness default`
|
|
2263
|
+
);
|
|
2264
|
+
sentLlm = successor;
|
|
2265
|
+
({ agent: created } = await api("POST", "/agents", {
|
|
2266
|
+
...body,
|
|
2267
|
+
llm: successor ?? void 0
|
|
2268
|
+
}));
|
|
2269
|
+
} else {
|
|
2270
|
+
throw error;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
console.log("Starting its runtime\u2026");
|
|
2274
|
+
await api("POST", "/sessions", { agent: created.id });
|
|
2275
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2276
|
+
writeLink(local.configRoot, {
|
|
2277
|
+
baseUrl: apiBase(),
|
|
2278
|
+
agentId: created.id,
|
|
2279
|
+
clonedAt: now,
|
|
2280
|
+
lastSyncedAt: now,
|
|
2281
|
+
llm: sentLlm,
|
|
2282
|
+
mcpServers: keptNames
|
|
2326
2283
|
});
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2329
|
-
|
|
2284
|
+
const url = `${apiBase()}/agents/${created.id}`;
|
|
2285
|
+
console.log(`Cloned \u2192 ${created.id} ${url}`);
|
|
2286
|
+
console.log(
|
|
2287
|
+
`Linked this machine (${linkPath(local.configRoot)}). Push prompt changes anytime by running \`sky clone\` again.`
|
|
2288
|
+
);
|
|
2289
|
+
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
2290
|
+
if (opts.open) openBrowser(url);
|
|
2330
2291
|
}
|
|
2331
|
-
async function
|
|
2332
|
-
|
|
2333
|
-
|
|
2292
|
+
async function syncClone(local, link2, opts) {
|
|
2293
|
+
const since = link2.lastSyncedAt ?? link2.clonedAt;
|
|
2294
|
+
console.log(
|
|
2295
|
+
`Syncing ${link2.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
|
|
2296
|
+
);
|
|
2297
|
+
if (opts.dryRun) {
|
|
2298
|
+
console.log(
|
|
2299
|
+
` would push prompt (${kb(local.prompt)}) to ${link2.agentId}. Dry run \u2014 nothing sent.`
|
|
2300
|
+
);
|
|
2301
|
+
return;
|
|
2302
|
+
}
|
|
2303
|
+
let agent2;
|
|
2334
2304
|
try {
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
}
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2305
|
+
({ agent: agent2 } = await api(
|
|
2306
|
+
"GET",
|
|
2307
|
+
`/agents/${encodeURIComponent(link2.agentId)}`
|
|
2308
|
+
));
|
|
2309
|
+
} catch (error) {
|
|
2310
|
+
if (error instanceof ApiError && error.status === 404) {
|
|
2311
|
+
throw new Error(
|
|
2312
|
+
`linked agent ${link2.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
|
|
2313
|
+
);
|
|
2314
|
+
}
|
|
2315
|
+
throw error;
|
|
2316
|
+
}
|
|
2317
|
+
if (agent2.agentType !== local.harness) {
|
|
2318
|
+
throw new Error(
|
|
2319
|
+
`linked agent ${link2.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
|
|
2320
|
+
);
|
|
2321
|
+
}
|
|
2322
|
+
const result = await api(
|
|
2323
|
+
"PUT",
|
|
2324
|
+
`/agents/${encodeURIComponent(link2.agentId)}/prompt`,
|
|
2325
|
+
{ prompt: local.prompt }
|
|
2326
|
+
);
|
|
2327
|
+
console.log(
|
|
2328
|
+
result.unchanged ? " prompt unchanged" : ` prompt updated (v${result.version}${result.applied === "live" ? ", hot-reloaded" : ", applies on next boot"})`
|
|
2329
|
+
);
|
|
2330
|
+
const drift = [];
|
|
2331
|
+
const localModel = local.model ?? null;
|
|
2332
|
+
const linkedModel = link2.llm ?? agent2.llm;
|
|
2333
|
+
if (localModel && linkedModel && localModel !== linkedModel) {
|
|
2334
|
+
drift.push(`model changed ${linkedModel} \u2192 ${localModel}`);
|
|
2335
|
+
}
|
|
2336
|
+
const before = new Set(link2.mcpServers ?? []);
|
|
2337
|
+
const nowKept = Object.keys(local.keptServers);
|
|
2338
|
+
const newServers = nowKept.filter((n) => !before.has(n));
|
|
2339
|
+
const goneServers = [...before].filter((n) => !nowKept.includes(n));
|
|
2340
|
+
if (newServers.length) drift.push(`${newServers.length} new MCP server${newServers.length === 1 ? "" : "s"} (${newServers.join(", ")})`);
|
|
2341
|
+
if (goneServers.length) drift.push(`${goneServers.length} removed MCP server${goneServers.length === 1 ? "" : "s"} (${goneServers.join(", ")})`);
|
|
2342
|
+
if (drift.length) {
|
|
2343
|
+
console.log(` skipped (set at launch): ${drift.join("; ")}`);
|
|
2344
|
+
console.log(" \u2192 a running agent can't change these; run `sky clone --new` to launch a fresh agent.");
|
|
2345
|
+
}
|
|
2346
|
+
if (local.droppedServers.length) {
|
|
2347
|
+
console.log(
|
|
2348
|
+
` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
2349
|
+
);
|
|
2349
2350
|
}
|
|
2351
|
+
writeLink(local.configRoot, { ...link2, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
2352
|
+
console.log(`Synced ${link2.agentId}.`);
|
|
2353
|
+
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
2354
|
+
if (opts.open) openBrowser(`${apiBase()}/agents/${link2.agentId}`);
|
|
2355
|
+
}
|
|
2356
|
+
async function clone(opts) {
|
|
2357
|
+
const harness2 = detectHarness(opts.harness);
|
|
2358
|
+
const local = readLocal(harness2);
|
|
2359
|
+
const link2 = opts.new ? void 0 : readLink(local.configRoot);
|
|
2360
|
+
if (link2) await syncClone(local, link2, opts);
|
|
2361
|
+
else await createClone(local, opts);
|
|
2350
2362
|
}
|
|
2351
2363
|
|
|
2352
2364
|
// src/channels.ts
|
|
@@ -5092,41 +5104,43 @@ async function listBrowserProfiles() {
|
|
|
5092
5104
|
const { data } = await api("GET", "/browser_profiles");
|
|
5093
5105
|
return data;
|
|
5094
5106
|
}
|
|
5095
|
-
async function getBrowserProfile(scope) {
|
|
5096
|
-
const profiles = await listBrowserProfiles();
|
|
5097
|
-
const profile = profiles.find((entry2) => entry2.scope === scope);
|
|
5098
|
-
if (!profile) throw new ApiError(404, "not_found", "browser profile not found");
|
|
5099
|
-
return profile;
|
|
5100
|
-
}
|
|
5101
5107
|
async function importBrowserProfileState(scope, state) {
|
|
5102
5108
|
return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/imports`, state);
|
|
5103
5109
|
}
|
|
5104
|
-
async function listBrowserProfileSites(
|
|
5105
|
-
const
|
|
5106
|
-
return
|
|
5110
|
+
async function listBrowserProfileSites() {
|
|
5111
|
+
const profiles = await listBrowserProfiles();
|
|
5112
|
+
return (profiles.find((entry2) => entry2.scope === "mine") ?? profiles.find((entry2) => entry2.scope === "workspace"))?.sites ?? [];
|
|
5107
5113
|
}
|
|
5108
|
-
async function
|
|
5109
|
-
const sites = await listBrowserProfileSites(
|
|
5114
|
+
async function removeBrowserProfileSite(site) {
|
|
5115
|
+
const sites = await listBrowserProfileSites();
|
|
5110
5116
|
const row = sites.find((entry2) => entry2.site === site) ?? sites.find((entry2) => entry2.site_id === site);
|
|
5111
|
-
if (!row) throw new ApiError(404, "not_found", "site not found in
|
|
5112
|
-
|
|
5117
|
+
if (!row) throw new ApiError(404, "not_found", "site not found in your browser");
|
|
5118
|
+
await api("DELETE", `/browser_profiles/mine/sites/${row.site_id ? `${encodeURIComponent(row.site_id)}?by=id` : encodeURIComponent(row.site)}`);
|
|
5113
5119
|
}
|
|
5114
|
-
async function
|
|
5115
|
-
await api("
|
|
5120
|
+
async function shareBrowserProfile(member, sites) {
|
|
5121
|
+
const { share } = await api("POST", "/browser_profiles/mine/shares", {
|
|
5122
|
+
grantee: member ? { user_id: member } : "workspace",
|
|
5123
|
+
sites: sites.length ? sites : null
|
|
5124
|
+
});
|
|
5125
|
+
return share;
|
|
5116
5126
|
}
|
|
5117
|
-
async function
|
|
5118
|
-
|
|
5119
|
-
const [identifier, query] = reference.split("?");
|
|
5120
|
-
return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/sites/${identifier}/move${query ? `?${query}` : ""}`, { to });
|
|
5127
|
+
async function unshareBrowserProfile(shareId) {
|
|
5128
|
+
await api("DELETE", `/browser_profiles/mine/shares/${encodeURIComponent(shareId)}`);
|
|
5121
5129
|
}
|
|
5122
5130
|
function renderBrowserProfileSite(site) {
|
|
5123
5131
|
const who = site.signed_in_by?.email ?? site.signed_in_by?.name ?? "unknown";
|
|
5124
5132
|
const used = site.last_used_at ? `used ${site.last_used_at.slice(0, 10)}` : "never used";
|
|
5125
5133
|
return `${site.site.padEnd(40)} ${site.status.padEnd(14)} ${who} ${used}${site.status === "removing" && site.site_id ? ` retry with site ID ${site.site_id}` : ""}`;
|
|
5126
5134
|
}
|
|
5135
|
+
function renderBrowserProfileShare(share) {
|
|
5136
|
+
const who = share.grantee.type === "workspace" ? "everyone in this workspace" : share.grantee.user.email ?? share.grantee.user.name ?? share.grantee.user.id;
|
|
5137
|
+
return `shared with ${who}: ${share.sites ? share.sites.join(", ") : "every site"} (share ${share.id})`;
|
|
5138
|
+
}
|
|
5127
5139
|
function renderBrowserProfile(profile) {
|
|
5128
|
-
const
|
|
5140
|
+
const title = profile.scope === "mine" ? "your browser" : profile.scope === "shared" ? `shared with you by ${profile.owner?.email ?? profile.owner?.name ?? profile.owner?.id ?? "someone"}` : "shared with this workspace";
|
|
5141
|
+
const lines = [`${title} revision: ${profile.revision} sites: ${profile.sites.length}`];
|
|
5129
5142
|
for (const site of profile.sites) lines.push(` ${renderBrowserProfileSite(site)}`);
|
|
5143
|
+
for (const share of profile.shares ?? []) lines.push(` ${renderBrowserProfileShare(share)}`);
|
|
5130
5144
|
return lines.join("\n");
|
|
5131
5145
|
}
|
|
5132
5146
|
|
|
@@ -5282,12 +5296,80 @@ function renderLibraryArtifact(artifact2) {
|
|
|
5282
5296
|
return `${artifact2.id} ${artifact2.name} ${version}${artifact2.shared ? " shared" : ""}`;
|
|
5283
5297
|
}
|
|
5284
5298
|
|
|
5299
|
+
// src/design-systems.ts
|
|
5300
|
+
import { readdirSync as readdirSync8, readFileSync as readFileSync16, statSync as statSync9 } from "fs";
|
|
5301
|
+
import { join as join18, relative as relative5, sep as sep3 } from "path";
|
|
5302
|
+
var TEXT2 = /\.(mdx?|markdown|txt|json|m?js|cjs|jsx|tsx?|css|html?|svg|xml|csv|ya?ml|toml)$/i;
|
|
5303
|
+
var MEDIA = {
|
|
5304
|
+
woff2: "font/woff2",
|
|
5305
|
+
woff: "font/woff",
|
|
5306
|
+
ttf: "font/ttf",
|
|
5307
|
+
otf: "font/otf",
|
|
5308
|
+
png: "image/png",
|
|
5309
|
+
jpg: "image/jpeg",
|
|
5310
|
+
jpeg: "image/jpeg",
|
|
5311
|
+
gif: "image/gif",
|
|
5312
|
+
webp: "image/webp",
|
|
5313
|
+
pdf: "application/pdf",
|
|
5314
|
+
mp4: "video/mp4",
|
|
5315
|
+
webm: "video/webm"
|
|
5316
|
+
};
|
|
5317
|
+
async function listDesignSystems() {
|
|
5318
|
+
return (await api("GET", "/artifacts?kind=design_system")).artifacts;
|
|
5319
|
+
}
|
|
5320
|
+
async function getDesignSystem(id) {
|
|
5321
|
+
return (await api("GET", `/artifacts/${encodeURIComponent(id)}/design-system`)).design_system;
|
|
5322
|
+
}
|
|
5323
|
+
async function importDesignSystem(task2, files2, title) {
|
|
5324
|
+
return (await api("POST", "/artifacts/design-systems", { task_id: task2, files: files2, ...title ? { title } : {} })).artifact;
|
|
5325
|
+
}
|
|
5326
|
+
async function saveDesignTokens(id, baseVersion, tokens, note) {
|
|
5327
|
+
return (await api("PUT", `/artifacts/${encodeURIComponent(id)}/design-system/tokens`, {
|
|
5328
|
+
base_version: baseVersion,
|
|
5329
|
+
tokens,
|
|
5330
|
+
...note ? { note } : {}
|
|
5331
|
+
})).artifact;
|
|
5332
|
+
}
|
|
5333
|
+
function fileTableFrom(dir) {
|
|
5334
|
+
const files2 = {};
|
|
5335
|
+
const walk = (current) => {
|
|
5336
|
+
for (const name of readdirSync8(current)) {
|
|
5337
|
+
if (name.startsWith(".") || name === "node_modules") continue;
|
|
5338
|
+
const path = join18(current, name);
|
|
5339
|
+
if (statSync9(path).isDirectory()) {
|
|
5340
|
+
walk(path);
|
|
5341
|
+
continue;
|
|
5342
|
+
}
|
|
5343
|
+
const key = relative5(dir, path).split(sep3).join("/");
|
|
5344
|
+
const bytes = readFileSync16(path);
|
|
5345
|
+
files2[key] = TEXT2.test(name) ? bytes.toString("utf8") : `data:${MEDIA[name.split(".").pop()?.toLowerCase() ?? ""] ?? "application/octet-stream"};base64,${bytes.toString("base64")}`;
|
|
5346
|
+
}
|
|
5347
|
+
};
|
|
5348
|
+
walk(dir);
|
|
5349
|
+
return files2;
|
|
5350
|
+
}
|
|
5351
|
+
function renderDesignSystem(system) {
|
|
5352
|
+
const tokens = system.tokens;
|
|
5353
|
+
const lines = [
|
|
5354
|
+
`${system.artifact_id} ${system.title} v${system.version}${system.editable ? "" : " read-only"}`,
|
|
5355
|
+
tokens ? `themes ${tokens.themes.map((theme) => theme.id).join(", ")} \xB7 ${tokens.colors.length} colors \xB7 ${tokens.typeGroups.reduce((sum, group) => sum + group.styles.length, 0)} type styles \xB7 ${tokens.spacing.length} spacing \xB7 ${tokens.radius.length} radius` : "no tokens.json",
|
|
5356
|
+
`components: ${system.components.map((component) => component.name).join(", ") || "none"}`,
|
|
5357
|
+
`assets: ${system.assets.map((group) => `${group.name} (${group.files.length})`).join(", ") || "none"}`
|
|
5358
|
+
];
|
|
5359
|
+
if (system.last_change) lines.push(`last change: ${system.last_change.by}${system.last_change.via ? ` via ${system.last_change.via}` : ""} at ${system.last_change.at}`);
|
|
5360
|
+
for (const token of tokens?.dropped ?? []) lines.push(`not read: ${token.family} ${token.name || "(unnamed)"}: ${token.reason}`);
|
|
5361
|
+
return lines.join("\n");
|
|
5362
|
+
}
|
|
5363
|
+
async function designComments(id, input) {
|
|
5364
|
+
return api(input ? "POST" : "GET", `/artifacts/${encodeURIComponent(id)}/design-system/comments`, input);
|
|
5365
|
+
}
|
|
5366
|
+
|
|
5285
5367
|
// src/api-command.ts
|
|
5286
|
-
import { readFileSync as
|
|
5368
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
|
|
5287
5369
|
var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
5288
|
-
function readData(data, stdin = () =>
|
|
5370
|
+
function readData(data, stdin = () => readFileSync17(0, "utf8")) {
|
|
5289
5371
|
if (data === void 0) return void 0;
|
|
5290
|
-
const raw = data === "-" ? stdin() : data.startsWith("@") ?
|
|
5372
|
+
const raw = data === "-" ? stdin() : data.startsWith("@") ? readFileSync17(data.slice(1), "utf8") : data;
|
|
5291
5373
|
try {
|
|
5292
5374
|
JSON.parse(raw);
|
|
5293
5375
|
} catch {
|
|
@@ -5304,9 +5386,9 @@ function requestPath(path, query = []) {
|
|
|
5304
5386
|
if (at <= 0) throw new Error(`--query needs key=value, got "${pair}"`);
|
|
5305
5387
|
params.append(pair.slice(0, at), pair.slice(at + 1));
|
|
5306
5388
|
}
|
|
5307
|
-
const
|
|
5389
|
+
const sep4 = clean.includes("?") ? "&" : "?";
|
|
5308
5390
|
const encoded = params.toString();
|
|
5309
|
-
return encoded ? `${clean}${
|
|
5391
|
+
return encoded ? `${clean}${sep4}${encoded}` : clean;
|
|
5310
5392
|
}
|
|
5311
5393
|
function matchOperations(doc, words) {
|
|
5312
5394
|
const needles = words.map((w) => w.toLowerCase());
|
|
@@ -5442,11 +5524,11 @@ function workspaceFlagIsCommands(argv, commandPath) {
|
|
|
5442
5524
|
}
|
|
5443
5525
|
|
|
5444
5526
|
// src/harness.ts
|
|
5445
|
-
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as
|
|
5446
|
-
import { dirname as dirname9, join as
|
|
5527
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync18, writeFileSync as writeFileSync10 } from "fs";
|
|
5528
|
+
import { dirname as dirname9, join as join19, resolve as resolve8 } from "path";
|
|
5447
5529
|
var HARNESS_FILE = "harness.json";
|
|
5448
5530
|
function harnessFilePath(dir) {
|
|
5449
|
-
return
|
|
5531
|
+
return join19(resolve8(dir), HARNESS_FILE);
|
|
5450
5532
|
}
|
|
5451
5533
|
function readHarnessFile(dir) {
|
|
5452
5534
|
const path = harnessFilePath(dir);
|
|
@@ -5454,7 +5536,7 @@ function readHarnessFile(dir) {
|
|
|
5454
5536
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
5455
5537
|
}
|
|
5456
5538
|
try {
|
|
5457
|
-
return JSON.parse(
|
|
5539
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
5458
5540
|
} catch (err) {
|
|
5459
5541
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5460
5542
|
}
|
|
@@ -6753,7 +6835,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
6753
6835
|
);
|
|
6754
6836
|
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(
|
|
6755
6837
|
run(async (taskId, path, local) => {
|
|
6756
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
6838
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync19(local), mediaTypeFor(local));
|
|
6757
6839
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
6758
6840
|
})
|
|
6759
6841
|
);
|
|
@@ -6917,7 +6999,7 @@ library.command("list").description("your Library, newest first").option("--sear
|
|
|
6917
6999
|
);
|
|
6918
7000
|
library.command("upload <path>").description("save a file from this computer to your Library").option("--workspace <key>", "place it in this workspace").action(
|
|
6919
7001
|
run(async (path, opts) => {
|
|
6920
|
-
const bytes =
|
|
7002
|
+
const bytes = readFileSync19(path);
|
|
6921
7003
|
const { artifact: artifact2 } = await uploadToLibrary({ bytes, name: basename9(path), mediaType: "application/octet-stream" }, `sky-upload-${crypto.randomUUID()}`, opts.workspace);
|
|
6922
7004
|
console.log(renderLibraryArtifact(artifact2));
|
|
6923
7005
|
})
|
|
@@ -6986,6 +7068,35 @@ artifact.command("rm <id>").description("delete an artifact").action(
|
|
|
6986
7068
|
console.log(`Deleted ${id}`);
|
|
6987
7069
|
})
|
|
6988
7070
|
);
|
|
7071
|
+
var designSystem = program.command("design-system").description("design systems: brand books, tokens and components agents design with");
|
|
7072
|
+
designSystem.command("list").description("the workspace's design systems").action(
|
|
7073
|
+
run(async () => {
|
|
7074
|
+
const list = await listDesignSystems();
|
|
7075
|
+
if (!list.length) console.log("no design systems");
|
|
7076
|
+
for (const a of list) console.log(renderArtifact(a));
|
|
7077
|
+
})
|
|
7078
|
+
);
|
|
7079
|
+
designSystem.command("show <id>").description("a design system's tokens, components and assets; --readme prints its brand book instead").option("--readme", "print project/README.md").action(
|
|
7080
|
+
run(async (id, opts) => {
|
|
7081
|
+
const system = await getDesignSystem(id);
|
|
7082
|
+
console.log(opts.readme ? system.readme ?? "this design system has no README.md" : renderDesignSystem(system));
|
|
7083
|
+
})
|
|
7084
|
+
);
|
|
7085
|
+
designSystem.command("import <task> <folder>").description("import a local design-system folder (one holding project/, or project/ itself) into a task and publish it").option("--title <title>", "the system's name; defaults to the title in design-system.json").action(
|
|
7086
|
+
run(async (task2, folder, opts) => {
|
|
7087
|
+
console.log(renderArtifact(await importDesignSystem(task2, fileTableFrom(resolve9(folder)), opts.title)));
|
|
7088
|
+
})
|
|
7089
|
+
);
|
|
7090
|
+
designSystem.command("tokens <id> <file>").description("save a tokens.json file as the system's next version; --base is the version you edited").requiredOption("--base <version>", "the version the file was edited from", (value) => Number(value)).option("--note <note>", "one line saying what changed").action(
|
|
7091
|
+
run(async (id, file, opts) => {
|
|
7092
|
+
const tokens = JSON.parse(readFileSync19(resolve9(file), "utf8"));
|
|
7093
|
+
console.log(renderArtifact(await saveDesignTokens(id, opts.base, tokens, opts.note)));
|
|
7094
|
+
})
|
|
7095
|
+
);
|
|
7096
|
+
designSystem.command("comments <id>").description("read design-system comments, or add one with --text and --base").option("--text <text>", "comment to add").option("--target <target>", "overview, color:<name> or component:<name>", "overview").option("--base <version>", "the version you viewed", Number).action(run(async (id, opts) => {
|
|
7097
|
+
if (opts.text !== void 0 && (!Number.isInteger(opts.base) || opts.base < 1)) throw Error("Adding a comment requires --base <version>.");
|
|
7098
|
+
console.log(JSON.stringify(await designComments(id, opts.text === void 0 ? void 0 : { text: opts.text, target: opts.target, base_version: opts.base }), null, 2));
|
|
7099
|
+
}));
|
|
6989
7100
|
function routineLine(r) {
|
|
6990
7101
|
const target = r.target.type === "session" ? `\u2192 ${r.target.session_id}` : `+ ${r.target.agent}`;
|
|
6991
7102
|
const cron = r.schedule ? `${r.schedule.expression} ${r.schedule.timezone}` : "manual-only";
|
|
@@ -7369,10 +7480,10 @@ memory.command("save <text...>").description("add an entry").action(run(async (t
|
|
|
7369
7480
|
memory.command("edit <index> <text...>").description("replace entry <index>").action(run(async (index, text) => writeMemory({ op: "update", index: memoryIndex(index), text: text.join(" ") })));
|
|
7370
7481
|
memory.command("rm <index>").description("remove entry <index>").action(run(async (index) => writeMemory({ op: "remove", index: memoryIndex(index) })));
|
|
7371
7482
|
memory.command("clear").description("remove every entry").action(run(async () => writeMemory({ op: "clear" })));
|
|
7372
|
-
var context = program.command("context").description("search
|
|
7373
|
-
context.command("search <words...>").description("
|
|
7483
|
+
var context = program.command("context").description("search everything you can open in the workspace, and read a hit");
|
|
7484
|
+
context.command("search <words...>").description('plain words, "exact phrase" or /regex/ (every matching line, like grep); follow --cursor for more').option("--scope <folder>", "memory/, chats, traces, outputs/, files/, meetings/, skills/<name>/, agents/ or docs/ (default everything)").option("--limit <n>", "hits per page (default 20)").option("--cursor <cursor>", "continue a previous search").option("--since <iso>", "inclusive lower time bound").option("--until <iso>", "exclusive upper time bound").option("--json", "print the API's answer as JSON").action(
|
|
7374
7485
|
run(async (words, opts) => {
|
|
7375
|
-
const page = await
|
|
7486
|
+
const page = await searchContext(words.join(" "), opts);
|
|
7376
7487
|
console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
|
|
7377
7488
|
})
|
|
7378
7489
|
);
|
|
@@ -7380,7 +7491,7 @@ context.command("read <ref>").description("read a search hit in full: <ref> is t
|
|
|
7380
7491
|
run(async (ref, opts) => {
|
|
7381
7492
|
const params = new URLSearchParams({ ref });
|
|
7382
7493
|
if (opts.offset) params.set("offset", opts.offset);
|
|
7383
|
-
const read = await
|
|
7494
|
+
const read = await contextRequest(`/context/read?${params}`);
|
|
7384
7495
|
console.log(read.text);
|
|
7385
7496
|
if (read.nextOffset !== null) console.error(`more: --offset ${read.nextOffset}`);
|
|
7386
7497
|
})
|
|
@@ -7690,7 +7801,7 @@ mac.command("take-back <mount>").description("end a folder, browser window, scre
|
|
|
7690
7801
|
})
|
|
7691
7802
|
);
|
|
7692
7803
|
var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
|
|
7693
|
-
browserProfile.command("list").description("your
|
|
7804
|
+
browserProfile.command("list").description("your browser, with whom you shared it, and the browsers shared with you in this workspace").option("--json", "print as JSON").action(
|
|
7694
7805
|
run(async (opts) => {
|
|
7695
7806
|
const profiles = await listBrowserProfiles();
|
|
7696
7807
|
if (opts.json) {
|
|
@@ -7701,9 +7812,9 @@ browserProfile.command("list").description("your workspace's browser profiles: t
|
|
|
7701
7812
|
for (const p of profiles) console.log(renderBrowserProfile(p));
|
|
7702
7813
|
})
|
|
7703
7814
|
);
|
|
7704
|
-
browserProfile.command("sites
|
|
7705
|
-
run(async (
|
|
7706
|
-
const sites = await listBrowserProfileSites(
|
|
7815
|
+
browserProfile.command("sites").description("list the sites your browser is signed in to").option("--json", "print as JSON").action(
|
|
7816
|
+
run(async (opts) => {
|
|
7817
|
+
const sites = await listBrowserProfileSites();
|
|
7707
7818
|
if (opts.json) {
|
|
7708
7819
|
console.log(JSON.stringify(sites, null, 2));
|
|
7709
7820
|
return;
|
|
@@ -7713,8 +7824,8 @@ browserProfile.command("sites <scope>").description("list the sites in a profile
|
|
|
7713
7824
|
for (const s of sites) console.log(renderBrowserProfileSite(s));
|
|
7714
7825
|
})
|
|
7715
7826
|
);
|
|
7716
|
-
browserProfile.command("import
|
|
7717
|
-
run(async (
|
|
7827
|
+
browserProfile.command("import").description("import saved browser state into your browser; reads Playwright storage state JSON from stdin or --file").option("--file <path>", "path to a Playwright storage state JSON file (reads stdin if omitted)").action(
|
|
7828
|
+
run(async (opts) => {
|
|
7718
7829
|
let raw;
|
|
7719
7830
|
if (opts.file) {
|
|
7720
7831
|
const { readFileSync: rf } = await import("fs");
|
|
@@ -7727,21 +7838,26 @@ browserProfile.command("import <scope>").description("import saved browser state
|
|
|
7727
7838
|
});
|
|
7728
7839
|
}
|
|
7729
7840
|
const state = JSON.parse(raw);
|
|
7730
|
-
const result = await importBrowserProfileState(
|
|
7841
|
+
const result = await importBrowserProfileState("mine", { state });
|
|
7731
7842
|
console.log(`Imported ${result.sites.length} site${result.sites.length === 1 ? "" : "s"}: ${result.sites.join(", ")}`);
|
|
7732
7843
|
})
|
|
7733
7844
|
);
|
|
7734
|
-
browserProfile.command("remove-site <
|
|
7735
|
-
run(async (
|
|
7736
|
-
await removeBrowserProfileSite(
|
|
7737
|
-
console.log(`
|
|
7845
|
+
browserProfile.command("remove-site <site>").description("sign your browser out of a site everywhere (the agent will need to sign in again)").action(
|
|
7846
|
+
run(async (site) => {
|
|
7847
|
+
await removeBrowserProfileSite(site);
|
|
7848
|
+
console.log(`Signed out of ${site}`);
|
|
7849
|
+
})
|
|
7850
|
+
);
|
|
7851
|
+
browserProfile.command("share").description("share your browser in this workspace: with one member (--member <user id>) or everyone; every site unless --sites").option("--member <userId>", "the member to share with (default: everyone in the workspace)").option("--sites <sites>", "comma-separated sites to share (default: every site)").action(
|
|
7852
|
+
run(async (opts) => {
|
|
7853
|
+
const share = await shareBrowserProfile(opts.member, (opts.sites ?? "").split(",").map((s) => s.trim()).filter(Boolean));
|
|
7854
|
+
console.log(renderBrowserProfileShare(share));
|
|
7738
7855
|
})
|
|
7739
7856
|
);
|
|
7740
|
-
browserProfile.command("
|
|
7741
|
-
run(async (
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
console.log(`Moved ${site} from ${scope} to ${result.to}`);
|
|
7857
|
+
browserProfile.command("unshare <shareId>").description("stop a share of your browser; it ends from the grantee's next turn").action(
|
|
7858
|
+
run(async (shareId) => {
|
|
7859
|
+
await unshareBrowserProfile(shareId);
|
|
7860
|
+
console.log(`Stopped share ${shareId}`);
|
|
7745
7861
|
})
|
|
7746
7862
|
);
|
|
7747
7863
|
var notifications = program.command("notifications").description("email and browser push notification preferences for tasks");
|
|
@@ -7801,21 +7917,21 @@ async function helpFetch(path) {
|
|
|
7801
7917
|
return await res.json();
|
|
7802
7918
|
}
|
|
7803
7919
|
var help = program.command("help").description("search the AgentSky help center");
|
|
7804
|
-
help.command("search <query...>").description(
|
|
7920
|
+
help.command("search <query...>").description('search help articles: plain words rank them, "exact" or /regex/ list every matching line').option("--top <n>", "how many results (1\u201320, default 5)", "5").option("--json", "print as JSON").action(
|
|
7805
7921
|
run(async (words, opts) => {
|
|
7806
7922
|
const q = words.join(" ");
|
|
7807
7923
|
const topK = Math.min(Math.max(parseInt(opts.top, 10) || 5, 1), 20);
|
|
7808
|
-
const {
|
|
7924
|
+
const { hits } = await helpFetch(`/api/context/search?${new URLSearchParams({ q, scope: "docs/", limit: String(topK) })}`);
|
|
7809
7925
|
if (opts.json) {
|
|
7810
|
-
console.log(JSON.stringify(
|
|
7926
|
+
console.log(JSON.stringify(hits, null, 2));
|
|
7811
7927
|
return;
|
|
7812
7928
|
}
|
|
7813
|
-
if (!
|
|
7929
|
+
if (!hits.length) {
|
|
7814
7930
|
console.log("no results");
|
|
7815
7931
|
return;
|
|
7816
7932
|
}
|
|
7817
|
-
for (const
|
|
7818
|
-
${
|
|
7933
|
+
for (const hit of hits) console.log(`${String(hit.ref.slug).padEnd(40)} ${hit.title}
|
|
7934
|
+
${hit.snippet.slice(0, 100)}`);
|
|
7819
7935
|
})
|
|
7820
7936
|
);
|
|
7821
7937
|
help.command("get <slug>").description("read a help article by its slug").option("--json", "print as JSON").action(
|
|
@@ -7898,7 +8014,7 @@ notes.command("read <storeId> <noteId>").description("print one note").action(
|
|
|
7898
8014
|
notes.command("save <storeId> <file>").description("write a note from a local file: --path creates one, --note saves a new version of one").option("--path <path>", "path of a new note, e.g. /projects/launch.md").option("--note <noteId>", "the note to update").action(
|
|
7899
8015
|
run(async (storeId, file, opts) => {
|
|
7900
8016
|
if (!opts.path === !opts.note) throw new Error("pass exactly one of --path (new note) or --note (update)");
|
|
7901
|
-
const content =
|
|
8017
|
+
const content = readFileSync19(file, "utf8");
|
|
7902
8018
|
const target = `/memory_stores/${encodeURIComponent(storeId)}/memories${opts.note ? `/${encodeURIComponent(opts.note)}` : ""}`;
|
|
7903
8019
|
const { memory: memory2 } = await api("POST", target, { content, ...opts.path ? { path: opts.path } : {} });
|
|
7904
8020
|
console.log(`Saved ${memory2.path} (${memory2.id})`);
|
|
@@ -7998,7 +8114,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
7998
8114
|
}));
|
|
7999
8115
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
8000
8116
|
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) => {
|
|
8001
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
8117
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-WLTD4C22.js");
|
|
8002
8118
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
8003
8119
|
const scopes = parseContentScopes(opts.capture);
|
|
8004
8120
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -8033,7 +8149,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
8033
8149
|
}));
|
|
8034
8150
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
8035
8151
|
try {
|
|
8036
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
8152
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-WLTD4C22.js");
|
|
8037
8153
|
const chunks = [];
|
|
8038
8154
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
8039
8155
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -8046,14 +8162,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
8046
8162
|
}
|
|
8047
8163
|
});
|
|
8048
8164
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
8049
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
8165
|
+
const { readState, flush, grantSender } = await import("./trace-WLTD4C22.js");
|
|
8050
8166
|
const state = readState();
|
|
8051
8167
|
if (!state) throw new Error("no trace is running here");
|
|
8052
8168
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
8053
8169
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
8054
8170
|
}));
|
|
8055
8171
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
8056
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
8172
|
+
const { readState, spoolStatus } = await import("./trace-WLTD4C22.js");
|
|
8057
8173
|
const state = readState();
|
|
8058
8174
|
if (!state) {
|
|
8059
8175
|
console.log("No trace is running here.");
|
|
@@ -8063,7 +8179,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
8063
8179
|
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`);
|
|
8064
8180
|
}));
|
|
8065
8181
|
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) => {
|
|
8066
|
-
const { readState } = await import("./trace-
|
|
8182
|
+
const { readState } = await import("./trace-WLTD4C22.js");
|
|
8067
8183
|
const ref = id ?? readState()?.trace_id;
|
|
8068
8184
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
8069
8185
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -8090,7 +8206,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
8090
8206
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
8091
8207
|
}));
|
|
8092
8208
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
8093
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
8209
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-WLTD4C22.js");
|
|
8094
8210
|
const state = readState();
|
|
8095
8211
|
if (!state) throw new Error("no trace is running here");
|
|
8096
8212
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -8100,7 +8216,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
8100
8216
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
8101
8217
|
}));
|
|
8102
8218
|
traceCmd.command("sync [payload]").description("upload this machine's agent sessions (Claude Code, Codex, Cursor, Gemini CLI and every other agent `--agents` lists) 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("--import <export>", "add a downloaded cloud export (claude.ai, ChatGPT, Gemini, Grok, DeepSeek \u2026: its .zip, folder or file) so sync uploads it (repeatable)", collect).option("--agents", "list the agents sync reads and which are on this machine").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) => {
|
|
8103
|
-
const lib = await import("./trace-library-
|
|
8219
|
+
const lib = await import("./trace-library-YMAVLH4Y.js");
|
|
8104
8220
|
if (opts.hook) {
|
|
8105
8221
|
try {
|
|
8106
8222
|
let raw = payload ?? "";
|
|
@@ -8159,10 +8275,10 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
8159
8275
|
console.log(`Added ${imported.sessions} ${imported.agent} session${imported.sessions === 1 ? "" : "s"} from ${input}.`);
|
|
8160
8276
|
}
|
|
8161
8277
|
if (opts.installSchedule) {
|
|
8162
|
-
const { join:
|
|
8278
|
+
const { join: join20 } = await import("path");
|
|
8163
8279
|
const { homedir: homedir10 } = await import("os");
|
|
8164
8280
|
const { execFileSync } = await import("child_process");
|
|
8165
|
-
const log =
|
|
8281
|
+
const log = join20(homedir10(), ".config", "sky", "trace-sync.log");
|
|
8166
8282
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
8167
8283
|
try {
|
|
8168
8284
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -8172,11 +8288,11 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
8172
8288
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
8173
8289
|
}
|
|
8174
8290
|
if (opts.status) {
|
|
8175
|
-
const { readFileSync:
|
|
8176
|
-
const { join:
|
|
8291
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
8292
|
+
const { join: join20 } = await import("path");
|
|
8177
8293
|
let state = { sessions: {} };
|
|
8178
8294
|
try {
|
|
8179
|
-
state = JSON.parse(
|
|
8295
|
+
state = JSON.parse(readFileSync20(join20(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
8180
8296
|
} catch {
|
|
8181
8297
|
}
|
|
8182
8298
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -8208,25 +8324,14 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
8208
8324
|
if (report.failed) process.exitCode = 1;
|
|
8209
8325
|
})();
|
|
8210
8326
|
});
|
|
8211
|
-
traceCmd.command("search <query...>").description("search every session in a workspace's trace library
|
|
8212
|
-
const { libraryRequest } = await import("./trace-library-2VHTJCOY.js");
|
|
8327
|
+
traceCmd.command("search <query...>").description("search every session in a workspace's trace library: the one search with --scope traces").option("--workspace <slug>", "the workspace to search (default: the current one)").option("--limit <n>", "hits per page", "20").option("--cursor <cursor>", "the next page of an earlier search").option("--json", "print the page as JSON").action(run(async (words, opts) => {
|
|
8213
8328
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
8214
8329
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
8215
|
-
const
|
|
8216
|
-
|
|
8217
|
-
if (opts.json) {
|
|
8218
|
-
console.log(JSON.stringify(page, null, 2));
|
|
8219
|
-
return;
|
|
8220
|
-
}
|
|
8221
|
-
if (!page.hits.length) console.log(page.partial ? `No hits, but the search was cut short (${page.coverage}).` : "No hits.");
|
|
8222
|
-
for (const { hit } of page.hits) {
|
|
8223
|
-
console.log(`${hit.occurredAt?.slice(0, 16).replace("T", " ") ?? " "} ${hit.title}`);
|
|
8224
|
-
console.log(` ${hit.snippet.replace(/\s+/g, " ")}`);
|
|
8225
|
-
}
|
|
8226
|
-
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
8330
|
+
const page = await searchContext(words.join(" "), { ...opts, scope: "traces" }, workspace2);
|
|
8331
|
+
console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
|
|
8227
8332
|
}));
|
|
8228
8333
|
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) => {
|
|
8229
|
-
const { libraryRequest } = await import("./trace-library-
|
|
8334
|
+
const { libraryRequest } = await import("./trace-library-YMAVLH4Y.js");
|
|
8230
8335
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
8231
8336
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
8232
8337
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|