@aksp/opencrew 1.2.0 → 1.2.2

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/CHANGELOG.md CHANGED
@@ -3,6 +3,63 @@
3
3
  All notable changes to opencrew are documented here.
4
4
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
5
5
 
6
+ ## [1.2.2] — 2026-08-02
7
+
8
+ ### Fixed
9
+ - **`parseArgs` truncates values containing `=`**: flags like `--description=foo=bar`
10
+ no longer lose everything after the second `=`.
11
+ - **`version` npm script uses `require()` in ESM project**: extracted to a dedicated
12
+ `scripts/stamp-version.js` that uses proper ESM imports.
13
+ - **`skills.engine.md` numbering was out of order** in Operation 2 (Install a Skill):
14
+ steps 3/4 repeated instead of continuing 5–9. Cross-references updated accordingly.
15
+ - **`init` now aborts when a workspace already exists** instead of proceeding with
16
+ `overwrite: false` (which silently did nothing). It prints instructions to use
17
+ `update` or reinstall from scratch.
18
+ - **`deleteDir` semantics**: now returns `false` when the path does not exist (was `true`).
19
+ - **`.env.example` placeholders** (`[REDACTED:API key param]`) removed — these were
20
+ security-redaction artifacts from the tooling, not real file content. No code change.
21
+
22
+ ### Added
23
+ - **Short flags**: `-y` (yes), `-v` (version), `-h` (help) now work alongside their
24
+ `--long-form` equivalents.
25
+ - **`OPENCREW_CATALOG_URL` env var**: forks can override the skill catalog base URL
26
+ without editing `catalog.json`. Documented in `CONTRIBUTING.md` → Forking.
27
+ - **`update` now warns** that catalog skills are fully overwritten before refreshing them.
28
+ - **`readJson` error messages now include the file path** (e.g. `Failed to read
29
+ /path/to/package.json: file not found`).
30
+ - **`pickIdes` validates preselected IDs**: unknown IDs from `--ide` are filtered with
31
+ a warning instead of being passed through silently.
32
+ - **`c.gray` removed** (unused). **`confirm()` removed** (dead code, never imported).
33
+ - **ESLint** (`eslint.config.js` + `npm run lint` + CI step) with `@eslint/js` flat config.
34
+ - **55 tests** (up from 30): new coverage for `paths.js`, `ui.js`, `fsx.js` error
35
+ scenarios, `normalizeIdes` string input, CLI smoke tests.
36
+
37
+ ### Changed
38
+ - **CI `npm audit` raised from `moderate` to `high`** to avoid spurious build failures
39
+ from dev-dependency vulnerabilities without attack vectors.
40
+ - **Playwright config**: `channel: "chrome"` removed — uses bundled Chromium for better
41
+ portability. `.mcp.json` now includes a `_comment` field explaining how to upgrade the
42
+ pinned `@playwright/mcp` version.
43
+ - **Node version check** in `cli.js` now uses a proper semver comparison that handles
44
+ `||` ranges (e.g. `>=18.0.0 || >=20.0.0`).
45
+
46
+ ### Docs
47
+ - **`discovery.prompt.md`**: `crew_code` uniqueness is now self-service (`ls crews/`)
48
+ instead of depending on the orchestrator to pass a list.
49
+ - **`runner.pipeline.md`**: language contract table documents all fixed PT-BR headers
50
+ and the policy for adding new ones.
51
+ - **`CONTRIBUTING.md`**: new "Forking" section with catalog URL, package name, and
52
+ publish instructions.
53
+
54
+ ## [1.2.1] — 2026-08-02
55
+
56
+ ### Changed
57
+ - **Menu discovery for repair**: the "My crews" menu entry now mentions `repair`, so the
58
+ command introduced in 1.2.0 is discoverable from the menu and not only from the command
59
+ routing table.
60
+ - **README**: standardized the project name as "OpenCrew" in prose (commands and the npm
61
+ package name stay lowercase).
62
+
6
63
  ## [1.2.0] — 2026-08-02
7
64
 
8
65
  ### Fixed
package/README.md CHANGED
@@ -94,7 +94,7 @@ Cada integração é só um ponteiro enxuto para a fonte única de verdade, o **
94
94
 
95
95
  ## Dashboard (opcional)
96
96
 
