@skrr-ai/cli 0.1.43 → 0.1.44

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 (63) hide show
  1. package/dist/base-command.d.ts +2 -0
  2. package/dist/base-command.js +1 -0
  3. package/dist/commands/balance/index.js +1 -1
  4. package/dist/commands/balance/show.d.ts +32 -0
  5. package/dist/commands/balance/show.js +74 -3
  6. package/dist/commands/code/handover.d.ts +1 -0
  7. package/dist/commands/code/handover.js +4 -0
  8. package/dist/commands/code/jobs/run.d.ts +1 -0
  9. package/dist/commands/code/jobs/run.js +4 -0
  10. package/dist/commands/daemon/install.js +7 -0
  11. package/dist/commands/harnesses/leases/show.js +14 -0
  12. package/dist/commands/instructions/install.d.ts +22 -0
  13. package/dist/commands/instructions/install.js +83 -7
  14. package/dist/commands/instructions/list.js +5 -0
  15. package/dist/commands/instructions/show.d.ts +6 -0
  16. package/dist/commands/instructions/show.js +34 -1
  17. package/dist/commands/instructions/status.js +17 -1
  18. package/dist/commands/payments/wallet.js +2 -2
  19. package/dist/commands/tasks/create.d.ts +6 -0
  20. package/dist/commands/tasks/create.js +26 -3
  21. package/dist/commands/tasks/labels/attach.js +4 -0
  22. package/dist/commands/tasks/list.d.ts +1 -0
  23. package/dist/commands/tasks/list.js +9 -0
  24. package/dist/commands/tasks/show.d.ts +23 -0
  25. package/dist/commands/tasks/show.js +61 -1
  26. package/dist/commands/tasks/update.js +12 -0
  27. package/dist/commands/views/create.js +12 -2
  28. package/dist/commands/views/list.js +3 -2
  29. package/dist/commands/views/show.js +2 -0
  30. package/dist/lib/agentic-stream.d.ts +10 -4
  31. package/dist/lib/agentic-stream.js +25 -11
  32. package/dist/lib/cli-installers.js +9 -1
  33. package/dist/lib/daemon-setup.d.ts +18 -1
  34. package/dist/lib/daemon-setup.js +33 -1
  35. package/dist/lib/first-party-harness-agent.d.ts +16 -1
  36. package/dist/lib/first-party-harness-agent.js +41 -12
  37. package/dist/lib/first-party-harness-doctor.js +34 -16
  38. package/dist/lib/first-party-harness.d.ts +18 -11
  39. package/dist/lib/first-party-harness.js +26 -21
  40. package/dist/lib/harnesses.d.ts +6 -0
  41. package/dist/lib/instruction-input.d.ts +21 -0
  42. package/dist/lib/instruction-input.js +30 -0
  43. package/dist/lib/instruction-provenance.d.ts +54 -0
  44. package/dist/lib/instruction-provenance.js +84 -0
  45. package/dist/lib/task-view-render.d.ts +14 -0
  46. package/dist/lib/task-view-render.js +55 -0
  47. package/dist/lib/tasks.d.ts +18 -0
  48. package/dist/lib/tasks.js +22 -1
  49. package/dist/lib/views/vocabulary.d.ts +1 -1
  50. package/dist/lib/views/vocabulary.js +3 -1
  51. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +75 -24
  52. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +143 -34
  53. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.d.ts +21 -1
  54. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/legacyStatePreflight.js +75 -19
  55. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +75 -24
  56. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +138 -34
  57. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.d.ts +21 -1
  58. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/legacyStatePreflight.js +74 -19
  59. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  60. package/dist/node_modules/@skrr-ai/data-provider/index.js +3516 -3513
  61. package/dist/node_modules/@skrr-ai/data-provider/package.json +1 -1
  62. package/oclif.manifest.json +21649 -21626
  63. package/package.json +3 -3
@@ -213,7 +213,8 @@ export declare function firstPartyHarnessBinaryNames(platform?: string): readonl
213
213
  * the managed `AGENTS.md` from `<home>`. Deriving it from the provider would, on
214
214
  * a fresh install after a provider rename, create a second directory the engine
