@skrr-ai/cli 0.1.31 → 0.1.33

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 (112) hide show
  1. package/bin/run.js +2 -2
  2. package/dist/commands/agents/list.d.ts +2 -1
  3. package/dist/commands/agents/list.js +2 -1
  4. package/dist/commands/agents/show.js +2 -1
  5. package/dist/commands/balance/statement.js +8 -4
  6. package/dist/commands/browser/install.js +6 -2
  7. package/dist/commands/browser/uninstall.js +6 -2
  8. package/dist/commands/code/acp.js +2 -1
  9. package/dist/commands/code/doctor.d.ts +1 -1
  10. package/dist/commands/code/doctor.js +6 -5
  11. package/dist/commands/code/handover.d.ts +2 -2
  12. package/dist/commands/code/handover.js +2 -2
  13. package/dist/commands/code/index.d.ts +2 -2
  14. package/dist/commands/code/index.js +18 -17
  15. package/dist/commands/code/install.d.ts +3 -3
  16. package/dist/commands/code/install.js +18 -11
  17. package/dist/commands/code/migrate.js +3 -2
  18. package/dist/commands/code/oversky-agent.d.ts +5 -4
  19. package/dist/commands/code/oversky-agent.js +24 -22
  20. package/dist/commands/code/run.js +2 -1
  21. package/dist/commands/harnesses/list.d.ts +9 -1
  22. package/dist/commands/harnesses/list.js +28 -7
  23. package/dist/commands/harnesses/show.js +7 -2
  24. package/dist/commands/instructions/install.js +16 -6
  25. package/dist/commands/learning/delete.d.ts +9 -0
  26. package/dist/commands/learning/delete.js +39 -0
  27. package/dist/commands/learning/export.d.ts +5 -0
  28. package/dist/commands/learning/export.js +22 -0
  29. package/dist/commands/learning/reset.d.ts +9 -0
  30. package/dist/commands/learning/reset.js +34 -0
  31. package/dist/commands/learning/set.d.ts +10 -0
  32. package/dist/commands/learning/set.js +52 -0
  33. package/dist/commands/learning/status.d.ts +8 -0
  34. package/dist/commands/learning/status.js +35 -0
  35. package/dist/commands/logout.js +10 -5
  36. package/dist/commands/machines/dedicated/attach.js +2 -1
  37. package/dist/commands/tasks/runs.js +3 -1
  38. package/dist/commands/workspaces/use.js +2 -1
  39. package/dist/lib/adaptive-learning.d.ts +37 -0
  40. package/dist/lib/adaptive-learning.js +39 -0
  41. package/dist/lib/code-handover.d.ts +1 -1
  42. package/dist/lib/code-handover.js +3 -2
  43. package/dist/lib/config.d.ts +30 -4
  44. package/dist/lib/config.js +2 -1
  45. package/dist/lib/daemon-installer.d.ts +1 -1
  46. package/dist/lib/daemon-target.js +2 -2
  47. package/dist/lib/dedicated-lease-command.js +2 -1
  48. package/dist/lib/dedicated-machines.d.ts +2 -2
  49. package/dist/lib/dedicated-machines.js +6 -3
  50. package/dist/lib/exec-runtime-binary.d.ts +2 -2
  51. package/dist/lib/exec-runtime-binary.js +2 -2
  52. package/dist/lib/{sky-code-agent.d.ts → first-party-harness-agent.d.ts} +20 -19
  53. package/dist/lib/{sky-code-agent.js → first-party-harness-agent.js} +85 -47
  54. package/dist/lib/{sky-code-broker.d.ts → first-party-harness-broker.d.ts} +6 -6
  55. package/dist/lib/{sky-code-broker.js → first-party-harness-broker.js} +23 -23
  56. package/dist/lib/{sky-code-doctor.d.ts → first-party-harness-doctor.d.ts} +12 -2
  57. package/dist/lib/{sky-code-doctor.js → first-party-harness-doctor.js} +181 -94
  58. package/dist/lib/{sky-code-managed.d.ts → first-party-harness-managed.d.ts} +55 -30
  59. package/dist/lib/{sky-code-managed.js → first-party-harness-managed.js} +92 -35
  60. package/dist/lib/{sky-code.d.ts → first-party-harness.d.ts} +107 -36
  61. package/dist/lib/{sky-code.js → first-party-harness.js} +172 -73
  62. package/dist/lib/harness-provider-input.d.ts +35 -0
  63. package/dist/lib/harness-provider-input.js +57 -0
  64. package/dist/lib/harness-tiers.d.ts +8 -2
  65. package/dist/lib/harness-tiers.js +11 -6
  66. package/dist/lib/task-instruction-offer.js +10 -4
  67. package/dist/lib/worklogs.js +2 -2
  68. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.d.ts +2 -2
  69. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.js +2 -2
  70. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.d.ts +248 -0
  71. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarness.js +361 -0
  72. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.d.ts +147 -0
  73. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessChannels.js +180 -0
  74. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.d.ts +111 -0
  75. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/firstPartyHarnessHome.js +314 -0
  76. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.d.ts +11 -5
  77. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/harnessTrust.js +14 -7
  78. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -1
  79. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +51 -14
  80. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.js +1 -1
  81. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +4 -4
  82. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +4 -4
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.d.ts +2 -2
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.js +2 -2
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.d.ts +248 -0
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarness.js +332 -0
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.d.ts +147 -0
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessChannels.js +170 -0
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.d.ts +111 -0
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/firstPartyHarnessHome.js +303 -0
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.d.ts +11 -5
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/harnessTrust.js +14 -7
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -1
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +12 -2
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.js +1 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +4 -4
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +4 -4
  98. package/dist/node_modules/@skrr-ai/auth-core/package.json +11 -1
  99. package/dist/node_modules/@skrr-ai/data-provider/index.js +4816 -4782
  100. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/index.d.ts +1 -1
  101. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/index.js +1 -1
  102. package/dist/node_modules/@skrr-ai/inference-broker/dist/cjs/managed-inference-broker.js +1 -1
  103. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/index.d.ts +1 -1
  104. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/index.js +1 -1
  105. package/dist/node_modules/@skrr-ai/inference-broker/dist/esm/managed-inference-broker.js +1 -1
  106. package/dist/node_modules/@skrr-ai/inference-broker/package.json +1 -1
  107. package/oclif.manifest.json +27119 -26808
  108. package/package.json +5 -2
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/skyCodeChannels.d.ts +0 -96
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/skyCodeChannels.js +0 -115
  111. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/skyCodeChannels.d.ts +0 -96
  112. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/skyCodeChannels.js +0 -107
