Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions src/commands/add.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,12 +385,16 @@ Examples:

// Track successful installs (fire and forget — dispatched to detached worker)
if (result.installed.length > 0) {
// skillPath is 'SKILL.md' for root skills, or a directory like
// 'skills/foo' for sub-skills in a monorepo.
const skillRepoPath = skillPath === SKILL_FILENAME ? undefined : skillPath;
trackInstall(
'add',
owner,
repo,
skillName,
result.installed.map((i) => i.agent),
skillRepoPath,
);
}
}
Expand Down
1 change: 1 addition & 0 deletions src/commands/install.ts
Original file line number Diff line number Diff line change
Expand Up @@ -732,6 +732,7 @@ Examples:
action.entry.repo,
result.skillName,
result.platform,
action.entry.path,
);
if (!jsonMode) {
// Show which agents it was installed to if it's a partial install
Expand Down
21 changes: 16 additions & 5 deletions src/telemetry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,25 +78,36 @@ export function trackCommand(command: string): void {
* @param repo GitHub repository name
* @param skillName Name of the skill being installed
* @param platform Names of the agents the skill was installed to (e.g. ['Claude Code', 'Cursor'])
* @param path Path to the skill within the repo. Undefined for root-level
* skills (whole repo is one skill). For monorepos, this disambiguates
* sibling skills (e.g. 'skills/council' vs 'skills/marketing').
*/
export function trackInstall(
command: string,
owner: string,
repo: string,
skillName: string,
platform: readonly string[] = [],
path?: string,
): void {
if (!command || !owner || !repo || !skillName) return;
// Canonical skill key: 'owner/repo' for root skills, 'owner/repo/path'
// for skills nested in a monorepo. Without the path component, every
// skill in a monorepo collapses to the same key.
const normalizedPath = path?.trim().replace(/^\/+|\/+$/g, '') || undefined;
const skillKey = normalizedPath ? `${owner}/${repo}/${normalizedPath}` : `${owner}/${repo}`;
// platform is a single text column on telemetry_events. We send a
// comma-separated, de-duplicated list so one install stays one row
// (preserving download-count semantics). Agent names contain no commas.
const platformText = Array.from(new Set(platform)).join(', ');
dispatch({
event_type: 'install',
command,
skill_key: `${owner}/${repo}`,
skill_key: skillKey,
// Fields for skill count increment
owner,
repo,
skillName,
// De-duplicated agent names the skill landed on. Maps to the `platform`
// column on the telemetry_events table. Empty array if unknown.
platform: Array.from(new Set(platform)),
skill_name: skillName,
platform: platformText,
});
}