cortex-agents 2.1.0 → 2.3.0

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 (41) hide show
  1. package/.opencode/agents/build.md +179 -21
  2. package/.opencode/agents/debug.md +97 -11
  3. package/.opencode/agents/devops.md +75 -7
  4. package/.opencode/agents/fullstack.md +89 -1
  5. package/.opencode/agents/plan.md +83 -6
  6. package/.opencode/agents/security.md +60 -1
  7. package/.opencode/agents/testing.md +45 -1
  8. package/README.md +292 -356
  9. package/dist/cli.js +230 -65
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +10 -5
  12. package/dist/tools/branch.d.ts +7 -1
  13. package/dist/tools/branch.d.ts.map +1 -1
  14. package/dist/tools/branch.js +88 -53
  15. package/dist/tools/cortex.d.ts +19 -0
  16. package/dist/tools/cortex.d.ts.map +1 -1
  17. package/dist/tools/cortex.js +110 -1
  18. package/dist/tools/session.d.ts.map +1 -1
  19. package/dist/tools/session.js +3 -1
  20. package/dist/tools/task.d.ts +20 -0
  21. package/dist/tools/task.d.ts.map +1 -0
  22. package/dist/tools/task.js +310 -0
  23. package/dist/tools/worktree.d.ts +42 -2
  24. package/dist/tools/worktree.d.ts.map +1 -1
  25. package/dist/tools/worktree.js +573 -98
  26. package/dist/utils/plan-extract.d.ts +37 -0
  27. package/dist/utils/plan-extract.d.ts.map +1 -0
  28. package/dist/utils/plan-extract.js +137 -0
  29. package/dist/utils/propagate.d.ts +22 -0
  30. package/dist/utils/propagate.d.ts.map +1 -0
  31. package/dist/utils/propagate.js +64 -0
  32. package/dist/utils/shell.d.ts +53 -0
  33. package/dist/utils/shell.d.ts.map +1 -0
  34. package/dist/utils/shell.js +118 -0
  35. package/dist/utils/terminal.d.ts +66 -0
  36. package/dist/utils/terminal.d.ts.map +1 -0
  37. package/dist/utils/terminal.js +627 -0
  38. package/dist/utils/worktree-detect.d.ts +20 -0
  39. package/dist/utils/worktree-detect.d.ts.map +1 -0
  40. package/dist/utils/worktree-detect.js +43 -0
  41. package/package.json +13 -9
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import * as path from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import prompts from "prompts";
6
6
  import { PRIMARY_AGENTS, SUBAGENTS, ALL_AGENTS, getPrimaryChoices, getSubagentChoices, } from "./registry.js";
7
- const VERSION = "2.1.0";
7
+ const VERSION = "2.3.0";
8
8
  const PLUGIN_NAME = "cortex-agents";
9
9
  const __filename = fileURLToPath(import.meta.url);
10
10
  const __dirname = path.dirname(__filename);
@@ -38,6 +38,66 @@ function writeConfig(configPath, config) {
38
38
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
39
39
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
40
40
  }
