@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
@@ -7,6 +7,24 @@ import type { ParsedDeliverableCoordinate, ParsedExpectationSpec } from '@skrr-a
7
7
  * Undefined means the flag was not passed.
8
8
  */
9
9
  export declare function resolveTaskGoalFlag(value: string | undefined): string | null | undefined;
10
+ export declare const TASK_GOAL_NOT_FOUND = "TASK_GOAL_NOT_FOUND";
11
+ /**
12
+ * The refusal for a goal id that names no goal, in CLI terms.
13
+ *
14
+ * The server's sentence carries the API's remedy — "detach the goal with
15
+ * goalId: null" on update, "leave goalId out" on create — and a CLI user can
16
+ * type neither. So the goal id THIS command sent is named, with the flag that
17
+ * gets past the refusal: `--goal none` on update (see `resolveTaskGoalFlag`),
18
+ * leaving the goal out on create, where no goal is simply the default
19
+ * (OSK-10219). `goalSource: 'json'` is a create whose id came from
20
+ * `--from-json` rather than `--goal`.
21
+ */
22
+ export declare function describeTaskGoalNotFound({ goalId, command, bin, goalSource, }: {
23
+ goalId: string;
24
+ command: 'create' | 'update';
25
+ bin?: string;
26
+ goalSource?: 'flag' | 'json';
27
+ }): string;
10
28
  export declare const TASK_PRIORITIES: readonly ["low", "medium", "high", "critical"];
11
29
  /**
12
30
  * The kinds `tasks events append --kind` will accept.
package/dist/lib/tasks.js CHANGED
@@ -1,7 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.RECOGNIZED_TASK_VERDICTS = exports.DELIVERABLE_LIFECYCLE_VALUES = exports.EXPECT_FLAG_HELP = exports.TASK_WORK_EVENT_KINDS = exports.TASK_PRIORITIES = void 0;
3
+ exports.RECOGNIZED_TASK_VERDICTS = exports.DELIVERABLE_LIFECYCLE_VALUES = exports.EXPECT_FLAG_HELP = exports.TASK_WORK_EVENT_KINDS = exports.TASK_PRIORITIES = exports.TASK_GOAL_NOT_FOUND = void 0;
4
4
  exports.resolveTaskGoalFlag = resolveTaskGoalFlag;
5
+ exports.describeTaskGoalNotFound = describeTaskGoalNotFound;
5
6
  exports.releasePinRepairGuard = releasePinRepairGuard;
6
7
  exports.taskUpdateMutationGuard = taskUpdateMutationGuard;
7
8
  exports.executionIsolationFromWorktreeFlag = executionIsolationFromWorktreeFlag;
@@ -52,6 +53,26 @@ function resolveTaskGoalFlag(value) {
52
53
  return null;
53
54
  return v;
54
55
  }
56
+ exports.TASK_GOAL_NOT_FOUND = 'TASK_GOAL_NOT_FOUND';
57
+ /**
58
+ * The refusal for a goal id that names no goal, in CLI terms.
59
+ *
60
+ * The server's sentence carries the API's remedy — "detach the goal with
61
+ * goalId: null" on update, "leave goalId out" on create — and a CLI user can
62
+ * type neither. So the goal id THIS command sent is named, with the flag that
63
+ * gets past the refusal: `--goal none` on update (see `resolveTaskGoalFlag`),
64
+ * leaving the goal out on create, where no goal is simply the default
65
+ * (OSK-10219). `goalSource: 'json'` is a create whose id came from
66
+ * `--from-json` rather than `--goal`.
67
+ */
68
+ function describeTaskGoalNotFound({ goalId, command, bin = 'skrr', goalSource = 'flag', }) {
69
+ const find = `Pass an existing goal id (\`${bin} goals list\` shows them)`;
70
+ if (command === 'update') {
71
+ return `No goal "${goalId}" exists. ${find}, or detach the task from its goal with \`--goal none\`.`;
72
+ }
73
+ const omit = goalSource === 'json' ? 'remove goalId from the --from-json body' : 'leave out --goal';
74
+ return `No goal "${goalId}" exists, so no task was created. ${find}, or ${omit} to create it without a goal.`;
75
+ }
55
76
  exports.TASK_PRIORITIES = ['low', 'medium', 'high', 'critical'];
