@orchestra-research/ai-research-skills 1.2.0 → 1.3.6
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 +62 -6
- package/package.json +1 -1
- package/src/agents.js +67 -3
- package/src/ascii.js +37 -0
- package/src/index.js +199 -24
- package/src/installer.js +407 -0
- package/src/prompts.js +146 -3
package/README.md
CHANGED
|
@@ -8,10 +8,10 @@ npx @orchestra-research/ai-research-skills
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
|
|
11
|
-
- **
|
|
11
|
+
- **83 skills** across 20 categories for AI research engineering
|
|
12
12
|
- **Auto-detects** installed coding agents
|
|
13
13
|
- **Interactive installer** with guided experience
|
|
14
|
-
- **
|
|
14
|
+
- **Global or local install** — install globally with symlinks, or per-project with `--local` for version-controlled, project-specific skill sets
|
|
15
15
|
- **Works with 8 agents**: Claude Code, OpenCode, OpenClaw, Cursor, Codex, Gemini CLI, Qwen Code, and shared `.agents/`
|
|
16
16
|
|
|
17
17
|
## Quick Start
|
|
@@ -34,7 +34,7 @@ This will:
|
|
|
34
34
|
# Interactive mode (recommended)
|
|
35
35
|
npx @orchestra-research/ai-research-skills
|
|
36
36
|
|
|
37
|
-
# Install everything
|
|
37
|
+
# Install everything (global)
|
|
38
38
|
npx @orchestra-research/ai-research-skills install --all
|
|
39
39
|
|
|
40
40
|
# Install a specific category
|
|
@@ -47,6 +47,46 @@ npx @orchestra-research/ai-research-skills list
|
|
|
47
47
|
npx @orchestra-research/ai-research-skills update
|
|
48
48
|
```
|
|
49
49
|
|
|
50
|
+
### Local Installation (per-project)
|
|
51
|
+
|
|
52
|
+
Install skills directly into your project directory so different projects can have different skill sets:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Install all skills locally to the current project
|
|
56
|
+
npx @orchestra-research/ai-research-skills install --all --local
|
|
57
|
+
|
|
58
|
+
# Install a category locally
|
|
59
|
+
npx @orchestra-research/ai-research-skills install --category post-training --local
|
|
60
|
+
|
|
61
|
+
# List locally installed skills
|
|
62
|
+
npx @orchestra-research/ai-research-skills list --local
|
|
63
|
+
|
|
64
|
+
# Update local skills
|
|
65
|
+
npx @orchestra-research/ai-research-skills update --local
|
|
66
|
+
|
|
67
|
+
# Uninstall local skills
|
|
68
|
+
npx @orchestra-research/ai-research-skills uninstall --local
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Local installation copies skills (not symlinks) into agent directories within your project:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
my-project/
|
|
75
|
+
├── .claude/skills/ # Claude Code picks these up
|
|
76
|
+
│ ├── grpo-rl-training/
|
|
77
|
+
│ └── vllm/
|
|
78
|
+
├── .cursor/skills/ # Cursor picks these up
|
|
79
|
+
│ ├── grpo-rl-training/
|
|
80
|
+
│ └── vllm/
|
|
81
|
+
├── .orchestra-skills.json # Tracks installed skills
|
|
82
|
+
└── ...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Benefits:
|
|
86
|
+
- **Per-project skills**: Each project gets only the skills it needs
|
|
87
|
+
- **Version control**: Commit skills to your repo so the whole team has them
|
|
88
|
+
- **Reproducible**: Lock file (`.orchestra-skills.json`) tracks what's installed
|
|
89
|
+
|
|
50
90
|
## Categories
|
|
51
91
|
|
|
52
92
|
| Category | Skills | Description |
|
|
@@ -61,25 +101,41 @@ npx @orchestra-research/ai-research-skills update
|
|
|
61
101
|
|
|
62
102
|
## How It Works
|
|
63
103
|
|
|
64
|
-
|
|
104
|
+
### Global Install (default)
|
|
105
|
+
|
|
106
|
+
1. **Canonical Storage**: Skills are stored once at `~/.orchestra/skills/`
|
|
65
107
|
2. **Symlinks**: Each agent gets symlinks pointing to the canonical copy
|
|
66
108
|
3. **Auto-activation**: Skills activate when you discuss relevant topics
|
|
67
109
|
|
|
68
110
|
```
|
|
69
|
-
~/.
|
|
111
|
+
~/.orchestra/skills/ # Single source of truth
|
|
70
112
|
├── 06-post-training/
|
|
71
113
|
│ ├── verl/
|
|
72
114
|
│ └── grpo-rl-training/
|
|
73
115
|
└── ...
|
|
74
116
|
|
|
75
117
|
~/.claude/skills/ # Symlinks for Claude Code
|
|
76
|
-
├── verl → ~/.
|
|
118
|
+
├── verl → ~/.orchestra/skills/.../verl
|
|
77
119
|
└── grpo-rl-training → ...
|
|
78
120
|
|
|
79
121
|
~/.cursor/skills/ # Symlinks for Cursor
|
|
80
122
|
└── (same links)
|
|
81
123
|
```
|
|
82
124
|
|
|
125
|
+
### Local Install (`--local`)
|
|
126
|
+
|
|
127
|
+
1. **Direct Copy**: Skills are copied into agent directories within your project
|
|
128
|
+
2. **Version Control**: Files can be committed to git for team sharing
|
|
129
|
+
3. **Lock File**: `.orchestra-skills.json` tracks what's installed
|
|
130
|
+
|
|
131
|
+
```
|
|
132
|
+
my-project/
|
|
133
|
+
├── .claude/skills/verl/ # Copied for Claude Code
|
|
134
|
+
├── .cursor/skills/verl/ # Copied for Cursor
|
|
135
|
+
├── .codex/skills/verl/ # Copied for Codex
|
|
136
|
+
└── .orchestra-skills.json # Lock file
|
|
137
|
+
```
|
|
138
|
+
|
|
83
139
|
## Supported Agents
|
|
84
140
|
|
|
85
141
|
| Agent | Config Directory |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@orchestra-research/ai-research-skills",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "Install AI research engineering skills to your coding agents (Claude Code, OpenCode, Cursor, Gemini CLI, and more)",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
package/src/agents.js
CHANGED
|
@@ -3,8 +3,13 @@ import { homedir } from 'os';
|
|
|
3
3
|
import { join } from 'path';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* Supported coding agents with their global config directories
|
|
7
|
-
*
|
|
6
|
+
* Supported coding agents with their global and local config directories
|
|
7
|
+
*
|
|
8
|
+
* Global: ~/.{agent}/skills/ (home directory)
|
|
9
|
+
* Local: .{agent}/skills/ (project directory)
|
|
10
|
+
*
|
|
11
|
+
* localConfigDir/localSkillsDir define where skills go at the project level.
|
|
12
|
+
* These may differ from global paths (e.g., OpenClaw uses <project>/skills/).
|
|
8
13
|
*/
|
|
9
14
|
export const SUPPORTED_AGENTS = [
|
|
10
15
|
{
|
|
@@ -12,53 +17,69 @@ export const SUPPORTED_AGENTS = [
|
|
|
12
17
|
name: 'Claude Code',
|
|
13
18
|
configDir: '.claude',
|
|
14
19
|
skillsDir: 'skills',
|
|
20
|
+
localConfigDir: '.claude',
|
|
21
|
+
localSkillsDir: 'skills',
|
|
15
22
|
},
|
|
16
23
|
{
|
|
17
24
|
id: 'cursor',
|
|
18
25
|
name: 'Cursor',
|
|
19
26
|
configDir: '.cursor',
|
|
20
27
|
skillsDir: 'skills',
|
|
28
|
+
localConfigDir: '.cursor',
|
|
29
|
+
localSkillsDir: 'skills',
|
|
21
30
|
},
|
|
22
31
|
{
|
|
23
32
|
id: 'codex',
|
|
24
33
|
name: 'Codex',
|
|
25
34
|
configDir: '.codex',
|
|
26
35
|
skillsDir: 'skills',
|
|
36
|
+
localConfigDir: '.codex',
|
|
37
|
+
localSkillsDir: 'skills',
|
|
27
38
|
},
|
|
28
39
|
{
|
|
29
40
|
id: 'gemini',
|
|
30
41
|
name: 'Gemini CLI',
|
|
31
42
|
configDir: '.gemini',
|
|
32
43
|
skillsDir: 'skills',
|
|
44
|
+
localConfigDir: '.gemini',
|
|
45
|
+
localSkillsDir: 'skills',
|
|
33
46
|
},
|
|
34
47
|
{
|
|
35
48
|
id: 'qwen',
|
|
36
49
|
name: 'Qwen Code',
|
|
37
50
|
configDir: '.qwen',
|
|
38
51
|
skillsDir: 'skills',
|
|
52
|
+
localConfigDir: '.qwen',
|
|
53
|
+
localSkillsDir: 'skills',
|
|
39
54
|
},
|
|
40
55
|
{
|
|
41
56
|
id: 'opencode',
|
|
42
57
|
name: 'OpenCode',
|
|
43
58
|
configDir: '.config/opencode',
|
|
44
59
|
skillsDir: 'skills',
|
|
60
|
+
localConfigDir: '.opencode',
|
|
61
|
+
localSkillsDir: 'skills',
|
|
45
62
|
},
|
|
46
63
|
{
|
|
47
64
|
id: 'openclaw',
|
|
48
65
|
name: 'OpenClaw',
|
|
49
66
|
configDir: '.openclaw',
|
|
50
67
|
skillsDir: 'skills',
|
|
68
|
+
localConfigDir: '.',
|
|
69
|
+
localSkillsDir: 'skills',
|
|
51
70
|
},
|
|
52
71
|
{
|
|
53
72
|
id: 'agents',
|
|
54
73
|
name: 'Shared Agents',
|
|
55
74
|
configDir: '.agents',
|
|
56
75
|
skillsDir: 'skills',
|
|
76
|
+
localConfigDir: '.agents',
|
|
77
|
+
localSkillsDir: 'skills',
|
|
57
78
|
},
|
|
58
79
|
];
|
|
59
80
|
|
|
60
81
|
/**
|
|
61
|
-
* Detect which coding agents are installed on the system
|
|
82
|
+
* Detect which coding agents are installed on the system (global)
|
|
62
83
|
* @returns {Array} List of detected agents with their paths
|
|
63
84
|
*/
|
|
64
85
|
export function detectAgents() {
|
|
@@ -81,6 +102,49 @@ export function detectAgents() {
|
|
|
81
102
|
return detected;
|
|
82
103
|
}
|
|
83
104
|
|
|
105
|
+
/**
|
|
106
|
+
* Build local agent targets for a given project directory
|
|
107
|
+
* @param {Array} agents - List of agent configs (from SUPPORTED_AGENTS or detectAgents)
|
|
108
|
+
* @param {string} projectDir - Absolute path to the project root
|
|
109
|
+
* @returns {Array} List of agents with local paths set
|
|
110
|
+
*/
|
|
111
|
+
export function buildLocalAgentTargets(agents, projectDir) {
|
|
112
|
+
return agents.map(agent => ({
|
|
113
|
+
...agent,
|
|
114
|
+
path: `./${agent.localConfigDir || agent.configDir}`,
|
|
115
|
+
fullPath: join(projectDir, agent.localConfigDir || agent.configDir),
|
|
116
|
+
skillsPath: join(projectDir, agent.localConfigDir || agent.configDir, agent.localSkillsDir || agent.skillsDir),
|
|
117
|
+
local: true,
|
|
118
|
+
}));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Detect which coding agents have local skills in a project directory
|
|
123
|
+
* @param {string} projectDir - Absolute path to the project root
|
|
124
|
+
* @returns {Array} List of agents with local skills directories
|
|
125
|
+
*/
|
|
126
|
+
export function detectLocalAgents(projectDir) {
|
|
127
|
+
const detected = [];
|
|
128
|
+
|
|
129
|
+
for (const agent of SUPPORTED_AGENTS) {
|
|
130
|
+
const localConfigDir = agent.localConfigDir || agent.configDir;
|
|
131
|
+
const localSkillsDir = agent.localSkillsDir || agent.skillsDir;
|
|
132
|
+
const skillsPath = join(projectDir, localConfigDir, localSkillsDir);
|
|
133
|
+
|
|
134
|
+
if (existsSync(skillsPath)) {
|
|
135
|
+
detected.push({
|
|
136
|
+
...agent,
|
|
137
|
+
path: `./${localConfigDir}`,
|
|
138
|
+
fullPath: join(projectDir, localConfigDir),
|
|
139
|
+
skillsPath,
|
|
140
|
+
local: true,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return detected;
|
|
146
|
+
}
|
|
147
|
+
|
|
84
148
|
/**
|
|
85
149
|
* Get agent by ID
|
|
86
150
|
* @param {string} id Agent ID
|
package/src/ascii.js
CHANGED
|
@@ -100,6 +100,43 @@ export function showSuccess(skillCount, agents) {
|
|
|
100
100
|
console.log();
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
+
/**
|
|
104
|
+
* Local installation success screen
|
|
105
|
+
*/
|
|
106
|
+
export function showLocalSuccess(skillCount, agents, projectDir) {
|
|
107
|
+
console.clear();
|
|
108
|
+
console.log();
|
|
109
|
+
console.log();
|
|
110
|
+
console.log(chalk.green.bold(' ✓ Local Installation Complete'));
|
|
111
|
+
console.log();
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(` Installed ${chalk.white(skillCount)} skills to ${chalk.white(agents.length)} agent${agents.length !== 1 ? 's' : ''}`);
|
|
114
|
+
console.log(` Project: ${chalk.white(projectDir)}`);
|
|
115
|
+
console.log();
|
|
116
|
+
|
|
117
|
+
console.log(chalk.dim(' Skills copied to:'));
|
|
118
|
+
for (const agent of agents) {
|
|
119
|
+
console.log(chalk.dim(` → ${agent.skillsPath.replace(projectDir, '.')}`));
|
|
120
|
+
}
|
|
121
|
+
console.log();
|
|
122
|
+
console.log(chalk.dim(' Skills are copied (not symlinked) and can be'));
|
|
123
|
+
console.log(chalk.dim(' committed to version control for team sharing.'));
|
|
124
|
+
console.log();
|
|
125
|
+
console.log(chalk.dim(' ────────────────────────────────────────────────────────────'));
|
|
126
|
+
console.log();
|
|
127
|
+
console.log(chalk.white(' Commands:'));
|
|
128
|
+
console.log();
|
|
129
|
+
console.log(` ${chalk.dim('$')} ${chalk.cyan('npx @orchestra-research/ai-research-skills list --local')}`);
|
|
130
|
+
console.log(` ${chalk.dim('$')} ${chalk.cyan('npx @orchestra-research/ai-research-skills update --local')}`);
|
|
131
|
+
console.log(` ${chalk.dim('$')} ${chalk.cyan('npx @orchestra-research/ai-research-skills uninstall --local')}`);
|
|
132
|
+
console.log();
|
|
133
|
+
console.log(chalk.dim(' ────────────────────────────────────────────────────────────'));
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(chalk.dim(' Tip: Add .orchestra-skills.json to your repo'));
|
|
136
|
+
console.log(chalk.dim(' so teammates can run `update --local` to sync.'));
|
|
137
|
+
console.log();
|
|
138
|
+
}
|
|
139
|
+
|
|
103
140
|
/**
|
|
104
141
|
* No agents found screen
|
|
105
142
|
*/
|
package/src/index.js
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import ora from 'ora';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
3
|
|
|
4
|
-
import { detectAgents } from './agents.js';
|
|
5
|
-
import { showWelcome, showAgentsDetected, showSuccess, showNoAgents, showMenuHeader } from './ascii.js';
|
|
4
|
+
import { detectAgents, buildLocalAgentTargets, detectLocalAgents, SUPPORTED_AGENTS } from './agents.js';
|
|
5
|
+
import { showWelcome, showAgentsDetected, showSuccess, showLocalSuccess, showNoAgents, showMenuHeader } from './ascii.js';
|
|
6
6
|
import {
|
|
7
7
|
askInstallChoice,
|
|
8
8
|
askCategories,
|
|
9
9
|
askIndividualSkills,
|
|
10
10
|
askConfirmation,
|
|
11
|
+
askLocalConfirmation,
|
|
11
12
|
askMainMenuAction,
|
|
12
13
|
askSelectAgents,
|
|
14
|
+
askSelectLocalAgents,
|
|
13
15
|
askAfterAction,
|
|
14
16
|
askUninstallChoice,
|
|
15
17
|
askSelectSkillsToUninstall,
|
|
@@ -20,7 +22,25 @@ import {
|
|
|
20
22
|
QUICK_START_SKILLS,
|
|
21
23
|
getTotalSkillCount,
|
|
22
24
|
} from './prompts.js';
|
|
23
|
-
import {
|
|
25
|
+
import {
|
|
26
|
+
installSkills,
|
|
27
|
+
installSpecificSkills,
|
|
28
|
+
installSkillsLocal,
|
|
29
|
+
installSpecificSkillsLocal,
|
|
30
|
+
listInstalledSkills,
|
|
31
|
+
listLocalSkills,
|
|
32
|
+
getAllCategoryIds,
|
|
33
|
+
updateInstalledSkills,
|
|
34
|
+
updateLocalSkills,
|
|
35
|
+
uninstallAllSkills,
|
|
36
|
+
uninstallSpecificSkills,
|
|
37
|
+
uninstallLocalSkills,
|
|
38
|
+
uninstallAllLocalSkills,
|
|
39
|
+
getInstalledSkillPaths,
|
|
40
|
+
getInstalledSkillsForSelection,
|
|
41
|
+
getLocalSkillPaths,
|
|
42
|
+
getLocalSkillsForSelection,
|
|
43
|
+
} from './installer.js';
|
|
24
44
|
|
|
25
45
|
/**
|
|
26
46
|
* Sleep utility
|
|
@@ -166,6 +186,103 @@ async function interactiveFlow() {
|
|
|
166
186
|
continue step2_menu;
|
|
167
187
|
}
|
|
168
188
|
|
|
189
|
+
if (menuAction === 'install-local') {
|
|
190
|
+
// LOCAL INSTALLATION FLOW
|
|
191
|
+
const projectDir = process.cwd();
|
|
192
|
+
const localAgents = buildLocalAgentTargets(
|
|
193
|
+
agents.length > 0 ? agents : SUPPORTED_AGENTS.slice(0, 1).map(a => ({ ...a })),
|
|
194
|
+
projectDir
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
// Choose what to install locally
|
|
198
|
+
step_local_choice:
|
|
199
|
+
while (true) {
|
|
200
|
+
showMenuHeader();
|
|
201
|
+
console.log(chalk.cyan(` Local install to: ${projectDir}`));
|
|
202
|
+
console.log();
|
|
203
|
+
const choice = await askInstallChoice();
|
|
204
|
+
|
|
205
|
+
if (choice === 'back') {
|
|
206
|
+
continue step2_menu;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
let categories = [];
|
|
210
|
+
let selectedSkills = [];
|
|
211
|
+
let skillCount = 0;
|
|
212
|
+
let installType = choice;
|
|
213
|
+
|
|
214
|
+
if (choice === 'everything') {
|
|
215
|
+
categories = getAllCategoryIds();
|
|
216
|
+
skillCount = getTotalSkillCount();
|
|
217
|
+
} else if (choice === 'quickstart') {
|
|
218
|
+
categories = [...new Set(QUICK_START_SKILLS.map(s => s.split('/')[0]))];
|
|
219
|
+
skillCount = QUICK_START_SKILLS.length;
|
|
220
|
+
} else if (choice === 'categories') {
|
|
221
|
+
step_local_categories:
|
|
222
|
+
while (true) {
|
|
223
|
+
showMenuHeader();
|
|
224
|
+
const result = await askCategories();
|
|
225
|
+
if (result.action === 'back') continue step_local_choice;
|
|
226
|
+
if (result.action === 'retry') continue step_local_categories;
|
|
227
|
+
categories = result.categories;
|
|
228
|
+
skillCount = CATEGORIES
|
|
229
|
+
.filter(c => categories.includes(c.id))
|
|
230
|
+
.reduce((sum, c) => sum + c.skills, 0);
|
|
231
|
+
break;
|
|
232
|
+
}
|
|
233
|
+
} else if (choice === 'individual') {
|
|
234
|
+
step_local_individual:
|
|
235
|
+
while (true) {
|
|
236
|
+
showMenuHeader();
|
|
237
|
+
const result = await askIndividualSkills();
|
|
238
|
+
if (result.action === 'back') continue step_local_choice;
|
|
239
|
+
if (result.action === 'retry') continue step_local_individual;
|
|
240
|
+
selectedSkills = result.skills;
|
|
241
|
+
skillCount = selectedSkills.length;
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Select local agents
|
|
247
|
+
let targetAgents = localAgents;
|
|
248
|
+
step_local_agents:
|
|
249
|
+
while (true) {
|
|
250
|
+
showMenuHeader();
|
|
251
|
+
const agentResult = await askSelectLocalAgents(localAgents);
|
|
252
|
+
if (agentResult.action === 'back') continue step_local_choice;
|
|
253
|
+
if (agentResult.action === 'retry') continue step_local_agents;
|
|
254
|
+
targetAgents = agentResult.agents;
|
|
255
|
+
|
|
256
|
+
// Confirmation
|
|
257
|
+
showMenuHeader();
|
|
258
|
+
const confirmAction = await askLocalConfirmation(skillCount, targetAgents, projectDir, categories, selectedSkills, installType);
|
|
259
|
+
if (confirmAction === 'exit') {
|
|
260
|
+
console.log(chalk.dim(' Goodbye!'));
|
|
261
|
+
console.log();
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
if (confirmAction === 'back') continue step_local_agents;
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Install locally
|
|
269
|
+
console.log();
|
|
270
|
+
console.log(chalk.cyan(' Installing locally...'));
|
|
271
|
+
console.log();
|
|
272
|
+
|
|
273
|
+
let installedCount;
|
|
274
|
+
if (selectedSkills.length > 0) {
|
|
275
|
+
installedCount = await installSpecificSkillsLocal(selectedSkills, targetAgents, projectDir);
|
|
276
|
+
} else {
|
|
277
|
+
installedCount = await installSkillsLocal(categories, targetAgents, projectDir);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
await sleep(500);
|
|
281
|
+
showLocalSuccess(installedCount, targetAgents, projectDir);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
169
286
|
// STEP 3: Choose what to install (menuAction === 'install')
|
|
170
287
|
step3_choice:
|
|
171
288
|
while (true) {
|
|
@@ -286,35 +403,77 @@ async function interactiveFlow() {
|
|
|
286
403
|
* Direct command mode (for power users)
|
|
287
404
|
*/
|
|
288
405
|
async function commandMode(options) {
|
|
406
|
+
const projectDir = process.cwd();
|
|
407
|
+
const isLocal = options.local;
|
|
408
|
+
|
|
289
409
|
if (options.command === 'list') {
|
|
290
|
-
|
|
410
|
+
if (isLocal) {
|
|
411
|
+
listLocalSkills(projectDir);
|
|
412
|
+
} else {
|
|
413
|
+
listInstalledSkills();
|
|
414
|
+
}
|
|
291
415
|
return;
|
|
292
416
|
}
|
|
293
417
|
|
|
294
418
|
if (options.command === 'update') {
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
419
|
+
if (isLocal) {
|
|
420
|
+
const agents = detectAgents();
|
|
421
|
+
const localAgents = buildLocalAgentTargets(
|
|
422
|
+
agents.length > 0 ? agents : [SUPPORTED_AGENTS[0]],
|
|
423
|
+
projectDir
|
|
424
|
+
);
|
|
425
|
+
const localPaths = getLocalSkillPaths(projectDir);
|
|
426
|
+
if (localPaths.length === 0) {
|
|
427
|
+
console.log(chalk.yellow('No local skills installed to update.'));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
console.log(chalk.cyan(`Updating ${localPaths.length} local skills...`));
|
|
431
|
+
await updateLocalSkills(localAgents, projectDir);
|
|
432
|
+
console.log(chalk.green('✓ Local skills updated!'));
|
|
433
|
+
} else {
|
|
434
|
+
const agents = detectAgents();
|
|
435
|
+
if (agents.length === 0) {
|
|
436
|
+
console.log(chalk.yellow('No agents detected.'));
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const installedPaths = getInstalledSkillPaths();
|
|
440
|
+
if (installedPaths.length === 0) {
|
|
441
|
+
console.log(chalk.yellow('No skills installed to update.'));
|
|
442
|
+
return;
|
|
443
|
+
}
|
|
444
|
+
console.log(chalk.cyan(`Updating ${installedPaths.length} installed skills...`));
|
|
445
|
+
await updateInstalledSkills(agents);
|
|
446
|
+
console.log(chalk.green('✓ Skills updated!'));
|
|
304
447
|
}
|
|
305
|
-
console.log(chalk.cyan(`Updating ${installedPaths.length} installed skills...`));
|
|
306
|
-
await updateInstalledSkills(agents);
|
|
307
|
-
console.log(chalk.green('✓ Skills updated!'));
|
|
308
448
|
return;
|
|
309
449
|
}
|
|
310
450
|
|
|
311
|
-
if (options.command === '
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
451
|
+
if (options.command === 'uninstall') {
|
|
452
|
+
if (isLocal) {
|
|
453
|
+
const agents = detectAgents();
|
|
454
|
+
const localAgents = buildLocalAgentTargets(
|
|
455
|
+
agents.length > 0 ? agents : [SUPPORTED_AGENTS[0]],
|
|
456
|
+
projectDir
|
|
457
|
+
);
|
|
458
|
+
const detectedLocal = detectLocalAgents(projectDir);
|
|
459
|
+
const targets = detectedLocal.length > 0 ? detectedLocal : localAgents;
|
|
460
|
+
console.log(chalk.cyan('Uninstalling local skills...'));
|
|
461
|
+
await uninstallAllLocalSkills(targets, projectDir);
|
|
462
|
+
console.log(chalk.green('✓ Local skills removed!'));
|
|
463
|
+
} else {
|
|
464
|
+
const agents = detectAgents();
|
|
465
|
+
if (agents.length === 0) {
|
|
466
|
+
console.log(chalk.yellow('No agents detected.'));
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
console.log(chalk.cyan('Uninstalling all skills...'));
|
|
470
|
+
await uninstallAllSkills(agents);
|
|
471
|
+
console.log(chalk.green('✓ Skills removed!'));
|
|
316
472
|
}
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
317
475
|
|
|
476
|
+
if (options.command === 'install' || options.all || options.category || options.skill) {
|
|
318
477
|
let categories;
|
|
319
478
|
if (options.all) {
|
|
320
479
|
categories = getAllCategoryIds();
|
|
@@ -334,9 +493,25 @@ async function commandMode(options) {
|
|
|
334
493
|
categories = getAllCategoryIds();
|
|
335
494
|
}
|
|
336
495
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
496
|
+
if (isLocal) {
|
|
497
|
+
const agents = detectAgents();
|
|
498
|
+
const localAgents = buildLocalAgentTargets(
|
|
499
|
+
agents.length > 0 ? agents : [SUPPORTED_AGENTS[0]],
|
|
500
|
+
projectDir
|
|
501
|
+
);
|
|
502
|
+
console.log(chalk.cyan(`Installing skills locally to ${projectDir}...`));
|
|
503
|
+
await installSkillsLocal(categories, localAgents, projectDir);
|
|
504
|
+
console.log(chalk.green('✓ Done! Skills installed to project directory.'));
|
|
505
|
+
} else {
|
|
506
|
+
const agents = detectAgents();
|
|
507
|
+
if (agents.length === 0) {
|
|
508
|
+
console.log(chalk.yellow('No agents detected.'));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
console.log(chalk.cyan('Installing skills...'));
|
|
512
|
+
await installSkills(categories, agents);
|
|
513
|
+
console.log(chalk.green('✓ Done!'));
|
|
514
|
+
}
|
|
340
515
|
return;
|
|
341
516
|
}
|
|
342
517
|
}
|
package/src/installer.js
CHANGED
|
@@ -8,6 +8,7 @@ import ora from 'ora';
|
|
|
8
8
|
const REPO_URL = 'https://github.com/Orchestra-Research/AI-research-SKILLs';
|
|
9
9
|
const CANONICAL_DIR = join(homedir(), '.orchestra', 'skills');
|
|
10
10
|
const LOCK_FILE = join(homedir(), '.orchestra', '.lock.json');
|
|
11
|
+
const LOCAL_LOCK_FILENAME = '.orchestra-skills.json';
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Copy directory contents (cross-platform replacement for `cp -r source/* dest/`)
|
|
@@ -621,3 +622,409 @@ export function getInstalledSkillsForSelection() {
|
|
|
621
622
|
}
|
|
622
623
|
});
|
|
623
624
|
}
|
|
625
|
+
|
|
626
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
627
|
+
// Local (project-level) installation
|
|
628
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Get the local lock file path for a project
|
|
632
|
+
*/
|
|
633
|
+
function getLocalLockPath(projectDir) {
|
|
634
|
+
return join(projectDir, LOCAL_LOCK_FILENAME);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Read local lock file
|
|
639
|
+
*/
|
|
640
|
+
function readLocalLock(projectDir) {
|
|
641
|
+
const lockPath = getLocalLockPath(projectDir);
|
|
642
|
+
if (existsSync(lockPath)) {
|
|
643
|
+
try {
|
|
644
|
+
return JSON.parse(readFileSync(lockPath, 'utf8'));
|
|
645
|
+
} catch {
|
|
646
|
+
return { version: null, installedAt: null, skills: [], agents: [] };
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
return { version: null, installedAt: null, skills: [], agents: [] };
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
/**
|
|
653
|
+
* Write local lock file
|
|
654
|
+
*/
|
|
655
|
+
function writeLocalLock(projectDir, data) {
|
|
656
|
+
writeFileSync(getLocalLockPath(projectDir), JSON.stringify(data, null, 2));
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/**
|
|
660
|
+
* Copy skills directly into agent local directories (no symlinks)
|
|
661
|
+
* @param {Object} agent - Agent with skillsPath set to local project path
|
|
662
|
+
* @param {Array} skills - Skills list from download
|
|
663
|
+
* @param {string} tempDir - Temp clone directory
|
|
664
|
+
*/
|
|
665
|
+
function copySkillsToLocal(agent, skills, tempDir) {
|
|
666
|
+
const agentSkillsPath = agent.skillsPath;
|
|
667
|
+
|
|
668
|
+
if (!existsSync(agentSkillsPath)) {
|
|
669
|
+
mkdirSync(agentSkillsPath, { recursive: true });
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
let copiedCount = 0;
|
|
673
|
+
|
|
674
|
+
for (const skill of skills) {
|
|
675
|
+
const sourcePath = skill.standalone
|
|
676
|
+
? join(tempDir, skill.category)
|
|
677
|
+
: join(tempDir, skill.category, skill.skill);
|
|
678
|
+
|
|
679
|
+
if (!existsSync(sourcePath)) continue;
|
|
680
|
+
|
|
681
|
+
const destName = skill.standalone ? skill.category : skill.skill;
|
|
682
|
+
const destPath = join(agentSkillsPath, destName);
|
|
683
|
+
|
|
684
|
+
// Remove existing if present
|
|
685
|
+
if (existsSync(destPath)) {
|
|
686
|
+
rmSync(destPath, { recursive: true, force: true });
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
mkdirSync(destPath, { recursive: true });
|
|
690
|
+
copyDirectoryContents(sourcePath, destPath);
|
|
691
|
+
copiedCount++;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
return copiedCount;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Download and install skills locally to agent project directories
|
|
699
|
+
*/
|
|
700
|
+
export async function installSkillsLocal(categories, agents, projectDir) {
|
|
701
|
+
const spinner = ora('Downloading from GitHub...').start();
|
|
702
|
+
|
|
703
|
+
const tempDir = join(homedir(), '.orchestra', '.temp-clone');
|
|
704
|
+
|
|
705
|
+
try {
|
|
706
|
+
if (existsSync(tempDir)) {
|
|
707
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
spinner.text = 'Cloning repository...';
|
|
711
|
+
execSync(`git clone --depth 1 ${REPO_URL}.git ${tempDir}`, {
|
|
712
|
+
stdio: 'pipe',
|
|
713
|
+
});
|
|
714
|
+
|
|
715
|
+
// Build skills list from categories
|
|
716
|
+
const skills = [];
|
|
717
|
+
for (const categoryId of categories) {
|
|
718
|
+
const categoryPath = join(tempDir, categoryId);
|
|
719
|
+
if (!existsSync(categoryPath)) continue;
|
|
720
|
+
|
|
721
|
+
const standaloneSkillPath = join(categoryPath, 'SKILL.md');
|
|
722
|
+
if (existsSync(standaloneSkillPath)) {
|
|
723
|
+
skills.push({ category: categoryId, skill: categoryId, standalone: true });
|
|
724
|
+
} else {
|
|
725
|
+
const entries = readdirSync(categoryPath, { withFileTypes: true });
|
|
726
|
+
for (const entry of entries) {
|
|
727
|
+
if (entry.isDirectory()) {
|
|
728
|
+
const skillPath = join(categoryPath, entry.name, 'SKILL.md');
|
|
729
|
+
if (existsSync(skillPath)) {
|
|
730
|
+
skills.push({ category: categoryId, skill: entry.name, standalone: false });
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
spinner.succeed(`Found ${skills.length} skills`);
|
|
738
|
+
|
|
739
|
+
// Copy to each agent's local directory
|
|
740
|
+
spinner.start('Installing to project...');
|
|
741
|
+
|
|
742
|
+
for (const agent of agents) {
|
|
743
|
+
const count = copySkillsToLocal(agent, skills, tempDir);
|
|
744
|
+
console.log(` ${chalk.green('✓')} ${agent.name.padEnd(14)} ${chalk.dim('→')} ${agent.skillsPath.replace(projectDir, '.').padEnd(30)} ${chalk.green(count + ' skills')}`);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
spinner.stop();
|
|
748
|
+
|
|
749
|
+
// Cleanup
|
|
750
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
751
|
+
|
|
752
|
+
// Update local lock file
|
|
753
|
+
const lock = readLocalLock(projectDir);
|
|
754
|
+
lock.version = '1.0.0';
|
|
755
|
+
lock.installedAt = new Date().toISOString();
|
|
756
|
+
lock.skills = [...(lock.skills || []).filter(s => {
|
|
757
|
+
const existing = `${s.category}/${s.skill}`;
|
|
758
|
+
return !skills.some(ns => `${ns.category}/${ns.skill}` === existing);
|
|
759
|
+
}), ...skills];
|
|
760
|
+
lock.agents = agents.map(a => a.id);
|
|
761
|
+
writeLocalLock(projectDir, lock);
|
|
762
|
+
|
|
763
|
+
return skills.length;
|
|
764
|
+
} catch (error) {
|
|
765
|
+
if (existsSync(tempDir)) {
|
|
766
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
767
|
+
}
|
|
768
|
+
spinner.fail('Installation failed');
|
|
769
|
+
throw error;
|
|
770
|
+
}
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Download and install specific skills locally
|
|
775
|
+
*/
|
|
776
|
+
export async function installSpecificSkillsLocal(skillPaths, agents, projectDir) {
|
|
777
|
+
const spinner = ora('Downloading from GitHub...').start();
|
|
778
|
+
|
|
779
|
+
const tempDir = join(homedir(), '.orchestra', '.temp-clone');
|
|
780
|
+
|
|
781
|
+
try {
|
|
782
|
+
if (existsSync(tempDir)) {
|
|
783
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
spinner.text = 'Cloning repository...';
|
|
787
|
+
execSync(`git clone --depth 1 ${REPO_URL}.git ${tempDir}`, {
|
|
788
|
+
stdio: 'pipe',
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
const skills = [];
|
|
792
|
+
for (const skillPath of skillPaths) {
|
|
793
|
+
const parts = skillPath.split('/');
|
|
794
|
+
const categoryId = parts[0];
|
|
795
|
+
const skillName = parts[1] || null;
|
|
796
|
+
|
|
797
|
+
if (skillName) {
|
|
798
|
+
const sourcePath = join(tempDir, categoryId, skillName);
|
|
799
|
+
if (existsSync(sourcePath)) {
|
|
800
|
+
skills.push({ category: categoryId, skill: skillName, standalone: false });
|
|
801
|
+
}
|
|
802
|
+
} else {
|
|
803
|
+
const sourcePath = join(tempDir, categoryId);
|
|
804
|
+
if (existsSync(sourcePath)) {
|
|
805
|
+
skills.push({ category: categoryId, skill: categoryId, standalone: true });
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
spinner.succeed(`Found ${skills.length} skills`);
|
|
811
|
+
|
|
812
|
+
// Copy to each agent's local directory
|
|
813
|
+
spinner.start('Installing to project...');
|
|
814
|
+
|
|
815
|
+
for (const agent of agents) {
|
|
816
|
+
const count = copySkillsToLocal(agent, skills, tempDir);
|
|
817
|
+
console.log(` ${chalk.green('✓')} ${agent.name.padEnd(14)} ${chalk.dim('→')} ${agent.skillsPath.replace(projectDir, '.').padEnd(30)} ${chalk.green(count + ' skills')}`);
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
spinner.stop();
|
|
821
|
+
|
|
822
|
+
// Cleanup
|
|
823
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
824
|
+
|
|
825
|
+
// Update local lock file
|
|
826
|
+
const lock = readLocalLock(projectDir);
|
|
827
|
+
lock.version = '1.0.0';
|
|
828
|
+
lock.installedAt = new Date().toISOString();
|
|
829
|
+
lock.skills = [...(lock.skills || []).filter(s => {
|
|
830
|
+
const existing = `${s.category}/${s.skill}`;
|
|
831
|
+
return !skills.some(ns => `${ns.category}/${ns.skill}` === existing);
|
|
832
|
+
}), ...skills];
|
|
833
|
+
lock.agents = agents.map(a => a.id);
|
|
834
|
+
writeLocalLock(projectDir, lock);
|
|
835
|
+
|
|
836
|
+
return skills.length;
|
|
837
|
+
} catch (error) {
|
|
838
|
+
if (existsSync(tempDir)) {
|
|
839
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
840
|
+
}
|
|
841
|
+
spinner.fail('Installation failed');
|
|
842
|
+
throw error;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
/**
|
|
847
|
+
* List locally installed skills for a project
|
|
848
|
+
*/
|
|
849
|
+
export function listLocalSkills(projectDir) {
|
|
850
|
+
const lock = readLocalLock(projectDir);
|
|
851
|
+
|
|
852
|
+
if (!lock.skills || lock.skills.length === 0) {
|
|
853
|
+
console.log(chalk.yellow(' No skills installed locally in this project.'));
|
|
854
|
+
console.log();
|
|
855
|
+
console.log(` Run ${chalk.cyan('npx @orchestra-research/ai-research-skills install --local')} to install skills.`);
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const byCategory = {};
|
|
860
|
+
let totalSkills = 0;
|
|
861
|
+
|
|
862
|
+
for (const skill of lock.skills) {
|
|
863
|
+
const category = skill.category;
|
|
864
|
+
if (!byCategory[category]) {
|
|
865
|
+
byCategory[category] = [];
|
|
866
|
+
}
|
|
867
|
+
if (skill.standalone) {
|
|
868
|
+
byCategory[category].push(category);
|
|
869
|
+
} else {
|
|
870
|
+
byCategory[category].push(skill.skill);
|
|
871
|
+
}
|
|
872
|
+
totalSkills++;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
console.log(chalk.white.bold(` Local Skills (${totalSkills})`));
|
|
876
|
+
console.log(chalk.dim(` Project: ${projectDir}`));
|
|
877
|
+
console.log();
|
|
878
|
+
|
|
879
|
+
for (const [category, skills] of Object.entries(byCategory)) {
|
|
880
|
+
console.log(chalk.cyan(` ${category}`));
|
|
881
|
+
for (const skill of skills) {
|
|
882
|
+
if (skill === category) {
|
|
883
|
+
console.log(` ${chalk.dim('●')} ${chalk.white('(standalone)')}`);
|
|
884
|
+
} else {
|
|
885
|
+
console.log(` ${chalk.dim('●')} ${skill}`);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
console.log();
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// Show agent directories
|
|
892
|
+
if (lock.agents && lock.agents.length > 0) {
|
|
893
|
+
console.log(chalk.dim(` Agents: ${lock.agents.join(', ')}`));
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* Get locally installed skill paths for a project
|
|
899
|
+
*/
|
|
900
|
+
export function getLocalSkillPaths(projectDir) {
|
|
901
|
+
const lock = readLocalLock(projectDir);
|
|
902
|
+
if (!lock.skills || lock.skills.length === 0) {
|
|
903
|
+
return [];
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
return lock.skills.map(s => {
|
|
907
|
+
return s.standalone ? s.category : `${s.category}/${s.skill}`;
|
|
908
|
+
});
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
/**
|
|
912
|
+
* Get locally installed skills with display info for selection
|
|
913
|
+
*/
|
|
914
|
+
export function getLocalSkillsForSelection(projectDir) {
|
|
915
|
+
const lock = readLocalLock(projectDir);
|
|
916
|
+
if (!lock.skills || lock.skills.length === 0) {
|
|
917
|
+
return [];
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
return lock.skills.map(s => {
|
|
921
|
+
if (s.standalone) {
|
|
922
|
+
return { path: s.category, name: s.category, category: 'Standalone', standalone: true };
|
|
923
|
+
} else {
|
|
924
|
+
return { path: `${s.category}/${s.skill}`, name: s.skill, category: s.category, standalone: false };
|
|
925
|
+
}
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Update locally installed skills
|
|
931
|
+
*/
|
|
932
|
+
export async function updateLocalSkills(agents, projectDir) {
|
|
933
|
+
const installedPaths = getLocalSkillPaths(projectDir);
|
|
934
|
+
|
|
935
|
+
if (installedPaths.length === 0) {
|
|
936
|
+
console.log(chalk.yellow(' No local skills installed to update.'));
|
|
937
|
+
return 0;
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Re-install the same skills
|
|
941
|
+
return await installSpecificSkillsLocal(installedPaths, agents, projectDir);
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
/**
|
|
945
|
+
* Uninstall specific local skills
|
|
946
|
+
*/
|
|
947
|
+
export async function uninstallLocalSkills(skillPaths, agents, projectDir) {
|
|
948
|
+
const spinner = ora('Removing local skills...').start();
|
|
949
|
+
|
|
950
|
+
try {
|
|
951
|
+
for (const skillPath of skillPaths) {
|
|
952
|
+
const parts = skillPath.split('/');
|
|
953
|
+
const categoryId = parts[0];
|
|
954
|
+
const skillName = parts[1] || null;
|
|
955
|
+
const linkName = skillName || categoryId;
|
|
956
|
+
|
|
957
|
+
// Remove from each agent's local directory
|
|
958
|
+
for (const agent of agents) {
|
|
959
|
+
const skillDir = join(agent.skillsPath, linkName);
|
|
960
|
+
if (existsSync(skillDir)) {
|
|
961
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
spinner.text = `Removed ${linkName}`;
|
|
966
|
+
}
|
|
967
|
+
|
|
968
|
+
spinner.succeed(`Removed ${skillPaths.length} skill${skillPaths.length !== 1 ? 's' : ''}`);
|
|
969
|
+
|
|
970
|
+
// Update local lock file
|
|
971
|
+
const lock = readLocalLock(projectDir);
|
|
972
|
+
if (lock.skills) {
|
|
973
|
+
lock.skills = lock.skills.filter(s => {
|
|
974
|
+
const path = s.standalone ? s.category : `${s.category}/${s.skill}`;
|
|
975
|
+
return !skillPaths.includes(path);
|
|
976
|
+
});
|
|
977
|
+
writeLocalLock(projectDir, lock);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return skillPaths.length;
|
|
981
|
+
} catch (error) {
|
|
982
|
+
spinner.fail('Uninstall failed');
|
|
983
|
+
throw error;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
/**
|
|
988
|
+
* Uninstall all local skills
|
|
989
|
+
*/
|
|
990
|
+
export async function uninstallAllLocalSkills(agents, projectDir) {
|
|
991
|
+
const lock = readLocalLock(projectDir);
|
|
992
|
+
const trackedSkills = lock.skills || [];
|
|
993
|
+
|
|
994
|
+
if (trackedSkills.length === 0) {
|
|
995
|
+
console.log(chalk.yellow(' No tracked local skills to remove.'));
|
|
996
|
+
return false;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
const spinner = ora('Removing all local skills...').start();
|
|
1000
|
+
|
|
1001
|
+
try {
|
|
1002
|
+
// Build set of directory names to remove (only tracked skills)
|
|
1003
|
+
const skillNames = trackedSkills.map(s => s.standalone ? s.category : s.skill);
|
|
1004
|
+
|
|
1005
|
+
for (const agent of agents) {
|
|
1006
|
+
if (existsSync(agent.skillsPath)) {
|
|
1007
|
+
for (const name of skillNames) {
|
|
1008
|
+
const skillDir = join(agent.skillsPath, name);
|
|
1009
|
+
if (existsSync(skillDir)) {
|
|
1010
|
+
rmSync(skillDir, { recursive: true, force: true });
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
console.log(` ${chalk.green('✓')} Removed skills from ${agent.name} (${agent.skillsPath.replace(projectDir, '.')})`);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Remove local lock file
|
|
1018
|
+
const lockPath = getLocalLockPath(projectDir);
|
|
1019
|
+
if (existsSync(lockPath)) {
|
|
1020
|
+
rmSync(lockPath, { force: true });
|
|
1021
|
+
console.log(` ${chalk.green('✓')} Removed ${LOCAL_LOCK_FILENAME}`);
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
spinner.stop();
|
|
1025
|
+
return true;
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
spinner.fail('Uninstall failed');
|
|
1028
|
+
throw error;
|
|
1029
|
+
}
|
|
1030
|
+
}
|
package/src/prompts.js
CHANGED
|
@@ -11,7 +11,7 @@ export const CATEGORIES = [
|
|
|
11
11
|
{ id: '04-mechanistic-interpretability', name: 'Mechanistic Interp.', skills: 4, examples: 'TransformerLens, SAELens, NNsight' },
|
|
12
12
|
{ id: '05-data-processing', name: 'Data Processing', skills: 2, examples: 'NeMo Curator, Ray Data' },
|
|
13
13
|
{ id: '06-post-training', name: 'Post-Training', skills: 8, examples: 'GRPO, verl, slime, miles, torchforge' },
|
|
14
|
-
{ id: '07-safety-alignment', name: 'Safety & Alignment', skills:
|
|
14
|
+
{ id: '07-safety-alignment', name: 'Safety & Alignment', skills: 4, examples: 'Constitutional AI, LlamaGuard, Prompt Guard' },
|
|
15
15
|
{ id: '08-distributed-training', name: 'Distributed Training', skills: 6, examples: 'DeepSpeed, FSDP, Megatron, Accelerate' },
|
|
16
16
|
{ id: '09-infrastructure', name: 'Infrastructure', skills: 3, examples: 'Modal, SkyPilot, Lambda Labs' },
|
|
17
17
|
{ id: '10-optimization', name: 'Optimization', skills: 6, examples: 'Flash Attention, GPTQ, AWQ, bitsandbytes' },
|
|
@@ -94,8 +94,10 @@ export function getTotalSkillCount() {
|
|
|
94
94
|
/**
|
|
95
95
|
* Ask main menu action after agent detection
|
|
96
96
|
*/
|
|
97
|
-
export async function askMainMenuAction() {
|
|
97
|
+
export async function askMainMenuAction(projectDir) {
|
|
98
98
|
console.log();
|
|
99
|
+
const cwd = projectDir || process.cwd();
|
|
100
|
+
const shortCwd = cwd.split('/').slice(-2).join('/');
|
|
99
101
|
const { action } = await inquirer.prompt([
|
|
100
102
|
{
|
|
101
103
|
type: 'list',
|
|
@@ -103,9 +105,10 @@ export async function askMainMenuAction() {
|
|
|
103
105
|
message: ' ',
|
|
104
106
|
choices: [
|
|
105
107
|
{ name: 'Install new skills', value: 'install' },
|
|
108
|
+
{ name: `Install to project (local) ${chalk.dim('→ ./' + shortCwd)}`, value: 'install-local' },
|
|
106
109
|
{ name: 'View installed skills', value: 'view' },
|
|
107
110
|
{ name: 'Update installed skills', value: 'update' },
|
|
108
|
-
{ name: 'Uninstall
|
|
111
|
+
{ name: 'Uninstall skills', value: 'uninstall' },
|
|
109
112
|
new inquirer.Separator(' '),
|
|
110
113
|
{ name: chalk.dim('Exit'), value: 'exit' },
|
|
111
114
|
],
|
|
@@ -115,6 +118,141 @@ export async function askMainMenuAction() {
|
|
|
115
118
|
return action;
|
|
116
119
|
}
|
|
117
120
|
|
|
121
|
+
/**
|
|
122
|
+
* Ask which agents to install to locally
|
|
123
|
+
*/
|
|
124
|
+
export async function askSelectLocalAgents(agents) {
|
|
125
|
+
console.log();
|
|
126
|
+
console.log(chalk.dim(' Install to which agents in this project?'));
|
|
127
|
+
console.log();
|
|
128
|
+
|
|
129
|
+
const { selection } = await inquirer.prompt([
|
|
130
|
+
{
|
|
131
|
+
type: 'list',
|
|
132
|
+
name: 'selection',
|
|
133
|
+
message: ' ',
|
|
134
|
+
choices: [
|
|
135
|
+
{ name: `All detected agents (${agents.length})`, value: 'all' },
|
|
136
|
+
{ name: 'Select specific agents', value: 'select' },
|
|
137
|
+
new inquirer.Separator(' '),
|
|
138
|
+
{ name: chalk.dim('← Back'), value: 'back' },
|
|
139
|
+
],
|
|
140
|
+
prefix: ' ',
|
|
141
|
+
},
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
if (selection === 'back') {
|
|
145
|
+
return { agents: [], action: 'back' };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (selection === 'all') {
|
|
149
|
+
return { agents, action: 'confirm' };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Select specific agents
|
|
153
|
+
console.log();
|
|
154
|
+
const { selectedAgents } = await inquirer.prompt([
|
|
155
|
+
{
|
|
156
|
+
type: 'checkbox',
|
|
157
|
+
name: 'selectedAgents',
|
|
158
|
+
message: ' ',
|
|
159
|
+
choices: agents.map(agent => ({
|
|
160
|
+
name: `${agent.name.padEnd(14)} ${chalk.dim(agent.path)}`,
|
|
161
|
+
value: agent,
|
|
162
|
+
checked: false,
|
|
163
|
+
})),
|
|
164
|
+
prefix: ' ',
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
|
|
168
|
+
if (selectedAgents.length === 0) {
|
|
169
|
+
console.log();
|
|
170
|
+
const { action } = await inquirer.prompt([
|
|
171
|
+
{
|
|
172
|
+
type: 'list',
|
|
173
|
+
name: 'action',
|
|
174
|
+
message: chalk.yellow('No agents selected'),
|
|
175
|
+
choices: [
|
|
176
|
+
{ name: 'Try again', value: 'retry' },
|
|
177
|
+
{ name: chalk.dim('← Back'), value: 'back' },
|
|
178
|
+
],
|
|
179
|
+
prefix: ' ',
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
182
|
+
return { agents: [], action };
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { agents: selectedAgents, action: 'confirm' };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Ask for local install confirmation
|
|
190
|
+
*/
|
|
191
|
+
export async function askLocalConfirmation(skillCount, agents, projectDir, categories, selectedSkills, installType) {
|
|
192
|
+
console.log();
|
|
193
|
+
console.log(chalk.white(' Local Installation Summary'));
|
|
194
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────'));
|
|
195
|
+
console.log();
|
|
196
|
+
|
|
197
|
+
console.log(` ${chalk.white('Skills:')} ${skillCount} skills`);
|
|
198
|
+
console.log(` ${chalk.white('Project:')} ${projectDir}`);
|
|
199
|
+
console.log(` ${chalk.white('Agents:')} ${agents.map(a => a.name).join(', ')}`);
|
|
200
|
+
console.log();
|
|
201
|
+
|
|
202
|
+
// Destinations
|
|
203
|
+
console.log(chalk.dim(' Destinations:'));
|
|
204
|
+
for (const agent of agents) {
|
|
205
|
+
console.log(chalk.dim(` • ${agent.skillsPath.replace(projectDir, '.')}`));
|
|
206
|
+
}
|
|
207
|
+
console.log();
|
|
208
|
+
|
|
209
|
+
// Description based on install type
|
|
210
|
+
if (installType === 'everything') {
|
|
211
|
+
console.log(chalk.dim(' All 20 categories'));
|
|
212
|
+
} else if (installType === 'quickstart') {
|
|
213
|
+
console.log(chalk.dim(' Essential skills for AI research'));
|
|
214
|
+
} else if (categories && categories.length > 0) {
|
|
215
|
+
const catNames = CATEGORIES
|
|
216
|
+
.filter(c => categories.includes(c.id))
|
|
217
|
+
.map(c => c.name);
|
|
218
|
+
console.log(chalk.dim(' Selected categories:'));
|
|
219
|
+
catNames.forEach(name => console.log(chalk.dim(` • ${name}`)));
|
|
220
|
+
} else if (selectedSkills && selectedSkills.length > 0) {
|
|
221
|
+
console.log(chalk.dim(' Selected skills:'));
|
|
222
|
+
const skillNames = INDIVIDUAL_SKILLS
|
|
223
|
+
.filter(s => selectedSkills.includes(s.id))
|
|
224
|
+
.map(s => s.name)
|
|
225
|
+
.slice(0, 8);
|
|
226
|
+
skillNames.forEach(name => console.log(chalk.dim(` • ${name}`)));
|
|
227
|
+
if (selectedSkills.length > 8) {
|
|
228
|
+
console.log(chalk.dim(` • ...and ${selectedSkills.length - 8} more`));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
console.log();
|
|
233
|
+
console.log(chalk.dim(' ─────────────────────────────────────────────────────'));
|
|
234
|
+
console.log();
|
|
235
|
+
console.log(chalk.dim(' Skills will be copied (not symlinked) so you can'));
|
|
236
|
+
console.log(chalk.dim(' commit them to version control.'));
|
|
237
|
+
console.log();
|
|
238
|
+
|
|
239
|
+
const { action } = await inquirer.prompt([
|
|
240
|
+
{
|
|
241
|
+
type: 'list',
|
|
242
|
+
name: 'action',
|
|
243
|
+
message: ' ',
|
|
244
|
+
choices: [
|
|
245
|
+
{ name: chalk.green('Install locally'), value: 'confirm' },
|
|
246
|
+
{ name: chalk.dim('← Back'), value: 'back' },
|
|
247
|
+
{ name: chalk.dim('Exit'), value: 'exit' },
|
|
248
|
+
],
|
|
249
|
+
prefix: ' ',
|
|
250
|
+
},
|
|
251
|
+
]);
|
|
252
|
+
|
|
253
|
+
return action;
|
|
254
|
+
}
|
|
255
|
+
|
|
118
256
|
/**
|
|
119
257
|
* Ask what to uninstall
|
|
120
258
|
*/
|
|
@@ -500,6 +638,7 @@ export function parseArgs(args) {
|
|
|
500
638
|
const options = {
|
|
501
639
|
command: null,
|
|
502
640
|
all: false,
|
|
641
|
+
local: false,
|
|
503
642
|
category: null,
|
|
504
643
|
skill: null,
|
|
505
644
|
agent: null,
|
|
@@ -514,8 +653,12 @@ export function parseArgs(args) {
|
|
|
514
653
|
options.command = 'list';
|
|
515
654
|
} else if (arg === 'update') {
|
|
516
655
|
options.command = 'update';
|
|
656
|
+
} else if (arg === 'uninstall') {
|
|
657
|
+
options.command = 'uninstall';
|
|
517
658
|
} else if (arg === '--all' || arg === '-a') {
|
|
518
659
|
options.all = true;
|
|
660
|
+
} else if (arg === '--local' || arg === '-l') {
|
|
661
|
+
options.local = true;
|
|
519
662
|
} else if (arg === '--agent' && args[i + 1]) {
|
|
520
663
|
options.agent = args[++i];
|
|
521
664
|
} else if (arg === '--category' && args[i + 1]) {
|