@shnitzel/plugscout 0.3.34 → 0.3.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.
@@ -1,71 +1,74 @@
1
1
  {
2
2
  "registries": {
3
3
  "community-skills-index": {
4
- "lastSuccessfulSyncAt": "2026-06-06T05:54:55.695Z"
4
+ "lastSuccessfulSyncAt": "2026-06-12T06:42:42.707Z"
5
5
  },
6
6
  "public-mcp-directory": {
7
- "lastSuccessfulSyncAt": "2026-06-06T05:55:09.584Z",
7
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:00.765Z",
8
8
  "lastUpdatedSince": "2026-02-25T14:46:42.122Z"
9
9
  },
10
10
  "official-claude-plugins": {
11
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.149Z"
11
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.360Z"
12
12
  },
13
13
  "official-copilot-extensions": {
14
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.372Z"
14
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.631Z"
15
15
  },
16
16
  "openai-skills-curated": {
17
- "lastSuccessfulSyncAt": "2026-06-06T05:55:09.668Z"
17
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:00.846Z"
18
18
  },
19
19
  "github-copilot-plugins-official": {
20
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.398Z"
20
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.639Z"
21
21
  },
22
22
  "github-awesome-copilot-marketplace": {
23
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.409Z"
23
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.652Z"
24
24
  },
25
25
  "anthropic-claude-connectors-scrape": {
26
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.475Z"
26
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.735Z"
27
27
  },
28
28
  "github-curated-industry-skills": {
29
- "lastSuccessfulSyncAt": "2026-06-06T05:54:55.696Z"
29
+ "lastSuccessfulSyncAt": "2026-06-12T06:42:42.708Z"
30
30
  },
31
31
  "anthropic-skills": {
32
- "lastSuccessfulSyncAt": "2026-06-06T05:55:09.695Z"
32
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:00.896Z"
33
33
  },
34
34
  "github-n-skills-marketplace": {
35
- "lastSuccessfulSyncAt": "2026-06-06T05:55:09.747Z"
35
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:00.959Z"
36
36
  },
37
37
  "trailofbits-skills-marketplace": {
38
- "lastSuccessfulSyncAt": "2026-06-06T05:55:09.920Z"
38
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:00.998Z"
39
39
  },
40
40
  "github-mhattingpete-claude-skills": {
41
- "lastSuccessfulSyncAt": "2026-06-06T05:55:09.957Z"
41
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.050Z"
42
42
  },
43
43
  "github-neon-ai-rules": {
44
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.010Z"
44
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.149Z"
45
45
  },
46
46
  "anthropic-claude-plugins-official-github": {
47
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.176Z"
47
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.385Z"
48
48
  },
49
49
  "anthropic-knowledge-work-plugins": {
50
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.217Z"
50
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.439Z"
51
51
  },
52
52
  "anthropic-financial-services-plugins": {
53
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.261Z"
53
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.500Z"
54
54
  },
55
55
  "github-docker-claude-plugins": {
56
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.316Z"
56
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.568Z"
57
57
  },
58
58
  "github-pleaseai-claude-code-plugins": {
59
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.372Z"
59
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.631Z"
60
60
  },
61
61
  "awesome-claude-code": {
62
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.529Z"
62
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:01.971Z"
63
63
  },
64
64
  "curated-cursor-extensions": {
65
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.582Z"
65
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:02.036Z"
66
66
  },
67
67
  "curated-gemini-extensions": {
68
- "lastSuccessfulSyncAt": "2026-06-06T05:55:10.671Z"
68
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:02.110Z"
69
+ },
70
+ "agentskills-il": {
71
+ "lastSuccessfulSyncAt": "2026-06-12T06:43:02.226Z"
69
72
  }
70
73
  }
71
74
  }
@@ -1,6 +1,7 @@
1
1
  import { adaptAwesomeClaudeCodeEntries } from './adapters/awesome-claude-code-v1.js';
2
2
  import { adaptCursorExtensionsEntries } from './adapters/cursor-extensions-v1.js';
3
3
  import { adaptGeminiExtensionsEntries } from './adapters/gemini-extensions-v1.js';
4
+ import { adaptAgentskillsIlEntries } from './adapters/agentskills-il-v1.js';
4
5
  import { adaptClaudeConnectorsScrapeEntries } from './adapters/claude-connectors-scrape-v1.js';
5
6
  import { adaptClaudeCodeMarketplaceEntries } from './adapters/claude-code-marketplace-v1.js';
6
7
  import { adaptClaudePluginsEntries } from './adapters/claude-plugins-v0.1.js';
