@kynver-app/runtime 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -8,18 +8,29 @@ Standalone Kynver AgentOS execution runtime and CLI (`kynver`).
8
8
  npm run kynver:build
9
9
  npm run kynver -- setup --api-base-url http://localhost:3000 --agent-os-id <id> --repo /path/to/repo --max-workers 13
10
10
  npm run kynver -- login --api-key "$KYNVER_API_KEY"
11
+ npm run kynver -- runner credential --agent-os-id <id>
11
12
  npm run kynver -- run create --repo /path/to/repo --name my-run
12
13
  npm run kynver -- daemon --run <runId> --agent-os-id <id> --execute
13
14
  ```
14
15
 
15
- Set `OPENCLAW_CRON_SECRET` or `KYNVER_RUNTIME_SECRET` for server callbacks.
16
+ ### Callback auth (scoped runner tokens)
17
+
18
+ User-hosted runners should **not** use deployment-wide `OPENCLAW_CRON_SECRET` / `KYNVER_RUNTIME_SECRET`. After `kynver login`, mint a workspace-bound token:
19
+
20
+ ```bash
21
+ kynver runner credential --agent-os-id <id>
22
+ ```
23
+
24
+ The token (`krc1.*`) is stored in `~/.kynver/credentials` as `runnerToken` and sent as `X-Kynver-Runner-Token` on dispatch/sweep/completion/progress callbacks. It is bound to one `agentOsId` and a capability set (`tasks`, `harness`, `plans.progress`, `runtime.read`, `operator`). The server persists only a token hash plus audit metadata, so credentials can be listed, revoked, and rotated without exposing the clear token again.
25
+
26
+ Global env secrets remain supported for first-party dogfood, QStash, and OpenClaw cron — server-side `verifyHarnessCallback` accepts QStash signatures, scoped tokens, or global secrets (in that precedence for non-QStash callers).
16
27
 
17
28
  ## CLI verbs
18
29
 
19
30
  ```text
20
31
  run create | list | status | dispatch | sweep
21
32
  worker start | status | tail | stop | complete
22
- login | setup | daemon
33
+ login | runner credential | setup | daemon
23
34
  ```
24
35
 
25
36
  `scripts/opus-harness.mjs` is a compatibility shim that delegates to this package.
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  // src/cli.ts
4
4
  import { mkdirSync as mkdirSync5 } from "node:fs";
5
- import path13 from "node:path";
5
+ import path14 from "node:path";
6
6
  import { fileURLToPath } from "node:url";
7
7
 
8
8
  // src/config.ts
@@ -132,20 +132,112 @@ function saveUserConfig(config) {
132
132
  writeFileSync2(CONFIG_FILE, `${JSON.stringify(config, null, 2)}
133
133
  `, { mode: 384 });
134
134
  }
135
- function saveApiKey(apiKey) {
135
+ function loadCredentialsFile() {
136
+ if (!existsSync2(CREDENTIALS_FILE)) return {};
137
+ try {
138
+ return JSON.parse(readFileSync2(CREDENTIALS_FILE, "utf8"));
139
+ } catch {
140
+ return {};
141
+ }
142
+ }
143
+ function saveCredentialsFile(parsed) {
136
144
  mkdirSync2(CONFIG_DIR, { recursive: true });
137
- writeFileSync2(CREDENTIALS_FILE, `${JSON.stringify({ apiKey }, null, 2)}
145
+ writeFileSync2(CREDENTIALS_FILE, `${JSON.stringify(parsed, null, 2)}
138
146
  `, { mode: 384 });
139
147
  }
