ponzu 1.68.3 → 1.69.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chromium.js +17 -0
- package/dist/index.js +888 -102
- 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 dirname12, isAbsolute as isAbsolute4, join as join20, resolve as resolve5 } from "path";
|
|
6982
6982
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
6983
6983
|
import { parseArgs } from "util";
|
|
6984
6984
|
|
|
@@ -16498,6 +16498,23 @@ function carryIds(prev, next, derived) {
|
|
|
16498
16498
|
}
|
|
16499
16499
|
return changed;
|
|
16500
16500
|
}
|
|
16501
|
+
// packages/core/src/suggestions.ts
|
|
16502
|
+
function sameName(a, b) {
|
|
16503
|
+
const norm = (s) => s.toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "");
|
|
16504
|
+
const x = norm(a);
|
|
16505
|
+
return x.length > 0 && x === norm(b);
|
|
16506
|
+
}
|
|
16507
|
+
function startedAs(idea, file) {
|
|
16508
|
+
const folder = file.key.split("/").pop()?.replace(/\.design$/, "") ?? "";
|
|
16509
|
+
return sameName(idea.name, file.name) || sameName(idea.name, folder);
|
|
16510
|
+
}
|
|
16511
|
+
var STARTED_FOR = 10 * 60000;
|
|
16512
|
+
function startedFrom(idea, files) {
|
|
16513
|
+
return files.find((f) => f.git?.project === idea.projectId && startedAs(idea, f));
|
|
16514
|
+
}
|
|
16515
|
+
function settled(file, now = Date.now()) {
|
|
16516
|
+
return !file.busy && !(file.created && now - file.created < STARTED_FOR);
|
|
16517
|
+
}
|
|
16501
16518
|
// packages/core/src/update.ts
|
|
16502
16519
|
function releaseNotesUrl(version) {
|
|
16503
16520
|
return `https://github.com/north-brook/ponzu/releases/download/v${version}/release-notes.json`;
|
|
@@ -41722,8 +41739,8 @@ async function previewStale(root) {
|
|
|
41722
41739
|
}
|
|
41723
41740
|
// packages/server/src/serve.ts
|
|
41724
41741
|
import { existsSync as existsSync8, statSync as statSync2 } from "fs";
|
|
41725
|
-
import { readFile as
|
|
41726
|
-
import { extname, join as
|
|
41742
|
+
import { readFile as readFile10 } from "fs/promises";
|
|
41743
|
+
import { extname, join as join13, normalize, resolve as resolve4, sep as sep3 } from "path";
|
|
41727
41744
|
|
|
41728
41745
|
// packages/store/src/fs.ts
|
|
41729
41746
|
import { watch as fsWatch, openAsBlob } from "fs";
|
|
@@ -43175,7 +43192,29 @@ function isListed(key) {
|
|
|
43175
43192
|
return !/\/(node_modules|\.Trash|\.cache)\//.test(key) && !/\/\.[^/]+\.design$/.test(key);
|
|
43176
43193
|
}
|
|
43177
43194
|
async function gitOf(key) {
|
|
43178
|
-
|
|
43195
|
+
const found = await checkoutOf(key);
|
|
43196
|
+
if (!found)
|
|
43197
|
+
return null;
|
|
43198
|
+
const { root, gitdir, common } = found;
|
|
43199
|
+
const at = { root, worktree: common !== gitdir, ...await projectOf(common) };
|
|
43200
|
+
const head = (await readFile4(join6(gitdir, "HEAD"), "utf8")).trim();
|
|
43201
|
+
const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
|
|
43202
|
+
if (ref)
|
|
43203
|
+
return { ...at, branch: ref[1] };
|
|
43204
|
+
return /^[0-9a-f]{7,}$/.test(head) ? { ...at, commit: head.slice(0, 7) } : at;
|
|
43205
|
+
}
|
|
43206
|
+
async function repositoryOf(dir) {
|
|
43207
|
+
const found = await checkoutOf(dir);
|
|
43208
|
+
if (!found)
|
|
43209
|
+
return null;
|
|
43210
|
+
const root = basename(found.common) === ".git" ? dirname4(found.common) : null;
|
|
43211
|
+
if (!root || !await exists(root))
|
|
43212
|
+
return null;
|
|
43213
|
+
const { project, projectName } = await projectOf(found.common);
|
|
43214
|
+
return { root, project: project ?? root, name: projectName ?? basename(root) };
|
|
43215
|
+
}
|
|
43216
|
+
async function checkoutOf(dir) {
|
|
43217
|
+
for (;; dir = dirname4(dir)) {
|
|
43179
43218
|
const dotgit = join6(dir, ".git");
|
|
43180
43219
|
const s = await stat5(dotgit).catch(() => null);
|
|
43181
43220
|
if (s) {
|
|
@@ -43188,12 +43227,7 @@ async function gitOf(key) {
|
|
|
43188
43227
|
}
|
|
43189
43228
|
const commondir = await readFile4(join6(gitdir, "commondir"), "utf8").catch(() => null);
|
|
43190
43229
|
const common = commondir === null ? gitdir : resolve2(gitdir, commondir.trim());
|
|
43191
|
-
|
|
43192
|
-
const head = (await readFile4(join6(gitdir, "HEAD"), "utf8")).trim();
|
|
43193
|
-
const ref = /^ref:\s*refs\/heads\/(.+)$/.exec(head);
|
|
43194
|
-
if (ref)
|
|
43195
|
-
return { ...at, branch: ref[1] };
|
|
43196
|
-
return /^[0-9a-f]{7,}$/.test(head) ? { ...at, commit: head.slice(0, 7) } : at;
|
|
43230
|
+
return { root: dir, gitdir, common };
|
|
43197
43231
|
}
|
|
43198
43232
|
if (dirname4(dir) === dir)
|
|
43199
43233
|
return null;
|
|
@@ -46024,6 +46058,731 @@ var RASTER = new Set([
|
|
|
46024
46058
|
"image/avif"
|
|
46025
46059
|
]);
|
|
46026
46060
|
|
|
46061
|
+
// packages/server/src/suggest.ts
|
|
46062
|
+
import { spawn as spawn5 } from "child_process";
|
|
46063
|
+
import { createReadStream } from "fs";
|
|
46064
|
+
import {
|
|
46065
|
+
mkdir as mkdir10,
|
|
46066
|
+
mkdtemp,
|
|
46067
|
+
readdir as readdir6,
|
|
46068
|
+
readFile as readFile9,
|
|
46069
|
+
realpath as realpath2,
|
|
46070
|
+
rename as rename8,
|
|
46071
|
+
rm as rm4,
|
|
46072
|
+
stat as stat9,
|
|
46073
|
+
writeFile as writeFile8
|
|
46074
|
+
} from "fs/promises";
|
|
46075
|
+
import { homedir as homedir4, tmpdir as tmpdir2 } from "os";
|
|
46076
|
+
import { basename as basename3, dirname as dirname5, join as join12 } from "path";
|
|
46077
|
+
var PROJECTS = 5;
|
|
46078
|
+
var IDEAS = 8;
|
|
46079
|
+
var PER_PROJECT = 4;
|
|
46080
|
+
var KEEP = 16;
|
|
46081
|
+
var STALE = 7 * 24 * 60 * 60000;
|
|
46082
|
+
var SESSIONS = 200;
|
|
46083
|
+
var HEAD_BYTES = 64 * 1024;
|
|
46084
|
+
var AT_ONCE = 8;
|
|
46085
|
+
var RUN_EVERY = 10 * 60000;
|
|
46086
|
+
var RUN_TIMEOUT = 3 * 60000;
|
|
46087
|
+
var QUIET = 20000;
|
|
46088
|
+
var TRIES = 2;
|
|
46089
|
+
var STEPS = 4;
|
|
46090
|
+
var LISTED = 600;
|
|
46091
|
+
var NOTE_BYTES = 4000;
|
|
46092
|
+
var GLANCE_SCHEMA = {
|
|
46093
|
+
type: "object",
|
|
46094
|
+
additionalProperties: false,
|
|
46095
|
+
required: ["ideas"],
|
|
46096
|
+
properties: {
|
|
46097
|
+
ideas: {
|
|
46098
|
+
type: "array",
|
|
46099
|
+
items: {
|
|
46100
|
+
type: "object",
|
|
46101
|
+
additionalProperties: false,
|
|
46102
|
+
required: ["name", "files"],
|
|
46103
|
+
properties: {
|
|
46104
|
+
name: { type: "string" },
|
|
46105
|
+
files: { type: "array", items: { type: "string" } }
|
|
46106
|
+
}
|
|
46107
|
+
}
|
|
46108
|
+
}
|
|
46109
|
+
}
|
|
46110
|
+
};
|
|
46111
|
+
var PROMPT_SCHEMA = {
|
|
46112
|
+
type: "object",
|
|
46113
|
+
additionalProperties: false,
|
|
46114
|
+
required: ["prompt"],
|
|
46115
|
+
properties: { prompt: { type: "string" } }
|
|
46116
|
+
};
|
|
46117
|
+
|
|
46118
|
+
class SuggestionService {
|
|
46119
|
+
opts;
|
|
46120
|
+
ideas = [];
|
|
46121
|
+
looking = [];
|
|
46122
|
+
ran = 0;
|
|
46123
|
+
running = false;
|
|
46124
|
+
stopped = new AbortController;
|
|
46125
|
+
loaded;
|
|
46126
|
+
saving = Promise.resolve();
|
|
46127
|
+
every;
|
|
46128
|
+
now;
|
|
46129
|
+
constructor(opts) {
|
|
46130
|
+
this.opts = opts;
|
|
46131
|
+
this.every = opts.every ?? RUN_EVERY;
|
|
46132
|
+
this.now = opts.now ?? Date.now;
|
|
46133
|
+
this.loaded = this.load();
|
|
46134
|
+
}
|
|
46135
|
+
async state() {
|
|
46136
|
+
await this.loaded;
|
|
46137
|
+
const [agent, designs] = await Promise.all([this.agent(), this.designs()]);
|
|
46138
|
+
const now = this.now();
|
|
46139
|
+
return {
|
|
46140
|
+
agent: agent?.id ?? null,
|
|
46141
|
+
ideas: this.ideas.filter((i) => {
|
|
46142
|
+
const started = startedFrom(i, designs);
|
|
46143
|
+
return !started || !settled(started, now);
|
|
46144
|
+
}),
|
|
46145
|
+
running: this.running,
|
|
46146
|
+
looking: this.looking
|
|
46147
|
+
};
|
|
46148
|
+
}
|
|
46149
|
+
async refresh() {
|
|
46150
|
+
await this.loaded;
|
|
46151
|
+
const due = () => !this.running && this.now() - this.ran >= this.every;
|
|
46152
|
+
if (due()) {
|
|
46153
|
+
const agent = await this.agent();
|
|
46154
|
+
if (agent && due()) {
|
|
46155
|
+
this.running = true;
|
|
46156
|
+
this.ran = this.now();
|
|
46157
|
+
this.run(agent).finally(() => {
|
|
46158
|
+
this.running = false;
|
|
46159
|
+
this.looking = [];
|
|
46160
|
+
});
|
|
46161
|
+
}
|
|
46162
|
+
}
|
|
46163
|
+
return this.state();
|
|
46164
|
+
}
|
|
46165
|
+
stop() {
|
|
46166
|
+
this.stopped.abort();
|
|
46167
|
+
}
|
|
46168
|
+
async agent() {
|
|
46169
|
+
const listed = this.opts.agents ? await this.opts.agents().catch(() => []) : null;
|
|
46170
|
+
const isolated = this.opts.isolated ?? (this.opts.run ? null : isolates);
|
|
46171
|
+
for (const id of ["claude", "codex"]) {
|
|
46172
|
+
if (listed && !listed.some((a) => a.id === id && a.found))
|
|
46173
|
+
continue;
|
|
46174
|
+
const bin = this.opts.bin(id);
|
|
46175
|
+
if (!bin)
|
|
46176
|
+
continue;
|
|
46177
|
+
if (isolated && !await isolated(id, bin, this.env()).catch(() => false))
|
|
46178
|
+
continue;
|
|
46179
|
+
return { id, bin };
|
|
46180
|
+
}
|
|
46181
|
+
return null;
|
|
46182
|
+
}
|
|
46183
|
+
env() {
|
|
46184
|
+
return { ...process.env, ...this.opts.env };
|
|
46185
|
+
}
|
|
46186
|
+
async designs() {
|
|
46187
|
+
return await this.opts.designs?.().catch(() => []) ?? [];
|
|
46188
|
+
}
|
|
46189
|
+
job(agent, project, kind, prompt, more = {}) {
|
|
46190
|
+
return {
|
|
46191
|
+
agent: agent.id,
|
|
46192
|
+
bin: agent.bin,
|
|
46193
|
+
kind,
|
|
46194
|
+
project,
|
|
46195
|
+
prompt,
|
|
46196
|
+
schema: kind === "glance" ? GLANCE_SCHEMA : PROMPT_SCHEMA,
|
|
46197
|
+
signal: AbortSignal.any([
|
|
46198
|
+
this.stopped.signal,
|
|
46199
|
+
AbortSignal.timeout(this.opts.timeout ?? RUN_TIMEOUT)
|
|
46200
|
+
]),
|
|
46201
|
+
env: this.env(),
|
|
46202
|
+
...more
|
|
46203
|
+
};
|
|
46204
|
+
}
|
|
46205
|
+
async run(agent) {
|
|
46206
|
+
await this.save();
|
|
46207
|
+
const projects = await recentProjects({ home: this.opts.home }).catch(() => []);
|
|
46208
|
+
if (!projects.length)
|
|
46209
|
+
return;
|
|
46210
|
+
const designs = await this.designs();
|
|
46211
|
+
const offered = this.ideas;
|
|
46212
|
+
const now = this.now();
|
|
46213
|
+
this.ideas = this.ideas.filter((i) => startedFrom(i, designs) || now - i.found < STALE);
|
|
46214
|
+
const open = this.ideas.filter((i) => !startedFrom(i, designs)).length;
|
|
46215
|
+
const counts = shares(IDEAS - open, projects.length);
|
|
46216
|
+
const home = this.opts.home;
|
|
46217
|
+
this.looking = projects.filter((_, i) => counts[i] > 0).map((p) => lookingAt(p, home));
|
|
46218
|
+
const runner = this.opts.run ?? runAgent;
|
|
46219
|
+
const lane = limiter(AT_ONCE);
|
|
46220
|
+
const writing = [];
|
|
46221
|
+
await Promise.all(projects.map(async (project, i) => {
|
|
46222
|
+
const count = counts[i];
|
|
46223
|
+
if (!count || this.stopped.signal.aborted)
|
|
46224
|
+
return;
|
|
46225
|
+
try {
|
|
46226
|
+
const taken = {
|
|
46227
|
+
designs: designs.filter((d) => d.git?.project === project.project).map((d) => d.name),
|
|
46228
|
+
suggested: offered.filter((i) => i.project === project.root).map((i) => i.name)
|
|
46229
|
+
};
|
|
46230
|
+
const listing = await listingOf(project.root, this.env());
|
|
46231
|
+
const ask = glanceFor(project, count, taken, listing, home);
|
|
46232
|
+
const answer = await lane(() => runner(this.job(agent, project, "glance", ask)));
|
|
46233
|
+
const found = glanceFrom(answer, project, { count, found: this.now() }).filter(({ idea }) => !startedFrom(idea, designs));
|
|
46234
|
+
if (!found.length)
|
|
46235
|
+
return;
|
|
46236
|
+
this.ideas = [
|
|
46237
|
+
...found.map((f) => f.idea),
|
|
46238
|
+
...this.ideas.filter((old) => !found.some((f) => sameIdea(f.idea, old)))
|
|
46239
|
+
].slice(0, KEEP);
|
|
46240
|
+
for (const f of found)
|
|
46241
|
+
writing.push(this.write(agent, project, f.idea, f.files, lane));
|
|
46242
|
+
} catch (err) {
|
|
46243
|
+
console.error(`Suggestions for ${project.name}: ${err.message ?? err}`);
|
|
46244
|
+
} finally {
|
|
46245
|
+
this.looking = this.looking.filter((l) => l.project !== project.root);
|
|
46246
|
+
}
|
|
46247
|
+
}));
|
|
46248
|
+
await Promise.all(writing);
|
|
46249
|
+
}
|
|
46250
|
+
async write(agent, project, idea, files, lane) {
|
|
46251
|
+
const ask = promptFor(idea, files, project, this.opts.home);
|
|
46252
|
+
const runner = this.opts.run ?? runAgent;
|
|
46253
|
+
const onStep = (step) => {
|
|
46254
|
+
idea.steps = [...idea.steps ?? [], step].slice(-STEPS);
|
|
46255
|
+
};
|
|
46256
|
+
let deadline;
|
|
46257
|
+
for (let tries = 0;tries < TRIES && !this.stopped.signal.aborted; tries++) {
|
|
46258
|
+
if (deadline?.aborted)
|
|
46259
|
+
break;
|
|
46260
|
+
idea.steps = [];
|
|
46261
|
+
try {
|
|
46262
|
+
const answer = await lane(() => {
|
|
46263
|
+
deadline ??= AbortSignal.timeout(this.opts.timeout ?? RUN_TIMEOUT);
|
|
46264
|
+
const signal = AbortSignal.any([this.stopped.signal, deadline]);
|
|
46265
|
+
return runner(this.job(agent, project, "prompt", ask, { onStep, quiet: QUIET, signal }));
|
|
46266
|
+
});
|
|
46267
|
+
const prompt = promptFrom(answer, idea, project, this.opts.home);
|
|
46268
|
+
if (!prompt)
|
|
46269
|
+
continue;
|
|
46270
|
+
idea.prompt = prompt;
|
|
46271
|
+
delete idea.steps;
|
|
46272
|
+
await this.save();
|
|
46273
|
+
return;
|
|
46274
|
+
} catch (err) {
|
|
46275
|
+
console.error(`Suggestion \u201C${idea.name}\u201D: ${err.message ?? err}`);
|
|
46276
|
+
}
|
|
46277
|
+
}
|
|
46278
|
+
this.ideas = this.ideas.filter((i) => i !== idea);
|
|
46279
|
+
}
|
|
46280
|
+
async load() {
|
|
46281
|
+
if (!this.opts.file)
|
|
46282
|
+
return;
|
|
46283
|
+
try {
|
|
46284
|
+
const saved = JSON.parse(await readFile9(this.opts.file, "utf8"));
|
|
46285
|
+
this.ran = typeof saved.ran === "number" ? saved.ran : 0;
|
|
46286
|
+
this.ideas = Array.isArray(saved.ideas) ? saved.ideas.filter((i) => isIdea(i) && i.prompt) : [];
|
|
46287
|
+
} catch {}
|
|
46288
|
+
}
|
|
46289
|
+
save() {
|
|
46290
|
+
const file = this.opts.file;
|
|
46291
|
+
if (!file)
|
|
46292
|
+
return Promise.resolve();
|
|
46293
|
+
this.saving = this.saving.then(async () => {
|
|
46294
|
+
const ideas = this.ideas.filter((i) => i.prompt).map(({ steps: _, ...i }) => i);
|
|
46295
|
+
const saved = { ran: this.ran, ideas };
|
|
46296
|
+
try {
|
|
46297
|
+
await mkdir10(dirname5(file), { recursive: true });
|
|
46298
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
46299
|
+
await writeFile8(tmp, `${JSON.stringify(saved, null, 2)}
|
|
46300
|
+
`);
|
|
46301
|
+
await rename8(tmp, file);
|
|
46302
|
+
} catch {}
|
|
46303
|
+
});
|
|
46304
|
+
return this.saving;
|
|
46305
|
+
}
|
|
46306
|
+
}
|
|
46307
|
+
function sameIdea(a, b) {
|
|
46308
|
+
return a.project === b.project && startedAs(a, { name: b.name, key: b.name });
|
|
46309
|
+
}
|
|
46310
|
+
function isIdea(i) {
|
|
46311
|
+
const o = i;
|
|
46312
|
+
return typeof o?.name === "string" && typeof o.project === "string" && typeof o.projectId === "string" && typeof o.projectName === "string" && typeof o.prompt === "string" && typeof o.found === "number";
|
|
46313
|
+
}
|
|
46314
|
+
function lookingAt(p, home) {
|
|
46315
|
+
return {
|
|
46316
|
+
project: p.root,
|
|
46317
|
+
projectId: p.project,
|
|
46318
|
+
projectName: p.name,
|
|
46319
|
+
folder: tilde(p.root, home)
|
|
46320
|
+
};
|
|
46321
|
+
}
|
|
46322
|
+
function shares(total, n) {
|
|
46323
|
+
return Array.from({ length: n }, (_, i) => Math.min(PER_PROJECT, Math.max(0, Math.floor(total / n) + (i < total % n ? 1 : 0))));
|
|
46324
|
+
}
|
|
46325
|
+
function limiter(n) {
|
|
46326
|
+
let running = 0;
|
|
46327
|
+
const waiting = [];
|
|
46328
|
+
return async (fn) => {
|
|
46329
|
+
if (running < n)
|
|
46330
|
+
running++;
|
|
46331
|
+
else
|
|
46332
|
+
await new Promise((go) => waiting.push(go));
|
|
46333
|
+
try {
|
|
46334
|
+
return await fn();
|
|
46335
|
+
} finally {
|
|
46336
|
+
const next = waiting.shift();
|
|
46337
|
+
if (next)
|
|
46338
|
+
next();
|
|
46339
|
+
else
|
|
46340
|
+
running--;
|
|
46341
|
+
}
|
|
46342
|
+
};
|
|
46343
|
+
}
|
|
46344
|
+
async function recentProjects(o = {}) {
|
|
46345
|
+
const home = o.home ?? homedir4();
|
|
46346
|
+
const env = o.env ?? (o.home ? {} : process.env);
|
|
46347
|
+
const limit = o.limit ?? PROJECTS;
|
|
46348
|
+
const sessions = [
|
|
46349
|
+
...await claudeSessions(env.CLAUDE_CONFIG_DIR ?? join12(home, ".claude")),
|
|
46350
|
+
...await codexSessions(env.CODEX_HOME ?? join12(home, ".codex"))
|
|
46351
|
+
].sort((a, b) => b.at - a.at);
|
|
46352
|
+
const out = new Map;
|
|
46353
|
+
const tried = new Set;
|
|
46354
|
+
for (const s of sessions.slice(0, SESSIONS)) {
|
|
46355
|
+
if (out.size >= limit)
|
|
46356
|
+
break;
|
|
46357
|
+
const cwd = await cwdOf(s.file);
|
|
46358
|
+
if (!cwd || tried.has(cwd))
|
|
46359
|
+
continue;
|
|
46360
|
+
tried.add(cwd);
|
|
46361
|
+
if (cwd === home || !cwd.startsWith(`${home}/`) && isTemporary(cwd))
|
|
46362
|
+
continue;
|
|
46363
|
+
if (!await stat9(cwd).then((st) => st.isDirectory(), () => false))
|
|
46364
|
+
continue;
|
|
46365
|
+
const repo = await repositoryOf(cwd).catch(() => null);
|
|
46366
|
+
if (!repo || repo.root === home || out.has(repo.project))
|
|
46367
|
+
continue;
|
|
46368
|
+
out.set(repo.project, {
|
|
46369
|
+
root: repo.root,
|
|
46370
|
+
name: basename3(repo.root),
|
|
46371
|
+
project: repo.project,
|
|
46372
|
+
at: s.at
|
|
46373
|
+
});
|
|
46374
|
+
}
|
|
46375
|
+
return [...out.values()];
|
|
46376
|
+
}
|
|
46377
|
+
async function claudeSessions(dir) {
|
|
46378
|
+
const folders = await readdir6(join12(dir, "projects")).catch(() => []);
|
|
46379
|
+
const out = [];
|
|
46380
|
+
await Promise.all(folders.map(async (f) => {
|
|
46381
|
+
const at = join12(dir, "projects", f);
|
|
46382
|
+
const files = await readdir6(at).catch(() => []);
|
|
46383
|
+
for (const name of files) {
|
|
46384
|
+
if (!name.endsWith(".jsonl"))
|
|
46385
|
+
continue;
|
|
46386
|
+
const file = join12(at, name);
|
|
46387
|
+
const s = await stat9(file).catch(() => null);
|
|
46388
|
+
if (s?.isFile())
|
|
46389
|
+
out.push({ file, at: s.mtimeMs });
|
|
46390
|
+
}
|
|
46391
|
+
}));
|
|
46392
|
+
return out;
|
|
46393
|
+
}
|
|
46394
|
+
async function codexSessions(dir) {
|
|
46395
|
+
const files = [];
|
|
46396
|
+
const walk = async (at, depth) => {
|
|
46397
|
+
const names = (await readdir6(at).catch(() => [])).sort().reverse();
|
|
46398
|
+
for (const name of names) {
|
|
46399
|
+
if (files.length >= SESSIONS)
|
|
46400
|
+
return;
|
|
46401
|
+
const path = join12(at, name);
|
|
46402
|
+
if (depth < 3)
|
|
46403
|
+
await walk(path, depth + 1);
|
|
46404
|
+
else if (name.endsWith(".jsonl"))
|
|
46405
|
+
files.push(path);
|
|
46406
|
+
}
|
|
46407
|
+
};
|
|
46408
|
+
await walk(join12(dir, "sessions"), 0);
|
|
46409
|
+
const out = [];
|
|
46410
|
+
for (const file of files) {
|
|
46411
|
+
const s = await stat9(file).catch(() => null);
|
|
46412
|
+
if (s?.isFile())
|
|
46413
|
+
out.push({ file, at: s.mtimeMs });
|
|
46414
|
+
}
|
|
46415
|
+
return out;
|
|
46416
|
+
}
|
|
46417
|
+
var CWD = /"cwd"\s*:\s*("(?:[^"\\]|\\.)*")/;
|
|
46418
|
+
async function cwdOf(file) {
|
|
46419
|
+
const head = await new Promise((done) => {
|
|
46420
|
+
let text = "";
|
|
46421
|
+
const stream = createReadStream(file, { encoding: "utf8", start: 0, end: HEAD_BYTES - 1 });
|
|
46422
|
+
stream.on("data", (chunk) => {
|
|
46423
|
+
text += chunk;
|
|
46424
|
+
if (CWD.test(text))
|
|
46425
|
+
stream.destroy();
|
|
46426
|
+
});
|
|
46427
|
+
stream.on("close", () => done(text));
|
|
46428
|
+
stream.on("error", () => done(text));
|
|
46429
|
+
});
|
|
46430
|
+
const m = CWD.exec(head);
|
|
46431
|
+
if (!m)
|
|
46432
|
+
return null;
|
|
46433
|
+
try {
|
|
46434
|
+
const cwd = JSON.parse(m[1]);
|
|
46435
|
+
return cwd.startsWith("/") ? cwd.replace(/\/+$/, "") || "/" : null;
|
|
46436
|
+
} catch {
|
|
46437
|
+
return null;
|
|
46438
|
+
}
|
|
46439
|
+
}
|
|
46440
|
+
function isTemporary(dir) {
|
|
46441
|
+
return [tmpdir2(), "/tmp", "/private/tmp", "/var/folders", "/private/var/folders"].some((t) => dir === t || dir.startsWith(`${t.replace(/\/+$/, "")}/`));
|
|
46442
|
+
}
|
|
46443
|
+
function tilde(path, home = homedir4()) {
|
|
46444
|
+
return path === home ? "~" : path.startsWith(`${home}/`) ? `~${path.slice(home.length)}` : path;
|
|
46445
|
+
}
|
|
46446
|
+
var UNLISTED_DIR = /(^|\/)(node_modules|dist|build|out|\.next|vendor|Pods)\//;
|
|
46447
|
+
var UNLISTED_FILE = /\.(png|jpe?g|gif|svg|ico|icns|webp|avif|mp4|mov|woff2?|ttf|otf|lock|snap)$/i;
|
|
46448
|
+
var NOTES = ["README.md", "AGENTS.md", "CLAUDE.md"];
|
|
46449
|
+
async function listingOf(root, env) {
|
|
46450
|
+
const out = await exec("git", ["-C", root, "ls-files", "--cached", "--others", "--exclude-standard"], { env, signal: AbortSignal.timeout(15000) }).catch(() => "");
|
|
46451
|
+
const all = out.split(`
|
|
46452
|
+
`).filter((f) => f && !UNLISTED_DIR.test(f) && !UNLISTED_FILE.test(f));
|
|
46453
|
+
const files = all.length > LISTED ? all.filter((f) => f.split("/").length <= 5).slice(0, LISTED) : all;
|
|
46454
|
+
const inside = await realpath2(root).catch(() => root);
|
|
46455
|
+
const notes = await Promise.all(NOTES.map(async (name) => {
|
|
46456
|
+
const at = await realpath2(join12(root, name)).catch(() => null);
|
|
46457
|
+
if (!at?.startsWith(`${inside}/`))
|
|
46458
|
+
return "";
|
|
46459
|
+
const text = await readFile9(at, "utf8").catch(() => "");
|
|
46460
|
+
return text.trim() ? `${name}:
|
|
46461
|
+
${text.slice(0, NOTE_BYTES).trim()}` : "";
|
|
46462
|
+
}));
|
|
46463
|
+
return { files, count: all.length, notes: notes.filter(Boolean).join(`
|
|
46464
|
+
|
|
46465
|
+
`) };
|
|
46466
|
+
}
|
|
46467
|
+
function glanceFor(p, count, taken, listing, home) {
|
|
46468
|
+
const folder = tilde(p.root, home);
|
|
46469
|
+
const names = (list) => list.map((n) => `\u201C${n}\u201D`).join(", ");
|
|
46470
|
+
const ideas = count === 1 ? "one design" : `${count} designs`;
|
|
46471
|
+
return [
|
|
46472
|
+
`You're suggesting designs for Ponzu, a design canvas where the person's coding agent draws in HTML and CSS and they steer it with comments on the canvas.`,
|
|
46473
|
+
``,
|
|
46474
|
+
`From the repository in ${folder}, listed below, suggest ${ideas} they might ask their agent for next. Each must start from something really in it, named the way the code names it: its screens and their states, a flow end to end, a component, the data model, how its parts fit together, its brand or docs, a page it's missing. Prefer what someone working on it would want to see laid out, or decide between a few takes on.`,
|
|
46475
|
+
``,
|
|
46476
|
+
`For each one give:`,
|
|
46477
|
+
`- name: what the design is called, like a file name, in sentence case and two to five words, e.g. "Every Seekle screen" or "Checkout, three takes".`,
|
|
46478
|
+
`- files: two to four paths from the list that it starts from.`,
|
|
46479
|
+
``,
|
|
46480
|
+
`Leave out branches, worktrees and time estimates.${taken.designs.length ? ` It already has these designs, so suggest others: ${names(taken.designs)}.` : ""}${taken.suggested.length ? ` These were suggested before; suggest different ones, not new takes on them: ${names(taken.suggested)}.` : ""}`,
|
|
46481
|
+
`Use no tools and change nothing: everything you need is here. Answer with the JSON alone: {"ideas": [{"name", "files"}]}.`,
|
|
46482
|
+
``,
|
|
46483
|
+
`<files count="${listing.count}">`,
|
|
46484
|
+
...listing.files,
|
|
46485
|
+
`</files>`,
|
|
46486
|
+
...listing.notes ? [``, `<notes>`, listing.notes, `</notes>`] : []
|
|
46487
|
+
].join(`
|
|
46488
|
+
`);
|
|
46489
|
+
}
|
|
46490
|
+
function promptFor(idea, files, p, home) {
|
|
46491
|
+
const folder = tilde(p.root, home);
|
|
46492
|
+
return [
|
|
46493
|
+
`You're writing a prompt for Ponzu, a design canvas where the person's coding agent draws in HTML and CSS and they steer it with comments on the canvas. They'll send it to their agent to make a design called \u201C${idea.name}\u201D in ${folder}${files.length ? `, starting from ${files.join(", ")}` : ""}. Read the code it should start from, a few files at most, then write it.`,
|
|
46494
|
+
``,
|
|
46495
|
+
`Its card shows the prompt, so it also teaches them how to ask for a design: write it in their voice, the way they'd type it, and be specific. Ask for what Ponzu is good at: each screen or state on its own artboard, side by side; a few takes to compare; a flow or a diagram drawn from the code; and their comments on the canvas to steer it.`,
|
|
46496
|
+
``,
|
|
46497
|
+
`Start it with: In ${folder}, make a new Ponzu design called \u201C${idea.name}\u201D. Then, in three to six sentences, ask for what to draw, name the files, screens and states in the code to start from, and say how to lay it out on the canvas. End by asking it to watch the canvas for their comments. Write plain text, no Markdown, and leave out branches, worktrees and time estimates.`,
|
|
46498
|
+
``,
|
|
46499
|
+
`Only read: change nothing. Answer with the JSON alone: {"prompt": "\u2026"}.`
|
|
46500
|
+
].join(`
|
|
46501
|
+
`);
|
|
46502
|
+
}
|
|
46503
|
+
function glanceFrom(answer, p, o) {
|
|
46504
|
+
const list = answer?.ideas;
|
|
46505
|
+
if (!Array.isArray(list))
|
|
46506
|
+
return [];
|
|
46507
|
+
const out = [];
|
|
46508
|
+
for (const raw of list) {
|
|
46509
|
+
const r = raw;
|
|
46510
|
+
const name = clean(r?.name);
|
|
46511
|
+
if (!name || name.length > 80)
|
|
46512
|
+
continue;
|
|
46513
|
+
if (out.some(({ idea }) => startedAs(idea, { name, key: name })))
|
|
46514
|
+
continue;
|
|
46515
|
+
const files = (Array.isArray(r.files) ? r.files : []).map(clean).filter(Boolean).slice(0, 4);
|
|
46516
|
+
out.push({
|
|
46517
|
+
idea: {
|
|
46518
|
+
name,
|
|
46519
|
+
project: p.root,
|
|
46520
|
+
projectId: p.project,
|
|
46521
|
+
projectName: p.name,
|
|
46522
|
+
prompt: "",
|
|
46523
|
+
steps: [],
|
|
46524
|
+
found: o.found
|
|
46525
|
+
},
|
|
46526
|
+
files
|
|
46527
|
+
});
|
|
46528
|
+
if (out.length >= o.count)
|
|
46529
|
+
break;
|
|
46530
|
+
}
|
|
46531
|
+
return out;
|
|
46532
|
+
}
|
|
46533
|
+
function promptFrom(answer, idea, p, home) {
|
|
46534
|
+
const prompt = clean(answer?.prompt);
|
|
46535
|
+
if (!prompt)
|
|
46536
|
+
return "";
|
|
46537
|
+
const folder = tilde(p.root, home);
|
|
46538
|
+
const opens = prompt.includes(idea.name) && (prompt.includes(folder) || prompt.includes(p.root));
|
|
46539
|
+
return opens ? prompt : `In ${folder}, make a new Ponzu design called \u201C${idea.name}\u201D. ${prompt}`;
|
|
46540
|
+
}
|
|
46541
|
+
function clean(v) {
|
|
46542
|
+
return typeof v === "string" ? v.replace(/\s+/g, " ").trim() : "";
|
|
46543
|
+
}
|
|
46544
|
+
function stepOf(tool, input, root) {
|
|
46545
|
+
const i = input ?? {};
|
|
46546
|
+
const text = (v) => typeof v === "string" ? v.trim() : "";
|
|
46547
|
+
if (tool === "Read") {
|
|
46548
|
+
const path = text(i.file_path);
|
|
46549
|
+
const rel = path.startsWith(`${root}/`) ? path.slice(root.length + 1) : path;
|
|
46550
|
+
return rel ? { verb: "read", what: rel.split("/").slice(-2).join("/") } : null;
|
|
46551
|
+
}
|
|
46552
|
+
if (tool === "Glob") {
|
|
46553
|
+
const pattern = text(i.pattern);
|
|
46554
|
+
return pattern ? { verb: "find", what: pattern } : null;
|
|
46555
|
+
}
|
|
46556
|
+
if (tool === "Grep") {
|
|
46557
|
+
const words = text(i.pattern).replace(/^\(\?i\)/, "").split("|")[0].replace(/\\[wsdb][+*?]?/g, " ").replace(/\.[+*?]?/g, " ").replace(/[\\^$()[\]{}?*+]/g, "").replace(/\s+/g, " ").trim().slice(0, 40);
|
|
46558
|
+
return words ? { verb: "search", what: words } : null;
|
|
46559
|
+
}
|
|
46560
|
+
return null;
|
|
46561
|
+
}
|
|
46562
|
+
var runAgent = async (job) => {
|
|
46563
|
+
if (job.agent === "claude")
|
|
46564
|
+
return runClaude(job);
|
|
46565
|
+
return runCodex(job);
|
|
46566
|
+
};
|
|
46567
|
+
var ISOLATION = {
|
|
46568
|
+
claude: ["--strict-mcp-config", "--no-session-persistence", "--settings", "--setting-sources"],
|
|
46569
|
+
codex: ["--ephemeral"]
|
|
46570
|
+
};
|
|
46571
|
+
var isolates = async (agent, bin, env) => {
|
|
46572
|
+
const has = flagsOf(await helpOf(bin, agent === "codex" ? ["exec", "--help"] : ["--help"], env));
|
|
46573
|
+
return ISOLATION[agent].every(has);
|
|
46574
|
+
};
|
|
46575
|
+
function flagsOf(help) {
|
|
46576
|
+
return (flag) => new RegExp(`^\\s*(?:-\\w, )?${flag}\\b`, "m").test(help);
|
|
46577
|
+
}
|
|
46578
|
+
async function runClaude(job) {
|
|
46579
|
+
const help = await helpOf(job.bin, ["--help"], job.env);
|
|
46580
|
+
const has = flagsOf(help);
|
|
46581
|
+
if (!ISOLATION.claude.every(has))
|
|
46582
|
+
throw new Error("Claude Code can't run isolated");
|
|
46583
|
+
const tools = job.kind === "glance" && has("--tools") ? "" : "Read,Glob,Grep";
|
|
46584
|
+
const stream = job.kind === "prompt" && has("--include-partial-messages");
|
|
46585
|
+
const args = [
|
|
46586
|
+
"-p",
|
|
46587
|
+
"--output-format",
|
|
46588
|
+
stream ? "stream-json" : "json",
|
|
46589
|
+
...stream ? ["--verbose", "--include-partial-messages"] : [],
|
|
46590
|
+
...tools && has("--json-schema") ? ["--json-schema", JSON.stringify(job.schema)] : [],
|
|
46591
|
+
...has("--tools") ? ["--tools", tools] : [],
|
|
46592
|
+
...tools ? ["--allowedTools", tools] : [],
|
|
46593
|
+
"--disallowedTools",
|
|
46594
|
+
"Bash,Edit,Write,MultiEdit,NotebookEdit,WebFetch,WebSearch,Task",
|
|
46595
|
+
...help.includes("dontAsk") ? ["--permission-mode", "dontAsk"] : [],
|
|
46596
|
+
"--setting-sources",
|
|
46597
|
+
"user",
|
|
46598
|
+
"--settings",
|
|
46599
|
+
JSON.stringify({ disableAllHooks: true }),
|
|
46600
|
+
"--strict-mcp-config",
|
|
46601
|
+
"--no-session-persistence",
|
|
46602
|
+
"--model",
|
|
46603
|
+
"sonnet"
|
|
46604
|
+
];
|
|
46605
|
+
let result = null;
|
|
46606
|
+
const onLine = (line) => {
|
|
46607
|
+
const e = parseJson(line);
|
|
46608
|
+
if (e?.type === "result")
|
|
46609
|
+
result = e;
|
|
46610
|
+
if (e?.type !== "assistant")
|
|
46611
|
+
return;
|
|
46612
|
+
for (const block of e.message?.content ?? []) {
|
|
46613
|
+
if (block?.type !== "tool_use" || !block.name)
|
|
46614
|
+
continue;
|
|
46615
|
+
const step = stepOf(block.name, block.input, job.project.root);
|
|
46616
|
+
if (step)
|
|
46617
|
+
job.onStep?.(step);
|
|
46618
|
+
}
|
|
46619
|
+
};
|
|
46620
|
+
const out = await exec(job.bin, args, {
|
|
46621
|
+
cwd: job.project.root,
|
|
46622
|
+
input: job.prompt,
|
|
46623
|
+
env: job.env,
|
|
46624
|
+
signal: job.signal,
|
|
46625
|
+
...stream ? { onLine, quiet: job.quiet } : {}
|
|
46626
|
+
});
|
|
46627
|
+
const r = stream ? result : parseJson(out);
|
|
46628
|
+
if (!r || r.is_error)
|
|
46629
|
+
throw new Error("Claude Code didn't answer");
|
|
46630
|
+
return r.structured_output ?? parseJson(r.result ?? "");
|
|
46631
|
+
}
|
|
46632
|
+
async function runCodex(job) {
|
|
46633
|
+
const help = await helpOf(job.bin, ["exec", "--help"], job.env);
|
|
46634
|
+
const has = flagsOf(help);
|
|
46635
|
+
if (!ISOLATION.codex.every(has))
|
|
46636
|
+
throw new Error("Codex can't run isolated");
|
|
46637
|
+
const dir = await mkdtemp(join12(tmpdir2(), "ponzu-suggest-"));
|
|
46638
|
+
try {
|
|
46639
|
+
const work = join12(dir, "work");
|
|
46640
|
+
const schema = join12(dir, "schema.json");
|
|
46641
|
+
const last = join12(dir, "answer.json");
|
|
46642
|
+
await mkdir10(work);
|
|
46643
|
+
await writeFile8(schema, JSON.stringify(job.schema));
|
|
46644
|
+
const off = (feature) => ["-c", `features.${feature}=false`];
|
|
46645
|
+
const args = [
|
|
46646
|
+
"exec",
|
|
46647
|
+
"--sandbox",
|
|
46648
|
+
"read-only",
|
|
46649
|
+
"--skip-git-repo-check",
|
|
46650
|
+
"--color",
|
|
46651
|
+
"never",
|
|
46652
|
+
"-C",
|
|
46653
|
+
work,
|
|
46654
|
+
"--output-schema",
|
|
46655
|
+
schema,
|
|
46656
|
+
"-o",
|
|
46657
|
+
last,
|
|
46658
|
+
"-c",
|
|
46659
|
+
'approval_policy="never"',
|
|
46660
|
+
...off("hooks"),
|
|
46661
|
+
...off("plugins"),
|
|
46662
|
+
...off("apps"),
|
|
46663
|
+
...off("memories"),
|
|
46664
|
+
...has("--ignore-user-config") ? ["--ignore-user-config"] : ["-c", "mcp_servers={}", "-c", "notify=[]"],
|
|
46665
|
+
...has("--ignore-rules") ? ["--ignore-rules"] : [],
|
|
46666
|
+
"--ephemeral",
|
|
46667
|
+
"-"
|
|
46668
|
+
];
|
|
46669
|
+
const out = await exec(job.bin, args, {
|
|
46670
|
+
cwd: work,
|
|
46671
|
+
input: job.prompt,
|
|
46672
|
+
env: job.env,
|
|
46673
|
+
signal: job.signal
|
|
46674
|
+
});
|
|
46675
|
+
const answer = await readFile9(last, "utf8").catch(() => out);
|
|
46676
|
+
return parseJson(answer);
|
|
46677
|
+
} finally {
|
|
46678
|
+
await rm4(dir, { recursive: true, force: true });
|
|
46679
|
+
}
|
|
46680
|
+
}
|
|
46681
|
+
function parseJson(text) {
|
|
46682
|
+
try {
|
|
46683
|
+
return JSON.parse(text);
|
|
46684
|
+
} catch {}
|
|
46685
|
+
const start = text.indexOf("{");
|
|
46686
|
+
const end = text.lastIndexOf("}");
|
|
46687
|
+
if (start < 0 || end <= start)
|
|
46688
|
+
return null;
|
|
46689
|
+
try {
|
|
46690
|
+
return JSON.parse(text.slice(start, end + 1));
|
|
46691
|
+
} catch {
|
|
46692
|
+
return null;
|
|
46693
|
+
}
|
|
46694
|
+
}
|
|
46695
|
+
var HELP_FOR = RUN_EVERY;
|
|
46696
|
+
var helps = new Map;
|
|
46697
|
+
function helpOf(bin, args, env) {
|
|
46698
|
+
const key = `${bin} ${args.join(" ")}`;
|
|
46699
|
+
const kept = helps.get(key);
|
|
46700
|
+
if (kept && Date.now() - kept.at < HELP_FOR)
|
|
46701
|
+
return kept.help;
|
|
46702
|
+
const help = exec(bin, args, { env, signal: AbortSignal.timeout(15000) }).catch(() => "");
|
|
46703
|
+
helps.set(key, { at: Date.now(), help });
|
|
46704
|
+
help.then((text) => {
|
|
46705
|
+
if (!text && helps.get(key)?.help === help)
|
|
46706
|
+
helps.delete(key);
|
|
46707
|
+
});
|
|
46708
|
+
return help;
|
|
46709
|
+
}
|
|
46710
|
+
function exec(bin, args, o) {
|
|
46711
|
+
return new Promise((done, fail) => {
|
|
46712
|
+
if (o.signal.aborted)
|
|
46713
|
+
return fail(new Error("stopped"));
|
|
46714
|
+
const child = spawn5(bin, args, {
|
|
46715
|
+
cwd: o.cwd,
|
|
46716
|
+
env: o.env,
|
|
46717
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
46718
|
+
});
|
|
46719
|
+
let out = "";
|
|
46720
|
+
let err = "";
|
|
46721
|
+
let quiet = false;
|
|
46722
|
+
const stop = () => {
|
|
46723
|
+
child.kill("SIGTERM");
|
|
46724
|
+
setTimeout(() => child.kill("SIGKILL"), 3000).unref();
|
|
46725
|
+
};
|
|
46726
|
+
let hush;
|
|
46727
|
+
const heard = () => {
|
|
46728
|
+
if (!o.quiet)
|
|
46729
|
+
return;
|
|
46730
|
+
clearTimeout(hush);
|
|
46731
|
+
hush = setTimeout(() => {
|
|
46732
|
+
quiet = true;
|
|
46733
|
+
stop();
|
|
46734
|
+
}, o.quiet);
|
|
46735
|
+
};
|
|
46736
|
+
heard();
|
|
46737
|
+
const lines = (flush) => {
|
|
46738
|
+
if (!o.onLine)
|
|
46739
|
+
return;
|
|
46740
|
+
let i = out.indexOf(`
|
|
46741
|
+
`);
|
|
46742
|
+
while (i >= 0) {
|
|
46743
|
+
o.onLine(out.slice(0, i));
|
|
46744
|
+
out = out.slice(i + 1);
|
|
46745
|
+
i = out.indexOf(`
|
|
46746
|
+
`);
|
|
46747
|
+
}
|
|
46748
|
+
if (flush && out)
|
|
46749
|
+
o.onLine(out);
|
|
46750
|
+
};
|
|
46751
|
+
o.signal.addEventListener("abort", stop, { once: true });
|
|
46752
|
+
child.stdout.on("data", (d) => {
|
|
46753
|
+
heard();
|
|
46754
|
+
if (out.length < 4000000)
|
|
46755
|
+
out += d.toString();
|
|
46756
|
+
lines(false);
|
|
46757
|
+
});
|
|
46758
|
+
child.stderr.on("data", (d) => {
|
|
46759
|
+
if (err.length < 4000)
|
|
46760
|
+
err += d.toString();
|
|
46761
|
+
});
|
|
46762
|
+
child.on("error", (e) => {
|
|
46763
|
+
clearTimeout(hush);
|
|
46764
|
+
o.signal.removeEventListener("abort", stop);
|
|
46765
|
+
fail(e);
|
|
46766
|
+
});
|
|
46767
|
+
child.on("close", (code) => {
|
|
46768
|
+
clearTimeout(hush);
|
|
46769
|
+
o.signal.removeEventListener("abort", stop);
|
|
46770
|
+
if (quiet)
|
|
46771
|
+
return fail(new Error("went quiet"));
|
|
46772
|
+
if (o.signal.aborted)
|
|
46773
|
+
return fail(new Error("timed out"));
|
|
46774
|
+
lines(true);
|
|
46775
|
+
if (code === 0)
|
|
46776
|
+
done(o.onLine ? "" : out);
|
|
46777
|
+
else
|
|
46778
|
+
fail(new Error(err.trim().split(`
|
|
46779
|
+
`).pop() || `exited with ${code}`));
|
|
46780
|
+
});
|
|
46781
|
+
child.stdin.on("error", () => {});
|
|
46782
|
+
child.stdin.end(o.input ?? "");
|
|
46783
|
+
});
|
|
46784
|
+
}
|
|
46785
|
+
|
|
46027
46786
|
// packages/server/src/serve.ts
|
|
46028
46787
|
var PROTOCOL = 1;
|
|
46029
46788
|
var PHONE_ACTIVE_MS = 2 * 60000;
|
|
@@ -46142,6 +46901,11 @@ function serve(ws, opts = {}) {
|
|
|
46142
46901
|
agentWorking
|
|
46143
46902
|
});
|
|
46144
46903
|
const feed = new CatalogFeed(home, ws);
|
|
46904
|
+
const suggestions = opts.suggest ? new SuggestionService({
|
|
46905
|
+
...opts.suggest,
|
|
46906
|
+
agents: opts.agents,
|
|
46907
|
+
designs: async () => (await home.local()).files
|
|
46908
|
+
}) : null;
|
|
46145
46909
|
const idleMs = () => idleFor({ agentIdle: mcp.idleMs(), phoneAt, phoneSockets: phoneSockets(), now: Date.now() });
|
|
46146
46910
|
const handle = async (req, srv, caller) => {
|
|
46147
46911
|
const url = new URL(req.url);
|
|
@@ -46244,6 +47008,16 @@ function serve(ws, opts = {}) {
|
|
|
46244
47008
|
feed.poke(0);
|
|
46245
47009
|
return Response.json({ ok: true });
|
|
46246
47010
|
}
|
|
47011
|
+
if (path === "/api/suggestions" || path === "/api/suggestions/refresh") {
|
|
47012
|
+
if (caller !== "local" || !sameHostOk(req, url))
|
|
47013
|
+
return new Response("Forbidden", { status: 403 });
|
|
47014
|
+
const refresh = path === "/api/suggestions/refresh";
|
|
47015
|
+
if (refresh && req.method !== "POST")
|
|
47016
|
+
return new Response("Method not allowed", { status: 405 });
|
|
47017
|
+
if (!suggestions)
|
|
47018
|
+
return Response.json({ agent: null, ideas: [], running: false, looking: [] });
|
|
47019
|
+
return Response.json(await (refresh ? suggestions.refresh() : suggestions.state()));
|
|
47020
|
+
}
|
|
46247
47021
|
if (path === "/api/update") {
|
|
46248
47022
|
if (req.method !== "POST")
|
|
46249
47023
|
return new Response("Method not allowed", { status: 405 });
|
|
@@ -46664,6 +47438,7 @@ function serve(ws, opts = {}) {
|
|
|
46664
47438
|
clearInterval(catchUp);
|
|
46665
47439
|
updater?.stop();
|
|
46666
47440
|
feed.stop();
|
|
47441
|
+
suggestions?.stop();
|
|
46667
47442
|
advert?.stop();
|
|
46668
47443
|
advert = null;
|
|
46669
47444
|
tailnetServer?.stop(force);
|
|
@@ -46808,10 +47583,10 @@ async function serveStudio(path, dir) {
|
|
|
46808
47583
|
<p>MCP endpoint: <code>/mcp</code></p></body>`, { headers: { "content-type": "text/html; charset=utf-8" } });
|
|
46809
47584
|
}
|
|
46810
47585
|
const clean = normalize(path).replace(/^(\.\.[/\\])+/, "");
|
|
46811
|
-
let file =
|
|
47586
|
+
let file = join13(dir, clean);
|
|
46812
47587
|
if (!file.startsWith(dir) || !existsSync8(file) || statSync2(file).isDirectory())
|
|
46813
|
-
file =
|
|
46814
|
-
const body = await
|
|
47588
|
+
file = join13(dir, "index.html");
|
|
47589
|
+
const body = await readFile10(file);
|
|
46815
47590
|
const ext = extname(file);
|
|
46816
47591
|
return new Response(body, {
|
|
46817
47592
|
headers: {
|
|
@@ -46863,7 +47638,7 @@ function remoteUpdater(endpoint, token) {
|
|
|
46863
47638
|
// apps/cli/package.json
|
|
46864
47639
|
var package_default = {
|
|
46865
47640
|
name: "ponzu",
|
|
46866
|
-
version: "1.
|
|
47641
|
+
version: "1.69.0",
|
|
46867
47642
|
description: "The open source canvas your agent draws on in HTML and CSS.",
|
|
46868
47643
|
type: "module",
|
|
46869
47644
|
license: "MIT",
|
|
@@ -46885,24 +47660,24 @@ var package_default = {
|
|
|
46885
47660
|
};
|
|
46886
47661
|
|
|
46887
47662
|
// apps/cli/src/host.ts
|
|
46888
|
-
import { spawn as
|
|
47663
|
+
import { spawn as spawn6 } from "child_process";
|
|
46889
47664
|
import { existsSync as existsSync9, readFileSync as readFileSync2, statSync as statSync3 } from "fs";
|
|
46890
|
-
import { chmod, copyFile, mkdir as
|
|
46891
|
-
import { homedir as
|
|
46892
|
-
import { dirname as
|
|
46893
|
-
var ponzuHome = () => process.env.PONZU_HOME ??
|
|
46894
|
-
var hostFile = () =>
|
|
47665
|
+
import { chmod, copyFile, mkdir as mkdir11, rename as rename9, rm as rm5, utimes as utimes3, writeFile as writeFile9 } from "fs/promises";
|
|
47666
|
+
import { homedir as homedir5 } from "os";
|
|
47667
|
+
import { dirname as dirname6, join as join14, posix, win32 } from "path";
|
|
47668
|
+
var ponzuHome = () => process.env.PONZU_HOME ?? join14(homedir5(), ".ponzu");
|
|
47669
|
+
var hostFile = () => join14(ponzuHome(), "host.json");
|
|
46895
47670
|
async function announceHost(url, opts = {}) {
|
|
46896
47671
|
if (!opts.takeOver && await runningHost())
|
|
46897
47672
|
return async () => {};
|
|
46898
47673
|
const path = hostFile();
|
|
46899
|
-
await
|
|
47674
|
+
await mkdir11(dirname6(path), { recursive: true });
|
|
46900
47675
|
const me = { url, mcp: `${url}/mcp`, pid: process.pid };
|
|
46901
|
-
await
|
|
47676
|
+
await writeFile9(path, `${JSON.stringify(me)}
|
|
46902
47677
|
`);
|
|
46903
47678
|
return async () => {
|
|
46904
47679
|
if (readHost()?.pid === process.pid)
|
|
46905
|
-
await
|
|
47680
|
+
await rm5(path, { force: true });
|
|
46906
47681
|
};
|
|
46907
47682
|
}
|
|
46908
47683
|
function readHost() {
|
|
@@ -46926,8 +47701,8 @@ async function runningHost() {
|
|
|
46926
47701
|
}
|
|
46927
47702
|
var SERVER = "Contents/Resources/server/ponzu";
|
|
46928
47703
|
var pathFor = (os) => os === "win32" ? win32 : posix;
|
|
46929
|
-
var appImageServer = () =>
|
|
46930
|
-
var appImageRecord = () =>
|
|
47704
|
+
var appImageServer = () => join14(ponzuHome(), "app/ponzu");
|
|
47705
|
+
var appImageRecord = () => join14(ponzuHome(), "app/appimage");
|
|
46931
47706
|
function recordedAppImage() {
|
|
46932
47707
|
try {
|
|
46933
47708
|
const path = readFileSync2(appImageRecord(), "utf8").trim();
|
|
@@ -46945,7 +47720,7 @@ function isFolder(app) {
|
|
|
46945
47720
|
}
|
|
46946
47721
|
function appServer(app, os = process.platform) {
|
|
46947
47722
|
if (os === "darwin")
|
|
46948
|
-
return
|
|
47723
|
+
return join14(app, SERVER);
|
|
46949
47724
|
const path = pathFor(os);
|
|
46950
47725
|
if (os === "linux" && !isFolder(app))
|
|
46951
47726
|
return appImageServer();
|
|
@@ -46978,7 +47753,7 @@ function ownBundle(execPath = process.execPath, os = process.platform, env = pro
|
|
|
46978
47753
|
}
|
|
46979
47754
|
function installedApps(os = process.platform, env = process.env) {
|
|
46980
47755
|
if (os === "darwin")
|
|
46981
|
-
return ["/Applications/Ponzu.app",
|
|
47756
|
+
return ["/Applications/Ponzu.app", join14(homedir5(), "Applications/Ponzu.app")];
|
|
46982
47757
|
if (os === "win32") {
|
|
46983
47758
|
return [
|
|
46984
47759
|
env.LOCALAPPDATA && win32.join(env.LOCALAPPDATA, "Programs", "ponzu"),
|
|
@@ -47011,15 +47786,15 @@ async function adoptAppImage(execPath = process.execPath, os = process.platform,
|
|
|
47011
47786
|
const from = statSync3(execPath);
|
|
47012
47787
|
const to = statSync3(copy, { throwIfNoEntry: false });
|
|
47013
47788
|
if (!to || to.size !== from.size || Math.trunc(to.mtimeMs) !== Math.trunc(from.mtimeMs)) {
|
|
47014
|
-
await
|
|
47789
|
+
await mkdir11(dirname6(copy), { recursive: true });
|
|
47015
47790
|
const temp = `${copy}.${process.pid}`;
|
|
47016
47791
|
await copyFile(execPath, temp);
|
|
47017
47792
|
await chmod(temp, 493);
|
|
47018
47793
|
await utimes3(temp, from.atime, from.mtime);
|
|
47019
|
-
await
|
|
47794
|
+
await rename9(temp, copy);
|
|
47020
47795
|
}
|
|
47021
47796
|
if (recordedAppImage() !== appimage)
|
|
47022
|
-
await
|
|
47797
|
+
await writeFile9(appImageRecord(), `${appimage}
|
|
47023
47798
|
`);
|
|
47024
47799
|
return appimage;
|
|
47025
47800
|
}
|
|
@@ -47047,7 +47822,7 @@ function startApp(app) {
|
|
|
47047
47822
|
const [cmd, args] = launchCommand(app);
|
|
47048
47823
|
return new Promise((resolve) => {
|
|
47049
47824
|
try {
|
|
47050
|
-
const child =
|
|
47825
|
+
const child = spawn6(cmd, args, { detached: true, stdio: "ignore", env });
|
|
47051
47826
|
child.on("error", () => resolve());
|
|
47052
47827
|
child.on("spawn", () => resolve());
|
|
47053
47828
|
child.unref();
|
|
@@ -47075,7 +47850,7 @@ async function findHost(launch) {
|
|
|
47075
47850
|
lastFailed = Date.now();
|
|
47076
47851
|
return url;
|
|
47077
47852
|
}
|
|
47078
|
-
var shimPath = (os = process.platform) =>
|
|
47853
|
+
var shimPath = (os = process.platform) => join14(ponzuHome(), os === "win32" ? "bin/ponzu.cmd" : "bin/ponzu");
|
|
47079
47854
|
function shimScript(app, os = process.platform) {
|
|
47080
47855
|
if (os === "win32") {
|
|
47081
47856
|
const candidates = [
|
|
@@ -47120,8 +47895,8 @@ exit 1
|
|
|
47120
47895
|
}
|
|
47121
47896
|
async function writeShim(app) {
|
|
47122
47897
|
const path = shimPath();
|
|
47123
|
-
await
|
|
47124
|
-
await
|
|
47898
|
+
await mkdir11(dirname6(path), { recursive: true });
|
|
47899
|
+
await writeFile9(path, shimScript(app));
|
|
47125
47900
|
await chmod(path, 493);
|
|
47126
47901
|
return path;
|
|
47127
47902
|
}
|
|
@@ -47129,27 +47904,27 @@ async function writeShim(app) {
|
|
|
47129
47904
|
// apps/cli/src/install.ts
|
|
47130
47905
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
47131
47906
|
import { existsSync as existsSync11, readFileSync as readFileSync4 } from "fs";
|
|
47132
|
-
import { mkdir as
|
|
47133
|
-
import { homedir as
|
|
47134
|
-
import { delimiter as delimiter2, dirname as
|
|
47907
|
+
import { mkdir as mkdir13, writeFile as writeFile11 } from "fs/promises";
|
|
47908
|
+
import { homedir as homedir7 } from "os";
|
|
47909
|
+
import { delimiter as delimiter2, dirname as dirname8, join as join16 } from "path";
|
|
47135
47910
|
|
|
47136
47911
|
// apps/cli/src/command.ts
|
|
47137
47912
|
import { spawnSync } from "child_process";
|
|
47138
47913
|
import { accessSync, constants, existsSync as existsSync10, lstatSync, readFileSync as readFileSync3, realpathSync as realpathSync3 } from "fs";
|
|
47139
|
-
import { mkdir as
|
|
47140
|
-
import { homedir as
|
|
47141
|
-
import { delimiter, dirname as
|
|
47142
|
-
function commandDirs(home =
|
|
47143
|
-
return [
|
|
47914
|
+
import { mkdir as mkdir12, symlink, writeFile as writeFile10 } from "fs/promises";
|
|
47915
|
+
import { homedir as homedir6 } from "os";
|
|
47916
|
+
import { delimiter, dirname as dirname7, join as join15 } from "path";
|
|
47917
|
+
function commandDirs(home = homedir6()) {
|
|
47918
|
+
return [join15(home, ".local/bin"), join15(home, "bin"), "/usr/local/bin"];
|
|
47144
47919
|
}
|
|
47145
|
-
var stateFile = () =>
|
|
47920
|
+
var stateFile = () => join15(ponzuHome(), "command.json");
|
|
47146
47921
|
async function linkCommand(shim, opts) {
|
|
47147
47922
|
const say = (s) => {
|
|
47148
47923
|
if (!opts.auto)
|
|
47149
47924
|
console.log(s);
|
|
47150
47925
|
};
|
|
47151
47926
|
if (process.platform === "win32")
|
|
47152
|
-
return addToUserPath(
|
|
47927
|
+
return addToUserPath(dirname7(shim), opts.auto, say);
|
|
47153
47928
|
const found = Bun.which("ponzu", { PATH: opts.path.join(delimiter) });
|
|
47154
47929
|
const app = found && opensApp(found) ? found : null;
|
|
47155
47930
|
if (found && !app) {
|
|
@@ -47160,25 +47935,25 @@ async function linkCommand(shim, opts) {
|
|
|
47160
47935
|
const state = readState();
|
|
47161
47936
|
if (opts.auto && state.linked)
|
|
47162
47937
|
return;
|
|
47163
|
-
const before = app ? opts.path.indexOf(
|
|
47938
|
+
const before = app ? opts.path.indexOf(dirname7(app)) : -1;
|
|
47164
47939
|
const dir = commandDirs(opts.home).find((d) => {
|
|
47165
47940
|
const at = opts.path.indexOf(d);
|
|
47166
47941
|
return at >= 0 && (before < 0 || at < before) && canLink(d, opts.home);
|
|
47167
47942
|
});
|
|
47168
47943
|
if (!dir) {
|
|
47169
|
-
say(process.platform === "darwin" ? `To run ponzu from a terminal, add ${
|
|
47944
|
+
say(process.platform === "darwin" ? `To run ponzu from a terminal, add ${dirname7(shim)} to your PATH, or choose Install Terminal Command in Ponzu's app menu.` : `To run ponzu from a terminal, add ${dirname7(shim)} to your PATH.`);
|
|
47170
47945
|
return;
|
|
47171
47946
|
}
|
|
47172
|
-
const link =
|
|
47947
|
+
const link = join15(dir, "ponzu");
|
|
47173
47948
|
try {
|
|
47174
|
-
await
|
|
47949
|
+
await mkdir12(dir, { recursive: true });
|
|
47175
47950
|
await symlink(shim, link);
|
|
47176
47951
|
} catch {
|
|
47177
47952
|
say(`Couldn't link ponzu into ${dir}.`);
|
|
47178
47953
|
return;
|
|
47179
47954
|
}
|
|
47180
|
-
await
|
|
47181
|
-
await
|
|
47955
|
+
await mkdir12(dirname7(stateFile()), { recursive: true });
|
|
47956
|
+
await writeFile10(stateFile(), `${JSON.stringify({ linked: link }, null, 2)}
|
|
47182
47957
|
`);
|
|
47183
47958
|
say(`Linked ${link}, so ponzu runs from a terminal.`);
|
|
47184
47959
|
}
|
|
@@ -47216,8 +47991,8 @@ async function addToUserPath(dir, auto, say) {
|
|
|
47216
47991
|
return;
|
|
47217
47992
|
if (userPath(dir, true) !== "added")
|
|
47218
47993
|
return say(`Couldn't add ${dir} to your PATH. Add it yourself to run ponzu from a terminal.`);
|
|
47219
|
-
await
|
|
47220
|
-
await
|
|
47994
|
+
await mkdir12(dirname7(stateFile()), { recursive: true });
|
|
47995
|
+
await writeFile10(stateFile(), `${JSON.stringify({ linked: dir }, null, 2)}
|
|
47221
47996
|
`);
|
|
47222
47997
|
say(`Added ${dir} to your PATH. Open a new terminal and run ponzu.`);
|
|
47223
47998
|
}
|
|
@@ -47228,9 +48003,9 @@ function readState() {
|
|
|
47228
48003
|
return {};
|
|
47229
48004
|
}
|
|
47230
48005
|
}
|
|
47231
|
-
function canLink(dir, home =
|
|
48006
|
+
function canLink(dir, home = homedir6()) {
|
|
47232
48007
|
try {
|
|
47233
|
-
lstatSync(
|
|
48008
|
+
lstatSync(join15(dir, "ponzu"));
|
|
47234
48009
|
return false;
|
|
47235
48010
|
} catch {}
|
|
47236
48011
|
if (!existsSync10(dir))
|
|
@@ -47244,7 +48019,7 @@ function canLink(dir, home = homedir5()) {
|
|
|
47244
48019
|
}
|
|
47245
48020
|
function opensApp(file) {
|
|
47246
48021
|
try {
|
|
47247
|
-
return existsSync10(
|
|
48022
|
+
return existsSync10(join15(dirname7(realpathSync3(file)), "resources/server/ponzu"));
|
|
47248
48023
|
} catch {
|
|
47249
48024
|
return false;
|
|
47250
48025
|
}
|
|
@@ -47264,7 +48039,7 @@ var AGENTS = [
|
|
|
47264
48039
|
label: "Claude Code",
|
|
47265
48040
|
detect: () => Boolean(which("claude")),
|
|
47266
48041
|
current: () => {
|
|
47267
|
-
const config = readJson(
|
|
48042
|
+
const config = readJson(join16(homedir7(), ".claude.json"));
|
|
47268
48043
|
return toEntry(config?.mcpServers?.ponzu);
|
|
47269
48044
|
},
|
|
47270
48045
|
register(entry) {
|
|
@@ -47314,7 +48089,7 @@ var AGENTS = [
|
|
|
47314
48089
|
{
|
|
47315
48090
|
id: "cursor",
|
|
47316
48091
|
label: "Cursor",
|
|
47317
|
-
detect: () => existsSync11(
|
|
48092
|
+
detect: () => existsSync11(join16(homedir7(), ".cursor")),
|
|
47318
48093
|
current: () => {
|
|
47319
48094
|
const config = readJson(cursorConfig());
|
|
47320
48095
|
return toEntry(config?.mcpServers?.ponzu);
|
|
@@ -47332,8 +48107,8 @@ var AGENTS = [
|
|
|
47332
48107
|
config = parsed;
|
|
47333
48108
|
}
|
|
47334
48109
|
config.mcpServers = { ...config.mcpServers, ponzu: entry };
|
|
47335
|
-
await
|
|
47336
|
-
await
|
|
48110
|
+
await mkdir13(dirname8(path), { recursive: true });
|
|
48111
|
+
await writeFile11(path, `${JSON.stringify(config, null, 2)}
|
|
47337
48112
|
`);
|
|
47338
48113
|
return true;
|
|
47339
48114
|
}
|
|
@@ -47401,7 +48176,7 @@ function agentStatus() {
|
|
|
47401
48176
|
}
|
|
47402
48177
|
function codexConfigured() {
|
|
47403
48178
|
try {
|
|
47404
|
-
return /^\[mcp_servers\.ponzu\]/m.test(readFileSync4(
|
|
48179
|
+
return /^\[mcp_servers\.ponzu\]/m.test(readFileSync4(join16(process.env.CODEX_HOME ?? join16(homedir7(), ".codex"), "config.toml"), "utf8"));
|
|
47405
48180
|
} catch {
|
|
47406
48181
|
return false;
|
|
47407
48182
|
}
|
|
@@ -47445,18 +48220,18 @@ function shouldAutoRegister(current, setUpBefore) {
|
|
|
47445
48220
|
function isLegacy(e) {
|
|
47446
48221
|
return "url" in e && /^http:\/\/(127\.0\.0\.1|localhost):7777\/mcp\/?$/.test(e.url);
|
|
47447
48222
|
}
|
|
47448
|
-
var doneFile = () =>
|
|
48223
|
+
var doneFile = () => join16(ponzuHome(), "agents.json");
|
|
47449
48224
|
function readDone() {
|
|
47450
48225
|
const raw = readJson(doneFile());
|
|
47451
48226
|
return new Set(raw?.configured ?? []);
|
|
47452
48227
|
}
|
|
47453
48228
|
async function writeDone(done) {
|
|
47454
|
-
await
|
|
47455
|
-
await
|
|
48229
|
+
await mkdir13(dirname8(doneFile()), { recursive: true });
|
|
48230
|
+
await writeFile11(doneFile(), `${JSON.stringify({ configured: [...done].sort() }, null, 2)}
|
|
47456
48231
|
`);
|
|
47457
48232
|
}
|
|
47458
48233
|
function cursorConfig() {
|
|
47459
|
-
return
|
|
48234
|
+
return join16(homedir7(), ".cursor", "mcp.json");
|
|
47460
48235
|
}
|
|
47461
48236
|
function readJson(path) {
|
|
47462
48237
|
try {
|
|
@@ -47469,13 +48244,13 @@ var searchPath;
|
|
|
47469
48244
|
function agentPath() {
|
|
47470
48245
|
if (searchPath !== undefined)
|
|
47471
48246
|
return searchPath;
|
|
47472
|
-
const home =
|
|
48247
|
+
const home = homedir7();
|
|
47473
48248
|
const dirs = [
|
|
47474
48249
|
...(process.env.PATH ?? "").split(delimiter2),
|
|
47475
|
-
|
|
47476
|
-
|
|
47477
|
-
|
|
47478
|
-
|
|
48250
|
+
join16(home, ".local/bin"),
|
|
48251
|
+
join16(home, ".claude/local"),
|
|
48252
|
+
join16(home, ".bun/bin"),
|
|
48253
|
+
join16(home, ".npm-global/bin"),
|
|
47479
48254
|
"/opt/homebrew/bin",
|
|
47480
48255
|
"/usr/local/bin",
|
|
47481
48256
|
...loginPath()
|
|
@@ -47503,6 +48278,12 @@ function loginPath() {
|
|
|
47503
48278
|
function which(cmd) {
|
|
47504
48279
|
return Bun.which(cmd, { PATH: agentPath() });
|
|
47505
48280
|
}
|
|
48281
|
+
function agentCli(cmd) {
|
|
48282
|
+
return which(cmd);
|
|
48283
|
+
}
|
|
48284
|
+
function agentEnv() {
|
|
48285
|
+
return { PATH: agentPath() };
|
|
48286
|
+
}
|
|
47506
48287
|
function capture2(cmd) {
|
|
47507
48288
|
const bin = which(cmd[0]);
|
|
47508
48289
|
if (!bin)
|
|
@@ -47529,13 +48310,13 @@ function run(cmd, quiet = false) {
|
|
|
47529
48310
|
// apps/cli/src/source.ts
|
|
47530
48311
|
import { createHash as createHash5 } from "crypto";
|
|
47531
48312
|
import { existsSync as existsSync12 } from "fs";
|
|
47532
|
-
import { dirname as
|
|
48313
|
+
import { dirname as dirname9, join as join17, sep as sep4 } from "path";
|
|
47533
48314
|
import { fileURLToPath } from "url";
|
|
47534
48315
|
function sourceCheckout(file = fileURLToPath(import.meta.url)) {
|
|
47535
48316
|
if (file.startsWith("/$bunfs/") || file.split(sep4).includes("node_modules"))
|
|
47536
48317
|
return null;
|
|
47537
|
-
for (let dir =
|
|
47538
|
-
if (existsSync12(
|
|
48318
|
+
for (let dir = dirname9(file);dir !== dirname9(dir); dir = dirname9(dir))
|
|
48319
|
+
if (existsSync12(join17(dir, ".git")))
|
|
47539
48320
|
return dir;
|
|
47540
48321
|
return null;
|
|
47541
48322
|
}
|
|
@@ -47574,12 +48355,12 @@ function firstFree(ports, hostname) {
|
|
|
47574
48355
|
// apps/cli/src/telemetry.ts
|
|
47575
48356
|
import { createHash as createHash6, randomUUID } from "crypto";
|
|
47576
48357
|
import { readFileSync as readFileSync5 } from "fs";
|
|
47577
|
-
import { mkdir as
|
|
48358
|
+
import { mkdir as mkdir14, rename as rename10, writeFile as writeFile12 } from "fs/promises";
|
|
47578
48359
|
import { arch, platform as platform2 } from "os";
|
|
47579
|
-
import { dirname as
|
|
48360
|
+
import { dirname as dirname11, join as join18 } from "path";
|
|
47580
48361
|
|
|
47581
48362
|
// node_modules/.bun/posthog-node@5.56.2/node_modules/posthog-node/dist/extensions/error-tracking/modifiers/module.node.mjs
|
|
47582
|
-
import { dirname as
|
|
48363
|
+
import { dirname as dirname10, posix as posix2, sep as sep5 } from "path";
|
|
47583
48364
|
function createModulerModifier() {
|
|
47584
48365
|
const getModuleFromFileName = createGetModuleFromFilename();
|
|
47585
48366
|
return async (frames) => {
|
|
@@ -47588,7 +48369,7 @@ function createModulerModifier() {
|
|
|
47588
48369
|
return frames;
|
|
47589
48370
|
};
|
|
47590
48371
|
}
|
|
47591
|
-
function createGetModuleFromFilename(basePath = process.argv[1] ?
|
|
48372
|
+
function createGetModuleFromFilename(basePath = process.argv[1] ? dirname10(process.argv[1]) : process.cwd(), isWindows = sep5 === "\\") {
|
|
47592
48373
|
const normalizedBase = isWindows ? normalizeWindowsPath(basePath) : basePath;
|
|
47593
48374
|
return (filename) => {
|
|
47594
48375
|
if (!filename)
|
|
@@ -56868,7 +57649,7 @@ var CATEGORY = {
|
|
|
56868
57649
|
praise: "Praise",
|
|
56869
57650
|
other: "Other"
|
|
56870
57651
|
};
|
|
56871
|
-
var settingsFile = () =>
|
|
57652
|
+
var settingsFile = () => join18(ponzuHome(), "telemetry.json");
|
|
56872
57653
|
function readSettings() {
|
|
56873
57654
|
try {
|
|
56874
57655
|
return JSON.parse(readFileSync5(settingsFile(), "utf8"));
|
|
@@ -56878,11 +57659,11 @@ function readSettings() {
|
|
|
56878
57659
|
}
|
|
56879
57660
|
async function writeSettings(settings) {
|
|
56880
57661
|
const path = settingsFile();
|
|
56881
|
-
await
|
|
57662
|
+
await mkdir14(dirname11(path), { recursive: true });
|
|
56882
57663
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
56883
|
-
await
|
|
57664
|
+
await writeFile12(tmp, `${JSON.stringify(settings, null, 2)}
|
|
56884
57665
|
`);
|
|
56885
|
-
await
|
|
57666
|
+
await rename10(tmp, path);
|
|
56886
57667
|
}
|
|
56887
57668
|
async function settings() {
|
|
56888
57669
|
const current = readSettings();
|
|
@@ -57055,13 +57836,13 @@ async function telemetryCommand(arg = "status") {
|
|
|
57055
57836
|
import { dlopen, FFIType, ptr } from "bun:ffi";
|
|
57056
57837
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
57057
57838
|
import { fstatSync, readFileSync as readFileSync6 } from "fs";
|
|
57058
|
-
import { mkdir as
|
|
57059
|
-
import { homedir as
|
|
57060
|
-
import { join as
|
|
57839
|
+
import { mkdir as mkdir15, rm as rm6, writeFile as writeFile13 } from "fs/promises";
|
|
57840
|
+
import { homedir as homedir8 } from "os";
|
|
57841
|
+
import { join as join19 } from "path";
|
|
57061
57842
|
var WAKE_LABEL = "design.ponzu.app.wake";
|
|
57062
57843
|
var WAKE_PORT = 7778;
|
|
57063
|
-
var agentsDir = () => process.env.PONZU_LAUNCH_AGENTS ??
|
|
57064
|
-
var wakePlistPath = () =>
|
|
57844
|
+
var agentsDir = () => process.env.PONZU_LAUNCH_AGENTS ?? join19(homedir8(), "Library/LaunchAgents");
|
|
57845
|
+
var wakePlistPath = () => join19(agentsDir(), `${WAKE_LABEL}.plist`);
|
|
57065
57846
|
var xml = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
57066
57847
|
function wakePlist(program, port = WAKE_PORT) {
|
|
57067
57848
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -57107,17 +57888,17 @@ async function installWake(program, run = launchctl) {
|
|
|
57107
57888
|
} catch {}
|
|
57108
57889
|
if (current === plist && run(["print", `${domain()}/${WAKE_LABEL}`]))
|
|
57109
57890
|
return false;
|
|
57110
|
-
await
|
|
57111
|
-
await
|
|
57891
|
+
await mkdir15(agentsDir(), { recursive: true });
|
|
57892
|
+
await writeFile13(path, plist);
|
|
57112
57893
|
run(["bootout", `${domain()}/${WAKE_LABEL}`]);
|
|
57113
57894
|
run(["bootstrap", domain(), path]);
|
|
57114
57895
|
return true;
|
|
57115
57896
|
}
|
|
57116
57897
|
async function uninstallWake(run = launchctl) {
|
|
57117
57898
|
run(["bootout", `${domain()}/${WAKE_LABEL}`]);
|
|
57118
|
-
await
|
|
57899
|
+
await rm6(wakePlistPath(), { force: true });
|
|
57119
57900
|
}
|
|
57120
|
-
var systemdDir = () => process.env.PONZU_SYSTEMD_USER ??
|
|
57901
|
+
var systemdDir = () => process.env.PONZU_SYSTEMD_USER ?? join19(process.env.XDG_CONFIG_HOME || join19(homedir8(), ".config"), "systemd/user");
|
|
57121
57902
|
var WAKE_SOCKET = "ponzu-wake.socket";
|
|
57122
57903
|
var WAKE_SERVICE = "ponzu-wake@.service";
|
|
57123
57904
|
var systemdWord = (s) => `"${s.replace(/[\\"]/g, "\\$&").replace(/%/g, "%%").replace(/\$/g, "$$$$")}"`;
|
|
@@ -57151,16 +57932,16 @@ async function installSystemdWake(program, run = systemctl) {
|
|
|
57151
57932
|
const units = wakeUnits(program);
|
|
57152
57933
|
const current = Object.entries(units).every(([name, text]) => {
|
|
57153
57934
|
try {
|
|
57154
|
-
return readFileSync6(
|
|
57935
|
+
return readFileSync6(join19(systemdDir(), name), "utf8") === text;
|
|
57155
57936
|
} catch {
|
|
57156
57937
|
return false;
|
|
57157
57938
|
}
|
|
57158
57939
|
});
|
|
57159
57940
|
if (current && run(["is-active", "--quiet", WAKE_SOCKET]))
|
|
57160
57941
|
return false;
|
|
57161
|
-
await
|
|
57942
|
+
await mkdir15(systemdDir(), { recursive: true });
|
|
57162
57943
|
for (const [name, text] of Object.entries(units))
|
|
57163
|
-
await
|
|
57944
|
+
await writeFile13(join19(systemdDir(), name), text);
|
|
57164
57945
|
run(["daemon-reload"]);
|
|
57165
57946
|
run(["enable", WAKE_SOCKET]);
|
|
57166
57947
|
run(["restart", WAKE_SOCKET]);
|
|
@@ -57169,7 +57950,7 @@ async function installSystemdWake(program, run = systemctl) {
|
|
|
57169
57950
|
async function uninstallSystemdWake(run = systemctl) {
|
|
57170
57951
|
run(["disable", "--now", WAKE_SOCKET]);
|
|
57171
57952
|
for (const name of [WAKE_SOCKET, WAKE_SERVICE])
|
|
57172
|
-
await
|
|
57953
|
+
await rm6(join19(systemdDir(), name), { force: true });
|
|
57173
57954
|
run(["daemon-reload"]);
|
|
57174
57955
|
}
|
|
57175
57956
|
function peerKind(ip) {
|
|
@@ -57334,7 +58115,7 @@ function lsofPeer() {
|
|
|
57334
58115
|
|
|
57335
58116
|
// apps/cli/src/index.ts
|
|
57336
58117
|
var VERSION2 = package_default.version;
|
|
57337
|
-
var here =
|
|
58118
|
+
var here = dirname12(fileURLToPath2(import.meta.url));
|
|
57338
58119
|
var HELP = `Ponzu ${VERSION2}: the open source canvas your agent draws on in HTML and CSS.
|
|
57339
58120
|
|
|
57340
58121
|
Usage
|
|
@@ -57459,7 +58240,7 @@ async function openWorkspace(files, log) {
|
|
|
57459
58240
|
const paths = files.length ? files.map((f) => resolve5(f)) : await findDesigns(process.cwd());
|
|
57460
58241
|
const named = [];
|
|
57461
58242
|
for (const p of paths) {
|
|
57462
|
-
if (!existsSync13(
|
|
58243
|
+
if (!existsSync13(join20(p, "design.json")) && existsSync13(p) === false && files.length) {
|
|
57463
58244
|
log(`Creating ${p}`);
|
|
57464
58245
|
}
|
|
57465
58246
|
const { key } = await ws.open(p);
|
|
@@ -57485,10 +58266,15 @@ async function runServe(files, o) {
|
|
|
57485
58266
|
root: process.cwd(),
|
|
57486
58267
|
tailnet: o.tailnet,
|
|
57487
58268
|
tailnetPort: 7777,
|
|
57488
|
-
dbFile:
|
|
57489
|
-
stateFile:
|
|
58269
|
+
dbFile: join20(ponzuHome(), "ponzu.db"),
|
|
58270
|
+
stateFile: join20(ponzuHome(), "catalog.json"),
|
|
57490
58271
|
spotlight: process.env.PONZU_SPOTLIGHT === "0" ? false : undefined,
|
|
57491
58272
|
agents: async () => agents(),
|
|
58273
|
+
suggest: {
|
|
58274
|
+
bin: (agent) => agentCli(agent),
|
|
58275
|
+
env: agentEnv(),
|
|
58276
|
+
file: join20(ponzuHome(), "suggestions.json")
|
|
58277
|
+
},
|
|
57492
58278
|
opened: named,
|
|
57493
58279
|
version: VERSION2,
|
|
57494
58280
|
telemetry: phoneTelemetry(),
|
|
@@ -57594,7 +58380,7 @@ function absolutePaths(args, cwd) {
|
|
|
57594
58380
|
}
|
|
57595
58381
|
async function runInit(target = "design/untitled.design") {
|
|
57596
58382
|
const path = resolve5(target.endsWith(".design") ? target : `${target}.design`);
|
|
57597
|
-
if (existsSync13(
|
|
58383
|
+
if (existsSync13(join20(path, "design.json"))) {
|
|
57598
58384
|
console.error(`${path} already exists.`);
|
|
57599
58385
|
process.exit(1);
|
|
57600
58386
|
}
|
|
@@ -57616,11 +58402,11 @@ Next: ponzu serve ${target}`);
|
|
|
57616
58402
|
}
|
|
57617
58403
|
function findStudio() {
|
|
57618
58404
|
const candidates = [
|
|
57619
|
-
|
|
57620
|
-
|
|
57621
|
-
|
|
58405
|
+
join20(here, "../studio"),
|
|
58406
|
+
join20(here, "../../studio/dist"),
|
|
58407
|
+
join20(here, "../../../apps/studio/dist")
|
|
57622
58408
|
];
|
|
57623
|
-
return candidates.find((c) => existsSync13(
|
|
58409
|
+
return candidates.find((c) => existsSync13(join20(c, "index.html")));
|
|
57624
58410
|
}
|
|
57625
58411
|
function lanAddresses() {
|
|
57626
58412
|
const out = [];
|