opencode-orchestrator 1.2.62 → 1.2.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,197 @@
1
1
  #!/usr/bin/env node
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
+ }) : x)(function(x) {
11
+ if (typeof require !== "undefined") return require.apply(this, arguments);
12
+ throw Error('Dynamic require of "' + x + '" is not supported');
13
+ });
14
+ var __commonJS = (cb, mod) => function __require2() {
15
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
16
+ };
17
+ var __copyProps = (to, from, except, desc) => {
18
+ if (from && typeof from === "object" || typeof from === "function") {
19
+ for (let key of __getOwnPropNames(from))
20
+ if (!__hasOwnProp.call(to, key) && key !== except)
21
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
22
+ }
23
+ return to;
24
+ };
25
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
26
+ // If the importer is in node compatibility mode or this is not an ESM
27
+ // file that has been converted to a CommonJS file using a Babel-
28
+ // compatible transform (i.e. "__esModule" has not been set), then set
29
+ // "default" to the CommonJS "module.exports" for node compatibility.
30
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
31
+ mod
32
+ ));
33
+
34
+ // node_modules/jsonc-parser/lib/umd/main.js
35
+ var require_main = __commonJS({
36
+ "node_modules/jsonc-parser/lib/umd/main.js"(exports, module) {
37
+ (function(factory) {
38
+ if (typeof module === "object" && typeof module.exports === "object") {
39
+ var v = factory(__require, exports);
40
+ if (v !== void 0) module.exports = v;
41
+ } else if (typeof define === "function" && define.amd) {
42
+ define(["require", "exports", "./impl/format", "./impl/edit", "./impl/scanner", "./impl/parser"], factory);
43
+ }
44
+ })(function(require2, exports2) {
45
+ "use strict";
46
+ Object.defineProperty(exports2, "__esModule", { value: true });
47
+ exports2.applyEdits = exports2.modify = exports2.format = exports2.printParseErrorCode = exports2.ParseErrorCode = exports2.stripComments = exports2.visit = exports2.getNodeValue = exports2.getNodePath = exports2.findNodeAtOffset = exports2.findNodeAtLocation = exports2.parseTree = exports2.parse = exports2.getLocation = exports2.SyntaxKind = exports2.ScanError = exports2.createScanner = void 0;
48
+ const formatter = require2("./impl/format");
49
+ const edit = require2("./impl/edit");
50
+ const scanner = require2("./impl/scanner");
51
+ const parser = require2("./impl/parser");
52
+ exports2.createScanner = scanner.createScanner;
53
+ var ScanError;
54
+ (function(ScanError2) {
55
+ ScanError2[ScanError2["None"] = 0] = "None";
56
+ ScanError2[ScanError2["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
57
+ ScanError2[ScanError2["UnexpectedEndOfString"] = 2] = "UnexpectedEndOfString";
58
+ ScanError2[ScanError2["UnexpectedEndOfNumber"] = 3] = "UnexpectedEndOfNumber";
59
+ ScanError2[ScanError2["InvalidUnicode"] = 4] = "InvalidUnicode";
60
+ ScanError2[ScanError2["InvalidEscapeCharacter"] = 5] = "InvalidEscapeCharacter";
61
+ ScanError2[ScanError2["InvalidCharacter"] = 6] = "InvalidCharacter";
62
+ })(ScanError || (exports2.ScanError = ScanError = {}));
63
+ var SyntaxKind;
64
+ (function(SyntaxKind2) {
65
+ SyntaxKind2[SyntaxKind2["OpenBraceToken"] = 1] = "OpenBraceToken";
66
+ SyntaxKind2[SyntaxKind2["CloseBraceToken"] = 2] = "CloseBraceToken";
67
+ SyntaxKind2[SyntaxKind2["OpenBracketToken"] = 3] = "OpenBracketToken";
68
+ SyntaxKind2[SyntaxKind2["CloseBracketToken"] = 4] = "CloseBracketToken";
69
+ SyntaxKind2[SyntaxKind2["CommaToken"] = 5] = "CommaToken";
70
+ SyntaxKind2[SyntaxKind2["ColonToken"] = 6] = "ColonToken";
71
+ SyntaxKind2[SyntaxKind2["NullKeyword"] = 7] = "NullKeyword";
72
+ SyntaxKind2[SyntaxKind2["TrueKeyword"] = 8] = "TrueKeyword";
73
+ SyntaxKind2[SyntaxKind2["FalseKeyword"] = 9] = "FalseKeyword";
74
+ SyntaxKind2[SyntaxKind2["StringLiteral"] = 10] = "StringLiteral";
75
+ SyntaxKind2[SyntaxKind2["NumericLiteral"] = 11] = "NumericLiteral";
76
+ SyntaxKind2[SyntaxKind2["LineCommentTrivia"] = 12] = "LineCommentTrivia";
77
+ SyntaxKind2[SyntaxKind2["BlockCommentTrivia"] = 13] = "BlockCommentTrivia";
78
+ SyntaxKind2[SyntaxKind2["LineBreakTrivia"] = 14] = "LineBreakTrivia";
79
+ SyntaxKind2[SyntaxKind2["Trivia"] = 15] = "Trivia";
80
+ SyntaxKind2[SyntaxKind2["Unknown"] = 16] = "Unknown";
81
+ SyntaxKind2[SyntaxKind2["EOF"] = 17] = "EOF";
82
+ })(SyntaxKind || (exports2.SyntaxKind = SyntaxKind = {}));
83
+ exports2.getLocation = parser.getLocation;
84
+ exports2.parse = parser.parse;
85
+ exports2.parseTree = parser.parseTree;
86
+ exports2.findNodeAtLocation = parser.findNodeAtLocation;
87
+ exports2.findNodeAtOffset = parser.findNodeAtOffset;
88
+ exports2.getNodePath = parser.getNodePath;
89
+ exports2.getNodeValue = parser.getNodeValue;
90
+ exports2.visit = parser.visit;
91
+ exports2.stripComments = parser.stripComments;
92
+ var ParseErrorCode;
93
+ (function(ParseErrorCode2) {
94
+ ParseErrorCode2[ParseErrorCode2["InvalidSymbol"] = 1] = "InvalidSymbol";
95
+ ParseErrorCode2[ParseErrorCode2["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
96
+ ParseErrorCode2[ParseErrorCode2["PropertyNameExpected"] = 3] = "PropertyNameExpected";
97
+ ParseErrorCode2[ParseErrorCode2["ValueExpected"] = 4] = "ValueExpected";
98
+ ParseErrorCode2[ParseErrorCode2["ColonExpected"] = 5] = "ColonExpected";
99
+ ParseErrorCode2[ParseErrorCode2["CommaExpected"] = 6] = "CommaExpected";
100
+ ParseErrorCode2[ParseErrorCode2["CloseBraceExpected"] = 7] = "CloseBraceExpected";
101
+ ParseErrorCode2[ParseErrorCode2["CloseBracketExpected"] = 8] = "CloseBracketExpected";
102
+ ParseErrorCode2[ParseErrorCode2["EndOfFileExpected"] = 9] = "EndOfFileExpected";
103
+ ParseErrorCode2[ParseErrorCode2["InvalidCommentToken"] = 10] = "InvalidCommentToken";
104
+ ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfComment"] = 11] = "UnexpectedEndOfComment";
105
+ ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfString"] = 12] = "UnexpectedEndOfString";
106
+ ParseErrorCode2[ParseErrorCode2["UnexpectedEndOfNumber"] = 13] = "UnexpectedEndOfNumber";
107
+ ParseErrorCode2[ParseErrorCode2["InvalidUnicode"] = 14] = "InvalidUnicode";
108
+ ParseErrorCode2[ParseErrorCode2["InvalidEscapeCharacter"] = 15] = "InvalidEscapeCharacter";
109
+ ParseErrorCode2[ParseErrorCode2["InvalidCharacter"] = 16] = "InvalidCharacter";
110
+ })(ParseErrorCode || (exports2.ParseErrorCode = ParseErrorCode = {}));
111
+ function printParseErrorCode2(code) {
112
+ switch (code) {
113
+ case 1:
114
+ return "InvalidSymbol";
115
+ case 2:
116
+ return "InvalidNumberFormat";
117
+ case 3:
118
+ return "PropertyNameExpected";
119
+ case 4:
120
+ return "ValueExpected";
121
+ case 5:
122
+ return "ColonExpected";
123
+ case 6:
124
+ return "CommaExpected";
125
+ case 7:
126
+ return "CloseBraceExpected";
127
+ case 8:
128
+ return "CloseBracketExpected";
129
+ case 9:
130
+ return "EndOfFileExpected";
131
+ case 10:
132
+ return "InvalidCommentToken";
133
+ case 11:
134
+ return "UnexpectedEndOfComment";
135
+ case 12:
136
+ return "UnexpectedEndOfString";
137
+ case 13:
138
+ return "UnexpectedEndOfNumber";
139
+ case 14:
140
+ return "InvalidUnicode";
141
+ case 15:
142
+ return "InvalidEscapeCharacter";
143
+ case 16:
144
+ return "InvalidCharacter";
145
+ }
146
+ return "<unknown ParseErrorCode>";
147
+ }
148
+ exports2.printParseErrorCode = printParseErrorCode2;
149
+ function format(documentText, range, options) {
150
+ return formatter.format(documentText, range, options);
151
+ }
152
+ exports2.format = format;
153
+ function modify2(text, path, value, options) {
154
+ return edit.setProperty(text, path, value, options);
155
+ }
156
+ exports2.modify = modify2;
157
+ function applyEdits2(text, edits) {
158
+ let sortedEdits = edits.slice(0).sort((a, b) => {
159
+ const diff = a.offset - b.offset;
160
+ if (diff === 0) {
161
+ return a.length - b.length;
162
+ }
163
+ return diff;
164
+ });
165
+ let lastModifiedOffset = text.length;
166
+ for (let i = sortedEdits.length - 1; i >= 0; i--) {
167
+ let e = sortedEdits[i];
168
+ if (e.offset + e.length <= lastModifiedOffset) {
169
+ text = edit.applyEdit(text, e);
170
+ } else {
171
+ throw new Error("Overlapping edit");
172
+ }
173
+ lastModifiedOffset = e.offset;
174
+ }
175
+ return text;
176
+ }
177
+ exports2.applyEdits = applyEdits2;
178
+ });
179
+ }
180
+ });
2
181
 
3
182
  // scripts/preuninstall.ts
183
+ var import_jsonc_parser = __toESM(require_main(), 1);
4
184
  import { existsSync, readFileSync, writeFileSync, appendFileSync, copyFileSync, renameSync, unlinkSync, readdirSync } from "fs";
5
185
  import { homedir, tmpdir } from "os";
6
- import { join } from "path";
186
+ import { dirname, join, basename } from "path";
187
+ var isCI = process.env.CI === "true" || process.env.CONTINUOUS_INTEGRATION === "true";
188
+ var TIMEOUT_MS = 3e4;
189
+ var timeoutId = setTimeout(() => {
190
+ console.log("\u26A0\uFE0F preuninstall timeout - exiting gracefully");
191
+ process.exit(0);
192
+ }, TIMEOUT_MS);
193
+ process.on("SIGINT", () => process.exit(0));
194
+ process.on("SIGTERM", () => process.exit(0));
7
195
  var LOG_FILE = join(tmpdir(), "opencode-orchestrator.log");
8
196
  function log(message, data) {
9
197
  try {
@@ -40,6 +228,41 @@ function formatError(err, context) {
40
228
  return `Failed to ${context}: ${String(err)}`;
41
229
  }
42
230
  var PLUGIN_NAME = "opencode-orchestrator";
231
+ function isOurPluginEntry(p) {
232
+ return p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`);
233
+ }
234
+ function getConfigFileCandidates(configDir) {
235
+ return [join(configDir, "opencode.jsonc"), join(configDir, "opencode.json")];
236
+ }
237
+ function resolveConfigFile(configDir) {
238
+ for (const candidate of getConfigFileCandidates(configDir)) {
239
+ if (existsSync(candidate)) {
240
+ return candidate;
241
+ }
242
+ }
243
+ return getConfigFileCandidates(configDir)[0];
244
+ }
245
+ function parseConfigContent(rawContent) {
246
+ const errors = [];
247
+ const config = (0, import_jsonc_parser.parse)(rawContent, errors, {
248
+ allowTrailingComma: true,
249
+ disallowComments: false
250
+ });
251
+ if (errors.length > 0) {
252
+ const [firstError] = errors;
253
+ const line = rawContent.slice(0, firstError.offset).split("\n").length;
254
+ const column = firstError.offset - rawContent.lastIndexOf("\n", firstError.offset - 1);
255
+ return {
256
+ parseError: `${(0, import_jsonc_parser.printParseErrorCode)(firstError.error)} at line ${line}, column ${column}`
257
+ };
258
+ }
259
+ if (typeof config !== "object" || config === null || Array.isArray(config)) {
260
+ return {
261
+ parseError: "Root config must be a JSON object"
262
+ };
263
+ }
264
+ return { config };
265
+ }
43
266
  function detectWSLWindowsConfigDir() {
44
267
  try {
45
268
  const isWSL = process.env.WSL_DISTRO_NAME || process.env.WSLENV;
@@ -97,7 +320,7 @@ function getConfigPaths() {
97
320
  paths.push(wslWindowsConfig);
98
321
  }
99
322
  }
100
- return paths;
323
+ return [...new Set(paths)];
101
324
  }
102
325
  function validateConfig(config) {
103
326
  try {
@@ -134,10 +357,19 @@ function createBackup(configFile) {
134
357
  return null;
135
358
  }
136
359
  }
137
- function atomicWriteJSON(filePath, data) {
360
+ function atomicWriteJSON(filePath, data, originalContent) {
138
361
  const tempFile = `${filePath}.tmp.${Date.now()}`;
139
362
  try {
140
- writeFileSync(tempFile, JSON.stringify(data, null, 2) + "\n", { mode: 420 });
363
+ let output = JSON.stringify(data, null, 2) + "\n";
364
+ if (filePath.endsWith(".jsonc") && originalContent !== void 0) {
365
+ const source = originalContent.trim() ? originalContent : "{}";
366
+ const edits = (0, import_jsonc_parser.modify)(source, ["plugin"], data.plugin, {
367
+ formattingOptions: { tabSize: 2, insertSpaces: true }
368
+ });
369
+ output = (0, import_jsonc_parser.applyEdits)(source, edits);
370
+ if (!output.endsWith("\n")) output += "\n";
371
+ }
372
+ writeFileSync(tempFile, output, { mode: 420 });
141
373
  renameSync(tempFile, filePath);
142
374
  log("Atomic write successful", { filePath });
143
375
  } catch (error) {
@@ -151,27 +383,29 @@ function atomicWriteJSON(filePath, data) {
151
383
  }
152
384
  }
153
385
  function removeFromConfig(configDir) {
154
- const configFile = join(configDir, "opencode.json");
386
+ const configFile = resolveConfigFile(configDir);
155
387
  let backupFile = null;
388
+ let originalContent;
156
389
  try {
157
390
  if (!existsSync(configFile)) {
158
391
  log("Config file does not exist", { configFile });
159
392
  return { success: false, backupFile: null };
160
393
  }
161
394
  backupFile = createBackup(configFile);
162
- const content = readFileSync(configFile, "utf-8").trim();
163
- if (!content) {
395
+ const content = readFileSync(configFile, "utf-8");
396
+ originalContent = content;
397
+ const trimmedContent = content.trim();
398
+ if (!trimmedContent) {
164
399
  log("Empty config file", { configFile });
165
400
  return { success: false, backupFile };
166
401
  }
167
- let config;
168
- try {
169
- config = JSON.parse(content);
170
- } catch (error) {
171
- log("Failed to parse config, skipping", { error: String(error), configFile });
402
+ const parsed = parseConfigContent(trimmedContent);
403
+ if (parsed.parseError) {
404
+ log("Failed to parse config, skipping", { error: parsed.parseError, configFile });
172
405
  console.log(`\u26A0\uFE0F Corrupted config detected. Backup saved: ${backupFile}`);
173
406
  return { success: false, backupFile };
174
407
  }
408
+ const config = parsed.config ?? {};
175
409
  if (!validateConfig(config)) {
176
410
  log("Invalid config structure, skipping", { config, configFile });
177
411
  return { success: false, backupFile };
@@ -184,7 +418,7 @@ function removeFromConfig(configDir) {
184
418
  const originalPlugins = [...config.plugin];
185
419
  config.plugin = config.plugin.filter((p) => {
186
420
  if (typeof p !== "string") return true;
187
- return p !== PLUGIN_NAME && !p.includes(PLUGIN_NAME);
421
+ return !isOurPluginEntry(p);
188
422
  });
189
423
  if (config.plugin.length === originalLength) {
190
424
  log("Plugin not found in config", { configFile });
@@ -198,12 +432,16 @@ function removeFromConfig(configDir) {
198
432
  newPlugins: config.plugin,
199
433
  configFile
200
434
  });
201
- atomicWriteJSON(configFile, config);
435
+ atomicWriteJSON(configFile, config, originalContent);
202
436
  try {
203
437
  const verifyContent = readFileSync(configFile, "utf-8");
204
- const verifyConfig = JSON.parse(verifyContent);
438
+ const verifyParsed = parseConfigContent(verifyContent);
439
+ if (verifyParsed.parseError || !verifyParsed.config) {
440
+ throw new Error(`Verification parse failed: ${verifyParsed.parseError ?? "unknown parse error"}`);
441
+ }
442
+ const verifyConfig = verifyParsed.config;
205
443
  const stillHasPlugin = verifyConfig.plugin?.some(
206
- (p) => typeof p === "string" && (p === PLUGIN_NAME || p.includes(PLUGIN_NAME))
444
+ (p) => typeof p === "string" && isOurPluginEntry(p)
207
445
  );
208
446
  if (stillHasPlugin) {
209
447
  throw new Error("Verification failed: plugin still present after removal");
@@ -234,9 +472,10 @@ function removeFromConfig(configDir) {
234
472
  }
235
473
  function cleanupOldBackups(configFile) {
236
474
  try {
237
- const configDir = join(configFile, "..");
475
+ const configDir = dirname(configFile);
476
+ const configBase = basename(configFile);
238
477
  const files = readdirSync(configDir);
239
- const backupFiles = files.filter((f) => f.startsWith("opencode.json.backup.")).sort().reverse();
478
+ const backupFiles = files.filter((f) => f.startsWith(`${configBase}.backup.`)).sort().reverse();
240
479
  for (let i = 5; i < backupFiles.length; i++) {
241
480
  const backupPath = join(configDir, backupFiles[i]);
242
481
  try {
@@ -250,13 +489,20 @@ function cleanupOldBackups(configFile) {
250
489
  }
251
490
  try {
252
491
  console.log("\u{1F9F9} OpenCode Orchestrator - Uninstalling...");
492
+ if (isCI) log("Running in CI mode");
253
493
  log("Uninstallation started", { platform: process.platform, node: process.version });
494
+ if (isCI) {
495
+ console.log("\u2139\uFE0F CI environment detected. Skipping automatic plugin cleanup.");
496
+ log("Skipping automatic plugin cleanup in CI");
497
+ clearTimeout(timeoutId);
498
+ process.exit(0);
499
+ }
254
500
  const configPaths = getConfigPaths();
255
501
  log("Config paths to check", configPaths);
256
502
  let removed = false;
257
503
  let backupCreated = null;
258
504
  for (const configDir of configPaths) {
259
- const configFile = join(configDir, "opencode.json");
505
+ const configFile = resolveConfigFile(configDir);
260
506
  const result = removeFromConfig(configDir);
261
507
  if (result.success) {
262
508
  console.log(`\u2705 Plugin removed: ${configFile}`);
@@ -281,4 +527,6 @@ try {
281
527
  console.error("\u274C " + formatError(error, "clean up config"));
282
528
  console.log(` Check logs: ${LOG_FILE}`);
283
529
  process.exit(0);
530
+ } finally {
531
+ clearTimeout(timeoutId);
284
532
  }
@@ -17,4 +17,3 @@ export * from "./verification/index.js";
17
17
  export * from "./lifecycle/index.js";
18
18
  export * from "./errors/index.js";
19
19
  export * from "./prompt/index.js";
20
- export { TASK_STATUS, TODO_STATUS } from "../core/agents/consts/task-status.const.js";
@@ -3,5 +3,6 @@
3
3
  */
4
4
  export { LOOP } from "./loop.js";
5
5
  export { MISSION_CONTROL, MISSION } from "./mission-control.js";
6
+ export { TASK_STATUS } from "./task-status.js";
6
7
  export { TODO_STATUS } from "./todo-status.js";
7
8
  export { LOOP_LABELS } from "./labels.js";
@@ -0,0 +1,9 @@
1
+ export declare const TASK_STATUS: {
2
+ readonly PENDING: "pending";
3
+ readonly RUNNING: "running";
4
+ readonly COMPLETED: "completed";
5
+ readonly FAILED: "failed";
6
+ readonly ERROR: "error";
7
+ readonly TIMEOUT: "timeout";
8
+ readonly CANCELLED: "cancelled";
9
+ };
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "opencode-orchestrator",
3
3
  "displayName": "OpenCode Orchestrator",
4
4
  "description": "Distributed Cognitive Architecture for OpenCode. Turns simple prompts into specialized multi-agent workflows (Planner, Coder, Reviewer).",
5
- "version": "1.2.62",
5
+ "version": "1.2.66",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -34,6 +34,7 @@
34
34
  },
35
35
  "files": [
36
36
  "dist",
37
+ "scripts/run-install-hook.mjs",
37
38
  "bin",
38
39
  "README.md",
39
40
  "LICENSE"
@@ -51,13 +52,16 @@
51
52
  "test:unit": "vitest run tests/unit --reporter=verbose",
52
53
  "test:e2e": "vitest run tests/e2e --reporter=verbose",
53
54
  "test:all": "npm run build && vitest run --reporter=verbose && echo '=== ALL TESTS PASSED ==='",
54
- "postinstall": "node dist/scripts/postinstall.js",
55
- "preuninstall": "node dist/scripts/preuninstall.js",
55
+ "postinstall": "node scripts/run-install-hook.mjs postinstall",
56
+ "preuninstall": "node scripts/run-install-hook.mjs preuninstall",
56
57
  "prepublishOnly": "npm run build",
57
58
  "publish:token": "npm publish --access public",
58
- "release:patch": "npm run build && npm run docker:rust-dist && npm version patch && git push --follow-tags && npm run publish:token",
59
- "release:minor": "npm run build && npm run docker:rust-dist && npm version minor && git push --follow-tags && npm run publish:token",
60
- "release:major": "npm run build && npm run docker:rust-dist && npm version major && git push --follow-tags && npm run publish:token",
59
+ "releae:patch": "npm run release:patch",
60
+ "sync:readme-version": "node scripts/sync-readme-version.mjs",
61
+ "version": "npm run sync:readme-version",
62
+ "release:patch": "npm version patch && npm run build && npm run docker:rust-dist && npm run publish:token",
63
+ "release:minor": "npm version minor && npm run build && npm run docker:rust-dist && npm run publish:token",
64
+ "release:major": "npm version major && npm run build && npm run docker:rust-dist && npm run publish:token",
61
65
  "reset:local": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && echo '=== Reset (Dev) complete. Run: opencode ==='",
62
66
  "reset:prod": "brew uninstall opencode 2>/dev/null; rm -rf ~/.config/opencode ~/.opencode ~/.local/share/opencode ~/.cache/opencode/node_modules/opencode-orchestrator && echo '=== Clean done ===' && brew install opencode && echo '{\"plugin\": [\"opencode-orchestrator\"], \"$schema\": \"https://opencode.ai/config.json\"}' > ~/.config/opencode/opencode.json && npm uninstall -g opencode-orchestrator && echo '=== Reset (Prod) complete. Run: opencode ==='",
63
67
  "ginstall": "npm install -g opencode-orchestrator",
@@ -69,6 +73,7 @@
69
73
  "dependencies": {
70
74
  "@opencode-ai/plugin": "^1.1.35",
71
75
  "@opencode-ai/sdk": "^1.1.35",
76
+ "jsonc-parser": "^3.3.1",
72
77
  "zod": "^4.3.6"
73
78
  },
74
79
  "devDependencies": {
@@ -0,0 +1,66 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
5
+ import path from "node:path";
6
+ import { fileURLToPath } from "node:url";
7
+
8
+ const VALID_HOOKS = new Set(["postinstall", "preuninstall"]);
9
+
10
+ function resolveRepoRoot() {
11
+ const scriptPath = fileURLToPath(import.meta.url);
12
+ return path.resolve(path.dirname(scriptPath), "..");
13
+ }
14
+
15
+ function resolveHookCommand(repoRoot, hookName) {
16
+ const distEntry = path.join(repoRoot, "dist", "scripts", `${hookName}.js`);
17
+ if (existsSync(distEntry)) {
18
+ return {
19
+ command: process.execPath,
20
+ args: [distEntry],
21
+ };
22
+ }
23
+
24
+ const sourceEntry = path.join(repoRoot, "scripts", `${hookName}.ts`);
25
+ if (existsSync(sourceEntry)) {
26
+ return {
27
+ command: process.execPath,
28
+ args: ["--experimental-strip-types", sourceEntry],
29
+ };
30
+ }
31
+
32
+ return null;
33
+ }
34
+
35
+ function main() {
36
+ const hookName = process.argv[2];
37
+ if (!VALID_HOOKS.has(hookName)) {
38
+ console.error(`Unknown install hook: ${hookName ?? "(missing)"}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ const repoRoot = resolveRepoRoot();
43
+ const hookCommand = resolveHookCommand(repoRoot, hookName);
44
+
45
+ if (!hookCommand) {
46
+ console.log(`⚠️ ${hookName} entrypoint not found. Skipping gracefully.`);
47
+ process.exit(0);
48
+ }
49
+
50
+ const result = spawnSync(hookCommand.command, hookCommand.args, {
51
+ cwd: repoRoot,
52
+ stdio: "inherit",
53
+ env: process.env,
54
+ });
55
+
56
+ if (typeof result.status === "number") {
57
+ process.exit(result.status);
58
+ }
59
+
60
+ if (result.error) {
61
+ console.error(`Failed to run ${hookName}: ${result.error.message}`);
62
+ }
63
+ process.exit(1);
64
+ }
65
+
66
+ main();