148
+ function loadApiKey() {
149
+ if (process.env.KYNVER_API_KEY) return process.env.KYNVER_API_KEY;
150
+ return loadCredentialsFile().apiKey;
151
+ }
152
+ function saveApiKey(apiKey) {
153
+ saveCredentialsFile({ ...loadCredentialsFile(), apiKey });
154
+ }
155
+ function loadRunnerToken(agentOsId) {
156
+ const creds = loadCredentialsFile();
157
+ if (!creds.runnerToken) return void 0;
158
+ if (agentOsId && creds.runnerTokenAgentOsId && creds.runnerTokenAgentOsId !== agentOsId) {
159
+ return void 0;
160
+ }
161
+ return creds.runnerToken;
162
+ }
163
+ function saveRunnerToken(agentOsId, token) {
164
+ saveCredentialsFile({
165
+ ...loadCredentialsFile(),
166
+ runnerToken: token,
167
+ runnerTokenAgentOsId: agentOsId
168
+ });
169
+ }
140
170
  function resolveBaseUrl(argsBaseUrl) {
141
171
  const baseUrl = argsBaseUrl || process.env.KYNVER_API_URL || process.env.OPENCLAW_CRON_FIRE_BASE_URL || loadUserConfig().apiBaseUrl;
142
172
  if (!baseUrl) failConfig("requires --base-url, KYNVER_API_URL, OPENCLAW_CRON_FIRE_BASE_URL, or ~/.kynver/config.json apiBaseUrl");
143
173
  return trimTrailingSlash(String(baseUrl));
144
174
  }
