skillfish 1.0.34 → 1.0.36

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
@@ -124,9 +124,9 @@ Skills can be pinned to a specific ref (tag, branch, or commit) using `@ref` syn
124
124
 
125
125
  ## Supported Agents
126
126
 
127
- Works with 32 agents including:
127
+ Works with 33 agents including:
128
128
 
129
- **Claude Code** · **Cursor** · **Windsurf** · **Codex** · **GitHub Copilot** · **Gemini CLI** · **OpenCode** · **Goose** · **Amp** · **Roo Code** · **Kiro CLI** · **Kilo Code** · **Trae** · **Cline** · **Antigravity** · **Droid** · **Augment** · **OpenClaw** · **CodeBuddy** · **Command Code** · **Crush** · **Kode** · **Mistral Vibe** · **Mux** · **OpenClaude IDE** · **OpenHands** · **Qoder** · **Qwen Code** · **Replit** · **Trae CN** · **Neovate** · **AdaL**
129
+ **Claude Code** · **Cursor** · **Windsurf** · **Codex** · **GitHub Copilot** · **Gemini CLI** · **OpenCode** · **Goose** · **Amp** · **Roo Code** · **Kiro CLI** · **Kimi CLI** · **Kilo Code** · **Trae** · **Cline** · **Antigravity** · **Droid** · **Augment** · **OpenClaw** · **CodeBuddy** · **Command Code** · **Crush** · **Kode** · **Mistral Vibe** · **Mux** · **OpenClaude IDE** · **OpenHands** · **Qoder** · **Qwen Code** · **Replit** · **Trae CN** · **Neovate** · **AdaL**
130
130
 
131
131
  <details>
132
132
  <summary>All supported agents</summary>
@@ -144,6 +144,7 @@ Works with 32 agents including:
144
144
  | Amp | `~/.agents/skills/` |
145
145
  | Roo Code | `~/.roo/skills/` |
146
146
  | Kiro CLI | `~/.kiro/skills/` |
147
+ | Kimi CLI | `~/.kimi/skills/` |
147
148
  | Kilo Code | `~/.kilocode/skills/` |
148
149
  | Trae | `~/.trae/skills/` |
149
150
  | Cline | `~/.cline/skills/` |
@@ -400,9 +401,29 @@ See [CHANGELOG.md](CHANGELOG.md) for release history.
400
401
  <details>
401
402
  <summary>Telemetry</summary>
402
403
 
403
- Anonymous, aggregate install counts only. No PII collected.
404
+ Anonymous, aggregate usage data no PII, no identifiers, no IP fingerprinting on our side.
404
405
 
405
- To opt out: `DO_NOT_TRACK=1` or `CI=true`.
406
+ **What we send**
407
+
408
+ - `command` events (one per CLI invocation): the subcommand name (`add`, `install`, `list`, etc.)
409
+ - `install` events (one per successful skill install): the skill repo (`owner/repo`), the skill name, and the platform names it was installed to (e.g. `Claude Code`, `Cursor`)
410
+
411
+ That's it. No usernames, no machine IDs, no file paths, no skill contents.
412
+
413
+ **How to opt out**
414
+
415
+ Set either env var to any non-falsy value:
416
+
417
+ ```bash
418
+ export DO_NOT_TRACK=1 # https://consoledonottrack.com/
419
+ export CI=true # already set in most CI environments
420
+ ```
421
+
422
+ Telemetry is also automatically disabled when running from source (e.g. `tsx`, `npm test`).
423
+
424
+ **How it's sent**
425
+
426
+ Each event is dispatched to a detached background process and the CLI returns immediately, so telemetry can never block your terminal. If our server is down or slow, your CLI still exits instantly.
406
427
 
407
428
  </details>
408
429
 
@@ -72,7 +72,7 @@ Examples:
72
72
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
73
73
  }
74
74
  // Track command usage (fire and forget)
75
- void trackCommand('add');
75
+ trackCommand('add');
76
76
  const force = options.force ?? false;
