@agentsky/sky 2.4.0 → 2.5.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.
@@ -18984,7 +18984,8 @@ var MODEL_REASONING_EFFORTS = {
18984
18984
  "gemini-3.5-flash": ["low", "medium", "high"],
18985
18985
  "glm-5.3": ["low", "high", "max"],
18986
18986
  "glm-5.3-flash": ["low", "high", "max"],
18987
- "grok-4.6": ["low", "medium", "high"]
18987
+ "grok-4.6": ["low", "medium", "high"],
18988
+ "grok-4.7": ["low", "medium", "high", "xhigh"]
18988
18989
  };
18989
18990
  var REASONING_EFFORTS = {
18990
18991
  agoragentic_acp: null,
@@ -21200,6 +21201,10 @@ var questionSchema = external_exports.object({
21200
21201
  blocksGoal: external_exports.boolean().optional(),
21201
21202
  /** Informational deadline. Passing it never approves or answers the question. */
21202
21203
  dueAt: external_exports.string().datetime({ offset: true }).optional(),
21204
+ /** Hard permission deadline; unlike dueAt, later answers are refused. */
21205
+ expiresAt: external_exports.string().datetime({ offset: true }).optional(),
21206
+ /** The owner withdrew this question. Never a human answer or consent. */
21207
+ cancelledAt: external_exports.string().datetime({ offset: true }).optional(),
21203
21208
  /** What the platform is asking for when the question is one of its own
21204
21209
  * requests rather than an agent's free question. Absent on an ordinary question. */
21205
21210
  kind: external_exports.enum(QUESTION_REQUEST_KINDS).optional(),
@@ -22151,11 +22156,38 @@ function isCompatible(agentType, llm) {
22151
22156
  function defaultLlm(agentType) {
22152
22157
  return allowedLlms[agentType][0];
22153
22158
  }
22159
+ var CHATGPT_PLAN_MODELS = /* @__PURE__ */ new Set([
22160
+ "gpt-6-astra",
22161
+ "gpt-5.6-sol",
22162
+ "gpt-5.6-terra",
22163
+ "gpt-5.6-luna",
22164
+ "gpt-5.5"
22165
+ ]);
22166
+ var CLAUDE_PLAN_MODELS = /* @__PURE__ */ new Set([
22167
+ "claude-opus-5",
22168
+ "claude-fable-5-1",
22169
+ "claude-sonnet-5",
22170
+ "claude-haiku-4-5",
22171
+ "claude-sonnet-4-6",
22172
+ "claude-opus-4-8",
22173
+ "claude-fable-5"
22174
+ ]);
22154
22175
  function modelSubscriptionProviderFor(agentType, llm) {
22155
- if (agentType === "claude_code" && llm.startsWith("claude-")) return "anthropic";
22156
- if (agentType === "codex" && llm.startsWith("gpt-")) return "openai";
22176
+ if (agentType === "claude_code" && CLAUDE_PLAN_MODELS.has(llm)) return "anthropic";
22177
+ if (agentType === "codex" && CHATGPT_PLAN_MODELS.has(llm)) return "openai";
22178
+ if (agentType === "cursor" && llm === "harness-default") return "cursor";
22157
22179
  return null;
22158
22180
  }
22181
+ var MODEL_SUBSCRIPTION_PROVIDER_NAME = {
22182
+ anthropic: "Claude",
22183
+ openai: "ChatGPT",
22184
+ cursor: "Cursor"
22185
+ };
22186
+ var MODEL_SUBSCRIPTION_PLAN_PHRASE = {
22187
+ anthropic: "Claude subscription",
22188
+ openai: "ChatGPT plan",
22189
+ cursor: "Cursor plan"
22190
+ };
22159
22191
  var AGENT_TYPE_META = {
22160
22192
  ...Object.fromEntries(ACP_AGENT_TYPES.map((agentType) => [agentType, {
22161
22193
  label: ACP_AGENTS[agentType].label,
@@ -22250,8 +22282,7 @@ var skillRefSchema = external_exports.discriminatedUnion("type", [
22250
22282
  storeSkillRefSchema,
22251
22283
  githubSkillRefSchema
22252
22284
  ]);
22253
- var MAX_SKILLS_PER_AGENT = 20;
22254
- var skillsArraySchema = external_exports.array(skillRefSchema).max(MAX_SKILLS_PER_AGENT, `At most ${MAX_SKILLS_PER_AGENT} skills per agent.`).superRefine((arr, ctx) => {
22285
+ var skillsArraySchema = external_exports.array(skillRefSchema).superRefine((arr, ctx) => {
22255
22286
  const targets = arr.map((s) => s.type === "skill" ? `skill:${s.skillId}` : `github:${s.url}`);
22256
22287
  if (new Set(targets).size !== targets.length) {
22257
22288
  ctx.addIssue({ code: "custom", message: "Duplicate skill entries." });
@@ -22312,6 +22343,50 @@ var hookSchema = external_exports.object({
22312
22343
  path: ["matcher"]
22313
22344
  });
22314
22345
  var hooksSchema = external_exports.array(hookSchema).max(20);
22346
+ var POLICY_ACTIONS = ["allow", "ask", "deny"];
22347
+ var toolCallPolicyConfigSchema = external_exports.object({
22348
+ action: external_exports.enum(POLICY_ACTIONS),
22349
+ matcher: external_exports.string().min(1).max(255).refine((v) => /^(?:[A-Za-z0-9_:.-]|\.\*)+(?:\|(?:[A-Za-z0-9_:.-]|\.\*)+)*$/.test(v), {
22350
+ message: "matcher must be tool names or patterns separated by |"
22351
+ }).optional(),
22352
+ commandPatterns: external_exports.array(external_exports.string().min(1).max(500)).max(20).optional()
22353
+ }).strict();
22354
+ var spendCapPolicyConfigSchema = external_exports.object({
22355
+ warnUsdMicros: external_exports.number().int().min(0).nullish(),
22356
+ hardCapUsdMicros: external_exports.number().int().min(1)
22357
+ }).strict().refine((c) => !c.warnUsdMicros || c.warnUsdMicros < c.hardCapUsdMicros, {
22358
+ message: "warnUsdMicros must be less than hardCapUsdMicros"
22359
+ });
22360
+ var callCountPolicyConfigSchema = external_exports.object({ limitPerSession: external_exports.number().int().min(1).max(1e5) }).strict();
22361
+ var capabilityPolicyConfigSchema = external_exports.record(external_exports.string(), external_exports.enum(POLICY_ACTIONS)).superRefine((obj, ctx) => {
22362
+ for (const k of Object.keys(obj)) {
22363
+ if (!CAPABILITY_IDS.includes(k)) {
22364
+ ctx.addIssue({ code: "custom", message: `unknown capability id: ${k}`, path: [k] });
22365
+ }
22366
+ }
22367
+ });
22368
+ var policyRuleBaseSchema = external_exports.object({
22369
+ name: external_exports.string().min(1).max(128),
22370
+ enabled: external_exports.boolean().default(true),
22371
+ position: external_exports.number().int().min(0).max(9999).default(0)
22372
+ });
22373
+ var policyRuleSchema = external_exports.discriminatedUnion("kind", [
22374
+ policyRuleBaseSchema.extend({ kind: external_exports.literal("TOOL_CALL"), config: toolCallPolicyConfigSchema }),
22375
+ policyRuleBaseSchema.extend({ kind: external_exports.literal("SPEND_CAP"), config: spendCapPolicyConfigSchema }),
22376
+ policyRuleBaseSchema.extend({ kind: external_exports.literal("CALL_COUNT"), config: callCountPolicyConfigSchema }),
22377
+ policyRuleBaseSchema.extend({ kind: external_exports.literal("CAPABILITY"), config: capabilityPolicyConfigSchema })
22378
+ ]);
22379
+ var policyRulePatchSchema = external_exports.object({
22380
+ name: external_exports.string().min(1).max(128).optional(),
22381
+ enabled: external_exports.boolean().optional(),
22382
+ position: external_exports.number().int().min(0).max(9999).optional(),
22383
+ config: external_exports.union([
22384
+ toolCallPolicyConfigSchema,
22385
+ spendCapPolicyConfigSchema,
22386
+ callCountPolicyConfigSchema,
22387
+ capabilityPolicyConfigSchema
22388
+ ]).optional()
22389
+ });
22315
22390
  var packagesSchema = external_exports.object({
22316
22391
  apt: external_exports.array(packageNameSchema).max(32).default([]),
22317
22392
  pip: external_exports.array(packageNameSchema).max(32).default([]),
@@ -22351,6 +22426,7 @@ var capabilityPoliciesSchema = external_exports.record(
22351
22426
  capabilityIdInputSchema,
22352
22427
  capabilityPermissionPolicySchema
22353
22428
  );
22429
+ var nativeToolPolicySchema = external_exports.enum(["always_allow", "always_ask"]);
22354
22430
  var publishSchema = external_exports.object({
22355
22431
  name: external_exports.string().trim().min(1, "Give your agent a name."),
22356
22432
  description: external_exports.string().default(""),
@@ -22398,6 +22474,8 @@ export {
22398
22474
  supersededBy,
22399
22475
  defaultLlm,
22400
22476
  modelSubscriptionProviderFor,
22477
+ MODEL_SUBSCRIPTION_PROVIDER_NAME,
22478
+ MODEL_SUBSCRIPTION_PLAN_PHRASE,
22401
22479
  capabilitiesSchema,
22402
22480
  instructionFileSchema,
22403
22481
  skillRefSchema,
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  AGENT_TYPES,
4
+ MODEL_SUBSCRIPTION_PLAN_PHRASE,
5
+ MODEL_SUBSCRIPTION_PROVIDER_NAME,
4
6
  REASONING_EFFORT_VALUES,
5
7
  capabilitiesSchema,
6
8
  customDataSchema,
@@ -14,10 +16,10 @@ import {
14
16
  skillRefSchema,
15
17
  splitMcpServers,
16
18
  supersededBy
17
- } from "./chunk-FSAF6HIL.js";
19
+ } from "./chunk-MHL5Z6IQ.js";
18
20
 
19
21
  // src/index.ts
20
- import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
22
+ import { existsSync as existsSync6, writeFileSync as writeFileSync5 } from "fs";
21
23
  import { basename as basename3, resolve as resolve3 } from "path";
22
24
  import { createInterface as createInterface3 } from "readline";
23
25
  import { Command } from "commander";
@@ -62,7 +64,7 @@ function currentUniverse() {
62
64
  }
63
65
 
64
66
  // src/version.ts
65
- var CLI_VERSION = "2.4.0";
67
+ var CLI_VERSION = "2.5.0";
66
68
  var USER_AGENT = `sky-cli/${CLI_VERSION} (${process.platform}; node/${process.versions.node})`;
67
69
 
68
70
  // src/api.ts
@@ -165,7 +167,7 @@ async function resolveSessionRef(ref) {
165
167
  return agent2.defaultSessionId;
166
168
  }
167
169
  async function* streamEvents(sessionId, signal) {
168
- const { parseSseStream } = await import("./src-67OBSB2Q.js");
170
+ const { parseSseStream } = await import("./src-GLBJHVIJ.js");
169
171
  const res = await fetch(
170
172
  `${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
171
173
  {
@@ -1073,6 +1075,33 @@ async function headlessLogin(verifier, challenge) {
1073
1075
  throw new Error("login timed out after 10 minutes");
1074
1076
  }
1075
1077
 
1078
+ // src/local-agents.ts
1079
+ import { existsSync as existsSync4 } from "fs";
1080
+ import { homedir as homedir4 } from "os";
1081
+ import { join as join4 } from "path";
1082
+ function localAgents(env = process.env, home = homedir4()) {
1083
+ const claude = env.CLAUDE_CONFIG_DIR || join4(home, ".claude");
1084
+ const codex = env.CODEX_HOME || join4(home, ".codex");
1085
+ const cursor = join4(home, ".cursor");
1086
+ return [
1087
+ { id: "claude_code", name: "Claude Code", root: claude, skillsDir: join4(claude, "skills") },
1088
+ { id: "codex", name: "Codex", root: codex, skillsDir: join4(codex, "skills") },
1089
+ { id: "cursor", name: "Cursor", root: cursor, skillsDir: join4(cursor, "skills") }
1090
+ ];
1091
+ }
1092
+ function detectLocalAgents(env = process.env, home = homedir4()) {
1093
+ return localAgents(env, home).filter((agent2) => existsSync4(agent2.root));
1094
+ }
1095
+ function selectLocalAgents(ids, env = process.env, home = homedir4()) {
1096
+ if (!ids?.length) return detectLocalAgents(env, home);
1097
+ const known = localAgents(env, home);
1098
+ return ids.map((id) => {
1099
+ const agent2 = known.find((candidate) => candidate.id === id);
1100
+ if (!agent2) throw new Error(`unknown agent ${id} \u2014 expected ${known.map((candidate) => candidate.id).join(" | ")}`);
1101
+ return agent2;
1102
+ });
1103
+ }
1104
+
1076
1105
  // src/render.ts
1077
1106
  var dim = (s) => `\x1B[2m${s}\x1B[0m`;
1078
1107
  var red = (s) => `\x1B[31m${s}\x1B[0m`;
@@ -1116,11 +1145,159 @@ function renderEvent(event, data) {
1116
1145
  }
1117
1146
  }
1118
1147
 
1148
+ // src/session-read.ts
1149
+ var TURN_TYPES = ["user.message", "agent.message", "session.status_idle", "session.status_terminated", "session.error"];
1150
+ function latestTurn(events) {
1151
+ let start = 0;
1152
+ for (let index = events.length - 1; index >= 0; index -= 1) {
1153
+ if (events[index].type === "user.message") {
1154
+ start = index + 1;
1155
+ break;
1156
+ }
1157
+ }
1158
+ const reading = { finished: false, messages: [], errors: [] };
1159
+ for (const event of events.slice(start)) {
1160
+ if (event.type === "agent.message" && event.text?.trim()) reading.messages.push(event.text);
1161
+ else if (event.type === "session.error" && event.error?.retry_status !== "retrying") {
1162
+ reading.errors.push(event.error?.message ?? event.error?.type ?? "unknown error");
1163
+ } else if (event.type === "session.status_terminated") reading.finished = true;
1164
+ else if (event.type === "session.status_idle" && event.stop_reason?.type !== "requires_action") reading.finished = true;
1165
+ }
1166
+ return reading;
1167
+ }
1168
+ async function history(sessionId) {
1169
+ const events = [];
1170
+ let cursor = null;
1171
+ for (; ; ) {
1172
+ const query = new URLSearchParams({ limit: "500" });
1173
+ for (const type of TURN_TYPES) query.append("types[]", type);
1174
+ if (cursor) query.set("cursor", cursor);
1175
+ const page = await api(
1176
+ "GET",
1177
+ `/sessions/${encodeURIComponent(sessionId)}/events?${query}`
1178
+ );
1179
+ events.push(...page.events);
1180
+ if (!page.hasMore || !page.cursor) return events;
1181
+ cursor = page.cursor;
1182
+ }
1183
+ }
1184
+ async function readTurn(sessionId, opts = {}) {
1185
+ const deadline = Date.now() + (opts.timeoutSeconds ?? 600) * 1e3;
1186
+ for (; ; ) {
1187
+ const reading = latestTurn(await history(sessionId));
1188
+ if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
1189
+ await new Promise((resolve4) => setTimeout(resolve4, opts.intervalMs ?? 3e3));
1190
+ }
1191
+ }
1192
+
1193
+ // src/skill.ts
1194
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
1195
+ import { join as join5 } from "path";
1196
+ var SKILL_NAME = "agentsky";
1197
+ var SKILL_MARKER = ".agentsky.json";
1198
+ function skillDocument() {
1199
+ return `---
1200
+ name: ${SKILL_NAME}
1201
+ description: Hand part of a job to another agent running in the cloud through AgentSky, and bring its result back. Use when the person asks for another agent or a named engine (Claude Code, Codex, Hermes, OpenClaw and others) to do, test or review something, or when independent work could run in parallel.
1202
+ ---
1203
+
1204
+ # Working with other agents through AgentSky
1205
+
1206
+ AgentSky runs long-lived agents in the cloud. With the \`sky\` command you can
1207
+ find one, give it a job, and read what it produced. You stay responsible for
1208
+ the result.
1209
+
1210
+ If the \`agentsky\` MCP tools are available in this session (\`search_catalog\`,
1211
+ \`start_session\`, \`read_session\`, \`send_message\`), use them instead of the
1212
+ commands below; they do the same things.
1213
+
1214
+ ## Before you start
1215
+
1216
+ Run \`sky auth whoami\`. If it says you are not logged in, ask the person to run
1217
+ \`sky auth login\` and stop there. Never ask them for a token.
1218
+
1219
+ ## When to hand work over
1220
+
1221
+ - When the person asks another agent to do, test or review something, do that.
1222
+ Honor an agent or engine they name.
1223
+ - Otherwise, hand over work another agent is better suited to, or independent
1224
+ parts that can run at the same time as your own.
1225
+ - Do the work yourself when it is small, when it depends on what you already
1226
+ have in front of you, or when explaining it would take as long as doing it.
1227
+
1228
+ ## How to pick
1229
+
1230
+ \`sky catalog search <words for the work> --json\`
1231
+
1232
+ Each result has a \`handle\`, what the agent does, its \`engine_type\`, its
1233
+ categories, how it is paid, and what it needs. If the person names an engine,
1234
+ match its \`engine_type\` and do not substitute another one silently. Run the
1235
+ command with no words to see every agent. If nothing fits, do the work
1236
+ yourself and say so.
1237
+
1238
+ ## How to brief
1239
+
1240
+ \`sky session create @<handle> --title "<short name>" --message "<brief>" --json\`
1241
+
1242
+ It prints the new session's \`id\`. The agent starts working at once, on its own
1243
+ computer in the cloud. It cannot see this conversation or this machine, so a
1244
+ brief is one self-contained message that states:
1245
+
1246
+ 1. what to produce,
1247
+ 2. what "done" means,
1248
+ 3. anything it must not do.
1249
+
1250
+ To give it a file, add \`--file <path>\`. Never put a password, key or token in a
1251
+ brief.
1252
+
1253
+ ## How to collect
1254
+
1255
+ \`sky session read <session id> --wait --json\`
1256
+
1257
+ It waits until the agent finishes its turn and prints its messages, with
1258
+ \`finished\` true or false. If \`finished\` is false the wait timed out: run it
1259
+ again. A first turn can take a minute to begin while the agent's computer
1260
+ starts.
1261
+
1262
+ To correct it, answer it or ask for more:
1263
+
1264
+ \`sky session message <session id> "<text>" --detach\`
1265
+
1266
+ then read again. An agent saying it is done is not evidence: check what it
1267
+ produced before you rely on it.
1268
+
1269
+ ## How to finish
1270
+
1271
+ Tell the person what was done and which agent did which part. Every agent you
1272
+ start is paid for by the person, so start only what the job needs.
1273
+ `;
1274
+ }
1275
+ function installedVersion(dir) {
1276
+ try {
1277
+ const marker = JSON.parse(readFileSync6(join5(dir, SKILL_MARKER), "utf8"));
1278
+ return typeof marker.version === "string" ? marker.version : null;
1279
+ } catch {
1280
+ return null;
1281
+ }
1282
+ }
1283
+ function installSkill(agent2, opts = {}) {
1284
+ const dir = join5(agent2.skillsDir, SKILL_NAME);
1285
+ const previous = installedVersion(dir);
1286
+ const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
1287
+ if (outcome !== "current" && !opts.dryRun) {
1288
+ mkdirSync2(dir, { recursive: true });
1289
+ writeFileSync4(join5(dir, "SKILL.md"), skillDocument());
1290
+ writeFileSync4(join5(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
1291
+ `);
1292
+ }
1293
+ return { agent: agent2.id, path: dir, outcome };
1294
+ }
1295
+
1119
1296
  // src/subscriptions.ts
1120
1297
  import { spawn as spawn3 } from "child_process";
1121
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
1122
- import { homedir as homedir4 } from "os";
1123
- import { join as join4 } from "path";
1298
+ import { existsSync as existsSync5, readFileSync as readFileSync7 } from "fs";
1299
+ import { homedir as homedir5 } from "os";
1300
+ import { join as join6 } from "path";
1124
1301
  import { createInterface as createInterface2 } from "readline";
1125
1302
  var ALIASES = {
1126
1303
  claude: "anthropic",
@@ -1136,6 +1313,14 @@ function providerFor(name) {
1136
1313
  }
1137
1314
  return provider;
1138
1315
  }
1316
+ function managedProviderFor(name) {
1317
+ if (name.toLowerCase() === "cursor") return "cursor";
1318
+ const provider = ALIASES[name.toLowerCase()];
1319
+ if (!provider) {
1320
+ throw new ApiError(400, "unknown_provider", `unknown provider "${name}" \u2014 use claude, chatgpt or cursor`);
1321
+ }
1322
+ return provider;
1323
+ }
1139
1324
  function ask(question) {
1140
1325
  return new Promise((done) => {
1141
1326
  const rl = createInterface2({ input: process.stdin, output: process.stdout });
@@ -1198,12 +1383,12 @@ async function connectClaude() {
1198
1383
  console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
1199
1384
  }
1200
1385
  async function connectChatgpt(opts) {
1201
- const codexHome = process.env.CODEX_HOME || join4(homedir4(), ".codex");
1202
- const path = join4(codexHome, "auth.json");
1203
- if (!existsSync4(path)) {
1386
+ const codexHome = process.env.CODEX_HOME || join6(homedir5(), ".codex");
1387
+ const path = join6(codexHome, "auth.json");
1388
+ if (!existsSync5(path)) {
1204
1389
  throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
1205
1390
  }
1206
- const raw = readFileSync6(path, "utf8");
1391
+ const raw = readFileSync7(path, "utf8");
1207
1392
  let accountId;
1208
1393
  try {
1209
1394
  const parsed = JSON.parse(raw);
@@ -1233,7 +1418,7 @@ async function listSubscriptions() {
1233
1418
  }
1234
1419
  for (const s of modelSubscriptions) {
1235
1420
  const bits = [
1236
- s.provider === "anthropic" ? "claude" : "chatgpt",
1421
+ MODEL_SUBSCRIPTION_PROVIDER_NAME[s.provider].toLowerCase(),
1237
1422
  s.status,
1238
1423
  s.expiresAt ? `expires ~${s.expiresAt.slice(0, 10)}` : null,
1239
1424
  s.lastUsedAt ? `last used ${s.lastUsedAt.slice(0, 10)}` : null,
@@ -1245,8 +1430,8 @@ async function listSubscriptions() {
1245
1430
  }
1246
1431
  }
1247
1432
  async function disconnect(name, opts) {
1248
- const provider = providerFor(name);
1249
- const label = provider === "anthropic" ? "Claude" : "ChatGPT";
1433
+ const provider = managedProviderFor(name);
1434
+ const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[provider];
1250
1435
  if (!opts.yes) {
1251
1436
  const ok = await confirm2(`Disconnect ${label}? Agents running on it will fail until reconnected or switched to Platform.`);
1252
1437
  if (!ok) {
@@ -1272,7 +1457,7 @@ function printModelBilling(session2) {
1272
1457
  if (session2.modelBilling !== "account") return;
1273
1458
  const provider = modelSubscriptionProviderFor(session2.agentType, session2.llm);
1274
1459
  if (!provider) return;
1275
- const label = provider === "anthropic" ? "your Claude subscription" : "your ChatGPT plan";
1460
+ const label = `your ${MODEL_SUBSCRIPTION_PLAN_PHRASE[provider]}`;
1276
1461
  console.log(`Model billing: ${label} \u2014 $0 model usage`);
1277
1462
  }
1278
1463
  var program = new Command();
@@ -1445,7 +1630,7 @@ function renderResource(r) {
1445
1630
  const ref = r.checkout ? `@${r.checkout.type === "branch" ? r.checkout.name : r.checkout.sha}` : "";
1446
1631
  return `${r.id} repo ${r.mount_path} ${r.url}${ref}`;
1447
1632
  }
1448
- 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(
1633
+ 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").option("--json", "print the created session as JSON").action(
1449
1634
  run(
1450
1635
  async (agentRef, opts) => {
1451
1636
  const resources = await resourceParams(opts);
@@ -1458,6 +1643,10 @@ session.command("create <agent>").description("create a session (starts provisio
1458
1643
  ...resources.length ? { resources } : {},
1459
1644
  ...opts.message ? { initial_events: [{ type: "user.message", content: [{ type: "text", text: opts.message }] }] } : {}
1460
1645
  });
1646
+ if (opts.json) {
1647
+ console.log(JSON.stringify(created));
1648
+ return;
1649
+ }
1461
1650
  console.log(`Created ${created.id} (${created.agent} \xB7 ${created.status})${created.default ? " [default]" : ""}`);
1462
1651
  printModelBilling(created);
1463
1652
  for (const r of created.resources ?? []) console.log(` ${renderResource(r)}`);
@@ -1534,6 +1723,21 @@ session.command("interrupt <ref>").description("abort the in-flight turn").actio
1534
1723
  console.log(res.status === "no_turn" ? "nothing in flight" : "interrupting");
1535
1724
  })
1536
1725
  );
1726
+ session.command("read <ref>").description("what the agent has said since the last message it was sent").option("--wait", "wait until the agent finishes its turn").option("--timeout <seconds>", "how long --wait waits (default 600)").option("--json", "print { finished, messages, errors } as JSON").action(
1727
+ run(async (ref, opts) => {
1728
+ const reading = await readTurn(await resolveSessionRef(ref), {
1729
+ wait: opts.wait,
1730
+ ...opts.timeout !== void 0 ? { timeoutSeconds: Number(opts.timeout) } : {}
1731
+ });
1732
+ if (opts.json) {
1733
+ console.log(JSON.stringify(reading));
1734
+ return;
1735
+ }
1736
+ for (const message of reading.messages) console.log(message);
1737
+ for (const error of reading.errors) console.error(`error: ${error}`);
1738
+ if (!reading.finished) console.error("The agent is still working. Run this again to read more.");
1739
+ })
1740
+ );
1537
1741
  var sessionResources = session.command("resources").description("files and GitHub repositories mounted into a session's working directory");
1538
1742
  var resourcesPath = (id) => `/sessions/${encodeURIComponent(id)}/resources`;
1539
1743
  sessionResources.command("list <ref>").description("the session's resources, oldest first").action(
@@ -1688,6 +1892,40 @@ async function chatLoop(sessionId, opts = {}) {
1688
1892
  await new Promise(() => {
1689
1893
  });
1690
1894
  }
1895
+ program.command("catalog").description("the agents that can be started by handle").command("search [words...]").description("find an agent for a piece of work; no words lists every agent").option("--category <category>", "only this category").option("--json", "print the full listings as JSON").action(
1896
+ run(async (words, opts) => {
1897
+ const query = new URLSearchParams();
1898
+ if (words.length) query.set("q", words.join(" "));
1899
+ if (opts.category) query.set("category", opts.category);
1900
+ const { agents } = await api("GET", `/catalog/agents${query.size ? `?${query}` : ""}`);
1901
+ if (opts.json) {
1902
+ console.log(JSON.stringify(agents));
1903
+ return;
1904
+ }
1905
+ if (!agents.length) {
1906
+ console.log("No agent matches. `sky catalog search` with no words lists every agent.");
1907
+ return;
1908
+ }
1909
+ for (const a of agents) console.log(`@${a.handle.padEnd(24)} ${a.name.padEnd(20)} ${a.tagline}`);
1910
+ console.log(`
1911
+ Start one: sky session create @<handle> --message "<the job>"`);
1912
+ })
1913
+ );
1914
+ program.command("skill").description("the AgentSky skill for the agents on this machine").command("install").description("add the AgentSky skill to each installed agent (Claude Code, Codex, Cursor)").option("--agent <id...>", "only these agents: claude_code | codex | cursor").option("--dry-run", "say what would be written; write nothing").option("--json", "print the outcome per agent as JSON").action(
1915
+ run(async (opts) => {
1916
+ const installs = selectLocalAgents(opts.agent).map((a) => installSkill(a, { dryRun: opts.dryRun }));
1917
+ if (opts.json) {
1918
+ console.log(JSON.stringify(installs));
1919
+ return;
1920
+ }
1921
+ if (!installs.length) {
1922
+ console.log("No agent found on this machine (looked for Claude Code, Codex and Cursor).");
1923
+ return;
1924
+ }
1925
+ const verb = { installed: opts.dryRun ? "would install" : "installed", updated: opts.dryRun ? "would update" : "updated", current: "up to date" };
1926
+ for (const i of installs) console.log(`${i.agent.padEnd(12)} ${verb[i.outcome].padEnd(14)} ${i.path}`);
1927
+ })
1928
+ );
1691
1929
  var secrets = program.command("secrets").description("agent secrets (values write-only)");
1692
1930
  secrets.command("set <id> <pairs...>").description("set KEY=VALUE pairs on the agent").action(
1693
1931
  run(async (id, pairs) => {
@@ -1723,7 +1961,7 @@ secrets.command("unset <id> <key>").action(
1723
1961
  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(
1724
1962
  run(async (opts) => {
1725
1963
  const path = resolve3(opts.file);
1726
- if (!existsSync5(path)) {
1964
+ if (!existsSync6(path)) {
1727
1965
  const name = basename3(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
1728
1966
  scaffoldAgentFile(path, name);
1729
1967
  console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
@@ -1921,7 +2159,7 @@ files.command("rm <id>").description("delete a file (a session that already rece
1921
2159
  files.command("download <id>").description("download a session file's bytes").requiredOption("-o, --out <path>", "where to write the bytes").action(
1922
2160
  run(async (id, opts) => {
1923
2161
  const { bytes } = await downloadFile(id);
1924
- writeFileSync4(opts.out, bytes);
2162
+ writeFileSync5(opts.out, bytes);
1925
2163
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
1926
2164
  })
1927
2165
  );
package/dist/products.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  skillsArraySchema,
9
9
  systemMessageEventSchema,
10
10
  userMessageEventSchema
11
- } from "./chunk-FSAF6HIL.js";
11
+ } from "./chunk-MHL5Z6IQ.js";
12
12
 
13
13
  // ../../packages/spec/src/compatibility.ts
14
14
  var capabilityInputsSchema = capabilitiesSchema;
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  external_exports,
3
3
  messagePartSchema
4
- } from "./chunk-FSAF6HIL.js";
4
+ } from "./chunk-MHL5Z6IQ.js";
5
5
 
6
6
  // ../../packages/chat/src/stream.ts
7
7
  async function* parseSseStream(body) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentsky/sky",
3
- "version": "2.4.0",
3
+ "version": "2.5.0",
4
4
  "description": "sky — the AgentSky developer CLI: launch and talk to long-lived agents from your terminal.",
5
5
  "license": "MIT",
6
6
  "type": "module",