@skrr-ai/cli 0.1.54 → 0.1.55

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.
@@ -1,3 +1,22 @@
1
+ /**
2
+ * A credential-envelope failure that carries a hint, as ONE plain line
3
+ * (OSK-12180) — or null when the warning is anything else.
4
+ *
5
+ * An unreadable stored credential is reported on every command until someone
6
+ * signs in again, and auth-core's structured form is six lines of object dump
7
+ * ending in the raw CryptoKit/OpenSSL string (`Unsupported state or unable to
8
+ * authenticate data`). The person reading it needs the hint auth-core already
9
+ * wrote for exactly this, and what to do; the key id and the crypto message are
10
+ * for whoever is debugging, which is what `DEBUG` is for everywhere else in
11
+ * this logger. The daemon logs through its own logger and keeps the structured
12
+ * record.
13
+ *
14
+ * The remedy is added only to the DEFAULT hint, which is about this config's
15
+ * own credential. A caller that supplied its own hint knows the case better —
16
+ * another config's credential is not fixed by `skrr login` here, and its hint
17
+ * says so (OSK-12135).
18
+ */
19
+ export declare function plainCredentialEnvelopeWarning(args: readonly unknown[]): string | null;
1
20
  type DedicatedGenerationPreparationOptions = {
2
21
  machineUuid?: string;
3
22
  configDir?: string;
@@ -4,6 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.CliCredentialInitializationError = void 0;
7
+ exports.plainCredentialEnvelopeWarning = plainCredentialEnvelopeWarning;
7
8
  exports.prepareDedicatedRuntimeAuthGeneration = prepareDedicatedRuntimeAuthGeneration;
8
9
  exports.ensureAuthCoreConfigured = ensureAuthCoreConfigured;
9
10
  exports.prefetchCliAuth = prefetchCliAuth;
@@ -44,6 +45,36 @@ function envFlag(name) {
44
45
  const v = process.env[name];
45
46
  return v === '1' || v === 'true' || v === 'TRUE';
46
47
  }
48
+ /**
49
+ * A credential-envelope failure that carries a hint, as ONE plain line
50
+ * (OSK-12180) — or null when the warning is anything else.
51
+ *
52
+ * An unreadable stored credential is reported on every command until someone
53
+ * signs in again, and auth-core's structured form is six lines of object dump
54
+ * ending in the raw CryptoKit/OpenSSL string (`Unsupported state or unable to
55
+ * authenticate data`). The person reading it needs the hint auth-core already
56
+ * wrote for exactly this, and what to do; the key id and the crypto message are
57
+ * for whoever is debugging, which is what `DEBUG` is for everywhere else in
58
+ * this logger. The daemon logs through its own logger and keeps the structured
59
+ * record.
60
+ *
61
+ * The remedy is added only to the DEFAULT hint, which is about this config's
62
+ * own credential. A caller that supplied its own hint knows the case better —
63
+ * another config's credential is not fixed by `skrr login` here, and its hint
64
+ * says so (OSK-12135).
65
+ */
66
+ function plainCredentialEnvelopeWarning(args) {
67
+ const [message, metadata] = args;
68
+ if (typeof message !== 'string' || !message.startsWith('[credEnvelope] '))
69
+ return null;
70
+ if (!metadata || typeof metadata !== 'object')
71
+ return null;
72
+ const hint = metadata.hint;
73
+ if (typeof hint !== 'string' || !hint.trim())
74
+ return null;
75
+ const remedy = hint === auth_core_1.DEFAULT_UNREADABLE_CREDENTIAL_HINT ? ' Run `skrr login`.' : '';
76
+ return `warning: ${hint.trim()}.${remedy} (DEBUG=1 shows the underlying error.)`;
77
+ }
47
78
  const cliLogger = {
48
79
  // The CLI is a short-lived foreground process. Trace/debug get dropped
49
80
  // unless DEBUG is set so we don't spam terminal output.
@@ -55,7 +86,13 @@ const cliLogger = {
55
86
  if (process.env.DEBUG)
56
87
  console.error('[auth-core info]', ...args);
57
88
  },
58
- warn: (...args) => console.error('[auth-core warn]', ...args),
89
+ warn: (...args) => {
90
+ const plain = process.env.DEBUG ? null : plainCredentialEnvelopeWarning(args);
91
+ if (plain)
92
+ console.error(plain);
93
+ else
94
+ console.error('[auth-core warn]', ...args);
95
+ },
59
96
  error: (...args) => console.error('[auth-core error]', ...args),
60
97
  };
61
98
  /**
@@ -74,7 +74,6 @@ const path = __importStar(require("node:path"));
74
74
  const auth_storage_1 = require("./auth-storage");
75
75
  const config_1 = require("./config");
76
76
  const cli_id_1 = require("./cli-id");
77
- const device_id_1 = require("./device-id");
78
77
  const keychain_1 = require("./keychain");
79
78
  /**
80
79
  * A named profile's own identity. Deterministic in (host, user, config root,
@@ -127,7 +126,7 @@ function planLoginCliId(cfg, deps = {}) {
127
126
  const machine = cfg.cliId?.trim();
128
127
  if (machine)
129
128
  return { cliId: machine, source: 'machine' };
130
- const cliId = (0, device_id_1.generateCliId)();
129
+ const cliId = (0, config_1.machineCliId)();
131
130
  return { cliId, source: 'machine', updatedConfig: { ...cfg, cliId } };
132
131
  }
133
132
  /**
@@ -190,6 +190,16 @@ export declare function isEnvAuthOverride(): boolean;
190
190
  * resolver and every existing install read it.
191
191
  */
192
192
  export declare function configRoot(env?: NodeJS.ProcessEnv): string;
193
+ /**
194
+ * The machine cliId THIS config root mints when it has none stored
195
+ * (OSK-12179). The primary root keeps the historical (host, user) id; any other
196
+ * root gets one of its own, so two roots of one user are two server-side
197
+ * families and one root's hand-off confirm cannot retire the other's session.
198
+ *
199
+ * Mints nothing and reads nothing stored: callers persist it (`cli-identity.ts`)
200
+ * or present it only when nothing is stored (the daemon broker).
201
+ */
202
+ export declare function machineCliId(): string;
193
203
  /**
194
204
  * The PRE-RENAME config root — the generation between `.sky` and `.skrr`.
195
205
  *
@@ -39,6 +39,7 @@ exports.describeDeployment = describeDeployment;
39
39
  exports.canonicalizeKnownBaseURL = canonicalizeKnownBaseURL;
40
40
  exports.isEnvAuthOverride = isEnvAuthOverride;
41
41
  exports.configRoot = configRoot;
42
+ exports.machineCliId = machineCliId;
42
43
  exports.priorConfigDir = priorConfigDir;
43
44
  exports.loadConfig = loadConfig;
44
45
  exports.saveConfig = saveConfig;
@@ -54,6 +55,7 @@ const path = __importStar(require("node:path"));
54
55
  const keychain_1 = require("./keychain");
55
56
  const auth_core_1 = require("@skrr-ai/auth-core");
56
57
  const cli_id_1 = require("./cli-id");
58
+ const device_id_1 = require("./device-id");
57
59
  const publicEndpoints_generated_1 = require("./publicEndpoints.generated");
58
60
  // Both from config/public-endpoints.json, the one domain contract, rather than a
59
61
  // fourth hand-copied pair. The CLI previously knew only about dev — there was no
@@ -269,6 +271,26 @@ function configRoot(env = process.env) {
269
271
  function newConfigDir() {
270
272
  return configRoot();
271
273
  }
274
+ /** The root this machine uses when nothing relocates it (`~/.skrr`). */
275
+ function defaultConfigRoot() {
276
+ return (0, auth_core_1.resolveConfigRoot)({});
277
+ }
278
+ /** Is this process on the primary root, however it was reached? */
279
+ function isDefaultConfigRoot() {
280
+ return path.resolve(configRoot()) === path.resolve(defaultConfigRoot());
281
+ }
282
+ /**
283
+ * The machine cliId THIS config root mints when it has none stored
284
+ * (OSK-12179). The primary root keeps the historical (host, user) id; any other
285
+ * root gets one of its own, so two roots of one user are two server-side
286
+ * families and one root's hand-off confirm cannot retire the other's session.
287
+ *
288
+ * Mints nothing and reads nothing stored: callers persist it (`cli-identity.ts`)
289
+ * or present it only when nothing is stored (the daemon broker).
290
+ */
291
+ function machineCliId() {
292
+ return (0, device_id_1.machineCliIdForRoot)(configRoot(), defaultConfigRoot());
293
+ }
272
294
  /**
273
295
  * Fields that belong to the MACHINE, not to a profile.
274
296
  *
@@ -472,6 +494,13 @@ function loadConfig() {
472
494
  if (!fs.existsSync(carriedPath))
473
495
  continue;
474
496
  const carried = migrateLegacyBaseURL(readJsonOrThrow(carriedPath));
497
+ // The ancestor lives under $HOME whatever root this is, so its `cliId`
498
+ // belongs to the PRIMARY root's sessions. A relocated root that adopted
499
+ // it would present the primary root's identity, and a hand-off confirm
500
+ // there retires the primary root's families (OSK-12179). It mints its own
501
+ // at first login instead (`machineCliId`).
502
+ if (!isDefaultConfigRoot())
503
+ delete carried.cliId;
475
504
  try {
476
505
  saveConfig(carried);
477
506
  }
@@ -12,3 +12,26 @@
12
12
  * 128-char + printable-ASCII guard.
13
13
  */
14
14
  export declare function generateCliId(): string;
15
+ /**
16
+ * The machine cliId for ONE config root (OSK-12179).
17
+ *
18
+ * `generateCliId()` hashes only (host, user), so every root on the machine got
19
+ * the same id: a fresh `SKRR_CONFIG_DIR` root's default profile minted exactly
20
+ * the id the real `~/.skrr` root had been presenting. The server's
21
+ * `cli-handoff/confirm` retires every OTHER live family of (user, cliId), so a
22
+ * sign-in in the second root could end the first root's session. The Keychain
23
+ * slots were already separated by root (OSK-12135), and a named profile's own
24
+ * id already hashes its root (`profileScopedCliId`); this was the one identity
25
+ * that did not.
26
+ *
27
+ * The DEFAULT root keeps `generateCliId()` byte for byte. Every install already
28
+ * stores or re-derives that value, every live session is bound to it, and a
29
+ * refresh presenting another id is refused — so changing it would sign the
30
+ * primary root out. Only a root somewhere else gets its own id, deterministic in
31
+ * (host, user, root) so a root that loses its config file and signs in again
32
+ * presents the same id rather than scattering families.
33
+ *
34
+ * A root is the default one when it resolves to the default path, however it
35
+ * was reached: `SKRR_CONFIG_DIR=~/.skrr` is the primary root, not a second one.
36
+ */
37
+ export declare function machineCliIdForRoot(root: string, defaultRoot: string): string;
@@ -1,11 +1,46 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
5
38
  Object.defineProperty(exports, "__esModule", { value: true });
6
39
  exports.generateCliId = generateCliId;
40
+ exports.machineCliIdForRoot = machineCliIdForRoot;
7
41
  const node_crypto_1 = require("node:crypto");
8
42
  const node_os_1 = __importDefault(require("node:os"));
43
+ const path = __importStar(require("node:path"));
9
44
  /**
10
45
  * Derive a stable per-machine identifier the server will treat as a
11
46
  * "device" for the device-code flow. Mirrors the daemon's `generateDaemonId`
@@ -25,3 +60,36 @@ function generateCliId() {
25
60
  const digest = (0, node_crypto_1.createHash)('sha256').update(`${hostname}|${username}|cli`).digest('hex');
26
61
  return `cli_${digest.slice(0, 12)}`;
27
62
  }
63
+ /**
64
+ * The machine cliId for ONE config root (OSK-12179).
65
+ *
66
+ * `generateCliId()` hashes only (host, user), so every root on the machine got
67
+ * the same id: a fresh `SKRR_CONFIG_DIR` root's default profile minted exactly
68
+ * the id the real `~/.skrr` root had been presenting. The server's
69
+ * `cli-handoff/confirm` retires every OTHER live family of (user, cliId), so a
70
+ * sign-in in the second root could end the first root's session. The Keychain
71
+ * slots were already separated by root (OSK-12135), and a named profile's own
72
+ * id already hashes its root (`profileScopedCliId`); this was the one identity
73
+ * that did not.
74
+ *
75
+ * The DEFAULT root keeps `generateCliId()` byte for byte. Every install already
76
+ * stores or re-derives that value, every live session is bound to it, and a
77
+ * refresh presenting another id is refused — so changing it would sign the
78
+ * primary root out. Only a root somewhere else gets its own id, deterministic in
79
+ * (host, user, root) so a root that loses its config file and signs in again
80
+ * presents the same id rather than scattering families.
81
+ *
82
+ * A root is the default one when it resolves to the default path, however it
83
+ * was reached: `SKRR_CONFIG_DIR=~/.skrr` is the primary root, not a second one.
84
+ */
85
+ function machineCliIdForRoot(root, defaultRoot) {
86
+ const resolved = path.resolve(root);
87
+ if (resolved === path.resolve(defaultRoot))
88
+ return generateCliId();
89
+ const hostname = node_os_1.default.hostname();
90
+ const username = node_os_1.default.userInfo().username || 'unknown';
91
+ const digest = (0, node_crypto_1.createHash)('sha256')
92
+ .update(`${hostname}|${username}|cli|root:${resolved}`)
93
+ .digest('hex');
94
+ return `cli_${digest.slice(0, 12)}`;
95
+ }
@@ -75,7 +75,6 @@ const auth_core_init_1 = require("./auth-core-init");
75
75
  const config_1 = require("./config");
76
76
  const cli_id_1 = require("./cli-id");
77
77
  const cred_envelope_1 = require("./cred-envelope");
78
- const device_id_1 = require("./device-id");
79
78
  const first_party_harness_1 = require("./first-party-harness");
80
79
  const first_party_harness_agent_1 = require("./first-party-harness-agent");
81
80
  const first_party_harness_managed_1 = require("./first-party-harness-managed");
@@ -862,8 +861,9 @@ function hasLocalCredential() {
862
861
  *
863
862
  * The id login and refresh present, read through `resolveCliId` (a named
864
863
  * profile may have its own, OSK-12156); regenerated deterministically as the
865
- * machine id when nothing is stored (same hostname+user always yields the same
866
- * value). Normalized to the `cli_` namespace the server requires,
864
+ * machine id of THIS config root when nothing is stored (the same host, user
865
+ * and root always yield the same value; a relocated root does not share the
866
+ * primary root's id, OSK-12179). Normalized to the `cli_` namespace the server requires,
867
867
  * because `OVERSKY_CLI_ID` is overlaid verbatim with no format check and a
868
868
  * hand-set value would otherwise come back as an opaque
869
869
  * `runtime-id-namespace-mismatch`.
@@ -879,7 +879,7 @@ function localRuntimeId() {
879
879
  })();
880
880
  if (configured?.startsWith('cli_'))
881
881
  return configured;
882
- return (0, device_id_1.generateCliId)();
882
+ return (0, config_1.machineCliId)();
883
883
  }
884
884
  function errorCodeOf(err) {
885
885
  const code = err?.code;