zerg-ztc 0.1.0 → 0.1.1

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.
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { LayoutNode } from '../core/index.js';
3
+ type BadgeInfo = {
4
+ type: 'paste' | 'file' | 'image';
5
+ preview: string;
6
+ full: string;
7
+ path?: string;
8
+ };
9
+ interface FrameRendererProps {
10
+ node: LayoutNode;
11
+ cols: number;
12
+ rows: number;
13
+ cellWidth: number;
14
+ cellHeight: number;
15
+ debugBorders?: boolean;
16
+ onBadgeHover?: (badge: BadgeInfo, x: number, y: number) => void;
17
+ onBadgeLeave?: () => void;
18
+ onBadgeClick?: (badge: BadgeInfo, x: number, y: number) => void;
19
+ onBadgeDblClick?: (badge: BadgeInfo, x: number, y: number) => void;
20
+ }
21
+ export declare const WebFrameRenderer: React.FC<FrameRendererProps>;
22
+ export {};
23
+ //# sourceMappingURL=frame_render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame_render.d.ts","sourceRoot":"","sources":["../../../src/ui/web/frame_render.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AACvC,OAAO,EAAE,UAAU,EAA8B,MAAM,kBAAkB,CAAC;AAE1E,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,OAAO,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,UAAU,kBAAkB;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,YAAY,CAAC,EAAE,MAAM,IAAI,CAAC;IAC1B,YAAY,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;IAChE,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CACpE;AA2GD,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAAE,CAAC,kBAAkB,CAmBzD,CAAC"}
@@ -0,0 +1,73 @@
1
+ import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useMemo } from 'react';
3
+ import { computeLayout } from '../core/index.js';
4
+ function colorMap(color) {
5
+ const map = {
6
+ gray: '#9a9a9a',
7
+ magenta: '#c084fc',
8
+ yellow: '#facc15',
9
+ green: '#34d399',
10
+ red: '#f87171',
11
+ cyan: '#67e8f9',
12
+ blue: '#60a5fa',
13
+ white: '#e5e7eb'
14
+ };
15
+ return map[color || ''] || color || '#e6e6e6';
16
+ }
17
+ function renderFrame(frame, keyPrefix, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick, offsetX = 0, offsetY = 0, out = []) {
18
+ const x = offsetX + frame.x;
19
+ const y = offsetY + frame.y;
20
+ const keyBase = `${keyPrefix}-${x}-${y}`;
21
+ if (frame.node.type === 'text') {
22
+ const style = frame.node.style || {};
23
+ if (debugBorders) {
24
+ out.push(_jsx("div", { style: {
25
+ position: 'absolute',
26
+ left: x * cellWidth,
27
+ top: y * cellHeight,
28
+ width: frame.width * cellWidth,
29
+ height: frame.height * cellHeight,
30
+ borderStyle: 'dashed',
31
+ borderWidth: 1,
32
+ borderColor: '#3a3a3a',
33
+ pointerEvents: 'none'
34
+ } }, `${keyBase}-debug`));
35
+ }
36
+ const badge = style.badge;
37
+ out.push(_jsx("span", { style: {
38
+ position: 'absolute',
39
+ left: x * cellWidth,
40
+ top: y * cellHeight,
41
+ color: style.inverse ? '#111' : colorMap(style.color),
42
+ fontWeight: style.bold ? 600 : undefined,
43
+ opacity: style.dimColor ? 0.6 : undefined,
44
+ background: style.inverse ? '#e6e6e6' : undefined,
45
+ colorScheme: 'only light',
46
+ cursor: badge ? 'pointer' : undefined
47
+ }, title: badge?.preview || undefined, onMouseEnter: badge ? (evt) => onBadgeHover?.(badge, evt.clientX, evt.clientY) : undefined, onMouseLeave: badge ? () => onBadgeLeave?.() : undefined, onClick: badge ? (evt) => onBadgeClick?.(badge, evt.clientX, evt.clientY) : undefined, onDoubleClick: badge ? (evt) => onBadgeDblClick?.(badge, evt.clientX, evt.clientY) : undefined, children: frame.node.text }, `${keyBase}-text`));
48
+ return out;
49
+ }
50
+ if (frame.node.type === 'box' && (frame.node.style?.borderStyle || debugBorders)) {
51
+ out.push(_jsx("div", { style: {
52
+ position: 'absolute',
53
+ left: x * cellWidth,
54
+ top: y * cellHeight,
55
+ width: frame.width * cellWidth,
56
+ height: frame.height * cellHeight,
57
+ borderStyle: frame.node.style?.borderStyle ? 'solid' : 'dashed',
58
+ borderWidth: 1,
59
+ borderColor: colorMap(frame.node.style?.borderColor || '#3a3a3a'),
60
+ pointerEvents: 'none'
61
+ } }, `${keyBase}-border`));
62
+ }
63
+ frame.children.forEach((child, idx) => {
64
+ renderFrame(child, `${keyBase}-${idx}`, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick, x, y, out);
65
+ });
66
+ return out;
67
+ }
68
+ export const WebFrameRenderer = ({ node, cols, rows, cellWidth, cellHeight, debugBorders = false, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick }) => {
69
+ const frame = useMemo(() => computeLayout(node, cols, rows), [node, cols, rows]);
70
+ const elements = useMemo(() => renderFrame(frame, 'root', cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick), [frame, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick]);
71
+ return _jsx(_Fragment, { children: elements });
72
+ };
73
+ //# sourceMappingURL=frame_render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"frame_render.js","sourceRoot":"","sources":["../../../src/ui/web/frame_render.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { WebNode } from './render.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/ui/web/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export { WebNode } from './render.js';
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/ui/web/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import { LayoutNode } from '../core/types.js';
3
+ export declare function WebNode({ node }: {
4
+ node: LayoutNode;
5
+ }): React.ReactElement;
6
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/ui/web/render.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,UAAU,EAAY,MAAM,kBAAkB,CAAC;AAsBxD,wBAAgB,OAAO,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,UAAU,CAAA;CAAE,GAAG,KAAK,CAAC,YAAY,CAiB1E"}
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { toCssStyle } from '../core/style.js';
3
+ const monoStyle = {
4
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
5
+ fontSize: 14,
6
+ lineHeight: '16px',
7
+ whiteSpace: 'pre'
8
+ };
9
+ function renderText(node) {
10
+ const style = {
11
+ ...monoStyle,
12
+ color: node.style?.color,
13
+ fontWeight: node.style?.bold ? 600 : undefined,
14
+ opacity: node.style?.dimColor ? 0.6 : undefined,
15
+ background: node.style?.inverse ? '#111' : undefined,
16
+ colorScheme: 'only light'
17
+ };
18
+ return _jsx("span", { style: style, children: node.text });
19
+ }
20
+ export function WebNode({ node }) {
21
+ if (node.type === 'text') {
22
+ return renderText(node);
23
+ }
24
+ const style = {
25
+ ...monoStyle,
26
+ ...toCssStyle(node.style)
27
+ };
28
+ return (_jsx("div", { style: style, children: node.children.map((child, index) => (_jsx(WebNode, { node: child }, `${child.type}-${index}`))) }));
29
+ }
30
+ //# sourceMappingURL=render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/ui/web/render.tsx"],"names":[],"mappings":";AAEA,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,SAAS,GAAwB;IACrC,UAAU,EAAE,oGAAoG;IAChH,QAAQ,EAAE,EAAE;IACZ,UAAU,EAAE,MAAM;IAClB,UAAU,EAAE,KAAK;CAClB,CAAC;AAEF,SAAS,UAAU,CAAC,IAAc;IAChC,MAAM,KAAK,GAAwB;QACjC,GAAG,SAAS;QACZ,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK;QACxB,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC9C,OAAO,EAAE,IAAI,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC/C,UAAU,EAAE,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QACpD,WAAW,EAAE,YAAY;KAC1B,CAAC;IACF,OAAO,eAAM,KAAK,EAAE,KAAK,YAAG,IAAI,CAAC,IAAI,GAAQ,CAAC;AAChD,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,EAAE,IAAI,EAAwB;IACpD,IAAI,IAAI,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;QACzB,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC;IAED,MAAM,KAAK,GAAG;QACZ,GAAG,SAAS;QACZ,GAAG,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;KACH,CAAC;IAEzB,OAAO,CACL,cAAK,KAAK,EAAE,KAAK,YACd,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CACnC,KAAC,OAAO,IAAgC,IAAI,EAAE,KAAK,IAArC,GAAG,KAAK,CAAC,IAAI,IAAI,KAAK,EAAE,CAAiB,CACxD,CAAC,GACE,CACP,CAAC;AACJ,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { InputBus } from '../ui/core/input.js';
2
+ import { LayoutNode } from '../ui/core/index.js';
3
+ export declare function useMirror(layout: LayoutNode | null, inputBus?: InputBus): void;
4
+ //# sourceMappingURL=mirror_hook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror_hook.d.ts","sourceRoot":"","sources":["../../src/web/mirror_hook.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAYjD,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,EAAE,QAAQ,CAAC,EAAE,QAAQ,GAAG,IAAI,CAU9E"}
@@ -0,0 +1,22 @@
1
+ import { useEffect } from 'react';
2
+ import { MirrorServer } from './mirror_server.js';
3
+ let server = null;
4
+ function getPort() {
5
+ const raw = process.env.ZTC_WEB_PORT;
6
+ if (!raw)
7
+ return 3939;
8
+ const parsed = Number(raw);
9
+ return Number.isFinite(parsed) ? parsed : 3939;
10
+ }
11
+ export function useMirror(layout, inputBus) {
12
+ useEffect(() => {
13
+ if (process.env.ZTC_WEB_MIRROR !== '1' || !layout)
14
+ return;
15
+ if (!server) {
16
+ server = new MirrorServer(getPort(), inputBus);
17
+ server.start();
18
+ }
19
+ server.publish(layout);
20
+ }, [inputBus, layout]);
21
+ }
22
+ //# sourceMappingURL=mirror_hook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror_hook.js","sourceRoot":"","sources":["../../src/web/mirror_hook.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGlC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD,IAAI,MAAM,GAAwB,IAAI,CAAC;AAEvC,SAAS,OAAO;IACd,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IACrC,IAAI,CAAC,GAAG;QAAE,OAAO,IAAI,CAAC;IACtB,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC;AACjD,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,MAAyB,EAAE,QAAmB;IACtE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,OAAO,CAAC,GAAG,CAAC,cAAc,KAAK,GAAG,IAAI,CAAC,MAAM;YAAE,OAAO;QAE1D,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,MAAM,GAAG,IAAI,YAAY,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,CAAC;YAC/C,MAAM,CAAC,KAAK,EAAE,CAAC;QACjB,CAAC;QACD,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACzB,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,16 @@
1
+ import { InputBus } from '../ui/core/input.js';
2
+ import { LayoutNode } from '../ui/core/index.js';
3
+ export declare class MirrorServer {
4
+ private clients;
5
+ private port;
6
+ private server;
7
+ private lastTree;
8
+ private inputBus?;
9
+ constructor(port: number, inputBus?: InputBus);
10
+ start(): void;
11
+ stop(): void;
12
+ publish(tree: LayoutNode): void;
13
+ private handleRequest;
14
+ private sendLayout;
15
+ }
16
+ //# sourceMappingURL=mirror_server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror_server.d.ts","sourceRoot":"","sources":["../../src/web/mirror_server.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,QAAQ,EAAc,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,UAAU,EAA8B,MAAM,qBAAqB,CAAC;AAS7E,qBAAa,YAAY;IACvB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,MAAM,CAA+C;IAC7D,OAAO,CAAC,QAAQ,CAA2B;IAC3C,OAAO,CAAC,QAAQ,CAAC,CAAW;gBAEhB,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,QAAQ;IAK7C,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI;IAIZ,OAAO,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI;YAOjB,aAAa;IA4J3B,OAAO,CAAC,UAAU;CAKnB"}
@@ -0,0 +1,177 @@
1
+ import { createServer } from 'http';
2
+ import { readFile, mkdir, writeFile } from 'fs/promises';
3
+ import { resolve, join, basename } from 'path';
4
+ import { homedir } from 'os';
5
+ import { randomUUID } from 'crypto';
6
+ import { computeLayout } from '../ui/core/index.js';
7
+ export class MirrorServer {
8
+ clients = new Map();
9
+ port;
10
+ server = createServer(this.handleRequest.bind(this));
11
+ lastTree = null;
12
+ inputBus;
13
+ constructor(port, inputBus) {
14
+ this.port = port;
15
+ this.inputBus = inputBus;
16
+ }
17
+ start() {
18
+ this.server.listen(this.port);
19
+ }
20
+ stop() {
21
+ this.server.close();
22
+ }
23
+ publish(tree) {
24
+ this.lastTree = tree;
25
+ for (const client of this.clients.values()) {
26
+ this.sendLayout(client, tree);
27
+ }
28
+ }
29
+ async handleRequest(req, res) {
30
+ const url = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
31
+ if (url.pathname === '/') {
32
+ const htmlPath = new URL('./mirror.html', import.meta.url);
33
+ const html = await readFile(htmlPath, 'utf-8').catch(async () => {
34
+ const fallback = resolve(process.cwd(), 'src/web/mirror.html');
35
+ return readFile(fallback, 'utf-8');
36
+ });
37
+ res.writeHead(200, { 'Content-Type': 'text/html' });
38
+ res.end(html);
39
+ return;
40
+ }
41
+ if (url.pathname === '/mirror-favicon.svg' || url.pathname === '/favicon.svg' || url.pathname === '/favicon.ico') {
42
+ const iconPath = new URL('./mirror-favicon.svg', import.meta.url);
43
+ const icon = await readFile(iconPath, 'utf-8').catch(async () => {
44
+ const fallback = resolve(process.cwd(), 'src/web/mirror-favicon.svg');
45
+ return readFile(fallback, 'utf-8');
46
+ });
47
+ const contentType = url.pathname === '/favicon.ico' ? 'image/x-icon' : 'image/svg+xml';
48
+ res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': 'no-cache' });
49
+ res.end(icon);
50
+ return;
51
+ }
52
+ if (url.pathname === '/events') {
53
+ const id = url.searchParams.get('id') || `client_${Date.now()}`;
54
+ res.writeHead(200, {
55
+ 'Content-Type': 'text/event-stream',
56
+ 'Cache-Control': 'no-cache',
57
+ Connection: 'keep-alive'
58
+ });
59
+ res.write('\n');
60
+ const client = {
61
+ id,
62
+ res,
63
+ cols: 80,
64
+ rows: 24
65
+ };
66
+ this.clients.set(id, client);
67
+ if (this.lastTree) {
68
+ this.sendLayout(client, this.lastTree);
69
+ }
70
+ req.on('close', () => {
71
+ this.clients.delete(id);
72
+ });
73
+ return;
74
+ }
75
+ if (url.pathname === '/size' && req.method === 'POST') {
76
+ const id = url.searchParams.get('id');
77
+ if (!id || !this.clients.has(id)) {
78
+ res.writeHead(400);
79
+ res.end();
80
+ return;
81
+ }
82
+ const body = await new Promise(resolve => {
83
+ let data = '';
84
+ req.on('data', chunk => { data += chunk; });
85
+ req.on('end', () => resolve(data));
86
+ });
87
+ try {
88
+ const parsed = JSON.parse(body);
89
+ const client = this.clients.get(id);
90
+ if (client) {
91
+ client.cols = parsed.cols;
92
+ client.rows = parsed.rows;
93
+ if (this.lastTree) {
94
+ this.sendLayout(client, this.lastTree);
95
+ }
96
+ }
97
+ }
98
+ catch {
99
+ // Ignore invalid payloads
100
+ }
101
+ res.writeHead(204);
102
+ res.end();
103
+ return;
104
+ }
105
+ if (url.pathname === '/input' && req.method === 'POST') {
106
+ const body = await new Promise(resolve => {
107
+ let data = '';
108
+ req.on('data', chunk => { data += chunk; });
109
+ req.on('end', () => resolve(data));
110
+ });
111
+ try {
112
+ const parsed = JSON.parse(body);
113
+ this.inputBus?.emit(parsed);
114
+ }
115
+ catch {
116
+ // Ignore invalid payloads
117
+ }
118
+ res.writeHead(204);
119
+ res.end();
120
+ return;
121
+ }
122
+ if (url.pathname === '/upload' && req.method === 'POST') {
123
+ const buffer = await new Promise(resolve => {
124
+ const chunks = [];
125
+ req.on('data', chunk => { chunks.push(Buffer.from(chunk)); });
126
+ req.on('end', () => resolve(Buffer.concat(chunks)));
127
+ });
128
+ const headerName = req.headers['x-filename'];
129
+ const rawName = typeof headerName === 'string' && headerName.trim().length > 0 ? headerName : 'upload.bin';
130
+ const safeName = basename(rawName).replace(/[^a-zA-Z0-9._-]/g, '_');
131
+ const dir = join(homedir(), '.ztc', 'uploads');
132
+ await mkdir(dir, { recursive: true });
133
+ const filePath = join(dir, `${Date.now()}-${randomUUID().slice(0, 8)}-${safeName}`);
134
+ await writeFile(filePath, buffer);
135
+ res.writeHead(200, { 'Content-Type': 'application/json' });
136
+ res.end(JSON.stringify({ path: filePath }));
137
+ return;
138
+ }
139
+ if (url.pathname === '/file' && req.method === 'GET') {
140
+ const rawPath = url.searchParams.get('path');
141
+ if (!rawPath) {
142
+ res.writeHead(400);
143
+ res.end();
144
+ return;
145
+ }
146
+ const allowedRoots = [
147
+ join(homedir(), '.ztc', 'uploads'),
148
+ join(homedir(), '.ztc', 'clipboard')
149
+ ];
150
+ const resolvedPath = resolve(rawPath);
151
+ const allowed = allowedRoots.some(root => resolvedPath.startsWith(resolve(root)));
152
+ if (!allowed) {
153
+ res.writeHead(403);
154
+ res.end();
155
+ return;
156
+ }
157
+ try {
158
+ const data = await readFile(resolvedPath);
159
+ res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
160
+ res.end(data);
161
+ }
162
+ catch {
163
+ res.writeHead(404);
164
+ res.end();
165
+ }
166
+ return;
167
+ }
168
+ res.writeHead(404);
169
+ res.end();
170
+ }
171
+ sendLayout(client, tree) {
172
+ const frame = computeLayout(tree, client.cols, client.rows);
173
+ const payload = JSON.stringify(frame);
174
+ client.res.write(`data: ${payload}\n\n`);
175
+ }
176
+ }
177
+ //# sourceMappingURL=mirror_server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mirror_server.js","sourceRoot":"","sources":["../../src/web/mirror_server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zerg-ztc",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Zerg Terminal Client - CLI agent for continual AI systems",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,148 @@
1
+ import React, { useMemo } from 'react';
2
+ import { LayoutNode, LayoutFrame, computeLayout } from '../core/index.js';
3
+
4
+ type BadgeInfo = {
5
+ type: 'paste' | 'file' | 'image';
6
+ preview: string;
7
+ full: string;
8
+ path?: string;
9
+ };
10
+
11
+ interface FrameRendererProps {
12
+ node: LayoutNode;
13
+ cols: number;
14
+ rows: number;
15
+ cellWidth: number;
16
+ cellHeight: number;
17
+ debugBorders?: boolean;
18
+ onBadgeHover?: (badge: BadgeInfo, x: number, y: number) => void;
19
+ onBadgeLeave?: () => void;
20
+ onBadgeClick?: (badge: BadgeInfo, x: number, y: number) => void;
21
+ onBadgeDblClick?: (badge: BadgeInfo, x: number, y: number) => void;
22
+ }
23
+
24
+ function colorMap(color?: string): string {
25
+ const map: Record<string, string> = {
26
+ gray: '#9a9a9a',
27
+ magenta: '#c084fc',
28
+ yellow: '#facc15',
29
+ green: '#34d399',
30
+ red: '#f87171',
31
+ cyan: '#67e8f9',
32
+ blue: '#60a5fa',
33
+ white: '#e5e7eb'
34
+ };
35
+ return map[color || ''] || color || '#e6e6e6';
36
+ }
37
+
38
+ function renderFrame(
39
+ frame: LayoutFrame,
40
+ keyPrefix: string,
41
+ cellWidth: number,
42
+ cellHeight: number,
43
+ debugBorders: boolean,
44
+ onBadgeHover?: (badge: BadgeInfo, x: number, y: number) => void,
45
+ onBadgeLeave?: () => void,
46
+ onBadgeClick?: (badge: BadgeInfo, x: number, y: number) => void,
47
+ onBadgeDblClick?: (badge: BadgeInfo, x: number, y: number) => void,
48
+ offsetX = 0,
49
+ offsetY = 0,
50
+ out: React.ReactElement[] = []
51
+ ): React.ReactElement[] {
52
+ const x = offsetX + frame.x;
53
+ const y = offsetY + frame.y;
54
+ const keyBase = `${keyPrefix}-${x}-${y}`;
55
+
56
+ if (frame.node.type === 'text') {
57
+ const style = frame.node.style || {};
58
+ if (debugBorders) {
59
+ out.push(
60
+ <div
61
+ key={`${keyBase}-debug`}
62
+ style={{
63
+ position: 'absolute',
64
+ left: x * cellWidth,
65
+ top: y * cellHeight,
66
+ width: frame.width * cellWidth,
67
+ height: frame.height * cellHeight,
68
+ borderStyle: 'dashed',
69
+ borderWidth: 1,
70
+ borderColor: '#3a3a3a',
71
+ pointerEvents: 'none'
72
+ }}
73
+ />
74
+ );
75
+ }
76
+ const badge = style.badge;
77
+ out.push(
78
+ <span
79
+ key={`${keyBase}-text`}
80
+ style={{
81
+ position: 'absolute',
82
+ left: x * cellWidth,
83
+ top: y * cellHeight,
84
+ color: style.inverse ? '#111' : colorMap(style.color),
85
+ fontWeight: style.bold ? 600 : undefined,
86
+ opacity: style.dimColor ? 0.6 : undefined,
87
+ background: style.inverse ? '#e6e6e6' : undefined,
88
+ colorScheme: 'only light',
89
+ cursor: badge ? 'pointer' : undefined
90
+ }}
91
+ title={badge?.preview || undefined}
92
+ onMouseEnter={badge ? (evt) => onBadgeHover?.(badge, evt.clientX, evt.clientY) : undefined}
93
+ onMouseLeave={badge ? () => onBadgeLeave?.() : undefined}
94
+ onClick={badge ? (evt) => onBadgeClick?.(badge, evt.clientX, evt.clientY) : undefined}
95
+ onDoubleClick={badge ? (evt) => onBadgeDblClick?.(badge, evt.clientX, evt.clientY) : undefined}
96
+ >
97
+ {frame.node.text}
98
+ </span>
99
+ );
100
+ return out;
101
+ }
102
+
103
+ if (frame.node.type === 'box' && (frame.node.style?.borderStyle || debugBorders)) {
104
+ out.push(
105
+ <div
106
+ key={`${keyBase}-border`}
107
+ style={{
108
+ position: 'absolute',
109
+ left: x * cellWidth,
110
+ top: y * cellHeight,
111
+ width: frame.width * cellWidth,
112
+ height: frame.height * cellHeight,
113
+ borderStyle: frame.node.style?.borderStyle ? 'solid' : 'dashed',
114
+ borderWidth: 1,
115
+ borderColor: colorMap(frame.node.style?.borderColor || '#3a3a3a'),
116
+ pointerEvents: 'none'
117
+ }}
118
+ />
119
+ );
120
+ }
121
+
122
+ frame.children.forEach((child, idx) => {
123
+ renderFrame(child, `${keyBase}-${idx}`, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick, x, y, out);
124
+ });
125
+
126
+ return out;
127
+ }
128
+
129
+ export const WebFrameRenderer: React.FC<FrameRendererProps> = ({
130
+ node,
131
+ cols,
132
+ rows,
133
+ cellWidth,
134
+ cellHeight,
135
+ debugBorders = false,
136
+ onBadgeHover,
137
+ onBadgeLeave,
138
+ onBadgeClick,
139
+ onBadgeDblClick
140
+ }) => {
141
+ const frame = useMemo(() => computeLayout(node, cols, rows), [node, cols, rows]);
142
+ const elements = useMemo(
143
+ () => renderFrame(frame, 'root', cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick),
144
+ [frame, cellWidth, cellHeight, debugBorders, onBadgeHover, onBadgeLeave, onBadgeClick, onBadgeDblClick]
145
+ );
146
+
147
+ return <>{elements}</>;
148
+ };
@@ -0,0 +1 @@
1
+ export { WebNode } from './render.js';
@@ -0,0 +1,41 @@
1
+ import React from 'react';
2
+ import { LayoutNode, TextNode } from '../core/types.js';
3
+ import { toCssStyle } from '../core/style.js';
4
+
5
+ const monoStyle: React.CSSProperties = {
6
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace',
7
+ fontSize: 14,
8
+ lineHeight: '16px',
9
+ whiteSpace: 'pre'
10
+ };
11
+
12
+ function renderText(node: TextNode): React.ReactElement {
13
+ const style: React.CSSProperties = {
14
+ ...monoStyle,
15
+ color: node.style?.color,
16
+ fontWeight: node.style?.bold ? 600 : undefined,
17
+ opacity: node.style?.dimColor ? 0.6 : undefined,
18
+ background: node.style?.inverse ? '#111' : undefined,
19
+ colorScheme: 'only light'
20
+ };
21
+ return <span style={style}>{node.text}</span>;
22
+ }
23
+
24
+ export function WebNode({ node }: { node: LayoutNode }): React.ReactElement {
25
+ if (node.type === 'text') {
26
+ return renderText(node);
27
+ }
28
+
29
+ const style = {
30
+ ...monoStyle,
31
+ ...toCssStyle(node.style)
32
+ } as React.CSSProperties;
33
+
34
+ return (
35
+ <div style={style}>
36
+ {node.children.map((child, index) => (
37
+ <WebNode key={`${child.type}-${index}`} node={child} />
38
+ ))}
39
+ </div>
40
+ );
41
+ }