@gizai/cli 2.0.1 → 2.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.
@@ -1,81 +0,0 @@
1
- // The Agent runtime's stdio MCP client. It runs in the person's Computer as
2
- // `node --input-type=module -e <this source> <request JSON> <server argv...>`: it starts the server, initializes it,
3
- // sends one request (tools/list follows every cursor) and prints the result as one JSON line. The server's stderr is
4
- // shown only when the call fails. It replaced `npx @modelcontextprotocol/inspector@0.4.1 --cli`, a version without a
5
- // CLI mode that opened its web UI and hung every plugin call until it was canceled (10/6).
6
- import { spawn } from 'node:child_process';
7
-
8
- const [requestJson, command, ...args] = process.argv.slice(1);
9
- const request = JSON.parse(requestJson);
10
- const child = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'] });
11
- let stderr = '';
12
- child.stderr.on('data', (chunk) => { stderr = (stderr + chunk).slice(-8000); });
13
-
14
- function fail(message) {
15
- process.stderr.write(`${message}\n${stderr}`);
16
- child.kill('SIGKILL');
17
- process.exit(1);
18
- }
19
- child.on('error', (error) => fail(`MCP server could not start: ${error.message}`));
20
- child.on('exit', (code, signal) => fail(`MCP server exited (${signal || code}) before it answered.`));
21
-
22
- const send = (message) => child.stdin.write(`${JSON.stringify({ jsonrpc: '2.0', ...message })}\n`);
23
- const pending = new Map();
24
- let nextId = 1;
25
- let buffer = '';
26
- child.stdout.setEncoding('utf8');
27
- child.stdout.on('data', (chunk) => {
28
- buffer += chunk;
29
- for (let newline = buffer.indexOf('\n'); newline >= 0; newline = buffer.indexOf('\n')) {
30
- const line = buffer.slice(0, newline).trim();
31
- buffer = buffer.slice(newline + 1);
32
- let message;
33
- // Like the MCP SDK, a line that is not JSON-RPC (a stray log) is skipped rather than ending the call.
34
- try { message = JSON.parse(line); } catch { continue; }
35
- if (message?.method && message.id !== undefined) {
36
- send(message.method === 'ping' ? { id: message.id, result: {} }
37
- : { id: message.id, error: { code: -32601, message: `${message.method} is not supported by this client.` } });
38
- continue;
39
- }
40
- const waiter = pending.get(message?.id);
41
- if (!waiter) continue;
42
- pending.delete(message.id);
43
- if (message.error) fail(`MCP ${waiter.method} failed: ${message.error.message || JSON.stringify(message.error)}`);
44
- else waiter.resolve(message.result);
45
- }
46
- });
47
-
48
- function call(method, params) {
49
- return new Promise((resolve) => {
50
- const id = nextId++;
51
- pending.set(id, { method, resolve });
52
- send({ id, method, ...(params === undefined ? {} : { params }) });
53
- });
54
- }
55
-
56
- await call('initialize', { protocolVersion: '2025-06-18', capabilities: {}, clientInfo: { name: 'giz-agent', version: '1' } });
57
- send({ method: 'notifications/initialized' });
58
- let result;
59
- if (request.method === 'tools/list') {
60
- const tools = [];
61
- const cursors = new Set();
62
- let cursor;
63
- for (;;) {
64
- const page = await call('tools/list', cursor === undefined ? undefined : { cursor });
65
- if (!Array.isArray(page?.tools)) fail('MCP server returned an invalid tool catalog.');
66
- tools.push(...page.tools);
67
- if (page?.nextCursor == null) break;
68
- if (cursors.has(page.nextCursor) || cursors.size >= 32) fail('MCP server did not finish its tool catalog.');
69
- cursors.add(page.nextCursor);
70
- cursor = page.nextCursor;
71
- }
72
- result = { tools };
73
- } else if (request.method === 'tools/call') {
74
- result = await call('tools/call', { name: request.name, arguments: request.arguments ?? {} });
75
- } else {
76
- fail(`Unsupported MCP request ${request.method}.`);
77
- }
78
- child.removeAllListeners('exit');
79
- process.stdout.write(`${JSON.stringify(result)}\n`);
80
- child.kill();
81
- process.exit(0);
@@ -1,321 +0,0 @@
1
- // media.capture inside the person's Computer: records a browser scenario at 2x device pixels, logs every pointer target,
2
- // typing span and wait, shortens long waits, and writes an editable HyperFrames take (camera that follows the action, a
3
- // redrawn cursor, click and typing feedback, speed chips). The request JSON file (its path is the one argument)
4
- // is deleted once read: its sign-in steps carry the person's secrets. Result JSON on stdout.
5
- // Runs with giz-media-node (video-tools runtime): playwright-core and HYPERFRAMES_BROWSER_PATH come from that runtime.
6
- import { createRequire } from 'node:module';
7
- import { execFileSync } from 'node:child_process';
8
- import fs from 'node:fs';
9
- import path from 'node:path';
10
-
11
- const request = JSON.parse(fs.readFileSync(process.argv[1], 'utf8'));
12
- fs.rmSync(process.argv[1], { force: true });
13
- const { chromium } = createRequire(path.join(process.cwd(), 'noop.js'))('playwright-core');
14
- const out = request.dir;
15
- const [VW, VH] = request.viewport;
16
- const [CW, CH] = request.canvas;
17
- const FPS = 30;
18
- const SHORTEN_AFTER = 2.5; // A wait longer than this plays at a labelled speed instead of real time.
19
- const SHORTENED = 1.2;
20
- fs.mkdirSync(path.join(out, 'frames'), { recursive: true });
21
-
22
- const browser = await chromium.launch({ executablePath: process.env.HYPERFRAMES_BROWSER_PATH, args: ['--force-device-scale-factor=2'] });
23
- const context = await browser.newContext({ viewport: { width: VW, height: VH }, deviceScaleFactor: 2, locale: request.locale,
24
- timezoneId: request.timezone });
25
- const page = await context.newPage();
26
- const events = [];
27
- let t0 = 0;
28
- const now = () => (Date.now() - t0) / 1000;
29
- const round = (value) => Math.round(value * 1000) / 1000;
30
- const log = (event) => { if (t0) events.push({ t: round(now()), ...event }); };
31
-
32
- function locate(target) {
33
- const text = String(target || '');
34
- if (text.startsWith('css:')) return page.locator(text.slice(4)).first();
35
- if (text.startsWith('label:')) return page.getByLabel(text.slice(6)).locator('visible=true').first();
36
- if (text.startsWith('placeholder:')) return page.getByPlaceholder(text.slice(12)).locator('visible=true').first();
37
- if (text.startsWith('role:')) {
38
- const [, role, ...name] = text.split(':');
39
- return page.getByRole(role, { name: name.join(':') || undefined }).locator('visible=true').first();
40
- }
41
- // Visible text: an exact match first, so "저장" does not pick "저장된 보기".
42
- const exact = page.getByText(text, { exact: true }).locator('visible=true');
43
- return exact.or(page.getByText(text).locator('visible=true')).first();
44
- }
45
-
46
- async function box(locator) {
47
- const b = await locator.boundingBox().catch(() => null);
48
- return b && { x: round(b.x), y: round(b.y), w: round(b.width), h: round(b.height) };
49
- }
50
-
51
- // A real pointer path, so hover states appear the way a person causes them.
52
- async function point(step) {
53
- const locator = locate(step.target);
54
- await locator.waitFor({ state: 'visible', timeout: step.timeoutMs ?? 15000 });
55
- await locator.scrollIntoViewIfNeeded().catch(() => {});
56
- const b = await box(locator);
57
- if (!b) throw new Error(`${step.target} has no visible box`);
58
- const x = b.x + b.w / 2, y = b.y + b.h / 2;
59
- await page.mouse.move(x, y, { steps: 18 });
60
- return { locator, b, x, y };
61
- }
62
-
63
- const label = (step) => step.label || (step.secret ? '••••' : step.target || step.value || step.action);
64
-
65
- async function run(step) {
66
- const timeout = step.timeoutMs ?? 15000;
67
- switch (step.action) {
68
- case 'open': await page.goto(new URL(step.value, page.url() === 'about:blank' ? request.url : page.url()).href, { waitUntil: 'domcontentloaded' }); log({ action: 'open', label: label(step) }); break;
69
- case 'click': case 'doubleClick': case 'rightClick': case 'hover': {
70
- const { b, x, y } = await point(step);
71
- log({ action: step.action, label: label(step), box: b, x, y });
72
- if (step.action === 'click') await page.mouse.click(x, y);
73
- if (step.action === 'doubleClick') await page.mouse.dblclick(x, y);
74
- if (step.action === 'rightClick') await page.mouse.click(x, y, { button: 'right' });
75
- break;
76
- }
77
- case 'type': case 'fill': {
78
- const { locator, b, x, y } = await point(step);
79
- log({ action: 'click', label: label(step), box: b, x, y });
80
- await page.mouse.click(x, y);
81
- if (step.action === 'fill') await locator.fill('');
82
- const from = now();
83
- await locator.pressSequentially(String(step.value ?? ''), { delay: 45 });
84
- log({ action: 'typing', label: label(step), box: await box(locator), from: round(from) });
85
- break;
86
- }
87
- case 'press': await page.keyboard.press(step.value); log({ action: 'press', label: step.value }); break;
88
- case 'upload': {
89
- const files = String(step.value || '').split('\n').filter(Boolean).map((file) => path.resolve('/home/user', file));
90
- await locate(step.target).setInputFiles(files, { timeout });
91
- log({ action: 'upload', label: step.label || files.map((file) => path.basename(file)).join(', ') });
92
- break;
93
- }
94
- case 'waitFor': {
95
- const from = now();
96
- await locate(step.target).waitFor({ state: 'visible', timeout: step.timeoutMs ?? 180000 });
97
- log({ action: 'wait', label: label(step), from: round(from) });
98
- break;
99
- }
100
- case 'scroll': {
101
- const locator = locate(step.target);
102
- await locator.waitFor({ state: 'attached', timeout });
103
- await locator.evaluate((element) => element.scrollIntoView({ block: 'center', behavior: 'smooth' }));
104
- await page.waitForTimeout(900);
105
- log({ action: 'scroll', label: label(step), box: await box(locator) });
106
- break;
107
- }
108
- case 'focus': {
109
- const locator = locate(step.target);
110
- await locator.waitFor({ state: 'visible', timeout });
111
- log({ action: 'focus', label: label(step), box: await box(locator) });
112
- await page.waitForTimeout(step.ms ?? 1500);
113
- break;
114
- }
115
- case 'pause': await page.waitForTimeout(step.ms ?? 1000); break;
116
- case 'mark': log({ action: 'mark', label: label(step) }); break;
117
- default: throw new Error(`Unknown capture action: ${step.action}`);
118
- }
119
- }
120
-
121
- const frames = [];
122
- let cdp;
123
- let failure;
124
- let failedStep;
125
- try {
126
- await page.goto(request.url, { waitUntil: 'domcontentloaded' });
127
- await page.waitForLoadState('networkidle', { timeout: 15000 }).catch(() => {});
128
- for (const [index, step] of request.setup.entries()) {
129
- failedStep = { phase: 'setup', index };
130
- await run(step);
131
- }
132
- // Only the scenario is recorded; signing in and preparing data stay off camera.
133
- cdp = await context.newCDPSession(page);
134
- cdp.on('Page.screencastFrame', async (frame) => {
135
- const file = `frames/${String(frames.length).padStart(6, '0')}.jpg`;
136
- fs.writeFileSync(path.join(out, file), Buffer.from(frame.data, 'base64'));
137
- frames.push([frame.metadata.timestamp, file]);
138
- await cdp.send('Page.screencastFrameAck', { sessionId: frame.sessionId }).catch(() => {});
139
- });
140
- t0 = Date.now();
141
- await cdp.send('Page.startScreencast', { format: 'jpeg', quality: 92, maxWidth: VW * 2, maxHeight: VH * 2 });
142
- await page.waitForTimeout(800);
143
- for (const [index, step] of request.steps.entries()) {
144
- failedStep = { phase: 'steps', index };
145
- await run(step);
146
- }
147
- await page.waitForTimeout(1200);
148
- failedStep = undefined;
149
- } catch (error) {
150
- failure = String(error?.message || error).split('\n')[0];
151
- await page.screenshot({ path: path.join(out, 'failed.png') }).catch(() => {});
152
- } finally {
153
- await cdp?.send('Page.stopScreencast').catch(() => {});
154
- }
155
- const end = now();
156
- await context.close();
157
- await browser.close();
158
- if (failure) {
159
- fs.rmSync(path.join(out, 'frames'), { recursive: true, force: true });
160
- console.log(JSON.stringify({ failed: true, failure, step: failedStep, screenshot: 'failed.png' }));
161
- process.exit(0);
162
- }
163
-
164
- // Long waits are shortened: a piecewise-linear clock maps recorded time to take time, and the frames, events and speed
165
- // chips all read that one map, so the cursor and camera stay on the action.
166
- const spans = events.filter((event) => event.action === 'wait' && event.t - event.from > SHORTEN_AFTER)
167
- .map((event) => ({ from: event.from, to: event.t, rate: Math.min(16, (event.t - event.from) / SHORTENED) }));
168
- const clock = (t) => {
169
- let shift = 0;
170
- for (const span of spans) {
171
- if (t <= span.from) break;
172
- const inside = Math.min(t, span.to) - span.from;
173
- shift += inside - inside / span.rate;
174
- }
175
- return round(t - shift);
176
- };
177
- const duration = clock(end);
178
- const start = frames[0][0];
179
- const list = frames.map(([stamp, file], index) => {
180
- const at = clock(stamp - start);
181
- const next = index + 1 < frames.length ? clock(frames[index + 1][0] - start) : duration;
182
- return `file '${file}'\nduration ${Math.max(0.001, next - at).toFixed(3)}`;
183
- });
184
- fs.writeFileSync(path.join(out, 'frames.txt'), `ffconcat version 1.0\n${list.join('\n')}\nfile '${frames.at(-1)[1]}'\n`);
185
- // A keyframe every second: HyperFrames seeks the recording frame by frame and freezes on sparse keyframes.
186
- // Threads and lookahead are fixed: ffmpeg sizes them by the cores it sees, and a 2 GB Computer that saw the host's cores
187
- // was OOM-killed at 1.4 GB on a 2880x1620 take (prod 10/8); fixed, it stays near 0.55 GB on any core count.
188
- try {
189
- execFileSync('ffmpeg', ['-v', 'error', '-y', '-threads', '2', '-filter_threads', '1', '-f', 'concat', '-safe', '0', '-i', path.join(out, 'frames.txt'),
190
- '-vf', `fps=${FPS},scale=trunc(iw/2)*2:trunc(ih/2)*2,format=yuv420p`, '-c:v', 'libx264', '-crf', '16', '-preset', 'medium', '-g', String(FPS),
191
- '-threads', '2', '-rc-lookahead', '10', '-movflags', '+faststart', path.join(out, 'screen.mp4')]);
192
- } catch (error) {
193
- // The job reports an out-of-memory kill as such (137), not as a stack the Agent would answer by lowering quality.
194
- if (error.signal === 'SIGKILL') { console.error('The screen recording encoder ran out of Computer memory.'); process.exit(137); }
195
- throw error;
196
- }
197
- fs.rmSync(path.join(out, 'frames'), { recursive: true, force: true });
198
- fs.rmSync(path.join(out, 'frames.txt'), { force: true });
199
- const timeline = events.map((event) => ({ ...event, t: clock(event.t), ...(event.from !== undefined ? { from: clock(event.from) } : {}) }));
200
- const shortened = spans.map((span) => ({ from: clock(span.from), to: clock(span.to), rate: Math.round(span.rate * 10) / 10 }));
201
- fs.writeFileSync(path.join(out, 'events.json'), JSON.stringify({ viewport: [VW, VH], scale: 2, duration, shortened, events: timeline }, null, 1));
202
- fs.writeFileSync(path.join(out, 'take.html'), composition(timeline, shortened));
203
- console.log(JSON.stringify({ duration, shortened, events: timeline.map(({ t, action, label: name, from }) => ({ t, action, label: name, ...(from !== undefined ? { from } : {}) })) }));
204
-
205
- // The take composition: the screen fills the canvas, the camera frames each cluster of actions and returns to the whole
206
- // screen between them, and the cursor keeps its size while the camera zooms because it lives inside the camera.
207
- function composition(timeline, shortened) {
208
- const scale = Math.min(CW / VW, CH / VH);
209
- const SW = VW * scale, SH = VH * scale, SX = (CW - SW) / 2, SY = (CH - SH) / 2;
210
- const maxZoom = request.camera === false ? 1 : request.maxZoom;
211
- const id = request.name;
212
- const tl = [];
213
- const html = [];
214
- const frame = (b) => {
215
- if (!b || maxZoom <= 1) return { s: 1, x: 0, y: 0 };
216
- const pad = 1.3;
217
- const s = Math.max(1, Math.min(maxZoom, SW / (b.w * scale * pad), SH / (b.h * scale * pad)));
218
- const cx = (b.x + b.w / 2) * scale, cy = (b.y + b.h / 2) * scale;
219
- const x = Math.min(0, Math.max(SW - SW * s, SW / 2 - cx * s));
220
- const y = Math.min(0, Math.max(SH - SH * s, SH / 2 - cy * s));
221
- return { s: round(s), x: Math.round(x), y: Math.round(y) };
222
- };
223
- const union = (a, b) => {
224
- const x = Math.min(a.x, b.x), y = Math.min(a.y, b.y);
225
- return { x, y, w: Math.max(a.x + a.w, b.x + b.w) - x, h: Math.max(a.y + a.h, b.y + b.h) - y };
226
- };
227
- // Actions close in time and space share one framing; a page change or a shortened wait ends it.
228
- const clusters = [];
229
- for (const event of timeline) {
230
- const last = clusters.at(-1);
231
- if (event.action === 'open' || event.action === 'wait') { if (last) last.closed = true; continue; }
232
- if (!event.box) continue;
233
- const begins = event.from ?? event.t;
234
- const ends = event.action === 'focus' ? event.t + 1.5 : event.t;
235
- if (last && !last.closed && begins - last.end < 3 && frame(union(last.box, event.box)).s >= 1.2) {
236
- last.box = union(last.box, event.box);
237
- last.end = Math.max(last.end, ends);
238
- } else clusters.push({ start: begins, end: ends, box: event.box });
239
- }
240
- // A framing too wide to zoom is a full-screen shot, so the camera always lands on what the next action shows.
241
- const moves = [];
242
- const wide = { s: 1, x: 0, y: 0 };
243
- let camera = wide, free = 0;
244
- for (const [index, cluster] of clusters.entries()) {
245
- const framed = frame(cluster.box);
246
- const target = framed.s < 1.15 ? wide : framed;
247
- if (target.s !== camera.s || target.x !== camera.x || target.y !== camera.y) {
248
- const at = Math.max(free, cluster.start - 0.8);
249
- moves.push({ at: round(at), d: round(Math.min(0.8, Math.max(0.3, cluster.start - at))), ...target });
250
- camera = target;
251
- }
252
- const hold = cluster.end + 1.4;
253
- free = hold;
254
- const next = clusters[index + 1];
255
- if (camera !== wide && (!next || next.start - 0.8 - hold > 1.6)) {
256
- moves.push({ at: round(Math.min(hold, duration - 0.9)), d: 0.9, ...wide });
257
- camera = wide;
258
- free = hold + 0.9;
259
- }
260
- }
261
- for (const move of moves) {
262
- tl.push(`tl.to("#${id}-cam",{scale:${move.s},x:${move.x},y:${move.y},duration:${move.d},ease:"power2.inOut"},${move.at});`);
263
- tl.push(`tl.to("#${id}-cursor .scale",{scale:${round(1 / move.s)},duration:${move.d},ease:"power2.inOut"},${move.at});`);
264
- }
265
- const pointers = timeline.filter((event) => ['click', 'doubleClick', 'rightClick', 'hover'].includes(event.action) && event.x !== undefined);
266
- if (pointers.length) {
267
- const first = pointers[0];
268
- tl.push(`tl.set("#${id}-cursor",{x:${Math.round(Math.min(VW - 40, first.x + 240) * scale)},y:${Math.round(Math.min(VH - 40, first.y + 160) * scale)}},0);`);
269
- let previousAt = 0;
270
- for (const event of pointers) {
271
- const move = round(Math.max(0.25, Math.min(0.7, event.t - previousAt - 0.1)));
272
- tl.push(`tl.to("#${id}-cursor",{x:${Math.round(event.x * scale)},y:${Math.round(event.y * scale)},duration:${move},ease:"power2.inOut"},${round(Math.max(0, event.t - move))});`);
273
- if (event.action !== 'hover') {
274
- tl.push(`tl.to("#${id}-cursor svg",{scale:0.82,duration:0.08,yoyo:true,repeat:1,transformOrigin:"6px 3px"},${event.t});`);
275
- tl.push(`tl.fromTo("#${id}-cursor .ring",{scale:0.2,opacity:0.9},{scale:1.6,opacity:0,duration:0.5,ease:"power2.out",immediateRender:false},${event.t});`);
276
- }
277
- previousAt = event.t;
278
- }
279
- }
280
- for (const [index, event] of timeline.filter((item) => item.action === 'typing' && item.box).entries()) {
281
- const b = event.box;
282
- html.push(`<div class="typing" id="${id}-type${index}" style="left:${Math.round(b.x * scale - 5)}px;top:${Math.round(b.y * scale - 5)}px;width:${Math.round(b.w * scale + 10)}px;height:${Math.round(b.h * scale + 10)}px"></div>`);
283
- tl.push(`tl.fromTo("#${id}-type${index}",{opacity:0},{opacity:1,duration:0.2},${event.from});`);
284
- tl.push(`tl.to("#${id}-type${index}",{opacity:0,duration:0.3},${round(event.t + 0.6)});`);
285
- }
286
- const chips = shortened.map((span, index) => {
287
- tl.push(`tl.fromTo("#${id}-speed${index}",{opacity:0,scale:0.9},{opacity:1,scale:1,duration:0.2},${span.from});`);
288
- tl.push(`tl.to("#${id}-speed${index}",{opacity:0,duration:0.2},${round(Math.max(span.from + 0.2, span.to - 0.2))});`);
289
- return `<div class="speed" id="${id}-speed${index}">▶▶ ${span.rate}×</div>`;
290
- });
291
- return `<!doctype html>
292
- <html><head><meta charset="UTF-8">
293
- <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
294
- <style>
295
- #${id}{position:relative;width:${CW}px;height:${CH}px;overflow:hidden;background:#0b0c10;}
296
- #${id} .screen{position:absolute;left:${SX}px;top:${SY}px;width:${SW}px;height:${SH}px;overflow:hidden;}
297
- #${id}-cam{position:absolute;inset:0;transform-origin:0 0;}
298
- #${id}-cam video{position:absolute;inset:0;width:100%;height:100%;object-fit:fill;}
299
- #${id} .typing{position:absolute;border-radius:10px;border:3px solid #6463cc;box-shadow:0 0 0 6px rgba(100,99,204,.18);opacity:0;}
300
- #${id}-cursor{position:absolute;left:0;top:0;width:0;height:0;z-index:5;}
301
- #${id}-cursor .scale{position:absolute;left:-6px;top:-3px;transform-origin:6px 3px;filter:drop-shadow(0 3px 6px rgba(0,0,0,.35));}
302
- #${id}-cursor .ring{position:absolute;left:-26px;top:-29px;width:64px;height:64px;border-radius:50%;border:3px solid #6463cc;opacity:0;}
303
- #${id} .speed{position:absolute;right:${Math.round(SX + 28)}px;top:${Math.round(SY + 24)}px;z-index:6;background:rgba(12,14,30,.86);color:#fff;
304
- font:600 24px/1 system-ui,sans-serif;padding:12px 20px;border-radius:999px;opacity:0;}
305
- </style></head>
306
- <body><div id="${id}" data-composition-id="${id}" data-start="0" data-width="${CW}" data-height="${CH}" data-duration="${duration}">
307
- <div class="screen"><div id="${id}-cam">
308
- <video id="${id}-screen" src="screen.mp4" muted playsinline data-start="0" data-duration="${duration}"></video>
309
- ${html.join('\n')}
310
- <div id="${id}-cursor"${pointers.length ? '' : ' style="display:none"'}><div class="scale"><div class="ring"></div><svg viewBox="0 0 32 32" width="${Math.round(40 * scale / 1.2)}" height="${Math.round(40 * scale / 1.2)}"><path d="M6 3 L6 25 L11.5 19.8 L15.4 28.6 L19.2 27 L15.4 18.4 L23 18.2 Z" fill="#111" stroke="#fff" stroke-width="2" stroke-linejoin="round"/></svg></div></div>
311
- </div></div>
312
- ${chips.join('\n')}
313
- </div>
314
- <script>
315
- const tl = gsap.timeline({ paused: true });
316
- ${tl.join('\n')}
317
- window.__timelines = window.__timelines || {};
318
- window.__timelines["${id}"] = tl;
319
- </script></body></html>
320
- `;
321
- }
package/cli/update.mjs DELETED
@@ -1,150 +0,0 @@
1
- import { randomBytes } from 'node:crypto'
2
- import { mkdir, open, readFile, rename, stat, unlink, writeFile } from 'node:fs/promises'
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { spawn, spawnSync } from 'node:child_process'
6
- import { fileURLToPath } from 'node:url'
7
-
8
- export const UPDATE_INTERVAL = 24 * 60 * 60 * 1000
9
- export const UPDATE_URL = 'https://registry.npmjs.org/@gizai%2fcli/latest'
10
- export const UPDATE_COMMAND = 'npm install -g @gizai/cli'
11
- const packageRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
12
- const cachePath = path.join(process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache'), 'giz', 'cli-update.json')
13
-
14
- export function installSource(root = packageRoot) {
15
- const resolved = path.resolve(root)
16
- const parts = resolved.split(path.sep)
17
- const nodeModules = parts.lastIndexOf('node_modules')
18
- if (nodeModules < 0) return { type: 'source' }
19
- const normalized = resolved.toLowerCase()
20
- if (normalized.includes(`${path.sep}.pnpm${path.sep}`) || /[\\/]pnpm[\\/]global[\\/]/.test(normalized)) return { type: 'pnpm', executable: 'pnpm', args: ['add', '-g', '@gizai/cli'], command: 'pnpm add -g @gizai/cli' }
21
- if (normalized.includes(`${path.sep}.bun${path.sep}`)) return { type: 'bun', executable: 'bun', args: ['add', '-g', '@gizai/cli'], command: 'bun add -g @gizai/cli' }
22
- if (normalized.includes(`${path.sep}.yarn${path.sep}`) || normalized.includes(`${path.sep}yarn${path.sep}global${path.sep}`)) return { type: 'yarn', executable: 'yarn', args: ['global', 'add', '@gizai/cli'], command: 'yarn global add @gizai/cli' }
23
- return { type: 'npm', executable: 'npm', args: ['install', '-g', '@gizai/cli'], command: UPDATE_COMMAND }
24
- }
25
-
26
- export function stableVersion(value) {
27
- const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(String(value || ''))
28
- if (!match) return undefined
29
- const prerelease = match[4]?.split('.')
30
- if (prerelease?.some((part) => !part || (/^\d+$/.test(part) && part.length > 1 && part.startsWith('0')))) return undefined
31
- return { major: match[1], minor: match[2], patch: match[3], prerelease }
32
- }
33
-
34
- export function compareVersions(left, right) {
35
- const a = stableVersion(left)
36
- const b = stableVersion(right)
37
- if (!a || !b) return undefined
38
- for (const key of ['major', 'minor', 'patch']) {
39
- if (a[key] === b[key]) continue
40
- return a[key].length === b[key].length ? (a[key] < b[key] ? -1 : 1) : (a[key].length < b[key].length ? -1 : 1)
41
- }
42
- if (!a.prerelease && !b.prerelease) return 0
43
- if (!a.prerelease) return 1
44
- if (!b.prerelease) return -1
45
- for (let index = 0; index < Math.max(a.prerelease.length, b.prerelease.length); index += 1) {
46
- if (a.prerelease[index] === undefined) return -1
47
- if (b.prerelease[index] === undefined) return 1
48
- if (a.prerelease[index] === b.prerelease[index]) continue
49
- const an = /^\d+$/.test(a.prerelease[index])
50
- const bn = /^\d+$/.test(b.prerelease[index])
51
- if (an && bn) {
52
- const left = a.prerelease[index]
53
- const right = b.prerelease[index]
54
- return left.length === right.length ? (left < right ? -1 : 1) : (left.length < right.length ? -1 : 1)
55
- }
56
- if (an !== bn) return an ? -1 : 1
57
- return a.prerelease[index] < b.prerelease[index] ? -1 : 1
58
- }
59
- return 0
60
- }
61
-
62
- export async function readUpdateCache(file = cachePath) {
63
- try {
64
- const cache = JSON.parse(await readFile(file, 'utf8'))
65
- if (!Number.isFinite(cache.checkedAt) || typeof cache.latest !== 'string' || !stableVersion(cache.latest)) return undefined
66
- return cache
67
- } catch { return undefined }
68
- }
69
-
70
- async function atomicCache(file, value) {
71
- await mkdir(path.dirname(file), { recursive: true, mode: 0o700 })
72
- const temporary = `${file}.${process.pid}.${randomBytes(6).toString('hex')}.tmp`
73
- try {
74
- await writeFile(temporary, `${JSON.stringify(value)}\n`, { mode: 0o600, flag: 'wx' })
75
- await rename(temporary, file)
76
- } finally { await unlink(temporary).catch(() => {}) }
77
- }
78
-
79
- export async function latestStableVersion(fetcher = fetch, url = UPDATE_URL) {
80
- const response = await fetcher(url, { signal: AbortSignal.timeout(5000), headers: { accept: 'application/json', 'npm-command': 'view' } })
81
- if (!response.ok) throw new Error(`Registry HTTP ${response.status}`)
82
- const version = (await response.json()).version
83
- const parsed = stableVersion(version)
84
- if (!parsed || parsed.prerelease) throw new Error('Registry returned no stable version')
85
- return version
86
- }
87
-
88
- export async function checkForUpdate({ file = cachePath, fetcher = fetch, now = Date.now, url = UPDATE_URL } = {}) {
89
- const lockFile = `${file}.lock`
90
- let createdLock = false
91
- try { await mkdir(path.dirname(file), { recursive: true, mode: 0o700 }) } catch { return undefined }
92
- let lockHandle
93
- try { lockHandle = await open(lockFile, 'wx', 0o600); createdLock = true }
94
- catch (error) {
95
- if (error.code === 'EEXIST') {
96
- try { if (now() - (await stat(lockFile)).mtimeMs > 5 * 60_000) { await unlink(lockFile); return await checkForUpdate({ file, fetcher, now, url }) } } catch {}
97
- }
98
- return undefined
99
- }
100
- try {
101
- const cached = await readUpdateCache(file)
102
- if (cached && now() - cached.checkedAt < UPDATE_INTERVAL) return cached
103
- let latest
104
- try {
105
- latest = await latestStableVersion(fetcher, url)
106
- } catch { return cached }
107
- if (!latest) return undefined
108
- const result = { checkedAt: now(), latest }
109
- await atomicCache(file, result)
110
- return result
111
- } finally {
112
- await lockHandle.close().catch(() => {})
113
- if (createdLock) await unlink(lockFile).catch(() => {})
114
- }
115
- }
116
-
117
- export async function explicitUpdate({ version, source = installSource(), fetcher = fetch, run = spawnSync }) {
118
- if (source.type === 'source') throw new Error('This Giz CLI is running from a source checkout; update it with your source control workflow.')
119
- const latest = await latestStableVersion(fetcher)
120
- const comparison = compareVersions(latest, version)
121
- if (comparison === undefined) throw new Error('The installed CLI version is invalid; update it with your package manager.')
122
- if (comparison <= 0) return { latest, updated: false, message: `Giz CLI ${version} is already up to date.` }
123
- if (source.type !== 'npm') return { latest, updated: false, message: `Update available: ${version} → ${latest}\n${source.command}` }
124
- const result = run(source.executable, source.args, { encoding: 'utf8', stdio: 'inherit' })
125
- if (result.error || result.status !== 0) throw new Error(`${source.executable} update failed (${result.error?.code || result.signal || result.status}). Run: ${source.command}`)
126
- return { latest, updated: true, message: `Giz CLI updated to ${latest}.` }
127
- }
128
-
129
- export function startBackgroundCheck({ file = cachePath, fetcherUrl = UPDATE_URL, entry = path.join(path.dirname(fileURLToPath(import.meta.url)), 'giz.mjs'), spawnProcess = spawn } = {}) {
130
- try {
131
- const child = spawnProcess(process.execPath, [entry, '__update-check', '--cache', file, '--registry', fetcherUrl], {
132
- detached: true, stdio: 'ignore', env: { ...process.env, GIZ_UPDATE_CHECK_BACKGROUND: '1' }
133
- })
134
- child.on?.('error', () => {})
135
- child.unref()
136
- return true
137
- } catch { return false }
138
- }
139
-
140
- export async function maybeNotify({ version, source = installSource(), mode = 'notify', env = process.env,
141
- stderr = process.stderr, stdout = process.stdout, cacheFile = cachePath, now = Date.now,
142
- spawnCheck = startBackgroundCheck } = {}) {
143
- if (source.type === 'source' || mode === 'off' || env.GIZ_NO_UPDATE_CHECK === '1') return
144
- const cache = await readUpdateCache(cacheFile)
145
- if (!cache || cache.checkedAt <= 0 || now() - cache.checkedAt >= UPDATE_INTERVAL) spawnCheck({ file: cacheFile })
146
- if (env.CI || !stderr.isTTY || stdout.isTTY !== true || env.GIZ_JSON === '1') return
147
- if (cache && compareVersions(cache.latest, version) > 0) {
148
- stderr.write(`Update available: ${version} → ${cache.latest}\n${source.command}\n`)
149
- }
150
- }
package/mcp.json DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
- "mcpServers": {
4
- "giz": { "type": "streamable-http", "url": "https://www.giz.ai/api/mcp" }
5
- }
6
- }
package/plugin.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
- "name": "giz",
4
- "version": "2.0.1",
5
- "description": "Run Giz models, media generation, Drive, tables, Apps and Computers from any agent: the giz skill plus the Giz MCP server.",
6
- "author": { "name": "Giz", "url": "https://www.giz.ai" },
7
- "homepage": "https://www.giz.ai/mcp/",
8
- "license": "UNLICENSED",
9
- "keywords": ["giz", "mcp", "media", "drive", "computer"]
10
- }