skills-atlas-cli 0.12.0 → 0.14.1
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/bin/skills.js +6 -2
- package/package.json +1 -1
- package/src/args.js +2 -0
- package/src/commands/setup.js +98 -0
- package/src/commands/suggest.js +29 -12
package/bin/skills.js
CHANGED
|
@@ -18,6 +18,7 @@ const gaps = require('../src/commands/gaps');
|
|
|
18
18
|
const gapAnalyze = require('../src/commands/gap-analyze');
|
|
19
19
|
const craft = require('../src/commands/craft');
|
|
20
20
|
const prune = require('../src/commands/prune');
|
|
21
|
+
const setup = require('../src/commands/setup');
|
|
21
22
|
const feedback = require('../src/commands/feedback');
|
|
22
23
|
const update = require('../src/commands/update');
|
|
23
24
|
const mcp = require('../src/commands/mcp');
|
|
@@ -26,7 +27,7 @@ const { categories, list } = require('../src/commands/categories');
|
|
|
26
27
|
const VERSION = require('../package.json').version;
|
|
27
28
|
// `use` = install + activate inline (emit the SKILL.md so an agent follows it now).
|
|
28
29
|
const use = argv => install([...argv, '--inline']);
|
|
29
|
-
const commands = { search, info, install, use, kit, sync, installed, upgrade, remove, outdated, doctor, suggest, hook, gaps, 'gap-analyze': gapAnalyze, craft, prune, feedback, update, categories, list, registry, mcp };
|
|
30
|
+
const commands = { search, info, install, use, kit, sync, installed, upgrade, remove, outdated, doctor, suggest, hook, gaps, 'gap-analyze': gapAnalyze, craft, prune, feedback, setup, update, categories, list, registry, mcp };
|
|
30
31
|
|
|
31
32
|
const HELP = `skills-atlas — search, install & manage AI agent skills
|
|
32
33
|
|
|
@@ -47,6 +48,9 @@ manage what you've installed:
|
|
|
47
48
|
kit set up the right skills for THIS project (detect + install)
|
|
48
49
|
sync reproduce a project's kit from skills-atlas.kit.json
|
|
49
50
|
|
|
51
|
+
getting started:
|
|
52
|
+
setup what you've got + how to use it (run after installing the plugin)
|
|
53
|
+
|
|
50
54
|
autopilot (opt-in):
|
|
51
55
|
hook on|off|status proactively suggest a skill in Claude when your prompt fits one
|
|
52
56
|
gaps kinds of work you keep doing without a skill (run: skills-atlas hook on)
|
|
@@ -80,7 +84,7 @@ async function main() {
|
|
|
80
84
|
}
|
|
81
85
|
// Opportunistic, non-blocking background catalog refresh so new skills appear over
|
|
82
86
|
// time without a manual `update`. Skipped for `update` itself; fully fail-silent.
|
|
83
|
-
if (sub !== 'update' && sub !== 'gap-analyze' && !process.env.SKILLS_ATLAS_SUBCALL) {
|
|
87
|
+
if (sub !== 'update' && sub !== 'gap-analyze' && sub !== 'setup' && !process.env.SKILLS_ATLAS_SUBCALL) {
|
|
84
88
|
try { require('../src/data').maybeBackgroundRefresh(); } catch { /* ignore */ }
|
|
85
89
|
}
|
|
86
90
|
await cmd(rest);
|
package/package.json
CHANGED
package/src/args.js
CHANGED
|
@@ -24,6 +24,8 @@ const ALL = {
|
|
|
24
24
|
verbose: { type: 'boolean' },
|
|
25
25
|
show: { type: 'boolean' },
|
|
26
26
|
once: { type: 'boolean' },
|
|
27
|
+
'session-start': { type: 'boolean' },
|
|
28
|
+
reset: { type: 'boolean' },
|
|
27
29
|
archetype: { type: 'string' },
|
|
28
30
|
update: { type: 'boolean' },
|
|
29
31
|
name: { type: 'string' },
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// `skills-atlas setup` — post-install onboarding + status. Run manually anytime
|
|
2
|
+
// (`skills-atlas setup`), or automatically once at session start via the plugin's
|
|
3
|
+
// SessionStart hook (`setup --session-start`). The one-time welcome is gated by a
|
|
4
|
+
// marker so it shows once, then stays silent. The session-start welcome is delivered
|
|
5
|
+
// via `systemMessage`, which the user sees VERBATIM (the model never sees it / can't
|
|
6
|
+
// paraphrase it). All local; nothing is sent.
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const { parse } = require('../args');
|
|
13
|
+
const registry = require('../registry');
|
|
14
|
+
const { green, dim, bold } = require('../format');
|
|
15
|
+
|
|
16
|
+
function cacheFile(name) {
|
|
17
|
+
const base = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
|
|
18
|
+
return path.join(base, 'skills-atlas', name);
|
|
19
|
+
}
|
|
20
|
+
const exists = f => { try { return fs.existsSync(f); } catch { return false; } };
|
|
21
|
+
function touch(f) {
|
|
22
|
+
try { fs.mkdirSync(path.dirname(f), { recursive: true }); fs.writeFileSync(f, new Date().toISOString() + '\n'); } catch { /* best-effort */ }
|
|
23
|
+
}
|
|
24
|
+
const onboardedFile = () => cacheFile('onboarded');
|
|
25
|
+
// Set by the plugin's welcome.js when the "engine not installed" notice was shown — so
|
|
26
|
+
// the eventual real welcome says "Nicely done" instead of the fresh-install greeting.
|
|
27
|
+
const installPromptShown = () => exists(cacheFile('install-prompt-shown'));
|
|
28
|
+
|
|
29
|
+
// The two settings worth surfacing up front (the rest live in the full `setup` view).
|
|
30
|
+
function welcomeSettings(ap) {
|
|
31
|
+
return 'Settings:\n' +
|
|
32
|
+
` language /skills-atlas:skill-autopilot lang en|zh (now: ${ap.replyLang})\n` +
|
|
33
|
+
` autopilot /skills-atlas:skill-autopilot on|off (${ap.enabled !== false ? 'on' : 'off'})\n\n` +
|
|
34
|
+
'More: /skills-atlas:setup';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// The one-time welcome text, or null if the user was already onboarded. Gated by the global
|
|
38
|
+
// `onboarded` marker so it shows exactly once; with { consume: true } it claims that one
|
|
39
|
+
// shot (marks onboarded). Shared by the SessionStart hook (`setup --session-start`) and the
|
|
40
|
+
// UserPromptSubmit fallback in `suggest` — which catches the case where the plugin was
|
|
41
|
+
// installed mid-session and no fresh session-start has fired the welcome yet.
|
|
42
|
+
function buildWelcome(ap, { consume = false } = {}) {
|
|
43
|
+
if (exists(onboardedFile())) return null;
|
|
44
|
+
if (consume) touch(onboardedFile());
|
|
45
|
+
const intro = installPromptShown()
|
|
46
|
+
? '🎉 Nicely done — Skills Atlas is configured and on. It\'ll quietly flag a ready-made skill when one fits, and offer to turn repeated workflows into skills of your own.'
|
|
47
|
+
: '✨ Skills Atlas is on. While you work, it keeps an eye out and quietly flags a ready-made skill the moment one fits — and if you catch yourself doing the same dance over and over, it\'ll offer to turn it into a skill of your own.';
|
|
48
|
+
return intro + '\n\n' + welcomeSettings(ap);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
module.exports = async function setup(argv) {
|
|
52
|
+
const { values } = parse(argv, ['session-start', 'json', 'reset']);
|
|
53
|
+
if (values.help) {
|
|
54
|
+
console.log('usage: skills-atlas setup [--reset]\n\n' +
|
|
55
|
+
'Show what Skills Atlas gives you and the autopilot status. Run it after installing\n' +
|
|
56
|
+
'the plugin. --reset makes the one-time welcome show again next session.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
if (values.reset) {
|
|
60
|
+
try { fs.rmSync(onboardedFile(), { force: true }); fs.rmSync(cacheFile('install-prompt-shown'), { force: true }); } catch { /* ignore */ }
|
|
61
|
+
console.log(`${green('✓')} onboarding reset — the welcome will show once more next session.`);
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const ap = registry.getAutopilot();
|
|
66
|
+
|
|
67
|
+
// --- Auto path (SessionStart hook): emit a ONE-TIME welcome via systemMessage (shown
|
|
68
|
+
// to the user verbatim), then stay silent forever. "Nicely done" variant if the user
|
|
69
|
+
// came here after the engine-not-installed notice; otherwise the fresh-install greeting. ---
|
|
70
|
+
if (values['session-start']) {
|
|
71
|
+
const msg = buildWelcome(ap, { consume: true });
|
|
72
|
+
if (msg === null) return; // already welcomed → silent
|
|
73
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'SessionStart' }, systemMessage: msg, suppressOutput: true }));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Manual path: human-readable status + what-you-can-do + full settings. ---
|
|
78
|
+
touch(onboardedFile()); // running setup yourself counts as onboarded
|
|
79
|
+
if (values.json) {
|
|
80
|
+
console.log(JSON.stringify({ installed: true, autopilot: ap.enabled !== false, suggest: ap.suggest, gapAlerts: ap.gapAlerts, prune: ap.prune, replyLang: ap.replyLang }));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
console.log(`${green('✓')} Skills Atlas is installed and ready.`);
|
|
84
|
+
console.log(` autopilot: ${ap.enabled !== false ? green('on') : dim('off')} ${dim('toggle: /skills-atlas:skill-autopilot on|off')}`);
|
|
85
|
+
console.log(`\n${bold('What you can do')} — right here in the conversation:`);
|
|
86
|
+
console.log(` ${green('find & install')} /skills-atlas:skill-search <query> → :skill-install <skill>`);
|
|
87
|
+
console.log(` ${green('set up a project')} /skills-atlas:skill-kit (detects the project type, proposes a kit)`);
|
|
88
|
+
console.log(` ${green('codify a workflow')} /skills-atlas:skill-craft (turns something you keep doing into a skill)`);
|
|
89
|
+
console.log(` ${green('review')} /skills-atlas:skill-gaps · :skill-prune · :skill-installed`);
|
|
90
|
+
console.log(`\n${bold('Settings')} ${dim('(optional)')}`);
|
|
91
|
+
console.log(` language /skills-atlas:skill-autopilot lang en|zh ${dim('(now: ' + ap.replyLang + ')')}`);
|
|
92
|
+
console.log(` autopilot /skills-atlas:skill-autopilot on|off ${dim('(' + (ap.enabled !== false ? 'on' : 'off') + ')')}`);
|
|
93
|
+
console.log(` fine-tune /skills-atlas:skill-autopilot suggest|gaps|prune on|off ${dim('· model')}`);
|
|
94
|
+
console.log(dim('\nThe autopilot also suggests skills proactively as you work — each judged for fit. Nothing leaves your machine.'));
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Reused by `suggest`'s first-run fallback (see src/commands/suggest.js).
|
|
98
|
+
module.exports.buildWelcome = buildWelcome;
|
package/src/commands/suggest.js
CHANGED
|
@@ -56,7 +56,24 @@ module.exports = async function suggest() {
|
|
|
56
56
|
let event = {};
|
|
57
57
|
try { event = JSON.parse(fs.readFileSync(0, 'utf8')); } catch { /* not JSON → bail */ }
|
|
58
58
|
const prompt = event.prompt || '';
|
|
59
|
-
if (!prompt
|
|
59
|
+
if (!prompt) return;
|
|
60
|
+
|
|
61
|
+
// First-run welcome fallback: if the plugin was installed mid-session (so no fresh
|
|
62
|
+
// SessionStart has fired the welcome yet), surface the one-time welcome on the user's
|
|
63
|
+
// next prompt — verbatim, via systemMessage. This MUST run ahead of the suggestion's
|
|
64
|
+
// min-length gate below: onboarding shouldn't depend on how long the prompt is (a short
|
|
65
|
+
// "hi" should still get the welcome). Shares the global `onboarded` marker with the
|
|
66
|
+
// SessionStart hook, so the welcome shows exactly once across both paths.
|
|
67
|
+
try {
|
|
68
|
+
const welcome = require('./setup').buildWelcome(registry.getAutopilot(), { consume: true });
|
|
69
|
+
if (welcome) {
|
|
70
|
+
console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'UserPromptSubmit' }, systemMessage: welcome, suppressOutput: true }));
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
} catch { /* fail-open */ }
|
|
74
|
+
|
|
75
|
+
// The proactive suggestion (below) needs a meatier prompt to match against.
|
|
76
|
+
if (prompt.length < 8) return;
|
|
60
77
|
|
|
61
78
|
const file = stateFile(event.session_id || event.sessionId);
|
|
62
79
|
const state = readState(file);
|
|
@@ -150,17 +167,17 @@ module.exports = async function suggest() {
|
|
|
150
167
|
return `- ${c.skill}${uc ? ` — ${uc}` : ''} (details: \`skills-atlas info ${c.skill}\` · use now: \`skills-atlas use ${c.skill} --yes --project\`)`;
|
|
151
168
|
}).join('\n');
|
|
152
169
|
emit(
|
|
153
|
-
`[Skills Atlas autopilot]
|
|
154
|
-
`
|
|
155
|
-
`genuinely fits what they actually asked
|
|
156
|
-
`
|
|
157
|
-
`
|
|
158
|
-
`
|
|
159
|
-
`
|
|
160
|
-
`
|
|
161
|
-
`
|
|
162
|
-
|
|
163
|
-
`
|
|
170
|
+
`[Skills Atlas autopilot] One of these catalog skills MIGHT fit what the user is doing. Judge for ` +
|
|
171
|
+
`yourself — most prompts match nothing, and staying silent is usually the right call:\n${lines}\n` +
|
|
172
|
+
`Mention one ONLY if it genuinely fits what they actually asked. If it does: in a sentence or two, ` +
|
|
173
|
+
`casually tell them what that skill ACTUALLY DOES and why it fits THIS task — enough that they ` +
|
|
174
|
+
`understand it (use the skill's EXACT name as listed above; it's a real curated skill from the Skills ` +
|
|
175
|
+
`Atlas catalog, not something you made up). Then offer the choice naturally, in prose — NOT a bulleted ` +
|
|
176
|
+
`sales menu: they can switch it on now (\`skills-atlas use <skill> --yes --project\`), or you can show ` +
|
|
177
|
+
`what it covers first (\`skills-atlas info <skill>\`) if they're unsure. Don't push. If nothing genuinely ` +
|
|
178
|
+
`fits, say NOTHING about any of this — don't mention this hook, the skills, or that a check ran. (To ` +
|
|
179
|
+
`look wider you may run \`skills-atlas search "<short intent>"\`; if they want a skill never suggested ` +
|
|
180
|
+
`again, \`skills-atlas feedback dismiss <skill>\`.)` + langHint(ap.replyLang));
|
|
164
181
|
state.lastSuggestedCount = state.count;
|
|
165
182
|
state.suggested = [...suggested, ...candidates.map(c => c.skill)];
|
|
166
183
|
writeState(file, state);
|