html2pptx-local-mcp 1.1.19 → 1.1.21

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.
Files changed (33) hide show
  1. package/app/docs/content.js +57 -23
  2. package/cli/dist/commands/edit.d.ts +1 -1
  3. package/cli/dist/commands/edit.js +231 -3
  4. package/cli/dist/index.js +0 -0
  5. package/lib/local-editor-server.js +316 -0
  6. package/lib/local-editor-state.js +45 -0
  7. package/lib/local-slide-editor-launcher.js +19 -18
  8. package/lib/pptx-studio-mcp-core.js +15 -9
  9. package/local-editor-app/app/api/edit-slide/local-health/route.js +16 -0
  10. package/local-editor-app/app/edit-slide/edit-slide-client.jsx +13153 -0
  11. package/local-editor-app/app/edit-slide/page.jsx +13 -0
  12. package/local-editor-app/app/globals.css +4 -0
  13. package/local-editor-app/app/layout.jsx +14 -0
  14. package/local-editor-app/components/studio/edit-property-panel.jsx +1061 -0
  15. package/local-editor-app/lib/edit-panel-value-normalizer.js +97 -0
  16. package/local-editor-app/lib/edit-slide-editor-helpers.js +120 -0
  17. package/local-editor-app/lib/edit-slide-url-security.js +247 -0
  18. package/local-editor-app/next.config.mjs +31 -0
  19. package/local-editor-app/package.json +7 -0
  20. package/mcp/pptx-studio-mcp-server.mjs +1 -1
  21. package/package.json +16 -3
  22. package/public/skills/html2pptx/SKILL.md +635 -0
  23. package/public/skills/html2pptx/references/automation-contract.md +68 -0
  24. package/public/skills/html2pptx/references/input-contract.md +107 -0
  25. package/public/skills/html2pptx/references/japanese-slide-design.md +273 -0
  26. package/public/skills/html2pptx/references/rewrite-patterns.md +218 -0
  27. package/public/skills/icon-generator/SKILL.md +133 -0
  28. package/public/skills/open-slide/SKILL.md +160 -0
  29. package/public/skills/publish-template/SKILL.md +215 -0
  30. package/public/skills/register-template/SKILL.md +142 -0
  31. package/scripts/extract-html2pptx-comments.mjs +172 -0
  32. package/scripts/install-mcp.mjs +58 -13
  33. package/scripts/install-skills.mjs +82 -0
