Merge pull request #331 from firecrawl/max/skill-update-notices · firecrawl/cli@eee1d59 · GitHub
Skip to content

Commit eee1d59

Browse files
authored
Merge pull request #331 from firecrawl/max/skill-update-notices
feat(skills): notify users about catalog updates
2 parents 84c8211 + 1e06fb6 commit eee1d59

17 files changed

Lines changed: 1634 additions & 36 deletions

‎README.md‎

Lines changed: 26 additions & 0 deletions

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "firecrawl-cli",
3-
"version": "1.26.10",
3+
"version": "1.26.11",
44
"publishConfig": {
55
"tag": "latest"
66
},
@@ -86,6 +86,7 @@
8686
"@inquirer/prompts": "^8.2.1",
8787
"commander": "^14.0.2",
8888
"firecrawl": "4.44.0",
89+
"string-width": "^4.2.3",
8990
"yaml": "^2.9.0",
9091
"zod-to-json-schema": "3.24.6"
9192
}

‎pnpm-lock.yaml‎

Lines changed: 38 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/__tests__/cli-argv.test.ts‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ describe('CLI argv parsing', () => {
77
const cliPath = resolve(process.cwd(), 'dist/index.js');
88
const testWithBuiltCli = existsSync(cliPath) ? it : it.skip;
99

10+
testWithBuiltCli(
11+
'skips skill notices for setup commands, not matching query words',
12+
() => {
13+
for (const [args, expected] of [
14+
[['search', 'setup', '--help'], true],
15+
[['search', 'init', '--help'], true],
16+
[['init', '--help'], false],
17+
[['setup', '--help'], false],
18+
[['-y'], false],
19+
[['--yes'], false],
20+
] as const) {
21+
const script = `
22+
require('./dist/utils/update-notice').maybeShowUpdateNotice = async () => {};
23+
require('./dist/utils/skill-update-notice').maybeShowSkillUpdateNotice = async () => console.log('SKILL_NOTICE_CHECK');
24+
require('./dist/commands/init').handleInitCommand = async () => {};
25+
process.argv = [process.execPath, ${JSON.stringify(cliPath)}, ...${JSON.stringify(args)}];
26+
require(${JSON.stringify(cliPath)});
27+
`;
28+
const result = spawnSync(process.execPath, ['-e', script], {
29+
encoding: 'utf8',
30+
});
31+
expect(result.status).toBe(0);
32+
expect(result.stdout.includes('SKILL_NOTICE_CHECK')).toBe(expected);
33+
}
34+
}
35+
);
36+
1037
testWithBuiltCli('rejects invalid PDF page caps before scraping', () => {
1138
for (const value of ['0', '10001', '2.5', '3pages']) {
1239
const result = spawnSync(

‎src/__tests__/commands/init.test.ts‎

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,22 @@ import { execSync } from 'child_process';
33
import * as skillsNative from '../../commands/skills-native';
44
import { handleInitCommand } from '../../commands/init';
55
import { CLI_SKILLS, WORKFLOW_SKILLS } from '../../commands/skills-install';
6+
import { getCatalogSkills } from '../../utils/skill-catalog';
67

78
const cliSkillFlags = `--skill ${CLI_SKILLS.join(' ')}`;
89
const workflowSkillFlags = `--skill ${WORKFLOW_SKILLS.join(' ')}`;
910

11+
vi.mock('../../utils/skill-catalog', () => ({
12+
getCatalogSkills: vi.fn(async () => [
13+
...CLI_SKILLS.map((name) => ({ name, family: 'core', files: {} })),
14+
...WORKFLOW_SKILLS.map((name) => ({
15+
name,
16+
family: 'workflows',
17+
files: {},
18+
})),
19+
]),
20+
}));
21+
1022
const { installMcpMock, getApiKeyMock, confirmMock, checkboxMock } = vi.hoisted(
1123
() => ({
1224
installMcpMock: vi.fn(),
@@ -98,6 +110,21 @@ describe('handleInitCommand', () => {
98110
);
99111
});
100112

113+
it('installs newly published workflows during non-interactive init', async () => {
114+
vi.mocked(getCatalogSkills)
115+
.mockResolvedValueOnce(
116+
CLI_SKILLS.map((name) => ({ name, family: 'core', files: {} }))
117+
)
118+
.mockResolvedValueOnce([
119+
{ name: 'firecrawl-new-workflow', family: 'workflows', files: {} },
120+
]);
121+
await handleInitCommand({ yes: true, skipInstall: true, skipAuth: true });
122+
expect(execSync).toHaveBeenCalledWith(
123+
'npx -y skills add firecrawl/skills --full-depth --global --yes --skill firecrawl-new-workflow',
124+
expect.objectContaining({ stdio: ['ignore', 'pipe', 'pipe'] })
125+
);
126+
});
127+
101128
it('summarizes interactive skills success even when npx output has no count', async () => {
102129
confirmMock
103130
.mockResolvedValueOnce(true)
@@ -141,6 +168,35 @@ describe('handleInitCommand', () => {
141168
expect(execSync).toHaveBeenCalledTimes(1);
142169
});
143170

171+
it('continues interactive workflow installation when catalog discovery fails', async () => {
172+
confirmMock
173+
.mockResolvedValueOnce(true)
174+
.mockResolvedValueOnce(false)
175+
.mockResolvedValueOnce(false);
176+
checkboxMock
177+
.mockResolvedValueOnce(['workflows'])
178+
.mockResolvedValueOnce(['firecrawl-lead-research']);
179+
vi.mocked(getCatalogSkills).mockRejectedValueOnce(new Error('HTTP 403'));
180+
await handleInitCommand({ skipInstall: true, skipAuth: true });
181+
expect(checkboxMock).toHaveBeenCalledWith(
182+
expect.objectContaining({
183+
message: 'Which workflow skills?',
184+
choices: WORKFLOW_SKILLS.map((name) => ({
185+
name,
186+
value: name,
187+
checked: true,
188+
})),
189+
})
190+
);
191+
expect(execSync).toHaveBeenCalledWith(
192+
'npx -y skills add firecrawl/skills --full-depth --global --yes --skill firecrawl-lead-research',
193+
expect.anything()
194+
);
195+
expect(console.log).toHaveBeenCalledWith(
196+
expect.stringContaining('Skills installed')
197+
);
198+
});
199+
144200
it('summarizes native interactive installation independently of its count', async () => {
145201
confirmMock
146202
.mockResolvedValueOnce(true)

‎src/__tests__/commands/setup.test.ts‎

Lines changed: 70 additions & 0 deletions

0 commit comments

Comments
 (0)