56
77
  const DESCRIPTION_FORMATS = ['html', 'markdown', 'plain'];
57
78
  /**
@@ -5,4 +5,4 @@
5
5
  * a re-export, not a second CLI vocabulary: adding a saved-view capability is
6
6
  * one data-provider registration followed by renderer work on each surface.
7
7
  */
8
- export { SPACE_RENDERABLE_VIEW_TYPES, TASK_RENDERABLE_VIEW_TYPES, VIEW_GROUP_BY_REGISTRY, VIEW_GROUP_BY_VALUES, VIEW_QUICK_FILTER_REGISTRY, VIEW_QUICK_FILTERS, VIEW_SORT_DIRECTIONS, VIEW_SORT_KEY_REGISTRY, VIEW_SORT_KEYS, VIEW_TYPE_REGISTRY, VIEW_TYPES, viewTypesForSurface, } from '@skrr-ai/data-provider';
8
+ export { SPACE_RENDERABLE_VIEW_TYPES, TASK_VIEW_DEFAULT_GROUP_BY, TASK_VIEW_DEFAULT_TYPE, TASK_RENDERABLE_VIEW_TYPES, VIEW_GROUP_BY_REGISTRY, VIEW_GROUP_BY_VALUES, VIEW_QUICK_FILTER_REGISTRY, VIEW_QUICK_FILTERS, VIEW_SORT_DIRECTIONS, VIEW_SORT_KEY_REGISTRY, VIEW_SORT_KEYS, VIEW_TYPE_REGISTRY, VIEW_TYPES, viewTypesForSurface, } from '@skrr-ai/data-provider';
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.viewTypesForSurface = exports.VIEW_TYPES = exports.VIEW_TYPE_REGISTRY = exports.VIEW_SORT_KEYS = exports.VIEW_SORT_KEY_REGISTRY = exports.VIEW_SORT_DIRECTIONS = exports.VIEW_QUICK_FILTERS = exports.VIEW_QUICK_FILTER_REGISTRY = exports.VIEW_GROUP_BY_VALUES = exports.VIEW_GROUP_BY_REGISTRY = exports.TASK_RENDERABLE_VIEW_TYPES = exports.SPACE_RENDERABLE_VIEW_TYPES = void 0;
3
+ exports.viewTypesForSurface = exports.VIEW_TYPES = exports.VIEW_TYPE_REGISTRY = exports.VIEW_SORT_KEYS = exports.VIEW_SORT_KEY_REGISTRY = exports.VIEW_SORT_DIRECTIONS = exports.VIEW_QUICK_FILTERS = exports.VIEW_QUICK_FILTER_REGISTRY = exports.VIEW_GROUP_BY_VALUES = exports.VIEW_GROUP_BY_REGISTRY = exports.TASK_RENDERABLE_VIEW_TYPES = exports.TASK_VIEW_DEFAULT_TYPE = exports.TASK_VIEW_DEFAULT_GROUP_BY = exports.SPACE_RENDERABLE_VIEW_TYPES = void 0;
4
4
  /**
5
5
  * CLI parity re-export for persisted task views.
6
6
  *
@@ -10,6 +10,8 @@ exports.viewTypesForSurface = exports.VIEW_TYPES = exports.VIEW_TYPE_REGISTRY =
10
10
  */
11
11
  var data_provider_1 = require("@skrr-ai/data-provider");
12
12
  Object.defineProperty(exports, "SPACE_RENDERABLE_VIEW_TYPES", { enumerable: true, get: function () { return data_provider_1.SPACE_RENDERABLE_VIEW_TYPES; } });
13
+ Object.defineProperty(exports, "TASK_VIEW_DEFAULT_GROUP_BY", { enumerable: true, get: function () { return data_provider_1.TASK_VIEW_DEFAULT_GROUP_BY; } });
14
+ Object.defineProperty(exports, "TASK_VIEW_DEFAULT_TYPE", { enumerable: true, get: function () { return data_provider_1.TASK_VIEW_DEFAULT_TYPE; } });
13
15
  Object.defineProperty(exports, "TASK_RENDERABLE_VIEW_TYPES", { enumerable: true, get: function () { return data_provider_1.TASK_RENDERABLE_VIEW_TYPES; } });
