@skrr-ai/cli 0.1.80 → 0.1.82

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.
Files changed (96) hide show
  1. package/dist/base-command.d.ts +20 -0
  2. package/dist/base-command.js +151 -7
  3. package/dist/commands/agents/visibility.d.ts +14 -0
  4. package/dist/commands/agents/visibility.js +52 -0
  5. package/dist/commands/create-token.d.ts +0 -1
  6. package/dist/commands/create-token.js +3 -1
  7. package/dist/commands/followups/checkin.js +1 -1
  8. package/dist/commands/list-tokens.d.ts +0 -1
  9. package/dist/commands/list-tokens.js +1 -1
  10. package/dist/commands/login.js +10 -0
  11. package/dist/commands/logout.js +4 -0
  12. package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
  13. package/dist/commands/revoke-token.d.ts +0 -1
  14. package/dist/commands/revoke-token.js +1 -1
  15. package/dist/commands/store/follow.d.ts +1 -0
  16. package/dist/commands/store/follow.js +7 -1
  17. package/dist/commands/store/follows/list.d.ts +11 -0
  18. package/dist/commands/store/follows/list.js +29 -0
  19. package/dist/commands/store/follows/remove.d.ts +16 -0
  20. package/dist/commands/store/follows/remove.js +37 -0
  21. package/dist/commands/store/installs.js +6 -1
  22. package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
  23. package/dist/commands/store/publishers/profile/draft.js +54 -0
  24. package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
  25. package/dist/commands/store/publishers/profile/handle.js +39 -0
  26. package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
  27. package/dist/commands/store/publishers/profile/hide.js +39 -0
  28. package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
  29. package/dist/commands/store/publishers/profile/preview.js +39 -0
  30. package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
  31. package/dist/commands/store/publishers/profile/publish.js +41 -0
  32. package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
  33. package/dist/commands/store/publishers/profile/restore.js +49 -0
  34. package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
  35. package/dist/commands/store/publishers/profile/revisions.js +30 -0
  36. package/dist/commands/store/publishers/profile/show.d.ts +11 -0
  37. package/dist/commands/store/publishers/profile/show.js +39 -0
  38. package/dist/commands/store/review.d.ts +26 -0
  39. package/dist/commands/store/review.js +93 -0
  40. package/dist/commands/store/reviews.d.ts +29 -0
  41. package/dist/commands/store/reviews.js +170 -0
  42. package/dist/commands/store/tip.d.ts +20 -0
  43. package/dist/commands/store/tip.js +57 -0
  44. package/dist/commands/tokens/create.d.ts +28 -0
  45. package/dist/commands/tokens/create.js +136 -0
  46. package/dist/commands/tokens/list.d.ts +11 -0
  47. package/dist/commands/tokens/list.js +53 -0
  48. package/dist/commands/tokens/revoke.d.ts +14 -0
  49. package/dist/commands/tokens/revoke.js +42 -0
  50. package/dist/lib/access-tokens.d.ts +59 -0
  51. package/dist/lib/access-tokens.js +50 -0
  52. package/dist/lib/auth-core-init.d.ts +6 -0
  53. package/dist/lib/auth-core-init.js +28 -6
  54. package/dist/lib/auth-storage.d.ts +5 -0
  55. package/dist/lib/auth-storage.js +8 -0
  56. package/dist/lib/config.d.ts +8 -0
  57. package/dist/lib/credential-resolver.d.ts +1 -1
  58. package/dist/lib/credential-resolver.js +15 -3
  59. package/dist/lib/daemonBroker.d.ts +48 -2
  60. package/dist/lib/daemonBroker.js +193 -15
  61. package/dist/lib/daemonBrokerRefusal.d.ts +14 -0
  62. package/dist/lib/daemonBrokerRefusal.js +18 -1
  63. package/dist/lib/dedicated-ssh.d.ts +9 -0
  64. package/dist/lib/dedicated-ssh.js +232 -63
  65. package/dist/lib/harness-context.d.ts +8 -0
  66. package/dist/lib/harness-context.js +55 -0
  67. package/dist/lib/login.js +4 -1
  68. package/dist/lib/node-adapter.js +2 -2
  69. package/dist/lib/publisher-profile-flags.d.ts +19 -0
  70. package/dist/lib/publisher-profile-flags.js +48 -0
  71. package/dist/lib/refresh.d.ts +1 -1
  72. package/dist/lib/refresh.js +10 -1
  73. package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
  74. package/dist/lib/ssh-ordered-bytes.js +31 -0
  75. package/dist/lib/usd-micros.d.ts +9 -0
  76. package/dist/lib/usd-micros.js +21 -0
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -2
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +9 -5
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +25 -0
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +35 -1
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -2
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -2
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +25 -0
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +33 -0
  93. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  94. package/dist/node_modules/@skrr-ai/data-provider/index.js +4302 -4249
  95. package/oclif.manifest.json +41314 -39972
  96. package/package.json +28 -4