215
215
  * never reads — managed instructions would stop applying with no error. It moves
216
- * when the engine's namespace moves, and not before.
216
+ * when the engine's namespace moves, and not before; it did in OSK-8674, and
217
+ * `migrateFirstPartyHarnessHome` moves a home under a legacy name onto it.
217
218
  */
218
219
  export declare function firstPartyHarnessHomeDirname(): string;
219
220
  /** Signed release manifest file name. */
@@ -250,7 +251,8 @@ export interface FirstPartyHarnessEnvName {
250
251
  * (OSK-8663) and are not read at all now. The ENGINE's own variables are a
251
252
  * different namespace (`FIRST_PARTY_HARNESS_ENGINE.env`): the fork reads them,
252
253
  * so a platform reader that must agree with the engine consults the engine's
253
- * name explicitly — the engine home is the one case (`home` below).
254
+ * names explicitly (`readFirstPartyHarnessEngineEnv`) — the engine home is the
255
+ * one case (`home` below).
254
256
  */
255
257
  export declare const FIRST_PARTY_HARNESS_ENV: Readonly<{
256
258
  /** Absolute path to an engine binary, overriding resolution. */
@@ -266,10 +268,11 @@ export declare const FIRST_PARTY_HARNESS_ENV: Readonly<{
266
268
  /** Engine version the desktop build bundles. */
267
269
  bundleVersion: FirstPartyHarnessEnvName;
268
270
  /**
269
- * Engine home override. The engine reads only its own variable
270
- * (`FIRST_PARTY_HARNESS_ENGINE.env.home`), so a reader that resolves the home
271
- * the engine will use reads that one second, and a launcher hands the engine
272
- * the resolved path under it.
271
+ * Engine home override. The engine reads only its own variable, under every
272
+ * name it has had (`readFirstPartyHarnessEngineEnv(env, 'home')`), so a reader
273
+ * that resolves the home the engine will use reads those second, and a
274
+ * launcher hands the engine the resolved path under every one of them
275
+ * (`firstPartyHarnessEngineEnvEntries`).
273
276
  */
274
277
  home: FirstPartyHarnessEnvName;
275
278
  /** Engine version baked into the cloud-coding image. */
@@ -294,36 +297,84 @@ export interface FirstPartyHarnessEnvReading {
294
297
  }
295
298
  /** Read one purpose's variable. A blank value counts as unset. */
296
299
  export declare function readFirstPartyHarnessEnv(env: Readonly<Record<string, string | undefined>>, key: FirstPartyHarnessEnvKey): FirstPartyHarnessEnvReading;
300
+ /**
301
+ * Every variable the engine reads, by purpose, as the suffix after the app
302
+ * name's prefix (`skrr-code` → `SKRR_CODE_<suffix>`). The fork names all of its
303
+ * own variables this way, so the names follow the namespace rather than being
304
+ * written out once per spelling.
305
+ */
306
+ declare const ENGINE_ENV_SUFFIXES: Readonly<{
307
+ serverPassword: "SERVER_PASSWORD";
308
+ serverUsername: "SERVER_USERNAME";
309
+ home: "HOME";
310
+ config: "CONFIG";
311
+ appName: "APP_NAME";
312
+ scriptName: "SCRIPT_NAME";
313
+ allowUpstreamEgress: "ALLOW_UPSTREAM_EGRESS";
314
+ compatOpencode: "COMPAT_OPENCODE";
315
+ noCompatPrompts: "NO_COMPAT_PROMPTS";
316
+ }>;
317
+ export type FirstPartyHarnessEngineEnvKey = keyof typeof ENGINE_ENV_SUFFIXES;
297
318
  /**
298
319
  * Names the ENGINE itself defines and reads. The platform sets or reads them
299
320
  * across the daemon↔engine boundary, so they are recorded here as values rather
300
- * than scattered as literals — but they are the fork's to change, and moving
301
- * one is a two-sided change (contract §6). Not part of this identity's rename.
321
+ * than scattered as literals. They are the fork's names, and moving one is a
322
+ * two-sided change: the engine reads the new name first and every legacy name
323
+ * second, and while engine and platform releases can arrive in either order the
324
+ * platform SETS every name and READS new-then-legacy (contract §6, OSK-8674).
302
325
  */
303
326
  export declare const FIRST_PARTY_HARNESS_ENGINE: Readonly<{
304
327
  /**
305
328
  * The engine's state namespace: XDG dirs, project marker, config basename, and
306
329
  * the engine home under the skrr root (`firstPartyHarnessHomeDirname`).
307
330
  */
308
- appName: "sky-code";
331
+ appName: string;
309
332
  /**
310
- * Earlier `appName`s whose home directory `migrateFirstPartyHarnessHome` moves
311
- * onto the current one. Empty until the engine's namespace is renamed.
333
+ * Earlier `appName`s, newest first. `migrateFirstPartyHarnessHome` moves a home
334
+ * under one of them onto the current name and leaves a symlink; readers of
335
+ * engine state look under them after the current name.
312
336
  */
313
337
  legacyAppNames: readonly string[];
314
338
  /** Project-local configuration directory inside a user's repository. */
315
- projectDirectory: ".sky-code";
316
- /** Environment variables the engine reads. */
317
- env: Readonly<{
318
- serverPassword: "SKY_CODE_SERVER_PASSWORD";
319
- serverUsername: "SKY_CODE_SERVER_USERNAME";
320
- home: "SKY_CODE_HOME";
321
- config: "SKY_CODE_CONFIG";
322
- appName: "SKY_CODE_APP_NAME";
323
- scriptName: "SKY_CODE_SCRIPT_NAME";
324
- allowUpstreamEgress: "SKY_CODE_ALLOW_UPSTREAM_EGRESS";
325
- compatOpencode: "SKY_CODE_COMPAT_OPENCODE";
326
- noCompatPrompts: "SKY_CODE_NO_COMPAT_PROMPTS";
327
- }>;
339
+ projectDirectory: `.${string}`;
340
+ /**
341
+ * Earlier project directories, newest first. The engine still reads them and
342
+ * the platform never rewrites a user's repository to move one, so a reader
343
+ * looks here after `projectDirectory`, and a writer adopts an existing file
344
+ * here rather than creating a second one beside it.
345
+ */
346
+ legacyProjectDirectories: readonly string[];
347
+ /**
348
+ * Environment variables the engine reads, under the CURRENT namespace. The
349
+ * engine also reads each one under every legacy namespace, second — use
350
+ * `readFirstPartyHarnessEngineEnv` to read one the way the engine does and
351
+ * `firstPartyHarnessEngineEnvEntries` to set one every engine build honours.
352
+ */
353
+ env: Readonly<Record<"appName" | "serverPassword" | "serverUsername" | "home" | "config" | "scriptName" | "allowUpstreamEgress" | "compatOpencode" | "noCompatPrompts", string>>;
328
354
  }>;
355
+ /**
356
+ * Every name the engine reads for one purpose, current namespace first — the
357
+ * engine's own lookup order.
358
+ */
359
+ export declare function firstPartyHarnessEngineEnvNames(key: FirstPartyHarnessEngineEnvKey): readonly string[];
360
+ /**
361
+ * Read one engine variable the way the engine reads it: the current name, then
362
+ * every legacy name. A blank value counts as unset.
363
+ */
364
+ export declare function readFirstPartyHarnessEngineEnv(env: Readonly<Record<string, string | undefined>>, key: FirstPartyHarnessEngineEnvKey): FirstPartyHarnessEnvReading;
365
+ /**
366
+ * One engine variable set under EVERY name the engine has read, for a process
367
+ * that spawns the engine. An engine binary is installed on its own schedule, so
368
+ * a launcher cannot know whether the one it starts reads the current name or a
369
+ * legacy one; setting both means either build receives the value.
370
+ */
371
+ export declare function firstPartyHarnessEngineEnvEntries(key: FirstPartyHarnessEngineEnvKey, value: string): Record<string, string>;
372
+ /** Every engine project directory, current first — the engine's read order. */
373
+ export declare function firstPartyHarnessEngineProjectDirectories(): readonly string[];
374
+ /**
375
+ * Every engine home directory name under a config root, current first: where an
376
+ * engine home may exist on disk. `firstPartyHarnessHomeDirname` is the one the
377
+ * platform writes.
378
+ */
379
+ export declare function firstPartyHarnessEngineHomeDirnames(): readonly string[];
329
380
  export {};
@@ -290,7 +290,8 @@ export function firstPartyHarnessBinaryNames(platform) {
290
290
  * the managed `AGENTS.md` from `<home>`. Deriving it from the provider would, on
291
291
  * a fresh install after a provider rename, create a second directory the engine
292
292
  * never reads — managed instructions would stop applying with no error. It moves
293
- * when the engine's namespace moves, and not before.
293
+ * when the engine's namespace moves, and not before; it did in OSK-8674, and
294
+ * `migrateFirstPartyHarnessHome` moves a home under a legacy name onto it.
294
295
  */
295
296
  export function firstPartyHarnessHomeDirname() {
296
297
  return FIRST_PARTY_HARNESS_ENGINE.appName;
@@ -338,7 +339,8 @@ function envName(name) {
338
339
  * (OSK-8663) and are not read at all now. The ENGINE's own variables are a
339
340
  * different namespace (`FIRST_PARTY_HARNESS_ENGINE.env`): the fork reads them,
340
341
  * so a platform reader that must agree with the engine consults the engine's
341
- * name explicitly — the engine home is the one case (`home` below).
342
+ * names explicitly (`readFirstPartyHarnessEngineEnv`) — the engine home is the
343
+ * one case (`home` below).
342
344
  */
343
345
  export const FIRST_PARTY_HARNESS_ENV = Object.freeze({
344
346
  /** Absolute path to an engine binary, overriding resolution. */
@@ -354,10 +356,11 @@ export const FIRST_PARTY_HARNESS_ENV = Object.freeze({
354
356
  /** Engine version the desktop build bundles. */
355
357
  bundleVersion: envName('SKRR_FIRST_PARTY_HARNESS_BUNDLE_VERSION'),
356
358
  /**
357
- * Engine home override. The engine reads only its own variable
358
- * (`FIRST_PARTY_HARNESS_ENGINE.env.home`), so a reader that resolves the home
359
- * the engine will use reads that one second, and a launcher hands the engine
360
- * the resolved path under it.
359
+ * Engine home override. The engine reads only its own variable, under every
360
+ * name it has had (`readFirstPartyHarnessEngineEnv(env, 'home')`), so a reader
361
+ * that resolves the home the engine will use reads those second, and a
362
+ * launcher hands the engine the resolved path under every one of them
363
+ * (`firstPartyHarnessEngineEnvEntries`).
361
364
  */
362
365
  home: envName('SKRR_FIRST_PARTY_HARNESS_HOME'),
363
366
  /** Engine version baked into the cloud-coding image. */
@@ -383,35 +386,136 @@ export function readFirstPartyHarnessEnv(env, key) {
383
386
  return { value: undefined, name: undefined };
384
387
  }
385
388
  /* ── The ENGINE's own namespace (defined inside the fork) ───────────────── */
389
+ /**
390
+ * The engine's state namespace today. Moved from `sky-code` by OSK-8674: XDG
391
+ * dirs, project directory, config basename, the engine home under the skrr
392
+ * root, and every variable the engine reads all derive from it.
393
+ */
394
+ const ENGINE_APP_NAME = 'skrr-code';
395
+ /**
396
+ * Earlier engine namespaces, newest first. Still READ on both sides of the
397
+ * daemon↔engine boundary: an engine binary, a daemon, a CLI and a desktop app
398
+ * each ship on their own schedule, and a machine holds state written under
399
+ * every name it has run.
400
+ */
401
+ const ENGINE_LEGACY_APP_NAMES = ['sky-code'];
402
+ /**
403
+ * Every variable the engine reads, by purpose, as the suffix after the app
404
+ * name's prefix (`skrr-code` → `SKRR_CODE_<suffix>`). The fork names all of its
405
+ * own variables this way, so the names follow the namespace rather than being
406
+ * written out once per spelling.
407
+ */
408
+ const ENGINE_ENV_SUFFIXES = Object.freeze({
409
+ serverPassword: 'SERVER_PASSWORD',
410
+ serverUsername: 'SERVER_USERNAME',
411
+ home: 'HOME',
412
+ config: 'CONFIG',
413
+ appName: 'APP_NAME',
414
+ scriptName: 'SCRIPT_NAME',
415
+ allowUpstreamEgress: 'ALLOW_UPSTREAM_EGRESS',
416
+ compatOpencode: 'COMPAT_OPENCODE',
417
+ noCompatPrompts: 'NO_COMPAT_PROMPTS',
418
+ });
419
+ function engineEnvName(appName, key) {
420
+ return `${appName.toUpperCase().replace(/-/g, '_')}_${ENGINE_ENV_SUFFIXES[key]}`;
421
+ }
422
+ function engineEnvTable(appName) {
423
+ return Object.freeze(Object.fromEntries(Object.keys(ENGINE_ENV_SUFFIXES).map((key) => [
424
+ key,
425
+ engineEnvName(appName, key),
426
+ ])));
427
+ }
428
+ function defineEngineNamespace(appName, legacyAppNames) {
429
+ const every = [appName, ...legacyAppNames];
430
+ for (const name of every) {
431
+ if (!SLUG.test(name)) {
432
+ throw new Error(`first-party harness engine app name ${JSON.stringify(name)} is not a slug; it becomes a ` +
433
+ 'directory name and a variable prefix');
434
+ }
435
+ }
436
+ if (new Set(every).size !== every.length) {
437
+ throw new Error('first-party harness engine appName and legacyAppNames must be distinct');
438
+ }
439
+ return Object.freeze({
440
+ /**
441
+ * The engine's state namespace: XDG dirs, project marker, config basename, and
442
+ * the engine home under the skrr root (`firstPartyHarnessHomeDirname`).
443
+ */
444
+ appName,
445
+ /**
446
+ * Earlier `appName`s, newest first. `migrateFirstPartyHarnessHome` moves a home
447
+ * under one of them onto the current name and leaves a symlink; readers of
448
+ * engine state look under them after the current name.
449
+ */
450
+ legacyAppNames: Object.freeze([...legacyAppNames]),
451
+ /** Project-local configuration directory inside a user's repository. */
452
+ projectDirectory: `.${appName}`,
453
+ /**
454
+ * Earlier project directories, newest first. The engine still reads them and
455
+ * the platform never rewrites a user's repository to move one, so a reader
456
+ * looks here after `projectDirectory`, and a writer adopts an existing file
457
+ * here rather than creating a second one beside it.
458
+ */
459
+ legacyProjectDirectories: Object.freeze(legacyAppNames.map((name) => `.${name}`)),
460
+ /**
461
+ * Environment variables the engine reads, under the CURRENT namespace. The
462
+ * engine also reads each one under every legacy namespace, second — use
463
+ * `readFirstPartyHarnessEngineEnv` to read one the way the engine does and
464
+ * `firstPartyHarnessEngineEnvEntries` to set one every engine build honours.
465
+ */
466
+ env: engineEnvTable(appName),
467
+ });
468
+ }
386
469
  /**
387
470
  * Names the ENGINE itself defines and reads. The platform sets or reads them
388
471
  * across the daemon↔engine boundary, so they are recorded here as values rather
389
- * than scattered as literals — but they are the fork's to change, and moving
390
- * one is a two-sided change (contract §6). Not part of this identity's rename.
472
+ * than scattered as literals. They are the fork's names, and moving one is a
473
+ * two-sided change: the engine reads the new name first and every legacy name
474
+ * second, and while engine and platform releases can arrive in either order the
475
+ * platform SETS every name and READS new-then-legacy (contract §6, OSK-8674).
391
476
  */
392
- export const FIRST_PARTY_HARNESS_ENGINE = Object.freeze({
393
- /**
394
- * The engine's state namespace: XDG dirs, project marker, config basename, and
395
- * the engine home under the skrr root (`firstPartyHarnessHomeDirname`).
396
- */
397
- appName: 'sky-code',
398
- /**
399
- * Earlier `appName`s whose home directory `migrateFirstPartyHarnessHome` moves
400
- * onto the current one. Empty until the engine's namespace is renamed.
401
- */
402
- legacyAppNames: Object.freeze([]),
403
- /** Project-local configuration directory inside a user's repository. */
404
- projectDirectory: '.sky-code',
405
- /** Environment variables the engine reads. */
406
- env: Object.freeze({
407
- serverPassword: 'SKY_CODE_SERVER_PASSWORD',
408
- serverUsername: 'SKY_CODE_SERVER_USERNAME',
409
- home: 'SKY_CODE_HOME',
410
- config: 'SKY_CODE_CONFIG',
411
- appName: 'SKY_CODE_APP_NAME',
412
- scriptName: 'SKY_CODE_SCRIPT_NAME',
413
- allowUpstreamEgress: 'SKY_CODE_ALLOW_UPSTREAM_EGRESS',
414
- compatOpencode: 'SKY_CODE_COMPAT_OPENCODE',
415
- noCompatPrompts: 'SKY_CODE_NO_COMPAT_PROMPTS',
416
- }),
417
- });
477
+ export const FIRST_PARTY_HARNESS_ENGINE = defineEngineNamespace(ENGINE_APP_NAME, ENGINE_LEGACY_APP_NAMES);
478
+ /**
479
+ * Every name the engine reads for one purpose, current namespace first — the
480
+ * engine's own lookup order.
481
+ */
482
+ export function firstPartyHarnessEngineEnvNames(key) {
483
+ return [FIRST_PARTY_HARNESS_ENGINE.appName, ...FIRST_PARTY_HARNESS_ENGINE.legacyAppNames].map((appName) => engineEnvName(appName, key));
484
+ }
485
+ /**
486
+ * Read one engine variable the way the engine reads it: the current name, then
487
+ * every legacy name. A blank value counts as unset.
488
+ */
489
+ export function readFirstPartyHarnessEngineEnv(env, key) {
490
+ for (const name of firstPartyHarnessEngineEnvNames(key)) {
491
+ const raw = env[name];
492
+ if (typeof raw === 'string' && raw.trim() !== '') {
493
+ return { value: raw.trim(), name };
494
+ }
495
+ }
496
+ return { value: undefined, name: undefined };
497
+ }
498
+ /**
499
+ * One engine variable set under EVERY name the engine has read, for a process
500
+ * that spawns the engine. An engine binary is installed on its own schedule, so
501
+ * a launcher cannot know whether the one it starts reads the current name or a
502
+ * legacy one; setting both means either build receives the value.
503
+ */
504
+ export function firstPartyHarnessEngineEnvEntries(key, value) {
505
+ return Object.fromEntries(firstPartyHarnessEngineEnvNames(key).map((name) => [name, value]));
506
+ }
507
+ /** Every engine project directory, current first — the engine's read order. */
508
+ export function firstPartyHarnessEngineProjectDirectories() {
509
+ return [
510
+ FIRST_PARTY_HARNESS_ENGINE.projectDirectory,
511
+ ...FIRST_PARTY_HARNESS_ENGINE.legacyProjectDirectories,
512
+ ];
513
+ }
514
+ /**
515
+ * Every engine home directory name under a config root, current first: where an
516
+ * engine home may exist on disk. `firstPartyHarnessHomeDirname` is the one the
517
+ * platform writes.
518
+ */
519
+ export function firstPartyHarnessEngineHomeDirnames() {
520
+ return [FIRST_PARTY_HARNESS_ENGINE.appName, ...FIRST_PARTY_HARNESS_ENGINE.legacyAppNames];
521
+ }
@@ -55,4 +55,24 @@ export interface LegacyStateReport {
55
55
  * real `~` passes or fails based on who runs it.
56
56
  */
57
57
  export declare function findLegacyLocalState(home?: string): LegacyStateReport;
58
- export declare function describeLegacyState(report: LegacyStateReport, binaryName: string): string;
58
+ /**
59
+ * Where the full procedure lives, by repository path. The refusal used to say
60
+ * "follow the team cutover runbook" without naming one, which stopped a person
61
+ * at a pointer they could not follow (OSK-6895). The steps the refusal prints
62
+ * are the runbook's own, so the path is for the parts a terminal message cannot
63
+ * carry: Linux and Windows service removal, Keychain entries, the old app.
64
+ */
65
+ export declare const LEGACY_CUTOVER_RUNBOOK = "docs/runbooks/skrr-cutover-dev-machine.md";
66
+ /**
67
+ * The message shown when preflight refuses.
68
+ *
69
+ * Written for someone who has just been stopped and wants to know why and what
70
+ * to do — so it names the exact paths, says explicitly that nothing was touched,
71
+ * and prints the cutover as commands to run rather than a pointer to follow. It
72
+ * does NOT offer a `--force`: the operator's next step is the backup, and a flag
73
+ * that skips a safety check is the flag everyone learns to paste.
74
+ *
75
+ * `command` is what the operator re-runs to install — `skrrd` when the runtime
76
+ * refused, `skrr daemon` when the CLI refused before fetching one.
77
+ */
78
+ export declare function describeLegacyState(report: LegacyStateReport, command: string, platform?: NodeJS.Platform): string;
@@ -43,22 +43,69 @@ export function findLegacyLocalState(home = os.homedir()) {
43
43
  return { findings, hasLegacyState: findings.length > 0 };
44
44
  }
45
45
  /**
46
- * The message shown when preflight refuses.
47
- *
48
- * Written for someone who has just been stopped and wants to know why and what
49
- * to do — so it names the exact paths, says explicitly that nothing was touched,
50
- * and points at the runbook rather than improvising a fix. It does NOT offer a
51
- * `--force`: the operator's next step is the backup the runbook specifies, and
52
- * a flag that skips a safety check is the flag everyone learns to paste.
53
- */
54
- /**
55
- * The pre-rename runtime binary. Named in the refusal above because deregistering
46
+ * The pre-rename runtime binary. Named in the refusal below because deregistering
56
47
  * the OLD service is the one step the NEW binary cannot do: `uninstall` resolves
57
48
  * the current profile's label (`ai.skrr.daemon.*`), and the legacy sweep is wired
58
49
  * only into the install path.
59
50
  */
60
51
  const LEGACY_BINARY_NAME = 'oversky';
61
- export function describeLegacyState(report, binaryName) {
52
+ /**
53
+ * Where the full procedure lives, by repository path. The refusal used to say
54
+ * "follow the team cutover runbook" without naming one, which stopped a person
55
+ * at a pointer they could not follow (OSK-6895). The steps the refusal prints
56
+ * are the runbook's own, so the path is for the parts a terminal message cannot
57
+ * carry: Linux and Windows service removal, Keychain entries, the old app.
58
+ */
59
+ export const LEGACY_CUTOVER_RUNBOOK = 'docs/runbooks/skrr-cutover-dev-machine.md';
60
+ /** Credential file patterns the runbook strips from its backup (§5.5). */
61
+ const BACKUP_CREDENTIAL_PATTERNS = ['auth.json', 'cli-auth.json', '*token*'];
62
+ function posixQuote(value) {
63
+ return `'${value.split("'").join(`'\\''`)}'`;
64
+ }
65
+ function powershellQuote(value) {
66
+ return `'${value.split("'").join("''")}'`;
67
+ }
68
+ /**
69
+ * The commands for the three steps a person runs by hand, in the shell they are
70
+ * actually in. A POSIX `rm -rf` pasted into PowerShell is not an instruction.
71
+ */
72
+ function cutoverCommands(paths, platform) {
73
+ if (platform === 'win32') {
74
+ const backupDir = '"$HOME\\skrr-cutover-backup"';
75
+ return {
76
+ backup: [
77
+ `New-Item -ItemType Directory -Force ${backupDir} | Out-Null`,
78
+ ...paths.map((p) => `Copy-Item -Recurse ${powershellQuote(p)} ${backupDir}`),
79
+ `Get-ChildItem -Recurse -File ${backupDir} -Include ${BACKUP_CREDENTIAL_PATTERNS.join(',')} | Remove-Item`,
80
+ ],
81
+ remove: paths.map((p) => `Remove-Item -Recurse -Force ${powershellQuote(p)}`),
82
+ };
83
+ }
84
+ const names = BACKUP_CREDENTIAL_PATTERNS.map((n) => `-name '${n}'`).join(' -o ');
85
+ return {
86
+ backup: [
87
+ 'mkdir -p ~/skrr-cutover-backup && chmod 700 ~/skrr-cutover-backup',
88
+ `cp -a ${paths.map(posixQuote).join(' ')} ~/skrr-cutover-backup/`,
89
+ `find ~/skrr-cutover-backup -type f \\( ${names} \\) -delete`,
90
+ ],
91
+ remove: [`rm -rf ${paths.map(posixQuote).join(' ')}`],
92
+ };
93
+ }
94
+ /**
95
+ * The message shown when preflight refuses.
96
+ *
97
+ * Written for someone who has just been stopped and wants to know why and what
98
+ * to do — so it names the exact paths, says explicitly that nothing was touched,
99
+ * and prints the cutover as commands to run rather than a pointer to follow. It
100
+ * does NOT offer a `--force`: the operator's next step is the backup, and a flag
101
+ * that skips a safety check is the flag everyone learns to paste.
102
+ *
103
+ * `command` is what the operator re-runs to install — `skrrd` when the runtime
104
+ * refused, `skrr daemon` when the CLI refused before fetching one.
105
+ */
106
+ export function describeLegacyState(report, command, platform = process.platform) {
107
+ const paths = report.findings.map((f) => f.path);
108
+ const { backup, remove } = cutoverCommands(paths, platform);
62
109
  const lines = [];
63
110
  lines.push('Found state from the previous product identity on this machine:');
64
111
  lines.push('');
@@ -69,20 +116,28 @@ export function describeLegacyState(report, binaryName) {
69
116
  lines.push('Installing beside it risks two runtimes holding two different service');
70
117
  lines.push('labels, each believing it owns the single-instance lock.');
71
118
  lines.push('');
72
- lines.push('Nothing above has been read, copied or removed. Follow the team cutover');
73
- lines.push('runbook — it takes a backup first — and then run this again:');
119
+ lines.push('Nothing above has been read, copied or removed, and no service was registered.');
120
+ lines.push('To finish the cutover:');
74
121
  lines.push('');
122
+ lines.push(' 1. Keep a copy, with its credentials removed, in case you need an old setting:');
123
+ for (const line of backup)
124
+ lines.push(` ${line}`);
75
125
  // The OLD binary, deliberately: it is the one that knows the old service
76
126
  // label. `<current> uninstall` resolves the CURRENT profile's label and would
77
127
  // report success while leaving the pre-rename service registered. Guarded by
78
128
  // "only if" rather than omitted, because this module inspects directories and
79
129
  // has no way to know whether that binary is still on PATH.
80
- lines.push(` ${LEGACY_BINARY_NAME} uninstall # only if the old runtime is still installed —`);
81
- lines.push(` # it is the one that knows the old service label`);
82
- lines.push(' # then remove the directories listed above, per the runbook');
130
+ lines.push(' 2. If the old runtime is still installed, deregister it with the old binary —');
131
+ lines.push(' it is the one that knows the old service label:');
132
+ lines.push(` ${LEGACY_BINARY_NAME} uninstall`);
133
+ lines.push(' 3. Remove the directories listed above:');
134
+ for (const line of remove)
135
+ lines.push(` ${line}`);
136
+ lines.push(' 4. Run this again:');
137
+ lines.push(` ${command} install`);
83
138
  lines.push('');
84
- lines.push(`Any pre-rename service still registered is swept by \`${binaryName} install\``);
85
- lines.push('once those directories are gone, so re-running the install completes the');
86
- lines.push('cutover either way.');
139
+ lines.push(`Any pre-rename service still registered is swept by \`${command} install\``);
140
+ lines.push('once those directories are gone, so step 4 completes the cutover either way.');
141
+ lines.push(`Full runbook, including Keychain entries and the old app: ${LEGACY_CUTOVER_RUNBOOK}`);
87
142
  return lines.join('\n');
88
143
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/auth-core",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "main": "dist/cjs/index.js",
5
5
  "types": "dist/esm/index.d.ts",
6
6
  "exports": {