@iceinvein/agent-skills 0.1.17 → 0.1.19
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 +11 -1
- package/dist/cli/index.js +140 -14
- package/package.json +1 -1
- package/skills/index.json +1 -1
- package/skills/terse/skill.json +6 -1
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ Skills that control how the agent communicates — output compression, token eff
|
|
|
87
87
|
## Commands
|
|
88
88
|
|
|
89
89
|
```
|
|
90
|
-
bunx @iceinvein/agent-skills install <skill> [--tool <tool>] [-g]
|
|
90
|
+
bunx @iceinvein/agent-skills install <skill> [--tool <tool>] [--activation <mode>] [-g]
|
|
91
91
|
bunx @iceinvein/agent-skills remove <skill> [-g]
|
|
92
92
|
bunx @iceinvein/agent-skills update <skill> [-g]
|
|
93
93
|
bunx @iceinvein/agent-skills list
|
|
@@ -97,8 +97,18 @@ bunx @iceinvein/agent-skills info <skill>
|
|
|
97
97
|
| Flag | |
|
|
98
98
|
|------|---|
|
|
99
99
|
| `--tool <tool>` | Target a specific tool: `claude`, `cursor`, `codex`, `gemini` |
|
|
100
|
+
| `--activation <mode>` | For skills that support it: `session` (manual `/skill`) or `global` (auto via `SessionStart` hook). Claude Code only. |
|
|
100
101
|
| `-g, --global` | Install to home directory (available in all projects) |
|
|
101
102
|
|
|
103
|
+
## Activation Modes (Claude Code)
|
|
104
|
+
|
|
105
|
+
Some skills — like `terse` — support activation modes. Pick one at install time:
|
|
106
|
+
|
|
107
|
+
- **session** (default) — invoke the skill manually with `/<skill>` each session
|
|
108
|
+
- **global** — auto-activate every Claude Code session via a `SessionStart` hook in `.claude/settings.json`
|
|
109
|
+
|
|
110
|
+
If a skill declares activation support and you're installing for Claude Code interactively, the CLI prompts you. Use `--activation session` or `--activation global` for scripted installs. `remove` strips the hook cleanly; `update` preserves your choice across version bumps.
|
|
111
|
+
|
|
102
112
|
## Supported Tools
|
|
103
113
|
|
|
104
114
|
| Tool | Prompt Skills | MCP Skills |
|
package/dist/cli/index.js
CHANGED
|
@@ -25,6 +25,7 @@ async function detectTools(cwd) {
|
|
|
25
25
|
// src/cli/types.ts
|
|
26
26
|
var TOOL_NAMES = ["claude", "cursor", "codex", "gemini"];
|
|
27
27
|
var SKILL_TYPES = ["prompt", "code", "hybrid"];
|
|
28
|
+
var ACTIVATION_MODES = ["session", "global"];
|
|
28
29
|
function validateManifest(data) {
|
|
29
30
|
if (typeof data !== "object" || data === null) {
|
|
30
31
|
return { ok: false, error: "Manifest must be an object" };
|
|
@@ -56,6 +57,29 @@ function validateManifest(data) {
|
|
|
56
57
|
if (typeof d.install !== "object" || d.install === null) {
|
|
57
58
|
return { ok: false, error: "Missing 'install' configuration" };
|
|
58
59
|
}
|
|
60
|
+
if (d.activation !== undefined) {
|
|
61
|
+
if (typeof d.activation !== "object" || d.activation === null) {
|
|
62
|
+
return { ok: false, error: "'activation' must be an object" };
|
|
63
|
+
}
|
|
64
|
+
const a = d.activation;
|
|
65
|
+
if (!Array.isArray(a.modes) || a.modes.length === 0) {
|
|
66
|
+
return { ok: false, error: "'activation.modes' must be a non-empty array" };
|
|
67
|
+
}
|
|
68
|
+
for (const m of a.modes) {
|
|
69
|
+
if (!ACTIVATION_MODES.includes(m)) {
|
|
70
|
+
return { ok: false, error: `Invalid activation mode '${m}': must be one of ${ACTIVATION_MODES.join(", ")}` };
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!ACTIVATION_MODES.includes(a.default)) {
|
|
74
|
+
return { ok: false, error: `Invalid 'activation.default': must be one of ${ACTIVATION_MODES.join(", ")}` };
|
|
75
|
+
}
|
|
76
|
+
if (!a.modes.includes(a.default)) {
|
|
77
|
+
return { ok: false, error: "'activation.default' must be one of 'activation.modes'" };
|
|
78
|
+
}
|
|
79
|
+
if (a.claudeHookDirective !== undefined && typeof a.claudeHookDirective !== "string") {
|
|
80
|
+
return { ok: false, error: "'activation.claudeHookDirective' must be a string" };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
59
83
|
return { ok: true, manifest: d };
|
|
60
84
|
}
|
|
61
85
|
|
|
@@ -109,9 +133,56 @@ async function fetchAllSkillFiles(skillName, manifest) {
|
|
|
109
133
|
// src/cli/adapters/claude.ts
|
|
110
134
|
import { existsSync as existsSync2, mkdirSync, rmSync, unlinkSync } from "node:fs";
|
|
111
135
|
import { dirname, join as join2 } from "node:path";
|
|
136
|
+
function matchesSkillDirective(command, skillName) {
|
|
137
|
+
return command.includes(`Activate ${skillName} skill`);
|
|
138
|
+
}
|
|
139
|
+
async function wireSessionStartHook(settingsPath, skillName, directive) {
|
|
140
|
+
let settings = {};
|
|
141
|
+
if (existsSync2(settingsPath)) {
|
|
142
|
+
settings = await Bun.file(settingsPath).json();
|
|
143
|
+
}
|
|
144
|
+
if (!settings.hooks)
|
|
145
|
+
settings.hooks = {};
|
|
146
|
+
if (!settings.hooks.SessionStart)
|
|
147
|
+
settings.hooks.SessionStart = [];
|
|
148
|
+
const command = `echo '${directive}'`;
|
|
149
|
+
for (const group of settings.hooks.SessionStart) {
|
|
150
|
+
for (const hook of group.hooks ?? []) {
|
|
151
|
+
if (matchesSkillDirective(hook.command, skillName))
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
settings.hooks.SessionStart.push({
|
|
156
|
+
hooks: [{ type: "command", command }]
|
|
157
|
+
});
|
|
158
|
+
mkdirSync(dirname(settingsPath), { recursive: true });
|
|
159
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
160
|
+
`);
|
|
161
|
+
}
|
|
162
|
+
async function unwireSessionStartHook(settingsPath, skillName) {
|
|
163
|
+
if (!existsSync2(settingsPath))
|
|
164
|
+
return;
|
|
165
|
+
const settings = await Bun.file(settingsPath).json();
|
|
166
|
+
const sessionStart = settings.hooks?.SessionStart;
|
|
167
|
+
if (!sessionStart)
|
|
168
|
+
return;
|
|
169
|
+
const filteredGroups = sessionStart.map((group) => ({
|
|
170
|
+
hooks: (group.hooks ?? []).filter((h) => !matchesSkillDirective(h.command, skillName))
|
|
171
|
+
})).filter((group) => group.hooks.length > 0);
|
|
172
|
+
if (filteredGroups.length === 0) {
|
|
173
|
+
delete settings.hooks.SessionStart;
|
|
174
|
+
} else {
|
|
175
|
+
settings.hooks.SessionStart = filteredGroups;
|
|
176
|
+
}
|
|
177
|
+
if (settings.hooks && Object.keys(settings.hooks).length === 0) {
|
|
178
|
+
delete settings.hooks;
|
|
179
|
+
}
|
|
180
|
+
await Bun.write(settingsPath, JSON.stringify(settings, null, 2) + `
|
|
181
|
+
`);
|
|
182
|
+
}
|
|
112
183
|
var claudeAdapter = {
|
|
113
184
|
name: "claude",
|
|
114
|
-
async install(cwd, manifest, files) {
|
|
185
|
+
async install(cwd, manifest, files, activation) {
|
|
115
186
|
const installed = [];
|
|
116
187
|
const config = manifest.install.claude;
|
|
117
188
|
if (!config)
|
|
@@ -147,6 +218,13 @@ var claudeAdapter = {
|
|
|
147
218
|
`);
|
|
148
219
|
installed.push(".claude/settings.json");
|
|
149
220
|
}
|
|
221
|
+
if (activation === "global" && manifest.activation?.claudeHookDirective) {
|
|
222
|
+
const settingsPath = join2(cwd, ".claude/settings.json");
|
|
223
|
+
await wireSessionStartHook(settingsPath, manifest.name, manifest.activation.claudeHookDirective);
|
|
224
|
+
if (!installed.includes(".claude/settings.json")) {
|
|
225
|
+
installed.push(".claude/settings.json");
|
|
226
|
+
}
|
|
227
|
+
}
|
|
150
228
|
return installed;
|
|
151
229
|
},
|
|
152
230
|
async remove(cwd, manifest, installedFiles) {
|
|
@@ -178,6 +256,10 @@ var claudeAdapter = {
|
|
|
178
256
|
}
|
|
179
257
|
}
|
|
180
258
|
}
|
|
259
|
+
if (manifest.activation?.claudeHookDirective) {
|
|
260
|
+
const settingsPath = join2(cwd, ".claude/settings.json");
|
|
261
|
+
await unwireSessionStartHook(settingsPath, manifest.name);
|
|
262
|
+
}
|
|
181
263
|
}
|
|
182
264
|
};
|
|
183
265
|
|
|
@@ -186,7 +268,7 @@ import { existsSync as existsSync3, mkdirSync as mkdirSync2, unlinkSync as unlin
|
|
|
186
268
|
import { dirname as dirname2, join as join3 } from "node:path";
|
|
187
269
|
var cursorAdapter = {
|
|
188
270
|
name: "cursor",
|
|
189
|
-
async install(cwd, manifest, files) {
|
|
271
|
+
async install(cwd, manifest, files, _activation) {
|
|
190
272
|
const installed = [];
|
|
191
273
|
const config = manifest.install.cursor;
|
|
192
274
|
if (!config)
|
|
@@ -260,7 +342,7 @@ import { existsSync as existsSync4 } from "node:fs";
|
|
|
260
342
|
import { join as join4 } from "node:path";
|
|
261
343
|
var codexAdapter = {
|
|
262
344
|
name: "codex",
|
|
263
|
-
async install(cwd, manifest, files) {
|
|
345
|
+
async install(cwd, manifest, files, _activation) {
|
|
264
346
|
const installed = [];
|
|
265
347
|
const config = manifest.install.codex;
|
|
266
348
|
if (!config)
|
|
@@ -316,7 +398,7 @@ import { existsSync as existsSync5, mkdirSync as mkdirSync3, unlinkSync as unlin
|
|
|
316
398
|
import { dirname as dirname3, join as join5 } from "node:path";
|
|
317
399
|
var geminiAdapter = {
|
|
318
400
|
name: "gemini",
|
|
319
|
-
async install(cwd, manifest, files) {
|
|
401
|
+
async install(cwd, manifest, files, _activation) {
|
|
320
402
|
const installed = [];
|
|
321
403
|
const config = manifest.install.gemini;
|
|
322
404
|
if (!config)
|
|
@@ -424,7 +506,7 @@ async function removeSkillFromLockfile(cwd, skillName) {
|
|
|
424
506
|
}
|
|
425
507
|
|
|
426
508
|
// src/cli/commands/install.ts
|
|
427
|
-
async function installSkill(cwd, manifest, files, targetTools) {
|
|
509
|
+
async function installSkill(cwd, manifest, files, targetTools, activation) {
|
|
428
510
|
const compatibleTools = targetTools.filter((t) => manifest.tools.includes(t));
|
|
429
511
|
const skipped = targetTools.filter((t) => !manifest.tools.includes(t));
|
|
430
512
|
if (compatibleTools.length === 0) {
|
|
@@ -437,7 +519,7 @@ async function installSkill(cwd, manifest, files, targetTools) {
|
|
|
437
519
|
const allFiles = [];
|
|
438
520
|
for (const tool of compatibleTools) {
|
|
439
521
|
const adapter = getAdapter(tool);
|
|
440
|
-
const toolFiles = await adapter.install(cwd, manifest, files);
|
|
522
|
+
const toolFiles = await adapter.install(cwd, manifest, files, activation);
|
|
441
523
|
installed[tool] = toolFiles;
|
|
442
524
|
allFiles.push(...toolFiles);
|
|
443
525
|
}
|
|
@@ -445,7 +527,8 @@ async function installSkill(cwd, manifest, files, targetTools) {
|
|
|
445
527
|
version: manifest.version,
|
|
446
528
|
tools: compatibleTools,
|
|
447
529
|
installedAt: new Date().toISOString(),
|
|
448
|
-
files: allFiles
|
|
530
|
+
files: allFiles,
|
|
531
|
+
...activation !== undefined ? { activation } : {}
|
|
449
532
|
});
|
|
450
533
|
return { ok: true, installed, skipped };
|
|
451
534
|
}
|
|
@@ -594,7 +677,7 @@ async function removeSkill2(cwd, skillName) {
|
|
|
594
677
|
}
|
|
595
678
|
|
|
596
679
|
// src/cli/commands/install.ts
|
|
597
|
-
async function installSkill2(cwd, manifest, files, targetTools) {
|
|
680
|
+
async function installSkill2(cwd, manifest, files, targetTools, activation) {
|
|
598
681
|
const compatibleTools = targetTools.filter((t) => manifest.tools.includes(t));
|
|
599
682
|
const skipped = targetTools.filter((t) => !manifest.tools.includes(t));
|
|
600
683
|
if (compatibleTools.length === 0) {
|
|
@@ -607,7 +690,7 @@ async function installSkill2(cwd, manifest, files, targetTools) {
|
|
|
607
690
|
const allFiles = [];
|
|
608
691
|
for (const tool of compatibleTools) {
|
|
609
692
|
const adapter = getAdapter(tool);
|
|
610
|
-
const toolFiles = await adapter.install(cwd, manifest, files);
|
|
693
|
+
const toolFiles = await adapter.install(cwd, manifest, files, activation);
|
|
611
694
|
installed[tool] = toolFiles;
|
|
612
695
|
allFiles.push(...toolFiles);
|
|
613
696
|
}
|
|
@@ -615,7 +698,8 @@ async function installSkill2(cwd, manifest, files, targetTools) {
|
|
|
615
698
|
version: manifest.version,
|
|
616
699
|
tools: compatibleTools,
|
|
617
700
|
installedAt: new Date().toISOString(),
|
|
618
|
-
files: allFiles
|
|
701
|
+
files: allFiles,
|
|
702
|
+
...activation !== undefined ? { activation } : {}
|
|
619
703
|
});
|
|
620
704
|
return { ok: true, installed, skipped };
|
|
621
705
|
}
|
|
@@ -629,6 +713,7 @@ async function updateSkill(cwd, skillName) {
|
|
|
629
713
|
}
|
|
630
714
|
const oldVersion = entry.version;
|
|
631
715
|
const tools = entry.tools;
|
|
716
|
+
const activation = entry.activation;
|
|
632
717
|
const manifestResult = await fetchSkillManifest2(skillName);
|
|
633
718
|
if (!manifestResult.ok) {
|
|
634
719
|
return { ok: false, error: manifestResult.error };
|
|
@@ -638,7 +723,7 @@ async function updateSkill(cwd, skillName) {
|
|
|
638
723
|
return { ok: false, error: filesResult.error };
|
|
639
724
|
}
|
|
640
725
|
await removeSkill2(cwd, skillName);
|
|
641
|
-
const installResult = await installSkill2(cwd, manifestResult.manifest, filesResult, tools);
|
|
726
|
+
const installResult = await installSkill2(cwd, manifestResult.manifest, filesResult, tools, activation);
|
|
642
727
|
if (!installResult.ok) {
|
|
643
728
|
return { ok: false, error: installResult.error };
|
|
644
729
|
}
|
|
@@ -832,6 +917,26 @@ Select (comma-separated numbers, e.g. 1,3): `);
|
|
|
832
917
|
}
|
|
833
918
|
return selected;
|
|
834
919
|
}
|
|
920
|
+
async function promptActivation(skillName, modes) {
|
|
921
|
+
console.log(`
|
|
922
|
+
How should ${skillName} activate in Claude Code?
|
|
923
|
+
`);
|
|
924
|
+
const labels = {
|
|
925
|
+
session: `Per-session - invoke manually with /${skillName}`,
|
|
926
|
+
global: "Global - auto-activate every session (adds SessionStart hook)"
|
|
927
|
+
};
|
|
928
|
+
for (let i = 0;i < modes.length; i++) {
|
|
929
|
+
console.log(` ${i + 1}) ${labels[modes[i]]}`);
|
|
930
|
+
}
|
|
931
|
+
const answer = await ask(`
|
|
932
|
+
Select: `);
|
|
933
|
+
const n = parseInt(answer, 10);
|
|
934
|
+
if (isNaN(n) || n < 1 || n > modes.length) {
|
|
935
|
+
console.error("Invalid selection. Exiting.");
|
|
936
|
+
process.exit(1);
|
|
937
|
+
}
|
|
938
|
+
return modes[n - 1];
|
|
939
|
+
}
|
|
835
940
|
|
|
836
941
|
// src/cli/index.ts
|
|
837
942
|
import { mkdirSync as mkdirSync4 } from "fs";
|
|
@@ -873,7 +978,7 @@ function printHelp() {
|
|
|
873
978
|
@iceinvein/agent-skills \u2014 Install agent skills into AI coding tools
|
|
874
979
|
|
|
875
980
|
Usage:
|
|
876
|
-
agent-skills install <skill> [--tool <tool>] [-g] Install a skill
|
|
981
|
+
agent-skills install <skill> [--tool <tool>] [--activation <session|global>] [-g] Install a skill
|
|
877
982
|
agent-skills remove <skill> [-g] Remove a skill
|
|
878
983
|
agent-skills update <skill> [-g] Update a skill
|
|
879
984
|
agent-skills update --all [-g] Update all installed skills
|
|
@@ -886,6 +991,7 @@ Flags:
|
|
|
886
991
|
--tool <tool> Install for a specific tool (${TOOL_NAMES2.join(", ")})
|
|
887
992
|
-g, --global Install to home directory (available in all projects)
|
|
888
993
|
--dry-run With bump --all: check without writing (exit 1 if unbumped)
|
|
994
|
+
--activation <mode> Set activation mode for skills that support it (session or global)
|
|
889
995
|
`);
|
|
890
996
|
}
|
|
891
997
|
function printInstalled(result, skipped) {
|
|
@@ -953,7 +1059,23 @@ async function main() {
|
|
|
953
1059
|
console.log(`Detected tools: ${tools.join(", ")}`);
|
|
954
1060
|
}
|
|
955
1061
|
}
|
|
956
|
-
|
|
1062
|
+
let activation;
|
|
1063
|
+
const manifest = manifestResult.manifest;
|
|
1064
|
+
if (manifest.activation && tools.includes("claude")) {
|
|
1065
|
+
const validModes = manifest.activation.modes;
|
|
1066
|
+
if (flags.activation) {
|
|
1067
|
+
if (!validModes.includes(flags.activation)) {
|
|
1068
|
+
console.error(`Error: activation mode '${flags.activation}' not supported. Must be one of: ${validModes.join(", ")}`);
|
|
1069
|
+
process.exit(1);
|
|
1070
|
+
}
|
|
1071
|
+
activation = flags.activation;
|
|
1072
|
+
} else if (process.stdin.isTTY) {
|
|
1073
|
+
activation = await promptActivation(manifest.name, validModes);
|
|
1074
|
+
} else {
|
|
1075
|
+
activation = manifest.activation.default;
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const result = await installSkill(installDir, manifestResult.manifest, filesResult, tools, activation);
|
|
957
1079
|
if (!result.ok) {
|
|
958
1080
|
console.error(`Error: ${result.error}`);
|
|
959
1081
|
process.exit(1);
|
|
@@ -1010,7 +1132,11 @@ async function main() {
|
|
|
1010
1132
|
console.error(`Error: ${result.error}`);
|
|
1011
1133
|
process.exit(1);
|
|
1012
1134
|
}
|
|
1013
|
-
|
|
1135
|
+
if (result.from === result.to) {
|
|
1136
|
+
console.log(`\u2298 '${skillName}' is already up to date (v${result.to})`);
|
|
1137
|
+
} else {
|
|
1138
|
+
console.log(`\u2713 Updated '${skillName}' from v${result.from} to v${result.to}`);
|
|
1139
|
+
}
|
|
1014
1140
|
break;
|
|
1015
1141
|
}
|
|
1016
1142
|
case "list": {
|
package/package.json
CHANGED
package/skills/index.json
CHANGED
package/skills/terse/skill.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terse",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Professional output compression — proper grammar, no fluff, ~50-60% fewer tokens. Three levels: clean, tight, sharp.",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"type": "prompt",
|
|
@@ -27,5 +27,10 @@
|
|
|
27
27
|
"gemini": {
|
|
28
28
|
"prompt": ".gemini/skills/terse.md"
|
|
29
29
|
}
|
|
30
|
+
},
|
|
31
|
+
"activation": {
|
|
32
|
+
"modes": ["session", "global"],
|
|
33
|
+
"default": "session",
|
|
34
|
+
"claudeHookDirective": "Activate terse skill at tight level for this session."
|
|
30
35
|
}
|
|
31
36
|
}
|