97
- O opencrew inclui um dashboard visual auto-contido — `dashboard/index.html`, um arquivo
97
+ O OpenCrew inclui um dashboard visual auto-contido — `dashboard/index.html`, um arquivo
98
98
  HTML único sem dependências que mostra a execução de uma crew como um escritório virtual
99
99
  animado (com agentes trabalhando em suas mesas, handoffs entre etapas, e indicador de
100
100
  progresso).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aksp/opencrew",
3
- "version": "1.2.0",
3
+ "version": "1.2.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -19,8 +19,9 @@
19
19
  ],
20
20
  "scripts": {
21
21
  "start": "node bin/opencrew.js",
22
- "test": "node --test tests/cli.test.js tests/docs.test.js tests/fsx.test.js tests/ides.test.js tests/init.test.js tests/update.test.js",
23
- "version": "node -e \"require('fs').writeFileSync('templates/_opencrew/.opencrew-version', require('./package.json').version + '\\n')\" && git add templates/_opencrew/.opencrew-version"
22
+ "test": "node --test tests/cli.test.js tests/docs.test.js tests/fsx.test.js tests/ides.test.js tests/init.test.js tests/paths.test.js tests/ui.test.js tests/update.test.js",
23
+ "lint": "eslint src/ tests/ scripts/",
24
+ "version": "node scripts/stamp-version.js && git add templates/_opencrew/.opencrew-version"
24
25
  },
25
26
  "keywords": [
26
27
  "ai-agents",
@@ -53,5 +54,10 @@
53
54
  "dependencies": {
54
55
  "@inquirer/checkbox": "^5.1.0",
55
56
  "@inquirer/confirm": "^5.1.0"
57
+ },
58
+ "devDependencies": {
59
+ "@eslint/js": "^9.0.0",
60
+ "eslint": "^9.0.0",
61
+ "globals": "^17.9.0"
56
62
  }
57
63
  }
package/src/cli.js CHANGED
@@ -5,16 +5,52 @@ import { init } from './commands/init.js';
5
5
  import { update } from './commands/update.js';
6
6
  import { c, log, err, warn, info } from './lib/ui.js';
7
7
 
8
+ // Extract the minimum required Node version from an engines.node range string.
9
+ // Handles: ">=20.0.0", "^20.5", ">=18.0.0 || >=20.0.0", plain "20.0.0".
10
+ function minNodeVersion(range) {
11
+ // Split on || and take the lowest version (user is expected to meet at least one).
12
+ const parts = range.split(/\s*\|\|\s*/);
13
+ let lowest = null;
14
+ for (const part of parts) {
15
+ const v = part.replace(/[^0-9.]/g, '');
16
+ if (!v) continue;
17
+ if (!lowest || lt(v, lowest)) lowest = v;
18
+ }
19
+ return lowest;
20
+ }
21
+
22
+ // Simple semver comparison (no prerelease tags). Returns true if a < b.
23
+ function lt(a, b) {
24
+ const pa = a.split('.').map(Number);
25
+ const pb = b.split('.').map(Number);
26
+ for (let i = 0; i < 3; i++) {
27
+ const na = pa[i] || 0;
28
+ const nb = pb[i] || 0;
29
+ if (na !== nb) return na < nb;
30
+ }
31
+ return false; // equal
32
+ }
33
+
8
34
  function parseArgs(argv) {
9
35
  const opts = { _: [] };
10
36
  for (const a of argv) {
11
37
  if (a.startsWith('--')) {
12
- const [k, v] = a.slice(2).split('=');
13
- opts[k] = v === undefined ? true : v;
38
+ const eq = a.indexOf('=');
39
+ const k = eq > -1 ? a.slice(2, eq) : a.slice(2);
40
+ const v = eq > -1 ? a.slice(eq + 1) : true;
41
+ opts[k] = v;
42
+ } else if (a.startsWith('-') && a.length === 2) {
43
+ // Short flags: -y → yes, -v → version, -h → help
44
+ const short = a[1];
45
+ opts[short] = true;
14
46
  } else {
15
47
  opts._.push(a);
16
48
  }
17
49
  }
50
+ // Normalize short flags to their long-form equivalents.
51
+ if (opts.y) opts.yes = true;
52
+ if (opts.v) opts.version = true;
53
+ if (opts.h) opts.help = true;
18
54
  return opts;
19
55
  }
20
56
 
@@ -28,13 +64,14 @@ ${c.bold('Usage')}
28
64
  ${c.bold('Commands')}