41
+ // ─── Per-Project Model Config (.opencode/models.json) ───────────────────────
42
+ const PROJECT_MODELS_FILE = "models.json";
43
+ function getProjectModelsPath() {
44
+ return path.join(process.cwd(), ".opencode", PROJECT_MODELS_FILE);
45
+ }
46
+ function hasProjectModelsConfig() {
47
+ return fs.existsSync(getProjectModelsPath());
48
+ }
49
+ function readProjectModels() {
50
+ const modelsPath = getProjectModelsPath();
51
+ if (!fs.existsSync(modelsPath))
52
+ return null;
53
+ try {
54
+ return JSON.parse(fs.readFileSync(modelsPath, "utf-8"));
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ }
60
+ function writeProjectModels(primary, subagent) {
61
+ const modelsPath = getProjectModelsPath();
62
+ const dir = path.dirname(modelsPath);
63
+ if (!fs.existsSync(dir)) {
64
+ fs.mkdirSync(dir, { recursive: true });
65
+ }
66
+ const config = {
67
+ primary: { model: primary },
68
+ subagent: { model: subagent },
69
+ agents: {},
70
+ };
71
+ for (const name of PRIMARY_AGENTS) {
72
+ config.agents[name] = { model: primary };
73
+ }
74
+ for (const name of SUBAGENTS) {
75
+ config.agents[name] = { model: subagent };
76
+ }
77
+ fs.writeFileSync(modelsPath, JSON.stringify(config, null, 2) + "\n");
78
+ }
79
+ /**
80
+ * Sync .opencode/models.json → local opencode.json agent model settings.
81
+ * Creates or merges into local opencode.json at project root.
82
+ */
83
+ function syncProjectModelsToConfig() {
84
+ const models = readProjectModels();
85
+ if (!models)
86
+ return false;
87
+ const localPath = path.join(process.cwd(), "opencode.json");
88
+ const config = fs.existsSync(localPath)
89
+ ? readConfig(localPath)
90
+ : { $schema: "https://opencode.ai/config.json" };
91
+ if (!config.agent)
92
+ config.agent = {};
93
+ for (const [name, entry] of Object.entries(models.agents)) {
94
+ if (!config.agent[name])
95
+ config.agent[name] = {};
96
+ config.agent[name].model = entry.model;
97
+ }
98
+ writeConfig(localPath, config);
99
+ return true;
100
+ }
41
101
  // ─── File Copy Helpers ───────────────────────────────────────────────────────
42
102
  function copyDirRecursive(src, dest) {
43
103
  fs.mkdirSync(dest, { recursive: true });
@@ -137,8 +197,16 @@ function install() {
137
197
  // Copy agents and skills into the global opencode config dir
138
198
  console.log("Installing agents and skills...");
139
199
  installAgentsAndSkills(globalDir);
200
+ // Sync per-project models if .opencode/models.json exists
201
+ if (hasProjectModelsConfig()) {
202
+ console.log("\nPer-project model config found (.opencode/models.json)");
203
+ if (syncProjectModelsToConfig()) {
204
+ console.log(" Synced model settings to local opencode.json");
205
+ }
206
+ }
140
207
  console.log("\nDone! Next steps:");
141
- console.log(` 1. Run 'npx ${PLUGIN_NAME} configure' to select your models`);
208
+ console.log(` 1. Run 'npx ${PLUGIN_NAME} configure' to select your models (global)`);
209
+ console.log(` Or 'npx ${PLUGIN_NAME} configure --project' for per-project config`);
142
210
  console.log(" 2. Restart OpenCode to load the plugin\n");
143
211
  }
144
212
  function uninstall() {
@@ -180,23 +248,82 @@ function uninstall() {
180
248
  async function configure() {
181
249
  const args = process.argv.slice(3);
182
250
  const isReset = args.includes("--reset");
251
+ const isProject = args.includes("--project");
183
252
  // Handle --reset flag
184
253
  if (isReset) {
185
- return configureReset();
254
+ return configureReset(isProject);
255
+ }
256
+ const scope = isProject ? "Per-Project" : "Global";
257
+ console.log(`\n🔧 Cortex Agents — ${scope} Model Configuration\n`);
258
+ if (isProject) {
259
+ const opencodeDir = path.join(process.cwd(), ".opencode");
260
+ if (!fs.existsSync(opencodeDir)) {
261
+ console.log(`⚠ No .opencode/ directory found in ${process.cwd()}.`);
262
+ console.log(` Run 'npx ${PLUGIN_NAME} install' first, or use 'configure' without --project for global config.\n`);
263
+ process.exit(1);
264
+ }
265
+ console.log(`Project: ${process.cwd()}`);
266
+ console.log(`Config: .opencode/models.json + opencode.json\n`);
186
267
  }
187
- console.log(`\n🔧 Cortex Agents Model Configuration\n`);
188
- // Ensure plugin is installed first
268
+ // Ensure plugin is installed first (for global mode)
189
269
  const configInfo = findOpencodeConfig();
190
270
  const config = configInfo
191
271
  ? readConfig(configInfo.path)
192
272
  : { $schema: "https://opencode.ai/config.json" };
193
- if (!config.plugin?.includes(PLUGIN_NAME)) {
273
+ if (!isProject && !config.plugin?.includes(PLUGIN_NAME)) {
194
274
  console.log(`⚠ Plugin not installed. Adding '${PLUGIN_NAME}' to config first.\n`);
195
275
  if (!config.plugin)
196
276
  config.plugin = [];
197
277
  config.plugin.push(PLUGIN_NAME);
198
278
  }
199
279
  // ── Primary model selection ────────────────────────────────
280
+ const { primary, subagent } = await promptModelSelection();
281
+ // ── Write config ───────────────────────────────────────────
282
+ if (isProject) {
283
+ // Per-project: write .opencode/models.json + sync to local opencode.json
284
+ writeProjectModels(primary, subagent);
285
+ syncProjectModelsToConfig();
286
+ const modelsPath = getProjectModelsPath();
287
+ const localConfigPath = path.join(process.cwd(), "opencode.json");
288
+ console.log("━".repeat(50));
289
+ console.log(`✓ Per-project config saved:\n`);
290
+ console.log(` Models: ${modelsPath}`);
291
+ console.log(` Runtime: ${localConfigPath}\n`);
292
+ }
293
+ else {
294
+ // Global: write to opencode.json (existing behavior)
295
+ if (!config.agent)
296
+ config.agent = {};
297
+ for (const name of PRIMARY_AGENTS) {
298
+ if (!config.agent[name])
299
+ config.agent[name] = {};
300
+ config.agent[name].model = primary;
301
+ }
302
+ for (const name of SUBAGENTS) {
303
+ if (!config.agent[name])
304
+ config.agent[name] = {};
305
+ config.agent[name].model = subagent;
306
+ }
307
+ const targetPath = configInfo?.path || path.join(getGlobalDir(), "opencode.json");
308
+ writeConfig(targetPath, config);
309
+ console.log("━".repeat(50));
310
+ console.log(`✓ Configuration saved to ${targetPath}\n`);
311
+ }
312
+ console.log(" Primary agents:");
313
+ for (const name of PRIMARY_AGENTS) {
314
+ console.log(` ${name.padEnd(10)} → ${primary}`);
315
+ }
316
+ console.log("\n Subagents:");
317
+ for (const name of SUBAGENTS) {
318
+ console.log(` ${name.padEnd(10)} → ${subagent}`);
319
+ }
320
+ console.log("\nRestart OpenCode to apply changes.\n");
321
+ }
322
+ /**
323
+ * Interactive prompt for primary + subagent model selection.
324
+ * Shared between global and per-project configure flows.
325
+ */
326
+ async function promptModelSelection() {
200
327
  const primaryChoices = getPrimaryChoices();
201
328
  primaryChoices.push({
202
329
  title: "Enter custom model ID",
@@ -211,7 +338,6 @@ async function configure() {
211
338
  choices: primaryChoices,
212
339
  hint: "Use arrow keys, Enter to confirm",
213
340
  });
214
- // User cancelled (Ctrl+C)
215
341
  if (primaryModel === undefined) {
216
342
  console.log("\nConfiguration cancelled.\n");
217
343
  process.exit(0);
@@ -265,36 +391,54 @@ async function configure() {
265
391
  subagent = custom;
266
392
  }
267
393
  console.log(`✓ Subagent model: ${subagent}\n`);
268
- // ── Write to opencode.json ─────────────────────────────────
269
- if (!config.agent)
270
- config.agent = {};
271
- for (const name of PRIMARY_AGENTS) {
272
- if (!config.agent[name])
273
- config.agent[name] = {};
274
- config.agent[name].model = primary;
275
- }
276
- for (const name of SUBAGENTS) {
277
- if (!config.agent[name])
278
- config.agent[name] = {};
279
- config.agent[name].model = subagent;
280
- }
281
- const targetPath = configInfo?.path || path.join(getGlobalDir(), "opencode.json");
282
- writeConfig(targetPath, config);
283
- // ── Print summary ──────────────────────────────────────────
284
- console.log("━".repeat(50));
285
- console.log(`✓ Configuration saved to ${targetPath}\n`);
286
- console.log(" Primary agents:");
287
- for (const name of PRIMARY_AGENTS) {
288
- console.log(` ${name.padEnd(10)} → ${primary}`);
289
- }
290
- console.log("\n Subagents:");
291
- for (const name of SUBAGENTS) {
292
- console.log(` ${name.padEnd(10)} → ${subagent}`);
293
- }
294
- console.log("\nRestart OpenCode to apply changes.\n");
394
+ return { primary, subagent };
295
395
  }
296
- function configureReset() {
297
- console.log(`\n🔧 Cortex Agents Reset Model Configuration\n`);
396
+ function configureReset(isProject = false) {
397
+ const scope = isProject ? "Per-Project" : "Global";
398
+ console.log(`\n🔧 Cortex Agents — Reset ${scope} Model Configuration\n`);
399
+ if (isProject) {
400
+ // Reset per-project config
401
+ const modelsPath = getProjectModelsPath();
402
+ let removedModels = false;
403
+ if (fs.existsSync(modelsPath)) {
404
+ fs.unlinkSync(modelsPath);
405
+ console.log(`✓ Removed ${modelsPath}`);
406
+ removedModels = true;
407
+ }
408
+ // Also clean agent models from local opencode.json
409
+ const localConfigPath = path.join(process.cwd(), "opencode.json");
410
+ if (fs.existsSync(localConfigPath)) {
411
+ const config = readConfig(localConfigPath);
412
+ if (config.agent) {
413
+ let resetCount = 0;
414
+ for (const name of ALL_AGENTS) {
415
+ if (config.agent[name]?.model) {
416
+ delete config.agent[name].model;
417
+ resetCount++;
418
+ if (Object.keys(config.agent[name]).length === 0) {
419
+ delete config.agent[name];
420
+ }
421
+ }
422
+ }
423
+ if (Object.keys(config.agent).length === 0) {
424
+ delete config.agent;
425
+ }
426
+ if (resetCount > 0) {
427
+ writeConfig(localConfigPath, config);
428
+ console.log(`✓ Removed ${resetCount} agent model entries from ${localConfigPath}`);
429
+ removedModels = true;
430
+ }
431
+ }
432
+ }
433
+ if (!removedModels) {
434
+ console.log("No per-project model configuration found. Nothing to reset.\n");
435
+ return;
436
+ }
437
+ console.log("\nAgents will now fall back to global or OpenCode default models.");
438
+ console.log("Restart OpenCode to apply changes.\n");
439
+ return;
440
+ }
441
+ // Global reset (existing behavior)
298
442
  const configInfo = findOpencodeConfig();
299
443
  if (!configInfo) {
300
444
  console.log("No OpenCode config found. Nothing to reset.\n");
@@ -355,12 +499,20 @@ function status() {
355
499
  : 0;
356
500
  console.log(`\nAgents installed: ${agentCount}`);
357
501
  console.log(`Skills installed: ${skillCount}`);
358
- // Show model configuration
502
+ // Show per-project model configuration
503
+ const projectModels = readProjectModels();
504
+ if (projectModels) {
505
+ console.log("\nPer-Project Models (.opencode/models.json):");
506
+ console.log(` Primary agents: ${projectModels.primary.model}`);
507
+ console.log(` Subagents: ${projectModels.subagent.model}`);
508
+ }
509
+ // Show global/active model configuration
359
510
  if (config.agent) {
360
511
  const primaryModels = PRIMARY_AGENTS.map((n) => config.agent?.[n]?.model).filter(Boolean);
361
512
  const subagentModels = SUBAGENTS.map((n) => config.agent?.[n]?.model).filter(Boolean);
362
513
  if (primaryModels.length > 0 || subagentModels.length > 0) {
363
- console.log("\nModel Configuration:");
514
+ const source = projectModels ? "Active" : "Global";
515
+ console.log(`\n${source} Model Configuration (opencode.json):`);
364
516
  if (primaryModels.length > 0) {
365
517
  const unique = [...new Set(primaryModels)];
366
518
  console.log(` Primary agents: ${unique.join(", ")}`);
@@ -370,14 +522,16 @@ function status() {
370
522
  console.log(` Subagents: ${unique.join(", ")}`);
371
523
  }
372
524
  }
373
- else {
525
+ else if (!projectModels) {
374
526
  console.log("\nModels: Not configured (using OpenCode defaults)");
375
- console.log(` Run 'npx ${PLUGIN_NAME} configure' to select models.`);
527
+ console.log(` Run 'npx ${PLUGIN_NAME} configure' for global config`);
528
+ console.log(` Run 'npx ${PLUGIN_NAME} configure --project' for per-project config`);
376
529
  }
377
530
  }
378
- else {
531
+ else if (!projectModels) {
379
532
  console.log("\nModels: Not configured (using OpenCode defaults)");
380
- console.log(` Run 'npx ${PLUGIN_NAME} configure' to select models.`);
533
+ console.log(` Run 'npx ${PLUGIN_NAME} configure' for global config`);
534
+ console.log(` Run 'npx ${PLUGIN_NAME} configure --project' for per-project config`);
381
535
  }
382
536
  if (!isInstalled) {
383
537
  console.log(`\nRun 'npx ${PLUGIN_NAME} install' to add to config.`);
@@ -387,48 +541,59 @@ function status() {
387
541
  function help() {
388
542
  console.log(`${PLUGIN_NAME} v${VERSION}
389
543
 
390
- Cortex agents for OpenCode agent handover notifications, mermaid documentation,
391
- worktree workflow, plan persistence, and session management.
544
+ Supercharge OpenCode with structured workflows, intelligent agents,
545
+ and automated development practices.
392
546
 
393
547
  USAGE:
394
548
  npx ${PLUGIN_NAME} <command> [options]
395
549
 
396
550
  COMMANDS:
397
551
  install Install plugin, agents, and skills into OpenCode config
398
- configure Interactive model selection for all agents
552
+ configure Interactive model selection (global)
553
+ configure --project Interactive model selection (per-project, saves to .opencode/)
399
554
  configure --reset Reset model configuration to OpenCode defaults
555
+ configure --project --reset Reset per-project model configuration
400
556
  uninstall Remove plugin, agents, skills, and model config
401
557
  status Show installation and model configuration status
402
558
  help Show this help message
403
559
 
404
560
  EXAMPLES:
405
- npx ${PLUGIN_NAME} install # Install plugin
406
- npx ${PLUGIN_NAME} configure # Select models interactively
407
- npx ${PLUGIN_NAME} configure --reset # Reset to default models
408
- npx ${PLUGIN_NAME} status # Check status
561
+ npx ${PLUGIN_NAME} install # Install plugin
562
+ npx ${PLUGIN_NAME} configure # Global model selection
563
+ npx ${PLUGIN_NAME} configure --project # Per-project models (.opencode/models.json)
564
+ npx ${PLUGIN_NAME} configure --reset # Reset global models
565
+ npx ${PLUGIN_NAME} configure --project --reset # Reset per-project models
566
+ npx ${PLUGIN_NAME} status # Check status
409
567
 
410
- AGENT TIERS:
411
- Primary agents (build, plan, debug):
568
+ AGENTS:
569
+ Primary (build, plan, debug):
412
570
  Handle complex tasks — select your best model.
413
571
 
414
572
  Subagents (fullstack, testing, security, devops):
415
573
  Handle focused tasks — a fast/cheap model works great.
416
574
 
417
- INCLUDED TOOLS:
418
- cortex_init, cortex_status - .cortex directory management
419
- worktree_create, worktree_list, worktree_remove, worktree_open
420
- branch_create, branch_status, branch_switch
421
- plan_save, plan_list, plan_load, plan_delete
422
- session_save, session_list, session_load
423
- docs_init, docs_save, docs_list, docs_index - mermaid documentation
424
-
425
- FEATURES:
426
- Agent handover notifications - Toast alerts when agents switch
427
- Mermaid documentation system - Auto-prompted docs with diagrams
575
+ TOOLS (23):
576
+ cortex_init, cortex_status .cortex directory management
577
+ cortex_configure Per-project model configuration
578
+ worktree_create, worktree_list Git worktree management
579
+ worktree_remove, worktree_open
580
+ worktree_launch Launch worktree (terminal/PTY/background)
581
+ branch_create, branch_status Git branch operations
582
+ branch_switch
583
+ plan_save, plan_list Plan persistence
584
+ plan_load, plan_delete
585
+ session_save, session_list Session management
586
+ session_load
587
+ docs_init, docs_save Mermaid documentation
588
+ docs_list, docs_index
589
+ task_finalize Commit, push, and create PR
428
590
 
429
- INCLUDED SKILLS:
430
- web-development, testing-strategies, security-hardening,
431
- deployment-automation, code-quality, git-workflow
591
+ SKILLS (14):
592
+ web-development, frontend-development, backend-development,
593
+ mobile-development, desktop-development, database-design,
594
+ api-design, architecture-patterns, design-patterns,
595
+ testing-strategies, security-hardening, deployment-automation,
596
+ performance-optimization, code-quality, git-workflow
432
597
  `);
433
598
  }
434
599
  // ─── CLI Entry Point ─────────────────────────────────────────────────────────
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAqBlD,eAAO,MAAM,YAAY,EAAE,MA8D1B,CAAC;AAGF,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAsBlD,eAAO,MAAM,YAAY,EAAE,MAmE1B,CAAC;AAGF,eAAe,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import * as branch from "./tools/branch";
5
5
  import * as plan from "./tools/plan";
6
6
  import * as session from "./tools/session";
7
7
  import * as docs from "./tools/docs";
8
+ import * as task from "./tools/task";
8
9
  // Agent descriptions for handover toast notifications
9
10
  const AGENT_DESCRIPTIONS = {
10
11
  build: "Development mode — ready to implement",
@@ -21,13 +22,15 @@ export const CortexPlugin = async (ctx) => {
21
22
  // Cortex tools - .cortex directory management
22
23
  cortex_init: cortex.init,
23
24
  cortex_status: cortex.status,
24
- // Worktree tools - git worktree management
25
- worktree_create: worktree.create,
25
+ cortex_configure: cortex.configure,
26
+ // Worktree tools - git worktree management (factories for toast notifications)
27
+ worktree_create: worktree.createCreate(ctx.client),
26
28
  worktree_list: worktree.list,
27
- worktree_remove: worktree.remove,
29
+ worktree_remove: worktree.createRemove(ctx.client),
28
30
  worktree_open: worktree.open,
29
- // Branch tools - git branch operations
30
- branch_create: branch.create,
31
+ worktree_launch: worktree.createLaunch(ctx.client, ctx.$),
32
+ // Branch tools - git branch operations (factory for toast notifications)
33
+ branch_create: branch.createCreate(ctx.client),
31
34
  branch_status: branch.status,
32
35
  branch_switch: branch.switch_,
33
36
  // Plan tools - implementation plan persistence
@@ -44,6 +47,8 @@ export const CortexPlugin = async (ctx) => {
44
47
  docs_save: docs.save,
45
48
  docs_list: docs.list,
46
49
  docs_index: docs.index,
50
+ // Task tools - finalize workflow (commit, push, PR)
51
+ task_finalize: task.finalize,
47
52
  },
48
53
  // Agent handover toast notifications
49
54
  async event({ event }) {
@@ -1,4 +1,10 @@
1
- export declare const create: {
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ type Client = PluginInput["client"];
3
+ /**
4
+ * Factory function that creates the branch_create tool with access
5
+ * to the OpenCode client for toast notifications.
6
+ */
7
+ export declare function createCreate(client: Client): {
2
8
  description: string;
3
9
  args: {
4
10
  name: import("zod").ZodString;
@@ -1 +1 @@
1
- {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/tools/branch.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;CA8CjB,CAAC;AAEH,eAAO,MAAM,MAAM;;;;CAiGjB,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;CAmClB,CAAC;AAGH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,CAAC"}
1
+ {"version":3,"file":"branch.d.ts","sourceRoot":"","sources":["../../src/tools/branch.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAMvD,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AAEpC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;EA0E1C;AAED,eAAO,MAAM,MAAM;;;;CAkGjB,CAAC;AAEH,eAAO,MAAM,OAAO;;;;;;;;CAmClB,CAAC;AAGH,OAAO,EAAE,OAAO,IAAI,MAAM,EAAE,CAAC"}
@@ -1,57 +1,91 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
+ import { git } from "../utils/shell.js";
2
3
  const PROTECTED_BRANCHES = ["main", "master", "develop", "production", "staging"];
3
- export const create = tool({
4
- description: "Create and checkout a new git branch with proper naming convention",
5
- args: {
6
- name: tool.schema
7
- .string()
8
- .describe("Branch name slug (e.g., 'user-authentication', 'fix-login')"),
9
- type: tool.schema
10
- .enum(["feature", "bugfix", "hotfix", "refactor", "docs", "test", "chore"])
11
- .describe("Branch type - determines prefix"),
12
- },
13
- async execute(args, context) {
14
- const { name, type } = args;
15
- const branchName = `${type}/${name}`;
16
- // Check if we're in a git repository
17
- try {
18
- await Bun.$ `git rev-parse --git-dir`.cwd(context.worktree).text();
19
- }
20
- catch {
21
- return "✗ Error: Not in a git repository";
22
- }
23
- // Check if branch already exists
24
- try {
25
- const branches = await Bun.$ `git branch --list ${branchName}`.cwd(context.worktree).text();
26
- if (branches.trim()) {
27
- return `✗ Error: Branch '${branchName}' already exists.
4
+ /**
5
+ * Factory function that creates the branch_create tool with access
6
+ * to the OpenCode client for toast notifications.
7
+ */
8
+ export function createCreate(client) {
9
+ return tool({
10
+ description: "Create and checkout a new git branch with proper naming convention",
11
+ args: {
12
+ name: tool.schema
13
+ .string()
14
+ .describe("Branch name slug (e.g., 'user-authentication', 'fix-login')"),
15
+ type: tool.schema
16
+ .enum(["feature", "bugfix", "hotfix", "refactor", "docs", "test", "chore"])
17
+ .describe("Branch type - determines prefix"),
18
+ },
19
+ async execute(args, context) {
20
+ const { name, type } = args;
21
+ const branchName = `${type}/${name}`;
22
+ // Check if we're in a git repository
23
+ try {
24
+ await git(context.worktree, "rev-parse", "--git-dir");
25
+ }
26
+ catch {
27
+ return "✗ Error: Not in a git repository";
28
+ }
29
+ // Check if branch already exists
30
+ try {
31
+ const { stdout } = await git(context.worktree, "branch", "--list", branchName);
32
+ if (stdout.trim()) {
33
+ return `✗ Error: Branch '${branchName}' already exists.
28
34
 
29
35
  Use branch_switch to switch to it, or choose a different name.`;
36
+ }
30
37
  }
31
- }
32
- catch {
33
- // Ignore error, branch check is optional
34
- }
35
- // Create and checkout the branch
36
- try {
37
- await Bun.$ `git checkout -b ${branchName}`.cwd(context.worktree);
38
- }
39
- catch (error) {
40
- return `✗ Error creating branch: ${error.message || error}`;
41
- }
42
- return `✓ Created and switched to branch: ${branchName}
38
+ catch {
39
+ // Ignore error, branch check is optional
40
+ }
41
+ // Create and checkout the branch
42
+ try {
43
+ await git(context.worktree, "checkout", "-b", branchName);
44
+ }
45
+ catch (error) {
46
+ try {
47
+ await client.tui.showToast({
48
+ body: {
49
+ title: `Branch: ${branchName}`,
50
+ message: `Failed to create: ${error.message || error}`,
51
+ variant: "error",
52
+ duration: 8000,
53
+ },
54
+ });
55
+ }
56
+ catch {
57
+ // Toast failure is non-fatal
58
+ }
59
+ return `✗ Error creating branch: ${error.message || error}`;
60
+ }
61
+ // Notify via toast
62
+ try {
63
+ await client.tui.showToast({
64
+ body: {
65
+ title: `Branch: ${branchName}`,
66
+ message: `Created and checked out`,
67
+ variant: "success",
68
+ duration: 4000,
69
+ },
70
+ });
71
+ }
72
+ catch {
73
+ // Toast failure is non-fatal
74
+ }
75
+ return `✓ Created and switched to branch: ${branchName}
43
76
 
44
77
  You are now on branch '${branchName}'.
45
78
  Make your changes and commit when ready.`;
46
- },
47
- });
79
+ },
80
+ });
81
+ }
48
82
  export const status = tool({
49
83
  description: "Get current git branch status - branch name, uncommitted changes, and whether on protected branch",
50
84
  args: {},
51
85
  async execute(args, context) {
52
86
  // Check if we're in a git repository
53
87
  try {
54
- await Bun.$ `git rev-parse --git-dir`.cwd(context.worktree).text();
88
+ await git(context.worktree, "rev-parse", "--git-dir");
55
89
  }
56
90
  catch {
57
91
  return "✗ Not in a git repository";
@@ -64,7 +98,8 @@ export const status = tool({
64
98
  let aheadBehind = "";
65
99
  // Get current branch
66
100
  try {
67
- currentBranch = (await Bun.$ `git branch --show-current`.cwd(context.worktree).text()).trim();
101
+ const { stdout } = await git(context.worktree, "branch", "--show-current");
102
+ currentBranch = stdout.trim();
68
103
  if (!currentBranch) {
69
104
  currentBranch = "(detached HEAD)";
70
105
  }
@@ -76,17 +111,17 @@ export const status = tool({
76
111
  isProtected = PROTECTED_BRANCHES.includes(currentBranch);
77
112
  // Check for changes
78
113
  try {
79
- const statusOutput = await Bun.$ `git status --porcelain`.cwd(context.worktree).text();
80
- const lines = statusOutput.trim().split("\n").filter((l) => l);
114
+ const { stdout } = await git(context.worktree, "status", "--porcelain");
115
+ const lines = stdout.trim().split("\n").filter((l) => l);
81
116
  for (const line of lines) {
82
- const status = line.substring(0, 2);
83
- if (status[0] !== " " && status[0] !== "?") {
117
+ const st = line.substring(0, 2);
118
+ if (st[0] !== " " && st[0] !== "?") {
84
119
  stagedChanges = true;
85
120
  }
86
- if (status[1] !== " " && status[1] !== "?") {
121
+ if (st[1] !== " " && st[1] !== "?") {
87
122
  hasChanges = true;
88
123
  }
89
- if (status === "??") {
124
+ if (st === "??") {
90
125
  untrackedFiles = true;
91
126
  }
92
127
  }
@@ -96,8 +131,8 @@ export const status = tool({
96
131
  }
97
132
  // Check ahead/behind
98
133
  try {
99
- const result = await Bun.$ `git rev-list --left-right --count HEAD...@{upstream}`.cwd(context.worktree).text();
100
- const [ahead, behind] = result.trim().split(/\s+/);
134
+ const { stdout } = await git(context.worktree, "rev-list", "--left-right", "--count", "HEAD...@{upstream}");
135
+ const [ahead, behind] = stdout.trim().split(/\s+/);
101
136
  if (parseInt(ahead) > 0 || parseInt(behind) > 0) {
102
137
  aheadBehind = `Ahead: ${ahead}, Behind: ${behind}`;
103
138
  }
@@ -146,10 +181,10 @@ export const switch_ = tool({
146
181
  const { branch } = args;
147
182
  // Check if branch exists
148
183
  try {
149
- const branches = await Bun.$ `git branch --list ${branch}`.cwd(context.worktree).text();
150
- if (!branches.trim()) {
184
+ const { stdout } = await git(context.worktree, "branch", "--list", branch);
185
+ if (!stdout.trim()) {
151
186
  // Try remote branch
152
- const remoteBranches = await Bun.$ `git branch -r --list origin/${branch}`.cwd(context.worktree).text();
187
+ const { stdout: remoteBranches } = await git(context.worktree, "branch", "-r", "--list", `origin/${branch}`);
153
188
  if (!remoteBranches.trim()) {
154
189
  return `✗ Error: Branch '${branch}' not found locally or on origin.
155
190
 
@@ -162,7 +197,7 @@ Use branch_create to create a new branch.`;
162
197
  }
163
198
  // Switch to branch
164
199
  try {
165
- await Bun.$ `git checkout ${branch}`.cwd(context.worktree);
200
+ await git(context.worktree, "checkout", branch);
166
201
  }
167
202
  catch (error) {
168
203
  return `✗ Error switching branch: ${error.message || error}