codiedev 0.10.0-beta.4 → 0.10.0-beta.5

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 (146) hide show
  1. package/README.md +7 -1
  2. package/assets/tui-brand-plugin.tsx +14 -2
  3. package/dist/backup/cache.js +1 -5
  4. package/dist/backup/match.js +0 -6
  5. package/dist/backup/run.js +3 -35
  6. package/dist/captureExclusions.js +0 -12
  7. package/dist/captureLog.js +0 -28
  8. package/dist/captureReadiness.js +0 -58
  9. package/dist/captureSignals.js +66 -3
  10. package/dist/cli.js +41 -18
  11. package/dist/codexHookTrust.js +61 -5
  12. package/dist/codexLimitScan.js +2 -109
  13. package/dist/codie.js +27 -40
  14. package/dist/codieBrandInstall.js +0 -18
  15. package/dist/codieLauncher.js +2 -70
  16. package/dist/codieRuntime.js +19 -23
  17. package/dist/codieRuntimeInstall.js +108 -23
  18. package/dist/codieStartup.js +0 -3
  19. package/dist/codieUsage.js +38 -41
  20. package/dist/commands/ask.js +0 -3
  21. package/dist/commands/capture.js +0 -2
  22. package/dist/commands/createTicket.js +0 -10
  23. package/dist/commands/doctor.js +41 -82
  24. package/dist/commands/inbox.js +0 -2
  25. package/dist/commands/library.js +0 -3
  26. package/dist/commands/pack.js +573 -0
  27. package/dist/commands/ping.js +0 -2
  28. package/dist/commands/pull.js +6 -10
  29. package/dist/commands/push.js +2 -12
  30. package/dist/commands/react.js +0 -2
  31. package/dist/commands/reverseTicket.js +0 -33
  32. package/dist/commands/search.js +0 -4
  33. package/dist/commands/send.js +0 -5
  34. package/dist/commands/share.js +0 -4
  35. package/dist/commands/shared.js +0 -35
  36. package/dist/commands/traces.js +280 -0
  37. package/dist/configDir.js +0 -10
  38. package/dist/connect.js +21 -77
  39. package/dist/connectFlow.js +0 -9
  40. package/dist/copilotHooks.js +395 -0
  41. package/dist/copilotInstallation.js +64 -0
  42. package/dist/cursorHooks.js +40 -17
  43. package/dist/cursorLifecycle.js +109 -0
  44. package/dist/detection.js +0 -30
  45. package/dist/harnessDirs.js +73 -0
  46. package/dist/hook.js +193 -316
  47. package/dist/inboxFormat.js +0 -24
  48. package/dist/launchContext.js +150 -0
  49. package/dist/launcher.js +140 -0
  50. package/dist/limitEvent.js +41 -35
  51. package/dist/limitEventReporter.js +1 -1
  52. package/dist/mcp.js +3 -77
  53. package/dist/nativeSteering.js +24 -0
  54. package/dist/packs/codie.js +182 -0
  55. package/dist/packs/harnesses.js +374 -0
  56. package/dist/packs/hookGate.js +256 -0
  57. package/dist/packs/install.js +285 -0
  58. package/dist/packs/manifest.js +126 -0
  59. package/dist/packs/paths.js +81 -0
  60. package/dist/packs/shim.js +214 -0
  61. package/dist/packs/store.js +155 -0
  62. package/dist/packs/sync.js +170 -0
  63. package/dist/prUrl.js +0 -5
  64. package/dist/repoResolver.js +0 -19
  65. package/dist/secret.js +0 -3
  66. package/dist/security.js +0 -20
  67. package/dist/securityDeepScan.js +1 -14
  68. package/dist/securityDoctor.js +0 -2
  69. package/dist/securityFeature.js +0 -1
  70. package/dist/securityInstall.js +6 -13
  71. package/dist/securityInventory.js +23 -13
  72. package/dist/securityPilot.js +0 -2
  73. package/dist/securityPolicy.js +0 -7
  74. package/dist/selfUpdate.js +331 -0
  75. package/dist/skillMarker.js +23 -0
  76. package/dist/subagents.js +135 -0
  77. package/dist/teamSkillContext.js +10 -0
  78. package/dist/transcriptModels.js +6 -8
  79. package/dist/upgradeNudge.js +0 -30
  80. package/dist/utils.js +286 -733
  81. package/dist/version.js +0 -10
  82. package/dist/versionCheck.js +0 -33
  83. package/package.json +7 -6
  84. package/dist/backup/cache.d.ts +0 -14
  85. package/dist/backup/match.d.ts +0 -2
  86. package/dist/backup/run.d.ts +0 -25
  87. package/dist/captureExclusions.d.ts +0 -30
  88. package/dist/captureLog.d.ts +0 -20
  89. package/dist/captureReadiness.d.ts +0 -65
  90. package/dist/captureSignals.d.ts +0 -19
  91. package/dist/cli.d.ts +0 -16
  92. package/dist/codexHookTrust.d.ts +0 -34
  93. package/dist/codexLimitScan.d.ts +0 -64
  94. package/dist/codie.d.ts +0 -2
  95. package/dist/codieBrandInstall.d.ts +0 -11
  96. package/dist/codieLauncher.d.ts +0 -86
  97. package/dist/codieRuntime.d.ts +0 -29
  98. package/dist/codieRuntimeInstall.d.ts +0 -28
  99. package/dist/codieStartup.d.ts +0 -26
  100. package/dist/codieUsage.d.ts +0 -57
  101. package/dist/commands/ask.d.ts +0 -1
  102. package/dist/commands/capture.d.ts +0 -14
  103. package/dist/commands/createTicket.d.ts +0 -27
  104. package/dist/commands/delete.d.ts +0 -1
  105. package/dist/commands/doctor.d.ts +0 -1
  106. package/dist/commands/inbox.d.ts +0 -3
  107. package/dist/commands/library.d.ts +0 -2
  108. package/dist/commands/note.d.ts +0 -1
  109. package/dist/commands/ping.d.ts +0 -1
  110. package/dist/commands/promote.d.ts +0 -1
  111. package/dist/commands/pull.d.ts +0 -2
  112. package/dist/commands/push.d.ts +0 -33
  113. package/dist/commands/react.d.ts +0 -2
  114. package/dist/commands/reverseTicket.d.ts +0 -2
  115. package/dist/commands/search.d.ts +0 -2
  116. package/dist/commands/security.d.ts +0 -1
  117. package/dist/commands/send.d.ts +0 -2
  118. package/dist/commands/share.d.ts +0 -2
  119. package/dist/commands/shared.d.ts +0 -64
  120. package/dist/configDir.d.ts +0 -1
  121. package/dist/connect.d.ts +0 -1
  122. package/dist/connectFlow.d.ts +0 -13
  123. package/dist/cursorHooks.d.ts +0 -23
  124. package/dist/detection.d.ts +0 -30
  125. package/dist/hook.d.ts +0 -6
  126. package/dist/inboxFormat.d.ts +0 -38
  127. package/dist/limitEvent.d.ts +0 -16
  128. package/dist/limitEventReporter.d.ts +0 -21
  129. package/dist/mcp.d.ts +0 -12
  130. package/dist/prUrl.d.ts +0 -9
  131. package/dist/repoResolver.d.ts +0 -17
  132. package/dist/secret.d.ts +0 -1
  133. package/dist/security.d.ts +0 -102
  134. package/dist/securityDeepScan.d.ts +0 -41
  135. package/dist/securityDoctor.d.ts +0 -32
  136. package/dist/securityFeature.d.ts +0 -5
  137. package/dist/securityInstall.d.ts +0 -63
  138. package/dist/securityInventory.d.ts +0 -54
  139. package/dist/securityPilot.d.ts +0 -19
  140. package/dist/securityPolicy.d.ts +0 -33
  141. package/dist/securitySession.d.ts +0 -6
  142. package/dist/transcriptModels.d.ts +0 -18
  143. package/dist/upgradeNudge.d.ts +0 -25
  144. package/dist/utils.d.ts +0 -346
  145. package/dist/version.d.ts +0 -1
  146. package/dist/versionCheck.d.ts +0 -68
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- // Stages the Codiedev TUI plugin into the engine's config directory.
3
- //
4
- // TUI plugins load only from tui.json in the OpenCode config dir, so the
5
- // packaged template is copied there and referenced by a relative spec. The
6
- // copy also carries the status line, which needs an absolute path back into
7
- // this package — the plugin sits outside it and cannot reach it any other way.
8
- //
9
- // Every step is best-effort: a developer who cannot write this directory still
10
- // gets a working harness, just the stock logo and no status line.
11
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
3
  if (k2 === undefined) k2 = k;
