@skrr-ai/cli 0.1.51 → 0.1.52

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 (53) hide show
  1. package/dist/base-command.d.ts +7 -0
  2. package/dist/base-command.js +38 -8
  3. package/dist/commands/agents/create.js +8 -4
  4. package/dist/commands/agents/update.d.ts +23 -0
  5. package/dist/commands/agents/update.js +54 -9
  6. package/dist/commands/commitments/next-tick.js +9 -1
  7. package/dist/commands/context.js +2 -1
  8. package/dist/commands/daemon/config.js +16 -1
  9. package/dist/commands/doctor.js +8 -1
  10. package/dist/commands/login.d.ts +2 -1
  11. package/dist/commands/login.js +26 -7
  12. package/dist/commands/logout.js +3 -0
  13. package/dist/commands/machines/dedicated/show.js +25 -2
  14. package/dist/commands/tasks/create.d.ts +5 -0
  15. package/dist/commands/tasks/create.js +26 -6
  16. package/dist/commands/tasks/start.d.ts +14 -0
  17. package/dist/commands/tasks/start.js +31 -1
  18. package/dist/commands/tasks/update.js +8 -1
  19. package/dist/commands/whoami.js +5 -3
  20. package/dist/lib/agent-config.d.ts +2 -0
  21. package/dist/lib/agent-config.js +14 -10
  22. package/dist/lib/agentic-stream.d.ts +20 -0
  23. package/dist/lib/agentic-stream.js +39 -5
  24. package/dist/lib/auth-failure.js +4 -1
  25. package/dist/lib/auth-storage.d.ts +24 -0
  26. package/dist/lib/auth-storage.js +342 -11
  27. package/dist/lib/cli-id.d.ts +22 -0
  28. package/dist/lib/cli-id.js +56 -0
  29. package/dist/lib/cli-identity.d.ts +49 -0
  30. package/dist/lib/cli-identity.js +159 -0
  31. package/dist/lib/commitments.d.ts +4 -0
  32. package/dist/lib/commitments.js +12 -1
  33. package/dist/lib/config.d.ts +17 -0
  34. package/dist/lib/config.js +15 -3
  35. package/dist/lib/daemonBroker.d.ts +26 -4
  36. package/dist/lib/daemonBroker.js +82 -10
  37. package/dist/lib/daemonBrokerRefusal.d.ts +51 -0
  38. package/dist/lib/daemonBrokerRefusal.js +127 -0
  39. package/dist/lib/dedicated-machines.d.ts +33 -2
  40. package/dist/lib/dedicated-machines.js +50 -1
  41. package/dist/lib/delegated-cli.js +16 -0
  42. package/dist/lib/first-party-harness-broker.js +6 -4
  43. package/dist/lib/keychain.d.ts +39 -0
  44. package/dist/lib/keychain.js +228 -39
  45. package/dist/lib/label-ref.d.ts +34 -0
  46. package/dist/lib/label-ref.js +68 -1
  47. package/dist/lib/login.js +24 -12
  48. package/dist/lib/oauthLogin.js +2 -8
  49. package/dist/lib/refresh.js +6 -2
  50. package/dist/lib/tasks.js +33 -8
  51. package/dist/node_modules/@skrr-ai/data-provider/index.js +3138 -3100
  52. package/oclif.manifest.json +36364 -36355
  53. package/package.json +1 -1
@@ -4,6 +4,7 @@ import { type AssigneeResolution, type MemberKind, type SpaceMember } from './li
4
4
  import { type CliConfig } from './lib/config';
5
5
  import { type OutboxMethod } from './lib/outbox';
6
6
  import { type ResolvedCredential } from './lib/credential-resolver';
7
+ import { type BrokerRefusalDescription } from './lib/daemonBrokerRefusal';
7
8
  import { type DefaultAgentResolution } from './lib/tasks';
8
9
  export declare function assigneePayloadNeedsResolution(payload: Record<string, unknown>): boolean;
9
10
  /**
@@ -68,6 +69,12 @@ export declare abstract class BaseCommand extends Command {
68
69
  */
69
70
  protected resolvedCredential: ResolvedCredential;
70
71
  protected bareMode: boolean;
72
+ /**
73
+ * Set when the auto-broker reached a live daemon and it REFUSED the hand-off.
74
+ * `requireAuth()` reports it instead of a bare "Not signed in", because the
75
+ * daemon's reason decides the repair (OSK-12149, OSK-12151).
76
+ */
77
+ protected brokerRefusal: BrokerRefusalDescription | null;
71
78
  init(): Promise<void>;
