cortex-agents 2.2.0 → 2.3.1
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/.opencode/agents/build.md +118 -70
- package/.opencode/agents/debug.md +132 -19
- package/.opencode/agents/devops.md +213 -72
- package/.opencode/agents/fullstack.md +183 -48
- package/.opencode/agents/plan.md +79 -4
- package/.opencode/agents/review.md +314 -0
- package/.opencode/agents/security.md +166 -53
- package/.opencode/agents/testing.md +215 -38
- package/README.md +98 -34
- package/dist/cli.js +209 -50
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +174 -8
- package/dist/registry.d.ts +2 -2
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +1 -1
- package/dist/tools/branch.d.ts +7 -1
- package/dist/tools/branch.d.ts.map +1 -1
- package/dist/tools/branch.js +88 -53
- package/dist/tools/cortex.d.ts +19 -0
- package/dist/tools/cortex.d.ts.map +1 -1
- package/dist/tools/cortex.js +109 -0
- package/dist/tools/session.d.ts.map +1 -1
- package/dist/tools/session.js +3 -1
- package/dist/tools/task.d.ts.map +1 -1
- package/dist/tools/task.js +65 -57
- package/dist/tools/worktree.d.ts +10 -2
- package/dist/tools/worktree.d.ts.map +1 -1
- package/dist/tools/worktree.js +320 -246
- package/dist/utils/shell.d.ts +53 -0
- package/dist/utils/shell.d.ts.map +1 -0
- package/dist/utils/shell.js +118 -0
- package/dist/utils/terminal.d.ts +66 -0
- package/dist/utils/terminal.d.ts.map +1 -0
- package/dist/utils/terminal.js +627 -0
- package/dist/utils/worktree-detect.d.ts.map +1 -1
- package/dist/utils/worktree-detect.js +5 -4
- package/package.json +5 -4
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import * as plan from "./tools/plan";
|
|
|
6
6
|
import * as session from "./tools/session";
|
|
7
7
|
import * as docs from "./tools/docs";
|
|
8
8
|
import * as task from "./tools/task";
|
|
9
|
-
// Agent
|
|
9
|
+
// ─── Agent Descriptions (for handover toasts) ───────────────────────────────
|
|
10
10
|
const AGENT_DESCRIPTIONS = {
|
|
11
11
|
build: "Development mode — ready to implement",
|
|
12
12
|
plan: "Planning mode — read-only analysis",
|
|
@@ -16,21 +16,110 @@ const AGENT_DESCRIPTIONS = {
|
|
|
16
16
|
security: "Security subagent — vulnerability audit",
|
|
17
17
|
devops: "DevOps subagent — CI/CD and deployment",
|
|
18
18
|
};
|
|
19
|
+
const TOOL_NOTIFICATIONS = {
|
|
20
|
+
task_finalize: {
|
|
21
|
+
successTitle: "Task Finalized",
|
|
22
|
+
successMsg: (args) => `Committed & pushed: ${(args.commitMessage ?? "").substring(0, 50)}`,
|
|
23
|
+
errorTitle: "Finalization Failed",
|
|
24
|
+
errorMsg: (_, out) => out
|
|
25
|
+
.replace(/^✗\s*/, "")
|
|
26
|
+
.split("\n")[0]
|
|
27
|
+
.substring(0, 100),
|
|
28
|
+
successDuration: 5000,
|
|
29
|
+
errorDuration: 10000,
|
|
30
|
+
},
|
|
31
|
+
plan_save: {
|
|
32
|
+
successTitle: "Plan Saved",
|
|
33
|
+
successMsg: (args) => args.title ?? "Plan saved",
|
|
34
|
+
errorTitle: "Plan Save Failed",
|
|
35
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
36
|
+
},
|
|
37
|
+
plan_delete: {
|
|
38
|
+
successTitle: "Plan Deleted",
|
|
39
|
+
successMsg: (args) => args.filename ?? "Plan deleted",
|
|
40
|
+
errorTitle: "Plan Delete Failed",
|
|
41
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
42
|
+
},
|
|
43
|
+
session_save: {
|
|
44
|
+
successTitle: "Session Saved",
|
|
45
|
+
successMsg: () => "Session summary recorded",
|
|
46
|
+
errorTitle: "Session Save Failed",
|
|
47
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
48
|
+
},
|
|
49
|
+
docs_save: {
|
|
50
|
+
successTitle: "Documentation Saved",
|
|
51
|
+
successMsg: (args) => `${args.type ?? "doc"}: ${args.title ?? "Untitled"}`,
|
|
52
|
+
errorTitle: "Doc Save Failed",
|
|
53
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
54
|
+
},
|
|
55
|
+
docs_init: {
|
|
56
|
+
successTitle: "Docs Initialized",
|
|
57
|
+
successMsg: () => "Documentation directory created",
|
|
58
|
+
errorTitle: "Docs Init Failed",
|
|
59
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
60
|
+
},
|
|
61
|
+
cortex_init: {
|
|
62
|
+
successTitle: "Project Initialized",
|
|
63
|
+
successMsg: () => ".cortex directory created",
|
|
64
|
+
errorTitle: "Init Failed",
|
|
65
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
66
|
+
},
|
|
67
|
+
cortex_configure: {
|
|
68
|
+
successTitle: "Models Configured",
|
|
69
|
+
successMsg: (args) => `Primary: ${args.primaryModel?.split("/").pop() || "set"}`,
|
|
70
|
+
errorTitle: "Configure Failed",
|
|
71
|
+
errorMsg: (_, out) => out.substring(0, 100),
|
|
72
|
+
},
|
|
73
|
+
branch_switch: {
|
|
74
|
+
successTitle: "Branch Switched",
|
|
75
|
+
successMsg: (args) => `Now on ${args.branch ?? "branch"}`,
|
|
76
|
+
errorTitle: "Branch Switch Failed",
|
|
77
|
+
errorMsg: (_, out) => out
|
|
78
|
+
.replace(/^✗\s*/, "")
|
|
79
|
+
.split("\n")[0]
|
|
80
|
+
.substring(0, 100),
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
// ─── Error Message Extraction ────────────────────────────────────────────────
|
|
84
|
+
//
|
|
85
|
+
// Extracts a human-readable message from the session error union type
|
|
86
|
+
// (ProviderAuthError | UnknownError | MessageOutputLengthError |
|
|
87
|
+
// MessageAbortedError | ApiError).
|
|
88
|
+
function extractErrorMessage(error) {
|
|
89
|
+
if (!error)
|
|
90
|
+
return "An unknown error occurred";
|
|
91
|
+
const msg = typeof error.data?.message === "string" ? error.data.message : "";
|
|
92
|
+
switch (error.name) {
|
|
93
|
+
case "ProviderAuthError":
|
|
94
|
+
return `Auth error: ${msg || "Provider authentication failed"}`;
|
|
95
|
+
case "UnknownError":
|
|
96
|
+
return msg || "An unknown error occurred";
|
|
97
|
+
case "MessageOutputLengthError":
|
|
98
|
+
return "Output length exceeded — try compacting the session";
|
|
99
|
+
case "MessageAbortedError":
|
|
100
|
+
return `Aborted: ${msg || "Message was aborted"}`;
|
|
101
|
+
case "APIError":
|
|
102
|
+
return `API error: ${msg || "Request failed"}`;
|
|
103
|
+
default:
|
|
104
|
+
return `Error: ${error.name}`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// ─── Plugin Entry ────────────────────────────────────────────────────────────
|
|
19
108
|
export const CortexPlugin = async (ctx) => {
|
|
20
109
|
return {
|
|
21
110
|
tool: {
|
|
22
111
|
// Cortex tools - .cortex directory management
|
|
23
112
|
cortex_init: cortex.init,
|
|
24
113
|
cortex_status: cortex.status,
|
|
25
|
-
|
|
26
|
-
|
|
114
|
+
cortex_configure: cortex.configure,
|
|
115
|
+
// Worktree tools - git worktree management (factories for toast notifications)
|
|
116
|
+
worktree_create: worktree.createCreate(ctx.client),
|
|
27
117
|
worktree_list: worktree.list,
|
|
28
|
-
worktree_remove: worktree.
|
|
118
|
+
worktree_remove: worktree.createRemove(ctx.client),
|
|
29
119
|
worktree_open: worktree.open,
|
|
30
|
-
// Dynamic tool: needs client + shell via factory (closure injection)
|
|
31
120
|
worktree_launch: worktree.createLaunch(ctx.client, ctx.$),
|
|
32
|
-
// Branch tools - git branch operations
|
|
33
|
-
branch_create: branch.
|
|
121
|
+
// Branch tools - git branch operations (factory for toast notifications)
|
|
122
|
+
branch_create: branch.createCreate(ctx.client),
|
|
34
123
|
branch_status: branch.status,
|
|
35
124
|
branch_switch: branch.switch_,
|
|
36
125
|
// Plan tools - implementation plan persistence
|
|
@@ -50,9 +139,49 @@ export const CortexPlugin = async (ctx) => {
|
|
|
50
139
|
// Task tools - finalize workflow (commit, push, PR)
|
|
51
140
|
task_finalize: task.finalize,
|
|
52
141
|
},
|
|
53
|
-
//
|
|
142
|
+
// ── Post-execution toast notifications ────────────────────────────────
|
|
143
|
+
//
|
|
144
|
+
// Fires after every tool execution. Uses the TOOL_NOTIFICATIONS map
|
|
145
|
+
// to determine which tools get toasts and what content to display.
|
|
146
|
+
// Tools with existing factory-based toasts are excluded from the map.
|
|
147
|
+
"tool.execute.after": async (input, output) => {
|
|
148
|
+
const config = TOOL_NOTIFICATIONS[input.tool];
|
|
149
|
+
if (!config)
|
|
150
|
+
return; // No notification configured for this tool
|
|
151
|
+
try {
|
|
152
|
+
const result = output.output;
|
|
153
|
+
const isSuccess = result.startsWith("✓");
|
|
154
|
+
const isError = result.startsWith("✗");
|
|
155
|
+
if (isSuccess) {
|
|
156
|
+
await ctx.client.tui.showToast({
|
|
157
|
+
body: {
|
|
158
|
+
title: config.successTitle,
|
|
159
|
+
message: config.successMsg(input.args, result),
|
|
160
|
+
variant: "success",
|
|
161
|
+
duration: config.successDuration ?? 4000,
|
|
162
|
+
},
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
else if (isError) {
|
|
166
|
+
await ctx.client.tui.showToast({
|
|
167
|
+
body: {
|
|
168
|
+
title: config.errorTitle,
|
|
169
|
+
message: config.errorMsg(input.args, result),
|
|
170
|
+
variant: "error",
|
|
171
|
+
duration: config.errorDuration ?? 8000,
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
// Informational or warning outputs (⚠) — no toast to avoid noise
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Toast failure is non-fatal
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
// ── Event-driven notifications ───────────────────────────────────────
|
|
54
182
|
async event({ event }) {
|
|
55
183
|
try {
|
|
184
|
+
// Agent handover notifications
|
|
56
185
|
if (event.type === "message.part.updated" &&
|
|
57
186
|
"part" in event.properties &&
|
|
58
187
|
event.properties.part.type === "agent") {
|
|
@@ -67,6 +196,43 @@ export const CortexPlugin = async (ctx) => {
|
|
|
67
196
|
},
|
|
68
197
|
});
|
|
69
198
|
}
|
|
199
|
+
// Session error notifications
|
|
200
|
+
if (event.type === "session.error") {
|
|
201
|
+
const rawError = event.properties.error;
|
|
202
|
+
// Runtime validation before cast — ensure error has expected shape
|
|
203
|
+
const error = rawError &&
|
|
204
|
+
typeof rawError === "object" &&
|
|
205
|
+
"name" in rawError &&
|
|
206
|
+
typeof rawError.name === "string"
|
|
207
|
+
? rawError
|
|
208
|
+
: undefined;
|
|
209
|
+
const message = extractErrorMessage(error);
|
|
210
|
+
await ctx.client.tui.showToast({
|
|
211
|
+
body: {
|
|
212
|
+
title: "Session Error",
|
|
213
|
+
message,
|
|
214
|
+
variant: "error",
|
|
215
|
+
duration: 10000,
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
// PTY exited notifications (relevant for worktree terminal sessions)
|
|
220
|
+
if (event.type === "pty.exited") {
|
|
221
|
+
const rawExitCode = event.properties.exitCode;
|
|
222
|
+
const exitCode = typeof rawExitCode === "number" && Number.isInteger(rawExitCode)
|
|
223
|
+
? rawExitCode
|
|
224
|
+
: -1;
|
|
225
|
+
await ctx.client.tui.showToast({
|
|
226
|
+
body: {
|
|
227
|
+
title: "Terminal Exited",
|
|
228
|
+
message: exitCode === 0
|
|
229
|
+
? "Terminal session completed successfully"
|
|
230
|
+
: `Terminal exited with code ${exitCode}`,
|
|
231
|
+
variant: exitCode === 0 ? "success" : "warning",
|
|
232
|
+
duration: exitCode === 0 ? 4000 : 8000,
|
|
233
|
+
},
|
|
234
|
+
});
|
|
235
|
+
}
|
|
70
236
|
}
|
|
71
237
|
catch {
|
|
72
238
|
// Toast failure is non-fatal — silently ignore
|
package/dist/registry.d.ts
CHANGED
|
@@ -19,11 +19,11 @@ export interface ModelEntry {
|
|
|
19
19
|
}
|
|
20
20
|
export declare const MODEL_REGISTRY: ModelEntry[];
|
|
21
21
|
/** Primary agents receive the best available model */
|
|
22
|
-
export declare const PRIMARY_AGENTS: readonly ["build", "plan", "debug"];
|
|
22
|
+
export declare const PRIMARY_AGENTS: readonly ["build", "plan", "debug", "review"];
|
|
23
23
|
/** Subagents receive a fast/cost-effective model */
|
|
24
24
|
export declare const SUBAGENTS: readonly ["fullstack", "testing", "security", "devops"];
|
|
25
25
|
/** All agent names combined */
|
|
26
|
-
export declare const ALL_AGENTS: readonly ["build", "plan", "debug", "fullstack", "testing", "security", "devops"];
|
|
26
|
+
export declare const ALL_AGENTS: readonly ["build", "plan", "debug", "review", "fullstack", "testing", "security", "devops"];
|
|
27
27
|
/**
|
|
28
28
|
* Build the interactive choices list for primary model selection.
|
|
29
29
|
* Shows premium and standard tier models (excluding fast).
|
package/dist/registry.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,UAAU,EAuGtC,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,cAAc,
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,UAAU;IACzB,uDAAuD;IACvD,EAAE,EAAE,MAAM,CAAC;IACX,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,IAAI,EAAE,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;IACtC,oDAAoD;IACpD,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,eAAO,MAAM,cAAc,EAAE,UAAU,EAuGtC,CAAC;AAEF,sDAAsD;AACtD,eAAO,MAAM,cAAc,+CAAgD,CAAC;AAE5E,oDAAoD;AACpD,eAAO,MAAM,SAAS,yDAA0D,CAAC;AAEjF,+BAA+B;AAC/B,eAAO,MAAM,UAAU,6FAA6C,CAAC;AAErE;;;GAGG;AACH,wBAAgB,iBAAiB;;;;IAMhC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,cAAc,EAAE,MAAM;;;;IAcxD"}
|
package/dist/registry.js
CHANGED
|
@@ -105,7 +105,7 @@ export const MODEL_REGISTRY = [
|
|
|
105
105
|
},
|
|
106
106
|
];
|
|
107
107
|
/** Primary agents receive the best available model */
|
|
108
|
-
export const PRIMARY_AGENTS = ["build", "plan", "debug"];
|
|
108
|
+
export const PRIMARY_AGENTS = ["build", "plan", "debug", "review"];
|
|
109
109
|
/** Subagents receive a fast/cost-effective model */
|
|
110
110
|
export const SUBAGENTS = ["fullstack", "testing", "security", "devops"];
|
|
111
111
|
/** All agent names combined */
|
package/dist/tools/branch.d.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
|
|
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":"
|
|
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"}
|
package/dist/tools/branch.js
CHANGED
|
@@ -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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
80
|
-
const lines =
|
|
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
|
|
83
|
-
if (
|
|
117
|
+
const st = line.substring(0, 2);
|
|
118
|
+
if (st[0] !== " " && st[0] !== "?") {
|
|
84
119
|
stagedChanges = true;
|
|
85
120
|
}
|
|
86
|
-
if (
|
|
121
|
+
if (st[1] !== " " && st[1] !== "?") {
|
|
87
122
|
hasChanges = true;
|
|
88
123
|
}
|
|
89
|
-
if (
|
|
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
|
|
100
|
-
const [ahead, behind] =
|
|
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
|
|
150
|
-
if (!
|
|
184
|
+
const { stdout } = await git(context.worktree, "branch", "--list", branch);
|
|
185
|
+
if (!stdout.trim()) {
|
|
151
186
|
// Try remote branch
|
|
152
|
-
const remoteBranches = await
|
|
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
|
|
200
|
+
await git(context.worktree, "checkout", branch);
|
|
166
201
|
}
|
|
167
202
|
catch (error) {
|
|
168
203
|
return `✗ Error switching branch: ${error.message || error}
|
package/dist/tools/cortex.d.ts
CHANGED
|
@@ -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":"
|
|
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"}
|
package/dist/tools/cortex.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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",
|
|
@@ -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":"
|
|
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"}
|
package/dist/tools/session.js
CHANGED
|
@@ -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
|
-
|
|
55
|
+
const { stdout } = await git(context.worktree, "branch", "--show-current");
|
|
56
|
+
currentBranch = stdout.trim();
|
|
55
57
|
}
|
|
56
58
|
catch {
|
|
57
59
|
currentBranch = "unknown";
|
package/dist/tools/task.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAyGA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../../src/tools/task.ts"],"names":[],"mappings":"AAyGA,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;CAqNnB,CAAC"}
|