@@ -0,0 +1,172 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { readFile } from 'node:fs/promises';
4
+ import { extname, resolve } from 'node:path';
5
+ import { JSDOM } from 'jsdom';
6
+
7
+ const ALLOWED_EXTENSIONS = new Set(['.html', '.htm']);
8
+
9
+ function usage() {
10
+ return [
11
+ 'Usage: html2pptx-comments <file.html> [--json|--format markdown|--format json]',
12
+ '',
13
+ 'Extracts edit-slide element comments saved as data-html2pptx-comment attributes.',
14
+ ].join('\n');
15
+ }
16
+
17
+ function parseArgs(argv) {
18
+ const args = argv.slice(2);
19
+ let file = '';
20
+ let format = 'markdown';
21
+ for (let i = 0; i < args.length; i += 1) {
22
+ const arg = args[i];
23
+ if (arg === '--help' || arg === '-h') {
24
+ return { help: true, file, format };
25
+ }
26
+ if (arg === '--json') {
27
+ format = 'json';
28
+ continue;
29
+ }
30
+ if (arg === '--format') {
31
+ const value = args[i + 1];
32
+ if (!['json', 'markdown'].includes(value)) {
33
+ throw new Error('--format must be "json" or "markdown".');
34
+ }
35
+ format = value;
36
+ i += 1;
37
+ continue;
38
+ }
39
+ if (arg.startsWith('-')) {
40
+ throw new Error(`Unknown option: ${arg}`);
41
+ }
42
+ if (file) {
43
+ throw new Error(`Unexpected extra file argument: ${arg}`);
44
+ }
45
+ file = arg;
46
+ }
47
+ return { help: false, file, format };
48
+ }
49
+
50
+ function cssEscape(value) {
51
+ return String(value).replace(/[^a-zA-Z0-9_-]/g, (char) => `\\${char}`);
52
+ }
53
+
54
+ function shortText(value, max = 80) {
55
+ const text = String(value || '').trim().replace(/\s+/g, ' ');
56
+ return text.length > max ? `${text.slice(0, max - 1)}...` : text;
57
+ }
58
+
59
+ function elementLabel(element) {
60
+ const tag = element.tagName.toLowerCase();
61
+ const named =
62
+ element.getAttribute('data-layer-name') ||
63
+ element.getAttribute('data-name') ||
64
+ element.getAttribute('aria-label') ||
65
+ element.getAttribute('alt') ||
66
+ shortText(element.textContent, 48);
67
+ return named ? `${tag}: ${named}` : tag;
68
+ }
69
+
70
+ function cssPath(element, root) {
71
+ if (!element || element === root) return '';
72
+ const parts = [];
73
+ let node = element;
74
+ while (node && node !== root && node.nodeType === 1) {
75
+ const tag = node.tagName.toLowerCase();
76
+ const id = node.getAttribute('id');
77
+ if (id) {
78
+ parts.unshift(`${tag}#${cssEscape(id)}`);
79
+ break;
80
+ }
81
+ const className = node.getAttribute('class');
82
+ const classes = className ? className.split(/\s+/).filter(Boolean).slice(0, 2).map(cssEscape) : [];
83
+ const classPart = classes.length ? `.${classes.join('.')}` : '';
84
+ const siblings = Array.from(node.parentElement?.children || []).filter((child) => child.tagName === node.tagName);
85
+ const indexPart = siblings.length > 1 ? `:nth-of-type(${siblings.indexOf(node) + 1})` : '';
86
+ parts.unshift(`${tag}${classPart}${indexPart}`);
87
+ node = node.parentElement;
88
+ }
89
+ return parts.join(' > ');
90
+ }
91
+
92
+ function extractComments(html, filePath) {
93
+ const dom = new JSDOM(html);
94
+ const document = dom.window.document;
95
+ const slides = Array.from(document.querySelectorAll('section.slide'));
96
+ const commented = Array.from(document.querySelectorAll('[data-html2pptx-comment]'));
97
+ return commented
98
+ .map((element, index) => {
99
+ const comment = shortText(element.getAttribute('data-html2pptx-comment'), 2000);
100
+ if (!comment) return null;
101
+ const slideElement = element.closest('section.slide');
102
+ const slideIndex = slideElement ? slides.indexOf(slideElement) : -1;
103
+ const root = slideElement || document.body;
104
+ const selector = element.getAttribute('data-html2pptx-comment-selector') || cssPath(element, root) || '__root__';
105
+ return {
106
+ index: index + 1,
107
+ file: filePath,
108
+ slide: slideIndex >= 0 ? slideIndex + 1 : null,
109
+ id: element.getAttribute('data-html2pptx-comment-id') || null,
110
+ selector,
111
+ tag: element.tagName.toLowerCase(),
112
+ label: elementLabel(element),
113
+ comment,
114
+ updatedAt: element.getAttribute('data-html2pptx-comment-updated-at') || null,
115
+ };
116
+ })
117
+ .filter(Boolean);
118
+ }
119
+
120
+ function formatMarkdown(filePath, comments) {
121
+ if (!comments.length) {
122
+ return `No html2pptx comments found in ${filePath}.`;
123
+ }
124
+ const lines = [
125
+ `# html2pptx comments`,
126
+ '',
127
+ `File: \`${filePath}\``,
128
+ `Count: ${comments.length}`,
129
+ '',
130
+ ];
131
+ for (const item of comments) {
132
+ lines.push(
133
+ `## ${item.index}. ${item.slide ? `Slide ${item.slide}` : 'Document'} / ${item.label}`,
134
+ '',
135
+ `- selector: \`${item.selector}\``,
136
+ item.id ? `- id: \`${item.id}\`` : null,
137
+ item.updatedAt ? `- updated: ${item.updatedAt}` : null,
138
+ '',
139
+ item.comment,
140
+ '',
141
+ );
142
+ }
143
+ return lines.filter((line) => line !== null).join('\n');
144
+ }
145
+
146
+ async function main() {
147
+ const { help, file, format } = parseArgs(process.argv);
148
+ if (help) {
149
+ console.log(usage());
150
+ return;
151
+ }
152
+ if (!file) {
153
+ throw new Error(`Missing file.\n\n${usage()}`);
154
+ }
155
+ const filePath = resolve(file);
156
+ const ext = extname(filePath).toLowerCase();
157
+ if (!ALLOWED_EXTENSIONS.has(ext)) {
158
+ throw new Error('Only .html and .htm files are supported.');
159
+ }
160
+ const html = await readFile(filePath, 'utf8');
161
+ const comments = extractComments(html, filePath);
162
+ if (format === 'json') {
163
+ console.log(JSON.stringify({ file: filePath, count: comments.length, comments }, null, 2));
164
+ } else {
165
+ console.log(formatMarkdown(filePath, comments));
166
+ }
167
+ }
168
+
169
+ main().catch((error) => {
170
+ console.error(error?.message || String(error));
171
+ process.exitCode = 1;
172
+ });
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { spawnSync } from 'node:child_process';
4
4
  import { existsSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { join } from 'node:path';
5
+ import { dirname, join } from 'node:path';
6
6
 
7
7
  const REMOTE_SERVER_NAME = 'html2pptx';
8
8
  const LOCAL_SERVER_NAME = 'html2pptx-local';
@@ -13,6 +13,7 @@ const args = process.argv.slice(2);
13
13
  const dryRun = args.includes('--dry-run');
14
14
  const help = args.includes('--help') || args.includes('-h');
15
15
  const client = resolveClient(args);
16
+ const localCommand = buildLocalMcpCommand();
16
17
 
17
18
  if (help) {
18
19
  printHelp();
@@ -20,7 +21,7 @@ if (help) {
20
21
  }
21
22
 
22
23
  if (!client) {
23
- console.error('Unknown MCP client. Use one of: claude, codex.');
24
+ console.error('Unknown MCP client. Use one of: claude, codex, grok.');
24
25
  printHelp();
25
26
  process.exit(1);
26
27
  }
@@ -29,7 +30,7 @@ const steps = buildSteps(client);
29
30
 
30
31
  console.log(`Installing html2pptx MCP for ${client}.`);
31
32
  console.log(`Remote server: ${REMOTE_SERVER_NAME} -> ${REMOTE_MCP_URL}`);
32
- console.log(`Local server: ${LOCAL_SERVER_NAME} -> npx -y -p ${LOCAL_PACKAGE_SPEC} html2pptx-mcp`);
33
+ console.log(`Local server: ${LOCAL_SERVER_NAME} -> ${renderCommand(localCommand.command, localCommand.args)}`);
33
34
  console.log('');
34
35
 
35
36
  for (const step of steps) {
@@ -61,6 +62,7 @@ function resolveClient(argv) {
61
62
  function normalizeClient(value) {
62
63
  if (value === 'claude' || value === 'claude-code') return 'claude';
63
64
  if (value === 'codex') return 'codex';
65
+ if (value === 'grok' || value === 'grok-build') return 'grok';
64
66
  return null;
65
67
  }
66
68
 
@@ -77,6 +79,29 @@ function buildSteps(targetClient) {
77
79
  ];
78
80
  }
79
81
 
82
+ if (targetClient === 'grok') {
83
+ return [
84
+ {
85
+ command: 'grok',
86
+ args: ['mcp', 'add', REMOTE_SERVER_NAME, '--url', REMOTE_MCP_URL],
87
+ },
88
+ {
89
+ command: 'grok',
90
+ args: [
91
+ 'mcp',
92
+ 'add',
93
+ LOCAL_SERVER_NAME,
94
+ '--command',
95
+ 'npx',
96
+ `--args=--yes`,
97
+ `--args=--package`,
98
+ `--args=${LOCAL_PACKAGE_SPEC}`,
99
+ `--args=html2pptx-mcp`,
100
+ ],
101
+ },
102
+ ];
103
+ }
104
+
80
105
  return [
81
106
  {
82
107
  command: 'codex',
@@ -89,11 +114,8 @@ function buildSteps(targetClient) {
89
114
  'add',
90
115
  LOCAL_SERVER_NAME,
91
116
  '--',
92
- 'npx',
93
- '-y',
94
- '-p',
95
- LOCAL_PACKAGE_SPEC,
96
- 'html2pptx-mcp',
117
+ localCommand.command,
118
+ ...localCommand.args,
97
119
  ],
98
120
  },
99
121
  ];
@@ -103,8 +125,8 @@ function writeClaudeLocalMcpConfig() {
103
125
  const configPath = join(process.env.HOME || process.cwd(), '.claude.json');
104
126
  const serverConfig = {
105
127
  type: 'stdio',
106
- command: 'npx',
107
- args: ['-y', '-p', LOCAL_PACKAGE_SPEC, 'html2pptx-mcp'],
128
+ command: localCommand.command,
129
+ args: localCommand.args,
108
130
  env: {},
109
131
  };
110
132
 
@@ -135,6 +157,27 @@ function writeClaudeLocalMcpConfig() {
135
157
  console.log(`Registered local MCP server ${LOCAL_SERVER_NAME} in Claude user config.`);
136
158
  }
137
159
 
160
+ export function buildLocalMcpCommand(env = process.env) {
161
+ const npmExecPath = env.npm_execpath || env.npm_execPath;
162
+ const npxCli = npmExecPath ? join(dirname(npmExecPath), 'npx-cli.js') : '';
163
+
164
+ if (npxCli && existsSync(npxCli)) {
165
+ return {
166
+ command: process.execPath,
167
+ args: [npxCli, '--yes', '--package', LOCAL_PACKAGE_SPEC, 'html2pptx-mcp'],
168
+ };
169
+ }
170
+
171
+ return {
172
+ command: 'npx',
173
+ args: ['--yes', '--package', LOCAL_PACKAGE_SPEC, 'html2pptx-mcp'],
174
+ };
175
+ }
176
+
177
+ function renderCommand(command, args) {
178
+ return [command, ...args].join(' ');
179
+ }
180
+
138
181
  function runStep(step) {
139
182
  const rendered = [step.command, ...step.args].join(' ');
140
183
  console.log(`> ${rendered}`);
@@ -174,13 +217,15 @@ function isAlreadyRegistered(output) {
174
217
  function printHelp() {
175
218
  console.log(`
176
219
  Usage:
177
- html2pptx-install-mcp [claude|codex] [--dry-run]
220
+ html2pptx-install-mcp [claude|codex|grok] [--dry-run]
178
221
  html2pptx-install-mcp --client claude
179
222
  html2pptx-install-mcp --client codex
223
+ html2pptx-install-mcp --client grok
180
224
 
181
225
  Examples:
182
- npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp claude
183
- npx -y -p html2pptx-local-mcp@latest html2pptx-install-mcp codex
226
+ npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp claude
227
+ npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp codex
228
+ npx --yes --package html2pptx-local-mcp@latest html2pptx-install-mcp grok
184
229
 
185
230
  Optional:
186
231
  HTML2PPTX_LOCAL_MCP_PACKAGE_SPEC=<package-or-tarball> html2pptx-install-mcp claude
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { cpSync, existsSync, mkdirSync } from 'node:fs';
4
+ import { dirname, join } from 'node:path';
5
+ import { fileURLToPath } from 'node:url';
6
+
7
+ const PACKAGE_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
8
+ const PUBLIC_SKILLS_DIR = join(PACKAGE_ROOT, 'public', 'skills');
9
+
10
+ const args = process.argv.slice(2);
11
+ const dryRun = args.includes('--dry-run');
12
+ const help = args.includes('--help') || args.includes('-h');
13
+ const client = resolveClient(args);
14
+
15
+ if (help) {
16
+ printHelp();
17
+ process.exit(0);
18
+ }
19
+
20
+ if (!client) {
21
+ console.error('Unknown skills client. Use: grok.');
22
+ printHelp();
23
+ process.exit(1);
24
+ }
25
+
26
+ console.log(`Installing html2pptx skills for ${client}.`);
27
+ installGrokSkills();
28
+ console.log('');
29
+ console.log('html2pptx skills setup finished.');
30
+
31
+ function resolveClient(argv) {
32
+ const clientFlagIndex = argv.findIndex((value) => value === '--client');
33
+ if (clientFlagIndex !== -1) {
34
+ return normalizeClient(argv[clientFlagIndex + 1]);
35
+ }
36
+
37
+ const clientEquals = argv.find((value) => value.startsWith('--client='));
38
+ if (clientEquals) {
39
+ return normalizeClient(clientEquals.slice('--client='.length));
40
+ }
41
+
42
+ const positional = argv.find((value) => !value.startsWith('-'));
43
+ return normalizeClient(positional || 'grok');
44
+ }
45
+
46
+ function normalizeClient(value) {
47
+ if (value === 'grok' || value === 'grok-build') return 'grok';
48
+ return null;
49
+ }
50
+
51
+ function installGrokSkills() {
52
+ const targetRoot = join(process.env.HOME || process.cwd(), '.grok', 'skills');
53
+
54
+ console.log(`> install Grok skills ${PUBLIC_SKILLS_DIR} -> ${targetRoot}`);
55
+
56
+ if (dryRun) return;
57
+
58
+ if (!existsSync(PUBLIC_SKILLS_DIR)) {
59
+ console.error(`Public skills directory not found: ${PUBLIC_SKILLS_DIR}`);
60
+ process.exit(1);
61
+ }
62
+
63
+ mkdirSync(targetRoot, { recursive: true });
64
+ cpSync(PUBLIC_SKILLS_DIR, targetRoot, {
65
+ recursive: true,
66
+ force: true,
67
+ errorOnExist: false,
68
+ });
69
+ console.log(`Installed html2pptx skills in ${targetRoot}.`);
70
+ }
71
+
72
+ function printHelp() {
73
+ console.log(`
74
+ Usage:
75
+ html2pptx-install-skills [grok] [--dry-run]
76
+ html2pptx-install-skills --client grok
77
+
78
+ Examples:
79
+ npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills grok
80
+ npx --yes --package html2pptx-local-mcp@latest html2pptx-install-skills --client grok
81
+ `.trim());
82
+ }