14
16
  Object.defineProperty(exports, "VIEW_GROUP_BY_REGISTRY", { enumerable: true, get: function () { return data_provider_1.VIEW_GROUP_BY_REGISTRY; } });
15
17
  Object.defineProperty(exports, "VIEW_GROUP_BY_VALUES", { enumerable: true, get: function () { return data_provider_1.VIEW_GROUP_BY_VALUES; } });
@@ -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 {};
@@ -97,6 +97,11 @@ exports.firstPartyHarnessFeedPrefixes = firstPartyHarnessFeedPrefixes;
97
97
  exports.firstPartyHarnessStagingPrefix = firstPartyHarnessStagingPrefix;
98
98
  exports.firstPartyHarnessDefaultFeedBase = firstPartyHarnessDefaultFeedBase;
99
99
  exports.readFirstPartyHarnessEnv = readFirstPartyHarnessEnv;
100
+ exports.firstPartyHarnessEngineEnvNames = firstPartyHarnessEngineEnvNames;
101
+ exports.readFirstPartyHarnessEngineEnv = readFirstPartyHarnessEngineEnv;
102
+ exports.firstPartyHarnessEngineEnvEntries = firstPartyHarnessEngineEnvEntries;
103
+ exports.firstPartyHarnessEngineProjectDirectories = firstPartyHarnessEngineProjectDirectories;
104
+ exports.firstPartyHarnessEngineHomeDirnames = firstPartyHarnessEngineHomeDirnames;
100
105
  /** A slug usable as a directory name, a capability prefix and an event segment. */
101
106
  const SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
