nexus-dev-toolkit 3.1.0__tar.gz → 3.1.2__tar.gz

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 (39) hide show
  1. {nexus_dev_toolkit-3.1.0/nexus_dev_toolkit.egg-info → nexus_dev_toolkit-3.1.2}/PKG-INFO +30 -15
  2. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/README.md +29 -14
  3. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_cli.py +257 -16
  4. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2/nexus_dev_toolkit.egg-info}/PKG-INFO +30 -15
  5. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/pyproject.toml +1 -1
  6. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tests/test_cli.py +47 -0
  7. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/LICENSE +0 -0
  8. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/SOURCES.txt +0 -0
  9. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/dependency_links.txt +0 -0
  10. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/entry_points.txt +0 -0
  11. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/requires.txt +0 -0
  12. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_dev_toolkit.egg-info/top_level.txt +0 -0
  13. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/nexus_server.py +0 -0
  14. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/setup.cfg +0 -0
  15. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tests/test_server.py +0 -0
  16. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/__init__.py +0 -0
  17. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/__init__.py +0 -0
  18. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/code-reviewer.md +0 -0
  19. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/database-reviewer.md +0 -0
  20. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/deployment-reviewer.md +0 -0
  21. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/monitoring-reviewer.md +0 -0
  22. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/agents/performance-reviewer.md +0 -0
  23. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/__init__.py +0 -0
  24. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/arch_ingest.py +0 -0
  25. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/package_resolver.py +0 -0
  26. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/project_rules.py +0 -0
  27. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/__init__.py +0 -0
  28. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/apply.md +0 -0
  29. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/code-review.md +0 -0
  30. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/database-review.md +0 -0
  31. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/deployment-review.md +0 -0
  32. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/epav.md +0 -0
  33. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/evaluate.md +0 -0
  34. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/monitoring-review.md +0 -0
  35. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/performance-review.md +0 -0
  36. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/plan.md +0 -0
  37. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/scaffold.md +0 -0
  38. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/skills/validate.md +0 -0
  39. {nexus_dev_toolkit-3.1.0 → nexus_dev_toolkit-3.1.2}/tools/epav/task_loader.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nexus-dev-toolkit
3
- Version: 3.1.0
3
+ Version: 3.1.2
4
4
  Summary: Developer workflow toolkit for Claude Code — Day 0 scaffold + Day 1 EPAV
5
5
  Author-email: Ronald dela Cruz <rcdelacruz@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -80,7 +80,8 @@ uv tool install nexus-dev-toolkit
80
80
 
81
81
  ```bash
82
82
  cd my-project
83
- nexus init .
83
+ nexus init . # Claude Code (default)
84
+ nexus init . --tool opencode # OpenCode
84
85
  ```
85
86
 
86
87
  ### 4. Place your reference docs
@@ -137,15 +138,11 @@ Each step is a built-in skill in `.claude/commands/`. Every task starts from the
137
138
 
138
139
  ## What `nexus init` Creates
139
140
 
141
+ **Claude Code** (`nexus init .`):
140
142
  ```
141
143
  .claude/
142
- ├── commands/
143
- │ ├── scaffold.md ← /scaffold — Day 0 one-time setup
144
- │ ├── evaluate.md ← /evaluate — orient on a task
145
- │ ├── plan.md ← /plan — blueprint, no code
146
- │ ├── apply.md ← /apply — implement the plan
147
- │ ├── validate.md ← /validate — verify acceptance criteria
148
- │ └── epav.md ← /epav — full cycle guide
144
+ ├── commands/ ← EPAV skills + 5 reviewer skills (/code-review, etc.)
145
+ ├── agents/ ← 5 reviewer subagents (code-reviewer, database-reviewer, etc.)
149
146
  └── settings.json ← PostToolUse: graphify auto-updates after every file edit
150
147
  knowledge/
151
148
  ├── rules/ ← coding standards, arch decisions
@@ -155,17 +152,35 @@ knowledge/
155
152
  .mcp.json ← MCP server config
156
153
  ```
157
154
 
155
+ **OpenCode** (`nexus init . --tool opencode`):
156
+ ```
157
+ .opencode/
158
+ ├── commands/ ← same EPAV skills + reviewer skills
159
+ ├── agents/ ← same reviewer subagents (adapted for OpenCode)
160
+ └── plugins/
161
+ └── graphify.js ← tool.execute.after: graphify auto-update
162
+ knowledge/ ← same structure
163
+ opencode.json ← MCP server config
164
+ ```
165
+
158
166
  ---
