skillfish 1.0.35 → 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 +22 -2
- package/dist/commands/add.js +3 -3
- package/dist/commands/bundle.js +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/install.js +6 -3
- package/dist/commands/list.js +1 -1
- package/dist/commands/remove.js +1 -1
- package/dist/commands/search.js +1 -1
- package/dist/commands/submit.js +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/telemetry-worker.d.ts +8 -0
- package/dist/telemetry-worker.js +41 -0
- package/dist/telemetry.d.ts +18 -6
- package/dist/telemetry.js +60 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -401,9 +401,29 @@ See [CHANGELOG.md](CHANGELOG.md) for release history.
|
|
|
401
401
|
<details>
|
|
402
402
|
<summary>Telemetry</summary>
|
|
403
403
|
|
|
404
|
-
Anonymous, aggregate
|
|
404
|
+
Anonymous, aggregate usage data — no PII, no identifiers, no IP fingerprinting on our side.
|
|
405
405
|
|
|
406
|
-
|
|
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.
|
|
407
427
|
|
|
408
428
|
</details>
|
|
409
429
|
|
package/dist/commands/add.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
268
|
+
trackInstall('add', owner, repo, skillName, result.installed.map((i) => i.agent));
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
// Summary
|
package/dist/commands/bundle.js
CHANGED
|
@@ -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
|
-
|
|
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');
|
package/dist/commands/init.js
CHANGED
|
@@ -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
|
-
|
|
164
|
+
trackCommand('init');
|
|
165
165
|
const skipPrompts = options.yes ?? false;
|
|
166
166
|
const projectFlag = options.project ?? false;
|
|
167
167
|
const globalFlag = options.global ?? false;
|
package/dist/commands/install.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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;
|
package/dist/commands/list.js
CHANGED
|
@@ -65,7 +65,7 @@ Examples:
|
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
// Track command usage (fire and forget)
|
|
68
|
-
|
|
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
|
package/dist/commands/remove.js
CHANGED
|
@@ -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
|
-
|
|
73
|
+
trackCommand('remove');
|
|
74
74
|
const skipConfirm = options.yes ?? false;
|
|
75
75
|
const removeAll = options.all ?? false;
|
|
76
76
|
const projectFlag = options.project ?? false;
|
package/dist/commands/search.js
CHANGED
|
@@ -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
|
-
|
|
74
|
+
trackCommand('search');
|
|
75
75
|
// Show spinner while searching
|
|
76
76
|
let response;
|
|
77
77
|
if (!jsonMode) {
|
package/dist/commands/submit.js
CHANGED
|
@@ -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
|
-
|
|
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;
|
package/dist/commands/update.js
CHANGED
|
@@ -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
|
-
|
|
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) {
|
|
@@ -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 {};
|
package/dist/telemetry.d.ts
CHANGED
|
@@ -1,17 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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):
|
|
18
|
+
export declare function trackCommand(command: string): void;
|
|
8
19
|
/**
|
|
9
|
-
* Track a skill install.
|
|
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
|
-
* @
|
|
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):
|
|
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
|
-
*
|
|
6
|
-
*
|
|
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
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
53
|
+
// ignore
|
|
27
54
|
}
|
|
28
55
|
}
|
|
29
56
|
/**
|
|
30
|
-
* Track a command execution. Fire
|
|
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
|
|
38
|
-
|
|
63
|
+
return;
|
|
64
|
+
dispatch({ event_type: 'command', command });
|
|
39
65
|
}
|
|
40
66
|
/**
|
|
41
|
-
* Track a skill install.
|
|
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
|
-
* @
|
|
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
|
|
52
|
-
|
|
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