Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .claude-plugin/hooks/pre-tool-use/model-router.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env node
"use strict";

module.exports = require("../../../pre-tool-use/model-router");
const { run } = require("../../../scripts/run-model-router-hook");

run("claude").catch(() => process.exit(0));
5 changes: 4 additions & 1 deletion .claude-plugin/hooks/session-start/model-router.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env node
"use strict";

module.exports = require("../../../pre-tool-use/model-router");
const { run } = require("../../../scripts/run-model-router-hook");

run("claude").catch(() => process.exit(0));
5 changes: 4 additions & 1 deletion .claude-plugin/hooks/user-prompt-submit/model-router.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env node
"use strict";

module.exports = require("../../../pre-tool-use/model-router");
const { run } = require("../../../scripts/run-model-router-hook");

run("claude").catch(() => process.exit(0));
5 changes: 4 additions & 1 deletion .codex-plugin/hooks/pre-tool-use/model-router.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env node
"use strict";

module.exports = require("../../../pre-tool-use/model-router");
const { run } = require("../../../scripts/run-model-router-hook");

run("codex").catch(() => process.exit(0));
5 changes: 4 additions & 1 deletion .codex-plugin/hooks/session-start/model-router.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env node
"use strict";

module.exports = require("../../../pre-tool-use/model-router");
const { run } = require("../../../scripts/run-model-router-hook");

run("codex").catch(() => process.exit(0));
5 changes: 4 additions & 1 deletion .codex-plugin/hooks/user-prompt-submit/model-router.js
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
#!/usr/bin/env node
"use strict";

module.exports = require("../../../pre-tool-use/model-router");
const { run } = require("../../../scripts/run-model-router-hook");

run("codex").catch(() => process.exit(0));
20 changes: 2 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<img src="apps/nexus-web/public/logos/nexus.svg" alt="Nexus Logo" width="260" />
</p>

Nexus Agent Kit is a plugin-first AI terminal workspace for Codex and Claude Code. It packages reusable skills, specialized agents, terminal commands, and MCP safety conventions so your AI sessions behave consistently across planning, CI/CD debugging, code review, TODO tracking, infrastructure design, tutorials, and tool-connected work.
Nexus Agent Kit is a plugin-first AI terminal workspace for Codex and Claude Code. It packages reusable skills, terminal commands, and MCP safety conventions so your AI sessions behave consistently across planning, CI/CD debugging, code review, TODO tracking, infrastructure design, tutorials, and tool-connected work.

Instead of re-explaining how you want the assistant to operate in every new repo or terminal, you install Nexus once and get a shared operating model for engineering work.

Expand Down Expand Up @@ -103,22 +103,6 @@ NEXUS_SMALL_PROMPT_MAX_WORDS=50
- Find free or self-hosted alternatives to Firebase, Heroku, Auth0, Datadog, and other paid services.
- Build a daily brief from TODOs and connected work systems.

## Agents

Nexus ships focused agent definitions that can be reused inside supported terminals.