159
167
 
160
168
  ## Commands
161
169
 
162
170
  ```bash
163
- nexus init . # set up .claude/commands/ + knowledge/ + .mcp.json
164
- nexus skill add code-review # create a custom skill in .claude/commands/
165
- nexus skill list # list all skills
166
- nexus rule add api-standards # create a rule in knowledge/rules/
167
- nexus rule list # list all rules
168
- nexus update # update to latest version
171
+ nexus init . # Claude Code — set up .claude/ + knowledge/ + .mcp.json
172
+ nexus init . --tool opencode # OpenCode — set up .opencode/ + knowledge/ + opencode.json
173
+ nexus --version # show installed version
174
+ nexus update # update to latest version
175
+
176
+ nexus skill list # list all skills in .claude/commands/
177
+ nexus skill add my-review # create a custom skill
178
+
179
+ nexus agent list # list all subagents in .claude/agents/
180
+ nexus agent add my-agent # create a custom subagent
181
+
182
+ nexus rule list # list all rules in knowledge/rules/
183
+ nexus rule add api-standards # create a project rule
169
184
  ```
170
185
 
171
186
  ---
@@ -59,7 +59,8 @@ uv tool install nexus-dev-toolkit
59
59
 
60
60
  ```bash
61
61
  cd my-project
62
- nexus init .
62
+ nexus init . # Claude Code (default)
63
+ nexus init . --tool opencode # OpenCode
63
64
  ```
64
65
 
65
66
  ### 4. Place your reference docs
@@ -116,15 +117,11 @@ Each step is a built-in skill in `.claude/commands/`. Every task starts from the
116
117
 
117
118
  ## What `nexus init` Creates
118
119
 
120
+ **Claude Code** (`nexus init .`):
119
121
  ```
120
122
  .claude/
121
- ├── commands/
122
- │ ├── scaffold.md ← /scaffold — Day 0 one-time setup
123
- │ ├── evaluate.md ← /evaluate — orient on a task
124
- │ ├── plan.md ← /plan — blueprint, no code
125
- │ ├── apply.md ← /apply — implement the plan
126
- │ ├── validate.md ← /validate — verify acceptance criteria
127
- │ └── epav.md ← /epav — full cycle guide
123
+ ├── commands/ ← EPAV skills + 5 reviewer skills (/code-review, etc.)
124
+ ├── agents/ ← 5 reviewer subagents (code-reviewer, database-reviewer, etc.)
128
125
  └── settings.json ← PostToolUse: graphify auto-updates after every file edit
129
126
  knowledge/
130
127
  ├── rules/ ← coding standards, arch decisions
@@ -134,17 +131,35 @@ knowledge/
134
131
  .mcp.json ← MCP server config
135
132
  ```
136
133
 
134
+ **OpenCode** (`nexus init . --tool opencode`):
135
+ ```
136
+ .opencode/
137
+ ├── commands/ ← same EPAV skills + reviewer skills
138
+ ├── agents/ ← same reviewer subagents (adapted for OpenCode)
139
+ └── plugins/
140
+ └── graphify.js ← tool.execute.after: graphify auto-update
141
+ knowledge/ ← same structure
142
+ opencode.json ← MCP server config
143
+ ```
144
+
137
145
  ---
138
146
 
139
147
  ## Commands
140
148
 
141
149
  ```bash
142
- nexus init . # set up .claude/commands/ + knowledge/ + .mcp.json
143
- nexus skill add code-review # create a custom skill in .claude/commands/
144
- nexus skill list # list all skills
145
- nexus rule add api-standards # create a rule in knowledge/rules/
146
- nexus rule list # list all rules
147
- nexus update # update to latest version
150
+ nexus init . # Claude Code — set up .claude/ + knowledge/ + .mcp.json
151
+ nexus init . --tool opencode # OpenCode — set up .opencode/ + knowledge/ + opencode.json
152
+ nexus --version # show installed version
153
+ nexus update # update to latest version
154
+
155
+ nexus skill list # list all skills in .claude/commands/
156
+ nexus skill add my-review # create a custom skill
157
+
158
+ nexus agent list # list all subagents in .claude/agents/
159
+ nexus agent add my-agent # create a custom subagent
160
+
161
+ nexus rule list # list all rules in knowledge/rules/
162
+ nexus rule add api-standards # create a project rule
148
163
  ```