77
77
  const trustSource = options.yes ?? false;
78
78
  const installAll = options.all ?? false;
@@ -263,9 +263,9 @@ Examples:
263
263
  }
264
264
  totalInstalled += result.installed.length;
265
265
  totalSkipped += result.skipped.length;
266
- // Track successful installs (fire and forget)
266
+ // Track successful installs (fire and forget — dispatched to detached worker)
267
267
  if (result.installed.length > 0) {
268
- void trackInstall('add', owner, repo, skillName);
268
+ trackInstall('add', owner, repo, skillName, result.installed.map((i) => i.agent));
269
269
  }
270
270
  }
271
271
  // Summary
@@ -78,7 +78,7 @@ Examples:
78
78
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
79
79
  }
80
80
  // Track command usage (fire and forget)
81
- void trackCommand('bundle');
81
+ trackCommand('bundle');
82
82
  // Determine scope (interactive if no flags specified)
83
83
  const { baseDir, location } = await selectBundleLocation(projectFlag, globalFlag, jsonMode);
84
84
  const manifestPath = getProjectManifestPath(location === 'global');
@@ -161,7 +161,7 @@ Examples:
161
161
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)} ${pc.dim('· Create a skill')}`);
162
162
  }
163
163
  // Track command usage (fire and forget)
164
- void trackCommand('init');
164
+ trackCommand('init');
165
165
  const skipPrompts = options.yes ?? false;
166
166
  const projectFlag = options.project ?? false;
167
167
  const globalFlag = options.global ?? false;
@@ -87,7 +87,7 @@ Examples:
87
87
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
88
88
  }
89
89
  // Track command usage (fire and forget)
90
- void trackCommand('install');
90
+ trackCommand('install');
91
91
  // Determine scope (interactive if no flags specified)
92
92
  const { location, baseDir, manifestPath } = await selectInstallLocation(projectFlag, globalFlag, jsonMode);
93
93
  jsonOutput.manifest_path = manifestPath;
@@ -473,6 +473,7 @@ Examples:
473
473
  skillName,
474
474
  success: false,
475
475
  installCount: 0,
476
+ platform: [],
476
477
  errorMsg: result.failureReason,
477
478
  };
478
479
  }
@@ -493,6 +494,7 @@ Examples:
493
494
  skillName,
494
495
  success: true,
495
496
  installCount: result.installed.length,
497
+ platform: result.installed.map((i) => i.agent),
496
498
  };
497
499
  }
498
500
  catch (err) {
@@ -518,6 +520,7 @@ Examples:
518
520
  skillName,
519
521
  success: false,
520
522
  installCount: 0,
523
+ platform: [],
521
524
  errorMsg,
522
525
  };
523
526
  }
@@ -534,8 +537,8 @@ Examples:
534
537
  const action = toInstall[i];
535
538
  if (result.success) {
536
539
  successCount++;
537
- // Track successful installs (fire and forget)
538
- void trackInstall('install', action.entry.owner, action.entry.repo, result.skillName);
540
+ // Track successful installs (fire and forget — dispatched to detached worker)
541
+ trackInstall('install', action.entry.owner, action.entry.repo, result.skillName, result.platform);
539
542
  if (!jsonMode) {
540
543
  // Show which agents it was installed to if it's a partial install
541
544
  const agentCount = action.targetAgents.length;
@@ -65,7 +65,7 @@ Examples:
65
65
  });
66
66
  }
67
67
  // Track command usage (fire and forget)
68
- void trackCommand('list');
68
+ trackCommand('list');
69
69
  // Determine which locations to check
70
70
  // By default, check both global and project. Flags narrow it down.
71
71
  const checkGlobal = !projectFlag; // Check global unless --project is set
@@ -70,7 +70,7 @@ Examples:
70
70
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
71
71
  }
72
72
  // Track command usage (fire and forget)
73
- void trackCommand('remove');
73
+ trackCommand('remove');
74
74
  const skipConfirm = options.yes ?? false;
75
75
  const removeAll = options.all ?? false;
76
76
  const projectFlag = options.project ?? false;
@@ -71,7 +71,7 @@ Examples:
71
71
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim('Search')}`);
72
72
  }
