@iceinvein/agent-skills 0.1.16 → 0.1.18
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 +293 -16
- package/package.json +6 -2
- package/skills/index.json +1 -1
- package/skills/terse/SKILL.md +20 -2
- package/skills/terse/skill.json +12 -2
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
|
}
|
|
@@ -657,6 +742,111 @@ async function updateAllSkills(cwd) {
|
|
|
657
742
|
return results;
|
|
658
743
|
}
|
|
659
744
|
|
|
745
|
+
// src/cli/commands/bump.ts
|
|
746
|
+
import { join as join7 } from "node:path";
|
|
747
|
+
|
|
748
|
+
// src/cli/semver.ts
|
|
749
|
+
function bumpVersion(current, level) {
|
|
750
|
+
const parts = current.split(".");
|
|
751
|
+
if (parts.length !== 3 || parts.some((p) => !/^\d+$/.test(p))) {
|
|
752
|
+
throw new Error(`Invalid semver version: '${current}'`);
|
|
753
|
+
}
|
|
754
|
+
const [major, minor, patch] = parts.map(Number);
|
|
755
|
+
switch (level) {
|
|
756
|
+
case "major":
|
|
757
|
+
return `${major + 1}.0.0`;
|
|
758
|
+
case "minor":
|
|
759
|
+
return `${major}.${minor + 1}.0`;
|
|
760
|
+
case "patch":
|
|
761
|
+
return `${major}.${minor}.${patch + 1}`;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
// src/cli/commands/bump.ts
|
|
766
|
+
async function bumpSkill(repoRoot, skillName, level) {
|
|
767
|
+
const manifestPath = join7(repoRoot, "skills", skillName, "skill.json");
|
|
768
|
+
const file = Bun.file(manifestPath);
|
|
769
|
+
if (!await file.exists()) {
|
|
770
|
+
return { ok: false, error: `Skill '${skillName}' not found at skills/${skillName}/skill.json` };
|
|
771
|
+
}
|
|
772
|
+
const manifest = await file.json();
|
|
773
|
+
const from = manifest.version;
|
|
774
|
+
let to;
|
|
775
|
+
try {
|
|
776
|
+
to = bumpVersion(from, level);
|
|
777
|
+
} catch (e) {
|
|
778
|
+
return { ok: false, error: `Invalid version '${from}' in skills/${skillName}/skill.json` };
|
|
779
|
+
}
|
|
780
|
+
manifest.version = to;
|
|
781
|
+
await Bun.write(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
782
|
+
`);
|
|
783
|
+
return { ok: true, from, to };
|
|
784
|
+
}
|
|
785
|
+
function getLatestTag(repoRoot) {
|
|
786
|
+
const result = Bun.spawnSync(["git", "describe", "--tags", "--abbrev=0"], {
|
|
787
|
+
cwd: repoRoot
|
|
788
|
+
});
|
|
789
|
+
if (result.exitCode !== 0)
|
|
790
|
+
return null;
|
|
791
|
+
return result.stdout.toString().trim();
|
|
792
|
+
}
|
|
793
|
+
var EMPTY_TREE = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
|
|
794
|
+
function getChangedSkills(repoRoot, sinceTag) {
|
|
795
|
+
const base = sinceTag ?? EMPTY_TREE;
|
|
796
|
+
const args = ["git", "diff", "--name-only", base, "HEAD", "--", "skills/"];
|
|
797
|
+
const result = Bun.spawnSync(args, { cwd: repoRoot });
|
|
798
|
+
const output = result.stdout.toString().trim();
|
|
799
|
+
if (!output)
|
|
800
|
+
return new Set;
|
|
801
|
+
const names = new Set;
|
|
802
|
+
for (const line of output.split(`
|
|
803
|
+
`)) {
|
|
804
|
+
const parts = line.split("/");
|
|
805
|
+
if (parts.length >= 2 && parts[0] === "skills") {
|
|
806
|
+
names.add(parts[1]);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return names;
|
|
810
|
+
}
|
|
811
|
+
function skillVersionChanged(repoRoot, skillName, sinceTag) {
|
|
812
|
+
const base = sinceTag ?? EMPTY_TREE;
|
|
813
|
+
const args = ["git", "diff", base, "HEAD", "--", `skills/${skillName}/skill.json`];
|
|
814
|
+
const result = Bun.spawnSync(args, { cwd: repoRoot });
|
|
815
|
+
const diff = result.stdout.toString();
|
|
816
|
+
const hasRemoved = diff.split(`
|
|
817
|
+
`).some((l) => l.startsWith("-") && !l.startsWith("---") && l.includes('"version"'));
|
|
818
|
+
const hasAdded = diff.split(`
|
|
819
|
+
`).some((l) => l.startsWith("+") && !l.startsWith("+++") && l.includes('"version"'));
|
|
820
|
+
return hasRemoved && hasAdded;
|
|
821
|
+
}
|
|
822
|
+
async function bumpAllChanged(repoRoot, level, dryRun) {
|
|
823
|
+
const tag = getLatestTag(repoRoot);
|
|
824
|
+
const changed = getChangedSkills(repoRoot, tag);
|
|
825
|
+
const results = [];
|
|
826
|
+
for (const skillName of changed) {
|
|
827
|
+
if (skillVersionChanged(repoRoot, skillName, tag))
|
|
828
|
+
continue;
|
|
829
|
+
if (dryRun) {
|
|
830
|
+
const manifestPath = join7(repoRoot, "skills", skillName, "skill.json");
|
|
831
|
+
const file = Bun.file(manifestPath);
|
|
832
|
+
if (!await file.exists())
|
|
833
|
+
continue;
|
|
834
|
+
const manifest = await file.json();
|
|
835
|
+
const from = manifest.version;
|
|
836
|
+
try {
|
|
837
|
+
const to = bumpVersion(from, level);
|
|
838
|
+
results.push({ name: skillName, ok: true, from, to });
|
|
839
|
+
} catch {
|
|
840
|
+
results.push({ name: skillName, ok: false, error: `Invalid version '${from}'` });
|
|
841
|
+
}
|
|
842
|
+
} else {
|
|
843
|
+
const result = await bumpSkill(repoRoot, skillName, level);
|
|
844
|
+
results.push({ name: skillName, ...result });
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
return results;
|
|
848
|
+
}
|
|
849
|
+
|
|
660
850
|
// src/cli/update-check.ts
|
|
661
851
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
662
852
|
async function checkForUpdates(cwd) {
|
|
@@ -727,10 +917,30 @@ Select (comma-separated numbers, e.g. 1,3): `);
|
|
|
727
917
|
}
|
|
728
918
|
return selected;
|
|
729
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
|
+
}
|
|
730
940
|
|
|
731
941
|
// src/cli/index.ts
|
|
732
942
|
import { mkdirSync as mkdirSync4 } from "fs";
|
|
733
|
-
import { join as
|
|
943
|
+
import { join as join8 } from "path";
|
|
734
944
|
import { homedir } from "os";
|
|
735
945
|
function resolveInstallDir(flags) {
|
|
736
946
|
if (flags.global !== undefined || flags.g !== undefined) {
|
|
@@ -738,7 +948,7 @@ function resolveInstallDir(flags) {
|
|
|
738
948
|
}
|
|
739
949
|
return process.cwd();
|
|
740
950
|
}
|
|
741
|
-
var BOOLEAN_FLAGS = new Set(["global", "g", "all"]);
|
|
951
|
+
var BOOLEAN_FLAGS = new Set(["global", "g", "all", "dry-run"]);
|
|
742
952
|
function parseArgs(argv) {
|
|
743
953
|
if (argv.length === 0)
|
|
744
954
|
return { command: "help", args: [], flags: {} };
|
|
@@ -768,16 +978,20 @@ function printHelp() {
|
|
|
768
978
|
@iceinvein/agent-skills \u2014 Install agent skills into AI coding tools
|
|
769
979
|
|
|
770
980
|
Usage:
|
|
771
|
-
agent-skills install <skill> [--tool <tool>] [-g] Install a skill
|
|
981
|
+
agent-skills install <skill> [--tool <tool>] [--activation <session|global>] [-g] Install a skill
|
|
772
982
|
agent-skills remove <skill> [-g] Remove a skill
|
|
773
983
|
agent-skills update <skill> [-g] Update a skill
|
|
774
984
|
agent-skills update --all [-g] Update all installed skills
|
|
985
|
+
agent-skills bump <skill> [patch|minor|major] Bump a skill's version
|
|
986
|
+
agent-skills bump --all [patch|minor|major] Bump all changed skills
|
|
775
987
|
agent-skills list List available skills
|
|
776
988
|
agent-skills info <skill> Show skill details
|
|
777
989
|
|
|
778
990
|
Flags:
|
|
779
991
|
--tool <tool> Install for a specific tool (${TOOL_NAMES2.join(", ")})
|
|
780
992
|
-g, --global Install to home directory (available in all projects)
|
|
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)
|
|
781
995
|
`);
|
|
782
996
|
}
|
|
783
997
|
function printInstalled(result, skipped) {
|
|
@@ -838,14 +1052,30 @@ async function main() {
|
|
|
838
1052
|
gemini: ".gemini"
|
|
839
1053
|
};
|
|
840
1054
|
if (dirs[tool]) {
|
|
841
|
-
mkdirSync4(
|
|
1055
|
+
mkdirSync4(join8(installDir, dirs[tool]), { recursive: true });
|
|
842
1056
|
}
|
|
843
1057
|
}
|
|
844
1058
|
} else {
|
|
845
1059
|
console.log(`Detected tools: ${tools.join(", ")}`);
|
|
846
1060
|
}
|
|
847
1061
|
}
|
|
848
|
-
|
|
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);
|
|
849
1079
|
if (!result.ok) {
|
|
850
1080
|
console.error(`Error: ${result.error}`);
|
|
851
1081
|
process.exit(1);
|
|
@@ -943,6 +1173,53 @@ ${m.name} v${m.version}`);
|
|
|
943
1173
|
console.log(` Package: ${m.mcp.package}`);
|
|
944
1174
|
break;
|
|
945
1175
|
}
|
|
1176
|
+
case "bump": {
|
|
1177
|
+
const BUMP_LEVELS = ["patch", "minor", "major"];
|
|
1178
|
+
const repoRoot = import.meta.dir.replace(/\/dist\/cli$|\/src\/cli$/, "");
|
|
1179
|
+
if (flags.all !== undefined) {
|
|
1180
|
+
const levelArg = args[0] ?? "patch";
|
|
1181
|
+
if (!BUMP_LEVELS.includes(levelArg)) {
|
|
1182
|
+
console.error(`Error: invalid bump level '${levelArg}'. Must be one of: ${BUMP_LEVELS.join(", ")}`);
|
|
1183
|
+
process.exit(1);
|
|
1184
|
+
}
|
|
1185
|
+
const dryRun = flags["dry-run"] !== undefined;
|
|
1186
|
+
const results = await bumpAllChanged(repoRoot, levelArg, dryRun);
|
|
1187
|
+
if (results.length === 0) {
|
|
1188
|
+
if (!dryRun)
|
|
1189
|
+
console.log("All skill versions are up to date.");
|
|
1190
|
+
process.exit(0);
|
|
1191
|
+
}
|
|
1192
|
+
for (const r of results) {
|
|
1193
|
+
if (!r.ok) {
|
|
1194
|
+
console.error(` \u2717 ${r.name}: ${r.error}`);
|
|
1195
|
+
} else if (dryRun) {
|
|
1196
|
+
console.log(` needs bump: ${r.name} ${r.from} \u2192 ${r.to}`);
|
|
1197
|
+
} else {
|
|
1198
|
+
console.log(` \u2713 ${r.name} ${r.from} \u2192 ${r.to}`);
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
if (dryRun)
|
|
1202
|
+
process.exit(1);
|
|
1203
|
+
break;
|
|
1204
|
+
}
|
|
1205
|
+
const skillName = args[0];
|
|
1206
|
+
if (!skillName) {
|
|
1207
|
+
console.error("Error: skill name required. Usage: agent-skills bump <skill> [patch|minor|major]");
|
|
1208
|
+
process.exit(1);
|
|
1209
|
+
}
|
|
1210
|
+
const level = args[1] ?? "patch";
|
|
1211
|
+
if (!BUMP_LEVELS.includes(level)) {
|
|
1212
|
+
console.error(`Error: invalid bump level '${args[1]}'. Must be one of: ${BUMP_LEVELS.join(", ")}`);
|
|
1213
|
+
process.exit(1);
|
|
1214
|
+
}
|
|
1215
|
+
const result = await bumpSkill(repoRoot, skillName, level);
|
|
1216
|
+
if (!result.ok) {
|
|
1217
|
+
console.error(`Error: ${result.error}`);
|
|
1218
|
+
process.exit(1);
|
|
1219
|
+
}
|
|
1220
|
+
console.log(`\u2713 ${skillName} ${result.from} \u2192 ${result.to}`);
|
|
1221
|
+
break;
|
|
1222
|
+
}
|
|
946
1223
|
case "help":
|
|
947
1224
|
default:
|
|
948
1225
|
printHelp();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@iceinvein/agent-skills",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.18",
|
|
4
4
|
"description": "Install agent skills into AI coding tools",
|
|
5
5
|
"author": "iceinvein",
|
|
6
6
|
"license": "MIT",
|
|
@@ -15,7 +15,11 @@
|
|
|
15
15
|
"scripts": {
|
|
16
16
|
"build": "bun build src/cli/index.ts --outdir dist/cli --target bun",
|
|
17
17
|
"test": "bun test",
|
|
18
|
-
"dev": "bun run src/cli/index.ts"
|
|
18
|
+
"dev": "bun run src/cli/index.ts",
|
|
19
|
+
"skill:bump": "bun run src/cli/index.ts bump",
|
|
20
|
+
"skill:bump:all": "bun run src/cli/index.ts bump --all",
|
|
21
|
+
"skill:bump:check": "bun run src/cli/index.ts bump --all --dry-run",
|
|
22
|
+
"release": "bash scripts/release.sh"
|
|
19
23
|
},
|
|
20
24
|
"keywords": [
|
|
21
25
|
"ai",
|
package/skills/index.json
CHANGED
package/skills/terse/SKILL.md
CHANGED
|
@@ -36,9 +36,27 @@ Every response, cut these patterns:
|
|
|
36
36
|
- Kill: [explanation] "So in summary, what we did was update the middleware to validate tokens correctly, which should fix the authentication issue."
|
|
37
37
|
- Write: [explanation ends]
|
|
38
38
|
|
|
39
|
-
**Narrating actions** — don't announce what you're about to do. Just do it.
|
|
39
|
+
**Narrating actions** — don't announce what you're about to do. Just do it. The tool call is the communication. Never preface a tool call with text explaining that you're about to make it.
|
|
40
40
|
- Kill: "Let me take a look at the file for you. I'll read it now and analyze what's going on."
|
|
41
|
-
-
|
|
41
|
+
- Kill: "Now let me fix that issue."
|
|
42
|
+
- Kill: "Let me check the tests."
|
|
43
|
+
- Kill: "I'll update the config next."
|
|
44
|
+
- Kill: "We need to update the schema first."
|
|
45
|
+
- Kill: "First, I'll read the file to understand the structure."
|
|
46
|
+
- Write: [tool call — no preamble]
|
|
47
|
+
|
|
48
|
+
**Banned action-narration openers** — these phrases before a tool call are always filler. Cut them 100% of the time:
|
|
49
|
+
- "Let me..." / "Now let me..." / "Now I'll..."
|
|
50
|
+
- "I'll..." / "I need to..." / "We need to..."
|
|
51
|
+
- "First, let me..." / "Next, I'll..."
|
|
52
|
+
- "Going to..." / "I'm going to..."
|
|
53
|
+
- "Time to..." / "Let's..."
|
|
54
|
+
|
|
55
|
+
If context is needed between tool calls, state the *finding* or *decision*, not the action:
|
|
56
|
+
- Kill: "Now let me update the handler to fix this."
|
|
57
|
+
- Write: "The handler is missing the null check." [edits file]
|
|
58
|
+
- Kill: "Let me run the tests to verify."
|
|
59
|
+
- Write: [runs tests]
|
|
42
60
|
|
|
43
61
|
**Over-explaining the obvious** — don't describe trivial operations.
|
|
44
62
|
- Kill: "I'll create a new file called `utils.ts`. This file will contain utility functions that we can reuse across the project."
|
package/skills/terse/skill.json
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "terse",
|
|
3
|
-
"version": "1.
|
|
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",
|
|
7
|
-
"tools": [
|
|
7
|
+
"tools": [
|
|
8
|
+
"claude",
|
|
9
|
+
"cursor",
|
|
10
|
+
"codex",
|
|
11
|
+
"gemini"
|
|
12
|
+
],
|
|
8
13
|
"files": {
|
|
9
14
|
"prompt": "SKILL.md"
|
|
10
15
|
},
|
|
@@ -22,5 +27,10 @@
|
|
|
22
27
|
"gemini": {
|
|
23
28
|
"prompt": ".gemini/skills/terse.md"
|
|
24
29
|
}
|
|
30
|
+
},
|
|
31
|
+
"activation": {
|
|
32
|
+
"modes": ["session", "global"],
|
|
33
|
+
"default": "session",
|
|
34
|
+
"claudeHookDirective": "Activate terse skill at tight level for this session."
|
|
25
35
|
}
|
|
26
36
|
}
|