@agentsky/sky 2.22.0 → 2.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-YMYQT6KR.js → chunk-277AZSSG.js} +56 -35
- package/dist/{chunk-XT3W75V3.js → chunk-RAFHOB5S.js} +1 -1
- package/dist/{chunk-EBH3KXSU.js → chunk-UHMKEYJN.js} +18 -3
- package/dist/index.js +1015 -165
- package/dist/{src-TGRDQ5XR.js → src-FB5IETJP.js} +1 -1
- package/dist/{trace-PDIOL2TC.js → trace-CFHCOGKB.js} +2 -2
- package/dist/{trace-library-TA3QUBHJ.js → trace-library-2VHTJCOY.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-UHMKEYJN.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-RAFHOB5S.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-277AZSSG.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 readFileSync18, 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 = " :: ";
|
|
@@ -410,6 +421,52 @@ function renderApiKey(key) {
|
|
|
410
421
|
return `${key.id} ${key.token_prefix}\u2026 ${key.status.padEnd(7)} ${key.scopes.join(",").padEnd(16)} ${where} ${used} ${key.name}${key.current ? " (this key)" : ""}`;
|
|
411
422
|
}
|
|
412
423
|
|
|
424
|
+
// src/account-access.ts
|
|
425
|
+
function linkToken(ref) {
|
|
426
|
+
const fromUrl = /\/s\/([A-Za-z0-9_-]+)\/?(?:[?#].*)?$/.exec(ref.trim());
|
|
427
|
+
const token = fromUrl ? fromUrl[1] : ref.trim();
|
|
428
|
+
if (!/^[A-Za-z0-9_-]{16,64}$/.test(token)) throw new ApiError(400, "invalid_request", "a share link is a /s/<token> URL or its token");
|
|
429
|
+
return token;
|
|
430
|
+
}
|
|
431
|
+
async function sendViaLink(ref, text, idempotencyKey) {
|
|
432
|
+
await api("POST", `/links/${encodeURIComponent(linkToken(ref))}/messages`, { text }, { idempotencyKey });
|
|
433
|
+
}
|
|
434
|
+
async function stopViaLink(ref) {
|
|
435
|
+
await api("POST", `/links/${encodeURIComponent(linkToken(ref))}/interrupt`);
|
|
436
|
+
}
|
|
437
|
+
function inviteCode(ref) {
|
|
438
|
+
const fromUrl = /\/i\/([^/?#]+)\/?(?:[?#].*)?$/.exec(ref.trim());
|
|
439
|
+
const code = fromUrl ? decodeURIComponent(fromUrl[1]) : ref.trim();
|
|
440
|
+
if (!code) throw new ApiError(400, "invalid_request", "a credit invite is an /i/<code> URL or its code");
|
|
441
|
+
return code;
|
|
442
|
+
}
|
|
443
|
+
async function claimCreditInvite(ref) {
|
|
444
|
+
return api("POST", "/billing/credit-invites/claim", { code: inviteCode(ref) });
|
|
445
|
+
}
|
|
446
|
+
function renderCreditInviteClaim(claim) {
|
|
447
|
+
const amount = `$${claim.amount_usd.toFixed(2)}`;
|
|
448
|
+
return claim.status === "granted" ? `Added ${amount} of credit to your balance` : `Already claimed: this link added ${amount} to your balance`;
|
|
449
|
+
}
|
|
450
|
+
async function listAppGrants() {
|
|
451
|
+
return (await api("GET", "/oauth_grants")).oauth_grants;
|
|
452
|
+
}
|
|
453
|
+
async function revokeAppGrant(clientId) {
|
|
454
|
+
await api("DELETE", `/oauth_grants/${encodeURIComponent(clientId)}`);
|
|
455
|
+
}
|
|
456
|
+
function renderAppGrant(grant) {
|
|
457
|
+
const since = grant.allowed_at ? ` since ${grant.allowed_at.slice(0, 10)}` : "";
|
|
458
|
+
const review = grant.review_status === "approved" ? "" : " (unreviewed)";
|
|
459
|
+
return `${grant.client_id} ${grant.name || "(unnamed)"} ${grant.scopes.join(" ")}${since}${review}`;
|
|
460
|
+
}
|
|
461
|
+
async function unsubscribeMarketing() {
|
|
462
|
+
const { notification_preferences } = await api(
|
|
463
|
+
"PATCH",
|
|
464
|
+
"/notification-preferences",
|
|
465
|
+
{ email: { marketing: false } }
|
|
466
|
+
);
|
|
467
|
+
return { marketing: notification_preferences.email.marketing };
|
|
468
|
+
}
|
|
469
|
+
|
|
413
470
|
// src/context.ts
|
|
414
471
|
function memoryBody(operation, version) {
|
|
415
472
|
return { ...operation, expectedVersion: version };
|
|
@@ -448,9 +505,9 @@ function renderContextPage(page) {
|
|
|
448
505
|
}
|
|
449
506
|
|
|
450
507
|
// src/agent-file.ts
|
|
451
|
-
import { existsSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
508
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
452
509
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
453
|
-
import { parse as parseToml
|
|
510
|
+
import { parse as parseToml } from "smol-toml";
|
|
454
511
|
var fileInstructionSchema = external_exports.object({
|
|
455
512
|
name: external_exports.string(),
|
|
456
513
|
content: external_exports.string().optional(),
|
|
@@ -466,7 +523,8 @@ var agentFileSchema = external_exports.object({
|
|
|
466
523
|
type: external_exports.enum(AGENT_TYPES).optional(),
|
|
467
524
|
llm: external_exports.string().optional(),
|
|
468
525
|
reasoning_effort: external_exports.enum(REASONING_EFFORT_VALUES).optional(),
|
|
469
|
-
capabilities: capabilitiesSchema.optional(),
|
|
526
|
+
capabilities: capabilitiesSchema.removeDefault().optional(),
|
|
527
|
+
// omitted = leave the agent's as they are
|
|
470
528
|
prompt: external_exports.string().optional(),
|
|
471
529
|
prompt_file: external_exports.string().optional(),
|
|
472
530
|
instructions: external_exports.array(fileInstructionSchema).optional(),
|
|
@@ -523,13 +581,40 @@ function readAgentFile(path) {
|
|
|
523
581
|
return { file, spec };
|
|
524
582
|
}
|
|
525
583
|
function stampId(path, id) {
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
584
|
+
writeFileSync2(path, stampIdText(readFileSync3(path, "utf8"), id));
|
|
585
|
+
}
|
|
586
|
+
function stampIdText(text, id) {
|
|
587
|
+
const out = [];
|
|
588
|
+
let topLevel = true;
|
|
589
|
+
let openString = null;
|
|
590
|
+
let stamped = false;
|
|
591
|
+
let nameAt = -1;
|
|
592
|
+
for (const line of text.split("\n")) {
|
|
593
|
+
if (openString) {
|
|
594
|
+
if (line.includes(openString)) openString = null;
|
|
595
|
+
out.push(line);
|
|
596
|
+
continue;
|
|
597
|
+
}
|
|
598
|
+
if (/^\s*\[/.test(line)) topLevel = false;
|
|
599
|
+
if (topLevel && /^\s*(id|slug)\s*=/.test(line)) {
|
|
600
|
+
if (/^\s*id\s*=/.test(line) && !stamped) {
|
|
601
|
+
out.push(`id = ${JSON.stringify(id)}`);
|
|
602
|
+
stamped = true;
|
|
603
|
+
}
|
|
604
|
+
continue;
|
|
605
|
+
}
|
|
606
|
+
if (topLevel && nameAt < 0 && /^\s*name\s*=/.test(line)) nameAt = out.length;
|
|
607
|
+
const opener = /=\s*("""|''')/.exec(line);
|
|
608
|
+
if (opener && !line.slice(opener.index + opener[0].length).includes(opener[1])) openString = opener[1];
|
|
609
|
+
out.push(line);
|
|
610
|
+
}
|
|
611
|
+
if (!stamped) out.splice(nameAt + 1, 0, `id = ${JSON.stringify(id)}`);
|
|
612
|
+
const next = out.join("\n");
|
|
613
|
+
if (parseToml(next).id !== id) throw new Error("could not stamp the agent id into the file");
|
|
614
|
+
return next;
|
|
530
615
|
}
|
|
531
616
|
function scaffoldAgentFile(path, name) {
|
|
532
|
-
if (
|
|
617
|
+
if (existsSync2(path)) throw new Error(`${path} already exists`);
|
|
533
618
|
writeFileSync2(
|
|
534
619
|
path,
|
|
535
620
|
`# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
|
|
@@ -550,6 +635,38 @@ You are ${name}, a helpful long-lived agent.
|
|
|
550
635
|
`
|
|
551
636
|
);
|
|
552
637
|
}
|
|
638
|
+
function deployPatch(spec) {
|
|
639
|
+
return {
|
|
640
|
+
displayName: spec.displayName,
|
|
641
|
+
...spec.agentType ? { agentType: spec.agentType } : {},
|
|
642
|
+
...spec.llm ? { llm: spec.llm } : {},
|
|
643
|
+
...spec.capabilities ? { capabilities: spec.capabilities } : {},
|
|
644
|
+
...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
645
|
+
...spec.skills ? { skills: spec.skills } : {},
|
|
646
|
+
...spec.hiringPolicy ? { hiringPolicy: spec.hiringPolicy } : {},
|
|
647
|
+
...spec.environmentId ? { environmentId: spec.environmentId } : {}
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
function deployReport(id, spec, before, after, promptChanged) {
|
|
651
|
+
if (spec.agentType && after.agentType !== spec.agentType) {
|
|
652
|
+
throw new Error(`${id} is still ${after.agentType}: the API did not apply type = "${spec.agentType}"`);
|
|
653
|
+
}
|
|
654
|
+
if (spec.llm && after.llm !== spec.llm) {
|
|
655
|
+
throw new Error(`${id} still runs ${after.llm}: the API did not apply llm = "${spec.llm}"`);
|
|
656
|
+
}
|
|
657
|
+
const changed = [];
|
|
658
|
+
if (promptChanged) changed.push("prompt");
|
|
659
|
+
if (after.version !== before.version) changed.push("settings");
|
|
660
|
+
if (!changed.length) return [`${id} is up to date \u2014 nothing to deploy (version ${after.version})`];
|
|
661
|
+
const lines = [`Deployed ${id}: ${changed.join(" and ")} updated (version ${before.version} \u2192 ${after.version})`];
|
|
662
|
+
if (after.agentType !== before.agentType || after.llm !== before.llm) {
|
|
663
|
+
lines.push(` now ${after.agentType} \xB7 ${after.llm} for new sessions (was ${before.agentType} \xB7 ${before.llm})`);
|
|
664
|
+
}
|
|
665
|
+
if (spec.skills && after.version !== before.version) {
|
|
666
|
+
lines.push(` Skills apply to new sessions; running sessions keep the skills they started with \u2014 start one with \`sky session create ${id}\`.`);
|
|
667
|
+
}
|
|
668
|
+
return lines;
|
|
669
|
+
}
|
|
553
670
|
|
|
554
671
|
// src/catalog.ts
|
|
555
672
|
var bare = (handle) => handle.replace(/^@/, "");
|
|
@@ -616,7 +733,7 @@ function renderCatalogAgent(agent2) {
|
|
|
616
733
|
|
|
617
734
|
// src/clone.ts
|
|
618
735
|
import { spawn } from "child_process";
|
|
619
|
-
import { existsSync as
|
|
736
|
+
import { existsSync as existsSync3, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
620
737
|
import { homedir } from "os";
|
|
621
738
|
import { basename as basename3, join as join3 } from "path";
|
|
622
739
|
import { createInterface } from "readline";
|
|
@@ -682,9 +799,9 @@ function readClaudeCode() {
|
|
|
682
799
|
sources.push("global CLAUDE.md");
|
|
683
800
|
}
|
|
684
801
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
685
|
-
const
|
|
686
|
-
if (
|
|
687
|
-
parts.push({ label: `Project ${name}`, content:
|
|
802
|
+
const project2 = readIfExists(join3(process.cwd(), name));
|
|
803
|
+
if (project2?.trim()) {
|
|
804
|
+
parts.push({ label: `Project ${name}`, content: project2 });
|
|
688
805
|
sources.push(`project ${name}`);
|
|
689
806
|
break;
|
|
690
807
|
}
|
|
@@ -722,9 +839,9 @@ function detectHarness(explicit) {
|
|
|
722
839
|
}
|
|
723
840
|
return explicit;
|
|
724
841
|
}
|
|
725
|
-
if (
|
|
726
|
-
if (
|
|
727
|
-
if (
|
|
842
|
+
if (existsSync3(claudeConfigRoot())) return "claude_code";
|
|
843
|
+
if (existsSync3(join3(homedir(), ".codex"))) return "codex";
|
|
844
|
+
if (existsSync3(join3(homedir(), ".openclaw"))) return "openclaw";
|
|
728
845
|
throw new Error(
|
|
729
846
|
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
730
847
|
);
|
|
@@ -739,11 +856,11 @@ function linkPath(configRoot) {
|
|
|
739
856
|
return join3(configRoot, "agentsky-link.json");
|
|
740
857
|
}
|
|
741
858
|
function readLink(configRoot) {
|
|
742
|
-
const
|
|
743
|
-
return
|
|
859
|
+
const link2 = readJson(linkPath(configRoot));
|
|
860
|
+
return link2?.agentId ? link2 : void 0;
|
|
744
861
|
}
|
|
745
|
-
function writeLink(configRoot,
|
|
746
|
-
writeFileSync3(linkPath(configRoot), `${JSON.stringify(
|
|
862
|
+
function writeLink(configRoot, link2) {
|
|
863
|
+
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link2, null, 2)}
|
|
747
864
|
`);
|
|
748
865
|
}
|
|
749
866
|
var DEFAULT_VAULT_NAME = "default";
|
|
@@ -909,14 +1026,14 @@ async function createClone(local, opts) {
|
|
|
909
1026
|
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
910
1027
|
if (opts.open) openBrowser(url);
|
|
911
1028
|
}
|
|
912
|
-
async function syncClone(local,
|
|
913
|
-
const since =
|
|
1029
|
+
async function syncClone(local, link2, opts) {
|
|
1030
|
+
const since = link2.lastSyncedAt ?? link2.clonedAt;
|
|
914
1031
|
console.log(
|
|
915
|
-
`Syncing ${
|
|
1032
|
+
`Syncing ${link2.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
|
|
916
1033
|
);
|
|
917
1034
|
if (opts.dryRun) {
|
|
918
1035
|
console.log(
|
|
919
|
-
` would push prompt (${kb(local.prompt)}) to ${
|
|
1036
|
+
` would push prompt (${kb(local.prompt)}) to ${link2.agentId}. Dry run \u2014 nothing sent.`
|
|
920
1037
|
);
|
|
921
1038
|
return;
|
|
922
1039
|
}
|
|
@@ -924,24 +1041,24 @@ async function syncClone(local, link, opts) {
|
|
|
924
1041
|
try {
|
|
925
1042
|
({ agent: agent2 } = await api(
|
|
926
1043
|
"GET",
|
|
927
|
-
`/agents/${encodeURIComponent(
|
|
1044
|
+
`/agents/${encodeURIComponent(link2.agentId)}`
|
|
928
1045
|
));
|
|
929
1046
|
} catch (error) {
|
|
930
1047
|
if (error instanceof ApiError && error.status === 404) {
|
|
931
1048
|
throw new Error(
|
|
932
|
-
`linked agent ${
|
|
1049
|
+
`linked agent ${link2.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
|
|
933
1050
|
);
|
|
934
1051
|
}
|
|
935
1052
|
throw error;
|
|
936
1053
|
}
|
|
937
1054
|
if (agent2.agentType !== local.harness) {
|
|
938
1055
|
throw new Error(
|
|
939
|
-
`linked agent ${
|
|
1056
|
+
`linked agent ${link2.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
|
|
940
1057
|
);
|
|
941
1058
|
}
|
|
942
1059
|
const result = await api(
|
|
943
1060
|
"PUT",
|
|
944
|
-
`/agents/${encodeURIComponent(
|
|
1061
|
+
`/agents/${encodeURIComponent(link2.agentId)}/prompt`,
|
|
945
1062
|
{ prompt: local.prompt }
|
|
946
1063
|
);
|
|
947
1064
|
console.log(
|
|
@@ -949,11 +1066,11 @@ async function syncClone(local, link, opts) {
|
|
|
949
1066
|
);
|
|
950
1067
|
const drift = [];
|
|
951
1068
|
const localModel = local.model ?? null;
|
|
952
|
-
const linkedModel =
|
|
1069
|
+
const linkedModel = link2.llm ?? agent2.llm;
|
|
953
1070
|
if (localModel && linkedModel && localModel !== linkedModel) {
|
|
954
1071
|
drift.push(`model changed ${linkedModel} \u2192 ${localModel}`);
|
|
955
1072
|
}
|
|
956
|
-
const before = new Set(
|
|
1073
|
+
const before = new Set(link2.mcpServers ?? []);
|
|
957
1074
|
const nowKept = Object.keys(local.keptServers);
|
|
958
1075
|
const newServers = nowKept.filter((n) => !before.has(n));
|
|
959
1076
|
const goneServers = [...before].filter((n) => !nowKept.includes(n));
|
|
@@ -968,39 +1085,39 @@ async function syncClone(local, link, opts) {
|
|
|
968
1085
|
` left out of clone (secret-bearing): ${local.droppedServers.map((d) => `${d.name} MCP`).join(", ")}`
|
|
969
1086
|
);
|
|
970
1087
|
}
|
|
971
|
-
writeLink(local.configRoot, { ...
|
|
972
|
-
console.log(`Synced ${
|
|
1088
|
+
writeLink(local.configRoot, { ...link2, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
1089
|
+
console.log(`Synced ${link2.agentId}.`);
|
|
973
1090
|
await confirmAndVaultSecrets(local.secretCandidates, opts.yes ?? false);
|
|
974
|
-
if (opts.open) openBrowser(`${apiBase()}/agents/${
|
|
1091
|
+
if (opts.open) openBrowser(`${apiBase()}/agents/${link2.agentId}`);
|
|
975
1092
|
}
|
|
976
1093
|
async function clone(opts) {
|
|
977
1094
|
const harness2 = detectHarness(opts.harness);
|
|
978
1095
|
const local = readLocal(harness2);
|
|
979
|
-
const
|
|
980
|
-
if (
|
|
1096
|
+
const link2 = opts.new ? void 0 : readLink(local.configRoot);
|
|
1097
|
+
if (link2) await syncClone(local, link2, opts);
|
|
981
1098
|
else await createClone(local, opts);
|
|
982
1099
|
}
|
|
983
1100
|
|
|
984
1101
|
// src/import-local.ts
|
|
985
|
-
import { closeSync as closeSync3, existsSync as
|
|
1102
|
+
import { closeSync as closeSync3, existsSync as existsSync6, openSync as openSync3, readdirSync as readdirSync4, readFileSync as readFileSync8, readSync as readSync3, statSync as statSync5 } from "fs";
|
|
986
1103
|
import { homedir as homedir4 } from "os";
|
|
987
1104
|
import { join as join7 } from "path";
|
|
988
1105
|
import { createHash as createHash3 } from "crypto";
|
|
989
1106
|
|
|
990
1107
|
// src/import-files.ts
|
|
991
1108
|
import { isUtf8 } from "buffer";
|
|
992
|
-
import { existsSync as
|
|
1109
|
+
import { existsSync as existsSync5, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
993
1110
|
import { homedir as homedir3 } from "os";
|
|
994
1111
|
import { dirname as dirname4, extname as extname2, isAbsolute, join as join5, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
|
|
995
1112
|
|
|
996
1113
|
// src/import-workspace.ts
|
|
997
1114
|
import { spawnSync } from "child_process";
|
|
998
|
-
import { existsSync as
|
|
1115
|
+
import { existsSync as existsSync4, readdirSync as readdirSync2, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
999
1116
|
import { homedir as homedir2 } from "os";
|
|
1000
1117
|
import { basename as basename4, dirname as dirname3, join as join4, relative, sep } from "path";
|
|
1001
1118
|
import { parse as parseToml2 } from "smol-toml";
|
|
1002
1119
|
var MAX_TREE = 5e3;
|
|
1003
|
-
var SKILL_LINE = /Base directory for this skill: (\/[^\
|
|
1120
|
+
var SKILL_LINE = /Base directory for this skill: (\/[^\n\r"'\\<>]+)/g;
|
|
1004
1121
|
function git(cwd, ...args) {
|
|
1005
1122
|
const run2 = spawnSync("git", ["-C", cwd, ...args], { encoding: "utf8", timeout: 1e4, maxBuffer: 32 * 1024 * 1024 });
|
|
1006
1123
|
return run2.status === 0 ? run2.stdout : null;
|
|
@@ -1046,8 +1163,7 @@ function skillRoots(cwd, home) {
|
|
|
1046
1163
|
];
|
|
1047
1164
|
}
|
|
1048
1165
|
function pluginSkill(plugin, skill, home) {
|
|
1049
|
-
const
|
|
1050
|
-
const walk = (dir, depth) => {
|
|
1166
|
+
const walk = (dir, depth, underPlugin) => {
|
|
1051
1167
|
if (depth > 7) return null;
|
|
1052
1168
|
let entries;
|
|
1053
1169
|
try {
|
|
@@ -1058,13 +1174,13 @@ function pluginSkill(plugin, skill, home) {
|
|
|
1058
1174
|
for (const entry2 of entries) {
|
|
1059
1175
|
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
1060
1176
|
const path = join4(dir, entry2.name);
|
|
1061
|
-
if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join4(path, "SKILL.md"))) return path;
|
|
1062
|
-
const found = walk(path, depth + 1);
|
|
1177
|
+
if (entry2.name === skill && basename4(dir) === "skills" && (underPlugin || path.includes(`${sep}${plugin}${sep}`)) && isFile(join4(path, "SKILL.md"))) return realpathSync(path);
|
|
1178
|
+
const found = walk(path, depth + 1, underPlugin);
|
|
1063
1179
|
if (found) return found;
|
|
1064
1180
|
}
|
|
1065
1181
|
return null;
|
|
1066
1182
|
};
|
|
1067
|
-
return walk(
|
|
1183
|
+
return walk(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "plugins"), 0, false) ?? walk(join4(home, "Library", "Application Support", "Claude", "local-agent-mode-sessions", "skills-plugin"), 0, true);
|
|
1068
1184
|
}
|
|
1069
1185
|
function findSkill(name, cwd, home) {
|
|
1070
1186
|
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
@@ -1084,23 +1200,24 @@ function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
|
1084
1200
|
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
1085
1201
|
return true;
|
|
1086
1202
|
};
|
|
1203
|
+
const called = [];
|
|
1087
1204
|
for (const record of records) {
|
|
1088
1205
|
const texts = [];
|
|
1089
1206
|
strings(record, texts);
|
|
1090
|
-
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].replace(/[.,;:)]+$/, ""));
|
|
1207
|
+
for (const text of texts) for (const match of text.matchAll(SKILL_LINE)) add(match[1].trim().replace(/[.,;:)]+$/, ""));
|
|
1091
1208
|
const content = record?.message?.content;
|
|
1092
1209
|
if (!Array.isArray(content)) continue;
|
|
1093
1210
|
for (const block of content) {
|
|
1094
1211
|
const b = block;
|
|
1095
|
-
if (b?.type
|
|
1096
|
-
const name = b.input.skill.replace(/^\//, "");
|
|
1097
|
-
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
1098
|
-
const dir = findSkill(name, cwd, home);
|
|
1099
|
-
if (!dir || !add(dir, name)) missing.add(name);
|
|
1212
|
+
if (b?.type === "tool_use" && b.name === "Skill" && typeof b.input?.skill === "string") called.push(b.input.skill.replace(/^\//, ""));
|
|
1100
1213
|
}
|
|
1101
1214
|
}
|
|
1102
1215
|
for (const path of touched) if (basename4(path) === "SKILL.md") add(dirname3(path));
|
|
1103
|
-
for (const name of
|
|
1216
|
+
for (const name of called) {
|
|
1217
|
+
if (!name || dirs.has(name) || [...dirs.keys()].some((key) => key.endsWith(`:${name}`) || name.endsWith(`:${key}`))) continue;
|
|
1218
|
+
const dir = findSkill(name, cwd, home);
|
|
1219
|
+
if (!dir || !add(dir, name)) missing.add(name);
|
|
1220
|
+
}
|
|
1104
1221
|
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
1105
1222
|
}
|
|
1106
1223
|
function readJson2(path) {
|
|
@@ -1224,7 +1341,7 @@ function skillFiles(skill) {
|
|
|
1224
1341
|
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1225
1342
|
}
|
|
1226
1343
|
};
|
|
1227
|
-
if (
|
|
1344
|
+
if (existsSync4(skill.dir)) walk(skill.dir, 0);
|
|
1228
1345
|
return out;
|
|
1229
1346
|
}
|
|
1230
1347
|
function codexThreadName(sessionId, home = homedir2()) {
|
|
@@ -1350,13 +1467,13 @@ function touchedPaths(records, cwd, home = homedir3()) {
|
|
|
1350
1467
|
}
|
|
1351
1468
|
function gitRoot(dir) {
|
|
1352
1469
|
for (let d = dir; ; d = dirname4(d)) {
|
|
1353
|
-
if (
|
|
1470
|
+
if (existsSync5(join5(d, ".git"))) return d;
|
|
1354
1471
|
if (d === dirname4(d)) return null;
|
|
1355
1472
|
}
|
|
1356
1473
|
}
|
|
1357
1474
|
function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
1358
1475
|
const out = [];
|
|
1359
|
-
if (cwd &&
|
|
1476
|
+
if (cwd && existsSync5(cwd)) {
|
|
1360
1477
|
const root = gitRoot(cwd) ?? cwd;
|
|
1361
1478
|
for (let d = resolve4(cwd); ; d = dirname4(d)) {
|
|
1362
1479
|
for (const name of INSTRUCTION_FILES) out.push(join5(d, name));
|
|
@@ -1879,7 +1996,7 @@ function sniffTitle(filePath) {
|
|
|
1879
1996
|
function listClaudeCodeSessions(limit) {
|
|
1880
1997
|
const root = process.env.CLAUDE_CONFIG_DIR ?? join7(homedir4(), ".claude");
|
|
1881
1998
|
const projectsDir = join7(root, "projects");
|
|
1882
|
-
if (!
|
|
1999
|
+
if (!existsSync6(projectsDir)) return [];
|
|
1883
2000
|
const sessions = [];
|
|
1884
2001
|
try {
|
|
1885
2002
|
for (const encoded of readdirSync4(projectsDir)) {
|
|
@@ -1905,7 +2022,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
1905
2022
|
let discoveryError;
|
|
1906
2023
|
const subagentFiles = [];
|
|
1907
2024
|
const subagentDir = join7(projectDir, sessionId, "subagents");
|
|
1908
|
-
if (
|
|
2025
|
+
if (existsSync6(subagentDir)) {
|
|
1909
2026
|
try {
|
|
1910
2027
|
const subFiles = readdirSync4(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1911
2028
|
for (const f of subFiles) {
|
|
@@ -2183,7 +2300,7 @@ async function importSession(opts) {
|
|
|
2183
2300
|
}
|
|
2184
2301
|
const result = await response.json();
|
|
2185
2302
|
const finalTraceId = result.traceId ?? traceId;
|
|
2186
|
-
const environment = opts.files === false || opts.environment === false || !cwd || !
|
|
2303
|
+
const environment = opts.files === false || opts.environment === false || !cwd || !existsSync6(cwd) ? null : await uploadEnvironment(base, key, finalTraceId, cwd, resourceLines, opts.onProgress).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
|
|
2187
2304
|
return {
|
|
2188
2305
|
traceId: finalTraceId,
|
|
2189
2306
|
url: `${base}/playground/new?import=${encodeURIComponent(finalTraceId)}`,
|
|
@@ -2296,7 +2413,7 @@ import { hostname } from "os";
|
|
|
2296
2413
|
import { spawn as spawn2 } from "child_process";
|
|
2297
2414
|
|
|
2298
2415
|
// src/mcp-register.ts
|
|
2299
|
-
import { existsSync as
|
|
2416
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
|
|
2300
2417
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
2301
2418
|
import { homedir as homedir6 } from "os";
|
|
2302
2419
|
import { join as join10 } from "path";
|
|
@@ -2304,7 +2421,7 @@ import { parse as parseToml3 } from "smol-toml";
|
|
|
2304
2421
|
import { isMap, parseDocument } from "yaml";
|
|
2305
2422
|
|
|
2306
2423
|
// src/local-agents.ts
|
|
2307
|
-
import { existsSync as
|
|
2424
|
+
import { existsSync as existsSync7 } from "fs";
|
|
2308
2425
|
import { homedir as homedir5 } from "os";
|
|
2309
2426
|
import { join as join8 } from "path";
|
|
2310
2427
|
function localAgents(env = process.env, home = homedir5()) {
|
|
@@ -2318,7 +2435,7 @@ function localAgents(env = process.env, home = homedir5()) {
|
|
|
2318
2435
|
];
|
|
2319
2436
|
}
|
|
2320
2437
|
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
2321
|
-
return localAgents(env, home).filter((agent2) =>
|
|
2438
|
+
return localAgents(env, home).filter((agent2) => existsSync7(agent2.root));
|
|
2322
2439
|
}
|
|
2323
2440
|
function selectLocalAgents(ids, env = process.env, home = homedir5()) {
|
|
2324
2441
|
if (!ids?.length) return detectLocalAgents(env, home);
|
|
@@ -2441,6 +2558,12 @@ Details: \`${REFERENCES_DIR}/workspaces.md\` in this skill's folder.
|
|
|
2441
2558
|
cheaper or different agent to get around a refusal.
|
|
2442
2559
|
- \`get_usage\` shows the balance; check it before starting several cloud sessions
|
|
2443
2560
|
at once so the person is told once, up front, instead of after the first fails.
|
|
2561
|
+
- Anything else the person can do on agentsky.dev or in the Mac app (rename or
|
|
2562
|
+
archive a thread, edit a Library file or skill, change an agent's model, invite
|
|
2563
|
+
a member, \u2026) is an API operation: find it with \`search_api\`, read its
|
|
2564
|
+
parameters with \`describe_api\`, and run it with \`call_api\`. It acts as the
|
|
2565
|
+
person with their own key, so make only the change they asked for. Where MCP
|
|
2566
|
+
is missing, \`sky api search <words>\` and \`sky api <METHOD> <path>\` do the same.
|
|
2444
2567
|
|
|
2445
2568
|
## How to pick
|
|
2446
2569
|
|
|
@@ -2828,7 +2951,7 @@ function installSkill(agent2, opts = {}) {
|
|
|
2828
2951
|
var MCP_SERVER_NAME = SKILL_NAME;
|
|
2829
2952
|
function detectHermes(env = process.env, home = homedir6()) {
|
|
2830
2953
|
const root = env.HERMES_HOME || join10(home, ".hermes");
|
|
2831
|
-
return
|
|
2954
|
+
return existsSync8(root) ? { id: "hermes", name: "Hermes", root } : null;
|
|
2832
2955
|
}
|
|
2833
2956
|
function detectMcpClients(env = process.env, home = homedir6()) {
|
|
2834
2957
|
const hermes = detectHermes(env, home);
|
|
@@ -2988,7 +3111,7 @@ function refreshMcpRegistrations(opts, clients = detectMcpClients(), runner = sy
|
|
|
2988
3111
|
}
|
|
2989
3112
|
|
|
2990
3113
|
// src/shell-env.ts
|
|
2991
|
-
import { appendFileSync, existsSync as
|
|
3114
|
+
import { appendFileSync, existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync6 } from "fs";
|
|
2992
3115
|
import { homedir as homedir7 } from "os";
|
|
2993
3116
|
import { basename as basename6, dirname as dirname5, join as join11 } from "path";
|
|
2994
3117
|
var TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
@@ -3004,13 +3127,13 @@ function shellStartupFiles(env = process.env, home = homedir7(), platform = proc
|
|
|
3004
3127
|
const shell = basename6(env.SHELL ?? "");
|
|
3005
3128
|
const files2 = [];
|
|
3006
3129
|
const zshenv = join11(env.ZDOTDIR || home, ".zshenv");
|
|
3007
|
-
if (shell === "zsh" || platform === "darwin" ||
|
|
3130
|
+
if (shell === "zsh" || platform === "darwin" || existsSync9(zshenv)) files2.push({ path: zshenv, kind: "sh" });
|
|
3008
3131
|
const bashrc = join11(home, ".bashrc");
|
|
3009
|
-
if (shell === "bash" ||
|
|
3132
|
+
if (shell === "bash" || existsSync9(bashrc)) files2.push({ path: bashrc, kind: "sh" });
|
|
3010
3133
|
const bashProfile = join11(home, ".bash_profile");
|
|
3011
|
-
if (
|
|
3134
|
+
if (existsSync9(bashProfile)) files2.push({ path: bashProfile, kind: "sh" });
|
|
3012
3135
|
const fish = join11(env.XDG_CONFIG_HOME || join11(home, ".config"), "fish");
|
|
3013
|
-
if (shell === "fish" ||
|
|
3136
|
+
if (shell === "fish" || existsSync9(fish)) files2.push({ path: join11(fish, "conf.d", "agentsky.fish"), kind: "fish" });
|
|
3014
3137
|
return files2;
|
|
3015
3138
|
}
|
|
3016
3139
|
function writeShellEnv(token, opts = {}) {
|
|
@@ -3026,7 +3149,7 @@ function writeShellEnv(token, opts = {}) {
|
|
|
3026
3149
|
writeFileSync6(fish, `${header2}${token ? `set -gx ${TOKEN_ENV} ${quoted(token)}` : `set -e ${TOKEN_ENV}`}
|
|
3027
3150
|
`, { mode: 384 });
|
|
3028
3151
|
for (const file of startup) {
|
|
3029
|
-
const existing =
|
|
3152
|
+
const existing = existsSync9(file.path) ? readFileSync11(file.path, "utf8") : "";
|
|
3030
3153
|
if (existing.includes(HOOK_MARK)) continue;
|
|
3031
3154
|
const line = file.kind === "fish" ? `${HOOK_MARK}
|
|
3032
3155
|
test -f ${quoted(fish)}; and source ${quoted(fish)}
|
|
@@ -3397,12 +3520,42 @@ function clip(value, max = 120) {
|
|
|
3397
3520
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
3398
3521
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
3399
3522
|
}
|
|
3400
|
-
function
|
|
3523
|
+
function createEventRenderer(out = process.stdout) {
|
|
3524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3525
|
+
let openStream = null;
|
|
3526
|
+
const closeLine = () => {
|
|
3527
|
+
if (openStream === null) return;
|
|
3528
|
+
out.write("\n");
|
|
3529
|
+
openStream = null;
|
|
3530
|
+
};
|
|
3531
|
+
return (event, data) => {
|
|
3532
|
+
const id = typeof data.id === "string" ? data.id : void 0;
|
|
3533
|
+
if (id) {
|
|
3534
|
+
if (seen.has(id)) return;
|
|
3535
|
+
seen.add(id);
|
|
3536
|
+
}
|
|
3537
|
+
if (event !== "agent.message") {
|
|
3538
|
+
if (event && event !== "keepalive") closeLine();
|
|
3539
|
+
renderOther(event, data);
|
|
3540
|
+
return;
|
|
3541
|
+
}
|
|
3542
|
+
const parts = Array.isArray(data.parts) ? data.parts : [];
|
|
3543
|
+
const streamId = typeof parts[0]?.stream_id === "string" ? parts[0].stream_id : null;
|
|
3544
|
+
const text = String(data.text ?? "");
|
|
3545
|
+
if (streamId === null) {
|
|
3546
|
+
closeLine();
|
|
3547
|
+
out.write(`${text}
|
|
3548
|
+
`);
|
|
3549
|
+
} else {
|
|
3550
|
+
if (openStream !== streamId) closeLine();
|
|
3551
|
+
out.write(text);
|
|
3552
|
+
openStream = streamId;
|
|
3553
|
+
}
|
|
3554
|
+
};
|
|
3555
|
+
}
|
|
3556
|
+
function renderOther(event, data) {
|
|
3401
3557
|
const part = data.part;
|
|
3402
3558
|
switch (event) {
|
|
3403
|
-
case "agent.message":
|
|
3404
|
-
console.log(String(data.text ?? ""));
|
|
3405
|
-
break;
|
|
3406
3559
|
case "agent.reasoning":
|
|
3407
3560
|
console.log(dim(`\xB7 ${clip(part?.text ?? "")}`));
|
|
3408
3561
|
break;
|
|
@@ -3434,11 +3587,12 @@ function renderEvent(event, data) {
|
|
|
3434
3587
|
}
|
|
3435
3588
|
|
|
3436
3589
|
// src/session-read.ts
|
|
3437
|
-
var
|
|
3590
|
+
var TURN_STARTS = /* @__PURE__ */ new Set(["user.message", "goal.defined", "goal.iteration"]);
|
|
3591
|
+
var TURN_TYPES = [...TURN_STARTS, "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
|
|
3438
3592
|
function latestTurn(events) {
|
|
3439
3593
|
let start = 0;
|
|
3440
3594
|
for (let index = events.length - 1; index >= 0; index -= 1) {
|
|
3441
|
-
if (events[index].type
|
|
3595
|
+
if (TURN_STARTS.has(events[index].type)) {
|
|
3442
3596
|
start = index + 1;
|
|
3443
3597
|
break;
|
|
3444
3598
|
}
|
|
@@ -3485,7 +3639,7 @@ import { createInterface as createInterface3 } from "readline";
|
|
|
3485
3639
|
|
|
3486
3640
|
// src/setup-local.ts
|
|
3487
3641
|
import { isUtf8 as isUtf83 } from "buffer";
|
|
3488
|
-
import { existsSync as
|
|
3642
|
+
import { existsSync as existsSync10, readdirSync as readdirSync6, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
|
|
3489
3643
|
import { homedir as homedir8 } from "os";
|
|
3490
3644
|
import { dirname as dirname6, isAbsolute as isAbsolute2, join as join13, resolve as resolve6 } from "path";
|
|
3491
3645
|
import { parse as parseToml4 } from "smol-toml";
|
|
@@ -3526,7 +3680,7 @@ function readLocalSkills(agents) {
|
|
|
3526
3680
|
const skills2 = [];
|
|
3527
3681
|
const ignored = [];
|
|
3528
3682
|
for (const agent2 of agents) {
|
|
3529
|
-
if (!
|
|
3683
|
+
if (!existsSync10(agent2.skillsDir)) continue;
|
|
3530
3684
|
for (const entry2 of readdirSync6(agent2.skillsDir, { withFileTypes: true })) {
|
|
3531
3685
|
const dir = join13(agent2.skillsDir, entry2.name);
|
|
3532
3686
|
let isDir2 = entry2.isDirectory();
|
|
@@ -3537,7 +3691,7 @@ function readLocalSkills(agents) {
|
|
|
3537
3691
|
isDir2 = false;
|
|
3538
3692
|
}
|
|
3539
3693
|
}
|
|
3540
|
-
if (!isDir2 || !
|
|
3694
|
+
if (!isDir2 || !existsSync10(join13(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
3541
3695
|
const name = frontmatterName(readFileSync12(join13(dir, "SKILL.md"), "utf8"));
|
|
3542
3696
|
if (!name) {
|
|
3543
3697
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
@@ -3644,7 +3798,7 @@ function findLocalSecrets(agents, home = homedir8()) {
|
|
|
3644
3798
|
|
|
3645
3799
|
// src/subscriptions.ts
|
|
3646
3800
|
import { spawn as spawn3 } from "child_process";
|
|
3647
|
-
import { existsSync as
|
|
3801
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "fs";
|
|
3648
3802
|
import { homedir as homedir9 } from "os";
|
|
3649
3803
|
import { join as join14 } from "path";
|
|
3650
3804
|
import { createInterface as createInterface2 } from "readline";
|
|
@@ -3743,7 +3897,7 @@ async function connectClaude() {
|
|
|
3743
3897
|
}
|
|
3744
3898
|
function codexSignIn(env = process.env, home = homedir9()) {
|
|
3745
3899
|
const path = join14(env.CODEX_HOME || join14(home, ".codex"), "auth.json");
|
|
3746
|
-
if (!
|
|
3900
|
+
if (!existsSync11(path)) {
|
|
3747
3901
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
3748
3902
|
}
|
|
3749
3903
|
const raw = readFileSync13(path, "utf8");
|
|
@@ -3756,7 +3910,7 @@ function codexSignIn(env = process.env, home = homedir9()) {
|
|
|
3756
3910
|
}
|
|
3757
3911
|
function cursorSignIn(env = process.env, home = homedir9()) {
|
|
3758
3912
|
const path = join14(env.XDG_CONFIG_HOME || join14(home, ".config"), "cursor", "auth.json");
|
|
3759
|
-
if (!
|
|
3913
|
+
if (!existsSync11(path)) {
|
|
3760
3914
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
3761
3915
|
}
|
|
3762
3916
|
const raw = readFileSync13(path, "utf8");
|
|
@@ -3878,7 +4032,9 @@ async function subscriptionUsage(name, account) {
|
|
|
3878
4032
|
return usage;
|
|
3879
4033
|
}
|
|
3880
4034
|
function renderSubscriptionUsage(name, usage) {
|
|
3881
|
-
const
|
|
4035
|
+
const provider = managedProviderFor(name);
|
|
4036
|
+
const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[provider];
|
|
4037
|
+
if (usage.status === "unavailable" && (usage.reason === "unsupported" || provider === "anthropic" && usage.reason === "permission")) return [`${label}: usage isn't available for this account type`];
|
|
3882
4038
|
if (usage.status === "unavailable") return [`${label}: usage unavailable (${usage.reason ?? "unknown"})`];
|
|
3883
4039
|
const head = [`${label}${usage.plan ? ` ${usage.plan}` : ""}`, usage.allowed === false ? "limit reached" : null].filter(Boolean).join(" \xB7 ");
|
|
3884
4040
|
const rows = usage.windows.map(
|
|
@@ -4353,12 +4509,25 @@ async function getSkillUsage(since) {
|
|
|
4353
4509
|
const qs = since ? `?since=${encodeURIComponent(since)}` : "";
|
|
4354
4510
|
return api("GET", `/skills/usage${qs}`);
|
|
4355
4511
|
}
|
|
4512
|
+
function nextSkills(current, skillId, verb) {
|
|
4513
|
+
const has = current.some((ref) => ref.type === "skill" && ref.skillId === skillId);
|
|
4514
|
+
if (verb === "attach") return has ? null : [...current, { type: "skill", skillId, version: "latest" }];
|
|
4515
|
+
return has ? current.filter((ref) => !(ref.type === "skill" && ref.skillId === skillId)) : null;
|
|
4516
|
+
}
|
|
4517
|
+
async function setAgentSkill(agentId, skillId, verb) {
|
|
4518
|
+
const path = `/agents/${encodeURIComponent(agentId)}`;
|
|
4519
|
+
const { agent: agent2 } = await api("GET", path);
|
|
4520
|
+
const skills2 = nextSkills(agent2.skills ?? [], skillId, verb);
|
|
4521
|
+
if (!skills2) return "unchanged";
|
|
4522
|
+
await api("PATCH", path, { skills: skills2, expectedVersion: agent2.version });
|
|
4523
|
+
return "changed";
|
|
4524
|
+
}
|
|
4356
4525
|
|
|
4357
4526
|
// src/task-export.ts
|
|
4358
4527
|
import { createHash as createHash6 } from "crypto";
|
|
4359
4528
|
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
4360
4529
|
import { dirname as dirname8, join as join17 } from "path";
|
|
4361
|
-
import { stringify as
|
|
4530
|
+
import { stringify as stringifyToml } from "smol-toml";
|
|
4362
4531
|
var TEXT_MAX = 2e4;
|
|
4363
4532
|
function scrub(text, names2) {
|
|
4364
4533
|
let out = redactPlainText(text).text;
|
|
@@ -4536,7 +4705,7 @@ Write your deliverables under /workspace/outputs.
|
|
|
4536
4705
|
`);
|
|
4537
4706
|
out.set(
|
|
4538
4707
|
"task.toml",
|
|
4539
|
-
|
|
4708
|
+
stringifyToml({
|
|
4540
4709
|
metadata: {
|
|
4541
4710
|
name: `agentsky-${idHash}`,
|
|
4542
4711
|
tier: opts.tier,
|
|
@@ -4961,6 +5130,44 @@ function renderBrowserProfile(profile) {
|
|
|
4961
5130
|
return lines.join("\n");
|
|
4962
5131
|
}
|
|
4963
5132
|
|
|
5133
|
+
// src/mac.ts
|
|
5134
|
+
var REACH_KINDS = ["folder", "browser", "screen", "host"];
|
|
5135
|
+
var OPEN = /* @__PURE__ */ new Set(["pending", "asking"]);
|
|
5136
|
+
var MAX_WAIT_SECONDS = 25;
|
|
5137
|
+
async function listMacs() {
|
|
5138
|
+
return (await api("GET", "/desktop/machines")).machines;
|
|
5139
|
+
}
|
|
5140
|
+
async function askMac(machine, kind, reason) {
|
|
5141
|
+
if (!REACH_KINDS.includes(kind)) throw new Error(`kind must be one of ${REACH_KINDS.join(", ")}`);
|
|
5142
|
+
return api("POST", `/desktop/machines/${encodeURIComponent(machine)}/reach-requests`, { kind, reason });
|
|
5143
|
+
}
|
|
5144
|
+
async function readReachRequest(id, waitSeconds = 0) {
|
|
5145
|
+
const wait = Math.min(Math.max(Math.floor(waitSeconds), 0), MAX_WAIT_SECONDS);
|
|
5146
|
+
return (await api("GET", `/desktop/reach-requests/${encodeURIComponent(id)}${wait ? `?wait=${wait}` : ""}`)).reach_request;
|
|
5147
|
+
}
|
|
5148
|
+
async function waitForAnswer(id, budgetSeconds) {
|
|
5149
|
+
const until = Date.now() + budgetSeconds * 1e3;
|
|
5150
|
+
for (; ; ) {
|
|
5151
|
+
const left = Math.ceil((until - Date.now()) / 1e3);
|
|
5152
|
+
const request = await readReachRequest(id, Math.max(left, 0));
|
|
5153
|
+
if (!OPEN.has(request.status) || left <= 0) return request;
|
|
5154
|
+
}
|
|
5155
|
+
}
|
|
5156
|
+
async function takeBack(mount) {
|
|
5157
|
+
await api("DELETE", `/desktop/mounts/${encodeURIComponent(mount)}`);
|
|
5158
|
+
}
|
|
5159
|
+
function renderMac(mac2) {
|
|
5160
|
+
const lends = mac2.reach ? mac2.reach.kinds.join(", ") || "nothing" : "unknown (update the Mac app)";
|
|
5161
|
+
const screen = mac2.reach && !mac2.reach.kinds.includes("screen") ? ` screen: ${mac2.reach.screen}` : "";
|
|
5162
|
+
const lines = [`${mac2.id} ${mac2.name} ${mac2.online ? "online" : "offline"} can lend: ${lends}${screen}`];
|
|
5163
|
+
for (const mount of mac2.mounts ?? []) lines.push(` ${mount.id} ${mount.kind.padEnd(7)} ${mount.root ?? mount.label}`);
|
|
5164
|
+
return lines.join("\n");
|
|
5165
|
+
}
|
|
5166
|
+
function renderReachRequest(request) {
|
|
5167
|
+
if (request.status === "granted" && request.mount) return `${request.id} granted ${request.mount.id} (${request.mount.kind}${request.mount.root ? ` ${request.mount.root}` : ""})`;
|
|
5168
|
+
return `${request.id} ${request.status}${request.error ? ` ${request.error.code}: ${request.error.message}` : ""}`;
|
|
5169
|
+
}
|
|
5170
|
+
|
|
4964
5171
|
// src/compare.ts
|
|
4965
5172
|
async function listComparisons(opts = {}) {
|
|
4966
5173
|
const qs = opts.archived ? "?archived=true" : "";
|
|
@@ -4981,6 +5188,53 @@ async function listThreads2(opts = {}) {
|
|
|
4981
5188
|
const { threads } = await api("GET", `/threads${qs}`);
|
|
4982
5189
|
return threads;
|
|
4983
5190
|
}
|
|
5191
|
+
async function startThread(body) {
|
|
5192
|
+
return api("POST", "/threads", body);
|
|
5193
|
+
}
|
|
5194
|
+
var threadPath = (id) => `/threads/${encodeURIComponent(id)}`;
|
|
5195
|
+
async function getThread(id) {
|
|
5196
|
+
return api("GET", threadPath(id));
|
|
5197
|
+
}
|
|
5198
|
+
async function organizeThread(id, change) {
|
|
5199
|
+
return api("PATCH", threadPath(id), change);
|
|
5200
|
+
}
|
|
5201
|
+
async function deleteThread(id) {
|
|
5202
|
+
return api("DELETE", threadPath(id));
|
|
5203
|
+
}
|
|
5204
|
+
async function sendThreadMessage(id, body) {
|
|
5205
|
+
return api("POST", `${threadPath(id)}/messages`, body);
|
|
5206
|
+
}
|
|
5207
|
+
async function addThreadAgent(id, body) {
|
|
5208
|
+
return api("POST", `${threadPath(id)}/agents`, body);
|
|
5209
|
+
}
|
|
5210
|
+
async function removeThreadAgent(id, columnId) {
|
|
5211
|
+
return api("DELETE", `${threadPath(id)}/agents/${encodeURIComponent(columnId)}`);
|
|
5212
|
+
}
|
|
5213
|
+
async function interruptThread(id, columnId) {
|
|
5214
|
+
return api("POST", `${threadPath(id)}/interrupt`, columnId ? { columnId } : {});
|
|
5215
|
+
}
|
|
5216
|
+
async function forkThread(id, body) {
|
|
5217
|
+
return api("POST", `${threadPath(id)}/fork`, body);
|
|
5218
|
+
}
|
|
5219
|
+
async function changeThreadModel(id, change, columnId) {
|
|
5220
|
+
return api("PATCH", `${threadPath(id)}/engine${columnId ? `?column=${encodeURIComponent(columnId)}` : ""}`, change);
|
|
5221
|
+
}
|
|
5222
|
+
async function searchThreads(q, opts = {}) {
|
|
5223
|
+
const params = new URLSearchParams({ q, ...opts.threads ? { threads: opts.threads } : {}, ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
5224
|
+
return api("GET", `/threads/search?${params}`);
|
|
5225
|
+
}
|
|
5226
|
+
async function threadOptions() {
|
|
5227
|
+
return api("GET", "/threads/options");
|
|
5228
|
+
}
|
|
5229
|
+
async function joinThread(sessionId) {
|
|
5230
|
+
return api("POST", "/threads/joinable", { sessionId });
|
|
5231
|
+
}
|
|
5232
|
+
async function listProjects() {
|
|
5233
|
+
return api("GET", "/projects");
|
|
5234
|
+
}
|
|
5235
|
+
async function rememberProject(project2) {
|
|
5236
|
+
return api("PUT", "/projects", { project: project2 });
|
|
5237
|
+
}
|
|
4984
5238
|
function renderComparisonSummary(c) {
|
|
4985
5239
|
const agents = c.agents.map((a) => `${a.agentType}/${a.llm}`).join(" vs ");
|
|
4986
5240
|
const status = c.statusLine ?? c.attention ?? (c.archived ? "archived" : "active");
|
|
@@ -4993,8 +5247,202 @@ function renderThreadSummary(t) {
|
|
|
4993
5247
|
return `${t.id.slice(0, 12)} ${flag}${t.title.slice(0, 40).padEnd(44)} [${agents}] ${status}`;
|
|
4994
5248
|
}
|
|
4995
5249
|
|
|
5250
|
+
// src/library.ts
|
|
5251
|
+
var artifactPath = (id) => `/library/artifacts/${encodeURIComponent(id)}`;
|
|
5252
|
+
async function listLibrary(query = {}) {
|
|
5253
|
+
const params = new URLSearchParams(Object.entries(query).filter((entry2) => Boolean(entry2[1])));
|
|
5254
|
+
const qs = params.size ? `?${params}` : "";
|
|
5255
|
+
return api("GET", `/library/artifacts${qs}`);
|
|
5256
|
+
}
|
|
5257
|
+
async function uploadToLibrary(file, requestId, workspace2) {
|
|
5258
|
+
const form = new FormData();
|
|
5259
|
+
form.append("file", new Blob([new Uint8Array(file.bytes)], { type: file.mediaType }), file.name);
|
|
5260
|
+
form.append("requestId", requestId);
|
|
5261
|
+
if (workspace2) form.append("workspace", workspace2);
|
|
5262
|
+
return apiForm("POST", "/library/artifacts", form);
|
|
5263
|
+
}
|
|
5264
|
+
async function getLibraryArtifact(id) {
|
|
5265
|
+
return api("GET", artifactPath(id));
|
|
5266
|
+
}
|
|
5267
|
+
async function deleteLibraryArtifact(id, revision) {
|
|
5268
|
+
return api("DELETE", artifactPath(id), { revision });
|
|
5269
|
+
}
|
|
5270
|
+
async function shareLibraryArtifact(id, enabled) {
|
|
5271
|
+
const { currentVersion } = await getLibraryArtifact(id);
|
|
5272
|
+
return api("PATCH", `${artifactPath(id)}/share`, { enabled, revision: currentVersion });
|
|
5273
|
+
}
|
|
5274
|
+
async function placeLibraryArtifact(id, workspace2) {
|
|
5275
|
+
return api("PATCH", `${artifactPath(id)}/workspace`, { workspace: workspace2 });
|
|
5276
|
+
}
|
|
5277
|
+
async function downloadLibraryArtifact(id, version) {
|
|
5278
|
+
return apiBytes("GET", `${artifactPath(id)}/download${version ? `?version=${version}` : ""}`);
|
|
5279
|
+
}
|
|
5280
|
+
function renderLibraryArtifact(artifact2) {
|
|
5281
|
+
const version = artifact2.currentVersion ? `v${artifact2.currentVersion}` : "";
|
|
5282
|
+
return `${artifact2.id} ${artifact2.name} ${version}${artifact2.shared ? " shared" : ""}`;
|
|
5283
|
+
}
|
|
5284
|
+
|
|
5285
|
+
// src/api-command.ts
|
|
5286
|
+
import { readFileSync as readFileSync16, writeFileSync as writeFileSync9 } from "fs";
|
|
5287
|
+
var API_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE"];
|
|
5288
|
+
function readData(data, stdin = () => readFileSync16(0, "utf8")) {
|
|
5289
|
+
if (data === void 0) return void 0;
|
|
5290
|
+
const raw = data === "-" ? stdin() : data.startsWith("@") ? readFileSync16(data.slice(1), "utf8") : data;
|
|
5291
|
+
try {
|
|
5292
|
+
JSON.parse(raw);
|
|
5293
|
+
} catch {
|
|
5294
|
+
throw new Error("--data must be JSON (inline, @file or - for stdin)");
|
|
5295
|
+
}
|
|
5296
|
+
return raw;
|
|
5297
|
+
}
|
|
5298
|
+
function requestPath(path, query = []) {
|
|
5299
|
+
if (!path.startsWith("/") || path.startsWith("//")) throw new Error("path must start with /, e.g. /threads");
|
|
5300
|
+
const clean = path.replace(/^\/v1(?=\/)/, "");
|
|
5301
|
+
const params = new URLSearchParams();
|
|
5302
|
+
for (const pair of query) {
|
|
5303
|
+
const at = pair.indexOf("=");
|
|
5304
|
+
if (at <= 0) throw new Error(`--query needs key=value, got "${pair}"`);
|
|
5305
|
+
params.append(pair.slice(0, at), pair.slice(at + 1));
|
|
5306
|
+
}
|
|
5307
|
+
const sep3 = clean.includes("?") ? "&" : "?";
|
|
5308
|
+
const encoded = params.toString();
|
|
5309
|
+
return encoded ? `${clean}${sep3}${encoded}` : clean;
|
|
5310
|
+
}
|
|
5311
|
+
function matchOperations(doc, words) {
|
|
5312
|
+
const needles = words.map((w) => w.toLowerCase());
|
|
5313
|
+
const out = [];
|
|
5314
|
+
for (const [path, item] of Object.entries(doc.paths ?? {})) {
|
|
5315
|
+
for (const [method, op] of Object.entries(item)) {
|
|
5316
|
+
if (!API_METHODS.includes(method.toUpperCase())) continue;
|
|
5317
|
+
const hay = `${method} ${path} ${op.summary ?? ""} ${op.operationId ?? ""}`.toLowerCase();
|
|
5318
|
+
if (needles.every((n) => hay.includes(n))) out.push({ method: method.toUpperCase(), path, summary: op.summary, operationId: op.operationId });
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
return out.sort((a, b) => a.path.localeCompare(b.path) || a.method.localeCompare(b.method));
|
|
5322
|
+
}
|
|
5323
|
+
async function searchApi(words) {
|
|
5324
|
+
const res = await apiRaw("GET", "/openapi.json", { auth: false });
|
|
5325
|
+
if (!res.ok) throw new Error(`could not read the API reference (${res.status})`);
|
|
5326
|
+
const ops = matchOperations(await res.json(), words);
|
|
5327
|
+
if (!ops.length) {
|
|
5328
|
+
console.log("no operations match");
|
|
5329
|
+
return;
|
|
5330
|
+
}
|
|
5331
|
+
for (const op of ops) console.log(`${op.method.padEnd(6)} ${op.path}${op.summary ? ` \u2014 ${op.summary}` : ""}`);
|
|
5332
|
+
}
|
|
5333
|
+
async function callApi(method, path, opts) {
|
|
5334
|
+
const verb = method.toUpperCase();
|
|
5335
|
+
if (!API_METHODS.includes(verb)) throw new Error(`method must be one of ${API_METHODS.join(", ")} or "search"`);
|
|
5336
|
+
const body = readData(opts.data);
|
|
5337
|
+
if (body !== void 0 && verb === "GET") throw new Error("a GET takes no --data; use --query");
|
|
5338
|
+
const res = await apiRaw(verb, requestPath(path, opts.query), { body, idempotencyKey: opts.idempotencyKey, universe: opts.workspace });
|
|
5339
|
+
const type = res.headers.get("content-type") ?? "";
|
|
5340
|
+
if (opts.output) {
|
|
5341
|
+
writeFileSync9(opts.output, new Uint8Array(await res.arrayBuffer()));
|
|
5342
|
+
console.error(`${res.status} \u2192 ${opts.output}`);
|
|
5343
|
+
} else if (/json/.test(type)) {
|
|
5344
|
+
const text = await res.text();
|
|
5345
|
+
let printed = text;
|
|
5346
|
+
try {
|
|
5347
|
+
printed = JSON.stringify(JSON.parse(text), null, 2);
|
|
5348
|
+
} catch {
|
|
5349
|
+
}
|
|
5350
|
+
(res.ok ? console.log : console.error)(printed);
|
|
5351
|
+
} else if (/^text\/|xml|csv|markdown/.test(type) || !type) {
|
|
5352
|
+
(res.ok ? console.log : console.error)(await res.text());
|
|
5353
|
+
} else {
|
|
5354
|
+
console.error(`${res.status}: ${type} body (${res.headers.get("content-length") ?? "?"} bytes); pass --output <file> to save it`);
|
|
5355
|
+
}
|
|
5356
|
+
if (!res.ok && res.status !== 304) process.exitCode = 1;
|
|
5357
|
+
}
|
|
5358
|
+
|
|
5359
|
+
// src/billing-plans.ts
|
|
5360
|
+
var readCodingPlan = () => api("GET", "/billing/coding-plan");
|
|
5361
|
+
function joinCodingPlan(dollars2, requestId) {
|
|
5362
|
+
const amount = Number(dollars2.trim().replace(/^\$/, ""));
|
|
5363
|
+
if (!Number.isInteger(amount) || amount <= 0) throw new Error(`<dollars> must be whole dollars, got ${JSON.stringify(dollars2)}`);
|
|
5364
|
+
return api("POST", "/billing/coding-plan", { amount_usd: amount, request_id: requestId });
|
|
5365
|
+
}
|
|
5366
|
+
var cancelCodingPlan = () => api("DELETE", "/billing/coding-plan");
|
|
5367
|
+
function renderCodingPlan(plan) {
|
|
5368
|
+
if (!plan.active) {
|
|
5369
|
+
const lines2 = [`coding plan: not a member (enrollment ${plan.enrollment}; monthly amounts $${plan.amounts_usd.join(", $")})`];
|
|
5370
|
+
if (plan.charge_failure) lines2.push(` last charge failed: ${plan.charge_failure}`);
|
|
5371
|
+
return lines2.join("\n");
|
|
5372
|
+
}
|
|
5373
|
+
const until = plan.current_period_end?.slice(0, 10) ?? "?";
|
|
5374
|
+
const lines = [`coding plan: $${plan.monthly_credit_usd}/month, paid until ${until}, ${plan.renews ? "renews" : "does not renew"}`];
|
|
5375
|
+
if (plan.charge_failure) lines.push(` last charge failed: ${plan.charge_failure}`);
|
|
5376
|
+
return lines.join("\n");
|
|
5377
|
+
}
|
|
5378
|
+
function readRequestUsage(opts) {
|
|
5379
|
+
const query = usageQuery(opts);
|
|
5380
|
+
const cursor = opts.cursor ? `${query ? "&" : "?"}cursor=${encodeURIComponent(opts.cursor)}` : "";
|
|
5381
|
+
return api("GET", `/billing/usage/requests${query}${cursor}`);
|
|
5382
|
+
}
|
|
5383
|
+
function renderRequestUsage(page) {
|
|
5384
|
+
const lines = page.rows.map((row) => `${row.at.slice(0, 19).replace("T", " ")} ${(row.apiKeyName ?? "").padEnd(16).slice(0, 16)} ${(row.model ?? "-").padEnd(28).slice(0, 28)} ${String(row.httpStatus ?? "-").padStart(3)} ${String(row.inputTokens ?? 0).padStart(9)} in ${String(row.outputTokens ?? 0).padStart(8)} out ${`$${(row.billedCostUsd ?? 0).toFixed(6)}`.padStart(11)}`);
|
|
5385
|
+
if (!lines.length) lines.push("no API requests in this range");
|
|
5386
|
+
if (page.nextCursor) lines.push(`more: --cursor ${page.nextCursor}`);
|
|
5387
|
+
return lines.join("\n");
|
|
5388
|
+
}
|
|
5389
|
+
var readReferralLinks = () => api("GET", "/billing/referral-links");
|
|
5390
|
+
function referralLinkBody(opts) {
|
|
5391
|
+
if (opts.price === void 0 === !opts.sku?.length) throw new Error("pass --price for every model, or --sku id=multiplier for each SKU, not both");
|
|
5392
|
+
const body = {};
|
|
5393
|
+
if (opts.price !== void 0) body.customer_multiplier = multiplier("--price", opts.price);
|
|
5394
|
+
if (opts.sku?.length) {
|
|
5395
|
+
body.sku_prices = opts.sku.map((pair) => {
|
|
5396
|
+
const at = pair.lastIndexOf("=");
|
|
5397
|
+
if (at <= 0) throw new Error(`--sku must be id=multiplier, got ${JSON.stringify(pair)}`);
|
|
5398
|
+
return { sku_id: pair.slice(0, at), multiplier: multiplier("--sku", pair.slice(at + 1)) };
|
|
5399
|
+
});
|
|
5400
|
+
}
|
|
5401
|
+
if (opts.email) body.audience_email = opts.email;
|
|
5402
|
+
if (opts.uses) body.max_redemptions = wholeNumber2("--uses", opts.uses);
|
|
5403
|
+
if (opts.days) body.expires_in_days = wholeNumber2("--days", opts.days);
|
|
5404
|
+
return body;
|
|
5405
|
+
}
|
|
5406
|
+
var createReferralLink = (opts) => api("POST", "/billing/referral-links", referralLinkBody(opts));
|
|
5407
|
+
var disableReferralLink = (id) => api("POST", `/billing/referral-links/${encodeURIComponent(id)}/disable`);
|
|
5408
|
+
function renderReferralLinks(dashboard) {
|
|
5409
|
+
const lines = [`priced from ${dashboard.source_plan.name}: customers pay at least ${Math.round(dashboard.source_plan.multiplier * 100)}% of public price`];
|
|
5410
|
+
for (const link2 of dashboard.links) {
|
|
5411
|
+
const state = link2.disabled_at ? "disabled" : `expires ${link2.expires_at.slice(0, 10)}`;
|
|
5412
|
+
const audience = link2.audience_email ? ` for ${link2.audience_email} (${link2.email_status})` : "";
|
|
5413
|
+
lines.push(` ${link2.id} ${Math.round(link2.customer_multiplier * 100)}% ${link2.redeemed_count}/${link2.max_redemptions} used ${state}${audience}`);
|
|
5414
|
+
lines.push(` ${link2.url}`);
|
|
5415
|
+
}
|
|
5416
|
+
if (!dashboard.links.length) lines.push(" no links yet");
|
|
5417
|
+
const earned = dashboard.earnings.reduce((sum, earning) => sum + earning.amount_usd, 0);
|
|
5418
|
+
lines.push(`earned $${earned.toFixed(2)} from ${dashboard.earnings.length} customer${dashboard.earnings.length === 1 ? "" : "s"}`);
|
|
5419
|
+
return lines.join("\n");
|
|
5420
|
+
}
|
|
5421
|
+
function multiplier(flag, value) {
|
|
5422
|
+
const trimmed = value.trim();
|
|
5423
|
+
const percent = trimmed.endsWith("%");
|
|
5424
|
+
const number = Number(percent ? trimmed.slice(0, -1) : trimmed);
|
|
5425
|
+
if (!Number.isFinite(number) || number <= 0) throw new Error(`${flag} must be a share of public price like 0.8 or 80%, got ${JSON.stringify(value)}`);
|
|
5426
|
+
return percent ? number / 100 : number;
|
|
5427
|
+
}
|
|
5428
|
+
function wholeNumber2(flag, value) {
|
|
5429
|
+
if (!/^\d+$/.test(value.trim())) throw new Error(`${flag} must be a whole number, got ${JSON.stringify(value)}`);
|
|
5430
|
+
return Number(value.trim());
|
|
5431
|
+
}
|
|
5432
|
+
|
|
5433
|
+
// src/workspace-flag.ts
|
|
5434
|
+
function workspaceFlagIsCommands(argv, commandPath) {
|
|
5435
|
+
let at = 0;
|
|
5436
|
+
let matched = 0;
|
|
5437
|
+
for (; at < argv.length && matched < commandPath.length; at += 1) {
|
|
5438
|
+
if (argv[at] === commandPath[matched]) matched += 1;
|
|
5439
|
+
}
|
|
5440
|
+
if (matched < commandPath.length) return false;
|
|
5441
|
+
return argv.slice(at).some((arg) => arg === "--workspace" || arg.startsWith("--workspace="));
|
|
5442
|
+
}
|
|
5443
|
+
|
|
4996
5444
|
// src/harness.ts
|
|
4997
|
-
import { existsSync as
|
|
5445
|
+
import { existsSync as existsSync12, mkdirSync as mkdirSync6, readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
|
|
4998
5446
|
import { dirname as dirname9, join as join18, resolve as resolve8 } from "path";
|
|
4999
5447
|
var HARNESS_FILE = "harness.json";
|
|
5000
5448
|
function harnessFilePath(dir) {
|
|
@@ -5002,11 +5450,11 @@ function harnessFilePath(dir) {
|
|
|
5002
5450
|
}
|
|
5003
5451
|
function readHarnessFile(dir) {
|
|
5004
5452
|
const path = harnessFilePath(dir);
|
|
5005
|
-
if (!
|
|
5453
|
+
if (!existsSync12(path)) {
|
|
5006
5454
|
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
5007
5455
|
}
|
|
5008
5456
|
try {
|
|
5009
|
-
return JSON.parse(
|
|
5457
|
+
return JSON.parse(readFileSync17(path, "utf8"));
|
|
5010
5458
|
} catch (err) {
|
|
5011
5459
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
5012
5460
|
}
|
|
@@ -5033,11 +5481,11 @@ var INIT_COMMENT = `# harness.json \u2014 workspace harness profile skeleton
|
|
|
5033
5481
|
`;
|
|
5034
5482
|
function harnessInit(dir) {
|
|
5035
5483
|
const path = harnessFilePath(dir);
|
|
5036
|
-
if (
|
|
5484
|
+
if (existsSync12(path)) {
|
|
5037
5485
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
5038
5486
|
}
|
|
5039
5487
|
mkdirSync6(dirname9(path), { recursive: true });
|
|
5040
|
-
|
|
5488
|
+
writeFileSync10(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
5041
5489
|
`);
|
|
5042
5490
|
console.log(`Created ${path}`);
|
|
5043
5491
|
console.log(INIT_COMMENT);
|
|
@@ -5170,9 +5618,16 @@ function printModelBilling(session2) {
|
|
|
5170
5618
|
}
|
|
5171
5619
|
var program = new Command();
|
|
5172
5620
|
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)");
|
|
5173
|
-
program.hook("preAction", () => {
|
|
5621
|
+
program.hook("preAction", (_root, action) => {
|
|
5174
5622
|
const workspace2 = program.opts().workspace;
|
|
5175
|
-
if (workspace2)
|
|
5623
|
+
if (!workspace2) return;
|
|
5624
|
+
const path = [];
|
|
5625
|
+
for (let command = action; command && command !== program; command = command.parent) path.unshift(command.name());
|
|
5626
|
+
if (action.options.some((option) => option.long === "--workspace") && workspaceFlagIsCommands(process.argv.slice(2), path)) {
|
|
5627
|
+
action.setOptionValue("workspace", workspace2);
|
|
5628
|
+
return;
|
|
5629
|
+
}
|
|
5630
|
+
process.env.SKY_WORKSPACE = workspace2;
|
|
5176
5631
|
});
|
|
5177
5632
|
var lastProgressDecile = -1;
|
|
5178
5633
|
function uploadProgress(sent, total) {
|
|
@@ -5207,6 +5662,9 @@ auth.command("whoami").description("who the current token acts as").action(
|
|
|
5207
5662
|
console.log(
|
|
5208
5663
|
`${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (default)" : ""} \xB7 scopes ${who.scopes.join(",")}`
|
|
5209
5664
|
);
|
|
5665
|
+
if (who.credential_universe) {
|
|
5666
|
+
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\`)`);
|
|
5667
|
+
}
|
|
5210
5668
|
})
|
|
5211
5669
|
);
|
|
5212
5670
|
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(
|
|
@@ -5231,6 +5689,19 @@ auth.command("usage <provider>").description("plan allowance left on a connected
|
|
|
5231
5689
|
auth.command("switch-to-credits <provider>").description("move the agents on a model subscription onto platform credits (do this before disconnecting)").option("--account <id>", "an additional account (default: the provider's first account)").option("-y, --yes", "skip the confirmation prompt").action(run(async (name, opts) => switchToCredits(name, opts)));
|
|
5232
5690
|
auth.command("accounts").description("list connected model subscriptions and the agents they power").action(run(async () => listSubscriptions()));
|
|
5233
5691
|
auth.command("disconnect <provider>").description("disconnect a model subscription (agents on it fail until switched)").option("-y, --yes", "skip the confirmation prompt").action(run(async (name, opts) => disconnect(name, opts)));
|
|
5692
|
+
auth.command("apps").description("the registered apps you let into your account, with the scopes you allowed").action(
|
|
5693
|
+
run(async () => {
|
|
5694
|
+
const grants = await listAppGrants();
|
|
5695
|
+
if (!grants.length) console.log("No apps have access to your account.");
|
|
5696
|
+
for (const grant of grants) console.log(renderAppGrant(grant));
|
|
5697
|
+
})
|
|
5698
|
+
);
|
|
5699
|
+
auth.command("revoke-app <clientId>").description("remove an app's access to your account; its tokens stop working at once (admin scope)").action(
|
|
5700
|
+
run(async (clientId) => {
|
|
5701
|
+
await revokeAppGrant(clientId);
|
|
5702
|
+
console.log(`Removed ${clientId}'s access to your account`);
|
|
5703
|
+
})
|
|
5704
|
+
);
|
|
5234
5705
|
var universe = program.command("universe").description("grouping boundary for agents");
|
|
5235
5706
|
universe.command("list").action(
|
|
5236
5707
|
run(async () => {
|
|
@@ -5277,7 +5748,7 @@ universe.command("invite <emails...>").description("invite people by email; prin
|
|
|
5277
5748
|
run(async (emails, opts) => {
|
|
5278
5749
|
const result = await invite(await workspaceSlug(opts.universe), emails, opts);
|
|
5279
5750
|
for (const i of result.invites) console.log(`invited ${i.email} as ${i.role} ${i.id}`);
|
|
5280
|
-
for (const
|
|
5751
|
+
for (const link2 of result.links) console.log(` ${link2.email} joins with: sky universe join ${link2.token}`);
|
|
5281
5752
|
if (!result.invites.length) console.log("nobody new to invite \u2014 everyone listed is already a member");
|
|
5282
5753
|
})
|
|
5283
5754
|
);
|
|
@@ -5330,6 +5801,12 @@ workspace.command("list").action(
|
|
|
5330
5801
|
})
|
|
5331
5802
|
);
|
|
5332
5803
|
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));
|
|
5804
|
+
async function renameWorkspaceCommand(slug, name) {
|
|
5805
|
+
const { universe: renamed } = await api("PATCH", `/universes/${encodeURIComponent(slug)}`, { name: name.join(" ") });
|
|
5806
|
+
console.log(`${renamed.slug} is now "${renamed.name}"`);
|
|
5807
|
+
}
|
|
5808
|
+
universe.command("rename <slug> <name...>").description("rename a workspace you own (its slug stays)").action(run(renameWorkspaceCommand));
|
|
5809
|
+
workspace.command("rename <slug> <name...>").description("rename a workspace you own (alias of universe rename)").action(run(renameWorkspaceCommand));
|
|
5333
5810
|
var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
|
|
5334
5811
|
async function policyScope(opts) {
|
|
5335
5812
|
return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
|
|
@@ -5440,7 +5917,7 @@ agent.command("destroy <id>").description("delete the spec; requires all session
|
|
|
5440
5917
|
console.log(`Destroyed ${id}`);
|
|
5441
5918
|
})
|
|
5442
5919
|
);
|
|
5443
|
-
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(
|
|
5920
|
+
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(
|
|
5444
5921
|
run(
|
|
5445
5922
|
async (id, text, opts) => sendAndFollow(await resolveSessionRef(id), text ?? "", opts)
|
|
5446
5923
|
)
|
|
@@ -5554,7 +6031,7 @@ session.command("delete <id>").description("permanently delete the session, its
|
|
|
5554
6031
|
console.log(`Deleted ${id}`);
|
|
5555
6032
|
})
|
|
5556
6033
|
);
|
|
5557
|
-
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(
|
|
6034
|
+
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(
|
|
5558
6035
|
run(
|
|
5559
6036
|
async (ref, text, opts) => sendAndFollow(await resolveSessionRef(ref), text ?? "", opts)
|
|
5560
6037
|
)
|
|
@@ -5594,12 +6071,12 @@ session.command("share <ref>").description("print the session's share link, turn
|
|
|
5594
6071
|
console.log(`Sharing is off for ${id}`);
|
|
5595
6072
|
return;
|
|
5596
6073
|
}
|
|
5597
|
-
const
|
|
6074
|
+
const link2 = await api("POST", path, {
|
|
5598
6075
|
access: opts.view ? "view" : "prompt",
|
|
5599
6076
|
...opts.rotate ? { rotate: true } : {}
|
|
5600
6077
|
});
|
|
5601
|
-
if (
|
|
5602
|
-
else console.log(`view-only token: ${
|
|
6078
|
+
if (link2.shareUrl) console.log(link2.shareUrl);
|
|
6079
|
+
else console.log(`view-only token: ${link2.token} \u2014 read with GET /links/${link2.token}/events`);
|
|
5603
6080
|
})
|
|
5604
6081
|
);
|
|
5605
6082
|
session.command("usage <ref>").description("what the session has cost so far, and its newest billed entries").option("--limit <n>", "entries to list (default 20)").option("--json", "print the API's answer as JSON").action(
|
|
@@ -5747,7 +6224,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
|
|
|
5747
6224
|
run(async (ref, path, opts) => {
|
|
5748
6225
|
const bytes = await readFile(await resolveSessionRef(ref), path);
|
|
5749
6226
|
if (opts.out) {
|
|
5750
|
-
|
|
6227
|
+
writeFileSync11(opts.out, bytes);
|
|
5751
6228
|
console.error(`saved ${bytes.length} bytes to ${opts.out}`);
|
|
5752
6229
|
} else process.stdout.write(bytes);
|
|
5753
6230
|
})
|
|
@@ -5764,7 +6241,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
|
|
|
5764
6241
|
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)})`);
|
|
5765
6242
|
if (!patch.length) console.error("nothing changed");
|
|
5766
6243
|
else if (opts.out) {
|
|
5767
|
-
|
|
6244
|
+
writeFileSync11(opts.out, patch);
|
|
5768
6245
|
console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
|
|
5769
6246
|
} else process.stdout.write(patch);
|
|
5770
6247
|
})
|
|
@@ -5818,9 +6295,10 @@ function interactionMode(opts) {
|
|
|
5818
6295
|
}
|
|
5819
6296
|
async function uploadAttachments(opts) {
|
|
5820
6297
|
const files2 = [];
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
6298
|
+
const refs = [...opts.image ?? [], ...opts.file ?? []].map((ref) => ({ ref, byId: false }));
|
|
6299
|
+
for (const { ref, byId } of [...refs, ...(opts.fileId ?? []).map((ref2) => ({ ref: ref2, byId: true }))]) {
|
|
6300
|
+
const { file, uploaded } = await attachment(ref, { byId });
|
|
6301
|
+
console.error(uploaded ? `uploaded ${file.filename} \u2192 ${file.id}` : `attaching ${file.id} (${file.filename})`);
|
|
5824
6302
|
files2.push(file);
|
|
5825
6303
|
}
|
|
5826
6304
|
return files2;
|
|
@@ -5837,9 +6315,10 @@ async function sendAndFollow(sessionId, text, opts) {
|
|
|
5837
6315
|
const controller = new AbortController();
|
|
5838
6316
|
let opened = null;
|
|
5839
6317
|
if (!opts.detach) {
|
|
6318
|
+
const render = createEventRenderer();
|
|
5840
6319
|
opened = (async () => {
|
|
5841
6320
|
for await (const frame of streamEvents(sessionId, controller.signal)) {
|
|
5842
|
-
|
|
6321
|
+
render(frame.event, frame.data);
|
|
5843
6322
|
if (turnOver(frame)) {
|
|
5844
6323
|
controller.abort();
|
|
5845
6324
|
break;
|
|
@@ -5860,10 +6339,11 @@ async function chatLoop(sessionId, opts = {}) {
|
|
|
5860
6339
|
const controller = new AbortController();
|
|
5861
6340
|
const rl = createInterface6({ input: process.stdin, output: process.stdout, prompt: "> " });
|
|
5862
6341
|
let system = opts.system;
|
|
6342
|
+
const render = createEventRenderer();
|
|
5863
6343
|
void (async () => {
|
|
5864
6344
|
try {
|
|
5865
6345
|
for await (const frame of streamEvents(sessionId, controller.signal)) {
|
|
5866
|
-
|
|
6346
|
+
render(frame.event, frame.data);
|
|
5867
6347
|
if (frame.event === "session.status_terminated") rl.close();
|
|
5868
6348
|
else if (frame.event === "session.status_idle") rl.prompt();
|
|
5869
6349
|
}
|
|
@@ -5970,10 +6450,32 @@ secrets.command("unset <id> <key>").action(
|
|
|
5970
6450
|
console.log(`unset ${key}`);
|
|
5971
6451
|
})
|
|
5972
6452
|
);
|
|
5973
|
-
|
|
6453
|
+
async function launchFromFile(path, spec, opts) {
|
|
6454
|
+
const { vcpus, memoryMb, ...specBody } = spec;
|
|
6455
|
+
const body = {
|
|
6456
|
+
...specBody,
|
|
6457
|
+
...opts.type ? { agentType: opts.type } : {},
|
|
6458
|
+
...opts.llm ? { llm: opts.llm } : {}
|
|
6459
|
+
};
|
|
6460
|
+
const { agent: created } = await api("POST", "/agents", body);
|
|
6461
|
+
const { session: session2 } = await api("POST", "/sessions", {
|
|
6462
|
+
agent: created.id,
|
|
6463
|
+
...opts.start ? {} : { provision: "deferred" },
|
|
6464
|
+
...vcpus !== void 0 ? { vcpus } : {},
|
|
6465
|
+
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
6466
|
+
});
|
|
6467
|
+
stampId(path, created.id);
|
|
6468
|
+
console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
|
|
6469
|
+
printModelBilling(session2);
|
|
6470
|
+
console.log(opts.start ? " its first session is starting now" : " its first session starts on the first message");
|
|
6471
|
+
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`);
|
|
6472
|
+
console.log(` sky agent message ${created.id} "hello"`);
|
|
6473
|
+
console.log(` sky agent chat ${created.id}`);
|
|
6474
|
+
}
|
|
6475
|
+
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(
|
|
5974
6476
|
run(async (opts) => {
|
|
5975
6477
|
const path = resolve9(opts.file);
|
|
5976
|
-
if (!
|
|
6478
|
+
if (!existsSync13(path)) {
|
|
5977
6479
|
const name = basename9(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
5978
6480
|
scaffoldAgentFile(path, name);
|
|
5979
6481
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
@@ -5983,55 +6485,27 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
|
|
|
5983
6485
|
console.log(`${file.id} already launched \u2014 use \`sky deploy\` to re-apply.`);
|
|
5984
6486
|
return;
|
|
5985
6487
|
}
|
|
5986
|
-
|
|
5987
|
-
const body = {
|
|
5988
|
-
...specBody,
|
|
5989
|
-
...opts.type ? { agentType: opts.type } : {},
|
|
5990
|
-
...opts.llm ? { llm: opts.llm } : {}
|
|
5991
|
-
};
|
|
5992
|
-
const { agent: created } = await api("POST", "/agents", body);
|
|
5993
|
-
console.log("Starting its runtime\u2026");
|
|
5994
|
-
const { session: session2 } = await api("POST", "/sessions", {
|
|
5995
|
-
agent: created.id,
|
|
5996
|
-
...vcpus !== void 0 ? { vcpus } : {},
|
|
5997
|
-
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
5998
|
-
});
|
|
5999
|
-
stampId(path, created.id);
|
|
6000
|
-
console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
|
|
6001
|
-
printModelBilling(session2);
|
|
6002
|
-
console.log(` sky agent message ${created.id} "hello"`);
|
|
6003
|
-
console.log(` sky agent chat ${created.id}`);
|
|
6488
|
+
await launchFromFile(path, spec, opts);
|
|
6004
6489
|
})
|
|
6005
6490
|
);
|
|
6006
|
-
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
6491
|
+
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(
|
|
6007
6492
|
run(async (opts) => {
|
|
6008
6493
|
const path = resolve9(opts.file);
|
|
6009
6494
|
const { file, spec } = readAgentFile(path);
|
|
6010
6495
|
if (!file.id) {
|
|
6011
6496
|
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
6012
|
-
|
|
6013
|
-
const { agent: created } = await api("POST", "/agents", specBody);
|
|
6014
|
-
await api("POST", "/sessions", {
|
|
6015
|
-
agent: created.id,
|
|
6016
|
-
...vcpus !== void 0 ? { vcpus } : {},
|
|
6017
|
-
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
6018
|
-
});
|
|
6019
|
-
stampId(path, created.id);
|
|
6020
|
-
console.log(`Launched ${created.id}`);
|
|
6497
|
+
await launchFromFile(path, spec, {});
|
|
6021
6498
|
return;
|
|
6022
6499
|
}
|
|
6500
|
+
const ref = encodeURIComponent(file.id);
|
|
6501
|
+
const { agent: before } = await api("GET", `/agents/${ref}`);
|
|
6502
|
+
let promptChanged = false;
|
|
6023
6503
|
if (spec.prompt !== void 0) {
|
|
6024
|
-
await api("PUT", `/agents/${
|
|
6025
|
-
|
|
6026
|
-
});
|
|
6504
|
+
const saved = await api("PUT", `/agents/${ref}/prompt`, { prompt: spec.prompt });
|
|
6505
|
+
promptChanged = saved.unchanged === false;
|
|
6027
6506
|
}
|
|
6028
|
-
await api("PATCH", `/agents/${
|
|
6029
|
-
|
|
6030
|
-
...spec.capabilities ? { capabilities: spec.capabilities } : {},
|
|
6031
|
-
...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
6032
|
-
...spec.skills ? { skills: spec.skills } : {}
|
|
6033
|
-
});
|
|
6034
|
-
console.log(`Deployed ${file.id} (prompt + settings re-applied)`);
|
|
6507
|
+
const { agent: after } = await api("PATCH", `/agents/${ref}`, deployPatch(spec));
|
|
6508
|
+
for (const line of deployReport(file.id, spec, before, after, promptChanged)) console.log(line);
|
|
6035
6509
|
})
|
|
6036
6510
|
);
|
|
6037
6511
|
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(
|
|
@@ -6272,14 +6746,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
6272
6746
|
run(async (taskId, path, opts) => {
|
|
6273
6747
|
const bytes = await readTaskFile(taskId, path);
|
|
6274
6748
|
if (opts.out) {
|
|
6275
|
-
|
|
6749
|
+
writeFileSync11(opts.out, bytes);
|
|
6276
6750
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6277
6751
|
} else process.stdout.write(bytes);
|
|
6278
6752
|
})
|
|
6279
6753
|
);
|
|
6280
6754
|
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(
|
|
6281
6755
|
run(async (taskId, path, local) => {
|
|
6282
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
6756
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync18(local), mediaTypeFor(local));
|
|
6283
6757
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
6284
6758
|
})
|
|
6285
6759
|
);
|
|
@@ -6352,7 +6826,7 @@ task.command("verdict <task> <did-the-job>").description("record whether the tas
|
|
|
6352
6826
|
console.log(`Recorded verdict on task ${updated.id}: did_the_job=${value === "yes"}`);
|
|
6353
6827
|
})
|
|
6354
6828
|
);
|
|
6355
|
-
var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent
|
|
6829
|
+
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)");
|
|
6356
6830
|
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(
|
|
6357
6831
|
run(async (dir, opts) => {
|
|
6358
6832
|
const { skill } = await uploadSkill(dir, opts.name);
|
|
@@ -6360,7 +6834,7 @@ skills.command("upload <dir>").description("upload a skill directory (SKILL.md a
|
|
|
6360
6834
|
console.log(JSON.stringify(skill));
|
|
6361
6835
|
return;
|
|
6362
6836
|
}
|
|
6363
|
-
console.log(`Uploaded ${skill.id} (${skill.displayName ?? dir}). Attach it in agent.toml:`);
|
|
6837
|
+
console.log(`Uploaded ${skill.id} (${skill.displayName ?? dir}). Attach it: sky skills attach ${skill.id} --agent <id>, or in agent.toml:`);
|
|
6364
6838
|
console.log(` [[skills]]
|
|
6365
6839
|
type = "skill"
|
|
6366
6840
|
skillId = "${skill.id}"`);
|
|
@@ -6373,6 +6847,18 @@ skills.command("list").description("the workspace's stored skills").action(
|
|
|
6373
6847
|
for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
|
|
6374
6848
|
})
|
|
6375
6849
|
);
|
|
6850
|
+
for (const [verb, blurb] of [
|
|
6851
|
+
["attach", "add a stored skill to an agent; its new sessions load it"],
|
|
6852
|
+
["detach", "remove a skill from an agent; its new sessions no longer load it"]
|
|
6853
|
+
]) {
|
|
6854
|
+
skills.command(`${verb} <skill>`).description(blurb).requiredOption("--agent <id>", "the agent").action(
|
|
6855
|
+
run(async (skillId, opts) => {
|
|
6856
|
+
const outcome = await setAgentSkill(opts.agent, skillId, verb);
|
|
6857
|
+
if (outcome === "unchanged") console.log(`${skillId} is ${verb === "attach" ? "already" : "not"} attached to ${opts.agent}`);
|
|
6858
|
+
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`);
|
|
6859
|
+
})
|
|
6860
|
+
);
|
|
6861
|
+
}
|
|
6376
6862
|
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(
|
|
6377
6863
|
run(async (opts) => {
|
|
6378
6864
|
const summary = await getSkillUsage(opts.since);
|
|
@@ -6416,6 +6902,56 @@ reflections.command("list").description("the workspace's proposals, newest first
|
|
|
6416
6902
|
);
|
|
6417
6903
|
reflections.command("approve <id>").description("write the proposal through the skill or memory store").action(run(async (id) => decideReflection(id, "approve")));
|
|
6418
6904
|
reflections.command("reject <id>").description("decline the proposal; nothing is written").action(run(async (id) => decideReflection(id, "reject")));
|
|
6905
|
+
var library = program.command("library").description("your artifact Library on agentsky.dev: files and sites your agents saved or you uploaded");
|
|
6906
|
+
library.command("list").description("your Library, newest first").option("--search <text>", "only names that match").option("--cursor <cursor>", "the next page").option("--json", "print as JSON").action(
|
|
6907
|
+
run(async (opts) => {
|
|
6908
|
+
const page = await listLibrary({ q: opts.search, cursor: opts.cursor });
|
|
6909
|
+
if (opts.json) {
|
|
6910
|
+
console.log(JSON.stringify(page, null, 2));
|
|
6911
|
+
return;
|
|
6912
|
+
}
|
|
6913
|
+
if (!page.artifacts.length) console.log("no artifacts");
|
|
6914
|
+
for (const a of page.artifacts) console.log(renderLibraryArtifact(a));
|
|
6915
|
+
if (page.nextCursor) console.log(`more: --cursor ${page.nextCursor}`);
|
|
6916
|
+
})
|
|
6917
|
+
);
|
|
6918
|
+
library.command("upload <path>").description("save a file from this computer to your Library").option("--workspace <key>", "place it in this workspace").action(
|
|
6919
|
+
run(async (path, opts) => {
|
|
6920
|
+
const bytes = readFileSync18(path);
|
|
6921
|
+
const { artifact: artifact2 } = await uploadToLibrary({ bytes, name: basename9(path), mediaType: "application/octet-stream" }, `sky-upload-${crypto.randomUUID()}`, opts.workspace);
|
|
6922
|
+
console.log(renderLibraryArtifact(artifact2));
|
|
6923
|
+
})
|
|
6924
|
+
);
|
|
6925
|
+
library.command("show <id>").description("one artifact and its saved versions").action(run(async (id) => {
|
|
6926
|
+
console.log(JSON.stringify(await getLibraryArtifact(id), null, 2));
|
|
6927
|
+
}));
|
|
6928
|
+
library.command("delete <id>").description("delete your artifact with all its versions").action(
|
|
6929
|
+
run(async (id) => {
|
|
6930
|
+
const { currentVersion } = await getLibraryArtifact(id);
|
|
6931
|
+
await deleteLibraryArtifact(id, currentVersion ?? 0);
|
|
6932
|
+
console.log(`deleted ${id}`);
|
|
6933
|
+
})
|
|
6934
|
+
);
|
|
6935
|
+
for (const [verb, enabled, description] of [
|
|
6936
|
+
["share", true, "turn on the artifact's public link"],
|
|
6937
|
+
["unshare", false, "turn off the artifact's public link"]
|
|
6938
|
+
]) {
|
|
6939
|
+
library.command(`${verb} <id>`).description(description).action(run(async (id) => {
|
|
6940
|
+
const state = await shareLibraryArtifact(id, enabled);
|
|
6941
|
+
console.log(state.url ?? `${verb}: ${id}`);
|
|
6942
|
+
}));
|
|
6943
|
+
}
|
|
6944
|
+
library.command("place <id> <workspace>").description('place your artifact in a workspace, or "me" to take it back to you alone').action(run(async (id, workspace2) => {
|
|
6945
|
+
await placeLibraryArtifact(id, workspace2 === "me" ? null : workspace2);
|
|
6946
|
+
console.log(`placed ${id}`);
|
|
6947
|
+
}));
|
|
6948
|
+
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(
|
|
6949
|
+
run(async (id, out, opts) => {
|
|
6950
|
+
const { bytes } = await downloadLibraryArtifact(id, opts.version ? Number(opts.version) : void 0);
|
|
6951
|
+
writeFileSync11(out, bytes);
|
|
6952
|
+
console.log(`wrote ${bytes.length} bytes to ${out}`);
|
|
6953
|
+
})
|
|
6954
|
+
);
|
|
6419
6955
|
var artifact = program.command("artifact").description("artifacts: publish a task's files as a named, shareable result");
|
|
6420
6956
|
artifact.command("list").description("the workspace's artifacts, or one task's with --task").option("--task <task>", "only this task's artifacts").action(
|
|
6421
6957
|
run(async (opts) => {
|
|
@@ -6433,7 +6969,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
|
|
|
6433
6969
|
run(async (id, path, opts) => {
|
|
6434
6970
|
const bytes = await readArtifactFile(id, path);
|
|
6435
6971
|
if (opts.out) {
|
|
6436
|
-
|
|
6972
|
+
writeFileSync11(opts.out, bytes);
|
|
6437
6973
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6438
6974
|
} else process.stdout.write(bytes);
|
|
6439
6975
|
})
|
|
@@ -6532,6 +7068,12 @@ for (const [verb, blurb] of [
|
|
|
6532
7068
|
})
|
|
6533
7069
|
);
|
|
6534
7070
|
}
|
|
7071
|
+
routine.command("rm <id>").description("delete a routine: it never runs again; the sessions it started are kept").action(
|
|
7072
|
+
run(async (id) => {
|
|
7073
|
+
await api("DELETE", `/routines/${encodeURIComponent(id)}`);
|
|
7074
|
+
console.log(`Deleted routine ${id}. It will not run again; sessions it started are kept.`);
|
|
7075
|
+
})
|
|
7076
|
+
);
|
|
6535
7077
|
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(
|
|
6536
7078
|
run(async (id, opts) => {
|
|
6537
7079
|
const params = new URLSearchParams({ limit: opts.limit ?? "20" });
|
|
@@ -6683,6 +7225,19 @@ program.command("config").description("show the CLI config location and current
|
|
|
6683
7225
|
console.log(` token: ${config.token ? `${config.token.slice(0, 10)}\u2026` : "(none)"}`);
|
|
6684
7226
|
console.log(` universe: ${config.currentUniverse ?? "(default)"}`);
|
|
6685
7227
|
});
|
|
7228
|
+
var link = program.command("link").description("a session someone shared with you by a guest link: send to it and stop it");
|
|
7229
|
+
link.command("send <link> <text...>").description("send a message through a guest link (/s/<token> URL or token); it runs and bills as the session's owner").option("--idempotency-key <key>", "repeat a send without sending twice (default: a new key)").action(
|
|
7230
|
+
run(async (ref, words, opts) => {
|
|
7231
|
+
await sendViaLink(ref, words.join(" "), opts.idempotencyKey ?? `sky-${crypto.randomUUID()}`);
|
|
7232
|
+
console.log("Sent. Read the reply with GET /links/<token>/events.");
|
|
7233
|
+
})
|
|
7234
|
+
);
|
|
7235
|
+
link.command("stop <link>").description("stop the turn running on the session a guest link opens").action(
|
|
7236
|
+
run(async (ref) => {
|
|
7237
|
+
await stopViaLink(ref);
|
|
7238
|
+
console.log("Stopped.");
|
|
7239
|
+
})
|
|
7240
|
+
);
|
|
6686
7241
|
var billing = program.command("billing").description("your balance, auto top-up, usage across sessions and API keys, and buying credit");
|
|
6687
7242
|
billing.command("show").description("balance, whether a card is saved, and auto top-up").action(
|
|
6688
7243
|
run(async () => {
|
|
@@ -6709,6 +7264,11 @@ billing.command("topup <dollars>").description("buy credit with your saved card:
|
|
|
6709
7264
|
console.log(renderTopup(result));
|
|
6710
7265
|
})
|
|
6711
7266
|
);
|
|
7267
|
+
billing.command("claim <link>").description("claim a credit invite link (agentsky.dev/i/<code>, or just the code) for your own balance").action(
|
|
7268
|
+
run(async (link2) => {
|
|
7269
|
+
console.log(renderCreditInviteClaim(await claimCreditInvite(link2)));
|
|
7270
|
+
})
|
|
7271
|
+
);
|
|
6712
7272
|
billing.command("auto-topup").description("show auto top-up, or change it: --on/--off, --threshold <5|10|20>, --amount <10|20|50|100>").option("--on", "charge the saved card when the balance falls under the threshold").option("--off", "stop charging automatically").option("--threshold <dollars>", "the balance that triggers a charge").option("--amount <dollars>", "the credit each charge adds").action(
|
|
6713
7273
|
run(async (opts) => {
|
|
6714
7274
|
const body = autoTopupBody(opts);
|
|
@@ -6731,13 +7291,42 @@ usageOptions(
|
|
|
6731
7291
|
run(async (opts) => {
|
|
6732
7292
|
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
6733
7293
|
if (opts.out) {
|
|
6734
|
-
|
|
7294
|
+
writeFileSync11(opts.out, bytes);
|
|
6735
7295
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
6736
7296
|
} else {
|
|
6737
7297
|
process.stdout.write(bytes);
|
|
6738
7298
|
}
|
|
6739
7299
|
})
|
|
6740
7300
|
);
|
|
7301
|
+
billing.command("requests").description("one row per API request your developer API keys made, newest first, a page at a time").option("--from <day>", "first UTC day, YYYY-MM-DD (default: 29 days ago)").option("--to <day>", "first UTC day not included (default: tomorrow)").option("--key <id>", "only this API key").option("--cursor <cursor>", "the next page, as printed after the previous one").option("--json", "print the API's answer as JSON").action(
|
|
7302
|
+
run(async (opts) => {
|
|
7303
|
+
const page = await readRequestUsage(opts);
|
|
7304
|
+
console.log(opts.json ? JSON.stringify(page, null, 2) : renderRequestUsage(page));
|
|
7305
|
+
})
|
|
7306
|
+
);
|
|
7307
|
+
var codingPlan = billing.command("coding-plan").description("the coding plan: monthly credit on your saved card, API gateway calls billed at cost");
|
|
7308
|
+
codingPlan.command("show").description("whether you are a member, the monthly amount, and whether it renews").action(run(async () => console.log(renderCodingPlan(await readCodingPlan()))));
|
|
7309
|
+
codingPlan.command("join <dollars>").description("join with your saved card (charged now), or change your monthly amount from the next renewal").option("--request-id <id>", "repeat a request without charging twice (default: a new id)").action(
|
|
7310
|
+
run(async (dollars2, opts) => {
|
|
7311
|
+
console.log(renderCodingPlan(await joinCodingPlan(dollars2, opts.requestId ?? `sky-${crypto.randomUUID()}`)));
|
|
7312
|
+
})
|
|
7313
|
+
);
|
|
7314
|
+
codingPlan.command("cancel").description("stop the plan renewing; the paid month stays").action(run(async () => console.log(renderCodingPlan(await cancelCodingPlan()))));
|
|
7315
|
+
var referralLinks = billing.command("referral-links").description("customer discount links for the API gateway (accounts on a discount plan)");
|
|
7316
|
+
referralLinks.command("list").description("your links, their use, and what referred customers earned you").action(run(async () => console.log(renderReferralLinks(await readReferralLinks()))));
|
|
7317
|
+
referralLinks.command("create").description("create a link priced at no less than your own price; --email sends it to one customer").option("--price <share>", "what the customer pays for every model, as a share of public price: 0.8 or 80%").option("--sku <id=share>", "a price for one SKU; repeat for several", collect).option("--email <address>", "email the link to this customer; it then redeems once").option("--uses <n>", "how many customers can redeem it (default 100)").option("--days <n>", "days until it expires (default 30)").action(
|
|
7318
|
+
run(async (opts) => {
|
|
7319
|
+
const { url, email_status: emailStatus } = await createReferralLink(opts);
|
|
7320
|
+
console.log(emailStatus ? `${url}
|
|
7321
|
+
email: ${emailStatus}` : url);
|
|
7322
|
+
})
|
|
7323
|
+
);
|
|
7324
|
+
referralLinks.command("disable <id>").description("stop a link being redeemed; customers already enrolled keep their price").action(
|
|
7325
|
+
run(async (id) => {
|
|
7326
|
+
await disableReferralLink(id);
|
|
7327
|
+
console.log(`Disabled ${id}`);
|
|
7328
|
+
})
|
|
7329
|
+
);
|
|
6741
7330
|
var keys = program.command("keys").description("your own API keys: list, create, revoke, restore (admin scope)");
|
|
6742
7331
|
keys.command("list").action(
|
|
6743
7332
|
run(async () => {
|
|
@@ -7058,6 +7647,48 @@ vendor.command("usage <id>").description("the agents and tasks that used this ve
|
|
|
7058
7647
|
for (const t of tasks) console.log(` task ${t.session_id} ${t.title ?? "(untitled)"} ${t.last_used_at.slice(0, 10)}`);
|
|
7059
7648
|
})
|
|
7060
7649
|
);
|
|
7650
|
+
var mac = program.command("mac").description("your Macs: what each lends to agents now, and asking one for a folder, a browser window, its screen or its apps and files");
|
|
7651
|
+
mac.command("list").description("your Macs: online or not, what each can lend, and the folders, browser windows and screens lent now").option("--json", "print as JSON").action(
|
|
7652
|
+
run(async (opts) => {
|
|
7653
|
+
const macs = await listMacs();
|
|
7654
|
+
if (opts.json) {
|
|
7655
|
+
console.log(JSON.stringify(macs, null, 2));
|
|
7656
|
+
return;
|
|
7657
|
+
}
|
|
7658
|
+
if (!macs.length) console.log("no Macs connected; sign in to the AgentSky Mac app");
|
|
7659
|
+
for (const entry2 of macs) console.log(renderMac(entry2));
|
|
7660
|
+
})
|
|
7661
|
+
);
|
|
7662
|
+
mac.command("ask <machine> <kind>").description("ask a Mac to lend a folder, a browser window, its screen or its apps and files (host: local chats, call notes, uploads, saves, local git); the person answers on the Mac. Prints the MCP URL a session attaches once it is granted").requiredOption("--reason <text>", "why, in one sentence the person reads on their Mac").option("--no-wait", "return as soon as the Mac has the ask").option("--json", "print as JSON").action(
|
|
7663
|
+
run(async (machine, kind, opts) => {
|
|
7664
|
+
const asked = await askMac(machine, kind, opts.reason);
|
|
7665
|
+
const answer = opts.wait ? await waitForAnswer(asked.reach_request.id, 240) : asked.reach_request;
|
|
7666
|
+
if (opts.json) {
|
|
7667
|
+
console.log(JSON.stringify({ reach_request: answer, mcp_url: asked.mcp_url }, null, 2));
|
|
7668
|
+
return;
|
|
7669
|
+
}
|
|
7670
|
+
console.log(renderReachRequest(answer));
|
|
7671
|
+
console.log(`mcp_url: ${asked.mcp_url}`);
|
|
7672
|
+
if (answer.status === "granted") console.log("attach it as an MCP server: mcp_servers on POST /v1/sessions (MCP start_session: sources), or PATCH /v1/sessions/{id}");
|
|
7673
|
+
else if (!["pending", "asking"].includes(answer.status)) process.exitCode = 1;
|
|
7674
|
+
})
|
|
7675
|
+
);
|
|
7676
|
+
mac.command("ask-status <id>").description("where an ask stands: pending, asking, granted, denied, unavailable or expired").option("--wait <seconds>", "wait up to this long (at most 25) for the person to answer", "0").option("--json", "print as JSON").action(
|
|
7677
|
+
run(async (id, opts) => {
|
|
7678
|
+
const answer = await readReachRequest(id, Number(opts.wait) || 0);
|
|
7679
|
+
if (opts.json) {
|
|
7680
|
+
console.log(JSON.stringify(answer, null, 2));
|
|
7681
|
+
return;
|
|
7682
|
+
}
|
|
7683
|
+
console.log(renderReachRequest(answer));
|
|
7684
|
+
})
|
|
7685
|
+
);
|
|
7686
|
+
mac.command("take-back <mount>").description("end a folder, browser window, screen or apps and files lent from a Mac; every later call to it fails").action(
|
|
7687
|
+
run(async (mount) => {
|
|
7688
|
+
await takeBack(mount);
|
|
7689
|
+
console.log(`took back ${mount}`);
|
|
7690
|
+
})
|
|
7691
|
+
);
|
|
7061
7692
|
var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
|
|
7062
7693
|
browserProfile.command("list").description("your workspace's browser profiles: the sites your agents stay signed in to").option("--json", "print as JSON").action(
|
|
7063
7694
|
run(async (opts) => {
|
|
@@ -7121,10 +7752,16 @@ notifications.command("get").description("show the current notification preferen
|
|
|
7121
7752
|
console.log(JSON.stringify(prefs, null, 2));
|
|
7122
7753
|
return;
|
|
7123
7754
|
}
|
|
7124
|
-
console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
|
|
7755
|
+
console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done} marketing=${prefs.email.marketing}`);
|
|
7125
7756
|
console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
|
|
7126
7757
|
})
|
|
7127
7758
|
);
|
|
7759
|
+
notifications.command("unsubscribe-marketing").description("stop AgentSky marketing email to your address; login and account email continue").action(
|
|
7760
|
+
run(async () => {
|
|
7761
|
+
const { marketing } = await unsubscribeMarketing();
|
|
7762
|
+
console.log(marketing ? "Marketing email is still on." : "Unsubscribed from AgentSky marketing email.");
|
|
7763
|
+
})
|
|
7764
|
+
);
|
|
7128
7765
|
notifications.command("set").description("update notification preferences (pass at least one flag)").option("--email-needs-you <bool>", "email when the task needs your input: true or false").option("--email-done <bool>", "email when the task finishes: true or false").option("--push-needs-you <bool>", "push notification when the task needs your input: true or false").option("--push-done <bool>", "push notification when the task finishes: true or false").action(
|
|
7129
7766
|
run(async (opts) => {
|
|
7130
7767
|
const parseBool = (v) => v === void 0 ? void 0 : v === "true" ? true : v === "false" ? false : (() => {
|
|
@@ -7193,6 +7830,85 @@ help.command("get <slug>").description("read a help article by its slug").option
|
|
|
7193
7830
|
console.log(article.body);
|
|
7194
7831
|
})
|
|
7195
7832
|
);
|
|
7833
|
+
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(
|
|
7834
|
+
run(async (method, rest, opts) => {
|
|
7835
|
+
if (method.toLowerCase() === "search") return searchApi(rest);
|
|
7836
|
+
if (rest.length !== 1) throw new Error("usage: sky api <METHOD> <path> (one path), or sky api search <words\u2026>");
|
|
7837
|
+
return callApi(method, rest[0], opts);
|
|
7838
|
+
})
|
|
7839
|
+
);
|
|
7840
|
+
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(
|
|
7841
|
+
run(async (id, opts) => {
|
|
7842
|
+
const body = {
|
|
7843
|
+
...opts.name ? { displayName: opts.name } : {},
|
|
7844
|
+
...opts.engine ? { agentType: opts.engine } : {},
|
|
7845
|
+
...opts.model ? { llm: opts.model } : {},
|
|
7846
|
+
...opts.thinking ? { reasoningEffort: opts.thinking === "default" ? null : opts.thinking } : {}
|
|
7847
|
+
};
|
|
7848
|
+
if (!Object.keys(body).length) throw new Error("pass at least one of --name, --engine, --model, --thinking");
|
|
7849
|
+
const { agent: a } = await api("PATCH", `/agents/${encodeURIComponent(id)}`, body);
|
|
7850
|
+
console.log(`${a.id} \u2014 ${a.displayName} \xB7 ${a.agentType} \xB7 ${a.llm}${a.reasoningEffort ? ` \xB7 ${a.reasoningEffort}` : ""}`);
|
|
7851
|
+
})
|
|
7852
|
+
);
|
|
7853
|
+
skills.command("revisions <skillId>").description("a skill's saved versions, newest first").option("--json", "print as JSON").action(
|
|
7854
|
+
run(async (skillId, opts) => {
|
|
7855
|
+
const page = await api("GET", `/skills/${encodeURIComponent(skillId)}/revisions`);
|
|
7856
|
+
const rows = page.revisions ?? [];
|
|
7857
|
+
if (opts.json) return void console.log(JSON.stringify(rows, null, 2));
|
|
7858
|
+
if (!rows.length) return void console.log("no versions");
|
|
7859
|
+
for (const r of rows) console.log(`${r.id} ${r.createdAt ?? r.created_at ?? ""}`);
|
|
7860
|
+
})
|
|
7861
|
+
);
|
|
7862
|
+
skills.command("cat <skillId> <revisionId> <path>").description("print one file (e.g. SKILL.md) of a skill version").action(
|
|
7863
|
+
run(async (skillId, revisionId, path) => {
|
|
7864
|
+
const res = await apiRaw("GET", `/skills/${encodeURIComponent(skillId)}/revisions/${encodeURIComponent(revisionId)}/content?path=${encodeURIComponent(path)}`);
|
|
7865
|
+
if (!res.ok) throw new ApiError(res.status, "http_error", await res.text() || `${res.status}`);
|
|
7866
|
+
process.stdout.write(await res.text());
|
|
7867
|
+
})
|
|
7868
|
+
);
|
|
7869
|
+
skills.command("rm <skillId>").description("delete a skill from the workspace").action(
|
|
7870
|
+
run(async (skillId) => {
|
|
7871
|
+
await api("DELETE", `/skills/${encodeURIComponent(skillId)}`);
|
|
7872
|
+
console.log(`Deleted ${skillId}`);
|
|
7873
|
+
})
|
|
7874
|
+
);
|
|
7875
|
+
var notes = program.command("notes").description("memory notes in the Library's memory stores: list, read, write (every save keeps a version)");
|
|
7876
|
+
notes.command("stores").description("the workspace's memory stores").action(
|
|
7877
|
+
run(async () => {
|
|
7878
|
+
const { memory_stores: stores } = await api("GET", "/memory_stores?limit=100");
|
|
7879
|
+
if (!stores.length) return void console.log("no memory stores");
|
|
7880
|
+
for (const st of stores) console.log(`${st.id} ${st.name}`);
|
|
7881
|
+
})
|
|
7882
|
+
);
|
|
7883
|
+
notes.command("list <storeId> [folder]").description("the notes in a store, optionally under a folder ending in /").action(
|
|
7884
|
+
run(async (storeId, folder) => {
|
|
7885
|
+
const q = folder ? `&path_prefix=${encodeURIComponent(folder)}` : "";
|
|
7886
|
+
const { memories } = await api("GET", `/memory_stores/${encodeURIComponent(storeId)}/memories?limit=100${q}`);
|
|
7887
|
+
if (!memories.length) return void console.log("no notes");
|
|
7888
|
+
for (const n of memories) console.log(`${n.id} ${n.path}`);
|
|
7889
|
+
})
|
|
7890
|
+
);
|
|
7891
|
+
notes.command("read <storeId> <noteId>").description("print one note").action(
|
|
7892
|
+
run(async (storeId, noteId) => {
|
|
7893
|
+
const { memory: memory2 } = await api("GET", `/memory_stores/${encodeURIComponent(storeId)}/memories/${encodeURIComponent(noteId)}`);
|
|
7894
|
+
process.stdout.write(memory2.content.endsWith("\n") ? memory2.content : `${memory2.content}
|
|
7895
|
+
`);
|
|
7896
|
+
})
|
|
7897
|
+
);
|
|
7898
|
+
notes.command("save <storeId> <file>").description("write a note from a local file: --path creates one, --note saves a new version of one").option("--path <path>", "path of a new note, e.g. /projects/launch.md").option("--note <noteId>", "the note to update").action(
|
|
7899
|
+
run(async (storeId, file, opts) => {
|
|
7900
|
+
if (!opts.path === !opts.note) throw new Error("pass exactly one of --path (new note) or --note (update)");
|
|
7901
|
+
const content = readFileSync18(file, "utf8");
|
|
7902
|
+
const target = `/memory_stores/${encodeURIComponent(storeId)}/memories${opts.note ? `/${encodeURIComponent(opts.note)}` : ""}`;
|
|
7903
|
+
const { memory: memory2 } = await api("POST", target, { content, ...opts.path ? { path: opts.path } : {} });
|
|
7904
|
+
console.log(`Saved ${memory2.path} (${memory2.id})`);
|
|
7905
|
+
})
|
|
7906
|
+
);
|
|
7907
|
+
workspace.command("context").description("what every session in the workspace loads at start: AGENTS.md, MEMORY.md, indexes, your member page").action(
|
|
7908
|
+
run(async () => {
|
|
7909
|
+
console.log(JSON.stringify(await api("GET", "/workspace-context"), null, 2));
|
|
7910
|
+
})
|
|
7911
|
+
);
|
|
7196
7912
|
var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
|
|
7197
7913
|
vault.command("list").description("the workspace's vaults").action(
|
|
7198
7914
|
run(async () => {
|
|
@@ -7282,7 +7998,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
7282
7998
|
}));
|
|
7283
7999
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
7284
8000
|
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) => {
|
|
7285
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
8001
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-CFHCOGKB.js");
|
|
7286
8002
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
7287
8003
|
const scopes = parseContentScopes(opts.capture);
|
|
7288
8004
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -7317,7 +8033,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
7317
8033
|
}));
|
|
7318
8034
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
7319
8035
|
try {
|
|
7320
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
8036
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-CFHCOGKB.js");
|
|
7321
8037
|
const chunks = [];
|
|
7322
8038
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
7323
8039
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -7330,14 +8046,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
7330
8046
|
}
|
|
7331
8047
|
});
|
|
7332
8048
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
7333
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
8049
|
+
const { readState, flush, grantSender } = await import("./trace-CFHCOGKB.js");
|
|
7334
8050
|
const state = readState();
|
|
7335
8051
|
if (!state) throw new Error("no trace is running here");
|
|
7336
8052
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
7337
8053
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
7338
8054
|
}));
|
|
7339
8055
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
7340
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
8056
|
+
const { readState, spoolStatus } = await import("./trace-CFHCOGKB.js");
|
|
7341
8057
|
const state = readState();
|
|
7342
8058
|
if (!state) {
|
|
7343
8059
|
console.log("No trace is running here.");
|
|
@@ -7347,7 +8063,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
7347
8063
|
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`);
|
|
7348
8064
|
}));
|
|
7349
8065
|
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) => {
|
|
7350
|
-
const { readState } = await import("./trace-
|
|
8066
|
+
const { readState } = await import("./trace-CFHCOGKB.js");
|
|
7351
8067
|
const ref = id ?? readState()?.trace_id;
|
|
7352
8068
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
7353
8069
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -7374,7 +8090,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
7374
8090
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
7375
8091
|
}));
|
|
7376
8092
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
7377
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
8093
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-CFHCOGKB.js");
|
|
7378
8094
|
const state = readState();
|
|
7379
8095
|
if (!state) throw new Error("no trace is running here");
|
|
7380
8096
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -7384,7 +8100,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
7384
8100
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
7385
8101
|
}));
|
|
7386
8102
|
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) => {
|
|
7387
|
-
const lib = await import("./trace-library-
|
|
8103
|
+
const lib = await import("./trace-library-2VHTJCOY.js");
|
|
7388
8104
|
if (opts.hook) {
|
|
7389
8105
|
try {
|
|
7390
8106
|
let raw = payload ?? "";
|
|
@@ -7456,11 +8172,11 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
7456
8172
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
7457
8173
|
}
|
|
7458
8174
|
if (opts.status) {
|
|
7459
|
-
const { readFileSync:
|
|
8175
|
+
const { readFileSync: readFileSync19 } = await import("fs");
|
|
7460
8176
|
const { join: join19 } = await import("path");
|
|
7461
8177
|
let state = { sessions: {} };
|
|
7462
8178
|
try {
|
|
7463
|
-
state = JSON.parse(
|
|
8179
|
+
state = JSON.parse(readFileSync19(join19(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
7464
8180
|
} catch {
|
|
7465
8181
|
}
|
|
7466
8182
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -7493,7 +8209,7 @@ traceCmd.command("sync [payload]").description("upload this machine's agent sess
|
|
|
7493
8209
|
})();
|
|
7494
8210
|
});
|
|
7495
8211
|
traceCmd.command("search <query...>").description("search every session in a workspace's trace library (literal substring)").option("--workspace <slug>", "the workspace to search (default: the current one)").option("--limit <n>", "hits per page", (v) => Number.parseInt(v, 10), 20).option("--cursor <cursor>", "the next page of an earlier search").option("--json", "print the page as JSON").action(run(async (words, opts) => {
|
|
7496
|
-
const { libraryRequest } = await import("./trace-library-
|
|
8212
|
+
const { libraryRequest } = await import("./trace-library-2VHTJCOY.js");
|
|
7497
8213
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
7498
8214
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
7499
8215
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -7510,7 +8226,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
7510
8226
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
7511
8227
|
}));
|
|
7512
8228
|
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) => {
|
|
7513
|
-
const { libraryRequest } = await import("./trace-library-
|
|
8229
|
+
const { libraryRequest } = await import("./trace-library-2VHTJCOY.js");
|
|
7514
8230
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
7515
8231
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
7516
8232
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|
|
@@ -7626,6 +8342,140 @@ thread.command("list").description("your workspace threads, most recently update
|
|
|
7626
8342
|
for (const t of threads) console.log(renderThreadSummary(t));
|
|
7627
8343
|
})
|
|
7628
8344
|
);
|
|
8345
|
+
thread.command("start <text>").description("start a thread and send it its first message, as the web composer does").requiredOption("--agent <type>", "the agent's harness type (e.g. claude_code, codex)").option("--llm <model>", "the model (the harness default when omitted)").option("--effort <level>", "thinking level").option("--vs <type:model...>", "more agents that get the same message, side by side", (val, acc) => [...acc, val], []).option("--saved-agent <id>", "run on one of your saved agents").option("--workspace <slug>", "start it in this team workspace").option("--plan", "send the first message in Plan mode").option("--json", "print the new thread as JSON").action(
|
|
8346
|
+
run(async (text, opts) => {
|
|
8347
|
+
const compared = (opts.vs ?? []).map((v) => {
|
|
8348
|
+
const [agentType, llm] = v.split(":");
|
|
8349
|
+
if (!agentType) throw new ApiError(400, "invalid_request", `--vs must be type[:model], got "${v}"`);
|
|
8350
|
+
return { agentType, ...llm ? { llm } : {} };
|
|
8351
|
+
});
|
|
8352
|
+
const result = await startThread({
|
|
8353
|
+
requestId: `sky-thread-${crypto.randomUUID()}`,
|
|
8354
|
+
agentType: opts.agent,
|
|
8355
|
+
text,
|
|
8356
|
+
...opts.llm ? { llm: opts.llm } : {},
|
|
8357
|
+
...opts.effort ? { reasoningEffort: opts.effort } : {},
|
|
8358
|
+
...compared.length ? { compare: compared } : {},
|
|
8359
|
+
...opts.savedAgent ? { agentId: opts.savedAgent } : {},
|
|
8360
|
+
...opts.workspace ? { workspace: opts.workspace } : {},
|
|
8361
|
+
...opts.plan ? { interactionMode: "plan" } : {}
|
|
8362
|
+
});
|
|
8363
|
+
if (opts.json) {
|
|
8364
|
+
console.log(JSON.stringify(result, null, 2));
|
|
8365
|
+
return;
|
|
8366
|
+
}
|
|
8367
|
+
console.log(`started ${result.playgroundId}`);
|
|
8368
|
+
for (const col of result.columns) console.log(` agent ${col.columnId} session ${col.sessionId}`);
|
|
8369
|
+
})
|
|
8370
|
+
);
|
|
8371
|
+
thread.command("show <id>").description("one thread with every agent and the session each one runs in").option("--json", "print as JSON").action(
|
|
8372
|
+
run(async (id, opts) => {
|
|
8373
|
+
const detail = await getThread(id);
|
|
8374
|
+
if (opts.json) {
|
|
8375
|
+
console.log(JSON.stringify(detail, null, 2));
|
|
8376
|
+
return;
|
|
8377
|
+
}
|
|
8378
|
+
console.log(`thread ${detail.id} ${detail.title}`);
|
|
8379
|
+
for (const col of detail.columns) console.log(` agent ${col.id} session ${col.sessionId} ${col.blind ? "(hidden until the vote)" : `${col.agentType}/${col.llm}`} ${col.status ?? col.podStatus ?? ""}`);
|
|
8380
|
+
})
|
|
8381
|
+
);
|
|
8382
|
+
thread.command("rename <id> <title>").description("rename a thread").action(run(async (id, title) => {
|
|
8383
|
+
await organizeThread(id, { title });
|
|
8384
|
+
console.log(`renamed ${id}`);
|
|
8385
|
+
}));
|
|
8386
|
+
for (const [verb, change, description] of [
|
|
8387
|
+
["pin", { pinned: true }, "pin a thread to the top of the rail"],
|
|
8388
|
+
["unpin", { pinned: false }, "unpin a thread"],
|
|
8389
|
+
["archive", { archived: true }, "put a thread away"],
|
|
8390
|
+
["unarchive", { archived: false }, "bring an archived thread back"],
|
|
8391
|
+
["mark-read", { seen: true }, "mark a thread read"],
|
|
8392
|
+
["mark-unread", { seen: false }, "mark a thread unread"]
|
|
8393
|
+
]) {
|
|
8394
|
+
thread.command(`${verb} <id>`).description(description).action(run(async (id) => {
|
|
8395
|
+
await organizeThread(id, change);
|
|
8396
|
+
console.log(`${verb}: ${id}`);
|
|
8397
|
+
}));
|
|
8398
|
+
}
|
|
8399
|
+
thread.command("delete <id>").description("delete a thread with its sessions").action(run(async (id) => {
|
|
8400
|
+
await deleteThread(id);
|
|
8401
|
+
console.log(`deleted ${id}`);
|
|
8402
|
+
}));
|
|
8403
|
+
thread.command("send <id> <text>").description("send one message to every agent of the thread, or to the ones --agent names").option("--agent <columnId...>", "only these agents (column ids from `sky thread show`)").option("--json", "print each agent's outcome as JSON").action(
|
|
8404
|
+
run(async (id, text, opts) => {
|
|
8405
|
+
const { results } = await sendThreadMessage(id, { text, deliveryId: crypto.randomUUID(), ...opts.agent?.length ? { columnIds: opts.agent } : {} });
|
|
8406
|
+
if (opts.json) {
|
|
8407
|
+
console.log(JSON.stringify(results, null, 2));
|
|
8408
|
+
return;
|
|
8409
|
+
}
|
|
8410
|
+
for (const [column, outcome] of Object.entries(results)) console.log(` ${column} ${outcome.ok ? "sent" : `not sent: ${outcome.error ?? "refused"}`}`);
|
|
8411
|
+
})
|
|
8412
|
+
);
|
|
8413
|
+
thread.command("add-agent <id>").description("add an agent to the thread, in a session of its own").requiredOption("--agent <type>", "the agent's harness type").option("--llm <model>", "the model").option("--effort <level>", "thinking level").action(
|
|
8414
|
+
run(async (id, opts) => {
|
|
8415
|
+
const { columns } = await addThreadAgent(id, { requestId: `sky-agent-${crypto.randomUUID()}`, agentType: opts.agent, ...opts.llm ? { llm: opts.llm } : {}, ...opts.effort ? { reasoningEffort: opts.effort } : {} });
|
|
8416
|
+
for (const col of columns) console.log(`added agent ${col.columnId} session ${col.sessionId}`);
|
|
8417
|
+
})
|
|
8418
|
+
);
|
|
8419
|
+
thread.command("remove-agent <id> <columnId>").description("take one agent out of the thread; its session is left as it is").action(run(async (id, columnId) => {
|
|
8420
|
+
await removeThreadAgent(id, columnId);
|
|
8421
|
+
console.log(`removed ${columnId}`);
|
|
8422
|
+
}));
|
|
8423
|
+
thread.command("stop <id>").description("stop what the thread's agents are doing").option("--agent <columnId>", "stop only this agent").action(run(async (id, opts) => {
|
|
8424
|
+
await interruptThread(id, opts.agent);
|
|
8425
|
+
console.log(`stopped ${id}`);
|
|
8426
|
+
}));
|
|
8427
|
+
thread.command("fork <id>").description("continue one agent's conversation in a thread of its own").option("--agent <columnId>", "the agent to fork (the first when omitted)").option("--at <messageId>", "carry the conversation only up to this message").option("--to <type:model>", "continue on another agent or model").action(
|
|
8428
|
+
run(async (id, opts) => {
|
|
8429
|
+
const [agentType, llm] = (opts.to ?? "").split(":");
|
|
8430
|
+
const { playgroundId } = await forkThread(id, {
|
|
8431
|
+
requestId: `sky-fork-${crypto.randomUUID()}`,
|
|
8432
|
+
...opts.agent ? { columnId: opts.agent } : {},
|
|
8433
|
+
...opts.at ? { messageId: opts.at } : {},
|
|
8434
|
+
...agentType ? { target: { agentType, ...llm ? { llm } : {} } } : {}
|
|
8435
|
+
});
|
|
8436
|
+
console.log(`forked into ${playgroundId}`);
|
|
8437
|
+
})
|
|
8438
|
+
);
|
|
8439
|
+
thread.command("model <id>").description("change the model or thinking level one agent of the thread runs on, from its next turn").option("--agent <columnId>", "the agent (the first when omitted)").option("--llm <model>", "the new model").option("--effort <level>", 'the new thinking level, or "default"').action(
|
|
8440
|
+
run(async (id, opts) => {
|
|
8441
|
+
if (!opts.llm && !opts.effort) throw new ApiError(400, "invalid_request", "Provide --llm or --effort.");
|
|
8442
|
+
const result = await changeThreadModel(id, { ...opts.llm ? { llm: opts.llm } : {}, ...opts.effort ? { reasoningEffort: opts.effort === "default" ? null : opts.effort } : {} }, opts.agent);
|
|
8443
|
+
console.log(`now ${result.llm}${result.reasoningEffort ? ` (${result.reasoningEffort})` : ""}`);
|
|
8444
|
+
})
|
|
8445
|
+
);
|
|
8446
|
+
thread.command("search <query>").description("search the words of your conversations").option("--archived", "search archived threads only").option("--all", "search active and archived threads").option("--cursor <cursor>", "the next page").action(
|
|
8447
|
+
run(async (query, opts) => {
|
|
8448
|
+
const threads = opts.all ? "all" : opts.archived ? "archived" : void 0;
|
|
8449
|
+
console.log(JSON.stringify(await searchThreads(query, { ...threads ? { threads } : {}, ...opts.cursor ? { cursor: opts.cursor } : {} }), null, 2));
|
|
8450
|
+
})
|
|
8451
|
+
);
|
|
8452
|
+
thread.command("options").description("what a new thread can start on: agents, models, thinking levels and grantable tools").action(run(async () => {
|
|
8453
|
+
console.log(JSON.stringify(await threadOptions(), null, 2));
|
|
8454
|
+
}));
|
|
8455
|
+
thread.command("join <sessionId>").description("open a teammate's thread, or one shared with you, as the session link does").action(
|
|
8456
|
+
run(async (sessionId) => {
|
|
8457
|
+
const opened = await joinThread(sessionId);
|
|
8458
|
+
console.log(opened.readOnly ? `read-only: session ${opened.sessionId}` : `opened ${opened.playgroundId}`);
|
|
8459
|
+
})
|
|
8460
|
+
);
|
|
8461
|
+
var project = program.command("project").description("the GitHub repositories a new thread can start on");
|
|
8462
|
+
project.command("list").description("repositories a new thread can start on, and the one you chose last").option("--json", "print as JSON").action(
|
|
8463
|
+
run(async (opts) => {
|
|
8464
|
+
const listed = await listProjects();
|
|
8465
|
+
if (opts.json) {
|
|
8466
|
+
console.log(JSON.stringify(listed, null, 2));
|
|
8467
|
+
return;
|
|
8468
|
+
}
|
|
8469
|
+
if (listed.unavailable) console.log(listed.unavailable.message);
|
|
8470
|
+
for (const repo of listed.repos) console.log(`${repo.fullName === listed.last?.repoFullName ? "* " : " "}${repo.fullName}`);
|
|
8471
|
+
})
|
|
8472
|
+
);
|
|
8473
|
+
project.command("use [repo]").description("remember a repository (owner/name) for your next new thread, or none when omitted").option("--branch <branch>", "the branch", "main").action(
|
|
8474
|
+
run(async (repo, opts) => {
|
|
8475
|
+
const { project: remembered } = await rememberProject(repo ? { repoFullName: repo, branch: opts.branch } : null);
|
|
8476
|
+
console.log(remembered ? `next thread starts on ${remembered.repoFullName}@${remembered.branch}` : "next thread starts on no repository");
|
|
8477
|
+
})
|
|
8478
|
+
);
|
|
7629
8479
|
session.command("feedback <id>").description("read the thumbs-up/down feedback on this session's messages").option("--json", "print as JSON").action(
|
|
7630
8480
|
run(async (id, opts) => {
|
|
7631
8481
|
const result = await api(
|
|
@@ -7682,7 +8532,7 @@ session.command("code-review <id>").description("run the code-review assistant o
|
|
|
7682
8532
|
}
|
|
7683
8533
|
})
|
|
7684
8534
|
);
|
|
7685
|
-
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(
|
|
8535
|
+
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(
|
|
7686
8536
|
run(async (id, opts) => {
|
|
7687
8537
|
const path = `/sessions/${encodeURIComponent(id)}/snapshot?thread=${encodeURIComponent(opts.thread)}`;
|
|
7688
8538
|
const result = await api(opts.revoke ? "DELETE" : "GET", path);
|