@@ -0,0 +1,303 @@
1
+ /**
2
+ * Where the first-party harness lives on disk, and the one-time move that keeps
3
+ * that true across a rename.
4
+ *
5
+ * ── Why one implementation ────────────────────────────────────────────────
6
+ *
7
+ * Three processes put an engine under the skrr root and read it back: the daemon
8
+ * installer, the CLI (`skrr code`), and the desktop app's bundled-engine
9
+ * install. Each used to build `<root>/sky-code/bin/sky-code` itself, and they
10
+ * already disagreed once about which root that was (see `configRoot.ts`). A
11
+ * rename that every one of them had to perform independently would disagree
12
+ * again, so the paths AND the move live here.
13
+ *
14
+ * ── The move ──────────────────────────────────────────────────────────────
15
+ *
16
+ * Idempotent, lossless, and safe to run concurrently from all three:
17
+ *
18
+ * 1. The HOME follows the engine's namespace. If `<root>/<appName>` does not
19
+ * exist and `<root>/<legacyAppName>` is a real directory, rename it and
20
+ * leave the legacy name as a relative symlink. (No legacy app name exists
21
+ * yet; this step is what an engine-namespace rename will use.)
22
+ * 2. The BINARY follows the provider. Inside `<home>/bin`, if the canonical
23
+ * binary does not exist and an alias binary is a real file, rename it and
24
+ * each companion (rollback copy, provenance sidecar), leaving a relative
25
+ * symlink at every old name.
26
+ *
27
+ * The two follow different names on purpose: the home is shared with the
28
+ * engine, which reads its config and managed `AGENTS.md` from it, so it can only
29
+ * move when the engine's namespace does (`firstPartyHarnessHomeDirname`). The
30
+ * symlinks are the compatibility story, not an afterthought: every CLI and
31
+ * daemon already installed reads the legacy binary path, and a symlink keeps
32
+ * them working with no coordination.
33
+ *
34
+ * It never overwrites, never deletes, and never renames THROUGH a symlink, so
35
+ * running it twice, or after a downgrade, changes nothing. Any error leaves the
36
+ * tree exactly as it was and is reported rather than thrown: a failed move must
37
+ * degrade to reading the old path, not to an engine that cannot start.
38
+ *
39
+ * Skipped on Windows, where creating a symlink needs a privilege a normal user
40
+ * does not hold, and where no engine artifact is published anyway.
41
+ */
42
+ import { lstatSync, readlinkSync, renameSync, symlinkSync } from 'node:fs';
43
+ import path from 'node:path';
44
+ import { resolveConfigRoot } from './configRoot.js';
45
+ import { FIRST_PARTY_HARNESS, FIRST_PARTY_HARNESS_ENGINE, firstPartyHarnessBinaryName, firstPartyHarnessHomeDirname, } from './firstPartyHarness.js';
46
+ /** Suffix of the rollback copy the installer keeps beside the binary. */
47
+ export const FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX = '.previous';
48
+ /**
49
+ * Suffix of the provenance record beside the binary. The `oversky` in it is a
50
+ * persisted file name that already exists on machines, not the product name.
51
+ */
52
+ export const FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX = '.oversky-provenance.json';
53
+ /** `<root>/<engine appName>` — the engine home, shared by the platform and the engine. */
54
+ export function firstPartyHarnessHomeDir(env = process.env) {
55
+ return path.join(resolveConfigRoot(env), firstPartyHarnessHomeDirname());
56
+ }
57
+ /** `<home>/bin`, where the platform installs the engine binary. */
58
+ export function firstPartyHarnessBinDir(env = process.env) {
59
+ return path.join(firstPartyHarnessHomeDir(env), 'bin');
60
+ }
61
+ /** The installed binary path for a spelling (canonical by default) — lookup order is canonical first. */
62
+ export function firstPartyHarnessInstalledBinaryPath(env = process.env, provider = FIRST_PARTY_HARNESS.provider, platform = process.platform) {
63
+ return path.join(firstPartyHarnessBinDir(env), firstPartyHarnessBinaryName(provider, platform));
64
+ }
65
+ const NODE_FS = {
66
+ lstat(p) {
67
+ try {
68
+ return lstatSync(p);
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ },
74
+ readlink(p) {
75
+ try {
76
+ return lstatSync(p).isSymbolicLink() ? readlinkSync(p) : null;
77
+ }
78
+ catch {
79
+ return null;
80
+ }
81
+ },
82
+ rename: renameSync,
83
+ symlink(target, at, type) {
84
+ symlinkSync(target, at, type);
85
+ },
86
+ };
87
+ function isRealDirectory(stat) {
88
+ return stat !== null && stat.isDirectory() && !stat.isSymbolicLink();
89
+ }
90
+ function isRealFile(stat) {
91
+ return stat !== null && stat.isFile() && !stat.isSymbolicLink();
92
+ }
93
+ function describe(err) {
94
+ return err instanceof Error ? err.message : String(err);
95
+ }
96
+ /** Companions that travel with the binary, binary first. */
97
+ const COMPANION_SUFFIXES = [
98
+ '',
99
+ FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX,
100
+ FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX,
101
+ ];
102
+ /**
103
+ * How much newer an alias binary must be before it is treated as a later
104
+ * install rather than the same one. Clock and filesystem-granularity slack.
105
+ */
106
+ const NEWER_THRESHOLD_MS = 1000;
107
+ /**
108
+ * Move a legacy engine home and binary onto the canonical names. See the file
109
+ * header for the exact contract. Returns what it did; never throws.
110
+ *
111
+ * Inside `<home>/bin` it resolves the three layouts a rename can leave:
112
+ *
113
+ * - canonical ABSENT, an alias is a real file — the ordinary legacy install.
114
+ * The alias moves to canonical and a link is left at its name.
115
+ * - canonical is a LINK to an alias that is a real file — what an alias link
116
+ * left DURING the expand phase becomes once the flip swaps which spelling is
117
+ * canonical. Leaving it means the next install writes its provenance record
118
+ * THROUGH the link onto the old binary's record, and the legacy name then
119
+ * verifies as `tampered`. The real file is adopted onto the canonical name
120
+ * (a rename replaces the link itself, never what it points at).
121
+ * - canonical and an alias are BOTH real files and the alias is newer — a
122
+ * client that predates the flip installed an update under the old name. The
123
+ * newer engine is what every reader should run, so it is promoted: the older
124
+ * canonical binary becomes the rollback copy, and the alias becomes a link.
125
+ *
126
+ * Any other layout is left exactly as it is.
127
+ */
128
+ export function migrateFirstPartyHarnessHome(options = {}) {
129
+ const env = options.env ?? process.env;
130
+ const platform = options.platform ?? process.platform;
131
+ const fs = options.fs ?? NODE_FS;
132
+ if (platform === 'win32') {
133
+ return { moved: [], errors: [], skipped: 'unsupported-platform' };
134
+ }
135
+ const moved = [];
136
+ const errors = [];
137
+ const canonical = FIRST_PARTY_HARNESS.provider;
138
+ const root = resolveConfigRoot(env);
139
+ const canonicalHome = firstPartyHarnessHomeDir(env);
140
+ // 1. The home directory — follows the engine's namespace.
141
+ if (fs.lstat(canonicalHome) === null) {
142
+ for (const legacyAppName of FIRST_PARTY_HARNESS_ENGINE.legacyAppNames) {
143
+ const aliasHome = path.join(root, legacyAppName);
144
+ if (!isRealDirectory(fs.lstat(aliasHome)))
145
+ continue;
146
+ try {
147
+ fs.rename(aliasHome, canonicalHome);
148
+ }
149
+ catch (err) {
150
+ // A concurrent migrator may have won; step 2 still runs against
151
+ // whatever is now at the canonical path.
152
+ errors.push(`could not move ${aliasHome} to ${canonicalHome}: ${describe(err)}`);
153
+ break;
154
+ }
155
+ moved.push(`${aliasHome} -> ${canonicalHome}`);
156
+ try {
157
+ fs.symlink(path.basename(canonicalHome), aliasHome, 'dir');
158
+ moved.push(`symlink ${aliasHome} -> ${path.basename(canonicalHome)}`);
159
+ }
160
+ catch (err) {
161
+ errors.push(`moved ${aliasHome} but could not leave a symlink behind: ${describe(err)}`);
162
+ }
163
+ break;
164
+ }
165
+ }
166
+ // 2. The binary and its companions — follow the provider — inside the home.
167
+ const binDir = path.join(canonicalHome, 'bin');
168
+ if (!isRealDirectory(fs.lstat(binDir))) {
169
+ return { moved, errors };
170
+ }
171
+ const canonicalName = firstPartyHarnessBinaryName(canonical, platform);
172
+ const canonicalBinary = path.join(binDir, canonicalName);
173
+ const canonicalStat = fs.lstat(canonicalBinary);
174
+ const aliasNames = FIRST_PARTY_HARNESS.aliases.map((alias) => firstPartyHarnessBinaryName(alias, platform));
175
+ /** Move `<from><suffix>` onto `<to><suffix>` and leave a link; `replaceable` says what may sit at the target. */
176
+ const relocate = (fromName, suffix, replaceable) => {
177
+ const from = path.join(binDir, `${fromName}${suffix}`);
178
+ const to = path.join(binDir, `${canonicalName}${suffix}`);
179
+ const toStat = fs.lstat(to);
180
+ if (!isRealFile(fs.lstat(from)) || !replaceable(to, toStat))
181
+ return 'skipped';
182
+ try {
183
+ // `rename` replaces a LINK at `to` itself, never the file it points at.
184
+ fs.rename(from, to);
185
+ }
186
+ catch (err) {
187
+ errors.push(`could not move ${from} to ${to}: ${describe(err)}`);
188
+ return 'failed';
189
+ }
190
+ moved.push(`${from} -> ${to}`);
191
+ try {
192
+ fs.symlink(path.basename(to), from, 'file');
193
+ moved.push(`symlink ${from} -> ${path.basename(to)}`);
194
+ }
195
+ catch (err) {
196
+ errors.push(`moved ${from} but could not leave a symlink behind: ${describe(err)}`);
197
+ }
198
+ return 'moved';
199
+ };
200
+ // 2a. Canonical absent: an ordinary legacy install.
201
+ if (canonicalStat === null) {
202
+ const aliasName = aliasNames.find((name) => isRealFile(fs.lstat(path.join(binDir, name))));
203
+ if (aliasName) {
204
+ for (const suffix of COMPANION_SUFFIXES) {
205
+ const outcome = relocate(aliasName, suffix, (_to, toStat) => toStat === null);
206
+ if (suffix === '' && outcome !== 'moved')
207
+ break;
208
+ }
209
+ }
210
+ return { moved, errors };
211
+ }
212
+ // 2b. Canonical is a link to a real alias file: adopt the file.
213
+ const linkText = fs.readlink(canonicalBinary);
214
+ if (linkText !== null) {
215
+ const linkedName = path.basename(linkText);
216
+ if (path.dirname(linkText) === '.' &&
217
+ aliasNames.includes(linkedName) &&
218
+ isRealFile(fs.lstat(path.join(binDir, linkedName)))) {
219
+ for (const suffix of COMPANION_SUFFIXES) {
220
+ const outcome = relocate(linkedName, suffix, (to, toStat) => toStat === null || fs.readlink(to) === `${linkedName}${suffix}`);
221
+ if (suffix === '' && outcome !== 'moved')
222
+ break;
223
+ }
224
+ }
225
+ return { moved, errors };
226
+ }
227
+ // 2c. Both real, alias newer: an update installed under the old name.
228
+ if (isRealFile(canonicalStat)) {
229
+ const newer = aliasNames
230
+ .map((name) => ({ name, stat: fs.lstat(path.join(binDir, name)) }))
231
+ .filter((entry) => isRealFile(entry.stat))
232
+ .filter((entry) => entry.stat.mtimeMs - canonicalStat.mtimeMs > NEWER_THRESHOLD_MS)
233
+ .sort((a, b) => b.stat.mtimeMs - a.stat.mtimeMs)[0];
234
+ if (newer) {
235
+ const previous = `${canonicalBinary}${FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX}`;
236
+ const previousStat = fs.lstat(previous);
237
+ if (previousStat !== null && !isRealFile(previousStat)) {
238
+ errors.push(`${previous} is not a regular file; left the newer ${newer.name} in place`);
239
+ return { moved, errors };
240
+ }
241
+ try {
242
+ // The older canonical engine becomes the rollback copy — the same
243
+ // meaning `.previous` has after any install.
244
+ fs.rename(canonicalBinary, previous);
245
+ }
246
+ catch (err) {
247
+ errors.push(`could not keep ${canonicalBinary} as ${previous}: ${describe(err)}`);
248
+ return { moved, errors };
249
+ }
250
+ moved.push(`${canonicalBinary} -> ${previous}`);
251
+ for (const suffix of ['', FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX]) {
252
+ // The alias's sidecar describes the alias's bytes, so it replaces the
253
+ // canonical one: a record must always sit beside the binary it describes.
254
+ const outcome = relocate(newer.name, suffix, (to, toStat) => suffix === '' ? toStat === null : toStat === null || isRealFile(toStat));
255
+ if (suffix === '' && outcome !== 'moved')
256
+ break;
257
+ }
258
+ }
259
+ }
260
+ return { moved, errors };
261
+ }
262
+ /**
263
+ * Leave a relative symlink at every alias name — binary and provenance sidecar —
264
+ * that does not exist yet, pointing at the canonical file.
265
+ *
266
+ * Call after installing the canonical binary. A platform CLI or daemon installed
267
+ * before a rename resolves the legacy name; the link keeps it on the current
268
+ * engine. It never replaces anything that exists, whether a real file (an older
269
+ * engine someone may still run) or a link someone else placed. Never throws.
270
+ */
271
+ export function linkFirstPartyHarnessAliases(options = {}) {
272
+ const env = options.env ?? process.env;
273
+ const platform = options.platform ?? process.platform;
274
+ const fs = options.fs ?? NODE_FS;
275
+ if (platform === 'win32') {
276
+ return { moved: [], errors: [], skipped: 'unsupported-platform' };
277
+ }
278
+ const moved = [];
279
+ const errors = [];
280
+ const binDir = firstPartyHarnessBinDir(env);
281
+ const canonicalName = firstPartyHarnessBinaryName(FIRST_PARTY_HARNESS.provider, platform);
282
+ if (!isRealFile(fs.lstat(path.join(binDir, canonicalName)))) {
283
+ return { moved, errors };
284
+ }
285
+ for (const alias of FIRST_PARTY_HARNESS.aliases) {
286
+ const aliasName = firstPartyHarnessBinaryName(alias, platform);
287
+ // Sidecar FIRST: a verifier that finds the binary link must find its record.
288
+ for (const suffix of [FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX, '']) {
289
+ const target = `${canonicalName}${suffix}`;
290
+ const at = path.join(binDir, `${aliasName}${suffix}`);
291
+ if (!isRealFile(fs.lstat(path.join(binDir, target))) || fs.lstat(at) !== null)
292
+ continue;
293
+ try {
294
+ fs.symlink(target, at, 'file');
295
+ moved.push(`symlink ${at} -> ${target}`);
296
+ }
297
+ catch (err) {
298
+ errors.push(`could not link ${at} -> ${target}: ${describe(err)}`);
299
+ }
300
+ }
301
+ }
302
+ return { moved, errors };
303
+ }
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ── Why this lives in auth-core ────────────────────────────────────────────
5
5
  *
6
- * `sky code doctor` must tell a user what tier their harness is and what that
6
+ * `skrr code doctor` must tell a user what tier their harness is and what that
7
7
  * means for credentials and permission prompts. The daemon is the authority for
8
8
  * that answer, but the CLI cannot import `daemon/src` — separate packages, and
9
9
  * the daemon already depends on the CLI, so the reverse edge would be a cycle.
@@ -18,7 +18,7 @@
18
18
  * parsed the daemon's source to catch drift. That guard worked — and it was still
19
19
  * only a guard. The regression it existed to catch had already shipped once: the
20
20
  * doctor described Tier 2 behaviour for a day after the owner decision of
21
- * 2026-08-11 moved `sky-code` to Tier 1, and a unit test asserting the stale
21
+ * 2026-08-11 moved the first-party harness to Tier 1, and a unit test asserting the stale
22
22
  * string made the wrong claim look load-bearing.
23
23
  *
24
24
  * ── What deliberately did NOT move ────────────────────────────────────────
@@ -44,10 +44,16 @@ export type HarnessTrustTier = 1 | 2 | 3;
44
44
  * map resolves to, so adding trust is always a deliberate, reviewable edit and
45
45
  * never an omission.
46
46
  *
47
- * `sky-code` is Tier 1 by OWNER DECISION (2026-08-11). The full reasoning, and
48
- * the measured cost of the Tier 2 posture it replaced, is recorded in
49
- * `daemon/src/harness-trust.ts` beside the gates that read it — the argument
47
+ * The first-party harness is Tier 1 by OWNER DECISION (2026-08-11). The full
48
+ * reasoning, and the measured cost of the Tier 2 posture it replaced, is recorded
49
+ * in `daemon/src/harness-trust.ts` beside the gates that read it — the argument
50
50
  * belongs where the enforcement is, not in a shared vocabulary module.
51
+ *
52
+ * It is entered under EVERY spelling (`firstPartyHarnessSpellings`), not only the
53
+ * canonical one. A backend name reaches this lookup from a daemon of any version,
54
+ * and an alias that fell through to the Tier 3 default would silently strip the
55
+ * harness of the trust the owner granted it — a failure that looks like a
56
+ * permission prompt storm, not like a rename.
51
57
  */
52
58
  export declare const HARNESS_TIERS: Readonly<Record<string, HarnessTrustTier>>;
53
59
  /**
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * ── Why this lives in auth-core ────────────────────────────────────────────
5
5
  *
6
- * `sky code doctor` must tell a user what tier their harness is and what that
6
+ * `skrr code doctor` must tell a user what tier their harness is and what that
7
7
  * means for credentials and permission prompts. The daemon is the authority for
8
8
  * that answer, but the CLI cannot import `daemon/src` — separate packages, and
9
9
  * the daemon already depends on the CLI, so the reverse edge would be a cycle.
@@ -18,7 +18,7 @@
18
18
  * parsed the daemon's source to catch drift. That guard worked — and it was still
19
19
  * only a guard. The regression it existed to catch had already shipped once: the
20
20
  * doctor described Tier 2 behaviour for a day after the owner decision of
21
- * 2026-08-11 moved `sky-code` to Tier 1, and a unit test asserting the stale
21
+ * 2026-08-11 moved the first-party harness to Tier 1, and a unit test asserting the stale
22
22
  * string made the wrong claim look load-bearing.
23
23
  *
24
24
  * ── What deliberately did NOT move ────────────────────────────────────────
@@ -34,6 +34,7 @@
34
34
  * failure than the drift this file fixes: a confident wrong answer beats an
35
35
  * honestly stale one for causing harm.
36
36
  */
37
+ import { firstPartyHarnessSpellings } from './firstPartyHarness.js';
37
38
  /**
38
39
  * Trust tier by backend name.
39
40
  *
@@ -42,20 +43,26 @@
42
43
  * map resolves to, so adding trust is always a deliberate, reviewable edit and
43
44
  * never an omission.
44
45
  *
45
- * `sky-code` is Tier 1 by OWNER DECISION (2026-08-11). The full reasoning, and
46
- * the measured cost of the Tier 2 posture it replaced, is recorded in
47
- * `daemon/src/harness-trust.ts` beside the gates that read it — the argument
46
+ * The first-party harness is Tier 1 by OWNER DECISION (2026-08-11). The full
47
+ * reasoning, and the measured cost of the Tier 2 posture it replaced, is recorded
48
+ * in `daemon/src/harness-trust.ts` beside the gates that read it — the argument
48
49
  * belongs where the enforcement is, not in a shared vocabulary module.
50
+ *
51
+ * It is entered under EVERY spelling (`firstPartyHarnessSpellings`), not only the
52
+ * canonical one. A backend name reaches this lookup from a daemon of any version,
53
+ * and an alias that fell through to the Tier 3 default would silently strip the
54
+ * harness of the trust the owner granted it — a failure that looks like a
55
+ * permission prompt storm, not like a rename.
49
56
  */
50
57
  export const HARNESS_TIERS = Object.freeze({
51
- // --- Tier 1: first-party / audited ---
58
+ // --- Tier 1: vendor first-party / audited ---
52
59
  claude: 1,
53
60
  codex: 1,
54
61
  opencode: 1,
55
62
  'opencode-acp': 1,
56
63
  gemini: 1,
57
64
  'gemini-acp': 1,
58
- 'sky-code': 1,
65
+ ...Object.fromEntries(firstPartyHarnessSpellings().map((spelling) => [spelling, 1])),
59
66
  // --- Tier 2: reviewed open-source community harnesses ---
60
67
  goose: 2,
61
68
  aider: 2,
@@ -35,7 +35,9 @@ export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConf
35
35
  export { sanitizeSpawnEnv, SENSITIVE_ENV_VARS, SENSITIVE_ENV_PREFIXES, SENSITIVE_ENV_SUFFIXES, SENSITIVE_ENV_NAME_TOKENS, SENSITIVE_ENV_PREFIX_EXCEPTIONS, } from './spawnEnv.js';
36
36
  export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATIVE_ID_PREFIXES, DEFAULT_BINARY_NAME, } from './localIdentity.js';
37
37
  export { findLegacyLocalState, describeLegacyState, type LegacyStateFinding, type LegacyStateReport, } from './legacyStatePreflight.js';
38
- export { DEFAULT_SKY_CODE_CHANNEL, DEFAULT_SKY_CODE_FEED_BASE, SKY_CODE_CHANNELS, SKY_CODE_CHANNEL_ENV, SKY_CODE_MANIFEST_FILE, isSkyCodeChannel, resolveChannelManifestUrl, resolveSkyCodeChannel, skyCodeChannelPrefix, skyCodeFeedBase, SKY_CODE_FEED_BASE_ENV, type SkyCodeChannel, } from './skyCodeChannels.js';
38
+ export * from './firstPartyHarness.js';
39
+ export { FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX, FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX, firstPartyHarnessBinDir, firstPartyHarnessHomeDir, firstPartyHarnessInstalledBinaryPath, linkFirstPartyHarnessAliases, migrateFirstPartyHarnessHome, type FirstPartyHarnessHomeFs, type FirstPartyHarnessHomeMigration, } from './firstPartyHarnessHome.js';
40
+ export { DEFAULT_FIRST_PARTY_HARNESS_CHANNEL, FIRST_PARTY_HARNESS_CHANNELS, firstPartyHarnessChannelPrefix, firstPartyHarnessFeedBase, firstPartyHarnessFeedCandidates, isFirstPartyHarnessChannel, resolveChannelManifestUrl, resolveFirstPartyHarnessChannel, type FirstPartyHarnessChannel, type FirstPartyHarnessFeedCandidate, DEFAULT_SKY_CODE_CHANNEL, DEFAULT_SKY_CODE_FEED_BASE, SKY_CODE_CHANNELS, SKY_CODE_CHANNEL_ENV, SKY_CODE_MANIFEST_FILE, isSkyCodeChannel, resolveSkyCodeChannel, skyCodeChannelPrefix, skyCodeFeedBase, SKY_CODE_FEED_BASE_ENV, type SkyCodeChannel, } from './firstPartyHarnessChannels.js';
39
41
  export { OVERSKY_RELEASE_PUBLIC_KEYS, type ReleaseKey } from './releaseKeys.js';
40
42
  export { MAX_SIGS, buildArtifactMessage, buildManifestMessage, getDeltaFor, verifyArtifact, verifyManifest, type DaemonVersionManifest, type DeltaDescriptor, type ManifestSignature, type PlatformArtifact, type VerifyResult, } from './releaseManifest.js';
41
43
  export { resolveConfigRoot, configRootPath, isConfigRootOverridden } from './configRoot.js';
@@ -126,7 +126,7 @@ export { ensureMachineDaemonId, legacyDaemonUuids, __resetCachedDaemonIdForTest,
126
126
  // shapes locally because it can't pull a Node-shaped package.
127
127
  export { PAIR_AAD_PREFIX, PAIR_AAD_MAX_LEN, PAIR_BUNDLE_MAX_BASE64_LEN, PAIR_NONCE_LEN, PAIR_PUBKEY_LEN, PAIR_SECRET_LEN, PairBundleError, assemblePairBundleBase64, base64ToUint8Array as pairBase64ToUint8Array, base64UrlToUint8Array as pairBase64UrlToUint8Array, buildPairAad, buildPairUrl, generatePairKeyPair, openPairBundleBase64, parsePairBundle, parsePairPlaintext, parsePairUrl, uint8ArrayToBase64 as pairUint8ArrayToBase64, uint8ArrayToBase64Url as pairUint8ArrayToBase64Url, } from './daemonPair.js';
128
128
  // OSK-3897 — harness trust tiers, shared by the daemon (the authority) and the
129
- // CLI (which publishes the fact in `sky code doctor`). The PROVENANCE half stays
129
+ // CLI (which publishes the fact in `skrr code doctor`). The PROVENANCE half stays
130
130
  // in the daemon on purpose: a tier is a published fact, provenance is evidence
131
131
  // about bytes on disk that only the spawning process can gather.
132
132
  export { HARNESS_TIERS, getHarnessTier, credentialPolicyForTier, tierPermitsConfiguredAutoMode, } from './harnessTrust.js';
@@ -142,7 +142,17 @@ export { CONFIG_DIR_NAME, LEGACY_CONFIG_DIR_NAMES, NATIVE_ID_PREFIX, LEGACY_NATI
142
142
  /* Pre-rename state detection. Callers REFUSE on a positive result; nothing here
143
143
  reads, copies or removes what it finds (§7.2, §8.4). */
144
144
  export { findLegacyLocalState, describeLegacyState, } from './legacyStatePreflight.js';
145
- export { DEFAULT_SKY_CODE_CHANNEL, DEFAULT_SKY_CODE_FEED_BASE, SKY_CODE_CHANNELS, SKY_CODE_CHANNEL_ENV, SKY_CODE_MANIFEST_FILE, isSkyCodeChannel, resolveChannelManifestUrl, resolveSkyCodeChannel, skyCodeChannelPrefix, skyCodeFeedBase, SKY_CODE_FEED_BASE_ENV, } from './skyCodeChannels.js';
145
+ // The first-party harness identity: the ONE place its product name is written.
146
+ // Pure, and also published as the `./first-party-harness` subpath for browser and
147
+ // React Native consumers. See firstPartyHarness.ts.
148
+ export * from './firstPartyHarness.js';
149
+ // Where the first-party harness lives under the skrr root, and the one-time move
150
+ // that keeps legacy installs working across a rename. Node-only.
151
+ export { FIRST_PARTY_HARNESS_PREVIOUS_SUFFIX, FIRST_PARTY_HARNESS_PROVENANCE_SUFFIX, firstPartyHarnessBinDir, firstPartyHarnessHomeDir, firstPartyHarnessInstalledBinaryPath, linkFirstPartyHarnessAliases, migrateFirstPartyHarnessHome, } from './firstPartyHarnessHome.js';
152
+ export { DEFAULT_FIRST_PARTY_HARNESS_CHANNEL, FIRST_PARTY_HARNESS_CHANNELS, firstPartyHarnessChannelPrefix, firstPartyHarnessFeedBase, firstPartyHarnessFeedCandidates, isFirstPartyHarnessChannel, resolveChannelManifestUrl, resolveFirstPartyHarnessChannel,
153
+ // Deprecated names kept for installed CLIs that resolve this published package
154
+ // with a caret range. Removed in the contract phase with a minor bump.
155
+ DEFAULT_SKY_CODE_CHANNEL, DEFAULT_SKY_CODE_FEED_BASE, SKY_CODE_CHANNELS, SKY_CODE_CHANNEL_ENV, SKY_CODE_MANIFEST_FILE, isSkyCodeChannel, resolveSkyCodeChannel, skyCodeChannelPrefix, skyCodeFeedBase, SKY_CODE_FEED_BASE_ENV, } from './firstPartyHarnessChannels.js';
146
156
  // Daemon release trust: the pinned ed25519 keys and the canonical signature
147
157
  // messages. Shared because BOTH the daemon (self-update) and the CLI
148
158
  // (`skrr daemon install`, which runs when there is no daemon to ask) verify the
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * WHY IT LIVES IN auth-core NOW
20
20
  *
21
- * It lived in `daemon/src/release-manifest.ts`, and `skyCodeChannels.ts` beside
21
+ * It lived in `daemon/src/release-manifest.ts`, and `firstPartyHarnessChannels.ts` beside
22
22
  * it recorded the reason for stopping there: "the installer, the signature
23
23
  * verification and the promotion machinery stay in the daemon, which is the
24
24
  * only process that performs them." That premise held exactly until the CLI
@@ -5,8 +5,8 @@
5
5
  * ── Why this lives in auth-core ────────────────────────────────────────────
6
6
  *
7
7
  * The daemon has stripped its own credentials out of `process.env` before every
8
- * harness spawn since `daemon/src/env-sanitize.ts` was written. `sky code` spawns
9
- * THE SAME engine — `daemon/src/backends/acp.ts` and `cli/src/lib/sky-code.ts`
8
+ * harness spawn since `daemon/src/env-sanitize.ts` was written. `skrr code` spawns
9
+ * THE SAME engine — `daemon/src/backends/acp.ts` and `cli/src/lib/first-party-harness.ts`
10
10
  * resolve the same binary, from the same managed location — and had no
11
11
  * equivalent, so `OVERSKY_TOKEN` (a cloud-reusable platform credential that
12
12
  * authenticates every `/api/*` call as the human) entered the environment of a
@@ -32,10 +32,10 @@
32
32
  * This is a DENY-LIST over the parent's own environment, and it strips provider
33
33
  * credentials because the daemon delivers a child's model credential explicitly
34
34
  * (`cred.env`, after the trust gate) and never by ambient inheritance. A parent
35
- * whose supported mode IS ambient inheritance — `sky code` running BYOK, where
35
+ * whose supported mode IS ambient inheritance — `skrr code` running BYOK, where
36
36
  * the user's own key is the credential the run is meant to spend — has to
37
37
  * re-admit that credential deliberately after this filter. See
38
- * `cli/src/lib/sky-code.ts` (`engineSpawnEnv`). Do not soften the list here to
38
+ * `cli/src/lib/first-party-harness.ts` (`engineSpawnEnv`). Do not soften the list here to
39
39
  * serve that case: a deny-list with a hole is not a deny-list.
40
40
  */
41
41
  /**
@@ -5,8 +5,8 @@
5
5
  * ── Why this lives in auth-core ────────────────────────────────────────────
6
6
  *
7
7
  * The daemon has stripped its own credentials out of `process.env` before every
8
- * harness spawn since `daemon/src/env-sanitize.ts` was written. `sky code` spawns
9
- * THE SAME engine — `daemon/src/backends/acp.ts` and `cli/src/lib/sky-code.ts`
8
+ * harness spawn since `daemon/src/env-sanitize.ts` was written. `skrr code` spawns
9
+ * THE SAME engine — `daemon/src/backends/acp.ts` and `cli/src/lib/first-party-harness.ts`
10
10
  * resolve the same binary, from the same managed location — and had no
11
11
  * equivalent, so `OVERSKY_TOKEN` (a cloud-reusable platform credential that
12
12
  * authenticates every `/api/*` call as the human) entered the environment of a
@@ -32,10 +32,10 @@
32
32
  * This is a DENY-LIST over the parent's own environment, and it strips provider
33
33
  * credentials because the daemon delivers a child's model credential explicitly
34
34
  * (`cred.env`, after the trust gate) and never by ambient inheritance. A parent
35
- * whose supported mode IS ambient inheritance — `sky code` running BYOK, where
35
+ * whose supported mode IS ambient inheritance — `skrr code` running BYOK, where
36
36
  * the user's own key is the credential the run is meant to spend — has to
37
37
  * re-admit that credential deliberately after this filter. See
38
- * `cli/src/lib/sky-code.ts` (`engineSpawnEnv`). Do not soften the list here to
38
+ * `cli/src/lib/first-party-harness.ts` (`engineSpawnEnv`). Do not soften the list here to
39
39
  * serve that case: a deny-list with a hole is not a deny-list.
40
40
  */
41
41
  /**
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@skrr-ai/auth-core",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "main": "dist/cjs/index.js",
5
5
  "types": "dist/esm/index.d.ts",
6
6
  "exports": {
@@ -23,6 +23,16 @@
23
23
  "types": "./dist/cjs/refreshClassification.d.ts",
24
24
  "default": "./dist/cjs/refreshClassification.js"
25
25
  }
26
+ },
27
+ "./first-party-harness": {
28
+ "import": {
29
+ "types": "./dist/esm/firstPartyHarness.d.ts",
30
+ "default": "./dist/esm/firstPartyHarness.js"
31
+ },
32
+ "require": {
33
+ "types": "./dist/cjs/firstPartyHarness.d.ts",
34
+ "default": "./dist/cjs/firstPartyHarness.js"
35
+ }
26
36
  }
27
37
  },
28
38
  "dependencies": {