@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/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
- customInstallSchema,
7
+ defaultLlm,
15
8
  external_exports,
16
- mcpConfigInstall,
9
+ isModelEffortSupported,
10
+ mcpServersFromKept,
11
+ modelSubscriptionProviderFor,
12
+ reasoningEffortDocLine,
17
13
  skillRefSchema,
18
- splitMcpServers
19
- } from "./chunk-L5LLCKZW.js";
20
- import "./chunk-MLKGABMK.js";
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 basename2, resolve as resolve3 } from "path";
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 = "0.2.1";
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
- `/api/v1/agents/${encodeURIComponent(ref)}`
152
+ `/agents/${encodeURIComponent(ref)}`
83
153
  ).catch(async () => {
84
- await api("GET", `/api/v1/sessions/${encodeURIComponent(ref)}`);
85
- return { agent: { slug: ref, defaultSessionId: ref } };
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.slug} has no sessions \u2014 create one: sky session create ${agent2.slug}`
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-Y2XOFL3Z.js");
167
+ const { parseSseStream } = await import("./src-EGXYUSME.js");
98
168
  const res = await fetch(
99
- `${apiBase()}/api/v1/sessions/${encodeURIComponent(sessionId)}/stream`,
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
- slug: external_exports.string().optional(),
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 = readFileSync(path, "utf8");
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
- const base = dirname(resolve(path));
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 readFileSync(resolve(base, ref), "utf8");
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 stampSlug(path, slug) {
183
- const doc = parseToml(readFileSync(path, "utf8"));
184
- doc.slug = slug;
185
- writeFileSync(path, stringifyToml(doc));
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
- writeFileSync(
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-v4-flash" # must be compatible with type
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 readFileSync2, writeFileSync as writeFileSync2 } from "fs";
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 ?? join(homedir(), ".claude");
411
+ return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir2(), ".claude");
214
412
  }
215
413
  function readIfExists(path) {
216
414
  try {
217
- return readFileSync2(path, "utf8");
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
- join(process.cwd(), ".claude", "settings.local.json"),
234
- join(process.cwd(), ".claude", "settings.json"),
235
- join(configRoot, "settings.json")
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(join(homedir(), ".claude.json"));
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
- join(process.cwd(), ".mcp.json")
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(join(configRoot, "CLAUDE.md"));
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(join(process.cwd(), name));
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(join(homedir(), ".codex"))) return "codex";
301
- if (existsSync2(join(homedir(), ".openclaw"))) return "openclaw";
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 join(configRoot, "agentsky-link.json");
511
+ return join2(configRoot, "agentsky-link.json");
314
512
  }
315
513
  function readLink(configRoot) {
316
514
  const link = readJson(linkPath(configRoot));
317
- return link?.agentSlug ? link : void 0;
515
+ return link?.agentId ? link : void 0;
318
516
  }
319
517
  function writeLink(configRoot, link) {
320
- writeFileSync2(linkPath(configRoot), `${JSON.stringify(link, null, 2)}
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 ${basename(process.cwd())})`);
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 installs = keptNames.length ? [mcpConfigInstall(local.keptServers)] : [];
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
- ...installs.length ? { customInstalls: installs } : {}
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", "/api/v1/agents", body));
576
+ ({ agent: created } = await api("POST", "/agents", body));
376
577
  } catch (error) {
377
578
  if (error instanceof ApiError && error.status === 422 && local.model) {
378
- console.log(` note: ${local.model} isn't available for ${local.harness} \u2014 using the harness default`);
379
- sentLlm = null;
380
- ({ agent: created } = await api("POST", "/api/v1/agents", {
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
- await api("POST", "/api/v1/sessions", { agent: created.slug });
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
- agentSlug: created.slug,
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.slug}`;
399
- console.log(`Cloned \u2192 ${created.slug} ${url}`);
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.agentSlug} from ${local.configRoot} (last synced ${since ? new Date(since).toLocaleDateString() : "never"})`
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.agentSlug}. Dry run \u2014 nothing sent.`
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
- `/api/v1/agents/${encodeURIComponent(link.agentSlug)}`
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.agentSlug} no longer exists \u2014 run \`sky clone --new\` to launch a fresh one`
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.agentSlug} is ${agent2.agentType}, this machine runs ${local.harness} \u2014 run \`sky clone --new\` to launch a fresh agent`
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
- `/api/v1/agents/${encodeURIComponent(link.agentSlug)}/prompt`,
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.agentSlug}.`);
466
- if (opts.open) openBrowser(`${apiBase()}/agents/${link.agentSlug}`);
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 = `${apiBase()}/cli/authorize?state=${state}&port=${port}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
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
- "/api/v1/cli/token-exchange",
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 = `${apiBase()}/cli/authorize?request=${request}&challenge=${encodeURIComponent(challenge)}&scopes=read,write`;
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()}/api/v1/cli/poll?request=${request}&verifier=${encodeURIComponent(verifier)}&clientName=${encodeURIComponent(`cli ${hostname()}`)}`,
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.message": {
602
- const channel = String(data.channel ?? "");
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
- case "turn.interrupted":
609
- console.log(dim("\u23F9 turn interrupted"));
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
- case "error":
612
- console.error(red(`error: ${String(data.message ?? data.code ?? "unknown")}`));
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 readFileSync3 } from "fs";
622
- import { homedir as homedir2 } from "os";
623
- import { join as join2 } from "path";
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
- `/api/v1/model-subscriptions/${provider}`,
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 || join2(homedir2(), ".codex");
702
- const path = join2(codexHome, "auth.json");
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 = readFileSync3(path, "utf8");
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
- "/api/v1/model-subscriptions"
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.slug ?? a.id} \u2014 ${a.name}`);
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
- `/api/v1/model-subscriptions/${provider}`
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.slug ?? a.id).join(", ")
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 Settings \u2192 API tokens)").action(
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: Settings \u2192 API tokens.)`);
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", "/api/v1/whoami");
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", "/api/v1/universes");
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", "/api/v1/universes", { slug });
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", `/api/v1/universes/${encodeURIComponent(slug)}`);
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 ? `/api/v1/universes/${encodeURIComponent(slug)}` : "/api/v1/whoami";
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("--name <name>", "agent name").option("-f, --file <path>", "create from an agent.toml").action(
843
- run(async (opts) => {
844
- const spec = opts.file ? readAgentFile(opts.file).spec : {
845
- name: opts.name ?? "My agent",
846
- agentType: opts.type,
847
- llm: opts.llm,
848
- displayName: opts.name
849
- };
850
- const { agent: created } = await api("POST", "/api/v1/agents", spec);
851
- console.log(`Created ${created.slug} (${created.agentType} \xB7 ${created.llm}) \u2014 spec only.`);
852
- console.log(`Start it: sky session create ${created.slug}`);
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", "/api/v1/agents");
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.slug.padEnd(24)} ${a.displayName.padEnd(20)} ${a.agentType}/${a.llm} ${state}`
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 <slug>").action(
871
- run(async (slug) => {
872
- const { agent: a } = await api("GET", `/api/v1/agents/${encodeURIComponent(slug)}`);
873
- console.log(`${a.slug} \u2014 ${a.displayName}${a.archived ? " (archived)" : ""}`);
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 <slug>").description("retire the spec \u2014 read-only, running sessions unaffected (admin scope)").action(
883
- run(async (slug) => {
884
- await api("POST", `/api/v1/agents/${encodeURIComponent(slug)}/archive`);
885
- console.log(`Archived ${slug} \u2014 sessions keep running; new sessions are rejected.`);
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 <slug>").description("delete the spec; requires all sessions destroyed first (admin scope)").action(
889
- run(async (slug) => {
890
- await api("DELETE", `/api/v1/agents/${encodeURIComponent(slug)}`);
891
- console.log(`Destroyed ${slug}`);
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 <slug> <text>").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit after the 202").action(
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 (slug, text, opts) => sendAndFollow(await resolveSessionRef(slug), text, Boolean(opts.detach))
1123
+ async (id, text, opts) => sendAndFollow(await resolveSessionRef(id), text ?? "", opts)
897
1124
  )
898
1125
  );
899
- agent.command("chat <slug>").description("interactive REPL with the default session").action(run(async (slug) => chatLoop(await resolveSessionRef(slug))));
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
- session.command("create <agent>").description("create a session (cheap \u2014 the pod starts on the first message)").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)").action(
902
- run(async (agentRef, opts) => {
903
- const { session: created } = await api("POST", "/api/v1/sessions", {
904
- agent: agentRef,
905
- ...opts.title ? { title: opts.title } : {},
906
- ...opts.vcpus !== void 0 ? { vcpus: Number(opts.vcpus) } : {},
907
- ...opts.memory !== void 0 ? { memoryMb: Number(opts.memory) } : {},
908
- ...opts.message ? { initial_events: [{ type: "user.message", parts: [{ type: "text", index: 0, text: opts.message }] }] } : {}
909
- });
910
- console.log(`Created ${created.id} (${created.agent} \xB7 ${created.status})${created.default ? " [default]" : ""}`);
911
- console.log(` sky agent message ${created.agent} "hello"`);
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", `/api/v1/sessions${qs}`);
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
- `/api/v1/sessions/${encodeURIComponent(id)}`
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
- session.command("destroy <id>").description("end the session and tear down its pod (admin scope)").action(
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", `/api/v1/sessions/${encodeURIComponent(id)}`);
945
- console.log(`Destroyed ${id}`);
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> <text>").description("send one message (session id, or agent slug = its default session)").option("--detach", "don't follow the stream; exit after the 202").action(
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, Boolean(opts.detach))
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
- `/api/v1/sessions/${encodeURIComponent(id)}/interrupt`
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("logs <ref>").option("-f, --follow", "follow (SSE)").option("--since <duration>", "how far back, e.g. 1h, 30m", "1h").action(
965
- run(async (ref, opts) => {
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 qs = `?since=${encodeURIComponent(opts.since)}${opts.follow ? "&follow=true" : ""}`;
968
- if (!opts.follow) {
969
- const { lines } = await api(
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 { parseSseStream } = await import("./src-Y2XOFL3Z.js");
977
- const { apiBase: apiBase2 } = await import("./config-HRDRNTUU.js");
978
- const token = apiToken();
979
- const res = await fetch(`${apiBase2()}/api/v1/sessions/${encodeURIComponent(id)}/logs${qs}`, {
980
- headers: {
981
- "user-agent": USER_AGENT,
982
- authorization: `Bearer ${token}`,
983
- accept: "text/event-stream"
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
- if (!res.ok || !res.body) throw new ApiError(res.status, "logs_failed", await res.text());
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
- }
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
- async function sendAndFollow(sessionId, text, detach) {
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.event === "turn.status_idle" || frame.event === "turn.interrupted") {
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 api("POST", `/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`, {
1012
- parts: [{ type: "text", index: 0, text }]
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 === "turn.status_idle") rl.prompt();
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
- void api("POST", `/api/v1/sessions/${encodeURIComponent(sessionId)}/messages`, {
1042
- parts: [{ type: "text", index: 0, text }]
1043
- }).catch((error) => {
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 <slug> <pairs...>").description("set KEY=VALUE pairs on the agent").action(
1057
- run(async (slug, pairs) => {
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", `/api/v1/agents/${encodeURIComponent(slug)}/secrets/${encodeURIComponent(key)}`, {
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 <slug>").action(
1070
- run(async (slug) => {
1071
- const { secrets: keys } = await api("GET", `/api/v1/agents/${encodeURIComponent(slug)}/secrets`);
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 <slug> <key>").action(
1079
- run(async (slug, key) => {
1418
+ secrets.command("unset <id> <key>").action(
1419
+ run(async (id, key) => {
1080
1420
  await api(
1081
1421
  "DELETE",
1082
- `/api/v1/agents/${encodeURIComponent(slug)}/secrets/${encodeURIComponent(key)}`
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 = basename2(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
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.slug) {
1097
- console.log(`${file.slug} already launched \u2014 use \`sky deploy\` to re-apply.`);
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 { agent: created } = await api("POST", "/api/v1/agents", specBody);
1102
- await api("POST", "/api/v1/sessions", {
1103
- agent: created.slug,
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
- stampSlug(path, created.slug);
1108
- console.log(`Launched ${created.slug} (${created.agentType} \xB7 ${created.llm})`);
1109
- console.log(` sky agent message ${created.slug} "hello"`);
1110
- console.log(` sky agent chat ${created.slug}`);
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.slug) {
1118
- console.log("No slug in the file yet \u2014 running launch instead.");
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", "/api/v1/agents", specBody);
1121
- await api("POST", "/api/v1/sessions", {
1122
- agent: created.slug,
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
- stampSlug(path, created.slug);
1127
- console.log(`Launched ${created.slug}`);
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", `/api/v1/agents/${encodeURIComponent(file.slug)}/prompt`, {
1478
+ await api("PUT", `/agents/${encodeURIComponent(file.id)}/prompt`, {
1132
1479
  prompt: spec.prompt
1133
1480
  });
1134
1481
  }
1135
- await api("PATCH", `/api/v1/agents/${encodeURIComponent(file.slug)}`, {
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.slug} (prompt + settings re-applied)`);
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()}`);