72
79
  /**
73
80
  * Read only the two global base flags. A narrow parser — oclif's
@@ -35,6 +35,7 @@ const task_ref_resolver_1 = require("./lib/task-ref-resolver");
35
35
  const space_resolver_1 = require("./lib/space-resolver");
36
36
  const projects_1 = require("./lib/projects");
37
37
  const config_1 = require("./lib/config");
38
+ const cli_id_1 = require("./lib/cli-id");
38
39
  const keychain_2 = require("./lib/keychain");
39
40
  const outbox_1 = require("./lib/outbox");
40
41
  const node_adapter_1 = require("./lib/node-adapter");
@@ -42,6 +43,7 @@ const migrate_1 = require("./lib/migrate");
42
43
  const auth_core_init_1 = require("./lib/auth-core-init");
43
44
  const credential_resolver_1 = require("./lib/credential-resolver");
44
45
  const daemonBroker_1 = require("./lib/daemonBroker");
46
+ const daemonBrokerRefusal_1 = require("./lib/daemonBrokerRefusal");
45
47
  const refresh_1 = require("./lib/refresh");
46
48
  const auth_storage_1 = require("./lib/auth-storage");
47
49
  const update_check_1 = require("./lib/update-check");
@@ -131,6 +133,12 @@ class BaseCommand extends core_1.Command {
131
133
  */
132
134
  resolvedCredential;
133
135
  bareMode = false;
