opencode-orchestrator 0.1.61 → 0.1.62

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.
package/dist/index.d.ts CHANGED
@@ -1,13 +1,13 @@
1
1
  /**
2
2
  * OpenCode Orchestrator Plugin
3
3
  *
4
- * 5-Agent Structured Architecture for OpenCode
4
+ * 5-Agent Structured Architecture
5
5
  *
6
- * Optimized for weak models (GLM-4.7, Gemma, Phi) through:
6
+ * Optimized for weak models through:
7
7
  * - XML-structured prompts with clear boundaries
8
8
  * - Explicit reasoning patterns (THINK → ACT → OBSERVE → ADJUST)
9
9
  * - Evidence-based completion requirements
10
- * - Parallel execution by default
10
+ * - Autonomous execution loop
11
11
  *
12
12
  * Agents: Commander, Architect, Builder, Inspector, Memory
13
13
  */
@@ -65,17 +65,23 @@ declare const OrchestratorPlugin: (input: PluginInput) => Promise<{
65
65
  };
66
66
  };
67
67
  config: (config: Record<string, unknown>) => Promise<void>;
68
- "chat.message": (input: any, output: any) => Promise<void>;
69
- "tool.execute.after": (input: {
68
+ "chat.message": (msgInput: any, msgOutput: any) => Promise<void>;
69
+ "tool.execute.after": (toolInput: {
70
70
  tool: string;
71
71
  sessionID: string;
72
72
  callID: string;
73
73
  arguments?: any;
74
- }, output: {
74
+ }, toolOutput: {
75
75
  title: string;
76
76
  output: string;
77
77
  metadata: any;
78
78
  }) => Promise<void>;
79
- "assistant.done": (input: any, output: any) => Promise<void>;
79
+ "assistant.done": (assistantInput: any, assistantOutput: any) => Promise<void>;
80
+ handler: ({ event }: {
81
+ event: {
82
+ type: string;
83
+ properties?: unknown;
84
+ };
85
+ }) => Promise<void>;
80
86
  }>;
81
87
  export default OrchestratorPlugin;
package/dist/index.js CHANGED
@@ -727,16 +727,22 @@ var state = {
727
727
 
728
728
  // src/tools/callAgent.ts
729
729
  import { tool } from "@opencode-ai/plugin";
730
+ var AGENT_EMOJI = {
731
+ [AGENT_NAMES.ARCHITECT]: "\u{1F3D7}\uFE0F",
732
+ [AGENT_NAMES.BUILDER]: "\u{1F528}",
733
+ [AGENT_NAMES.INSPECTOR]: "\u{1F50D}",
734
+ [AGENT_NAMES.MEMORY]: "\u{1F4BE}"
735
+ };
730
736
  var callAgentTool = tool({
731
737
  description: `Call a specialized agent for parallel execution.
732
738
 
733
739
  <agents>
734
740
  | Agent | Role | When to Use |
735
741
  |-------|------|-------------|
736
- | ${AGENT_NAMES.ARCHITECT} | Planner | Complex task \u2192 DAG, OR 3+ failures \u2192 strategy |
737
- | ${AGENT_NAMES.BUILDER} | Developer | Any code implementation (logic + UI) |
738
- | ${AGENT_NAMES.INSPECTOR} | Quality | Before completion, OR on errors (auto-fixes) |
739
- | ${AGENT_NAMES.MEMORY} | Context | After each task, OR at session start |
742
+ | ${AGENT_NAMES.ARCHITECT} \u{1F3D7}\uFE0F | Planner | Complex task \u2192 DAG, OR 3+ failures \u2192 strategy |
743
+ | ${AGENT_NAMES.BUILDER} \u{1F528} | Developer | Any code implementation (logic + UI) |
744
+ | ${AGENT_NAMES.INSPECTOR} \u{1F50D} | Quality | Before completion, OR on errors (auto-fixes) |
745
+ | ${AGENT_NAMES.MEMORY} \u{1F4BE} | Context | After each task, OR at session start |
740
746
  </agents>
741
747
 
742
748
  <execution_rules>
@@ -758,11 +764,12 @@ var callAgentTool = tool({
758
764
  async execute(args) {
759
765
  const agentDef = AGENTS[args.agent];
760
766
  if (!agentDef) {
761
- return `Error: Unknown agent: ${args.agent}`;
767
+ return `\u274C Error: Unknown agent: ${args.agent}`;
762
768
  }
769
+ const emoji = AGENT_EMOJI[args.agent] || "\u{1F916}";
763
770
  const prompt = `
764
771
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
765
- ${agentDef.id.toUpperCase()} :: ${agentDef.description}
772
+ ${emoji} ${agentDef.id.toUpperCase()} :: ${agentDef.description}
766
773
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
767
774
 
768
775
  <system>
@@ -832,14 +839,29 @@ New Approach: [What to try differently]
832
839
 
833
840
  <execution_flow>
834
841
  Step 1: Call Memory to load any existing context
842
+ - IF Memory returns empty/nothing: That's OK, proceed to Step 2
843
+ - Memory being empty just means fresh start
835
844
  Step 2: If complex task, call Architect to create parallel DAG
836
845
  Step 3: Execute tasks with same parallel_group CONCURRENTLY
837
846
  Step 4: After EACH task, call Inspector to verify with evidence
838
847
  Step 5: Update Memory with progress after each verified task
839
848
  Step 6: REPEAT steps 3-5 until ALL tasks are verified complete
840
- Step 7: Report "MISSION COMPLETE" with summary of evidence
849
+ Step 7: Report "\u2705 MISSION COMPLETE" with summary of evidence
841
850
  </execution_flow>
842
851
 
852
+ <empty_response_handling>
853
+ If ANY agent returns empty, useless, or says "nothing found":
854
+ - DO NOT STOP
855
+ - DO NOT ask user what to do
856
+ - TRY A DIFFERENT APPROACH:
857
+ 1. If Memory empty \u2192 Proceed with Architect
858
+ 2. If Architect failed \u2192 Try simpler breakdown
859
+ 3. If Builder failed \u2192 Call Inspector to diagnose
860
+ 4. If Inspector failed \u2192 Try again with more context
861
+
862
+ NEVER stop because an agent returned nothing. ALWAYS try another way.
863
+ </empty_response_handling>
864
+
843
865
  <agents>
844
866
  You have 4 specialized agents. Call them using the delegation format below.
845
867
 
@@ -1110,17 +1132,41 @@ function detectSlashCommand(text) {
1110
1132
  }
1111
1133
 
1112
1134
  // src/index.ts
1135
+ var DEFAULT_MAX_STEPS = 50;
1136
+ var TASK_COMMAND_MAX_STEPS = 200;
1137
+ var AGENT_EMOJI2 = {
1138
+ "architect": "\u{1F3D7}\uFE0F",
1139
+ "builder": "\u{1F528}",
1140
+ "inspector": "\u{1F50D}",
1141
+ "memory": "\u{1F4BE}",
1142
+ "commander": "\u{1F3AF}"
1143
+ };
1144
+ var CONTINUE_INSTRUCTION = `[AUTO-CONTINUE]
1145
+
1146
+ Mission not complete. Keep executing.
1147
+
1148
+ <rules>
1149
+ 1. DO NOT stop - mission is incomplete
1150
+ 2. DO NOT wait for user input
1151
+ 3. If previous action failed, try different approach
1152
+ 4. If agent returned nothing, proceed to next step
1153
+ </rules>
1154
+
1155
+ <next_step>
1156
+ What is the current state?
1157
+ What is the next action?
1158
+ Execute it NOW.
1159
+ </next_step>`;
1113
1160
  var OrchestratorPlugin = async (input) => {
1114
- const { directory } = input;
1161
+ const { directory, client } = input;
1162
+ const sessions = /* @__PURE__ */ new Map();
1115
1163
  return {
1116
- // Register tools
1117
1164
  tool: {
1118
1165
  call_agent: callAgentTool,
1119
1166
  slashcommand: createSlashcommandTool(),
1120
1167
  grep_search: grepSearchTool(directory),
1121
1168
  glob_search: globSearchTool(directory)
1122
1169
  },
1123
- // Register commands and agents for OpenCode UI
1124
1170
  config: async (config) => {
1125
1171
  const existingCommands = config.command ?? {};
1126
1172
  const existingAgents = config.agent ?? {};
@@ -1135,36 +1181,35 @@ var OrchestratorPlugin = async (input) => {
1135
1181
  const orchestratorAgents = {
1136
1182
  Commander: {
1137
1183
  name: "Commander",
1138
- description: "5-agent orchestrator - runs until mission complete",
1184
+ description: "Autonomous orchestrator - executes until mission complete",
1139
1185
  systemPrompt: AGENTS.commander.systemPrompt
1140
1186
  }
1141
1187
  };
1142
- config.command = {
1143
- ...orchestratorCommands,
1144
- ...existingCommands
1145
- };
1146
- config.agent = {
1147
- ...orchestratorAgents,
1148
- ...existingAgents
1149
- };
1188
+ config.command = { ...orchestratorCommands, ...existingCommands };
1189
+ config.agent = { ...orchestratorAgents, ...existingAgents };
1150
1190
  },
1151
- // Handle incoming messages - auto-activate mission mode
1152
- "chat.message": async (input2, output) => {
1153
- const parts = output.parts;
1191
+ "chat.message": async (msgInput, msgOutput) => {
1192
+ const parts = msgOutput.parts;
1154
1193
  const textPartIndex = parts.findIndex((p) => p.type === "text" && p.text);
1155
1194
  if (textPartIndex === -1) return;
1156
1195
  const originalText = parts[textPartIndex].text || "";
1157
1196
  const parsed = detectSlashCommand(originalText);
1158
- const agentName = input2.agent?.toLowerCase() || "";
1159
- if (agentName === "commander" && !state.missionActive) {
1160
- const sessionID = input2.sessionID;
1197
+ const sessionID = msgInput.sessionID;
1198
+ const agentName = (msgInput.agent || "").toLowerCase();
1199
+ if (agentName === "commander" && !sessions.has(sessionID)) {
1200
+ sessions.set(sessionID, {
1201
+ active: true,
1202
+ step: 0,
1203
+ maxSteps: DEFAULT_MAX_STEPS,
1204
+ timestamp: Date.now()
1205
+ });
1206
+ state.missionActive = true;
1161
1207
  state.sessions.set(sessionID, {
1162
1208
  enabled: true,
1163
1209
  iterations: 0,
1164
1210
  taskRetries: /* @__PURE__ */ new Map(),
1165
1211
  currentTask: ""
1166
1212
  });
1167
- state.missionActive = true;
1168
1213
  if (!parsed) {
1169
1214
  const userMessage = originalText.trim();
1170
1215
  if (userMessage) {
@@ -1175,129 +1220,178 @@ var OrchestratorPlugin = async (input) => {
1175
1220
  }
1176
1221
  }
1177
1222
  }
1178
- if (parsed) {
1223
+ if (parsed?.command === "task") {
1224
+ sessions.set(sessionID, {
1225
+ active: true,
1226
+ step: 0,
1227
+ maxSteps: TASK_COMMAND_MAX_STEPS,
1228
+ timestamp: Date.now()
1229
+ });
1230
+ state.missionActive = true;
1231
+ state.sessions.set(sessionID, {
1232
+ enabled: true,
1233
+ iterations: 0,
1234
+ taskRetries: /* @__PURE__ */ new Map(),
1235
+ currentTask: ""
1236
+ });
1237
+ parts[textPartIndex].text = COMMANDS["task"].template.replace(
1238
+ /\$ARGUMENTS/g,
1239
+ parsed.args || "continue previous work"
1240
+ );
1241
+ } else if (parsed) {
1179
1242
  const command = COMMANDS[parsed.command];
1180
1243
  if (command) {
1181
1244
  parts[textPartIndex].text = command.template.replace(
1182
1245
  /\$ARGUMENTS/g,
1183
- parsed.args || "continue from where we left off"
1246
+ parsed.args || "continue"
1184
1247
  );
1185
- if (parsed.command === "task") {
1186
- const sessionID = input2.sessionID;
1187
- state.sessions.set(sessionID, {
1188
- enabled: true,
1189
- iterations: 0,
1190
- taskRetries: /* @__PURE__ */ new Map(),
1191
- currentTask: ""
1192
- });
1193
- state.missionActive = true;
1194
- }
1195
1248
  }
1196
1249
  }
1197
1250
  },
1198
- // Track tool execution and update task graph
1199
- "tool.execute.after": async (input2, output) => {
1200
- if (!state.missionActive) return;
1201
- const session = state.sessions.get(input2.sessionID);
1202
- if (!session?.enabled) return;
1203
- session.iterations++;
1204
- if (input2.tool === "call_agent" && input2.arguments?.task) {
1205
- const taskIdMatch = input2.arguments.task.match(/\[(TASK-\d+)\]/i);
1251
+ "tool.execute.after": async (toolInput, toolOutput) => {
1252
+ const session = sessions.get(toolInput.sessionID);
1253
+ if (!session?.active) return;
1254
+ session.step++;
1255
+ session.timestamp = Date.now();
1256
+ const stateSession = state.sessions.get(toolInput.sessionID);
1257
+ if (toolInput.tool === "call_agent" && toolInput.arguments?.task && stateSession) {
1258
+ const taskIdMatch = toolInput.arguments.task.match(/\[(TASK-\d+)\]/i);
1206
1259
  if (taskIdMatch) {
1207
- session.currentTask = taskIdMatch[1].toUpperCase();
1208
- session.graph?.updateTask(session.currentTask, { status: "running" });
1260
+ stateSession.currentTask = taskIdMatch[1].toUpperCase();
1261
+ stateSession.graph?.updateTask(stateSession.currentTask, { status: "running" });
1209
1262
  }
1263
+ const agentName = toolInput.arguments.agent;
1264
+ const emoji = AGENT_EMOJI2[agentName] || "\u{1F916}";
1265
+ toolOutput.output = `${emoji} [${agentName.toUpperCase()}] Working...
1266
+
1267
+ ` + toolOutput.output;
1210
1268
  }
1211
- if (session.iterations >= state.maxIterations) {
1269
+ if (session.step >= session.maxSteps) {
1270
+ session.active = false;
1212
1271
  state.missionActive = false;
1213
- session.enabled = false;
1214
1272
  return;
1215
1273
  }
1216
- if (output.output.includes("[") && output.output.includes("]") && output.output.includes("{") && input2.tool === "call_agent") {
1217
- const jsonMatch = output.output.match(/```json\n([\s\S]*?)\n```/) || output.output.match(/\[\s+\{[\s\S]*?\}\s+\]/);
1274
+ if (toolOutput.output.includes("[") && toolOutput.output.includes("{") && toolInput.tool === "call_agent" && stateSession) {
1275
+ const jsonMatch = toolOutput.output.match(/```json\n([\s\S]*?)\n```/) || toolOutput.output.match(/\[\s*\{[\s\S]*?\}\s*\]/);
1218
1276
  if (jsonMatch) {
1219
1277
  try {
1220
1278
  const tasks = JSON.parse(jsonMatch[1] || jsonMatch[0]);
1221
1279
  if (Array.isArray(tasks) && tasks.length > 0) {
1222
- session.graph = new TaskGraph(tasks);
1223
- output.output += `
1280
+ stateSession.graph = new TaskGraph(tasks);
1281
+ toolOutput.output += `
1224
1282
 
1225
1283
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1226
- \u2705 MISSION INITIALIZED
1227
- ${session.graph.getTaskSummary()}`;
1284
+ \u2705 INITIALIZED
1285
+ ${stateSession.graph.getTaskSummary()}`;
1228
1286
  }
1229
1287
  } catch {
1230
1288
  }
1231
1289
  }
1232
1290
  }
1233
- if (session.graph) {
1234
- if (output.output.includes("\u2705 PASS") || output.output.includes("AUDIT RESULT: PASS")) {
1235
- const taskId = session.currentTask;
1291
+ if (stateSession?.graph) {
1292
+ const taskId = stateSession.currentTask;
1293
+ if (toolOutput.output.includes("\u2705 PASS") || toolOutput.output.includes("AUDIT RESULT: PASS")) {
1236
1294
  if (taskId) {
1237
- session.graph.updateTask(taskId, { status: "completed" });
1238
- session.taskRetries.clear();
1239
- output.output += `
1295
+ stateSession.graph.updateTask(taskId, { status: "completed" });
1296
+ stateSession.taskRetries.clear();
1297
+ toolOutput.output += `
1240
1298
 
1241
1299
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1242
- \u2705 TASK ${taskId} VERIFIED
1243
- ${session.graph.getTaskSummary()}`;
1300
+ \u2705 ${taskId} VERIFIED
1301
+ ${stateSession.graph.getTaskSummary()}`;
1244
1302
  }
1245
- } else if (output.output.includes("\u274C FAIL") || output.output.includes("AUDIT RESULT: FAIL")) {
1246
- const taskId = session.currentTask;
1303
+ } else if (toolOutput.output.includes("\u274C FAIL") || toolOutput.output.includes("AUDIT RESULT: FAIL")) {
1247
1304
  if (taskId) {
1248
- const errorId = `error-${taskId}`;
1249
- const retries = (session.taskRetries.get(errorId) || 0) + 1;
1250
- session.taskRetries.set(errorId, retries);
1305
+ const retries = (stateSession.taskRetries.get(taskId) || 0) + 1;
1306
+ stateSession.taskRetries.set(taskId, retries);
1251
1307
  if (retries >= state.maxRetries) {
1252
- session.graph.updateTask(taskId, { status: "failed" });
1253
- output.output += `
1308
+ stateSession.graph.updateTask(taskId, { status: "failed" });
1309
+ toolOutput.output += `
1254
1310
 
1255
1311
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1256
- \u26A0\uFE0F TASK ${taskId} FAILED (${retries}x)
1257
- Call Architect for new strategy.`;
1312
+ \u26A0\uFE0F ${taskId} FAILED (${retries}x)`;
1258
1313
  } else {
1259
- output.output += `
1314
+ toolOutput.output += `
1260
1315
 
1261
1316
  \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1262
- \u{1F504} RETRY ${retries}/${state.maxRetries} for ${taskId}`;
1317
+ \u{1F504} RETRY ${retries}/${state.maxRetries}`;
1263
1318
  }
1264
1319
  }
1265
1320
  }
1321
+ const readyTasks = stateSession.graph.getReadyTasks();
1322
+ if (readyTasks.length > 0) {
1323
+ toolOutput.output += `
1324
+ \u{1F449} NEXT: ${readyTasks.map((t) => `[${t.id}]`).join(", ")}`;
1325
+ }
1266
1326
  }
1267
- if (session.graph) {
1268
- const readyTasks = session.graph.getReadyTasks();
1269
- const guidance = readyTasks.length > 0 ? `
1270
- \u{1F449} READY: ${readyTasks.map((t) => `[${t.id}]`).join(", ")}` : `
1271
- \u26A0\uFE0F No ready tasks. Check dependencies.`;
1272
- output.output += `
1273
-
1274
- \u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501
1275
- ${session.graph.getTaskSummary()}${guidance}`;
1276
- }
1277
- output.output += `
1327
+ toolOutput.output += `
1278
1328
 
1279
- [Step ${session.iterations}/${state.maxIterations}]`;
1329
+ [${session.step}/${session.maxSteps}]`;
1280
1330
  },
1281
- // Relentless Loop: Auto-continue until mission complete
1282
- "assistant.done": async (input2, output) => {
1283
- if (!state.missionActive) return;
1284
- const session = state.sessions.get(input2.sessionID);
1285
- if (!session?.enabled) return;
1286
- const text = output.text || "";
1287
- const isComplete = text.includes("\u2705 MISSION COMPLETE") || text.includes("MISSION COMPLETE") || session.graph && session.graph.isCompleted?.();
1288
- if (isComplete) {
1289
- session.enabled = false;
1331
+ "assistant.done": async (assistantInput, assistantOutput) => {
1332
+ const sessionID = assistantInput.sessionID;
1333
+ const session = sessions.get(sessionID);
1334
+ if (!session?.active) return;
1335
+ const parts = assistantOutput.parts;
1336
+ const textContent = parts?.filter((p) => p.type === "text" || p.type === "reasoning").map((p) => p.text || "").join("\n") || "";
1337
+ if (textContent.includes("\u2705 MISSION COMPLETE") || textContent.includes("MISSION COMPLETE")) {
1338
+ session.active = false;
1290
1339
  state.missionActive = false;
1291
- state.sessions.delete(input2.sessionID);
1340
+ sessions.delete(sessionID);
1341
+ state.sessions.delete(sessionID);
1292
1342
  return;
1293
1343
  }
1294
- if (session.iterations >= state.maxIterations) {
1295
- session.enabled = false;
1344
+ if (textContent.includes("/stop") || textContent.includes("/cancel")) {
1345
+ session.active = false;
1296
1346
  state.missionActive = false;
1347
+ sessions.delete(sessionID);
1348
+ state.sessions.delete(sessionID);
1297
1349
  return;
1298
1350
  }
1299
- output.continue = true;
1300
- output.continueMessage = "continue";
1351
+ session.step++;
1352
+ session.timestamp = Date.now();
1353
+ if (session.step >= session.maxSteps) {
1354
+ session.active = false;
1355
+ state.missionActive = false;
1356
+ return;
1357
+ }
1358
+ try {
1359
+ if (client?.session?.prompt) {
1360
+ await client.session.prompt({
1361
+ path: { id: sessionID },
1362
+ body: {
1363
+ parts: [{
1364
+ type: "text",
1365
+ text: CONTINUE_INSTRUCTION + `
1366
+
1367
+ [Step ${session.step}/${session.maxSteps}]`
1368
+ }]
1369
+ }
1370
+ });
1371
+ }
1372
+ } catch {
1373
+ try {
1374
+ await new Promise((r) => setTimeout(r, 500));
1375
+ if (client?.session?.prompt) {
1376
+ await client.session.prompt({
1377
+ path: { id: sessionID },
1378
+ body: { parts: [{ type: "text", text: "continue" }] }
1379
+ });
1380
+ }
1381
+ } catch {
1382
+ session.active = false;
1383
+ state.missionActive = false;
1384
+ }
1385
+ }
1386
+ },
1387
+ handler: async ({ event }) => {
1388
+ if (event.type === "session.deleted") {
1389
+ const props = event.properties;
1390
+ if (props?.info?.id) {
1391
+ sessions.delete(props.info.id);
1392
+ state.sessions.delete(props.info.id);
1393
+ }
1394
+ }
1301
1395
  }
1302
1396
  };
1303
1397
  };
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": "0.1.61",
5
+ "version": "0.1.62",
6
6
  "author": "agnusdei1207",
7
7
  "license": "MIT",
8
8
  "repository": {
@@ -55,6 +55,7 @@
55
55
  "postinstall": "node dist/scripts/postinstall.js 2>/dev/null || true",
56
56
  "preuninstall": "node dist/scripts/preuninstall.js 2>/dev/null || true",
57
57
  "prepublishOnly": "npm run build:js",
58
+ "dev:install": "npm run build:js && npm install -g .",
58
59
  "util:stars": "gh api repos/agnusdei1207/opencode-orchestrator/stargazers --jq '.[].login'"
59
60
  },
60
61
  "dependencies": {