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