149
164
 
150
165
  ---
@@ -11,12 +11,14 @@ from rich.table import Table
11
11
  app = typer.Typer(name="nexus", no_args_is_help=False, help="nexus-dev-toolkit — Day 0 scaffold + Day 1 EPAV workflow for Claude Code")
12
12
  skill_app = typer.Typer(name="skill", no_args_is_help=True, help="Manage skills in .claude/commands/")
13
13
  rule_app = typer.Typer(name="rule", no_args_is_help=True, help="Manage rules in knowledge/rules/")
14
+ agent_app = typer.Typer(name="agent", no_args_is_help=True, help="Manage subagents in .claude/agents/")
14
15
  app.add_typer(skill_app, name="skill")
15
16
  app.add_typer(rule_app, name="rule")
17
+ app.add_typer(agent_app, name="agent")
16
18
 
17
19
  console = Console()
18
20
 
19
- _VERSION = "3.1.0"
21
+ _VERSION = "3.1.2"
20
22
 
21
23
  _LOGO = """\
22
24
  [cyan]███╗ ██╗███████╗██╗ ██╗██╗ ██╗███████╗[/cyan]
@@ -35,8 +37,17 @@ def _print_logo() -> None:
35
37
  console.print()
36
38
 
37
39
 
40
+ def _version_callback(value: bool) -> None:
41
+ if value:
42
+ console.print(f"nexus-dev-toolkit v{_VERSION}")
43
+ raise typer.Exit()
44
+
45
+
38
46
  @app.callback(invoke_without_command=True)
39
- def _callback(ctx: typer.Context) -> None:
47
+ def _callback(
48
+ ctx: typer.Context,
49
+ version: bool = typer.Option(None, "--version", "-V", callback=_version_callback, is_eager=True, help="Show version and exit."),
50
+ ) -> None:
40
51
  if ctx.invoked_subcommand is None:
41
52
  _print_logo()
42
53
  console.print(ctx.get_help())
@@ -106,6 +117,29 @@ _MCP_BLOCK = {
106
117
  }
107
118
  }
108
119
 
120
+ _OPENCODE_MCP_BLOCK = {
121
+ "nexus-mcp": {
122
+ "type": "local",
123
+ "command": ["uvx", "--refresh", "--from", "nexus-dev-toolkit", "nexus-mcp"],
124
+ }
125
+ }
126
+
127
+ _OPENCODE_GRAPHIFY_PLUGIN = """\
128
+ // .opencode/plugins/graphify.js
129
+ // Auto-updates the graphify knowledge graph after every tool execution.
130
+ import { execSync } from "node:child_process"
131
+
132
+ export const GraphifyPlugin = async ({ directory }) => {
133
+ return {
134
+ "tool.execute.after": async () => {
135
+ try {
136
+ execSync("graphify update . --force", { cwd: directory, stdio: "ignore" })
137
+ } catch (_) {}
138
+ },
139
+ }
140
+ }
141
+ """
142
+
109
143
 
110
144
  def _init_project(project_dir: Path) -> list[str]:
111
145
  """
@@ -153,6 +187,81 @@ def _init_project(project_dir: Path) -> list[str]:
153
187
  return created
154
188
 
155
189
 
