@skrr-ai/cli 0.1.55 → 0.1.56

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.
@@ -3,7 +3,7 @@ export { resolveProfileFromArgv } from './lib/keychain';
3
3
  import { type AssigneeResolution, type MemberKind, type SpaceMember } from './lib/assignee-resolver';
4
4
  import { type CliConfig } from './lib/config';
5
5
  import { type OutboxMethod } from './lib/outbox';
6
- import { type ResolvedCredential } from './lib/credential-resolver';
6
+ import { type CredentialProvenance, type ResolvedCredential } from './lib/credential-resolver';
7
7
  import { type BrokerRefusalDescription } from './lib/daemonBrokerRefusal';
8
8
  import { type DefaultAgentResolution } from './lib/tasks';
9
9
  export declare function assigneePayloadNeedsResolution(payload: Record<string, unknown>): boolean;
@@ -68,6 +68,13 @@ export declare abstract class BaseCommand extends Command {
68
68
  * from here.
69
69
  */
70
70
  protected resolvedCredential: ResolvedCredential;
71
+ /**
72
+ * Where `resolvedCredential` really came from when its `source` slot cannot
73
+ * say — an in-memory daemon hand-off or Agent session token is recorded as
74
+ * `env-token` for refresh semantics, but was not read from OVERSKY_TOKEN
75
+ * (OSK-12185). Null for every stored/env/flag credential.
76
+ */
77
+ protected credentialProvenance: CredentialProvenance | null;
71
78
  protected bareMode: boolean;
72
79
  /**
73
80
  * Set when the auto-broker reached a live daemon and it REFUSED the hand-off.
@@ -132,6 +132,13 @@ class BaseCommand extends core_1.Command {
132
132
  * from here.
133
133
  */
134
134
  resolvedCredential;
135
+ /**
136
+ * Where `resolvedCredential` really came from when its `source` slot cannot
137
+ * say — an in-memory daemon hand-off or Agent session token is recorded as
138
+ * `env-token` for refresh semantics, but was not read from OVERSKY_TOKEN
139
+ * (OSK-12185). Null for every stored/env/flag credential.
140
+ */
141
+ credentialProvenance = null;
135
142
  bareMode = false;
136
143
  /**
137
144
  * Set when the auto-broker reached a live daemon and it REFUSED the hand-off.
@@ -173,6 +180,7 @@ class BaseCommand extends core_1.Command {
173
180
  return;
174
181
  }
175
182
  autonomousCredential = delegatedCredential;
183
+ this.credentialProvenance = { kind: 'daemon-delegated' };
176
184
  }
177
185
  catch (err) {
178
186
  this.failWithCliError({
@@ -214,6 +222,10 @@ class BaseCommand extends core_1.Command {
214
222
  delete process.env.OVERSKY_WORKSPACE_ID;
215
223
  }
216
224
  this.resolvedCredential = autonomousCredential;
225
+ if (!this.credentialProvenance && autonomousCredential.source === 'env-token') {
226
+ // `resolveTrustedAutonomousCredential` read OVERSKY_SESSION_TOKEN.
227
+ this.credentialProvenance = { kind: 'agent-session' };
228
+ }
217
229
  (0, node_adapter_1.setAdapterCredentialOverride)(this.resolvedCredential);
218
230
  (0, data_provider_1.setHttpAdapter)((0, node_adapter_1.createNodeAdapter)());
219
231
  return;
@@ -327,6 +339,17 @@ class BaseCommand extends core_1.Command {
327
339
  : 'file',
328
340
  kind: (0, auth_core_1.classifyTokenKind)(accessToken),
329
341
  };
342
+ // The slot above says `env-token`; the label must not. The token
343
+ // came from the daemon's hand-off descriptor, and OVERSKY_TOKEN is
344
+ // typically unset (OSK-12185).
345
+ this.credentialProvenance = autoResult.outcome.brokered
346
+ ? {
347
+ kind: 'daemon-handoff',
348
+ ...(autoResult.outcome.descriptorPath
349
+ ? { descriptorPath: autoResult.outcome.descriptorPath }
350
+ : {}),
351
+ }
352
+ : null;
330
353
  }
331
354
  else if ((this.brokerRefusal = (0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin))) {
332
355
  // Reported by requireAuth(); nothing to print here.
@@ -821,7 +844,7 @@ class BaseCommand extends core_1.Command {
821
844
  credentialSourceLabel() {
822
845
  if (!this.resolvedCredential?.token)
823
846
  return null;
824
- return (0, credential_resolver_1.formatCredentialSource)(this.resolvedCredential.source);
847
+ return (0, credential_resolver_1.describeCredentialSource)(this.resolvedCredential.source, this.credentialProvenance);
825
848
  }
826
849
  async catch(err) {
827
850
  if (isOclifExitError(err)) {
@@ -173,6 +173,16 @@ export type ProjectedToolEvent = {
173
173
  * of the run. The pairing is unambiguous within a turn.
174
174
  */
175
175
  export declare function projectDaemonToolEvents(envelopes: unknown[], namesByCall: Map<string, string>): ProjectedToolEvent[];
176
+ /**
177
+ * The line printed when a tool call ends, in the words its status earns.
178
+ *
179
+ * Every end used to print `[tool completed]`, including a failed call. And a
180
+ * call the harness never closed — which the daemon now reports as
181
+ * `unreported` rather than `failed` (OSK-12188) — must read as neither: it may
182
+ * well have run. Its close is made when the session exits, so the line says
183
+ * that too, rather than let the moment it prints stand for when the call ran.
184
+ */
185
+ export declare function describeToolEnd(toolName: unknown, status: unknown): string;
176
186
  /**
177
187
  * WHO or WHAT decided a permission request, as a reader needs it (OSK-12113).
178
188
  *
@@ -7,6 +7,7 @@ exports.envelopesText = envelopesText;
7
7
  exports.envelopeKey = envelopeKey;
8
8
  exports.unseenEnvelopes = unseenEnvelopes;
9
9
  exports.projectDaemonToolEvents = projectDaemonToolEvents;
10
+ exports.describeToolEnd = describeToolEnd;
10
11
  exports.describePermissionDecider = describePermissionDecider;
11
12
  exports.projectPermissionResolutions = projectPermissionResolutions;
12
13
  exports.renderableDelta = renderableDelta;
@@ -249,6 +250,28 @@ function projectDaemonToolEvents(envelopes, namesByCall) {
249
250
  }
250
251
  return out;
251
252
  }
253
+ /**
254
+ * The line printed when a tool call ends, in the words its status earns.
255
+ *
256
+ * Every end used to print `[tool completed]`, including a failed call. And a
257
+ * call the harness never closed — which the daemon now reports as
258
+ * `unreported` rather than `failed` (OSK-12188) — must read as neither: it may
259
+ * well have run. Its close is made when the session exits, so the line says
260
+ * that too, rather than let the moment it prints stand for when the call ran.
261
+ */
262
+ function describeToolEnd(toolName, status) {
263
+ const name = String(toolName ?? 'unknown');
264
+ switch (status) {
265
+ case 'failed':
266
+ return `[tool failed] ${name}`;
267
+ case 'cancelled':
268
+ return `[tool cancelled] ${name}`;
269
+ case 'unreported':
270
+ return `[tool result not reported] ${name} (the harness never said how it ended; closed when the session ended)`;
271
+ default:
272
+ return `[tool completed] ${name}`;
273
+ }
274
+ }
252
275
  const SURFACE_LABELS = {
253
276
  web: 'the web app',
254
277
  cli: 'the skrr CLI',
@@ -1126,7 +1149,7 @@ class AgenticStreamClient {
1126
1149
  this.renderedText = this.accumulated;
1127
1150
  }
1128
1151
  else if (event.type === 'tool.completed') {
1129
- output.write(`[tool completed] ${String(event.toolName ?? 'unknown')}\n`);
1152
+ output.write(`${describeToolEnd(event.toolName, event.status)}\n`);
1130
1153
  this.renderedText = this.accumulated;
1131
1154
  }
1132
1155
  else if (event.type === 'stream.gap') {
@@ -17,6 +17,35 @@
17
17
  * says so (OSK-12135).
18
18
  */
19
19
  export declare function plainCredentialEnvelopeWarning(args: readonly unknown[]): string | null;
20
+ /**
21
+ * The one line an EXPECTED credential-key change prints (OSK-12186).
22
+ *
23
+ * When the key that wraps this profile's credential key changes underneath it
24
+ * — a Dedicated guest moved to a new image gets a new `/etc/machine-id`, which
25
+ * the Linux key is derived from; a new Mac or a reset Keychain does the same —
26
+ * auth-core cannot unwrap the old key, enrolls a new one and carries on. That
27
+ * is recovery, not damage, but it reached the terminal as four warnings: the
28
+ * unwrap failure, the rotation notice, and one decrypt failure per credential
29
+ * still sealed under the old key, each ending in a raw crypto string. The
30
+ * second run was clean, which is exactly what corruption does not look like.
31
+ *
32
+ * It is distinct from a GENUINELY unreadable credential (OSK-12180): there the
33
+ * key unwraps and one stored value still cannot be opened, nothing will fix it
34
+ * by itself, and that line names `skrr login`. Here the old values are
35
+ * unreadable BECAUSE the key was replaced, and the next step happens on its
36
+ * own when a local daemon can hand a credential over.
37
+ */
38
+ export declare const CREDENTIAL_KEY_CHANGED_NOTICE: string;
39
+ /**
40
+ * What the CLI prints for one auth-core warning: a replacement line, nothing
41
+ * (`''`), or null for "print it as auth-core wrote it".
42
+ *
43
+ * Stateful by design — the process that detected a key change has already
44
+ * told the person why every old credential is unreadable, so the per-value
45
+ * decrypt failures that follow add nothing. A fresh process starts clean, and
46
+ * a later genuinely unreadable credential still gets its own OSK-12180 line.
47
+ */
48
+ export declare function createCredentialEnvelopeWarningPresenter(): (args: readonly unknown[]) => string | null;
20
49
  type DedicatedGenerationPreparationOptions = {
21
50
  machineUuid?: string;
22
51
  configDir?: string;
@@ -3,8 +3,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.CliCredentialInitializationError = void 0;
6
+ exports.CliCredentialInitializationError = exports.CREDENTIAL_KEY_CHANGED_NOTICE = void 0;
7
7
  exports.plainCredentialEnvelopeWarning = plainCredentialEnvelopeWarning;
8
+ exports.createCredentialEnvelopeWarningPresenter = createCredentialEnvelopeWarningPresenter;
8
9
  exports.prepareDedicatedRuntimeAuthGeneration = prepareDedicatedRuntimeAuthGeneration;
9
10
  exports.ensureAuthCoreConfigured = ensureAuthCoreConfigured;
10
11
  exports.prefetchCliAuth = prefetchCliAuth;
@@ -75,6 +76,64 @@ function plainCredentialEnvelopeWarning(args) {
75
76
  const remedy = hint === auth_core_1.DEFAULT_UNREADABLE_CREDENTIAL_HINT ? ' Run `skrr login`.' : '';
76
77
  return `warning: ${hint.trim()}.${remedy} (DEBUG=1 shows the underlying error.)`;
77
78
  }
79
+ /**
80
+ * The one line an EXPECTED credential-key change prints (OSK-12186).
81
+ *
82
+ * When the key that wraps this profile's credential key changes underneath it
83
+ * — a Dedicated guest moved to a new image gets a new `/etc/machine-id`, which
84
+ * the Linux key is derived from; a new Mac or a reset Keychain does the same —
85
+ * auth-core cannot unwrap the old key, enrolls a new one and carries on. That
86
+ * is recovery, not damage, but it reached the terminal as four warnings: the
87
+ * unwrap failure, the rotation notice, and one decrypt failure per credential
88
+ * still sealed under the old key, each ending in a raw crypto string. The
89
+ * second run was clean, which is exactly what corruption does not look like.
90
+ *
91
+ * It is distinct from a GENUINELY unreadable credential (OSK-12180): there the
92
+ * key unwraps and one stored value still cannot be opened, nothing will fix it
93
+ * by itself, and that line names `skrr login`. Here the old values are
94
+ * unreadable BECAUSE the key was replaced, and the next step happens on its
95
+ * own when a local daemon can hand a credential over.
96
+ */
97
+ exports.CREDENTIAL_KEY_CHANGED_NOTICE = "note: this machine's credential key changed (a new machine or image), so credentials " +
98
+ 'saved under the old key can no longer be read. skrr gets a new one from the local daemon when ' +
99
+ 'one is running; otherwise run `skrr login`. (DEBUG=1 shows the underlying error.)';
100
+ /**
101
+ * What the CLI prints for one auth-core warning: a replacement line, nothing
102
+ * (`''`), or null for "print it as auth-core wrote it".
103
+ *
104
+ * Stateful by design — the process that detected a key change has already
105
+ * told the person why every old credential is unreadable, so the per-value
106
+ * decrypt failures that follow add nothing. A fresh process starts clean, and
107
+ * a later genuinely unreadable credential still gets its own OSK-12180 line.
108
+ */
109
+ function createCredentialEnvelopeWarningPresenter() {
110
+ let keyChanged = false;
111
+ return (args) => {
112
+ const [message, metadata] = args;
113
+ const event = typeof message === 'string' && message.startsWith('[credEnvelope] ')
114
+ ? message.slice('[credEnvelope] '.length)
115
+ : null;
116
+ const meta = (metadata && typeof metadata === 'object' ? metadata : {});
117
+ // auth-core emits this and then, from the same branch, `kek_rotated`: the
118
+ // wrapped key was present and would not unwrap. The notice below is the
119
+ // one line for both.
120
+ if (event === 'cred_envelope.unwrap.failed' && meta.reason === 'wrapped_dek_unwrap_threw') {
121
+ return '';
122
+ }
123
+ if (event === 'cred_envelope.kek_rotated') {
124
+ if (keyChanged)
125
+ return '';
126
+ keyChanged = true;
127
+ return exports.CREDENTIAL_KEY_CHANGED_NOTICE;
128
+ }
129
+ // Every value sealed under the old key fails here now — the notice has
130
+ // already said why. A MALFORMED envelope is a different fact and still prints.
131
+ if (keyChanged && event === 'cred_envelope.read.decrypt_failed')
132
+ return '';
133
+ return plainCredentialEnvelopeWarning(args);
134
+ };
135
+ }
136
+ const presentCredentialEnvelopeWarning = createCredentialEnvelopeWarningPresenter();
78
137
  const cliLogger = {
79
138
  // The CLI is a short-lived foreground process. Trace/debug get dropped
80
139
  // unless DEBUG is set so we don't spam terminal output.
@@ -87,7 +146,9 @@ const cliLogger = {
87
146
  console.error('[auth-core info]', ...args);
88
147
  },
89
148
  warn: (...args) => {
90
- const plain = process.env.DEBUG ? null : plainCredentialEnvelopeWarning(args);
149
+ const plain = process.env.DEBUG ? null : presentCredentialEnvelopeWarning(args);
150
+ if (plain === '')
151
+ return;
91
152
  if (plain)
92
153
  console.error(plain);
93
154
  else
@@ -64,6 +64,33 @@ export declare function resolveCredential(opts?: ResolveOptions): Promise<Resolv
64
64
  * a token (e.g., just post-login) don't need to re-resolve.
65
65
  */
66
66
  export declare function kindOf(token: string | null | undefined): TokenKind;
67
+ /**
68
+ * Where an in-memory credential ACTUALLY came from, when the shared
69
+ * `CredentialSource` union has no slot for it.
70
+ *
71
+ * Two CLI paths hold a token that was never read from `OVERSKY_TOKEN` yet
72
+ * are recorded as `env-token` — the union's only "a token this process holds
73
+ * in memory" slot, and the one `ensureFreshCliCredential` correctly skips: a
74
+ * daemon hand-off redeem (renewed by re-redeeming its descriptor) and a
75
+ * daemon-launched Agent session (`OVERSKY_SESSION_TOKEN`). Labelling either
76
+ * by the slot alone printed `Token source: OVERSKY_TOKEN` for a variable
77
+ * that was not set, which sends a debugging user to the wrong place
78
+ * (OSK-12185). The provenance travels BESIDE the credential so the slot keeps
79
+ * its refresh semantics and the label tells the truth.
80
+ */
81
+ export type CredentialProvenance = {
82
+ kind: 'daemon-handoff';
83
+ descriptorPath?: string;
84
+ } | {
85
+ kind: 'daemon-delegated';
86
+ } | {
87
+ kind: 'agent-session';
88
+ };
89
+ /**
90
+ * The label `whoami`, `context` and `doctor` print — text and `--json` read
91
+ * the same value. A recorded provenance wins over the source slot.
92
+ */
93
+ export declare function describeCredentialSource(source: CredentialSource, provenance?: CredentialProvenance | null): string;
67
94
  /**
68
95
  * Human-readable label for a `CredentialSource`. Used by `skrr whoami`
69
96
  * and (Phase H) `skrr doctor`.
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.resolveTrustedAutonomousCredential = resolveTrustedAutonomousCredential;
4
4
  exports.resolveCredential = resolveCredential;
5
5
  exports.kindOf = kindOf;
6
+ exports.describeCredentialSource = describeCredentialSource;
6
7
  exports.formatCredentialSource = formatCredentialSource;
7
8
  /**
8
9
  * credential-resolver.ts — Phase H.1 precedence for the Sky CLI.
@@ -118,6 +119,24 @@ async function resolveCredential(opts = {}) {
118
119
  function kindOf(token) {
119
120
  return (0, auth_core_1.classifyTokenKind)(token);
120
121
  }
122
+ /**
123
+ * The label `whoami`, `context` and `doctor` print — text and `--json` read
124
+ * the same value. A recorded provenance wins over the source slot.
125
+ */
126
+ function describeCredentialSource(source, provenance) {
127
+ if (provenance?.kind === 'daemon-handoff') {
128
+ return provenance.descriptorPath
129
+ ? `daemon hand-off (${provenance.descriptorPath})`
130
+ : 'daemon hand-off';
131
+ }
132
+ if (provenance?.kind === 'daemon-delegated') {
133
+ return 'daemon delegated credential (OVERSKY_DELEGATED_CLI_BROKER_URL)';
134
+ }
135
+ if (provenance?.kind === 'agent-session') {
136
+ return 'agent session (OVERSKY_SESSION_TOKEN)';
137
+ }
138
+ return formatCredentialSource(source);
139
+ }
121
140
  /**
122
141
  * Human-readable label for a `CredentialSource`. Used by `skrr whoami`
123
142
  * and (Phase H) `skrr doctor`.
@@ -115,6 +115,13 @@ export interface DaemonBrokeredOutcome {
115
115
  /** Server the minted credential belongs to (daemon-reported, else the
116
116
  * descriptor's own `serverUrl`). */
117
117
  serverUrl?: string;
118
+ /**
119
+ * The descriptor file the token was redeemed from. The token lives only in
120
+ * this process's memory, so this path is the only honest answer to "where
121
+ * did this credential come from" — `skrr whoami` prints it as the token
122
+ * source (OSK-12185).
123
+ */
124
+ descriptorPath?: string;
118
125
  }
119
126
  /**
120
127
  * Explicit reasons the broker path could fail. These exist to keep the
@@ -463,6 +463,7 @@ async function brokerThrough(bootstrap, opts, sourcePath) {
463
463
  ...((response.serverUrl ?? bootstrap.serverUrl)
464
464
  ? { serverUrl: response.serverUrl ?? bootstrap.serverUrl }
465
465
  : {}),
466
+ ...(sourcePath ? { descriptorPath: sourcePath } : {}),
466
467
  };
467
468
  }
468
469
  // The descriptor ALSO advertises the durable mint (a laptop island during