@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 +218 -0
- package/bin/cli.js +9 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +34 -0
- package/dist/install.d.ts +1 -0
- package/dist/install.js +35 -0
- package/dist/remove.d.ts +1 -0
- package/dist/remove.js +32 -0
- package/dist/update.d.ts +1 -0
- package/dist/update.js +53 -0
- package/dist/utils.d.ts +30 -0
- package/dist/utils.js +154 -0
- package/package.json +47 -0
- package/templates/agent.json +11 -0
- package/templates/command-refresh.md +100 -0
- package/templates/command-workflow.md +188 -0
- package/templates/skill/SKILL.md +284 -0
- package/templates/skill/assets/tools-template.json +140 -0
- package/templates/skill/assets/workflow-schema.json +81 -0
- package/templates/skill/assets/workflow-templates.json +112 -0
- package/templates/skill/references/error-handling.md +260 -0
- package/templates/skill/references/result-handling.md +242 -0
- package/templates/skill/references/tool-categories.md +247 -0
- package/templates/skill/references/tool-execution.md +347 -0
- package/templates/skill/references/workflows.md +233 -0
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
package/dist/index.d.ts
ADDED
|
@@ -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>;
|
package/dist/install.js
ADDED
|
@@ -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
|
+
}
|
package/dist/remove.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/update.d.ts
ADDED
|
@@ -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
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -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
|
+
}
|