any-skills 0.1.1 → 0.1.3

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 CHANGED
@@ -1,22 +1,59 @@
1
1
  # any-skills
2
2
 
3
- When installed as a dependency, this package creates a shared skills directory (default: `.skills`) so Claude, Codex, and similar tools can reuse the same skills.
3
+ Create a shared skills directory and link tool-specific folders so Claude, Codex,
4
+ Gemini, and others can reuse the same skills.
4
5
 
5
- - `.claude/skills`
6
- - `.codex/skills`
6
+ ## What it does
7
7
 
8
- ## How it works
8
+ - Creates a shared `.skills` directory in the working directory (or uses the
9
+ configured target).
10
+ - Creates tool links such as `.claude/skills` or `.codex/skills`.
11
+ - Auto-detects `claude`, `codex`, and `gemini` commands on your PATH by default.
9
12
 
10
- The `postinstall` script runs at install time. It uses the user's install working directory as the root (prefers `INIT_CWD`, then `npm_config_local_prefix`, and falls back to `process.cwd()`). If the shared skills directory does not exist, it is created, then the tool-specific symlinks are generated (unless overridden by configuration).
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install any-skills --save-dev
17
+ ```
18
+
19
+ Or install globally:
20
+
21
+ ```sh
22
+ npm install -g any-skills
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ Create links in the current directory:
28
+
29
+ ```sh
30
+ npx any-skills
31
+ ```
32
+
33
+ Create links in your home directory (for example `~/.claude/skills`):
34
+
35
+ ```sh
36
+ npx any-skills -g
37
+ ```
38
+
39
+ Limit to specific tools:
40
+
41
+ ```sh
42
+ npx any-skills claude
43
+ npx any-skills deepseek
44
+ ```
45
+
46
+ If a tool is not in the configured map, it links to `.<name>/skills` by default.
11
47
 
12
48
  ## Configuration
13
49
 
14
- You can customize which links are created by adding `.skillsrc` (JSON) in your project root.
50
+ Create `.skillsrc` (JSON) in the working directory.
15
51
 
16
52
  Supported fields:
17
53
 
18
54
  - `target`: optional string. Overrides where skills are stored (default: `.skills`).
19
55
  - `links`: array of link definitions. Strings map to `target` by default.
56
+ - `tools`: optional object mapping command names to link paths.
20
57
 
21
58
  Example:
22
59
 
@@ -27,22 +64,28 @@ Example:
27
64
  }
28
65
  ```
29
66
 
67
+ Example (auto-detect commands):
68
+
69
+ ```json
70
+ {
71
+ "tools": {
72
+ "claude": ".claude/skills",
73
+ "codex": ".codex/skills",
74
+ "gemini": ".gemini/skills"
75
+ }
76
+ }
77
+ ```
78
+
30
79
  ## Cross-platform behavior
31
80
 
32
81
  - macOS / Linux uses `dir` symlinks
33
82
  - Windows uses `junction` for better compatibility
34
83
 
35
- ## Usage
36
-
37
- ```sh
38
- npm install any-skills --save-dev
39
- ```
40
-
41
84
  ## Git ignore
42
85
 
43
86
  Add the tool-specific shared skill directories to your `.gitignore`:
44
87
 
45
88
  ```