190
+ def _strip_claude_frontmatter(content: str) -> str:
191
+ """Remove Claude Code-only frontmatter fields (tools, model) for OpenCode compatibility."""
192
+ if not content.startswith("---"):
193
+ return content
194
+ parts = content.split("---", 2)
195
+ if len(parts) < 3:
196
+ return content
197
+ lines = [
198
+ line for line in parts[1].splitlines()
199
+ if not line.startswith("tools:") and not line.startswith("model:")
200
+ ]
201
+ return "---" + "\n".join(lines) + "---" + parts[2]
202
+
203
+
204
+ def _init_opencode(project_dir: Path) -> list[str]:
205
+ """
206
+ nexus init --tool opencode — sets up:
207
+ .opencode/commands/ ← built-in skills
208
+ .opencode/agents/ ← built-in subagents
209
+ .opencode/plugins/ ← graphify PostToolUse hook
210
+ opencode.json ← MCP server config
211
+ knowledge/ ← empty scaffold
212
+ """
213
+ created = []
214
+
215
+ # .opencode/commands/ — copy built-in skills
216
+ commands_dir = project_dir / ".opencode" / "commands"
217
+ commands_dir.mkdir(parents=True, exist_ok=True)
218
+ for skill_name in _BUILTIN_SKILLS:
219
+ src = _SKILLS_SRC / skill_name
220
+ dest = commands_dir / skill_name
221
+ if src.exists() and not dest.exists():
222
+ shutil.copy2(src, dest)
223
+ created.append(f".opencode/commands/{skill_name}")
224
+
225
+ # .opencode/agents/ — copy built-in subagents, stripping Claude Code-only frontmatter
226
+ agents_dir = project_dir / ".opencode" / "agents"
227
+ agents_dir.mkdir(parents=True, exist_ok=True)
228
+ for agent_name in _BUILTIN_AGENTS:
229
+ src = _AGENTS_SRC / agent_name
230
+ dest = agents_dir / agent_name
231
+ if src.exists() and not dest.exists():
232
+ dest.write_text(_strip_claude_frontmatter(src.read_text()), encoding="utf-8")
233
+ created.append(f".opencode/agents/{agent_name}")
234
+
235
+ # .opencode/plugins/graphify.js — PostToolUse hook
236
+ plugins_dir = project_dir / ".opencode" / "plugins"
237
+ plugins_dir.mkdir(parents=True, exist_ok=True)
238
+ plugin_path = plugins_dir / "graphify.js"
239
+ if not plugin_path.exists():
240
+ plugin_path.write_text(_OPENCODE_GRAPHIFY_PLUGIN, encoding="utf-8")
241
+ created.append(".opencode/plugins/graphify.js")
242
+
243
+ # opencode.json — MCP server config
244
+ opencode_json = project_dir / "opencode.json"
245
+ existing: dict = {}
246
+ if opencode_json.exists():
247
+ try:
248
+ existing = json.loads(opencode_json.read_text())
249
+ except Exception:
250
+ pass
251
+ already_has_mcp = "nexus-mcp" in existing.get("mcp", {})
252
+ existing.setdefault("$schema", "https://opencode.ai/config.json")
253
+ existing.setdefault("mcp", {}).update(_OPENCODE_MCP_BLOCK)
254
+ opencode_json.write_text(json.dumps(existing, indent=2))
255
+ if not already_has_mcp:
256
+ created.append("opencode.json")
257
+
258
+ # knowledge/ scaffold
259
+ for d in _KNOWLEDGE_DIRS:
260
+ (project_dir / d).mkdir(parents=True, exist_ok=True)
261
+
262
+ return created
263
+
264
+
156
265
  def _write_mcp_config(project_dir: Path) -> str:
157
266
  mcp_path = project_dir / ".mcp.json"
158
267
  existing: dict = {}
@@ -168,26 +277,80 @@ def _write_mcp_config(project_dir: Path) -> str:
168
277
 
169
278
  # ── Commands ──────────────────────────────────────────────────────────────────
170
279
 
280
+ _SUPPORTED_TOOLS = ["claude", "opencode"]
281
+
282
+ _TOOL_CHECK = {
283
+ "claude": {
284
+ "bin": "claude",
285
+ "name": "Claude Code",
286
+ "install": "npm install -g @anthropic-ai/claude-code && claude login",
287
+ "install_cmd": ["npm", "install", "-g", "@anthropic-ai/claude-code"],
288
+ },
289
+ "opencode": {
290
+ "bin": "opencode",
291
+ "name": "OpenCode",
292
+ "install": "curl -fsSL https://opencode.ai/install | bash",
293
+ "install_cmd": None, # pipe install — run via shell
294
+ },
295
+ }
296
+
297
+
298
+ def _check_and_offer_install(tool: str) -> None:
299
+ info = _TOOL_CHECK[tool]
300
+ if shutil.which(info["bin"]):
301
+ return
302
+
303
+ console.print(f" [yellow]⚠[/yellow] {info['name']} not found — you need it to use nexus.\n")
304
+ answer = console.input(f" Install {info['name']} now? [y/N] ").strip().lower()
305
+ if answer != "y":
306
+ console.print(f" [dim]Skipping. Install manually: {info['install']}[/dim]\n")
307
+ return
308
+
309
+ console.print(f" [cyan]▶[/cyan] Installing {info['name']}…\n")
310
+ if info["install_cmd"]:
311
+ result = subprocess.run(info["install_cmd"])
312
+ else:
313
+ result = subprocess.run(info["install"], shell=True)
314
+
315
+ if result.returncode == 0:
316
+ console.print(f" [green]✓[/green] {info['name']} installed.\n")
317
+ else:
318
+ console.print(f" [red]✗[/red] Install failed. Run manually: {info['install']}\n")
319
+
320
+
171
321
  @app.command()