13
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -50,15 +41,8 @@ const os = __importStar(require("os"));
50
41
  const path = __importStar(require("path"));
51
42
  const codieLauncher_1 = require("./codieLauncher");
52
43
  function assetPath(name) {
53
- // dist/codieBrandInstall.js → ../assets/<name>; src/… → ../assets/<name>.
54
44
  return path.join(__dirname, "..", "assets", name);
55
45
  }
56
- /**
57
- * The usage module this install should load. The shipped package has the
58
- * compiled file; a source checkout (and the test run) has the TypeScript one.
59
- * Naming a path that does not exist would cost the status line silently, so a
60
- * miss raises instead and the caller falls back to the plain plugin.
61
- */
62
46
  function usageModulePath() {
63
47
  const candidates = [
64
48
  path.join(__dirname, "codieUsage.js"),
@@ -69,8 +53,6 @@ function usageModulePath() {
69
53
  throw new Error("this install has no Codiedev usage module");
70
54
  return found;
71
55
  }
72
- /** The engine resolves its config dir per the XDG spec, so a customized
73
- * XDG_CONFIG_HOME must be honored or tui.json lands where it never reads. */
74
56
  function openCodeConfigDir() {
75
57
  return path.join(process.env.XDG_CONFIG_HOME?.trim() || path.join(os.homedir(), ".config"), "opencode");
76
58
  }
@@ -1,13 +1,4 @@
1
1
  "use strict";
2
- // Pure launcher logic for the Codie harness.
3
- //
4
- // Ported from codiedev-gateway-cli/npm/lib/launcher.js when the harness folded
5
- // into this package so `npm i -g codiedev` is the only install a developer
6
- // needs — Claude Code, Codex, Cursor, and Codie all come from one package and
7
- // share the one token `codiedev connect` stored.
8
- //
9
- // Everything here is pure (no fs/network side effects beyond an injected
10
- // fetch) so the governance rules can be tested without a live gateway.
11
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
12
3
  if (k2 === undefined) k2 = k;
13
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -58,6 +49,7 @@ exports.parseCodieArgs = parseCodieArgs;
58
49
  exports.validateCodieArgs = validateCodieArgs;
59
50
  const path = __importStar(require("path"));
60
51
  const url_1 = require("url");
52
+ const harnessDirs_1 = require("./harnessDirs");
61
53
  const DEFAULT_NAMES = {
62
54
  "openai/gpt-5.6-luna": "GPT-5.6 Luna",
63
55
  "openai/gpt-5.3-codex": "GPT-5.3 Codex",
@@ -67,9 +59,6 @@ const DEFAULT_NAMES = {
67
59
  "z-ai/glm-5.2": "GLM 5.2 · Fireworks ZDR",
68
60
  "moonshotai/kimi-k2.7-code": "Kimi K2.7 Code · Fireworks ZDR",
69
61
  };
70
- /** A rate card is usable only when both sides are real, finite and not
71
- * negative. Half a card prices a session wrong in a way nobody can see, so a
72
- * partial one is dropped and the model stays unpriced. */
73
62
  function usableModelCost(value) {
74
63
  if (!value || typeof value !== "object")
75
64
  return undefined;
@@ -82,10 +71,6 @@ function usableModelCost(value) {
82
71
  return undefined;
83
72
  return { input, output };
84
73
  }
85
- /** A context/output window is usable only when both sides are real, finite and
86
- * positive. A partial window makes the engine's "% of context used" read wrong
87
- * in a way nobody can see, so a partial one is dropped and the model keeps the
88
- * engine's default. */
89
74
  function usableModelLimit(value) {
90
75
  if (!value || typeof value !== "object")
91
76
  return undefined;
@@ -99,9 +84,6 @@ function usableModelLimit(value) {
99
84
  return { context, output };
100
85
  }
101
86
  exports.CODIE_SECURITY_BLOCK_MARKER = "__CODIEDEV_SECURITY_BLOCK__";
102
- /** Extract the private adapter marker used to carry a local block reason
103
- * across OpenCode's generic 500 boundary. The marker never contains prompt or
104
- * tool input — only the deterministic policy reason. */
105
87
  function extractCodieSecurityBlock(stderr) {
106
88
  for (const line of stderr.split(/\r?\n/)) {
107
89
  const markerIndex = line.indexOf(exports.CODIE_SECURITY_BLOCK_MARKER);
@@ -113,17 +95,10 @@ function extractCodieSecurityBlock(stderr) {
113
95
  return parsed.reason.trim();
114
96
  }
115
97
  catch {
116
- // Ignore malformed or foreign stderr and preserve the original runtime error.
117
98
  }
118
99
  }
119
100
  return undefined;
120
101
  }
121
- /**
122
- * Pin non-interactive runs to the directory where `codie` was launched.
123
- * OpenCode can reuse a previously-known project instance; without an explicit
124
- * --dir, a real smoke run launched in a throwaway repo resumed the caller's
125
- * main checkout and executed there. TUI mode already owns its cwd directly.
126
- */
127
102
  function withCodieWorkingDirectory(args, cwd) {
128
103
  if (args[0] !== "run")
129
104
  return args;
@@ -145,8 +120,6 @@ function governedModel(value, approved, fallback, label) {
145
120
  return `codiedev/${model}`;
146
121
  }
147
122
  function economicalModel(catalog, primary) {
148
- // Catalog ids arrive already normalized through bareModel(), so the
149
- // gateway's `codiedev/fast` alias appears here as plain "fast".
150
123
  const preferred = ["openai/gpt-5.1-codex-mini", "fast", "z-ai/glm-5.2"];
151
124
  return preferred.find((id) => catalog.some((model) => model.id === id)) || primary;
152
125
  }
@@ -178,11 +151,6 @@ function buildCodieConfig({ existing = {}, catalog, env = {}, plugin, plugins =
178
151
  const existingPlugins = Array.isArray(existing.plugin) ? existing.plugin : [];
179
152
  const requiredPlugins = [...plugins, plugin].filter(Boolean);
180
153
  const models = Object.fromEntries(catalog.map((model) => {
181
- // The engine prices a session from this rate card. Without it every
182
- // model reads $0.00, which is how the harness came to show no spend at
183
- // all. The gateway publishes prompt and completion prices only, so
184
- // cached tokens are priced at zero here and the engine's session figure
185
- // stays an estimate — the budget line from /me/usage is the true spend.
186
154
  const cost = usableModelCost(model.cost);
187
155
  const limit = usableModelLimit(model.limit);
188
156
  return [
@@ -222,14 +190,6 @@ function buildCodieConfig({ existing = {}, catalog, env = {}, plugin, plugins =
222
190
  };
223
191
  }
224
192
  exports.USAGE_MODULE_PLACEHOLDER = "__CODIEDEV_USAGE_MODULE__";
225
- /**
226
- * Point the staged TUI plugin at this install's usage module.
227
- *
228
- * The plugin is copied into the engine's own config directory, so it cannot
229
- * reach the package by a relative path — the absolute path is baked in at
230
- * stage time. It goes in as a JSON literal so a Windows path keeps its
231
- * backslashes instead of turning into escape sequences.
232
- */
233
193
  function stageUsageModule(source, modulePath) {
234
194
  if (!source.includes(exports.USAGE_MODULE_PLACEHOLDER)) {
235
195
  throw new Error(`plugin template is missing the ${exports.USAGE_MODULE_PLACEHOLDER} placeholder`);
@@ -237,8 +197,6 @@ function stageUsageModule(source, modulePath) {
237
197
  return source.split(exports.USAGE_MODULE_PLACEHOLDER).join(JSON.stringify(modulePath));
238
198
  }
239
199
  exports.BRAND_PLUGIN_SPEC = "./plugins/codiedev-brand.tsx";
240
- // Pure merge for ~/.config/opencode/tui.json: TUI plugins load from tui.json,
241
- // not the opencode.json we inject via OPENCODE_CONFIG_CONTENT.
242
200
  function withBrandPlugin(existing) {
243
201
  const config = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
244
202
  const plugins = Array.isArray(config.plugin) ? config.plugin : [];
@@ -247,13 +205,6 @@ function withBrandPlugin(existing) {
247
205
  return { config, changed: false };
248
206
  return { config: { ...config, plugin: [...plugins, exports.BRAND_PLUGIN_SPEC] }, changed: true };
249
207
  }
250
- // ── Engine wordmark suppression ─────────────────────────────────────────
251
- // The OpenCode engine prints a block-letter "opencode" wordmark to terminal
252
- // scrollback around each session (the boot/exit banner). A four-row terminal
253
- // raster cannot reproduce the real Codiedev wordmark cleanly, so leave that
254
- // surface blank and keep the proper brand mark on the plugin-driven home
255
- // screen. The bundled JS table is replaced byte-for-byte so binary offsets do
256
- // not move.
257
208
  function escapeJsSource(row) {
258
209
  let out = "";
259
210
  for (const ch of row) {
@@ -266,7 +217,6 @@ function wordmarkRegion(left, right, { escapeUnicode = true } = {}) {
266
217
  const rows = (list) => list.map((row) => `"${escapeUnicode ? escapeJsSource(row) : row}"`).join(",");
267
218
  return `var vn={left:[${rows(left)}],right:[${rows(right)}]};`;
268
219
  }
269
- // o p e n | c o d e — must match the engine's bundle byte-for-byte.
270
220
  exports.STOCK_WORDMARK = wordmarkRegion([
271
221
  " ",
272
222
  "█▀▀█ █▀▀█ █▀▀█ █▀▀▄",
@@ -278,12 +228,6 @@ exports.STOCK_WORDMARK = wordmarkRegion([
278
228
  "█___ █__█ █__█ █^^^",
279
229
  "▀▀▀▀ ▀▀▀▀ ▀▀▀▀ ▀▀▀▀",
280
230
  ]);
281
- // c o d i | e d e v, rasterized from public/codiedevlogo.svg at eight vertical
282
- // pixels and packed into four terminal rows with half-block characters. This
283
- // preserves the real mark's round lowercase bowls, linked c/o, ascenders, and
284
- // compact v instead of approximating the letters with OpenCode's square font.
285
- // The engine inserts one column between left and right, so the blank source
286
- // column at the codi/edev boundary is intentionally omitted.
287
231
  const CODIE_LEFT = [
288
232
  " ▄ ▄",
289
233
  " ▄▄█▄▄ ▄▄█▄▄ ▄████ █",
@@ -296,9 +240,6 @@ const CODIE_RIGHT = [
296
240
  "██▀▀█ █ █ ██▄▀█ ▀█▄█ ",
297
241
  "▀▀█▀▀ ▀▀▀▀▀ ▀▀▀▀ ▀▀ ",
298
242
  ];
299
- // Migration source for the first hand-drawn banner shipped during development.
300
- // Recognizing it lets an existing install upgrade in place instead of keeping
301
- // the old square letterforms until OpenCode happens to reinstall its binary.
302
243
  const LEGACY_CODIE_LEFT = [
303
244
  " ▄ ▄ ",
304
245
  "█▀▀▀ █▀▀█ █▀▀█ █ ",
@@ -320,9 +261,6 @@ exports.LEGACY_CODIEDEV_WORDMARK = (() => {
320
261
  return wordmarkRegion(LEGACY_CODIE_LEFT, padded);
321
262
  })();
322
263
  exports.CODIEDEV_WORDMARK = (() => {
323
- // Raw Unicode is valid JavaScript and costs fewer source bytes than \uXXXX.
324
- // Put the remaining byte padding outside the strings so it cannot widen the
325
- // rendered banner or shift its centering.
326
264
  const unpadded = wordmarkRegion(CODIE_LEFT, CODIE_RIGHT, { escapeUnicode: false });
327
265
  const deficit = Buffer.byteLength(exports.STOCK_WORDMARK) - Buffer.byteLength(unpadded);
328
266
  if (deficit < 0)
@@ -344,9 +282,6 @@ exports.HIDDEN_WORDMARK = (() => {
344
282
  }
345
283
  return region;
346
284
  })();
347
- // Pure: locate the stock or an earlier Codiedev wordmark in an engine binary.
348
- // Refuses to touch the file unless exactly one known region appears; an
349
- // already-hidden or reshaped upstream bundle is left alone.
350
285
  function patchBrandWordmark(binary) {
351
286
  const stock = Buffer.from(exports.STOCK_WORDMARK);
352
287
  const candidates = [stock, Buffer.from(exports.LEGACY_CODIEDEV_WORDMARK), Buffer.from(exports.CODIEDEV_WORDMARK)];
@@ -364,7 +299,7 @@ function patchBrandWordmark(binary) {
364
299
  return { changed: true, offset: matches[0], stock, replacement: Buffer.from(exports.HIDDEN_WORDMARK) };
365
300
  }
366
301
  function resolveCapturePlugin({ homeDir, existsSync, }) {
367
- const capturePath = path.join(homeDir, ".config", "opencode", "plugins", "codiedev.js");
302
+ const capturePath = path.join((0, harnessDirs_1.openCodeConfigDir)(process.env, homeDir), "plugins", "codiedev.js");
368
303
  if (!existsSync(capturePath)) {
369
304
  throw new Error("Codiedev session capture is not installed. Run `codiedev connect` first");
370
305
  }
@@ -378,9 +313,6 @@ async function fetchApprovedCatalog({ baseUrl, key, fetchImpl = fetch, }) {
378
313
  headers: { Authorization: `Bearer ${key}` },
379
314
  signal: controller.signal,
380
315
  });
381
- // The `codie` bin ships to every connected machine, including companies
382
- // whose gateway is still switched off. Those developers must get an
383
- // answer, not an HTTP status.
384
316
  if (response.status === 403) {
385
317
  throw new Error("The Codie harness is not enabled for your company yet. Ask your Codiedev admin to turn it on — " +
386
318
  "your session capture keeps working in the meantime.");
@@ -1,14 +1,4 @@
1
1
  "use strict";
2
- // Where the Codie harness gets its engine.
3
- //
4
- // The engine (`opencode-ai`) is a ~132MB platform binary. It is deliberately
5
- // NOT a dependency of this package: the overwhelming majority of installs are
6
- // capture-only (Claude Code, Codex, Cursor) and must not pay for a harness
7
- // they never launch. So `npm i -g codiedev` stays small, and the first `codie`
8
- // run downloads the engine into the Codiedev state dir.
9
- //
10
- // Resolution is pure and injectable so the precedence can be tested without
11
- // touching the network or a real 132MB install.
12
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
13
3
  if (k2 === undefined) k2 = k;
14
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -43,20 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
43
33
  };
44
34
  })();
45
35
  Object.defineProperty(exports, "__esModule", { value: true });
46
- exports.CODIE_MIN_NODE_MAJOR = exports.OPENCODE_RUNTIME_VERSION = exports.OPENCODE_RUNTIME_SPEC = void 0;
36
+ exports.ENGINE_MIN_BYTES = exports.CODIE_MIN_NODE_MAJOR = exports.OPENCODE_RUNTIME_VERSION = exports.OPENCODE_RUNTIME_SPEC = void 0;
47
37
  exports.codieRuntimeDir = codieRuntimeDir;
38
+ exports.isEngineSize = isEngineSize;
48
39
  exports.installedRuntimePackageJson = installedRuntimePackageJson;
49
40
  exports.resolveCodieRuntime = resolveCodieRuntime;
50
41
  exports.assertCodieNodeVersion = assertCodieNodeVersion;
51
42
  const path = __importStar(require("path"));
52
- // Pinned exactly, not a range. patchBrandWordmark() only rewrites bundles it
53
- // recognizes byte-for-byte, so a floating version would silently regress the
54
- // terminal branding whenever OpenCode ships a new build.
55
43
  exports.OPENCODE_RUNTIME_SPEC = "opencode-ai@1.18.4";
56
44
  exports.OPENCODE_RUNTIME_VERSION = exports.OPENCODE_RUNTIME_SPEC.slice(exports.OPENCODE_RUNTIME_SPEC.lastIndexOf("@") + 1);
57
- // The harness needs 20; capture does not. package.json `engines` stays >=18 so
58
- // `codiedev connect` still installs for Node 18 users — this floor is enforced
59
- // at `codie` launch instead.
60
45
  exports.CODIE_MIN_NODE_MAJOR = 20;
61
46
  function codieRuntimeDir(stateDir) {
62
47
  return path.join(stateDir, "runtime");
@@ -64,11 +49,14 @@ function codieRuntimeDir(stateDir) {
64
49
  function installedBinary(stateDir) {
65
50
  return path.join(codieRuntimeDir(stateDir), "node_modules", "opencode-ai", "bin", "opencode.exe");
66
51
  }
52
+ exports.ENGINE_MIN_BYTES = 1_000_000;
53
+ function isEngineSize(bytes) {
54
+ return bytes >= exports.ENGINE_MIN_BYTES;
55
+ }
67
56
  function installedRuntimePackageJson(stateDir) {
68
57
  return path.join(codieRuntimeDir(stateDir), "node_modules", "opencode-ai", "package.json");
69
58
  }
70
- function resolveCodieRuntime({ env, stateDir, existsSync, readInstalledVersion, resolveBundled, }) {
71
- // 1. Explicit override — the air-gapped / pre-provisioned escape hatch.
59
+ function resolveCodieRuntime({ env, stateDir, existsSync, readInstalledVersion, resolveBundled, isEngine = () => true, }) {
72
60
  const override = env.CODIEDEV_OPENCODE_BINARY?.trim();
73
61
  if (override) {
74
62
  if (!existsSync(override)) {
@@ -76,15 +64,21 @@ function resolveCodieRuntime({ env, stateDir, existsSync, readInstalledVersion,
76
64
  }
77
65
  return { kind: "ready", binary: override };
78
66
  }
79
- // 2. A bundled engine, if this package was installed with one (dev checkout,
80
- // or a future vendored distribution).
81
67
  const bundled = resolveBundled();
82
- if (bundled)
68
+ if (bundled && isEngine(bundled))
83
69
  return { kind: "ready", binary: bundled };
84
- // 3. The lazily installed engine.
85
70
  const binary = installedBinary(stateDir);
86
71
  if (existsSync(binary)) {
87
72
  const currentVersion = readInstalledVersion?.(installedRuntimePackageJson(stateDir)) ?? null;
73
+ if (!isEngine(binary) && (!currentVersion || currentVersion === exports.OPENCODE_RUNTIME_VERSION)) {
74
+ return {
75
+ kind: "unlinked",
76
+ installDir: codieRuntimeDir(stateDir),
77
+ binary,
78
+ spec: exports.OPENCODE_RUNTIME_SPEC,
79
+ packageDir: path.dirname(installedRuntimePackageJson(stateDir)),
80
+ };
81
+ }
88
82
  if (currentVersion && currentVersion !== exports.OPENCODE_RUNTIME_VERSION) {
89
83
  return {
90
84
  kind: "outdated",
@@ -93,6 +87,8 @@ function resolveCodieRuntime({ env, stateDir, existsSync, readInstalledVersion,
93
87
  spec: exports.OPENCODE_RUNTIME_SPEC,
94
88
  currentVersion,
95
89
  expectedVersion: exports.OPENCODE_RUNTIME_VERSION,
90
+ linked: isEngine(binary),
91
+ packageDir: path.dirname(installedRuntimePackageJson(stateDir)),
96
92
  };
97
93
  }
98
94
  return { kind: "ready", binary };
@@ -1,10 +1,4 @@
1
1
  "use strict";
2
- // First-run install of the OpenCode engine for the Codie harness.
3
- //
4
- // Staged then renamed: npm writes into a sibling staging directory and only an
5
- // install that actually produced the engine binary gets promoted to the real
6
- // runtime path. An interrupted or half-finished download therefore never
7
- // leaves a directory that resolveCodieRuntime() would report as "ready".
8
2
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
9
3
  if (k2 === undefined) k2 = k;
10
4
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -39,9 +33,15 @@ var __importStar = (this && this.__importStar) || (function () {
39
33
  };
40
34
  })();
41
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.IGNORE_SCRIPTS = void 0;
42
37
  exports.buildRuntimeInstallArgs = buildRuntimeInstallArgs;
43
38
  exports.installCodieRuntime = installCodieRuntime;
44
39
  exports.ensureCodieRuntimeBinary = ensureCodieRuntimeBinary;
40
+ exports.linkFailure = linkFailure;
41
+ exports.windowsCommand = windowsCommand;
42
+ exports.isEngineOnDisk = isEngineOnDisk;
43
+ exports.npmIgnoresScripts = npmIgnoresScripts;
44
+ exports.linkEngineOnDisk = linkEngineOnDisk;
45
45
  exports.installCodieRuntimeOnDisk = installCodieRuntimeOnDisk;
46
46
  exports.defaultStagingRoot = defaultStagingRoot;
47
47
  const fs = __importStar(require("fs"));
@@ -50,8 +50,6 @@ const path = __importStar(require("path"));
50
50
  const child_process_1 = require("child_process");
51
51
  const codieRuntime_1 = require("./codieRuntime");
52
52
  function buildRuntimeInstallArgs(stagingDir, spec) {
53
- // No --registry / --proxy: inherit whatever npm config the developer's
54
- // machine already has so an internal mirror keeps working.
55
53
  return [
56
54
  "install",
57
55
  "--prefix",
@@ -64,7 +62,7 @@ function buildRuntimeInstallArgs(stagingDir, spec) {
64
62
  ];
65
63
  }
66
64
  function installCodieRuntime(deps) {
67
- const { runtime, makeStagingDir, run, exists, rename, remove, log } = deps;
65
+ const { runtime, makeStagingDir, run, exists, rename, remove, log, isEngine = () => true, linkEngine } = deps;
68
66
  const staging = makeStagingDir();
69
67
  log(`Installing the Codie engine (${runtime.spec}). This runs once, and only for \`codie\`.`);
70
68
  let result;
@@ -79,56 +77,141 @@ function installCodieRuntime(deps) {
79
77
  remove(staging);
80
78
  throw installFailure(result.stderr?.trim() || `npm exited with ${result.status}`);
81
79
  }
82
- // Trust the artifact, not the exit code — a misconfigured mirror can succeed
83
- // with nothing useful in the tarball.
84
80
  const stagedBinary = path.join(staging, "node_modules", "opencode-ai", "bin", "opencode.exe");
85
81
  if (!exists(stagedBinary)) {
86
82
  remove(staging);
87
83
  throw new Error(`The Codie engine install did not produce a runtime binary. ` +
88
84
  `Check that ${runtime.spec} is available from your npm registry.`);
89
85
  }
86
+ if (!isEngine(stagedBinary)) {
87
+ let linked = { ok: false };
88
+ try {
89
+ linked = linkEngine?.(path.dirname(path.dirname(stagedBinary))) ?? { ok: false };
90
+ }
91
+ catch (error) {
92
+ linked = { ok: false, detail: error instanceof Error ? error.message : String(error) };
93
+ }
94
+ if (!linked.ok || !isEngine(stagedBinary)) {
95
+ if (linked.detail === exports.IGNORE_SCRIPTS) {
96
+ try {
97
+ rename(staging, runtime.installDir);
98
+ }
99
+ finally {
100
+ remove(staging);
101
+ }
102
+ }
103
+ else {
104
+ remove(staging);
105
+ }
106
+ throw new Error(linkFailure(linked, path.join(runtime.installDir, "node_modules", "opencode-ai")));
107
+ }
108
+ }
90
109
  try {
91
110
  rename(staging, runtime.installDir);
92
111
  }
93
112
  finally {
94
- // No-op after a successful promote (rmSync is force: true); after a failed
95
- // one it keeps a ~132MB staging dir from being orphaned — mkdtemp mints a
96
- // fresh sibling every attempt, so leaks would accumulate.
97
113
  remove(staging);
98
114
  }
99
115
  }
100
- // Resolve the engine, downloading it once if this is the first `codie` run.
101
- async function ensureCodieRuntimeBinary({ resolve, install, confirmUpdate = async () => false, }) {
116
+ async function ensureCodieRuntimeBinary({ resolve, install, confirmUpdate = async () => false, repair, }) {
102
117
  const runtime = resolve();
103
118
  if (runtime.kind === "ready")
104
119
  return runtime.binary;
105
- if (runtime.kind === "outdated" && !(await confirmUpdate(runtime)))
120
+ if (runtime.kind === "outdated" && !(await confirmUpdate(runtime))) {
121
+ if (runtime.linked !== false || !repair || !runtime.packageDir)
122
+ return runtime.binary;
123
+ if (repair({ kind: "unlinked", installDir: runtime.installDir, binary: runtime.binary, spec: runtime.spec, packageDir: runtime.packageDir })) {
124
+ return runtime.binary;
125
+ }
126
+ install(runtime);
106
127
  return runtime.binary;
128
+ }
129
+ if (runtime.kind === "unlinked" && repair?.(runtime)) {
130
+ const after = resolve();
131
+ if (after.kind === "ready")
132
+ return after.binary;
133
+ }
107
134
  install(runtime);
108
135
  return runtime.binary;
109
136
  }
137
+ function linkFailure(linked, packageDir) {
138
+ if (linked.detail === exports.IGNORE_SCRIPTS) {
139
+ const script = packageDir ? `"${path.join(packageDir, "postinstall.mjs")}"` : "postinstall.mjs in the opencode-ai package";
140
+ return (`The Codie engine installed, but npm is set to ignore install scripts (ignore-scripts=true), ` +
141
+ `so codie won't run opencode-ai's setup step. Run \`node ${script}\` yourself, ` +
142
+ `or set CODIEDEV_OPENCODE_BINARY to an opencode binary.`);
143
+ }
144
+ return (`The Codie engine installed, but its setup step (opencode-ai's postinstall) failed, so there is no runnable engine` +
145
+ (linked.detail ? `: ${linked.detail}` : ".") +
146
+ ` Set CODIEDEV_OPENCODE_BINARY to an opencode binary to use a pre-provisioned engine.`);
147
+ }
148
+ exports.IGNORE_SCRIPTS = "ignore-scripts";
110
149
  function installFailure(detail) {
111
150
  return new Error(`Could not download the Codie engine: ${detail}\n` +
112
151
  `If you are behind a proxy or an internal registry, make sure npm can reach it. ` +
113
152
  `To use a pre-provisioned engine instead, set CODIEDEV_OPENCODE_BINARY to its path.`);
114
153
  }
115
- // ── Real-world wiring ───────────────────────────────────────────────────
116
- // Kept separate from the pure logic above so tests never shell out to npm.
154
+ function windowsCommand(command, args) {
155
+ const quote = (a) => (/[\s"&|<>^()%!]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a);
156
+ return [command, ...args.map(quote)].join(" ");
157
+ }
158
+ function isEngineOnDisk(candidate) {
159
+ try {
160
+ return (0, codieRuntime_1.isEngineSize)(fs.statSync(candidate).size);
161
+ }
162
+ catch {
163
+ return false;
164
+ }
165
+ }
166
+ function npmIgnoresScripts(env = process.env) {
167
+ const fromEnv = env.npm_config_ignore_scripts ?? env.NPM_CONFIG_IGNORE_SCRIPTS;
168
+ if (fromEnv !== undefined)
169
+ return !["", "false", "0", "null", "no"].includes(fromEnv.trim().toLowerCase());
170
+ const opts = { encoding: "utf8", timeout: 10_000, windowsHide: true };
171
+ const result = process.platform === "win32"
172
+ ? (0, child_process_1.spawnSync)("npm.cmd config get ignore-scripts", { ...opts, shell: true })
173
+ : (0, child_process_1.spawnSync)("npm", ["config", "get", "ignore-scripts"], opts);
174
+ return result.status === 0 && (result.stdout || "").trim() === "true";
175
+ }
176
+ const LINK_TIMEOUT_MS = 120_000;
177
+ function linkEngineOnDisk(packageDir, { ignoreScripts = npmIgnoresScripts } = {}) {
178
+ const script = path.join(packageDir, "postinstall.mjs");
179
+ if (!fs.existsSync(script))
180
+ return { ok: false, detail: "opencode-ai has no postinstall.mjs" };
181
+ if (ignoreScripts())
182
+ return { ok: false, detail: exports.IGNORE_SCRIPTS };
183
+ const result = (0, child_process_1.spawnSync)(process.execPath, [script], {
184
+ cwd: packageDir,
185
+ encoding: "utf8",
186
+ stdio: ["ignore", "ignore", "pipe"],
187
+ timeout: LINK_TIMEOUT_MS,
188
+ windowsHide: true,
189
+ });
190
+ if (result.error || result.signal) {
191
+ return { ok: false, detail: result.error ? result.error.message : `timed out after ${LINK_TIMEOUT_MS / 1000}s` };
192
+ }
193
+ if (result.status !== 0)
194
+ return { ok: false, detail: (result.stderr || "").trim().split("\n").slice(-3).join(" ") || `exit ${result.status}` };
195
+ return isEngineOnDisk(path.join(packageDir, "bin", "opencode.exe"))
196
+ ? { ok: true }
197
+ : { ok: false, detail: "postinstall ran but left no engine" };
198
+ }
117
199
  function installCodieRuntimeOnDisk(runtime, stateDir) {
118
200
  installCodieRuntime({
119
201
  runtime,
120
202
  makeStagingDir: () => {
121
- // Sibling of the final dir so the promote is a same-filesystem rename.
122
203
  fs.mkdirSync(path.dirname((0, codieRuntime_1.codieRuntimeDir)(stateDir)), { recursive: true });
123
204
  return fs.mkdtempSync(path.join(path.dirname((0, codieRuntime_1.codieRuntimeDir)(stateDir)), ".runtime-staging-"));
124
205
  },
125
206
  run: (args) => {
126
- const npm = process.platform === "win32" ? "npm.cmd" : "npm";
127
- const result = (0, child_process_1.spawnSync)(npm, args, {
207
+ const opts = {
128
208
  encoding: "utf8",
129
209
  stdio: ["ignore", "inherit", "pipe"],
130
210
  env: { ...process.env, npm_config_yes: "true" },
131
- });
211
+ };
212
+ const result = process.platform === "win32"
213
+ ? (0, child_process_1.spawnSync)(windowsCommand("npm.cmd", args), { ...opts, shell: true, windowsHide: true })
214
+ : (0, child_process_1.spawnSync)("npm", args, opts);
132
215
  if (result.error)
133
216
  throw result.error;
134
217
  return { status: result.status, stderr: result.stderr || "" };
@@ -140,6 +223,8 @@ function installCodieRuntimeOnDisk(runtime, stateDir) {
140
223
  },
141
224
  remove: (target) => fs.rmSync(target, { recursive: true, force: true }),
142
225
  log: (message) => console.error(message),
226
+ isEngine: isEngineOnDisk,
227
+ linkEngine: linkEngineOnDisk,
143
228
  });
144
229
  }
145
230
  function defaultStagingRoot() {
@@ -3,9 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.refreshCodieSecurityPolicy = refreshCodieSecurityPolicy;
4
4
  const securityFeature_1 = require("./securityFeature");
5
5
  const securityPolicy_1 = require("./securityPolicy");
6
- /** Refresh the endpoint policy before Codie hands the terminal to OpenCode.
7
- * Enforcement remains available offline because hooks keep using the last
8
- * verified company-scoped policy when this best-effort refresh cannot land. */
9
6
  async function refreshCodieSecurityPolicy(config, syncPolicy = securityPolicy_1.syncEndpointSecurityPolicy) {
10
7
  if (!(0, securityFeature_1.isAgentSecurityEnabled)(config))
11
8
  return { status: "disabled" };