@agentsky/sky 2.23.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-HDPDY77W.js → chunk-D3WG4BMK.js} +57 -31
- package/dist/{chunk-HQPOCQRQ.js → chunk-KWLEADJQ.js} +19 -4
- package/dist/{chunk-V5XLV37X.js → chunk-S4YG3TXA.js} +1 -1
- package/dist/index.js +1496 -1073
- package/dist/{src-UMT6FHLG.js → src-BMXKAMOB.js} +1 -1
- package/dist/{trace-ZRU624EY.js → trace-WLTD4C22.js} +2 -2
- package/dist/{trace-library-QOQUAL5H.js → trace-library-YMAVLH4Y.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,12 +4,13 @@ import {
|
|
|
4
4
|
api,
|
|
5
5
|
apiBytes,
|
|
6
6
|
apiForm,
|
|
7
|
+
apiRaw,
|
|
7
8
|
downloadFileTo,
|
|
8
9
|
postEvents,
|
|
9
10
|
resolveSessionRef,
|
|
10
11
|
streamEvents,
|
|
11
12
|
uploadFile
|
|
12
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-KWLEADJQ.js";
|
|
13
14
|
import {
|
|
14
15
|
CLI_VERSION,
|
|
15
16
|
USER_AGENT,
|
|
@@ -20,7 +21,7 @@ import {
|
|
|
20
21
|
currentUniverse,
|
|
21
22
|
loadConfig,
|
|
22
23
|
saveConfig
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-S4YG3TXA.js";
|
|
24
25
|
import {
|
|
25
26
|
AGENT_TYPES,
|
|
26
27
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -42,17 +43,17 @@ import {
|
|
|
42
43
|
skillRefSchema,
|
|
43
44
|
splitMcpServers,
|
|
44
45
|
supersededBy
|
|
45
|
-
} from "./chunk-
|
|
46
|
+
} from "./chunk-D3WG4BMK.js";
|
|
46
47
|
import "./chunk-MLKGABMK.js";
|
|
47
48
|
|
|
48
49
|
// src/index.ts
|
|
49
|
-
import { existsSync as
|
|
50
|
+
import { existsSync as existsSync13, readFileSync as readFileSync19, writeFileSync as writeFileSync11 } from "fs";
|
|
50
51
|
import { basename as basename9, resolve as resolve9 } from "path";
|
|
51
52
|
import { createInterface as createInterface6 } from "readline";
|
|
52
53
|
import { Command } from "commander";
|
|
53
54
|
|
|
54
55
|
// src/files.ts
|
|
55
|
-
import { readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
56
|
+
import { existsSync, readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
56
57
|
import { basename as basename2, extname, join as join2 } from "path";
|
|
57
58
|
|
|
58
59
|
// src/resumable-upload.ts
|
|
@@ -250,6 +251,16 @@ function eventsFor(text, files2, system, interactionMode2) {
|
|
|
250
251
|
if (system?.trim()) events.push({ type: "system.message", content: [{ type: "text", text: system }] });
|
|
251
252
|
return events;
|
|
252
253
|
}
|
|
254
|
+
function isFileId(ref) {
|
|
255
|
+
return /^file_[A-Za-z0-9]+$/.test(ref);
|
|
256
|
+
}
|
|
257
|
+
async function attachment(ref, opts = {}) {
|
|
258
|
+
if (opts.byId || isFileId(ref) && !existsSync(ref)) {
|
|
259
|
+
if (!isFileId(ref)) throw new Error(`--file-id takes a stored file's id (file_\u2026), got ${JSON.stringify(ref)}`);
|
|
260
|
+
return { file: await api("GET", `/files/${encodeURIComponent(ref)}`), uploaded: false };
|
|
261
|
+
}
|
|
262
|
+
return { file: await uploadLocalFile(ref), uploaded: true };
|
|
263
|
+
}
|
|
253
264
|
|
|
254
265
|
// src/goal.ts
|
|
255
266
|
var CHECK_SEPARATOR = " :: ";
|
|
@@ -456,837 +467,266 @@ async function unsubscribeMarketing() {
|
|
|
456
467
|
return { marketing: notification_preferences.email.marketing };
|
|
457
468
|
}
|
|
458
469
|
|
|
459
|
-
// src/
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
if (!/^\d+$/.test(value.trim())) throw new Error(`the entry number must be a whole number, got ${JSON.stringify(value)}`);
|
|
465
|
-
return Number(value.trim());
|
|
466
|
-
}
|
|
467
|
-
function renderMemory(document) {
|
|
468
|
-
if (!document.entries.length) return "no saved memory \u2014 `sky memory save <text>` adds an entry";
|
|
469
|
-
return document.entries.map((entry2) => `${String(entry2.index).padStart(3)} ${entry2.text}`).join("\n");
|
|
470
|
-
}
|
|
471
|
-
function searchQuery(query, opts) {
|
|
472
|
-
const params = new URLSearchParams({ q: query });
|
|
473
|
-
params.set("scope", opts.scope ?? "sessions");
|
|
474
|
-
if (opts.limit) params.set("limit", opts.limit);
|
|
475
|
-
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
476
|
-
if (opts.since) params.set("since", opts.since);
|
|
477
|
-
if (opts.until) params.set("until", opts.until);
|
|
478
|
-
return `?${params.toString()}`;
|
|
479
|
-
}
|
|
480
|
-
function refArgument(ref) {
|
|
481
|
-
return `'${JSON.stringify(ref).replace(/'/g, "'\\''")}'`;
|
|
482
|
-
}
|
|
483
|
-
function renderContextPage(page) {
|
|
484
|
-
const lines = [];
|
|
485
|
-
for (const { hit } of page.hits) {
|
|
486
|
-
lines.push(`${hit.source.padEnd(8)} ${hit.occurredAt?.slice(0, 10) ?? " "} ${hit.title}`);
|
|
487
|
-
lines.push(` ${hit.snippet.replace(/\s+/g, " ").slice(0, 160)}`);
|
|
488
|
-
lines.push(` sky context read ${refArgument(hit.ref)}`);
|
|
489
|
-
}
|
|
490
|
-
if (!page.hits.length) lines.push("no matches on this page");
|
|
491
|
-
if (page.partial) lines.push(`partial: ${page.coverage ?? "not every source was searched"}`);
|
|
492
|
-
if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
|
|
493
|
-
return lines.join("\n");
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// src/agent-file.ts
|
|
497
|
-
import { existsSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
498
|
-
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
499
|
-
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
500
|
-
var fileInstructionSchema = external_exports.object({
|
|
501
|
-
name: external_exports.string(),
|
|
502
|
-
content: external_exports.string().optional(),
|
|
503
|
-
file: external_exports.string().optional()
|
|
504
|
-
});
|
|
505
|
-
var agentFileSchema = external_exports.object({
|
|
506
|
-
name: external_exports.string().min(1),
|
|
507
|
-
id: external_exports.string().optional(),
|
|
508
|
-
// stamped by launch/deploy; identifies the live agent
|
|
509
|
-
slug: external_exports.string().optional(),
|
|
510
|
-
// pre-RFC-0079 stamp; ignored (agents are id-addressed)
|
|
511
|
-
description: external_exports.string().optional(),
|
|
512
|
-
type: external_exports.enum(AGENT_TYPES).optional(),
|
|
513
|
-
llm: external_exports.string().optional(),
|
|
514
|
-
reasoning_effort: external_exports.enum(REASONING_EFFORT_VALUES).optional(),
|
|
515
|
-
capabilities: capabilitiesSchema.optional(),
|
|
516
|
-
prompt: external_exports.string().optional(),
|
|
517
|
-
prompt_file: external_exports.string().optional(),
|
|
518
|
-
instructions: external_exports.array(fileInstructionSchema).optional(),
|
|
519
|
-
skills: external_exports.array(skillRefSchema).optional(),
|
|
520
|
-
customData: external_exports.array(customDataSchema).optional(),
|
|
521
|
-
/** "deny" makes the agent work alone: its sessions may not hire helpers. */
|
|
522
|
-
hiring_policy: external_exports.enum(["allow", "deny"]).optional(),
|
|
523
|
-
/** The environment its sessions boot in when they name none. */
|
|
524
|
-
environment: external_exports.string().min(1).optional(),
|
|
525
|
-
/** Advanced machine shape (create-only): 1|2|4|8 vCPUs, 256MB-step memory. */
|
|
526
|
-
vcpus: external_exports.number().int().optional(),
|
|
527
|
-
memory_mb: external_exports.number().int().optional()
|
|
528
|
-
});
|
|
529
|
-
function readAgentFile(path) {
|
|
530
|
-
const raw = readFileSync3(path, "utf8");
|
|
531
|
-
const parsed = agentFileSchema.safeParse(parseToml(raw));
|
|
532
|
-
if (!parsed.success) {
|
|
533
|
-
const issue = parsed.error.issues[0];
|
|
534
|
-
throw new Error(`${path}: ${issue?.path.join(".") ?? ""} ${issue?.message ?? "invalid"}`);
|
|
535
|
-
}
|
|
536
|
-
const file = parsed.data;
|
|
537
|
-
if (file.reasoning_effort && file.type && !isModelEffortSupported(file.type, file.llm ?? defaultLlm(file.type), file.reasoning_effort)) {
|
|
538
|
-
throw new Error(
|
|
539
|
-
`${path}: reasoning_effort '${file.reasoning_effort}' is not available for ${file.type} \u2014 ${reasoningEffortDocLine()}`
|
|
540
|
-
);
|
|
541
|
-
}
|
|
542
|
-
const base = dirname2(resolve2(path));
|
|
543
|
-
const inline = (ref, inlineContent) => {
|
|
544
|
-
if (inlineContent !== void 0) return inlineContent;
|
|
545
|
-
if (!ref) return void 0;
|
|
546
|
-
return readFileSync3(resolve2(base, ref), "utf8");
|
|
547
|
-
};
|
|
548
|
-
const spec = {
|
|
549
|
-
name: file.name,
|
|
550
|
-
description: file.description,
|
|
551
|
-
agentType: file.type,
|
|
552
|
-
llm: file.llm,
|
|
553
|
-
reasoningEffort: file.reasoning_effort,
|
|
554
|
-
displayName: file.name,
|
|
555
|
-
capabilities: file.capabilities,
|
|
556
|
-
prompt: inline(file.prompt_file, file.prompt),
|
|
557
|
-
instructions: file.instructions?.map((f) => {
|
|
558
|
-
const content = inline(f.file, f.content);
|
|
559
|
-
if (content === void 0) throw new Error(`instruction ${f.name}: content or file required`);
|
|
560
|
-
return { name: f.name, content };
|
|
561
|
-
}),
|
|
562
|
-
skills: file.skills,
|
|
563
|
-
customData: file.customData,
|
|
564
|
-
hiringPolicy: file.hiring_policy,
|
|
565
|
-
environmentId: file.environment,
|
|
566
|
-
vcpus: file.vcpus,
|
|
567
|
-
memoryMb: file.memory_mb
|
|
568
|
-
};
|
|
569
|
-
return { file, spec };
|
|
570
|
-
}
|
|
571
|
-
function stampId(path, id) {
|
|
572
|
-
const doc = parseToml(readFileSync3(path, "utf8"));
|
|
573
|
-
doc.id = id;
|
|
574
|
-
delete doc.slug;
|
|
575
|
-
writeFileSync2(path, stringifyToml(doc));
|
|
576
|
-
}
|
|
577
|
-
function scaffoldAgentFile(path, name) {
|
|
578
|
-
if (existsSync(path)) throw new Error(`${path} already exists`);
|
|
579
|
-
writeFileSync2(
|
|
580
|
-
path,
|
|
581
|
-
`# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
|
|
582
|
-
name = "${name}"
|
|
583
|
-
# type = "pi" # pi (default) | hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode
|
|
584
|
-
# llm = "deepseek-flash" # must be compatible with type
|
|
585
|
-
# reasoning_effort = "high" # per harness \u2014 ${reasoningEffortDocLine()}
|
|
586
|
-
# capabilities = []
|
|
587
|
-
# hiring_policy = "deny" # its sessions work alone; "allow" (default) lets them hire helpers
|
|
588
|
-
# environment = "env_..." # the environment its sessions boot in when they name none
|
|
589
|
-
# [[skills]] # a skill from \`sky skills upload <dir>\`
|
|
590
|
-
# type = "skill"
|
|
591
|
-
# skillId = "skill_..."
|
|
592
|
-
|
|
593
|
-
prompt = """
|
|
594
|
-
You are ${name}, a helpful long-lived agent.
|
|
595
|
-
"""
|
|
596
|
-
`
|
|
597
|
-
);
|
|
598
|
-
}
|
|
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";
|
|
599
475
|
|
|
600
|
-
// src/
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (filter.text?.trim()) params.set("q", filter.text.trim());
|
|
606
|
-
if (filter.category) params.set("category", filter.category);
|
|
607
|
-
const query = params.toString();
|
|
608
|
-
return query ? `?${query}` : "";
|
|
609
|
-
}
|
|
610
|
-
function listingBody(agentId, options) {
|
|
611
|
-
return {
|
|
612
|
-
agent_id: agentId,
|
|
613
|
-
handle: bare(options.handle),
|
|
614
|
-
tagline: options.tagline,
|
|
615
|
-
...options.description ? { description: options.description } : {},
|
|
616
|
-
...options.category?.length ? { categories: options.category } : {},
|
|
617
|
-
...options.does?.length ? { does: options.does } : {},
|
|
618
|
-
...options.doesNot?.length ? { does_not: options.doesNot } : {},
|
|
619
|
-
...options.example?.length ? { examples: options.example } : {}
|
|
620
|
-
};
|
|
621
|
-
}
|
|
622
|
-
async function listCatalog(filter = {}) {
|
|
623
|
-
return (await api("GET", `/catalog/agents${catalogQuery(filter)}`, void 0, optionalAuth())).agents;
|
|
624
|
-
}
|
|
625
|
-
async function showCatalogAgent(handle) {
|
|
626
|
-
return (await api("GET", `/catalog/agents/${encodeURIComponent(bare(handle))}`, void 0, optionalAuth())).agent;
|
|
627
|
-
}
|
|
628
|
-
async function listCategories() {
|
|
629
|
-
return (await api("GET", "/catalog/categories", void 0, { auth: false })).categories;
|
|
630
|
-
}
|
|
631
|
-
async function publishListing(agentId, options) {
|
|
632
|
-
return (await api("POST", "/catalog/listings", listingBody(agentId, options))).agent;
|
|
633
|
-
}
|
|
634
|
-
async function removeListing(handle) {
|
|
635
|
-
await api("DELETE", `/catalog/listings/${encodeURIComponent(bare(handle))}`);
|
|
636
|
-
}
|
|
637
|
-
async function setEngineStatus(engine, status, reason) {
|
|
638
|
-
await api("PATCH", `/catalog/engines/${encodeURIComponent(engine)}`, { status, reason });
|
|
639
|
-
const state = await api("GET", "/catalog/engines", void 0, { auth: false });
|
|
640
|
-
if (state.delisted.includes(engine) !== (status === "delisted")) {
|
|
641
|
-
throw new Error("The catalog does not show the requested state; check whether another operator changed it.");
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
var uses = (agent2) => `${agent2.stats.uses} ${agent2.stats.uses === 1 ? "use" : "uses"}`;
|
|
645
|
-
var mine = (agent2) => agent2.visibility === "workspace" ? " (yours)" : "";
|
|
646
|
-
function renderCatalogAgent(agent2) {
|
|
647
|
-
const section = (title, lines) => lines.length ? [`${title}:`, ...lines.map((line) => ` - ${line}`)] : [];
|
|
648
|
-
return [
|
|
649
|
-
`@${agent2.handle} ${agent2.name}${mine(agent2)}`,
|
|
650
|
-
agent2.tagline,
|
|
651
|
-
...agent2.description && agent2.description !== agent2.tagline ? ["", agent2.description] : [],
|
|
652
|
-
"",
|
|
653
|
-
`Categories: ${agent2.categories.join(", ") || "none"}`,
|
|
654
|
-
`Used: ${uses(agent2)}`,
|
|
655
|
-
...section("Does", agent2.does),
|
|
656
|
-
...section("Does not", agent2.does_not),
|
|
657
|
-
...section("Examples", agent2.examples),
|
|
658
|
-
"",
|
|
659
|
-
`Hire it: sky session create @${agent2.handle} --message "\u2026"`
|
|
660
|
-
].join("\n");
|
|
661
|
-
}
|
|
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";
|
|
662
481
|
|
|
663
|
-
// src/
|
|
664
|
-
import {
|
|
665
|
-
import { existsSync as existsSync2, readFileSync as
|
|
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";
|
|
666
485
|
import { homedir } from "os";
|
|
667
|
-
import { basename as basename3, join as join3 } from "path";
|
|
668
|
-
import {
|
|
669
|
-
var
|
|
670
|
-
var
|
|
671
|
-
function
|
|
672
|
-
|
|
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;
|
|
673
493
|
}
|
|
674
|
-
|
|
494
|
+
var isFile = (path) => {
|
|
675
495
|
try {
|
|
676
|
-
return
|
|
496
|
+
return statSync3(path).isFile();
|
|
677
497
|
} catch {
|
|
678
|
-
return
|
|
498
|
+
return false;
|
|
679
499
|
}
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
const raw = readIfExists(path);
|
|
683
|
-
if (raw === void 0) return void 0;
|
|
500
|
+
};
|
|
501
|
+
var isDir = (path) => {
|
|
684
502
|
try {
|
|
685
|
-
return
|
|
503
|
+
return statSync3(path).isDirectory();
|
|
686
504
|
} catch {
|
|
687
|
-
return
|
|
505
|
+
return false;
|
|
688
506
|
}
|
|
689
|
-
}
|
|
690
|
-
function
|
|
691
|
-
const
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
for (const path of candidates) {
|
|
697
|
-
const model = readJson(path)?.model;
|
|
698
|
-
if (typeof model === "string" && model.trim()) return model.trim();
|
|
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 {
|
|
699
514
|
}
|
|
700
|
-
return
|
|
515
|
+
return [join3(home, "AGENTS.md"), join3(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
701
516
|
}
|
|
702
|
-
function
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
const projectConfig = readJson(
|
|
708
|
-
join3(process.cwd(), ".mcp.json")
|
|
709
|
-
);
|
|
710
|
-
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
711
|
-
return merged;
|
|
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);
|
|
712
522
|
}
|
|
713
|
-
function
|
|
714
|
-
const
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
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
|
+
];
|
|
720
533
|
}
|
|
721
|
-
function
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
730
|
-
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
731
|
-
const project2 = readIfExists(join3(process.cwd(), name));
|
|
732
|
-
if (project2?.trim()) {
|
|
733
|
-
parts.push({ label: `Project ${name}`, content: project2 });
|
|
734
|
-
sources.push(`project ${name}`);
|
|
735
|
-
break;
|
|
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;
|
|
736
542
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
const def = allServers[d.name];
|
|
744
|
-
if (!def?.env) continue;
|
|
745
|
-
for (const key of d.envKeys) {
|
|
746
|
-
const value = def.env[key];
|
|
747
|
-
if (value !== void 0 && value.trim()) {
|
|
748
|
-
secretCandidates.push({ serverName: d.name, envKey: key, value, url: d.url });
|
|
749
|
-
}
|
|
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;
|
|
750
549
|
}
|
|
751
|
-
|
|
752
|
-
return {
|
|
753
|
-
harness: "claude_code",
|
|
754
|
-
configRoot,
|
|
755
|
-
prompt,
|
|
756
|
-
promptSources: sources,
|
|
757
|
-
truncated,
|
|
758
|
-
model: claudeModel(configRoot),
|
|
759
|
-
keptServers: kept,
|
|
760
|
-
droppedServers: dropped,
|
|
761
|
-
secretCandidates
|
|
550
|
+
return null;
|
|
762
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);
|
|
763
553
|
}
|
|
764
|
-
function
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
return
|
|
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);
|
|
770
560
|
}
|
|
771
|
-
|
|
772
|
-
if (existsSync2(join3(homedir(), ".codex"))) return "codex";
|
|
773
|
-
if (existsSync2(join3(homedir(), ".openclaw"))) return "openclaw";
|
|
774
|
-
throw new Error(
|
|
775
|
-
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
function readLocal(harness2) {
|
|
779
|
-
if (harness2 === "claude_code") return readClaudeCode();
|
|
780
|
-
throw new Error(
|
|
781
|
-
`${harness2} cloning lands next \u2014 claude_code is the first supported harness`
|
|
782
|
-
);
|
|
783
|
-
}
|
|
784
|
-
function linkPath(configRoot) {
|
|
785
|
-
return join3(configRoot, "agentsky-link.json");
|
|
561
|
+
return null;
|
|
786
562
|
}
|
|
787
|
-
function
|
|
788
|
-
const
|
|
789
|
-
|
|
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
|
+
}
|
|
583
|
+
}
|
|
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);
|
|
589
|
+
}
|
|
590
|
+
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
790
591
|
}
|
|
791
|
-
function
|
|
792
|
-
|
|
793
|
-
|
|
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
|
+
}
|
|
794
599
|
}
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const
|
|
798
|
-
|
|
799
|
-
"/vaults"
|
|
800
|
-
);
|
|
801
|
-
return vaults.find((v) => v.display_name === DEFAULT_VAULT_NAME)?.id ?? null;
|
|
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;
|
|
802
604
|
}
|
|
803
|
-
|
|
804
|
-
const existing = await fetchDefaultVaultId();
|
|
805
|
-
if (existing) return existing;
|
|
605
|
+
function safeUrl(raw) {
|
|
806
606
|
try {
|
|
807
|
-
const
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
return
|
|
811
|
-
} catch
|
|
812
|
-
|
|
813
|
-
const raced = await fetchDefaultVaultId();
|
|
814
|
-
if (raced) return raced;
|
|
815
|
-
}
|
|
816
|
-
throw error;
|
|
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;
|
|
817
613
|
}
|
|
818
614
|
}
|
|
819
|
-
|
|
820
|
-
if (!
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
);
|
|
825
|
-
return;
|
|
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 } : {} };
|
|
826
624
|
}
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
if (!
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
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()) {
|
|
630
|
+
const out = [];
|
|
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);
|
|
838
636
|
}
|
|
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") {
|
|
839
647
|
try {
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
auth: {
|
|
844
|
-
type: "environment_variable",
|
|
845
|
-
secret_name: c.envKey,
|
|
846
|
-
value: c.value,
|
|
847
|
-
allowed_hosts: c.url ? [c.url] : []
|
|
848
|
-
}
|
|
849
|
-
});
|
|
850
|
-
console.log(` \u2713 saved ${c.envKey}`);
|
|
851
|
-
saved += 1;
|
|
852
|
-
} catch (error) {
|
|
853
|
-
if (error instanceof ApiError && error.status === 409) {
|
|
854
|
-
console.log(` \u2014 ${c.envKey} already in vault`);
|
|
855
|
-
} else {
|
|
856
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
857
|
-
console.log(` \u2717 ${c.envKey} failed: ${msg}`);
|
|
858
|
-
}
|
|
648
|
+
const config = parseToml(readFileSync3(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "config.toml"), "utf8"));
|
|
649
|
+
push(config.mcp_servers, "user");
|
|
650
|
+
} catch {
|
|
859
651
|
}
|
|
860
652
|
}
|
|
861
|
-
|
|
862
|
-
console.log(
|
|
863
|
-
`
|
|
864
|
-
Vault: ${saved} saved, ${skipped} skipped. Attach the "default" vault to a session to use these keys.`
|
|
865
|
-
);
|
|
866
|
-
}
|
|
653
|
+
return out;
|
|
867
654
|
}
|
|
868
|
-
function
|
|
869
|
-
return
|
|
870
|
-
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
871
|
-
rl.question(`${question} [Y/n] `, (answer) => {
|
|
872
|
-
rl.close();
|
|
873
|
-
done(/^(y(es)?)?$/i.test(answer.trim()));
|
|
874
|
-
});
|
|
875
|
-
});
|
|
655
|
+
function remoteWithoutCredentials(remote) {
|
|
656
|
+
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
876
657
|
}
|
|
877
|
-
function
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
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;
|
|
882
678
|
}
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
console.log(
|
|
899
|
-
`Left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
900
|
-
);
|
|
901
|
-
}
|
|
902
|
-
if (opts.dryRun) {
|
|
903
|
-
console.log("Dry run \u2014 nothing sent.");
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
if (!opts.yes && !await confirm("Launch this as a new cloud agent?")) {
|
|
907
|
-
console.log("Aborted \u2014 nothing sent.");
|
|
908
|
-
return;
|
|
909
|
-
}
|
|
910
|
-
const { servers: mcpServers2, skipped } = mcpServersFromKept(local.keptServers);
|
|
911
|
-
if (skipped.length) {
|
|
912
|
-
console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
|
|
913
|
-
}
|
|
914
|
-
const body = {
|
|
915
|
-
agentType: local.harness,
|
|
916
|
-
...local.model ? { llm: local.model } : {},
|
|
917
|
-
...local.prompt ? { prompt: local.prompt } : {},
|
|
918
|
-
...mcpServers2.length ? { mcpServers: mcpServers2 } : {}
|
|
679
|
+
return {
|
|
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
|
|
919
694
|
};
|
|
920
|
-
let sentLlm = local.model ?? null;
|
|
921
|
-
let created;
|
|
922
|
-
try {
|
|
923
|
-
({ agent: created } = await api("POST", "/agents", body));
|
|
924
|
-
} catch (error) {
|
|
925
|
-
if (error instanceof ApiError && error.status === 422 && local.model) {
|
|
926
|
-
const successor = supersededBy(local.model);
|
|
927
|
-
console.log(
|
|
928
|
-
successor ? ` note: ${local.model} is superseded \u2014 using ${successor}` : ` note: ${local.model} isn't available for ${local.harness} \u2014 using the harness default`
|
|
929
|
-
);
|
|
930
|
-
sentLlm = successor;
|
|
931
|
-
({ agent: created } = await api("POST", "/agents", {
|
|
932
|
-
...body,
|
|
933
|
-
llm: successor ?? void 0
|
|
934
|
-
}));
|
|
935
|
-
} else {
|
|
936
|
-
throw error;
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
console.log("Starting its runtime\u2026");
|
|
940
|
-
await api("POST", "/sessions", { agent: created.id });
|
|
941
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
942
|
-
writeLink(local.configRoot, {
|
|
943
|
-
baseUrl: apiBase(),
|
|
944
|
-
agentId: created.id,
|
|
945
|
-
clonedAt: now,
|
|
946
|
-
lastSyncedAt: now,
|
|
947
|
-
llm: sentLlm,
|
|
948
|
-
mcpServers: keptNames
|
|
949
|
-
});
|
|
950
|
-
const url = `${apiBase()}/agents/${created.id}`;
|
|
951
|
-
console.log(`Cloned \u2192 ${created.id} ${url}`);
|
|
952
|
-
console.log(
|
|
953
|
-
`Linked this machine (${linkPath(local.configRoot)}). Push prompt changes anytime by running \`sky clone\` again.`
|
|
954
|
-
);
|
|
955
|
-
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
956
|
-
if (opts.open) openBrowser(url);
|
|
957
|
-
}
|
|
958
|
-
async function syncClone(local, link2, opts) {
|
|
959
|
-
const since = link2.lastSyncedAt ?? link2.clonedAt;
|
|
960
|
-
console.log(
|
|
961
|
-
`Syncing ${link2.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
|
|
962
|
-
);
|
|
963
|
-
if (opts.dryRun) {
|
|
964
|
-
console.log(
|
|
965
|
-
` would push prompt (${kb(local.prompt)}) to ${link2.agentId}. Dry run \u2014 nothing sent.`
|
|
966
|
-
);
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
let agent2;
|
|
970
|
-
try {
|
|
971
|
-
({ agent: agent2 } = await api(
|
|
972
|
-
"GET",
|
|
973
|
-
`/agents/${encodeURIComponent(link2.agentId)}`
|
|
974
|
-
));
|
|
975
|
-
} catch (error) {
|
|
976
|
-
if (error instanceof ApiError && error.status === 404) {
|
|
977
|
-
throw new Error(
|
|
978
|
-
`linked agent ${link2.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
throw error;
|
|
982
|
-
}
|
|
983
|
-
if (agent2.agentType !== local.harness) {
|
|
984
|
-
throw new Error(
|
|
985
|
-
`linked agent ${link2.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
|
|
986
|
-
);
|
|
987
|
-
}
|
|
988
|
-
const result = await api(
|
|
989
|
-
"PUT",
|
|
990
|
-
`/agents/${encodeURIComponent(link2.agentId)}/prompt`,
|
|
991
|
-
{ prompt: local.prompt }
|
|
992
|
-
);
|
|
993
|
-
console.log(
|
|
994
|
-
result.unchanged ? " prompt unchanged" : ` prompt updated (v${result.version}${result.applied === "live" ? ", hot-reloaded" : ", applies on next boot"})`
|
|
995
|
-
);
|
|
996
|
-
const drift = [];
|
|
997
|
-
const localModel = local.model ?? null;
|
|
998
|
-
const linkedModel = link2.llm ?? agent2.llm;
|
|
999
|
-
if (localModel && linkedModel && localModel !== linkedModel) {
|
|
1000
|
-
drift.push(`model changed ${linkedModel} \u2192 ${localModel}`);
|
|
1001
|
-
}
|
|
1002
|
-
const before = new Set(link2.mcpServers ?? []);
|
|
1003
|
-
const nowKept = Object.keys(local.keptServers);
|
|
1004
|
-
const newServers = nowKept.filter((n) => !before.has(n));
|
|
1005
|
-
const goneServers = [...before].filter((n) => !nowKept.includes(n));
|
|
1006
|
-
if (newServers.length) drift.push(`${newServers.length} new MCP server${newServers.length === 1 ? "" : "s"} (${newServers.join(", ")})`);
|
|
1007
|
-
if (goneServers.length) drift.push(`${goneServers.length} removed MCP server${goneServers.length === 1 ? "" : "s"} (${goneServers.join(", ")})`);
|
|
1008
|
-
if (drift.length) {
|
|
1009
|
-
console.log(` skipped (set at launch): ${drift.join("; ")}`);
|
|
1010
|
-
console.log(" \u2192 a running agent can't change these; run `sky clone --new` to launch a fresh agent.");
|
|
1011
|
-
}
|
|
1012
|
-
if (local.droppedServers.length) {
|
|
1013
|
-
console.log(
|
|
1014
|
-
` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
1015
|
-
);
|
|
1016
|
-
}
|
|
1017
|
-
writeLink(local.configRoot, { ...link2, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1018
|
-
console.log(`Synced ${link2.agentId}.`);
|
|
1019
|
-
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
1020
|
-
if (opts.open) openBrowser(`${apiBase()}/agents/${link2.agentId}`);
|
|
1021
|
-
}
|
|
1022
|
-
async function clone(opts) {
|
|
1023
|
-
const harness2 = detectHarness(opts.harness);
|
|
1024
|
-
const local = readLocal(harness2);
|
|
1025
|
-
const link2 = opts.new ? void 0 : readLink(local.configRoot);
|
|
1026
|
-
if (link2) await syncClone(local, link2, opts);
|
|
1027
|
-
else await createClone(local, opts);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
// src/import-local.ts
|
|
1031
|
-
import { closeSync as closeSync3, existsSync as existsSync5, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
|
|
1032
|
-
import { homedir as homedir4 } from "os";
|
|
1033
|
-
import { join as join7 } from "path";
|
|
1034
|
-
import { createHash as createHash3 } from "crypto";
|
|
1035
|
-
|
|
1036
|
-
// src/import-files.ts
|
|
1037
|
-
import { isUtf8 } from "buffer";
|
|
1038
|
-
import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
1039
|
-
import { homedir as homedir3 } from "os";
|
|
1040
|
-
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";
|
|
1041
|
-
|
|
1042
|
-
// src/import-workspace.ts
|
|
1043
|
-
import { spawnSync } from "child_process";
|
|
1044
|
-
import { existsSync as existsSync3, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
1045
|
-
import { homedir as homedir2 } from "os";
|
|
1046
|
-
import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
|
|
1047
|
-
import { parse as parseToml2 } from "smol-toml";
|
|
1048
|
-
var MAX_TREE = 5e3;
|
|
1049
|
-
var SKILL_LINE = /Base directory for this skill: (\/[^\n\r"'\\<>]+)/g;
|
|
1050
|
-
function git(cwd, ...args) {
|
|
1051
|
-
const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
|
|
1052
|
-
return run2.status === 0 ? run2.stdout : null;
|
|
1053
|
-
}
|
|
1054
|
-
var isFile = (path) => {
|
|
1055
|
-
try {
|
|
1056
|
-
return statSync3(path).isFile();
|
|
1057
|
-
} catch {
|
|
1058
|
-
return false;
|
|
1059
|
-
}
|
|
1060
|
-
};
|
|
1061
|
-
var isDir = (path) => {
|
|
1062
|
-
try {
|
|
1063
|
-
return statSync3(path).isDirectory();
|
|
1064
|
-
} catch {
|
|
1065
|
-
return false;
|
|
1066
|
-
}
|
|
1067
|
-
};
|
|
1068
|
-
function sharedInstructionPaths(home = homedir2()) {
|
|
1069
|
-
const rules = join4(home, ".agents", "rules");
|
|
1070
|
-
let ruleFiles = [];
|
|
1071
|
-
try {
|
|
1072
|
-
ruleFiles = readdirSync2(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join4(rules, name));
|
|
1073
|
-
} catch {
|
|
1074
|
-
}
|
|
1075
|
-
return [join4(home, "AGENTS.md"), join4(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
1076
|
-
}
|
|
1077
|
-
function strings(value, out, depth = 0) {
|
|
1078
|
-
if (depth > 12) return;
|
|
1079
|
-
if (typeof value === "string") out.push(value);
|
|
1080
|
-
else if (Array.isArray(value)) for (const item of value) strings(item, out, depth + 1);
|
|
1081
|
-
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
1082
|
-
}
|
|
1083
|
-
function skillRoots(cwd, home) {
|
|
1084
|
-
const claude = process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude");
|
|
1085
|
-
const codex = process.env.CODEX_HOME ?? join4(home, ".codex");
|
|
1086
|
-
return [
|
|
1087
|
-
...cwd ? [join4(cwd, ".claude", "skills"), join4(cwd, ".agents", "skills")] : [],
|
|
1088
|
-
join4(home, ".agents", "skills"),
|
|
1089
|
-
join4(claude, "skills"),
|
|
1090
|
-
join4(codex, "skills"),
|
|
1091
|
-
join4(home, ".hermes", "skills")
|
|
1092
|
-
];
|
|
1093
695
|
}
|
|
1094
|
-
function
|
|
1095
|
-
const
|
|
1096
|
-
|
|
696
|
+
function skillFiles(skill) {
|
|
697
|
+
const out = [];
|
|
698
|
+
const walk = (dir, depth) => {
|
|
699
|
+
if (depth > 8 || out.length > 400) return;
|
|
1097
700
|
let entries;
|
|
1098
701
|
try {
|
|
1099
702
|
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1100
703
|
} catch {
|
|
1101
|
-
return
|
|
704
|
+
return;
|
|
1102
705
|
}
|
|
1103
706
|
for (const entry2 of entries) {
|
|
1104
|
-
if (
|
|
1105
|
-
const path =
|
|
1106
|
-
if (entry2.
|
|
1107
|
-
|
|
1108
|
-
if (found) return found;
|
|
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("/")}` });
|
|
1109
711
|
}
|
|
1110
|
-
return null;
|
|
1111
712
|
};
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
function findSkill(name, cwd, home) {
|
|
1115
|
-
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
1116
|
-
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
1117
|
-
for (const root of skillRoots(cwd, home)) {
|
|
1118
|
-
const direct = join4(root, name);
|
|
1119
|
-
if (isFile(join4(direct, "SKILL.md"))) return realpathSync(direct);
|
|
1120
|
-
}
|
|
1121
|
-
return null;
|
|
1122
|
-
}
|
|
1123
|
-
function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
1124
|
-
const dirs = /* @__PURE__ */ new Map();
|
|
1125
|
-
const missing = /* @__PURE__ */ new Set();
|
|
1126
|
-
const add = (dir, name = basename4(dir)) => {
|
|
1127
|
-
if (!isFile(join4(dir, "SKILL.md"))) return false;
|
|
1128
|
-
const real = realpathSync(dir);
|
|
1129
|
-
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
1130
|
-
return true;
|
|
1131
|
-
};
|
|
1132
|
-
const called = [];
|
|
1133
|
-
for (const record of records) {
|
|
1134
|
-
const texts = [];
|
|
1135
|
-
strings(record, texts);
|
|
1136
|
-
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].trim().replace(/[.,;:)]+$/, ""));
|
|
1137
|
-
const content = record?.message?.content;
|
|
1138
|
-
if (!Array.isArray(content)) continue;
|
|
1139
|
-
for (const block of content) {
|
|
1140
|
-
const b = block;
|
|
1141
|
-
if (b?.type === "tool_use" && b.name === "Skill" && typeof b.input?.skill === "string") called.push(b.input.skill.replace(/^\//, ""));
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
for (const path of touched) if (basename4(path) === "SKILL.md") add(dirname3(path));
|
|
1145
|
-
for (const name of called) {
|
|
1146
|
-
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
1147
|
-
const dir = findSkill(name, cwd, home);
|
|
1148
|
-
if (!dir || !add(dir, name)) missing.add(name);
|
|
1149
|
-
}
|
|
1150
|
-
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
713
|
+
if (existsSync2(skill.dir)) walk(skill.dir, 0);
|
|
714
|
+
return out;
|
|
1151
715
|
}
|
|
1152
|
-
function
|
|
716
|
+
function codexThreadName(sessionId, home = homedir()) {
|
|
717
|
+
let name = null;
|
|
1153
718
|
try {
|
|
1154
|
-
const
|
|
1155
|
-
|
|
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
|
+
}
|
|
1156
727
|
} catch {
|
|
1157
|
-
return null;
|
|
1158
728
|
}
|
|
1159
|
-
|
|
1160
|
-
function names(value) {
|
|
1161
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
|
|
1162
|
-
const keys2 = Object.keys(value).sort();
|
|
1163
|
-
return keys2.length ? keys2 : void 0;
|
|
1164
|
-
}
|
|
1165
|
-
function safeUrl(raw) {
|
|
1166
|
-
try {
|
|
1167
|
-
const url = new URL(raw);
|
|
1168
|
-
if (!/^https?:$/.test(url.protocol)) return null;
|
|
1169
|
-
const query = [.../* @__PURE__ */ new Set([...url.searchParams.keys()])].sort();
|
|
1170
|
-
return { url: `${url.protocol}//${url.host}${url.pathname}`, ...query.length ? { query } : {} };
|
|
1171
|
-
} catch {
|
|
1172
|
-
return null;
|
|
1173
|
-
}
|
|
1174
|
-
}
|
|
1175
|
-
function serverEntry(name, def, scope) {
|
|
1176
|
-
if (!def || typeof def !== "object") return null;
|
|
1177
|
-
const env = names(def.env);
|
|
1178
|
-
const headers = names(def.headers ?? def.http_headers);
|
|
1179
|
-
if (typeof def.url === "string") {
|
|
1180
|
-
const url = safeUrl(def.url);
|
|
1181
|
-
if (!url) return null;
|
|
1182
|
-
const type = def.type === "sse" ? "sse" : "http";
|
|
1183
|
-
return { name, scope, transport: type, ...url, ...headers ? { headers } : {}, ...env ? { env } : {} };
|
|
1184
|
-
}
|
|
1185
|
-
if (typeof def.command !== "string" || !def.command) return null;
|
|
1186
|
-
const args = Array.isArray(def.args) ? def.args.filter((arg) => typeof arg === "string") : [];
|
|
1187
|
-
return { name, scope, transport: "stdio", command: def.command, ...args.length ? { args } : {}, ...env ? { env } : {} };
|
|
1188
|
-
}
|
|
1189
|
-
function mcpServers(harness2, cwd, home = homedir2()) {
|
|
1190
|
-
const out = [];
|
|
1191
|
-
const push = (servers, scope) => {
|
|
1192
|
-
if (!servers || typeof servers !== "object") return;
|
|
1193
|
-
for (const [name, def] of Object.entries(servers)) {
|
|
1194
|
-
const entry2 = serverEntry(name, def, scope);
|
|
1195
|
-
if (entry2 && !out.some((existing) => existing.name === name)) out.push(entry2);
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
if (harness2 === "claude_code") {
|
|
1199
|
-
const config = readJson2(join4(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
1200
|
-
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
1201
|
-
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
1202
|
-
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
1203
|
-
if (root) push(readJson2(join4(root, ".mcp.json"))?.mcpServers, "project");
|
|
1204
|
-
push(config?.mcpServers, "user");
|
|
1205
|
-
}
|
|
1206
|
-
if (harness2 === "codex") {
|
|
1207
|
-
try {
|
|
1208
|
-
const config = parseToml2(readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "config.toml"), "utf8"));
|
|
1209
|
-
push(config.mcp_servers, "user");
|
|
1210
|
-
} catch {
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
|
-
return out;
|
|
1214
|
-
}
|
|
1215
|
-
function remoteWithoutCredentials(remote) {
|
|
1216
|
-
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
1217
|
-
}
|
|
1218
|
-
function workspaceState(cwd, harness2, skills2, home = homedir2(), title = null) {
|
|
1219
|
-
const present = !!cwd && isDir(cwd);
|
|
1220
|
-
const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
|
|
1221
|
-
let gitState = null;
|
|
1222
|
-
let tree = [];
|
|
1223
|
-
let patch = null;
|
|
1224
|
-
if (root) {
|
|
1225
|
-
const dirty = (git(root, "status", "--porcelain=v1", "--untracked-files=normal") ?? "").split("\n").filter(Boolean).map((line) => line.slice(3));
|
|
1226
|
-
const ahead = git(root, "rev-list", "--count", "@{upstream}..HEAD");
|
|
1227
|
-
const remote = git(root, "remote", "get-url", "origin");
|
|
1228
|
-
const branch = git(root, "rev-parse", "--abbrev-ref", "HEAD")?.trim() || null;
|
|
1229
|
-
gitState = {
|
|
1230
|
-
remote: remote ? remoteWithoutCredentials(remote) : null,
|
|
1231
|
-
branch: branch === "HEAD" ? null : branch,
|
|
1232
|
-
commit: git(root, "rev-parse", "HEAD")?.trim() || null,
|
|
1233
|
-
dirty: dirty.slice(0, 500),
|
|
1234
|
-
ahead: ahead === null ? null : Number(ahead.trim()) || 0
|
|
1235
|
-
};
|
|
1236
|
-
tree = (git(cwd, "-c", "core.quotePath=false", "ls-files") ?? "").split("\n").filter(Boolean);
|
|
1237
|
-
if (dirty.length) patch = git(cwd, "diff", "HEAD", "--no-color", "--no-ext-diff", "--relative", "--", ".") || null;
|
|
1238
|
-
}
|
|
1239
|
-
return {
|
|
1240
|
-
manifest: {
|
|
1241
|
-
format: "agentsky.import-workspace",
|
|
1242
|
-
format_version: 1,
|
|
1243
|
-
harness: harness2,
|
|
1244
|
-
title: title?.trim().slice(0, 120) || null,
|
|
1245
|
-
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
1246
|
-
project: cwd ? basename4(root ?? cwd) : null,
|
|
1247
|
-
git: gitState,
|
|
1248
|
-
tree: tree.slice(0, MAX_TREE),
|
|
1249
|
-
tree_truncated: tree.length > MAX_TREE,
|
|
1250
|
-
skills: [...skills2.found.map((skill) => ({ name: skill.name, found: true })), ...skills2.missing.map((name) => ({ name, found: false }))],
|
|
1251
|
-
mcp_servers: mcpServers(harness2, cwd, home)
|
|
1252
|
-
},
|
|
1253
|
-
patch
|
|
1254
|
-
};
|
|
1255
|
-
}
|
|
1256
|
-
function skillFiles(skill) {
|
|
1257
|
-
const out = [];
|
|
1258
|
-
const walk = (dir, depth) => {
|
|
1259
|
-
if (depth > 8 || out.length > 400) return;
|
|
1260
|
-
let entries;
|
|
1261
|
-
try {
|
|
1262
|
-
entries = readdirSync2(dir, { withFileTypes: true });
|
|
1263
|
-
} catch {
|
|
1264
|
-
return;
|
|
1265
|
-
}
|
|
1266
|
-
for (const entry2 of entries) {
|
|
1267
|
-
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1268
|
-
const path = join4(dir, entry2.name);
|
|
1269
|
-
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1270
|
-
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1271
|
-
}
|
|
1272
|
-
};
|
|
1273
|
-
if (existsSync3(skill.dir)) walk(skill.dir, 0);
|
|
1274
|
-
return out;
|
|
1275
|
-
}
|
|
1276
|
-
function codexThreadName(sessionId, home = homedir2()) {
|
|
1277
|
-
let name = null;
|
|
1278
|
-
try {
|
|
1279
|
-
for (const line of readFileSync5(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
|
|
1280
|
-
if (!line.includes(sessionId)) continue;
|
|
1281
|
-
try {
|
|
1282
|
-
const entry2 = JSON.parse(line);
|
|
1283
|
-
if (entry2.id === sessionId && typeof entry2.thread_name === "string" && entry2.thread_name.trim()) name = entry2.thread_name.trim();
|
|
1284
|
-
} catch {
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
} catch {
|
|
1288
|
-
}
|
|
1289
|
-
return name;
|
|
729
|
+
return name;
|
|
1290
730
|
}
|
|
1291
731
|
|
|
1292
732
|
// src/import-files.ts
|
|
@@ -1338,7 +778,7 @@ function classify(path) {
|
|
|
1338
778
|
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
1339
779
|
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
1340
780
|
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
1341
|
-
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(
|
|
781
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename4(path))) return { kind: "text", mediaType: "text/plain" };
|
|
1342
782
|
return null;
|
|
1343
783
|
}
|
|
1344
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;
|
|
@@ -1352,13 +792,13 @@ function shellText(input) {
|
|
|
1352
792
|
const command = Array.isArray(i.command) ? i.command.filter((part) => typeof part === "string").join(" ") : str(i.command);
|
|
1353
793
|
return [command, str(i.cmd), str(i.script)].filter(Boolean).join("\n");
|
|
1354
794
|
}
|
|
1355
|
-
function touchedPaths(records, cwd, home =
|
|
795
|
+
function touchedPaths(records, cwd, home = homedir2()) {
|
|
1356
796
|
const out = /* @__PURE__ */ new Set();
|
|
1357
797
|
const add = (raw, base) => {
|
|
1358
798
|
if (!raw || raw.length > 1e3) return;
|
|
1359
799
|
const path = raw.replace(/^~(?=\/)/, home);
|
|
1360
|
-
if (isAbsolute(path)) out.add(
|
|
1361
|
-
else if (base) out.add(
|
|
800
|
+
if (isAbsolute(path)) out.add(resolve2(path));
|
|
801
|
+
else if (base) out.add(resolve2(base, path));
|
|
1362
802
|
};
|
|
1363
803
|
const call = (name, input) => {
|
|
1364
804
|
const i = obj(input);
|
|
@@ -1395,22 +835,22 @@ function touchedPaths(records, cwd, home = homedir3()) {
|
|
|
1395
835
|
return [...out];
|
|
1396
836
|
}
|
|
1397
837
|
function gitRoot(dir) {
|
|
1398
|
-
for (let d = dir; ; d =
|
|
1399
|
-
if (
|
|
1400
|
-
if (d ===
|
|
838
|
+
for (let d = dir; ; d = dirname3(d)) {
|
|
839
|
+
if (existsSync3(join4(d, ".git"))) return d;
|
|
840
|
+
if (d === dirname3(d)) return null;
|
|
1401
841
|
}
|
|
1402
842
|
}
|
|
1403
|
-
function instructionPaths(cwd, harness2, home =
|
|
843
|
+
function instructionPaths(cwd, harness2, home = homedir2()) {
|
|
1404
844
|
const out = [];
|
|
1405
|
-
if (cwd &&
|
|
845
|
+
if (cwd && existsSync3(cwd)) {
|
|
1406
846
|
const root = gitRoot(cwd) ?? cwd;
|
|
1407
|
-
for (let d =
|
|
1408
|
-
for (const name of INSTRUCTION_FILES) out.push(
|
|
1409
|
-
if (d === root || d ===
|
|
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;
|
|
1410
850
|
}
|
|
1411
851
|
}
|
|
1412
|
-
if (harness2 === "claude_code") out.push(
|
|
1413
|
-
if (harness2 === "codex") out.push(
|
|
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"));
|
|
1414
854
|
out.push(...sharedInstructionPaths(home));
|
|
1415
855
|
return out.filter((path) => {
|
|
1416
856
|
try {
|
|
@@ -1420,8 +860,8 @@ function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
|
1420
860
|
}
|
|
1421
861
|
});
|
|
1422
862
|
}
|
|
1423
|
-
function resourceName(path, cwd, home =
|
|
1424
|
-
const inside = cwd ? relative2(
|
|
863
|
+
function resourceName(path, cwd, home = homedir2()) {
|
|
864
|
+
const inside = cwd ? relative2(resolve2(cwd), path) : "";
|
|
1425
865
|
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
1426
866
|
const underHome = relative2(home, path);
|
|
1427
867
|
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
@@ -1432,8 +872,8 @@ function resourceLine(name, mediaType, data) {
|
|
|
1432
872
|
return redactLine(JSON.stringify({ type: "import.resource", name, media_type: mediaType, data: data.toString("base64") }), { requireStructured: true }).line + "\n";
|
|
1433
873
|
}
|
|
1434
874
|
function collectSessionFiles(records, opts) {
|
|
1435
|
-
const home = opts.home ??
|
|
1436
|
-
const exclude = new Set((opts.exclude ?? []).map((path) =>
|
|
875
|
+
const home = opts.home ?? homedir2();
|
|
876
|
+
const exclude = new Set((opts.exclude ?? []).map((path) => resolve2(path)));
|
|
1437
877
|
const leftOut = [];
|
|
1438
878
|
const candidates = [];
|
|
1439
879
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1498,7 +938,7 @@ function collectSessionFiles(records, opts) {
|
|
|
1498
938
|
for (const file of candidates) {
|
|
1499
939
|
let data;
|
|
1500
940
|
try {
|
|
1501
|
-
data =
|
|
941
|
+
data = readFileSync4(file.path);
|
|
1502
942
|
} catch {
|
|
1503
943
|
leftOut.push({ path: file.path, reason: "missing" });
|
|
1504
944
|
continue;
|
|
@@ -1535,9 +975,9 @@ function collectSessionFiles(records, opts) {
|
|
|
1535
975
|
// src/import-environment.ts
|
|
1536
976
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
1537
977
|
import { createHash as createHash2 } from "crypto";
|
|
1538
|
-
import { closeSync as closeSync2, createWriteStream, lstatSync, mkdtempSync, openSync as openSync2, readdirSync as readdirSync3, readFileSync as
|
|
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";
|
|
1539
979
|
import { tmpdir } from "os";
|
|
1540
|
-
import { join as
|
|
980
|
+
import { join as join5 } from "path";
|
|
1541
981
|
import { isUtf8 as isUtf82 } from "buffer";
|
|
1542
982
|
import { createGzip } from "zlib";
|
|
1543
983
|
|
|
@@ -1723,7 +1163,7 @@ function listFiles(cwd) {
|
|
|
1723
1163
|
for (const entry2 of entries) {
|
|
1724
1164
|
if (SKIPPED.has(entry2.name)) continue;
|
|
1725
1165
|
const rel = prefix ? `${prefix}/${entry2.name}` : entry2.name;
|
|
1726
|
-
if (entry2.isDirectory()) walk(
|
|
1166
|
+
if (entry2.isDirectory()) walk(join5(dir, entry2.name), rel);
|
|
1727
1167
|
else out.push(rel);
|
|
1728
1168
|
if (out.length > MAX_ENVIRONMENT_FILES) return;
|
|
1729
1169
|
}
|
|
@@ -1733,15 +1173,15 @@ function listFiles(cwd) {
|
|
|
1733
1173
|
}
|
|
1734
1174
|
function textOf(path, size) {
|
|
1735
1175
|
if (size > MAX_REDACTED_TEXT_BYTES) return null;
|
|
1736
|
-
const data =
|
|
1176
|
+
const data = readFileSync5(path);
|
|
1737
1177
|
if (data.subarray(0, 8e3).includes(0) || !isUtf82(data)) return null;
|
|
1738
1178
|
return data;
|
|
1739
1179
|
}
|
|
1740
1180
|
async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
1741
1181
|
const listed = listFiles(cwd);
|
|
1742
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.`);
|
|
1743
|
-
const scratch = mkdtempSync(
|
|
1744
|
-
const path =
|
|
1183
|
+
const scratch = mkdtempSync(join5(tmpdir(), "sky-environment-"));
|
|
1184
|
+
const path = join5(scratch, "environment.tar.gz");
|
|
1745
1185
|
const leftOut = [];
|
|
1746
1186
|
const written2 = /* @__PURE__ */ new Set();
|
|
1747
1187
|
const hash = createHash2("sha256");
|
|
@@ -1769,7 +1209,7 @@ async function packEnvironment(cwd, resources, platform = IMPORT_PLATFORM) {
|
|
|
1769
1209
|
});
|
|
1770
1210
|
try {
|
|
1771
1211
|
for (const rel of listed) {
|
|
1772
|
-
const full =
|
|
1212
|
+
const full = join5(cwd, ...rel.split("/"));
|
|
1773
1213
|
if (SECRET_FILE2.test(rel)) {
|
|
1774
1214
|
leftOut.push({ path: rel, reason: "credential file" });
|
|
1775
1215
|
continue;
|
|
@@ -1923,13 +1363,13 @@ function sniffTitle(filePath) {
|
|
|
1923
1363
|
}
|
|
1924
1364
|
}
|
|
1925
1365
|
function listClaudeCodeSessions(limit) {
|
|
1926
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
1927
|
-
const projectsDir =
|
|
1928
|
-
if (!
|
|
1366
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join6(homedir3(), ".claude");
|
|
1367
|
+
const projectsDir = join6(root, "projects");
|
|
1368
|
+
if (!existsSync4(projectsDir)) return [];
|
|
1929
1369
|
const sessions = [];
|
|
1930
1370
|
try {
|
|
1931
1371
|
for (const encoded of readdirSync4(projectsDir)) {
|
|
1932
|
-
const projectDir =
|
|
1372
|
+
const projectDir = join6(projectsDir, encoded);
|
|
1933
1373
|
let entries;
|
|
1934
1374
|
try {
|
|
1935
1375
|
entries = readdirSync4(projectDir);
|
|
@@ -1940,7 +1380,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
1940
1380
|
for (const entry2 of entries) {
|
|
1941
1381
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
1942
1382
|
const sessionId = entry2.slice(0, -6);
|
|
1943
|
-
const mainFile =
|
|
1383
|
+
const mainFile = join6(projectDir, entry2);
|
|
1944
1384
|
let stat;
|
|
1945
1385
|
try {
|
|
1946
1386
|
stat = statSync5(mainFile);
|
|
@@ -1950,12 +1390,12 @@ function listClaudeCodeSessions(limit) {
|
|
|
1950
1390
|
if (!stat.isFile()) continue;
|
|
1951
1391
|
let discoveryError;
|
|
1952
1392
|
const subagentFiles = [];
|
|
1953
|
-
const subagentDir =
|
|
1954
|
-
if (
|
|
1393
|
+
const subagentDir = join6(projectDir, sessionId, "subagents");
|
|
1394
|
+
if (existsSync4(subagentDir)) {
|
|
1955
1395
|
try {
|
|
1956
1396
|
const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1957
1397
|
for (const f of subFiles) {
|
|
1958
|
-
subagentFiles.push(
|
|
1398
|
+
subagentFiles.push(join6(subagentDir, f));
|
|
1959
1399
|
}
|
|
1960
1400
|
} catch {
|
|
1961
1401
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -1977,7 +1417,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
1977
1417
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1978
1418
|
}
|
|
1979
1419
|
function listCodexSessions(limit) {
|
|
1980
|
-
const sessionsDir =
|
|
1420
|
+
const sessionsDir = join6(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join6(homedir3(), ".codex"), "sessions");
|
|
1981
1421
|
const records = [];
|
|
1982
1422
|
function walk(dir) {
|
|
1983
1423
|
let entries;
|
|
@@ -1987,7 +1427,7 @@ function listCodexSessions(limit) {
|
|
|
1987
1427
|
return;
|
|
1988
1428
|
}
|
|
1989
1429
|
for (const entry2 of entries) {
|
|
1990
|
-
const full =
|
|
1430
|
+
const full = join6(dir, entry2.name);
|
|
1991
1431
|
if (entry2.isDirectory()) {
|
|
1992
1432
|
walk(full);
|
|
1993
1433
|
continue;
|
|
@@ -2074,7 +1514,7 @@ async function mintTrace(base, harness2) {
|
|
|
2074
1514
|
function loadSummaryFile(summaryPath) {
|
|
2075
1515
|
let raw;
|
|
2076
1516
|
try {
|
|
2077
|
-
raw =
|
|
1517
|
+
raw = readFileSync6(summaryPath, "utf8");
|
|
2078
1518
|
} catch {
|
|
2079
1519
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
2080
1520
|
}
|
|
@@ -2117,7 +1557,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
2117
1557
|
for (const path of session2.files) {
|
|
2118
1558
|
const stat = statSync5(path);
|
|
2119
1559
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
2120
|
-
const text =
|
|
1560
|
+
const text = readFileSync6(path, "utf8");
|
|
2121
1561
|
for (const raw of text.split("\n")) {
|
|
2122
1562
|
if (!raw.trim()) continue;
|
|
2123
1563
|
let record;
|
|
@@ -2190,92 +1630,735 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
2190
1630
|
};
|
|
2191
1631
|
summaryJson = JSON.stringify(summary);
|
|
2192
1632
|
}
|
|
2193
|
-
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";
|
|
2194
|
-
const gaps = [];
|
|
2195
|
-
if (encryptedReasoningCount > 0) gaps.push({ kind: "encrypted_reasoning", count: encryptedReasoningCount });
|
|
2196
|
-
if (compactionCount > 0) gaps.push({ kind: "compaction", count: compactionCount });
|
|
2197
|
-
if (unreadableCount > 0) gaps.push({ kind: "unreadable", count: unreadableCount });
|
|
2198
|
-
const cwd = meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : "");
|
|
2199
|
-
return { body, transcriptLines: lines.length, outputBytes, redactionCount, gaps, attachedFiles: files2.attached, leftOutFiles: files2.leftOut, setup: files2.setup, cwd, resourceLines: files2.lines };
|
|
2200
|
-
}
|
|
2201
|
-
async function importSession(opts) {
|
|
2202
|
-
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
2203
|
-
const destination2 = new URL(base);
|
|
2204
|
-
if (destination2.username || destination2.password || destination2.protocol !== "https:" && !(destination2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(destination2.hostname))) {
|
|
2205
|
-
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
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 };
|
|
1640
|
+
}
|
|
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.");
|
|
1646
|
+
}
|
|
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));
|
|
1657
|
+
}
|
|
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}).`);
|
|
1669
|
+
}
|
|
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
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
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;
|
|
1699
|
+
}
|
|
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
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1721
|
+
// src/context.ts
|
|
1722
|
+
async function contextRequest(path, universe2) {
|
|
1723
|
+
try {
|
|
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 });
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
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) };
|
|
1733
|
+
}
|
|
1734
|
+
function memoryBody(operation, version) {
|
|
1735
|
+
return { ...operation, expectedVersion: version };
|
|
1736
|
+
}
|
|
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());
|
|
1740
|
+
}
|
|
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)}`);
|
|
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");
|
|
1768
|
+
}
|
|
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;
|
|
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);
|
|
1873
|
+
}
|
|
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;
|
|
1878
|
+
}
|
|
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
|
+
);
|
|
1900
|
+
}
|
|
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
|
+
};
|
|
1912
|
+
}
|
|
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;
|
|
1932
|
+
}
|
|
1933
|
+
|
|
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 } : {}
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
async function listCatalog(filter = {}) {
|
|
1957
|
+
return (await api("GET", `/catalog/agents${catalogQuery(filter)}`, void 0, optionalAuth())).agents;
|
|
1958
|
+
}
|
|
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.");
|
|
1976
|
+
}
|
|
1977
|
+
}
|
|
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");
|
|
1995
|
+
}
|
|
1996
|
+
|
|
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");
|
|
2007
|
+
}
|
|
2008
|
+
function readIfExists(path) {
|
|
2009
|
+
try {
|
|
2010
|
+
return readFileSync8(path, "utf8");
|
|
2011
|
+
} catch {
|
|
2012
|
+
return void 0;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
function readJson2(path) {
|
|
2016
|
+
const raw = readIfExists(path);
|
|
2017
|
+
if (raw === void 0) return void 0;
|
|
2018
|
+
try {
|
|
2019
|
+
return JSON.parse(raw);
|
|
2020
|
+
} catch {
|
|
2021
|
+
return void 0;
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
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();
|
|
2033
|
+
}
|
|
2034
|
+
return void 0;
|
|
2035
|
+
}
|
|
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;
|
|
2070
|
+
}
|
|
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 });
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
}
|
|
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
|
+
);
|
|
2111
|
+
}
|
|
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
|
+
);
|
|
2117
|
+
}
|
|
2118
|
+
function linkPath(configRoot) {
|
|
2119
|
+
return join7(configRoot, "agentsky-link.json");
|
|
2120
|
+
}
|
|
2121
|
+
function readLink(configRoot) {
|
|
2122
|
+
const link2 = readJson2(linkPath(configRoot));
|
|
2123
|
+
return link2?.agentId ? link2 : void 0;
|
|
2124
|
+
}
|
|
2125
|
+
function writeLink(configRoot, link2) {
|
|
2126
|
+
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link2, null, 2)}
|
|
2127
|
+
`);
|
|
2128
|
+
}
|
|
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;
|
|
2140
|
+
try {
|
|
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;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
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] : []
|
|
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}`);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
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
|
+
});
|
|
2209
|
+
});
|
|
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 {
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
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
|
+
);
|
|
2235
|
+
}
|
|
2236
|
+
if (opts.dryRun) {
|
|
2237
|
+
console.log("Dry run \u2014 nothing sent.");
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
if (!opts.yes && !await confirm("Launch this as a new cloud agent?")) {
|
|
2241
|
+
console.log("Aborted \u2014 nothing sent.");
|
|
2242
|
+
return;
|
|
2243
|
+
}
|
|
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 } : {}
|
|
2253
|
+
};
|
|
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
|
|
2283
|
+
});
|
|
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);
|
|
2291
|
+
}
|
|
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;
|
|
2304
|
+
try {
|
|
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
|
+
);
|
|
2206
2321
|
}
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
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}`);
|
|
2217
2335
|
}
|
|
2218
|
-
|
|
2219
|
-
const
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
});
|
|
2226
|
-
|
|
2227
|
-
const result2 = await response.json().catch(() => null);
|
|
2228
|
-
throw new Error(typeof result2?.error === "string" ? result2.error : result2?.error?.message ?? `Upload failed (${response.status}).`);
|
|
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.");
|
|
2229
2345
|
}
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
traceId: finalTraceId,
|
|
2235
|
-
url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
|
|
2236
|
-
harness: opts.session.harness,
|
|
2237
|
-
sessionId: opts.session.sessionId,
|
|
2238
|
-
transcriptFiles: opts.session.files.length,
|
|
2239
|
-
bytes: outputBytes,
|
|
2240
|
-
redactionCount,
|
|
2241
|
-
gaps,
|
|
2242
|
-
attachedFiles,
|
|
2243
|
-
leftOutFiles,
|
|
2244
|
-
setup,
|
|
2245
|
-
environment
|
|
2246
|
-
};
|
|
2247
|
-
}
|
|
2248
|
-
async function grantCall(base, key, path, body) {
|
|
2249
|
-
const response = await fetch(`${base}${path}`, {
|
|
2250
|
-
method: "POST",
|
|
2251
|
-
redirect: "error",
|
|
2252
|
-
signal: AbortSignal.timeout(3e5),
|
|
2253
|
-
headers: { authorization: `Bearer ${key}`, "content-type": "application/json", "user-agent": USER_AGENT },
|
|
2254
|
-
body: JSON.stringify(body)
|
|
2255
|
-
});
|
|
2256
|
-
const answer = await response.json().catch(() => null);
|
|
2257
|
-
if (!response.ok) throw new Error(typeof answer?.error === "string" ? answer.error : answer?.error?.message ?? `The environment upload failed (${response.status}).`);
|
|
2258
|
-
return answer;
|
|
2259
|
-
}
|
|
2260
|
-
async function uploadEnvironment(base, key, traceId, cwd, resourceLines, onProgress) {
|
|
2261
|
-
onProgress?.("Packing the working folder (secrets redacted locally)\u2026");
|
|
2262
|
-
const packed = await packEnvironment(cwd, resourcesOf(resourceLines));
|
|
2263
|
-
try {
|
|
2264
|
-
const path = `/api/traces/${encodeURIComponent(traceId)}/environment`;
|
|
2265
|
-
const { upload } = await grantCall(base, key, path, { name: packed.folder, size: packed.bytes, sha256: packed.sha256, files: packed.files });
|
|
2266
|
-
let shown = -1;
|
|
2267
|
-
await transfer(upload, packed.path, (sent, total) => {
|
|
2268
|
-
const percent = Math.floor(sent / total * 100);
|
|
2269
|
-
if (percent >= shown + 10 || sent === total) {
|
|
2270
|
-
shown = percent;
|
|
2271
|
-
onProgress?.(`Uploading the working folder\u2026 ${percent}% of ${(total / 1024 / 1024).toFixed(1)} MiB`);
|
|
2272
|
-
}
|
|
2273
|
-
});
|
|
2274
|
-
await grantCall(base, key, `${path}/complete`, {});
|
|
2275
|
-
return { folder: packed.folder, files: packed.files, bytes: packed.bytes, redacted: packed.redacted, leftOut: packed.leftOut.length };
|
|
2276
|
-
} finally {
|
|
2277
|
-
packed.dispose();
|
|
2346
|
+
if (local.droppedServers.length) {
|
|
2347
|
+
console.log(
|
|
2348
|
+
` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
2349
|
+
);
|
|
2278
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);
|
|
2279
2362
|
}
|
|
2280
2363
|
|
|
2281
2364
|
// src/channels.ts
|
|
@@ -2342,7 +2425,7 @@ import { hostname } from "os";
|
|
|
2342
2425
|
import { spawn as spawn2 } from "child_process";
|
|
2343
2426
|
|
|
2344
2427
|
// src/mcp-register.ts
|
|
2345
|
-
import { existsSync as
|
|
2428
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
2346
2429
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
2347
2430
|
import { homedir as homedir6 } from "os";
|
|
2348
2431
|
import { join as join10 } from "path";
|
|
@@ -2350,7 +2433,7 @@ import { parse as parseToml3 } from "smol-toml";
|
|
|
2350
2433
|
import { isMap, parseDocument } from "yaml";
|
|
2351
2434
|
|
|
2352
2435
|
// src/local-agents.ts
|
|
2353
|
-
import { existsSync as
|
|
2436
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2354
2437
|
import { homedir as homedir5 } from "os";
|
|
2355
2438
|
import { join as join8 } from "path";
|
|
2356
2439
|
function localAgents(env = process.env, home = homedir5()) {
|
|
@@ -2364,7 +2447,7 @@ function localAgents(env = process.env, home = homedir5()) {
|
|
|
2364
2447
|
];
|
|
2365
2448
|
}
|
|
2366
2449
|
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
2367
|
-
return localAgents(env, home).filter((agent2) =>
|
|
2450
|
+
return localAgents(env, home).filter((agent2) => existsSync7(agent2.root));
|
|
2368
2451
|
}
|
|
2369
2452
|
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
2370
2453
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
@@ -2487,6 +2570,12 @@ Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
|
|
|
2487
2570
|
cheaper or different agent to get around a refusal.
|
|
2488
2571
|
- \`get_usage\` shows the balance; check it before starting several cloud sessions
|
|
2489
2572
|
at once so the person is told once, up front, instead of after the first fails.
|
|
2573
|
+
- Anything else the person can do on agentsky.dev or in the Mac app (rename or
|
|
2574
|
+
archive a thread, edit a Library file or skill, change an agent's model, invite
|
|
2575
|
+
a member, \u2026) is an API operation: find it with \`search_api\`, read its
|
|
2576
|
+
parameters with \`describe_api\`, and run it with \`call_api\`. It acts as the
|
|
2577
|
+
person with their own key, so make only the change they asked for. Where MCP
|
|
2578
|
+
is missing, \`sky api search <words>\` and \`sky api <METHOD> <path>\` do the same.
|
|
2490
2579
|
|
|
2491
2580
|
## How to pick
|
|
2492
2581
|
|
|
@@ -2874,7 +2963,7 @@ function installSkill(agent2, opts = {}) {
|
|
|
2874
2963
|
var MCP_SERVER_NAME = SKILL_NAME;
|
|
2875
2964
|
function detectHermes(env = process.env, home = homedir6()) {
|
|
2876
2965
|
const root = env.HERMES_HOME || join10(home, ".hermes");
|
|
2877
|
-
return
|
|
2966
|
+
return existsSync8(root) ? { id: "hermes", name: "Hermes", root } : null;
|
|
2878
2967
|
}
|
|
2879
2968
|
function detectMcpClients(env = process.env, home = homedir6()) {
|
|
2880
2969
|
const hermes = detectHermes(env, home);
|
|
@@ -3034,7 +3123,7 @@ function refreshMcpRegistrations(opts, clients = detectMcpClients(), runner = sy
|
|
|
3034
3123
|
}
|
|
3035
3124
|
|
|
3036
3125
|
// src/shell-env.ts
|
|
3037
|
-
import { appendFileSync, existsSync as
|
|
3126
|
+
import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
3038
3127
|
import { homedir as homedir7 } from "os";
|
|
3039
3128
|
import { basename as basename6, dirname as dirname5, join as join11 } from "path";
|
|
3040
3129
|
var TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
@@ -3050,13 +3139,13 @@ function shellStartupFiles(env = process.env, home = homedir7(), platform = proc
|
|
|
3050
3139
|
const shell = basename6(env.SHELL ?? "");
|
|
3051
3140
|
const files2 = [];
|
|
3052
3141
|
const zshenv = join11(env.ZDOTDIR || home, ".zshenv");
|
|
3053
|
-
if (shell === "zsh" || platform === "darwin" ||
|
|
3142
|
+
if (shell === "zsh" || platform === "darwin" || existsSync9(zshenv)) files2.push({ path: zshenv, kind: "sh" });
|
|
3054
3143
|
const bashrc = join11(home, ".bashrc");
|
|
3055
|
-
if (shell === "bash" ||
|
|
3144
|
+
if (shell === "bash" || existsSync9(bashrc)) files2.push({ path: bashrc, kind: "sh" });
|
|
3056
3145
|
const bashProfile = join11(home, ".bash_profile");
|
|
3057
|
-
if (
|
|
3146
|
+
if (existsSync9(bashProfile)) files2.push({ path: bashProfile, kind: "sh" });
|
|
3058
3147
|
const fish = join11(env.XDG_CONFIG_HOME || join11(home, ".config"), "fish");
|
|
3059
|
-
if (shell === "fish" ||
|
|
3148
|
+
if (shell === "fish" || existsSync9(fish)) files2.push({ path: join11(fish, "conf.d", "agentsky.fish"), kind: "fish" });
|
|
3060
3149
|
return files2;
|
|
3061
3150
|
}
|
|
3062
3151
|
function writeShellEnv(token, opts = {}) {
|
|
@@ -3072,7 +3161,7 @@ function writeShellEnv(token, opts = {}) {
|
|
|
3072
3161
|
writeFileSync6(fish, `${header2}${token ? `set -gx ${TOKEN_ENV} ${quoted(token)}` : `set -e ${TOKEN_ENV}`}
|
|
3073
3162
|
`, { mode: 384 });
|
|
3074
3163
|
for (const file of startup) {
|
|
3075
|
-
const existing =
|
|
3164
|
+
const existing = existsSync9(file.path) ? readFileSync11(file.path, "utf8") : "";
|
|
3076
3165
|
if (existing.includes(HOOK_MARK)) continue;
|
|
3077
3166
|
const line = file.kind === "fish" ? `${HOOK_MARK}
|
|
3078
3167
|
test -f ${quoted(fish)}; and source ${quoted(fish)}
|
|
@@ -3443,12 +3532,42 @@ function clip(value, max = 120) {
|
|
|
3443
3532
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
3444
3533
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
3445
3534
|
}
|
|
3446
|
-
function
|
|
3535
|
+
function createEventRenderer(out = process.stdout) {
|
|
3536
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3537
|
+
let openStream = null;
|
|
3538
|
+
const closeLine = () => {
|
|
3539
|
+
if (openStream === null) return;
|
|
3540
|
+
out.write("\n");
|
|
3541
|
+
openStream = null;
|
|
3542
|
+
};
|
|
3543
|
+
return (event, data) => {
|
|
3544
|
+
const id = typeof data.id === "string" ? data.id : void 0;
|
|
3545
|
+
if (id) {
|
|
3546
|
+
if (seen.has(id)) return;
|
|
3547
|
+
seen.add(id);
|
|
3548
|
+
}
|
|
3549
|
+
if (event !== "agent.message") {
|
|
3550
|
+
if (event && event !== "keepalive") closeLine();
|
|
3551
|
+
renderOther(event, data);
|
|
3552
|
+
return;
|
|
3553
|
+
}
|
|
3554
|
+
const parts = Array.isArray(data.parts) ? data.parts : [];
|
|
3555
|
+
const streamId = typeof parts[0]?.stream_id === "string" ? parts[0].stream_id : null;
|
|
3556
|
+
const text = String(data.text ?? "");
|
|
3557
|
+
if (streamId === null) {
|
|
3558
|
+
closeLine();
|
|
3559
|
+
out.write(`${text}
|
|
3560
|
+
`);
|
|
3561
|
+
} else {
|
|
3562
|
+
if (openStream !== streamId) closeLine();
|
|
3563
|
+
out.write(text);
|
|
3564
|
+
openStream = streamId;
|
|
3565
|
+
}
|
|
3566
|
+
};
|
|
3567
|
+
}
|
|
3568
|
+
function renderOther(event, data) {
|
|
3447
3569
|
const part = data.part;
|
|
3448
3570
|
switch (event) {
|
|
3449
|
-
case "agent.message":
|
|
3450
|
-
console.log(String(data.text ?? ""));
|
|
3451
|
-
break;
|
|
3452
3571
|
case "agent.reasoning":
|
|
3453
3572
|
console.log(dim(`\xB7 ${clip(part?.text ?? "")}`));
|
|
3454
3573
|
break;
|
|
@@ -3480,11 +3599,12 @@ function renderEvent(event, data) {
|
|
|
3480
3599
|
}
|
|
3481
3600
|
|
|
3482
3601
|
// src/session-read.ts
|
|
3483
|
-
var
|
|
3602
|
+
var TURN_STARTS = /* @__PURE__ */ new Set(["user.message", "goal.defined", "goal.iteration"]);
|
|
3603
|
+
var TURN_TYPES = [...TURN_STARTS, "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
|
|
3484
3604
|
function latestTurn(events) {
|
|
3485
3605
|
let start = 0;
|
|
3486
3606
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
3487
|
-
if (events[index].type
|
|
3607
|
+
if (TURN_STARTS.has(events[index].type)) {
|
|
3488
3608
|
start = index + 1;
|
|
3489
3609
|
break;
|
|
3490
3610
|
}
|
|
@@ -3531,7 +3651,7 @@ import { createInterface as createInterface3 } from "readline";
|
|
|
3531
3651
|
|
|
3532
3652
|
// src/setup-local.ts
|
|
3533
3653
|
import { isUtf8 as isUtf83 } from "buffer";
|
|
3534
|
-
import { existsSync as
|
|
3654
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
3535
3655
|
import { homedir as homedir8 } from "os";
|
|
3536
3656
|
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, resolve as resolve6 } from "path";
|
|
3537
3657
|
import { parse as parseToml4 } from "smol-toml";
|
|
@@ -3572,7 +3692,7 @@ function readLocalSkills(agents) {
|
|
|
3572
3692
|
const skills2 = [];
|
|
3573
3693
|
const ignored = [];
|
|
3574
3694
|
for (const agent2 of agents) {
|
|
3575
|
-
if (!
|
|
3695
|
+
if (!existsSync10(agent2.skillsDir)) continue;
|
|
3576
3696
|
for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
|
|
3577
3697
|
const dir = join13(agent2.skillsDir, entry2.name);
|
|
3578
3698
|
let isDir2 = entry2.isDirectory();
|
|
@@ -3583,7 +3703,7 @@ function readLocalSkills(agents) {
|
|
|
3583
3703
|
isDir2 = false;
|
|
3584
3704
|
}
|
|
3585
3705
|
}
|
|
3586
|
-
if (!isDir2 || !
|
|
3706
|
+
if (!isDir2 || !existsSync10(join13(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
3587
3707
|
const name = frontmatterName(readFileSync12(join13(dir, "SKILL.md"), "utf8"));
|
|
3588
3708
|
if (!name) {
|
|
3589
3709
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
@@ -3690,7 +3810,7 @@ function findLocalSecrets(agents, home = homedir8()) {
|
|
|
3690
3810
|
|
|
3691
3811
|
// src/subscriptions.ts
|
|
3692
3812
|
import { spawn as spawn3 } from "child_process";
|
|
3693
|
-
import { existsSync as
|
|
3813
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
3694
3814
|
import { homedir as homedir9 } from "os";
|
|
3695
3815
|
import { join as join14 } from "path";
|
|
3696
3816
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -3789,7 +3909,7 @@ async function connectClaude() {
|
|
|
3789
3909
|
}
|
|
3790
3910
|
function codexSignIn(env = process.env, home = homedir9()) {
|
|
3791
3911
|
const path = join14(env.CODEX_HOME || join14(home, ".codex"), "auth.json");
|
|
3792
|
-
if (!
|
|
3912
|
+
if (!existsSync11(path)) {
|
|
3793
3913
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
3794
3914
|
}
|
|
3795
3915
|
const raw = readFileSync13(path, "utf8");
|
|
@@ -3802,7 +3922,7 @@ function codexSignIn(env = process.env, home = homedir9()) {
|
|
|
3802
3922
|
}
|
|
3803
3923
|
function cursorSignIn(env = process.env, home = homedir9()) {
|
|
3804
3924
|
const path = join14(env.XDG_CONFIG_HOME || join14(home, ".config"), "cursor", "auth.json");
|
|
3805
|
-
if (!
|
|
3925
|
+
if (!existsSync11(path)) {
|
|
3806
3926
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
3807
3927
|
}
|
|
3808
3928
|
const raw = readFileSync13(path, "utf8");
|
|
@@ -3924,7 +4044,9 @@ async function subscriptionUsage(name, account) {
|
|
|
3924
4044
|
return usage;
|
|
3925
4045
|
}
|
|
3926
4046
|
function renderSubscriptionUsage(name, usage) {
|
|
3927
|
-
const
|
|
4047
|
+
const provider = managedProviderFor(name);
|
|
4048
|
+
const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[provider];
|
|
4049
|
+
if (usage.status === "unavailable" && (usage.reason === "unsupported" || provider === "anthropic" && usage.reason === "permission")) return [`${label}: usage isn't available for this account type`];
|
|
3928
4050
|
if (usage.status === "unavailable") return [`${label}: usage unavailable (${usage.reason ?? "unknown"})`];
|
|
3929
4051
|
const head = [`${label}${usage.plan ? ` ${usage.plan}` : ""}`, usage.allowed === false ? "limit reached" : null].filter(Boolean).join(" \xB7 ");
|
|
3930
4052
|
const rows = usage.windows.map(
|
|
@@ -4399,12 +4521,25 @@ async function getSkillUsage(since) {
|
|
|
4399
4521
|
const qs = since ? `?since=${encodeURIComponent(since)}` : "";
|
|
4400
4522
|
return api("GET", `/skills/usage${qs}`);
|
|
4401
4523
|
}
|
|
4524
|
+
function nextSkills(current, skillId, verb) {
|
|
4525
|
+
const has = current.some((ref) => ref.type === "skill" && ref.skillId === skillId);
|
|
4526
|
+
if (verb === "attach") return has ? null : [...current, { type: "skill", skillId, version: "latest" }];
|
|
4527
|
+
return has ? current.filter((ref) => !(ref.type === "skill" && ref.skillId === skillId)) : null;
|
|
4528
|
+
}
|
|
4529
|
+
async function setAgentSkill(agentId, skillId, verb) {
|
|
4530
|
+
const path = `/agents/${encodeURIComponent(agentId)}`;
|
|
4531
|
+
const { agent: agent2 } = await api("GET", path);
|
|
4532
|
+
const skills2 = nextSkills(agent2.skills ?? [], skillId, verb);
|
|
4533
|
+
if (!skills2) return "unchanged";
|
|
4534
|
+
await api("PATCH", path, { skills: skills2, expectedVersion: agent2.version });
|
|
4535
|
+
return "changed";
|
|
4536
|
+
}
|
|
4402
4537
|
|
|
4403
4538
|
// src/task-export.ts
|
|
4404
4539
|
import { createHash as createHash6 } from "crypto";
|
|
4405
4540
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
4406
4541
|
import { dirname as dirname8, join as join17 } from "path";
|
|
4407
|
-
import { stringify as
|
|
4542
|
+
import { stringify as stringifyToml } from "smol-toml";
|
|
4408
4543
|
var TEXT_MAX = 2e4;
|
|
4409
4544
|
function scrub(text, names2) {
|
|
4410
4545
|
let out = redactPlainText(text).text;
|
|
@@ -4582,7 +4717,7 @@ Write your deliverables under /workspace/outputs.
|
|
|
4582
4717
|
`);
|
|
4583
4718
|
out.set(
|
|
4584
4719
|
"task.toml",
|
|
4585
|
-
|
|
4720
|
+
stringifyToml({
|
|
4586
4721
|
metadata: {
|
|
4587
4722
|
name: `agentsky-${idHash}`,
|
|
4588
4723
|
tier: opts.tier,
|
|
@@ -4969,41 +5104,43 @@ async function listBrowserProfiles() {
|
|
|
4969
5104
|
const { data } = await api("GET", "/browser_profiles");
|
|
4970
5105
|
return data;
|
|
4971
5106
|
}
|
|
4972
|
-
async function getBrowserProfile(scope) {
|
|
4973
|
-
const profiles = await listBrowserProfiles();
|
|
4974
|
-
const profile = profiles.find((entry2) => entry2.scope === scope);
|
|
4975
|
-
if (!profile) throw new ApiError(404, "not_found", "browser profile not found");
|
|
4976
|
-
return profile;
|
|
4977
|
-
}
|
|
4978
5107
|
async function importBrowserProfileState(scope, state) {
|
|
4979
5108
|
return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/imports`, state);
|
|
4980
5109
|
}
|
|
4981
|
-
async function listBrowserProfileSites(
|
|
4982
|
-
const
|
|
4983
|
-
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 ?? [];
|
|
4984
5113
|
}
|
|
4985
|
-
async function
|
|
4986
|
-
const sites = await listBrowserProfileSites(
|
|
5114
|
+
async function removeBrowserProfileSite(site) {
|
|
5115
|
+
const sites = await listBrowserProfileSites();
|
|
4987
5116
|
const row = sites.find((entry2) => entry2.site === site) ?? sites.find((entry2) => entry2.site_id === site);
|
|
4988
|
-
if (!row) throw new ApiError(404, "not_found", "site not found in
|
|
4989
|
-
|
|
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)}`);
|
|
4990
5119
|
}
|
|
4991
|
-
async function
|
|
4992
|
-
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;
|
|
4993
5126
|
}
|
|
4994
|
-
async function
|
|
4995
|
-
|
|
4996
|
-
const [identifier, query] = reference.split("?");
|
|
4997
|
-
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)}`);
|
|
4998
5129
|
}
|
|
4999
5130
|
function renderBrowserProfileSite(site) {
|
|
5000
5131
|
const who = site.signed_in_by?.email ?? site.signed_in_by?.name ?? "unknown";
|
|
5001
5132
|
const used = site.last_used_at ? `used ${site.last_used_at.slice(0, 10)}` : "never used";
|
|
5002
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}` : ""}`;
|
|
5003
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
|
+
}
|
|
5004
5139
|
function renderBrowserProfile(profile) {
|
|
5005
|
-
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}`];
|
|
5006
5142
|
for (const site of profile.sites) lines.push(` ${renderBrowserProfileSite(site)}`);
|
|
5143
|
+
for (const share of profile.shares ?? []) lines.push(` ${renderBrowserProfileShare(share)}`);
|
|
5007
5144
|
return lines.join("\n");
|
|
5008
5145
|
}
|
|
5009
5146
|
|
|
@@ -5159,6 +5296,148 @@ function renderLibraryArtifact(artifact2) {
|
|
|
5159
5296
|
return `${artifact2.id} ${artifact2.name} ${version}${artifact2.shared ? " shared" : ""}`;
|
|
5160
5297
|
}
|
|
5161
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
|
+
|
|
5367
|
+
// src/api-command.ts
|
|
5368
|
+
import { readFileSync as readFileSync17, writeFileSync as writeFileSync9 } from "fs";
|
|
5369
|
+
var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
5370
|
+
function readData(data, stdin = () => readFileSync17(0, "utf8")) {
|
|
5371
|
+
if (data === void 0) return void 0;
|
|
5372
|
+
const raw = data === "-" ? stdin() : data.startsWith("@") ? readFileSync17(data.slice(1), "utf8") : data;
|
|
5373
|
+
try {
|
|
5374
|
+
JSON.parse(raw);
|
|
5375
|
+
} catch {
|
|
5376
|
+
throw new Error("--data must be JSON (inline, @file or - for stdin)");
|
|
5377
|
+
}
|
|
5378
|
+
return raw;
|
|
5379
|
+
}
|
|
5380
|
+
function requestPath(path, query = []) {
|
|
5381
|
+
if (!path.startsWith("/") || path.startsWith("//")) throw new Error("path must start with /, e.g. /threads");
|
|
5382
|
+
const clean = path.replace(/^\/v1(?=\/)/, "");
|
|
5383
|
+
const params = new URLSearchParams();
|
|
5384
|
+
for (const pair of query) {
|
|
5385
|
+
const at = pair.indexOf("=");
|
|
5386
|
+
if (at <= 0) throw new Error(`--query needs key=value, got "${pair}"`);
|
|
5387
|
+
params.append(pair.slice(0, at), pair.slice(at + 1));
|
|
5388
|
+
}
|
|
5389
|
+
const sep4 = clean.includes("?") ? "&" : "?";
|
|
5390
|
+
const encoded = params.toString();
|
|
5391
|
+
return encoded ? `${clean}${sep4}${encoded}` : clean;
|
|
5392
|
+
}
|
|
5393
|
+
function matchOperations(doc, words) {
|
|
5394
|
+
const needles = words.map((w) => w.toLowerCase());
|
|
5395
|
+
const out = [];
|
|
5396
|
+
for (const [path, item] of Object.entries(doc.paths ?? {})) {
|
|
5397
|
+
for (const [method, op] of Object.entries(item)) {
|
|
5398
|
+
if (!API_METHODS.includes(method.toUpperCase())) continue;
|
|
5399
|
+
const hay = `${method} ${path} ${op.summary ?? ""} ${op.operationId ?? ""}`.toLowerCase();
|
|
5400
|
+
if (needles.every((n) => hay.includes(n))) out.push({ method: method.toUpperCase(), path, summary: op.summary, operationId: op.operationId });
|
|
5401
|
+
}
|
|
5402
|
+
}
|
|
5403
|
+
return out.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
5404
|
+
}
|
|
5405
|
+
async function searchApi(words) {
|
|
5406
|
+
const res = await apiRaw("GET", "/openapi.json", { auth: false });
|
|
5407
|
+
if (!res.ok) throw new Error(`could not read the API reference (${res.status})`);
|
|
5408
|
+
const ops = matchOperations(await res.json(), words);
|
|
5409
|
+
if (!ops.length) {
|
|
5410
|
+
console.log("no operations match");
|
|
5411
|
+
return;
|
|
5412
|
+
}
|
|
5413
|
+
for (const op of ops) console.log(`${op.method.padEnd(6)} ${op.path}${op.summary ? ` \u2014 ${op.summary}` : ""}`);
|
|
5414
|
+
}
|
|
5415
|
+
async function callApi(method, path, opts) {
|
|
5416
|
+
const verb = method.toUpperCase();
|
|
5417
|
+
if (!API_METHODS.includes(verb)) throw new Error(`method must be one of ${API_METHODS.join(", ")} or "search"`);
|
|
5418
|
+
const body = readData(opts.data);
|
|
5419
|
+
if (body !== void 0 && verb === "GET") throw new Error("a GET takes no --data; use --query");
|
|
5420
|
+
const res = await apiRaw(verb, requestPath(path, opts.query), { body, idempotencyKey: opts.idempotencyKey, universe: opts.workspace });
|
|
5421
|
+
const type = res.headers.get("content-type") ?? "";
|
|
5422
|
+
if (opts.output) {
|
|
5423
|
+
writeFileSync9(opts.output, new Uint8Array(await res.arrayBuffer()));
|
|
5424
|
+
console.error(`${res.status} \u2192 ${opts.output}`);
|
|
5425
|
+
} else if (/json/.test(type)) {
|
|
5426
|
+
const text = await res.text();
|
|
5427
|
+
let printed = text;
|
|
5428
|
+
try {
|
|
5429
|
+
printed = JSON.stringify(JSON.parse(text), null, 2);
|
|
5430
|
+
} catch {
|
|
5431
|
+
}
|
|
5432
|
+
(res.ok ? console.log : console.error)(printed);
|
|
5433
|
+
} else if (/^text\/|xml|csv|markdown/.test(type) || !type) {
|
|
5434
|
+
(res.ok ? console.log : console.error)(await res.text());
|
|
5435
|
+
} else {
|
|
5436
|
+
console.error(`${res.status}: ${type} body (${res.headers.get("content-length") ?? "?"} bytes); pass --output <file> to save it`);
|
|
5437
|
+
}
|
|
5438
|
+
if (!res.ok && res.status !== 304) process.exitCode = 1;
|
|
5439
|
+
}
|
|
5440
|
+
|
|
5162
5441
|
// src/billing-plans.ts
|
|
5163
5442
|
var readCodingPlan = () => api("GET", "/billing/coding-plan");
|
|
5164
5443
|
function joinCodingPlan(dollars2, requestId) {
|
|
@@ -5233,20 +5512,31 @@ function wholeNumber2(flag, value) {
|
|
|
5233
5512
|
return Number(value.trim());
|
|
5234
5513
|
}
|
|
5235
5514
|
|
|
5515
|
+
// src/workspace-flag.ts
|
|
5516
|
+
function workspaceFlagIsCommands(argv, commandPath) {
|
|
5517
|
+
let at = 0;
|
|
5518
|
+
let matched = 0;
|
|
5519
|
+
for (; at < argv.length && matched < commandPath.length; at += 1) {
|
|
5520
|
+
if (argv[at] === commandPath[matched]) matched += 1;
|
|
5521
|
+
}
|
|
5522
|
+
if (matched < commandPath.length) return false;
|
|
5523
|
+
return argv.slice(at).some((arg) => arg === "--workspace" || arg.startsWith("--workspace="));
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5236
5526
|
// src/harness.ts
|
|
5237
|
-
import { existsSync as
|
|
5238
|
-
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";
|
|
5239
5529
|
var HARNESS_FILE = "harness.json";
|
|
5240
5530
|
function harnessFilePath(dir) {
|
|
5241
|
-
return
|
|
5531
|
+
return join19(resolve8(dir), HARNESS_FILE);
|
|
5242
5532
|
}
|
|
5243
5533
|
function readHarnessFile(dir) {
|
|
5244
5534
|
const path = harnessFilePath(dir);
|
|
5245
|
-
if (!
|
|
5535
|
+
if (!existsSync12(path)) {
|
|
5246
5536
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
5247
5537
|
}
|
|
5248
5538
|
try {
|
|
5249
|
-
return JSON.parse(
|
|
5539
|
+
return JSON.parse(readFileSync18(path, "utf8"));
|
|
5250
5540
|
} catch (err) {
|
|
5251
5541
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5252
5542
|
}
|
|
@@ -5273,11 +5563,11 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
5273
5563
|
`;
|
|
5274
5564
|
function harnessInit(dir) {
|
|
5275
5565
|
const path = harnessFilePath(dir);
|
|
5276
|
-
if (
|
|
5566
|
+
if (existsSync12(path)) {
|
|
5277
5567
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
5278
5568
|
}
|
|
5279
5569
|
mkdirSync6(dirname9(path), { recursive: true });
|
|
5280
|
-
|
|
5570
|
+
writeFileSync10(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
5281
5571
|
`);
|
|
5282
5572
|
console.log(`Created ${path}`);
|
|
5283
5573
|
console.log(INIT_COMMENT);
|
|
@@ -5410,9 +5700,16 @@ function printModelBilling(session2) {
|
|
|
5410
5700
|
}
|
|
5411
5701
|
var program = new Command();
|
|
5412
5702
|
program.name("sky").description("Launch and talk to long-lived agents \u2014 agentsky.dev").version(CLI_VERSION).option("--workspace <slug>", "workspace to act in (overrides sky universe switch and SKY_UNIVERSE; alias of --universe on sub-commands)");
|
|
5413
|
-
program.hook("preAction", () => {
|
|
5703
|
+
program.hook("preAction", (_root, action) => {
|
|
5414
5704
|
const workspace2 = program.opts().workspace;
|
|
5415
|
-
if (workspace2)
|
|
5705
|
+
if (!workspace2) return;
|
|
5706
|
+
const path = [];
|
|
5707
|
+
for (let command = action; command && command !== program; command = command.parent) path.unshift(command.name());
|
|
5708
|
+
if (action.options.some((option) => option.long === "--workspace") && workspaceFlagIsCommands(process.argv.slice(2), path)) {
|
|
5709
|
+
action.setOptionValue("workspace", workspace2);
|
|
5710
|
+
return;
|
|
5711
|
+
}
|
|
5712
|
+
process.env.SKY_WORKSPACE = workspace2;
|
|
5416
5713
|
});
|
|
5417
5714
|
var lastProgressDecile = -1;
|
|
5418
5715
|
function uploadProgress(sent, total) {
|
|
@@ -5447,6 +5744,9 @@ auth.command("whoami").description("who the current token acts as").action(
|
|
|
5447
5744
|
console.log(
|
|
5448
5745
|
`${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (default)" : ""} \xB7 scopes ${who.scopes.join(",")}`
|
|
5449
5746
|
);
|
|
5747
|
+
if (who.credential_universe) {
|
|
5748
|
+
console.log(` this key is fixed to workspace ${who.credential_universe}: it cannot act in other workspaces or create new ones (make an account-wide key with \`sky keys create\`, or \`sky auth login\`)`);
|
|
5749
|
+
}
|
|
5450
5750
|
})
|
|
5451
5751
|
);
|
|
5452
5752
|
auth.command("connect <provider>").description("connect a model subscription: claude (setup-token) or chatgpt (Codex sign-in); --browser signs any plan in on its own page").option("-y, --yes", "skip the consent prompt").option("--browser", "sign in on the provider's page instead of using this machine's sign-in (claude, chatgpt, cursor, grok, muse)").option("--account <id>", "with --browser: re-sign an additional account, or `new` to add one").option("--label <label>", "with --browser: name the account").option("--no-open", "with --browser: print the sign-in link without opening it").action(
|
|
@@ -5583,6 +5883,12 @@ workspace.command("list").action(
|
|
|
5583
5883
|
})
|
|
5584
5884
|
);
|
|
5585
5885
|
workspace.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
|
|
5886
|
+
async function renameWorkspaceCommand(slug, name) {
|
|
5887
|
+
const { universe: renamed } = await api("PATCH", `/universes/${encodeURIComponent(slug)}`, { name: name.join(" ") });
|
|
5888
|
+
console.log(`${renamed.slug} is now "${renamed.name}"`);
|
|
5889
|
+
}
|
|
5890
|
+
universe.command("rename <slug> <name...>").description("rename a workspace you own (its slug stays)").action(run(renameWorkspaceCommand));
|
|
5891
|
+
workspace.command("rename <slug> <name...>").description("rename a workspace you own (alias of universe rename)").action(run(renameWorkspaceCommand));
|
|
5586
5892
|
var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
|
|
5587
5893
|
async function policyScope(opts) {
|
|
5588
5894
|
return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
|
|
@@ -5693,7 +5999,7 @@ agent.command("destroy <id>").description("delete the spec; requires all session
|
|
|
5693
5999
|
console.log(`Destroyed ${id}`);
|
|
5694
6000
|
})
|
|
5695
6001
|
);
|
|
5696
|
-
agent.command("message <id> [text]").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
|
|
6002
|
+
agent.command("message <id> [text]").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks); a file_\u2026 id attaches that stored file").option("--file-id <id...>", "attach files already uploaded (`sky files upload`), by id, without uploading again").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
|
|
5697
6003
|
run(
|
|
5698
6004
|
async (id, text, opts) => sendAndFollow(await resolveSessionRef(id), text ?? "", opts)
|
|
5699
6005
|
)
|
|
@@ -5807,7 +6113,7 @@ session.command("delete <id>").description("permanently delete the session, its
|
|
|
5807
6113
|
console.log(`Deleted ${id}`);
|
|
5808
6114
|
})
|
|
5809
6115
|
);
|
|
5810
|
-
session.command("message <ref> [text]").description("send one message (session id, or agent id = its default session)").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
|
|
6116
|
+
session.command("message <ref> [text]").description("send one message (session id, or agent id = its default session)").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks); a file_\u2026 id attaches that stored file").option("--file-id <id...>", "attach files already uploaded (`sky files upload`), by id, without uploading again").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
|
|
5811
6117
|
run(
|
|
5812
6118
|
async (ref, text, opts) => sendAndFollow(await resolveSessionRef(ref), text ?? "", opts)
|
|
5813
6119
|
)
|
|
@@ -6000,7 +6306,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
|
|
|
6000
6306
|
run(async (ref, path, opts) => {
|
|
6001
6307
|
const bytes = await readFile(await resolveSessionRef(ref), path);
|
|
6002
6308
|
if (opts.out) {
|
|
6003
|
-
|
|
6309
|
+
writeFileSync11(opts.out, bytes);
|
|
6004
6310
|
console.error(`saved ${bytes.length} bytes to ${opts.out}`);
|
|
6005
6311
|
} else process.stdout.write(bytes);
|
|
6006
6312
|
})
|
|
@@ -6017,7 +6323,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
|
|
|
6017
6323
|
console.error(`${meta.repo}: ${meta.commits.length} commit${meta.commits.length === 1 ? "" : "s"} since ${meta.base_branch || "the imported copy"} (${meta.base_commit.slice(0, 12)})`);
|
|
6018
6324
|
if (!patch.length) console.error("nothing changed");
|
|
6019
6325
|
else if (opts.out) {
|
|
6020
|
-
|
|
6326
|
+
writeFileSync11(opts.out, patch);
|
|
6021
6327
|
console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
|
|
6022
6328
|
} else process.stdout.write(patch);
|
|
6023
6329
|
})
|
|
@@ -6071,9 +6377,10 @@ function interactionMode(opts) {
|
|
|
6071
6377
|
}
|
|
6072
6378
|
async function uploadAttachments(opts) {
|
|
6073
6379
|
const files2 = [];
|
|
6074
|
-
|
|
6075
|
-
|
|
6076
|
-
|
|
6380
|
+
const refs = [...opts.image ?? [], ...opts.file ?? []].map((ref) => ({ ref, byId: false }));
|
|
6381
|
+
for (const { ref, byId } of [...refs, ...(opts.fileId ?? []).map((ref2) => ({ ref: ref2, byId: true }))]) {
|
|
6382
|
+
const { file, uploaded } = await attachment(ref, { byId });
|
|
6383
|
+
console.error(uploaded ? `uploaded ${file.filename} \u2192 ${file.id}` : `attaching ${file.id} (${file.filename})`);
|
|
6077
6384
|
files2.push(file);
|
|
6078
6385
|
}
|
|
6079
6386
|
return files2;
|
|
@@ -6090,9 +6397,10 @@ async function sendAndFollow(sessionId, text, opts) {
|
|
|
6090
6397
|
const controller = new AbortController();
|
|
6091
6398
|
let opened = null;
|
|
6092
6399
|
if (!opts.detach) {
|
|
6400
|
+
const render = createEventRenderer();
|
|
6093
6401
|
opened = (async () => {
|
|
6094
6402
|
for await (const frame of streamEvents(sessionId, controller.signal)) {
|
|
6095
|
-
|
|
6403
|
+
render(frame.event, frame.data);
|
|
6096
6404
|
if (turnOver(frame)) {
|
|
6097
6405
|
controller.abort();
|
|
6098
6406
|
break;
|
|
@@ -6113,10 +6421,11 @@ async function chatLoop(sessionId, opts = {}) {
|
|
|
6113
6421
|
const controller = new AbortController();
|
|
6114
6422
|
const rl = createInterface6({ input: process.stdin, output: process.stdout, prompt: "> " });
|
|
6115
6423
|
let system = opts.system;
|
|
6424
|
+
const render = createEventRenderer();
|
|
6116
6425
|
void (async () => {
|
|
6117
6426
|
try {
|
|
6118
6427
|
for await (const frame of streamEvents(sessionId, controller.signal)) {
|
|
6119
|
-
|
|
6428
|
+
render(frame.event, frame.data);
|
|
6120
6429
|
if (frame.event === "session.status_terminated") rl.close();
|
|
6121
6430
|
else if (frame.event === "session.status_idle") rl.prompt();
|
|
6122
6431
|
}
|
|
@@ -6223,10 +6532,32 @@ secrets.command("unset <id> <key>").action(
|
|
|
6223
6532
|
console.log(`unset ${key}`);
|
|
6224
6533
|
})
|
|
6225
6534
|
);
|
|
6226
|
-
|
|
6535
|
+
async function launchFromFile(path, spec, opts) {
|
|
6536
|
+
const { vcpus, memoryMb, ...specBody } = spec;
|
|
6537
|
+
const body = {
|
|
6538
|
+
...specBody,
|
|
6539
|
+
...opts.type ? { agentType: opts.type } : {},
|
|
6540
|
+
...opts.llm ? { llm: opts.llm } : {}
|
|
6541
|
+
};
|
|
6542
|
+
const { agent: created } = await api("POST", "/agents", body);
|
|
6543
|
+
const { session: session2 } = await api("POST", "/sessions", {
|
|
6544
|
+
agent: created.id,
|
|
6545
|
+
...opts.start ? {} : { provision: "deferred" },
|
|
6546
|
+
...vcpus !== void 0 ? { vcpus } : {},
|
|
6547
|
+
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
6548
|
+
});
|
|
6549
|
+
stampId(path, created.id);
|
|
6550
|
+
console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
|
|
6551
|
+
printModelBilling(session2);
|
|
6552
|
+
console.log(opts.start ? " its first session is starting now" : " its first session starts on the first message");
|
|
6553
|
+
if (!spec.llm && !opts.llm) console.log(` model: ${created.llm}, the ${created.agentType} default \u2014 set llm = "\u2026" in ${basename9(path)} and run \`sky deploy\` to change it`);
|
|
6554
|
+
console.log(` sky agent message ${created.id} "hello"`);
|
|
6555
|
+
console.log(` sky agent chat ${created.id}`);
|
|
6556
|
+
}
|
|
6557
|
+
program.command("launch").description("scaffold ./agent.toml if absent, create the agent and its first session (idle until the first message), print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").option("--start", "boot the first session now instead of on the first message").action(
|
|
6227
6558
|
run(async (opts) => {
|
|
6228
6559
|
const path = resolve9(opts.file);
|
|
6229
|
-
if (!
|
|
6560
|
+
if (!existsSync13(path)) {
|
|
6230
6561
|
const name = basename9(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
6231
6562
|
scaffoldAgentFile(path, name);
|
|
6232
6563
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
@@ -6236,55 +6567,27 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
|
|
|
6236
6567
|
console.log(`${file.id} already launched \u2014 use \`sky deploy\` to re-apply.`);
|
|
6237
6568
|
return;
|
|
6238
6569
|
}
|
|
6239
|
-
|
|
6240
|
-
const body = {
|
|
6241
|
-
...specBody,
|
|
6242
|
-
...opts.type ? { agentType: opts.type } : {},
|
|
6243
|
-
...opts.llm ? { llm: opts.llm } : {}
|
|
6244
|
-
};
|
|
6245
|
-
const { agent: created } = await api("POST", "/agents", body);
|
|
6246
|
-
console.log("Starting its runtime\u2026");
|
|
6247
|
-
const { session: session2 } = await api("POST", "/sessions", {
|
|
6248
|
-
agent: created.id,
|
|
6249
|
-
...vcpus !== void 0 ? { vcpus } : {},
|
|
6250
|
-
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
6251
|
-
});
|
|
6252
|
-
stampId(path, created.id);
|
|
6253
|
-
console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
|
|
6254
|
-
printModelBilling(session2);
|
|
6255
|
-
console.log(` sky agent message ${created.id} "hello"`);
|
|
6256
|
-
console.log(` sky agent chat ${created.id}`);
|
|
6570
|
+
await launchFromFile(path, spec, opts);
|
|
6257
6571
|
})
|
|
6258
6572
|
);
|
|
6259
|
-
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
6573
|
+
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent: an unchanged file changes nothing)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
6260
6574
|
run(async (opts) => {
|
|
6261
6575
|
const path = resolve9(opts.file);
|
|
6262
6576
|
const { file, spec } = readAgentFile(path);
|
|
6263
6577
|
if (!file.id) {
|
|
6264
6578
|
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
6265
|
-
|
|
6266
|
-
const { agent: created } = await api("POST", "/agents", specBody);
|
|
6267
|
-
await api("POST", "/sessions", {
|
|
6268
|
-
agent: created.id,
|
|
6269
|
-
...vcpus !== void 0 ? { vcpus } : {},
|
|
6270
|
-
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
6271
|
-
});
|
|
6272
|
-
stampId(path, created.id);
|
|
6273
|
-
console.log(`Launched ${created.id}`);
|
|
6579
|
+
await launchFromFile(path, spec, {});
|
|
6274
6580
|
return;
|
|
6275
6581
|
}
|
|
6582
|
+
const ref = encodeURIComponent(file.id);
|
|
6583
|
+
const { agent: before } = await api("GET", `/agents/${ref}`);
|
|
6584
|
+
let promptChanged = false;
|
|
6276
6585
|
if (spec.prompt !== void 0) {
|
|
6277
|
-
await api("PUT", `/agents/${
|
|
6278
|
-
|
|
6279
|
-
});
|
|
6586
|
+
const saved = await api("PUT", `/agents/${ref}/prompt`, { prompt: spec.prompt });
|
|
6587
|
+
promptChanged = saved.unchanged === false;
|
|
6280
6588
|
}
|
|
6281
|
-
await api("PATCH", `/agents/${
|
|
6282
|
-
|
|
6283
|
-
...spec.capabilities ? { capabilities: spec.capabilities } : {},
|
|
6284
|
-
...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
6285
|
-
...spec.skills ? { skills: spec.skills } : {}
|
|
6286
|
-
});
|
|
6287
|
-
console.log(`Deployed ${file.id} (prompt + settings re-applied)`);
|
|
6589
|
+
const { agent: after } = await api("PATCH", `/agents/${ref}`, deployPatch(spec));
|
|
6590
|
+
for (const line of deployReport(file.id, spec, before, after, promptChanged)) console.log(line);
|
|
6288
6591
|
})
|
|
6289
6592
|
);
|
|
6290
6593
|
program.command("clone").description("clone your local agent (Claude Code, \u2026) into the cloud; re-run to sync its prompt").option("--harness <type>", "claude_code | codex | openclaw (default: detected)").option("--new", "always launch a fresh agent, even if this machine is linked").option("--dry-run", "print the plan and send nothing").option("-y, --yes", "skip the confirmation").option("--open", "open the agent page when done").action(
|
|
@@ -6525,14 +6828,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
6525
6828
|
run(async (taskId, path, opts) => {
|
|
6526
6829
|
const bytes = await readTaskFile(taskId, path);
|
|
6527
6830
|
if (opts.out) {
|
|
6528
|
-
|
|
6831
|
+
writeFileSync11(opts.out, bytes);
|
|
6529
6832
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6530
6833
|
} else process.stdout.write(bytes);
|
|
6531
6834
|
})
|
|
6532
6835
|
);
|
|
6533
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(
|
|
6534
6837
|
run(async (taskId, path, local) => {
|
|
6535
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
6838
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync19(local), mediaTypeFor(local));
|
|
6536
6839
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
6537
6840
|
})
|
|
6538
6841
|
);
|
|
@@ -6605,7 +6908,7 @@ task.command("verdict <task> <did-the-job>").description("record whether the tas
|
|
|
6605
6908
|
console.log(`Recorded verdict on task ${updated.id}: did_the_job=${value === "yes"}`);
|
|
6606
6909
|
})
|
|
6607
6910
|
);
|
|
6608
|
-
var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent
|
|
6911
|
+
var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent (sky skills attach, or [[skills]] in agent.toml)");
|
|
6609
6912
|
skills.command("upload <dir>").description("upload a skill directory (SKILL.md at its root) as a new skill").option("--name <name>", "display name (default: the directory's name)").option("--json", "print the stored skill as JSON").action(
|
|
6610
6913
|
run(async (dir, opts) => {
|
|
6611
6914
|
const { skill } = await uploadSkill(dir, opts.name);
|
|
@@ -6613,7 +6916,7 @@ skills.command("upload <dir>").description("upload a skill directory (SKILL.md a
|
|
|
6613
6916
|
console.log(JSON.stringify(skill));
|
|
6614
6917
|
return;
|
|
6615
6918
|
}
|
|
6616
|
-
console.log(`Uploaded ${skill.id} (${skill.displayName ?? dir}). Attach it in agent.toml:`);
|
|
6919
|
+
console.log(`Uploaded ${skill.id} (${skill.displayName ?? dir}). Attach it: sky skills attach ${skill.id} --agent <id>, or in agent.toml:`);
|
|
6617
6920
|
console.log(` [[skills]]
|
|
6618
6921
|
type = "skill"
|
|
6619
6922
|
skillId = "${skill.id}"`);
|
|
@@ -6626,6 +6929,18 @@ skills.command("list").description("the workspace's stored skills").action(
|
|
|
6626
6929
|
for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
|
|
6627
6930
|
})
|
|
6628
6931
|
);
|
|
6932
|
+
for (const [verb, blurb] of [
|
|
6933
|
+
["attach", "add a stored skill to an agent; its new sessions load it"],
|
|
6934
|
+
["detach", "remove a skill from an agent; its new sessions no longer load it"]
|
|
6935
|
+
]) {
|
|
6936
|
+
skills.command(`${verb} <skill>`).description(blurb).requiredOption("--agent <id>", "the agent").action(
|
|
6937
|
+
run(async (skillId, opts) => {
|
|
6938
|
+
const outcome = await setAgentSkill(opts.agent, skillId, verb);
|
|
6939
|
+
if (outcome === "unchanged") console.log(`${skillId} is ${verb === "attach" ? "already" : "not"} attached to ${opts.agent}`);
|
|
6940
|
+
else console.log(`${verb === "attach" ? "Attached" : "Detached"} ${skillId} ${verb === "attach" ? "to" : "from"} ${opts.agent} \u2014 new sessions ${verb === "attach" ? "load it" : "no longer load it"}; running sessions keep the skills they started with`);
|
|
6941
|
+
})
|
|
6942
|
+
);
|
|
6943
|
+
}
|
|
6629
6944
|
skills.command("usage").description("view and miss counts per skill name in this workspace").option("--since <iso>", `start date (ISO 8601); default is the last 30 days`).option("--json", "print JSON").action(
|
|
6630
6945
|
run(async (opts) => {
|
|
6631
6946
|
const summary = await getSkillUsage(opts.since);
|
|
@@ -6684,7 +6999,7 @@ library.command("list").description("your Library, newest first").option("--sear
|
|
|
6684
6999
|
);
|
|
6685
7000
|
library.command("upload <path>").description("save a file from this computer to your Library").option("--workspace <key>", "place it in this workspace").action(
|
|
6686
7001
|
run(async (path, opts) => {
|
|
6687
|
-
const bytes =
|
|
7002
|
+
const bytes = readFileSync19(path);
|
|
6688
7003
|
const { artifact: artifact2 } = await uploadToLibrary({ bytes, name: basename9(path), mediaType: "application/octet-stream" }, `sky-upload-${crypto.randomUUID()}`, opts.workspace);
|
|
6689
7004
|
console.log(renderLibraryArtifact(artifact2));
|
|
6690
7005
|
})
|
|
@@ -6715,7 +7030,7 @@ library.command("place <id> <workspace>").description('place your artifact in a
|
|
|
6715
7030
|
library.command("download <id> <out>").description("save one version's bytes to a file").option("--version <n>", "a saved version (the current one when omitted)").action(
|
|
6716
7031
|
run(async (id, out, opts) => {
|
|
6717
7032
|
const { bytes } = await downloadLibraryArtifact(id, opts.version ? Number(opts.version) : void 0);
|
|
6718
|
-
|
|
7033
|
+
writeFileSync11(out, bytes);
|
|
6719
7034
|
console.log(`wrote ${bytes.length} bytes to ${out}`);
|
|
6720
7035
|
})
|
|
6721
7036
|
);
|
|
@@ -6736,7 +7051,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
|
|
|
6736
7051
|
run(async (id, path, opts) => {
|
|
6737
7052
|
const bytes = await readArtifactFile(id, path);
|
|
6738
7053
|
if (opts.out) {
|
|
6739
|
-
|
|
7054
|
+
writeFileSync11(opts.out, bytes);
|
|
6740
7055
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6741
7056
|
} else process.stdout.write(bytes);
|
|
6742
7057
|
})
|
|
@@ -6753,6 +7068,35 @@ artifact.command("rm <id>").description("delete an artifact").action(
|
|
|
6753
7068
|
console.log(`Deleted ${id}`);
|
|
6754
7069
|
})
|
|
6755
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
|
+
}));
|
|
6756
7100
|
function routineLine(r) {
|
|
6757
7101
|
const target = r.target.type === "session" ? `\u2192 ${r.target.session_id}` : `+ ${r.target.agent}`;
|
|
6758
7102
|
const cron = r.schedule ? `${r.schedule.expression} ${r.schedule.timezone}` : "manual-only";
|
|
@@ -6835,6 +7179,12 @@ for (const [verb, blurb] of [
|
|
|
6835
7179
|
})
|
|
6836
7180
|
);
|
|
6837
7181
|
}
|
|
7182
|
+
routine.command("rm <id>").description("delete a routine: it never runs again; the sessions it started are kept").action(
|
|
7183
|
+
run(async (id) => {
|
|
7184
|
+
await api("DELETE", `/routines/${encodeURIComponent(id)}`);
|
|
7185
|
+
console.log(`Deleted routine ${id}. It will not run again; sessions it started are kept.`);
|
|
7186
|
+
})
|
|
7187
|
+
);
|
|
6838
7188
|
routine.command("runs [id]").description("every fire attempt, newest first \u2014 of one routine, or all of them").option("--failed", "only runs that failed to start").option("--limit <n>", "how many (default 20)").action(
|
|
6839
7189
|
run(async (id, opts) => {
|
|
6840
7190
|
const params = new URLSearchParams({ limit: opts.limit ?? "20" });
|
|
@@ -7052,7 +7402,7 @@ usageOptions(
|
|
|
7052
7402
|
run(async (opts) => {
|
|
7053
7403
|
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
7054
7404
|
if (opts.out) {
|
|
7055
|
-
|
|
7405
|
+
writeFileSync11(opts.out, bytes);
|
|
7056
7406
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
7057
7407
|
} else {
|
|
7058
7408
|
process.stdout.write(bytes);
|
|
@@ -7130,10 +7480,10 @@ memory.command("save <text...>").description("add an entry").action(run(async (t
|
|
|
7130
7480
|
memory.command("edit <index> <text...>").description("replace entry <index>").action(run(async (index, text) => writeMemory({ op: "update", index: memoryIndex(index), text: text.join(" ") })));
|
|
7131
7481
|
memory.command("rm <index>").description("remove entry <index>").action(run(async (index) => writeMemory({ op: "remove", index: memoryIndex(index) })));
|
|
7132
7482
|
memory.command("clear").description("remove every entry").action(run(async () => writeMemory({ op: "clear" })));
|
|
7133
|
-
var context = program.command("context").description("search
|
|
7134
|
-
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(
|
|
7135
7485
|
run(async (words, opts) => {
|
|
7136
|
-
const page = await
|
|
7486
|
+
const page = await searchContext(words.join(" "), opts);
|
|
7137
7487
|
console.log(opts.json ? JSON.stringify(page, null, 2) : renderContextPage(page));
|
|
7138
7488
|
})
|
|
7139
7489
|
);
|
|
@@ -7141,7 +7491,7 @@ context.command("read <ref>").description("read a search hit in full: <ref> is t
|
|
|
7141
7491
|
run(async (ref, opts) => {
|
|
7142
7492
|
const params = new URLSearchParams({ ref });
|
|
7143
7493
|
if (opts.offset) params.set("offset", opts.offset);
|
|
7144
|
-
const read = await
|
|
7494
|
+
const read = await contextRequest(`/context/read?${params}`);
|
|
7145
7495
|
console.log(read.text);
|
|
7146
7496
|
if (read.nextOffset !== null) console.error(`more: --offset ${read.nextOffset}`);
|
|
7147
7497
|
})
|
|
@@ -7451,7 +7801,7 @@ mac.command("take-back <mount>").description("end a folder, browser window, scre
|
|
|
7451
7801
|
})
|
|
7452
7802
|
);
|
|
7453
7803
|
var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
|
|
7454
|
-
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(
|
|
7455
7805
|
run(async (opts) => {
|
|
7456
7806
|
const profiles = await listBrowserProfiles();
|
|
7457
7807
|
if (opts.json) {
|
|
@@ -7462,9 +7812,9 @@ browserProfile.command("list").description("your workspace's browser profiles: t
|
|
|
7462
7812
|
for (const p of profiles) console.log(renderBrowserProfile(p));
|
|
7463
7813
|
})
|
|
7464
7814
|
);
|
|
7465
|
-
browserProfile.command("sites
|
|
7466
|
-
run(async (
|
|
7467
|
-
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();
|
|
7468
7818
|
if (opts.json) {
|
|
7469
7819
|
console.log(JSON.stringify(sites, null, 2));
|
|
7470
7820
|
return;
|
|
@@ -7474,8 +7824,8 @@ browserProfile.command("sites <scope>").description("list the sites in a profile
|
|
|
7474
7824
|
for (const s of sites) console.log(renderBrowserProfileSite(s));
|
|
7475
7825
|
})
|
|
7476
7826
|
);
|
|
7477
|
-
browserProfile.command("import
|
|
7478
|
-
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) => {
|
|
7479
7829
|
let raw;
|
|
7480
7830
|
if (opts.file) {
|
|
7481
7831
|
const { readFileSync: rf } = await import("fs");
|
|
@@ -7488,21 +7838,26 @@ browserProfile.command("import <scope>").description("import saved browser state
|
|
|
7488
7838
|
});
|
|
7489
7839
|
}
|
|
7490
7840
|
const state = JSON.parse(raw);
|
|
7491
|
-
const result = await importBrowserProfileState(
|
|
7841
|
+
const result = await importBrowserProfileState("mine", { state });
|
|
7492
7842
|
console.log(`Imported ${result.sites.length} site${result.sites.length === 1 ? "" : "s"}: ${result.sites.join(", ")}`);
|
|
7493
7843
|
})
|
|
7494
7844
|
);
|
|
7495
|
-
browserProfile.command("remove-site <
|
|
7496
|
-
run(async (
|
|
7497
|
-
await removeBrowserProfileSite(
|
|
7498
|
-
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));
|
|
7499
7855
|
})
|
|
7500
7856
|
);
|
|
7501
|
-
browserProfile.command("
|
|
7502
|
-
run(async (
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
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}`);
|
|
7506
7861
|
})
|
|
7507
7862
|
);
|
|
7508
7863
|
var notifications = program.command("notifications").description("email and browser push notification preferences for tasks");
|
|
@@ -7562,21 +7917,21 @@ async function helpFetch(path) {
|
|
|
7562
7917
|
return await res.json();
|
|
7563
7918
|
}
|
|
7564
7919
|
var help = program.command("help").description("search the AgentSky help center");
|
|
7565
|
-
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(
|
|
7566
7921
|
run(async (words, opts) => {
|
|
7567
7922
|
const q = words.join(" ");
|
|
7568
7923
|
const topK = Math.min(Math.max(parseInt(opts.top, 10) || 5, 1), 20);
|
|
7569
|
-
const {
|
|
7924
|
+
const { hits } = await helpFetch(`/api/context/search?${new URLSearchParams({ q, scope: "docs/", limit: String(topK) })}`);
|
|
7570
7925
|
if (opts.json) {
|
|
7571
|
-
console.log(JSON.stringify(
|
|
7926
|
+
console.log(JSON.stringify(hits, null, 2));
|
|
7572
7927
|
return;
|
|
7573
7928
|
}
|
|
7574
|
-
if (!
|
|
7929
|
+
if (!hits.length) {
|
|
7575
7930
|
console.log("no results");
|
|
7576
7931
|
return;
|
|
7577
7932
|
}
|
|
7578
|
-
for (const
|
|
7579
|
-
${
|
|
7933
|
+
for (const hit of hits) console.log(`${String(hit.ref.slug).padEnd(40)} ${hit.title}
|
|
7934
|
+
${hit.snippet.slice(0, 100)}`);
|
|
7580
7935
|
})
|
|
7581
7936
|
);
|
|
7582
7937
|
help.command("get <slug>").description("read a help article by its slug").option("--json", "print as JSON").action(
|
|
@@ -7591,6 +7946,85 @@ help.command("get <slug>").description("read a help article by its slug").option
|
|
|
7591
7946
|
console.log(article.body);
|
|
7592
7947
|
})
|
|
7593
7948
|
);
|
|
7949
|
+
program.command("api <method> [path...]").description("call any AgentSky API operation (GET|POST|PUT|PATCH|DELETE <path>), or `sky api search <words\u2026>` to find one").option("-d, --data <json>", "JSON body: inline, @file, or - for stdin").option("-q, --query <key=value>", "query parameter (repeatable)", (v, acc) => [...acc, v], []).option("--workspace <slug>", "send the request in this workspace (X-Universe)").option("--idempotency-key <key>", "retry-safe key for a POST").option("-o, --output <file>", "save the body to a file (for downloads)").action(
|
|
7950
|
+
run(async (method, rest, opts) => {
|
|
7951
|
+
if (method.toLowerCase() === "search") return searchApi(rest);
|
|
7952
|
+
if (rest.length !== 1) throw new Error("usage: sky api <METHOD> <path> (one path), or sky api search <words\u2026>");
|
|
7953
|
+
return callApi(method, rest[0], opts);
|
|
7954
|
+
})
|
|
7955
|
+
);
|
|
7956
|
+
agent.command("edit <id>").description("change an agent's name, engine, model or thinking level (as its Library folder does)").option("--name <name>", "new name").option("--engine <agentType>", "engine, e.g. CLAUDE_CODE, CODEX, HERMES").option("--model <llm>", "model id, e.g. anthropic/claude-sonnet-4-5").option("--thinking <effort>", 'reasoning effort, or "default" to clear the pin').action(
|
|
7957
|
+
run(async (id, opts) => {
|
|
7958
|
+
const body = {
|
|
7959
|
+
...opts.name ? { displayName: opts.name } : {},
|
|
7960
|
+
...opts.engine ? { agentType: opts.engine } : {},
|
|
7961
|
+
...opts.model ? { llm: opts.model } : {},
|
|
7962
|
+
...opts.thinking ? { reasoningEffort: opts.thinking === "default" ? null : opts.thinking } : {}
|
|
7963
|
+
};
|
|
7964
|
+
if (!Object.keys(body).length) throw new Error("pass at least one of --name, --engine, --model, --thinking");
|
|
7965
|
+
const { agent: a } = await api("PATCH", `/agents/${encodeURIComponent(id)}`, body);
|
|
7966
|
+
console.log(`${a.id} \u2014 ${a.displayName} \xB7 ${a.agentType} \xB7 ${a.llm}${a.reasoningEffort ? ` \xB7 ${a.reasoningEffort}` : ""}`);
|
|
7967
|
+
})
|
|
7968
|
+
);
|
|
7969
|
+
skills.command("revisions <skillId>").description("a skill's saved versions, newest first").option("--json", "print as JSON").action(
|
|
7970
|
+
run(async (skillId, opts) => {
|
|
7971
|
+
const page = await api("GET", `/skills/${encodeURIComponent(skillId)}/revisions`);
|
|
7972
|
+
const rows = page.revisions ?? [];
|
|
7973
|
+
if (opts.json) return void console.log(JSON.stringify(rows, null, 2));
|
|
7974
|
+
if (!rows.length) return void console.log("no versions");
|
|
7975
|
+
for (const r of rows) console.log(`${r.id} ${r.createdAt ?? r.created_at ?? ""}`);
|
|
7976
|
+
})
|
|
7977
|
+
);
|
|
7978
|
+
skills.command("cat <skillId> <revisionId> <path>").description("print one file (e.g. SKILL.md) of a skill version").action(
|
|
7979
|
+
run(async (skillId, revisionId, path) => {
|
|
7980
|
+
const res = await apiRaw("GET", `/skills/${encodeURIComponent(skillId)}/revisions/${encodeURIComponent(revisionId)}/content?path=${encodeURIComponent(path)}`);
|
|
7981
|
+
if (!res.ok) throw new ApiError(res.status, "http_error", await res.text() || `${res.status}`);
|
|
7982
|
+
process.stdout.write(await res.text());
|
|
7983
|
+
})
|
|
7984
|
+
);
|
|
7985
|
+
skills.command("rm <skillId>").description("delete a skill from the workspace").action(
|
|
7986
|
+
run(async (skillId) => {
|
|
7987
|
+
await api("DELETE", `/skills/${encodeURIComponent(skillId)}`);
|
|
7988
|
+
console.log(`Deleted ${skillId}`);
|
|
7989
|
+
})
|
|
7990
|
+
);
|
|
7991
|
+
var notes = program.command("notes").description("memory notes in the Library's memory stores: list, read, write (every save keeps a version)");
|
|
7992
|
+
notes.command("stores").description("the workspace's memory stores").action(
|
|
7993
|
+
run(async () => {
|
|
7994
|
+
const { memory_stores: stores } = await api("GET", "/memory_stores?limit=100");
|
|
7995
|
+
if (!stores.length) return void console.log("no memory stores");
|
|
7996
|
+
for (const st of stores) console.log(`${st.id} ${st.name}`);
|
|
7997
|
+
})
|
|
7998
|
+
);
|
|
7999
|
+
notes.command("list <storeId> [folder]").description("the notes in a store, optionally under a folder ending in /").action(
|
|
8000
|
+
run(async (storeId, folder) => {
|
|
8001
|
+
const q = folder ? `&path_prefix=${encodeURIComponent(folder)}` : "";
|
|
8002
|
+
const { memories } = await api("GET", `/memory_stores/${encodeURIComponent(storeId)}/memories?limit=100${q}`);
|
|
8003
|
+
if (!memories.length) return void console.log("no notes");
|
|
8004
|
+
for (const n of memories) console.log(`${n.id} ${n.path}`);
|
|
8005
|
+
})
|
|
8006
|
+
);
|
|
8007
|
+
notes.command("read <storeId> <noteId>").description("print one note").action(
|
|
8008
|
+
run(async (storeId, noteId) => {
|
|
8009
|
+
const { memory: memory2 } = await api("GET", `/memory_stores/${encodeURIComponent(storeId)}/memories/${encodeURIComponent(noteId)}`);
|
|
8010
|
+
process.stdout.write(memory2.content.endsWith("\n") ? memory2.content : `${memory2.content}
|
|
8011
|
+
`);
|
|
8012
|
+
})
|
|
8013
|
+
);
|
|
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(
|
|
8015
|
+
run(async (storeId, file, opts) => {
|
|
8016
|
+
if (!opts.path === !opts.note) throw new Error("pass exactly one of --path (new note) or --note (update)");
|
|
8017
|
+
const content = readFileSync19(file, "utf8");
|
|
8018
|
+
const target = `/memory_stores/${encodeURIComponent(storeId)}/memories${opts.note ? `/${encodeURIComponent(opts.note)}` : ""}`;
|
|
8019
|
+
const { memory: memory2 } = await api("POST", target, { content, ...opts.path ? { path: opts.path } : {} });
|
|
8020
|
+
console.log(`Saved ${memory2.path} (${memory2.id})`);
|
|
8021
|
+
})
|
|
8022
|
+
);
|
|
8023
|
+
workspace.command("context").description("what every session in the workspace loads at start: AGENTS.md, MEMORY.md, indexes, your member page").action(
|
|
8024
|
+
run(async () => {
|
|
8025
|
+
console.log(JSON.stringify(await api("GET", "/workspace-context"), null, 2));
|
|
8026
|
+
})
|
|
8027
|
+
);
|
|
7594
8028
|
var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
|
|
7595
8029
|
vault.command("list").description("the workspace's vaults").action(
|
|
7596
8030
|
run(async () => {
|
|
@@ -7680,7 +8114,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
7680
8114
|
}));
|
|
7681
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)");
|
|
7682
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) => {
|
|
7683
|
-
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");
|
|
7684
8118
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
7685
8119
|
const scopes = parseContentScopes(opts.capture);
|
|
7686
8120
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -7715,7 +8149,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
7715
8149
|
}));
|
|
7716
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 () => {
|
|
7717
8151
|
try {
|
|
7718
|
-
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");
|
|
7719
8153
|
const chunks = [];
|
|
7720
8154
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
7721
8155
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -7728,14 +8162,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
7728
8162
|
}
|
|
7729
8163
|
});
|
|
7730
8164
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
7731
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
8165
|
+
const { readState, flush, grantSender } = await import("./trace-WLTD4C22.js");
|
|
7732
8166
|
const state = readState();
|
|
7733
8167
|
if (!state) throw new Error("no trace is running here");
|
|
7734
8168
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
7735
8169
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
7736
8170
|
}));
|
|
7737
8171
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
7738
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
8172
|
+
const { readState, spoolStatus } = await import("./trace-WLTD4C22.js");
|
|
7739
8173
|
const state = readState();
|
|
7740
8174
|
if (!state) {
|
|
7741
8175
|
console.log("No trace is running here.");
|
|
@@ -7745,7 +8179,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
7745
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`);
|
|
7746
8180
|
}));
|
|
7747
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) => {
|
|
7748
|
-
const { readState } = await import("./trace-
|
|
8182
|
+
const { readState } = await import("./trace-WLTD4C22.js");
|
|
7749
8183
|
const ref = id ?? readState()?.trace_id;
|
|
7750
8184
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
7751
8185
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -7772,7 +8206,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
7772
8206
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
7773
8207
|
}));
|
|
7774
8208
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
7775
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
8209
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-WLTD4C22.js");
|
|
7776
8210
|
const state = readState();
|
|
7777
8211
|
if (!state) throw new Error("no trace is running here");
|
|
7778
8212
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -7782,7 +8216,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
7782
8216
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
7783
8217
|
}));
|
|
7784
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) => {
|
|
7785
|
-
const lib = await import("./trace-library-
|
|
8219
|
+
const lib = await import("./trace-library-YMAVLH4Y.js");
|
|
7786
8220
|
if (opts.hook) {
|
|
7787
8221
|
try {
|
|
7788
8222
|
let raw = payload ?? "";
|
|
@@ -7841,10 +8275,10 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
7841
8275
|
console.log(`Added ${imported.sessions} ${imported.agent} session${imported.sessions === 1 ? "" : "s"} from ${input}.`);
|
|
7842
8276
|
}
|
|
7843
8277
|
if (opts.installSchedule) {
|
|
7844
|
-
const { join:
|
|
8278
|
+
const { join: join20 } = await import("path");
|
|
7845
8279
|
const { homedir: homedir10 } = await import("os");
|
|
7846
8280
|
const { execFileSync } = await import("child_process");
|
|
7847
|
-
const log =
|
|
8281
|
+
const log = join20(homedir10(), ".config", "sky", "trace-sync.log");
|
|
7848
8282
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
7849
8283
|
try {
|
|
7850
8284
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -7854,11 +8288,11 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
7854
8288
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
7855
8289
|
}
|
|
7856
8290
|
if (opts.status) {
|
|
7857
|
-
const { readFileSync:
|
|
7858
|
-
const { join:
|
|
8291
|
+
const { readFileSync: readFileSync20 } = await import("fs");
|
|
8292
|
+
const { join: join20 } = await import("path");
|
|
7859
8293
|
let state = { sessions: {} };
|
|
7860
8294
|
try {
|
|
7861
|
-
state = JSON.parse(
|
|
8295
|
+
state = JSON.parse(readFileSync20(join20(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
7862
8296
|
} catch {
|
|
7863
8297
|
}
|
|
7864
8298
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -7890,25 +8324,14 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
7890
8324
|
if (report.failed) process.exitCode = 1;
|
|
7891
8325
|
})();
|
|
7892
8326
|
});
|
|
7893
|
-
traceCmd.command("search <query...>").description("search every session in a workspace's trace library
|
|
7894
|
-
const { libraryRequest } = await import("./trace-library-QOQUAL5H.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) => {
|
|
7895
8328
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
7896
8329
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
7897
|
-
const
|
|
7898
|
-
|
|
7899
|
-
if (opts.json) {
|
|
7900
|
-
console.log(JSON.stringify(page, null, 2));
|
|
7901
|
-
return;
|
|
7902
|
-
}
|
|
7903
|
-
if (!page.hits.length) console.log(page.partial ? `No hits, but the search was cut short (${page.coverage}).` : "No hits.");
|
|
7904
|
-
for (const { hit } of page.hits) {
|
|
7905
|
-
console.log(`${hit.occurredAt?.slice(0, 16).replace("T", " ") ?? " "} ${hit.title}`);
|
|
7906
|
-
console.log(` ${hit.snippet.replace(/\s+/g, " ")}`);
|
|
7907
|
-
}
|
|
7908
|
-
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));
|
|
7909
8332
|
}));
|
|
7910
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) => {
|
|
7911
|
-
const { libraryRequest } = await import("./trace-library-
|
|
8334
|
+
const { libraryRequest } = await import("./trace-library-YMAVLH4Y.js");
|
|
7912
8335
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
7913
8336
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
7914
8337
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|
|
@@ -8214,7 +8637,7 @@ session.command("code-review <id>").description("run the code-review assistant o
|
|
|
8214
8637
|
}
|
|
8215
8638
|
})
|
|
8216
8639
|
);
|
|
8217
|
-
session.command("snapshot <id>").description("read or revoke the public share link for a session").requiredOption("--thread <threadId>", "the thread/comparison that owns this session").option("--revoke", "revoke the snapshot link instead of reading it").option("--json", "print as JSON").action(
|
|
8640
|
+
session.command("snapshot <id>").usage("<id> --thread <threadId> [--revoke] [--json]").description("read or revoke the public share link for a session; --thread is required").requiredOption("--thread <threadId>", "(required) the thread/comparison that owns this session").option("--revoke", "revoke the snapshot link instead of reading it").option("--json", "print as JSON").action(
|
|
8218
8641
|
run(async (id, opts) => {
|
|
8219
8642
|
const path = `/sessions/${encodeURIComponent(id)}/snapshot?thread=${encodeURIComponent(opts.thread)}`;
|
|
8220
8643
|
const result = await api(opts.revoke ? "DELETE" : "GET", path);
|