gipity 1.0.424 → 1.0.426
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/api.js +5 -3
- package/dist/commands/add.js +11 -0
- package/dist/commands/bug.js +21 -1
- package/dist/commands/fn.js +21 -2
- package/dist/commands/logs.js +4 -1
- package/dist/commands/page-eval.js +75 -5
- package/dist/commands/page-inspect.js +1 -1
- package/dist/commands/page-screenshot.js +43 -2
- package/dist/commands/page.js +1 -1
- package/dist/commands/sandbox.js +44 -5
- package/dist/commands/test.js +39 -3
- package/dist/commands/workflow.js +26 -16
- package/dist/index.js +325 -106
- package/dist/knowledge.js +1 -0
- package/dist/sync.js +17 -3
- package/dist/trace.js +69 -0
- package/dist/updater/shim.js +55 -4
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3944,10 +3944,10 @@ function prompt(question) {
|
|
|
3944
3944
|
return Promise.reject(new Error(`prompt() called without a TTY: ${question.trim()}`));
|
|
3945
3945
|
}
|
|
3946
3946
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3947
|
-
return new Promise((
|
|
3947
|
+
return new Promise((resolve17) => {
|
|
3948
3948
|
rl.question(question, (answer) => {
|
|
3949
3949
|
rl.close();
|
|
3950
|
-
|
|
3950
|
+
resolve17(answer.trim());
|
|
3951
3951
|
});
|
|
3952
3952
|
});
|
|
3953
3953
|
}
|
|
@@ -3977,7 +3977,7 @@ async function confirm(question, opts = {}) {
|
|
|
3977
3977
|
const wasRaw = stdin.isRaw ?? false;
|
|
3978
3978
|
stdin.setRawMode(true);
|
|
3979
3979
|
stdin.resume();
|
|
3980
|
-
return new Promise((
|
|
3980
|
+
return new Promise((resolve17) => {
|
|
3981
3981
|
stdin.once("data", (key) => {
|
|
3982
3982
|
stdin.setRawMode(wasRaw);
|
|
3983
3983
|
stdin.pause();
|
|
@@ -3992,12 +3992,12 @@ async function confirm(question, opts = {}) {
|
|
|
3992
3992
|
else if (k === "n") answer = false;
|
|
3993
3993
|
else answer = defaultYes;
|
|
3994
3994
|
console.log(answer ? "y" : "n");
|
|
3995
|
-
|
|
3995
|
+
resolve17(answer);
|
|
3996
3996
|
});
|
|
3997
3997
|
});
|
|
3998
3998
|
}
|
|
3999
3999
|
function pickOne(label2, max, defaultIdx = 1) {
|
|
4000
|
-
return new Promise((
|
|
4000
|
+
return new Promise((resolve17) => {
|
|
4001
4001
|
process.stdout.write(` ${bold(label2)} (1-${max}) [${bold(String(defaultIdx))}]: `);
|
|
4002
4002
|
const { stdin } = process;
|
|
4003
4003
|
const wasRaw = stdin.isRaw ?? false;
|
|
@@ -4013,15 +4013,15 @@ function pickOne(label2, max, defaultIdx = 1) {
|
|
|
4013
4013
|
}
|
|
4014
4014
|
if (ch === "\r" || ch === "\n") {
|
|
4015
4015
|
console.log(String(defaultIdx));
|
|
4016
|
-
return
|
|
4016
|
+
return resolve17(defaultIdx);
|
|
4017
4017
|
}
|
|
4018
4018
|
const n = parseInt(ch, 10);
|
|
4019
4019
|
if (n >= 1 && n <= max) {
|
|
4020
4020
|
console.log(String(n));
|
|
4021
|
-
return
|
|
4021
|
+
return resolve17(n);
|
|
4022
4022
|
}
|
|
4023
4023
|
console.log(String(defaultIdx));
|
|
4024
|
-
|
|
4024
|
+
resolve17(defaultIdx);
|
|
4025
4025
|
});
|
|
4026
4026
|
});
|
|
4027
4027
|
}
|
|
@@ -5370,8 +5370,8 @@ var require_streamx = __commonJS({
|
|
|
5370
5370
|
return this;
|
|
5371
5371
|
},
|
|
5372
5372
|
next() {
|
|
5373
|
-
return new Promise(function(
|
|
5374
|
-
promiseResolve =
|
|
5373
|
+
return new Promise(function(resolve17, reject) {
|
|
5374
|
+
promiseResolve = resolve17;
|
|
5375
5375
|
promiseReject = reject;
|
|
5376
5376
|
const data = stream.read();
|
|
5377
5377
|
if (data !== null) ondata(data);
|
|
@@ -5401,11 +5401,11 @@ var require_streamx = __commonJS({
|
|
|
5401
5401
|
}
|
|
5402
5402
|
function destroy(err) {
|
|
5403
5403
|
stream.destroy(err);
|
|
5404
|
-
return new Promise((
|
|
5405
|
-
if (stream._duplexState & DESTROYED) return
|
|
5404
|
+
return new Promise((resolve17, reject) => {
|
|
5405
|
+
if (stream._duplexState & DESTROYED) return resolve17({ value: void 0, done: true });
|
|
5406
5406
|
stream.once("close", function() {
|
|
5407
5407
|
if (err) reject(err);
|
|
5408
|
-
else
|
|
5408
|
+
else resolve17({ value: void 0, done: true });
|
|
5409
5409
|
});
|
|
5410
5410
|
});
|
|
5411
5411
|
}
|
|
@@ -5449,8 +5449,8 @@ var require_streamx = __commonJS({
|
|
|
5449
5449
|
const writes = pending + (ws._duplexState & WRITE_WRITING ? 1 : 0);
|
|
5450
5450
|
if (writes === 0) return Promise.resolve(true);
|
|
5451
5451
|
if (state.drains === null) state.drains = [];
|
|
5452
|
-
return new Promise((
|
|
5453
|
-
state.drains.push({ writes, resolve:
|
|
5452
|
+
return new Promise((resolve17) => {
|
|
5453
|
+
state.drains.push({ writes, resolve: resolve17 });
|
|
5454
5454
|
});
|
|
5455
5455
|
}
|
|
5456
5456
|
write(data) {
|
|
@@ -5555,10 +5555,10 @@ var require_streamx = __commonJS({
|
|
|
5555
5555
|
cb(null);
|
|
5556
5556
|
}
|
|
5557
5557
|
function pipelinePromise(...streams) {
|
|
5558
|
-
return new Promise((
|
|
5558
|
+
return new Promise((resolve17, reject) => {
|
|
5559
5559
|
return pipeline(...streams, (err) => {
|
|
5560
5560
|
if (err) return reject(err);
|
|
5561
|
-
|
|
5561
|
+
resolve17();
|
|
5562
5562
|
});
|
|
5563
5563
|
});
|
|
5564
5564
|
}
|
|
@@ -6213,16 +6213,16 @@ var require_extract = __commonJS({
|
|
|
6213
6213
|
entryCallback = null;
|
|
6214
6214
|
cb(err);
|
|
6215
6215
|
}
|
|
6216
|
-
function onnext(
|
|
6216
|
+
function onnext(resolve17, reject) {
|
|
6217
6217
|
if (error2) {
|
|
6218
6218
|
return reject(error2);
|
|
6219
6219
|
}
|
|
6220
6220
|
if (entryStream) {
|
|
6221
|
-
|
|
6221
|
+
resolve17({ value: entryStream, done: false });
|
|
6222
6222
|
entryStream = null;
|
|
6223
6223
|
return;
|
|
6224
6224
|
}
|
|
6225
|
-
promiseResolve =
|
|
6225
|
+
promiseResolve = resolve17;
|
|
6226
6226
|
promiseReject = reject;
|
|
6227
6227
|
consumeCallback(null);
|
|
6228
6228
|
if (extract3._finished && promiseResolve) {
|
|
@@ -6250,11 +6250,11 @@ var require_extract = __commonJS({
|
|
|
6250
6250
|
function destroy(err) {
|
|
6251
6251
|
extract3.destroy(err);
|
|
6252
6252
|
consumeCallback(err);
|
|
6253
|
-
return new Promise((
|
|
6254
|
-
if (extract3.destroyed) return
|
|
6253
|
+
return new Promise((resolve17, reject) => {
|
|
6254
|
+
if (extract3.destroyed) return resolve17({ value: void 0, done: true });
|
|
6255
6255
|
extract3.once("close", function() {
|
|
6256
6256
|
if (err) reject(err);
|
|
6257
|
-
else
|
|
6257
|
+
else resolve17({ value: void 0, done: true });
|
|
6258
6258
|
});
|
|
6259
6259
|
});
|
|
6260
6260
|
}
|
|
@@ -6705,7 +6705,7 @@ async function postForTarEntries(path4, body, retried = false) {
|
|
|
6705
6705
|
if (!res.body) throw new ApiError(500, "EMPTY_RESPONSE", "Server returned no body");
|
|
6706
6706
|
const extract3 = tar.extract();
|
|
6707
6707
|
const entries = [];
|
|
6708
|
-
const done = new Promise((
|
|
6708
|
+
const done = new Promise((resolve17, reject) => {
|
|
6709
6709
|
extract3.on("entry", (header, stream, next) => {
|
|
6710
6710
|
const chunks = [];
|
|
6711
6711
|
stream.on("data", (c) => chunks.push(c));
|
|
@@ -6716,7 +6716,7 @@ async function postForTarEntries(path4, body, retried = false) {
|
|
|
6716
6716
|
stream.on("error", reject);
|
|
6717
6717
|
stream.resume();
|
|
6718
6718
|
});
|
|
6719
|
-
extract3.on("finish", () =>
|
|
6719
|
+
extract3.on("finish", () => resolve17());
|
|
6720
6720
|
extract3.on("error", reject);
|
|
6721
6721
|
});
|
|
6722
6722
|
Readable.fromWeb(res.body).pipe(extract3);
|
|
@@ -6862,11 +6862,11 @@ async function getAccountSlug() {
|
|
|
6862
6862
|
accountSlugCache = res.data.accountSlug;
|
|
6863
6863
|
return accountSlugCache;
|
|
6864
6864
|
}
|
|
6865
|
-
async function publicPost(path4, body) {
|
|
6865
|
+
async function publicPost(path4, body, extraHeaders) {
|
|
6866
6866
|
const url = `${baseUrl()}${path4}`;
|
|
6867
6867
|
const res = await fetch(url, {
|
|
6868
6868
|
method: "POST",
|
|
6869
|
-
headers: { ...clientHeaders(), "Content-Type": "application/json" },
|
|
6869
|
+
headers: { ...clientHeaders(), "Content-Type": "application/json", ...extraHeaders },
|
|
6870
6870
|
body: JSON.stringify(body)
|
|
6871
6871
|
});
|
|
6872
6872
|
if (!res.ok) {
|
|
@@ -7176,7 +7176,7 @@ init_config();
|
|
|
7176
7176
|
init_utils();
|
|
7177
7177
|
import { readFileSync as readFileSync23 } from "fs";
|
|
7178
7178
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
7179
|
-
import { dirname as dirname13, resolve as
|
|
7179
|
+
import { dirname as dirname13, resolve as resolve16 } from "path";
|
|
7180
7180
|
|
|
7181
7181
|
// src/helpers/output.ts
|
|
7182
7182
|
var frameOpen = false;
|
|
@@ -7380,6 +7380,7 @@ gipity bug report --category <cli|deploy|template|kit|db|docs|skill|service|sand
|
|
|
7380
7380
|
- **Never include PII or user data** (emails, names, secrets, tokens, prompt/file contents) \u2014 describe the platform problem in the abstract.
|
|
7381
7381
|
- File it for *platform* problems, not your own mistakes or the app's own bugs. One report per distinct problem.
|
|
7382
7382
|
- Reports go to a review queue for the team to triage into fixes; see what you've filed with \`gipity bug list\`.
|
|
7383
|
+
- Filed one by mistake? Withdraw it yourself: \`gipity bug retract <report-id> [--reason "<why>"]\` \u2014 works while it's still queued (status new/triaged). Never file a second report asking a human to close the first.
|
|
7383
7384
|
|
|
7384
7385
|
## Tool output is complete and synchronous
|
|
7385
7386
|
|
|
@@ -7615,25 +7616,25 @@ async function hashFile(path4) {
|
|
|
7615
7616
|
const hash = createHash("sha256");
|
|
7616
7617
|
let size = 0;
|
|
7617
7618
|
const stream = createReadStream(path4);
|
|
7618
|
-
await new Promise((
|
|
7619
|
+
await new Promise((resolve17, reject) => {
|
|
7619
7620
|
stream.on("data", (chunk) => {
|
|
7620
7621
|
const buf = chunk;
|
|
7621
7622
|
hash.update(buf);
|
|
7622
7623
|
size += buf.length;
|
|
7623
7624
|
});
|
|
7624
|
-
stream.on("end", () =>
|
|
7625
|
+
stream.on("end", () => resolve17());
|
|
7625
7626
|
stream.on("error", reject);
|
|
7626
7627
|
});
|
|
7627
7628
|
return { sha256: hash.digest("hex"), size };
|
|
7628
7629
|
}
|
|
7629
7630
|
function readRange(path4, start, end) {
|
|
7630
|
-
return new Promise((
|
|
7631
|
+
return new Promise((resolve17, reject) => {
|
|
7631
7632
|
const chunks = [];
|
|
7632
7633
|
const stream = createReadStream(path4, { start, end });
|
|
7633
7634
|
stream.on("data", (c) => {
|
|
7634
7635
|
chunks.push(c);
|
|
7635
7636
|
});
|
|
7636
|
-
stream.on("end", () =>
|
|
7637
|
+
stream.on("end", () => resolve17(Buffer.concat(chunks)));
|
|
7637
7638
|
stream.on("error", reject);
|
|
7638
7639
|
});
|
|
7639
7640
|
}
|
|
@@ -8325,7 +8326,7 @@ async function fetchRemote(projectGuid) {
|
|
|
8325
8326
|
function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
8326
8327
|
const extract3 = tar2.extract();
|
|
8327
8328
|
const files = /* @__PURE__ */ new Map();
|
|
8328
|
-
return new Promise((
|
|
8329
|
+
return new Promise((resolve17, reject) => {
|
|
8329
8330
|
let settled = false;
|
|
8330
8331
|
let idle;
|
|
8331
8332
|
const arm = () => {
|
|
@@ -8361,7 +8362,7 @@ function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
|
8361
8362
|
});
|
|
8362
8363
|
entryStream.resume();
|
|
8363
8364
|
});
|
|
8364
|
-
extract3.on("finish", () => done(
|
|
8365
|
+
extract3.on("finish", () => done(resolve17, files));
|
|
8365
8366
|
extract3.on("error", (e) => done(reject, e));
|
|
8366
8367
|
stream.on("error", (e) => done(reject, e));
|
|
8367
8368
|
arm();
|
|
@@ -8585,7 +8586,8 @@ function readGipityIgnore(root) {
|
|
|
8585
8586
|
}
|
|
8586
8587
|
function effectiveIgnore(root, configIgnore) {
|
|
8587
8588
|
const base = configIgnore && configIgnore.length ? configIgnore : DEFAULT_SYNC_IGNORE;
|
|
8588
|
-
|
|
8589
|
+
const merged = [...base, GIPITY_IGNORE_FILE, ...readGipityIgnore(root)];
|
|
8590
|
+
return [...merged, ...SCRATCH_IGNORE.filter((p) => !merged.includes(p))];
|
|
8589
8591
|
}
|
|
8590
8592
|
function isLocalTreeClean() {
|
|
8591
8593
|
try {
|
|
@@ -10335,6 +10337,7 @@ init_auth();
|
|
|
10335
10337
|
import { readFileSync as readFileSync11 } from "node:fs";
|
|
10336
10338
|
init_api();
|
|
10337
10339
|
init_colors();
|
|
10340
|
+
init_auth();
|
|
10338
10341
|
init_config();
|
|
10339
10342
|
|
|
10340
10343
|
// src/page-fixtures.ts
|
|
@@ -10391,6 +10394,17 @@ function normalizeEvalResult(raw) {
|
|
|
10391
10394
|
}
|
|
10392
10395
|
return { result: raw, noValue: false };
|
|
10393
10396
|
}
|
|
10397
|
+
var EXPR_ECHO_MAX_CHARS = 120;
|
|
10398
|
+
function summarizeExpr(expr) {
|
|
10399
|
+
const lines = expr.split("\n");
|
|
10400
|
+
const meaningful = lines.filter((l) => l.trim() !== "");
|
|
10401
|
+
const oneLine = meaningful.length <= 1;
|
|
10402
|
+
if (oneLine && expr.trim().length <= EXPR_ECHO_MAX_CHARS) return expr.trim();
|
|
10403
|
+
const first = (meaningful[0] ?? "").trim();
|
|
10404
|
+
const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS - 1)}\u2026` : first;
|
|
10405
|
+
const shape = oneLine ? `(${expr.trim().length} chars)` : `(+${meaningful.length - 1} more ${meaningful.length - 1 === 1 ? "line" : "lines"}, ${expr.trim().length} chars)`;
|
|
10406
|
+
return `${head} ${shape}`;
|
|
10407
|
+
}
|
|
10394
10408
|
var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
10395
10409
|
var MAX_WAIT_MS = 3e4;
|
|
10396
10410
|
function capWaitMs(rawWait, url) {
|
|
@@ -10435,12 +10449,15 @@ function evalExecTimeoutMessage(result) {
|
|
|
10435
10449
|
return `the expression hit the ~${EVAL_EXEC_BUDGET_MS / 1e3}s in-page execution budget \u2014 the eval body (including its own await/setTimeout pauses) ran longer than that. This budget is the time the expression itself is allowed to run; it is separate from --wait, which only sleeps BEFORE the eval and cannot extend it. Split a long interactive check into several shorter 'page eval' calls (e.g. one per state to verify), keeping each body's in-page waits well under ${EVAL_EXEC_BUDGET_MS / 1e3}s.`;
|
|
10436
10450
|
}
|
|
10437
10451
|
var JS_DECOY_FLAGS = ["--js", "--javascript", "--script", "--code", "--expr", "--eval", "--exec"];
|
|
10438
|
-
var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script)").argument("<url>", "URL to load").argument("[expr]", "JavaScript to evaluate in page context (inline expression or statement body with return/await; result is JSON-serialized). Omit when using --file. Time budget: the body has ~20s to finish after page load - keep driver scripts within it.").option("--file <path>", "Read the script body from a file instead of the inline <expr> arg (mutually exclusive). Runs as an async function body, so top-level return/await work. Same ~20s post-load budget as <expr>.").option(
|
|
10452
|
+
var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script). ONE client per call - to verify realtime/presence across concurrent clients use `page test --observe` instead").argument("<url>", "URL to load").argument("[expr]", "JavaScript to evaluate in page context (inline expression or statement body with return/await; result is JSON-serialized). Omit when using --file. Time budget: the body has ~20s to finish after page load - keep driver scripts within it.").option("--file <path>", "Read the script body from a file instead of the inline <expr> arg (mutually exclusive). Runs as an async function body, so top-level return/await work. Same ~20s post-load budget as <expr>.").option(
|
|
10439
10453
|
"--fixture <path>",
|
|
10440
10454
|
"Host a local file and expose it to the eval as `fixtureUrl` (and under `fixtures` by basename) to fetch in-page. For verifying a render/parse path against a real binary (an MP3, an image) - no size limit, auto-deleted after the run. Repeat for several files (single-value so it never swallows the inline <expr>).",
|
|
10441
10455
|
(val, prev) => [...prev, val],
|
|
10442
10456
|
[]
|
|
10443
|
-
).option(
|
|
10457
|
+
).option(
|
|
10458
|
+
"--reload <expr>",
|
|
10459
|
+
"After the first eval, reload the page IN PLACE (localStorage/sessionStorage/cookies preserved) and evaluate this second expression against the post-reload DOM. One command verifies persisted state survives a reload: seed/assert state with <expr>, then assert the restored UI here."
|
|
10460
|
+
).option("--reload-file <path>", "Read the post-reload expression from a file instead of inline --reload (mutually exclusive)").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before evaluating (lets late async work settle; max 30000)", "500").option("--wait-for <selector>", "Wait until this CSS selector appears before evaluating (deterministic; replaces --wait)").option("--wait-timeout <ms>", "Max ms to wait for --wait-for before giving up", "5000").option("--auth", "Evaluate signed in as you (your Gipity account), so a page behind a Sign-in-with-Gipity login is reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai.").option("--json", "Output as JSON").action((url, exprArg, opts) => run("Page eval", async () => {
|
|
10444
10461
|
const decoy = JS_DECOY_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
10445
10462
|
if (decoy) {
|
|
10446
10463
|
pageEvalCommand.error(
|
|
@@ -10468,6 +10485,17 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
10468
10485
|
pageEvalCommand.error(`error: Cannot read file: ${opts.file}`);
|
|
10469
10486
|
}
|
|
10470
10487
|
}
|
|
10488
|
+
if (opts.reload !== void 0 && opts.reloadFile) {
|
|
10489
|
+
pageEvalCommand.error("error: Pass either --reload <expr> or --reload-file <path>, not both");
|
|
10490
|
+
}
|
|
10491
|
+
let reloadExpr = opts.reload;
|
|
10492
|
+
if (opts.reloadFile) {
|
|
10493
|
+
try {
|
|
10494
|
+
reloadExpr = readFileSync11(opts.reloadFile, "utf8");
|
|
10495
|
+
} catch {
|
|
10496
|
+
pageEvalCommand.error(`error: Cannot read file: ${opts.reloadFile}`);
|
|
10497
|
+
}
|
|
10498
|
+
}
|
|
10471
10499
|
const waitMs = capWaitMs(opts.wait, url);
|
|
10472
10500
|
const parsedTimeout = parseInt(opts.waitTimeout, 10);
|
|
10473
10501
|
const waitForTimeoutMs = Number.isFinite(parsedTimeout) && parsedTimeout >= 0 ? parsedTimeout : 5e3;
|
|
@@ -10494,29 +10522,47 @@ return (${expr});`;
|
|
|
10494
10522
|
url,
|
|
10495
10523
|
expr: sentExpr,
|
|
10496
10524
|
waitMs,
|
|
10525
|
+
reloadExpr,
|
|
10497
10526
|
waitForSelector: opts.waitFor || void 0,
|
|
10498
10527
|
waitForTimeoutMs: opts.waitFor ? waitForTimeoutMs : void 0,
|
|
10499
10528
|
auth: opts.auth || void 0
|
|
10500
10529
|
});
|
|
10501
|
-
const d = await pollEvalResult(kickoff.data.evalJobId, waitMs);
|
|
10530
|
+
const d = await pollEvalResult(kickoff.data.evalJobId, reloadExpr !== void 0 ? waitMs * 2 : waitMs);
|
|
10502
10531
|
const { result, noValue } = normalizeEvalResult(d.result);
|
|
10532
|
+
const reload = d.reloadResult !== void 0 ? normalizeEvalResult(d.reloadResult) : void 0;
|
|
10503
10533
|
const execTimeout = evalExecTimeoutMessage(d.result);
|
|
10504
10534
|
if (execTimeout) throw new Error(execTimeout);
|
|
10505
10535
|
if (opts.json) {
|
|
10506
|
-
console.log(JSON.stringify(
|
|
10536
|
+
console.log(JSON.stringify({
|
|
10537
|
+
...d,
|
|
10538
|
+
result,
|
|
10539
|
+
...reload ? { reloadResult: reload.result } : {},
|
|
10540
|
+
...noValue ? { hint: EVAL_NO_VALUE_HINT } : {}
|
|
10541
|
+
}));
|
|
10507
10542
|
return;
|
|
10508
10543
|
}
|
|
10509
10544
|
console.log(`${brand("Eval")} ${bold(d.url || url)}`);
|
|
10510
10545
|
if (d.navigationIncomplete) {
|
|
10511
10546
|
console.log(`${warning("\u26A0 Navigation incomplete:")} ${d.note || "page did not reach full load"}`);
|
|
10512
10547
|
}
|
|
10548
|
+
if (d.auth?.requested) {
|
|
10549
|
+
const who = getAuth()?.email;
|
|
10550
|
+
console.log(d.auth.established ? `${muted("Auth:")} ${success("session established")}${who ? muted(` as ${who}`) : ""} ${muted("(what the page renders with it is app-defined)")}` : `${warning("Auth: session NOT established")}${d.auth.detail ? ` \u2014 ${d.auth.detail}` : ""} ${muted("(this is the anonymous view)")}`);
|
|
10551
|
+
}
|
|
10513
10552
|
if (hosted.length) console.log(`${muted("Fixtures:")} ${hosted.map((h) => h.name).join(", ")}`);
|
|
10514
|
-
console.log(opts.file ? `${muted("Script:")} ${opts.file}` : `${muted("Expression:")} ${expr}`);
|
|
10553
|
+
console.log(opts.file ? `${muted("Script:")} ${opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
10515
10554
|
console.log(`
|
|
10516
10555
|
${result.trim() ? result : muted("(empty result)")}`);
|
|
10517
10556
|
if (noValue) console.log(muted(`
|
|
10518
10557
|
${EVAL_NO_VALUE_HINT}`));
|
|
10519
10558
|
if (d.truncated) console.log(muted("\n(result truncated to fit context - narrow the expression for the full value)"));
|
|
10559
|
+
if (reload) {
|
|
10560
|
+
console.log(`
|
|
10561
|
+
${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserved)")}`);
|
|
10562
|
+
console.log(reload.result.trim() ? reload.result : muted("(empty result)"));
|
|
10563
|
+
if (reload.noValue) console.log(muted(EVAL_NO_VALUE_HINT));
|
|
10564
|
+
if (d.reloadTruncated) console.log(muted("(reload result truncated to fit context - narrow the expression for the full value)"));
|
|
10565
|
+
}
|
|
10520
10566
|
} finally {
|
|
10521
10567
|
for (const h of hosted) {
|
|
10522
10568
|
try {
|
|
@@ -10538,6 +10584,16 @@ Examples:
|
|
|
10538
10584
|
# fetch-able 'fixtureUrl', runs the eval, then deletes the hosted copy:
|
|
10539
10585
|
gipity page eval "https://dev.gipity.ai/me/app/" --fixture ./sample.mp3 \\
|
|
10540
10586
|
"(async()=>{ const b = await fetch(fixtureUrl).then(r=>r.arrayBuffer()); return window.App.parseId3(b); })()"
|
|
10587
|
+
# Verify persisted state survives a reload (localStorage/sessionStorage kept):
|
|
10588
|
+
# run <expr>, reload the page in place, then run the --reload expression:
|
|
10589
|
+
gipity page eval "https://dev.gipity.ai/me/app/" \\
|
|
10590
|
+
"localStorage.setItem('todo','milk'); document.title" \\
|
|
10591
|
+
--reload "({ restored: localStorage.getItem('todo'), heading: document.querySelector('h1')?.textContent })"
|
|
10592
|
+
|
|
10593
|
+
Module resolution: dynamic import() specifiers starting with ./ or ../ resolve
|
|
10594
|
+
against the PAGE URL, so import('./packages/i18n/index.js') loads the app's own
|
|
10595
|
+
module without hand-building the deployed /account/project/ path. Absolute paths
|
|
10596
|
+
and full URLs pass through unchanged.
|
|
10541
10597
|
|
|
10542
10598
|
The eval body runs under a ~20s in-page execution budget (its own await/setTimeout
|
|
10543
10599
|
pauses count; --wait only sleeps BEFORE the eval and does not extend it). For a long
|
|
@@ -10553,6 +10609,7 @@ Testing realtime/shared state across clients?
|
|
|
10553
10609
|
// src/commands/page-screenshot.ts
|
|
10554
10610
|
init_api();
|
|
10555
10611
|
init_config();
|
|
10612
|
+
init_auth();
|
|
10556
10613
|
init_colors();
|
|
10557
10614
|
init_utils();
|
|
10558
10615
|
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
@@ -10572,6 +10629,15 @@ function label(text) {
|
|
|
10572
10629
|
function fmtMs(ms) {
|
|
10573
10630
|
return ms >= 1e3 ? `${(ms / 1e3).toFixed(1)}s` : `${ms}ms`;
|
|
10574
10631
|
}
|
|
10632
|
+
function printAuthLine(auth) {
|
|
10633
|
+
if (!auth?.requested) return;
|
|
10634
|
+
const who = getAuth()?.email;
|
|
10635
|
+
console.log(auth.established ? `${label("Auth")} ${success("session established")}${who ? muted(` as ${who}`) : ""} ${muted("(what the page renders with it is app-defined)")}` : `${warning("Auth: session NOT established")}${auth.detail ? ` \u2014 ${auth.detail}` : ""} ${muted("(this is the anonymous view)")}`);
|
|
10636
|
+
}
|
|
10637
|
+
function printActionErrorLine(actionError) {
|
|
10638
|
+
if (!actionError) return;
|
|
10639
|
+
console.log(`${warning("\u26A0 --action failed:")} ${actionError} ${muted("(this image shows the page BEFORE the action ran)")}`);
|
|
10640
|
+
}
|
|
10575
10641
|
function fmtPerformance(p) {
|
|
10576
10642
|
const parts = [
|
|
10577
10643
|
`TTFB ${fmtMs(p.ttfb)}`,
|
|
@@ -10653,7 +10719,14 @@ function splitCsv(values) {
|
|
|
10653
10719
|
function appendOption(value, previous = []) {
|
|
10654
10720
|
return [...previous, value];
|
|
10655
10721
|
}
|
|
10656
|
-
var
|
|
10722
|
+
var ACTION_DECOY_FLAGS = ["--eval", "--js", "--javascript", "--script", "--code", "--exec"];
|
|
10723
|
+
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").option("--post-load-delay <ms>", "Delay after DOMContentLoaded before capture, in ms (default: 1000)").option("--action <js>", `Run JS in the page before capturing \u2014 e.g. click a button to enter a state ("document.getElementById('play').click()"). Runs as an async function body, so const/await and app-relative import('./\u2026') work. Runs after the post-load delay, then settles again before the shot. If it throws, the capture still happens and the failure is reported.`).option("--full", "Capture the full scrollable page (default: viewport only). Scrolls the page through first so scroll-reveal/fade-in-on-scroll (IntersectionObserver) sections render into the shot instead of capturing blank.").option("-o, --output <file>", "Output path (single viewport only; default .gipity/screenshots/ss-<host>-<timestamp>.png)").option("--device <names>", `Device preset(s): ${Object.keys(DEVICE_PRESETS).join(", ")} (comma-separated or repeat flag). mobile/tablet emulate a real touch device \u2014 touch events, mobile user-agent, DPR \u2014 so touch-gated mobile UI actually renders.`, appendOption, []).option("--viewport <dims>", "Raw viewport(s): WxH or WxH@dpr (comma-separated or repeat flag)", appendOption, []).option("--no-reload-between", "Skip reload between viewports (faster, lower fidelity - only safe for static pages)").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps render headlessly (audio is a built-in tone, not real speech)").option("--auth", "Capture the page signed in as you (your Gipity account), so UI behind a Sign-in-with-Gipity login is shown. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai.").option("--json", "Output JSON metadata instead of a friendly summary").addOption(new Option("--wait <ms>", "Alias for --post-load-delay").hideHelp()).addOption(new Option("--full-page", "Alias for --full").hideHelp()).action((url, opts) => run("Page screenshot", async () => {
|
|
10724
|
+
const decoy = ACTION_DECOY_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
10725
|
+
if (decoy) {
|
|
10726
|
+
pageScreenshotCommand.error(
|
|
10727
|
+
`error: ${decoy} is not a flag on screenshot \u2014 use --action "<js>" to run JavaScript in the page before the capture, e.g. gipity page screenshot "<url>" --action "document.getElementById('play').click()"`
|
|
10728
|
+
);
|
|
10729
|
+
}
|
|
10657
10730
|
const delayRaw = opts.postLoadDelay ?? opts.wait ?? "1000";
|
|
10658
10731
|
const postLoadDelayMs = delayRaw !== void 0 ? parseInt(String(delayRaw), 10) : void 0;
|
|
10659
10732
|
if (postLoadDelayMs !== void 0 && (!Number.isFinite(postLoadDelayMs) || postLoadDelayMs < 0)) {
|
|
@@ -10707,7 +10780,8 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
10707
10780
|
title: meta.title,
|
|
10708
10781
|
final_url: meta.finalUrl,
|
|
10709
10782
|
status: meta.status,
|
|
10710
|
-
performance: meta.performance
|
|
10783
|
+
performance: meta.performance,
|
|
10784
|
+
...meta.auth ? { auth: meta.auth } : {}
|
|
10711
10785
|
},
|
|
10712
10786
|
screenshots: meta.screenshots.map((s, i) => ({
|
|
10713
10787
|
file: savedFiles[i],
|
|
@@ -10734,6 +10808,8 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
10734
10808
|
if (meta.finalUrl) console.log(`${label("Web page URL")} ${meta.finalUrl}`);
|
|
10735
10809
|
if (meta.status != null) console.log(`${label("Web page status")} ${meta.status}`);
|
|
10736
10810
|
if (meta.performance) console.log(`${label("Web page perf")} ${fmtPerformance(meta.performance)}`);
|
|
10811
|
+
printAuthLine(meta.auth);
|
|
10812
|
+
printActionErrorLine(meta.actionError);
|
|
10737
10813
|
if (s.viewport.device) console.log(`${label("Emulated device")} ${s.viewport.device} ${muted("(touch events, mobile user-agent)")}`);
|
|
10738
10814
|
const sizePart = formatSize(s.screenshotSizeBytes) + (meta.full ? " (full page)" : "");
|
|
10739
10815
|
console.log(`${label("Screenshot size")} ${sizePart}`);
|
|
@@ -10746,6 +10822,8 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
10746
10822
|
if (meta.finalUrl) console.log(`${label("Web page URL")} ${meta.finalUrl}`);
|
|
10747
10823
|
if (meta.status != null) console.log(`${label("Web page status")} ${meta.status}`);
|
|
10748
10824
|
if (meta.performance) console.log(`${label("Web page perf")} ${fmtPerformance(meta.performance)}`);
|
|
10825
|
+
printAuthLine(meta.auth);
|
|
10826
|
+
printActionErrorLine(meta.actionError);
|
|
10749
10827
|
for (let i = 0; i < meta.screenshots.length; i++) {
|
|
10750
10828
|
const s = meta.screenshots[i];
|
|
10751
10829
|
const dims = `${s.viewport.width}\xD7${s.viewport.height}${s.viewport.deviceScaleFactor > 1 ? ` @${s.viewport.deviceScaleFactor}x` : ""}`;
|
|
@@ -10758,6 +10836,7 @@ ${brand("@ " + dims)}${deviceTag}`);
|
|
|
10758
10836
|
console.log(`${label("Screenshot file")} ${success(savedFiles[i])}`);
|
|
10759
10837
|
}
|
|
10760
10838
|
}));
|
|
10839
|
+
for (const f of ACTION_DECOY_FLAGS) pageScreenshotCommand.addOption(new Option(`${f} <value>`).hideHelp());
|
|
10761
10840
|
pageScreenshotCommand.addHelpText("after", `
|
|
10762
10841
|
Examples:
|
|
10763
10842
|
gipity page screenshot "https://dev.gipity.ai/me/app/"
|
|
@@ -10794,7 +10873,7 @@ function shortUrl(url, truncate = true, maxLen = 100) {
|
|
|
10794
10873
|
const tailLen = Math.floor(keep / 2);
|
|
10795
10874
|
return result.slice(0, headLen) + "\u2026" + result.slice(-tailLen);
|
|
10796
10875
|
}
|
|
10797
|
-
var pageInspectCommand = new Command("inspect").description("Inspect a web page (console, failed resources, timing, layout overflow)").argument("<url>", "URL to inspect").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before capturing (lets late async/LCP work settle; max 30000)", "500").option("--wait-for <selector>", "Wait until this CSS selector appears before capturing (deterministic; replaces --wait)").option("--wait-timeout <ms>", "Max ms to wait for --wait-for before giving up", "5000").option("--json", "Output as JSON").option("--no-reprobe", "Skip the automatic second probe that filters one-time transient console errors - roughly halves inspect time on a page with any console error, at the cost of possibly reporting a cold-load transient as real").option("--no-truncate", "Show full URLs instead of truncating long ones with middle-ellipsis").option("--all", "Include render-blocking, large resources, oversized images, overflow culprits, and LCP detail").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps run headlessly (audio is a built-in tone, not real speech)").option("--device <name>", `Inspect as a real touch device: ${TOUCH_DEVICES.join(", ")}. Emulates touch events, mobile user-agent and DPR, so a touch-gated mobile layout is what gets inspected.`).option("--auth", "Load the page signed in as you (your Gipity account), so pages behind a Sign-in-with-Gipity login are reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai.").addOption(new Option("--screenshot [path]", "Capture a screenshot").hideHelp()).action((url, opts) => {
|
|
10876
|
+
var pageInspectCommand = new Command("inspect").description("Inspect a web page (console, failed resources, timing, layout overflow). ONE passive load - to verify realtime/presence across concurrent clients use `page test --observe`").argument("<url>", "URL to inspect").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before capturing (lets late async/LCP work settle; max 30000)", "500").option("--wait-for <selector>", "Wait until this CSS selector appears before capturing (deterministic; replaces --wait)").option("--wait-timeout <ms>", "Max ms to wait for --wait-for before giving up", "5000").option("--json", "Output as JSON").option("--no-reprobe", "Skip the automatic second probe that filters one-time transient console errors - roughly halves inspect time on a page with any console error, at the cost of possibly reporting a cold-load transient as real").option("--no-truncate", "Show full URLs instead of truncating long ones with middle-ellipsis").option("--all", "Include render-blocking, large resources, oversized images, overflow culprits, and LCP detail").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps run headlessly (audio is a built-in tone, not real speech)").option("--device <name>", `Inspect as a real touch device: ${TOUCH_DEVICES.join(", ")}. Emulates touch events, mobile user-agent and DPR, so a touch-gated mobile layout is what gets inspected.`).option("--auth", "Load the page signed in as you (your Gipity account), so pages behind a Sign-in-with-Gipity login are reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai.").addOption(new Option("--screenshot [path]", "Capture a screenshot").hideHelp()).action((url, opts) => {
|
|
10798
10877
|
if (opts.screenshot !== void 0) {
|
|
10799
10878
|
console.error(error("page inspect does not capture screenshots. Use page screenshot:"));
|
|
10800
10879
|
console.error(` gipity page screenshot ${url}${typeof opts.screenshot === "string" ? ` -o ${opts.screenshot}` : ""}`);
|
|
@@ -11223,7 +11302,7 @@ memoryCommand.command("delete <topic>").description("Delete a topic").option("--
|
|
|
11223
11302
|
init_api();
|
|
11224
11303
|
init_config();
|
|
11225
11304
|
import { readFileSync as readFileSync12, existsSync as existsSync9, statSync as statSync6 } from "fs";
|
|
11226
|
-
import { dirname as dirname8, extname as extname4, relative as relative3 } from "path";
|
|
11305
|
+
import { dirname as dirname8, extname as extname4, relative as relative3, resolve as resolve11 } from "path";
|
|
11227
11306
|
init_colors();
|
|
11228
11307
|
var LANG_MAP = {
|
|
11229
11308
|
js: "javascript",
|
|
@@ -11345,6 +11424,11 @@ sandboxCommand.command("run [args...]").description("Run code").option("--langua
|
|
|
11345
11424
|
"--input <path>",
|
|
11346
11425
|
"Narrow to specific project files instead of auto-mirroring the whole tree (repeatable). Use this only for >1 GB projects or when you want surgical control.",
|
|
11347
11426
|
(v, prev) => [...prev ?? [], v]
|
|
11427
|
+
).option(
|
|
11428
|
+
"--no-sync-output <glob>",
|
|
11429
|
+
'Do not persist run outputs matching this glob back to the project (repeatable). For byproducts you want to inspect once but never keep, e.g. --no-sync-output "docs/preview*". Supports *, **, and dir/ prefixes.',
|
|
11430
|
+
(v, prev) => [...prev, v],
|
|
11431
|
+
[]
|
|
11348
11432
|
).option("--json", "Output as JSON").addHelpText("after", `
|
|
11349
11433
|
By default the whole project is auto-mirrored into /work/ (up to 1 GB) -
|
|
11350
11434
|
so your code can reference project files by their relative path, and any
|
|
@@ -11416,6 +11500,11 @@ GCC/Rust).
|
|
|
11416
11500
|
}
|
|
11417
11501
|
}
|
|
11418
11502
|
const language = resolveLanguage({ langFromInterp, langOpt: opts.language, filePath, inlineCode });
|
|
11503
|
+
const noSyncOutput = opts.syncOutput ?? [];
|
|
11504
|
+
if (noSyncOutput.some((g) => !g.trim())) {
|
|
11505
|
+
console.error(error('--no-sync-output requires a non-empty glob (e.g. --no-sync-output "docs/preview*")'));
|
|
11506
|
+
process.exit(1);
|
|
11507
|
+
}
|
|
11419
11508
|
const { config } = await resolveProjectContext();
|
|
11420
11509
|
const timeout = parseInt(opts.timeout, 10);
|
|
11421
11510
|
const cwd = resolveRelativeCwd();
|
|
@@ -11436,7 +11525,11 @@ GCC/Rust).
|
|
|
11436
11525
|
language,
|
|
11437
11526
|
timeout: isNaN(timeout) ? 30 : timeout,
|
|
11438
11527
|
input_files: opts.input,
|
|
11439
|
-
cwd
|
|
11528
|
+
cwd,
|
|
11529
|
+
// The filter must run SERVER-side (in the output extractor): skipping
|
|
11530
|
+
// only in the CLI would still write the files into project storage and
|
|
11531
|
+
// make every later sync propose deleting them.
|
|
11532
|
+
noSyncOutput: noSyncOutput.length ? noSyncOutput : void 0
|
|
11440
11533
|
});
|
|
11441
11534
|
const res = opts.json ? await doRun() : await withSpinner("Running in sandbox\u2026", doRun, { done: null });
|
|
11442
11535
|
const pulledLocal = !!(res.data.outputFiles?.length && getConfigPath());
|
|
@@ -11456,10 +11549,27 @@ GCC/Rust).
|
|
|
11456
11549
|
if (res.data.stdout) console.log(res.data.stdout);
|
|
11457
11550
|
if (res.data.stderr) console.error(res.data.stderr);
|
|
11458
11551
|
if (res.data.timedOut) console.error(`[Timed out after ${res.data.durationMs}ms]`);
|
|
11552
|
+
if (res.data.stdoutTruncated || res.data.stderrTruncated) {
|
|
11553
|
+
console.error(dim("Note: output truncated at 256 KB. Write large results to a file instead of printing them."));
|
|
11554
|
+
}
|
|
11459
11555
|
if (res.data.outputFiles && res.data.outputFiles.length > 0) {
|
|
11460
|
-
|
|
11461
|
-
|
|
11462
|
-
|
|
11556
|
+
const projectRoot = getProjectRoot();
|
|
11557
|
+
const landed = (f) => pulledLocal && !!projectRoot && existsSync9(resolve11(projectRoot, f));
|
|
11558
|
+
const onDisk = res.data.outputFiles.filter(landed);
|
|
11559
|
+
const notOnDisk = res.data.outputFiles.filter((f) => !landed(f));
|
|
11560
|
+
if (onDisk.length > 0) {
|
|
11561
|
+
console.log("\nOutput files synced to this directory:");
|
|
11562
|
+
for (const f of onDisk) console.log(`${f}`);
|
|
11563
|
+
}
|
|
11564
|
+
if (notOnDisk.length > 0) {
|
|
11565
|
+
console.log("\nOutput files saved to project:");
|
|
11566
|
+
for (const f of notOnDisk) console.log(`${f}`);
|
|
11567
|
+
if (pulledLocal) console.log(dim("Not pulled locally - run 'gipity sync' to fetch them."));
|
|
11568
|
+
}
|
|
11569
|
+
}
|
|
11570
|
+
if (res.data.skippedOutputFiles && res.data.skippedOutputFiles.length > 0) {
|
|
11571
|
+
console.log(dim("\nNot persisted (--no-sync-output):"));
|
|
11572
|
+
for (const f of res.data.skippedOutputFiles) console.log(dim(`${f}`));
|
|
11463
11573
|
}
|
|
11464
11574
|
if (res.data.exitCode !== 0) {
|
|
11465
11575
|
process.exit(res.data.exitCode);
|
|
@@ -11858,8 +11968,24 @@ function formatRunLine(r) {
|
|
|
11858
11968
|
const statusColor = r.status === "completed" ? success : r.status === "failed" ? error : muted;
|
|
11859
11969
|
return `${muted(r.short_guid)} ${statusColor(r.status)} ${dur} ${runTokens(r)} tokens ${muted(fmtTime(r.started_at))}`;
|
|
11860
11970
|
}
|
|
11971
|
+
function printStepRuns(steps, emptyNote) {
|
|
11972
|
+
if (steps.length === 0) {
|
|
11973
|
+
console.log(` ${muted(emptyNote)}`);
|
|
11974
|
+
return;
|
|
11975
|
+
}
|
|
11976
|
+
for (const s of steps) {
|
|
11977
|
+
const statusColor = s.status === "completed" ? success : s.status === "failed" ? error : muted;
|
|
11978
|
+
const model = s.model_used ? ` ${muted(`[${s.model_used}]`)}` : "";
|
|
11979
|
+
const name = s.step_name ? `${bold(s.step_name)} ` : "";
|
|
11980
|
+
console.log(` ${s.step_order}. ${name}${statusColor(s.status)} ${s.tokens_used ?? 0} tokens${model}`);
|
|
11981
|
+
if (s.error_message) console.log(` ${error(s.error_message)}`);
|
|
11982
|
+
if (s.output_json !== null && s.output_json !== void 0) {
|
|
11983
|
+
console.log(JSON.stringify(s.output_json, null, 2).split("\n").map((l) => ` ${l}`).join("\n"));
|
|
11984
|
+
}
|
|
11985
|
+
}
|
|
11986
|
+
}
|
|
11861
11987
|
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
11862
|
-
var sleep2 = (ms) => new Promise((
|
|
11988
|
+
var sleep2 = (ms) => new Promise((resolve17) => setTimeout(resolve17, ms));
|
|
11863
11989
|
async function waitForRun(wfGuid, prevGuid, timeoutSec) {
|
|
11864
11990
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
11865
11991
|
let runGuid;
|
|
@@ -11942,6 +12068,7 @@ workflowCommand.command("run <name>").description("Trigger a workflow (add --wai
|
|
|
11942
12068
|
} else {
|
|
11943
12069
|
console.log(formatRunLine(r));
|
|
11944
12070
|
if (r.error_message) console.log(` ${error(r.error_message)}`);
|
|
12071
|
+
printStepRuns(r.step_runs ?? [], "(no steps recorded)");
|
|
11945
12072
|
}
|
|
11946
12073
|
if (r.status !== "completed") process.exit(1);
|
|
11947
12074
|
}));
|
|
@@ -11956,21 +12083,7 @@ workflowCommand.command("runs <name> [runGuid]").description("List recent runs,
|
|
|
11956
12083
|
return;
|
|
11957
12084
|
}
|
|
11958
12085
|
console.log(formatRunLine(r));
|
|
11959
|
-
|
|
11960
|
-
if (steps.length === 0) {
|
|
11961
|
-
console.log(" (no steps recorded)");
|
|
11962
|
-
return;
|
|
11963
|
-
}
|
|
11964
|
-
for (const s of steps) {
|
|
11965
|
-
const statusColor = s.status === "completed" ? success : s.status === "failed" ? error : muted;
|
|
11966
|
-
const model = s.model_used ? ` ${muted(`[${s.model_used}]`)}` : "";
|
|
11967
|
-
console.log(` ${s.step_order}. ${statusColor(s.status)} ${s.tokens_used ?? 0} tokens${model}`);
|
|
11968
|
-
if (s.error_message) console.log(` ${error(s.error_message)}`);
|
|
11969
|
-
if (s.output_json !== null && s.output_json !== void 0) {
|
|
11970
|
-
const pretty = JSON.stringify(s.output_json, null, 2).split("\n").map((l) => ` ${l}`).join("\n");
|
|
11971
|
-
console.log(pretty);
|
|
11972
|
-
}
|
|
11973
|
-
}
|
|
12086
|
+
printStepRuns(r.step_runs ?? [], "(no steps recorded)");
|
|
11974
12087
|
return;
|
|
11975
12088
|
}
|
|
11976
12089
|
const res = await get(`/workflows/${wf.short_guid}/runs`);
|
|
@@ -12465,7 +12578,7 @@ storageCommand.command("retention").description("View or adjust your file versio
|
|
|
12465
12578
|
init_platform();
|
|
12466
12579
|
init_auth();
|
|
12467
12580
|
init_api();
|
|
12468
|
-
import { join as join14, dirname as dirname10, resolve as
|
|
12581
|
+
import { join as join14, dirname as dirname10, resolve as resolve13, basename as basename4, sep as sep4 } from "path";
|
|
12469
12582
|
import { existsSync as existsSync11, mkdirSync as mkdirSync9, readFileSync as readFileSync15, writeFileSync as writeFileSync9, renameSync as renameSync2, readdirSync as readdirSync5, statSync as statSync7 } from "fs";
|
|
12470
12583
|
import { homedir as homedir10 } from "os";
|
|
12471
12584
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
@@ -12780,7 +12893,7 @@ function windowsTaskPlan(cliPath) {
|
|
|
12780
12893
|
init_platform();
|
|
12781
12894
|
init_api();
|
|
12782
12895
|
import { hostname as hostname3, userInfo, platform as osPlatform2 } from "os";
|
|
12783
|
-
import { resolve as
|
|
12896
|
+
import { resolve as resolve12, dirname as dirname9 } from "path";
|
|
12784
12897
|
import { mkdirSync as mkdirSync8, writeFileSync as writeFileSync8 } from "fs";
|
|
12785
12898
|
|
|
12786
12899
|
// src/relay/machine-id.ts
|
|
@@ -12842,7 +12955,7 @@ function friendlyDeviceName() {
|
|
|
12842
12955
|
return host || `My ${kind}`;
|
|
12843
12956
|
}
|
|
12844
12957
|
function resolveCliPath() {
|
|
12845
|
-
return
|
|
12958
|
+
return resolve12(process.argv[1] ?? "gipity");
|
|
12846
12959
|
}
|
|
12847
12960
|
async function pairDevice(opts = {}) {
|
|
12848
12961
|
const existing = getDevice();
|
|
@@ -13558,7 +13671,7 @@ var claudeCommand = new Command("claude").description("Set up and run Claude Cod
|
|
|
13558
13671
|
let existing = getConfig();
|
|
13559
13672
|
let forceAdoptCwd = false;
|
|
13560
13673
|
if (existing && !opts.newProject && !opts.project && !nonInteractive) {
|
|
13561
|
-
const cwdHasConfig = existsSync11(
|
|
13674
|
+
const cwdHasConfig = existsSync11(resolve13(process.cwd(), ".gipity.json"));
|
|
13562
13675
|
if (!cwdHasConfig && isLikelyEmpty(process.cwd())) {
|
|
13563
13676
|
const ancestorRoot = dirname10(getConfigPath());
|
|
13564
13677
|
console.log(` ${bold("You are inside an existing Gipity project.")}
|
|
@@ -14231,6 +14344,10 @@ var addCommand = new Command("add").description("Add a template or kit").argumen
|
|
|
14231
14344
|
force: opts.force
|
|
14232
14345
|
};
|
|
14233
14346
|
}
|
|
14347
|
+
if (!opts.json && !process.stdout.isTTY) {
|
|
14348
|
+
const isKit = KITS.some((k) => k.key === name);
|
|
14349
|
+
console.error(muted(isKit ? "Installing kit (server-side install pipeline)..." : "Installing (server writes files + generates favicons; first add for a title can take ~10s)..."));
|
|
14350
|
+
}
|
|
14234
14351
|
const doAdd = () => post(`/projects/${config.projectGuid}/add`, body);
|
|
14235
14352
|
const res = opts.json ? await doAdd() : await withSpinner("Installing\u2026", doAdd, { done: null });
|
|
14236
14353
|
const syncResult = await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
@@ -14551,7 +14668,7 @@ logsCommand.command("fn <name>").description("Show function logs").option("--lim
|
|
|
14551
14668
|
for (const log3 of res.data) {
|
|
14552
14669
|
const time = new Date(log3.created_at).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit" });
|
|
14553
14670
|
const dur = log3.duration_ms !== null ? `${log3.duration_ms}ms`.padEnd(8) : "".padEnd(8);
|
|
14554
|
-
const statusColor = log3.status === "
|
|
14671
|
+
const statusColor = log3.status === "ok" ? success : log3.status === "error" ? error : warning;
|
|
14555
14672
|
const status = statusColor(log3.status.padEnd(8));
|
|
14556
14673
|
const trigger = muted(log3.trigger_type.padEnd(8));
|
|
14557
14674
|
const err = log3.error_message ? ` ${error(`"${log3.error_message}"`)}` : "";
|
|
@@ -15015,7 +15132,7 @@ ${error(`\u2717 ${failed.length} of ${results.length} file(s) missing or wrong-t
|
|
|
15015
15132
|
}));
|
|
15016
15133
|
|
|
15017
15134
|
// src/commands/page.ts
|
|
15018
|
-
var pageCommand = new Command("page").description("Inspect web pages").addCommand(pageInspectCommand).addCommand(pageEvalCommand).addCommand(pageScreenshotCommand).addCommand(pageTestCommand).addCommand(pageFetchCommand);
|
|
15135
|
+
var pageCommand = new Command("page").description("Inspect, drive, and test web pages in a real browser (page test = N concurrent clients for realtime/presence verification)").addCommand(pageInspectCommand).addCommand(pageEvalCommand).addCommand(pageScreenshotCommand).addCommand(pageTestCommand).addCommand(pageFetchCommand);
|
|
15019
15136
|
pageCommand.action(() => {
|
|
15020
15137
|
pageCommand.help();
|
|
15021
15138
|
});
|
|
@@ -15142,14 +15259,25 @@ ${error(`error: ${log3.error_message}`)}`;
|
|
|
15142
15259
|
return line;
|
|
15143
15260
|
});
|
|
15144
15261
|
}));
|
|
15145
|
-
|
|
15262
|
+
async function callAnon(projectGuid, name, body) {
|
|
15263
|
+
let appToken;
|
|
15264
|
+
try {
|
|
15265
|
+
const minted = await publicPost("/api/token", { app: projectGuid });
|
|
15266
|
+
appToken = minted.data.token;
|
|
15267
|
+
} catch {
|
|
15268
|
+
}
|
|
15269
|
+
return publicPost(
|
|
15270
|
+
`/api/${projectGuid}/fn/${encodeURIComponent(name)}`,
|
|
15271
|
+
body,
|
|
15272
|
+
appToken ? { "X-App-Token": appToken } : void 0
|
|
15273
|
+
);
|
|
15274
|
+
}
|
|
15275
|
+
fnCommand.command("call <name> [body]").description("Call a function").option("--data <json>", "JSON request body").option("--anon", "Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account").option("--field <path>", "Print only this field of the result (dot path, e.g. items.0.short_guid)").option("--json", "Output as JSON").action((name, bodyArg, opts) => run("Call", async () => {
|
|
15146
15276
|
const config = requireConfig();
|
|
15147
15277
|
const raw = bodyArg || opts.data || "{}";
|
|
15148
15278
|
const body = JSON.parse(raw);
|
|
15149
|
-
const
|
|
15150
|
-
|
|
15151
|
-
body
|
|
15152
|
-
);
|
|
15279
|
+
const path4 = `/api/${config.projectGuid}/fn/${encodeURIComponent(name)}`;
|
|
15280
|
+
const res = opts.anon ? await callAnon(config.projectGuid, name, body) : await post(path4, body);
|
|
15153
15281
|
if (opts.field) {
|
|
15154
15282
|
emitField(res.data, opts.field);
|
|
15155
15283
|
return;
|
|
@@ -15319,7 +15447,9 @@ so the team can triage it into a fix.
|
|
|
15319
15447
|
|
|
15320
15448
|
Categories: ${CATEGORIES.join(", ")}
|
|
15321
15449
|
Severity: ${SEVERITY_HINT}
|
|
15322
|
-
Never include PII or user data \u2014 describe the platform problem in the abstract
|
|
15450
|
+
Never include PII or user data \u2014 describe the platform problem in the abstract.
|
|
15451
|
+
Filed one by mistake? Withdraw it yourself with \`gipity bug retract <id>\`
|
|
15452
|
+
(works until a human picks it up) \u2014 don't file a second report asking for a close.`
|
|
15323
15453
|
);
|
|
15324
15454
|
bugCommand.command("report").description("File a bug / friction report about the Gipity platform").requiredOption("--category <cat>", `One of: ${CATEGORIES.join(", ")}`).requiredOption("--severity <sev>", SEVERITY_HINT).requiredOption("--summary <text>", "One line, 7 words max. No PII/user data.").option("--detail <text>", "Succinct: what you did, what failed, the workaround. No PII/user data.").option("--project <guid-or-slug>", "Attribute to a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Bug report", async () => {
|
|
15325
15455
|
const category = String(opts.category).toLowerCase();
|
|
@@ -15340,6 +15470,24 @@ bugCommand.command("report").description("File a bug / friction report about the
|
|
|
15340
15470
|
}
|
|
15341
15471
|
console.log(success(`\u2713 Bug report filed (${res.data.report_guid}) \u2014 queued for triage.`));
|
|
15342
15472
|
}));
|
|
15473
|
+
bugCommand.command("retract <id>").description("Withdraw a bug report you filed (e.g. it was your own mistake)").option("--reason <text>", "Short note for triage on why you are retracting").option("--project <guid-or-slug>", "Resolve auth against a specific project instead of cwd / Home").option("--json", "Output raw JSON").addHelpText(
|
|
15474
|
+
"after",
|
|
15475
|
+
`
|
|
15476
|
+
Only works on your own reports, and only while they are still queued
|
|
15477
|
+
(status new/triaged). Once a report is filed/fixed/dismissed, a human
|
|
15478
|
+
owns closing it out. Find report ids with \`gipity bug list\`.`
|
|
15479
|
+
).action((id, opts) => run("Bug report", async () => {
|
|
15480
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
15481
|
+
const res = await post(
|
|
15482
|
+
`/api/${config.projectGuid}/services/bug-report/retract`,
|
|
15483
|
+
{ report_guid: id, reason: opts.reason }
|
|
15484
|
+
);
|
|
15485
|
+
if (opts.json) {
|
|
15486
|
+
console.log(JSON.stringify(res.data));
|
|
15487
|
+
return;
|
|
15488
|
+
}
|
|
15489
|
+
console.log(success(`\u2713 Bug report ${res.data.report_guid} retracted \u2014 it is out of the triage queue.`));
|
|
15490
|
+
}));
|
|
15343
15491
|
bugCommand.command("list").description("List the bug / friction reports you have filed").option("--project <guid-or-slug>", "Resolve auth against a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Bug report", async () => {
|
|
15344
15492
|
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
15345
15493
|
const res = await get(
|
|
@@ -15597,9 +15745,9 @@ jobCommand.command("cancel <runGuid>").description("Cancel a queued or running j
|
|
|
15597
15745
|
}));
|
|
15598
15746
|
jobCommand.command("run-local <name>").description("Run the job in a local Docker container against your local filesystem (no platform round-trip)").option("--image <image>", "Docker image to run inside", "easyclaw/sandbox:latest").option("--no-deps", "Skip pip/npm install even if a deps file is present").action(async (name, opts) => {
|
|
15599
15747
|
const { existsSync: existsSync20, statSync: statSync9 } = await import("node:fs");
|
|
15600
|
-
const { resolve:
|
|
15748
|
+
const { resolve: resolve17, join: join22 } = await import("node:path");
|
|
15601
15749
|
const { spawnCommand: spawnCommand2, spawnSyncCommand: spawnSyncCommand2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
15602
|
-
const jobDir =
|
|
15750
|
+
const jobDir = resolve17(process.cwd(), "jobs", name);
|
|
15603
15751
|
if (!existsSync20(jobDir) || !statSync9(jobDir).isDirectory()) {
|
|
15604
15752
|
console.error(error(`jobs/${name}/ not found in current directory`));
|
|
15605
15753
|
process.exit(1);
|
|
@@ -15621,7 +15769,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
15621
15769
|
},
|
|
15622
15770
|
{ file: "main.sh", runtime: "bash", entry: "bash" }
|
|
15623
15771
|
];
|
|
15624
|
-
const picked = variants.find((v) => existsSync20(
|
|
15772
|
+
const picked = variants.find((v) => existsSync20(join22(jobDir, v.file)));
|
|
15625
15773
|
if (!picked) {
|
|
15626
15774
|
console.error(error(`No main.py / main.js / main.sh found under jobs/${name}/`));
|
|
15627
15775
|
process.exit(1);
|
|
@@ -15646,7 +15794,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
15646
15794
|
"-v",
|
|
15647
15795
|
`${jobDir}:/work`
|
|
15648
15796
|
];
|
|
15649
|
-
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync20(
|
|
15797
|
+
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync20(join22(jobDir, picked.depsFile));
|
|
15650
15798
|
const handlerCmd = `${picked.entry} /work/${picked.file}`;
|
|
15651
15799
|
let shellCmd;
|
|
15652
15800
|
if (needsDeps && picked.installCmd) {
|
|
@@ -16211,14 +16359,14 @@ var domainCommand = new Command("domain").description("Manage custom domains").a
|
|
|
16211
16359
|
|
|
16212
16360
|
// src/commands/realtime.ts
|
|
16213
16361
|
import { existsSync as existsSync13 } from "fs";
|
|
16214
|
-
import { dirname as dirname12, resolve as
|
|
16362
|
+
import { dirname as dirname12, resolve as resolve14 } from "path";
|
|
16215
16363
|
init_api();
|
|
16216
16364
|
init_config();
|
|
16217
16365
|
init_colors();
|
|
16218
16366
|
function hasDeployManifest() {
|
|
16219
16367
|
const cfgPath = getConfigPath();
|
|
16220
16368
|
if (!cfgPath) return false;
|
|
16221
|
-
return existsSync13(
|
|
16369
|
+
return existsSync13(resolve14(dirname12(cfgPath), "gipity.yaml"));
|
|
16222
16370
|
}
|
|
16223
16371
|
var roomCommand = new Command("room").description("Manage realtime rooms").argument("[action]", "list | create | delete | info", "list").argument("[name]", "room name (for create | delete | info)").option("--type <type>", "room type for create: state | relay", "state").option("--auth <level>", "auth level for create: public | user", "public").option("--max-clients <n>", "max clients for create (1-200)").option("--json", "Output as JSON").action((action, name, opts) => run("Realtime room", async () => {
|
|
16224
16372
|
const config = requireConfig();
|
|
@@ -16308,6 +16456,16 @@ function statusIcon2(status) {
|
|
|
16308
16456
|
if (status === "skipped") return muted("\u2192");
|
|
16309
16457
|
return muted("?");
|
|
16310
16458
|
}
|
|
16459
|
+
function printSkippedCandidates(skipped) {
|
|
16460
|
+
if (skipped.length === 0) return;
|
|
16461
|
+
console.log("");
|
|
16462
|
+
console.log(muted(`Found ${skipped.length} test-looking file${skipped.length === 1 ? "" : "s"} that gipity test does not run:`));
|
|
16463
|
+
for (const s of skipped.slice(0, 10)) {
|
|
16464
|
+
console.log(muted(` ${s.path} (${s.reason})`));
|
|
16465
|
+
}
|
|
16466
|
+
if (skipped.length > 10) console.log(muted(` ... and ${skipped.length - 10} more`));
|
|
16467
|
+
console.log(muted('Run frontend/kit module tests in the sandbox: gipity sandbox run bash "node <file>"'));
|
|
16468
|
+
}
|
|
16311
16469
|
var LONG_RUN_MS = 6e4;
|
|
16312
16470
|
async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
16313
16471
|
const startTime = Date.now();
|
|
@@ -16384,7 +16542,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
16384
16542
|
}
|
|
16385
16543
|
}
|
|
16386
16544
|
}
|
|
16387
|
-
await new Promise((
|
|
16545
|
+
await new Promise((resolve17) => setTimeout(resolve17, getPollInterval()));
|
|
16388
16546
|
}
|
|
16389
16547
|
}
|
|
16390
16548
|
var testCommand = new Command("test").description("Run tests").argument("[path]", 'Test path filter (e.g. "api", "e2e/portal")').option("--filter <path>", "Alias for the positional test path filter").option("--timeout <ms>", "Per-test timeout in ms", "30000").option("--retry <n>", "Retry failed tests N times", "0").option("--no-sync", "Skip sync-up before tests").option("--json", "Output as JSON").action((pathFilter, opts) => run("Test", async () => {
|
|
@@ -16419,9 +16577,17 @@ var testCommand = new Command("test").description("Run tests").argument("[path]"
|
|
|
16419
16577
|
process.exit(1);
|
|
16420
16578
|
}
|
|
16421
16579
|
if (!filterPath && data.total === 0 && data.results.length === 0) {
|
|
16422
|
-
|
|
16580
|
+
const root = getProjectRoot();
|
|
16581
|
+
console.log(muted(`No tests ran - no tests/*.test.js files under the project root${root ? ` (${root})` : ""}.`));
|
|
16423
16582
|
console.log(muted("gipity test runs server-function tests only; a frontend-only app (no functions/) has nothing here to run."));
|
|
16424
16583
|
console.log(muted("Verify a frontend app by driving the live page: gipity page inspect <url> (or gipity page eval)."));
|
|
16584
|
+
try {
|
|
16585
|
+
const listRes = await get(
|
|
16586
|
+
`/projects/${config.projectGuid}/test/list`
|
|
16587
|
+
);
|
|
16588
|
+
printSkippedCandidates(listRes.data.skipped ?? []);
|
|
16589
|
+
} catch {
|
|
16590
|
+
}
|
|
16425
16591
|
return;
|
|
16426
16592
|
}
|
|
16427
16593
|
if (data.status === "failed" && data.errorMessage) {
|
|
@@ -16489,7 +16655,9 @@ testCommand.command("list").description("List the test files that would run \u20
|
|
|
16489
16655
|
return;
|
|
16490
16656
|
}
|
|
16491
16657
|
if (total === 0) {
|
|
16492
|
-
|
|
16658
|
+
const root = getProjectRoot();
|
|
16659
|
+
console.log(muted(pathFilter ? `No test files matched filter: ${pathFilter}` : `No test files found under tests/ at the project root${root ? ` (${root})` : ""}. Add *.test.js files under tests/.`));
|
|
16660
|
+
if (!pathFilter) printSkippedCandidates(res.data.skipped ?? []);
|
|
16493
16661
|
return;
|
|
16494
16662
|
}
|
|
16495
16663
|
console.log(bold(`Test files${pathFilter ? ` (filter: ${pathFilter})` : ""}: ${total}`));
|
|
@@ -17459,13 +17627,13 @@ function parseVersion(out) {
|
|
|
17459
17627
|
return m ? m[0] : void 0;
|
|
17460
17628
|
}
|
|
17461
17629
|
function runProbe(cmd, args) {
|
|
17462
|
-
return new Promise((
|
|
17630
|
+
return new Promise((resolve17) => {
|
|
17463
17631
|
let out = "";
|
|
17464
17632
|
let settled = false;
|
|
17465
17633
|
const done = (s) => {
|
|
17466
17634
|
if (!settled) {
|
|
17467
17635
|
settled = true;
|
|
17468
|
-
|
|
17636
|
+
resolve17(s);
|
|
17469
17637
|
}
|
|
17470
17638
|
};
|
|
17471
17639
|
let child;
|
|
@@ -17852,9 +18020,9 @@ async function dispatchLoop(ctx, opts) {
|
|
|
17852
18020
|
while (inflight.size >= MAX_CONCURRENT_DISPATCHES && !ctx.abort.signal.aborted) {
|
|
17853
18021
|
await Promise.race([
|
|
17854
18022
|
...inflight,
|
|
17855
|
-
new Promise((
|
|
17856
|
-
if (ctx.abort.signal.aborted) return
|
|
17857
|
-
ctx.abort.signal.addEventListener("abort", () =>
|
|
18023
|
+
new Promise((resolve17) => {
|
|
18024
|
+
if (ctx.abort.signal.aborted) return resolve17();
|
|
18025
|
+
ctx.abort.signal.addEventListener("abort", () => resolve17(), { once: true });
|
|
17858
18026
|
})
|
|
17859
18027
|
]);
|
|
17860
18028
|
}
|
|
@@ -18339,7 +18507,7 @@ async function killRunningForConv(convGuid) {
|
|
|
18339
18507
|
}
|
|
18340
18508
|
}
|
|
18341
18509
|
const graceTimers = [];
|
|
18342
|
-
const escalate = new Promise((
|
|
18510
|
+
const escalate = new Promise((resolve17) => {
|
|
18343
18511
|
const t = setTimeout(() => {
|
|
18344
18512
|
for (const e of matches) {
|
|
18345
18513
|
log2("warn", "previous dispatch ignored SIGTERM - escalating to SIGKILL", { conv: convGuid });
|
|
@@ -18348,7 +18516,7 @@ async function killRunningForConv(convGuid) {
|
|
|
18348
18516
|
} catch {
|
|
18349
18517
|
}
|
|
18350
18518
|
}
|
|
18351
|
-
|
|
18519
|
+
resolve17();
|
|
18352
18520
|
}, KILL_GRACE_MS);
|
|
18353
18521
|
graceTimers.push(t);
|
|
18354
18522
|
});
|
|
@@ -18358,7 +18526,7 @@ async function killRunningForConv(convGuid) {
|
|
|
18358
18526
|
}
|
|
18359
18527
|
async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
18360
18528
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
18361
|
-
return new Promise((
|
|
18529
|
+
return new Promise((resolve17, reject) => {
|
|
18362
18530
|
const child = spawnCommand(cmd, ["sync", "--json"], {
|
|
18363
18531
|
cwd,
|
|
18364
18532
|
env: childEnv(),
|
|
@@ -18398,7 +18566,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
18398
18566
|
child.on("exit", (code) => finish(() => {
|
|
18399
18567
|
if (code === 0) {
|
|
18400
18568
|
log2("info", "sync done", { cwd, stdoutLen });
|
|
18401
|
-
|
|
18569
|
+
resolve17();
|
|
18402
18570
|
} else {
|
|
18403
18571
|
reject(new Error(`gipity sync exited ${code}${stderrBuf ? `: ${stderrBuf.trim().slice(0, 300)}` : ""}`));
|
|
18404
18572
|
}
|
|
@@ -18409,7 +18577,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
18409
18577
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
18410
18578
|
const fullArgs = [...args, "--output-format", "stream-json", "--verbose", "--include-partial-messages"];
|
|
18411
18579
|
const env = childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid, GIPITY_CAPTURE: "off" });
|
|
18412
|
-
return new Promise((
|
|
18580
|
+
return new Promise((resolve17, reject) => {
|
|
18413
18581
|
const child = spawnCommand(cmd, fullArgs, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
18414
18582
|
let resolveExited = () => {
|
|
18415
18583
|
};
|
|
@@ -18575,7 +18743,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
18575
18743
|
void postProgress(d.conversation_guid, buildProgressPayload(false));
|
|
18576
18744
|
if (ownQueue) await q.close();
|
|
18577
18745
|
cleanup();
|
|
18578
|
-
|
|
18746
|
+
resolve17({
|
|
18579
18747
|
exitCode: code ?? 1,
|
|
18580
18748
|
killed,
|
|
18581
18749
|
runtimeLimit,
|
|
@@ -18589,10 +18757,10 @@ async function runDispatchSync(d, cwd) {
|
|
|
18589
18757
|
let killed = false;
|
|
18590
18758
|
try {
|
|
18591
18759
|
await spawnSync2(cwd, PROJECT_SYNC_TIMEOUT_MS, (child) => {
|
|
18592
|
-
const exited = new Promise((
|
|
18760
|
+
const exited = new Promise((resolve17) => {
|
|
18593
18761
|
child.once("exit", (_code, signal) => {
|
|
18594
18762
|
if (signal === "SIGTERM") killed = true;
|
|
18595
|
-
|
|
18763
|
+
resolve17();
|
|
18596
18764
|
});
|
|
18597
18765
|
});
|
|
18598
18766
|
running.set(d.short_guid, { child, convGuid: d.conversation_guid, exited });
|
|
@@ -18616,18 +18784,18 @@ function killDispatch(shortGuid) {
|
|
|
18616
18784
|
}
|
|
18617
18785
|
}
|
|
18618
18786
|
function sleep4(ms, signal) {
|
|
18619
|
-
return new Promise((
|
|
18620
|
-
if (signal?.aborted) return
|
|
18787
|
+
return new Promise((resolve17) => {
|
|
18788
|
+
if (signal?.aborted) return resolve17();
|
|
18621
18789
|
let onAbort = null;
|
|
18622
18790
|
const t = setTimeout(() => {
|
|
18623
18791
|
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
18624
|
-
|
|
18792
|
+
resolve17();
|
|
18625
18793
|
}, ms);
|
|
18626
18794
|
if (signal) {
|
|
18627
18795
|
onAbort = () => {
|
|
18628
18796
|
clearTimeout(t);
|
|
18629
18797
|
signal.removeEventListener("abort", onAbort);
|
|
18630
|
-
|
|
18798
|
+
resolve17();
|
|
18631
18799
|
};
|
|
18632
18800
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
18633
18801
|
}
|
|
@@ -19020,7 +19188,7 @@ init_utils();
|
|
|
19020
19188
|
init_colors();
|
|
19021
19189
|
import { existsSync as existsSync19, rmSync, unlinkSync as unlinkSync6, readFileSync as readFileSync21, writeFileSync as writeFileSync13 } from "fs";
|
|
19022
19190
|
import { homedir as homedir14, platform as osPlatform4 } from "os";
|
|
19023
|
-
import { join as join20, resolve as
|
|
19191
|
+
import { join as join20, resolve as resolve15 } from "path";
|
|
19024
19192
|
function removeGipityPluginConfig() {
|
|
19025
19193
|
const settingsPath = join20(homedir14(), ".claude", "settings.json");
|
|
19026
19194
|
if (!existsSync19(settingsPath)) return false;
|
|
@@ -19075,7 +19243,7 @@ function tildify2(p) {
|
|
|
19075
19243
|
return p === home || p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
|
|
19076
19244
|
}
|
|
19077
19245
|
function resolveCliPath2() {
|
|
19078
|
-
return
|
|
19246
|
+
return resolve15(process.argv[1] ?? "gipity");
|
|
19079
19247
|
}
|
|
19080
19248
|
async function stopDaemon() {
|
|
19081
19249
|
if (!isDaemonRunning()) return;
|
|
@@ -19694,13 +19862,64 @@ function collectRealFlags(argv2, program3) {
|
|
|
19694
19862
|
return flags;
|
|
19695
19863
|
}
|
|
19696
19864
|
|
|
19865
|
+
// src/trace.ts
|
|
19866
|
+
import { openSync as openSync4, writeSync, mkdirSync as mkdirSync12 } from "fs";
|
|
19867
|
+
import { join as join21 } from "path";
|
|
19868
|
+
import { homedir as homedir15 } from "os";
|
|
19869
|
+
var TRACE_KEY = /* @__PURE__ */ Symbol.for("gipity.traceOutput");
|
|
19870
|
+
function installOutputTrace(label2) {
|
|
19871
|
+
if (process.env.GIPITY_TRACE_OUTPUT !== "1") return;
|
|
19872
|
+
try {
|
|
19873
|
+
const g = globalThis;
|
|
19874
|
+
const existing = g[TRACE_KEY];
|
|
19875
|
+
if (existing) {
|
|
19876
|
+
existing.emit({ event: "reenter", label: label2 });
|
|
19877
|
+
return;
|
|
19878
|
+
}
|
|
19879
|
+
const dir = join21(process.env.GIPITY_DIR || join21(homedir15(), ".gipity"), "trace");
|
|
19880
|
+
mkdirSync12(dir, { recursive: true });
|
|
19881
|
+
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
|
|
19882
|
+
const fd = openSync4(join21(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
|
|
19883
|
+
const emit = (rec) => {
|
|
19884
|
+
try {
|
|
19885
|
+
writeSync(fd, JSON.stringify({ t: (/* @__PURE__ */ new Date()).toISOString(), ...rec }) + "\n");
|
|
19886
|
+
} catch {
|
|
19887
|
+
}
|
|
19888
|
+
};
|
|
19889
|
+
g[TRACE_KEY] = { emit };
|
|
19890
|
+
emit({
|
|
19891
|
+
event: "start",
|
|
19892
|
+
label: label2,
|
|
19893
|
+
pid: process.pid,
|
|
19894
|
+
ppid: process.ppid,
|
|
19895
|
+
argv: process.argv,
|
|
19896
|
+
cwd: process.cwd(),
|
|
19897
|
+
tty: { stdout: !!process.stdout.isTTY, stderr: !!process.stderr.isTTY }
|
|
19898
|
+
});
|
|
19899
|
+
for (const name of ["stdout", "stderr"]) {
|
|
19900
|
+
const stream = process[name];
|
|
19901
|
+
const original = stream.write.bind(stream);
|
|
19902
|
+
stream.write = ((chunk, encoding, cb) => {
|
|
19903
|
+
emit({
|
|
19904
|
+
stream: name,
|
|
19905
|
+
data: typeof chunk === "string" ? chunk : Buffer.from(chunk).toString("utf8")
|
|
19906
|
+
});
|
|
19907
|
+
return original(chunk, encoding, cb);
|
|
19908
|
+
});
|
|
19909
|
+
}
|
|
19910
|
+
process.on("exit", (code) => emit({ event: "exit", code }));
|
|
19911
|
+
} catch {
|
|
19912
|
+
}
|
|
19913
|
+
}
|
|
19914
|
+
|
|
19697
19915
|
// src/index.ts
|
|
19916
|
+
installOutputTrace("index");
|
|
19698
19917
|
var __dirname = dirname13(fileURLToPath3(import.meta.url));
|
|
19699
|
-
var pkg = JSON.parse(readFileSync23(
|
|
19918
|
+
var pkg = JSON.parse(readFileSync23(resolve16(__dirname, "../package.json"), "utf-8"));
|
|
19700
19919
|
function versionLabel() {
|
|
19701
19920
|
const base = `v${pkg.version}`;
|
|
19702
19921
|
try {
|
|
19703
|
-
const info2 = JSON.parse(readFileSync23(
|
|
19922
|
+
const info2 = JSON.parse(readFileSync23(resolve16(__dirname, "build-info.json"), "utf-8"));
|
|
19704
19923
|
if (info2?.sha) return `${base} (dev ${info2.sha}${info2.dirty ? ", modified" : ""})`;
|
|
19705
19924
|
} catch {
|
|
19706
19925
|
}
|