@@ -52,6 +52,10 @@ export declare abstract class BaseCommand extends Command {
52
52
  * from here.
53
53
  */
54
54
  protected resolvedCredential: ResolvedCredential;
55
+ /** Re-authorize a long-lived transport using this command's credential
56
+ * source. A daemon handoff is redeemed again; its refresh family is never
57
+ * borrowed or written into the CLI's own login. */
58
+ protected refreshTransportToken(): Promise<string>;
55
59
  /**
56
60
  * Where `resolvedCredential` really came from when its `source` slot cannot
57
61
  * say — an in-memory daemon hand-off or Agent session token is recorded as
@@ -467,5 +471,21 @@ export declare function describeDaemonScopedCredential(bin: string, scope?: Inco
467
471
  * question. Merging the two is the mistake its own comment warns about.
468
472
  */
469
473
  export declare function isTypedServerRefusal(body: Record<string, unknown> | null): boolean;
474
+ /**
475
+ * Did the API itself answer this gateway-class status (502/503/504) with a
476
+ * typed code?
477
+ *
478
+ * Those statuses mean "outage" only when something IN FRONT of the API wrote
479
+ * them — the load balancer or CDN, whose bodies are HTML or empty, and which
480
+ * `parseErrorBody` therefore reads as null. A JSON body carrying `code` or
481
+ * `errorType` was written by a route or middleware that ran and chose the
482
+ * status: the API was up, and the request did not time out at the gateway.
483
+ *
484
+ * Checked AFTER `isTypedServerRefusal`, which handles the subset that also
485
+ * explains itself; this covers the coded rest — including production bodies
486
+ * whose message the 5xx redaction boundary replaced. A JSON body with no code
487
+ * stays in the outage branch: it says nothing a reader could act on.
488
+ */
489
+ export declare function isApplicationCodedUnavailable(status: number | undefined, body: Record<string, unknown> | null): boolean;
470
490
  export declare function isRetryableFailure(status: number | undefined, body: Record<string, unknown> | null): boolean;
471
491
  export { Flags };
@@ -15,6 +15,7 @@ exports.describeForbidden = describeForbidden;
15
15
  exports.isDaemonScopedCredential = isDaemonScopedCredential;
16
16
  exports.describeDaemonScopedCredential = describeDaemonScopedCredential;
17
17
  exports.isTypedServerRefusal = isTypedServerRefusal;
18
+ exports.isApplicationCodedUnavailable = isApplicationCodedUnavailable;
18
19
  exports.isRetryableFailure = isRetryableFailure;
19
20
  const node_fs_1 = require("node:fs");
20
21
  const core_1 = require("@oclif/core");
@@ -54,6 +55,7 @@ const edge_error_page_1 = require("./lib/edge-error-page");
54
55
  const step_up_1 = require("./lib/step-up");
55
56
  const delegated_cli_1 = require("./lib/delegated-cli");
56
57
  const dedicated_guest_1 = require("./lib/dedicated-guest");
58
+ const harness_context_1 = require("./lib/harness-context");
57
59
  const tasks_1 = require("./lib/tasks");
58
60
  function assigneePayloadNeedsResolution(payload) {
59
61
  const fields = [
@@ -155,6 +157,30 @@ class BaseCommand extends core_1.Command {
155
157
  * from here.
156
158
  */
157
159
  resolvedCredential;
160
+ /** Re-authorize a long-lived transport using this command's credential
161
+ * source. A daemon handoff is redeemed again; its refresh family is never
162
+ * borrowed or written into the CLI's own login. */
163
+ async refreshTransportToken() {
164
+ if (this.credentialProvenance?.kind === 'daemon-handoff') {
165
+ const result = await (0, daemonBroker_1.redeemBrokeredHandoffToken)(true);
166
+ if (!result?.accessToken)
167
+ throw new Error('The local daemon could not renew CLI authentication.');
168
+ this.resolvedCredential = {
169
+ token: result.accessToken,
170
+ source: 'env-token',
171
+ kind: (0, auth_core_1.classifyTokenKind)(result.accessToken),
172
+ };
173
+ }
174
+ else {
175
+ this.resolvedCredential = (await (0, refresh_1.ensureFreshCliCredential)({
176
+ credential: this.resolvedCredential,
177
+ bareMode: this.bareMode,
178
+ })).credential;
179
+ }
180
+ if (!this.resolvedCredential.token)
181
+ throw new Error('CLI authentication is unavailable.');
182
+ return this.resolvedCredential.token;
183
+ }
158
184
  /**
159
185
  * Where `resolvedCredential` really came from when its `source` slot cannot
160
186
  * say — an in-memory daemon hand-off or Agent session token is recorded as
@@ -253,6 +279,21 @@ class BaseCommand extends core_1.Command {
253
279
  (0, data_provider_1.setHttpAdapter)((0, node_adapter_1.createNodeAdapter)());
254
280
  return;
255
281
  }
282
+ // OSK-13254 — a process the daemon started for an agent or tool borrows
283
+ // from the daemon and never owns the person's stored session, so the
284
+ // commands that create, replace or end that session are not its to run.
285
+ const harnessContext = (0, harness_context_1.isHarnessCliContext)();
286
+ if (harnessContext && this.id && harness_context_1.HARNESS_REFUSED_AUTH_COMMANDS.has(this.id)) {
287
+ this.failWithCliError({
288
+ message: `\`${this.config.bin} ${this.id}\` is not available here: this process was started by the skrr ` +
289
+ 'background service for an agent or tool, and skrr in it borrows short-lived tokens from ' +
290
+ 'that service instead of holding a sign-in. Run it in your own terminal.',
291
+ code: 'HARNESS_CONTEXT_AUTH_COMMAND',
292
+ exit: 2,
293
+ retryable: false,
294
+ });
295
+ return;
296
+ }
256
297
  // A replacement guest keeps its workspace but receives a fresh Linux
257
298
  // machine-id. Retire the prior instance's machine-bound CLI envelopes
258
299
  // before migration/prefetch reads them; the current daemon handoff below
@@ -261,7 +302,9 @@ class BaseCommand extends core_1.Command {
261
302
  // One-shot migration: legacy token/refreshToken in cli-config.json
262
303
  // → keychain (macOS) or cli-auth.json (elsewhere). Idempotent.
263
304
  try {
264
- (0, migrate_1.migrateAuthOutOfConfig)();
305
+ // A borrower never writes the person's stored session (OSK-13254).
306
+ if (!harnessContext)
307
+ (0, migrate_1.migrateAuthOutOfConfig)();
265
308
  }
266
309
  catch {
267
310
  // Never block a command on migration failure; the resolver still
@@ -317,9 +360,18 @@ class BaseCommand extends core_1.Command {
317
360
  allowUnreadableStoredCredential: this.id === 'login' || this.id === 'logout' || this.id === 'pair',
318
361
  });
319
362
  let ignoredStoredCredential = null;
320
- if (!this.resolvedCredential.token && !this.bareMode && !parsed.token) {
363
+ if (!this.resolvedCredential.token && !this.bareMode && !parsed.token && !harnessContext) {
321
364
  ignoredStoredCredential = (0, auth_storage_1.readFromBackend)(this.cliConfig.baseURL);
322
365
  }
366
+ // OSK-13254 — nothing at all is stored for this profile: a first sign-in,
367
+ // which may ADOPT a CLI session of its own through the daemon instead of
368
+ // borrowing per process. Anything stored — a refused session, another
369
+ // server's, another config root's — is left alone and the daemon lends.
370
+ const nothingStored = !harnessContext &&
371
+ !!ignoredStoredCredential &&
372
+ !ignoredStoredCredential.bundle &&
373
+ ignoredStoredCredential.ownerStatus !== 'foreign' &&
374
+ (ignoredStoredCredential.originStatus ?? 'none') === 'none';
323
375
  // Daemon-as-broker auto-mint. When the resolver came up empty AND
324
376
  // a local `oversky` daemon is already authenticated for this user,
325
377
  // silently mint a sibling cli-scope refresh family via the daemon's
@@ -347,8 +399,14 @@ class BaseCommand extends core_1.Command {
347
399
  cliConfig: this.cliConfig,
348
400
  bareMode: this.bareMode,
349
401
  commandId: this.id,
402
+ adopt: nothingStored,
350
403
  }));
351
- if (autoResult.triggered && autoResult.outcome) {
404
+ if (autoResult.adoptedBySibling) {
405
+ // Another `skrr` adopted this profile's session while this one waited
406
+ // for the adoption lock: read it like any stored session.
407
+ this.resolvedCredential = await (0, credential_resolver_1.resolveCredential)({ bareMode: this.bareMode });
408
+ }
409
+ else if (autoResult.triggered && autoResult.outcome) {
352
410
  if (autoResult.outcome.ok) {
353
411
  // Persist the cliId now so a sibling skrr process spawned
354
412
  // immediately after sees the same identity.
@@ -396,6 +454,14 @@ class BaseCommand extends core_1.Command {
396
454
  : {}),
397
455
  }
398
456
  : null;
457
+ if (!autoResult.outcome.brokered && nothingStored) {
458
+ // Said once, on stderr, the moment it happens: a session now exists
459
+ // that the person did not type a command to create (OSK-13254).
460
+ process.stderr.write(`[skrr] Signed this CLI in through the skrr background service on this computer. ` +
461
+ `The session is saved to ${(0, credential_resolver_1.describeCredentialSource)(this.resolvedCredential.source)} ` +
462
+ `and renews itself, so skrr keeps working while the service restarts. ` +
463
+ `It is listed under Active Sessions; \`${this.config.bin} logout\` ends it.\n`);
464
+ }
399
465
  }
400
466
  else if ((this.brokerRefusal =
401
467
  (0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin) ??
@@ -1176,11 +1242,15 @@ class BaseCommand extends core_1.Command {
1176
1242
  // `login` sends a CI job to do the one thing it cannot, and hides the
1177
1243
  // real mistake (dogfood OSK-4693). Name the credential instead.
1178
1244
  const presentedCiToken = Boolean(this.resolvedCredential?.token?.startsWith('osk_ci_'));
1245
+ // A personal access token (OSK-13208) has no session behind it to renew
1246
+ // and no daemon to ask: the server's own reason (expired, revoked) IS
1247
+ // the answer, and the repair is a new token, not `login`.
1248
+ const presentedPat = Boolean(this.resolvedCredential?.token?.startsWith('osk_pat_'));
1179
1249
  // OSK-12116 — the 401 is usually the SECOND failure: the token expired
1180
1250
  // (routine) and renewing it was refused (the fault). The server's code
1181
1251
  // for the retry is `TOKEN_EXPIRED`, which names the routine half; the
1182
1252
  // latched refresh refusal names the half the reader can act on.
1183
- const refreshRefusal = presentedCiToken
1253
+ const refreshRefusal = presentedCiToken || presentedPat
1184
1254
  ? null
1185
1255
  : (0, auth_failure_1.describeRefreshRefusal)(readRefreshRefusal(), {
1186
1256
  bin: this.config.bin,
@@ -1190,9 +1260,9 @@ class BaseCommand extends core_1.Command {
1190
1260
  // refused refresh, and the daemon refused too. Its reason decides the
1191
1261
  // repair (OSK-12149, OSK-12151), and on a Dedicated guest there is no
1192
1262
  // login to run, so its remedy replaces the generic "Run login".
1193
- const brokerRefusal = presentedCiToken || !e.brokerFailure
1263
+ const brokerRefusal = presentedCiToken || presentedPat || !e.brokerFailure
1194
1264
  ? null
1195
- : (0, daemonBrokerRefusal_1.describeBrokerRefusal)(e.brokerFailure, this.config.bin);
1265
+ : (0, daemonBrokerRefusal_1.describeBrokerFailure)(e.brokerFailure, this.config.bin);
1196
1266
  const renewal = refreshRefusal
1197
1267
  ? refreshRefusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
1198
1268
  : "This CLI's own sign-in could not be renewed.";
@@ -1209,6 +1279,12 @@ class BaseCommand extends core_1.Command {
1209
1279
  `daemon (\`OVERSKY_TOKEN=<token> skrrd start\`). ${this.config.bin} commands do ` +
1210
1280
  `not accept it — sign in with \`${this.config.bin} login\` instead.`;
1211
1281
  }
1282
+ else if (presentedPat) {
1283
+ authMessage =
1284
+ `Authentication failed: ${bodyMessage || 'the personal access token was refused.'} ` +
1285
+ `Create a new token with \`${this.config.bin} tokens create\` from a signed-in terminal ` +
1286
+ `and update the secret that sets SKRR_TOKEN.`;
1287
+ }
1212
1288
  else if (brokerRefusal) {
1213
1289
  authMessage = `Authentication failed. ${renewal} ${brokerRefusal.headline}\n→ ${brokerRefusal.remedy}`;
1214
1290
  }
@@ -1332,6 +1408,45 @@ class BaseCommand extends core_1.Command {
1332
1408
  requestId: stringProp(body, 'requestId') || e.requestId,
1333
1409
  });
1334
1410
  }
1411
+ else if (isApplicationCodedUnavailable(e.status, body)) {
1412
+ // A 502/503/504 the API ITSELF wrote, with a typed code — not a page
1413
+ // from the load balancer or CDN. The outage branch below would say the
1414
+ // platform is down, absolve this command, and (for a write) call the
1415
+ // outcome unknown because "the gateway may have timed out after the
1416
+ // server committed it". None of that holds when the API answered.
1417
+ //
1418
+ // Observed 2026-10-01: `skrr agents chat --daemon <id>` printed exactly
1419
+ // that outage paragraph for `POST /api/agentic/chat` → 503
1420
+ // `preferred_daemon_unavailable` — "The selected local daemon is not
1421
+ // connected" — a synchronous refusal before anything was enqueued,
1422
+ // while skrr.ai served every other request. The reader went looking
1423
+ // for a platform outage.
1424
+ //
1425
+ // No incident is recorded: one route refusing in its own words is not
1426
+ // evidence the control plane is failing. `isOutageFailure` is left
1427
+ // alone because it also decides whether the outbox queues a write.
1428
+ const retryable = isRetryableFailure(e.status, body);
1429
+ const authored = bodyMessage && !isRedactedServerMessage(bodyMessage) ? bodyMessage : '';
1430
+ const requestId = stringProp(body, 'requestId') || e.requestId;
1431
+ this.failWithCliError({
1432
+ message: say(`${this.cliConfig.baseURL} refused this request (HTTP ${e.status}, ${bodyCode})` +
1433
+ `${authored ? `: ${authored}` : '.'} ` +
1434
+ 'The API answered this itself, so the request did not time out at the gateway. ' +
1435
+ (authored ? '' : 'It did not say why in this response. ') +
1436
+ (retryable
1437
+ ? 'Retrying may succeed once that condition clears.'
1438
+ : 'Retrying the same request will not help until that condition changes.') +
1439
+ (requestId && !authored
1440
+ ? ` Request id ${requestId} — quote it when reporting this.`
1441
+ : '')),
1442
+ code: bodyCode,
1443
+ status: e.status,
1444
+ exit: retryable ? 5 : 1,
1445
+ retryable,
1446
+ details: body ?? undefined,
1447
+ ...(requestId ? { requestId } : {}),
1448
+ });
1449
+ }
1335
1450
  else if ((0, outbox_1.isOutageFailure)(e)) {
1336
1451
  // A gateway/unavailable status is an OUTAGE, and saying so is the whole
1337
1452
  // point. During the OSK-2806 incident these surfaced as bare
@@ -1512,7 +1627,7 @@ class BaseCommand extends core_1.Command {
1512
1627
  cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
1513
1628
  });
1514
1629
  const brokerRefusal = e.brokerFailure
1515
- ? (0, daemonBrokerRefusal_1.describeBrokerRefusal)(e.brokerFailure, this.config.bin)
1630
+ ? (0, daemonBrokerRefusal_1.describeBrokerFailure)(e.brokerFailure, this.config.bin)
1516
1631
  : null;
1517
1632
  const why = (refusal
1518
1633
  ? refusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
@@ -2030,6 +2145,35 @@ function isTypedServerRefusal(body) {
2030
2145
  return false;
2031
2146
  return (typeof body.cause === 'string' || typeof body.remedy === 'string' || body.retryable === false);
2032
2147
  }
2148
+ /**
2149
+ * Did the API itself answer this gateway-class status (502/503/504) with a
2150
+ * typed code?
2151
+ *
2152
+ * Those statuses mean "outage" only when something IN FRONT of the API wrote
2153
+ * them — the load balancer or CDN, whose bodies are HTML or empty, and which
2154
+ * `parseErrorBody` therefore reads as null. A JSON body carrying `code` or
2155
+ * `errorType` was written by a route or middleware that ran and chose the
2156
+ * status: the API was up, and the request did not time out at the gateway.
2157
+ *
2158
+ * Checked AFTER `isTypedServerRefusal`, which handles the subset that also
2159
+ * explains itself; this covers the coded rest — including production bodies
2160
+ * whose message the 5xx redaction boundary replaced. A JSON body with no code
2161
+ * stays in the outage branch: it says nothing a reader could act on.
2162
+ */
2163
+ function isApplicationCodedUnavailable(status, body) {
2164
+ if (status !== 502 && status !== 503 && status !== 504)
2165
+ return false;
2166
+ return Boolean(stringProp(body, 'code') || stringProp(body, 'errorType'));
2167
+ }
2168
+ /**
2169
+ * The sentence `redactServerErrorResponses` substitutes for a 5xx message in
2170
+ * production. It is the boundary's, not the route's, so it is never relayed as
2171
+ * if it explained anything.
2172
+ */
2173
+ const REDACTED_SERVER_MESSAGE = 'An unexpected error occurred.';
2174
+ function isRedactedServerMessage(message) {
2175
+ return message.trim() === REDACTED_SERVER_MESSAGE;
2176
+ }
2033
2177
  function isRetryableFailure(status, body) {
2034
2178
  const declared = body?.retryable;
2035
2179
  if (typeof declared === 'boolean')
@@ -1,3 +1,4 @@
1
+ import type { StoreReleaseLicense } from '@skrr-ai/data-provider';
1
2
  import { BaseCommand } from '../../base-command';
2
3
  /**
3
4
  * `--capability-rationale <permission-key>=<note>` → the publisher notes a
@@ -18,6 +19,16 @@ export declare function parseReleaseDependencies(entries: readonly string[] | un
18
19
  }>;
19
20
  error?: string;
20
21
  };
22
+ /**
23
+ * `--license` / `--remix` → the `license` declaration a public release carries
24
+ * (SUP11). Values are already constrained by the flags' `options`; this only
25
+ * enforces the pairing the flags cannot express: a remix grant is part of a
26
+ * license declaration, so it is meaningless without one.
27
+ */
28
+ export declare function buildReleaseLicense(licenseId: string | undefined, remix: string | undefined): {
29
+ license?: StoreReleaseLicense;
30
+ error?: string;
31
+ };
21
32
  export default class AgentsVisibility extends BaseCommand {
22
33
  static description: string;
23
34
  static examples: string[];
@@ -36,6 +47,9 @@ export default class AgentsVisibility extends BaseCommand {
36
47
  'capability-rationale': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
37
48
  language: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
38
49
  dependency: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
50
+ license: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
51
+ remix: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
52
+ 'source-visibility': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
39
53
  };
40
54
  run(): Promise<void>;
41
55
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseCapabilityRationales = parseCapabilityRationales;
4
4
  exports.parseReleaseDependencies = parseReleaseDependencies;
5
+ exports.buildReleaseLicense = buildReleaseLicense;
5
6
  const core_1 = require("@oclif/core");
6
7
  const data_provider_1 = require("@skrr-ai/data-provider");
7
8
  const base_command_1 = require("../../base-command");
@@ -48,6 +49,25 @@ function parseReleaseDependencies(entries) {
48
49
  }
49
50
  return { dependencies };
50
51
  }
52
+ /**
53
+ * `--license` / `--remix` → the `license` declaration a public release carries
54
+ * (SUP11). Values are already constrained by the flags' `options`; this only
55
+ * enforces the pairing the flags cannot express: a remix grant is part of a
56
+ * license declaration, so it is meaningless without one.
57
+ */
58
+ function buildReleaseLicense(licenseId, remix) {
59
+ if (!licenseId) {
60
+ return remix
61
+ ? { error: '--remix is part of a license declaration; pass it with --license <id>.' }
62
+ : {};
63
+ }
64
+ return {
65
+ license: {
66
+ id: licenseId,
67
+ ...(remix ? { remix: remix } : {}),
68
+ },
69
+ };
70
+ }
51
71
  class AgentsVisibility extends base_command_1.BaseCommand {
52
72
  static description = 'Set the visibility of an agent';
53
73
  static examples = [
@@ -57,6 +77,8 @@ class AgentsVisibility extends base_command_1.BaseCommand {
57
77
  '<%= config.bin %> agents visibility <agent-id> --public --capability-rationale "network:*=Looks up flight prices on the web"',
58
78
  '<%= config.bin %> agents visibility <agent-id> --public --language en --language zh-Hans',
59
79
  '<%= config.bin %> agents visibility <agent-id> --public --dependency skill:<skill-id>@<store-release-id>',
80
+ '<%= config.bin %> agents visibility <agent-id> --public --license mit --remix attributed --source-visibility public',
81
+ '<%= config.bin %> agents visibility <agent-id> --public --license proprietary --source-visibility sealed',
60
82
  ];
61
83
  static args = {
62
84
  id: core_1.Args.string({ description: 'Agent ID', required: true, ignoreStdin: true }),
@@ -96,6 +118,18 @@ class AgentsVisibility extends base_command_1.BaseCommand {
96
118
  description: 'Pin an attached Skill or Action to its immutable Store release: skill:<id>@<store-release-id> (repeatable)',
97
119
  multiple: true,
98
120
  }),
121
+ license: core_1.Flags.string({
122
+ description: 'The license the public release is offered under. Recorded on the release and shown on the listing; proprietary grants no reuse',
123
+ options: [...data_provider_1.STORE_RELEASE_LICENSE_IDS],
124
+ }),
125
+ remix: core_1.Flags.string({
126
+ description: "Whether a buyer's installed copy may be published as a derived listing: attributed (the fork credits this release) or none. Needs --license; defaults to none",
127
+ options: [...data_provider_1.STORE_RELEASE_REMIX_GRANTS],
128
+ }),
129
+ 'source-visibility': core_1.Flags.string({
130
+ description: "Who may read the release's source, independent of price: licensed (license holders), public (anyone who sees the listing) or sealed (you only). Omit to keep the price-derived rule",
131
+ options: [...data_provider_1.STORE_RELEASE_SOURCE_VISIBILITIES],
132
+ }),
99
133
  };
100
134
  async run() {
101
135
  this.requireAuth();
@@ -128,6 +162,15 @@ class AgentsVisibility extends base_command_1.BaseCommand {
128
162
  exit: 1,
129
163
  });
130
164
  }
165
+ const { license, error: licenseError } = buildReleaseLicense(flags.license, flags.remix);
166
+ if (licenseError)
167
+ this.error(licenseError, { exit: 1 });
168
+ const sourceVisibility = flags['source-visibility'];
169
+ // The server refuses both declarations with isPublic: false — they are
170
+ // terms of a public release, and an unpublish creates none.
171
+ if ((license || sourceVisibility) && !isPublic) {
172
+ this.error('--license, --remix and --source-visibility are terms of a public release; pass them with --public.', { exit: 1 });
173
+ }
131
174
  let result;
132
175
  try {
133
176
  result = await data_provider_1.dataService.setAgentVisibility({
@@ -141,6 +184,8 @@ class AgentsVisibility extends base_command_1.BaseCommand {
141
184
  capabilityRationales,
142
185
  supportedLanguages: flags.language,
143
186
  dependencies,
187
+ license,
188
+ sourceVisibility,
144
189
  });
145
190
  }
146
191
  catch (err) {
@@ -152,6 +197,13 @@ class AgentsVisibility extends base_command_1.BaseCommand {
152
197
  }
153
198
  const label = isPublic ? 'public' : 'private';
154
199
  this.log(`Set agent ${args.id} visibility: ${label}`);
200
+ const recorded = result;
201
+ if (recorded?.license) {
202
+ this.log(`License: ${recorded.license.id} (remix: ${recorded.license.remix})`);
203
+ }
204
+ if (recorded?.sourceVisibility) {
205
+ this.log(`Source visibility: ${recorded.sourceVisibility}`);
206
+ }
155
207
  }
156
208
  }
157
209
  exports.default = AgentsVisibility;
@@ -43,7 +43,6 @@ import { BaseCommand } from '../base-command';
43
43
  */
44
44
  export default class CreateToken extends BaseCommand {
45
45
  static description: string;
46
- static aliases: string[];
47
46
  static examples: string[];
48
47
  static flags: {
49
48
  name: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
@@ -49,7 +49,9 @@ const api_fetch_1 = require("../lib/api-fetch");
49
49
  class CreateToken extends base_command_1.BaseCommand {
50
50
  static description = 'Create a long-lived token for bootstrapping a daemon on a CI/headless machine. ' +
51
51
  'Printed exactly once. Not accepted by `skrr` commands themselves.';
52
- static aliases = ['tokens:create'];
52
+ // `tokens:create` used to alias this command. It now names personal access
53
+ // tokens (OSK-13208), the credential `skrr` itself accepts; this one only
54
+ // bootstraps a daemon, and sharing the name is what sent CI here.
53
55
  static examples = [
54
56
  '<%= config.bin %> create-token --name "github-actions-prod" --ttl 1y',
55
57
  '<%= config.bin %> create-token --name "local-ci" --ttl 30d',
@@ -49,7 +49,7 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
49
49
  catch (err) {
50
50
  this.failWithCliError({
51
51
  message: err instanceof Error ? err.message : String(err),
52
- code: 'FOLLOWUP_SUBJECT_INVALID',
52
+ code: 'FOLLOWUP_INVALID_FIELD',
53
53
  exit: 2,
54
54
  retryable: false,
55
55
  });
@@ -8,7 +8,6 @@ import { BaseCommand } from '../base-command';
8
8
  */
9
9
  export default class ListTokens extends BaseCommand {
10
10
  static description: string;
11
- static aliases: string[];
12
11
  static flags: {
13
12
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
13
  all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
@@ -13,7 +13,7 @@ const api_fetch_1 = require("../lib/api-fetch");
13
13
  */
14
14
  class ListTokens extends base_command_1.BaseCommand {
15
15
  static description = 'List your long-lived CI tokens';
16
- static aliases = ['tokens:list'];
16
+ // No `tokens:list` alias: `skrr tokens` is personal access tokens (OSK-13208).
17
17
  static flags = {
18
18
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
19
19
  all: core_1.Flags.boolean({
@@ -126,6 +126,16 @@ class Login extends base_command_1.BaseCommand {
126
126
  `(${explicitBaseUrl}). Pass one of them.`, { exit: 2 });
127
127
  }
128
128
  const requestedBaseUrl = envBaseUrl ?? explicitBaseUrl;
129
+ // OSK-13254 — an explicit sign-in lifts what `skrr logout` set: from here a
130
+ // command that finds no stored session may adopt one through the daemon
131
+ // again. Cleared up front, because asking to sign in is the decision.
132
+ {
133
+ const current = (0, config_1.loadConfig)();
134
+ if (current.adoptDaemonSession === false) {
135
+ const { adoptDaemonSession: _cleared, ...rest } = current;
136
+ (0, config_1.saveConfig)(rest);
137
+ }
138
+ }
129
139
  /**
130
140
  * Undo the base-URL write if this login does not complete.
131
141
  *
@@ -260,6 +260,10 @@ class Logout extends base_command_1.BaseCommand {
260
260
  ...(agentSetting.value ? { [first_party_harness_managed_1.AGENT_CONFIG_KEY]: agentSetting.value } : {}),
261
261
  ...(cfg.credentials ? { credentials: cfg.credentials } : {}),
262
262
  migrated: { ...(cfg.migrated ?? {}), authOutOfConfig: true },
263
+ // OSK-13254 — signed out means no durable session reappears behind this
264
+ // command. The daemon may still LEND this CLI short-lived tokens (said
265
+ // below); `skrr login` clears the flag.
266
+ adoptDaemonSession: false,
263
267
  });
264
268
  const finalMessage = flags['all-devices']
265
269
  ? allDevicesError
@@ -44,6 +44,7 @@ class DedicatedMachinesSshProxy extends dedicated_lease_command_1.DedicatedLease
44
44
  code = await (0, dedicated_ssh_1.runSshProxy)({
45
45
  baseURL: this.cliConfig.baseURL,
46
46
  token: this.resolvedCredential.token,
47
+ getToken: () => this.refreshTransportToken(),
47
48
  leaseId: lease,
48
49
  input: process.stdin,
49
50
  output: process.stdout,
@@ -11,7 +11,6 @@ import { BaseCommand } from '../base-command';
11
11
  */
12
12
  export default class RevokeToken extends BaseCommand {
13
13
  static description: string;
14
- static aliases: string[];
15
14
  static args: {
16
15
  id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
17
16
  };
@@ -15,7 +15,7 @@ const api_fetch_1 = require("../lib/api-fetch");
15
15
  */
16
16
  class RevokeToken extends base_command_1.BaseCommand {
17
17
  static description = 'Revoke a long-lived CI token by ID or unique prefix';
18
- static aliases = ['tokens:revoke'];
18
+ // No `tokens:revoke` alias: `skrr tokens` is personal access tokens (OSK-13208).
19
19
  static args = {
20
20
  id: core_1.Args.string({
21
21
  name: 'id',
@@ -8,6 +8,7 @@ export default class StoreFollow extends BaseCommand {
8
8
  };
9
9
  static flags: {
10
10
  undo: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
+ status: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
11
12
  json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
12
13
  };
13
14
  run(): Promise<void>;
@@ -18,13 +18,19 @@ class StoreFollow extends base_command_1.BaseCommand {
18
18
  }),
19
19
  };
20
20
  static flags = {
21
- undo: core_1.Flags.boolean({ description: 'Unfollow the publisher' }),
21
+ undo: core_1.Flags.boolean({ description: 'Unfollow the publisher', exclusive: ['status'] }),
22
+ status: core_1.Flags.boolean({ description: 'Show whether you follow the publisher; change nothing' }),
22
23
  json: core_1.Flags.boolean({ description: 'Output as JSON' }),
23
24
  };
24
25
  async run() {
25
26
  this.requireAuth();
26
27
  const { args, flags } = await this.parse(StoreFollow);
27
28
  try {
29
+ if (flags.status) {
30
+ const overlay = await data_provider_1.dataService.getStorePublisherFollowOverlay(args.handle);
31
+ this.log(JSON.stringify(overlay, null, 2));
32
+ return;
33
+ }
28
34
  const result = flags.undo
29
35
  ? await data_provider_1.dataService.unfollowStorePublisherByHandle(args.handle)
30
36
  : await data_provider_1.dataService.followStorePublisherByHandle(args.handle);
@@ -0,0 +1,11 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /** The publishers you follow, newest first, one page at a time. */
3
+ export default class StoreFollowsList extends BaseCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
8
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
9
+ };
10
+ run(): Promise<void>;
11
+ }
@@ -0,0 +1,29 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const core_1 = require("@oclif/core");
4
+ const data_provider_1 = require("@skrr-ai/data-provider");
5
+ const base_command_1 = require("../../../base-command");
6
+ /** The publishers you follow, newest first, one page at a time. */
7
+ class StoreFollowsList extends base_command_1.BaseCommand {
8
+ static description = 'List the store publishers you follow';
9
+ static examples = [
10
+ '<%= config.bin %> store follows list',
11
+ '<%= config.bin %> store follows list --cursor <next>',
12
+ ];
13
+ static flags = {
14
+ cursor: core_1.Flags.string({ description: 'Continue from a previous page' }),
15
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
16
+ };
17
+ async run() {
18
+ this.requireAuth();
19
+ const { flags } = await this.parse(StoreFollowsList);
20
+ try {
21
+ const result = await data_provider_1.dataService.getStorePublisherFollows(flags.cursor);
22
+ this.log(JSON.stringify(result, null, 2));
23
+ }
24
+ catch (err) {
25
+ this.handleApiError(err);
26
+ }
27
+ }
28
+ }
29
+ exports.default = StoreFollowsList;
@@ -0,0 +1,16 @@
1
+ import { BaseCommand } from '../../../base-command';
2
+ /**
3
+ * Remove a follow by its follow id — the one way to drop a follow whose
4
+ * publisher has since renamed, hidden or lost its handle.
5
+ */
6
+ export default class StoreFollowsRemove extends BaseCommand {
7
+ static description: string;
8
+ static examples: string[];
9
+ static args: {
10
+ followId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
11
+ };
12
+ static flags: {
13
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
14
+ };
15
+ run(): Promise<void>;
16
+ }