@agentsky/sky 0.2.1 → 2.3.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-L5LLCKZW.js → chunk-2KQLWHHC.js} +8827 -2129
- package/dist/index.d.ts +1 -0
- package/dist/index.js +753 -226
- package/dist/products.d.ts +585 -0
- package/dist/products.js +207 -0
- package/dist/src-EGXYUSME.js +281 -0
- package/package.json +21 -6
- package/dist/chunk-MLKGABMK.js +0 -9
- package/dist/chunk-NLEDGUN7.js +0 -39
- package/dist/config-HRDRNTUU.js +0 -17
- package/dist/src-Y2XOFL3Z.js +0 -175
package/dist/index.js
CHANGED
|
@@ -1,32 +1,67 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
apiBase,
|
|
4
|
-
apiToken,
|
|
5
|
-
configPath,
|
|
6
|
-
currentUniverse,
|
|
7
|
-
loadConfig,
|
|
8
|
-
saveConfig
|
|
9
|
-
} from "./chunk-NLEDGUN7.js";
|
|
10
2
|
import {
|
|
11
3
|
AGENT_TYPES,
|
|
4
|
+
REASONING_EFFORT_VALUES,
|
|
12
5
|
capabilitiesSchema,
|
|
13
6
|
customDataSchema,
|
|
14
|
-
|
|
7
|
+
defaultLlm,
|
|
15
8
|
external_exports,
|
|
16
|
-
|
|
9
|
+
isModelEffortSupported,
|
|
10
|
+
mcpServersFromKept,
|
|
11
|
+
modelSubscriptionProviderFor,
|
|
12
|
+
reasoningEffortDocLine,
|
|
17
13
|
skillRefSchema,
|
|
18
|
-
splitMcpServers
|
|
19
|
-
|
|
20
|
-
|
|
14
|
+
splitMcpServers,
|
|
15
|
+
supersededBy
|
|
16
|
+
} from "./chunk-2KQLWHHC.js";
|
|
21
17
|
|
|
22
18
|
// src/index.ts
|
|
23
|
-
import { existsSync as existsSync4 } from "fs";
|
|
24
|
-
import { basename as
|
|
19
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
20
|
+
import { basename as basename3, resolve as resolve3 } from "path";
|
|
25
21
|
import { createInterface as createInterface3 } from "readline";
|
|
26
22
|
import { Command } from "commander";
|
|
27
23
|
|
|
24
|
+
// src/config.ts
|
|
25
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
26
|
+
import { homedir } from "os";
|
|
27
|
+
import { dirname, join } from "path";
|
|
28
|
+
var DEFAULT_API_BASE = "https://api.agentsky.dev/v1";
|
|
29
|
+
var DEFAULT_CONSOLE_BASE = "https://platform.agentsky.dev";
|
|
30
|
+
function configPath() {
|
|
31
|
+
return join(process.env.SKY_CONFIG_DIR ?? join(homedir(), ".config", "sky"), "config.json");
|
|
32
|
+
}
|
|
33
|
+
function loadConfig() {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(readFileSync(configPath(), "utf8"));
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
function saveConfig(config) {
|
|
41
|
+
const path = configPath();
|
|
42
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
43
|
+
writeFileSync(path, `${JSON.stringify(config, null, 2)}
|
|
44
|
+
`, { mode: 384 });
|
|
45
|
+
}
|
|
46
|
+
function apiBase() {
|
|
47
|
+
return (process.env.SKY_API_URL ?? loadConfig().apiBase ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
48
|
+
}
|
|
49
|
+
function consoleBase() {
|
|
50
|
+
const configured = process.env.SKY_CONSOLE_URL ?? loadConfig().consoleBase;
|
|
51
|
+
if (configured) return configured.replace(/\/$/, "");
|
|
52
|
+
const api2 = apiBase();
|
|
53
|
+
if (api2 === DEFAULT_API_BASE) return DEFAULT_CONSOLE_BASE;
|
|
54
|
+
return api2.replace(/\/(?:api\/)?v1$/, "") || api2;
|
|
55
|
+
}
|
|
56
|
+
function apiToken() {
|
|
57
|
+
return process.env.SKY_API_TOKEN ?? loadConfig().token;
|
|
58
|
+
}
|
|
59
|
+
function currentUniverse() {
|
|
60
|
+
return process.env.SKY_UNIVERSE ?? loadConfig().currentUniverse;
|
|
61
|
+
}
|
|
62
|
+
|
|
28
63
|
// src/version.ts
|
|
29
|
-
var CLI_VERSION = "
|
|
64
|
+
var CLI_VERSION = "2.3.0";
|
|
30
65
|
var USER_AGENT = `sky-cli/${CLI_VERSION} (${process.platform}; node/${process.versions.node})`;
|
|
31
66
|
|
|
32
67
|
// src/api.ts
|
|
@@ -68,35 +103,70 @@ async function api(method, path, body, opts = {}) {
|
|
|
68
103
|
headers: {
|
|
69
104
|
"user-agent": USER_AGENT,
|
|
70
105
|
...opts.auth === false ? {} : authHeaders(),
|
|
71
|
-
...body !== void 0 ? { "content-type": "application/json" } : {}
|
|
106
|
+
...body !== void 0 ? { "content-type": "application/json" } : {},
|
|
107
|
+
...opts.idempotencyKey ? { "idempotency-key": opts.idempotencyKey } : {}
|
|
72
108
|
},
|
|
73
109
|
body: body !== void 0 ? JSON.stringify(body) : void 0
|
|
74
110
|
});
|
|
75
111
|
if (!res.ok) await parseError(res);
|
|
112
|
+
if (res.status === 204) return void 0;
|
|
76
113
|
return await res.json();
|
|
77
114
|
}
|
|
115
|
+
async function postEvents(sessionId, events, idempotencyKey) {
|
|
116
|
+
const res = await fetch(`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/events`, {
|
|
117
|
+
method: "POST",
|
|
118
|
+
headers: {
|
|
119
|
+
"user-agent": USER_AGENT,
|
|
120
|
+
...authHeaders(),
|
|
121
|
+
"content-type": "application/json",
|
|
122
|
+
...idempotencyKey ? { "idempotency-key": idempotencyKey } : {}
|
|
123
|
+
},
|
|
124
|
+
body: JSON.stringify({ events })
|
|
125
|
+
});
|
|
126
|
+
if (!res.ok) await parseError(res);
|
|
127
|
+
return await res.json();
|
|
128
|
+
}
|
|
129
|
+
async function uploadFile(bytes, filename, mediaType, expiresInSeconds) {
|
|
130
|
+
const form = new FormData();
|
|
131
|
+
form.append("file", new Blob([bytes], { type: mediaType }), filename);
|
|
132
|
+
if (expiresInSeconds) form.append("expires_in_seconds", String(expiresInSeconds));
|
|
133
|
+
const res = await fetch(`${apiBase()}/files`, {
|
|
134
|
+
method: "POST",
|
|
135
|
+
headers: { "user-agent": USER_AGENT, ...authHeaders() },
|
|
136
|
+
body: form
|
|
137
|
+
});
|
|
138
|
+
if (!res.ok) await parseError(res);
|
|
139
|
+
return await res.json();
|
|
140
|
+
}
|
|
141
|
+
async function downloadFile(fileId) {
|
|
142
|
+
const res = await fetch(`${apiBase()}/files/${encodeURIComponent(fileId)}/content`, {
|
|
143
|
+
headers: { "user-agent": USER_AGENT, ...authHeaders() }
|
|
144
|
+
});
|
|
145
|
+
if (!res.ok) await parseError(res);
|
|
146
|
+
return { bytes: new Uint8Array(await res.arrayBuffer()), mediaType: res.headers.get("content-type") ?? "application/octet-stream" };
|
|
147
|
+
}
|
|
78
148
|
async function resolveSessionRef(ref) {
|
|
79
149
|
if (ref.startsWith("sess-")) return ref;
|
|
80
150
|
const { agent: agent2 } = await api(
|
|
81
151
|
"GET",
|
|
82
|
-
`/
|
|
152
|
+
`/agents/${encodeURIComponent(ref)}`
|
|
83
153
|
).catch(async () => {
|
|
84
|
-
await api("GET", `/
|
|
85
|
-
return { agent: {
|
|
154
|
+
await api("GET", `/sessions/${encodeURIComponent(ref)}`);
|
|
155
|
+
return { agent: { id: ref, defaultSessionId: ref } };
|
|
86
156
|
});
|
|
87
157
|
if (!agent2.defaultSessionId) {
|
|
88
158
|
throw new ApiError(
|
|
89
159
|
404,
|
|
90
160
|
"no_sessions",
|
|
91
|
-
`agent ${agent2.
|
|
161
|
+
`agent ${agent2.id} has no sessions \u2014 create one: sky session create ${agent2.id}`
|
|
92
162
|
);
|
|
93
163
|
}
|
|
94
164
|
return agent2.defaultSessionId;
|
|
95
165
|
}
|
|
96
166
|
async function* streamEvents(sessionId, signal) {
|
|
97
|
-
const { parseSseStream } = await import("./src-
|
|
167
|
+
const { parseSseStream } = await import("./src-EGXYUSME.js");
|
|
98
168
|
const res = await fetch(
|
|
99
|
-
`${apiBase()}/
|
|
169
|
+
`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
|
|
100
170
|
{
|
|
101
171
|
headers: { "user-agent": USER_AGENT, ...authHeaders(), accept: "text/event-stream" },
|
|
102
172
|
signal
|
|
@@ -117,9 +187,128 @@ async function* streamEvents(sessionId, signal) {
|
|
|
117
187
|
}
|
|
118
188
|
}
|
|
119
189
|
|
|
190
|
+
// src/files.ts
|
|
191
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
192
|
+
import { basename, extname } from "path";
|
|
193
|
+
var MEDIA_TYPES = {
|
|
194
|
+
".png": "image/png",
|
|
195
|
+
".jpg": "image/jpeg",
|
|
196
|
+
".jpeg": "image/jpeg",
|
|
197
|
+
".gif": "image/gif",
|
|
198
|
+
".webp": "image/webp",
|
|
199
|
+
".pdf": "application/pdf",
|
|
200
|
+
".txt": "text/plain",
|
|
201
|
+
".md": "text/markdown",
|
|
202
|
+
".csv": "text/csv",
|
|
203
|
+
".json": "application/json",
|
|
204
|
+
".html": "text/html",
|
|
205
|
+
".xml": "application/xml",
|
|
206
|
+
".yaml": "application/yaml",
|
|
207
|
+
".yml": "application/yaml"
|
|
208
|
+
};
|
|
209
|
+
function mediaTypeFor(path) {
|
|
210
|
+
return MEDIA_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
211
|
+
}
|
|
212
|
+
async function uploadLocalFile(path, expiresInSeconds) {
|
|
213
|
+
const bytes = readFileSync2(path);
|
|
214
|
+
return uploadFile(new Uint8Array(bytes), basename(path), mediaTypeFor(path), expiresInSeconds);
|
|
215
|
+
}
|
|
216
|
+
function fileBlock(file) {
|
|
217
|
+
const source = { type: "file", file_id: file.id };
|
|
218
|
+
return file.mime_type.startsWith("image/") ? { type: "image", source } : { type: "document", source, title: file.filename };
|
|
219
|
+
}
|
|
220
|
+
function eventsFor(text, files2, system) {
|
|
221
|
+
const content = [];
|
|
222
|
+
if (text.trim()) content.push({ type: "text", text });
|
|
223
|
+
for (const file of files2) content.push(fileBlock(file));
|
|
224
|
+
if (content.length === 0) throw new Error("nothing to send: pass text, --file or --image");
|
|
225
|
+
const events = [{ type: "user.message", content }];
|
|
226
|
+
if (system?.trim()) events.push({ type: "system.message", content: [{ type: "text", text: system }] });
|
|
227
|
+
return events;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// src/goal.ts
|
|
231
|
+
var CHECK_SEPARATOR = " :: ";
|
|
232
|
+
function checkCriterion(value) {
|
|
233
|
+
const at = value.indexOf(CHECK_SEPARATOR);
|
|
234
|
+
const text = at === -1 ? "" : value.slice(0, at).trim();
|
|
235
|
+
const command = (at === -1 ? value : value.slice(at + CHECK_SEPARATOR.length)).trim();
|
|
236
|
+
if (!command) throw new Error(`--check needs a command: ${JSON.stringify(value)}`);
|
|
237
|
+
return { text: text || `\`${command}\` succeeds`, command };
|
|
238
|
+
}
|
|
239
|
+
function wholeNumber(flag, value, min) {
|
|
240
|
+
if (!/^\d+$/.test(value.trim())) throw new Error(`${flag} must be a whole number, got ${JSON.stringify(value)}`);
|
|
241
|
+
const parsed = Number(value.trim());
|
|
242
|
+
if (!Number.isSafeInteger(parsed) || parsed < min) throw new Error(`${flag} must be at least ${min}`);
|
|
243
|
+
return parsed;
|
|
244
|
+
}
|
|
245
|
+
function clearable(value) {
|
|
246
|
+
return value !== void 0;
|
|
247
|
+
}
|
|
248
|
+
function limitFields(opts) {
|
|
249
|
+
const body = {};
|
|
250
|
+
if (opts.maxIterations !== void 0) body.max_iterations = wholeNumber("--max-iterations", opts.maxIterations, 1);
|
|
251
|
+
if (clearable(opts.maxSpendCents)) {
|
|
252
|
+
body.max_spend = opts.maxSpendCents === "none" ? null : { amount: String(wholeNumber("--max-spend-cents", opts.maxSpendCents, 1)), currency: "USD" };
|
|
253
|
+
}
|
|
254
|
+
if (clearable(opts.maxTokens)) {
|
|
255
|
+
body.max_tokens = opts.maxTokens === "none" ? null : wholeNumber("--max-tokens", opts.maxTokens, 1);
|
|
256
|
+
}
|
|
257
|
+
if (clearable(opts.maxMinutes)) {
|
|
258
|
+
body.max_duration_minutes = opts.maxMinutes === "none" ? null : wholeNumber("--max-minutes", opts.maxMinutes, 1);
|
|
259
|
+
}
|
|
260
|
+
return body;
|
|
261
|
+
}
|
|
262
|
+
function criteriaFields(opts) {
|
|
263
|
+
const criteria = [...opts.criterion ?? [], ...(opts.check ?? []).map(checkCriterion)];
|
|
264
|
+
return criteria.length ? criteria : null;
|
|
265
|
+
}
|
|
266
|
+
function setGoalBody(objective, opts) {
|
|
267
|
+
const text = objective.trim();
|
|
268
|
+
if (!text) throw new Error("an objective is required");
|
|
269
|
+
const criteria = criteriaFields(opts);
|
|
270
|
+
return { objective: text, ...criteria ? { criteria } : {}, ...limitFields(opts) };
|
|
271
|
+
}
|
|
272
|
+
function editGoalBody(opts) {
|
|
273
|
+
const criteria = criteriaFields(opts);
|
|
274
|
+
const body = {
|
|
275
|
+
...opts.objective !== void 0 ? { objective: opts.objective.trim() } : {},
|
|
276
|
+
...criteria ? { criteria } : {},
|
|
277
|
+
...limitFields(opts)
|
|
278
|
+
};
|
|
279
|
+
if (!Object.keys(body).length) throw new Error("nothing to change \u2014 pass --objective, --criterion, --check, or a limit");
|
|
280
|
+
return body;
|
|
281
|
+
}
|
|
282
|
+
function dollars(cents) {
|
|
283
|
+
return `$${(Number(cents) / 100).toFixed(2)}`;
|
|
284
|
+
}
|
|
285
|
+
function renderGoal(goal2) {
|
|
286
|
+
if (!goal2) return 'No goal. Set one: sky goal set <session> "<objective>"';
|
|
287
|
+
const lines = [];
|
|
288
|
+
const state = goal2.status === "stopped" && goal2.stop_reason ? `stopped (${goal2.stop_reason})` : goal2.status;
|
|
289
|
+
lines.push(`${goal2.id} ${state} iteration ${goal2.iteration}/${goal2.max_iterations}`);
|
|
290
|
+
lines.push(`objective: ${goal2.objective}`);
|
|
291
|
+
const spend = goal2.max_spend ? `${dollars(goal2.consumed_spend.amount)} of ${dollars(goal2.max_spend.amount)}` : dollars(goal2.consumed_spend.amount);
|
|
292
|
+
const tokens = goal2.max_tokens === null ? `${goal2.consumed_tokens}` : `${goal2.consumed_tokens} of ${goal2.max_tokens}`;
|
|
293
|
+
lines.push(`spent: ${spend} tokens: ${tokens}${goal2.deadline_at ? ` deadline: ${goal2.deadline_at}` : ""}`);
|
|
294
|
+
const verdicts = new Map((goal2.last_evaluation?.criteria ?? []).map((c) => [c.id, c]));
|
|
295
|
+
lines.push(`criteria${goal2.criteria_source === "derived" ? " (written from the objective)" : ""}:`);
|
|
296
|
+
for (const criterion of goal2.criteria) {
|
|
297
|
+
const verdict = verdicts.get(criterion.id);
|
|
298
|
+
const mark = verdict ? verdict.met ? "[x]" : "[ ]" : "[?]";
|
|
299
|
+
lines.push(` ${mark} ${criterion.id} ${criterion.text}`);
|
|
300
|
+
if (criterion.command) lines.push(` $ ${criterion.command}`);
|
|
301
|
+
if (verdict && !verdict.met && verdict.reason) lines.push(` ${verdict.reason}`);
|
|
302
|
+
}
|
|
303
|
+
if (goal2.last_evaluation && goal2.last_evaluation.result !== "satisfied") {
|
|
304
|
+
lines.push(`last check (iteration ${goal2.last_evaluation.iteration}): ${goal2.last_evaluation.explanation}`);
|
|
305
|
+
}
|
|
306
|
+
return lines.join("\n");
|
|
307
|
+
}
|
|
308
|
+
|
|
120
309
|
// src/agent-file.ts
|
|
121
|
-
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
122
|
-
import { dirname, resolve } from "path";
|
|
310
|
+
import { existsSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
311
|
+
import { dirname as dirname2, resolve } from "path";
|
|
123
312
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
124
313
|
var fileInstructionSchema = external_exports.object({
|
|
125
314
|
name: external_exports.string(),
|
|
@@ -128,41 +317,49 @@ var fileInstructionSchema = external_exports.object({
|
|
|
128
317
|
});
|
|
129
318
|
var agentFileSchema = external_exports.object({
|
|
130
319
|
name: external_exports.string().min(1),
|
|
131
|
-
|
|
320
|
+
id: external_exports.string().optional(),
|
|
132
321
|
// stamped by launch/deploy; identifies the live agent
|
|
322
|
+
slug: external_exports.string().optional(),
|
|
323
|
+
// pre-RFC-0079 stamp; ignored (agents are id-addressed)
|
|
133
324
|
description: external_exports.string().optional(),
|
|
134
325
|
type: external_exports.enum(AGENT_TYPES).optional(),
|
|
135
326
|
llm: external_exports.string().optional(),
|
|
327
|
+
reasoning_effort: external_exports.enum(REASONING_EFFORT_VALUES).optional(),
|
|
136
328
|
capabilities: capabilitiesSchema.optional(),
|
|
137
329
|
prompt: external_exports.string().optional(),
|
|
138
330
|
prompt_file: external_exports.string().optional(),
|
|
139
331
|
instructions: external_exports.array(fileInstructionSchema).optional(),
|
|
140
332
|
skills: external_exports.array(skillRefSchema).optional(),
|
|
141
|
-
customInstalls: external_exports.array(customInstallSchema).optional(),
|
|
142
333
|
customData: external_exports.array(customDataSchema).optional(),
|
|
143
334
|
/** Advanced machine shape (create-only): 1|2|4|8 vCPUs, 256MB-step memory. */
|
|
144
335
|
vcpus: external_exports.number().int().optional(),
|
|
145
336
|
memory_mb: external_exports.number().int().optional()
|
|
146
337
|
});
|
|
147
338
|
function readAgentFile(path) {
|
|
148
|
-
const raw =
|
|
339
|
+
const raw = readFileSync3(path, "utf8");
|
|
149
340
|
const parsed = agentFileSchema.safeParse(parseToml(raw));
|
|
150
341
|
if (!parsed.success) {
|
|
151
342
|
const issue = parsed.error.issues[0];
|
|
152
343
|
throw new Error(`${path}: ${issue?.path.join(".") ?? ""} ${issue?.message ?? "invalid"}`);
|
|
153
344
|
}
|
|
154
345
|
const file = parsed.data;
|
|
155
|
-
|
|
346
|
+
if (file.reasoning_effort && file.type && !isModelEffortSupported(file.type, file.llm ?? defaultLlm(file.type), file.reasoning_effort)) {
|
|
347
|
+
throw new Error(
|
|
348
|
+
`${path}: reasoning_effort '${file.reasoning_effort}' is not available for ${file.type} \u2014 ${reasoningEffortDocLine()}`
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
const base = dirname2(resolve(path));
|
|
156
352
|
const inline = (ref, inlineContent) => {
|
|
157
353
|
if (inlineContent !== void 0) return inlineContent;
|
|
158
354
|
if (!ref) return void 0;
|
|
159
|
-
return
|
|
355
|
+
return readFileSync3(resolve(base, ref), "utf8");
|
|
160
356
|
};
|
|
161
357
|
const spec = {
|
|
162
358
|
name: file.name,
|
|
163
359
|
description: file.description,
|
|
164
360
|
agentType: file.type,
|
|
165
361
|
llm: file.llm,
|
|
362
|
+
reasoningEffort: file.reasoning_effort,
|
|
166
363
|
displayName: file.name,
|
|
167
364
|
capabilities: file.capabilities,
|
|
168
365
|
prompt: inline(file.prompt_file, file.prompt),
|
|
@@ -172,26 +369,27 @@ function readAgentFile(path) {
|
|
|
172
369
|
return { name: f.name, content };
|
|
173
370
|
}),
|
|
174
371
|
skills: file.skills,
|
|
175
|
-
customInstalls: file.customInstalls,
|
|
176
372
|
customData: file.customData,
|
|
177
373
|
vcpus: file.vcpus,
|
|
178
374
|
memoryMb: file.memory_mb
|
|
179
375
|
};
|
|
180
376
|
return { file, spec };
|
|
181
377
|
}
|
|
182
|
-
function
|
|
183
|
-
const doc = parseToml(
|
|
184
|
-
doc.
|
|
185
|
-
|
|
378
|
+
function stampId(path, id) {
|
|
379
|
+
const doc = parseToml(readFileSync3(path, "utf8"));
|
|
380
|
+
doc.id = id;
|
|
381
|
+
delete doc.slug;
|
|
382
|
+
writeFileSync2(path, stringifyToml(doc));
|
|
186
383
|
}
|
|
187
384
|
function scaffoldAgentFile(path, name) {
|
|
188
385
|
if (existsSync(path)) throw new Error(`${path} already exists`);
|
|
189
|
-
|
|
386
|
+
writeFileSync2(
|
|
190
387
|
path,
|
|
191
388
|
`# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
|
|
192
389
|
name = "${name}"
|
|
193
|
-
# type = "hermes" # hermes | claude_code | codex | openclaw
|
|
194
|
-
# llm = "deepseek-
|
|
390
|
+
# type = "hermes" # hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode
|
|
391
|
+
# llm = "deepseek-flash" # must be compatible with type
|
|
392
|
+
# reasoning_effort = "high" # per harness \u2014 ${reasoningEffortDocLine()}
|
|
195
393
|
# capabilities = []
|
|
196
394
|
|
|
197
395
|
prompt = """
|
|
@@ -203,18 +401,18 @@ You are ${name}, a helpful long-lived agent.
|
|
|
203
401
|
|
|
204
402
|
// src/clone.ts
|
|
205
403
|
import { spawn } from "child_process";
|
|
206
|
-
import { existsSync as existsSync2, readFileSync as
|
|
207
|
-
import { homedir } from "os";
|
|
208
|
-
import { basename, join } from "path";
|
|
404
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
405
|
+
import { homedir as homedir2 } from "os";
|
|
406
|
+
import { basename as basename2, join as join2 } from "path";
|
|
209
407
|
import { createInterface } from "readline";
|
|
210
408
|
var PROMPT_CAP = 1e5;
|
|
211
409
|
var HARNESSES = ["claude_code", "codex", "openclaw"];
|
|
212
410
|
function claudeConfigRoot() {
|
|
213
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
411
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir2(), ".claude");
|
|
214
412
|
}
|
|
215
413
|
function readIfExists(path) {
|
|
216
414
|
try {
|
|
217
|
-
return
|
|
415
|
+
return readFileSync4(path, "utf8");
|
|
218
416
|
} catch {
|
|
219
417
|
return void 0;
|
|
220
418
|
}
|
|
@@ -230,9 +428,9 @@ function readJson(path) {
|
|
|
230
428
|
}
|
|
231
429
|
function claudeModel(configRoot) {
|
|
232
430
|
const candidates = [
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
431
|
+
join2(process.cwd(), ".claude", "settings.local.json"),
|
|
432
|
+
join2(process.cwd(), ".claude", "settings.json"),
|
|
433
|
+
join2(configRoot, "settings.json")
|
|
236
434
|
];
|
|
237
435
|
for (const path of candidates) {
|
|
238
436
|
const model = readJson(path)?.model;
|
|
@@ -242,11 +440,11 @@ function claudeModel(configRoot) {
|
|
|
242
440
|
}
|
|
243
441
|
function claudeMcpServers() {
|
|
244
442
|
const merged = {};
|
|
245
|
-
const userConfig = readJson(
|
|
443
|
+
const userConfig = readJson(join2(homedir2(), ".claude.json"));
|
|
246
444
|
Object.assign(merged, userConfig?.mcpServers ?? {});
|
|
247
445
|
Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
|
|
248
446
|
const projectConfig = readJson(
|
|
249
|
-
|
|
447
|
+
join2(process.cwd(), ".mcp.json")
|
|
250
448
|
);
|
|
251
449
|
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
252
450
|
return merged;
|
|
@@ -263,13 +461,13 @@ function readClaudeCode() {
|
|
|
263
461
|
const configRoot = claudeConfigRoot();
|
|
264
462
|
const parts = [];
|
|
265
463
|
const sources = [];
|
|
266
|
-
const global = readIfExists(
|
|
464
|
+
const global = readIfExists(join2(configRoot, "CLAUDE.md"));
|
|
267
465
|
if (global?.trim()) {
|
|
268
466
|
parts.push({ label: "Global CLAUDE.md", content: global });
|
|
269
467
|
sources.push("global CLAUDE.md");
|
|
270
468
|
}
|
|
271
469
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
272
|
-
const project = readIfExists(
|
|
470
|
+
const project = readIfExists(join2(process.cwd(), name));
|
|
273
471
|
if (project?.trim()) {
|
|
274
472
|
parts.push({ label: `Project ${name}`, content: project });
|
|
275
473
|
sources.push(`project ${name}`);
|
|
@@ -297,8 +495,8 @@ function detectHarness(explicit) {
|
|
|
297
495
|
return explicit;
|
|
298
496
|
}
|
|
299
497
|
if (existsSync2(claudeConfigRoot())) return "claude_code";
|
|
300
|
-
if (existsSync2(
|
|
301
|
-
if (existsSync2(
|
|
498
|
+
if (existsSync2(join2(homedir2(), ".codex"))) return "codex";
|
|
499
|
+
if (existsSync2(join2(homedir2(), ".openclaw"))) return "openclaw";
|
|
302
500
|
throw new Error(
|
|
303
501
|
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
304
502
|
);
|
|
@@ -310,14 +508,14 @@ function readLocal(harness) {
|
|
|
310
508
|
);
|
|
311
509
|
}
|
|
312
510
|
function linkPath(configRoot) {
|
|
313
|
-
return
|
|
511
|
+
return join2(configRoot, "agentsky-link.json");
|
|
314
512
|
}
|
|
315
513
|
function readLink(configRoot) {
|
|
316
514
|
const link = readJson(linkPath(configRoot));
|
|
317
|
-
return link?.
|
|
515
|
+
return link?.agentId ? link : void 0;
|
|
318
516
|
}
|
|
319
517
|
function writeLink(configRoot, link) {
|
|
320
|
-
|
|
518
|
+
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link, null, 2)}
|
|
321
519
|
`);
|
|
322
520
|
}
|
|
323
521
|
function confirm(question) {
|
|
@@ -346,7 +544,7 @@ async function createClone(local, opts) {
|
|
|
346
544
|
`model ${local.model ?? "(harness default)"}`,
|
|
347
545
|
`${keptNames.length} MCP server${keptNames.length === 1 ? "" : "s"}${keptNames.length ? ` (${keptNames.join(", ")})` : ""}`
|
|
348
546
|
].join(" \xB7 ");
|
|
349
|
-
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${
|
|
547
|
+
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${basename2(process.cwd())})`);
|
|
350
548
|
console.log(`Will clone: ${summary}`);
|
|
351
549
|
if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
|
|
352
550
|
if (local.droppedServers.length) {
|
|
@@ -362,41 +560,48 @@ async function createClone(local, opts) {
|
|
|
362
560
|
console.log("Aborted \u2014 nothing sent.");
|
|
363
561
|
return;
|
|
364
562
|
}
|
|
365
|
-
const
|
|
563
|
+
const { servers: mcpServers, skipped } = mcpServersFromKept(local.keptServers);
|
|
564
|
+
if (skipped.length) {
|
|
565
|
+
console.log(` note: local (stdio) MCP servers have no cloud equivalent and were skipped: ${skipped.join(", ")}`);
|
|
566
|
+
}
|
|
366
567
|
const body = {
|
|
367
568
|
agentType: local.harness,
|
|
368
569
|
...local.model ? { llm: local.model } : {},
|
|
369
570
|
...local.prompt ? { prompt: local.prompt } : {},
|
|
370
|
-
...
|
|
571
|
+
...mcpServers.length ? { mcpServers } : {}
|
|
371
572
|
};
|
|
372
573
|
let sentLlm = local.model ?? null;
|
|
373
574
|
let created;
|
|
374
575
|
try {
|
|
375
|
-
({ agent: created } = await api("POST", "/
|
|
576
|
+
({ agent: created } = await api("POST", "/agents", body));
|
|
376
577
|
} catch (error) {
|
|
377
578
|
if (error instanceof ApiError && error.status === 422 && local.model) {
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
579
|
+
const successor = supersededBy(local.model);
|
|
580
|
+
console.log(
|
|
581
|
+
successor ? ` note: ${local.model} is superseded \u2014 using ${successor}` : ` note: ${local.model} isn't available for ${local.harness} \u2014 using the harness default`
|
|
582
|
+
);
|
|
583
|
+
sentLlm = successor;
|
|
584
|
+
({ agent: created } = await api("POST", "/agents", {
|
|
381
585
|
...body,
|
|
382
|
-
llm: void 0
|
|
586
|
+
llm: successor ?? void 0
|
|
383
587
|
}));
|
|
384
588
|
} else {
|
|
385
589
|
throw error;
|
|
386
590
|
}
|
|
387
591
|
}
|
|
388
|
-
|
|
592
|
+
console.log("Starting its runtime\u2026");
|
|
593
|
+
await api("POST", "/sessions", { agent: created.id });
|
|
389
594
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
390
595
|
writeLink(local.configRoot, {
|
|
391
596
|
baseUrl: apiBase(),
|
|
392
|
-
|
|
597
|
+
agentId: created.id,
|
|
393
598
|
clonedAt: now,
|
|
394
599
|
lastSyncedAt: now,
|
|
395
600
|
llm: sentLlm,
|
|
396
601
|
mcpServers: keptNames
|
|
397
602
|
});
|
|
398
|
-
const url = `${apiBase()}/agents/${created.
|
|
399
|
-
console.log(`Cloned \u2192 ${created.
|
|
603
|
+
const url = `${apiBase()}/agents/${created.id}`;
|
|
604
|
+
console.log(`Cloned \u2192 ${created.id} ${url}`);
|
|
400
605
|
console.log(
|
|
401
606
|
`Linked this machine (${linkPath(local.configRoot)}). Push prompt changes anytime by running \`sky clone\` again.`
|
|
402
607
|
);
|
|
@@ -405,11 +610,11 @@ async function createClone(local, opts) {
|
|
|
405
610
|
async function syncClone(local, link, opts) {
|
|
406
611
|
const since = link.lastSyncedAt ?? link.clonedAt;
|
|
407
612
|
console.log(
|
|
408
|
-
`Syncing ${link.
|
|
613
|
+
`Syncing ${link.agentId} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
|
|
409
614
|
);
|
|
410
615
|
if (opts.dryRun) {
|
|
411
616
|
console.log(
|
|
412
|
-
` would push prompt (${kb(local.prompt)}) to ${link.
|
|
617
|
+
` would push prompt (${kb(local.prompt)}) to ${link.agentId}. Dry run \u2014 nothing sent.`
|
|
413
618
|
);
|
|
414
619
|
return;
|
|
415
620
|
}
|
|
@@ -417,24 +622,24 @@ async function syncClone(local, link, opts) {
|
|
|
417
622
|
try {
|
|
418
623
|
({ agent: agent2 } = await api(
|
|
419
624
|
"GET",
|
|
420
|
-
`/
|
|
625
|
+
`/agents/${encodeURIComponent(link.agentId)}`
|
|
421
626
|
));
|
|
422
627
|
} catch (error) {
|
|
423
628
|
if (error instanceof ApiError && error.status === 404) {
|
|
424
629
|
throw new Error(
|
|
425
|
-
`linked agent ${link.
|
|
630
|
+
`linked agent ${link.agentId} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
|
|
426
631
|
);
|
|
427
632
|
}
|
|
428
633
|
throw error;
|
|
429
634
|
}
|
|
430
635
|
if (agent2.agentType !== local.harness) {
|
|
431
636
|
throw new Error(
|
|
432
|
-
`linked agent ${link.
|
|
637
|
+
`linked agent ${link.agentId} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
|
|
433
638
|
);
|
|
434
639
|
}
|
|
435
640
|
const result = await api(
|
|
436
641
|
"PUT",
|
|
437
|
-
`/
|
|
642
|
+
`/agents/${encodeURIComponent(link.agentId)}/prompt`,
|
|
438
643
|
{ prompt: local.prompt }
|
|
439
644
|
);
|
|
440
645
|
console.log(
|
|
@@ -462,8 +667,8 @@ async function syncClone(local, link, opts) {
|
|
|
462
667
|
);
|
|
463
668
|
}
|
|
464
669
|
writeLink(local.configRoot, { ...link, lastSyncedAt: (/* @__PURE__ */ new Date()).toISOString() });
|
|
465
|
-
console.log(`Synced ${link.
|
|
466
|
-
if (opts.open) openBrowser(`${apiBase()}/agents/${link.
|
|
670
|
+
console.log(`Synced ${link.agentId}.`);
|
|
671
|
+
if (opts.open) openBrowser(`${apiBase()}/agents/${link.agentId}`);
|
|
467
672
|
}
|
|
468
673
|
async function clone(opts) {
|
|
469
674
|
const harness = detectHarness(opts.harness);
|
|
@@ -526,7 +731,7 @@ async function browserLogin(verifier, challenge) {
|
|
|
526
731
|
server.listen(0, "127.0.0.1", () => {
|
|
527
732
|
const address = server.address();
|
|
528
733
|
const port = typeof address === "object" && address ? address.port : 0;
|
|
529
|
-
const authorizeUrl = `${
|
|
734
|
+
const authorizeUrl = `${consoleBase()}/cli/authorize?state=${state}&port=${port}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
|
|
530
735
|
console.log("Opening your browser to authorize this machine\u2026");
|
|
531
736
|
console.log(`If it doesn't open, visit:
|
|
532
737
|
${authorizeUrl}`);
|
|
@@ -539,14 +744,14 @@ async function browserLogin(verifier, challenge) {
|
|
|
539
744
|
});
|
|
540
745
|
return api(
|
|
541
746
|
"POST",
|
|
542
|
-
"/
|
|
747
|
+
"/cli/token-exchange",
|
|
543
748
|
{ code, verifier, clientName: `cli ${hostname()}` },
|
|
544
749
|
{ auth: false }
|
|
545
750
|
);
|
|
546
751
|
}
|
|
547
752
|
async function headlessLogin(verifier, challenge) {
|
|
548
753
|
const request = b64url(randomBytes(24));
|
|
549
|
-
const authorizeUrl = `${
|
|
754
|
+
const authorizeUrl = `${consoleBase()}/cli/authorize?request=${request}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
|
|
550
755
|
console.log(`Visit this URL on any device to authorize:
|
|
551
756
|
${authorizeUrl}
|
|
552
757
|
`);
|
|
@@ -555,7 +760,7 @@ async function headlessLogin(verifier, challenge) {
|
|
|
555
760
|
while (Date.now() < deadline) {
|
|
556
761
|
await new Promise((r) => setTimeout(r, 3e3));
|
|
557
762
|
const res = await fetch(
|
|
558
|
-
`${apiBase()}/
|
|
763
|
+
`${apiBase()}/cli/poll?request=${request}&verifier=${encodeURIComponent(verifier)}&clientName=${encodeURIComponent(`cli ${hostname()}`)}`,
|
|
559
764
|
{ headers: { "user-agent": USER_AGENT } }
|
|
560
765
|
);
|
|
561
766
|
if (!res.ok) {
|
|
@@ -575,7 +780,6 @@ async function headlessLogin(verifier, challenge) {
|
|
|
575
780
|
// src/render.ts
|
|
576
781
|
var dim = (s) => `\x1B[2m${s}\x1B[0m`;
|
|
577
782
|
var red = (s) => `\x1B[31m${s}\x1B[0m`;
|
|
578
|
-
var cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
579
783
|
function clip(value, max = 120) {
|
|
580
784
|
const text = typeof value === "string" ? value : JSON.stringify(value);
|
|
581
785
|
return text.length > max ? `${text.slice(0, max)}\u2026` : text;
|
|
@@ -598,18 +802,18 @@ function renderEvent(event, data) {
|
|
|
598
802
|
case "agent.status":
|
|
599
803
|
console.log(dim(`\u2026 ${String(part?.level ?? "")} ${String(part?.text ?? "")}`.trim()));
|
|
600
804
|
break;
|
|
601
|
-
case "user.
|
|
602
|
-
|
|
603
|
-
if (channel && channel !== "api") {
|
|
604
|
-
console.log(cyan(`[${channel}] ${clip(data.text ?? "")}`));
|
|
605
|
-
}
|
|
805
|
+
case "user.interrupt":
|
|
806
|
+
console.log(dim("\u23F9 interrupting"));
|
|
606
807
|
break;
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
808
|
+
case "session.error": {
|
|
809
|
+
const error = data.error;
|
|
810
|
+
const message = String(error?.message ?? error?.type ?? "unknown");
|
|
811
|
+
if (error?.retry_status === "retrying") console.error(dim(`retrying: ${message}`));
|
|
812
|
+
else console.error(red(`error: ${message}`));
|
|
610
813
|
break;
|
|
611
|
-
|
|
612
|
-
|
|
814
|
+
}
|
|
815
|
+
case "session.status_terminated":
|
|
816
|
+
console.log(dim("session terminated"));
|
|
613
817
|
break;
|
|
614
818
|
default:
|
|
615
819
|
break;
|
|
@@ -618,9 +822,9 @@ function renderEvent(event, data) {
|
|
|
618
822
|
|
|
619
823
|
// src/subscriptions.ts
|
|
620
824
|
import { spawn as spawn3 } from "child_process";
|
|
621
|
-
import { existsSync as existsSync3, readFileSync as
|
|
622
|
-
import { homedir as
|
|
623
|
-
import { join as
|
|
825
|
+
import { existsSync as existsSync3, readFileSync as readFileSync5 } from "fs";
|
|
826
|
+
import { homedir as homedir3 } from "os";
|
|
827
|
+
import { join as join3 } from "path";
|
|
624
828
|
import { createInterface as createInterface2 } from "readline";
|
|
625
829
|
var ALIASES = {
|
|
626
830
|
claude: "anthropic",
|
|
@@ -678,7 +882,7 @@ function mintClaudeToken() {
|
|
|
678
882
|
async function put(provider, credential) {
|
|
679
883
|
const { modelSubscription } = await api(
|
|
680
884
|
"PUT",
|
|
681
|
-
`/
|
|
885
|
+
`/model-subscriptions/${provider}`,
|
|
682
886
|
{ credential }
|
|
683
887
|
);
|
|
684
888
|
return modelSubscription;
|
|
@@ -698,12 +902,12 @@ async function connectClaude() {
|
|
|
698
902
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
699
903
|
}
|
|
700
904
|
async function connectChatgpt(opts) {
|
|
701
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
702
|
-
const path =
|
|
905
|
+
const codexHome = process.env.CODEX_HOME || join3(homedir3(), ".codex");
|
|
906
|
+
const path = join3(codexHome, "auth.json");
|
|
703
907
|
if (!existsSync3(path)) {
|
|
704
908
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
705
909
|
}
|
|
706
|
-
const raw =
|
|
910
|
+
const raw = readFileSync5(path, "utf8");
|
|
707
911
|
let accountId;
|
|
708
912
|
try {
|
|
709
913
|
const parsed = JSON.parse(raw);
|
|
@@ -725,7 +929,7 @@ async function connectChatgpt(opts) {
|
|
|
725
929
|
async function listSubscriptions() {
|
|
726
930
|
const { modelSubscriptions } = await api(
|
|
727
931
|
"GET",
|
|
728
|
-
"/
|
|
932
|
+
"/model-subscriptions"
|
|
729
933
|
);
|
|
730
934
|
if (modelSubscriptions.length === 0) {
|
|
731
935
|
console.log("No model subscriptions connected. Try `sky auth connect claude` or `sky auth connect chatgpt`.");
|
|
@@ -741,7 +945,7 @@ async function listSubscriptions() {
|
|
|
741
945
|
s.useForNewAgents ? "used for new agents" : "not used for new agents"
|
|
742
946
|
].filter(Boolean);
|
|
743
947
|
console.log(bits.join(" \xB7 "));
|
|
744
|
-
for (const a of s.agents) console.log(` ${a.
|
|
948
|
+
for (const a of s.agents) console.log(` ${a.agentId ?? a.id} \u2014 ${a.name}`);
|
|
745
949
|
}
|
|
746
950
|
}
|
|
747
951
|
async function disconnect(name, opts) {
|
|
@@ -756,18 +960,25 @@ async function disconnect(name, opts) {
|
|
|
756
960
|
}
|
|
757
961
|
const { affectedAgents } = await api(
|
|
758
962
|
"DELETE",
|
|
759
|
-
`/
|
|
963
|
+
`/model-subscriptions/${provider}`
|
|
760
964
|
);
|
|
761
965
|
console.log(`Disconnected ${label}.`);
|
|
762
966
|
if (affectedAgents.length > 0) {
|
|
763
967
|
console.log(
|
|
764
|
-
`${affectedAgents.length} agent${affectedAgents.length === 1 ? "" : "s"} still point at it and will fail: ` + affectedAgents.map((a) => a.
|
|
968
|
+
`${affectedAgents.length} agent${affectedAgents.length === 1 ? "" : "s"} still point at it and will fail: ` + affectedAgents.map((a) => a.agentId ?? a.id).join(", ")
|
|
765
969
|
);
|
|
766
970
|
console.log("Switch them to Platform in each agent's Configure tab (or reconnect).");
|
|
767
971
|
}
|
|
768
972
|
}
|
|
769
973
|
|
|
770
974
|
// src/index.ts
|
|
975
|
+
function printModelBilling(session2) {
|
|
976
|
+
if (session2.modelBilling !== "account") return;
|
|
977
|
+
const provider = modelSubscriptionProviderFor(session2.agentType, session2.llm);
|
|
978
|
+
if (!provider) return;
|
|
979
|
+
const label = provider === "anthropic" ? "your Claude subscription" : "your ChatGPT plan";
|
|
980
|
+
console.log(`Model billing: ${label} \u2014 $0 model usage`);
|
|
981
|
+
}
|
|
771
982
|
var program = new Command();
|
|
772
983
|
program.name("sky").description("Launch and talk to long-lived agents \u2014 agentsky.dev").version(CLI_VERSION);
|
|
773
984
|
function fail(error) {
|
|
@@ -778,15 +989,15 @@ function fail(error) {
|
|
|
778
989
|
var run = (fn) => (...args) => fn(...args).catch(fail);
|
|
779
990
|
var auth = program.command("auth").description("login, logout, whoami, model subscriptions");
|
|
780
991
|
auth.command("login").description("authorize this machine via the browser").option("--no-browser", "print a URL and poll instead of opening a browser").action(run(async (opts) => login({ noBrowser: opts.browser === false })));
|
|
781
|
-
auth.command("logout").description("forget the stored token (revoke it in
|
|
992
|
+
auth.command("logout").description("forget the stored token (revoke it in Developer \u2192 API keys)").action(
|
|
782
993
|
run(async () => {
|
|
783
994
|
saveConfig({ ...loadConfig(), token: void 0, email: void 0 });
|
|
784
|
-
console.log(`Logged out. (Token revocation:
|
|
995
|
+
console.log(`Logged out. (Token revocation: Developer \u2192 API keys.)`);
|
|
785
996
|
})
|
|
786
997
|
);
|
|
787
998
|
auth.command("whoami").description("who the current token acts as").action(
|
|
788
999
|
run(async () => {
|
|
789
|
-
const who = await api("GET", "/
|
|
1000
|
+
const who = await api("GET", "/whoami");
|
|
790
1001
|
console.log(
|
|
791
1002
|
`${who.user.email ?? "unknown"} \xB7 universe ${who.universe.slug}${who.universe.isPersonal ? " (personal)" : ""} \xB7 scopes ${who.scopes.join(",")}`
|
|
792
1003
|
);
|
|
@@ -804,7 +1015,7 @@ auth.command("disconnect <provider>").description("disconnect a model subscripti
|
|
|
804
1015
|
var universe = program.command("universe").description("grouping boundary for agents");
|
|
805
1016
|
universe.command("list").action(
|
|
806
1017
|
run(async () => {
|
|
807
|
-
const { universes } = await api("GET", "/
|
|
1018
|
+
const { universes } = await api("GET", "/universes");
|
|
808
1019
|
const current = currentUniverse();
|
|
809
1020
|
for (const u of universes) {
|
|
810
1021
|
const marker = u.slug === current || !current && u.isPersonal ? "*" : " ";
|
|
@@ -814,13 +1025,13 @@ universe.command("list").action(
|
|
|
814
1025
|
);
|
|
815
1026
|
universe.command("create <slug>").action(
|
|
816
1027
|
run(async (slug) => {
|
|
817
|
-
const created = await api("POST", "/
|
|
1028
|
+
const created = await api("POST", "/universes", { slug });
|
|
818
1029
|
console.log(`Created universe ${created.universe.slug}`);
|
|
819
1030
|
})
|
|
820
1031
|
);
|
|
821
1032
|
universe.command("switch <slug>").action(
|
|
822
1033
|
run(async (slug) => {
|
|
823
|
-
await api("GET", `/
|
|
1034
|
+
await api("GET", `/universes/${encodeURIComponent(slug)}`);
|
|
824
1035
|
saveConfig({ ...loadConfig(), currentUniverse: slug });
|
|
825
1036
|
console.log(`Switched to ${slug}`);
|
|
826
1037
|
})
|
|
@@ -828,7 +1039,7 @@ universe.command("switch <slug>").action(
|
|
|
828
1039
|
universe.command("show").action(
|
|
829
1040
|
run(async () => {
|
|
830
1041
|
const slug = currentUniverse();
|
|
831
|
-
const path = slug ? `/
|
|
1042
|
+
const path = slug ? `/universes/${encodeURIComponent(slug)}` : "/whoami";
|
|
832
1043
|
if (slug) {
|
|
833
1044
|
const { universe: u } = await api("GET", path);
|
|
834
1045
|
console.log(`${u.slug} \u2014 ${u.name} \xB7 ${u.agents} agents \xB7 ${u.members} members`);
|
|
@@ -839,22 +1050,37 @@ universe.command("show").action(
|
|
|
839
1050
|
})
|
|
840
1051
|
);
|
|
841
1052
|
var agent = program.command("agent").description("agent specs: create, inspect, talk (via the default session)");
|
|
842
|
-
agent.command("create").description("create an agent spec \u2014 no runtime; `sky session create` starts one").option("--type <type>", "hermes | claude_code | codex | openclaw").option("--llm <llm>", "model id (defaults per type)").option(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1053
|
+
agent.command("create").description("create an agent spec \u2014 no runtime; `sky session create` starts one").option("--type <type>", "hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "model id (defaults per type)").option(
|
|
1054
|
+
"--reasoning-effort <level>",
|
|
1055
|
+
`harness-native reasoning effort (default: the harness's own) \u2014 ${reasoningEffortDocLine()}`
|
|
1056
|
+
).option("--name <name>", "agent name").option(
|
|
1057
|
+
"--capability <id>",
|
|
1058
|
+
"grant a built-in capability (repeatable; ids at /docs/capabilities; none by default)",
|
|
1059
|
+
(id, all) => [...all, id],
|
|
1060
|
+
[]
|
|
1061
|
+
).option("-f, --file <path>", "create from an agent.toml").action(
|
|
1062
|
+
run(
|
|
1063
|
+
async (opts) => {
|
|
1064
|
+
const spec = opts.file ? readAgentFile(opts.file).spec : {
|
|
1065
|
+
name: opts.name ?? "My agent",
|
|
1066
|
+
agentType: opts.type,
|
|
1067
|
+
llm: opts.llm,
|
|
1068
|
+
reasoningEffort: opts.reasoningEffort,
|
|
1069
|
+
displayName: opts.name,
|
|
1070
|
+
...opts.capability.length ? { capabilities: opts.capability } : {}
|
|
1071
|
+
};
|
|
1072
|
+
const { agent: created } = await api("POST", "/agents", spec);
|
|
1073
|
+
console.log(`Created ${created.id} (${created.agentType} \xB7 ${created.llm}) \u2014 spec only.`);
|
|
1074
|
+
console.log(
|
|
1075
|
+
created.capabilities.length ? `Capabilities: ${created.capabilities.join(", ")}` : "Capabilities: none (grant with --capability <id> or PATCH capabilities)"
|
|
1076
|
+
);
|
|
1077
|
+
console.log(`Start it: sky session create ${created.id}`);
|
|
1078
|
+
}
|
|
1079
|
+
)
|
|
854
1080
|
);
|
|
855
1081
|
agent.command("list").action(
|
|
856
1082
|
run(async () => {
|
|
857
|
-
const { agents } = await api("GET", "/
|
|
1083
|
+
const { agents } = await api("GET", "/agents");
|
|
858
1084
|
if (!agents.length) {
|
|
859
1085
|
console.log("No agents yet. `sky launch` or `sky agent create` to get one.");
|
|
860
1086
|
return;
|
|
@@ -862,16 +1088,17 @@ agent.command("list").action(
|
|
|
862
1088
|
for (const a of agents) {
|
|
863
1089
|
const state = a.archived ? "archived" : `${a.sessionCount} session${a.sessionCount === 1 ? "" : "s"}`;
|
|
864
1090
|
console.log(
|
|
865
|
-
`${a.
|
|
1091
|
+
`${a.id.padEnd(32)} ${a.displayName.padEnd(20)} ${a.agentType}/${a.llm} ${state}`
|
|
866
1092
|
);
|
|
867
1093
|
}
|
|
868
1094
|
})
|
|
869
1095
|
);
|
|
870
|
-
agent.command("show <
|
|
871
|
-
run(async (
|
|
872
|
-
const { agent: a } = await api("GET", `/
|
|
873
|
-
console.log(`${a.
|
|
1096
|
+
agent.command("show <id>").action(
|
|
1097
|
+
run(async (id) => {
|
|
1098
|
+
const { agent: a } = await api("GET", `/agents/${encodeURIComponent(id)}`);
|
|
1099
|
+
console.log(`${a.id} \u2014 ${a.displayName}${a.archived ? " (archived)" : ""}`);
|
|
874
1100
|
console.log(` type ${a.agentType} \xB7 ${a.llm}`);
|
|
1101
|
+
if (a.reasoningEffort) console.log(` effort ${a.reasoningEffort}`);
|
|
875
1102
|
console.log(` version ${a.version}`);
|
|
876
1103
|
console.log(` sessions ${a.sessionCount}`);
|
|
877
1104
|
console.log(` universe ${a.universe}`);
|
|
@@ -879,42 +1106,73 @@ agent.command("show <slug>").action(
|
|
|
879
1106
|
if (a.prompt) console.log(` prompt ${a.prompt.split("\n")[0]?.slice(0, 80)}\u2026`);
|
|
880
1107
|
})
|
|
881
1108
|
);
|
|
882
|
-
agent.command("archive <
|
|
883
|
-
run(async (
|
|
884
|
-
await api("POST", `/
|
|
885
|
-
console.log(`Archived ${
|
|
1109
|
+
agent.command("archive <id>").description("retire the spec \u2014 read-only, running sessions unaffected (admin scope)").action(
|
|
1110
|
+
run(async (id) => {
|
|
1111
|
+
await api("POST", `/agents/${encodeURIComponent(id)}/archive`);
|
|
1112
|
+
console.log(`Archived ${id} \u2014 sessions keep running; new sessions are rejected.`);
|
|
886
1113
|
})
|
|
887
1114
|
);
|
|
888
|
-
agent.command("destroy <
|
|
889
|
-
run(async (
|
|
890
|
-
await api("DELETE", `/
|
|
891
|
-
console.log(`Destroyed ${
|
|
1115
|
+
agent.command("destroy <id>").description("delete the spec; requires all sessions deleted first (admin scope)").action(
|
|
1116
|
+
run(async (id) => {
|
|
1117
|
+
await api("DELETE", `/agents/${encodeURIComponent(id)}`);
|
|
1118
|
+
console.log(`Destroyed ${id}`);
|
|
892
1119
|
})
|
|
893
1120
|
);
|
|
894
|
-
agent.command("message <
|
|
1121
|
+
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").action(
|
|
895
1122
|
run(
|
|
896
|
-
async (
|
|
1123
|
+
async (id, text, opts) => sendAndFollow(await resolveSessionRef(id), text ?? "", opts)
|
|
897
1124
|
)
|
|
898
1125
|
);
|
|
899
|
-
agent.command("chat <
|
|
1126
|
+
agent.command("chat <id>").description("interactive REPL with the default session").option("--system <text>", "per-turn system text sent with the first message").action(run(async (id, opts) => chatLoop(await resolveSessionRef(id), opts)));
|
|
900
1127
|
var session = program.command("session").description("sessions: the running instances of an agent");
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
1128
|
+
async function fileResource(spec) {
|
|
1129
|
+
const at = spec.lastIndexOf(":/");
|
|
1130
|
+
const path = at > 0 ? spec.slice(0, at) : spec;
|
|
1131
|
+
const mount = at > 0 ? spec.slice(at + 1) : void 0;
|
|
1132
|
+
const file = await uploadLocalFile(path);
|
|
1133
|
+
console.error(`uploaded ${file.filename} \u2192 ${file.id}`);
|
|
1134
|
+
return { type: "file", file_id: file.id, ...mount ? { mount_path: mount } : {} };
|
|
1135
|
+
}
|
|
1136
|
+
function repoResource(spec, token) {
|
|
1137
|
+
const at = spec.lastIndexOf("@");
|
|
1138
|
+
const url = at > "https://".length ? spec.slice(0, at) : spec;
|
|
1139
|
+
const ref = at > "https://".length ? spec.slice(at + 1) : "";
|
|
1140
|
+
const checkout = ref ? /^[0-9a-f]{7,64}$/.test(ref) ? { type: "commit", sha: ref } : { type: "branch", name: ref } : void 0;
|
|
1141
|
+
return { type: "github_repository", url, ...checkout ? { checkout } : {}, ...token ? { authorization_token: token } : {} };
|
|
1142
|
+
}
|
|
1143
|
+
async function resourceParams(opts) {
|
|
1144
|
+
const files2 = await Promise.all((opts.file ?? []).map(fileResource));
|
|
1145
|
+
return [...files2, ...(opts.repo ?? []).map((spec) => repoResource(spec, opts.repoToken))];
|
|
1146
|
+
}
|
|
1147
|
+
function renderResource(r) {
|
|
1148
|
+
if (r.type === "file") return `${r.id} file ${r.mount_path} ${r.file_id}`;
|
|
1149
|
+
const ref = r.checkout ? `@${r.checkout.type === "branch" ? r.checkout.name : r.checkout.sha}` : "";
|
|
1150
|
+
return `${r.id} repo ${r.mount_path} ${r.url}${ref}`;
|
|
1151
|
+
}
|
|
1152
|
+
session.command("create <agent>").description("create a session (starts provisioning by default)").option("--title <title>", "session title").option("--message <text>", "start the first turn immediately, provisioning even with --deferred").option("--deferred", "leave the session idle without a pod until its first turn").option("--vcpus <n>", "machine vCPUs: 1, 2, 4 or 8 (default 2)").option("--memory <mb>", "machine memory in MB, 256MB steps (default 4096)").option("--file <path...>", "mount local files read-only in the working directory: path[:/mount] (uploaded first)").option("--repo <url...>", "clone GitHub repositories into the working directory: https://github.com/{owner}/{repo}[@branch|@sha]").option("--repo-token <token>", "token for the --repo clones; compiled into the sandbox's egress policy, never sent to it").action(
|
|
1153
|
+
run(
|
|
1154
|
+
async (agentRef, opts) => {
|
|
1155
|
+
const resources = await resourceParams(opts);
|
|
1156
|
+
const { session: created } = await api("POST", "/sessions", {
|
|
1157
|
+
agent: agentRef,
|
|
1158
|
+
...opts.title ? { title: opts.title } : {},
|
|
1159
|
+
...opts.vcpus !== void 0 ? { vcpus: Number(opts.vcpus) } : {},
|
|
1160
|
+
...opts.memory !== void 0 ? { memoryMb: Number(opts.memory) } : {},
|
|
1161
|
+
...opts.deferred ? { provision: "deferred" } : {},
|
|
1162
|
+
...resources.length ? { resources } : {},
|
|
1163
|
+
...opts.message ? { initial_events: [{ type: "user.message", content: [{ type: "text", text: opts.message }] }] } : {}
|
|
1164
|
+
});
|
|
1165
|
+
console.log(`Created ${created.id} (${created.agent} \xB7 ${created.status})${created.default ? " [default]" : ""}`);
|
|
1166
|
+
printModelBilling(created);
|
|
1167
|
+
for (const r of created.resources ?? []) console.log(` ${renderResource(r)}`);
|
|
1168
|
+
console.log(` sky agent message ${created.agent} "hello"`);
|
|
1169
|
+
}
|
|
1170
|
+
)
|
|
913
1171
|
);
|
|
914
1172
|
session.command("list [agent]").action(
|
|
915
1173
|
run(async (agentRef) => {
|
|
916
1174
|
const qs = agentRef ? `?agent=${encodeURIComponent(agentRef)}` : "";
|
|
917
|
-
const { sessions } = await api("GET", `/
|
|
1175
|
+
const { sessions } = await api("GET", `/sessions${qs}`);
|
|
918
1176
|
if (!sessions.length) {
|
|
919
1177
|
console.log("No sessions. `sky session create <agent>` to start one.");
|
|
920
1178
|
return;
|
|
@@ -930,7 +1188,7 @@ session.command("show <id>").action(
|
|
|
930
1188
|
run(async (id) => {
|
|
931
1189
|
const { session: s } = await api(
|
|
932
1190
|
"GET",
|
|
933
|
-
`/
|
|
1191
|
+
`/sessions/${encodeURIComponent(id)}`
|
|
934
1192
|
);
|
|
935
1193
|
console.log(`${s.id}${s.default ? " [default]" : ""}`);
|
|
936
1194
|
console.log(` agent ${s.agent ?? "-"}`);
|
|
@@ -939,68 +1197,150 @@ session.command("show <id>").action(
|
|
|
939
1197
|
console.log(` model ${s.agentType} \xB7 ${s.llm}`);
|
|
940
1198
|
})
|
|
941
1199
|
);
|
|
942
|
-
|
|
1200
|
+
function failWithInterruptHint(error, id) {
|
|
1201
|
+
if (error instanceof ApiError && error.code === "session_running") {
|
|
1202
|
+
console.error(
|
|
1203
|
+
`sky: session is running \u2014 \`sky session interrupt ${id}\`, wait for idle, then retry`
|
|
1204
|
+
);
|
|
1205
|
+
process.exit(1);
|
|
1206
|
+
}
|
|
1207
|
+
throw error;
|
|
1208
|
+
}
|
|
1209
|
+
session.command("archive <id>").description("irreversibly archive: no new messages, pod torn down, history stays readable").action(
|
|
1210
|
+
run(async (id) => {
|
|
1211
|
+
await api("POST", `/sessions/${encodeURIComponent(id)}/archive`).catch(
|
|
1212
|
+
(error) => failWithInterruptHint(error, id)
|
|
1213
|
+
);
|
|
1214
|
+
console.log(`Archived ${id} \u2014 terminated; history stays readable. This is irreversible.`);
|
|
1215
|
+
})
|
|
1216
|
+
);
|
|
1217
|
+
session.command("delete <id>").description("permanently delete the session, its events, and its sandbox").action(
|
|
943
1218
|
run(async (id) => {
|
|
944
|
-
await api("DELETE", `/
|
|
945
|
-
|
|
1219
|
+
await api("DELETE", `/sessions/${encodeURIComponent(id)}`).catch(
|
|
1220
|
+
(error) => failWithInterruptHint(error, id)
|
|
1221
|
+
);
|
|
1222
|
+
console.log(`Deleted ${id}`);
|
|
946
1223
|
})
|
|
947
1224
|
);
|
|
948
|
-
session.command("message <ref>
|
|
1225
|
+
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").action(
|
|
949
1226
|
run(
|
|
950
|
-
async (ref, text, opts) => sendAndFollow(await resolveSessionRef(ref), text,
|
|
1227
|
+
async (ref, text, opts) => sendAndFollow(await resolveSessionRef(ref), text ?? "", opts)
|
|
951
1228
|
)
|
|
952
1229
|
);
|
|
953
|
-
session.command("chat <ref>").description("interactive REPL over the standing stream").action(run(async (ref) => chatLoop(await resolveSessionRef(ref))));
|
|
1230
|
+
session.command("chat <ref>").description("interactive REPL over the standing stream").option("--system <text>", "per-turn system text sent with the first message").action(run(async (ref, opts) => chatLoop(await resolveSessionRef(ref), opts)));
|
|
954
1231
|
session.command("interrupt <ref>").description("abort the in-flight turn").action(
|
|
955
1232
|
run(async (ref) => {
|
|
956
1233
|
const id = await resolveSessionRef(ref);
|
|
957
1234
|
const res = await api(
|
|
958
1235
|
"POST",
|
|
959
|
-
`/
|
|
1236
|
+
`/sessions/${encodeURIComponent(id)}/interrupt`
|
|
960
1237
|
);
|
|
961
1238
|
console.log(res.status === "no_turn" ? "nothing in flight" : "interrupting");
|
|
962
1239
|
})
|
|
963
1240
|
);
|
|
964
|
-
session.command("
|
|
965
|
-
|
|
1241
|
+
var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
|
|
1242
|
+
var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
|
|
1243
|
+
sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
|
|
1244
|
+
run(async (ref) => {
|
|
966
1245
|
const id = await resolveSessionRef(ref);
|
|
967
|
-
const
|
|
968
|
-
if (!
|
|
969
|
-
|
|
970
|
-
"GET",
|
|
971
|
-
`/api/v1/sessions/${encodeURIComponent(id)}/logs${qs}`
|
|
972
|
-
);
|
|
973
|
-
for (const entry of lines) console.log(`${entry.at} ${entry.line}`);
|
|
1246
|
+
const { data } = await api("GET", resourcesPath(id));
|
|
1247
|
+
if (!data.length) {
|
|
1248
|
+
console.log("No resources. `sky session resources add <session> <path>` mounts a file.");
|
|
974
1249
|
return;
|
|
975
1250
|
}
|
|
976
|
-
const
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
1251
|
+
for (const r of data) console.log(renderResource(r));
|
|
1252
|
+
})
|
|
1253
|
+
);
|
|
1254
|
+
sessionResources.command("get <ref> <rid>").description("one resource, as JSON").action(
|
|
1255
|
+
run(async (ref, rid) => {
|
|
1256
|
+
const id = await resolveSessionRef(ref);
|
|
1257
|
+
console.log(JSON.stringify(await api("GET", `${resourcesPath(id)}/${encodeURIComponent(rid)}`), null, 2));
|
|
1258
|
+
})
|
|
1259
|
+
);
|
|
1260
|
+
sessionResources.command("add <ref> <path>").description("upload a local file and mount it read-only: path[:/mount] \u2014 on disk before the next turn").action(
|
|
1261
|
+
run(async (ref, spec) => {
|
|
1262
|
+
const id = await resolveSessionRef(ref);
|
|
1263
|
+
const added = await api("POST", resourcesPath(id), await fileResource(spec));
|
|
1264
|
+
console.log(renderResource(added));
|
|
1265
|
+
})
|
|
1266
|
+
);
|
|
1267
|
+
sessionResources.command("rm <ref> <rid>").description("remove a mounted file (repositories stay for the session's lifetime)").action(
|
|
1268
|
+
run(async (ref, rid) => {
|
|
1269
|
+
const id = await resolveSessionRef(ref);
|
|
1270
|
+
const gone = await api("DELETE", `${resourcesPath(id)}/${encodeURIComponent(rid)}`);
|
|
1271
|
+
console.log(`Removed ${gone.id}`);
|
|
1272
|
+
})
|
|
1273
|
+
);
|
|
1274
|
+
sessionResources.command("rotate <ref> <rid> <token>").description("replace a repository's token; the sandbox's egress policy is updated in place").action(
|
|
1275
|
+
run(async (ref, rid, token) => {
|
|
1276
|
+
const id = await resolveSessionRef(ref);
|
|
1277
|
+
const updated = await api("POST", `${resourcesPath(id)}/${encodeURIComponent(rid)}`, {
|
|
1278
|
+
authorization_token: token
|
|
985
1279
|
});
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1280
|
+
console.log(`Rotated ${updated.id}`);
|
|
1281
|
+
})
|
|
1282
|
+
);
|
|
1283
|
+
var sessionPorts = session.command("ports").description("local servers in the session's pod, each exposed on an origin of its own");
|
|
1284
|
+
var portsPath = (id) => `/sessions/${encodeURIComponent(id)}/ports`;
|
|
1285
|
+
function renderPort(p) {
|
|
1286
|
+
return `${String(p.port).padEnd(5)} ${p.visibility.padEnd(7)} ${p.url}`;
|
|
1287
|
+
}
|
|
1288
|
+
sessionPorts.command("list <ref>").description("the session's exposed ports, oldest first").action(
|
|
1289
|
+
run(async (ref) => {
|
|
1290
|
+
const id = await resolveSessionRef(ref);
|
|
1291
|
+
const { ports } = await api("GET", portsPath(id));
|
|
1292
|
+
if (!ports.length) {
|
|
1293
|
+
console.log("No exposed ports. `sky session ports expose <session> <port>` exposes one.");
|
|
1294
|
+
return;
|
|
993
1295
|
}
|
|
1296
|
+
for (const p of ports) console.log(renderPort(p));
|
|
994
1297
|
})
|
|
995
1298
|
);
|
|
996
|
-
|
|
1299
|
+
sessionPorts.command("expose <ref> <port>").description("expose a port the agent's local server listens on; prints its address").option("--public", "anyone with the address may open it; without it the port is private (only entry links admit a browser), and an already public port is made private again").action(
|
|
1300
|
+
run(async (ref, port, opts) => {
|
|
1301
|
+
const id = await resolveSessionRef(ref);
|
|
1302
|
+
const exposed = await api("PUT", `${portsPath(id)}/${encodeURIComponent(port)}`, { visibility: opts.public ? "public" : "private" });
|
|
1303
|
+
console.log(renderPort(exposed));
|
|
1304
|
+
})
|
|
1305
|
+
);
|
|
1306
|
+
sessionPorts.command("open <ref> <port>").description("print an entry link for an exposed port \u2014 single-use and good for a minute on a private port").action(
|
|
1307
|
+
run(async (ref, port) => {
|
|
1308
|
+
const id = await resolveSessionRef(ref);
|
|
1309
|
+
const grant = await api("POST", `${portsPath(id)}/${encodeURIComponent(port)}/grants`, {});
|
|
1310
|
+
console.log(grant.url);
|
|
1311
|
+
})
|
|
1312
|
+
);
|
|
1313
|
+
sessionPorts.command("revoke <ref> <port>").description("stop exposing a port; its address answers 404 from then on").action(
|
|
1314
|
+
run(async (ref, port) => {
|
|
1315
|
+
const id = await resolveSessionRef(ref);
|
|
1316
|
+
await api("DELETE", `${portsPath(id)}/${encodeURIComponent(port)}`);
|
|
1317
|
+
console.log(`Revoked port ${port}`);
|
|
1318
|
+
})
|
|
1319
|
+
);
|
|
1320
|
+
async function uploadAttachments(opts) {
|
|
1321
|
+
const files2 = [];
|
|
1322
|
+
for (const path of [...opts.image ?? [], ...opts.file ?? []]) {
|
|
1323
|
+
const file = await uploadLocalFile(path);
|
|
1324
|
+
console.error(`uploaded ${file.filename} \u2192 ${file.id}`);
|
|
1325
|
+
files2.push(file);
|
|
1326
|
+
}
|
|
1327
|
+
return files2;
|
|
1328
|
+
}
|
|
1329
|
+
function turnOver(frame) {
|
|
1330
|
+
if (frame.event === "session.status_terminated") return true;
|
|
1331
|
+
if (frame.event !== "session.status_idle") return false;
|
|
1332
|
+
const stopReason = frame.data.stop_reason;
|
|
1333
|
+
return stopReason?.type !== "requires_action";
|
|
1334
|
+
}
|
|
1335
|
+
async function sendAndFollow(sessionId, text, opts) {
|
|
1336
|
+
const events = eventsFor(text, await uploadAttachments(opts), opts.system);
|
|
997
1337
|
const controller = new AbortController();
|
|
998
1338
|
let opened = null;
|
|
999
|
-
if (!detach) {
|
|
1339
|
+
if (!opts.detach) {
|
|
1000
1340
|
opened = (async () => {
|
|
1001
1341
|
for await (const frame of streamEvents(sessionId, controller.signal)) {
|
|
1002
1342
|
renderEvent(frame.event, frame.data);
|
|
1003
|
-
if (frame
|
|
1343
|
+
if (turnOver(frame)) {
|
|
1004
1344
|
controller.abort();
|
|
1005
1345
|
break;
|
|
1006
1346
|
}
|
|
@@ -1008,24 +1348,24 @@ async function sendAndFollow(sessionId, text, detach) {
|
|
|
1008
1348
|
})();
|
|
1009
1349
|
await new Promise((r) => setTimeout(r, 300));
|
|
1010
1350
|
}
|
|
1011
|
-
await
|
|
1012
|
-
|
|
1013
|
-
});
|
|
1014
|
-
if (detach) {
|
|
1351
|
+
await postEvents(sessionId, events);
|
|
1352
|
+
if (opts.detach) {
|
|
1015
1353
|
console.log("accepted");
|
|
1016
1354
|
return;
|
|
1017
1355
|
}
|
|
1018
1356
|
await opened;
|
|
1019
1357
|
}
|
|
1020
|
-
async function chatLoop(sessionId) {
|
|
1358
|
+
async function chatLoop(sessionId, opts = {}) {
|
|
1021
1359
|
console.log(`Chatting with ${sessionId} \u2014 Ctrl-C to leave.`);
|
|
1022
1360
|
const controller = new AbortController();
|
|
1023
1361
|
const rl = createInterface3({ input: process.stdin, output: process.stdout, prompt: "> " });
|
|
1362
|
+
let system = opts.system;
|
|
1024
1363
|
void (async () => {
|
|
1025
1364
|
try {
|
|
1026
1365
|
for await (const frame of streamEvents(sessionId, controller.signal)) {
|
|
1027
1366
|
renderEvent(frame.event, frame.data);
|
|
1028
|
-
if (frame.event === "
|
|
1367
|
+
if (frame.event === "session.status_terminated") rl.close();
|
|
1368
|
+
else if (frame.event === "session.status_idle") rl.prompt();
|
|
1029
1369
|
}
|
|
1030
1370
|
} catch (error) {
|
|
1031
1371
|
fail(error);
|
|
@@ -1038,9 +1378,9 @@ async function chatLoop(sessionId) {
|
|
|
1038
1378
|
rl.prompt();
|
|
1039
1379
|
return;
|
|
1040
1380
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1381
|
+
const events = eventsFor(text, [], system);
|
|
1382
|
+
system = void 0;
|
|
1383
|
+
void postEvents(sessionId, events).catch((error) => {
|
|
1044
1384
|
console.error(`sky: ${error instanceof Error ? error.message : String(error)}`);
|
|
1045
1385
|
rl.prompt();
|
|
1046
1386
|
});
|
|
@@ -1053,90 +1393,99 @@ async function chatLoop(sessionId) {
|
|
|
1053
1393
|
});
|
|
1054
1394
|
}
|
|
1055
1395
|
var secrets = program.command("secrets").description("agent secrets (values write-only)");
|
|
1056
|
-
secrets.command("set <
|
|
1057
|
-
run(async (
|
|
1396
|
+
secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the agent").action(
|
|
1397
|
+
run(async (id, pairs) => {
|
|
1058
1398
|
for (const pair of pairs) {
|
|
1059
1399
|
const eq = pair.indexOf("=");
|
|
1060
1400
|
if (eq < 1) throw new Error(`expected KEY=VALUE, got ${pair}`);
|
|
1061
1401
|
const key = pair.slice(0, eq);
|
|
1062
|
-
await api("PUT", `/
|
|
1402
|
+
await api("PUT", `/agents/${encodeURIComponent(id)}/secrets/${encodeURIComponent(key)}`, {
|
|
1063
1403
|
value: pair.slice(eq + 1)
|
|
1064
1404
|
});
|
|
1065
1405
|
console.log(`set ${key}`);
|
|
1066
1406
|
}
|
|
1067
1407
|
})
|
|
1068
1408
|
);
|
|
1069
|
-
secrets.command("list <
|
|
1070
|
-
run(async (
|
|
1071
|
-
const { secrets: keys } = await api("GET", `/
|
|
1409
|
+
secrets.command("list <id>").action(
|
|
1410
|
+
run(async (id) => {
|
|
1411
|
+
const { secrets: keys } = await api("GET", `/agents/${encodeURIComponent(id)}/secrets`);
|
|
1072
1412
|
if (!keys.length) console.log("no secrets set");
|
|
1073
1413
|
for (const s of keys) {
|
|
1074
1414
|
console.log(`\u25CF ${s.key}${s.description ? ` \u2014 ${s.description}` : ""}`);
|
|
1075
1415
|
}
|
|
1076
1416
|
})
|
|
1077
1417
|
);
|
|
1078
|
-
secrets.command("unset <
|
|
1079
|
-
run(async (
|
|
1418
|
+
secrets.command("unset <id> <key>").action(
|
|
1419
|
+
run(async (id, key) => {
|
|
1080
1420
|
await api(
|
|
1081
1421
|
"DELETE",
|
|
1082
|
-
`/
|
|
1422
|
+
`/agents/${encodeURIComponent(id)}/secrets/${encodeURIComponent(key)}`
|
|
1083
1423
|
);
|
|
1084
1424
|
console.log(`unset ${key}`);
|
|
1085
1425
|
})
|
|
1086
1426
|
);
|
|
1087
|
-
program.command("launch").description("scaffold ./agent.toml if absent, create the agent, print its address").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
1427
|
+
program.command("launch").description("scaffold ./agent.toml if absent, create the agent, print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").action(
|
|
1088
1428
|
run(async (opts) => {
|
|
1089
1429
|
const path = resolve3(opts.file);
|
|
1090
1430
|
if (!existsSync4(path)) {
|
|
1091
|
-
const name =
|
|
1431
|
+
const name = basename3(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
1092
1432
|
scaffoldAgentFile(path, name);
|
|
1093
1433
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
1094
1434
|
}
|
|
1095
1435
|
const { file, spec } = readAgentFile(path);
|
|
1096
|
-
if (file.
|
|
1097
|
-
console.log(`${file.
|
|
1436
|
+
if (file.id) {
|
|
1437
|
+
console.log(`${file.id} already launched \u2014 use \`sky deploy\` to re-apply.`);
|
|
1098
1438
|
return;
|
|
1099
1439
|
}
|
|
1100
1440
|
const { vcpus, memoryMb, ...specBody } = spec;
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1441
|
+
const body = {
|
|
1442
|
+
...specBody,
|
|
1443
|
+
...opts.type ? { agentType: opts.type } : {},
|
|
1444
|
+
...opts.llm ? { llm: opts.llm } : {}
|
|
1445
|
+
};
|
|
1446
|
+
const { agent: created } = await api("POST", "/agents", body);
|
|
1447
|
+
console.log("Starting its runtime\u2026");
|
|
1448
|
+
const { session: session2 } = await api("POST", "/sessions", {
|
|
1449
|
+
agent: created.id,
|
|
1104
1450
|
...vcpus !== void 0 ? { vcpus } : {},
|
|
1105
1451
|
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
1106
1452
|
});
|
|
1107
|
-
|
|
1108
|
-
console.log(`Launched ${created.
|
|
1109
|
-
|
|
1110
|
-
console.log(` sky agent
|
|
1453
|
+
stampId(path, created.id);
|
|
1454
|
+
console.log(`Launched ${created.id} (${created.agentType} \xB7 ${created.llm})`);
|
|
1455
|
+
printModelBilling(session2);
|
|
1456
|
+
console.log(` sky agent message ${created.id} "hello"`);
|
|
1457
|
+
console.log(` sky agent chat ${created.id}`);
|
|
1111
1458
|
})
|
|
1112
1459
|
);
|
|
1113
1460
|
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
1114
1461
|
run(async (opts) => {
|
|
1115
1462
|
const path = resolve3(opts.file);
|
|
1116
1463
|
const { file, spec } = readAgentFile(path);
|
|
1117
|
-
if (!file.
|
|
1118
|
-
console.log("No
|
|
1464
|
+
if (!file.id) {
|
|
1465
|
+
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
1119
1466
|
const { vcpus, memoryMb, ...specBody } = spec;
|
|
1120
|
-
const { agent: created } = await api("POST", "/
|
|
1121
|
-
await api("POST", "/
|
|
1122
|
-
agent: created.
|
|
1467
|
+
const { agent: created } = await api("POST", "/agents", specBody);
|
|
1468
|
+
await api("POST", "/sessions", {
|
|
1469
|
+
agent: created.id,
|
|
1123
1470
|
...vcpus !== void 0 ? { vcpus } : {},
|
|
1124
1471
|
...memoryMb !== void 0 ? { memoryMb } : {}
|
|
1125
1472
|
});
|
|
1126
|
-
|
|
1127
|
-
console.log(`Launched ${created.
|
|
1473
|
+
stampId(path, created.id);
|
|
1474
|
+
console.log(`Launched ${created.id}`);
|
|
1128
1475
|
return;
|
|
1129
1476
|
}
|
|
1130
1477
|
if (spec.prompt !== void 0) {
|
|
1131
|
-
await api("PUT", `/
|
|
1478
|
+
await api("PUT", `/agents/${encodeURIComponent(file.id)}/prompt`, {
|
|
1132
1479
|
prompt: spec.prompt
|
|
1133
1480
|
});
|
|
1134
1481
|
}
|
|
1135
|
-
await api("PATCH", `/
|
|
1482
|
+
await api("PATCH", `/agents/${encodeURIComponent(file.id)}`, {
|
|
1136
1483
|
displayName: spec.displayName,
|
|
1137
|
-
...spec.capabilities ? { capabilities: spec.capabilities } : {}
|
|
1484
|
+
...spec.capabilities ? { capabilities: spec.capabilities } : {},
|
|
1485
|
+
...spec.reasoningEffort ? { reasoningEffort: spec.reasoningEffort } : {},
|
|
1486
|
+
...spec.skills ? { skills: spec.skills } : {}
|
|
1138
1487
|
});
|
|
1139
|
-
console.log(`Deployed ${file.
|
|
1488
|
+
console.log(`Deployed ${file.id} (prompt + settings re-applied)`);
|
|
1140
1489
|
})
|
|
1141
1490
|
);
|
|
1142
1491
|
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(
|
|
@@ -1144,6 +1493,184 @@ program.command("clone").description("clone your local agent (Claude Code, \u202
|
|
|
1144
1493
|
async (opts) => clone(opts)
|
|
1145
1494
|
)
|
|
1146
1495
|
);
|
|
1496
|
+
var files = program.command("files").description("files: upload once, reference by id from any message");
|
|
1497
|
+
files.command("upload <path>").description("upload a local file; prints the file id to reference from a message").option("--expires <seconds>", "expire the upload after this many seconds (3600\u20137776000)").action(
|
|
1498
|
+
run(async (path, opts) => {
|
|
1499
|
+
const file = await uploadLocalFile(path, opts.expires ? Number(opts.expires) : void 0);
|
|
1500
|
+
console.log(`${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`);
|
|
1501
|
+
})
|
|
1502
|
+
);
|
|
1503
|
+
files.command("list").description("list your uploads, or one session's files with --session").option("--session <ref>", "list the files of this session (its message copies and outputs)").option("--limit <n>", "page size (default 20)").option("--page <cursor>", "continue from a previous page").action(
|
|
1504
|
+
run(async (opts) => {
|
|
1505
|
+
const params = new URLSearchParams();
|
|
1506
|
+
if (opts.session) params.set("scope_id", await resolveSessionRef(opts.session));
|
|
1507
|
+
if (opts.limit) params.set("limit", opts.limit);
|
|
1508
|
+
if (opts.page) params.set("page", opts.page);
|
|
1509
|
+
const query = params.toString();
|
|
1510
|
+
const page = await api("GET", `/files${query ? `?${query}` : ""}`);
|
|
1511
|
+
for (const file of page.data) {
|
|
1512
|
+
console.log(`${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes${file.downloadable ? "" : " (upload)"}`);
|
|
1513
|
+
}
|
|
1514
|
+
if (page.next_page) console.log(`\u2026 more: --page ${page.next_page}`);
|
|
1515
|
+
})
|
|
1516
|
+
);
|
|
1517
|
+
files.command("get <id>").description("show one file's metadata").action(
|
|
1518
|
+
run(
|
|
1519
|
+
async (id) => console.log(JSON.stringify(await api("GET", `/files/${encodeURIComponent(id)}`), null, 2))
|
|
1520
|
+
)
|
|
1521
|
+
);
|
|
1522
|
+
files.command("rm <id>").description("delete a file (a session that already received it keeps its copy)").action(
|
|
1523
|
+
run(async (id) => {
|
|
1524
|
+
await api("DELETE", `/files/${encodeURIComponent(id)}`);
|
|
1525
|
+
console.log(`Deleted ${id}`);
|
|
1526
|
+
})
|
|
1527
|
+
);
|
|
1528
|
+
files.command("download <id>").description("download a session file's bytes").requiredOption("-o, --out <path>", "where to write the bytes").action(
|
|
1529
|
+
run(async (id, opts) => {
|
|
1530
|
+
const { bytes } = await downloadFile(id);
|
|
1531
|
+
writeFileSync4(opts.out, bytes);
|
|
1532
|
+
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
1533
|
+
})
|
|
1534
|
+
);
|
|
1535
|
+
function routineLine(r) {
|
|
1536
|
+
const target = r.target.type === "session" ? `\u2192 ${r.target.session_id}` : `+ ${r.target.agent}`;
|
|
1537
|
+
const cron = r.schedule ? `${r.schedule.expression} ${r.schedule.timezone}` : "manual-only";
|
|
1538
|
+
const state = r.archived_at ? "archived" : r.status === "paused" ? `paused (${r.paused_reason?.error?.type ?? r.paused_reason?.type ?? "manual"})` : "active";
|
|
1539
|
+
return `${r.id.padEnd(30)} ${r.name.padEnd(24)} ${cron.padEnd(26)} ${target.padEnd(30)} ${state}`;
|
|
1540
|
+
}
|
|
1541
|
+
var routine = program.command("routine").description("run agents on a schedule: cron-fired sessions or turns into a long-lived session");
|
|
1542
|
+
routine.command("create <name>").description("create a routine; --agent starts a fresh session per fire, --session fires into one session").option("--agent <id>", "target: create a new session from this agent each fire").option("--session <id>", "target: send each fire into this existing session").option("--message <text>", "the user message delivered on every fire (required)").option("--cron <expression>", '5-field POSIX cron, e.g. "0 9 * * 1-5" (omit for manual-only)').option("--timezone <tz>", "IANA timezone for --cron (default UTC)").option("--budget-cents <n>", "per-run spend cap in whole US cents").action(
|
|
1543
|
+
run(
|
|
1544
|
+
async (name, opts) => {
|
|
1545
|
+
if (!opts.message) throw new Error("--message is required");
|
|
1546
|
+
if (!opts.agent === !opts.session) throw new Error("pass exactly one of --agent or --session");
|
|
1547
|
+
const { routine: created } = await api("POST", "/routines", {
|
|
1548
|
+
name,
|
|
1549
|
+
target: opts.agent ? { type: "new_session", agent: opts.agent } : { type: "session", session_id: opts.session },
|
|
1550
|
+
initial_events: [
|
|
1551
|
+
{ type: "user.message", content: [{ type: "text", text: opts.message }] }
|
|
1552
|
+
],
|
|
1553
|
+
...opts.cron ? { schedule: { type: "cron", expression: opts.cron, timezone: opts.timezone ?? "UTC" } } : {},
|
|
1554
|
+
...opts.budgetCents ? { budget: { type: "limit", max_list_cost: { amount: opts.budgetCents, currency: "USD" } } } : {}
|
|
1555
|
+
});
|
|
1556
|
+
console.log(`Created ${created.id}`);
|
|
1557
|
+
if (created.schedule) {
|
|
1558
|
+
console.log(` next fires: ${created.schedule.upcoming_runs_at.slice(0, 3).join(", ")}`);
|
|
1559
|
+
} else {
|
|
1560
|
+
console.log(` manual-only \u2014 fire with: sky routine run ${created.id}`);
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
)
|
|
1564
|
+
);
|
|
1565
|
+
routine.command("list").action(
|
|
1566
|
+
run(async () => {
|
|
1567
|
+
const { items } = await api("GET", "/routines");
|
|
1568
|
+
if (!items.length) {
|
|
1569
|
+
console.log("No routines. `sky routine create <name> --agent <id> --message <text> --cron <expr>`");
|
|
1570
|
+
return;
|
|
1571
|
+
}
|
|
1572
|
+
for (const r of items) console.log(routineLine(r));
|
|
1573
|
+
})
|
|
1574
|
+
);
|
|
1575
|
+
routine.command("show <id>").action(
|
|
1576
|
+
run(async (id) => {
|
|
1577
|
+
const { routine: r } = await api(
|
|
1578
|
+
"GET",
|
|
1579
|
+
`/routines/${encodeURIComponent(id)}`
|
|
1580
|
+
);
|
|
1581
|
+
console.log(routineLine(r));
|
|
1582
|
+
const { items } = await api(
|
|
1583
|
+
"GET",
|
|
1584
|
+
`/routine-runs?routine=${encodeURIComponent(id)}&limit=5`
|
|
1585
|
+
);
|
|
1586
|
+
for (const runRow of items) {
|
|
1587
|
+
const outcome = runRow.error ? `FAILED ${runRow.error.type}` : runRow.session_id ?? "ok";
|
|
1588
|
+
console.log(` ${runRow.created_at} ${runRow.trigger_context.type.padEnd(8)} ${outcome}`);
|
|
1589
|
+
}
|
|
1590
|
+
})
|
|
1591
|
+
);
|
|
1592
|
+
routine.command("run <id>").description("fire now, outside the schedule (works while paused)").action(
|
|
1593
|
+
run(async (id) => {
|
|
1594
|
+
const { run: fired } = await api(
|
|
1595
|
+
"POST",
|
|
1596
|
+
`/routines/${encodeURIComponent(id)}/run`
|
|
1597
|
+
);
|
|
1598
|
+
if (fired.error) console.log(`Run ${fired.id} failed: ${fired.error.type} \u2014 ${fired.error.message}`);
|
|
1599
|
+
else console.log(`Run ${fired.id} \u2192 ${fired.session_id}`);
|
|
1600
|
+
})
|
|
1601
|
+
);
|
|
1602
|
+
for (const [verb, blurb] of [
|
|
1603
|
+
["pause", "suppress scheduled fires (manual runs still work)"],
|
|
1604
|
+
["unpause", "resume from the next occurrence \u2014 missed fires are not backfilled"],
|
|
1605
|
+
["archive", "terminal: the schedule stops and the routine becomes immutable"]
|
|
1606
|
+
]) {
|
|
1607
|
+
routine.command(`${verb} <id>`).description(blurb).action(
|
|
1608
|
+
run(async (id) => {
|
|
1609
|
+
const { routine: r } = await api(
|
|
1610
|
+
"POST",
|
|
1611
|
+
`/routines/${encodeURIComponent(id)}/${verb}`
|
|
1612
|
+
);
|
|
1613
|
+
console.log(`${r.id}: ${r.archived_at ? "archived" : r.status}`);
|
|
1614
|
+
})
|
|
1615
|
+
);
|
|
1616
|
+
}
|
|
1617
|
+
var collect = (value, previous = []) => [...previous, value];
|
|
1618
|
+
function goalPath(sessionId, suffix = "") {
|
|
1619
|
+
return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
|
|
1620
|
+
}
|
|
1621
|
+
function withGoalWriteOptions(command) {
|
|
1622
|
+
return command.option("--criterion <text>", "a requirement the grader checks (repeatable)", collect).option("--check <command>", 'a shell command that must exit 0, optionally "<text> :: <command>" (repeatable)', collect).option("--max-iterations <n>", "self-started turns before the goal stops (default 20)").option("--max-spend-cents <n>", 'spend ceiling in whole US cents, or "none"').option("--max-tokens <n>", 'token ceiling (uncached input + output), or "none"').option("--max-minutes <n>", `wall-clock ceiling from the goal's start, or "none"`).option("--idempotency-key <key>", "make a retried request safe to repeat");
|
|
1623
|
+
}
|
|
1624
|
+
var goal = program.command("goal").description("give a session a standing objective it keeps working toward until the criteria are met");
|
|
1625
|
+
goal.command("get <session>").description("show the session's goal, its limits, and what the last check found").option("--json", "print the raw goal object").action(
|
|
1626
|
+
run(async (ref, opts) => {
|
|
1627
|
+
const { goal: current } = await api("GET", goalPath(await resolveSessionRef(ref)));
|
|
1628
|
+
console.log(opts.json ? JSON.stringify(current, null, 2) : renderGoal(current));
|
|
1629
|
+
})
|
|
1630
|
+
);
|
|
1631
|
+
withGoalWriteOptions(
|
|
1632
|
+
goal.command("set <session> <objective...>").description("set the goal, replacing any existing one; work starts right away")
|
|
1633
|
+
).action(
|
|
1634
|
+
run(async (ref, words, opts) => {
|
|
1635
|
+
const { goal: created } = await api(
|
|
1636
|
+
"POST",
|
|
1637
|
+
goalPath(await resolveSessionRef(ref)),
|
|
1638
|
+
setGoalBody(words.join(" "), opts),
|
|
1639
|
+
{ idempotencyKey: opts.idempotencyKey }
|
|
1640
|
+
);
|
|
1641
|
+
console.log(renderGoal(created));
|
|
1642
|
+
})
|
|
1643
|
+
);
|
|
1644
|
+
withGoalWriteOptions(
|
|
1645
|
+
goal.command("edit <session>").description("change the goal in place; iterations and spend so far carry over").option("--objective <text>", "the new objective")
|
|
1646
|
+
).action(
|
|
1647
|
+
run(async (ref, opts) => {
|
|
1648
|
+
const { goal: edited } = await api(
|
|
1649
|
+
"PATCH",
|
|
1650
|
+
goalPath(await resolveSessionRef(ref)),
|
|
1651
|
+
editGoalBody(opts),
|
|
1652
|
+
{ idempotencyKey: opts.idempotencyKey }
|
|
1653
|
+
);
|
|
1654
|
+
console.log(renderGoal(edited));
|
|
1655
|
+
})
|
|
1656
|
+
);
|
|
1657
|
+
for (const [verb, method, suffix, blurb] of [
|
|
1658
|
+
["pause", "POST", "/pause", "stop taking turns and interrupt the run the goal started"],
|
|
1659
|
+
["resume", "POST", "/resume", "start taking turns again; a deadline moves out by the paused time"],
|
|
1660
|
+
["clear", "DELETE", "", "stop the goal for good and interrupt the run it started"]
|
|
1661
|
+
]) {
|
|
1662
|
+
goal.command(`${verb} <session>`).description(blurb).option("--idempotency-key <key>", "make a retried request safe to repeat").action(
|
|
1663
|
+
run(async (ref, opts) => {
|
|
1664
|
+
const { goal: changed } = await api(
|
|
1665
|
+
method,
|
|
1666
|
+
goalPath(await resolveSessionRef(ref), suffix),
|
|
1667
|
+
void 0,
|
|
1668
|
+
{ idempotencyKey: opts.idempotencyKey }
|
|
1669
|
+
);
|
|
1670
|
+
console.log(`${changed.id}: ${changed.status}${changed.stop_reason ? ` (${changed.stop_reason})` : ""}`);
|
|
1671
|
+
})
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1147
1674
|
program.command("config").description("show the CLI config location and current values").action(() => {
|
|
1148
1675
|
const config = loadConfig();
|
|
1149
1676
|
console.log(`config: ${configPath()}`);
|