wizz-method 1.2.2 → 1.3.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/package.json +3 -2
- package/skills-registry.yaml +67 -4
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +5 -0
- package/src/modules/wizz/agents/wizz-social/SKILL.md +84 -0
- package/src/modules/wizz/agents/wizz-social/customize.toml +72 -0
- package/src/modules/wizz/agents/wizz-social/knowledge/analise-imagem-referencia.md +40 -0
- package/src/modules/wizz/agents/wizz-social/knowledge/biblioteca-prompts-imagem.md +102 -0
- package/src/modules/wizz/agents/wizz-social/knowledge/blueprint-roteiro-viral.md +341 -0
- package/src/modules/wizz/agents/wizz-social/knowledge/legenda-instagram.md +53 -0
- package/src/modules/wizz/agents/wizz-social/knowledge/prompts-imagem-video.md +100 -0
- package/src/modules/wizz/module.yaml +7 -0
- package/tools/installer/core/installer.js +37 -2
- package/tools/installer/ide/platform-codes.yaml +9 -0
- package/tools/installer/modules/deps-cache.js +140 -0
- package/tools/installer/modules/mcp-config.js +169 -1
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// Writes a project-local cache manifest of the skill dependencies (CLIs + MCPs)
|
|
2
|
+
// resolved at install-time for the chosen areas.
|
|
3
|
+
//
|
|
4
|
+
// Behavior B of the "Doutrina de Instalação" (skills-registry.yaml, rule 2):
|
|
5
|
+
// when the installed skills declare NPX/CLI/MCP dependencies, detect them and
|
|
6
|
+
// cache the result in the project so future runs and the maestro can see —
|
|
7
|
+
// without re-resolving the registry or re-probing PATH — exactly which deps
|
|
8
|
+
// belong to this project's skills and what state they were left in:
|
|
9
|
+
// - clis.installed / clis.recommended / clis.alreadyPresent
|
|
10
|
+
// - mcps.written (merged into .mcp.json) / mcps.recommended
|
|
11
|
+
//
|
|
12
|
+
// This is a passive manifest (a cache, not an installer): it records the plan
|
|
13
|
+
// the install already executed. It never runs commands, never writes secrets
|
|
14
|
+
// (MCP env stays as the registry `${VAR}` placeholders), and is a no-op when
|
|
15
|
+
// there is nothing to record. Written into `_wizz/_config/` next to the
|
|
16
|
+
// installed skills-registry.yaml so the routing source and the dep cache stay
|
|
17
|
+
// co-located.
|
|
18
|
+
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const fs = require('../fs-native');
|
|
21
|
+
|
|
22
|
+
const CACHE_BASENAME = 'skill-deps-cache.json';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Shape a resolved CLI entry for the cache: identity + the copy-pasteable
|
|
26
|
+
* install command, dropping the transient `installed` probe flag (its bucket
|
|
27
|
+
* already encodes that). Returns a new object; never mutates the input.
|
|
28
|
+
* @param {{id: string, when?: string, install?: string, areas?: string[]}} cli
|
|
29
|
+
* @returns {{id: string, when: string, install: string, areas: string[]}}
|
|
30
|
+
*/
|
|
31
|
+
function shapeCli(cli) {
|
|
32
|
+
return {
|
|
33
|
+
id: cli.id,
|
|
34
|
+
when: cli.when || '',
|
|
35
|
+
install: cli.install || '',
|
|
36
|
+
areas: Array.isArray(cli.areas) ? [...cli.areas] : [],
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Shape a resolved MCP entry for the cache: identity + the server block as it
|
|
42
|
+
* came from the registry (env values remain `${VAR}` placeholders — no real
|
|
43
|
+
* token is ever cached). Returns a new object; never mutates the input.
|
|
44
|
+
* @param {{id: string, when?: string, server?: Object, areas?: string[]}} mcp
|
|
45
|
+
* @returns {{id: string, when: string, server: Object, areas: string[]}}
|
|
46
|
+
*/
|
|
47
|
+
function shapeMcp(mcp) {
|
|
48
|
+
return {
|
|
49
|
+
id: mcp.id,
|
|
50
|
+
when: mcp.when || '',
|
|
51
|
+
server: mcp.server ? { ...mcp.server } : {},
|
|
52
|
+
areas: Array.isArray(mcp.areas) ? [...mcp.areas] : [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build the cache object from the install-time cli/mcp plans. Pure: no I/O,
|
|
58
|
+
* no mutation of the inputs. Exposed for tests and for the caller to inspect.
|
|
59
|
+
* @param {Object} args
|
|
60
|
+
* @param {string[]} [args.selectedAreas] - Chosen area keys ([] => all areas)
|
|
61
|
+
* @param {{toInstall?: Object[], toRecommend?: Object[], alreadyInstalled?: Object[]}} [args.cliPlan]
|
|
62
|
+
* @param {{toWrite?: Object[], toRecommend?: Object[]}} [args.mcpPlan]
|
|
63
|
+
* @returns {Object} The manifest to be written.
|
|
64
|
+
*/
|
|
65
|
+
function buildDepsCache({ selectedAreas, cliPlan = {}, mcpPlan = {} }) {
|
|
66
|
+
return {
|
|
67
|
+
version: 1,
|
|
68
|
+
generatedAt: new Date().toISOString(),
|
|
69
|
+
selectedAreas: Array.isArray(selectedAreas) ? [...selectedAreas] : [],
|
|
70
|
+
clis: {
|
|
71
|
+
installed: (cliPlan.toInstall || []).map(shapeCli),
|
|
72
|
+
recommended: (cliPlan.toRecommend || []).map(shapeCli),
|
|
73
|
+
alreadyPresent: (cliPlan.alreadyInstalled || []).map(shapeCli),
|
|
74
|
+
},
|
|
75
|
+
mcps: {
|
|
76
|
+
written: (mcpPlan.toWrite || []).map(shapeMcp),
|
|
77
|
+
recommended: (mcpPlan.toRecommend || []).map(shapeMcp),
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* True when a built cache has no dependency entries at all — nothing worth
|
|
84
|
+
* persisting (e.g. no `bmm` module, or the chosen areas declare no deps).
|
|
85
|
+
* @param {Object} cache - Output of buildDepsCache
|
|
86
|
+
* @returns {boolean}
|
|
87
|
+
*/
|
|
88
|
+
function isEmptyDepsCache(cache) {
|
|
89
|
+
const c = cache.clis || {};
|
|
90
|
+
const m = cache.mcps || {};
|
|
91
|
+
return (
|
|
92
|
+
(c.installed || []).length === 0 &&
|
|
93
|
+
(c.recommended || []).length === 0 &&
|
|
94
|
+
(c.alreadyPresent || []).length === 0 &&
|
|
95
|
+
(m.written || []).length === 0 &&
|
|
96
|
+
(m.recommended || []).length === 0
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Build and write the deps cache into `<wizzDir>/_config/skill-deps-cache.json`.
|
|
102
|
+
* No-op (writes nothing, returns wrote:false) when there are no deps to record,
|
|
103
|
+
* so it is always safe to call. Never throws for lack of deps; a real fs error
|
|
104
|
+
* propagates to the caller, which already wraps this step in a warn-not-abort.
|
|
105
|
+
*
|
|
106
|
+
* @param {Object} args
|
|
107
|
+
* @param {string} args.wizzDir - Installed `_wizz` directory
|
|
108
|
+
* @param {string[]} [args.selectedAreas] - Chosen area keys ([] => all)
|
|
109
|
+
* @param {Object} [args.cliPlan] - The install-time CLI plan (from ui.selectClis)
|
|
110
|
+
* @param {Object} [args.mcpPlan] - The install-time MCP plan (from ui.selectMcps)
|
|
111
|
+
* @param {(absPath: string) => void} [args.trackFile] - Record the written file
|
|
112
|
+
* @returns {Promise<{wrote: boolean, file: string|null, counts: Object}>}
|
|
113
|
+
*/
|
|
114
|
+
async function writeDepsCache({ wizzDir, selectedAreas, cliPlan, mcpPlan, trackFile = () => {} }) {
|
|
115
|
+
const cache = buildDepsCache({ selectedAreas, cliPlan, mcpPlan });
|
|
116
|
+
const counts = {
|
|
117
|
+
clisInstalled: cache.clis.installed.length,
|
|
118
|
+
clisRecommended: cache.clis.recommended.length,
|
|
119
|
+
clisAlreadyPresent: cache.clis.alreadyPresent.length,
|
|
120
|
+
mcpsWritten: cache.mcps.written.length,
|
|
121
|
+
mcpsRecommended: cache.mcps.recommended.length,
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
if (isEmptyDepsCache(cache)) return { wrote: false, file: null, counts };
|
|
125
|
+
|
|
126
|
+
const file = path.join(wizzDir, '_config', CACHE_BASENAME);
|
|
127
|
+
await fs.ensureDir(path.dirname(file));
|
|
128
|
+
await fs.writeJson(file, cache, { spaces: 2 });
|
|
129
|
+
trackFile(file);
|
|
130
|
+
return { wrote: true, file, counts };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
module.exports = {
|
|
134
|
+
buildDepsCache,
|
|
135
|
+
isEmptyDepsCache,
|
|
136
|
+
writeDepsCache,
|
|
137
|
+
shapeCli,
|
|
138
|
+
shapeMcp,
|
|
139
|
+
CACHE_BASENAME,
|
|
140
|
+
};
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
|
|
21
21
|
const path = require('node:path');
|
|
22
22
|
const fs = require('../fs-native');
|
|
23
|
+
const { defaultExec } = require('./cli-config');
|
|
23
24
|
|
|
24
25
|
/**
|
|
25
26
|
* Resolve the recommended MCP entries for the chosen areas, deduped by id.
|
|
@@ -48,6 +49,12 @@ function resolveMcps(registry, selectedAreas) {
|
|
|
48
49
|
id: mcp.id,
|
|
49
50
|
when: mcp.when || '',
|
|
50
51
|
server: mcp.server,
|
|
52
|
+
// `setup` (optional) declares how to make a `command:`-relative server
|
|
53
|
+
// actually usable before it is written: install the backing package,
|
|
54
|
+
// download runtime assets, resolve the binary to an absolute path, and
|
|
55
|
+
// verify it boots. Carried here so prepareMcps() can act on it; dropped
|
|
56
|
+
// from what toServerConfig() writes (it never belongs in .mcp.json).
|
|
57
|
+
setup: mcp.setup || null,
|
|
51
58
|
areas: areaKey ? [areaKey] : [],
|
|
52
59
|
});
|
|
53
60
|
};
|
|
@@ -74,6 +81,157 @@ function toServerConfig(server) {
|
|
|
74
81
|
return out;
|
|
75
82
|
}
|
|
76
83
|
|
|
84
|
+
/**
|
|
85
|
+
* POSIX-quote a path so it survives interpolation into a shell command even
|
|
86
|
+
* when it contains spaces or shell metacharacters. Single-quote wrapped, with
|
|
87
|
+
* embedded single quotes escaped the classic `'\''` way.
|
|
88
|
+
* @param {string} value
|
|
89
|
+
* @returns {string}
|
|
90
|
+
*/
|
|
91
|
+
function shellQuote(value) {
|
|
92
|
+
return `'${String(value).replaceAll("'", `'\\''`)}'`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Substitute the `{bin}` placeholder in a setup command with the shell-quoted
|
|
97
|
+
* absolute path of the resolved binary. Lets the registry write portable setup
|
|
98
|
+
* commands (`{bin} install`, `{bin} --version`) that bind to the real path at
|
|
99
|
+
* run time instead of relying on PATH.
|
|
100
|
+
* @param {string} command - Command template possibly containing `{bin}`
|
|
101
|
+
* @param {string} binPath - Absolute path to substitute
|
|
102
|
+
* @returns {string}
|
|
103
|
+
*/
|
|
104
|
+
function substituteBin(command, binPath) {
|
|
105
|
+
return String(command).replaceAll('{bin}', shellQuote(binPath));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve the absolute path of a binary an MCP server shells out to. A bare
|
|
110
|
+
* `command: "scrapling"` is fragile: the MCP subprocess spawned by the client
|
|
111
|
+
* does not always inherit the user's ~/.local/bin on PATH, so a relative name
|
|
112
|
+
* fails with ENOENT even when the tool is installed. We resolve and write the
|
|
113
|
+
* absolute path instead. Tries `command -v` first (honors the current PATH),
|
|
114
|
+
* then the conventional user-local bin dir where `uv tool` / `pipx` / `pip
|
|
115
|
+
* --user` land executables. Returns null when the binary is nowhere to be found.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} bin - Binary name to locate
|
|
118
|
+
* @param {Function} [exec] - Injected runner (command, opts) => {ok, stdout,...}
|
|
119
|
+
* @returns {Promise<string|null>} Absolute path, or null if not found.
|
|
120
|
+
*/
|
|
121
|
+
async function resolveBinPath(bin, exec = defaultExec) {
|
|
122
|
+
if (!bin) return null;
|
|
123
|
+
const viaWhich = await exec(`command -v ${bin}`, { timeoutMs: 5000 });
|
|
124
|
+
if (viaWhich.ok) {
|
|
125
|
+
const found = (viaWhich.stdout || '').trim().split('\n')[0].trim();
|
|
126
|
+
if (found) return found;
|
|
127
|
+
}
|
|
128
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
129
|
+
if (home) {
|
|
130
|
+
const candidate = path.join(home, '.local', 'bin', bin);
|
|
131
|
+
if (await fs.pathExists(candidate)) return candidate;
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Run an MCP entry's `setup` block so the server is genuinely usable BEFORE it
|
|
138
|
+
* lands in .mcp.json. This closes the ENOENT gap where the installer wrote a
|
|
139
|
+
* server config but never installed the backing tool. Contract (detect-first,
|
|
140
|
+
* mirroring cli-config's install philosophy):
|
|
141
|
+
*
|
|
142
|
+
* 1. DETECT — resolve the binary; if already present we skip installation.
|
|
143
|
+
* 2. INSTALL — on a miss, run `setup.install` (package + its MCP extra), then
|
|
144
|
+
* re-resolve. `setup.post_install` (e.g. downloading browsers) runs after,
|
|
145
|
+
* only on a fresh install, with `{bin}` bound to the resolved path.
|
|
146
|
+
* 3. RESOLVE — rewrite `server.command` to the ABSOLUTE path so the client's
|
|
147
|
+
* MCP subprocess never depends on PATH inheriting ~/.local/bin.
|
|
148
|
+
* 4. VERIFY — run `setup.verify` ({bin} bound). A failure means writing the
|
|
149
|
+
* config would point at a broken/missing binary, so we DO NOT write it —
|
|
150
|
+
* the entry is reported failed and the caller drops it with a clear message.
|
|
151
|
+
*
|
|
152
|
+
* Immutable: returns a new entry with a patched `server.command`; the input is
|
|
153
|
+
* never mutated. Entries without a `setup` block pass through untouched.
|
|
154
|
+
*
|
|
155
|
+
* @param {Object} args
|
|
156
|
+
* @param {Object} args.mcp - Resolved MCP entry (id, server, setup?)
|
|
157
|
+
* @param {Function} [args.exec] - Injected runner (command, opts) => {ok,...}
|
|
158
|
+
* @returns {Promise<{mcp: Object, ok: boolean, ran: boolean, error: string|null}>}
|
|
159
|
+
*/
|
|
160
|
+
async function prepareMcp({ mcp, exec = defaultExec }) {
|
|
161
|
+
const setup = mcp && mcp.setup;
|
|
162
|
+
if (!setup) return { mcp, ok: true, ran: false, error: null };
|
|
163
|
+
|
|
164
|
+
const bin = setup.bin || (mcp.server && mcp.server.command);
|
|
165
|
+
if (!bin) {
|
|
166
|
+
return { mcp, ok: false, ran: false, error: 'setup declarado sem `bin` nem `server.command` para resolver' };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 1. DETECT — install only when the binary is not already resolvable.
|
|
170
|
+
let binPath = await resolveBinPath(bin, exec);
|
|
171
|
+
const freshInstall = !binPath;
|
|
172
|
+
|
|
173
|
+
if (freshInstall) {
|
|
174
|
+
if (!setup.install) {
|
|
175
|
+
return { mcp, ok: false, ran: false, error: `binário '${bin}' ausente e setup não tem comando \`install\`` };
|
|
176
|
+
}
|
|
177
|
+
const installed = await exec(setup.install, { timeoutMs: 900_000 });
|
|
178
|
+
if (!installed.ok) {
|
|
179
|
+
return { mcp, ok: false, ran: true, error: `instalação falhou: ${(installed.stderr || 'erro desconhecido').trim()}` };
|
|
180
|
+
}
|
|
181
|
+
binPath = await resolveBinPath(bin, exec);
|
|
182
|
+
if (!binPath) {
|
|
183
|
+
return {
|
|
184
|
+
mcp,
|
|
185
|
+
ok: false,
|
|
186
|
+
ran: true,
|
|
187
|
+
error: `'${bin}' não encontrado no PATH nem em ~/.local/bin após a instalação`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// 2. POST-INSTALL (e.g. browser download) — fresh installs only, idempotent.
|
|
191
|
+
if (setup.post_install) {
|
|
192
|
+
const post = await exec(substituteBin(setup.post_install, binPath), { timeoutMs: 900_000 });
|
|
193
|
+
if (!post.ok) {
|
|
194
|
+
return { mcp, ok: false, ran: true, error: `pós-instalação falhou: ${(post.stderr || 'erro desconhecido').trim()}` };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// 4. VERIFY — never write a config pointing at a binary that cannot boot.
|
|
200
|
+
if (setup.verify) {
|
|
201
|
+
const verified = await exec(substituteBin(setup.verify, binPath), { timeoutMs: 60_000 });
|
|
202
|
+
if (!verified.ok) {
|
|
203
|
+
return { mcp, ok: false, ran: freshInstall, error: `verificação falhou: ${(verified.stderr || 'erro desconhecido').trim()}` };
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 3. RESOLVE — patch the absolute path; leave args/env/everything else intact.
|
|
208
|
+
const patched = { ...mcp, server: { ...mcp.server, command: binPath } };
|
|
209
|
+
return { mcp: patched, ok: true, ran: freshInstall, error: null };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Prepare every MCP entry (see prepareMcp), splitting the set into `ready`
|
|
214
|
+
* (safe to write, with any `server.command` patched to an absolute path) and
|
|
215
|
+
* `failed` (setup did not complete — the caller must NOT write these and should
|
|
216
|
+
* surface the error so the user can fix it manually). Never throws: each entry
|
|
217
|
+
* is isolated so one broken tool never blocks the others or the wider install.
|
|
218
|
+
*
|
|
219
|
+
* @param {Object} args
|
|
220
|
+
* @param {Array<Object>} args.mcps - Resolved MCP entries to prepare
|
|
221
|
+
* @param {Function} [args.exec] - Injected runner (command, opts) => {ok,...}
|
|
222
|
+
* @returns {Promise<{ready: Array<Object>, failed: Array<{id: string, error: string}>}>}
|
|
223
|
+
*/
|
|
224
|
+
async function prepareMcps({ mcps, exec = defaultExec }) {
|
|
225
|
+
const ready = [];
|
|
226
|
+
const failed = [];
|
|
227
|
+
for (const mcp of mcps || []) {
|
|
228
|
+
const res = await prepareMcp({ mcp, exec });
|
|
229
|
+
if (res.ok) ready.push(res.mcp);
|
|
230
|
+
else failed.push({ id: mcp.id, error: res.error || 'falha desconhecida' });
|
|
231
|
+
}
|
|
232
|
+
return { ready, failed };
|
|
233
|
+
}
|
|
234
|
+
|
|
77
235
|
/**
|
|
78
236
|
* Render the `claude mcp add` command for an MCP entry — used to recommend
|
|
79
237
|
* servers the user chose NOT to write (nothing is lost; they can add later).
|
|
@@ -134,4 +292,14 @@ async function writeMcpConfig({ projectDir, mcps, trackFile = () => {} }) {
|
|
|
134
292
|
return { added, skipped, file };
|
|
135
293
|
}
|
|
136
294
|
|
|
137
|
-
module.exports = {
|
|
295
|
+
module.exports = {
|
|
296
|
+
resolveMcps,
|
|
297
|
+
toServerConfig,
|
|
298
|
+
renderAddCommand,
|
|
299
|
+
writeMcpConfig,
|
|
300
|
+
prepareMcp,
|
|
301
|
+
prepareMcps,
|
|
302
|
+
resolveBinPath,
|
|
303
|
+
shellQuote,
|
|
304
|
+
substituteBin,
|
|
305
|
+
};
|