73
73
  // Track command usage (fire and forget)
74
- void trackCommand('search');
74
+ trackCommand('search');
75
75
  // Show spinner while searching
76
76
  let response;
77
77
  if (!jsonMode) {
@@ -61,7 +61,7 @@ Examples:
61
61
  p.intro(`${pc.bgCyan(pc.black(' skillfish submit '))} ${pc.dim(`v${version}`)}`);
62
62
  }
63
63
  // Track command usage (fire and forget)
64
- void trackCommand('submit');
64
+ trackCommand('submit');
65
65
  const skipConfirm = options.yes ?? false;
66
66
  // Parse repo format - supports owner/repo or full GitHub URL
67
67
  let owner;
@@ -60,7 +60,7 @@ Examples:
60
60
  p.intro(`${pc.bgCyan(pc.black(' skillfish '))} ${pc.dim(`v${version}`)}`);
61
61
  }
62
62
  // Track command usage (fire and forget)
63
- void trackCommand('update');
63
+ trackCommand('update');
64
64
  // Detect agents (check both global and project for updates)
65
65
  const detected = getDetectedAgentsForLocation('both', process.cwd());
66
66
  if (detected.length === 0) {
@@ -80,6 +80,12 @@ export const AGENT_CONFIGS = [
80
80
  homePaths: ['.kiro'],
81
81
  cwdPaths: ['.kiro'],
82
82
  },
83
+ {
84
+ name: 'Kimi CLI',
85
+ dir: '.kimi/skills',
86
+ homePaths: ['.kimi/kimi.json', '.kimi/config.toml', '.kimi'],
87
+ cwdPaths: ['.kimi'],
88
+ },
83
89
  {
84
90
  name: 'Kilo Code',
85
91
  dir: '.kilocode/skills',
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Detached telemetry worker. Reads a JSON payload from stdin, POSTs it to the
3
+ * telemetry endpoint, then exits. Intended to be spawned by `telemetry.ts` so
4
+ * the parent CLI can exit immediately without waiting on the network request.
5
+ *
6
+ * Failures are swallowed silently — telemetry must never surface to the user.
7
+ */
8
+ export {};
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Detached telemetry worker. Reads a JSON payload from stdin, POSTs it to the
3
+ * telemetry endpoint, then exits. Intended to be spawned by `telemetry.ts` so
4
+ * the parent CLI can exit immediately without waiting on the network request.
5
+ *
6
+ * Failures are swallowed silently — telemetry must never surface to the user.
7
+ */
8
+ const TELEMETRY_URL = 'https://mcpmarket.com/api/telemetry';
9
+ const TELEMETRY_TIMEOUT = 5000;
10
+ async function main() {
11
+ try {
12
+ const chunks = [];
13
+ for await (const chunk of process.stdin) {
14
+ chunks.push(chunk);
15
+ }
16
+ const body = Buffer.concat(chunks).toString('utf-8');
17
+ if (!body)
18
+ return;
19
+ const controller = new AbortController();
20
+ const timeoutId = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT);
21
+ try {
22
+ await fetch(TELEMETRY_URL, {
23
+ method: 'POST',
24
+ headers: { 'Content-Type': 'application/json' },
25
+ body,
26
+ signal: controller.signal,
27
+ });
28
+ }
29
+ catch {
30
+ // network/timeout — ignore
31
+ }
32
+ finally {
33
+ clearTimeout(timeoutId);
34
+ }
35
+ }
36
+ catch {
37
+ // stdin/parse error — ignore
38
+ }
39
+ }
40
+ void main().finally(() => process.exit(0));
41
+ export {};
@@ -1,17 +1,29 @@
1
1
  /**
2
- * Track a command execution. Fire and forget.
2
+ * Anonymous CLI usage telemetry.
3
+ *
4
+ * Telemetry is dispatched to a detached child process (`telemetry-worker.js`)
5
+ * that owns the HTTP request and survives the parent's exit. This means:
6
+ * - The CLI returns to the user immediately, never blocked by network I/O.
7
+ * - `process.exit()` in command code does not abort the in-flight POST.
8
+ *
9
+ * Disabled when `DO_NOT_TRACK=1` or `CI=true`. Also disabled when the module
10
+ * is loaded from TypeScript source (dev/test via tsx) since the compiled
11
+ * worker only exists in `dist/`.
12
+ */
13
+ /**
14
+ * Track a command execution. Fire-and-forget; returns immediately.
3
15
  *
4
16
  * @param command The command name (e.g., 'add', 'bundle', 'install')
5
- * @returns Promise that resolves when telemetry is sent (or times out)
6
17
  */
7
- export declare function trackCommand(command: string): Promise<void>;
18
+ export declare function trackCommand(command: string): void;
8
19
  /**
9
- * Track a skill install. Inserts into telemetry_events and increments skill download count.
20
+ * Track a skill install. Fire-and-forget; returns immediately.
21
+ * Inserts into telemetry_events and increments skill download count.
10
22
  *
11
23
  * @param command The command that triggered the install ('add' or 'install')
12
24
  * @param owner GitHub repository owner
13
25
  * @param repo GitHub repository name
14
26
  * @param skillName Name of the skill being installed
15
- * @returns Promise that resolves when telemetry is sent (or times out)
27
+ * @param platform Names of the agents the skill was installed to (e.g. ['Claude Code', 'Cursor'])
16
28
  */
17
- export declare function trackInstall(command: string, owner: string, repo: string, skillName: string): Promise<void>;
29
+ export declare function trackInstall(command: string, owner: string, repo: string, skillName: string, platform?: readonly string[]): void;
package/dist/telemetry.js CHANGED
@@ -1,55 +1,82 @@
1
- const TELEMETRY_URL = 'https://mcpmarket.com/api/telemetry';
2
- /** Timeout for telemetry requests (ms) */
3
- const TELEMETRY_TIMEOUT = 5000;
4
1
  /**
5
- * Send a telemetry payload. Returns a promise that resolves when the request
6
- * completes (or times out). Never rejects.
2
+ * Anonymous CLI usage telemetry.
3
+ *
4
+ * Telemetry is dispatched to a detached child process (`telemetry-worker.js`)
5
+ * that owns the HTTP request and survives the parent's exit. This means:
6
+ * - The CLI returns to the user immediately, never blocked by network I/O.
7
+ * - `process.exit()` in command code does not abort the in-flight POST.
8
+ *
9
+ * Disabled when `DO_NOT_TRACK=1` or `CI=true`. Also disabled when the module
10
+ * is loaded from TypeScript source (dev/test via tsx) since the compiled
11
+ * worker only exists in `dist/`.
12
+ */
13
+ import { spawn } from 'child_process';
14
+ import { fileURLToPath } from 'url';
15
+ /**
16
+ * Treat any non-empty value other than "0"/"false" as "disabled". This matches
17
+ * the de-facto behavior of the consoledonottrack.com convention used by other
18
+ * CLI tools — `DO_NOT_TRACK=true`, `DO_NOT_TRACK=yes`, etc. all disable.
7
19
  */
8
- function sendTelemetry(payload) {
20
+ function isTruthyEnv(value) {
21
+ if (!value)
22
+ return false;
23
+ const normalized = value.trim().toLowerCase();
24
+ return normalized !== '' && normalized !== '0' && normalized !== 'false';
25
+ }
26
+ function isTelemetryDisabled() {
27
+ return isTruthyEnv(process.env.DO_NOT_TRACK) || isTruthyEnv(process.env.CI);
28
+ }
29
+ function dispatch(payload) {
30
+ if (isTelemetryDisabled())
31
+ return;
32
+ // The worker only ships as a compiled .js artifact. When loaded from .ts
33
+ // source (tsx in dev/test), there is no worker to spawn — skip silently.
34
+ if (import.meta.url.endsWith('.ts'))
35
+ return;
9
36
  try {
10
- if (process.env.DO_NOT_TRACK === '1' || process.env.CI === 'true') {
11
- return Promise.resolve();
12
- }
13
- const controller = new AbortController();
14
- const timeoutId = setTimeout(() => controller.abort(), TELEMETRY_TIMEOUT);
15
- return fetch(TELEMETRY_URL, {
16
- method: 'POST',
17
- headers: { 'Content-Type': 'application/json' },
18
- body: JSON.stringify(payload),
19
- signal: controller.signal,
20
- })
21
- .then(() => { })
22
- .catch(() => { })
23
- .finally(() => clearTimeout(timeoutId));
37
+ const workerPath = fileURLToPath(new URL('./telemetry-worker.js', import.meta.url));
38
+ const child = spawn(process.execPath, [workerPath], {
39
+ detached: true,
40
+ stdio: ['pipe', 'ignore', 'ignore'],
41
+ windowsHide: true,
42
+ });
43
+ // Swallow spawn errors (ENOENT, EPERM, etc.) — telemetry must not surface.
44
+ child.on('error', () => { });
45
+ // Detach from the parent's reference count so process.exit() doesn't wait.
46
+ child.unref();
47
+ // Small JSON payloads fit comfortably in the kernel pipe buffer, so this
48
+ // write completes synchronously and the child can read it after the parent
49
+ // exits.
50
+ child.stdin?.end(JSON.stringify(payload));
24
51
  }
25
52
  catch {
26
- return Promise.resolve();
53
+ // ignore
27
54
  }
28
55
  }
29
56
  /**
30
- * Track a command execution. Fire and forget.
57
+ * Track a command execution. Fire-and-forget; returns immediately.
31
58
  *
32
59
  * @param command The command name (e.g., 'add', 'bundle', 'install')
33
- * @returns Promise that resolves when telemetry is sent (or times out)
34
60
  */
35
61
  export function trackCommand(command) {
36
62
  if (!command)
37
- return Promise.resolve();
38
- return sendTelemetry({ event_type: 'command', command });
63
+ return;
64
+ dispatch({ event_type: 'command', command });
39
65
  }
40
66
  /**
41
- * Track a skill install. Inserts into telemetry_events and increments skill download count.
67
+ * Track a skill install. Fire-and-forget; returns immediately.
68
+ * Inserts into telemetry_events and increments skill download count.
42
69
  *
43
70
  * @param command The command that triggered the install ('add' or 'install')
44
71
  * @param owner GitHub repository owner
45
72
  * @param repo GitHub repository name
46
73
  * @param skillName Name of the skill being installed
47
- * @returns Promise that resolves when telemetry is sent (or times out)
74
+ * @param platform Names of the agents the skill was installed to (e.g. ['Claude Code', 'Cursor'])
48
75
  */
49
- export function trackInstall(command, owner, repo, skillName) {
76
+ export function trackInstall(command, owner, repo, skillName, platform = []) {
50
77
  if (!command || !owner || !repo || !skillName)
51
- return Promise.resolve();
52
- return sendTelemetry({
78
+ return;
79
+ dispatch({
53
80
  event_type: 'install',
54
81
  command,
55
82
  skill_key: `${owner}/${repo}`,
@@ -57,5 +84,8 @@ export function trackInstall(command, owner, repo, skillName) {
57
84
  owner,
58
85
  repo,
59
86
  skillName,
87
+ // De-duplicated agent names the skill landed on. Maps to the `platform`
88
+ // column on the telemetry_events table. Empty array if unknown.
89
+ platform: Array.from(new Set(platform)),
60
90
  });
61
91
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillfish",
3
- "version": "1.0.34",
3
+ "version": "1.0.36",
4
4
  "description": "All in one Skill manager for AI coding agents. Install, update, and sync Skills across Claude Code, Cursor, Copilot + more.",
5
5
  "type": "module",
6
6
  "bin": {