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
@@ -1,3 +1,4 @@
1
+ import { z } from "zod";
1
2
  export declare const init: {
2
3
  description: string;
3
4
  args: {};
@@ -8,4 +9,22 @@ export declare const status: {
8
9
  args: {};
9
10
  execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
10
11
  };
12
+ /**
13
+ * cortex_configure — Write per-project model configuration to ./opencode.json.
14
+ *
15
+ * Accepts a primary model (for build/plan/debug) and a subagent model
16
+ * (for fullstack/testing/security/devops). Merges into any existing
17
+ * opencode.json at the project root, preserving other settings.
18
+ */
19
+ export declare const configure: {
20
+ description: string;
21
+ args: {
22
+ primaryModel: z.ZodString;
23
+ subagentModel: z.ZodString;
24
+ };
25
+ execute(args: {
26
+ primaryModel: string;
27
+ subagentModel: string;
28
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
29
+ };
11
30
  //# sourceMappingURL=cortex.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cortex.d.ts","sourceRoot":"","sources":["../../src/tools/cortex.ts"],"names":[],"mappings":"AAkEA,eAAO,MAAM,IAAI;;;;CAkDf,CAAC;AAEH,eAAO,MAAM,MAAM;;;;CAyDjB,CAAC"}
1
+ {"version":3,"file":"cortex.d.ts","sourceRoot":"","sources":["../../src/tools/cortex.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAqExB,eAAO,MAAM,IAAI;;;;CAkDf,CAAC;AAEH,eAAO,MAAM,MAAM;;;;CAyDjB,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,SAAS;;;;;;;;;;CAkHpB,CAAC"}
@@ -1,11 +1,13 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
+ import { z } from "zod";
5
+ import { PRIMARY_AGENTS, SUBAGENTS, MODEL_REGISTRY, } from "../registry.js";
4
6
  const CORTEX_DIR = ".cortex";
5
7
  const DEFAULT_CONFIG = {
6
8
  version: "1.0.0",
7
9
  worktree: {
8
- root: "../.worktrees",
10
+ root: ".worktrees",
9
11
  autoCleanup: false,
10
12
  },
11
13
  branches: {
@@ -146,3 +148,110 @@ Plans: ${planCount}`;
146
148
  return output;
147
149
  },
148
150
  });
151
+ /**
152
+ * cortex_configure — Write per-project model configuration to ./opencode.json.
153
+ *
154
+ * Accepts a primary model (for build/plan/debug) and a subagent model
155
+ * (for fullstack/testing/security/devops). Merges into any existing
156
+ * opencode.json at the project root, preserving other settings.
157
+ */
158
+ export const configure = tool({
159
+ description: "Save per-project model configuration to ./opencode.json. " +
160
+ "Sets the model for primary agents (build, plan, debug) and subagents (fullstack, testing, security, devops). " +
161
+ "Available models — Premium: " +
162
+ MODEL_REGISTRY.filter((m) => m.tier === "premium")
163
+ .map((m) => `${m.name} (${m.id})`)
164
+ .join(", ") +
165
+ ". Standard: " +
166
+ MODEL_REGISTRY.filter((m) => m.tier === "standard")
167
+ .map((m) => `${m.name} (${m.id})`)
168
+ .join(", ") +
169
+ ". Fast: " +
170
+ MODEL_REGISTRY.filter((m) => m.tier === "fast")
171
+ .map((m) => `${m.name} (${m.id})`)
172
+ .join(", ") +
173
+ ".",
174
+ args: {
175
+ primaryModel: z
176
+ .string()
177
+ .describe("Model ID for primary agents (build, plan, debug). Format: provider/model-name"),
178
+ subagentModel: z
179
+ .string()
180
+ .describe("Model ID for subagents (fullstack, testing, security, devops). Format: provider/model-name"),
181
+ },
182
+ async execute(args, context) {
183
+ const configPath = path.join(context.worktree, "opencode.json");
184
+ // Read existing config or start fresh
185
+ let config = {};
186
+ if (fs.existsSync(configPath)) {
187
+ try {
188
+ config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
189
+ }
190
+ catch {
191
+ // Malformed JSON — start fresh but warn
192
+ config = {};
193
+ }
194
+ }
195
+ // Ensure schema and plugin
196
+ if (!config.$schema) {
197
+ config.$schema = "https://opencode.ai/config.json";
198
+ }
199
+ const plugins = config.plugin ?? [];
200
+ if (!plugins.includes("cortex-agents")) {
201
+ plugins.push("cortex-agents");
202
+ }
203
+ config.plugin = plugins;
204
+ // Build agent config
205
+ const agent = config.agent ?? {};
206
+ for (const name of PRIMARY_AGENTS) {
207
+ if (!agent[name])
208
+ agent[name] = {};
209
+ agent[name].model = args.primaryModel;
210
+ }
211
+ for (const name of SUBAGENTS) {
212
+ if (!agent[name])
213
+ agent[name] = {};
214
+ agent[name].model = args.subagentModel;
215
+ }
216
+ config.agent = agent;
217
+ // Write to project root (runtime config — what OpenCode reads)
218
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
219
+ // Also write .opencode/models.json (per-project source of truth, version controlled)
220
+ const modelsPath = path.join(context.worktree, ".opencode", "models.json");
221
+ const modelsDir = path.dirname(modelsPath);
222
+ if (fs.existsSync(modelsDir)) {
223
+ const modelsConfig = {
224
+ primary: { model: args.primaryModel },
225
+ subagent: { model: args.subagentModel },
226
+ agents: {},
227
+ };
228
+ const agentsMap = {};
229
+ for (const name of PRIMARY_AGENTS) {
230
+ agentsMap[name] = { model: args.primaryModel };
231
+ }
232
+ for (const name of SUBAGENTS) {
233
+ agentsMap[name] = { model: args.subagentModel };
234
+ }
235
+ modelsConfig.agents = agentsMap;
236
+ fs.writeFileSync(modelsPath, JSON.stringify(modelsConfig, null, 2) + "\n");
237
+ }
238
+ // Build summary
239
+ const primaryDisplay = MODEL_REGISTRY.find((m) => m.id === args.primaryModel)?.name ??
240
+ args.primaryModel;
241
+ const subagentDisplay = MODEL_REGISTRY.find((m) => m.id === args.subagentModel)?.name ??
242
+ args.subagentModel;
243
+ const savedTo = fs.existsSync(modelsDir)
244
+ ? `${configPath}\n .opencode/models.json`
245
+ : configPath;
246
+ return `✓ Model configuration saved to:
247
+ ${savedTo}
248
+
249
+ Primary agents (build, plan, debug):
250
+ → ${primaryDisplay} (${args.primaryModel})
251
+
252
+ Subagents (fullstack, testing, security, devops):
253
+ → ${subagentDisplay} (${args.subagentModel})
254
+
255
+ Restart OpenCode to apply changes.`;
256
+ },
257
+ });
@@ -1 +1 @@
1
- {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"AA2BA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;CAsFf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CA8Df,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CAuBf,CAAC"}
1
+ {"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../../src/tools/session.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,IAAI;;;;;;;;;;;;;;;;CAuFf,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CA8Df,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CAuBf,CAAC"}
@@ -1,6 +1,7 @@
1
1
  import { tool } from "@opencode-ai/plugin";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
+ import { git } from "../utils/shell.js";
4
5
  const CORTEX_DIR = ".cortex";
5
6
  const SESSIONS_DIR = "sessions";
6
7
  function getDatePrefix() {
@@ -51,7 +52,8 @@ export const save = tool({
51
52
  let currentBranch = branch;
52
53
  if (!currentBranch) {
53
54
  try {
54
- currentBranch = (await Bun.$ `git branch --show-current`.cwd(context.worktree).text()).trim();
55
+ const { stdout } = await git(context.worktree, "branch", "--show-current");
56
+ currentBranch = stdout.trim();
55
57
  }
56
58
  catch {
57
59
  currentBranch = "unknown";
@@ -0,0 +1,20 @@
1
+ export declare const finalize: {
2
+ description: string;
3
+ args: {
4
+ commitMessage: import("zod").ZodString;
5
+ prTitle: import("zod").ZodOptional<import("zod").ZodString>;
6
+ prBody: import("zod").ZodOptional<import("zod").ZodString>;
7
+ baseBranch: import("zod").ZodOptional<import("zod").ZodString>;
8
+ planFilename: import("zod").ZodOptional<import("zod").ZodString>;
9
+ draft: import("zod").ZodOptional<import("zod").ZodBoolean>;
10
+ };
11
+ execute(args: {
12
+ commitMessage: string;
13
+ prTitle?: string | undefined;
14
+ prBody?: string | undefined;
15
+ baseBranch?: string | undefined;
16
+ planFilename?: string | undefined;
17
+ draft?: boolean | undefined;
18
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
19
+ };
20
+ //# sourceMappingURL=task.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAyGA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;CAqNnB,CAAC"}
@@ -0,0 +1,310 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { detectWorktreeInfo } from "../utils/worktree-detect.js";
5
+ import { findPlanContent, extractPlanSections, buildPrBodyFromPlan, } from "../utils/plan-extract.js";
6
+ import { git, gh, which } from "../utils/shell.js";
7
+ const PROTECTED_BRANCHES = ["main", "master", "develop", "production", "staging"];
8
+ const DOCS_DIR = "docs";
9
+ // ─── Helpers ─────────────────────────────────────────────────────────────────
10
+ /**
11
+ * Check if `gh` CLI is installed and authenticated.
12
+ */
13
+ async function checkGhCli(cwd) {
14
+ // Check if gh exists
15
+ const ghPath = await which("gh");
16
+ if (!ghPath) {
17
+ return {
18
+ ok: false,
19
+ error: "GitHub CLI (gh) is not installed. Install it from https://cli.github.com/ and run `gh auth login`.",
20
+ };
21
+ }
22
+ // Check if authenticated
23
+ try {
24
+ await gh(cwd, "auth", "status");
25
+ }
26
+ catch {
27
+ return {
28
+ ok: false,
29
+ error: "GitHub CLI is not authenticated. Run `gh auth login` to authenticate.",
30
+ };
31
+ }
32
+ return { ok: true };
33
+ }
34
+ /**
35
+ * Check if a remote named "origin" is configured.
36
+ */
37
+ async function checkRemote(cwd) {
38
+ try {
39
+ const { stdout } = await git(cwd, "remote", "-v");
40
+ if (!stdout.includes("origin")) {
41
+ return {
42
+ ok: false,
43
+ error: "No 'origin' remote configured. Add one with: git remote add origin <url>",
44
+ };
45
+ }
46
+ return { ok: true };
47
+ }
48
+ catch {
49
+ return { ok: false, error: "Could not check git remotes." };
50
+ }
51
+ }
52
+ /**
53
+ * Check whether docs/ has any content beyond .gitkeep files.
54
+ */
55
+ function checkDocsExist(worktree) {
56
+ const docsRoot = path.join(worktree, DOCS_DIR);
57
+ if (!fs.existsSync(docsRoot)) {
58
+ return { exists: false, count: 0 };
59
+ }
60
+ let count = 0;
61
+ const subDirs = ["decisions", "features", "flows"];
62
+ for (const sub of subDirs) {
63
+ const subPath = path.join(docsRoot, sub);
64
+ if (fs.existsSync(subPath)) {
65
+ const files = fs.readdirSync(subPath).filter((f) => f.endsWith(".md") && f !== ".gitkeep");
66
+ count += files.length;
67
+ }
68
+ }
69
+ return { exists: count > 0, count };
70
+ }
71
+ /**
72
+ * Get a summary of commits that will be in the PR (commits ahead of base).
73
+ */
74
+ async function getCommitLog(cwd, baseBranch) {
75
+ try {
76
+ const { stdout } = await git(cwd, "log", `${baseBranch}..HEAD`, "--oneline");
77
+ return stdout.trim();
78
+ }
79
+ catch {
80
+ // If base branch doesn't exist locally, try origin/<base>
81
+ try {
82
+ const { stdout } = await git(cwd, "log", `origin/${baseBranch}..HEAD`, "--oneline");
83
+ return stdout.trim();
84
+ }
85
+ catch {
86
+ return "";
87
+ }
88
+ }
89
+ }
90
+ // ─── Tool ────────────────────────────────────────────────────────────────────
91
+ export const finalize = tool({
92
+ description: "Finalize a completed task: stage all changes, commit, push to origin, and create a PR via GitHub CLI. " +
93
+ "Auto-detects worktrees and targets the main branch. " +
94
+ "Auto-populates the PR body from .cortex/plans/ if a plan exists. " +
95
+ "Run docs_list and session_save BEFORE calling this tool.",
96
+ args: {
97
+ commitMessage: tool.schema
98
+ .string()
99
+ .describe("Commit message in conventional format (e.g., 'feat: add worktree launch tool')"),
100
+ prTitle: tool.schema
101
+ .string()
102
+ .optional()
103
+ .describe("PR title (defaults to the commit message)"),
104
+ prBody: tool.schema
105
+ .string()
106
+ .optional()
107
+ .describe("Custom PR body in markdown. If omitted, auto-generated from .cortex/plans/ or commit log."),
108
+ baseBranch: tool.schema
109
+ .string()
110
+ .optional()
111
+ .describe("Target branch for PR (auto-detected: 'main' for worktrees, default branch otherwise)"),
112
+ planFilename: tool.schema
113
+ .string()
114
+ .optional()
115
+ .describe("Plan filename from .cortex/plans/ to include in PR body"),
116
+ draft: tool.schema
117
+ .boolean()
118
+ .optional()
119
+ .describe("Create as draft PR (default: false)"),
120
+ },
121
+ async execute(args, context) {
122
+ const { commitMessage, prTitle, prBody: customPrBody, baseBranch: customBaseBranch, planFilename, draft = false, } = args;
123
+ const cwd = context.worktree;
124
+ const output = [];
125
+ const warnings = [];
126
+ // ── 1. Validate: git repo ─────────────────────────────────
127
+ try {
128
+ await git(cwd, "rev-parse", "--git-dir");
129
+ }
130
+ catch {
131
+ return "✗ Error: Not in a git repository.";
132
+ }
133
+ // ── 2. Detect worktree + branch ───────────────────────────
134
+ const wtInfo = await detectWorktreeInfo(cwd);
135
+ const branchName = wtInfo.currentBranch;
136
+ if (!branchName || branchName === "(unknown)") {
137
+ return "✗ Error: Could not determine current branch.";
138
+ }
139
+ if (PROTECTED_BRANCHES.includes(branchName)) {
140
+ return `✗ Error: Cannot finalize on protected branch '${branchName}'.
141
+ Create a feature/bugfix branch first with branch_create or worktree_create.`;
142
+ }
143
+ // ── 3. Determine base branch ──────────────────────────────
144
+ let baseBranch = customBaseBranch || "";
145
+ if (!baseBranch) {
146
+ if (wtInfo.isWorktree) {
147
+ baseBranch = "main";
148
+ }
149
+ else {
150
+ // Try to detect default branch from origin
151
+ try {
152
+ const { stdout } = await git(cwd, "symbolic-ref", "refs/remotes/origin/HEAD");
153
+ baseBranch = stdout.trim().replace("refs/remotes/origin/", "");
154
+ }
155
+ catch {
156
+ baseBranch = "main"; // Sensible default
157
+ }
158
+ }
159
+ }
160
+ output.push(`Branch: ${branchName} → ${baseBranch}`);
161
+ if (wtInfo.isWorktree) {
162
+ output.push(`Worktree detected (main tree: ${wtInfo.mainWorktreePath})`);
163
+ }
164
+ // ── 4. Check prerequisites ────────────────────────────────
165
+ const ghCheck = await checkGhCli(cwd);
166
+ if (!ghCheck.ok) {
167
+ return `✗ ${ghCheck.error}`;
168
+ }
169
+ const remoteCheck = await checkRemote(cwd);
170
+ if (!remoteCheck.ok) {
171
+ return `✗ ${remoteCheck.error}`;
172
+ }
173
+ // ── 5. Check docs (warning only) ─────────────────────────
174
+ const docsCheck = checkDocsExist(cwd);
175
+ if (!docsCheck.exists) {
176
+ warnings.push("No documentation found in docs/. Consider creating docs with docs_save before finalizing.");
177
+ }
178
+ else {
179
+ output.push(`Documentation: ${docsCheck.count} doc(s) found`);
180
+ }
181
+ // ── 6. Stage all changes ──────────────────────────────────
182
+ try {
183
+ await git(cwd, "add", "-A");
184
+ }
185
+ catch (error) {
186
+ return `✗ Error staging changes: ${error.message || error}`;
187
+ }
188
+ // ── 7. Commit ─────────────────────────────────────────────
189
+ let commitHash = "";
190
+ let commitSkipped = false;
191
+ try {
192
+ // Check if there's anything to commit
193
+ const { stdout: statusOut } = await git(cwd, "status", "--porcelain");
194
+ if (!statusOut.trim()) {
195
+ commitSkipped = true;
196
+ output.push("No new changes to commit (working tree clean)");
197
+ }
198
+ else {
199
+ await git(cwd, "commit", "-m", commitMessage);
200
+ const { stdout: hashOut } = await git(cwd, "rev-parse", "--short", "HEAD");
201
+ commitHash = hashOut.trim();
202
+ output.push(`Committed: ${commitHash} — ${commitMessage}`);
203
+ }
204
+ }
205
+ catch (error) {
206
+ return `✗ Error committing: ${error.message || error}`;
207
+ }
208
+ // ── 8. Push to origin ─────────────────────────────────────
209
+ try {
210
+ await git(cwd, "push", "-u", "origin", branchName);
211
+ output.push(`Pushed to origin/${branchName}`);
212
+ }
213
+ catch (error) {
214
+ return `✗ Error pushing to origin: ${error.message || error}
215
+
216
+ All previous steps succeeded (changes committed). Try pushing manually:
217
+ git push -u origin ${branchName}`;
218
+ }
219
+ // ── 9. Build PR body ──────────────────────────────────────
220
+ let prBodyContent = customPrBody || "";
221
+ if (!prBodyContent) {
222
+ // Try to build from plan
223
+ const plan = findPlanContent(cwd, planFilename, branchName);
224
+ if (plan) {
225
+ const sections = extractPlanSections(plan.content, plan.filename);
226
+ prBodyContent = buildPrBodyFromPlan(sections);
227
+ output.push(`PR body generated from plan: ${plan.filename}`);
228
+ }
229
+ else {
230
+ // Fall back to commit log
231
+ const commitLog = await getCommitLog(cwd, baseBranch);
232
+ if (commitLog) {
233
+ prBodyContent = `## Changes\n\n\`\`\`\n${commitLog}\n\`\`\``;
234
+ }
235
+ else {
236
+ prBodyContent = `Implementation on branch \`${branchName}\``;
237
+ }
238
+ }
239
+ }
240
+ // ── 10. Create PR via gh ──────────────────────────────────
241
+ const finalPrTitle = prTitle || commitMessage;
242
+ let prUrl = "";
243
+ try {
244
+ // Check if PR already exists for this branch
245
+ const { stdout: existingPr } = await gh(cwd, "pr", "view", branchName, "--json", "url", "--jq", ".url");
246
+ if (existingPr.trim()) {
247
+ prUrl = existingPr.trim();
248
+ output.push(`PR already exists: ${prUrl}`);
249
+ }
250
+ else {
251
+ prUrl = await createPr(cwd, baseBranch, finalPrTitle, prBodyContent, draft);
252
+ output.push(`PR created: ${prUrl}`);
253
+ }
254
+ }
255
+ catch {
256
+ // PR doesn't exist yet, create it
257
+ try {
258
+ prUrl = await createPr(cwd, baseBranch, finalPrTitle, prBodyContent, draft);
259
+ output.push(`PR created: ${prUrl}`);
260
+ }
261
+ catch (error) {
262
+ // PR creation failed but everything else succeeded
263
+ output.push(`⚠ PR creation failed: ${error.message || error}`);
264
+ output.push("");
265
+ output.push("Changes are committed and pushed. Create the PR manually:");
266
+ output.push(` gh pr create --base ${baseBranch} --title "${finalPrTitle}"`);
267
+ }
268
+ }
269
+ // ── Build final output ────────────────────────────────────
270
+ let finalOutput = `✓ Task finalized\n\n`;
271
+ finalOutput += output.join("\n");
272
+ if (warnings.length > 0) {
273
+ finalOutput += `\n\nWarnings:\n${warnings.map((w) => ` ⚠ ${w}`).join("\n")}`;
274
+ }
275
+ if (wtInfo.isWorktree) {
276
+ finalOutput += `\n\nThis is a worktree. When the PR is merged, you can clean up with:
277
+ worktree_remove (name: "${path.basename(cwd)}", deleteBranch: true)`;
278
+ }
279
+ return finalOutput;
280
+ },
281
+ });
282
+ /**
283
+ * Create a PR using gh CLI with array-based args (no shell injection).
284
+ * Uses a temp body file to avoid shell escaping issues with PR body content.
285
+ */
286
+ async function createPr(cwd, baseBranch, title, body, draft) {
287
+ const bodyFile = path.join(cwd, ".cortex", ".pr-body-tmp.md");
288
+ const cortexDir = path.join(cwd, ".cortex");
289
+ if (!fs.existsSync(cortexDir)) {
290
+ fs.mkdirSync(cortexDir, { recursive: true });
291
+ }
292
+ fs.writeFileSync(bodyFile, body);
293
+ try {
294
+ const createArgs = [
295
+ "pr", "create",
296
+ "--base", baseBranch,
297
+ "--title", title,
298
+ "--body-file", bodyFile,
299
+ ];
300
+ if (draft)
301
+ createArgs.push("--draft");
302
+ const { stdout } = await gh(cwd, ...createArgs);
303
+ return stdout.trim();
304
+ }
305
+ finally {
306
+ if (fs.existsSync(bodyFile)) {
307
+ fs.unlinkSync(bodyFile);
308
+ }
309
+ }
310
+ }
@@ -1,4 +1,11 @@
1
- export declare const create: {
1
+ import type { PluginInput } from "@opencode-ai/plugin";
2
+ type Client = PluginInput["client"];
3
+ type Shell = PluginInput["$"];
4
+ /**
5
+ * Factory function that creates the worktree_create tool with access
6
+ * to the OpenCode client for toast notifications.
7
+ */
8
+ export declare function createCreate(client: Client): {
2
9
  description: string;
3
10
  args: {
4
11
  name: import("zod").ZodString;
@@ -22,7 +29,11 @@ export declare const list: {
22
29
  args: {};
23
30
  execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
24
31
  };
25
- export declare const remove: {
32
+ /**
33
+ * Factory function that creates the worktree_remove tool with access
34
+ * to the OpenCode client for toast notifications and PTY cleanup.
35
+ */
36
+ export declare function createRemove(client: Client): {
26
37
  description: string;
27
38
  args: {
28
39
  name: import("zod").ZodString;
@@ -42,4 +53,33 @@ export declare const open: {
42
53
  name: string;
43
54
  }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
44
55
  };
56
+ /**
57
+ * Factory function that creates the worktree_launch tool with access
58
+ * to the OpenCode client (for PTY and toast) and shell.
59
+ *
60
+ * This uses a closure to capture `client` and `shell` since ToolContext
61
+ * does not provide access to the OpenCode client API.
62
+ */
63
+ export declare function createLaunch(client: Client, shell: Shell): {
64
+ description: string;
65
+ args: {
66
+ name: import("zod").ZodString;
67
+ mode: import("zod").ZodEnum<{
68
+ terminal: "terminal";
69
+ pty: "pty";
70
+ background: "background";
71
+ }>;
72
+ plan: import("zod").ZodOptional<import("zod").ZodString>;
73
+ agent: import("zod").ZodOptional<import("zod").ZodString>;
74
+ prompt: import("zod").ZodOptional<import("zod").ZodString>;
75
+ };
76
+ execute(args: {
77
+ name: string;
78
+ mode: "terminal" | "pty" | "background";
79
+ plan?: string | undefined;
80
+ agent?: string | undefined;
81
+ prompt?: string | undefined;
82
+ }, context: import("@opencode-ai/plugin").ToolContext): Promise<string>;
83
+ };
84
+ export {};
45
85
  //# sourceMappingURL=worktree.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/tools/worktree.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;CA8DjB,CAAC;AAEH,eAAO,MAAM,IAAI;;;;CAiCf,CAAC;AAEH,eAAO,MAAM,MAAM;;;;;;;;;;CA4DjB,CAAC;AAEH,eAAO,MAAM,IAAI;;;;;;;;CAgDf,CAAC"}
1
+ {"version":3,"file":"worktree.d.ts","sourceRoot":"","sources":["../../src/tools/worktree.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAiBvD,KAAK,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;AACpC,KAAK,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;AAE9B;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;;;;;;;;;EAuF1C;AAED,eAAO,MAAM,IAAI;;;;CAiCf,CAAC;AAEH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM;;;;;;;;;;EAkI1C;AAED,eAAO,MAAM,IAAI;;;;;;;;CAgDf,CAAC;AAkUH;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK;;;;;;;;;;;;;;;;;;;;EAsIxD"}