markui-cli 0.1.0
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/README.md +32 -0
- package/bin/markui.js +146 -0
- package/package.json +36 -0
- package/src/injector.js +107 -0
- package/src/overlay/overlay.css +712 -0
- package/src/overlay/overlay.js +884 -0
- package/src/prompt-builder.js +24 -0
- package/src/proxy.js +250 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# MarkUI
|
|
2
|
+
|
|
3
|
+
Annotate your running localhost UI and generate structured prompts for Claude Code.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g markui
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
Start your dev server, then in a new terminal:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
markui
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Open http://localhost:3131 in your browser.
|
|
20
|
+
|
|
21
|
+
Click "Annotate", click elements, leave instructions, hit "Copy for Claude".
|
|
22
|
+
|
|
23
|
+
## Options
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
markui --target 3000 # specify which port your app runs on
|
|
27
|
+
markui --port 3131 # specify which port MarkUI runs on
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## Works with
|
|
31
|
+
|
|
32
|
+
React, Vue, Svelte, Next.js, Vite, plain HTML — anything that runs on localhost.
|
package/bin/markui.js
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const net = require('net');
|
|
4
|
+
const minimist = require('minimist');
|
|
5
|
+
const { startProxy, startAssetServer } = require('../src/proxy');
|
|
6
|
+
const { inject, eject } = require('../src/injector');
|
|
7
|
+
|
|
8
|
+
const args = minimist(process.argv.slice(2), {
|
|
9
|
+
alias: { t: 'target', p: 'port', h: 'help' },
|
|
10
|
+
default: { port: 3131 }
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
const command = args._[0];
|
|
14
|
+
|
|
15
|
+
if (args.help) {
|
|
16
|
+
console.log(`
|
|
17
|
+
markui — Annotate your localhost UI for Claude Code
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
markui Start markui (auto-injects, open your normal dev URL)
|
|
21
|
+
markui inject Manually inject markui tags into index.html
|
|
22
|
+
markui eject Remove markui tags from index.html
|
|
23
|
+
markui --target 3000 Proxy mode for a specific port (use localhost:3131)
|
|
24
|
+
markui --port 4000 Change MarkUI's asset port (default: 3131)
|
|
25
|
+
|
|
26
|
+
How it works:
|
|
27
|
+
1. Start your dev server (npm run dev, etc.)
|
|
28
|
+
2. Run "markui" in your project directory
|
|
29
|
+
3. Open your normal dev server URL — the overlay is already there
|
|
30
|
+
4. Ctrl+C to stop (auto-cleans injected tags)
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--target, -t Proxy a specific port (fallback if no index.html found)
|
|
34
|
+
--port, -p Port MarkUI asset server runs on (default: 3131)
|
|
35
|
+
--help, -h Show this help message
|
|
36
|
+
`);
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (command === 'inject') {
|
|
41
|
+
inject(args.port);
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (command === 'eject') {
|
|
46
|
+
eject();
|
|
47
|
+
process.exit(0);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Common dev server ports: React/Next (3000), Vite (5173), Angular (4200),
|
|
51
|
+
// generic (8080/8000/8888), Flask/Django (5000), Parcel (1234), Astro (4321),
|
|
52
|
+
// Svelte (5000), Nuxt (3000), Remix (3000), Gatsby (8000), Hugo (1313),
|
|
53
|
+
// Rails (3000), Laravel (8000), Spring (8080), Express (3000/4000)
|
|
54
|
+
const SCAN_PORTS = [3000, 5173, 4200, 8080, 8000, 8888, 5000, 4000, 1234, 4321, 1313, 3001, 3002, 5500];
|
|
55
|
+
|
|
56
|
+
function checkPort(port) {
|
|
57
|
+
return new Promise((resolve) => {
|
|
58
|
+
const sock = new net.Socket();
|
|
59
|
+
sock.setTimeout(300);
|
|
60
|
+
sock.once('connect', () => {
|
|
61
|
+
sock.destroy();
|
|
62
|
+
resolve(true);
|
|
63
|
+
});
|
|
64
|
+
sock.once('timeout', () => {
|
|
65
|
+
sock.destroy();
|
|
66
|
+
resolve(false);
|
|
67
|
+
});
|
|
68
|
+
sock.once('error', () => {
|
|
69
|
+
sock.destroy();
|
|
70
|
+
resolve(false);
|
|
71
|
+
});
|
|
72
|
+
sock.connect(port, '127.0.0.1');
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function detectTarget() {
|
|
77
|
+
for (const port of SCAN_PORTS) {
|
|
78
|
+
if (await checkPort(port)) return port;
|
|
79
|
+
}
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function main() {
|
|
84
|
+
let targetPort = args.target;
|
|
85
|
+
const proxyPort = args.port;
|
|
86
|
+
|
|
87
|
+
// Try inject + asset server mode first (user keeps their normal URL)
|
|
88
|
+
const hasIndexHtml = (() => {
|
|
89
|
+
try { return inject(proxyPort, true); } catch(e) { return false; }
|
|
90
|
+
})();
|
|
91
|
+
|
|
92
|
+
if (hasIndexHtml) {
|
|
93
|
+
// Inject mode: tags added to index.html, start asset server
|
|
94
|
+
// User opens their normal dev server URL
|
|
95
|
+
inject(proxyPort);
|
|
96
|
+
|
|
97
|
+
// Clean up injected tags on exit
|
|
98
|
+
const cleanup = () => {
|
|
99
|
+
try { eject(); } catch(e) {}
|
|
100
|
+
process.exit(0);
|
|
101
|
+
};
|
|
102
|
+
process.on('SIGINT', cleanup);
|
|
103
|
+
process.on('SIGTERM', cleanup);
|
|
104
|
+
|
|
105
|
+
if (targetPort) {
|
|
106
|
+
console.log(`\n Your dev server is on http://localhost:${targetPort} — open that URL.\n`);
|
|
107
|
+
} else {
|
|
108
|
+
targetPort = await detectTarget();
|
|
109
|
+
if (targetPort) {
|
|
110
|
+
console.log(`\n Dev server detected on http://localhost:${targetPort} — open that URL.\n`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
startAssetServer({ port: proxyPort });
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Fallback: proxy mode (no index.html found to inject into)
|
|
119
|
+
if (targetPort) {
|
|
120
|
+
const alive = await checkPort(targetPort);
|
|
121
|
+
if (!alive) {
|
|
122
|
+
console.error(
|
|
123
|
+
`\nNothing found on port ${targetPort}. Is your dev server running?\n`
|
|
124
|
+
);
|
|
125
|
+
process.exit(1);
|
|
126
|
+
}
|
|
127
|
+
if (targetPort === proxyPort) {
|
|
128
|
+
console.error(
|
|
129
|
+
`\nTarget port and MarkUI port are both ${proxyPort}. Use --port to pick a different MarkUI port.\n`
|
|
130
|
+
);
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
startProxy({ targetPort, proxyPort });
|
|
134
|
+
} else {
|
|
135
|
+
console.log('Auto-detecting dev server...');
|
|
136
|
+
targetPort = await detectTarget();
|
|
137
|
+
|
|
138
|
+
if (targetPort && targetPort !== proxyPort) {
|
|
139
|
+
startProxy({ targetPort, proxyPort });
|
|
140
|
+
} else {
|
|
141
|
+
startAssetServer({ port: proxyPort });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "markui-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Annotate your localhost UI and generate prompts for Claude Code",
|
|
5
|
+
"bin": {
|
|
6
|
+
"markui": "bin/markui.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"src",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"prepublishOnly": "node -e \"require('./src/proxy'); console.log('Sanity check passed')\""
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"http-proxy": "^1.18.1",
|
|
18
|
+
"minimist": "^1.2.8"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"claude",
|
|
22
|
+
"ai",
|
|
23
|
+
"developer-tools",
|
|
24
|
+
"ui",
|
|
25
|
+
"annotation",
|
|
26
|
+
"llm"
|
|
27
|
+
],
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=16"
|
|
30
|
+
},
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "git+https://github.com/adebolaali/markui.git"
|
|
34
|
+
},
|
|
35
|
+
"license": "MIT"
|
|
36
|
+
}
|
package/src/injector.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
|
|
4
|
+
const SEARCH_PATHS = [
|
|
5
|
+
'index.html',
|
|
6
|
+
'public/index.html',
|
|
7
|
+
'src/index.html',
|
|
8
|
+
'app/index.html',
|
|
9
|
+
'dist/index.html',
|
|
10
|
+
'build/index.html',
|
|
11
|
+
'static/index.html',
|
|
12
|
+
'www/index.html',
|
|
13
|
+
'templates/index.html',
|
|
14
|
+
'views/index.html'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
const MARKER_START = '<!-- markui:start -->';
|
|
18
|
+
const MARKER_END = '<!-- markui:end -->';
|
|
19
|
+
|
|
20
|
+
function buildSnippet(port) {
|
|
21
|
+
return (
|
|
22
|
+
MARKER_START + '\n' +
|
|
23
|
+
`<script src="http://localhost:${port}/__markui__/overlay.js"></script>\n` +
|
|
24
|
+
`<link rel="stylesheet" href="http://localhost:${port}/__markui__/overlay.css">\n` +
|
|
25
|
+
MARKER_END
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function findIndexHtml() {
|
|
30
|
+
const cwd = process.cwd();
|
|
31
|
+
for (const rel of SEARCH_PATHS) {
|
|
32
|
+
const full = path.join(cwd, rel);
|
|
33
|
+
if (fs.existsSync(full)) return full;
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function inject(port, dryRun) {
|
|
39
|
+
const file = findIndexHtml();
|
|
40
|
+
if (!file) {
|
|
41
|
+
if (dryRun) return false;
|
|
42
|
+
console.error(
|
|
43
|
+
'\nCould not find index.html. Checked:\n' +
|
|
44
|
+
SEARCH_PATHS.map(p => ' ./' + p).join('\n') + '\n'
|
|
45
|
+
);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
if (dryRun) return true;
|
|
49
|
+
|
|
50
|
+
let html = fs.readFileSync(file, 'utf8');
|
|
51
|
+
|
|
52
|
+
// Remove existing injection if present
|
|
53
|
+
const markerRe = new RegExp(
|
|
54
|
+
escapeRegExp(MARKER_START) + '[\\s\\S]*?' + escapeRegExp(MARKER_END) + '\\n?',
|
|
55
|
+
'g'
|
|
56
|
+
);
|
|
57
|
+
html = html.replace(markerRe, '');
|
|
58
|
+
|
|
59
|
+
const snippet = buildSnippet(port);
|
|
60
|
+
|
|
61
|
+
if (html.includes('</body>')) {
|
|
62
|
+
html = html.replace('</body>', snippet + '\n</body>');
|
|
63
|
+
} else if (html.includes('</html>')) {
|
|
64
|
+
html = html.replace('</html>', snippet + '\n</html>');
|
|
65
|
+
} else {
|
|
66
|
+
html += '\n' + snippet + '\n';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
fs.writeFileSync(file, html);
|
|
70
|
+
|
|
71
|
+
const rel = path.relative(process.cwd(), file);
|
|
72
|
+
console.log(`Injected into ${rel} — run markui to start the asset server`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function eject() {
|
|
76
|
+
const file = findIndexHtml();
|
|
77
|
+
if (!file) {
|
|
78
|
+
console.error(
|
|
79
|
+
'\nCould not find index.html. Checked:\n' +
|
|
80
|
+
SEARCH_PATHS.map(p => ' ./' + p).join('\n') + '\n'
|
|
81
|
+
);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let html = fs.readFileSync(file, 'utf8');
|
|
86
|
+
|
|
87
|
+
const markerRe = new RegExp(
|
|
88
|
+
escapeRegExp(MARKER_START) + '[\\s\\S]*?' + escapeRegExp(MARKER_END) + '\\n?',
|
|
89
|
+
'g'
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
if (!markerRe.test(html)) {
|
|
93
|
+
console.log('No markui tags found in ' + path.relative(process.cwd(), file));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
html = html.replace(markerRe, '');
|
|
98
|
+
fs.writeFileSync(file, html);
|
|
99
|
+
|
|
100
|
+
console.log('Removed markui from ' + path.relative(process.cwd(), file));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function escapeRegExp(str) {
|
|
104
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { inject, eject };
|