@haposoft/cafekit 0.10.0 → 0.10.3

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 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 '))} Installer v${packageJson.version} · Multi-platform SDD`);
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);
@@ -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,184 @@
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
+ /**
85
+ * Download the Puppeteer Chromium browser for chrome-devtools.
86
+ * Prefer puppeteer's own install script (cache-aware, same as its postinstall);
87
+ * fall back to the bundled @puppeteer/browsers CLI. `npx puppeteer ...` is
88
+ * avoided — puppeteer ships no `puppeteer` bin under that invocation and `--yes`
89
+ * would refetch from the registry.
90
+ */
91
+ function installChromium(scriptsDir) {
92
+ // Absolute path so it resolves regardless of the child process cwd.
93
+ const installMjs = path.resolve(scriptsDir, 'node_modules', 'puppeteer', 'install.mjs');
94
+ if (fs.existsSync(installMjs)) {
95
+ return run('node', [installMjs], { cwd: scriptsDir }).ok;
96
+ }
97
+ return run('npx', ['--yes', '@puppeteer/browsers', 'install', 'chrome'], { cwd: scriptsDir }).ok;
98
+ }
99
+
100
+ /** Does a skill scripts dir declare a given npm dependency? */
101
+ function pkgHasDep(scriptsDir, name) {
102
+ try {
103
+ const pkg = JSON.parse(fs.readFileSync(path.join(scriptsDir, 'package.json'), 'utf8'));
104
+ return Boolean((pkg.dependencies && pkg.dependencies[name]) || (pkg.devDependencies && pkg.devDependencies[name]));
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /** Download the Playwright Chromium browser (for the pptx html2pptx workflow). */
111
+ function installPlaywrightBrowser(scriptsDir) {
112
+ return run('npx', ['--yes', 'playwright', 'install', 'chromium'], { cwd: scriptsDir }).ok;
113
+ }
114
+
115
+ /**
116
+ * List installed skills that ship a runnable scripts/requirements.txt
117
+ * (excludes the tests/ requirements). Returns [{ skill, file }].
118
+ */
119
+ function collectRequirements(skillsDir) {
120
+ const out = [];
121
+ if (!fs.existsSync(skillsDir)) return out;
122
+ for (const skill of fs.readdirSync(skillsDir)) {
123
+ const file = path.join(skillsDir, skill, 'scripts', 'requirements.txt');
124
+ if (fs.existsSync(file)) out.push({ skill, file });
125
+ }
126
+ return out;
127
+ }
128
+
129
+ /**
130
+ * List installed skills with a scripts/package.json that still needs installing
131
+ * (no node_modules present). Returns [{ skill, dir }].
132
+ */
133
+ function collectSkillPackages(skillsDir) {
134
+ const out = [];
135
+ if (!fs.existsSync(skillsDir)) return out;
136
+ for (const skill of fs.readdirSync(skillsDir)) {
137
+ const dir = path.join(skillsDir, skill, 'scripts');
138
+ if (fs.existsSync(path.join(dir, 'package.json')) && !fs.existsSync(path.join(dir, 'node_modules'))) {
139
+ out.push({ skill, dir });
140
+ }
141
+ }
142
+ return out;
143
+ }
144
+
145
+ /** External binaries some skills shell out to — detected, never auto-installed. */
146
+ const SYSTEM_TOOLS = [
147
+ { cmd: 'ffmpeg', why: 'ai-multimodal (audio/video)', brew: 'brew install ffmpeg', apt: 'sudo apt install ffmpeg', win: 'winget install ffmpeg' },
148
+ { cmd: 'pdftotext', why: 'pdf (text extraction, poppler)', brew: 'brew install poppler', apt: 'sudo apt install poppler-utils', win: 'choco install poppler' },
149
+ { cmd: 'rsvg-convert', why: 'generate-graph (PNG export, librsvg)', brew: 'brew install librsvg', apt: 'sudo apt install librsvg2-bin', win: 'choco install rsvg-convert' },
150
+ { cmd: 'tesseract', why: 'pdf/ai-multimodal (OCR)', brew: 'brew install tesseract', apt: 'sudo apt install tesseract-ocr', win: 'choco install tesseract' }
151
+ ];
152
+
153
+ /** Global npm CLIs some skills expect — detected, never auto-installed. */
154
+ const GLOBAL_NPM = [
155
+ { cmd: 'agent-browser', pkg: 'agent-browser', why: 'agent-browser skill' }
156
+ ];
157
+
158
+ /** Per-OS install hint for a SYSTEM_TOOLS entry. */
159
+ function systemHint(tool) {
160
+ if (process.platform === 'darwin') return tool.brew;
161
+ if (process.platform === 'win32') return tool.win;
162
+ return tool.apt;
163
+ }
164
+
165
+ module.exports = {
166
+ isWindows,
167
+ hasCmd,
168
+ findPython,
169
+ venvDir,
170
+ venvPython,
171
+ venvValid,
172
+ createVenv,
173
+ pipUpgrade,
174
+ pipInstall,
175
+ npmInstall,
176
+ installChromium,
177
+ collectRequirements,
178
+ collectSkillPackages,
179
+ pkgHasDep,
180
+ installPlaywrightBrowser,
181
+ SYSTEM_TOOLS,
182
+ GLOBAL_NPM,
183
+ systemHint
184
+ };
@@ -1,75 +1,23 @@
1
1
  /**
2
2
  * Phase: post-install runtime configuration.
3
3
  *
4
- * OpenCode model, optional Gemini CLI install + API key, and addressing config.
5
- * Interactive prompts use ctx.ui (clack); in non-interactive/dry-run they are
6
- * skipped via fallbacks so spawned/CI runs never hang. Writes (.env, CLAUDE.md,
7
- * opencode.json) happen only when a value is actually provided.
4
+ * OpenCode model + addressing config. Interactive prompts use ctx.ui (clack);
5
+ * in non-interactive/dry-run they are skipped via fallbacks so spawned/CI runs
6
+ * never hang.
7
+ *
8
+ * Note: CafeKit no longer prompts for a Gemini API key. The upstream gemini-cli
9
+ * was removed, and the key now serves only the ai-multimodal skill — which
10
+ * documents it in its own `.env.example`. Users set GEMINI_API_KEY there when
11
+ * (and only if) they use that skill.
8
12
  */
9
13
 
10
14
  const fs = require('fs');
11
15
  const path = require('path');
12
- const { execSync } = require('child_process');
13
- const { PLATFORMS } = require('../lib/context');
14
16
  const { setupOpenCodeModel } = require('../lib/opencode-install');
15
17
 
16
- function checkGeminiCLI() {
17
- try {
18
- execSync('which gemini', { stdio: 'ignore' });
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
- }
36
- }
37
-
38
- function configureGeminiKey(ctx, apiKey, platforms) {
39
- const envBody = `GEMINI_API_KEY=${apiKey}\nVISUAL_MODEL=gemma-4-31b-it\nSEARCH_MODEL=gemini-2.5-pro\n`;
40
- const targets = platforms.filter((key) => PLATFORMS[key]).map((key) => PLATFORMS[key].folder);
41
- if (targets.length === 0) targets.push('.claude');
42
-
43
- for (const folder of targets) {
44
- try {
45
- const targetDir = path.join(process.cwd(), folder);
46
- if (!fs.existsSync(targetDir)) fs.mkdirSync(targetDir, { recursive: true });
47
- fs.writeFileSync(path.join(targetDir, '.env'), envBody, { mode: 0o600 });
48
- ctx.ui.success(`Gemini API key stored in ${folder}/.env (0600)`);
49
- } catch (error) {
50
- ctx.ui.error(`Failed to configure Gemini API key: ${error.message}`);
51
- }
52
- }
53
- }
54
-
55
- async function setupGemini(ctx) {
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
-
66
- const apiKey = await ctx.ui.text(
67
- { message: 'Gemini API key (Enter to skip)', placeholder: 'aistudio.google.com/apikey' },
68
- ''
69
- );
70
- if (apiKey && typeof apiKey === 'string' && apiKey.trim()) {
71
- configureGeminiKey(ctx, apiKey.trim(), ctx.platforms);
72
- }
18
+ /** Human assistant name for the active platform(s). */
19
+ function assistantName(ctx) {
20
+ return ctx.platforms.includes('claude') ? 'Claude Code' : 'OpenCode';
73
21
  }
74
22
 
75
23
  function configureAddressing(ctx, userAddress) {
@@ -79,10 +27,11 @@ function configureAddressing(ctx, userAddress) {
79
27
  return;
80
28
  }
81
29
 
30
+ const name = assistantName(ctx);
82
31
  let content = fs.readFileSync(claudeMdFile, 'utf8');
83
32
  const addressingSection = `## Addressing (Context Overflow Indicator)
84
33
 
85
- The AI always addresses the user as "${userAddress}" throughout the conversation. If the AI stops doing so, it is a sign the context has been compacted/truncated — tell the user to consider \`/clear\`.`;
34
+ ${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
35
 
87
36
  // Idempotent: replace existing section (shared marker) or append.
88
37
  const regex = /##[^\n]*Context Overflow Indicator[^\n]*[\s\S]*?(?=\n##|\n*$)/;
@@ -91,14 +40,14 @@ The AI always addresses the user as "${userAddress}" throughout the conversation
91
40
  : `${content.endsWith('\n') ? content : `${content}\n`}\n${addressingSection}\n`;
92
41
 
93
42
  fs.writeFileSync(claudeMdFile, content, 'utf8');
94
- ctx.ui.success(`Addressing set: AI will call you "${userAddress}"`);
43
+ ctx.ui.success(`${name} will call you "${userAddress}"`);
95
44
  }
96
45
 
97
46
  async function setupAddressing(ctx) {
98
47
  if (!ctx.platforms.includes('claude')) return;
99
48
 
100
49
  const answer = await ctx.ui.text(
101
- { message: 'How should the AI address you? (e.g. boss, sir — Enter to skip)', placeholder: 'Enter to skip' },
50
+ { message: 'How should the AI address you?', placeholder: 'e.g. boss, sir — Enter to skip' },
102
51
  ''
103
52
  );
104
53
  if (ctx.ui.isCancel(answer)) return;
@@ -115,7 +64,7 @@ async function setupAddressing(ctx) {
115
64
  /** Run the post-install configuration sequence. */
116
65
  async function runPostInstall(ctx) {
117
66
  if (ctx.dryRun) {
118
- ctx.ui.info('[dry-run] Skipping OpenCode model / Gemini / addressing setup');
67
+ ctx.ui.info('[dry-run] Skipping OpenCode model / addressing setup');
119
68
  return ctx;
120
69
  }
121
70
 
@@ -126,7 +75,6 @@ async function runPostInstall(ctx) {
126
75
  await setupOpenCodeModel(ctx.platforms, ctx.results);
127
76
  }
128
77
 
129
- await setupGemini(ctx);
130
78
  await setupAddressing(ctx);
131
79
 
132
80
  // Re-record CLAUDE.md baseline so the installer-managed file (template +
@@ -138,4 +86,4 @@ async function runPostInstall(ctx) {
138
86
  return ctx;
139
87
  }
140
88
 
141
- module.exports = { checkGeminiCLI, configureGeminiKey, configureAddressing, runPostInstall };
89
+ module.exports = { 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.0",
3
+ "version": "0.10.3",
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,9 @@
1
+ {
2
+ "name": "cafekit-skill-docx",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "description": "Node dependencies for the CafeKit docx skill (docx-js authoring).",
6
+ "dependencies": {
7
+ "docx": "^9.0.0"
8
+ }
9
+ }
@@ -0,0 +1,5 @@
1
+ # DOCX skill Python dependencies (installed by `cafekit --with-skills-deps`)
2
+ # Used by the OOXML editing path (document.py / utilities.py).
3
+
4
+ # Secure XML parsing for existing .docx files
5
+ defusedxml>=0.7.1
@@ -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
+ }
@@ -0,0 +1,4 @@
1
+ # PPTX skill Python dependencies (installed by `cafekit --with-skills-deps`)
2
+
3
+ # Extract text from presentations
4
+ markitdown[pptx]>=0.0.1