29
65
  init Scaffold an opencrew workspace in the current folder
30
66
  update Refresh the framework (keeps your crews, memory and .env)
67
+ upgrade Alias for update
31
68
  help Show this help
32
69
  version Print the version
33
70
 
34
71
  ${c.bold('Options for init')}
35
72
  --ide=a,b Preselect IDEs (skip the prompt). Valid: ${allIdeIds().join(', ')}
36
73
  --all Configure every supported IDE
37
- --yes Non-interactive; accept defaults
74
+ --yes, -y Non-interactive; accept defaults
38
75
 
39
76
  ${c.bold('Options for update')}
40
77
  --check Dry-run: report whether an update is available without making changes
@@ -43,6 +80,7 @@ ${c.bold('Examples')}
43
80
  npx @aksp/opencrew init
44
81
  npx @aksp/opencrew init --ide=claude-code,codex
45
82
  npx @aksp/opencrew init --all
83
+ npx @aksp/opencrew init -y
46
84
  npx @aksp/opencrew update
47
85
  npx @aksp/opencrew update --check
48
86
  `);
@@ -66,16 +104,13 @@ export async function run(argv) {
66
104
 
67
105
  // Validate Node version against engines.node requirement.
68
106
  if (engines.node) {
69
- const min = engines.node.replace(/[^0-9.]/g, '');
70
- if (min) {
71
- const major = Number(min.split('.')[0]);
72
- const current = Number(process.versions.node.split('.')[0]);
73
- if (current < major) {
74
- warn(`opencrew requires Node.js ${engines.node}. You have v${process.versions.node}.`);
75
- info(`Upgrade Node or use a compatible version.`);
76
- process.exitCode = 1;
77
- return;
78
- }
107
+ const required = minNodeVersion(engines.node);
108
+ const current = process.versions.node;
109
+ if (required && lt(current, required)) {
110
+ warn(`opencrew requires Node.js ${engines.node}. You have v${current}.`);
111
+ info(`Upgrade Node or use a compatible version.`);
112
+ process.exitCode = 1;
113
+ return;
79
114
  }
80
115
  }
81
116
 
@@ -2,7 +2,7 @@ import path from 'node:path';
2
2
  import { promises as fs } from 'node:fs';
3
3
  import { templatesDir, packageJsonPath } from '../lib/paths.js';
4
4
  import { copyDir, exists, writeFileSafe, readJson } from '../lib/fsx.js';
5
- import { IDES, ideById, allIdeIds } from '../lib/ides.js';
5
+ import { ideById, allIdeIds } from '../lib/ides.js';
6
6
  import { pickIdes } from '../lib/prompts.js';
7
7
  import { c, log, info, ok, warn, step } from '../lib/ui.js';
8
8
 
@@ -17,8 +17,9 @@ export async function init(opts = {}) {
17
17
  const alreadyInstalled = await exists(path.join(target, '_opencrew', 'core'));
18
18
  if (alreadyInstalled) {
19
19
  warn('An opencrew workspace already exists here.');
20
- warn(`Framework files are refreshed but your crews, memory and .env are kept intact.`);
21
- warn(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew update')}\n`);
20
+ info(`To update only the framework, use: ${c.cyan('npx @aksp/opencrew update')}`);
21
+ info(`To reinstall from scratch, delete _opencrew/ first, then run init again.`);
22
+ return;
22
23
  }
23
24
 
24
25
  // 1. Copy the framework payload (never clobber user work).
