ponzu 1.71.1 → 1.71.2
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/dist/index.js +195 -95
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -6978,7 +6978,7 @@ var require_dist = __commonJS(function(exports, module) {
|
|
|
6978
6978
|
// apps/cli/src/index.ts
|
|
6979
6979
|
import { existsSync as existsSync13 } from "fs";
|
|
6980
6980
|
import { networkInterfaces } from "os";
|
|
6981
|
-
import { dirname as
|
|
6981
|
+
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join21, resolve as resolve5 } from "path";
|
|
6982
6982
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6983
6983
|
import { parseArgs } from "util";
|
|
6984
6984
|
|
|
@@ -33328,7 +33328,7 @@ When to watch: whenever the person might react to the work on the canvas. That's
|
|
|
33328
33328
|
|
|
33329
33329
|
Watching doesn't cut you off from chat. A message the person types reaches you as soon as the current wait returns, within a minute. To tell them something (here's the link, which option you'd pick), write it in chat, then keep watching.
|
|
33330
33330
|
|
|
33331
|
-
1. listen: wait_for_activity with the cursor from your last call. Start with types comment.created, comment.replied, comment.edited, file.changed. An edit replaces what the comment asks; work from the new text. If the wait says Ponzu restarted or lost track, it lists the open threads waiting on you instead: work through those, then carry on with the cursor it gives. The wait hears from every file you've worked in this session and names the file for any event outside the one you passed: pass that file when you act on it.
|
|
33331
|
+
1. listen: wait_for_activity with the cursor from your last call. Start with types comment.created, comment.replied, comment.edited, file.changed. An edit replaces what the comment asks; work from the new text. If the wait says Ponzu restarted or lost track, it lists the open threads waiting on you instead: work through those, then carry on with the cursor it gives. If it says Ponzu stopped and isn't back yet, nothing was checked: wait again with the cursor it gives, as Ponzu is most likely restarting into an update. The wait hears from every file you've worked in this session and names the file for any event outside the one you passed: pass that file when you act on it.
|
|
33332
33332
|
2. triage each comment:
|
|
33333
33333
|
- change ("make this bigger", "try blue"): do it.
|
|
33334
33334
|
- question ("why this font?"): reply with an answer, leave it open.
|
|
@@ -33542,7 +33542,7 @@ var listenTools = [
|
|
|
33542
33542
|
title: "Wait for activity",
|
|
33543
33543
|
group: "listen",
|
|
33544
33544
|
readOnly: true,
|
|
33545
|
-
description: "Watch the canvas. Call this instead of ending your turn whenever the person might react to your work there: after you put something up for review, or while you design together. Calling it again and again is the loop (get_guide('watch')). Long-polls for what people do in the canvas: new comments and replies, edits made in a text editor, other agents' changes, in this file and the others you've worked in this session, saying which file each is in. Returns immediately if anything happened after `since`; otherwise waits up to `timeout` seconds. Pass back the returned cursor next time so nothing is missed. If Ponzu restarted or lost track
|
|
33545
|
+
description: "Watch the canvas. Call this instead of ending your turn whenever the person might react to your work there: after you put something up for review, or while you design together. Calling it again and again is the loop (get_guide('watch')). Long-polls for what people do in the canvas: new comments and replies, edits made in a text editor, other agents' changes, in this file and the others you've worked in this session, saying which file each is in. Returns immediately if anything happened after `since`; otherwise waits up to `timeout` seconds. Pass back the returned cursor next time so nothing is missed. If Ponzu restarts during the wait, the wait carries on once it's back. If it restarted or lost track before you waited, it returns at once with the open threads waiting on you: handle those, then keep watching with the cursor it gives. The person's chat messages still reach you between waits.",
|
|
33546
33546
|
input: {
|
|
33547
33547
|
file: fileArg,
|
|
33548
33548
|
since: number2().int().min(0).optional().describe("Cursor from the last call. Omit to start from now."),
|
|
@@ -33571,7 +33571,7 @@ var listenTools = [
|
|
|
33571
33571
|
const waiting = waitingText(files, all);
|
|
33572
33572
|
return {
|
|
33573
33573
|
text: `${GAP_NOTE[gap]} ${waiting.text}
|
|
33574
|
-
|
|
33574
|
+
Keep watching: wait again with cursor=${next}.`,
|
|
33575
33575
|
data: { cursor: next, events: [], gap, waiting: waiting.ids },
|
|
33576
33576
|
metrics: { cursor: gap, events: 0, scope_files: files.length }
|
|
33577
33577
|
};
|
|
@@ -36773,6 +36773,7 @@ data:
|
|
|
36773
36773
|
|
|
36774
36774
|
// packages/mcp/src/index.ts
|
|
36775
36775
|
var FILES_META = "ponzu.design/files";
|
|
36776
|
+
var WAIT_META = "ponzu.design/wait";
|
|
36776
36777
|
function clientKind(name = "") {
|
|
36777
36778
|
const n = name.toLowerCase();
|
|
36778
36779
|
if (n.includes("claude"))
|
|
@@ -36841,7 +36842,7 @@ function createMcpServer(ws, opts = {}) {
|
|
|
36841
36842
|
destructiveHint: def.destructive ?? false,
|
|
36842
36843
|
openWorldHint: false
|
|
36843
36844
|
}
|
|
36844
|
-
}, async (args, extra) => toMcp(await runTool(def, args, { ws, caller: getCaller(), signal: extra.signal, session }), session));
|
|
36845
|
+
}, async (args, extra) => toMcp(await runTool(def, args, { ws, caller: getCaller(), signal: extra.signal, session }), session, def.name));
|
|
36845
36846
|
}
|
|
36846
36847
|
for (const [topic, text] of Object.entries(GUIDES)) {
|
|
36847
36848
|
server.registerResource(`guide-${topic}`, `ponzu://guide/${topic}`, { title: `Ponzu guide: ${topic}`, mimeType: "text/markdown" }, async (uri) => ({ contents: [{ uri: uri.href, mimeType: "text/markdown", text }] }));
|
|
@@ -36880,13 +36881,19 @@ Read get_guide('basics'). Write the brief and your direction to a note, set toke
|
|
|
36880
36881
|
}));
|
|
36881
36882
|
return server;
|
|
36882
36883
|
}
|
|
36883
|
-
function toMcp(r, session) {
|
|
36884
|
+
function toMcp(r, session, tool) {
|
|
36884
36885
|
const content = [];
|
|
36885
36886
|
for (const img of r.images ?? [])
|
|
36886
36887
|
content.push({ type: "image", data: img.data, mimeType: img.mime });
|
|
36887
36888
|
content.push({ type: "text", text: r.text });
|
|
36888
|
-
const
|
|
36889
|
-
|
|
36889
|
+
const meta = {};
|
|
36890
|
+
if (session.files?.length)
|
|
36891
|
+
meta[FILES_META] = session.files;
|
|
36892
|
+
if (tool === "wait_for_activity" && r.data) {
|
|
36893
|
+
const { cursor, gap, waiting } = r.data;
|
|
36894
|
+
meta[WAIT_META] = { cursor, ...gap ? { gap, waiting: waiting?.length ?? 0 } : {} };
|
|
36895
|
+
}
|
|
36896
|
+
return { content, isError: r.isError, ...Object.keys(meta).length ? { _meta: meta } : {} };
|
|
36890
36897
|
}
|
|
36891
36898
|
function createHttpHandler(ws, opts = {}) {
|
|
36892
36899
|
const sessions = new Map;
|
|
@@ -37085,6 +37092,7 @@ function onBodyDone(res, done) {
|
|
|
37085
37092
|
|
|
37086
37093
|
// packages/mcp/src/bridge.ts
|
|
37087
37094
|
var CALL_TIMEOUT = 15 * 60000;
|
|
37095
|
+
var RESTART_GRACE = 1e4;
|
|
37088
37096
|
var readOnly = new Set(tools.filter((t) => t.readOnly).map((t) => t.name));
|
|
37089
37097
|
async function serveBridge(opts, transport = new StdioServerTransport) {
|
|
37090
37098
|
const server = new Server({ name: opts.name ?? "ponzu", version: opts.version ?? "0.1.0", title: "Ponzu" }, {
|
|
@@ -37214,19 +37222,70 @@ async function serveBridge(opts, transport = new StdioServerTransport) {
|
|
|
37214
37222
|
return fn(await localClient(), extra.signal);
|
|
37215
37223
|
}
|
|
37216
37224
|
};
|
|
37217
|
-
|
|
37218
|
-
const
|
|
37219
|
-
|
|
37220
|
-
|
|
37221
|
-
|
|
37225
|
+
const hostBack = async (until, signal) => {
|
|
37226
|
+
const giveUp = Math.min(until, Date.now() + (opts.restartGrace ?? RESTART_GRACE));
|
|
37227
|
+
while (!signal.aborted) {
|
|
37228
|
+
if (await opts.findHost(false).catch(() => null))
|
|
37229
|
+
return true;
|
|
37230
|
+
if (Date.now() >= giveUp)
|
|
37231
|
+
return false;
|
|
37232
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
37233
|
+
}
|
|
37234
|
+
return false;
|
|
37235
|
+
};
|
|
37236
|
+
const waitThrough = async (params, signal) => {
|
|
37237
|
+
const { since, timeout } = params.arguments ?? {};
|
|
37238
|
+
const deadline = Date.now() + (typeof timeout === "number" ? timeout : 60) * 1000;
|
|
37239
|
+
const left = () => Math.max(0, Math.ceil((deadline - Date.now()) / 1000));
|
|
37240
|
+
const withArgs = (more) => ({
|
|
37241
|
+
...params,
|
|
37242
|
+
arguments: { ...params.arguments, ...more }
|
|
37243
|
+
});
|
|
37244
|
+
let cutOff = false;
|
|
37245
|
+
const result = await call(params, signal, async () => {
|
|
37246
|
+
cutOff = true;
|
|
37247
|
+
if (!await hostBack(deadline, signal) && Date.now() >= deadline)
|
|
37248
|
+
return null;
|
|
37249
|
+
return withArgs({ timeout: left() });
|
|
37250
|
+
});
|
|
37251
|
+
if (!result) {
|
|
37252
|
+
const again = typeof since === "number" ? `Wait again with cursor=${since}` : "Wait again";
|
|
37253
|
+
return {
|
|
37254
|
+
content: [
|
|
37255
|
+
{
|
|
37256
|
+
type: "text",
|
|
37257
|
+
text: `Ponzu stopped while you waited and isn't back yet, so nothing was checked. ${again}: it's most likely restarting into an update.`
|
|
37258
|
+
}
|
|
37259
|
+
]
|
|
37260
|
+
};
|
|
37261
|
+
}
|
|
37262
|
+
const wait = result._meta?.[WAIT_META];
|
|
37263
|
+
if (!cutOff || wait?.gap !== "restart" || wait.waiting || !left())
|
|
37264
|
+
return result;
|
|
37265
|
+
const again = await call(withArgs({ since: wait.cursor, timeout: left() }), signal);
|
|
37266
|
+
const first = again.content.findIndex((c) => c.type === "text");
|
|
37267
|
+
return {
|
|
37268
|
+
...again,
|
|
37269
|
+
content: again.content.map((c, i) => i === first && c.type === "text" ? {
|
|
37270
|
+
...c,
|
|
37271
|
+
text: `Ponzu restarted while you waited, with nothing waiting on you.
|
|
37272
|
+
${c.text}`
|
|
37273
|
+
} : c)
|
|
37274
|
+
};
|
|
37275
|
+
};
|
|
37276
|
+
const call = async (params, signal, cutOff) => {
|
|
37277
|
+
const name = params.name;
|
|
37278
|
+
let sent = params;
|
|
37279
|
+
let ridden = cutOff;
|
|
37280
|
+
const options = { signal, timeout: CALL_TIMEOUT };
|
|
37222
37281
|
for (let attempt = 0;attempt < 2; attempt++) {
|
|
37223
37282
|
const client = await hostClient();
|
|
37224
37283
|
if (!client)
|
|
37225
37284
|
break;
|
|
37226
37285
|
try {
|
|
37227
|
-
return remember(await using(client, () => client.request({ method: "tools/call", params }, CallToolResultSchema, options)));
|
|
37286
|
+
return remember(await using(client, () => client.request({ method: "tools/call", params: sent }, CallToolResultSchema, options)));
|
|
37228
37287
|
} catch (err) {
|
|
37229
|
-
if (
|
|
37288
|
+
if (signal.aborted || err instanceof McpError && err.code !== ErrorCode.ConnectionClosed)
|
|
37230
37289
|
throw err;
|
|
37231
37290
|
await dropHost(client);
|
|
37232
37291
|
if (!neverArrived(err) && !readOnly.has(name)) {
|
|
@@ -37240,10 +37299,27 @@ async function serveBridge(opts, transport = new StdioServerTransport) {
|
|
|
37240
37299
|
isError: true
|
|
37241
37300
|
};
|
|
37242
37301
|
}
|
|
37302
|
+
if (ridden && !neverArrived(err)) {
|
|
37303
|
+
const next = await ridden();
|
|
37304
|
+
if (!next)
|
|
37305
|
+
return null;
|
|
37306
|
+
sent = next;
|
|
37307
|
+
ridden = undefined;
|
|
37308
|
+
}
|
|
37243
37309
|
}
|
|
37244
37310
|
}
|
|
37311
|
+
if (sent !== params)
|
|
37312
|
+
return null;
|
|
37245
37313
|
await opts.prepareLocal?.();
|
|
37246
|
-
return remember(await (await localClient()).request({ method: "tools/call", params }, CallToolResultSchema, options));
|
|
37314
|
+
return remember(await (await localClient()).request({ method: "tools/call", params: sent }, CallToolResultSchema, options));
|
|
37315
|
+
};
|
|
37316
|
+
server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
|
|
37317
|
+
const name = req.params.name;
|
|
37318
|
+
const args = req.params.arguments ?? {};
|
|
37319
|
+
const params = { ...req.params, arguments: opts.mapArgs ? opts.mapArgs(name, args) : args };
|
|
37320
|
+
if (name === "wait_for_activity")
|
|
37321
|
+
return waitThrough(params, extra.signal);
|
|
37322
|
+
return await call(params, extra.signal);
|
|
37247
37323
|
});
|
|
37248
37324
|
server.setRequestHandler(ListToolsRequestSchema, (req, extra) => list((c, signal) => c.listTools(req.params, { signal }))(req, extra));
|
|
37249
37325
|
server.setRequestHandler(ListResourcesRequestSchema, (req, extra) => list((c, signal) => c.listResources(req.params, { signal }))(req, extra));
|
|
@@ -47640,7 +47716,7 @@ function remoteUpdater(endpoint, token) {
|
|
|
47640
47716
|
// apps/cli/package.json
|
|
47641
47717
|
var package_default = {
|
|
47642
47718
|
name: "ponzu",
|
|
47643
|
-
version: "1.71.
|
|
47719
|
+
version: "1.71.2",
|
|
47644
47720
|
description: "The open source canvas your agent draws on in HTML and CSS.",
|
|
47645
47721
|
type: "module",
|
|
47646
47722
|
license: "MIT",
|
|
@@ -47663,17 +47739,40 @@ var package_default = {
|
|
|
47663
47739
|
|
|
47664
47740
|
// apps/cli/src/host.ts
|
|
47665
47741
|
import { spawn as spawn6 } from "child_process";
|
|
47666
|
-
import { existsSync as existsSync9, readFileSync as
|
|
47742
|
+
import { existsSync as existsSync9, readFileSync as readFileSync3, statSync as statSync3 } from "fs";
|
|
47667
47743
|
import { chmod, copyFile, mkdir as mkdir11, rename as rename9, rm as rm5, utimes as utimes3, writeFile as writeFile9 } from "fs/promises";
|
|
47744
|
+
import { homedir as homedir6 } from "os";
|
|
47745
|
+
import { dirname as dirname7, join as join15, posix, win32 } from "path";
|
|
47746
|
+
|
|
47747
|
+
// packages/server/src/updating.ts
|
|
47748
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync2, renameSync as renameSync2, rmSync, writeFileSync } from "fs";
|
|
47668
47749
|
import { homedir as homedir5 } from "os";
|
|
47669
|
-
import { dirname as dirname6, join as join14
|
|
47670
|
-
var
|
|
47671
|
-
var
|
|
47750
|
+
import { dirname as dirname6, join as join14 } from "path";
|
|
47751
|
+
var UPDATING_WITHIN = 2 * 60000;
|
|
47752
|
+
var updatingFile = () => join14(process.env.PONZU_HOME ?? join14(homedir5(), ".ponzu"), "updating.json");
|
|
47753
|
+
function read() {
|
|
47754
|
+
try {
|
|
47755
|
+
const u = JSON.parse(readFileSync2(updatingFile(), "utf8"));
|
|
47756
|
+
if (typeof u.from !== "string" || typeof u.to !== "string" || typeof u.at !== "number")
|
|
47757
|
+
return null;
|
|
47758
|
+
return { from: u.from, to: u.to, at: u.at, ...u.told ? { told: true } : {} };
|
|
47759
|
+
} catch {
|
|
47760
|
+
return null;
|
|
47761
|
+
}
|
|
47762
|
+
}
|
|
47763
|
+
function updating(now = Date.now()) {
|
|
47764
|
+
const u = read();
|
|
47765
|
+
return u && now - u.at < UPDATING_WITHIN ? u : null;
|
|
47766
|
+
}
|
|
47767
|
+
|
|
47768
|
+
// apps/cli/src/host.ts
|
|
47769
|
+
var ponzuHome = () => process.env.PONZU_HOME ?? join15(homedir6(), ".ponzu");
|
|
47770
|
+
var hostFile = () => join15(ponzuHome(), "host.json");
|
|
47672
47771
|
async function announceHost(url, opts = {}) {
|
|
47673
47772
|
if (!opts.takeOver && await runningHost())
|
|
47674
47773
|
return async () => {};
|
|
47675
47774
|
const path = hostFile();
|
|
47676
|
-
await mkdir11(
|
|
47775
|
+
await mkdir11(dirname7(path), { recursive: true });
|
|
47677
47776
|
const me = { url, mcp: `${url}/mcp`, pid: process.pid };
|
|
47678
47777
|
await writeFile9(path, `${JSON.stringify(me)}
|
|
47679
47778
|
`);
|
|
@@ -47684,7 +47783,7 @@ async function announceHost(url, opts = {}) {
|
|
|
47684
47783
|
}
|
|
47685
47784
|
function readHost() {
|
|
47686
47785
|
try {
|
|
47687
|
-
return JSON.parse(
|
|
47786
|
+
return JSON.parse(readFileSync3(hostFile(), "utf8"));
|
|
47688
47787
|
} catch {
|
|
47689
47788
|
return null;
|
|
47690
47789
|
}
|
|
@@ -47703,11 +47802,11 @@ async function runningHost() {
|
|
|
47703
47802
|
}
|
|
47704
47803
|
var SERVER = "Contents/Resources/server/ponzu";
|
|
47705
47804
|
var pathFor = (os) => os === "win32" ? win32 : posix;
|
|
47706
|
-
var appImageServer = () =>
|
|
47707
|
-
var appImageRecord = () =>
|
|
47805
|
+
var appImageServer = () => join15(ponzuHome(), "app/ponzu");
|
|
47806
|
+
var appImageRecord = () => join15(ponzuHome(), "app/appimage");
|
|
47708
47807
|
function recordedAppImage() {
|
|
47709
47808
|
try {
|
|
47710
|
-
const path =
|
|
47809
|
+
const path = readFileSync3(appImageRecord(), "utf8").trim();
|
|
47711
47810
|
return path && existsSync9(path) ? path : null;
|
|
47712
47811
|
} catch {
|
|
47713
47812
|
return null;
|
|
@@ -47722,7 +47821,7 @@ function isFolder(app) {
|
|
|
47722
47821
|
}
|
|
47723
47822
|
function appServer(app, os = process.platform) {
|
|
47724
47823
|
if (os === "darwin")
|
|
47725
|
-
return
|
|
47824
|
+
return join15(app, SERVER);
|
|
47726
47825
|
const path = pathFor(os);
|
|
47727
47826
|
if (os === "linux" && !isFolder(app))
|
|
47728
47827
|
return appImageServer();
|
|
@@ -47755,7 +47854,7 @@ function ownBundle(execPath = process.execPath, os = process.platform, env = pro
|
|
|
47755
47854
|
}
|
|
47756
47855
|
function installedApps(os = process.platform, env = process.env) {
|
|
47757
47856
|
if (os === "darwin")
|
|
47758
|
-
return ["/Applications/Ponzu.app",
|
|
47857
|
+
return ["/Applications/Ponzu.app", join15(homedir6(), "Applications/Ponzu.app")];
|
|
47759
47858
|
if (os === "win32") {
|
|
47760
47859
|
return [
|
|
47761
47860
|
env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "Programs", "ponzu"),
|
|
@@ -47788,7 +47887,7 @@ async function adoptAppImage(execPath = process.execPath, os = process.platform,
|
|
|
47788
47887
|
const from = statSync3(execPath);
|
|
47789
47888
|
const to = statSync3(copy, { throwIfNoEntry: false });
|
|
47790
47889
|
if (!to || to.size !== from.size || Math.trunc(to.mtimeMs) !== Math.trunc(from.mtimeMs)) {
|
|
47791
|
-
await mkdir11(
|
|
47890
|
+
await mkdir11(dirname7(copy), { recursive: true });
|
|
47792
47891
|
const temp = `${copy}.${process.pid}`;
|
|
47793
47892
|
await copyFile(execPath, temp);
|
|
47794
47893
|
await chmod(temp, 493);
|
|
@@ -47803,7 +47902,7 @@ async function adoptAppImage(execPath = process.execPath, os = process.platform,
|
|
|
47803
47902
|
async function launchApp(app, timeoutMs = 30000) {
|
|
47804
47903
|
const deadline = Date.now() + timeoutMs;
|
|
47805
47904
|
for (let tick = 0;Date.now() < deadline; tick++) {
|
|
47806
|
-
if (tick % 40 === 0)
|
|
47905
|
+
if (tick % 40 === 0 && !updating())
|
|
47807
47906
|
startApp(app);
|
|
47808
47907
|
const url = await runningHost();
|
|
47809
47908
|
if (url)
|
|
@@ -47852,7 +47951,7 @@ async function findHost(launch) {
|
|
|
47852
47951
|
lastFailed = Date.now();
|
|
47853
47952
|
return url;
|
|
47854
47953
|
}
|
|
47855
|
-
var shimPath = (os = process.platform) =>
|
|
47954
|
+
var shimPath = (os = process.platform) => join15(ponzuHome(), os === "win32" ? "bin/ponzu.cmd" : "bin/ponzu");
|
|
47856
47955
|
function shimScript(app, os = process.platform) {
|
|
47857
47956
|
if (os === "win32") {
|
|
47858
47957
|
const candidates = [
|
|
@@ -47897,7 +47996,7 @@ exit 1
|
|
|
47897
47996
|
}
|
|
47898
47997
|
async function writeShim(app) {
|
|
47899
47998
|
const path = shimPath();
|
|
47900
|
-
await mkdir11(
|
|
47999
|
+
await mkdir11(dirname7(path), { recursive: true });
|
|
47901
48000
|
await writeFile9(path, shimScript(app));
|
|
47902
48001
|
await chmod(path, 493);
|
|
47903
48002
|
return path;
|
|
@@ -47905,28 +48004,28 @@ async function writeShim(app) {
|
|
|
47905
48004
|
|
|
47906
48005
|
// apps/cli/src/install.ts
|
|
47907
48006
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
47908
|
-
import { existsSync as existsSync11, readFileSync as
|
|
48007
|
+
import { existsSync as existsSync11, readFileSync as readFileSync5 } from "fs";
|
|
47909
48008
|
import { mkdir as mkdir13, writeFile as writeFile11 } from "fs/promises";
|
|
47910
|
-
import { homedir as
|
|
47911
|
-
import { delimiter as delimiter2, dirname as
|
|
48009
|
+
import { homedir as homedir8 } from "os";
|
|
48010
|
+
import { delimiter as delimiter2, dirname as dirname9, join as join17 } from "path";
|
|
47912
48011
|
|
|
47913
48012
|
// apps/cli/src/command.ts
|
|
47914
48013
|
import { spawnSync } from "child_process";
|
|
47915
|
-
import { accessSync, constants, existsSync as existsSync10, lstatSync, readFileSync as
|
|
48014
|
+
import { accessSync, constants, existsSync as existsSync10, lstatSync, readFileSync as readFileSync4, realpathSync as realpathSync3 } from "fs";
|
|
47916
48015
|
import { mkdir as mkdir12, symlink, writeFile as writeFile10 } from "fs/promises";
|
|
47917
|
-
import { homedir as
|
|
47918
|
-
import { delimiter, dirname as
|
|
47919
|
-
function commandDirs(home =
|
|
47920
|
-
return [
|
|
48016
|
+
import { homedir as homedir7 } from "os";
|
|
48017
|
+
import { delimiter, dirname as dirname8, join as join16 } from "path";
|
|
48018
|
+
function commandDirs(home = homedir7()) {
|
|
48019
|
+
return [join16(home, ".local/bin"), join16(home, "bin"), "/usr/local/bin"];
|
|
47921
48020
|
}
|
|
47922
|
-
var stateFile = () =>
|
|
48021
|
+
var stateFile = () => join16(ponzuHome(), "command.json");
|
|
47923
48022
|
async function linkCommand(shim, opts) {
|
|
47924
48023
|
const say = (s) => {
|
|
47925
48024
|
if (!opts.auto)
|
|
47926
48025
|
console.log(s);
|
|
47927
48026
|
};
|
|
47928
48027
|
if (process.platform === "win32")
|
|
47929
|
-
return addToUserPath(
|
|
48028
|
+
return addToUserPath(dirname8(shim), opts.auto, say);
|
|
47930
48029
|
const found = Bun.which("ponzu", { PATH: opts.path.join(delimiter) });
|
|
47931
48030
|
const app = found && opensApp(found) ? found : null;
|
|
47932
48031
|
if (found && !app) {
|
|
@@ -47937,16 +48036,16 @@ async function linkCommand(shim, opts) {
|
|
|
47937
48036
|
const state = readState();
|
|
47938
48037
|
if (opts.auto && state.linked)
|
|
47939
48038
|
return;
|
|
47940
|
-
const before = app ? opts.path.indexOf(
|
|
48039
|
+
const before = app ? opts.path.indexOf(dirname8(app)) : -1;
|
|
47941
48040
|
const dir = commandDirs(opts.home).find((d) => {
|
|
47942
48041
|
const at = opts.path.indexOf(d);
|
|
47943
48042
|
return at >= 0 && (before < 0 || at < before) && canLink(d, opts.home);
|
|
47944
48043
|
});
|
|
47945
48044
|
if (!dir) {
|
|
47946
|
-
say(process.platform === "darwin" ? `To run ponzu from a terminal, add ${
|
|
48045
|
+
say(process.platform === "darwin" ? `To run ponzu from a terminal, add ${dirname8(shim)} to your PATH, or choose Install Terminal Command in Ponzu's app menu.` : `To run ponzu from a terminal, add ${dirname8(shim)} to your PATH.`);
|
|
47947
48046
|
return;
|
|
47948
48047
|
}
|
|
47949
|
-
const link =
|
|
48048
|
+
const link = join16(dir, "ponzu");
|
|
47950
48049
|
try {
|
|
47951
48050
|
await mkdir12(dir, { recursive: true });
|
|
47952
48051
|
await symlink(shim, link);
|
|
@@ -47954,7 +48053,7 @@ async function linkCommand(shim, opts) {
|
|
|
47954
48053
|
say(`Couldn't link ponzu into ${dir}.`);
|
|
47955
48054
|
return;
|
|
47956
48055
|
}
|
|
47957
|
-
await mkdir12(
|
|
48056
|
+
await mkdir12(dirname8(stateFile()), { recursive: true });
|
|
47958
48057
|
await writeFile10(stateFile(), `${JSON.stringify({ linked: link }, null, 2)}
|
|
47959
48058
|
`);
|
|
47960
48059
|
say(`Linked ${link}, so ponzu runs from a terminal.`);
|
|
@@ -47993,21 +48092,21 @@ async function addToUserPath(dir, auto, say) {
|
|
|
47993
48092
|
return;
|
|
47994
48093
|
if (userPath(dir, true) !== "added")
|
|
47995
48094
|
return say(`Couldn't add ${dir} to your PATH. Add it yourself to run ponzu from a terminal.`);
|
|
47996
|
-
await mkdir12(
|
|
48095
|
+
await mkdir12(dirname8(stateFile()), { recursive: true });
|
|
47997
48096
|
await writeFile10(stateFile(), `${JSON.stringify({ linked: dir }, null, 2)}
|
|
47998
48097
|
`);
|
|
47999
48098
|
say(`Added ${dir} to your PATH. Open a new terminal and run ponzu.`);
|
|
48000
48099
|
}
|
|
48001
48100
|
function readState() {
|
|
48002
48101
|
try {
|
|
48003
|
-
return JSON.parse(
|
|
48102
|
+
return JSON.parse(readFileSync4(stateFile(), "utf8"));
|
|
48004
48103
|
} catch {
|
|
48005
48104
|
return {};
|
|
48006
48105
|
}
|
|
48007
48106
|
}
|
|
48008
|
-
function canLink(dir, home =
|
|
48107
|
+
function canLink(dir, home = homedir7()) {
|
|
48009
48108
|
try {
|
|
48010
|
-
lstatSync(
|
|
48109
|
+
lstatSync(join16(dir, "ponzu"));
|
|
48011
48110
|
return false;
|
|
48012
48111
|
} catch {}
|
|
48013
48112
|
if (!existsSync10(dir))
|
|
@@ -48021,7 +48120,7 @@ function canLink(dir, home = homedir6()) {
|
|
|
48021
48120
|
}
|
|
48022
48121
|
function opensApp(file) {
|
|
48023
48122
|
try {
|
|
48024
|
-
return existsSync10(
|
|
48123
|
+
return existsSync10(join16(dirname8(realpathSync3(file)), "resources/server/ponzu"));
|
|
48025
48124
|
} catch {
|
|
48026
48125
|
return false;
|
|
48027
48126
|
}
|
|
@@ -48041,7 +48140,7 @@ var AGENTS = [
|
|
|
48041
48140
|
label: "Claude Code",
|
|
48042
48141
|
detect: () => Boolean(which("claude")),
|
|
48043
48142
|
current: () => {
|
|
48044
|
-
const config = readJson(
|
|
48143
|
+
const config = readJson(join17(homedir8(), ".claude.json"));
|
|
48045
48144
|
return toEntry(config?.mcpServers?.ponzu);
|
|
48046
48145
|
},
|
|
48047
48146
|
register(entry) {
|
|
@@ -48091,7 +48190,7 @@ var AGENTS = [
|
|
|
48091
48190
|
{
|
|
48092
48191
|
id: "cursor",
|
|
48093
48192
|
label: "Cursor",
|
|
48094
|
-
detect: () => existsSync11(
|
|
48193
|
+
detect: () => existsSync11(join17(homedir8(), ".cursor")),
|
|
48095
48194
|
current: () => {
|
|
48096
48195
|
const config = readJson(cursorConfig());
|
|
48097
48196
|
return toEntry(config?.mcpServers?.ponzu);
|
|
@@ -48109,7 +48208,7 @@ var AGENTS = [
|
|
|
48109
48208
|
config = parsed;
|
|
48110
48209
|
}
|
|
48111
48210
|
config.mcpServers = { ...config.mcpServers, ponzu: entry };
|
|
48112
|
-
await mkdir13(
|
|
48211
|
+
await mkdir13(dirname9(path), { recursive: true });
|
|
48113
48212
|
await writeFile11(path, `${JSON.stringify(config, null, 2)}
|
|
48114
48213
|
`);
|
|
48115
48214
|
return true;
|
|
@@ -48178,7 +48277,7 @@ function agentStatus() {
|
|
|
48178
48277
|
}
|
|
48179
48278
|
function codexConfigured() {
|
|
48180
48279
|
try {
|
|
48181
|
-
return /^\[mcp_servers\.ponzu\]/m.test(
|
|
48280
|
+
return /^\[mcp_servers\.ponzu\]/m.test(readFileSync5(join17(process.env.CODEX_HOME ?? join17(homedir8(), ".codex"), "config.toml"), "utf8"));
|
|
48182
48281
|
} catch {
|
|
48183
48282
|
return false;
|
|
48184
48283
|
}
|
|
@@ -48222,22 +48321,22 @@ function shouldAutoRegister(current, setUpBefore) {
|
|
|
48222
48321
|
function isLegacy(e) {
|
|
48223
48322
|
return "url" in e && /^http:\/\/(127\.0\.0\.1|localhost):7777\/mcp\/?$/.test(e.url);
|
|
48224
48323
|
}
|
|
48225
|
-
var doneFile = () =>
|
|
48324
|
+
var doneFile = () => join17(ponzuHome(), "agents.json");
|
|
48226
48325
|
function readDone() {
|
|
48227
48326
|
const raw = readJson(doneFile());
|
|
48228
48327
|
return new Set(raw?.configured ?? []);
|
|
48229
48328
|
}
|
|
48230
48329
|
async function writeDone(done) {
|
|
48231
|
-
await mkdir13(
|
|
48330
|
+
await mkdir13(dirname9(doneFile()), { recursive: true });
|
|
48232
48331
|
await writeFile11(doneFile(), `${JSON.stringify({ configured: [...done].sort() }, null, 2)}
|
|
48233
48332
|
`);
|
|
48234
48333
|
}
|
|
48235
48334
|
function cursorConfig() {
|
|
48236
|
-
return
|
|
48335
|
+
return join17(homedir8(), ".cursor", "mcp.json");
|
|
48237
48336
|
}
|
|
48238
48337
|
function readJson(path) {
|
|
48239
48338
|
try {
|
|
48240
|
-
return JSON.parse(
|
|
48339
|
+
return JSON.parse(readFileSync5(path, "utf8"));
|
|
48241
48340
|
} catch {
|
|
48242
48341
|
return null;
|
|
48243
48342
|
}
|
|
@@ -48246,13 +48345,13 @@ var searchPath;
|
|
|
48246
48345
|
function agentPath() {
|
|
48247
48346
|
if (searchPath !== undefined)
|
|
48248
48347
|
return searchPath;
|
|
48249
|
-
const home =
|
|
48348
|
+
const home = homedir8();
|
|
48250
48349
|
const dirs = [
|
|
48251
48350
|
...(process.env.PATH ?? "").split(delimiter2),
|
|
48252
|
-
|
|
48253
|
-
|
|
48254
|
-
|
|
48255
|
-
|
|
48351
|
+
join17(home, ".local/bin"),
|
|
48352
|
+
join17(home, ".claude/local"),
|
|
48353
|
+
join17(home, ".bun/bin"),
|
|
48354
|
+
join17(home, ".npm-global/bin"),
|
|
48256
48355
|
"/opt/homebrew/bin",
|
|
48257
48356
|
"/usr/local/bin",
|
|
48258
48357
|
...loginPath()
|
|
@@ -48312,13 +48411,13 @@ function run(cmd, quiet = false) {
|
|
|
48312
48411
|
// apps/cli/src/source.ts
|
|
48313
48412
|
import { createHash as createHash5 } from "crypto";
|
|
48314
48413
|
import { existsSync as existsSync12 } from "fs";
|
|
48315
|
-
import { dirname as
|
|
48414
|
+
import { dirname as dirname10, join as join18, sep as sep4 } from "path";
|
|
48316
48415
|
import { fileURLToPath } from "url";
|
|
48317
48416
|
function sourceCheckout(file = fileURLToPath(import.meta.url)) {
|
|
48318
48417
|
if (file.startsWith("/$bunfs/") || file.split(sep4).includes("node_modules"))
|
|
48319
48418
|
return null;
|
|
48320
|
-
for (let dir =
|
|
48321
|
-
if (existsSync12(
|
|
48419
|
+
for (let dir = dirname10(file);dir !== dirname10(dir); dir = dirname10(dir))
|
|
48420
|
+
if (existsSync12(join18(dir, ".git")))
|
|
48322
48421
|
return dir;
|
|
48323
48422
|
return null;
|
|
48324
48423
|
}
|
|
@@ -48356,13 +48455,13 @@ function firstFree(ports, hostname) {
|
|
|
48356
48455
|
|
|
48357
48456
|
// apps/cli/src/telemetry.ts
|
|
48358
48457
|
import { createHash as createHash6, randomUUID } from "crypto";
|
|
48359
|
-
import { readFileSync as
|
|
48458
|
+
import { readFileSync as readFileSync6 } from "fs";
|
|
48360
48459
|
import { mkdir as mkdir14, rename as rename10, writeFile as writeFile12 } from "fs/promises";
|
|
48361
48460
|
import { arch, platform as platform2 } from "os";
|
|
48362
|
-
import { dirname as
|
|
48461
|
+
import { dirname as dirname12, join as join19 } from "path";
|
|
48363
48462
|
|
|
48364
48463
|
// node_modules/.bun/posthog-node@5.56.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
|
|
48365
|
-
import { dirname as
|
|
48464
|
+
import { dirname as dirname11, posix as posix2, sep as sep5 } from "path";
|
|
48366
48465
|
function createModulerModifier() {
|
|
48367
48466
|
const getModuleFromFileName = createGetModuleFromFilename();
|
|
48368
48467
|
return async (frames) => {
|
|
@@ -48371,7 +48470,7 @@ function createModulerModifier() {
|
|
|
48371
48470
|
return frames;
|
|
48372
48471
|
};
|
|
48373
48472
|
}
|
|
48374
|
-
function createGetModuleFromFilename(basePath = process.argv[1] ?
|
|
48473
|
+
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname11(process.argv[1]) : process.cwd(), isWindows = sep5 === "\\") {
|
|
48375
48474
|
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
48376
48475
|
return (filename) => {
|
|
48377
48476
|
if (!filename)
|
|
@@ -57651,17 +57750,17 @@ var CATEGORY = {
|
|
|
57651
57750
|
praise: "Praise",
|
|
57652
57751
|
other: "Other"
|
|
57653
57752
|
};
|
|
57654
|
-
var settingsFile = () =>
|
|
57753
|
+
var settingsFile = () => join19(ponzuHome(), "telemetry.json");
|
|
57655
57754
|
function readSettings() {
|
|
57656
57755
|
try {
|
|
57657
|
-
return JSON.parse(
|
|
57756
|
+
return JSON.parse(readFileSync6(settingsFile(), "utf8"));
|
|
57658
57757
|
} catch (err) {
|
|
57659
57758
|
return err.code === "ENOENT" ? {} : null;
|
|
57660
57759
|
}
|
|
57661
57760
|
}
|
|
57662
57761
|
async function writeSettings(settings) {
|
|
57663
57762
|
const path = settingsFile();
|
|
57664
|
-
await mkdir14(
|
|
57763
|
+
await mkdir14(dirname12(path), { recursive: true });
|
|
57665
57764
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
57666
57765
|
await writeFile12(tmp, `${JSON.stringify(settings, null, 2)}
|
|
57667
57766
|
`);
|
|
@@ -57837,14 +57936,14 @@ async function telemetryCommand(arg = "status") {
|
|
|
57837
57936
|
// apps/cli/src/wake.ts
|
|
57838
57937
|
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
57839
57938
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
57840
|
-
import { fstatSync, readFileSync as
|
|
57939
|
+
import { fstatSync, readFileSync as readFileSync7 } from "fs";
|
|
57841
57940
|
import { mkdir as mkdir15, rm as rm6, writeFile as writeFile13 } from "fs/promises";
|
|
57842
|
-
import { homedir as
|
|
57843
|
-
import { join as
|
|
57941
|
+
import { homedir as homedir9 } from "os";
|
|
57942
|
+
import { join as join20 } from "path";
|
|
57844
57943
|
var WAKE_LABEL = "design.ponzu.app.wake";
|
|
57845
57944
|
var WAKE_PORT = 7778;
|
|
57846
|
-
var agentsDir = () => process.env.PONZU_LAUNCH_AGENTS ??
|
|
57847
|
-
var wakePlistPath = () =>
|
|
57945
|
+
var agentsDir = () => process.env.PONZU_LAUNCH_AGENTS ?? join20(homedir9(), "Library/LaunchAgents");
|
|
57946
|
+
var wakePlistPath = () => join20(agentsDir(), `${WAKE_LABEL}.plist`);
|
|
57848
57947
|
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
57849
57948
|
function wakePlist(program, port = WAKE_PORT) {
|
|
57850
57949
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -57886,7 +57985,7 @@ async function installWake(program, run = launchctl) {
|
|
|
57886
57985
|
const plist = wakePlist(program);
|
|
57887
57986
|
let current = null;
|
|
57888
57987
|
try {
|
|
57889
|
-
current =
|
|
57988
|
+
current = readFileSync7(path, "utf8");
|
|
57890
57989
|
} catch {}
|
|
57891
57990
|
if (current === plist && run(["print", `${domain()}/${WAKE_LABEL}`]))
|
|
57892
57991
|
return false;
|
|
@@ -57900,7 +57999,7 @@ async function uninstallWake(run = launchctl) {
|
|
|
57900
57999
|
run(["bootout", `${domain()}/${WAKE_LABEL}`]);
|
|
57901
58000
|
await rm6(wakePlistPath(), { force: true });
|
|
57902
58001
|
}
|
|
57903
|
-
var systemdDir = () => process.env.PONZU_SYSTEMD_USER ??
|
|
58002
|
+
var systemdDir = () => process.env.PONZU_SYSTEMD_USER ?? join20(process.env.XDG_CONFIG_HOME || join20(homedir9(), ".config"), "systemd/user");
|
|
57904
58003
|
var WAKE_SOCKET = "ponzu-wake.socket";
|
|
57905
58004
|
var WAKE_SERVICE = "ponzu-wake@.service";
|
|
57906
58005
|
var systemdWord = (s) => `"${s.replace(/[\\"]/g, "\\$&").replace(/%/g, "%%").replace(/\$/g, "$$$$")}"`;
|
|
@@ -57934,7 +58033,7 @@ async function installSystemdWake(program, run = systemctl) {
|
|
|
57934
58033
|
const units = wakeUnits(program);
|
|
57935
58034
|
const current = Object.entries(units).every(([name, text]) => {
|
|
57936
58035
|
try {
|
|
57937
|
-
return
|
|
58036
|
+
return readFileSync7(join20(systemdDir(), name), "utf8") === text;
|
|
57938
58037
|
} catch {
|
|
57939
58038
|
return false;
|
|
57940
58039
|
}
|
|
@@ -57943,7 +58042,7 @@ async function installSystemdWake(program, run = systemctl) {
|
|
|
57943
58042
|
return false;
|
|
57944
58043
|
await mkdir15(systemdDir(), { recursive: true });
|
|
57945
58044
|
for (const [name, text] of Object.entries(units))
|
|
57946
|
-
await writeFile13(
|
|
58045
|
+
await writeFile13(join20(systemdDir(), name), text);
|
|
57947
58046
|
run(["daemon-reload"]);
|
|
57948
58047
|
run(["enable", WAKE_SOCKET]);
|
|
57949
58048
|
run(["restart", WAKE_SOCKET]);
|
|
@@ -57952,7 +58051,7 @@ async function installSystemdWake(program, run = systemctl) {
|
|
|
57952
58051
|
async function uninstallSystemdWake(run = systemctl) {
|
|
57953
58052
|
run(["disable", "--now", WAKE_SOCKET]);
|
|
57954
58053
|
for (const name of [WAKE_SOCKET, WAKE_SERVICE])
|
|
57955
|
-
await rm6(
|
|
58054
|
+
await rm6(join20(systemdDir(), name), { force: true });
|
|
57956
58055
|
run(["daemon-reload"]);
|
|
57957
58056
|
}
|
|
57958
58057
|
function peerKind(ip) {
|
|
@@ -58117,7 +58216,7 @@ function lsofPeer() {
|
|
|
58117
58216
|
|
|
58118
58217
|
// apps/cli/src/index.ts
|
|
58119
58218
|
var VERSION2 = package_default.version;
|
|
58120
|
-
var here =
|
|
58219
|
+
var here = dirname13(fileURLToPath2(import.meta.url));
|
|
58121
58220
|
var HELP = `Ponzu ${VERSION2}: the open source canvas your agent draws on in HTML and CSS.
|
|
58122
58221
|
|
|
58123
58222
|
Usage
|
|
@@ -58242,7 +58341,7 @@ async function openWorkspace(files, log) {
|
|
|
58242
58341
|
const paths = files.length ? files.map((f) => resolve5(f)) : await findDesigns(process.cwd());
|
|
58243
58342
|
const named = [];
|
|
58244
58343
|
for (const p of paths) {
|
|
58245
|
-
if (!existsSync13(
|
|
58344
|
+
if (!existsSync13(join21(p, "design.json")) && existsSync13(p) === false && files.length) {
|
|
58246
58345
|
log(`Creating ${p}`);
|
|
58247
58346
|
}
|
|
58248
58347
|
const { key } = await ws.open(p);
|
|
@@ -58254,7 +58353,8 @@ async function openWorkspace(files, log) {
|
|
|
58254
58353
|
telemetry?.capture("host started", {
|
|
58255
58354
|
launched_by: process.env.PONZU_LAUNCHED_BY ?? "person",
|
|
58256
58355
|
designs: ws.designs().length,
|
|
58257
|
-
renderer: Boolean(renderer)
|
|
58356
|
+
renderer: Boolean(renderer),
|
|
58357
|
+
...process.env.PONZU_UPDATE ? { update: process.env.PONZU_UPDATE } : {}
|
|
58258
58358
|
});
|
|
58259
58359
|
return { ws, telemetry, named };
|
|
58260
58360
|
}
|
|
@@ -58268,14 +58368,14 @@ async function runServe(files, o) {
|
|
|
58268
58368
|
root: process.cwd(),
|
|
58269
58369
|
tailnet: o.tailnet,
|
|
58270
58370
|
tailnetPort: 7777,
|
|
58271
|
-
dbFile:
|
|
58272
|
-
stateFile:
|
|
58371
|
+
dbFile: join21(ponzuHome(), "ponzu.db"),
|
|
58372
|
+
stateFile: join21(ponzuHome(), "catalog.json"),
|
|
58273
58373
|
spotlight: process.env.PONZU_SPOTLIGHT === "0" ? false : undefined,
|
|
58274
58374
|
agents: async () => agents(),
|
|
58275
58375
|
suggest: {
|
|
58276
58376
|
bin: (agent) => agentCli(agent),
|
|
58277
58377
|
env: agentEnv(),
|
|
58278
|
-
file:
|
|
58378
|
+
file: join21(ponzuHome(), "suggestions.json")
|
|
58279
58379
|
},
|
|
58280
58380
|
opened: named,
|
|
58281
58381
|
version: VERSION2,
|
|
@@ -58382,7 +58482,7 @@ function absolutePaths(args, cwd) {
|
|
|
58382
58482
|
}
|
|
58383
58483
|
async function runInit(target = "design/untitled.design") {
|
|
58384
58484
|
const path = resolve5(target.endsWith(".design") ? target : `${target}.design`);
|
|
58385
|
-
if (existsSync13(
|
|
58485
|
+
if (existsSync13(join21(path, "design.json"))) {
|
|
58386
58486
|
console.error(`${path} already exists.`);
|
|
58387
58487
|
process.exit(1);
|
|
58388
58488
|
}
|
|
@@ -58404,11 +58504,11 @@ Next: ponzu serve ${target}`);
|
|
|
58404
58504
|
}
|
|
58405
58505
|
function findStudio() {
|
|
58406
58506
|
const candidates = [
|
|
58407
|
-
|
|
58408
|
-
|
|
58409
|
-
|
|
58507
|
+
join21(here, "../studio"),
|
|
58508
|
+
join21(here, "../../studio/dist"),
|
|
58509
|
+
join21(here, "../../../apps/studio/dist")
|
|
58410
58510
|
];
|
|
58411
|
-
return candidates.find((c) => existsSync13(
|
|
58511
|
+
return candidates.find((c) => existsSync13(join21(c, "index.html")));
|
|
58412
58512
|
}
|
|
58413
58513
|
function lanAddresses() {
|
|
58414
58514
|
const out = [];
|