@almyty/skills 1.0.7 → 1.0.9
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 +58 -0
- package/dist/agents.d.ts +34 -0
- package/dist/agents.js +49 -2
- package/dist/index.js +95 -6
- package/dist/installer.d.ts +11 -7
- package/dist/installer.js +61 -17
- package/dist/target-selector.d.ts +30 -0
- package/dist/target-selector.js +311 -0
- package/package.json +5 -4
package/README.md
CHANGED
|
@@ -35,6 +35,64 @@ $ npx @almyty/skills install @acme/petstore
|
|
|
35
35
|
$ npx @almyty/skills run @acme/petstore/get-pet --id 123
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
+
## Where skills get installed
|
|
39
|
+
|
|
40
|
+
`install` writes a `SKILL.md` file per skill into one or more agent
|
|
41
|
+
directories. The CLI detects agents at two scopes:
|
|
42
|
+
|
|
43
|
+
- **Project scope** — a config dir exists in the current project
|
|
44
|
+
(e.g. `./.codex/`). Skills install to `./.codex/skills/`, only
|
|
45
|
+
this checkout sees them.
|
|
46
|
+
- **Home scope** — a config dir exists in your home directory
|
|
47
|
+
(e.g. `~/.codex/`). Skills install to `~/.codex/skills/`, every
|
|
48
|
+
project the agent opens picks them up.
|
|
49
|
+
|
|
50
|
+
Default behavior:
|
|
51
|
+
|
|
52
|
+
- **Interactive (TTY, no flags):** the picker lists every detected
|
|
53
|
+
agent at both scopes (each labeled `(project)` or `(home)`),
|
|
54
|
+
every other supported agent as opt-in, the universal
|
|
55
|
+
`.agents/skills/` convention, and a custom-path option. Pick any
|
|
56
|
+
combination.
|
|
57
|
+
- **`--yes` or non-TTY:** project-detected agents + `.agents/skills/`.
|
|
58
|
+
Home-detected agents are NOT installed automatically — pass
|
|
59
|
+
`--global` to opt in.
|
|
60
|
+
- **`--global` alone:** every home-detected agent. No project install.
|
|
61
|
+
- **`--all`:** every project-detected agent + universal. Combine
|
|
62
|
+
with `--global` to also include home-detected.
|
|
63
|
+
- **`--agent <name>`:** install to a specific agent. Picks the
|
|
64
|
+
detected scope (project preferred). With `--global`, prefers
|
|
65
|
+
home. If neither is detected, creates the project-scope dir
|
|
66
|
+
(the agent will pick it up on next scan).
|
|
67
|
+
|
|
68
|
+
| Flag | Meaning |
|
|
69
|
+
|------|---------|
|
|
70
|
+
| `--agent <name>`, `-a` | Install to the named agent. Repeatable. Partial-match. |
|
|
71
|
+
| `--agent '*'` | Every known agent at project scope, regardless of detection. |
|
|
72
|
+
| `--path <dir>`, `-p` | Custom skills directory. Repeatable. Bypasses detection. |
|
|
73
|
+
| `--all` | Every project-detected agent + `.agents/skills/`. |
|
|
74
|
+
| `--global`, `-G` | Use home scope (`~/.<agent>/skills/`). Modifier on `--agent`, or standalone for "every home-detected". |
|
|
75
|
+
| `--yes`, `-y` | Skip the picker; use the non-interactive defaults. |
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
$ npx @almyty/skills install @acme/petstore # interactive picker
|
|
81
|
+
$ npx @almyty/skills install @acme/petstore --all # every project-detected
|
|
82
|
+
$ npx @almyty/skills install @acme/petstore --all --global # project AND home detected
|
|
83
|
+
$ npx @almyty/skills install @acme/petstore --global # only home-detected agents
|
|
84
|
+
$ npx @almyty/skills install @acme/petstore -a codex # codex at whichever scope it lives
|
|
85
|
+
$ npx @almyty/skills install @acme/petstore -a codex --global # force codex at ~/.codex/skills
|
|
86
|
+
$ npx @almyty/skills install @acme/petstore --agent '*' -y # every known agent at project
|
|
87
|
+
$ npx @almyty/skills install @acme/petstore -p ./agents/skills # custom directory
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
The 25+ supported agents include Claude Code, Codex, Cursor, Windsurf,
|
|
91
|
+
GitHub Copilot, Gemini CLI, Amp, Cline, Continue, Goose, Junie, Roo
|
|
92
|
+
Code, Trae, OpenHands, OpenCode, Augment, and others. See
|
|
93
|
+
`src/agents.ts` for the full registry — each entry maps a detection
|
|
94
|
+
directory to the `<dir>/skills` path that agent reads on session start.
|
|
95
|
+
|
|
38
96
|
## Configuration
|
|
39
97
|
|
|
40
98
|
Create `.almytyrc` in your project or home directory:
|
package/dist/agents.d.ts
CHANGED
|
@@ -7,15 +7,49 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Agent list aligned with https://github.com/vercel-labs/skills
|
|
9
9
|
*/
|
|
10
|
+
/**
|
|
11
|
+
* Where an install ends up. `project` means the skill is bound to
|
|
12
|
+
* this directory's `.x/skills/` so only this checkout sees it;
|
|
13
|
+
* `home` means it's installed at the user's home-scope dir
|
|
14
|
+
* (e.g. `~/.codex/skills/`) and every project shares it. `custom`
|
|
15
|
+
* means the caller specified a `--path` and we don't track scope.
|
|
16
|
+
*/
|
|
17
|
+
export type InstallScope = 'project' | 'home' | 'custom';
|
|
10
18
|
export interface AgentTarget {
|
|
11
19
|
name: string;
|
|
12
20
|
configDir: string;
|
|
13
21
|
skillsDir: string;
|
|
22
|
+
scope?: InstallScope;
|
|
14
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Known agent configurations (30+ agents).
|
|
26
|
+
* Aligned with Vercel's skills CLI.
|
|
27
|
+
* See: https://github.com/vercel-labs/skills/blob/main/src/agents.ts
|
|
28
|
+
*/
|
|
29
|
+
export declare const AGENT_CONFIGS: Array<{
|
|
30
|
+
name: string;
|
|
31
|
+
detectDirs: string[];
|
|
32
|
+
skillsDir: string;
|
|
33
|
+
}>;
|
|
15
34
|
/**
|
|
16
35
|
* Detect which AI agents are configured in the given project directory.
|
|
36
|
+
* Project-scope only — for home-scope detection see detectHomeAgents.
|
|
17
37
|
*/
|
|
18
38
|
export declare function detectAgents(projectDir: string): AgentTarget[];
|
|
39
|
+
/**
|
|
40
|
+
* Detect agents the user has installed globally (home-scope) — i.e.
|
|
41
|
+
* `~/.codex/`, `~/.claude/`, etc. exist regardless of cwd. The
|
|
42
|
+
* skillsDir returned is the home-scope path (`~/.codex/skills/`),
|
|
43
|
+
* which Codex and Claude Code both read for cross-project skills.
|
|
44
|
+
*
|
|
45
|
+
* Mirrors the project-scope detection: same detectDirs but anchored
|
|
46
|
+
* at $HOME instead of the project root. The shared `.agents/skills/`
|
|
47
|
+
* universal convention is intentionally NOT emitted here — that
|
|
48
|
+
* convention is repo-local (it lives next to the project's source
|
|
49
|
+
* so the team sees it under version control); if a user wants
|
|
50
|
+
* universal installs they can use --path explicitly.
|
|
51
|
+
*/
|
|
52
|
+
export declare function detectHomeAgents(home?: string): AgentTarget[];
|
|
19
53
|
/**
|
|
20
54
|
* If no agents are detected, return defaults:
|
|
21
55
|
* - .claude/skills/ (Claude Code native)
|
package/dist/agents.js
CHANGED
|
@@ -8,19 +8,26 @@
|
|
|
8
8
|
* Agent list aligned with https://github.com/vercel-labs/skills
|
|
9
9
|
*/
|
|
10
10
|
import { existsSync, mkdirSync } from 'fs';
|
|
11
|
+
import { homedir } from 'os';
|
|
11
12
|
import { join } from 'path';
|
|
12
13
|
/**
|
|
13
14
|
* Known agent configurations (30+ agents).
|
|
14
15
|
* Aligned with Vercel's skills CLI.
|
|
15
16
|
* See: https://github.com/vercel-labs/skills/blob/main/src/agents.ts
|
|
16
17
|
*/
|
|
17
|
-
const AGENT_CONFIGS = [
|
|
18
|
+
export const AGENT_CONFIGS = [
|
|
18
19
|
// --- Major agents ---
|
|
19
20
|
{ name: 'Claude Code', detectDirs: ['.claude'], skillsDir: '.claude/skills' },
|
|
20
21
|
{ name: 'Cursor', detectDirs: ['.cursor', '.cursorrc'], skillsDir: '.agents/skills' },
|
|
21
22
|
{ name: 'GitHub Copilot', detectDirs: ['.github/copilot'], skillsDir: '.agents/skills' },
|
|
22
23
|
{ name: 'Windsurf', detectDirs: ['.windsurf'], skillsDir: '.windsurf/skills' },
|
|
23
|
-
|
|
24
|
+
// Codex reads skills from `.codex/skills/<skill>/SKILL.md` for repo-
|
|
25
|
+
// local skills (and `$CODEX_HOME/skills/...`, default ~/.codex/skills,
|
|
26
|
+
// for user-scoped). The agentskills.io universal `.agents/skills/`
|
|
27
|
+
// path is NOT consulted by Codex, so installs targeted at `.agents/`
|
|
28
|
+
// never appeared in the running agent. Source: Codex curated skill
|
|
29
|
+
// docs (~/.codex/vendor_imports/skills/skills/.curated/*/SKILL.md).
|
|
30
|
+
{ name: 'Codex', detectDirs: ['.codex'], skillsDir: '.codex/skills' },
|
|
24
31
|
// --- Additional agents ---
|
|
25
32
|
{ name: 'Amp', detectDirs: ['.amp'], skillsDir: '.agents/skills' },
|
|
26
33
|
{ name: 'Augment', detectDirs: ['.augment'], skillsDir: '.augment/skills' },
|
|
@@ -50,6 +57,7 @@ const AGENT_CONFIGS = [
|
|
|
50
57
|
];
|
|
51
58
|
/**
|
|
52
59
|
* Detect which AI agents are configured in the given project directory.
|
|
60
|
+
* Project-scope only — for home-scope detection see detectHomeAgents.
|
|
53
61
|
*/
|
|
54
62
|
export function detectAgents(projectDir) {
|
|
55
63
|
const agents = [];
|
|
@@ -65,12 +73,51 @@ export function detectAgents(projectDir) {
|
|
|
65
73
|
name: config.name,
|
|
66
74
|
configDir: config.detectDirs[0],
|
|
67
75
|
skillsDir: fullSkillsDir,
|
|
76
|
+
scope: 'project',
|
|
68
77
|
});
|
|
69
78
|
}
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
return agents;
|
|
73
82
|
}
|
|
83
|
+
/**
|
|
84
|
+
* Detect agents the user has installed globally (home-scope) — i.e.
|
|
85
|
+
* `~/.codex/`, `~/.claude/`, etc. exist regardless of cwd. The
|
|
86
|
+
* skillsDir returned is the home-scope path (`~/.codex/skills/`),
|
|
87
|
+
* which Codex and Claude Code both read for cross-project skills.
|
|
88
|
+
*
|
|
89
|
+
* Mirrors the project-scope detection: same detectDirs but anchored
|
|
90
|
+
* at $HOME instead of the project root. The shared `.agents/skills/`
|
|
91
|
+
* universal convention is intentionally NOT emitted here — that
|
|
92
|
+
* convention is repo-local (it lives next to the project's source
|
|
93
|
+
* so the team sees it under version control); if a user wants
|
|
94
|
+
* universal installs they can use --path explicitly.
|
|
95
|
+
*/
|
|
96
|
+
export function detectHomeAgents(home = homedir()) {
|
|
97
|
+
const agents = [];
|
|
98
|
+
const seenSkillsDirs = new Set();
|
|
99
|
+
for (const config of AGENT_CONFIGS) {
|
|
100
|
+
const detected = config.detectDirs.some(dir => existsSync(join(home, dir)));
|
|
101
|
+
if (!detected)
|
|
102
|
+
continue;
|
|
103
|
+
// Home-scope install path: replace the project-prefixed
|
|
104
|
+
// skillsDir with a home-prefixed equivalent. Most agents store
|
|
105
|
+
// skills under <configDir>/skills, so just join home with the
|
|
106
|
+
// skillsDir from AGENT_CONFIGS — that's already the right
|
|
107
|
+
// structure (e.g., `.codex/skills` → `<home>/.codex/skills`).
|
|
108
|
+
const fullSkillsDir = join(home, config.skillsDir);
|
|
109
|
+
if (seenSkillsDirs.has(fullSkillsDir))
|
|
110
|
+
continue;
|
|
111
|
+
seenSkillsDirs.add(fullSkillsDir);
|
|
112
|
+
agents.push({
|
|
113
|
+
name: config.name,
|
|
114
|
+
configDir: config.detectDirs[0],
|
|
115
|
+
skillsDir: fullSkillsDir,
|
|
116
|
+
scope: 'home',
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
return agents;
|
|
120
|
+
}
|
|
74
121
|
/**
|
|
75
122
|
* If no agents are detected, return defaults:
|
|
76
123
|
* - .claude/skills/ (Claude Code native)
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,8 @@ import { getAllTargets } from './agents.js';
|
|
|
5
5
|
import { installSkills, removeSkills, listInstalledSkills } from './installer.js';
|
|
6
6
|
import { loadConfig, resolveTargets } from './config.js';
|
|
7
7
|
import { generateMetaSkill } from './meta-skill.js';
|
|
8
|
-
|
|
8
|
+
import { selectInstallTargetsAuto, selectInstallTargetsInteractive, } from './target-selector.js';
|
|
9
|
+
const VERSION = '1.0.9';
|
|
9
10
|
function printHelp() {
|
|
10
11
|
console.log(`
|
|
11
12
|
almyty Skills CLI v${VERSION}
|
|
@@ -40,19 +41,65 @@ Config:
|
|
|
40
41
|
ALMYTY_TOKEN Override auth token
|
|
41
42
|
|
|
42
43
|
Options:
|
|
44
|
+
--agent, -a <name> Install target by name (repeatable; e.g.
|
|
45
|
+
-a codex -a claude). Accepts '*' or 'all'
|
|
46
|
+
for every known agent regardless of
|
|
47
|
+
detection. Partial match.
|
|
48
|
+
--path, -p <dir> Custom skills dir, repeatable. Bypasses
|
|
49
|
+
agent detection.
|
|
50
|
+
--all Install to every PROJECT-detected agent
|
|
51
|
+
(plus the universal .agents/skills/).
|
|
52
|
+
Combine with --global to also include
|
|
53
|
+
home-detected. Skips the picker.
|
|
54
|
+
--global, -G Use home-scope (~/.codex/skills/, etc.)
|
|
55
|
+
instead of project-scope. With --all,
|
|
56
|
+
includes home-detected agents alongside
|
|
57
|
+
project-detected. With --agent <name>,
|
|
58
|
+
prefers home-scope when the agent is
|
|
59
|
+
detected at $HOME.
|
|
60
|
+
--yes, -y Skip the interactive picker. Falls back
|
|
61
|
+
to detected agents (or defaults).
|
|
43
62
|
--interval, -i <seconds> Daemon poll interval in seconds (default: 60)
|
|
44
63
|
--url <url> almyty API URL (default: https://api.almyty.com)
|
|
45
64
|
--dir <path> Project directory (default: current directory)
|
|
46
65
|
--help, -h Show help
|
|
47
66
|
--version, -v Show version
|
|
48
67
|
|
|
68
|
+
Target selection (install):
|
|
69
|
+
Without flags in a TTY, install shows a multi-select picker of all
|
|
70
|
+
known agents (detected ones pre-checked) plus a "custom path…" option.
|
|
71
|
+
In a non-TTY, the picker is skipped: install writes to detected agents
|
|
72
|
+
and the universal .agents/skills/ directory.
|
|
73
|
+
|
|
49
74
|
Examples:
|
|
50
75
|
npx @almyty/skills daemon
|
|
51
76
|
npx @almyty/skills install myorg/petstore/get-pet
|
|
77
|
+
npx @almyty/skills install @org/gw -a codex -a claude
|
|
78
|
+
npx @almyty/skills install @org/gw --path ./tmp/skills --yes
|
|
79
|
+
npx @almyty/skills install @org/gw --all
|
|
52
80
|
npx @almyty/skills search "weather"
|
|
53
81
|
npx @almyty/skills run myorg/petstore/get-pet --petId 123
|
|
54
82
|
`);
|
|
55
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* Repeatable flags accumulate into a string[] when supplied more
|
|
86
|
+
* than once. Used by `--agent` and `--path` so callers can pick
|
|
87
|
+
* multiple targets without inventing comma syntax (the selector
|
|
88
|
+
* also splits on comma/space, so both styles work).
|
|
89
|
+
*/
|
|
90
|
+
const REPEATABLE_FLAGS = new Set(['agent', 'path']);
|
|
91
|
+
function appendRepeatable(flags, key, value) {
|
|
92
|
+
const existing = flags[key];
|
|
93
|
+
if (existing === undefined || existing === true || existing === false) {
|
|
94
|
+
flags[key] = value;
|
|
95
|
+
}
|
|
96
|
+
else if (typeof existing === 'string') {
|
|
97
|
+
flags[key] = [existing, value];
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
existing.push(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
56
103
|
function parseArgs(argv) {
|
|
57
104
|
const result = { positional: [], flags: {} };
|
|
58
105
|
let i = 0;
|
|
@@ -70,6 +117,23 @@ function parseArgs(argv) {
|
|
|
70
117
|
else if (arg === '--interval' || arg === '-i') {
|
|
71
118
|
result.flags.interval = argv[++i] || '60';
|
|
72
119
|
}
|
|
120
|
+
else if (arg === '--agent' || arg === '-a') {
|
|
121
|
+
appendRepeatable(result.flags, 'agent', argv[++i] || '');
|
|
122
|
+
}
|
|
123
|
+
else if (arg === '--path' || arg === '-p') {
|
|
124
|
+
appendRepeatable(result.flags, 'path', argv[++i] || '');
|
|
125
|
+
}
|
|
126
|
+
else if (arg === '--all') {
|
|
127
|
+
result.flags.all = true;
|
|
128
|
+
}
|
|
129
|
+
else if (arg === '--yes' || arg === '-y') {
|
|
130
|
+
result.flags.yes = true;
|
|
131
|
+
}
|
|
132
|
+
else if (arg === '--global' || arg === '-G') {
|
|
133
|
+
// --global / -G installs at home scope (~/.codex/skills/, etc.)
|
|
134
|
+
// -g remains aliased to --gateway for back-compat.
|
|
135
|
+
result.flags.global = true;
|
|
136
|
+
}
|
|
73
137
|
else if (arg === '--help' || arg === '-h') {
|
|
74
138
|
result.flags.help = true;
|
|
75
139
|
}
|
|
@@ -83,7 +147,12 @@ function parseArgs(argv) {
|
|
|
83
147
|
const key = arg.slice(2);
|
|
84
148
|
const next = argv[i + 1];
|
|
85
149
|
if (next && !next.startsWith('--')) {
|
|
86
|
-
|
|
150
|
+
if (REPEATABLE_FLAGS.has(key)) {
|
|
151
|
+
appendRepeatable(result.flags, key, next);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result.flags[key] = next;
|
|
155
|
+
}
|
|
87
156
|
i++;
|
|
88
157
|
}
|
|
89
158
|
else {
|
|
@@ -121,9 +190,12 @@ function requireRef(args, command) {
|
|
|
121
190
|
}
|
|
122
191
|
function parseRunParams(args) {
|
|
123
192
|
const params = {};
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
193
|
+
const reserved = new Set([
|
|
194
|
+
'url', 'dir', 'help', 'version', 'interval', 'gateway',
|
|
195
|
+
'agent', 'path', 'all', 'yes', 'global',
|
|
196
|
+
]);
|
|
197
|
+
for (const [key, value] of Object.entries(args.flags)) {
|
|
198
|
+
if (reserved.has(key))
|
|
127
199
|
continue;
|
|
128
200
|
params[key] = value;
|
|
129
201
|
}
|
|
@@ -311,7 +383,24 @@ async function main() {
|
|
|
311
383
|
return;
|
|
312
384
|
}
|
|
313
385
|
console.log(`\n${gwName} (${skills.length} skill(s))`);
|
|
314
|
-
const
|
|
386
|
+
const selection = {
|
|
387
|
+
projectDir,
|
|
388
|
+
config,
|
|
389
|
+
agentFlag: args.flags.agent,
|
|
390
|
+
pathFlag: args.flags.path,
|
|
391
|
+
all: !!args.flags.all,
|
|
392
|
+
yes: !!args.flags.yes,
|
|
393
|
+
global: !!args.flags.global,
|
|
394
|
+
};
|
|
395
|
+
let targets = selectInstallTargetsAuto(selection);
|
|
396
|
+
if (targets === null) {
|
|
397
|
+
// Interactive picker — TTY + no flags + no .almytyrc.
|
|
398
|
+
targets = await selectInstallTargetsInteractive(selection);
|
|
399
|
+
}
|
|
400
|
+
if (targets.length === 0) {
|
|
401
|
+
console.error('No install targets resolved. Pass --agent / --path / --all or run without flags in a TTY.');
|
|
402
|
+
process.exit(1);
|
|
403
|
+
}
|
|
315
404
|
const results = targets.map(target => installSkills(skills, target));
|
|
316
405
|
console.log('');
|
|
317
406
|
for (const result of results) {
|
package/dist/installer.d.ts
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill installer — writes SKILL.md files to agent directories.
|
|
3
|
+
*
|
|
4
|
+
* Naming: the skill's own name is used directly for both the
|
|
5
|
+
* directory and the SKILL.md frontmatter `name:` field. We used to
|
|
6
|
+
* prefix `almyty-` to flag installs as ours; that turned the agent-
|
|
7
|
+
* visible label into `$almyty-open-meteo-weather-get-v1-forecast`,
|
|
8
|
+
* which is noisy and uninformative. Identification for `remove`
|
|
9
|
+
* and `installed` now reads the `metadata.author: almyty` line
|
|
10
|
+
* already present in every SKILL.md we generate.
|
|
3
11
|
*/
|
|
4
12
|
import type { SkillFile } from './client.js';
|
|
5
13
|
import type { AgentTarget } from './agents.js';
|
|
@@ -11,14 +19,10 @@ export interface InstallResult {
|
|
|
11
19
|
}
|
|
12
20
|
/**
|
|
13
21
|
* Install skill files into an agent's skills directory.
|
|
14
|
-
* Each skill
|
|
22
|
+
* Each skill lives at `<skillsDir>/<skill-name>/SKILL.md`.
|
|
15
23
|
*/
|
|
16
24
|
export declare function installSkills(skills: SkillFile[], target: AgentTarget): InstallResult;
|
|
17
|
-
/**
|
|
18
|
-
* Remove all almyty-installed skills from an agent's skills directory.
|
|
19
|
-
*/
|
|
25
|
+
/** Remove every almyty-installed skill from an agent's skills directory. */
|
|
20
26
|
export declare function removeSkills(target: AgentTarget): number;
|
|
21
|
-
/**
|
|
22
|
-
* List installed almyty skills in an agent's skills directory.
|
|
23
|
-
*/
|
|
27
|
+
/** List almyty-installed skills in an agent's skills directory. */
|
|
24
28
|
export declare function listInstalledSkills(target: AgentTarget): string[];
|
package/dist/installer.js
CHANGED
|
@@ -1,22 +1,68 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Skill installer — writes SKILL.md files to agent directories.
|
|
3
|
+
*
|
|
4
|
+
* Naming: the skill's own name is used directly for both the
|
|
5
|
+
* directory and the SKILL.md frontmatter `name:` field. We used to
|
|
6
|
+
* prefix `almyty-` to flag installs as ours; that turned the agent-
|
|
7
|
+
* visible label into `$almyty-open-meteo-weather-get-v1-forecast`,
|
|
8
|
+
* which is noisy and uninformative. Identification for `remove`
|
|
9
|
+
* and `installed` now reads the `metadata.author: almyty` line
|
|
10
|
+
* already present in every SKILL.md we generate.
|
|
3
11
|
*/
|
|
4
|
-
import { writeFileSync, mkdirSync, existsSync, readdirSync, rmSync } from 'fs';
|
|
12
|
+
import { writeFileSync, mkdirSync, existsSync, readdirSync, rmSync, readFileSync } from 'fs';
|
|
5
13
|
import { join } from 'path';
|
|
6
|
-
|
|
14
|
+
/** Legacy prefix from <=v1.0.9 — older installs still use this dir
|
|
15
|
+
* name. Kept here so `remove`/`installed` continue to find them. */
|
|
16
|
+
const LEGACY_SKILL_PREFIX = 'almyty-';
|
|
17
|
+
/** Marker line we expect inside every SKILL.md frontmatter we wrote. */
|
|
18
|
+
const ALMYTY_MARKER = /^\s*author:\s*almyty\s*$/m;
|
|
19
|
+
/**
|
|
20
|
+
* Strip the legacy `name: almyty-<x>` line in the SKILL.md frontmatter
|
|
21
|
+
* to `name: <x>` so the agent-visible identifier is the skill's own
|
|
22
|
+
* slug. Backend currently writes the prefixed form; once that's
|
|
23
|
+
* cleaned up upstream this becomes a no-op.
|
|
24
|
+
*/
|
|
25
|
+
function rewriteNameInFrontmatter(content) {
|
|
26
|
+
return content.replace(/^name:\s*almyty-/m, 'name: ');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Read SKILL.md and decide whether it was installed by this CLI
|
|
30
|
+
* (or its predecessor). Two signals: the legacy `almyty-` directory
|
|
31
|
+
* prefix, OR the `metadata.author: almyty` line in the frontmatter.
|
|
32
|
+
*/
|
|
33
|
+
function isAlmytyInstall(skillsDir, dirName) {
|
|
34
|
+
if (dirName.startsWith(LEGACY_SKILL_PREFIX))
|
|
35
|
+
return true;
|
|
36
|
+
const skillFile = join(skillsDir, dirName, 'SKILL.md');
|
|
37
|
+
if (!existsSync(skillFile))
|
|
38
|
+
return false;
|
|
39
|
+
try {
|
|
40
|
+
return ALMYTY_MARKER.test(readFileSync(skillFile, 'utf-8'));
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
7
46
|
/**
|
|
8
47
|
* Install skill files into an agent's skills directory.
|
|
9
|
-
* Each skill
|
|
48
|
+
* Each skill lives at `<skillsDir>/<skill-name>/SKILL.md`.
|
|
10
49
|
*/
|
|
11
50
|
export function installSkills(skills, target) {
|
|
12
51
|
const files = [];
|
|
13
52
|
mkdirSync(target.skillsDir, { recursive: true });
|
|
14
53
|
for (const skill of skills) {
|
|
15
|
-
const dirName =
|
|
54
|
+
const dirName = skill.name;
|
|
16
55
|
const skillDir = join(target.skillsDir, dirName);
|
|
17
56
|
const skillFile = join(skillDir, 'SKILL.md');
|
|
57
|
+
// Cleanup: if a previous install used the `almyty-<name>` dir
|
|
58
|
+
// shape, remove it before writing the new shape so the agent
|
|
59
|
+
// doesn't see two copies of the same skill.
|
|
60
|
+
const legacyDir = join(target.skillsDir, `${LEGACY_SKILL_PREFIX}${skill.name}`);
|
|
61
|
+
if (legacyDir !== skillDir && existsSync(legacyDir)) {
|
|
62
|
+
rmSync(legacyDir, { recursive: true, force: true });
|
|
63
|
+
}
|
|
18
64
|
mkdirSync(skillDir, { recursive: true });
|
|
19
|
-
writeFileSync(skillFile, skill.content, 'utf-8');
|
|
65
|
+
writeFileSync(skillFile, rewriteNameInFrontmatter(skill.content), 'utf-8');
|
|
20
66
|
files.push(skillFile);
|
|
21
67
|
}
|
|
22
68
|
return {
|
|
@@ -26,30 +72,28 @@ export function installSkills(skills, target) {
|
|
|
26
72
|
files,
|
|
27
73
|
};
|
|
28
74
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Remove all almyty-installed skills from an agent's skills directory.
|
|
31
|
-
*/
|
|
75
|
+
/** Remove every almyty-installed skill from an agent's skills directory. */
|
|
32
76
|
export function removeSkills(target) {
|
|
33
77
|
if (!existsSync(target.skillsDir))
|
|
34
78
|
return 0;
|
|
35
79
|
const entries = readdirSync(target.skillsDir, { withFileTypes: true });
|
|
36
80
|
let removed = 0;
|
|
37
81
|
for (const entry of entries) {
|
|
38
|
-
if (entry.isDirectory()
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
82
|
+
if (!entry.isDirectory())
|
|
83
|
+
continue;
|
|
84
|
+
if (!isAlmytyInstall(target.skillsDir, entry.name))
|
|
85
|
+
continue;
|
|
86
|
+
rmSync(join(target.skillsDir, entry.name), { recursive: true, force: true });
|
|
87
|
+
removed++;
|
|
42
88
|
}
|
|
43
89
|
return removed;
|
|
44
90
|
}
|
|
45
|
-
/**
|
|
46
|
-
* List installed almyty skills in an agent's skills directory.
|
|
47
|
-
*/
|
|
91
|
+
/** List almyty-installed skills in an agent's skills directory. */
|
|
48
92
|
export function listInstalledSkills(target) {
|
|
49
93
|
if (!existsSync(target.skillsDir))
|
|
50
94
|
return [];
|
|
51
95
|
const entries = readdirSync(target.skillsDir, { withFileTypes: true });
|
|
52
96
|
return entries
|
|
53
|
-
.filter(e => e.isDirectory() && e.name
|
|
54
|
-
.map(e => e.name.replace(
|
|
97
|
+
.filter((e) => e.isDirectory() && isAlmytyInstall(target.skillsDir, e.name))
|
|
98
|
+
.map((e) => e.name.replace(LEGACY_SKILL_PREFIX, ''));
|
|
55
99
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AgentTarget } from './agents.js';
|
|
2
|
+
import type { AlmytyConfig } from './config.js';
|
|
3
|
+
export interface SelectionInput {
|
|
4
|
+
projectDir: string;
|
|
5
|
+
config: AlmytyConfig;
|
|
6
|
+
/** `--agent <name>` (repeatable, comma/space split, supports `*` / `all`). */
|
|
7
|
+
agentFlag?: string | string[];
|
|
8
|
+
/** `--path <dir>` (repeatable, comma split). */
|
|
9
|
+
pathFlag?: string | string[];
|
|
10
|
+
/** `--all`: every project-detected agent + universal. */
|
|
11
|
+
all?: boolean;
|
|
12
|
+
/** `--yes` / `-y`: skip the interactive picker. */
|
|
13
|
+
yes?: boolean;
|
|
14
|
+
/** `--global` / `-G`: include / prefer home-scope installs. */
|
|
15
|
+
global?: boolean;
|
|
16
|
+
/** Override $HOME (test seam). */
|
|
17
|
+
home?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve targets for non-interactive paths. Returns null when the
|
|
21
|
+
* caller must fall through to the interactive picker (TTY + no
|
|
22
|
+
* resolving flag/config).
|
|
23
|
+
*/
|
|
24
|
+
export declare function selectInstallTargetsAuto(input: SelectionInput): AgentTarget[] | null;
|
|
25
|
+
/**
|
|
26
|
+
* Drive the multi-select picker. Lists every detected agent at
|
|
27
|
+
* BOTH scopes, with detection scope shown as a hint, plus a custom
|
|
28
|
+
* path option. Returns the chosen targets.
|
|
29
|
+
*/
|
|
30
|
+
export declare function selectInstallTargetsInteractive(input: SelectionInput): Promise<AgentTarget[]>;
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target selection for the install path.
|
|
3
|
+
*
|
|
4
|
+
* Two scopes:
|
|
5
|
+
* - **project**: writes to `<cwd>/.<agent>/skills/` so the skill
|
|
6
|
+
* is part of this checkout. Detected by an `.x/` config dir in
|
|
7
|
+
* `projectDir`.
|
|
8
|
+
* - **home**: writes to `~/.<agent>/skills/`, shared across every
|
|
9
|
+
* project the user opens with that agent. Detected by an `.x/`
|
|
10
|
+
* config dir in `$HOME`.
|
|
11
|
+
*
|
|
12
|
+
* Order of precedence (highest first):
|
|
13
|
+
* 1. CLI `--path` — explicit custom dir(s); detection ignored.
|
|
14
|
+
* 2. CLI `--all` — every PROJECT-detected agent + universal
|
|
15
|
+
* CLI `--all --global` — every PROJECT-detected + every HOME-detected
|
|
16
|
+
* CLI `--global` (alone) — every HOME-detected agent
|
|
17
|
+
* 3. CLI `--agent foo` — the named agent at whichever scope it's
|
|
18
|
+
* detected in (project preferred); home if `--global` is set.
|
|
19
|
+
* `--agent '*'` / `--agent all` — every known agent dir at project scope
|
|
20
|
+
* regardless of detection.
|
|
21
|
+
* 4. `.almytyrc` skillsDir / agents whitelist.
|
|
22
|
+
* 5. Interactive picker (TTY) — multi-select listing every detected
|
|
23
|
+
* agent in both scopes plus a "custom path…" option.
|
|
24
|
+
* 6. Non-TTY fallback — project-detected + universal, or defaults.
|
|
25
|
+
*/
|
|
26
|
+
import { resolve, join } from 'path';
|
|
27
|
+
import { homedir } from 'os';
|
|
28
|
+
import { AGENT_CONFIGS, detectAgents, detectHomeAgents, getDefaultTargets, } from './agents.js';
|
|
29
|
+
function toList(v) {
|
|
30
|
+
if (!v)
|
|
31
|
+
return [];
|
|
32
|
+
const arr = Array.isArray(v) ? v : [v];
|
|
33
|
+
return arr
|
|
34
|
+
.flatMap((s) => s.split(/[,\s]+/))
|
|
35
|
+
.map((s) => s.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
}
|
|
38
|
+
function customPathTarget(projectDir, p) {
|
|
39
|
+
const abs = resolve(projectDir, p);
|
|
40
|
+
return {
|
|
41
|
+
name: `custom (${p})`,
|
|
42
|
+
configDir: p,
|
|
43
|
+
skillsDir: abs,
|
|
44
|
+
scope: 'custom',
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function projectAgentTarget(projectDir, name) {
|
|
48
|
+
const cfg = AGENT_CONFIGS.find((c) => c.name === name);
|
|
49
|
+
if (!cfg)
|
|
50
|
+
return null;
|
|
51
|
+
return {
|
|
52
|
+
name: cfg.name,
|
|
53
|
+
configDir: cfg.detectDirs[0],
|
|
54
|
+
skillsDir: join(projectDir, cfg.skillsDir),
|
|
55
|
+
scope: 'project',
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
function homeAgentTarget(home, name) {
|
|
59
|
+
const cfg = AGENT_CONFIGS.find((c) => c.name === name);
|
|
60
|
+
if (!cfg)
|
|
61
|
+
return null;
|
|
62
|
+
return {
|
|
63
|
+
name: cfg.name,
|
|
64
|
+
configDir: cfg.detectDirs[0],
|
|
65
|
+
skillsDir: join(home, cfg.skillsDir),
|
|
66
|
+
scope: 'home',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
function universalTarget(projectDir) {
|
|
70
|
+
return {
|
|
71
|
+
name: 'Universal (.agents/skills)',
|
|
72
|
+
configDir: '.agents',
|
|
73
|
+
skillsDir: join(projectDir, '.agents/skills'),
|
|
74
|
+
scope: 'project',
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function dedupeBySkillsDir(targets) {
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const out = [];
|
|
80
|
+
for (const t of targets) {
|
|
81
|
+
if (seen.has(t.skillsDir))
|
|
82
|
+
continue;
|
|
83
|
+
seen.add(t.skillsDir);
|
|
84
|
+
out.push(t);
|
|
85
|
+
}
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
function matchAgentName(filter, candidate) {
|
|
89
|
+
const norm = (s) => s.toLowerCase().replace(/[\s-]/g, '');
|
|
90
|
+
return norm(candidate).includes(norm(filter));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resolve targets for non-interactive paths. Returns null when the
|
|
94
|
+
* caller must fall through to the interactive picker (TTY + no
|
|
95
|
+
* resolving flag/config).
|
|
96
|
+
*/
|
|
97
|
+
export function selectInstallTargetsAuto(input) {
|
|
98
|
+
const { projectDir, config, all, yes, global } = input;
|
|
99
|
+
const home = input.home ?? homedir();
|
|
100
|
+
const agentFlag = toList(input.agentFlag);
|
|
101
|
+
const pathFlag = toList(input.pathFlag);
|
|
102
|
+
// 1. Explicit `--path` wins outright. Detection ignored.
|
|
103
|
+
if (pathFlag.length > 0) {
|
|
104
|
+
return dedupeBySkillsDir(pathFlag.map((p) => customPathTarget(projectDir, p)));
|
|
105
|
+
}
|
|
106
|
+
// 2. Bulk install via `--all` and/or `--global`. Only fires when
|
|
107
|
+
// no `--agent` filter is set — otherwise `--global` is a
|
|
108
|
+
// *modifier* on the agent-by-name resolution below, not a
|
|
109
|
+
// standalone "every home-detected" command.
|
|
110
|
+
if ((all || global) && agentFlag.length === 0) {
|
|
111
|
+
const projectDetected = all ? detectAgents(projectDir) : [];
|
|
112
|
+
const homeDetected = global ? detectHomeAgents(home) : [];
|
|
113
|
+
const universal = all ? [universalTarget(projectDir)] : [];
|
|
114
|
+
const combined = [...projectDetected, ...homeDetected, ...universal];
|
|
115
|
+
if (combined.length === 0) {
|
|
116
|
+
throw new Error(global && !all
|
|
117
|
+
? 'No agents detected at home scope (~/.<agent>/). Run `npx @almyty/skills install` interactively or pass `--agent <name>`.'
|
|
118
|
+
: 'No agents detected in this project. Try --agent <name> or --path <dir>.');
|
|
119
|
+
}
|
|
120
|
+
return dedupeBySkillsDir(combined);
|
|
121
|
+
}
|
|
122
|
+
// 3. `--agent foo,bar`. Wildcard expands to every known agent at
|
|
123
|
+
// project scope. Otherwise: prefer project scope when detected,
|
|
124
|
+
// else fall through to home scope when detected, else
|
|
125
|
+
// project-scope at the default path (the agent's not installed
|
|
126
|
+
// anywhere yet; the user is opting in by name).
|
|
127
|
+
if (agentFlag.length > 0) {
|
|
128
|
+
if (agentFlag.some((f) => f === '*' || f.toLowerCase() === 'all')) {
|
|
129
|
+
const everyKnown = AGENT_CONFIGS
|
|
130
|
+
.map((c) => projectAgentTarget(projectDir, c.name))
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
return dedupeBySkillsDir([...everyKnown, universalTarget(projectDir)]);
|
|
133
|
+
}
|
|
134
|
+
const projectDetectedNames = new Set(detectAgents(projectDir).map((a) => a.name));
|
|
135
|
+
const homeDetectedNames = new Set(detectHomeAgents(home).map((a) => a.name));
|
|
136
|
+
const targets = [];
|
|
137
|
+
const unmatched = [];
|
|
138
|
+
for (const filter of agentFlag) {
|
|
139
|
+
const cfg = AGENT_CONFIGS.find((c) => matchAgentName(filter, c.name));
|
|
140
|
+
if (!cfg) {
|
|
141
|
+
unmatched.push(filter);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
// Pick the right scope. With `--global` always take home if
|
|
145
|
+
// available; otherwise prefer project, fall through to home.
|
|
146
|
+
if (global && homeDetectedNames.has(cfg.name)) {
|
|
147
|
+
targets.push(homeAgentTarget(home, cfg.name));
|
|
148
|
+
}
|
|
149
|
+
else if (projectDetectedNames.has(cfg.name)) {
|
|
150
|
+
targets.push(projectAgentTarget(projectDir, cfg.name));
|
|
151
|
+
}
|
|
152
|
+
else if (homeDetectedNames.has(cfg.name)) {
|
|
153
|
+
targets.push(homeAgentTarget(home, cfg.name));
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
// Not detected anywhere — install at project scope by name
|
|
157
|
+
// request. The agent picks it up next time it scans.
|
|
158
|
+
targets.push(projectAgentTarget(projectDir, cfg.name));
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
if (unmatched.length > 0) {
|
|
162
|
+
throw new Error(`No known agents matched --agent ${unmatched.join(',')}. ` +
|
|
163
|
+
`Run with --help to see supported names.`);
|
|
164
|
+
}
|
|
165
|
+
return dedupeBySkillsDir([...targets, universalTarget(projectDir)]);
|
|
166
|
+
}
|
|
167
|
+
// 4. Project `.almytyrc` skillsDir override.
|
|
168
|
+
if (config.skillsDir) {
|
|
169
|
+
return [
|
|
170
|
+
{
|
|
171
|
+
name: 'custom (.almytyrc)',
|
|
172
|
+
configDir: config.skillsDir,
|
|
173
|
+
skillsDir: resolve(projectDir, config.skillsDir),
|
|
174
|
+
scope: 'custom',
|
|
175
|
+
},
|
|
176
|
+
];
|
|
177
|
+
}
|
|
178
|
+
// 5. Project `.almytyrc` agents whitelist (project-scope only).
|
|
179
|
+
if (config.agents && config.agents.length > 0) {
|
|
180
|
+
const matched = [];
|
|
181
|
+
for (const filter of config.agents) {
|
|
182
|
+
const cfg = AGENT_CONFIGS.find((c) => matchAgentName(filter, c.name));
|
|
183
|
+
if (cfg)
|
|
184
|
+
matched.push(projectAgentTarget(projectDir, cfg.name));
|
|
185
|
+
}
|
|
186
|
+
if (matched.length > 0) {
|
|
187
|
+
return dedupeBySkillsDir([...matched, universalTarget(projectDir)]);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// 6. `--yes` / non-TTY: pick a sensible default WITHOUT prompting.
|
|
191
|
+
// Project-detected + universal first; if nothing project-side,
|
|
192
|
+
// show home-detected as a hint (the user probably wants the
|
|
193
|
+
// interactive picker for this; here we just return defaults
|
|
194
|
+
// so scripted use doesn't hang).
|
|
195
|
+
if (yes || !process.stdin.isTTY) {
|
|
196
|
+
const projectDetected = detectAgents(projectDir);
|
|
197
|
+
if (projectDetected.length > 0) {
|
|
198
|
+
return dedupeBySkillsDir([
|
|
199
|
+
...projectDetected,
|
|
200
|
+
universalTarget(projectDir),
|
|
201
|
+
]);
|
|
202
|
+
}
|
|
203
|
+
return dedupeBySkillsDir([
|
|
204
|
+
...getDefaultTargets(projectDir),
|
|
205
|
+
universalTarget(projectDir),
|
|
206
|
+
]);
|
|
207
|
+
}
|
|
208
|
+
// Interactive picker required.
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Drive the multi-select picker. Lists every detected agent at
|
|
213
|
+
* BOTH scopes, with detection scope shown as a hint, plus a custom
|
|
214
|
+
* path option. Returns the chosen targets.
|
|
215
|
+
*/
|
|
216
|
+
export async function selectInstallTargetsInteractive(input) {
|
|
217
|
+
const { projectDir } = input;
|
|
218
|
+
const home = input.home ?? homedir();
|
|
219
|
+
const clack = await import('@clack/prompts');
|
|
220
|
+
const projectDetected = detectAgents(projectDir);
|
|
221
|
+
const homeDetected = detectHomeAgents(home);
|
|
222
|
+
// Build options: project entries first (preferred), then home,
|
|
223
|
+
// then known-but-not-detected as a separate group, then universal,
|
|
224
|
+
// then custom-path. We use synthetic value tokens so we can
|
|
225
|
+
// resolve back to AgentTargets after the multiselect returns.
|
|
226
|
+
const seen = new Set();
|
|
227
|
+
const options = [];
|
|
228
|
+
for (const a of projectDetected) {
|
|
229
|
+
options.push({
|
|
230
|
+
value: `project:${a.name}`,
|
|
231
|
+
label: `${a.name} (project)`,
|
|
232
|
+
hint: a.skillsDir.replace(projectDir, '.'),
|
|
233
|
+
});
|
|
234
|
+
seen.add(`${a.scope}:${a.name}`);
|
|
235
|
+
}
|
|
236
|
+
for (const a of homeDetected) {
|
|
237
|
+
if (seen.has(`project:${a.name}`)) {
|
|
238
|
+
// Same agent also detected locally — already shown above; the
|
|
239
|
+
// user can still install both scopes by adding the home line
|
|
240
|
+
// here, so keep it.
|
|
241
|
+
}
|
|
242
|
+
options.push({
|
|
243
|
+
value: `home:${a.name}`,
|
|
244
|
+
label: `${a.name} (home)`,
|
|
245
|
+
hint: a.skillsDir.replace(home, '~'),
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
options.push({
|
|
249
|
+
value: '__universal__',
|
|
250
|
+
label: 'Universal (.agents/skills)',
|
|
251
|
+
hint: 'cross-client convention; project scope',
|
|
252
|
+
});
|
|
253
|
+
// List the agents that are recognized but not detected anywhere
|
|
254
|
+
// so users can opt in by checkbox without retyping the name.
|
|
255
|
+
const detectedNames = new Set([...projectDetected, ...homeDetected].map((a) => a.name));
|
|
256
|
+
for (const cfg of AGENT_CONFIGS) {
|
|
257
|
+
if (detectedNames.has(cfg.name))
|
|
258
|
+
continue;
|
|
259
|
+
options.push({
|
|
260
|
+
value: `project:${cfg.name}`,
|
|
261
|
+
label: `${cfg.name}`,
|
|
262
|
+
hint: `not detected — would create ${cfg.skillsDir}`,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
options.push({
|
|
266
|
+
value: '__custom__',
|
|
267
|
+
label: 'Custom path…',
|
|
268
|
+
hint: 'enter a directory after this prompt',
|
|
269
|
+
});
|
|
270
|
+
const initialValues = projectDetected.map((a) => `project:${a.name}`);
|
|
271
|
+
if (initialValues.length === 0)
|
|
272
|
+
initialValues.push('__universal__');
|
|
273
|
+
const picked = await clack.multiselect({
|
|
274
|
+
message: 'Where should the skills be installed?',
|
|
275
|
+
options,
|
|
276
|
+
initialValues,
|
|
277
|
+
required: true,
|
|
278
|
+
});
|
|
279
|
+
if (clack.isCancel(picked)) {
|
|
280
|
+
clack.cancel('Install cancelled.');
|
|
281
|
+
process.exit(0);
|
|
282
|
+
}
|
|
283
|
+
const values = picked;
|
|
284
|
+
const targets = [];
|
|
285
|
+
for (const v of values) {
|
|
286
|
+
if (v === '__universal__') {
|
|
287
|
+
targets.push(universalTarget(projectDir));
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (v === '__custom__') {
|
|
291
|
+
const path = await clack.text({
|
|
292
|
+
message: 'Custom skills directory path:',
|
|
293
|
+
placeholder: 'e.g. .my-agent/skills',
|
|
294
|
+
validate: (s) => (s && s.trim() ? undefined : 'Required'),
|
|
295
|
+
});
|
|
296
|
+
if (clack.isCancel(path)) {
|
|
297
|
+
clack.cancel('Install cancelled.');
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
300
|
+
targets.push(customPathTarget(projectDir, String(path).trim()));
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const [scope, name] = v.split(':');
|
|
304
|
+
const t = scope === 'home'
|
|
305
|
+
? homeAgentTarget(home, name)
|
|
306
|
+
: projectAgentTarget(projectDir, name);
|
|
307
|
+
if (t)
|
|
308
|
+
targets.push(t);
|
|
309
|
+
}
|
|
310
|
+
return dedupeBySkillsDir(targets);
|
|
311
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almyty/skills",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Install API skills into AI coding agents
|
|
3
|
+
"version": "1.0.9",
|
|
4
|
+
"description": "Install API skills into AI coding agents \u2014 Claude Code, Cursor, Windsurf, Copilot, Codex",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -32,7 +32,8 @@
|
|
|
32
32
|
"author": "almyty",
|
|
33
33
|
"license": "BSL-1.1",
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@almyty/client": "^0.1.0"
|
|
35
|
+
"@almyty/client": "^0.1.0",
|
|
36
|
+
"@clack/prompts": "^1.2.0"
|
|
36
37
|
},
|
|
37
38
|
"devDependencies": {
|
|
38
39
|
"@types/node": "^25.4.0",
|
|
@@ -40,4 +41,4 @@
|
|
|
40
41
|
"typescript": "^5.3.0",
|
|
41
42
|
"vitest": "^4.1.0"
|
|
42
43
|
}
|
|
43
|
-
}
|
|
44
|
+
}
|