@haposoft/cafekit 0.10.0 → 0.10.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/bin/install.js +3 -1
- package/bin/lib/context.js +4 -1
- package/bin/lib/skill-deps.js +173 -0
- package/bin/phases/post-install.js +18 -41
- package/bin/phases/skills-setup.js +119 -0
- package/package.json +1 -1
- package/src/claude/skills/docx/scripts/package.json +9 -0
- package/src/claude/skills/docx/scripts/requirements.txt +5 -0
- package/src/claude/skills/pdf/scripts/requirements.txt +17 -0
- package/src/claude/skills/pptx/scripts/package.json +14 -0
- package/src/claude/skills/pptx/scripts/requirements.txt +4 -0
package/bin/install.js
CHANGED
|
@@ -34,6 +34,7 @@ const { installOpenCodeRuntime } = require('./phases/opencode-runtime');
|
|
|
34
34
|
const { writePlatformVersionMetadata } = require('./phases/write-metadata');
|
|
35
35
|
const { ensureGitignore } = require('./phases/root-config');
|
|
36
36
|
const { runPostInstall } = require('./phases/post-install');
|
|
37
|
+
const { setupSkillDeps } = require('./phases/skills-setup');
|
|
37
38
|
const { printSummary } = require('./phases/summary');
|
|
38
39
|
|
|
39
40
|
/** Install a single platform: payload + runtime + metadata, under one spinner. */
|
|
@@ -85,7 +86,7 @@ async function main() {
|
|
|
85
86
|
|
|
86
87
|
try {
|
|
87
88
|
ctx = buildContext(process.argv, `${Date.now()}`);
|
|
88
|
-
ctx.ui.intro(`${ctx.ui.pc.bgCyan(ctx.ui.pc.black(' CafeKit '))}
|
|
89
|
+
ctx.ui.intro(`${ctx.ui.pc.bgCyan(ctx.ui.pc.black(' CafeKit '))}Installer v${packageJson.version} · Multi-platform SDD`);
|
|
89
90
|
if (got.reclaimed) ctx.ui.info('Reclaimed a stale install lock from a dead process.');
|
|
90
91
|
|
|
91
92
|
await resolvePlatforms(ctx);
|
|
@@ -108,6 +109,7 @@ async function main() {
|
|
|
108
109
|
|
|
109
110
|
ensureGitignore(ctx);
|
|
110
111
|
await runPostInstall(ctx);
|
|
112
|
+
await setupSkillDeps(ctx);
|
|
111
113
|
printSummary(ctx);
|
|
112
114
|
|
|
113
115
|
if (!ctx.dryRun) backup.prune(3);
|
package/bin/lib/context.js
CHANGED
|
@@ -183,7 +183,8 @@ function parseInstallerArgs(argv) {
|
|
|
183
183
|
const args = {
|
|
184
184
|
forceOverwrite: false,
|
|
185
185
|
dryRun: false,
|
|
186
|
-
yes: false
|
|
186
|
+
yes: false,
|
|
187
|
+
withSkillsDeps: false
|
|
187
188
|
};
|
|
188
189
|
|
|
189
190
|
for (let i = 2; i < argv.length; i++) {
|
|
@@ -195,6 +196,8 @@ function parseInstallerArgs(argv) {
|
|
|
195
196
|
args.dryRun = true;
|
|
196
197
|
} else if (arg === '--yes' || arg === '-y') {
|
|
197
198
|
args.yes = true;
|
|
199
|
+
} else if (arg === '--with-skills-deps') {
|
|
200
|
+
args.withSkillsDeps = true;
|
|
198
201
|
}
|
|
199
202
|
}
|
|
200
203
|
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Skill dependency setup primitives (cross-platform, no sudo).
|
|
3
|
+
*
|
|
4
|
+
* Provides the building blocks the skills-setup phase uses to provision the
|
|
5
|
+
* parts that a plain file-copy install can't: a Python venv + pip requirements,
|
|
6
|
+
* skill-local npm installs, and the Puppeteer Chromium binary. System binaries
|
|
7
|
+
* and global npm tools are NOT auto-installed here — they are detected and the
|
|
8
|
+
* phase prints guidance.
|
|
9
|
+
*
|
|
10
|
+
* All commands run via spawnSync (blocking is fine for a CLI). Detection is
|
|
11
|
+
* silent; install callers decide how to surface progress/failures.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const fs = require('fs');
|
|
15
|
+
const path = require('path');
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
17
|
+
|
|
18
|
+
const isWindows = process.platform === 'win32';
|
|
19
|
+
|
|
20
|
+
/** Run a command, return { ok, stdout, stderr, status }. Never throws. */
|
|
21
|
+
function run(cmd, args, opts = {}) {
|
|
22
|
+
const r = spawnSync(cmd, args, { encoding: 'utf8', ...opts });
|
|
23
|
+
return {
|
|
24
|
+
ok: !r.error && r.status === 0,
|
|
25
|
+
status: r.status,
|
|
26
|
+
stdout: r.stdout || '',
|
|
27
|
+
stderr: r.stderr || ''
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Is a command available on PATH? */
|
|
32
|
+
function hasCmd(name) {
|
|
33
|
+
const probe = isWindows ? run('where', [name]) : run('which', [name]);
|
|
34
|
+
return probe.ok;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Find a usable Python launcher ('python3' or 'python'), or null. */
|
|
38
|
+
function findPython() {
|
|
39
|
+
for (const cmd of ['python3', 'python']) {
|
|
40
|
+
if (hasCmd(cmd) && run(cmd, ['--version']).ok) return cmd;
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function venvDir(skillsDir) {
|
|
46
|
+
return path.join(skillsDir, '.venv');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Path to the venv's python interpreter (OS-specific). */
|
|
50
|
+
function venvPython(skillsDir) {
|
|
51
|
+
return isWindows
|
|
52
|
+
? path.join(venvDir(skillsDir), 'Scripts', 'python.exe')
|
|
53
|
+
: path.join(venvDir(skillsDir), 'bin', 'python3');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function venvValid(skillsDir) {
|
|
57
|
+
return fs.existsSync(venvPython(skillsDir));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Create the venv (idempotent: skips if already valid). Returns true on success. */
|
|
61
|
+
function createVenv(skillsDir, py) {
|
|
62
|
+
if (venvValid(skillsDir)) return true;
|
|
63
|
+
fs.mkdirSync(skillsDir, { recursive: true });
|
|
64
|
+
return run(py, ['-m', 'venv', venvDir(skillsDir)]).ok;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Upgrade pip inside the venv (best-effort). */
|
|
68
|
+
function pipUpgrade(skillsDir) {
|
|
69
|
+
return run(venvPython(skillsDir), ['-m', 'pip', 'install', '--upgrade', 'pip', '--prefer-binary']).ok;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Install a requirements.txt into the venv (wheels first). Returns true on success. */
|
|
73
|
+
function pipInstall(skillsDir, requirementsPath) {
|
|
74
|
+
return run(venvPython(skillsDir), [
|
|
75
|
+
'-m', 'pip', 'install', '-r', requirementsPath, '--prefer-binary'
|
|
76
|
+
]).ok;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** `npm install` in a skill scripts dir. Returns true on success. */
|
|
80
|
+
function npmInstall(dir) {
|
|
81
|
+
return run('npm', ['install', '--no-audit', '--no-fund'], { cwd: dir }).ok;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Download the Puppeteer Chromium browser for chrome-devtools. */
|
|
85
|
+
function installChromium(scriptsDir) {
|
|
86
|
+
return run('npx', ['--yes', 'puppeteer', 'browsers', 'install', 'chrome'], { cwd: scriptsDir }).ok;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Does a skill scripts dir declare a given npm dependency? */
|
|
90
|
+
function pkgHasDep(scriptsDir, name) {
|
|
91
|
+
try {
|
|
92
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(scriptsDir, 'package.json'), 'utf8'));
|
|
93
|
+
return Boolean((pkg.dependencies && pkg.dependencies[name]) || (pkg.devDependencies && pkg.devDependencies[name]));
|
|
94
|
+
} catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Download the Playwright Chromium browser (for the pptx html2pptx workflow). */
|
|
100
|
+
function installPlaywrightBrowser(scriptsDir) {
|
|
101
|
+
return run('npx', ['--yes', 'playwright', 'install', 'chromium'], { cwd: scriptsDir }).ok;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* List installed skills that ship a runnable scripts/requirements.txt
|
|
106
|
+
* (excludes the tests/ requirements). Returns [{ skill, file }].
|
|
107
|
+
*/
|
|
108
|
+
function collectRequirements(skillsDir) {
|
|
109
|
+
const out = [];
|
|
110
|
+
if (!fs.existsSync(skillsDir)) return out;
|
|
111
|
+
for (const skill of fs.readdirSync(skillsDir)) {
|
|
112
|
+
const file = path.join(skillsDir, skill, 'scripts', 'requirements.txt');
|
|
113
|
+
if (fs.existsSync(file)) out.push({ skill, file });
|
|
114
|
+
}
|
|
115
|
+
return out;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* List installed skills with a scripts/package.json that still needs installing
|
|
120
|
+
* (no node_modules present). Returns [{ skill, dir }].
|
|
121
|
+
*/
|
|
122
|
+
function collectSkillPackages(skillsDir) {
|
|
123
|
+
const out = [];
|
|
124
|
+
if (!fs.existsSync(skillsDir)) return out;
|
|
125
|
+
for (const skill of fs.readdirSync(skillsDir)) {
|
|
126
|
+
const dir = path.join(skillsDir, skill, 'scripts');
|
|
127
|
+
if (fs.existsSync(path.join(dir, 'package.json')) && !fs.existsSync(path.join(dir, 'node_modules'))) {
|
|
128
|
+
out.push({ skill, dir });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return out;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** External binaries some skills shell out to — detected, never auto-installed. */
|
|
135
|
+
const SYSTEM_TOOLS = [
|
|
136
|
+
{ cmd: 'ffmpeg', why: 'ai-multimodal (audio/video)', brew: 'brew install ffmpeg', apt: 'sudo apt install ffmpeg', win: 'winget install ffmpeg' },
|
|
137
|
+
{ cmd: 'pdftotext', why: 'pdf (text extraction, poppler)', brew: 'brew install poppler', apt: 'sudo apt install poppler-utils', win: 'choco install poppler' },
|
|
138
|
+
{ cmd: 'rsvg-convert', why: 'generate-graph (PNG export, librsvg)', brew: 'brew install librsvg', apt: 'sudo apt install librsvg2-bin', win: 'choco install rsvg-convert' },
|
|
139
|
+
{ cmd: 'tesseract', why: 'pdf/ai-multimodal (OCR)', brew: 'brew install tesseract', apt: 'sudo apt install tesseract-ocr', win: 'choco install tesseract' }
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
/** Global npm CLIs some skills expect — detected, never auto-installed. */
|
|
143
|
+
const GLOBAL_NPM = [
|
|
144
|
+
{ cmd: 'agent-browser', pkg: 'agent-browser', why: 'agent-browser skill' }
|
|
145
|
+
];
|
|
146
|
+
|
|
147
|
+
/** Per-OS install hint for a SYSTEM_TOOLS entry. */
|
|
148
|
+
function systemHint(tool) {
|
|
149
|
+
if (process.platform === 'darwin') return tool.brew;
|
|
150
|
+
if (process.platform === 'win32') return tool.win;
|
|
151
|
+
return tool.apt;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
isWindows,
|
|
156
|
+
hasCmd,
|
|
157
|
+
findPython,
|
|
158
|
+
venvDir,
|
|
159
|
+
venvPython,
|
|
160
|
+
venvValid,
|
|
161
|
+
createVenv,
|
|
162
|
+
pipUpgrade,
|
|
163
|
+
pipInstall,
|
|
164
|
+
npmInstall,
|
|
165
|
+
installChromium,
|
|
166
|
+
collectRequirements,
|
|
167
|
+
collectSkillPackages,
|
|
168
|
+
pkgHasDep,
|
|
169
|
+
installPlaywrightBrowser,
|
|
170
|
+
SYSTEM_TOOLS,
|
|
171
|
+
GLOBAL_NPM,
|
|
172
|
+
systemHint
|
|
173
|
+
};
|
|
@@ -1,38 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Phase: post-install runtime configuration.
|
|
3
3
|
*
|
|
4
|
-
* OpenCode model, optional Gemini
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* OpenCode model, optional Gemini API key, and addressing config. Interactive
|
|
5
|
+
* prompts use ctx.ui (clack); in non-interactive/dry-run they are skipped via
|
|
6
|
+
* fallbacks so spawned/CI runs never hang. Writes (.env, CLAUDE.md,
|
|
7
7
|
* opencode.json) happen only when a value is actually provided.
|
|
8
|
+
*
|
|
9
|
+
* Note: CafeKit no longer installs the `gemini-cli` (the upstream package was
|
|
10
|
+
* removed). We only configure GEMINI_API_KEY, which the SDK-based skills
|
|
11
|
+
* (e.g. hapo:ai-multimodal) read directly.
|
|
8
12
|
*/
|
|
9
13
|
|
|
10
14
|
const fs = require('fs');
|
|
11
15
|
const path = require('path');
|
|
12
|
-
const { execSync } = require('child_process');
|
|
13
16
|
const { PLATFORMS } = require('../lib/context');
|
|
14
17
|
const { setupOpenCodeModel } = require('../lib/opencode-install');
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return true;
|
|
20
|
-
} catch {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function installGeminiCLI(ctx) {
|
|
26
|
-
ctx.ui.startSpinner('Installing gemini-cli...');
|
|
27
|
-
try {
|
|
28
|
-
execSync('npm install -g @google/gemini-cli', { stdio: 'ignore' });
|
|
29
|
-
ctx.ui.stopSpinner('gemini-cli installed');
|
|
30
|
-
return true;
|
|
31
|
-
} catch {
|
|
32
|
-
ctx.ui.stopSpinner('Could not install gemini-cli automatically');
|
|
33
|
-
ctx.ui.warn('Run manually: npm install -g @google/gemini-cli');
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
19
|
+
/** Human assistant name for the active platform(s). */
|
|
20
|
+
function assistantName(ctx) {
|
|
21
|
+
return ctx.platforms.includes('claude') ? 'Claude Code' : 'OpenCode';
|
|
36
22
|
}
|
|
37
23
|
|
|
38
24
|
function configureGeminiKey(ctx, apiKey, platforms) {
|
|
@@ -52,19 +38,9 @@ function configureGeminiKey(ctx, apiKey, platforms) {
|
|
|
52
38
|
}
|
|
53
39
|
}
|
|
54
40
|
|
|
55
|
-
async function
|
|
56
|
-
if (checkGeminiCLI()) {
|
|
57
|
-
ctx.ui.info('gemini-cli already installed');
|
|
58
|
-
} else if (ctx.interactive) {
|
|
59
|
-
const yes = await ctx.ui.confirm(
|
|
60
|
-
{ message: 'Install gemini-cli? (enables hapo:inspect ext mode)', initialValue: false },
|
|
61
|
-
false
|
|
62
|
-
);
|
|
63
|
-
if (yes === true) installGeminiCLI(ctx);
|
|
64
|
-
}
|
|
65
|
-
|
|
41
|
+
async function setupGeminiKey(ctx) {
|
|
66
42
|
const apiKey = await ctx.ui.text(
|
|
67
|
-
{ message: 'Gemini API key (Enter to skip)', placeholder: 'aistudio.google.com/apikey' },
|
|
43
|
+
{ message: 'Gemini API key for AI skills (Enter to skip)', placeholder: 'aistudio.google.com/apikey' },
|
|
68
44
|
''
|
|
69
45
|
);
|
|
70
46
|
if (apiKey && typeof apiKey === 'string' && apiKey.trim()) {
|
|
@@ -79,10 +55,11 @@ function configureAddressing(ctx, userAddress) {
|
|
|
79
55
|
return;
|
|
80
56
|
}
|
|
81
57
|
|
|
58
|
+
const name = assistantName(ctx);
|
|
82
59
|
let content = fs.readFileSync(claudeMdFile, 'utf8');
|
|
83
60
|
const addressingSection = `## Addressing (Context Overflow Indicator)
|
|
84
61
|
|
|
85
|
-
|
|
62
|
+
${name} always addresses the user as "${userAddress}" throughout the conversation. If it stops doing so, it is a sign the context has been compacted/truncated — tell the user to consider \`/clear\`.`;
|
|
86
63
|
|
|
87
64
|
// Idempotent: replace existing section (shared marker) or append.
|
|
88
65
|
const regex = /##[^\n]*Context Overflow Indicator[^\n]*[\s\S]*?(?=\n##|\n*$)/;
|
|
@@ -91,14 +68,14 @@ The AI always addresses the user as "${userAddress}" throughout the conversation
|
|
|
91
68
|
: `${content.endsWith('\n') ? content : `${content}\n`}\n${addressingSection}\n`;
|
|
92
69
|
|
|
93
70
|
fs.writeFileSync(claudeMdFile, content, 'utf8');
|
|
94
|
-
ctx.ui.success(
|
|
71
|
+
ctx.ui.success(`${name} will call you "${userAddress}"`);
|
|
95
72
|
}
|
|
96
73
|
|
|
97
74
|
async function setupAddressing(ctx) {
|
|
98
75
|
if (!ctx.platforms.includes('claude')) return;
|
|
99
76
|
|
|
100
77
|
const answer = await ctx.ui.text(
|
|
101
|
-
{ message: 'How should the AI address you?
|
|
78
|
+
{ message: 'How should the AI address you?', placeholder: 'e.g. boss, sir — Enter to skip' },
|
|
102
79
|
''
|
|
103
80
|
);
|
|
104
81
|
if (ctx.ui.isCancel(answer)) return;
|
|
@@ -126,7 +103,7 @@ async function runPostInstall(ctx) {
|
|
|
126
103
|
await setupOpenCodeModel(ctx.platforms, ctx.results);
|
|
127
104
|
}
|
|
128
105
|
|
|
129
|
-
await
|
|
106
|
+
await setupGeminiKey(ctx);
|
|
130
107
|
await setupAddressing(ctx);
|
|
131
108
|
|
|
132
109
|
// Re-record CLAUDE.md baseline so the installer-managed file (template +
|
|
@@ -138,4 +115,4 @@ async function runPostInstall(ctx) {
|
|
|
138
115
|
return ctx;
|
|
139
116
|
}
|
|
140
117
|
|
|
141
|
-
module.exports = {
|
|
118
|
+
module.exports = { configureGeminiKey, configureAddressing, runPostInstall };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Phase: skill dependency setup (opt-in, safe cross-platform).
|
|
3
|
+
*
|
|
4
|
+
* Provisions the parts a file-copy install can't:
|
|
5
|
+
* - Python venv at <skillsDir>/.venv + pip install each skill's requirements
|
|
6
|
+
* - skill-local `npm install` (scripts/package.json without node_modules)
|
|
7
|
+
* - Puppeteer Chromium binary (chrome-devtools)
|
|
8
|
+
*
|
|
9
|
+
* System binaries (ffmpeg, poppler, librsvg, tesseract) and global npm CLIs are
|
|
10
|
+
* NOT auto-installed — they are detected and printed as guidance. All steps are
|
|
11
|
+
* non-fatal: a failure never breaks the install.
|
|
12
|
+
*
|
|
13
|
+
* Runs only when opted in: `--with-skills-deps`, or an interactive confirm.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { PLATFORMS } = require('../lib/context');
|
|
19
|
+
const dep = require('../lib/skill-deps');
|
|
20
|
+
|
|
21
|
+
async function shouldRun(ctx) {
|
|
22
|
+
if (ctx.dryRun) return false;
|
|
23
|
+
if (ctx.options.withSkillsDeps) return true;
|
|
24
|
+
if (!ctx.interactive) return false;
|
|
25
|
+
const yes = await ctx.ui.confirm(
|
|
26
|
+
{ message: 'Install skill dependencies now? (Python venv + pip + Chromium — slower)', initialValue: false },
|
|
27
|
+
false
|
|
28
|
+
);
|
|
29
|
+
return yes === true;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Provision Python venv + pip requirements for a platform's skills. */
|
|
33
|
+
function setupPython(ctx, skillsDir) {
|
|
34
|
+
const reqs = dep.collectRequirements(skillsDir);
|
|
35
|
+
if (reqs.length === 0) return;
|
|
36
|
+
|
|
37
|
+
const py = dep.findPython();
|
|
38
|
+
if (!py) {
|
|
39
|
+
ctx.ui.warn('Python 3 not found — skipped venv. Install Python 3, then re-run with --with-skills-deps.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
ctx.ui.startSpinner('Creating Python venv...');
|
|
44
|
+
if (!dep.createVenv(skillsDir, py)) {
|
|
45
|
+
ctx.ui.stopSpinner('Could not create Python venv');
|
|
46
|
+
ctx.ui.warn(`venv creation failed at ${dep.venvDir(skillsDir)} — check your Python install.`);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
dep.pipUpgrade(skillsDir);
|
|
50
|
+
ctx.ui.stopSpinner(`Python venv ready (${dep.venvDir(skillsDir)})`);
|
|
51
|
+
|
|
52
|
+
for (const { skill, file } of reqs) {
|
|
53
|
+
ctx.ui.startSpinner(`Installing Python deps: ${skill}...`);
|
|
54
|
+
const ok = dep.pipInstall(skillsDir, file);
|
|
55
|
+
ctx.ui.stopSpinner(ok ? `${skill}: Python deps installed` : `${skill}: some Python deps failed`);
|
|
56
|
+
if (!ok) {
|
|
57
|
+
ctx.ui.warn(`pip failed for ${skill}. Retry: "${dep.venvPython(skillsDir)}" -m pip install -r "${file}"`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Run skill-local npm installs + Chromium for a platform's skills. */
|
|
63
|
+
function setupNode(ctx, skillsDir) {
|
|
64
|
+
for (const { skill, dir } of dep.collectSkillPackages(skillsDir)) {
|
|
65
|
+
ctx.ui.startSpinner(`Installing npm deps: ${skill}...`);
|
|
66
|
+
const ok = dep.npmInstall(dir);
|
|
67
|
+
ctx.ui.stopSpinner(ok ? `${skill}: npm deps installed` : `${skill}: npm install failed`);
|
|
68
|
+
if (!ok) {
|
|
69
|
+
ctx.ui.warn(`npm install failed in ${dir} — retry manually.`);
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
// Skills using Playwright (e.g. pptx html2pptx) need a browser binary too.
|
|
73
|
+
if (dep.pkgHasDep(dir, 'playwright')) {
|
|
74
|
+
ctx.ui.startSpinner(`Downloading Playwright browser for ${skill}...`);
|
|
75
|
+
const pw = dep.installPlaywrightBrowser(dir);
|
|
76
|
+
ctx.ui.stopSpinner(pw ? `${skill}: Playwright browser ready` : `${skill}: Playwright browser skipped`);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const chromeScripts = path.join(skillsDir, 'chrome-devtools', 'scripts');
|
|
81
|
+
if (fs.existsSync(path.join(chromeScripts, 'package.json'))) {
|
|
82
|
+
ctx.ui.startSpinner('Downloading Chromium for chrome-devtools...');
|
|
83
|
+
const ok = dep.installChromium(chromeScripts);
|
|
84
|
+
ctx.ui.stopSpinner(ok ? 'Chromium ready' : 'Chromium download skipped/failed');
|
|
85
|
+
if (!ok) ctx.ui.warn('Chromium not downloaded — chrome-devtools may prompt on first use.');
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Detect (don't install) system binaries + global npm CLIs and print guidance. */
|
|
90
|
+
function guideManual(ctx) {
|
|
91
|
+
const missingTools = dep.SYSTEM_TOOLS.filter((t) => !dep.hasCmd(t.cmd));
|
|
92
|
+
const missingNpm = dep.GLOBAL_NPM.filter((g) => !dep.hasCmd(g.cmd));
|
|
93
|
+
if (missingTools.length === 0 && missingNpm.length === 0) return;
|
|
94
|
+
|
|
95
|
+
const lines = [];
|
|
96
|
+
for (const t of missingTools) lines.push(`${t.cmd} — ${t.why}\n ${dep.systemHint(t)}`);
|
|
97
|
+
for (const g of missingNpm) lines.push(`${g.cmd} — ${g.why}\n npm install -g ${g.pkg}`);
|
|
98
|
+
ctx.ui.note(lines.join('\n'), 'Optional tools to install manually');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async function setupSkillDeps(ctx) {
|
|
102
|
+
if (!(await shouldRun(ctx))) {
|
|
103
|
+
if (!ctx.dryRun) {
|
|
104
|
+
ctx.ui.info('Skill dependencies skipped — run later with: npx @haposoft/cafekit --with-skills-deps');
|
|
105
|
+
}
|
|
106
|
+
return ctx;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
for (const key of ctx.platforms) {
|
|
110
|
+
const skillsDir = PLATFORMS[key].skillsDir;
|
|
111
|
+
setupPython(ctx, skillsDir);
|
|
112
|
+
setupNode(ctx, skillsDir);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
guideManual(ctx);
|
|
116
|
+
return ctx;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
module.exports = { setupSkillDeps };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@haposoft/cafekit",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.2",
|
|
4
4
|
"description": "Claude Code-first spec-driven workflow for AI coding assistants. Bundles CafeKit hapo: skills, runtime hooks, agents, and installer scaffolding.",
|
|
5
5
|
"author": "Haposoft <nghialt@haposoft.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# PDF skill dependencies (installed by `cafekit --with-skills-deps`)
|
|
2
|
+
# System tools also needed: poppler (pdftotext/pdf2image), tesseract (OCR).
|
|
3
|
+
|
|
4
|
+
# Read / write / split / merge
|
|
5
|
+
pypdf>=4.0.0
|
|
6
|
+
|
|
7
|
+
# Text + table extraction
|
|
8
|
+
pdfplumber>=0.11.0
|
|
9
|
+
pandas>=2.0.0
|
|
10
|
+
|
|
11
|
+
# Create PDFs
|
|
12
|
+
reportlab>=4.0.0
|
|
13
|
+
|
|
14
|
+
# OCR for scanned PDFs (needs system poppler + tesseract)
|
|
15
|
+
pytesseract>=0.3.10
|
|
16
|
+
pdf2image>=1.17.0
|
|
17
|
+
Pillow>=10.0.0
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cafekit-skill-pptx",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "Node dependencies for the CafeKit pptx skill (html2pptx workflow).",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"pptxgenjs": "^3.12.0",
|
|
8
|
+
"playwright": "^1.40.0",
|
|
9
|
+
"sharp": "^0.33.0",
|
|
10
|
+
"react-icons": "^5.0.0",
|
|
11
|
+
"react": "^18.0.0",
|
|
12
|
+
"react-dom": "^18.0.0"
|
|
13
|
+
}
|
|
14
|
+
}
|