136
+ /**
137
+ * Set when the auto-broker reached a live daemon and it REFUSED the hand-off.
138
+ * `requireAuth()` reports it instead of a bare "Not signed in", because the
139
+ * daemon's reason decides the repair (OSK-12149, OSK-12151).
140
+ */
141
+ brokerRefusal = null;
134
142
  async init() {
135
143
  await super.init();
136
144
  // The profile is resolved from raw argv, BEFORE oclif parses anything.
@@ -269,11 +277,13 @@ class BaseCommand extends core_1.Command {
269
277
  // skrr command reads it through the normal resolver chain — no
270
278
  // per-command broker round-trip.
271
279
  //
272
- // Fail-quiet: any broker failure leaves resolvedCredential null and
273
- // the command's own `requireAuth()` will surface the "Not signed
274
- // in" error a moment later. Mismatched baseURL (a real foot-gun the
275
- // user might not notice) is the one exception — we print a one-
276
- // line hint so they know how to fix it.
280
+ // A broker failure leaves resolvedCredential null and the command's own
281
+ // `requireAuth()` surfaces the error a moment later. It is quiet only when
282
+ // no daemon was there to ask. A daemon that ANSWERED and refused is kept
283
+ // in `brokerRefusal`, and `requireAuth()` reports its reason and the
284
+ // repair that reason implies — "Not signed in. Run `skrr login`" was true
285
+ // and pointed at the wrong fix (OSK-12149). Rate limiting and a mismatched
286
+ // baseURL keep their own one-line hints below.
277
287
  if (!this.resolvedCredential.token) {
278
288
  const autoResult = await (0, daemonBroker_1.maybeAutoBroker)({
279
289
  cliConfig: this.cliConfig,
@@ -318,6 +328,9 @@ class BaseCommand extends core_1.Command {
318
328
  kind: (0, auth_core_1.classifyTokenKind)(accessToken),
319
329
  };
320
330
  }
331
+ else if ((this.brokerRefusal = (0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin))) {
332
+ // Reported by requireAuth(); nothing to print here.
333
+ }
321
334
  else if (autoResult.outcome.reason === 'rate_limited') {
322
335
  // Otherwise the only thing the user sees is "Not signed in", which is
323
336
  // true and points at the wrong repair: they are signed in, the local
@@ -340,7 +353,12 @@ class BaseCommand extends core_1.Command {
340
353
  }
341
354
  }
342
355
  if (!this.resolvedCredential.token && ignoredStoredCredential) {
343
- if (ignoredStoredCredential.originStatus === 'mismatch') {
356
+ const foreign = (0, auth_storage_1.describeForeignCredential)(ignoredStoredCredential);
357
+ if (foreign) {
358
+ process.stderr.write(`[skrr] ${foreign} Run \`skrr login\` to sign this configuration in; the other ` +
359
+ `configuration keeps its own credential.\n`);
360
+ }
361
+ else if (ignoredStoredCredential.originStatus === 'mismatch') {
344
362
  process.stderr.write(`[skrr] Ignoring stored credentials issued by ${ignoredStoredCredential.storedServerOrigin}; ` +
345
363
  `skrr is configured for ${this.cliConfig.baseURL}. Run \`skrr login --base-url ${this.cliConfig.baseURL}\` ` +
346
364
  `or switch back to the issuing server.\n`);
@@ -616,6 +634,18 @@ class BaseCommand extends core_1.Command {
616
634
  /** Abort the command with a helpful message if the user isn't signed in. */
617
635
  requireAuth() {
618
636
  if (!this.resolvedCredential?.token) {
637
+ const refusal = this.brokerRefusal;
638
+ if (refusal) {
639
+ // Same code as the plain case — scripts branch on NOT_SIGNED_IN — with
640
+ // the daemon's answer in the message and in `details.broker`.
641
+ this.failWithCliError({
642
+ message: `Not signed in. ${refusal.headline}\n→ ${refusal.remedy}`,
643
+ code: 'NOT_SIGNED_IN',
644
+ exit: 2,
645
+ retryable: refusal.transient,
646
+ details: { broker: refusal.broker, suggestion: refusal.remedy },
647
+ });
648
+ }
619
649
  this.failWithCliError({
620
650
  message: `Not signed in. Run \`${this.config.bin} login\` first.`,
621
651
  code: 'NOT_SIGNED_IN',
@@ -1011,7 +1041,7 @@ class BaseCommand extends core_1.Command {
1011
1041
  ? null
1012
1042
  : (0, auth_failure_1.describeRefreshRefusal)(readRefreshRefusal(), {
1013
1043
  bin: this.config.bin,
1014
- cliId: this.cliConfig?.cliId,
1044
+ cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
1015
1045
  });
1016
1046
  this.failWithCliError({
1017
1047
  message: say(presentedCiToken
@@ -1309,7 +1339,7 @@ class BaseCommand extends core_1.Command {
1309
1339
  if (e.status === 401) {
1310
1340
  const refusal = (0, auth_failure_1.describeRefreshRefusal)(readRefreshRefusal(), {
1311
1341
  bin: this.config.bin,
1312
- cliId: this.cliConfig?.cliId,
1342
+ cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
1313
1343
  });
1314
1344
  const why = refusal
1315
1345
  ? refusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
@@ -246,22 +246,26 @@ class AgentsCreate extends base_command_1.BaseCommand {
246
246
  '09:00-18:00@America/Los_Angeles',
247
247
  }),
248
248
  // Retired (OSK-11975): a Commitment turn works under the Agent's own
249
- // permissions, so these three are stored but no longer read at runtime.
249
+ // permissions. Hidden, warned on use, and never sent (OSK-12068) — see
250
+ // `mergeAgentConfigFlags`. Kept parseable so an old script still runs.
250
251
  'reasoning-tool-escape': core_1.Flags.string({
251
- description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
252
+ description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
252
253
  options: [...agent_config_1.REASONING_TOOL_ESCAPES],
253
254
  multiple: true,
254
255
  deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
256
+ hidden: true,
255
257
  }),
256
258
  'commitment-responsibility': core_1.Flags.string({
257
- description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
259
+ description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
258
260
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
259
261
  deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
262
+ hidden: true,
260
263
  }),
261
264
  'commitment-responsibility-ceiling': core_1.Flags.string({
262
- description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
265
+ description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
263
266
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
264
267
  deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
268
+ hidden: true,
265
269
  }),
266
270
  'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
267
271
  'tool-resources': core_1.Flags.string({
@@ -55,6 +55,29 @@ export default class AgentsUpdate extends BaseCommand {
55
55
  };
56
56
  run(): Promise<void>;
57
57
  }
58
+ /**
59
+ * The per-Agent mutation fence's "another request holds this right now"
60
+ * refusals. `updateAgent` takes a short lease on the Agent before it writes, so
61
+ * a concurrent writer — or, until OSK-12138, a lagging replica read of a lease
62
+ * row the same request had just created — refuses the update with one of these.
63
+ * Nothing was written, so repeating the identical PATCH is safe.
64
+ */
65
+ export declare const AGENT_FENCE_RETRY_CODES: ReadonlySet<string>;
66
+ /**
67
+ * Run an Agent write, and repeat it ONCE if the fence refused it as transient.
68
+ *
69
+ * Retried only when the server said both things: a fence code AND
70
+ * `retryable: true`. `MARKETPLACE_SOURCE_LIFECYCLE_BUSY` also covers a source
71
+ * that is being deleted or published, where the server answers
72
+ * `retryable: false`, and those must surface rather than be repeated. One retry,
73
+ * after a short pause, because the dogfooded case (OSK-12138) cleared on the
74
+ * first identical retry; anything that survives it is worth showing.
75
+ *
76
+ * Exported for tests.
77
+ */
78
+ export declare function retryOnceOnAgentFence<T>(operation: () => Promise<T>, { delayMs }?: {
79
+ delayMs?: number;
80
+ }): Promise<T>;
58
81
  /**
59
82
  * What changing the agent's default permission mode actually changed
60
83
  * (OSK-12124). It is the AGENT's default, so it governs every conversation,
@@ -1,5 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AGENT_FENCE_RETRY_CODES = void 0;
4
+ exports.retryOnceOnAgentFence = retryOnceOnAgentFence;
3
5
  exports.defaultPermissionModeNote = defaultPermissionModeNote;
4
6
  const core_1 = require("@oclif/core");
5
7
  const data_provider_1 = require("@skrr-ai/data-provider");
@@ -99,22 +101,26 @@ class AgentsUpdate extends base_command_1.BaseCommand {
99
101
  '09:00-18:00@America/Los_Angeles',
100
102
  }),
101
103
  // Retired (OSK-11975): a Commitment turn works under the Agent's own
102
- // permissions, so these three are stored but no longer read at runtime.
104
+ // permissions. Hidden, warned on use, and never sent (OSK-12068) — see
105
+ // `mergeAgentConfigFlags`. Kept parseable so an old script still runs.
103
106
  'reasoning-tool-escape': core_1.Flags.string({
104
- description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
107
+ description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
105
108
  options: [...agent_config_1.REASONING_TOOL_ESCAPES],
106
109
  multiple: true,
107
110
  deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
111
+ hidden: true,
108
112
  }),
109
113
  'commitment-responsibility': core_1.Flags.string({
110
- description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
114
+ description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
111
115
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
112
116
  deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
117
+ hidden: true,
113
118
  }),
114
119
  'commitment-responsibility-ceiling': core_1.Flags.string({
115
- description: "Deprecated — no longer has any effect. Commitment runs use the agent's own permissions.",
120
+ description: "Retired — has no effect and is not sent. Commitment runs use the agent's own permissions.",
116
121
  options: [...agent_config_1.COMMITMENT_RESPONSIBILITY_LEVELS],
117
122
  deprecated: { message: agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED },
123
+ hidden: true,
118
124
  }),
119
125
  'max-turns': core_1.Flags.integer({ description: 'Maximum turns in one agent run (1-500)' }),
120
126
  'tool-resources': core_1.Flags.string({
@@ -184,16 +190,18 @@ class AgentsUpdate extends base_command_1.BaseCommand {
184
190
  this.error(err.message, { exit: 1 });
185
191
  }
186
192
  if (Object.keys(payload).length === 0) {
187
- this.error('Nothing to update. Pass at least one flag (e.g. --name) or --from-json.', {
188
- exit: 1,
189
- });
193
+ const retired = agent_config_1.RETIRED_COMMITMENT_PERMISSION_FLAGS.filter((flag) => flags[flag] !== undefined);
194
+ this.error(retired.length > 0
195
+ ? `Nothing to update: ${retired.map((flag) => `--${flag}`).join(', ')} ` +
196
+ `${retired.length === 1 ? 'is' : 'are'} retired and not sent. ${agent_config_1.COMMITMENT_PERMISSION_FLAG_RETIRED}`
197
+ : 'Nothing to update. Pass at least one flag (e.g. --name) or --from-json.', { exit: 1 });
190
198
  }
191
199
  let updated;
192
200
  try {
193
- updated = await data_provider_1.dataService.updateAgent({
201
+ updated = await retryOnceOnAgentFence(() => data_provider_1.dataService.updateAgent({
194
202
  agent_id: args.id,
195
203
  data: payload,
196
- });
204
+ }));
197
205
  }
198
206
  catch (err) {
199
207
  this.handleApiError(err);
@@ -222,6 +230,43 @@ class AgentsUpdate extends base_command_1.BaseCommand {
222
230
  }
223
231
  }
224
232
  exports.default = AgentsUpdate;
233
+ /**
234
+ * The per-Agent mutation fence's "another request holds this right now"
235
+ * refusals. `updateAgent` takes a short lease on the Agent before it writes, so
236
+ * a concurrent writer — or, until OSK-12138, a lagging replica read of a lease
237
+ * row the same request had just created — refuses the update with one of these.
238
+ * Nothing was written, so repeating the identical PATCH is safe.
239
+ */
240
+ exports.AGENT_FENCE_RETRY_CODES = new Set([
241
+ 'MARKETPLACE_SOURCE_LIFECYCLE_RACE',
242
+ 'MARKETPLACE_SOURCE_LIFECYCLE_BUSY',
243
+ 'MARKETPLACE_SOURCE_LIFECYCLE_CAS_LOST',
244
+ ]);
245
+ /**
246
+ * Run an Agent write, and repeat it ONCE if the fence refused it as transient.
247
+ *
248
+ * Retried only when the server said both things: a fence code AND
249
+ * `retryable: true`. `MARKETPLACE_SOURCE_LIFECYCLE_BUSY` also covers a source
250
+ * that is being deleted or published, where the server answers
251
+ * `retryable: false`, and those must surface rather than be repeated. One retry,
252
+ * after a short pause, because the dogfooded case (OSK-12138) cleared on the
253
+ * first identical retry; anything that survives it is worth showing.
254
+ *
255
+ * Exported for tests.
256
+ */
257
+ async function retryOnceOnAgentFence(operation, { delayMs = 750 } = {}) {
258
+ try {
259
+ return await operation();
260
+ }
261
+ catch (err) {
262
+ const { code, body } = (0, base_command_1.apiRefusal)(err);
263
+ if (!code || !exports.AGENT_FENCE_RETRY_CODES.has(code) || body?.retryable !== true)
264
+ throw err;
265
+ if (delayMs > 0)
266
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
267
+ return operation();
268
+ }
269
+ }
225
270
  /**
226
271
  * What changing the agent's default permission mode actually changed
227
272
  * (OSK-12124). It is the AGENT's default, so it governs every conversation,
@@ -86,10 +86,18 @@ function formatNextTick(projection) {
86
86
  // dispatch-refusal loop can't hide behind an on_track projection.
87
87
  if (projection.health || projection.liveness) {
88
88
  const census = projection.liveness?.terminalDispositionCensus;
89
+ const plural = (count) => (count === 1 ? '' : 's');
89
90
  const detail = [
90
91
  projection.liveness?.diagnosisKind,
91
92
  census?.workFailures
92
- ? `${census.workFailures} recent run failure${census.workFailures === 1 ? '' : 's'}`
93
+ ? `${census.workFailures} recent run failure${plural(census.workFailures)}`
94
+ : null,
95
+ // A run that never started is not a failure of the work (OSK-12137).
96
+ census?.machineFailures
97
+ ? `${census.machineFailures} run${plural(census.machineFailures)} never started`
98
+ : null,
99
+ census?.contractFailures
100
+ ? `${census.contractFailures} result${plural(census.contractFailures)} rejected`
93
101
  : null,
94
102
  ]
95
103
  .filter(Boolean)
@@ -4,6 +4,7 @@ const core_1 = require("@oclif/core");
4
4
  const data_provider_1 = require("@skrr-ai/data-provider");
5
5
  const base_command_1 = require("../base-command");
6
6
  const config_1 = require("../lib/config");
7
+ const cli_id_1 = require("../lib/cli-id");
7
8
  const keychain_1 = require("../lib/keychain");
8
9
  const cli_runtime_1 = require("../lib/cli-runtime");
9
10
  const runtime_context_1 = require("../lib/runtime-context");
@@ -48,7 +49,7 @@ class Context extends base_command_1.BaseCommand {
48
49
  profile: (0, keychain_1.getActiveProfile)(),
49
50
  config: {
50
51
  path: (0, config_1.getConfigPath)(),
51
- cliId: this.cliConfig.cliId ?? null,
52
+ cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
52
53
  },
53
54
  auth: {
54
55
  signedIn: Boolean(this.resolvedCredential?.token),
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const exec_runtime_binary_1 = require("../../lib/exec-runtime-binary");
5
+ const live_runtime_binary_1 = require("../../lib/live-runtime-binary");
5
6
  /**
6
7
  * `skrr daemon config` — set this machine's daemon configuration, including
7
8
  * each tool category's permission mode (OSK-12099). Before this the only way
@@ -23,7 +24,21 @@ class DaemonConfig extends core_1.Command {
23
24
  // flag this wrapper has not redeclared (OSK-296).
24
25
  const argv = this.argv;
25
26
  try {
26
- const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['config', ...argv]);
27
+ // Config writes the file the LIVE daemon reloads, so it must be parsed by
28
+ // the live daemon's own binary — the same routing status uses (OSK-12102).
29
+ // Forwarding to the first `skrrd` on PATH let an older build accept
30
+ // `--auto-approve-bash banana` as false and reject `--mode`, then write
31
+ // the live daemon's config anyway (OSK-12099 verifier).
32
+ const pathBinary = (0, exec_runtime_binary_1.findRuntimeBinary)();
33
+ const decision = pathBinary
34
+ ? (0, live_runtime_binary_1.decideStatusBinary)(pathBinary, (0, live_runtime_binary_1.profileFromArgs)(argv), (0, live_runtime_binary_1.defaultLiveRuntimeDeps)())
35
+ : null;
36
+ const note = decision ? (0, live_runtime_binary_1.describeStatusBinary)(decision) : null;
37
+ if (note)
38
+ process.stderr.write(`${note}\n`);
39
+ const code = await (0, exec_runtime_binary_1.execRuntimeBinary)(['config', ...argv], {
40
+ binary: decision?.binary,
41
+ });
27
42
  this.exit(code);
28
43
  }
29
44
  catch (err) {
@@ -43,6 +43,7 @@ const auth_core_1 = require("@skrr-ai/auth-core");
43
43
  const base_command_1 = require("../base-command");
44
44
  const auth_storage_1 = require("../lib/auth-storage");
45
45
  const config_1 = require("../lib/config");
46
+ const cli_id_1 = require("../lib/cli-id");
46
47
  const keychain_1 = require("../lib/keychain");
47
48
  const cli_runtime_1 = require("../lib/cli-runtime");
48
49
  const command_manifest_1 = require("../lib/command-manifest");
@@ -272,6 +273,9 @@ class Doctor extends base_command_1.BaseCommand {
272
273
  ? ['auth_token_present_but_user_lookup_failed']
273
274
  : []),
274
275
  ...(!checks.authToken ? ['not_signed_in_or_no_token_available'] : []),
276
+ ...(credential.ownerStatus === 'foreign'
277
+ ? ['stored_credential_belongs_to_another_skrr_config_run_skrr_login']
278
+ : []),
275
279
  ];
276
280
  const payload = {
277
281
  ok: issues.length === 0,
@@ -286,13 +290,16 @@ class Doctor extends base_command_1.BaseCommand {
286
290
  profile: (0, keychain_1.getActiveProfile)(),
287
291
  config: {
288
292
  path: (0, config_1.getConfigPath)(),
289
- cliId: this.cliConfig.cliId ?? null,
293
+ cliId: (0, cli_id_1.resolveCliId)(this.cliConfig) ?? null,
290
294
  },
291
295
  auth: {
292
296
  signedIn: Boolean(account),
293
297
  tokenSource: this.credentialSourceLabel(),
294
298
  tokenKind: this.resolvedCredential?.kind ?? null,
295
299
  refreshAvailable: Boolean(credential.bundle?.refreshToken),
300
+ // OSK-12135 — why a stored credential was not used, when it belongs to
301
+ // another skrr config on this machine. Null when there is nothing to say.
302
+ storedCredentialOwner: (0, auth_storage_1.describeForeignCredential)(credential),
296
303
  accessExpiresAt: accessExpiresAt ? new Date(accessExpiresAt).toISOString() : null,
297
304
  accessExpired: accessExpiresAt ? accessExpiresAt <= now : null,
298
305
  refreshExpiresAt: refreshExpiresAt ? new Date(refreshExpiresAt).toISOString() : null,
@@ -44,7 +44,8 @@ export default class Login extends BaseCommand {
44
44
  *
45
45
  * `cliId` resolution priority:
46
46
  * 1. `OVERSKY_CLI_ID` env var — explicit, wins over everything.
47
- * 2. `cliConfig.cliId` — set by a prior `skrr login` on this machine.
47
+ * 2. `resolveCliId(cliConfig)` — the id a prior `skrr login` of this
48
+ * profile presents (its own, or the machine's).
48
49
  * Honored so a same-machine restore "just works" without forcing
49
50
  * the operator to re-export the cliId.
50
51
  * 3. error — without a cliId the refresh row binding can't be matched
@@ -7,6 +7,7 @@ const base_command_1 = require("../base-command");
7
7
  const daemonHandoff_1 = require("../lib/daemonHandoff");
8
8
  const daemon_setup_1 = require("../lib/daemon-setup");
9
9
  const config_1 = require("../lib/config");
10
+ const cli_id_1 = require("../lib/cli-id");
10
11
  const keychain_1 = require("../lib/keychain");
11
12
  const login_1 = require("../lib/login");
12
13
  const device_code_1 = require("../lib/device-code");
@@ -40,16 +41,33 @@ const prompt_1 = require("../lib/prompt");
40
41
  * `cliLoginWithRefreshToken` for the full rationale.
41
42
  */
42
43
  class Login extends base_command_1.BaseCommand {
43
- static description = 'Sign in to a skrr instance (PKCE localhost callback; device-code fallback). ' +
44
- 'On an interactive first login on a computer without the skrr background service, skrr ' +
45
- 'offers to install it. Choose no, or set SKRR_SKIP_DAEMON_HANDOFF=1 before running this ' +
46
- 'command, to skip that optional setup.';
44
+ // OSK-12152 — the help used to describe only the browser flow. `skrr login`
45
+ // asks the local daemon FIRST, and nothing said so; nor that what it stores
46
+ // differs from what plain commands borrow from the daemon on their own.
47
+ static description = 'Sign in to a skrr instance and store a durable session for this profile.\n\n' +
48
+ 'It tries, in order:\n' +
49
+ ' 1. the skrr background service on this computer, when one is running and signed in to the ' +
50
+ 'same server — it mints this profile a CLI session with no browser;\n' +
51
+ ' 2. a browser sign-in through a localhost callback (PKCE);\n' +
52
+ ' 3. a device code — under SSH, with --device-code, or when the callback cannot run.\n' +
53
+ 'If the background service answers but refuses, skrr says why before moving on.\n\n' +
54
+ 'What it stores: a refresh session in this profile’s credential store (the macOS Keychain, ' +
55
+ 'or ~/.skrr/cli-auth.json elsewhere) that keeps renewing itself. Plain skrr commands on a ' +
56
+ 'computer whose background service is signed in need no login at all: they borrow a ' +
57
+ 'short-lived token from it for each run and store nothing (an older background service ' +
58
+ 'mints them a stored session instead). On a Dedicated Runtime, where the service only ' +
59
+ 'lends short-lived tokens, nothing is stored.\n\n' +
60
+ 'Set OVERSKY_SKIP_DAEMON_BROKER=1 to skip step 1 (--device-code skips it too). After signing ' +
61
+ 'in, skrr also signs the background service in, and on an interactive first login on a ' +
62
+ 'computer without it, offers to install it. Set SKRR_SKIP_DAEMON_HANDOFF=1 to skip that ' +
63
+ 'optional setup; it does not skip step 1.';
47
64
  static examples = [
48
65
  '<%= config.bin %> login',
49
66
  '<%= config.bin %> login --env prod',
50
67
  '<%= config.bin %> login --base-url https://skrr.ai',
51
68
  '<%= config.bin %> login --server http://localhost:3090',
52
69
  '<%= config.bin %> login --device-code',
70
+ 'OVERSKY_SKIP_DAEMON_BROKER=1 <%= config.bin %> login',
53
71
  '<%= config.bin %> login --port 1455',
54
72
  '<%= config.bin %> login --recovery-code "W7K2J-X9P3M-R6Y4Q-..."',
55
73
  'OVERSKY_REFRESH_TOKEN=... OVERSKY_CLI_ID=... <%= config.bin %> login',
@@ -244,7 +262,8 @@ class Login extends base_command_1.BaseCommand {
244
262
  *
245
263
  * `cliId` resolution priority:
246
264
  * 1. `OVERSKY_CLI_ID` env var — explicit, wins over everything.
247
- * 2. `cliConfig.cliId` — set by a prior `skrr login` on this machine.
265
+ * 2. `resolveCliId(cliConfig)` — the id a prior `skrr login` of this
266
+ * profile presents (its own, or the machine's).
248
267
  * Honored so a same-machine restore "just works" without forcing
249
268
  * the operator to re-export the cliId.
250
269
  * 3. error — without a cliId the refresh row binding can't be matched
@@ -252,8 +271,8 @@ class Login extends base_command_1.BaseCommand {
252
271
  * pointing at `skrr whoami` on the original box.
253
272
  */
254
273
  async runRefreshTokenLogin(refreshToken) {
255
- const envCliId = process.env.OVERSKY_CLI_ID;
256
- const cliId = typeof envCliId === 'string' && envCliId.length > 0 ? envCliId : this.cliConfig.cliId;
274
+ // `resolveCliId` already puts OVERSKY_CLI_ID / SKRR_CLI_ID first.
275
+ const cliId = (0, cli_id_1.resolveCliId)(this.cliConfig);
257
276
  if (!cliId) {
258
277
  this.error('OVERSKY_REFRESH_TOKEN is set but no cliId is available. The refresh\n' +
259
278
  'row is bound to the cliId it was issued for; without it the server\n' +
@@ -244,6 +244,9 @@ class Logout extends base_command_1.BaseCommand {
244
244
  (0, config_1.saveConfig)({
245
245
  baseURL: cfg.baseURL,
246
246
  ...(cfg.cliId ? { cliId: cfg.cliId } : {}),
247
+ // A named profile's own identity outlives a session exactly as the
248
+ // machine's does, so its next login re-presents it (OSK-12156).
249
+ ...(cfg.profileCliId ? { profileCliId: cfg.profileCliId } : {}),
247
250
  ...(agentSetting.value ? { [first_party_harness_managed_1.AGENT_CONFIG_KEY]: agentSetting.value } : {}),
248
251
  ...(cfg.credentials ? { credentials: cfg.credentials } : {}),
249
252
  migrated: { ...(cfg.migrated ?? {}), authOutOfConfig: true },
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  const core_1 = require("@oclif/core");
4
4
  const dedicated_lease_command_1 = require("../../../lib/dedicated-lease-command");
5
+ const daemon_installer_1 = require("../../../lib/daemon-installer");
5
6
  const dedicated_machines_1 = require("../../../lib/dedicated-machines");
6
7
  const harnesses_1 = require("../../../lib/harnesses");
7
8
  class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseCommand {
@@ -40,6 +41,12 @@ class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseComm
40
41
  catch {
41
42
  harnesses = [];
42
43
  }
44
+ // The latest daemon RELEASE, from the same signed feed a daemon updates
45
+ // from. The lease says which daemon the approved image carries; only this
46
+ // says whether a newer release exists that no approved image carries yet —
47
+ // which is what `skrrd update --check` on the machine reports (OSK-12153).
48
+ // Best-effort: an unreachable feed is said, never read as "none newer".
49
+ const latestDaemonRelease = lease.health?.image ? await readLatestDaemonRelease() : null;
43
50
  if (flags.json) {
44
51
  const engines = (0, dedicated_machines_1.dedicatedLeaseHarnesses)(lease, harnesses).map((harness) => ({
45
52
  harnessId: harness._id,
@@ -47,11 +54,27 @@ class DedicatedMachinesShow extends dedicated_lease_command_1.DedicatedLeaseComm
47
54
  status: harness.status,
48
55
  lastSeenAt: harness.lastSeenAt,
49
56
  }));
50
- this.log(JSON.stringify({ ...response, harnesses: engines }, null, 2));
57
+ this.log(JSON.stringify({
58
+ ...response,
59
+ harnesses: engines,
60
+ ...(latestDaemonRelease ? { latestDaemonRelease } : {}),
61
+ }, null, 2));
51
62
  return;
52
63
  }
53
- for (const line of (0, dedicated_machines_1.describeDedicatedLease)(lease, harnesses))
64
+ for (const line of (0, dedicated_machines_1.describeDedicatedLease)(lease, harnesses, { latestDaemonRelease })) {
54
65
  this.log(line);
66
+ }
55
67
  }
56
68
  }
57
69
  exports.default = DedicatedMachinesShow;
70
+ async function readLatestDaemonRelease() {
71
+ try {
72
+ const result = await (0, daemon_installer_1.fetchVerifiedDaemonManifest)();
73
+ if (result.ok)
74
+ return { version: result.manifest.version };
75
+ return { error: result.code };
76
+ }
77
+ catch (err) {
78
+ return { error: err?.message || 'unreadable' };
79
+ }
80
+ }
@@ -25,6 +25,11 @@ import { type TaskRecord } from '../../lib/tasks';
25
25
  * Same shape as `warnDroppedDefaultAssignees` below, which does this for a
26
26
  * silently-dropped assignee (OSK-4857).
27
27
  */
28
+ /**
29
+ * `tagLabelWarning` for a task: the task-label vocabulary of the ambient
30
+ * workspace. Shared by `tasks create` and `tasks update`.
31
+ */
32
+ export declare function taskTagLabelWarning(tags: string[] | undefined, attachedLabelIds: string[] | undefined): Promise<string>;
28
33
  export declare function droppedEstimateWarning(requested: number | undefined, written: Record<string, unknown> | null | undefined, spaceId?: string | null): string | null;
29
34
  /** A create may have committed before any of these response failures surfaced. */
30
35
  export declare function taskCreateMayHaveCommitted(error: unknown): boolean;