172
322
  def init(
173
323
  project_dir: str = typer.Argument(".", help="Project directory to initialize"),
324
+ tool: str = typer.Option("claude", "--tool", "-t", help=f"AI coding tool to set up for ({', '.join(_SUPPORTED_TOOLS)})"),
174
325
  ) -> None:
175
- """Initialize .claude/commands/, .claude/settings.json, and knowledge/ in a project."""
176
- root = Path(project_dir).resolve()
177
- console.print(f" [cyan]▶[/cyan] Initializing nexus in [bold]{root}[/bold]\n")
326
+ """Initialize nexus in a project directory. Defaults to Claude Code."""
327
+ if tool not in _SUPPORTED_TOOLS:
328
+ console.print(f" [red]✗[/red] Unknown tool '{tool}'. Supported: {', '.join(_SUPPORTED_TOOLS)}")
329
+ raise typer.Exit(1)
178
330
 
179
- created = _init_project(root)
180
- for f in created:
181
- console.print(f" [green]✓[/green] {f}")
182
-
183
- if not created:
184
- console.print(" [yellow]·[/yellow] Already initialized — nothing to do")
185
- return
331
+ _check_and_offer_install(tool)
186
332
 
187
- mcp = _write_mcp_config(root)
188
- console.print(f" [green]✓[/green] {mcp}")
189
-
190
- console.print(f"\n [bold green]Done.[/bold green] Open [bold]{root}[/bold] in Claude Code and type [cyan]/scaffold[/cyan]\n")
333
+ root = Path(project_dir).resolve()
334
+ console.print(f" [cyan]▶[/cyan] Initializing nexus for [bold]{tool}[/bold] in [bold]{root}[/bold]\n")
335
+
336
+ if tool == "opencode":
337
+ created = _init_opencode(root)
338
+ for f in created:
339
+ console.print(f" [green]✓[/green] {f}")
340
+ if not created:
341
+ console.print(" [yellow]·[/yellow] Already initialized — nothing to do")
342
+ return
343
+ console.print(f"\n [bold green]Done.[/bold green] Open [bold]{root}[/bold] in OpenCode and type [cyan]/scaffold[/cyan]\n")
344
+ else:
345
+ created = _init_project(root)
346
+ for f in created:
347
+ console.print(f" [green]✓[/green] {f}")
348
+ if not created:
349
+ console.print(" [yellow]·[/yellow] Already initialized — nothing to do")
350
+ return
351
+ mcp = _write_mcp_config(root)
352
+ console.print(f" [green]✓[/green] {mcp}")
353
+ console.print(f"\n [bold green]Done.[/bold green] Open [bold]{root}[/bold] in Claude Code and type [cyan]/scaffold[/cyan]\n")
191
354
 
192
355
 
193
356
  @app.command()
