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.
- package/app/docs/content.js +57 -23
- package/cli/dist/commands/edit.d.ts +1 -1
- package/cli/dist/commands/edit.js +231 -3
- package/cli/dist/index.js +0 -0
- package/lib/local-editor-server.js +316 -0
- package/lib/local-editor-state.js +45 -0
- package/lib/local-slide-editor-launcher.js +19 -18
- package/lib/pptx-studio-mcp-core.js +15 -9
- package/local-editor-app/app/api/edit-slide/local-health/route.js +16 -0
- package/local-editor-app/app/edit-slide/edit-slide-client.jsx +13153 -0
- package/local-editor-app/app/edit-slide/page.jsx +13 -0
- package/local-editor-app/app/globals.css +4 -0
- package/local-editor-app/app/layout.jsx +14 -0
- package/local-editor-app/components/studio/edit-property-panel.jsx +1061 -0
- package/local-editor-app/lib/edit-panel-value-normalizer.js +97 -0
- package/local-editor-app/lib/edit-slide-editor-helpers.js +120 -0
- package/local-editor-app/lib/edit-slide-url-security.js +247 -0
- package/local-editor-app/next.config.mjs +31 -0
- package/local-editor-app/package.json +7 -0
- package/mcp/pptx-studio-mcp-server.mjs +1 -1
- package/package.json +16 -3
- package/public/skills/html2pptx/SKILL.md +635 -0
- package/public/skills/html2pptx/references/automation-contract.md +68 -0
- package/public/skills/html2pptx/references/input-contract.md +107 -0
- package/public/skills/html2pptx/references/japanese-slide-design.md +273 -0
- package/public/skills/html2pptx/references/rewrite-patterns.md +218 -0
- package/public/skills/icon-generator/SKILL.md +133 -0
- package/public/skills/open-slide/SKILL.md +160 -0
- package/public/skills/publish-template/SKILL.md +215 -0
- package/public/skills/register-template/SKILL.md +142 -0
- package/scripts/extract-html2pptx-comments.mjs +172 -0
- package/scripts/install-mcp.mjs +58 -13
- 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
|
+
});
|
package/scripts/install-mcp.mjs
CHANGED
|
@@ -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} ->
|
|
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
|
-
|
|
93
|
-
|
|
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:
|
|
107
|
-
args:
|
|
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
|
|
183
|
-
npx
|
|
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
|
+
}
|