102
107
  function defineIdentity(identity) {
@@ -320,7 +325,8 @@ function firstPartyHarnessBinaryNames(platform) {
320
325
  * the managed `AGENTS.md` from `<home>`. Deriving it from the provider would, on
321
326
  * a fresh install after a provider rename, create a second directory the engine
322
327
  * never reads — managed instructions would stop applying with no error. It moves
323
- * when the engine's namespace moves, and not before.
328
+ * when the engine's namespace moves, and not before; it did in OSK-8674, and
329
+ * `migrateFirstPartyHarnessHome` moves a home under a legacy name onto it.
324
330
  */
325
331
  function firstPartyHarnessHomeDirname() {
326
332
  return exports.FIRST_PARTY_HARNESS_ENGINE.appName;
@@ -368,7 +374,8 @@ function envName(name) {
368
374
  * (OSK-8663) and are not read at all now. The ENGINE's own variables are a
369
375
  * different namespace (`FIRST_PARTY_HARNESS_ENGINE.env`): the fork reads them,
370
376
  * so a platform reader that must agree with the engine consults the engine's
371
- * name explicitly — the engine home is the one case (`home` below).
377
+ * names explicitly (`readFirstPartyHarnessEngineEnv`) — the engine home is the
378
+ * one case (`home` below).
372
379
  */
373
380
  exports.FIRST_PARTY_HARNESS_ENV = Object.freeze({
374
381
  /** Absolute path to an engine binary, overriding resolution. */
@@ -384,10 +391,11 @@ exports.FIRST_PARTY_HARNESS_ENV = Object.freeze({
384
391
  /** Engine version the desktop build bundles. */
385
392
  bundleVersion: envName('SKRR_FIRST_PARTY_HARNESS_BUNDLE_VERSION'),
386
393
  /**
387
- * Engine home override. The engine reads only its own variable
388
- * (`FIRST_PARTY_HARNESS_ENGINE.env.home`), so a reader that resolves the home
389
- * the engine will use reads that one second, and a launcher hands the engine
390
- * the resolved path under it.
394
+ * Engine home override. The engine reads only its own variable, under every
395
+ * name it has had (`readFirstPartyHarnessEngineEnv(env, 'home')`), so a reader
396
+ * that resolves the home the engine will use reads those second, and a
397
+ * launcher hands the engine the resolved path under every one of them
398
+ * (`firstPartyHarnessEngineEnvEntries`).
391
399
  */
392
400
  home: envName('SKRR_FIRST_PARTY_HARNESS_HOME'),
393
401
  /** Engine version baked into the cloud-coding image. */
@@ -413,35 +421,136 @@ function readFirstPartyHarnessEnv(env, key) {
413
421
  return { value: undefined, name: undefined };
414
422
  }
415
423
  /* ── The ENGINE's own namespace (defined inside the fork) ───────────────── */
424
+ /**
425
+ * The engine's state namespace today. Moved from `sky-code` by OSK-8674: XDG
426
+ * dirs, project directory, config basename, the engine home under the skrr
427
+ * root, and every variable the engine reads all derive from it.
428
+ */
429
+ const ENGINE_APP_NAME = 'skrr-code';
430
+ /**
431
+ * Earlier engine namespaces, newest first. Still READ on both sides of the
432
+ * daemon↔engine boundary: an engine binary, a daemon, a CLI and a desktop app
433
+ * each ship on their own schedule, and a machine holds state written under
434
+ * every name it has run.
435
+ */
436
+ const ENGINE_LEGACY_APP_NAMES = ['sky-code'];
437
+ /**
438
+ * Every variable the engine reads, by purpose, as the suffix after the app
439
+ * name's prefix (`skrr-code` → `SKRR_CODE_<suffix>`). The fork names all of its
440
+ * own variables this way, so the names follow the namespace rather than being
441
+ * written out once per spelling.
442
+ */
443
+ const ENGINE_ENV_SUFFIXES = Object.freeze({
444
+ serverPassword: 'SERVER_PASSWORD',
445
+ serverUsername: 'SERVER_USERNAME',
446
+ home: 'HOME',
447
+ config: 'CONFIG',
448
+ appName: 'APP_NAME',
449
+ scriptName: 'SCRIPT_NAME',
450
+ allowUpstreamEgress: 'ALLOW_UPSTREAM_EGRESS',
451
+ compatOpencode: 'COMPAT_OPENCODE',
452
+ noCompatPrompts: 'NO_COMPAT_PROMPTS',
453
+ });
454
+ function engineEnvName(appName, key) {
455
+ return `${appName.toUpperCase().replace(/-/g, '_')}_${ENGINE_ENV_SUFFIXES[key]}`;
456
+ }
457
+ function engineEnvTable(appName) {
458
+ return Object.freeze(Object.fromEntries(Object.keys(ENGINE_ENV_SUFFIXES).map((key) => [
459
+ key,
460
+ engineEnvName(appName, key),
461
+ ])));
462
+ }
463
+ function defineEngineNamespace(appName, legacyAppNames) {
464
+ const every = [appName, ...legacyAppNames];
465
+ for (const name of every) {
466
+ if (!SLUG.test(name)) {
467
+ throw new Error(`first-party harness engine app name ${JSON.stringify(name)} is not a slug; it becomes a ` +
468
+ 'directory name and a variable prefix');
469
+ }
470
+ }
471
+ if (new Set(every).size !== every.length) {
472
+ throw new Error('first-party harness engine appName and legacyAppNames must be distinct');
473
+ }
474
+ return Object.freeze({
475
+ /**
476
+ * The engine's state namespace: XDG dirs, project marker, config basename, and
477
+ * the engine home under the skrr root (`firstPartyHarnessHomeDirname`).
478
+ */
479
+ appName,
480
+ /**
481
+ * Earlier `appName`s, newest first. `migrateFirstPartyHarnessHome` moves a home
482
+ * under one of them onto the current name and leaves a symlink; readers of
483
+ * engine state look under them after the current name.
484
+ */
485
+ legacyAppNames: Object.freeze([...legacyAppNames]),
486
+ /** Project-local configuration directory inside a user's repository. */
487
+ projectDirectory: `.${appName}`,
488
+ /**
489
+ * Earlier project directories, newest first. The engine still reads them and
490
+ * the platform never rewrites a user's repository to move one, so a reader
491
+ * looks here after `projectDirectory`, and a writer adopts an existing file
492
+ * here rather than creating a second one beside it.
493
+ */
494
+ legacyProjectDirectories: Object.freeze(legacyAppNames.map((name) => `.${name}`)),
495
+ /**
496
+ * Environment variables the engine reads, under the CURRENT namespace. The
497
+ * engine also reads each one under every legacy namespace, second — use
498
+ * `readFirstPartyHarnessEngineEnv` to read one the way the engine does and
499
+ * `firstPartyHarnessEngineEnvEntries` to set one every engine build honours.
500
+ */
501
+ env: engineEnvTable(appName),
502
+ });
503
+ }
416
504
  /**
417
505
  * Names the ENGINE itself defines and reads. The platform sets or reads them
418
506
  * across the daemon↔engine boundary, so they are recorded here as values rather
419
- * than scattered as literals — but they are the fork's to change, and moving
420
- * one is a two-sided change (contract §6). Not part of this identity's rename.
507
+ * than scattered as literals. They are the fork's names, and moving one is a
508
+ * two-sided change: the engine reads the new name first and every legacy name
509
+ * second, and while engine and platform releases can arrive in either order the
510
+ * platform SETS every name and READS new-then-legacy (contract §6, OSK-8674).
421
511
  */
422
- exports.FIRST_PARTY_HARNESS_ENGINE = Object.freeze({
423
- /**
424
- * The engine's state namespace: XDG dirs, project marker, config basename, and
425
- * the engine home under the skrr root (`firstPartyHarnessHomeDirname`).
426
- */
427
- appName: 'sky-code',
428
- /**
429
- * Earlier `appName`s whose home directory `migrateFirstPartyHarnessHome` moves
430
- * onto the current one. Empty until the engine's namespace is renamed.
431
- */
432
- legacyAppNames: Object.freeze([]),
433
- /** Project-local configuration directory inside a user's repository. */
434
- projectDirectory: '.sky-code',
435
- /** Environment variables the engine reads. */
436
- env: Object.freeze({
437
- serverPassword: 'SKY_CODE_SERVER_PASSWORD',
438
- serverUsername: 'SKY_CODE_SERVER_USERNAME',
439
- home: 'SKY_CODE_HOME',
440
- config: 'SKY_CODE_CONFIG',
441
- appName: 'SKY_CODE_APP_NAME',
442
- scriptName: 'SKY_CODE_SCRIPT_NAME',
443
- allowUpstreamEgress: 'SKY_CODE_ALLOW_UPSTREAM_EGRESS',
444
- compatOpencode: 'SKY_CODE_COMPAT_OPENCODE',
445
- noCompatPrompts: 'SKY_CODE_NO_COMPAT_PROMPTS',
446
- }),
447
- });
512
+ exports.FIRST_PARTY_HARNESS_ENGINE = defineEngineNamespace(ENGINE_APP_NAME, ENGINE_LEGACY_APP_NAMES);
513
+ /**
514
+ * Every name the engine reads for one purpose, current namespace first — the
515
+ * engine's own lookup order.
516
+ */
517
+ function firstPartyHarnessEngineEnvNames(key) {
518
+ return [exports.FIRST_PARTY_HARNESS_ENGINE.appName, ...exports.FIRST_PARTY_HARNESS_ENGINE.legacyAppNames].map((appName) => engineEnvName(appName, key));
519
+ }
520
+ /**
521
+ * Read one engine variable the way the engine reads it: the current name, then
522
+ * every legacy name. A blank value counts as unset.
523
+ */
524
+ function readFirstPartyHarnessEngineEnv(env, key) {
525
+ for (const name of firstPartyHarnessEngineEnvNames(key)) {
526
+ const raw = env[name];
527
+ if (typeof raw === 'string' && raw.trim() !== '') {
528
+ return { value: raw.trim(), name };
529
+ }
530
+ }
531
+ return { value: undefined, name: undefined };
532
+ }
533
+ /**
534
+ * One engine variable set under EVERY name the engine has read, for a process
535
+ * that spawns the engine. An engine binary is installed on its own schedule, so
536
+ * a launcher cannot know whether the one it starts reads the current name or a
537
+ * legacy one; setting both means either build receives the value.
538
+ */
539
+ function firstPartyHarnessEngineEnvEntries(key, value) {
540
+ return Object.fromEntries(firstPartyHarnessEngineEnvNames(key).map((name) => [name, value]));
541
+ }
542
+ /** Every engine project directory, current first — the engine's read order. */
543
+ function firstPartyHarnessEngineProjectDirectories() {
544
+ return [
545
+ exports.FIRST_PARTY_HARNESS_ENGINE.projectDirectory,
546
+ ...exports.FIRST_PARTY_HARNESS_ENGINE.legacyProjectDirectories,
547
+ ];
548
+ }
549
+ /**
550
+ * Every engine home directory name under a config root, current first: where an
551
+ * engine home may exist on disk. `firstPartyHarnessHomeDirname` is the one the
552
+ * platform writes.
553
+ */
554
+ function firstPartyHarnessEngineHomeDirnames() {
555
+ return [exports.FIRST_PARTY_HARNESS_ENGINE.appName, ...exports.FIRST_PARTY_HARNESS_ENGINE.legacyAppNames];
556
+ }
@@ -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;
@@ -3,6 +3,7 @@ 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.LEGACY_CUTOVER_RUNBOOK = void 0;
6
7
  exports.findLegacyLocalState = findLegacyLocalState;
7
8
  exports.describeLegacyState = describeLegacyState;
8
9
  const node_fs_1 = __importDefault(require("node:fs"));
@@ -50,22 +51,69 @@ function findLegacyLocalState(home = node_os_1.default.homedir()) {
50
51
  return { findings, hasLegacyState: findings.length > 0 };
51
52
  }
52
53
  /**
53
- * The message shown when preflight refuses.
54
- *
55
- * Written for someone who has just been stopped and wants to know why and what
56
- * to do — so it names the exact paths, says explicitly that nothing was touched,
57
- * and points at the runbook rather than improvising a fix. It does NOT offer a
58
- * `--force`: the operator's next step is the backup the runbook specifies, and
59
- * a flag that skips a safety check is the flag everyone learns to paste.
60
- */
61
- /**
62
- * The pre-rename runtime binary. Named in the refusal above because deregistering
54
+ * The pre-rename runtime binary. Named in the refusal below because deregistering
63
55
  * the OLD service is the one step the NEW binary cannot do: `uninstall` resolves
64
56
  * the current profile's label (`ai.skrr.daemon.*`), and the legacy sweep is wired
65
57
  * only into the install path.
66
58
  */
67
59
  const LEGACY_BINARY_NAME = 'oversky';
68
- function describeLegacyState(report, binaryName) {
60
+ /**
61
+ * Where the full procedure lives, by repository path. The refusal used to say
62
+ * "follow the team cutover runbook" without naming one, which stopped a person
63
+ * at a pointer they could not follow (OSK-6895). The steps the refusal prints
64
+ * are the runbook's own, so the path is for the parts a terminal message cannot
65
+ * carry: Linux and Windows service removal, Keychain entries, the old app.
66
+ */
67
+ exports.LEGACY_CUTOVER_RUNBOOK = 'docs/runbooks/skrr-cutover-dev-machine.md';
68
+ /** Credential file patterns the runbook strips from its backup (§5.5). */
69
+ const BACKUP_CREDENTIAL_PATTERNS = ['auth.json', 'cli-auth.json', '*token*'];
70
+ function posixQuote(value) {
71
+ return `'${value.split("'").join(`'\\''`)}'`;
72
+ }
73
+ function powershellQuote(value) {
74
+ return `'${value.split("'").join("''")}'`;
75
+ }
76
+ /**
77
+ * The commands for the three steps a person runs by hand, in the shell they are
78
+ * actually in. A POSIX `rm -rf` pasted into PowerShell is not an instruction.
79
+ */
80
+ function cutoverCommands(paths, platform) {
81
+ if (platform === 'win32') {
82
+ const backupDir = '"$HOME\\skrr-cutover-backup"';
83
+ return {
84
+ backup: [
85
+ `New-Item -ItemType Directory -Force ${backupDir} | Out-Null`,
86
+ ...paths.map((p) => `Copy-Item -Recurse ${powershellQuote(p)} ${backupDir}`),
87
+ `Get-ChildItem -Recurse -File ${backupDir} -Include ${BACKUP_CREDENTIAL_PATTERNS.join(',')} | Remove-Item`,
88
+ ],
89
+ remove: paths.map((p) => `Remove-Item -Recurse -Force ${powershellQuote(p)}`),
90
+ };
91
+ }
92
+ const names = BACKUP_CREDENTIAL_PATTERNS.map((n) => `-name '${n}'`).join(' -o ');
93
+ return {
94
+ backup: [
95
+ 'mkdir -p ~/skrr-cutover-backup && chmod 700 ~/skrr-cutover-backup',
96
+ `cp -a ${paths.map(posixQuote).join(' ')} ~/skrr-cutover-backup/`,
97
+ `find ~/skrr-cutover-backup -type f \\( ${names} \\) -delete`,
98
+ ],
99
+ remove: [`rm -rf ${paths.map(posixQuote).join(' ')}`],
100
+ };
101
+ }
102
+ /**
103
+ * The message shown when preflight refuses.
104
+ *
105
+ * Written for someone who has just been stopped and wants to know why and what
106
+ * to do — so it names the exact paths, says explicitly that nothing was touched,
107
+ * and prints the cutover as commands to run rather than a pointer to follow. It
108
+ * does NOT offer a `--force`: the operator's next step is the backup, and a flag
109
+ * that skips a safety check is the flag everyone learns to paste.
110
+ *
111
+ * `command` is what the operator re-runs to install — `skrrd` when the runtime
112
+ * refused, `skrr daemon` when the CLI refused before fetching one.
113
+ */
114
+ function describeLegacyState(report, command, platform = process.platform) {
115
+ const paths = report.findings.map((f) => f.path);
116
+ const { backup, remove } = cutoverCommands(paths, platform);
69
117
  const lines = [];
70
118
  lines.push('Found state from the previous product identity on this machine:');
71
119
  lines.push('');
@@ -76,20 +124,28 @@ function describeLegacyState(report, binaryName) {
76
124
  lines.push('Installing beside it risks two runtimes holding two different service');
77
125
  lines.push('labels, each believing it owns the single-instance lock.');
78
126
  lines.push('');
79
- lines.push('Nothing above has been read, copied or removed. Follow the team cutover');
80
- lines.push('runbook — it takes a backup first — and then run this again:');
127
+ lines.push('Nothing above has been read, copied or removed, and no service was registered.');
128
+ lines.push('To finish the cutover:');
81
129
  lines.push('');
130
+ lines.push(' 1. Keep a copy, with its credentials removed, in case you need an old setting:');
131
+ for (const line of backup)
132
+ lines.push(` ${line}`);
82
133
  // The OLD binary, deliberately: it is the one that knows the old service
83
134
  // label. `<current> uninstall` resolves the CURRENT profile's label and would
84
135
  // report success while leaving the pre-rename service registered. Guarded by
85
136
  // "only if" rather than omitted, because this module inspects directories and
86
137
  // has no way to know whether that binary is still on PATH.
87
- lines.push(` ${LEGACY_BINARY_NAME} uninstall # only if the old runtime is still installed —`);
88
- lines.push(` # it is the one that knows the old service label`);
89
- lines.push(' # then remove the directories listed above, per the runbook');
138
+ lines.push(' 2. If the old runtime is still installed, deregister it with the old binary —');
139
+ lines.push(' it is the one that knows the old service label:');
140
+ lines.push(` ${LEGACY_BINARY_NAME} uninstall`);
141
+ lines.push(' 3. Remove the directories listed above:');
142
+ for (const line of remove)
143
+ lines.push(` ${line}`);
144
+ lines.push(' 4. Run this again:');
145
+ lines.push(` ${command} install`);
90
146
  lines.push('');
91
- lines.push(`Any pre-rename service still registered is swept by \`${binaryName} install\``);
92
- lines.push('once those directories are gone, so re-running the install completes the');
93
- lines.push('cutover either way.');
147
+ lines.push(`Any pre-rename service still registered is swept by \`${command} install\``);
148
+ lines.push('once those directories are gone, so step 4 completes the cutover either way.');
149
+ lines.push(`Full runbook, including Keychain entries and the old app: ${exports.LEGACY_CUTOVER_RUNBOOK}`);
94
150
  return lines.join('\n');
95
151
  }