servicemap 0.1.7 → 0.1.8

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
@@ -13,7 +13,7 @@ npx servicemap
13
13
  ```
14
14
 
15
15
  That opens the interactive install survey.
16
- It lets users choose compatible AI coding agents and model environments.
16
+ It installs to the universal `.agents/skills` folder and lets users choose additional agent-specific skill folders.
17
17
 
18
18
  To install the bundled AI skill so `/servicemap` appears in compatible coding agents:
19
19
 
@@ -41,7 +41,6 @@ npx servicemap skills install --agent claude
41
41
  ```
42
42
 
43
43
  Agents with known local skill folders get the real `servicemap` skill installed.
44
- Other selected AI tools get setup notes under `~/.servicemap/model-guides`.
45
44
 
46
45
  After a global install, the command is:
47
46
 
@@ -72,7 +71,7 @@ servicemap install-skill --agent codex
72
71
  servicemap install-skill --agent claude
73
72
  servicemap install-skill --agent grok
74
73
  servicemap install-skill --agent all
75
- servicemap install-skill --targets codex,gemini,cursor
74
+ servicemap install-skill --targets universal,claude,grok
76
75
  servicemap skills install --agent codex
77
76
  ```
78
77
 
@@ -92,7 +91,7 @@ node bin/servicemap.mjs generate --root demo-project --allow-emails founder@exam
92
91
  node bin/servicemap.mjs --root no-website-demo --dry-run
93
92
  node bin/servicemap.mjs --dry-run
94
93
  node bin/servicemap.mjs install-skill --agent all --dry-run
95
- node bin/servicemap.mjs install-skill --targets codex,gemini,cursor --dry-run
94
+ node bin/servicemap.mjs install-skill --targets universal,claude,grok --dry-run
96
95
  node bin/servicemap.mjs skills install --agent claude --dry-run
97
96
  python3 /Users/dougie/.codex/skills/.system/skill-creator/scripts/quick_validate.py servicemap
98
97
  ```
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { spawn } from 'node:child_process';
3
3
  import { existsSync } from 'node:fs';