| Agent | Category | Purpose |
| --- | --- | --- |
| [`nexus-coordinator`](agents/coordinator.md) | ![Coordination](https://img.shields.io/badge/Coordination-0F766E?style=flat) ![Parallelism](https://img.shields.io/badge/Parallelism-0891B2?style=flat) | Decomposes work into a dependency graph, identifies parallel tasks, and routes subtasks to specialist agents. |
| [`nexus-explorer`](agents/explorer.md) | ![Exploration](https://img.shields.io/badge/Exploration-7C3AED?style=flat) ![Research](https://img.shields.io/badge/Research-6D28D9?style=flat) | Compares viable approaches and recommends a path before detailed planning begins. |
| [`nexus-planner`](agents/planner.md) | ![Planning](https://img.shields.io/badge/Planning-1D4ED8?style=flat) ![Coding](https://img.shields.io/badge/Coding-2563EB?style=flat) | Produces approval-first technical plans for production work. |
| [`nexus-debugger`](agents/debugger.md) | ![Debugging](https://img.shields.io/badge/Debugging-DC2626?style=flat) ![CI/CD](https://img.shields.io/badge/CI%2FCD-EA580C?style=flat) | Investigates failures, identifies root cause, and verifies fixes. |
| [`nexus-reviewer`](agents/reviewer.md) | ![Review](https://img.shields.io/badge/Review-7C3AED?style=flat) ![Coding](https://img.shields.io/badge/Coding-2563EB?style=flat) | Reviews code and workflows for bugs, regressions, and risk. |
| [`nexus-todo-manager`](agents/todo-manager.md) | ![Operations](https://img.shields.io/badge/Operations-0F766E?style=flat) ![TODOs](https://img.shields.io/badge/TODOs-14B8A6?style=flat) | Maintains labeled global TODOs and keeps persistent follow-ups organized. |
| [`nexus-social-assistant`](agents/social-assistant.md) | ![Comms](https://img.shields.io/badge/Comms-DB2777?style=flat) ![Briefing](https://img.shields.io/badge/Briefing-EC4899?style=flat) | Builds daily briefs from Slack, Outlook, Jira, and Nexus TODO context. |
| [`nexus-tutorial-architect`](agents/tutorial.md) | ![Teaching](https://img.shields.io/badge/Teaching-059669?style=flat) ![Documentation](https://img.shields.io/badge/Documentation-0EA5E9?style=flat) | Creates executable tutorial notebooks and reproducible learning assets. |
| [`nexus:fast-editor`](agents/fast-editor.md) | ![Coding](https://img.shields.io/badge/Coding-2563EB?style=flat) | Minimal, fast agent for small deterministic edits (1–10 lines). No plans, no explanations — just the change. |

## Skills

Skills are grouped below by role so it is easier to understand what the plugin actually adds to the terminal.
Expand Down Expand Up @@ -212,7 +196,7 @@ Skills are grouped below by role so it is easier to understand what the plugin a

| Skill | Category | Purpose |
| --- | --- | --- |
| [`nexus-token-saving`](skills/token-saving/SKILL.md) | ![Operations](https://img.shields.io/badge/Operations-0F766E?style=flat) | Best practices to minimize token usage while preserving output quality in Codex and Claude Code sessions. |
| [`nexus-token-optimizer`](skills/token-optimizer/SKILL.md) | ![Operations](https://img.shields.io/badge/Operations-0F766E?style=flat) | Always-on token optimization rules for efficient context gathering, tool usage, and concise responses. |

## Commands

Expand Down
66 changes: 66 additions & 0 deletions scripts/run-model-router-hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env node
"use strict";

const fs = require("node:fs");
const modelRouterHook = require("../pre-tool-use/model-router");

function parseInput() {
if (process.stdin.isTTY) return {};
try {
return JSON.parse(fs.readFileSync(0, "utf8"));
} catch {
return {};
}
}

function normalizeMessageHistory(event) {
if (Array.isArray(event.message_history)) return event.message_history;
if (Array.isArray(event.messageHistory)) return event.messageHistory;

const prompt = event.prompt || event.user_prompt || event.userPrompt || event.message;
if (typeof prompt === "string" && prompt.trim()) {
return [{ content: prompt }];
}

return [];
}

function normalizePayload(event, runtimeHint) {
const hookEventName = event.hook_event_name || event.hookEventName;
const toolName = event.tool_name || event.toolName;
const sessionId = event.session_id || event.sessionId || `${runtimeHint || "claude"}-hook-session`;

const context = {
cwd: event.cwd || process.cwd(),
session: { id: sessionId },
messageHistory: normalizeMessageHistory(event),
};

return { toolName, hookEventName, context };
}

async function run(runtimeHint) {
const event = parseInput();
const payload = normalizePayload(event, runtimeHint);

if (!process.env.NEXUS_AI_RUNTIME && runtimeHint) {
process.env.NEXUS_AI_RUNTIME = runtimeHint;
}

const result = await modelRouterHook(payload);
if (result && typeof result === "object") {
process.stdout.write(`${JSON.stringify(result)}\n`);
}
}

if (require.main === module) {
const runtimeHint = process.argv[2];
run(runtimeHint).catch((error) => {
process.stderr.write(`nexus model-router hook error: ${error.message}\n`);
process.exit(0);
});
}

module.exports = {
run,
};
1 change: 1 addition & 0 deletions test/bootstrap-agent-docs.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ test("bootstrap creates CLAUDE.md under claude home for claude runtime", () => {

const claudeContent = fs.readFileSync(path.join(targetRoot, "CLAUDE.md"), "utf8");
assert.match(claudeContent, /search for relevant skills first/i);
assert.match(claudeContent, /token-optimizer skill/i);
});

test("bootstrap is idempotent once state and markers exist", () => {
Expand Down
32 changes: 32 additions & 0 deletions test/model-router-hook.test.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";

const assert = require("node:assert/strict");
const { spawnSync } = require("node:child_process");
const fs = require("node:fs");
const os = require("node:os");
const path = require("node:path");
Expand Down Expand Up @@ -152,3 +153,34 @@ test("codex session-start writes observed model state", async () => {
assert.equal(state.currentModel, "gpt-5.5");
});
});

test("claude session-start hook command bootstraps CLAUDE.md", async () => {
await withIsolatedEnv(async () => {
const repoRoot = makeTempDir("nexus-router-repo-");
const homeRoot = makeTempDir("nexus-router-home-");
process.chdir(repoRoot);
process.env.HOME = homeRoot;

const hookPath = path.join(__dirname, "..", ".claude-plugin", "hooks", "session-start", "model-router.js");
const hookInput = {
hook_event_name: "SessionStart",
session_id: "claude-session-hook-bootstrap",
cwd: repoRoot,
};

const hookResult = spawnSync(process.execPath, [hookPath], {
env: {
...process.env,
HOME: homeRoot,
},
input: `${JSON.stringify(hookInput)}\n`,
encoding: "utf8",
});

assert.equal(hookResult.status, 0, hookResult.stderr);
const claudeFile = path.join(homeRoot, ".claude", "CLAUDE.md");
assert.equal(fs.existsSync(claudeFile), true);
const claudeContent = fs.readFileSync(claudeFile, "utf8");
assert.match(claudeContent, /token-optimizer skill/i);
});
});
2 changes: 2 additions & 0 deletions test/plugin-hooks.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ test("claude plugin ships the same hook surface as codex plugin", () => {
for (const relPath of codexHooks) {
const codexMode = fs.statSync(path.join(codexRoot, relPath)).mode;
const claudeMode = fs.statSync(path.join(claudeRoot, relPath)).mode;
assert.equal(isExecutable(codexMode), true, `codex hook is not executable: ${relPath}`);
assert.equal(isExecutable(claudeMode), true, `claude hook is not executable: ${relPath}`);
assert.equal(
isExecutable(claudeMode),
isExecutable(codexMode),
Expand Down
Loading