@nano-step/skill-manager 4.0.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.
package/README.md ADDED
@@ -0,0 +1,218 @@
1
+ # @nano-step/skill-manager
2
+
3
+ CLI tool that installs and manages **AI agent skills** into [OpenCode](https://github.com/sst/opencode) projects. Reduces token usage by **80-95%** by isolating MCP tool definitions in a dedicated subagent context.
4
+
5
+ ## Why?
6
+
7
+ When using many MCP (Model Context Protocol) tools, the tool definitions consume thousands of tokens in your main agent's context window. This tool creates a lightweight routing layer that:
8
+
9
+ 1. **Installs a skill** (`mcp-management`) with routing logic and documentation
10
+ 2. **Configures a subagent** (`mcp-manager`) using a fast, cheap model
11
+ 3. **Caches tool metadata** in `.opencode/mcp-tools.json` for instant routing
12
+ 4. **Returns summarized results** — main agent stays lean
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npx @nano-step/skill-manager
18
+ ```
19
+
20
+ This will:
21
+ - Detect your OpenCode config directory (`.opencode/` or `~/.config/opencode/`)
22
+ - Install the `mcp-management` skill with routing logic
23
+ - Install the `/mcp-refresh` command
24
+ - Add the `mcp-manager` subagent to `oh-my-opencode.json`
25
+
26
+ ## Usage
27
+
28
+ ### Install (first time)
29
+
30
+ ```bash
31
+ npx @nano-step/skill-manager
32
+ ```
33
+
34
+ ### Update (to latest version)
35
+
36
+ ```bash
37
+ npx @nano-step/skill-manager --update
38
+ ```
39
+
40
+ Creates timestamped backups of customized files before updating.
41
+
42
+ ### Remove
43
+
44
+ ```bash
45
+ npx @nano-step/skill-manager --remove
46
+ ```
47
+
48
+ Cleanly removes all installed artifacts.
49
+
50
+ ## What Gets Installed
51
+
52
+ | Artifact | Location | Purpose |
53
+ |----------|----------|---------|
54
+ | Skill | `{config}/skills/mcp-management/` | Routing logic & documentation |
55
+ | Command | `{config}/command/agent-skill-refresh.md` | Re-index MCP tools |
56
+ | Agent | `{config}/oh-my-opencode.json` | `mcp-manager` subagent config |
57
+ | Version | `{config}/.agent-skill-version.json` | Track installed version |
58
+
59
+ ## After Installation
60
+
61
+ Run the refresh command in OpenCode to create the initial tool cache:
62
+
63
+ ```
64
+ /agent-skill-refresh
65
+ ```
66
+
67
+ This analyzes all available MCP tools and creates a semantic index at `.opencode/mcp-tools.json`.
68
+
69
+ ## Config Detection
70
+
71
+ The tool looks for OpenCode configuration in this order:
72
+
73
+ 1. **Project-level**: `.opencode/` in current directory (preferred)
74
+ 2. **Global**: `~/.config/opencode/`
75
+
76
+ ## How It Works
77
+
78
+ ```
79
+ User request → Main agent (Opus/Sonnet)
80
+ ↓ delegates MCP task
81
+ mcp-manager subagent (Haiku — fast & cheap)
82
+ ↓ reads .opencode/mcp-tools.json cache
83
+ ↓ routes to correct MCP tool category
84
+ ↓ executes tool, summarizes result
85
+ ↓ returns to main agent
86
+ Main agent continues with minimal token cost
87
+ ```
88
+
89
+ ### Tool Categories
90
+
91
+ | Category | Tools | Example |
92
+ |----------|-------|---------|
93
+ | browser | chrome-devtools__* | Screenshots, clicks, navigation |
94
+ | github | github-*__* | PRs, issues, repos |
95
+ | graphql | graphql-tools__* | Schema inspection, queries |
96
+ | docs | context7__* | Documentation lookup |
97
+ | reasoning | Sequential-Thinking__* | Complex analysis |
98
+
99
+ ## Advanced Features (v4.0.0)
100
+
101
+ ### Direct Passthrough
102
+
103
+ Skip routing when you know the exact tool name:
104
+
105
+ ```
106
+ MetaMCP_chrome-devtools__take_screenshot
107
+ ```
108
+
109
+ Any tool name containing `__` executes directly without category lookup.
110
+
111
+ ### Batch Operations
112
+
113
+ Execute multiple tools in one request:
114
+
115
+ ```
116
+ BATCH: [
117
+ {"tool": "screenshot", "params": {}},
118
+ {"tool": "get_title", "params": {}}
119
+ ]
120
+ ```
121
+
122
+ - Maximum 10 tools per batch
123
+ - Returns array of results in execution order
124
+
125
+ ### Tool Chaining
126
+
127
+ Chain tools with output passing:
128
+
129
+ ```
130
+ CHAIN: [
131
+ {"tool": "get_element", "params": {"selector": "#btn"}, "output_as": "el"},
132
+ {"tool": "click", "params": {"element": "$el"}}
133
+ ]
134
+ ```
135
+
136
+ - Use `$varname` to reference previous outputs
137
+ - Maximum 5 tools per chain
138
+
139
+ ### Automatic Retry
140
+
141
+ All tool executions automatically retry on failure:
142
+
143
+ - Up to 3 attempts with delays: 0s → 1s → 2s
144
+ - Returns detailed failure report if all attempts fail
145
+
146
+ ### Workflows
147
+
148
+ Define prerequisite steps that automatically execute before certain tools:
149
+
150
+ ```bash
151
+ # Add a workflow from template
152
+ /agent-skill-workflow add --template database
153
+
154
+ # List active workflows
155
+ /agent-skill-workflow list
156
+
157
+ # Disable temporarily
158
+ /agent-skill-workflow disable database-safe-query
159
+ ```
160
+
161
+ **Built-in templates:**
162
+
163
+ | Template | Description | Prerequisites |
164
+ |----------|-------------|---------------|
165
+ | `database` | Safe database queries | list_databases → list_tables → inspect_table |
166
+ | `browser` | Safe browser interaction | take_snapshot |
167
+ | `github-pr` | PR review workflow | get_pr → get_files → get_status |
168
+
169
+ **Workflow modes:**
170
+ - `enforce`: Auto-run prerequisites (default)
171
+ - `warn`: Show warning, allow skip
172
+ - `suggest`: Mention only, don't block
173
+
174
+ ## Development
175
+
176
+ ### Build
177
+
178
+ ```bash
179
+ npm install
180
+ npm run build
181
+ ```
182
+
183
+ ### Test Locally
184
+
185
+ ```bash
186
+ # After building
187
+ node bin/cli.js # Install
188
+ node bin/cli.js --update # Update
189
+ node bin/cli.js --remove # Remove
190
+ ```
191
+
192
+ ### Project Structure
193
+
194
+ ```
195
+ skill-manager/
196
+ ├── src/ # TypeScript source
197
+ │ ├── index.ts # CLI entry point
198
+ │ ├── install.ts # Installation logic
199
+ │ ├── update.ts # Update with backup
200
+ │ ├── remove.ts # Clean removal
201
+ │ └── utils.ts # Shared utilities
202
+ ├── templates/ # Files to install
203
+ │ ├── agent.json # mcp-manager agent config
204
+ │ ├── command-refresh.md # /agent-skill-refresh command
205
+ │ └── skill/ # mcp-management skill
206
+ ├── bin/cli.js # Executable entry
207
+ └── dist/ # Compiled output
208
+ ```
209
+
210
+ ## Related Projects
211
+
212
+ - [nano-brain](https://github.com/nano-step/nano-brain) — Persistent memory for AI agents
213
+ - [graphql-inspector-mcp](https://github.com/nano-step/graphql-inspector-mcp) — GraphQL schema inspection MCP server
214
+ - [mcp-database-inspector](https://github.com/nano-step/mcp-database-inspector) — Database inspection MCP server
215
+
216
+ ## License
217
+
218
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { run } = require("../dist/index.js");
4
+
5
+ run().catch((error) => {
6
+ // eslint-disable-next-line no-console
7
+ console.error(error instanceof Error ? error.message : error);
8
+ process.exit(1);
9
+ });
@@ -0,0 +1 @@
1
+ export declare function run(): Promise<void>;
package/dist/index.js ADDED
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.run = run;
7
+ const commander_1 = require("commander");
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const install_1 = require("./install");
10
+ const update_1 = require("./update");
11
+ const remove_1 = require("./remove");
12
+ async function run() {
13
+ const program = new commander_1.Command();
14
+ program
15
+ .name("skill-manager")
16
+ .description("Install and manage AI agent skills and configurations")
17
+ .option("--update", "Update existing agent skill installation")
18
+ .option("--remove", "Remove agent skill installation")
19
+ .parse(process.argv);
20
+ const options = program.opts();
21
+ if (options.update && options.remove) {
22
+ console.error(chalk_1.default.red("Cannot use --update and --remove together."));
23
+ process.exit(1);
24
+ }
25
+ if (options.remove) {
26
+ await (0, remove_1.remove)();
27
+ return;
28
+ }
29
+ if (options.update) {
30
+ await (0, update_1.update)();
31
+ return;
32
+ }
33
+ await (0, install_1.install)();
34
+ }
@@ -0,0 +1 @@
1
+ export declare function install(): Promise<void>;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.install = install;
7
+ const path_1 = __importDefault(require("path"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const utils_1 = require("./utils");
11
+ async function install() {
12
+ const paths = await (0, utils_1.detectOpenCodePaths)();
13
+ await (0, utils_1.ensureDirExists)(paths.commandDir);
14
+ await (0, utils_1.ensureDirExists)(paths.skillsDir);
15
+ const skillTargetDir = path_1.default.join(paths.skillsDir, utils_1.SKILL_DIR_NAME);
16
+ const commandTargetPath = path_1.default.join(paths.commandDir, "agent-skill-refresh.md");
17
+ await fs_extra_1.default.copy(paths.templateSkillDir, skillTargetDir, { overwrite: true });
18
+ const commandTemplate = await (0, utils_1.readText)(paths.templateCommandPath);
19
+ await (0, utils_1.writeText)(commandTargetPath, commandTemplate);
20
+ const agentTemplate = await (0, utils_1.readJsonFile)(paths.templateAgentPath, {});
21
+ const agentConfig = await (0, utils_1.readJsonFile)(paths.agentConfigPath, {});
22
+ const agents = (agentConfig.agents || {});
23
+ const templateAgents = (agentTemplate.agents || agentTemplate);
24
+ agentConfig.agents = { ...agents, ...templateAgents };
25
+ await (0, utils_1.writeJsonFile)(paths.agentConfigPath, agentConfig);
26
+ await (0, utils_1.writeJsonFile)(paths.versionFilePath, { version: utils_1.PACKAGE_VERSION, installedAt: new Date().toISOString() });
27
+ const state = await (0, utils_1.getInstallationState)(paths);
28
+ if (!state.skillInstalled || !state.commandInstalled || !state.agentInstalled) {
29
+ throw new Error("Installation verification failed. Some files were not installed.");
30
+ }
31
+ if (Object.keys(agentConfig).length > 0 && Object.prototype.hasOwnProperty.call(agentConfig, utils_1.AGENT_ID)) {
32
+ console.log(chalk_1.default.yellow("agent-skill-manager agent already existed; configuration was updated."));
33
+ }
34
+ console.log(chalk_1.default.green("Agent skill manager installed successfully."));
35
+ }
@@ -0,0 +1 @@
1
+ export declare function remove(): Promise<void>;
package/dist/remove.js ADDED
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.remove = remove;
7
+ const path_1 = __importDefault(require("path"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const utils_1 = require("./utils");
11
+ async function remove() {
12
+ const paths = await (0, utils_1.detectOpenCodePaths)();
13
+ const skillTargetDir = path_1.default.join(paths.skillsDir, utils_1.SKILL_DIR_NAME);
14
+ const commandTargetPath = path_1.default.join(paths.commandDir, "agent-skill-refresh.md");
15
+ if (await fs_extra_1.default.pathExists(skillTargetDir)) {
16
+ await fs_extra_1.default.remove(skillTargetDir);
17
+ }
18
+ if (await fs_extra_1.default.pathExists(commandTargetPath)) {
19
+ await fs_extra_1.default.remove(commandTargetPath);
20
+ }
21
+ const agentConfig = await (0, utils_1.readJsonFile)(paths.agentConfigPath, {});
22
+ const agents = (agentConfig.agents || {});
23
+ if (Object.prototype.hasOwnProperty.call(agents, utils_1.AGENT_ID)) {
24
+ delete agents[utils_1.AGENT_ID];
25
+ agentConfig.agents = agents;
26
+ await (0, utils_1.writeJsonFile)(paths.agentConfigPath, agentConfig);
27
+ }
28
+ if (await fs_extra_1.default.pathExists(paths.versionFilePath)) {
29
+ await fs_extra_1.default.remove(paths.versionFilePath);
30
+ }
31
+ console.log(chalk_1.default.green("Agent skill manager removed successfully."));
32
+ }
@@ -0,0 +1 @@
1
+ export declare function update(): Promise<void>;
package/dist/update.js ADDED
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.update = update;
7
+ const path_1 = __importDefault(require("path"));
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const fs_extra_1 = __importDefault(require("fs-extra"));
10
+ const utils_1 = require("./utils");
11
+ async function update() {
12
+ const paths = await (0, utils_1.detectOpenCodePaths)();
13
+ await (0, utils_1.ensureDirExists)(paths.commandDir);
14
+ await (0, utils_1.ensureDirExists)(paths.skillsDir);
15
+ const skillTargetDir = path_1.default.join(paths.skillsDir, utils_1.SKILL_DIR_NAME);
16
+ const commandTargetPath = path_1.default.join(paths.commandDir, "agent-skill-refresh.md");
17
+ await (0, utils_1.ensureDirExists)(skillTargetDir);
18
+ const state = await (0, utils_1.getInstallationState)(paths);
19
+ if (state.installedVersion === utils_1.PACKAGE_VERSION) {
20
+ console.log(chalk_1.default.green("Agent skill manager is already up to date."));
21
+ return;
22
+ }
23
+ const [skillCustomized, commandCustomized] = await Promise.all([
24
+ (0, utils_1.directoryDiffersFromTemplate)(skillTargetDir, paths.templateSkillDir),
25
+ (0, utils_1.fileDiffersFromTemplate)(commandTargetPath, paths.templateCommandPath),
26
+ ]);
27
+ if (skillCustomized || commandCustomized) {
28
+ console.log(chalk_1.default.yellow("Detected customized files. Backups will be created before update."));
29
+ }
30
+ if (await fs_extra_1.default.pathExists(skillTargetDir)) {
31
+ console.log(chalk_1.default.yellow("Backing up existing skill directory before update."));
32
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
33
+ const backupDir = `${skillTargetDir}.backup-${timestamp}.bak`;
34
+ await fs_extra_1.default.copy(skillTargetDir, backupDir, { overwrite: true });
35
+ }
36
+ if (await fs_extra_1.default.pathExists(commandTargetPath)) {
37
+ await (0, utils_1.backupFile)(commandTargetPath, "backup");
38
+ }
39
+ if (await fs_extra_1.default.pathExists(paths.agentConfigPath)) {
40
+ await (0, utils_1.backupFile)(paths.agentConfigPath, "backup");
41
+ }
42
+ await fs_extra_1.default.copy(paths.templateSkillDir, skillTargetDir, { overwrite: true });
43
+ const commandTemplate = await fs_extra_1.default.readFile(paths.templateCommandPath, "utf8");
44
+ await fs_extra_1.default.writeFile(commandTargetPath, commandTemplate, "utf8");
45
+ const agentTemplate = await (0, utils_1.readJsonFile)(paths.templateAgentPath, {});
46
+ const agentConfig = await (0, utils_1.readJsonFile)(paths.agentConfigPath, {});
47
+ const agents = (agentConfig.agents || {});
48
+ const templateAgents = (agentTemplate.agents || agentTemplate);
49
+ agentConfig.agents = { ...agents, ...templateAgents };
50
+ await (0, utils_1.writeJsonFile)(paths.agentConfigPath, agentConfig);
51
+ await (0, utils_1.writeJsonFile)(paths.versionFilePath, { version: utils_1.PACKAGE_VERSION, updatedAt: new Date().toISOString() });
52
+ console.log(chalk_1.default.green("Agent skill manager updated successfully."));
53
+ }
@@ -0,0 +1,30 @@
1
+ export declare const PACKAGE_VERSION = "4.0.0";
2
+ export declare const AGENT_ID = "mcp-manager";
3
+ export declare const SKILL_DIR_NAME = "mcp-management";
4
+ export interface OpenCodePaths {
5
+ configDir: string;
6
+ projectDir: string;
7
+ commandDir: string;
8
+ skillsDir: string;
9
+ agentConfigPath: string;
10
+ versionFilePath: string;
11
+ templateSkillDir: string;
12
+ templateCommandPath: string;
13
+ templateAgentPath: string;
14
+ }
15
+ export interface InstallationState {
16
+ installedVersion: string | null;
17
+ skillInstalled: boolean;
18
+ commandInstalled: boolean;
19
+ agentInstalled: boolean;
20
+ }
21
+ export declare function detectOpenCodePaths(): Promise<OpenCodePaths>;
22
+ export declare function ensureDirExists(dirPath: string): Promise<void>;
23
+ export declare function readJsonFile<T>(filePath: string, fallback: T): Promise<T>;
24
+ export declare function writeJsonFile(filePath: string, data: unknown): Promise<void>;
25
+ export declare function readText(filePath: string): Promise<string>;
26
+ export declare function writeText(filePath: string, data: string): Promise<void>;
27
+ export declare function backupFile(filePath: string, suffix: string): Promise<string>;
28
+ export declare function fileDiffersFromTemplate(filePath: string, templatePath: string): Promise<boolean>;
29
+ export declare function directoryDiffersFromTemplate(dirPath: string, templateDir: string): Promise<boolean>;
30
+ export declare function getInstallationState(paths: OpenCodePaths): Promise<InstallationState>;
package/dist/utils.js ADDED
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SKILL_DIR_NAME = exports.AGENT_ID = exports.PACKAGE_VERSION = void 0;
7
+ exports.detectOpenCodePaths = detectOpenCodePaths;
8
+ exports.ensureDirExists = ensureDirExists;
9
+ exports.readJsonFile = readJsonFile;
10
+ exports.writeJsonFile = writeJsonFile;
11
+ exports.readText = readText;
12
+ exports.writeText = writeText;
13
+ exports.backupFile = backupFile;
14
+ exports.fileDiffersFromTemplate = fileDiffersFromTemplate;
15
+ exports.directoryDiffersFromTemplate = directoryDiffersFromTemplate;
16
+ exports.getInstallationState = getInstallationState;
17
+ const path_1 = __importDefault(require("path"));
18
+ const os_1 = __importDefault(require("os"));
19
+ const fs_extra_1 = __importDefault(require("fs-extra"));
20
+ exports.PACKAGE_VERSION = "4.0.0";
21
+ exports.AGENT_ID = "mcp-manager";
22
+ exports.SKILL_DIR_NAME = "mcp-management";
23
+ async function detectOpenCodePaths() {
24
+ const homeConfig = path_1.default.join(os_1.default.homedir(), ".config", "opencode");
25
+ const cwd = process.cwd();
26
+ const projectConfig = path_1.default.join(cwd, ".opencode");
27
+ const hasHomeConfig = await fs_extra_1.default.pathExists(homeConfig);
28
+ const hasProjectConfig = await fs_extra_1.default.pathExists(projectConfig);
29
+ if (!hasHomeConfig && !hasProjectConfig) {
30
+ throw new Error("OpenCode config not found. Expected ~/.config/opencode or .opencode in project.");
31
+ }
32
+ const configDir = hasProjectConfig ? projectConfig : homeConfig;
33
+ const commandDir = path_1.default.join(configDir, "command");
34
+ const skillsDir = path_1.default.join(configDir, "skills");
35
+ const agentConfigPath = path_1.default.join(configDir, "oh-my-opencode.json");
36
+ const packageRoot = path_1.default.join(__dirname, "..");
37
+ const templateSkillDir = path_1.default.join(packageRoot, "templates", "skill");
38
+ const templateCommandPath = path_1.default.join(packageRoot, "templates", "command-refresh.md");
39
+ const templateAgentPath = path_1.default.join(packageRoot, "templates", "agent.json");
40
+ const versionFilePath = path_1.default.join(configDir, ".agent-skill-version.json");
41
+ return {
42
+ configDir,
43
+ projectDir: cwd,
44
+ commandDir,
45
+ skillsDir,
46
+ agentConfigPath,
47
+ versionFilePath,
48
+ templateSkillDir,
49
+ templateCommandPath,
50
+ templateAgentPath,
51
+ };
52
+ }
53
+ async function ensureDirExists(dirPath) {
54
+ await fs_extra_1.default.ensureDir(dirPath);
55
+ }
56
+ async function readJsonFile(filePath, fallback) {
57
+ const exists = await fs_extra_1.default.pathExists(filePath);
58
+ if (!exists) {
59
+ return fallback;
60
+ }
61
+ const data = await fs_extra_1.default.readFile(filePath, "utf8");
62
+ return JSON.parse(data);
63
+ }
64
+ async function writeJsonFile(filePath, data) {
65
+ await fs_extra_1.default.writeFile(filePath, JSON.stringify(data, null, 2) + "\n", "utf8");
66
+ }
67
+ async function readText(filePath) {
68
+ return fs_extra_1.default.readFile(filePath, "utf8");
69
+ }
70
+ async function writeText(filePath, data) {
71
+ await fs_extra_1.default.writeFile(filePath, data, "utf8");
72
+ }
73
+ async function backupFile(filePath, suffix) {
74
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
75
+ const backupPath = `${filePath}.${suffix}-${timestamp}.bak`;
76
+ await fs_extra_1.default.copy(filePath, backupPath);
77
+ return backupPath;
78
+ }
79
+ async function fileDiffersFromTemplate(filePath, templatePath) {
80
+ const exists = await fs_extra_1.default.pathExists(filePath);
81
+ if (!exists) {
82
+ return false;
83
+ }
84
+ const [current, template] = await Promise.all([readText(filePath), readText(templatePath)]);
85
+ return current.trim() !== template.trim();
86
+ }
87
+ async function listFilesRecursively(dirPath, baseDir) {
88
+ const entries = await fs_extra_1.default.readdir(dirPath, { withFileTypes: true });
89
+ const files = [];
90
+ for (const entry of entries) {
91
+ const fullPath = path_1.default.join(dirPath, entry.name);
92
+ if (entry.isDirectory()) {
93
+ files.push(...(await listFilesRecursively(fullPath, baseDir)));
94
+ }
95
+ else if (entry.isFile()) {
96
+ files.push(path_1.default.relative(baseDir, fullPath));
97
+ }
98
+ }
99
+ return files;
100
+ }
101
+ async function directoryDiffersFromTemplate(dirPath, templateDir) {
102
+ const [dirExists, templateExists] = await Promise.all([
103
+ fs_extra_1.default.pathExists(dirPath),
104
+ fs_extra_1.default.pathExists(templateDir),
105
+ ]);
106
+ if (!dirExists) {
107
+ return false;
108
+ }
109
+ if (!templateExists) {
110
+ return true;
111
+ }
112
+ const [currentFiles, templateFiles] = await Promise.all([
113
+ listFilesRecursively(dirPath, dirPath),
114
+ listFilesRecursively(templateDir, templateDir),
115
+ ]);
116
+ currentFiles.sort();
117
+ templateFiles.sort();
118
+ if (currentFiles.length !== templateFiles.length) {
119
+ return true;
120
+ }
121
+ for (let index = 0; index < currentFiles.length; index += 1) {
122
+ if (currentFiles[index] !== templateFiles[index]) {
123
+ return true;
124
+ }
125
+ }
126
+ for (const relativePath of currentFiles) {
127
+ const [currentBuffer, templateBuffer] = await Promise.all([
128
+ fs_extra_1.default.readFile(path_1.default.join(dirPath, relativePath)),
129
+ fs_extra_1.default.readFile(path_1.default.join(templateDir, relativePath)),
130
+ ]);
131
+ if (!currentBuffer.equals(templateBuffer)) {
132
+ return true;
133
+ }
134
+ }
135
+ return false;
136
+ }
137
+ async function getInstallationState(paths) {
138
+ const skillPath = path_1.default.join(paths.skillsDir, exports.SKILL_DIR_NAME, "SKILL.md");
139
+ const commandPath = path_1.default.join(paths.commandDir, "agent-skill-refresh.md");
140
+ const [versionData, skillInstalled, commandInstalled] = await Promise.all([
141
+ readJsonFile(paths.versionFilePath, {}),
142
+ fs_extra_1.default.pathExists(skillPath),
143
+ fs_extra_1.default.pathExists(commandPath),
144
+ ]);
145
+ const agentConfig = await readJsonFile(paths.agentConfigPath, {});
146
+ const agents = agentConfig.agents;
147
+ const agentInstalled = agents ? Object.prototype.hasOwnProperty.call(agents, exports.AGENT_ID) : false;
148
+ return {
149
+ installedVersion: typeof versionData.version === "string" ? versionData.version : null,
150
+ skillInstalled,
151
+ commandInstalled,
152
+ agentInstalled,
153
+ };
154
+ }
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@nano-step/skill-manager",
3
+ "version": "4.0.0",
4
+ "description": "CLI tool that installs and manages AI agent skills, MCP tool routing, and workflow configurations.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "bin": {
8
+ "skill-manager": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin",
12
+ "dist",
13
+ "templates"
14
+ ],
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "prepublishOnly": "npm run build"
18
+ },
19
+ "keywords": [
20
+ "nano-step",
21
+ "opencode",
22
+ "mcp",
23
+ "model-context-protocol",
24
+ "ai",
25
+ "llm",
26
+ "cli"
27
+ ],
28
+ "author": "nano-step",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/nano-step/skill-manager"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "dependencies": {
38
+ "chalk": "^5.3.0",
39
+ "commander": "^12.0.0",
40
+ "fs-extra": "^11.2.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/fs-extra": "^11.0.4",
44
+ "@types/node": "^20.11.30",
45
+ "typescript": "^5.4.5"
46
+ }
47
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "agents": {
3
+ "mcp-manager": {
4
+ "mode": "subagent",
5
+ "model": "gitlab/duo-chat-haiku-4-5",
6
+ "temperature": 0.1,
7
+ "description": "MCP tool router - isolates MCP tools in separate context window to save tokens",
8
+ "prompt_append": "You are mcp-manager, a specialized agent for executing MCP (Model Context Protocol) tools. Your purpose is to ISOLATE MCP tool definitions from the main agent's context window, reducing token usage by 80-95%.\n\nWORKFLOW:\n1. Read .opencode/mcp-tools.json cache for tool routing (if exists)\n2. Parse the task to identify which MCP category is needed (browser, github, graphql, docs, reasoning)\n3. Select the appropriate tool(s) from that category\n4. Execute the tool with correct parameters\n5. Filter/summarize results before returning to main agent\n\nTOOL CATEGORIES:\n- browser: chrome-devtools__* (screenshots, clicks, navigation, DOM)\n- github: github-*__* (PRs, issues, repos, commits, branches)\n- graphql: graphql-tools__* (schema, queries, mutations)\n- docs: context7__* (documentation lookup, library references)\n- reasoning: Sequential-Thinking__* (complex multi-step analysis)\n\nRESULT HANDLING:\n- For large results (>1000 chars): Summarize key information\n- For file operations: Return path/status, not full content\n- For data queries: Return relevant subset, not full dataset\n- Always include: Success/failure status, key identifiers, actionable next steps\n\nERROR HANDLING:\n- Tool not found: Suggest similar tools or ask for clarification\n- Tool execution failed: Return error details and potential fixes\n- Cache missing: Prompt user to run /mcp-refresh command\n- Never silently fail\n\nYou are FAST (Haiku model) and DETERMINISTIC (temp 0.1). Focus on accurate tool selection and execution."
9
+ }
10
+ }
11
+ }