4
- import { cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
4
+ import { cp, mkdir, readFile, rm } from 'node:fs/promises';
5
5
  import { dirname, join, resolve } from 'node:path';
6
6
  import { homedir } from 'node:os';
7
7
  import readline from 'node:readline';
@@ -12,67 +12,63 @@ const scanScript = resolve(packageRoot, 'servicemap/scripts/scan-project.mjs');
12
12
  const pageScript = resolve(packageRoot, 'servicemap/scripts/generate-page.mjs');
13
13
  const desktopScript = resolve(packageRoot, 'servicemap/scripts/generate-desktop-app.mjs');
14
14
 
15
+ const universalRoot = () => resolve(homedir(), '.agents/skills');
16
+
17
+ const universalAgents = [
18
+ 'Amp',
19
+ 'Cline',
20
+ 'Codex',
21
+ 'Cursor',
22
+ 'Gemini CLI',
23
+ 'GitHub Copilot',
24
+ 'OpenCode',
25
+ 'Warp',
26
+ 'Zed',
27
+ ];
28
+
15
29
  const targetCatalog = [
16
- { id: 'codex', label: 'OpenAI (Codex, ChatGPT, GPT models)', kind: 'skill', root: () => resolve(homedir(), '.agents/skills') },
17
- { id: 'claude', label: 'Anthropic (Claude Code, Claude models)', kind: 'skill', root: () => resolve(homedir(), '.claude/skills') },
18
- { id: 'grok', label: 'xAI (Grok)', kind: 'skill', root: () => resolve(homedir(), '.grok/skills') },
19
- { id: 'gemini', label: 'Google (Gemini, Vertex AI)', kind: 'guide' },
20
- { id: 'cursor', label: 'Cursor', kind: 'guide' },
21
- { id: 'windsurf', label: 'Windsurf', kind: 'guide' },
22
- { id: 'copilot', label: 'GitHub Copilot', kind: 'guide' },
23
- { id: 'vscode-chat', label: 'VS Code Chat', kind: 'guide' },
24
- { id: 'continue', label: 'Continue.dev', kind: 'guide' },
25
- { id: 'aider', label: 'Aider', kind: 'guide' },
26
- { id: 'cline', label: 'Cline', kind: 'guide' },
27
- { id: 'roo-code', label: 'Roo Code', kind: 'guide' },
28
- { id: 'zed-ai', label: 'Zed AI', kind: 'guide' },
29
- { id: 'replit-agent', label: 'Replit Agent', kind: 'guide' },
30
- { id: 'lovable', label: 'Lovable', kind: 'guide' },
31
- { id: 'bolt', label: 'Bolt', kind: 'guide' },
32
- { id: 'v0', label: 'v0', kind: 'guide' },
33
- { id: 'perplexity', label: 'Perplexity', kind: 'guide' },
34
- { id: 'mistral', label: 'Mistral', kind: 'guide' },
35
- { id: 'deepseek', label: 'DeepSeek', kind: 'guide' },
36
- { id: 'qwen', label: 'Qwen', kind: 'guide' },
37
- { id: 'meta', label: 'Meta (Llama)', kind: 'guide' },
38
- { id: 'local-models', label: 'Local models (Ollama, LM Studio)', kind: 'guide' },
39
- { id: 'openrouter', label: 'OpenRouter', kind: 'guide' },
40
- { id: 'bedrock', label: 'Amazon Bedrock', kind: 'guide' },
41
- { id: 'azure-openai', label: 'Azure OpenAI', kind: 'guide' },
42
- { id: 'poe', label: 'Poe', kind: 'guide' },
43
- { id: 'you-com', label: 'You.com', kind: 'guide' },
44
- { id: 'phind', label: 'Phind', kind: 'guide' },
45
- { id: 'sourcegraph-cody', label: 'Sourcegraph Cody', kind: 'guide' },
46
- { id: 'tabnine', label: 'Tabnine', kind: 'guide' },
47
- { id: 'codeium', label: 'Codeium', kind: 'guide' },
48
- { id: 'jetbrains-ai', label: 'JetBrains AI', kind: 'guide' },
49
- { id: 'custom', label: 'Other custom model or agent', kind: 'guide' },
30
+ { id: 'claude', label: 'Claude Code', root: () => resolve(homedir(), '.claude/skills') },
31
+ { id: 'grok', label: 'Grok', root: () => resolve(homedir(), '.grok/skills') },
32
+ { id: 'aiderdesk', label: 'AiderDesk', root: () => resolve(homedir(), '.aider-desk/skills') },
33
+ { id: 'astrbot', label: 'AstrBot', root: () => resolve(homedir(), '.astrbot/data/skills') },
34
+ { id: 'autohand', label: 'Autohand Code CLI', root: () => resolve(homedir(), '.autohand/skills') },
35
+ { id: 'augment', label: 'Augment', root: () => resolve(homedir(), '.augment/skills') },
36
+ { id: 'ibm-bob', label: 'IBM Bob', root: () => resolve(homedir(), '.bob/skills') },
37
+ { id: 'openclaw', label: 'OpenClaw', root: () => resolve(homedir(), '.openclaw/skills') },
38
+ { id: 'codearts', label: 'CodeArts Agent', root: () => resolve(homedir(), '.codeartsdoer/skills') },
39
+ { id: 'roo-code', label: 'Roo Code', root: () => resolve(homedir(), '.roo/skills') },
40
+ { id: 'continue', label: 'Continue', root: () => resolve(homedir(), '.continue/skills') },
41
+ { id: 'windsurf', label: 'Windsurf', root: () => resolve(homedir(), '.windsurf/skills') },
42
+ { id: 'replit-agent', label: 'Replit Agent', root: () => resolve(homedir(), '.replit/skills') },
43
+ { id: 'jetbrains-ai', label: 'JetBrains AI', root: () => resolve(homedir(), '.jetbrains-ai/skills') },
50
44
  ];
51
45
 
52
46
  const targetAliases = new Map([
53
- ['agents', 'codex'],
54
- ['chatgpt', 'codex'],
55
- ['openai', 'codex'],
56
- ['openai-api', 'codex'],
57
- ['gpt-6-astra', 'codex'],
58
- ['gpt-6-sol', 'codex'],
59
- ['gpt-6-luna', 'codex'],
60
- ['gpt-5.6-sol', 'codex'],
61
- ['gpt-5.6-terra', 'codex'],
62
- ['gpt-5.6-luna', 'codex'],
47
+ ['codex', 'universal'],
48
+ ['agents', 'universal'],
49
+ ['universal', 'universal'],
50
+ ['cursor', 'universal'],
51
+ ['cline', 'universal'],
52
+ ['opencode', 'universal'],
53
+ ['zed', 'universal'],
54
+ ['github-copilot', 'universal'],
55
+ ['copilot', 'universal'],
56
+ ['gemini-cli', 'universal'],
57
+ ['chatgpt', 'universal'],
58
+ ['openai', 'universal'],
59
+ ['openai-api', 'universal'],
60
+ ['gpt-6-astra', 'universal'],
61
+ ['gpt-6-sol', 'universal'],
62
+ ['gpt-6-luna', 'universal'],
63
+ ['gpt-5.6-sol', 'universal'],
64
+ ['gpt-5.6-terra', 'universal'],
65
+ ['gpt-5.6-luna', 'universal'],
63
66
  ['anthropic', 'claude'],
64
67
  ['anthropic-api', 'claude'],
65
68
  ['claude-opus', 'claude'],
66
69
  ['claude-sonnet', 'claude'],
67
70
  ['claude-haiku', 'claude'],
68
- ['google', 'gemini'],
69
- ['gemini-pro', 'gemini'],
70
- ['gemini-flash', 'gemini'],
71
- ['gemini-api', 'gemini'],
72
- ['vertex-ai', 'gemini'],
73
- ['llama', 'meta'],
74
- ['ollama', 'local-models'],
75
- ['lm-studio', 'local-models'],
71
+ ['xai', 'grok'],
76
72
  ]);
77
73
 
78
74
  function parseArgs() {
@@ -178,34 +174,48 @@ async function readScan(root) {
178
174
 
179
175
  function targetsByIds(ids) {
180
176
  const byId = new Map(targetCatalog.map((target) => [target.id, target]));
181
- return ids.map((id) => {
182
- const target = byId.get(targetAliases.get(id) ?? id);
177
+ const targets = [];
178
+ let includeUniversal = false;
179
+
180
+ for (const id of ids) {
181
+ const resolvedId = targetAliases.get(id) ?? id;
182
+ if (resolvedId === 'universal') {
183
+ includeUniversal = true;
184
+ continue;
185
+ }
186
+ const target = byId.get(resolvedId);
183
187
  if (!target) {
184
188
  throw new Error(`Unknown target "${id}". Use --survey to choose from the list.`);
185
189
  }
186
- return target;
187
- });
190
+ targets.push(target);
191
+ }
192
+
193
+ if (includeUniversal) {
194
+ targets.unshift({ id: 'universal', label: 'Universal', root: universalRoot });
195
+ }
196
+
197
+ return targets;
188
198
  }
189
199
 
190
200
  function targetsForOptions(options) {
191
201
  if (options.targets?.length) return targetsByIds(options.targets);
192
- if (options.agent === 'all') return targetCatalog.filter((target) => target.kind === 'skill');
202
+ if (options.agent === 'all') return [{ id: 'universal', label: 'Universal', root: universalRoot }, ...targetCatalog];
193
203
  return targetsByIds([options.agent]);
194
204
  }
195
205
 
196
206
  function skillRootsForTargets(options, targets) {
197
- const skillTargets = targets.filter((target) => target.kind === 'skill');
198
- if (options.skillsDir && skillTargets.length) return [options.skillsDir];
199
- return [...new Set(skillTargets.map((target) => target.root()))];
200
- }
201
-
202
- function guideTargets(targets) {
203
- return targets.filter((target) => target.kind === 'guide');
207
+ if (options.skillsDir && targets.length) return [options.skillsDir];
208
+ return [...new Set(targets.map((target) => target.root()))];
204
209
  }
205
210
 
206
211
  function renderSurvey(selected, cursor) {
207
212
  console.clear();
208
- console.log('Where should Servicemap be available?');
213
+ console.log('Which agents do you want to install Servicemap to?\n');
214
+ console.log('— Universal (.agents/skills) — always included —');
215
+ for (const agent of universalAgents) {
216
+ console.log(` • ${agent}`);
217
+ }
218
+ console.log('\n— Additional agents —');
209
219
  console.log('Use ↑/↓, Space to select, Enter to install.\n');
210
220
 
211
221
  targetCatalog.forEach((target, index) => {
@@ -256,59 +266,18 @@ function runTargetSurvey() {
256
266
  });
257
267
  }
258
268
 
259
- function guideText(target) {
260
- return `# Servicemap for ${target.label}
261
-
262
- These setup notes help ${target.label} use Servicemap when you want it to create a service map for a software project.
263
-
264
- Invoke: /servicemap
265
-
266
- Goal:
267
- Scan the current project for third-party services, resolve service logos from the bundled Servicemap database, and create an /internalservicemap page when a website is present.
268
- If no website can be safely found, create a desktop app fallback named servicemap-desktop.
269
-
270
- Recommended local command:
271
- \`\`\`bash
272
- npx servicemap --root <project-path>
273
- \`\`\`
274
-
275
- If your agent supports local skills, install the real skill folder with:
276
- \`\`\`bash
277
- npx servicemap install-skill --agent all
278
- \`\`\`
279
- `;
280
- }
281
-
282
- async function writeGuidePacks(targets, options) {
283
- const guideRoot = resolve(homedir(), '.servicemap/model-guides');
284
- for (const target of guideTargets(targets)) {
285
- const destination = join(guideRoot, target.id);
286
- if (options.dryRun) {
287
- console.log(`Would write Servicemap guide for ${target.label} to ${destination}`);
288
- continue;
289
- }
290
- await mkdir(destination, { recursive: true });
291
- await cp(resolve(packageRoot, 'servicemap/SKILL.md'), join(destination, 'SKILL.md'));
292
- await mkdir(join(destination, 'servicemap'), { recursive: true });
293
- await cp(resolve(packageRoot, 'servicemap'), join(destination, 'servicemap'), { recursive: true });
294
- await writeFile(join(destination, 'README.md'), guideText(target));
295
- console.log(`Wrote Servicemap guide for ${target.label} to ${destination}`);
296
- }
297
- }
298
-
299
269
  async function installSkill(options) {
300
270
  const source = resolve(packageRoot, 'servicemap');
301
271
  const selectedTargets = options.survey && !options.yes
302
- ? await runTargetSurvey()
272
+ ? [{ id: 'universal', label: 'Universal', root: universalRoot }, ...await runTargetSurvey()]
303
273
  : targetsForOptions(options);
304
274
  const roots = skillRootsForTargets(options, selectedTargets);
305
- const notes = guideTargets(selectedTargets);
306
275
 
307
276
  if (!existsSync(source)) {
308
277
  throw new Error(`Bundled skill folder not found: ${source}`);
309
278
  }
310
279
 
311
- if (roots.length === 0 && notes.length === 0) {
280
+ if (roots.length === 0) {
312
281
  console.log('No targets selected. Nothing was installed.');
313
282
  return;
314
283
  }
@@ -317,7 +286,6 @@ async function installSkill(options) {
317
286
  for (const root of roots) {
318
287
  console.log(`Would install Servicemap skill to ${join(root, 'servicemap')}`);
319
288
  }
320
- await writeGuidePacks(selectedTargets, options);
321
289
  return;
322
290
  }
323
291
 
@@ -329,11 +297,7 @@ async function installSkill(options) {
329
297
  console.log(`Installed Servicemap skill to ${destination}`);
330
298
  }
331
299
 
332
- await writeGuidePacks(selectedTargets, options);
333
-
334
- if (roots.length > 0) {
335
- console.log('Restart your AI app if /servicemap does not appear immediately.');
336
- }
300
+ console.log('Restart your AI app if /servicemap does not appear immediately.');
337
301
  }
338
302
 
339
303
  async function main() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "servicemap",
3
- "version": "0.1.7",
3
+ "version": "0.1.8",
4
4
  "description": "Generate an internal service map page or desktop app for a software project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -12,7 +12,7 @@
12
12
  "README.md"
13
13
  ],
14
14
  "scripts": {
15
- "test": "node bin/servicemap.mjs generate --root demo-project --allow-emails founder@example.com,ops@example.com --dry-run && node bin/servicemap.mjs --root no-website-demo --dry-run && node bin/servicemap.mjs --dry-run && HOME=\"$(mktemp -d)\" node bin/servicemap.mjs && node bin/servicemap.mjs install-skill --agent all --dry-run && node bin/servicemap.mjs install-skill --targets codex,gemini,cursor --dry-run && node bin/servicemap.mjs install-skill --targets chatgpt,gpt-6-astra,gemini-flash --dry-run && node bin/servicemap.mjs skills install --agent claude --dry-run",
15
+ "test": "node bin/servicemap.mjs generate --root demo-project --allow-emails founder@example.com,ops@example.com --dry-run && node bin/servicemap.mjs --root no-website-demo --dry-run && node bin/servicemap.mjs --dry-run && HOME=\"$(mktemp -d)\" node bin/servicemap.mjs && node bin/servicemap.mjs install-skill --agent all --dry-run && node bin/servicemap.mjs install-skill --targets universal,claude,grok --dry-run && node bin/servicemap.mjs install-skill --targets codex,chatgpt,gpt-6-astra --dry-run && node bin/servicemap.mjs skills install --agent claude --dry-run",
16
16
  "validate:skill": "python3 /Users/dougie/.codex/skills/.system/skill-creator/scripts/quick_validate.py servicemap"
17
17
  },
18
18
  "keywords": [