@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 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,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 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,
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
- 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
- }
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 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
-
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
- 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\`.`;
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(`Addressing set: AI will call you "${userAddress}"`);
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? (e.g. boss, sir — Enter to skip)', placeholder: 'Enter to skip' },
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 setupGemini(ctx);
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 = { checkGeminiCLI, configureGeminiKey, configureAddressing, runPostInstall };
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.0",
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,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