@@ -59,6 +59,7 @@ export async function update(opts = {}) {
59
59
  // the catalog — are never touched, since copyDir only visits paths that exist
60
60
  // in the source (templates/skills/).
61
61
  step('Refreshing catalog skills');
62
+ warn('Catalog skills are fully overwritten — your edits to any built-in skill files will be lost.');
62
63
  let s = 0;
63
64
  await copyDir(path.join(templatesDir, 'skills'), path.join(target, 'skills'), {
64
65
  overwrite: true,
package/src/lib/fsx.js CHANGED
@@ -57,6 +57,7 @@ export async function writeFileSafe(p, content, { overwrite = true } = {}) {
57
57
  * @returns {Promise<boolean>} true if something was deleted, false if it didn't exist.
58
58
  */
59
59
  export async function deleteDir(p) {
60
+ if (!(await exists(p))) return false;
60
61
  try {
61
62
  await fs.rm(p, { recursive: true, force: true });
62
63
  return true;
@@ -66,5 +67,10 @@ export async function deleteDir(p) {
66
67
  }
67
68
 
68
69
  export async function readJson(p) {
69
- return JSON.parse(await fs.readFile(p, 'utf8'));
70
+ try {
71
+ return JSON.parse(await fs.readFile(p, 'utf8'));
72
+ } catch (e) {
73
+ const reason = e.code === 'ENOENT' ? 'file not found' : e.message;
74
+ throw new Error(`Failed to read ${p}: ${reason}`);
75
+ }
70
76
  }
@@ -4,9 +4,22 @@ import { IDES, allIdeIds } from './ides.js';
4
4
  import { warn } from './ui.js';
5
5
 
6
6
  export async function pickIdes({ preselected } = {}) {
7
+ const validIds = new Set(allIdeIds());
7
8
  const isTTY = process.stdin.isTTY && process.stdout.isTTY;
9
+
10
+ // Filter and validate preselected IDs.
11
+ let chosen = preselected?.length ? preselected : null;
12
+ if (chosen) {
13
+ const invalid = chosen.filter((id) => !validIds.has(id));
14
+ if (invalid.length) {
15
+ warn(`Unknown IDE(s) ignored: ${invalid.join(', ')}. Valid: ${allIdeIds().join(', ')}`);
16
+ chosen = chosen.filter((id) => validIds.has(id));
17
+ }
18
+ if (!chosen.length) chosen = null; // fall through to defaults
19
+ }
20
+
8
21
  if (!isTTY) {
9
- const chosen = preselected?.length ? preselected : allIdeIds();
22
+ if (!chosen) chosen = allIdeIds();
10
23
  warn(`Non-interactive terminal — configuring IDEs: ${chosen.join(', ')}`);
11
24
  return chosen;
12
25
  }
@@ -18,7 +31,7 @@ export async function pickIdes({ preselected } = {}) {
18
31
  ...IDES.map((i) => ({
19
32
  name: i.label,
20
33
  value: i.id,
21
- checked: preselected ? preselected.includes(i.id) : i.id === 'claude-code',
34
+ checked: chosen ? chosen.includes(i.id) : i.id === 'claude-code',
22
35
  })),
23
36
  new Separator(),
24
37
  ],
@@ -26,10 +39,3 @@ export async function pickIdes({ preselected } = {}) {
26
39
  });
27
40
  return answer;
28
41
  }
29
-
30
- export async function confirm(message, def = true) {
31
- const isTTY = process.stdin.isTTY && process.stdout.isTTY;
32
- if (!isTTY) return def;
33
- const { default: confirmPrompt } = await import('@inquirer/confirm');
34
- return confirmPrompt({ message, default: def });
35
- }
package/src/lib/ui.js CHANGED
@@ -9,7 +9,6 @@ export const c = {
9
9
  yellow: wrap('33'),
10
10
  cyan: wrap('36'),
11
11
  red: wrap('31'),
12
- gray: wrap('90'),
13
12
  };
14
13
 
15
14
  export const log = (...a) => console.log(...a);
@@ -1,4 +1,5 @@
1
1
  {
2
+ "_comment": "The Playwright MCP version is pinned for stability. To upgrade: npx @playwright/mcp@latest --version, then update the version below and run 'npx @aksp/opencrew update' to refresh.",
2
3
  "mcpServers": {
3
4
  "playwright": {
4
5
  "command": "npx",
@@ -36,7 +36,7 @@ When the user types `/opencrew` or asks for the menu, present an interactive sel
36
36
  **Primary menu (first question):**
37
37
  - **Create a new crew** — Describe what you need and I'll build a crew for you
38
38
  - **Run an existing crew** — Execute a crew's pipeline
39
- - **My crews** — View, edit, or delete your crews
39
+ - **My crews** — View, edit, repair, or delete your crews
40
40
  - **More options** — Skills, company profile, settings, and help
41
41
 
42
42
  If the user selects "More options", present a second selector the same way:
@@ -1 +1 @@
1
- 1.2.0
1
+ 1.2.2
@@ -4,8 +4,7 @@
4
4
  "isolated": false,
5
5
  "userDataDir": "_opencrew/_browser_profile",
6
6
  "launchOptions": {
7
- "headless": true,
8
- "channel": "chrome"
7
+ "headless": true
9
8
  }
10
9
  }
11
10
  }
@@ -251,7 +251,7 @@ target_formats: # content crews only; empty list for others
251
251
 
252
252
  The `crew_code` must be a short, URL-safe slug derived from the crew's purpose (e.g., `content-calendar`, `competitor-tracker`, `lead-notify`).
253
253
 
254
- **CRITICAL — Name uniqueness:** The `crew_code` MUST NEVER match any existing folder name in `crews/`. You will receive a list of existing crew names. If the slug you derive already exists, append a numeric suffix (`-2`, `-3`, etc.) to guarantee uniqueness. Never reuse an existing crew folder name — doing so would overwrite another crew's files.
254
+ **CRITICAL — Name uniqueness:** The `crew_code` MUST NEVER match any existing folder name in `crews/`. Before finalizing, run `ls crews/ 2>/dev/null` (Bash) to list existing crew directories. If the slug you derive matches an existing folder, append a numeric suffix (`-2`, `-3`, etc.) until it is unique. Never reuse an existing crew folder name — doing so would overwrite another crew's files.
255
255
 
256
256
  ---
257
257
 
@@ -28,7 +28,23 @@ Before starting execution:
28
28
  when `false`, skip ALL of them; never create, update, or delete
29
29
  `crews/{name}/state.json`.
30
30
 
31
- > **Note on language**: the section headers below (`## Estilo de Escrita`, `## Design Visual`, etc.) and the `runs.md` table columns (`Data | Run ID | Tema | ...`) are fixed PT-BR structural labels, not translated per Output Language — opencrew's primary supported audience is PT-BR (see AGENTS.md → Language Handling). Only the *content* written under these headers follows the user's preferred language.
31
+ > **Note on language**: The structural labels listed below are **fixed PT-BR** and must
32
+ > never be translated — opencrew's primary supported audience is PT-BR (see AGENTS.md →
33
+ > Language Handling). Only the *content* written under these headers follows the user's
34
+ > preferred language.
35
+ >
36
+ > | Fixed PT-BR header | Location | Purpose |
37
+ > |---|---|---|
38
+ > | `## Estilo de Escrita` | `memories.md` | Writing style rules accumulated per crew |
39
+ > | `## Design Visual` | `memories.md` | Visual design preferences per crew |
40
+ > | `## Estrutura de Conteúdo` | `memories.md` | Content structure rules per crew |
41
+ > | `## Proibições Explícitas` | `memories.md` | User bans and hard blocks per crew |
42
+ > | `## Técnico (específico do crew)` | `memories.md` | Technical crew-specific settings |
43
+ > | `Data \| Run ID \| Tema \| Output \| Resultado` | `runs.md` | Run history table columns |
44
+ >
45
+ > When adding new structural sections to `memories.md` or `runs.md`, keep headers in PT-BR
46
+ > unless the user base expands beyond PT-BR — at that point, discuss a migration strategy
47
+ > (e.g. i18n key mapping) rather than mixing languages in a single file.
32
48
 
33
49
  1b. **Memory format migration** — After loading `memories.md`, check whether it uses the new format by scanning for the `## Estilo de Escrita` section header:
34
50
  ```bash
@@ -20,10 +20,11 @@ You are the Skills Engine. Your job is to manage skill integrations for opencrew
20
20
  ### Catalog URL Resolution
21
21
 
22
22
  When fetching skill files from the catalog, resolve the base URL in this order:
23
- 1. Read `skills/catalog.json` (installed locally during init/update) → use its `baseUrl` field
24
- 2. If not available, default to:
23
+ 1. Check the `OPENCREW_CATALOG_URL` environment variable — if set, use it directly as the base URL. This allows forks to point to their own catalog without editing catalog.json.
24
+ 2. Read `skills/catalog.json` (installed locally during init/update) → use its `baseUrl` field
25
+ 3. If not available, default to:
25
26
  `https://raw.githubusercontent.com/alberthpalhares/opencrew/main/templates/skills`
26
- 3. Append `/<name>/SKILL.md` (or other file paths) to the base URL
27
+ 4. Append `/<name>/SKILL.md` (or other file paths) to the base URL
27
28
 
28
29
  ## How Skills Are Detected
29
30
 
@@ -122,21 +123,21 @@ For the full SKILL.md specification, see `skills/opencrew-skill-creator/referenc
122
123
  - If fetch fails (404 or network error) → **ERROR**: "Skill '<name>' not found in the skills catalog."
123
124
  - Do NOT proceed if the SKILL.md cannot be fetched.
124
125
 
125
- 3. **Create the skill directory**:
126
+ 5. **Create the skill directory**:
126
127
  ```
127
128
  skills/<name>/
128
129
  ```
129
130
 
130
- 4. **Write SKILL.md** to `skills/<name>/SKILL.md`
131
+ 6. **Write SKILL.md** to `skills/<name>/SKILL.md`
131
132
 
132
- 5. **Fetch additional files** (if the skill requires them):
133
+ 7. **Fetch additional files** (if the skill requires them):
133
134
  - If the SKILL.md frontmatter has `script.path` → fetch the script file from:
134
135
  `{baseUrl}/<name>/{script.path}`
135
136
  Create subdirectories (e.g., `scripts/`) as needed.
136
137
  - If the skill has a `references/` directory mentioned → fetch those files too.
137
138
  - If the skill has an `assets/` directory mentioned → fetch those files too.
138
139
 
139
- 6. **Parse SKILL.md frontmatter** and check requirements:
140
+ 8. **Parse SKILL.md frontmatter** and check requirements:
140
141
 
141
142
  #### a. Environment Variables (if `env:` is present)
142
143
 
@@ -169,7 +170,7 @@ For the full SKILL.md specification, see `skills/opencrew-skill-creator/referenc
169
170
  1. Yes, overwrite
170
171
  2. No, keep existing
171
172
  If "No" → skip MCP configuration but still complete installation
172
- 3. Use the values already collected in step 6.a for each env var in the skill's `env`
173
+ 3. Use the values already collected in step 8.a for each env var in the skill's `env`
173
174
  array (do not ask again).
174
175
  4. Add to `mcpServers`:
175
176
  ```json
@@ -187,7 +188,7 @@ For the full SKILL.md specification, see `skills/opencrew-skill-creator/referenc
187
188
 
188
189
  1. Read `.claude/settings.local.json` (create with `{"mcpServers": {}}` if it doesn't exist)
189
190
  2. Check for `server_name` conflict (same as stdio above)
190
- 3. Use the values already collected in step 6.a for each env var in the skill's `env`
191
+ 3. Use the values already collected in step 8.a for each env var in the skill's `env`
191
192
  array (do not ask again).
192
193
  4. Build the mcpServers entry:
193
194
  - Start with `{ "type": "http", "url": "{url}" }`
@@ -208,7 +209,7 @@ For the full SKILL.md specification, see `skills/opencrew-skill-creator/referenc
208
209
  #### d. Script Setup (if `type: script` or `type: hybrid`)
209
210
 
210
211
  1. Verify the script file exists at `skills/<name>/{script.path}`
211
- - If missing and wasn't fetched in step 5 → **ERROR**: "Script file not found. Installation may be incomplete."
212
+ - If missing and wasn't fetched in step 7 → **ERROR**: "Script file not found. Installation may be incomplete."
212
213
  2. If the skill lists dependencies in `script.dependencies`:
213
214
  - For `runtime: node` → run: `npm install {packages}`
214
215
  - For `runtime: python` → run: `pip install {packages}`
@@ -219,7 +220,7 @@ For the full SKILL.md specification, see `skills/opencrew-skill-creator/referenc
219
220
 
220
221
  No additional setup needed. The skill is fully defined by its SKILL.md instructions.
221
222
 
222
- 7. **Confirm installation**:
223
+ 9. **Confirm installation**:
223
224
  "✅ {name} installed! Crews can now use `{name}` in their skills list."
224
225
 
225
226
  ### 3. Create a Custom Skill
@@ -300,7 +301,7 @@ before the pipeline begins (fail fast).
300
301
  d. **Verify env vars** (if `env:` is present):
301
302
  - Check each variable in `.env`
302
303
  - If any are missing → ask the user conversationally, right here in chat, the same
303
- way as Operation 2, step 6.a (never point them to the `.env` file to edit it
304
+ way as Operation 2, step 8.a (never point them to the `.env` file to edit it
304
305
  themselves). If they provide a value, write it to `.env` and continue. If they
305
306
  skip, warn: "⚠️ Skill '{skill}' is missing environment variable(s): {list}. It may
306
307
  not work correctly." — but do NOT block pipeline execution either way.