@@ -47,5 +48,8 @@ export function adaptRegistryEntries(registry, entries) {
47
48
  if (registry.adapter === 'gemini-extensions-v1') {
48
49
  return adaptGeminiExtensionsEntries(registry.id, entries);
49
50
  }
51
+ if (registry.adapter === 'agentskills-il-v1') {
52
+ return adaptAgentskillsIlEntries(registry.id, entries);
53
+ }
50
54
  return entries;
51
55
  }
@@ -0,0 +1,180 @@
1
+ import { dedupe, readString } from './shared.js';
2
+ export function adaptAgentskillsIlEntries(sourceId, entries) {
3
+ return entries
4
+ .map((entry) => mapEntry(sourceId, entry))
5
+ .filter((entry) => entry !== null);
6
+ }
7
+ function mapEntry(sourceId, entry) {
8
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
9
+ return null;
10
+ }
11
+ const record = entry;
12
+ const source = readString(record, ['_source']);
13
+ if (source === 'mcp') {
14
+ return mapMcpEntry(sourceId, record);
15
+ }
16
+ return mapSkillEntry(sourceId, record);
17
+ }
18
+ function mapSkillEntry(sourceId, record) {
19
+ const slug = readString(record, ['_slug']);
20
+ if (!slug)
21
+ return null;
22
+ const displayName = resolveLocalized(record['display_name'], 'en') ?? slug;
23
+ const displayDescription = resolveLocalized(record['display_description'], 'en') ?? `AgentSkills IL skill: ${slug}`;
24
+ const tagsEn = extractStringArray(resolveLocalized(record['tags'], 'en'));
25
+ const tagsHe = extractStringArray(resolveLocalized(record['tags'], 'he'));
26
+ const category = readString(record, ['category']) ?? readString(record, ['_repo']) ?? 'general';
27
+ const repoUrl = readString(record, ['_repoUrl']);
28
+ const supportedAgents = extractStringArray(record['supported_agents']);
29
+ const compatibility = dedupe(['general', ...agentsToCompatibility(supportedAgents)]);
30
+ const capabilities = dedupe([...tagsEn, categoryToCapability(category)].filter(Boolean));
31
+ return {
32
+ id: `skill:agentskills-il/${slug}`,
33
+ kind: 'skill',
34
+ provider: 'skills-il',
35
+ name: displayName,
36
+ description: displayDescription,
37
+ capabilities,
38
+ compatibility,
39
+ source: sourceId,
40
+ install: {
41
+ kind: 'manual',
42
+ instructions: `npx skills-il ${slug}`,
43
+ url: repoUrl ?? `https://agentskills.co.il/skills/${slug}`
44
+ },
45
+ adoptionSignal: 55,
46
+ maintenanceSignal: 72,
47
+ provenanceSignal: 85,
48
+ freshnessSignal: 75,
49
+ securitySignals: {
50
+ knownVulnerabilities: 0,
51
+ suspiciousPatterns: 0,
52
+ injectionFindings: 0,
53
+ exfiltrationSignals: 0,
54
+ integrityAlerts: 0
55
+ },
56
+ metadata: {
57
+ repositoryUrl: repoUrl,
58
+ category,
59
+ tagsHe,
60
+ descriptionHe: resolveLocalized(record['display_description'], 'he'),
61
+ version: readString(record, ['version']),
62
+ sourceType: 'vendor-feed',
63
+ sourceConfidence: 'official'
64
+ }
65
+ };
66
+ }
67
+ function mapMcpEntry(sourceId, record) {
68
+ const slug = readString(record, ['_slug']);
69
+ const pkgName = readString(record, ['name']);
70
+ if (!slug && !pkgName)
71
+ return null;
72
+ const id = slug ?? pkgName ?? '';
73
+ const name = toTitle(slug ?? pkgName?.replace(/^@[^/]+\//, '') ?? id);
74
+ const description = readString(record, ['description']) ?? `AgentSkills IL MCP server: ${id}`;
75
+ const keywords = extractStringArray(record['keywords']);
76
+ const repoUrl = readString(record, ['_repoUrl']);
77
+ const capabilities = dedupe(keywords.length > 0 ? keywords : ['israel']);
78
+ return {
79
+ id: `mcp:agentskills-il/${slug ?? id}`,
80
+ kind: 'mcp',
81
+ provider: 'skills-il',
82
+ name,
83
+ description,
84
+ transport: 'stdio',
85
+ authModel: 'none',
86
+ capabilities,
87
+ compatibility: ['general', 'node'],
88
+ source: sourceId,
89
+ install: {
90
+ kind: 'skill.sh',
91
+ target: pkgName ?? `@skills-il/${slug}`,
92
+ args: []
93
+ },
94
+ adoptionSignal: 50,
95
+ maintenanceSignal: 70,
96
+ provenanceSignal: 85,
97
+ freshnessSignal: 75,
98
+ securitySignals: {
99
+ knownVulnerabilities: 0,
100
+ suspiciousPatterns: 0,
101
+ injectionFindings: 0,
102
+ exfiltrationSignals: 0,
103
+ integrityAlerts: 0
104
+ },
105
+ metadata: {
106
+ repositoryUrl: repoUrl,
107
+ packageIdentifier: pkgName,
108
+ packageRegistryType: 'npm',
109
+ packageRuntime: 'node',
110
+ version: readString(record, ['version']),
111
+ sourceType: 'vendor-feed',
112
+ sourceConfidence: 'official'
113
+ }
114
+ };
115
+ }
116
+ function resolveLocalized(value, lang) {
117
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
118
+ if (typeof value === 'string')
119
+ return value;
120
+ if (Array.isArray(value))
121
+ return value;
122
+ return undefined;
123
+ }
124
+ const rec = value;
125
+ const v = rec[lang];
126
+ if (typeof v === 'string')
127
+ return v;
128
+ if (Array.isArray(v))
129
+ return v;
130
+ return undefined;
131
+ }
132
+ function extractStringArray(value) {
133
+ if (Array.isArray(value)) {
134
+ return value.filter((v) => typeof v === 'string' && v.trim().length > 0);
135
+ }
136
+ if (typeof value === 'string' && value.trim().length > 0) {
137
+ return [value];
138
+ }
139
+ return [];
140
+ }
141
+ function agentsToCompatibility(agents) {
142
+ const map = {
143
+ 'claude-code': 'claude',
144
+ cursor: 'cursor',
145
+ 'github-copilot': 'github',
146
+ windsurf: 'windsurf',
147
+ 'gemini-cli': 'gemini',
148
+ codex: 'openai',
149
+ opencode: 'general',
150
+ antigravity: 'general',
151
+ };
152
+ return agents.map((a) => map[a]).filter((v) => Boolean(v));
153
+ }
154
+ function categoryToCapability(category) {
155
+ const map = {
156
+ 'tax-and-finance': 'finance',
157
+ localization: 'localization',
158
+ 'government-services': 'government',
159
+ 'security-compliance': 'security',
160
+ communication: 'communication',
161
+ 'developer-tools': 'automation',
162
+ 'food-and-dining': 'food',
163
+ 'health-services': 'health',
164
+ 'marketing-growth': 'marketing',
165
+ 'legal-tech': 'legal',
166
+ education: 'education',
167
+ accounting: 'finance',
168
+ 'design-systems': 'design',
169
+ courses: 'education',
170
+ };
171
+ return map[category] ?? category;
172
+ }
173
+ function toTitle(slug) {
174
+ return slug
175
+ .replace(/^@[^/]+\//, '')
176
+ .split(/[-_]/g)
177
+ .filter(Boolean)
178
+ .map((part) => part[0].toUpperCase() + part.slice(1))
179
+ .join(' ');
180
+ }
@@ -1,9 +1,26 @@
1
1
  import { dedupe, extractStringArray, readNestedString, readString } from './shared.js';
2
2
  export function adaptMcpRegistryEntries(sourceId, entries) {
3
3
  return entries
4
+ .filter((entry) => isMcpEntryLatest(entry))
4
5
  .map((entry) => mapMcpRegistryEntry(sourceId, entry))
5
6
  .filter((entry) => entry !== null);
6
7
  }
8
+ function isMcpEntryLatest(entry) {
9
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
10
+ return true;
11
+ }
12
+ const record = entry;
13
+ const meta = record._meta;
14
+ if (!meta || typeof meta !== 'object' || Array.isArray(meta)) {
15
+ return true;
16
+ }
17
+ const official = meta['io.modelcontextprotocol.registry/official'];
18
+ if (!official || typeof official !== 'object' || Array.isArray(official)) {
19
+ return true;
20
+ }
21
+ const isLatest = official.isLatest;
22
+ return isLatest !== false;
23
+ }
7
24
  function mapMcpRegistryEntry(sourceId, entry) {
8
25
  if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
9
26
  return null;
@@ -21,7 +21,7 @@ export async function resolveRegistryEntries(registry, options = {}, fetchImpl =
21
21
  }
22
22
  }
23
23
  try {
24
- const parsed = await fetchRemoteRegistryEntries(registry, options, fetchImpl);
24
+ const parsed = await fetchRemoteRegistryEntries(registry, options, fetchImpl, options.onProgress);
25
25
  if (parsed.length === 0 && registry.entries.length > 0) {
26
26
  const level = options.updatedSince ? 'info' : 'warn';
27
27
  const reason = options.updatedSince ? 'returned no updates' : 'returned no entries';
@@ -38,14 +38,16 @@ export async function resolveRegistryEntries(registry, options = {}, fetchImpl =
38
38
  throw error;
39
39
  }
40
40
  }
41
- export async function fetchRemoteRegistryEntries(registry, options = {}, fetchImpl = fetch) {
41
+ export async function fetchRemoteRegistryEntries(registry, options = {}, fetchImpl = fetch, onProgress) {
42
42
  if (!registry.remote) {
43
43
  throw new Error(`Registry ${registry.id} has no remote definition`);
44
44
  }
45
45
  validateRemoteHost(registry);
46
46
  const allEntries = [];
47
47
  let cursor;
48
+ let page = 0;
48
49
  do {
50
+ page++;
49
51
  const payload = await fetchRemoteRegistryPayload(registry, fetchImpl, {
50
52
  cursor,
51
53
  updatedSince: options.updatedSince
@@ -53,6 +55,9 @@ export async function fetchRemoteRegistryEntries(registry, options = {}, fetchIm
53
55
  const parsed = extractEntries(payload, registry.remote.format, registry.remote.entryPath, registry.kind);
54
56
  allEntries.push(...parsed);
55
57
  cursor = resolveNextCursor(payload, registry.remote.pagination?.nextCursorPath);
58
+ if (cursor && onProgress) {
59
+ onProgress(` ↓ ${registry.id} page ${page + 1}… (${allEntries.length} so far)`);
60
+ }
56
61
  } while (cursor && registry.remote.pagination);
57
62
  return allEntries;
58
63
  }
@@ -50,7 +50,7 @@ export async function syncCatalogs(today = new Date().toISOString().slice(0, 10)
50
50
  }
51
51
  progress(` ↓ ${registry.id} (${registry.kind})…`);
52
52
  const updatedSince = registry.remote?.supportsUpdatedSince ? getUpdatedSince(syncState, registry.id) : undefined;
53
- const resolved = await resolveRegistryEntries(registry, { updatedSince });
53
+ const resolved = await resolveRegistryEntries(registry, { updatedSince, onProgress: progress });
54
54
  const adaptedEntries = resolved.source === 'remote' ? adaptRegistryEntries(registry, resolved.entries) : resolved.entries;
55
55
  // Always include curated local entries so they persist even when remote succeeds
56
56
  const curatedEntries = resolved.source === 'remote' ? registry.entries : [];
@@ -139,7 +139,8 @@ export const RegistrySchema = z.object({
139
139
  'claude-connectors-scrape-v1',
140
140
  'awesome-claude-code-v1',
141
141
  'cursor-extensions-v1',
142
- 'gemini-extensions-v1'
142
+ 'gemini-extensions-v1',
143
+ 'agentskills-il-v1'
143
144
  ])
144
145
  .default('direct'),
145
146
  enabled: z.boolean().default(true),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shnitzel/plugscout",
3
- "version": "0.3.34",
3
+ "version": "0.3.36",
4
4
  "description": "Claude plugins + Claude connectors + Copilot extensions + Skills + MCP security intelligence framework",
5
5
  "private": false,
6
6
  "type": "module",
@@ -96,7 +96,7 @@
96
96
  "devDependencies": {
97
97
  "@types/node": "^25.5.0",
98
98
  "@types/react": "^19.2.14",
99
- "@remotion/cli": "4.0.470",
99
+ "@remotion/cli": "4.0.473",
100
100
  "@types/semver": "^7.7.1",
101
101
  "@typescript-eslint/eslint-plugin": "^7.10.0",
102
102
  "@typescript-eslint/parser": "^7.10.0",
@@ -105,10 +105,10 @@
105
105
  "prettier": "^3.2.5",
106
106
  "react": "^19.2.4",
107
107
  "react-dom": "^19.2.4",
108
- "remotion": "4.0.470",
108
+ "remotion": "4.0.473",
109
109
  "ts-node": "^10.9.2",
110
110
  "tsx": "^4.7.1",
111
111
  "typescript": "^5.4.5",
112
- "vitest": "4.1.0"
112
+ "vitest": "4.1.8"
113
113
  }
114
114
  }