46
- .claude.skills
47
- .codex.skills
89
+ .claude/skills
90
+ .codex/skills
48
91
  ```
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "any-skills",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Share skills between Claude, Codex, and similar tools via a shared .skills directory.",
5
5
  "type": "commonjs",
6
- "scripts": {
7
- "postinstall": "node scripts/postinstall.js"
6
+ "bin": {
7
+ "any-skills": "scripts/cli.js"
8
8
  }
9
9
  }
package/scripts/cli.js ADDED
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const os = require('os');
5
+ const { linkSkills } = require('./link-skills');
6
+
7
+ function parseArgs(argv) {
8
+ const flags = new Set();
9
+ const tools = [];
10
+ for (const arg of argv) {
11
+ if (arg === '--global' || arg === '-g') {
12
+ flags.add('global');
13
+ continue;
14
+ }
15
+ if (arg.startsWith('-')) {
16
+ continue;
17
+ }
18
+ tools.push(arg);
19
+ }
20
+ return { flags, tools };
21
+ }
22
+
23
+ const rootDir = process.cwd();
24
+ const { flags, tools } = parseArgs(process.argv.slice(2));
25
+ const linkRoot = flags.has('global') ? os.homedir() : rootDir;
26
+ const explicitTools = tools.length ? tools : null;
27
+ const exitCode = linkSkills(rootDir, { linkRoot, explicitTools });
28
+ if (exitCode) {
29
+ process.exit(exitCode);
30
+ }
@@ -3,24 +3,13 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
 
6
- function getInstallRoot() {
7
- const initCwd = process.env.INIT_CWD;
8
- if (initCwd) {
9
- return path.resolve(initCwd);
10
- }
11
-
12
- const npmPrefix = process.env.npm_config_local_prefix;
13
- if (npmPrefix) {
14
- return path.resolve(npmPrefix);
15
- }
16
-
17
- return process.cwd();
18
- }
19
-
20
- const rootDir = getInstallRoot();
21
6
  const targetName = '.skills';
22
7
  const configFileName = '.skillsrc';
23
- const defaultLinkTargets = ['.claude/skills', '.codex/skills'];
8
+ const defaultToolTargets = {
9
+ claude: '.claude/skills',
10
+ codex: '.codex/skills',
11
+ gemini: '.gemini/skills',
12
+ };
24
13
 
25
14
  function ensureDir(dirPath) {
26
15
  if (!fs.existsSync(dirPath)) {
@@ -129,7 +118,7 @@ function resolveRootPath(value, baseDir) {
129
118
  return path.isAbsolute(value) ? value : path.join(baseDir, value);
130
119
  }
131
120
 
132
- function resolveTarget(config) {
121
+ function resolveTarget(config, rootDir) {
133
122
  if (
134
123
  config &&
135
124
  typeof config.target === 'string' &&
@@ -141,7 +130,7 @@ function resolveTarget(config) {
141
130
  return path.join(rootDir, targetName);
142
131
  }
143
132
 
144
- function normalizeLinkEntry(entry, target, configPath) {
133
+ function normalizeLinkEntry(entry, target, configPath, rootDir) {
145
134
  if (typeof entry === 'string') {
146
135
  return { link: entry, error: false };
147
136
  }
@@ -202,17 +191,108 @@ function getConfigEntries(config) {
202
191
  return null;
203
192
  }
204
193
 
205
- function buildLinkMappings({ config, configPath, exists, target }) {
194
+ function commandExists(command) {
195
+ const pathEnv = process.env.PATH || '';
196
+ const pathEntries = pathEnv.split(path.delimiter).filter(Boolean);
197
+ const isWindows = process.platform === 'win32';
198
+ const extensions = isWindows
199
+ ? (process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM')
200
+ .split(';')
201
+ .filter(Boolean)
202
+ : [''];
203
+
204
+ for (const entry of pathEntries) {
205
+ for (const ext of extensions) {
206
+ const candidate = path.join(entry, `${command}${ext}`);
207
+ try {
208
+ fs.accessSync(candidate, fs.constants.X_OK);
209
+ return true;
210
+ } catch (err) {
211
+ if (err.code !== 'ENOENT') {
212
+ continue;
213
+ }
214
+ }
215
+ }
216
+ }
217
+
218
+ return false;
219
+ }
220
+
221
+ function detectLinkTargets(config) {
222
+ const toolTargets =
223
+ config && config.tools && typeof config.tools === 'object'
224
+ ? config.tools
225
+ : defaultToolTargets;
226
+
227
+ const targets = [];
228
+ for (const [command, linkTarget] of Object.entries(toolTargets)) {
229
+ if (typeof linkTarget !== 'string' || linkTarget.trim() === '') {
230
+ continue;
231
+ }
232
+ if (commandExists(command)) {
233
+ targets.push(linkTarget);
234
+ }
235
+ }
236
+
237
+ return targets;
238
+ }
239
+
240
+ function resolveExplicitTargets(config, toolNames) {
241
+ const toolTargets =
242
+ config && config.tools && typeof config.tools === 'object'
243
+ ? config.tools
244
+ : defaultToolTargets;
245
+
246
+ const targets = [];
247
+ for (const name of toolNames) {
248
+ const mappedTarget = toolTargets[name];
249
+ if (typeof mappedTarget === 'string' && mappedTarget.trim() !== '') {
250
+ targets.push(mappedTarget);
251
+ continue;
252
+ }
253
+ const safeName = name.trim();
254
+ if (safeName) {
255
+ targets.push(`.${safeName}/skills`);
256
+ }
257
+ }
258
+
259
+ return targets;
260
+ }
261
+
262
+ function buildLinkMappings({
263
+ config,
264
+ configPath,
265
+ exists,
266
+ target,
267
+ rootDir,
268
+ linkRoot,
269
+ explicitTools,
270
+ }) {
271
+ const resolvedLinkRoot = linkRoot || rootDir;
272
+ const explicitTargets = Array.isArray(explicitTools)
273
+ ? resolveExplicitTargets(config, explicitTools)
274
+ : null;
206
275
  const entries = getConfigEntries(config);
207
276
  if (!entries) {
277
+ if (explicitTargets) {
278
+ return {
279
+ mappings: explicitTargets.map((linkTarget) => ({
280
+ linkPath: resolveRootPath(linkTarget, resolvedLinkRoot),
281
+ targetPath: target,
282
+ })),
283
+ error: false,
284
+ };
285
+ }
286
+
208
287
  if (exists) {
209
288
  console.warn(
210
- `[any-skills] No link configuration found in ${configPath}; using defaults.`
289
+ `[any-skills] No link configuration found in ${configPath}; using auto-detected tools.`
211
290
  );
212
291
  }
292
+ const detectedTargets = detectLinkTargets(config);
213
293
  return {
214
- mappings: defaultLinkTargets.map((linkTarget) => ({
215
- linkPath: resolveRootPath(linkTarget, rootDir),
294
+ mappings: detectedTargets.map((linkTarget) => ({
295
+ linkPath: resolveRootPath(linkTarget, resolvedLinkRoot),
216
296
  targetPath: target,
217
297
  })),
218
298
  error: false,
@@ -225,7 +305,7 @@ function buildLinkMappings({ config, configPath, exists, target }) {
225
305
 
226
306
  const mappings = [];
227
307
  for (const entry of entries) {
228
- const normalized = normalizeLinkEntry(entry, target, configPath);
308
+ const normalized = normalizeLinkEntry(entry, target, configPath, rootDir);
229
309
  if (!normalized) {
230
310
  continue;
231
311
  }
@@ -233,7 +313,7 @@ function buildLinkMappings({ config, configPath, exists, target }) {
233
313
  return { mappings: [], error: true };
234
314
  }
235
315
  mappings.push({
236
- linkPath: resolveRootPath(normalized.link, rootDir),
316
+ linkPath: resolveRootPath(normalized.link, resolvedLinkRoot),
237
317
  targetPath: target,
238
318
  });
239
319
  }
@@ -241,7 +321,7 @@ function buildLinkMappings({ config, configPath, exists, target }) {
241
321
  return { mappings, error: false };
242
322
  }
243
323
 
244
- function linkSkills() {
324
+ function linkSkills(rootDir, options = {}) {
245
325
  const { config, configPath, exists, error } = readSkillsConfig(rootDir);
246
326
  if (error) {
247
327
  console.error(
@@ -250,7 +330,7 @@ function linkSkills() {
250
330
  return 1;
251
331
  }
252
332
 
253
- const target = resolveTarget(config);
333
+ const target = resolveTarget(config, rootDir);
254
334
  if (!ensureTarget(target)) {
255
335
  return 1;
256
336
  }
@@ -260,6 +340,9 @@ function linkSkills() {
260
340
  configPath,
261
341
  exists,
262
342
  target,
343
+ rootDir,
344
+ linkRoot: options.linkRoot,
345
+ explicitTools: options.explicitTools,
263
346
  });
264
347
  if (mappingError) {
265
348
  return 1;
@@ -272,7 +355,6 @@ function linkSkills() {
272
355
  return 0;
273
356
  }
274
357
 
275
- const exitCode = linkSkills();
276
- if (exitCode) {
277
- process.exit(exitCode);
278
- }
358
+ module.exports = {
359
+ linkSkills,
360
+ };