145
- function resolveCallbackSecret(argsSecret) {
146
- const secret = argsSecret || process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
147
- if (!secret) failConfig("requires --secret, KYNVER_RUNTIME_SECRET, or OPENCLAW_CRON_SECRET");
148
- return String(secret);
175
+ function resolveCallbackSecret(argsSecret, agentOsId) {
176
+ const scoped = argsSecret || loadRunnerToken(agentOsId) || loadRunnerToken(loadUserConfig().agentOsId);
177
+ if (scoped) return String(scoped);
178
+ const globalSecret = process.env.KYNVER_RUNTIME_SECRET || process.env.OPENCLAW_CRON_SECRET;
179
+ if (globalSecret) {
180
+ console.warn(
181
+ "[kynver] using deployment-level callback secret; run `kynver runner credential --agent-os-id <id>` for a scoped token"
182
+ );
183
+ return String(globalSecret);
184
+ }
185
+ failConfig(
186
+ "requires --secret, a scoped runner token (`kynver runner credential`), ~/.kynver/credentials runnerToken, or (legacy) KYNVER_RUNTIME_SECRET / OPENCLAW_CRON_SECRET"
187
+ );
188
+ }
189
+ async function fetchRunnerCredential(agentOsId, opts) {
190
+ const apiKey = opts?.apiKey || loadApiKey();
191
+ if (!apiKey) throw new Error("API key required \u2014 run `kynver login` first");
192
+ const base = resolveBaseUrl(opts?.baseUrl);
193
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runner-credentials`;
194
+ const res = await fetch(url, {
195
+ method: "POST",
196
+ headers: {
197
+ "Content-Type": "application/json",
198
+ Authorization: `Bearer ${apiKey}`
199
+ },
200
+ body: JSON.stringify({})
201
+ });
202
+ const text = await res.text();
203
+ let parsed = null;
204
+ try {
205
+ parsed = JSON.parse(text);
206
+ } catch {
207
+ parsed = null;
208
+ }
209
+ if (!res.ok || !parsed?.token) {
210
+ throw new Error(
211
+ `runner credential mint failed (${res.status}): ${parsed?.error ?? text.slice(0, 200)}`
212
+ );
213
+ }
214
+ return parsed.token;
215
+ }
216
+ async function mintRunnerCredential(args) {
217
+ const agentOsId = (args.agentOsId ? String(args.agentOsId) : loadUserConfig().agentOsId) || "";
218
+ if (!agentOsId) failConfig("runner credential requires --agent-os-id or agentOsId in ~/.kynver/config.json");
219
+ try {
220
+ const token = await fetchRunnerCredential(agentOsId, {
221
+ baseUrl: args.baseUrl ? String(args.baseUrl) : void 0
222
+ });
223
+ saveRunnerToken(agentOsId, token);
224
+ console.log(
225
+ JSON.stringify(
226
+ {
227
+ ok: true,
228
+ agentOsId,
229
+ credentialsPath: CREDENTIALS_FILE,
230
+ tokenPrefix: `${token.slice(0, 12)}\u2026`,
231
+ note: "Scoped runner token saved; callbacks use X-Kynver-Runner-Token."
232
+ },
233
+ null,
234
+ 2
235
+ )
236
+ );
237
+ } catch (err) {
238
+ console.error(err instanceof Error ? err.message : String(err));
239
+ process.exit(1);
240
+ }
149
241
  }
150
242
  function failConfig(message) {
151
243
  console.error(message);
@@ -180,13 +272,28 @@ async function runSetup(args) {
180
272
  workerProvider: typeof args.provider === "string" ? args.provider : existing.workerProvider || "claude"
181
273
  };
182
274
  saveUserConfig(config);
275
+ let runnerCredentialNote;
276
+ const apiKey = loadApiKey();
277
+ const agentOsId = config.agentOsId;
278
+ if (apiKey && agentOsId) {
279
+ try {
280
+ const token = await fetchRunnerCredential(agentOsId, {
281
+ baseUrl: typeof args.apiBaseUrl === "string" ? args.apiBaseUrl : config.apiBaseUrl,
282
+ apiKey
283
+ });
284
+ saveRunnerToken(agentOsId, token);
285
+ runnerCredentialNote = "Scoped runner token minted and saved to ~/.kynver/credentials.";
286
+ } catch {
287
+ runnerCredentialNote = "Runner token not minted (server offline or master secret unset). Run `kynver runner credential` after deploy.";
288
+ }
289
+ }
183
290
  console.log(
184
291
  JSON.stringify(
185
292
  {
186
293
  ok: true,
187
294
  configPath: CONFIG_FILE,
188
295
  config,
189
- note: "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Advanced RAM tuning stays internal."
296
+ note: runnerCredentialNote ?? "Set worker limit once with --max-workers N (or omit to auto-size from RAM). Run `kynver login` + `kynver runner credential` for scoped callbacks."
190
297
  },
191
298
  null,
192
299
  2
@@ -200,15 +307,27 @@ async function runLogin(args) {
200
307
  console.log(JSON.stringify({ ok: true, credentialsPath: CREDENTIALS_FILE }, null, 2));
201
308
  }
202
309
 
310
+ // src/callback-headers.ts
311
+ function buildHarnessCallbackHeaders(secret) {
312
+ const trimmed = String(secret).trim();
313
+ if (trimmed.startsWith("krc1.")) {
314
+ return {
315
+ "Content-Type": "application/json",
316
+ "X-Kynver-Runner-Token": trimmed
317
+ };
318
+ }
319
+ return {
320
+ "Content-Type": "application/json",
321
+ "X-OpenClaw-Cron-Secret": trimmed,
322
+ "X-Kynver-Runtime-Secret": trimmed
323
+ };
324
+ }
325
+
203
326
  // src/callbacks.ts
204
327
  async function postJson(url, secret, body) {
205
328
  const res = await fetch(url, {
206
329
  method: "POST",
207
- headers: {
208
- "Content-Type": "application/json",
209
- "X-OpenClaw-Cron-Secret": String(secret),
210
- "X-Kynver-Runtime-Secret": String(secret)
211
- },
330
+ headers: buildHarnessCallbackHeaders(secret),
212
331
  body: JSON.stringify(body)
213
332
  });
214
333
  let response = null;
@@ -222,10 +341,7 @@ async function postJson(url, secret, body) {
222
341
  async function getJson(url, secret) {
223
342
  const res = await fetch(url, {
224
343
  method: "GET",
225
- headers: {
226
- "X-OpenClaw-Cron-Secret": String(secret),
227
- "X-Kynver-Runtime-Secret": String(secret)
228
- }
344
+ headers: buildHarnessCallbackHeaders(secret)
229
345
  });
230
346
  let response = null;
231
347
  try {
@@ -243,12 +359,12 @@ var DEFAULT_CRITICAL_FREE_BYTES = 15 * 1024 * 1024 * 1024;
243
359
  var DEFAULT_MAX_USED_PERCENT = 80;
244
360
  var DEFAULT_HARD_MAX_USED_PERCENT = 90;
245
361
  function observeRunnerDiskGate(input = {}) {
246
- const path14 = input.diskPath?.trim() || "/";
362
+ const path15 = input.diskPath?.trim() || "/";
247
363
  const warnBelowBytes = input.diskFreeWarnBytes ?? DEFAULT_WARN_FREE_BYTES;
248
364
  const criticalBelowBytes = input.diskFreeCriticalBytes ?? DEFAULT_CRITICAL_FREE_BYTES;
249
365
  const maxUsedPercent = input.diskMaxUsedPercent ?? DEFAULT_MAX_USED_PERCENT;
250
366
  const hardMaxUsedPercent = input.diskHardMaxUsedPercent ?? DEFAULT_HARD_MAX_USED_PERCENT;
251
- const stats = statfsSync(path14);
367
+ const stats = statfsSync(path15);
252
368
  const freeBytes = Number(stats.bavail) * Number(stats.bsize);
253
369
  const totalBytes = Number(stats.blocks) * Number(stats.bsize);
254
370
  const usedPercent = totalBytes > 0 ? (totalBytes - freeBytes) / totalBytes * 100 : 100;
@@ -268,7 +384,7 @@ function observeRunnerDiskGate(input = {}) {
268
384
  }
269
385
  return {
270
386
  ok,
271
- path: path14,
387
+ path: path15,
272
388
  freeBytes,
273
389
  totalBytes,
274
390
  usedPercent,
@@ -874,6 +990,7 @@ function spawnWorkerProcess(run, opts) {
874
990
  ownedPaths: opts.ownedPaths,
875
991
  ...opts.agentOsId ? { agentOsId: String(opts.agentOsId) } : {},
876
992
  ...opts.taskId ? { taskId: String(opts.taskId) } : {},
993
+ ...opts.planId ? { planId: String(opts.planId) } : {},
877
994
  ...opts.leaseOwner ? { leaseOwner: String(opts.leaseOwner) } : {},
878
995
  ...opts.dispatched ? { dispatched: true } : {},
879
996
  startedAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -940,7 +1057,7 @@ async function dispatchRun(args) {
940
1057
  const run = loadRun(String(required(String(args.run || ""), "--run")));
941
1058
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
942
1059
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
943
- const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
1060
+ const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
944
1061
  const execute = args.execute === true || args.execute === "true";
945
1062
  const dryRun = !execute;
946
1063
  const leaseOwner = `openclaw-harness:${run.id}`;
@@ -1014,6 +1131,7 @@ async function dispatchRun(args) {
1014
1131
  const task = decision.task;
1015
1132
  const name = safeSlug(`t-${task.id}-a${task.attempt}`);
1016
1133
  try {
1134
+ const planId = task.planId ? String(task.planId) : void 0;
1017
1135
  const worker = spawnWorkerProcess(run, {
1018
1136
  name,
1019
1137
  task: buildDispatchTaskText(task, agentOsId),
@@ -1021,6 +1139,7 @@ async function dispatchRun(args) {
1021
1139
  model: args.model ? String(args.model) : void 0,
1022
1140
  agentOsId,
1023
1141
  taskId: String(task.id),
1142
+ planId,
1024
1143
  leaseOwner,
1025
1144
  dispatched: true
1026
1145
  });
@@ -1082,7 +1201,7 @@ async function sweepRun(args) {
1082
1201
  const run = loadRun(String(required(String(args.run || ""), "--run")));
1083
1202
  const agentOsId = String(required(String(args.agentOsId || ""), "--agent-os-id"));
1084
1203
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1085
- const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
1204
+ const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
1086
1205
  const leaseOwner = `openclaw-harness:${run.id}`;
1087
1206
  const releasedLocalOrphans = [];
1088
1207
  for (const name of Object.keys(run.workers || {})) {
@@ -1200,7 +1319,7 @@ async function tryCompleteWorker(args) {
1200
1319
  return { ok: true, skipped: true, reason: "worker-not-finished" };
1201
1320
  }
1202
1321
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1203
- const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
1322
+ const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
1204
1323
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/harness/completion`;
1205
1324
  const body = {
1206
1325
  source: "openclaw-harness",
@@ -1214,11 +1333,7 @@ async function tryCompleteWorker(args) {
1214
1333
  };
1215
1334
  const res = await fetch(url, {
1216
1335
  method: "POST",
1217
- headers: {
1218
- "Content-Type": "application/json",
1219
- "X-OpenClaw-Cron-Secret": secret,
1220
- "X-Kynver-Runtime-Secret": secret
1221
- },
1336
+ headers: buildHarnessCallbackHeaders(secret),
1222
1337
  body: JSON.stringify(body)
1223
1338
  });
1224
1339
  let parsed = null;
@@ -1353,12 +1468,57 @@ function stopWorker(args) {
1353
1468
  }
1354
1469
 
1355
1470
  // src/pipeline-tick.ts
1471
+ import path13 from "node:path";
1472
+
1473
+ // src/plan-progress-daemon-sync.ts
1356
1474
  import path12 from "node:path";
1357
1475
 
1476
+ // src/plan-progress-sync.ts
1477
+ async function syncPlanProgress(args) {
1478
+ const base = resolveBaseUrl(args.baseUrl);
1479
+ const secret = resolveCallbackSecret(args.secret);
1480
+ const url = `${base}/api/agent-os/by-id/${encodeURIComponent(args.agentOsId)}/tasks/${encodeURIComponent(args.taskId)}/plan-progress-sync`;
1481
+ const res = await postJson(url, secret, {
1482
+ phase: args.phase,
1483
+ taskId: args.taskId,
1484
+ blocker: args.blocker,
1485
+ artifact: args.artifact
1486
+ });
1487
+ return { ok: res.ok, status: res.status, response: res.response };
1488
+ }
1489
+
1490
+ // src/plan-progress-daemon-sync.ts
1491
+ async function syncActiveWorkerPlanProgress(runId, args) {
1492
+ const run = loadRun(runId);
1493
+ const agentOsId = String(args.agentOsId || "");
1494
+ if (!agentOsId) return [];
1495
+ const outcomes = [];
1496
+ for (const name of Object.keys(run.workers || {})) {
1497
+ const worker = readJson(
1498
+ path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1499
+ null
1500
+ );
1501
+ if (!worker?.dispatched || !worker.taskId) continue;
1502
+ const status = computeWorkerStatus(worker);
1503
+ if (status.heartbeatBlocker) {
1504
+ const res = await syncPlanProgress({
1505
+ agentOsId,
1506
+ taskId: worker.taskId,
1507
+ phase: "heartbeat_blocker",
1508
+ blocker: status.heartbeatBlocker,
1509
+ baseUrl: args.baseUrl ? String(args.baseUrl) : void 0,
1510
+ secret: args.secret ? String(args.secret) : void 0
1511
+ });
1512
+ outcomes.push({ worker: name, phase: "heartbeat_blocker", ok: res.ok });
1513
+ }
1514
+ }
1515
+ return outcomes;
1516
+ }
1517
+
1358
1518
  // src/workspace-runtime-config.ts
1359
1519
  async function fetchWorkspaceRuntimePreferences(agentOsId, args) {
1360
1520
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1361
- const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
1521
+ const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
1362
1522
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/runtime`;
1363
1523
  try {
1364
1524
  const res = await getJson(url, secret);
@@ -1382,7 +1542,7 @@ async function completeFinishedWorkers(runId, args) {
1382
1542
  const outcomes = [];
1383
1543
  for (const name of Object.keys(run.workers || {})) {
1384
1544
  const worker = readJson(
1385
- path12.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1545
+ path13.join(runDirectory(run.id), "workers", safeSlug(name), "worker.json"),
1386
1546
  null
1387
1547
  );
1388
1548
  if (!worker?.dispatched || !worker.taskId) continue;
@@ -1401,7 +1561,7 @@ async function completeFinishedWorkers(runId, args) {
1401
1561
  }
1402
1562
  async function postOperatorTick(agentOsId, runId, resourceGate, args) {
1403
1563
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1404
- const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
1564
+ const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
1405
1565
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/operator/tick`;
1406
1566
  const res = await postJson(url, secret, {
1407
1567
  agentOsId,
@@ -1417,6 +1577,7 @@ async function runPipelineTick(args) {
1417
1577
  const execute = args.execute !== false && args.execute !== "false";
1418
1578
  runStatus({ run: runId });
1419
1579
  const completedWorkers = await completeFinishedWorkers(runId, args);
1580
+ const planProgressSync = await syncActiveWorkerPlanProgress(runId, args);
1420
1581
  const workspacePrefs = await fetchWorkspaceRuntimePreferences(agentOsId, args);
1421
1582
  const resourceGate = observeRunnerResourceGate({
1422
1583
  runId,
@@ -1456,6 +1617,7 @@ async function runPipelineTick(args) {
1456
1617
  execute,
1457
1618
  resourceGate,
1458
1619
  completedWorkers,
1620
+ planProgressSync,
1459
1621
  operatorTick,
1460
1622
  sweep,
1461
1623
  dispatch,
@@ -1523,7 +1685,7 @@ async function emitPlanProgress(args) {
1523
1685
  evidence.push(parseEvidenceArg(rawEvidence));
1524
1686
  }
1525
1687
  const base = resolveBaseUrl(args.baseUrl ? String(args.baseUrl) : void 0);
1526
- const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0);
1688
+ const secret = resolveCallbackSecret(args.secret ? String(args.secret) : void 0, agentOsId);
1527
1689
  const url = `${base}/api/agent-os/by-id/${encodeURIComponent(agentOsId)}/plans/${encodeURIComponent(planId)}/progress-events`;
1528
1690
  const cfg = loadUserConfig();
1529
1691
  const provider = cfg.workerProvider ? `provider:${cfg.workerProvider}` : void 0;
@@ -1542,10 +1704,7 @@ async function emitPlanProgress(args) {
1542
1704
  };
1543
1705
  const res = await fetch(url, {
1544
1706
  method: "POST",
1545
- headers: {
1546
- "Content-Type": "application/json",
1547
- "X-OpenClaw-Cron-Secret": secret
1548
- },
1707
+ headers: buildHarnessCallbackHeaders(secret),
1549
1708
  body: JSON.stringify(body)
1550
1709
  });
1551
1710
  const text = await res.text();
@@ -1611,6 +1770,7 @@ function usage(code = 0) {
1611
1770
  [
1612
1771
  "Usage:",
1613
1772
  " kynver login --api-key KEY",
1773
+ " kynver runner credential [--agent-os-id ID] [--base-url URL]",
1614
1774
  " kynver setup [--api-base-url URL] [--agent-os-id ID] [--agent-os-slug SLUG] [--repo PATH] [--max-workers N] [--provider claude|cursor]",
1615
1775
  " kynver daemon --run RUN_ID --agent-os-id AOS_ID [--execute] [--interval-ms MS]",
1616
1776
  " kynver run create --repo /path/repo [--name name] [--base origin/main]",
@@ -1634,7 +1794,7 @@ async function main(argv = process.argv.slice(2)) {
1634
1794
  const scope = argv.shift();
1635
1795
  let action;
1636
1796
  let rest;
1637
- if (scope === "run" || scope === "worker" || scope === "plan") {
1797
+ if (scope === "run" || scope === "worker" || scope === "plan" || scope === "runner") {
1638
1798
  action = argv.shift();
1639
1799
  rest = argv;
1640
1800
  } else {
@@ -1645,6 +1805,7 @@ async function main(argv = process.argv.slice(2)) {
1645
1805
  mkdirSync5(runsDir, { recursive: true });
1646
1806
  mkdirSync5(worktreesDir, { recursive: true });
1647
1807
  if (scope === "login") return void await runLogin(args);
1808
+ if (scope === "runner" && action === "credential") return void await mintRunnerCredential(args);
1648
1809
  if (scope === "setup") return void await runSetup(args);
1649
1810
  if (scope === "daemon") return void await runDaemon(args);
1650
1811
  if (scope === "plan" && action === "progress") return void await emitPlanProgress(args);
@@ -1661,7 +1822,7 @@ async function main(argv = process.argv.slice(2)) {
1661
1822
  if (scope === "worker" && action === "complete") return void await completeWorker(args);
1662
1823
  unknownCommand(scope, action);
1663
1824
  }
1664
- var isCliEntry = process.argv[1] && path13.resolve(process.argv[1]) === path13.resolve(fileURLToPath(import.meta.url));
1825
+ var isCliEntry = process.argv[1] && path14.resolve(process.argv[1]) === path14.resolve(fileURLToPath(import.meta.url));
1665
1826
  if (isCliEntry) {
1666
1827
  void main().catch((error) => {
1667
1828
  console.error(error);