@@ -337,6 +500,84 @@ def rule_list(
337
500
  console.print(table)
338
501
 
339
502
 
503
+ # ── agent subcommands ─────────────────────────────────────────────────────────
504
+
505
+ _AGENT_TEMPLATE = """\
506
+ ---
507
+ name: {name}
508
+ description: Describe when Claude should spawn this agent (role, not action — e.g. "reviews X for Y")
509
+ ---
510
+
511
+ # {name}
512
+
513
+ ## Role
514
+
515
+ Describe what this agent does.
516
+
517
+ ## Steps
518
+
519
+ ### 1 — Discover context
520
+
521
+ Read CLAUDE.md, project manifest, and relevant source files.
522
+
523
+ ### 2 — Perform task
524
+
525
+ What the agent should do.
526
+
527
+ ### 3 — Output
528
+
529
+ What the agent should return when done.
530
+ """
531
+
532
+
533
+ @agent_app.command("add")
534
+ def agent_add(
535
+ name: str = typer.Argument(..., help="Agent name (e.g. 'security-reviewer')"),
536
+ project_dir: str = typer.Option(".", "--dir", "-d"),
537
+ ) -> None:
538
+ """Create a new subagent in .claude/agents/."""
539
+ root = Path(project_dir).resolve()
540
+ dest = root / ".claude" / "agents" / f"{name}.md"
541
+ dest.parent.mkdir(parents=True, exist_ok=True)
542
+
543
+ if dest.exists():
544
+ console.print(f" [yellow]·[/yellow] .claude/agents/{name}.md already exists")
545
+ return
546
+
547
+ dest.write_text(_AGENT_TEMPLATE.format(name=name), encoding="utf-8")
548
+ console.print(f" [green]✓[/green] Created .claude/agents/{name}.md")
549
+ console.print(f" [dim]Edit the description so Claude knows when to spawn it.[/dim]")
550
+
551
+
552
+ @agent_app.command("list")
553
+ def agent_list(
554
+ project_dir: str = typer.Option(".", "--dir", "-d"),
555
+ ) -> None:
556
+ """List all subagents in .claude/agents/."""
557
+ root = Path(project_dir).resolve()
558
+ agents_dir = root / ".claude" / "agents"
559
+
560
+ if not agents_dir.exists():
561
+ console.print(" [yellow]·[/yellow] No .claude/agents/ found. Run [cyan]nexus init[/cyan] first.")
562
+ return
563
+
564
+ agents = sorted(agents_dir.glob("*.md"))
565
+ if not agents:
566
+ console.print(" [yellow]·[/yellow] No agents yet. Run [cyan]nexus agent add <name>[/cyan]")
567
+ return
568
+
569
+ table = Table(show_header=True, header_style="dim")
570
+ table.add_column("Agent", style="cyan")
571
+ table.add_column("Source")
572
+
573
+ builtins = set(_BUILTIN_AGENTS)
574
+ for a in agents:
575
+ source = "built-in" if a.name in builtins else "custom"
576
+ table.add_row(a.stem, source)
577
+
578
+ console.print(table)
579
+
580
+
340
581
  def main() -> None:
341
582
  app()
342
583
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nexus-dev-toolkit
3
- Version: 3.1.0
3
+ Version: 3.1.2
4
4
  Summary: Developer workflow toolkit for Claude Code — Day 0 scaffold + Day 1 EPAV
5
5
  Author-email: Ronald dela Cruz <rcdelacruz@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -80,7 +80,8 @@ uv tool install nexus-dev-toolkit
80
80
 
81
81
  ```bash
82
82
  cd my-project
83
- nexus init .
83
+ nexus init . # Claude Code (default)
84
+ nexus init . --tool opencode # OpenCode
84
85
  ```
85
86
 
86
87
  ### 4. Place your reference docs
@@ -137,15 +138,11 @@ Each step is a built-in skill in `.claude/commands/`. Every task starts from the
137
138
 
138
139
  ## What `nexus init` Creates
139
140
 
141
+ **Claude Code** (`nexus init .`):
140
142
  ```
141
143
  .claude/
142
- ├── commands/
143
- │ ├── scaffold.md ← /scaffold — Day 0 one-time setup
144
- │ ├── evaluate.md ← /evaluate — orient on a task
145
- │ ├── plan.md ← /plan — blueprint, no code
146
- │ ├── apply.md ← /apply — implement the plan
147
- │ ├── validate.md ← /validate — verify acceptance criteria
148
- │ └── epav.md ← /epav — full cycle guide
144
+ ├── commands/ ← EPAV skills + 5 reviewer skills (/code-review, etc.)
145
+ ├── agents/ ← 5 reviewer subagents (code-reviewer, database-reviewer, etc.)
149
146
  └── settings.json ← PostToolUse: graphify auto-updates after every file edit
150
147
  knowledge/
151
148
  ├── rules/ ← coding standards, arch decisions
@@ -155,17 +152,35 @@ knowledge/
155
152
  .mcp.json ← MCP server config
156
153
  ```
157
154
 
155
+ **OpenCode** (`nexus init . --tool opencode`):
156
+ ```
157
+ .opencode/
158
+ ├── commands/ ← same EPAV skills + reviewer skills
159
+ ├── agents/ ← same reviewer subagents (adapted for OpenCode)
160
+ └── plugins/
161
+ └── graphify.js ← tool.execute.after: graphify auto-update
162
+ knowledge/ ← same structure
163
+ opencode.json ← MCP server config
164
+ ```
165
+
158
166
  ---
159
167
 
160
168
  ## Commands
161
169
 
162
170
  ```bash
163
- nexus init . # set up .claude/commands/ + knowledge/ + .mcp.json
164
- nexus skill add code-review # create a custom skill in .claude/commands/
165
- nexus skill list # list all skills
166
- nexus rule add api-standards # create a rule in knowledge/rules/
167
- nexus rule list # list all rules
168
- nexus update # update to latest version
171
+ nexus init . # Claude Code — set up .claude/ + knowledge/ + .mcp.json
172
+ nexus init . --tool opencode # OpenCode — set up .opencode/ + knowledge/ + opencode.json
173
+ nexus --version # show installed version
174
+ nexus update # update to latest version
175
+
176
+ nexus skill list # list all skills in .claude/commands/
177
+ nexus skill add my-review # create a custom skill
178
+
179
+ nexus agent list # list all subagents in .claude/agents/
180
+ nexus agent add my-agent # create a custom subagent
181
+
182
+ nexus rule list # list all rules in knowledge/rules/
183
+ nexus rule add api-standards # create a project rule
169
184
  ```
170
185
 
171
186
  ---
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "nexus-dev-toolkit"
3
- version = "3.1.0"
3
+ version = "3.1.2"
4
4
  description = "Developer workflow toolkit for Claude Code — Day 0 scaffold + Day 1 EPAV"
5
5
  authors = [
6
6
  {name = "Ronald dela Cruz", email = "rcdelacruz@users.noreply.github.com"}
@@ -103,3 +103,50 @@ def test_rule_list(tmp_project):
103
103
  result = runner.invoke(app, ["rule", "list", "--dir", str(tmp_project)])
104
104
  assert result.exit_code == 0
105
105
  assert "api-standards" in result.output
106
+
107
+
108
+ def test_init_opencode_creates_commands(tmp_project):
109
+ result = runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
110
+ assert result.exit_code == 0
111
+ commands_dir = tmp_project / ".opencode" / "commands"
112
+ assert commands_dir.exists()
113
+ for skill in _BUILTIN_SKILLS:
114
+ assert (commands_dir / skill).exists(), f"Missing skill: {skill}"
115
+
116
+
117
+ def test_init_opencode_creates_agents(tmp_project):
118
+ result = runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
119
+ assert result.exit_code == 0
120
+ agents_dir = tmp_project / ".opencode" / "agents"
121
+ assert agents_dir.exists()
122
+ for agent in _BUILTIN_AGENTS:
123
+ assert (agents_dir / agent).exists(), f"Missing agent: {agent}"
124
+
125
+
126
+ def test_init_opencode_creates_plugin(tmp_project):
127
+ runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
128
+ plugin = tmp_project / ".opencode" / "plugins" / "graphify.js"
129
+ assert plugin.exists()
130
+ assert "tool.execute.after" in plugin.read_text()
131
+
132
+
133
+ def test_init_opencode_creates_opencode_json(tmp_project):
134
+ runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
135
+ config = tmp_project / "opencode.json"
136
+ assert config.exists()
137
+ data = json.loads(config.read_text())
138
+ assert "nexus-mcp" in data["mcp"]
139
+ assert data["mcp"]["nexus-mcp"]["type"] == "local"
140
+ assert data["mcp"]["nexus-mcp"]["command"][0] == "uvx"
141
+
142
+
143
+ def test_init_opencode_idempotent(tmp_project):
144
+ runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
145
+ result = runner.invoke(app, ["init", str(tmp_project), "--tool", "opencode"])
146
+ assert result.exit_code == 0
147
+ assert "Already initialized" in result.output
148
+
149
+
150
+ def test_init_unknown_tool(tmp_project):
151
+ result = runner.invoke(app, ["init", str(tmp_project), "--tool", "cursor"])
152
+ assert result.exit_code == 1