gipity 1.1.2 → 1.1.4
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/catalog.js +1 -3
- package/dist/commands/build.js +99 -29
- package/dist/commands/deploy.js +6 -2
- package/dist/commands/file.js +18 -0
- package/dist/commands/fn.js +6 -6
- package/dist/commands/generate.js +76 -12
- package/dist/commands/job.js +125 -8
- package/dist/commands/page-eval.js +79 -10
- package/dist/commands/page-inspect.js +26 -1
- package/dist/commands/page-screenshot.js +33 -4
- package/dist/commands/records.js +14 -1
- package/dist/commands/sandbox.js +181 -9
- package/dist/commands/service.js +6 -1
- package/dist/commands/test.js +32 -0
- package/dist/commands/update.js +4 -0
- package/dist/commands/upload.js +69 -121
- package/dist/helpers/body.js +117 -0
- package/dist/helpers/duration.js +34 -0
- package/dist/helpers/index.js +2 -0
- package/dist/index.js +848 -375
- package/dist/knowledge.js +4 -7
- package/dist/updater/bootstrap.js +27 -16
- package/dist/updater/check.js +86 -10
- package/dist/updater/install.js +82 -0
- package/dist/updater/shim.js +90 -27
- 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 rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
3947
|
-
return new Promise((
|
|
3947
|
+
return new Promise((resolve19) => {
|
|
3948
3948
|
rl2.question(question, (answer) => {
|
|
3949
3949
|
rl2.close();
|
|
3950
|
-
|
|
3950
|
+
resolve19(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((resolve19) => {
|
|
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 (k7 === "n") answer = false;
|
|
3993
3993
|
else answer = defaultYes;
|
|
3994
3994
|
console.log(answer ? "y" : "n");
|
|
3995
|
-
|
|
3995
|
+
resolve19(answer);
|
|
3996
3996
|
});
|
|
3997
3997
|
});
|
|
3998
3998
|
}
|
|
3999
3999
|
function pickOne(label2, max, defaultIdx = 1) {
|
|
4000
|
-
return new Promise((
|
|
4000
|
+
return new Promise((resolve19) => {
|
|
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 resolve19(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 resolve19(n);
|
|
4022
4022
|
}
|
|
4023
4023
|
console.log(String(defaultIdx));
|
|
4024
|
-
|
|
4024
|
+
resolve19(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(resolve19, reject) {
|
|
5374
|
+
promiseResolve = resolve19;
|
|
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((resolve19, reject) => {
|
|
5405
|
+
if (stream._duplexState & DESTROYED) return resolve19({ value: void 0, done: true });
|
|
5406
5406
|
stream.once("close", function() {
|
|
5407
5407
|
if (err) reject(err);
|
|
5408
|
-
else
|
|
5408
|
+
else resolve19({ value: void 0, done: true });
|
|
5409
5409
|
});
|
|
5410
5410
|
});
|
|
5411
5411
|
}
|
|
@@ -5449,8 +5449,8 @@ var require_streamx = __commonJS({
|
|
|
5449
5449
|
const writes = pending + (ws2._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((resolve19) => {
|
|
5453
|
+
state.drains.push({ writes, resolve: resolve19 });
|
|
5454
5454
|
});
|
|
5455
5455
|
}
|
|
5456
5456
|
write(data) {
|
|
@@ -5555,10 +5555,10 @@ var require_streamx = __commonJS({
|
|
|
5555
5555
|
cb2(null);
|
|
5556
5556
|
}
|
|
5557
5557
|
function pipelinePromise(...streams) {
|
|
5558
|
-
return new Promise((
|
|
5558
|
+
return new Promise((resolve19, reject) => {
|
|
5559
5559
|
return pipeline(...streams, (err) => {
|
|
5560
5560
|
if (err) return reject(err);
|
|
5561
|
-
|
|
5561
|
+
resolve19();
|
|
5562
5562
|
});
|
|
5563
5563
|
});
|
|
5564
5564
|
}
|
|
@@ -6213,16 +6213,16 @@ var require_extract = __commonJS({
|
|
|
6213
6213
|
entryCallback = null;
|
|
6214
6214
|
cb2(err);
|
|
6215
6215
|
}
|
|
6216
|
-
function onnext(
|
|
6216
|
+
function onnext(resolve19, reject) {
|
|
6217
6217
|
if (error2) {
|
|
6218
6218
|
return reject(error2);
|
|
6219
6219
|
}
|
|
6220
6220
|
if (entryStream) {
|
|
6221
|
-
|
|
6221
|
+
resolve19({ value: entryStream, done: false });
|
|
6222
6222
|
entryStream = null;
|
|
6223
6223
|
return;
|
|
6224
6224
|
}
|
|
6225
|
-
promiseResolve =
|
|
6225
|
+
promiseResolve = resolve19;
|
|
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((resolve19, reject) => {
|
|
6254
|
+
if (extract3.destroyed) return resolve19({ value: void 0, done: true });
|
|
6255
6255
|
extract3.once("close", function() {
|
|
6256
6256
|
if (err) reject(err);
|
|
6257
|
-
else
|
|
6257
|
+
else resolve19({ value: void 0, done: true });
|
|
6258
6258
|
});
|
|
6259
6259
|
});
|
|
6260
6260
|
}
|
|
@@ -6707,7 +6707,7 @@ async function postForTarEntries(path5, body, retried = false) {
|
|
|
6707
6707
|
if (!res.body) throw new ApiError(500, "EMPTY_RESPONSE", "Server returned no body");
|
|
6708
6708
|
const extract3 = tar.extract();
|
|
6709
6709
|
const entries = [];
|
|
6710
|
-
const done = new Promise((
|
|
6710
|
+
const done = new Promise((resolve19, reject) => {
|
|
6711
6711
|
extract3.on("entry", (header, stream, next) => {
|
|
6712
6712
|
const chunks = [];
|
|
6713
6713
|
stream.on("data", (c) => chunks.push(c));
|
|
@@ -6718,7 +6718,7 @@ async function postForTarEntries(path5, body, retried = false) {
|
|
|
6718
6718
|
stream.on("error", reject);
|
|
6719
6719
|
stream.resume();
|
|
6720
6720
|
});
|
|
6721
|
-
extract3.on("finish", () =>
|
|
6721
|
+
extract3.on("finish", () => resolve19());
|
|
6722
6722
|
extract3.on("error", reject);
|
|
6723
6723
|
});
|
|
6724
6724
|
Readable.fromWeb(res.body).pipe(extract3);
|
|
@@ -31863,9 +31863,9 @@ var {
|
|
|
31863
31863
|
// src/index.ts
|
|
31864
31864
|
init_config();
|
|
31865
31865
|
init_utils();
|
|
31866
|
-
import { readFileSync as
|
|
31866
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
31867
31867
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31868
|
-
import { dirname as dirname13, resolve as
|
|
31868
|
+
import { dirname as dirname13, resolve as resolve18 } from "path";
|
|
31869
31869
|
|
|
31870
31870
|
// src/helpers/output.ts
|
|
31871
31871
|
var frameOpen = false;
|
|
@@ -31970,12 +31970,10 @@ Kits are reusable building blocks added to an existing app, not whole templates
|
|
|
31970
31970
|
- \`gipity add chatbot\` - Drop-in chatbot - configurable persona, scope guardrails, static knowledge (20k budget), streaming responses. Headless engine + bubble widget; bring your own UI if you want. Works in any app.
|
|
31971
31971
|
- \`gipity add audio-align\` - Audio + lyrics -> word-level timing JSON. Demucs vocal isolation + MMS_FA forced alignment, runs as a Modal L4 GPU job (~$0.01 per 3-min song). For karaoke captions, subtitling, language learning, dubbing alignment.
|
|
31972
31972
|
- \`gipity add i18n\` - Multi-language for web apps - language picker, locale persistence, RTL, plural/translation lookup. Scaffolds src/js/strings.js and wires it up; move your copy there and read it with t('key'). Web only.
|
|
31973
|
-
- \`gipity add records\` - Registry-driven records: declare objects/fields as data, get generic CRUD functions with validation, full-text search, soft delete, ACTOR provenance, and an audit event spine - every write is transactional (row + event). Field types include relations ({id,label}), currency, emails/phones/links composites. Ships backend functions + migrations. Needs a database (web-fullstack/api template). See the \`app-records\` skill.
|
|
31974
|
-
- \`gipity add views\` - Generic UI over records-kit objects: sortable/filterable table with full-text search, create/edit/delete forms with type-appropriate widgets, kanban board with drag-to-update. Renders entirely from the field registry - zero per-object UI code. Requires the records kit. See the \`app-records\` skill.
|
|
31975
|
-
- \`gipity add agent-api\` - Make your app agent-operable: named API keys (kit_api_keys) let agents and scripts write through the records kit's single write path with AGENT/API actor attribution - machine writes land on the same audit spine as human edits. Requires the records kit. See the \`app-records\` skill.
|
|
31976
31973
|
- \`gipity add contacts\` - Source-agnostic contact data layer for lead-gen/CRM apps: import people from LinkedIn CSV + Gmail + pasted lists, resolve duplicates into one person while keeping EVERY value from every source with provenance (multi-valued attributes, never overwrites). Exact email/URL auto-merge; fuzzy name+company goes to a human merge-review queue (reversible). Re-imports detect job changes and emit signals. User-definable tags, full-text search, and a transactional event spine. Ships backend functions + migrations. Needs a database (web-fullstack/api template).
|
|
31977
31974
|
- \`gipity add stripe\` - Charge your app's end-users for one-time purchases and subscriptions via Stripe. Owner connects their own Stripe account through Gipity-hosted onboarding (no API keys to paste); money lands in their account, Gipity takes a small platform fee. Ships a buy-button / pricing component, a subscription-status helper for gating UI, a webhook-verified fulfillment function, and the payments/subscriptions tables. The platform brokers checkout + signature-verified webhooks. Needs a database (web-fullstack/api template).
|
|
31978
|
-
- \`gipity add notify\` - Web push notifications for any web app, including iOS home-screen web apps (iOS 16.4+). The platform owns the VAPID keys, encryption, and delivery \u2014 no keys to paste, no crypto, no server. Ships a <gipity-notify-button>, a service worker, and a PWA manifest; the device subscribes itself and self-heals stale subscriptions. Send from a function with the injected notify() service (one flat credit per send, owner-billed) or test with \`gipity notify test\`. Works in any template \u2014 no database required
|
|
31975
|
+
- \`gipity add notify\` - Web push notifications for any web app, including iOS home-screen web apps (iOS 16.4+). The platform owns the VAPID keys, encryption, and delivery \u2014 no keys to paste, no crypto, no server. Ships a <gipity-notify-button>, a service worker, and a PWA manifest; the device subscribes itself and self-heals stale subscriptions. Send from a function with the injected notify() service (one flat credit per send, owner-billed) or test with \`gipity notify test\`. Works in any template \u2014 no database required.
|
|
31976
|
+
- \`gipity add servicenow\` - Use ServiceNow tables as a data source: OAuth (client-credentials) authenticated polling pull into a local Postgres mirror (sn_records, any table, configurable), plus write-back into ServiceNow via the Table API. Optional real-time sync layers a ServiceNow Business Rule + Script Include on top, pushing changes to a webhook the instant they happen. Ships a cron workflow for the pull cycle, sn-pull/sn-write/sn-webhook functions, and setup scripts that automate the ServiceNow-side configuration. Needs a database (web-fullstack/api template).`;
|
|
31979
31977
|
var SKILLS_CONTENT = `# Gipity Integration
|
|
31980
31978
|
|
|
31981
31979
|
Gipity is the cloud platform your project runs on - hosting, databases, deployment, file storage, code execution, workflows, and monitoring. Gip is the cloud agent that runs on Gipity.
|
|
@@ -32088,7 +32086,7 @@ To keep local-only material (research clones, scratch data, vendored references)
|
|
|
32088
32086
|
Deploy is opt-in, not opt-out: the \`files\` phase uploads **only** what's under \`src/\` (plus \`functions/\` and \`migrations/\` as backend, not CDN files). Anything else at the project root is kept but never deployed. Put each kind of file in the right bucket so scratch and reference material can't bloat a deploy:
|
|
32089
32087
|
|
|
32090
32088
|
- **\`src/\`** - the app itself. Synced **and** deployed to the CDN. Only app code, assets, and pages belong here.
|
|
32091
|
-
- **\`tmp/\`** - ephemeral scratch: file conversions, intermediate outputs, design staging. **Already ignored** (never synced, never deployed) - the one place to do throwaway work. Use this single root. (\`*_tmp/\` dirs and \`.gipityscratch/\` are auto-ignored too, as a safety net, so legacy scattered scratch like \`_vsd_tmp/\` can't leak - but write new scratch to \`tmp/\`, not scattered dirs.) Because it never syncs, \`tmp/\` is also never mirrored into the sandbox: **don't stage \`gipity sandbox run\` inputs here** - the sandbox won't see them. Stage inputs under \`src/\`/\`docs/\` and delete them after.
|
|
32089
|
+
- **\`tmp/\`** - ephemeral scratch: file conversions, intermediate outputs, design staging. **Already ignored** (never synced, never deployed) - the one place to do throwaway work. Use this single root. Leave scratch files where they are when you're done; there's nothing to clean up (no \`rm\` turn) - they're invisible to sync and deploy. (\`*_tmp/\` dirs and \`.gipityscratch/\` are auto-ignored too, as a safety net, so legacy scattered scratch like \`_vsd_tmp/\` can't leak - but write new scratch to \`tmp/\`, not scattered dirs.) Because it never syncs, \`tmp/\` is also never mirrored into the sandbox: **don't stage \`gipity sandbox run\` inputs here** - the sandbox won't see them. Stage inputs under \`src/\`/\`docs/\` and delete them after.
|
|
32092
32090
|
- **\`docs/\`** - reference material you want to keep: UI/architecture diagrams, design decks, notes, ADRs. Synced and versioned on the server (backed up, rollback-able) but **never deployed**, because it's outside \`src/\`. This is the home for "keep forever, don't ship" artifacts.
|
|
32093
32091
|
- **\`tests/\`** - \`*.test.js\` suites. Synced, run by \`gipity test\`, never deployed. \`gipity test list [path]\` lists the test files (and what a filter selects) without running them.
|
|
32094
32092
|
|
|
@@ -32104,7 +32102,7 @@ App services skills (load before calling \`/services/*\` endpoints):
|
|
|
32104
32102
|
- \`app-audio\` - sound effects, music, transcription
|
|
32105
32103
|
- \`app-auth\` - sign in with Gipity, popup vs redirect
|
|
32106
32104
|
- \`app-files\` - uploads, variants, file listing
|
|
32107
|
-
- \`app-image\` - text-to-image
|
|
32105
|
+
- \`app-image\` - text-to-image AND instruction editing (pass \`images\` to edit an uploaded photo); providers, sizes, aspect ratios
|
|
32108
32106
|
- \`app-llm\` - chat completions, streaming, image input
|
|
32109
32107
|
- \`app-location\` - user location & reverse geocoding for deployed apps (first-party - no third-party geocoder)
|
|
32110
32108
|
- \`app-notify\` - web push notifications for deployed apps (incl. iOS home-screen web apps) - notify kit + injected notify() service, platform owns the keys
|
|
@@ -32127,7 +32125,6 @@ App development skills:
|
|
|
32127
32125
|
- \`web-ui-patterns\` - default Gipity look (theme tokens) + web UI recipes - feeds, copy-to-clipboard
|
|
32128
32126
|
|
|
32129
32127
|
Kit skills (reusable building blocks - \`gipity add <kit>\`):
|
|
32130
|
-
- \`app-records\` - the records + views kits: registry-driven CRUD with search + audit history, and generated tables/kanban/forms (gipity add records)
|
|
32131
32128
|
- \`audio-align\` - the audio-align kit: forced alignment of audio + lyrics into word-level timing JSON
|
|
32132
32129
|
- \`chatbot\` - the chatbot kit: persona + scope guardrails + static knowledge, bubble widget or headless engine
|
|
32133
32130
|
|
|
@@ -32359,25 +32356,25 @@ async function hashFile(path5) {
|
|
|
32359
32356
|
const hash = createHash("sha256");
|
|
32360
32357
|
let size = 0;
|
|
32361
32358
|
const stream = createReadStream(path5);
|
|
32362
|
-
await new Promise((
|
|
32359
|
+
await new Promise((resolve19, reject) => {
|
|
32363
32360
|
stream.on("data", (chunk) => {
|
|
32364
32361
|
const buf = chunk;
|
|
32365
32362
|
hash.update(buf);
|
|
32366
32363
|
size += buf.length;
|
|
32367
32364
|
});
|
|
32368
|
-
stream.on("end", () =>
|
|
32365
|
+
stream.on("end", () => resolve19());
|
|
32369
32366
|
stream.on("error", reject);
|
|
32370
32367
|
});
|
|
32371
32368
|
return { sha256: hash.digest("hex"), size };
|
|
32372
32369
|
}
|
|
32373
32370
|
function readRange(path5, start, end) {
|
|
32374
|
-
return new Promise((
|
|
32371
|
+
return new Promise((resolve19, reject) => {
|
|
32375
32372
|
const chunks = [];
|
|
32376
32373
|
const stream = createReadStream(path5, { start, end });
|
|
32377
32374
|
stream.on("data", (c) => {
|
|
32378
32375
|
chunks.push(c);
|
|
32379
32376
|
});
|
|
32380
|
-
stream.on("end", () =>
|
|
32377
|
+
stream.on("end", () => resolve19(Buffer.concat(chunks)));
|
|
32381
32378
|
stream.on("error", reject);
|
|
32382
32379
|
});
|
|
32383
32380
|
}
|
|
@@ -33229,7 +33226,7 @@ async function fetchRemote(projectGuid) {
|
|
|
33229
33226
|
function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
33230
33227
|
const extract3 = tar2.extract();
|
|
33231
33228
|
const files = /* @__PURE__ */ new Map();
|
|
33232
|
-
return new Promise((
|
|
33229
|
+
return new Promise((resolve19, reject) => {
|
|
33233
33230
|
let settled = false;
|
|
33234
33231
|
let idle;
|
|
33235
33232
|
const arm = () => {
|
|
@@ -33265,7 +33262,7 @@ function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
|
33265
33262
|
});
|
|
33266
33263
|
entryStream.resume();
|
|
33267
33264
|
});
|
|
33268
|
-
extract3.on("finish", () => done(
|
|
33265
|
+
extract3.on("finish", () => done(resolve19, files));
|
|
33269
33266
|
extract3.on("error", (e) => done(reject, e));
|
|
33270
33267
|
stream.on("error", (e) => done(reject, e));
|
|
33271
33268
|
arm();
|
|
@@ -34251,6 +34248,89 @@ async function syncBeforeAction(opts) {
|
|
|
34251
34248
|
}
|
|
34252
34249
|
}
|
|
34253
34250
|
|
|
34251
|
+
// src/helpers/body.ts
|
|
34252
|
+
import { readFileSync as readFileSync7 } from "node:fs";
|
|
34253
|
+
function readStdin() {
|
|
34254
|
+
if (process.stdin.isTTY) return "";
|
|
34255
|
+
try {
|
|
34256
|
+
return readFileSync7(0, "utf-8");
|
|
34257
|
+
} catch {
|
|
34258
|
+
return "";
|
|
34259
|
+
}
|
|
34260
|
+
}
|
|
34261
|
+
function readFileField(spec) {
|
|
34262
|
+
const eq2 = spec.indexOf("=");
|
|
34263
|
+
if (eq2 === -1) {
|
|
34264
|
+
throw new Error(`Invalid --file '${spec}': expected field=@path (e.g. --file image=@receipt.png).`);
|
|
34265
|
+
}
|
|
34266
|
+
const field = spec.slice(0, eq2).trim();
|
|
34267
|
+
let path5 = spec.slice(eq2 + 1);
|
|
34268
|
+
if (path5.startsWith("@")) path5 = path5.slice(1);
|
|
34269
|
+
if (!field) throw new Error(`Invalid --file '${spec}': missing field name before '='.`);
|
|
34270
|
+
if (!path5) throw new Error(`Invalid --file '${spec}': missing file path after '='.`);
|
|
34271
|
+
try {
|
|
34272
|
+
return [field, { data: readFileSync7(path5).toString("base64"), media_type: guessMime(path5) }];
|
|
34273
|
+
} catch (e) {
|
|
34274
|
+
throw new Error(`Cannot read --file '${path5}': ${e.message}`);
|
|
34275
|
+
}
|
|
34276
|
+
}
|
|
34277
|
+
function resolveBody(raw, fileSpecs) {
|
|
34278
|
+
const body = resolveJsonBody(raw);
|
|
34279
|
+
if (!fileSpecs || fileSpecs.length === 0) return body;
|
|
34280
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
34281
|
+
throw new Error("--file needs an object body to attach into (got a non-object JSON body).");
|
|
34282
|
+
}
|
|
34283
|
+
const obj = body;
|
|
34284
|
+
for (const spec of fileSpecs) {
|
|
34285
|
+
const [field, attachment] = readFileField(spec);
|
|
34286
|
+
obj[field] = attachment;
|
|
34287
|
+
}
|
|
34288
|
+
return obj;
|
|
34289
|
+
}
|
|
34290
|
+
function resolveJsonBody(raw) {
|
|
34291
|
+
if (raw == null || raw === "") return {};
|
|
34292
|
+
let source;
|
|
34293
|
+
let origin;
|
|
34294
|
+
if (raw === "-" || raw === "@-") {
|
|
34295
|
+
source = readStdin();
|
|
34296
|
+
origin = "stdin";
|
|
34297
|
+
if (source.trim() === "") {
|
|
34298
|
+
throw new Error("No JSON on stdin (pipe a body in, e.g. `cat body.json | gipity fn call foo -d -`).");
|
|
34299
|
+
}
|
|
34300
|
+
} else if (raw.startsWith("@")) {
|
|
34301
|
+
const path5 = raw.slice(1);
|
|
34302
|
+
try {
|
|
34303
|
+
source = readFileSync7(path5, "utf-8");
|
|
34304
|
+
} catch (e) {
|
|
34305
|
+
throw new Error(`Cannot read body file '${path5}': ${e.message}`);
|
|
34306
|
+
}
|
|
34307
|
+
origin = `file '${path5}'`;
|
|
34308
|
+
} else {
|
|
34309
|
+
source = raw;
|
|
34310
|
+
origin = "inline JSON";
|
|
34311
|
+
}
|
|
34312
|
+
try {
|
|
34313
|
+
return JSON.parse(source);
|
|
34314
|
+
} catch (e) {
|
|
34315
|
+
throw new Error(`Invalid JSON in ${origin}: ${e.message}`);
|
|
34316
|
+
}
|
|
34317
|
+
}
|
|
34318
|
+
|
|
34319
|
+
// src/helpers/duration.ts
|
|
34320
|
+
var UNIT_TO_MS = { ms: 1, s: 1e3, m: 6e4 };
|
|
34321
|
+
function parseDuration(raw, unit) {
|
|
34322
|
+
if (raw === void 0) return null;
|
|
34323
|
+
const m = raw.trim().match(/^(\d+(?:\.\d+)?)\s*(ms|s|m)?$/i);
|
|
34324
|
+
if (!m) return null;
|
|
34325
|
+
const n = parseFloat(m[1]);
|
|
34326
|
+
if (!Number.isFinite(n)) return null;
|
|
34327
|
+
const suffix = m[2]?.toLowerCase();
|
|
34328
|
+
if (!suffix) return { value: n, hadSuffix: false };
|
|
34329
|
+
const ms2 = n * UNIT_TO_MS[suffix];
|
|
34330
|
+
const value = unit === "ms" ? ms2 : ms2 / 1e3;
|
|
34331
|
+
return { value, hadSuffix: true };
|
|
34332
|
+
}
|
|
34333
|
+
|
|
34254
34334
|
// src/commands/token.ts
|
|
34255
34335
|
var tokenCommand = new Command("token").description("Manage API tokens").addHelpText("after", "\nLong-lived agent API tokens (gip_at_*) for headless agents and CI.");
|
|
34256
34336
|
var fmtDate = (d) => d ? new Date(d).toLocaleDateString() : "never";
|
|
@@ -34302,7 +34382,7 @@ init_api();
|
|
|
34302
34382
|
init_config();
|
|
34303
34383
|
init_auth();
|
|
34304
34384
|
import { basename, resolve as resolve7, dirname as dirname5 } from "path";
|
|
34305
|
-
import { existsSync as existsSync8, readFileSync as
|
|
34385
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
34306
34386
|
init_colors();
|
|
34307
34387
|
init_utils();
|
|
34308
34388
|
|
|
@@ -34477,7 +34557,7 @@ async function finalizeLocalProject(opts) {
|
|
|
34477
34557
|
}
|
|
34478
34558
|
|
|
34479
34559
|
// src/relay/paths.ts
|
|
34480
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as
|
|
34560
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
|
|
34481
34561
|
import { homedir as homedir4 } from "os";
|
|
34482
34562
|
import { join as join6, resolve as resolve5 } from "path";
|
|
34483
34563
|
function getProjectsRoot() {
|
|
@@ -34485,7 +34565,7 @@ function getProjectsRoot() {
|
|
|
34485
34565
|
const defaultDir = join6(homedir4(), "GipityProjects");
|
|
34486
34566
|
try {
|
|
34487
34567
|
if (existsSync6(settingsPath)) {
|
|
34488
|
-
const settings = JSON.parse(
|
|
34568
|
+
const settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
|
|
34489
34569
|
if (settings.projectsDir) return resolve5(settings.projectsDir);
|
|
34490
34570
|
} else {
|
|
34491
34571
|
mkdirSync5(join6(homedir4(), ".gipity"), { recursive: true });
|
|
@@ -34497,7 +34577,7 @@ function getProjectsRoot() {
|
|
|
34497
34577
|
}
|
|
34498
34578
|
|
|
34499
34579
|
// src/relay/state.ts
|
|
34500
|
-
import { readFileSync as
|
|
34580
|
+
import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
34501
34581
|
import { join as join7 } from "path";
|
|
34502
34582
|
import { homedir as homedir5 } from "os";
|
|
34503
34583
|
var RELAY_DIR = process.env.GIPITY_DIR || join7(homedir5(), ".gipity");
|
|
@@ -34509,7 +34589,7 @@ function emptyState() {
|
|
|
34509
34589
|
function loadState() {
|
|
34510
34590
|
if (!existsSync7(RELAY_FILE)) return emptyState();
|
|
34511
34591
|
try {
|
|
34512
|
-
const raw = JSON.parse(
|
|
34592
|
+
const raw = JSON.parse(readFileSync9(RELAY_FILE, "utf-8"));
|
|
34513
34593
|
return {
|
|
34514
34594
|
device: raw.device ?? null,
|
|
34515
34595
|
paused: Boolean(raw.paused),
|
|
@@ -34611,7 +34691,7 @@ function clearDaemonPid() {
|
|
|
34611
34691
|
function isDaemonRunning() {
|
|
34612
34692
|
if (!existsSync7(RELAY_PID_FILE)) return false;
|
|
34613
34693
|
try {
|
|
34614
|
-
const raw =
|
|
34694
|
+
const raw = readFileSync9(RELAY_PID_FILE, "utf-8").trim();
|
|
34615
34695
|
const pid = parseInt(raw, 10);
|
|
34616
34696
|
if (!pid || isNaN(pid)) {
|
|
34617
34697
|
try {
|
|
@@ -34957,7 +35037,7 @@ Working with an existing Gipity project:
|
|
|
34957
35037
|
if (adopted.applied > 0) console.log(`Synced ${adopted.applied} change${adopted.applied > 1 ? "s" : ""} with Gipity.`);
|
|
34958
35038
|
if (opts.capture === false) {
|
|
34959
35039
|
try {
|
|
34960
|
-
const cfg = JSON.parse(
|
|
35040
|
+
const cfg = JSON.parse(readFileSync10(resolve7(cwd, ".gipity.json"), "utf-8"));
|
|
34961
35041
|
cfg.captureHooks = false;
|
|
34962
35042
|
saveConfigAt(cwd, cfg);
|
|
34963
35043
|
} catch {
|
|
@@ -34991,7 +35071,7 @@ init_auth();
|
|
|
34991
35071
|
init_api();
|
|
34992
35072
|
init_config();
|
|
34993
35073
|
init_colors();
|
|
34994
|
-
import { existsSync as existsSync9, readFileSync as
|
|
35074
|
+
import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
|
|
34995
35075
|
import { join as join9, resolve as resolve8 } from "path";
|
|
34996
35076
|
import { homedir as homedir7 } from "os";
|
|
34997
35077
|
function checkGipityPlugin() {
|
|
@@ -34999,7 +35079,7 @@ function checkGipityPlugin() {
|
|
|
34999
35079
|
let settings = {};
|
|
35000
35080
|
if (existsSync9(path5)) {
|
|
35001
35081
|
try {
|
|
35002
|
-
settings = JSON.parse(
|
|
35082
|
+
settings = JSON.parse(readFileSync11(path5, "utf-8"));
|
|
35003
35083
|
} catch {
|
|
35004
35084
|
}
|
|
35005
35085
|
}
|
|
@@ -35159,106 +35239,60 @@ var pushCommand = new Command("push").description("Push one or more files").argu
|
|
|
35159
35239
|
});
|
|
35160
35240
|
|
|
35161
35241
|
// src/commands/upload.ts
|
|
35242
|
+
init_api();
|
|
35162
35243
|
init_config();
|
|
35163
|
-
import { statSync as statSync4
|
|
35164
|
-
import {
|
|
35244
|
+
import { statSync as statSync4 } from "fs";
|
|
35245
|
+
import { basename as basename2 } from "path";
|
|
35165
35246
|
init_utils();
|
|
35166
35247
|
init_colors();
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
function destFor(localFile, srcRoot, destRoot) {
|
|
35181
|
-
const rel2 = localFile.slice(srcRoot.length).replace(/\\/g, "/").replace(/^\/+/, "");
|
|
35182
|
-
return posix.join(destRoot.replace(/\\/g, "/").replace(/\/$/, ""), rel2);
|
|
35183
|
-
}
|
|
35184
|
-
var uploadCommand = new Command("upload").description("Upload files").argument("<src>", "Local source file or directory").argument("[dest]", "Destination path in the project (defaults to /)").option("-r, --recursive", "Upload a directory recursively").option("--mime <type>", "Override the content-type (default: detect from extension)").option("--concurrency <n>", `Parallel files (default ${UPLOAD_CONCURRENCY})`).option("--dry-run", "Print what would be uploaded; do not call the network").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").action(async (src, destArg, opts) => {
|
|
35248
|
+
var uploadCommand = new Command("upload").description(`Upload a local file and print a durable, worker-reachable URL for it.
|
|
35249
|
+
|
|
35250
|
+
The URL works immediately - no \`gipity deploy\` - and any job or function can
|
|
35251
|
+
fetch it. Use it to hand a GPU/CPU job a real input file when testing end-to-end:
|
|
35252
|
+
|
|
35253
|
+
gipity upload song.mp3
|
|
35254
|
+
gipity job submit split-stems --data '{"audio_url":"<printed url>"}'
|
|
35255
|
+
|
|
35256
|
+
By default the file is PUBLIC: a plain \`media.gipity.ai\` CDN url that resolves
|
|
35257
|
+
from anywhere, so a cloud worker can always fetch it. Pass --private for a
|
|
35258
|
+
token-signed serve url instead (reachable only by holders of the url).`).argument("<file>", "Local file to upload").option("--private", "Store as a private token-signed serve URL instead of a public CDN url").option("--content-type <mime>", "Override the content type (default: detected from the file extension)").option("--json", "Output as JSON").action((file, opts) => run("Upload", async () => {
|
|
35259
|
+
const { config } = await resolveProjectContext();
|
|
35260
|
+
let size;
|
|
35185
35261
|
try {
|
|
35186
|
-
const
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
const planned = [];
|
|
35190
|
-
if (srcStat.isDirectory()) {
|
|
35191
|
-
if (!opts.recursive) {
|
|
35192
|
-
throw new Error(`${src} is a directory - pass -r/--recursive to upload it`);
|
|
35193
|
-
}
|
|
35194
|
-
const srcAbs = resolve10(src);
|
|
35195
|
-
const sliceRoot = dirname6(srcAbs);
|
|
35196
|
-
for (const file of walkFiles(src)) {
|
|
35197
|
-
planned.push({
|
|
35198
|
-
localPath: file,
|
|
35199
|
-
virtualPath: destFor(resolve10(file), sliceRoot, dest),
|
|
35200
|
-
size: statSync4(file).size
|
|
35201
|
-
});
|
|
35202
|
-
}
|
|
35203
|
-
} else if (srcStat.isFile()) {
|
|
35204
|
-
const looksLikeDir = dest.endsWith("/") || opts.recursive === true;
|
|
35205
|
-
const virtualPath = looksLikeDir ? posix.join(dest.replace(/\/$/, ""), basename2(src)) : dest;
|
|
35206
|
-
planned.push({ localPath: src, virtualPath, size: srcStat.size });
|
|
35207
|
-
} else {
|
|
35208
|
-
throw new Error(`${src} is neither a regular file nor a directory`);
|
|
35209
|
-
}
|
|
35210
|
-
if (planned.length === 0) {
|
|
35211
|
-
console.log("Nothing to upload (0 files).");
|
|
35212
|
-
return;
|
|
35213
|
-
}
|
|
35214
|
-
const totalBytes = planned.reduce((s, f) => s + f.size, 0);
|
|
35215
|
-
console.log(`Plan: ${planned.length} file${planned.length > 1 ? "s" : ""}, ${formatSize(totalBytes)}`);
|
|
35216
|
-
for (const f of planned) {
|
|
35217
|
-
console.log(`${f.localPath} ${dim("\u2192")} ${f.virtualPath} (${formatSize(f.size)})`);
|
|
35218
|
-
}
|
|
35219
|
-
if (opts.dryRun) {
|
|
35220
|
-
console.log("\n--dry-run: skipping all network calls.");
|
|
35221
|
-
return;
|
|
35222
|
-
}
|
|
35223
|
-
const concurrency = Math.max(1, parseInt(opts.concurrency ?? String(UPLOAD_CONCURRENCY), 10));
|
|
35224
|
-
const uploadOpts = { mime: opts.mime };
|
|
35225
|
-
let cursor = 0;
|
|
35226
|
-
let uploaded = 0, skipped = 0, resumed = 0, failed = 0;
|
|
35227
|
-
const workers = [];
|
|
35228
|
-
for (let w = 0; w < Math.min(concurrency, planned.length); w++) {
|
|
35229
|
-
workers.push((async () => {
|
|
35230
|
-
while (true) {
|
|
35231
|
-
const idx = cursor++;
|
|
35232
|
-
if (idx >= planned.length) return;
|
|
35233
|
-
const f = planned[idx];
|
|
35234
|
-
try {
|
|
35235
|
-
const result = await uploadOneFile(config.projectGuid, f.localPath, f.virtualPath, uploadOpts);
|
|
35236
|
-
if (result.status === "skipped") {
|
|
35237
|
-
skipped++;
|
|
35238
|
-
console.log(`${dim("skip")} ${f.virtualPath} (already current)`);
|
|
35239
|
-
} else if (result.status === "resumed") {
|
|
35240
|
-
resumed++;
|
|
35241
|
-
console.log(`${dim("resumed")} ${f.virtualPath} v${result.version}`);
|
|
35242
|
-
} else {
|
|
35243
|
-
uploaded++;
|
|
35244
|
-
console.log(`${dim("uploaded")} ${f.virtualPath} v${result.version}`);
|
|
35245
|
-
}
|
|
35246
|
-
} catch (err) {
|
|
35247
|
-
failed++;
|
|
35248
|
-
console.error(error(` fail ${f.virtualPath}: ${err.message}`));
|
|
35249
|
-
}
|
|
35250
|
-
}
|
|
35251
|
-
})());
|
|
35252
|
-
}
|
|
35253
|
-
await Promise.all(workers);
|
|
35254
|
-
console.log(`
|
|
35255
|
-
Uploaded: ${uploaded}, Resumed: ${resumed}, Skipped: ${skipped}, Failed: ${failed}`);
|
|
35256
|
-
if (failed > 0) process.exit(1);
|
|
35262
|
+
const st2 = statSync4(file);
|
|
35263
|
+
if (!st2.isFile()) throw new Error("not a regular file");
|
|
35264
|
+
size = st2.size;
|
|
35257
35265
|
} catch (err) {
|
|
35258
|
-
|
|
35259
|
-
|
|
35266
|
+
throw new Error(`can't read ${file}: ${err.message}`);
|
|
35267
|
+
}
|
|
35268
|
+
if (size === 0) throw new Error(`${file} is empty - nothing to upload.`);
|
|
35269
|
+
const filename = basename2(file);
|
|
35270
|
+
const contentType = opts.contentType || guessMime(file);
|
|
35271
|
+
const doUpload = async () => {
|
|
35272
|
+
const init = await post(`/api/${config.projectGuid}/uploads/init`, {
|
|
35273
|
+
filename,
|
|
35274
|
+
content_type: contentType,
|
|
35275
|
+
size,
|
|
35276
|
+
public: !opts.private
|
|
35277
|
+
});
|
|
35278
|
+
const fields = await transferToS3(file, size, contentType, init.data);
|
|
35279
|
+
const completeBody = {
|
|
35280
|
+
upload_guid: init.data.upload_guid
|
|
35281
|
+
};
|
|
35282
|
+
if ("parts" in fields) completeBody.parts = fields.parts;
|
|
35283
|
+
const comp = await post(`/api/${config.projectGuid}/uploads/complete`, completeBody);
|
|
35284
|
+
return comp.data;
|
|
35285
|
+
};
|
|
35286
|
+
const data = opts.json ? await doUpload() : await withSpinner(`Uploading ${filename} (${formatSize(size)})\u2026`, doUpload, { done: null });
|
|
35287
|
+
if (opts.json) {
|
|
35288
|
+
console.log(JSON.stringify(data));
|
|
35289
|
+
return;
|
|
35260
35290
|
}
|
|
35261
|
-
});
|
|
35291
|
+
console.log(success(`Uploaded ${data.name} (${formatSize(data.size)})`));
|
|
35292
|
+
console.log(` ${brand(data.url)}`);
|
|
35293
|
+
console.log(muted(` Durable, worker-reachable URL - pass it straight to a job/function as an input URL.`));
|
|
35294
|
+
console.log(muted(` ${data.is_public ? "public (CDN)" : "private (token-signed)"} \xB7 guid: ${data.guid}`));
|
|
35295
|
+
}));
|
|
35262
35296
|
|
|
35263
35297
|
// src/commands/deploy.ts
|
|
35264
35298
|
init_api();
|
|
@@ -35273,7 +35307,7 @@ init_colors();
|
|
|
35273
35307
|
init_auth();
|
|
35274
35308
|
|
|
35275
35309
|
// src/commands/page-eval.ts
|
|
35276
|
-
import { readFileSync as
|
|
35310
|
+
import { readFileSync as readFileSync13 } from "node:fs";
|
|
35277
35311
|
init_api();
|
|
35278
35312
|
init_colors();
|
|
35279
35313
|
init_auth();
|
|
@@ -35282,8 +35316,8 @@ init_config();
|
|
|
35282
35316
|
// src/page-fixtures.ts
|
|
35283
35317
|
init_api();
|
|
35284
35318
|
init_config();
|
|
35285
|
-
import { readFileSync as
|
|
35286
|
-
import { basename as basename3, resolve as
|
|
35319
|
+
import { readFileSync as readFileSync12, statSync as statSync5, existsSync as existsSync10, readdirSync as readdirSync6 } from "node:fs";
|
|
35320
|
+
import { basename as basename3, resolve as resolve10, relative as relative3, join as join10 } from "node:path";
|
|
35287
35321
|
var FIND_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".gipity", "dist", "build", ".next", "coverage"]);
|
|
35288
35322
|
function findByBasename(root, name, limit = 3) {
|
|
35289
35323
|
const hits = [];
|
|
@@ -35294,22 +35328,22 @@ function findByBasename(root, name, limit = 3) {
|
|
|
35294
35328
|
visited++;
|
|
35295
35329
|
let entries;
|
|
35296
35330
|
try {
|
|
35297
|
-
entries =
|
|
35331
|
+
entries = readdirSync6(dir, { withFileTypes: true });
|
|
35298
35332
|
} catch {
|
|
35299
35333
|
continue;
|
|
35300
35334
|
}
|
|
35301
35335
|
for (const e of entries) {
|
|
35302
35336
|
if (e.isDirectory()) {
|
|
35303
|
-
if (!FIND_SKIP.has(e.name)) queue.push(
|
|
35337
|
+
if (!FIND_SKIP.has(e.name)) queue.push(join10(dir, e.name));
|
|
35304
35338
|
} else if (e.name === name && hits.length < limit) {
|
|
35305
|
-
hits.push(
|
|
35339
|
+
hits.push(join10(dir, e.name));
|
|
35306
35340
|
}
|
|
35307
35341
|
}
|
|
35308
35342
|
}
|
|
35309
35343
|
return hits;
|
|
35310
35344
|
}
|
|
35311
35345
|
function assertLocalAsset(flag, localPath) {
|
|
35312
|
-
const abs =
|
|
35346
|
+
const abs = resolve10(localPath);
|
|
35313
35347
|
if (existsSync10(abs)) return;
|
|
35314
35348
|
const root = getProjectRoot() ?? process.cwd();
|
|
35315
35349
|
const elsewhere = findByBasename(root, basename3(localPath)).filter((p) => p !== abs);
|
|
@@ -35336,7 +35370,7 @@ async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
|
|
|
35336
35370
|
const res = await fetch(init.data.url, {
|
|
35337
35371
|
method: "PUT",
|
|
35338
35372
|
headers: { "Content-Type": contentType },
|
|
35339
|
-
body:
|
|
35373
|
+
body: readFileSync12(localPath)
|
|
35340
35374
|
});
|
|
35341
35375
|
if (!res.ok) {
|
|
35342
35376
|
throw new Error(`upload of fixture "${name}" failed: ${res.status} ${res.statusText}`);
|
|
@@ -35435,6 +35469,9 @@ function capWaitForTimeoutMs(raw) {
|
|
|
35435
35469
|
));
|
|
35436
35470
|
return WAIT_FOR_MAX_MS;
|
|
35437
35471
|
}
|
|
35472
|
+
function readScriptFile(pathArg) {
|
|
35473
|
+
return readFileSync13(pathArg === "-" ? 0 : pathArg, "utf8");
|
|
35474
|
+
}
|
|
35438
35475
|
function parsesAsExpression(s) {
|
|
35439
35476
|
try {
|
|
35440
35477
|
new Function(`return (${s}
|
|
@@ -35455,6 +35492,7 @@ var CAMERA_SETUP_BUDGET_MS = 3e4;
|
|
|
35455
35492
|
var EVAL_SCRIPT_BUDGET_MS = 3e4;
|
|
35456
35493
|
var EVAL_SCRIPT_BUDGET_CAMERA_MS = 45e3;
|
|
35457
35494
|
var EVAL_SCRIPT_BUDGET_MAX_MS = 9e4;
|
|
35495
|
+
var EVAL_SCRIPT_BUDGET_MIN_MS = 1e3;
|
|
35458
35496
|
function capScriptBudgetMs(rawTimeout, hasMedia) {
|
|
35459
35497
|
const fallback = hasMedia ? EVAL_SCRIPT_BUDGET_CAMERA_MS : EVAL_SCRIPT_BUDGET_MS;
|
|
35460
35498
|
if (rawTimeout === void 0) return fallback;
|
|
@@ -35466,7 +35504,7 @@ function capScriptBudgetMs(rawTimeout, hasMedia) {
|
|
|
35466
35504
|
));
|
|
35467
35505
|
return EVAL_SCRIPT_BUDGET_MAX_MS;
|
|
35468
35506
|
}
|
|
35469
|
-
return Math.max(
|
|
35507
|
+
return Math.max(EVAL_SCRIPT_BUDGET_MIN_MS, parsed);
|
|
35470
35508
|
}
|
|
35471
35509
|
function evalWorkBudgetMs(o) {
|
|
35472
35510
|
const script = o.scriptBudgetMs ?? (o.hasCamera ? EVAL_SCRIPT_BUDGET_CAMERA_MS : EVAL_SCRIPT_BUDGET_MS);
|
|
@@ -35528,14 +35566,14 @@ function evalExecTimeoutMessage(result, budgetMs) {
|
|
|
35528
35566
|
If the slow part is a ONE-TIME page init (a WASM/model download, a big asset, a first-frame pipeline warm-up), do NOT split the body across several 'page eval' calls \u2014 every call is a fresh page load that re-pays that init, so each one hits this same wall. Have the page kick it off on load (not behind a click) and absorb it in the pre-eval window instead.`;
|
|
35529
35567
|
}
|
|
35530
35568
|
var JS_DECOY_FLAGS = ["--js", "--javascript", "--script", "--code", "--expr", "--eval", "--exec", "--action"];
|
|
35531
|
-
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; await works, and the trailing expression is returned automatically \u2014 REPL-style \u2014 so no explicit return is needed; result is JSON-serialized). Omit when using --file. Time budget: the body has ${EVAL_SCRIPT_BUDGET_MS / 1e3}s to finish after page load (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera) - raise it with --timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s.`).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 post-load budget as <expr> (--timeout).`).option(
|
|
35569
|
+
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; await works, and the trailing expression is returned automatically \u2014 REPL-style \u2014 so no explicit return is needed; result is JSON-serialized). Omit when using --file. Time budget: the body has ${EVAL_SCRIPT_BUDGET_MS / 1e3}s to finish after page load (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera) - raise it with --timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s.`).option("--file <path>", `Read the script body from a file instead of the inline <expr> arg (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' \u2026 EOF) with no tmp file. Runs as an async function body, so top-level return/await work. Same post-load budget as <expr> (--timeout).`).option(
|
|
35532
35570
|
"--step <expr>",
|
|
35533
35571
|
`Run another expression against the SAME loaded page, after <expr> (repeat, max ${MAX_EVAL_STEPS}). Whatever that page load paid for \u2014 a vision model coming up, a game booting, a socket connecting \u2014 stays up for every step, so an N-part check costs ONE page load instead of N. Each step gets its own in-page budget and its own reported result.`,
|
|
35534
35572
|
(val, prev) => [...prev, val],
|
|
35535
35573
|
[]
|
|
35536
35574
|
).option(
|
|
35537
35575
|
"--fixture <path>",
|
|
35538
|
-
|
|
35576
|
+
'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. The hosted URL has permissive CORS, so `new Image(); img.crossOrigin="anonymous"; img.src=fixtureUrl` decodes untainted for a canvas/pixel read - this is the UPLOAD analog of --camera: feed a known photo/video to a file-upload vision app (web-vision-detect) and run its detector, no need to deploy a throwaway test asset into the app. Repeat for several files (single-value so it never swallows the inline <expr>).',
|
|
35539
35577
|
(val, prev) => [...prev, val],
|
|
35540
35578
|
[]
|
|
35541
35579
|
).option(
|
|
@@ -35546,7 +35584,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35546
35584
|
`Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, so a camera app's real pipeline (getUserMedia \u2192 MediaPipe/YOLOX \u2192 your app logic) runs headlessly on a frame you choose. Implies --fake-media and waits ${CAMERA_DEFAULT_WAIT_MS / 1e3}s for the vision model to load. No frame handy? gipity generate image "a hand making a closed fist, palm to camera".`
|
|
35547
35585
|
).option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so a voice/camera app runs headlessly instead of hitting its no-camera path. The feed is a built-in test pattern (and a tone) \u2014 nothing a vision model can recognize; to drive a vision app use --camera <path> instead.").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before evaluating (lets late async work settle; max 30000). With --wait-for, it elapses AFTER the selector appears - gate, then settle.", "500").option("--wait-for <selector>", `Wait until this CSS selector appears before evaluating, then evaluate (deterministic - beats guessing a --wait). Gate on the state you are ASSERTING, not just a ready flag: '#verdict:not(:empty)' returns the instant the round lands, where a fixed wait snapshots mid-sequence. Same flag on page inspect/screenshot. Max ${WAIT_FOR_MAX_MS}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (max ${WAIT_FOR_MAX_MS})`, "5000").option(
|
|
35548
35586
|
"--timeout <ms>",
|
|
35549
|
-
`How long the script itself may run IN the page
|
|
35587
|
+
`How long the script itself may run IN the page. Bare number = MILLISECONDS (so 90s = 90000, NOT 90); or pass an explicit unit that means the same on both this and \`sandbox run --timeout\` \u2014 --timeout 90s. Its own await/setTimeout pauses count (default ${EVAL_SCRIPT_BUDGET_MS}, ${EVAL_SCRIPT_BUDGET_CAMERA_MS} with --camera/--fake-media; floor 1000, max ${EVAL_SCRIPT_BUDGET_MAX_MS}). Raise it to trace a sequence that unfolds over time (a game round, an animation). Distinct from --wait, which only sleeps BEFORE the script.`
|
|
35550
35588
|
).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. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--json", "Output as JSON").action((url, exprArg, opts) => run("Page eval", async () => {
|
|
35551
35589
|
const decoy = JS_DECOY_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
35552
35590
|
if (decoy) {
|
|
@@ -35570,9 +35608,9 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35570
35608
|
let expr = exprArg;
|
|
35571
35609
|
if (opts.file) {
|
|
35572
35610
|
try {
|
|
35573
|
-
expr =
|
|
35611
|
+
expr = readScriptFile(opts.file);
|
|
35574
35612
|
} catch {
|
|
35575
|
-
pageEvalCommand.error(`error: Cannot read file: ${opts.file}`);
|
|
35613
|
+
pageEvalCommand.error(opts.file === "-" ? `error: --file - reads the script from stdin, but stdin was empty/unreadable \u2014 pipe it in, e.g. gipity page eval "<url>" --file - <<'EOF' \u2026 EOF` : `error: Cannot read file: ${opts.file}`);
|
|
35576
35614
|
}
|
|
35577
35615
|
}
|
|
35578
35616
|
if (opts.reload !== void 0 && opts.reloadFile) {
|
|
@@ -35581,7 +35619,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35581
35619
|
let reloadExpr = opts.reload;
|
|
35582
35620
|
if (opts.reloadFile) {
|
|
35583
35621
|
try {
|
|
35584
|
-
reloadExpr =
|
|
35622
|
+
reloadExpr = readScriptFile(opts.reloadFile);
|
|
35585
35623
|
} catch {
|
|
35586
35624
|
pageEvalCommand.error(`error: Cannot read file: ${opts.reloadFile}`);
|
|
35587
35625
|
}
|
|
@@ -35592,6 +35630,20 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35592
35630
|
`error: at most ${MAX_EVAL_STEPS} --step expressions ride on one page load (got ${steps.length}) \u2014 do more per step, or split this into two evals`
|
|
35593
35631
|
);
|
|
35594
35632
|
}
|
|
35633
|
+
if (opts.timeout !== void 0) {
|
|
35634
|
+
const dur = parseDuration(opts.timeout, "ms");
|
|
35635
|
+
if (dur?.hadSuffix) opts.timeout = String(Math.round(dur.value));
|
|
35636
|
+
}
|
|
35637
|
+
if (opts.timeout !== void 0) {
|
|
35638
|
+
const t = parseInt(opts.timeout, 10);
|
|
35639
|
+
if (Number.isFinite(t) && t > 0 && t < EVAL_SCRIPT_BUDGET_MIN_MS) {
|
|
35640
|
+
const overMax = t * 1e3 > EVAL_SCRIPT_BUDGET_MAX_MS;
|
|
35641
|
+
const asMs = Math.min(t * 1e3, EVAL_SCRIPT_BUDGET_MAX_MS);
|
|
35642
|
+
throw new Error(
|
|
35643
|
+
`--timeout is in MILLISECONDS (got ${t}, under the ${EVAL_SCRIPT_BUDGET_MIN_MS}ms in-page floor). Unlike \`sandbox run --timeout\` (seconds), page eval's is ms \u2014 or pass an explicit unit that means the same on both: --timeout ${t}s. ${t} looks like seconds, so for ${t} second${t === 1 ? "" : "s"} pass --timeout ${t}s (= ${asMs}ms)${overMax ? ` (${t}s is over the ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s max, so this is the ceiling)` : ""}.`
|
|
35644
|
+
);
|
|
35645
|
+
}
|
|
35646
|
+
}
|
|
35595
35647
|
let waitMs = capWaitMs(opts.wait, url);
|
|
35596
35648
|
const waitForTimeoutMs = capWaitForTimeoutMs(opts.waitTimeout);
|
|
35597
35649
|
const hasMedia = !!opts.camera || !!opts.fakeMedia;
|
|
@@ -35706,7 +35758,7 @@ ${hint}`);
|
|
|
35706
35758
|
}
|
|
35707
35759
|
if (camera) console.log(`${muted("Camera:")} ${camera.name} ${muted("(played as the webcam feed; getUserMedia resolves)")}`);
|
|
35708
35760
|
if (hosted.length) console.log(`${muted("Fixtures:")} ${hosted.map((h) => h.name).join(", ")}`);
|
|
35709
|
-
console.error(opts.file ? `${muted("Script:")} ${opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
35761
|
+
console.error(opts.file ? `${muted("Script:")} ${opts.file === "-" ? "(stdin)" : opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
35710
35762
|
console.log(`
|
|
35711
35763
|
${result.trim() ? result : muted("(empty result)")}`);
|
|
35712
35764
|
if (noValue) console.log(muted(`
|
|
@@ -35739,7 +35791,7 @@ ${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserve
|
|
|
35739
35791
|
}
|
|
35740
35792
|
}));
|
|
35741
35793
|
for (const f of JS_DECOY_FLAGS) pageEvalCommand.addOption(new Option(`${f} <value>`).hideHelp());
|
|
35742
|
-
pageEvalCommand.addHelpText("after", `
|
|
35794
|
+
pageEvalCommand.addHelpText("after", (context) => context.error ? "" : `
|
|
35743
35795
|
Examples:
|
|
35744
35796
|
gipity page eval "https://dev.gipity.ai/me/app/" "document.title"
|
|
35745
35797
|
# Functionally test a page's own code paths: save a script that drives the UI
|
|
@@ -35775,6 +35827,16 @@ Examples:
|
|
|
35775
35827
|
"window.__vision.gesture()" \\
|
|
35776
35828
|
--step "document.getElementById('see').textContent" \\
|
|
35777
35829
|
--step "({ score: score.textContent, verdict: verdict.textContent })"
|
|
35830
|
+
# File-UPLOAD vision app (web-vision-detect, no camera)? --fixture is the analog of
|
|
35831
|
+
# --camera: it hosts your test photo at a CORS-permissive URL, so you feed the kit's
|
|
35832
|
+
# detector a known image and read back the labels - no deploying a throwaway asset into
|
|
35833
|
+
# the app tree (and no cleanup redeploy). crossOrigin='anonymous' keeps the canvas clean.
|
|
35834
|
+
gipity generate image "street scene: three people, two cars, one dog" -o street.jpg
|
|
35835
|
+
gipity page eval "https://dev.gipity.ai/me/app/" --fixture street.jpg \\
|
|
35836
|
+
"const { createDetector } = await import('./packages/web-vision-detect/index.js'); \\
|
|
35837
|
+
const det = await createDetector({ model: 'nano' }); \\
|
|
35838
|
+
const img = new Image(); img.crossOrigin = 'anonymous'; img.src = fixtureUrl; await img.decode(); \\
|
|
35839
|
+
const { detections } = await det.detect(img); return detections.map(d => d.label);"
|
|
35778
35840
|
|
|
35779
35841
|
Module resolution: dynamic import() specifiers starting with ./ or ../ resolve
|
|
35780
35842
|
against the PAGE URL, so import('./packages/i18n/index.js') loads the app's own
|
|
@@ -35815,7 +35877,7 @@ init_auth();
|
|
|
35815
35877
|
init_colors();
|
|
35816
35878
|
init_utils();
|
|
35817
35879
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
35818
|
-
import { dirname as
|
|
35880
|
+
import { dirname as dirname6, join as join11, resolve as resolvePath } from "path";
|
|
35819
35881
|
var DEVICE_PRESETS = {
|
|
35820
35882
|
default: { width: 1280, height: 720 },
|
|
35821
35883
|
desktop: { width: 1920, height: 1080 },
|
|
@@ -35879,7 +35941,7 @@ function dimSuffix(vp2) {
|
|
|
35879
35941
|
}
|
|
35880
35942
|
function defaultScreenshotDir() {
|
|
35881
35943
|
const root = getProjectRoot();
|
|
35882
|
-
return
|
|
35944
|
+
return join11(root ?? ".", "screenshots");
|
|
35883
35945
|
}
|
|
35884
35946
|
function timestampSlug(d = /* @__PURE__ */ new Date()) {
|
|
35885
35947
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -35942,14 +36004,25 @@ var WAIT_FOR_MAX_MS2 = 15e3;
|
|
|
35942
36004
|
var WAIT_FOR_DEFAULT_MS = 15e3;
|
|
35943
36005
|
var MAX_POST_LOAD_DELAY_MS = 3e4;
|
|
35944
36006
|
var CAMERA_DEFAULT_DELAY_MS = 15e3;
|
|
35945
|
-
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").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), e.g. --viewport 390x844 for a phone-sized window", appendOption, []).option("--wait <ms>", `Sleep this many ms after DOMContentLoaded before capturing (default 1000, max ${MAX_POST_LOAD_DELAY_MS}). With --camera it defaults to ${CAMERA_DEFAULT_DELAY_MS} so the vision model is up. Same flag as page eval/inspect.`).option("--wait-for <selector>", `Wait until this CSS selector appears before capturing, then capture (deterministic - beats guessing a --wait). Same flag as page eval/inspect. Gate on what you are photographing ('[data-vision="ready"]', '#verdict:not(:empty)'). Max ${WAIT_FOR_MAX_MS2}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (default ${WAIT_FOR_DEFAULT_MS}, max ${WAIT_FOR_MAX_MS2}). Timing out is reported - the capture still happens, so you see the state it got stuck in.`).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("--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. The video feed is a built-in test pattern \u2014 to capture what the app does with a REAL frame (a hand, a face, an object), use --camera <path> instead.").option("--camera <path>", "Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, then capture \u2014 so the shot shows the app reacting to a frame you chose (detected gesture, boxes, labels). Implies --fake-media.").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. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--ephemeral", "Skip the project screenshot history: do not persist this capture to Gipity (screenshots/ in the project). Local file is still written.").option("--json", "Output JSON metadata instead of a friendly summary").addOption(new Option("--post-load-delay <ms>", "Alias for --wait").hideHelp()).addOption(new Option("--full-page", "Alias for --full").hideHelp()).addOption(new Option("--width <px>", "Alias: --viewport WxH").hideHelp()).addOption(new Option("--height <px>", "Alias: --viewport WxH").hideHelp()).action((url, opts) => run("Page screenshot", async () => {
|
|
36007
|
+
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").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), e.g. --viewport 390x844 for a phone-sized window", appendOption, []).option("--wait <ms>", `Sleep this many ms after DOMContentLoaded before capturing (default 1000, max ${MAX_POST_LOAD_DELAY_MS}). With --camera it defaults to ${CAMERA_DEFAULT_DELAY_MS} so the vision model is up. Same flag as page eval/inspect.`).option("--wait-for <selector>", `Wait until this CSS selector appears before capturing, then capture (deterministic - beats guessing a --wait). Same flag as page eval/inspect. Gate on what you are photographing ('[data-vision="ready"]', '#verdict:not(:empty)'). Max ${WAIT_FOR_MAX_MS2}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (default ${WAIT_FOR_DEFAULT_MS}, max ${WAIT_FOR_MAX_MS2}). Timing out is reported - the capture still happens, so you see the state it got stuck in.`).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("--file <path>", "Read the pre-capture script from a file instead of inline --action (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' \u2026 EOF) with no tmp file. For a multi-step driver \u2014 click through a flow, wait for it to render \u2014 then capture. Same async-function-body semantics as --action; same --file flag as page eval.").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("--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. The video feed is a built-in test pattern \u2014 to capture what the app does with a REAL frame (a hand, a face, an object), use --camera <path> instead.").option("--camera <path>", "Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, then capture \u2014 so the shot shows the app reacting to a frame you chose (detected gesture, boxes, labels). Implies --fake-media.").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. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--ephemeral", "Skip the project screenshot history: do not persist this capture to Gipity (screenshots/ in the project). Local file is still written.").option("--json", "Output JSON metadata instead of a friendly summary").addOption(new Option("--post-load-delay <ms>", "Alias for --wait").hideHelp()).addOption(new Option("--full-page", "Alias for --full").hideHelp()).addOption(new Option("--width <px>", "Alias: --viewport WxH").hideHelp()).addOption(new Option("--height <px>", "Alias: --viewport WxH").hideHelp()).action((url, opts) => run("Page screenshot", async () => {
|
|
35946
36008
|
const aliasFlag = ACTION_ALIAS_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
35947
36009
|
if (aliasFlag) {
|
|
35948
36010
|
console.error(muted(
|
|
35949
36011
|
`Note: treating ${aliasFlag} as --action \u2014 it runs your JS in the page before the capture. --action is the canonical flag.`
|
|
35950
36012
|
));
|
|
35951
36013
|
}
|
|
35952
|
-
const
|
|
36014
|
+
const inlineAction = [opts.action, ...ACTION_ALIAS_FLAGS.map((f) => opts[f.slice(2)])].filter(Boolean).join("\n");
|
|
36015
|
+
if (opts.file && inlineAction) {
|
|
36016
|
+
throw new Error("Pass either --file <path> or an inline --action script, not both");
|
|
36017
|
+
}
|
|
36018
|
+
let actionScript = inlineAction;
|
|
36019
|
+
if (opts.file) {
|
|
36020
|
+
try {
|
|
36021
|
+
actionScript = readScriptFile(opts.file);
|
|
36022
|
+
} catch {
|
|
36023
|
+
throw new Error(opts.file === "-" ? `--file - reads the pre-capture script from stdin, but stdin was empty/unreadable \u2014 pipe it in, e.g. gipity page screenshot "<url>" --file - <<'EOF' \u2026 EOF` : `Cannot read file: ${opts.file}`);
|
|
36024
|
+
}
|
|
36025
|
+
}
|
|
35953
36026
|
const delayRaw = opts.wait ?? opts.postLoadDelay;
|
|
35954
36027
|
const chosenDelay = delayRaw !== void 0;
|
|
35955
36028
|
let postLoadDelayMs = chosenDelay ? parseInt(String(delayRaw), 10) : 1e3;
|
|
@@ -36063,8 +36136,8 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36063
36136
|
const dir = defaultScreenshotDir();
|
|
36064
36137
|
const savedFiles = [];
|
|
36065
36138
|
for (let i = 0; i < pngs.length; i++) {
|
|
36066
|
-
const target = opts.output ? opts.output :
|
|
36067
|
-
mkdirSync7(
|
|
36139
|
+
const target = opts.output ? opts.output : join11(dir, names[i] ?? shotName(meta.screenshots[i].viewport));
|
|
36140
|
+
mkdirSync7(dirname6(target), { recursive: true });
|
|
36068
36141
|
writeFileSync8(target, pngs[i].buffer);
|
|
36069
36142
|
savedFiles.push(resolvePath(target));
|
|
36070
36143
|
}
|
|
@@ -36162,9 +36235,17 @@ Waiting for the page to reach a state before the shot?
|
|
|
36162
36235
|
|
|
36163
36236
|
Capturing a state that needs an interaction (start a game, open a menu, dismiss a modal)?
|
|
36164
36237
|
Use --action to run JS in the page before the shot \u2014 it fires after the wait
|
|
36165
|
-
(and after any --wait-for gate), then settles again so the result has painted.
|
|
36166
|
-
|
|
36167
|
-
|
|
36238
|
+
(and after any --wait-for gate), then settles again so the result has painted. For
|
|
36239
|
+
a multi-step driver (click, wait, click) pipe it as a heredoc with --file - (same
|
|
36240
|
+
as 'page eval') instead of cramming it into one inline string:
|
|
36241
|
+
gipity page screenshot "https://dev.gipity.ai/me/app/" --file - <<'EOF'
|
|
36242
|
+
document.getElementById('load-sample').click();
|
|
36243
|
+
await new Promise(r => setTimeout(r, 500));
|
|
36244
|
+
document.getElementById('run').click();
|
|
36245
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
36246
|
+
EOF
|
|
36247
|
+
Do NOT hand-roll a 'page eval' that returns a base64 image: the eval result is
|
|
36248
|
+
capped (~16KB) and truncates the PNG.
|
|
36168
36249
|
|
|
36169
36250
|
Capturing an off-screen region or reading element data?
|
|
36170
36251
|
\u2022 --full captures the ENTIRE scrollable page (then crop to the region).
|
|
@@ -36213,7 +36294,17 @@ async function inspectPage(url, opts = {}) {
|
|
|
36213
36294
|
auth: opts.auth || void 0
|
|
36214
36295
|
};
|
|
36215
36296
|
const res = await post(`/tools/browser/inspect`, inspectBody);
|
|
36216
|
-
const b7 = res
|
|
36297
|
+
const b7 = res?.data;
|
|
36298
|
+
if (!b7 || typeof b7 !== "object") {
|
|
36299
|
+
if (opts.json) {
|
|
36300
|
+
console.log(JSON.stringify({ url, captured: false, note: "inspector returned no page data" }));
|
|
36301
|
+
return;
|
|
36302
|
+
}
|
|
36303
|
+
console.log(`${brand("Inspecting")} ${bold(url)}`);
|
|
36304
|
+
console.log(warning("Captured no page data \u2014 the page returned an empty response."));
|
|
36305
|
+
console.log(muted("The page may have failed to load, redirected away, or rendered nothing server-side. Retry once; if it persists pass --wait-for <selector> to wait for a specific element to appear, or --auth if it is behind Sign in with Gipity."));
|
|
36306
|
+
return;
|
|
36307
|
+
}
|
|
36217
36308
|
const isPlatformLog = (entry) => {
|
|
36218
36309
|
const urlPart = entry.replace(/\s*\([^)]*\)\s*$/, "");
|
|
36219
36310
|
try {
|
|
@@ -36269,6 +36360,9 @@ async function inspectPage(url, opts = {}) {
|
|
|
36269
36360
|
console.log(`${muted("Title:")} ${b7.title || "(none)"}`);
|
|
36270
36361
|
console.log(`${muted("Elements:")} ${b7.elementCount || 0}`);
|
|
36271
36362
|
console.log(`${muted("Page weight:")} ${info(formatSize(b7.totalBytes || 0))}`);
|
|
36363
|
+
if ((b7.elementCount || 0) === 0 && !b7.title) {
|
|
36364
|
+
console.log(`${warning("\u26A0 Rendered no DOM (0 elements, no title)")} ${muted("\u2014 the client-side render likely had not run yet (try --wait-for <selector>), the page failed to load, or it is behind a sign-in gate (try --auth).")}`);
|
|
36365
|
+
}
|
|
36272
36366
|
console.log(`
|
|
36273
36367
|
${bold("Timing:")}`);
|
|
36274
36368
|
console.log(`${muted("TTFB:")} ${timing.ttfb}ms`);
|
|
@@ -36455,7 +36549,7 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
|
|
|
36455
36549
|
console.log(success(`\u2713 Deployed to ${target}`) + muted(` (${d.elapsedMs}ms)`));
|
|
36456
36550
|
if (d.url) console.log(`${muted("Live:")} ${brand(d.url)}`);
|
|
36457
36551
|
if (d.phases?.some((p) => p.name === "functions")) {
|
|
36458
|
-
console.log(`${muted("Functions:")} POST ${brand(`${
|
|
36552
|
+
console.log(`${muted("Functions:")} POST ${brand(`${resolveApiBase()}/api/${config.projectGuid}/fn/<name>`)}`);
|
|
36459
36553
|
console.log(muted(" (same address on dev and prod; names: gipity fn list; verify the public path: gipity fn call <name> --anon)"));
|
|
36460
36554
|
}
|
|
36461
36555
|
await inspectAfter();
|
|
@@ -36640,8 +36734,8 @@ memoryCommand.command("delete <topic>").description("Delete a topic").option("--
|
|
|
36640
36734
|
// src/commands/sandbox.ts
|
|
36641
36735
|
init_api();
|
|
36642
36736
|
init_config();
|
|
36643
|
-
import { readFileSync as
|
|
36644
|
-
import { dirname as
|
|
36737
|
+
import { readFileSync as readFileSync14, existsSync as existsSync11, statSync as statSync6, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
36738
|
+
import { dirname as dirname7, extname as extname4, relative as relative4, resolve as resolve11, sep as sep3 } from "path";
|
|
36645
36739
|
init_colors();
|
|
36646
36740
|
var LANG_MAP = {
|
|
36647
36741
|
js: "javascript",
|
|
@@ -36750,16 +36844,42 @@ function explainSplitArgs(args) {
|
|
|
36750
36844
|
);
|
|
36751
36845
|
return lines.join("\n");
|
|
36752
36846
|
}
|
|
36847
|
+
var SCRATCH_DIR_PATTERNS = SCRATCH_IGNORE.map((p) => p.replace(/\/$/, "")).filter(Boolean).map((p) => p.replace(/[.]/g, "\\.").replace(/\*/g, "[A-Za-z0-9._-]*"));
|
|
36848
|
+
function scratchRefsInCode(code) {
|
|
36849
|
+
const dirs = SCRATCH_DIR_PATTERNS.join("|");
|
|
36850
|
+
if (!dirs) return [];
|
|
36851
|
+
const re2 = new RegExp(`(?:\\/work\\/|(?<![\\w/.]))(?:${dirs})\\/[A-Za-z0-9._\\-\\/]+`, "g");
|
|
36852
|
+
return [...new Set((code.match(re2) ?? []).map((m) => m.replace(/^\/work\//, "")))];
|
|
36853
|
+
}
|
|
36854
|
+
function isWriteTarget(code, p) {
|
|
36855
|
+
const path5 = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36856
|
+
const m = new RegExp(`(?:\\/work\\/)?${path5}(?![\\w./-])`).exec(code);
|
|
36857
|
+
if (!m) return false;
|
|
36858
|
+
const idx = m.index;
|
|
36859
|
+
const before = code.slice(Math.max(0, idx - 40), idx);
|
|
36860
|
+
if (/(?:^|[\s'"`;(])(?:-o|-of|--out(?:put)?[= ]|>>?|tee(?:\s+-a)?)\s*['"`]?(?:\/work\/)?$/.test(before)) return true;
|
|
36861
|
+
const segStart = Math.max(
|
|
36862
|
+
code.lastIndexOf("&&", idx),
|
|
36863
|
+
code.lastIndexOf("||", idx),
|
|
36864
|
+
code.lastIndexOf(";", idx),
|
|
36865
|
+
code.lastIndexOf("|", idx),
|
|
36866
|
+
code.lastIndexOf("\n", idx)
|
|
36867
|
+
);
|
|
36868
|
+
const seg = code.slice(segStart + 1, idx).replace(/^[\s&|;]+/, "");
|
|
36869
|
+
const cmd = /^\s*([A-Za-z0-9_./-]+)/.exec(seg)?.[1]?.split("/").pop()?.toLowerCase();
|
|
36870
|
+
const afterInputFlag = /(?:^|[\s'"`])(?:-i|--input)[= ]\s*['"`]?$/.test(before);
|
|
36871
|
+
return cmd === "ffmpeg" && !afterInputFlag;
|
|
36872
|
+
}
|
|
36753
36873
|
function resolveRelativeCwd() {
|
|
36754
36874
|
const configPath = getConfigPath();
|
|
36755
36875
|
if (!configPath) return void 0;
|
|
36756
|
-
const projectRoot =
|
|
36876
|
+
const projectRoot = dirname7(configPath);
|
|
36757
36877
|
const rel2 = relative4(projectRoot, process.cwd());
|
|
36758
36878
|
if (!rel2 || rel2.startsWith("..")) return void 0;
|
|
36759
36879
|
return rel2.split(/[\\/]/).filter(Boolean).join("/");
|
|
36760
36880
|
}
|
|
36761
36881
|
var sandboxCommand = new Command("sandbox").description("Run code in a sandbox");
|
|
36762
|
-
sandboxCommand.command("run [args...]").description("Run code").option("--language <language>", "Language: js, py, or bash (required unless pinned by an interpreter token or --file extension)").option("--file <path>", "Read the code body from a file instead of the inline <code> arg; --language is inferred from the extension when not given").addOption(new Option("--code <code>", "Alias for the positional inline <code> arg").hideHelp()).option("--timeout <seconds>", "Execution timeout
|
|
36882
|
+
sandboxCommand.command("run [args...]").description("Run code").option("--language <language>", "Language: js, py, or bash (required unless pinned by an interpreter token or --file extension)").option("--file <path>", "Read the code body from a file instead of the inline <code> arg; --language is inferred from the extension when not given").addOption(new Option("--code <code>", "Alias for the positional inline <code> arg").hideHelp()).addOption(new Option("-c, --cmd <code>", 'Inline code (bash idiom: `sandbox run bash -c "<code>"`)').hideHelp()).addOption(new Option("-e, --eval <code>", 'Inline code (node idiom: `sandbox run node -e "<code>"`)').hideHelp()).option("--timeout <seconds>", "Execution timeout. Bare number = seconds; or pass an explicit unit that means the same on both this and `page eval --timeout`, e.g. --timeout 90s.", "30").option(
|
|
36763
36883
|
"--input <path>",
|
|
36764
36884
|
"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.",
|
|
36765
36885
|
(v7, prev) => [...prev ?? [], v7]
|
|
@@ -36803,6 +36923,8 @@ Examples:
|
|
|
36803
36923
|
$ gipity sandbox run --file build_report.py
|
|
36804
36924
|
$ gipity sandbox run python build_report.py # same thing, interpreter shorthand
|
|
36805
36925
|
$ gipity sandbox run bash "echo hi; ffmpeg -version" # inline, language pinned
|
|
36926
|
+
$ gipity sandbox run bash -c "ffmpeg -version" # the bash -c idiom also works
|
|
36927
|
+
$ gipity sandbox run node -e "console.log(process.version)" # and node -e
|
|
36806
36928
|
|
|
36807
36929
|
# Surgical: only these files are mirrored in
|
|
36808
36930
|
$ gipity sandbox run --language bash \\
|
|
@@ -36820,12 +36942,15 @@ GCC/Rust).
|
|
|
36820
36942
|
let inlineCode;
|
|
36821
36943
|
let filePath = opts.file;
|
|
36822
36944
|
let langFromInterp;
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
36945
|
+
const flagCode = opts.code ?? opts.cmd ?? opts.eval;
|
|
36946
|
+
if (flagCode !== void 0) {
|
|
36947
|
+
const leadIsInterp = args.length === 1 && INTERPRETERS[args[0].toLowerCase()] !== void 0;
|
|
36948
|
+
if (args.length > (leadIsInterp ? 1 : 0)) {
|
|
36949
|
+
console.error(error("Pass the code once: either positionally or via --code/--cmd/-c/--eval/-e, not both"));
|
|
36826
36950
|
process.exit(1);
|
|
36827
36951
|
}
|
|
36828
|
-
|
|
36952
|
+
const interp = leadIsInterp ? args[0] : opts.cmd !== void 0 ? "bash" : opts.eval !== void 0 ? "node" : void 0;
|
|
36953
|
+
args = interp ? [interp, flagCode] : [flagCode];
|
|
36829
36954
|
}
|
|
36830
36955
|
if (args.length >= 2 && INTERPRETERS[args[0].toLowerCase()] !== void 0) {
|
|
36831
36956
|
langFromInterp = INTERPRETERS[args[0].toLowerCase()];
|
|
@@ -36839,7 +36964,9 @@ GCC/Rust).
|
|
|
36839
36964
|
process.exit(1);
|
|
36840
36965
|
}
|
|
36841
36966
|
if (inlineCode !== void 0 && filePath) {
|
|
36842
|
-
console.error(error("Pass
|
|
36967
|
+
console.error(error("Pass the code ONE way, not both: an inline <code> arg OR --file <path>."));
|
|
36968
|
+
console.error(dim(" inline: gipity sandbox run bash 'echo hi'"));
|
|
36969
|
+
console.error(dim(" file: gipity sandbox run --file script.sh"));
|
|
36843
36970
|
process.exit(1);
|
|
36844
36971
|
}
|
|
36845
36972
|
if (inlineCode === void 0 && !filePath) {
|
|
@@ -36849,7 +36976,7 @@ GCC/Rust).
|
|
|
36849
36976
|
let source = inlineCode;
|
|
36850
36977
|
if (filePath) {
|
|
36851
36978
|
try {
|
|
36852
|
-
source =
|
|
36979
|
+
source = readFileSync14(filePath, "utf8");
|
|
36853
36980
|
} catch {
|
|
36854
36981
|
console.error(error(`Cannot read file: ${filePath}`));
|
|
36855
36982
|
process.exit(1);
|
|
@@ -36862,7 +36989,8 @@ GCC/Rust).
|
|
|
36862
36989
|
process.exit(1);
|
|
36863
36990
|
}
|
|
36864
36991
|
const { config } = await resolveProjectContext();
|
|
36865
|
-
const
|
|
36992
|
+
const durOpt = parseDuration(opts.timeout, "s");
|
|
36993
|
+
const timeout = durOpt ? Math.max(1, Math.round(durOpt.value)) : parseInt(opts.timeout, 10);
|
|
36866
36994
|
const cwd = resolveRelativeCwd();
|
|
36867
36995
|
const scratchInputs = (opts.input ?? []).filter(
|
|
36868
36996
|
(p) => shouldIgnore(p.replace(/\\/g, "/").replace(/^\.\//, ""), SCRATCH_IGNORE)
|
|
@@ -36873,6 +37001,18 @@ GCC/Rust).
|
|
|
36873
37001
|
console.error(dim(" Stage inputs at a real project path (src/, docs/, assets/) and delete them afterward."));
|
|
36874
37002
|
process.exit(1);
|
|
36875
37003
|
}
|
|
37004
|
+
if (source) {
|
|
37005
|
+
const scratchReads = scratchRefsInCode(source).filter(
|
|
37006
|
+
(p) => shouldIgnore(p, SCRATCH_IGNORE) && existsSync11(resolve11(process.cwd(), p)) && !isWriteTarget(source, p)
|
|
37007
|
+
);
|
|
37008
|
+
if (scratchReads.length) {
|
|
37009
|
+
console.error(error(`Scratch files are never mirrored into the sandbox, so it can't read: ${scratchReads.join(", ")}`));
|
|
37010
|
+
console.error(dim(` ${SCRATCH_IGNORE.join(", ")} are ignored by sync, so the sandbox never sees them.`));
|
|
37011
|
+
console.error(dim(" Stage the input at a real project path (src/, docs/, assets/) and delete it afterward."));
|
|
37012
|
+
console.error(dim(" (Writing OUTPUT under tmp/ is fine \u2014 scratch outputs come back to your local tmp/.)"));
|
|
37013
|
+
process.exit(1);
|
|
37014
|
+
}
|
|
37015
|
+
}
|
|
36876
37016
|
if (getConfigPath()) {
|
|
36877
37017
|
await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
36878
37018
|
}
|
|
@@ -36895,12 +37035,12 @@ GCC/Rust).
|
|
|
36895
37035
|
}
|
|
36896
37036
|
const scratchWritten = [];
|
|
36897
37037
|
if (res.data.scratchFiles?.length) {
|
|
36898
|
-
const base =
|
|
37038
|
+
const base = resolve11(getProjectRoot() ?? process.cwd());
|
|
36899
37039
|
for (const f of res.data.scratchFiles) {
|
|
36900
37040
|
const rel2 = f.path.replace(/\\/g, "/");
|
|
36901
|
-
const abs =
|
|
37041
|
+
const abs = resolve11(base, rel2);
|
|
36902
37042
|
if (abs !== base && !abs.startsWith(base + sep3)) continue;
|
|
36903
|
-
mkdirSync8(
|
|
37043
|
+
mkdirSync8(dirname7(abs), { recursive: true });
|
|
36904
37044
|
writeFileSync9(abs, Buffer.from(f.contentBase64, "base64"));
|
|
36905
37045
|
scratchWritten.push(rel2);
|
|
36906
37046
|
}
|
|
@@ -36924,7 +37064,7 @@ GCC/Rust).
|
|
|
36924
37064
|
}
|
|
36925
37065
|
if (res.data.outputFiles && res.data.outputFiles.length > 0) {
|
|
36926
37066
|
const projectRoot = getProjectRoot();
|
|
36927
|
-
const landed = (f) => pulledLocal && !!projectRoot && existsSync11(
|
|
37067
|
+
const landed = (f) => pulledLocal && !!projectRoot && existsSync11(resolve11(projectRoot, f));
|
|
36928
37068
|
const onDisk = res.data.outputFiles.filter(landed);
|
|
36929
37069
|
const notOnDisk = res.data.outputFiles.filter((f) => !landed(f));
|
|
36930
37070
|
if (onDisk.length > 0) {
|
|
@@ -36936,6 +37076,12 @@ GCC/Rust).
|
|
|
36936
37076
|
for (const f of notOnDisk) console.log(`${f}`);
|
|
36937
37077
|
if (pulledLocal) console.log(dim("Not pulled locally - run 'gipity sync' to fetch them."));
|
|
36938
37078
|
}
|
|
37079
|
+
const rootStrays = [...onDisk, ...notOnDisk].filter((f) => !f.includes("/"));
|
|
37080
|
+
if (rootStrays.length > 0) {
|
|
37081
|
+
console.log(dim(`
|
|
37082
|
+
Note: ${rootStrays.join(", ")} landed at the project root and will deploy with the files phase.`));
|
|
37083
|
+
console.log(dim("For a throwaway probe or fixture, write it under tmp/ instead - scratch outputs stay on local disk and never sync or deploy."));
|
|
37084
|
+
}
|
|
36939
37085
|
}
|
|
36940
37086
|
if (scratchWritten.length > 0) {
|
|
36941
37087
|
console.log("\nScratch outputs (local only - never synced or deployed):");
|
|
@@ -36945,7 +37091,27 @@ GCC/Rust).
|
|
|
36945
37091
|
console.log(dim("\nDiscarded (--discard-output):"));
|
|
36946
37092
|
for (const f of res.data.skippedOutputFiles) console.log(dim(`${f}`));
|
|
36947
37093
|
}
|
|
37094
|
+
if (res.data.exitCode === 0) {
|
|
37095
|
+
const emptyScratch = (res.data.scratchFiles ?? []).filter((f) => f.contentBase64.length === 0).map((f) => f.path.replace(/\\/g, "/"));
|
|
37096
|
+
const root = getProjectRoot();
|
|
37097
|
+
const emptyOutputs = pulledLocal && root ? (res.data.outputFiles ?? []).filter((f) => {
|
|
37098
|
+
try {
|
|
37099
|
+
return statSync6(resolve11(root, f)).size === 0;
|
|
37100
|
+
} catch {
|
|
37101
|
+
return false;
|
|
37102
|
+
}
|
|
37103
|
+
}) : [];
|
|
37104
|
+
const empties = [.../* @__PURE__ */ new Set([...emptyScratch, ...emptyOutputs])];
|
|
37105
|
+
if (empties.length > 0) {
|
|
37106
|
+
console.error(dim(`Note: ${empties.join(", ")} was written but is empty (0 bytes) - the command exited 0 but likely failed silently (a broken filter, or an out-of-memory kill the shell swallowed). Re-check the command, or shrink the job if it may have run out of memory.`));
|
|
37107
|
+
}
|
|
37108
|
+
}
|
|
36948
37109
|
if (res.data.exitCode !== 0) {
|
|
37110
|
+
if (res.data.exitCode === 137) {
|
|
37111
|
+
console.error(error("The sandbox process was killed (exit 137) - it ran out of memory."));
|
|
37112
|
+
console.error(dim(" Shrink the job: for ffmpeg, downscale (-vf scale=640:-2) and add -preset ultrafast;"));
|
|
37113
|
+
console.error(dim(" for ImageMagick, lower -density/-resize. Process in smaller chunks if it still OOMs."));
|
|
37114
|
+
}
|
|
36949
37115
|
process.exit(res.data.exitCode);
|
|
36950
37116
|
}
|
|
36951
37117
|
}
|
|
@@ -37047,7 +37213,7 @@ chatCommand.command("delete <guid>").description("Delete a chat").option("--json
|
|
|
37047
37213
|
// src/commands/project.ts
|
|
37048
37214
|
init_api();
|
|
37049
37215
|
init_config();
|
|
37050
|
-
import { join as
|
|
37216
|
+
import { join as join12 } from "path";
|
|
37051
37217
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
37052
37218
|
init_colors();
|
|
37053
37219
|
init_utils();
|
|
@@ -37105,7 +37271,7 @@ projectCommand.command("create <name>").description("Create a project").option("
|
|
|
37105
37271
|
}
|
|
37106
37272
|
const res = await post("/projects", body);
|
|
37107
37273
|
const project = res.data;
|
|
37108
|
-
const dir =
|
|
37274
|
+
const dir = join12(getProjectsRoot(), project.slug);
|
|
37109
37275
|
mkdirSync9(dir, { recursive: true });
|
|
37110
37276
|
const accountSlug = await getAccountSlug();
|
|
37111
37277
|
let agentGuid = "";
|
|
@@ -37361,7 +37527,7 @@ function printStepRuns(steps, emptyNote) {
|
|
|
37361
37527
|
}
|
|
37362
37528
|
}
|
|
37363
37529
|
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
37364
|
-
var sleep2 = (ms2) => new Promise((
|
|
37530
|
+
var sleep2 = (ms2) => new Promise((resolve19) => setTimeout(resolve19, ms2));
|
|
37365
37531
|
async function waitForRun(wfGuid, prevGuid, timeoutSec) {
|
|
37366
37532
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
37367
37533
|
let runGuid;
|
|
@@ -37821,6 +37987,19 @@ fileCommand.command("cat <path>").description("Read a file").option("--json", "O
|
|
|
37821
37987
|
process.stdout.write(res.data.content);
|
|
37822
37988
|
}
|
|
37823
37989
|
}));
|
|
37990
|
+
fileCommand.command("url <path>").description("Durable public URL for a project file - no deploy needed. Reachable by browsers, GPU jobs, and external services the moment the file syncs; ideal for throwaway test fixtures (delete the file to revoke the link).").option("--download", "URL forces a download (Content-Disposition: attachment)").option("--json", "Output as JSON").action((path5, opts) => run("Url", async () => {
|
|
37991
|
+
const { config } = await resolveProjectContext();
|
|
37992
|
+
const qs = new URLSearchParams({ path: path5 });
|
|
37993
|
+
if (opts.download) qs.set("download", "1");
|
|
37994
|
+
const res = await get(
|
|
37995
|
+
`/projects/${config.projectGuid}/files/url?${qs.toString()}`
|
|
37996
|
+
);
|
|
37997
|
+
if (opts.json) {
|
|
37998
|
+
console.log(JSON.stringify(res.data));
|
|
37999
|
+
} else {
|
|
38000
|
+
console.log(res.data.url);
|
|
38001
|
+
}
|
|
38002
|
+
}));
|
|
37824
38003
|
fileCommand.command("tree [path]").description("Show the file tree").option("--json", "Output as JSON").action((path5, opts) => run("Tree", async () => {
|
|
37825
38004
|
const { config } = await resolveProjectContext();
|
|
37826
38005
|
const query = path5 ? `?path=${encodeURIComponent(path5)}` : "";
|
|
@@ -37982,8 +38161,8 @@ storageCommand.command("retention").description("View or adjust your file versio
|
|
|
37982
38161
|
init_platform();
|
|
37983
38162
|
init_auth();
|
|
37984
38163
|
init_api();
|
|
37985
|
-
import { join as
|
|
37986
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as
|
|
38164
|
+
import { join as join16, dirname as dirname9, resolve as resolve13, basename as basename4, sep as sep5 } from "path";
|
|
38165
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync2, readdirSync as readdirSync7, statSync as statSync7 } from "fs";
|
|
37987
38166
|
import { homedir as homedir12 } from "os";
|
|
37988
38167
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
37989
38168
|
import { randomUUID } from "crypto";
|
|
@@ -38131,11 +38310,11 @@ function buildFreshWrap(contextBlock, userMsg) {
|
|
|
38131
38310
|
// src/relay/onboarding.ts
|
|
38132
38311
|
init_utils();
|
|
38133
38312
|
init_colors();
|
|
38134
|
-
import { readFileSync as
|
|
38313
|
+
import { readFileSync as readFileSync16 } from "node:fs";
|
|
38135
38314
|
|
|
38136
38315
|
// src/relay/installers.ts
|
|
38137
38316
|
import { homedir as homedir8, platform as osPlatform } from "os";
|
|
38138
|
-
import { join as
|
|
38317
|
+
import { join as join13 } from "path";
|
|
38139
38318
|
var UnsupportedPlatformError = class extends Error {
|
|
38140
38319
|
constructor(plat) {
|
|
38141
38320
|
super(`gipity relay install does not support ${plat}`);
|
|
@@ -38153,8 +38332,8 @@ function display(argvs) {
|
|
|
38153
38332
|
}
|
|
38154
38333
|
function launchdPlan(cliPath) {
|
|
38155
38334
|
const label2 = "ai.gipity.relay";
|
|
38156
|
-
const path5 =
|
|
38157
|
-
const logDir =
|
|
38335
|
+
const path5 = join13(homedir8(), "Library", "LaunchAgents", `${label2}.plist`);
|
|
38336
|
+
const logDir = join13(homedir8(), "Library", "Logs", "Gipity");
|
|
38158
38337
|
const uid = process.getuid?.() ?? 0;
|
|
38159
38338
|
const target = `gui/${uid}`;
|
|
38160
38339
|
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -38178,8 +38357,8 @@ function launchdPlan(cliPath) {
|
|
|
38178
38357
|
<dict>
|
|
38179
38358
|
<key>SuccessfulExit</key><false/>
|
|
38180
38359
|
</dict>
|
|
38181
|
-
<key>StandardOutPath</key><string>${
|
|
38182
|
-
<key>StandardErrorPath</key><string>${
|
|
38360
|
+
<key>StandardOutPath</key><string>${join13(logDir, "relay.out.log")}</string>
|
|
38361
|
+
<key>StandardErrorPath</key><string>${join13(logDir, "relay.err.log")}</string>
|
|
38183
38362
|
<key>ProcessType</key><string>Background</string>
|
|
38184
38363
|
</dict>
|
|
38185
38364
|
</plist>
|
|
@@ -38202,7 +38381,7 @@ function launchdPlan(cliPath) {
|
|
|
38202
38381
|
}
|
|
38203
38382
|
function systemdUserPlan(cliPath) {
|
|
38204
38383
|
const unitName = "gipity-relay.service";
|
|
38205
|
-
const path5 =
|
|
38384
|
+
const path5 = join13(homedir8(), ".config", "systemd", "user", unitName);
|
|
38206
38385
|
const content = `[Unit]
|
|
38207
38386
|
Description=Gipity relay - local Claude Code control from the Gipity web CLI
|
|
38208
38387
|
After=network-online.target
|
|
@@ -38240,7 +38419,7 @@ WantedBy=default.target
|
|
|
38240
38419
|
}
|
|
38241
38420
|
function windowsTaskPlan(cliPath) {
|
|
38242
38421
|
const taskName = "GipityRelay";
|
|
38243
|
-
const path5 =
|
|
38422
|
+
const path5 = join13(homedir8(), "AppData", "Local", "Gipity", "relay-task.xml");
|
|
38244
38423
|
const runsViaNode = /\.[cm]?js$/i.test(cliPath);
|
|
38245
38424
|
const command = runsViaNode ? process.execPath : cliPath;
|
|
38246
38425
|
const argLine = runsViaNode ? `"${cliPath}" relay run` : "relay run";
|
|
@@ -38302,13 +38481,13 @@ function windowsTaskPlan(cliPath) {
|
|
|
38302
38481
|
init_platform();
|
|
38303
38482
|
init_api();
|
|
38304
38483
|
import { hostname as hostname3, userInfo, platform as osPlatform2 } from "os";
|
|
38305
|
-
import { resolve as
|
|
38484
|
+
import { resolve as resolve12, dirname as dirname8 } from "path";
|
|
38306
38485
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
38307
38486
|
|
|
38308
38487
|
// src/relay/machine-id.ts
|
|
38309
38488
|
import { createHash as createHash3 } from "crypto";
|
|
38310
38489
|
import { execFileSync } from "child_process";
|
|
38311
|
-
import { readFileSync as
|
|
38490
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
38312
38491
|
import { hostname as hostname2, platform, arch } from "os";
|
|
38313
38492
|
function rawMachineId() {
|
|
38314
38493
|
const override = process.env.GIPITY_RELAY_MACHINE_ID?.trim();
|
|
@@ -38317,7 +38496,7 @@ function rawMachineId() {
|
|
|
38317
38496
|
if (platform() === "linux") {
|
|
38318
38497
|
for (const path5 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
38319
38498
|
try {
|
|
38320
|
-
const id2 =
|
|
38499
|
+
const id2 = readFileSync15(path5, "utf-8").trim();
|
|
38321
38500
|
if (id2) return `linux:${id2}`;
|
|
38322
38501
|
} catch {
|
|
38323
38502
|
}
|
|
@@ -38364,7 +38543,7 @@ function friendlyDeviceName() {
|
|
|
38364
38543
|
return host || `My ${kind}`;
|
|
38365
38544
|
}
|
|
38366
38545
|
function resolveCliPath() {
|
|
38367
|
-
return
|
|
38546
|
+
return resolve12(process.argv[1] ?? "gipity");
|
|
38368
38547
|
}
|
|
38369
38548
|
async function pairDevice(opts = {}) {
|
|
38370
38549
|
const existing = getDevice();
|
|
@@ -38412,7 +38591,7 @@ function startDaemon() {
|
|
|
38412
38591
|
function installAutostart(opts = {}) {
|
|
38413
38592
|
const stdio = opts.stdio ?? "ignore";
|
|
38414
38593
|
const plan2 = planFor({ cliPath: resolveCliPath() });
|
|
38415
|
-
mkdirSync10(
|
|
38594
|
+
mkdirSync10(dirname8(plan2.path), { recursive: true });
|
|
38416
38595
|
writeFileSync10(plan2.path, plan2.content);
|
|
38417
38596
|
let ok = true;
|
|
38418
38597
|
for (const argv2 of plan2.enableCmds) {
|
|
@@ -38441,7 +38620,7 @@ function isWsl() {
|
|
|
38441
38620
|
if (process.platform !== "linux") return false;
|
|
38442
38621
|
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
38443
38622
|
try {
|
|
38444
|
-
return /microsoft/i.test(
|
|
38623
|
+
return /microsoft/i.test(readFileSync16("/proc/version", "utf-8"));
|
|
38445
38624
|
} catch {
|
|
38446
38625
|
return false;
|
|
38447
38626
|
}
|
|
@@ -38814,7 +38993,7 @@ function printFull(opts, outerW) {
|
|
|
38814
38993
|
import { execSync as execSync2 } from "child_process";
|
|
38815
38994
|
import { existsSync as existsSync12 } from "fs";
|
|
38816
38995
|
import { homedir as homedir10 } from "os";
|
|
38817
|
-
import { join as
|
|
38996
|
+
import { join as join14 } from "path";
|
|
38818
38997
|
var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
|
|
38819
38998
|
function claudeInstallPlan(platformOverride) {
|
|
38820
38999
|
const plat = platformOverride ?? process.platform;
|
|
@@ -38833,7 +39012,7 @@ function isClaudeInstalled(platformOverride) {
|
|
|
38833
39012
|
}
|
|
38834
39013
|
function isClaudeAuthenticated() {
|
|
38835
39014
|
if (process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY) return true;
|
|
38836
|
-
return existsSync12(
|
|
39015
|
+
return existsSync12(join14(homedir10(), ".claude", ".credentials.json"));
|
|
38837
39016
|
}
|
|
38838
39017
|
function probeClaudeAuthenticated() {
|
|
38839
39018
|
if (!isClaudeInstalled()) return false;
|
|
@@ -39002,14 +39181,14 @@ function getAdapterBySource(source) {
|
|
|
39002
39181
|
}
|
|
39003
39182
|
|
|
39004
39183
|
// src/prefs.ts
|
|
39005
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as
|
|
39184
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
39006
39185
|
import { homedir as homedir11 } from "os";
|
|
39007
|
-
import { join as
|
|
39008
|
-
var PREFS_PATH =
|
|
39186
|
+
import { join as join15 } from "path";
|
|
39187
|
+
var PREFS_PATH = join15(homedir11(), ".gipity", "prefs.json");
|
|
39009
39188
|
function readPrefs() {
|
|
39010
39189
|
if (!existsSync13(PREFS_PATH)) return {};
|
|
39011
39190
|
try {
|
|
39012
|
-
const parsed = JSON.parse(
|
|
39191
|
+
const parsed = JSON.parse(readFileSync17(PREFS_PATH, "utf-8"));
|
|
39013
39192
|
return {
|
|
39014
39193
|
lastAgent: typeof parsed.lastAgent === "string" ? parsed.lastAgent : void 0
|
|
39015
39194
|
};
|
|
@@ -39021,25 +39200,25 @@ function writePrefs(update) {
|
|
|
39021
39200
|
try {
|
|
39022
39201
|
const current = readPrefs();
|
|
39023
39202
|
const next = { ...current, ...update };
|
|
39024
|
-
mkdirSync11(
|
|
39203
|
+
mkdirSync11(join15(homedir11(), ".gipity"), { recursive: true });
|
|
39025
39204
|
writeFileSync11(PREFS_PATH, JSON.stringify(next, null, 2) + "\n");
|
|
39026
39205
|
} catch {
|
|
39027
39206
|
}
|
|
39028
39207
|
}
|
|
39029
39208
|
|
|
39030
39209
|
// src/commands/build.ts
|
|
39031
|
-
var __clDir =
|
|
39210
|
+
var __clDir = dirname9(fileURLToPath2(import.meta.url));
|
|
39032
39211
|
function readOwnPkg(fromDir) {
|
|
39033
|
-
for (let d = fromDir; ; d =
|
|
39034
|
-
const p =
|
|
39212
|
+
for (let d = fromDir; ; d = dirname9(d)) {
|
|
39213
|
+
const p = join16(d, "package.json");
|
|
39035
39214
|
if (existsSync14(p)) {
|
|
39036
39215
|
try {
|
|
39037
|
-
const pkg2 = JSON.parse(
|
|
39216
|
+
const pkg2 = JSON.parse(readFileSync18(p, "utf-8"));
|
|
39038
39217
|
if (pkg2.name === "gipity") return pkg2;
|
|
39039
39218
|
} catch {
|
|
39040
39219
|
}
|
|
39041
39220
|
}
|
|
39042
|
-
if (
|
|
39221
|
+
if (dirname9(d) === d) return {};
|
|
39043
39222
|
}
|
|
39044
39223
|
}
|
|
39045
39224
|
var __clPkg = readOwnPkg(__clDir);
|
|
@@ -39083,12 +39262,12 @@ function localFsFallback(dir) {
|
|
|
39083
39262
|
const topLevelEntries = [];
|
|
39084
39263
|
const walk = (d, depth) => {
|
|
39085
39264
|
try {
|
|
39086
|
-
for (const name of
|
|
39265
|
+
for (const name of readdirSync7(d).sort()) {
|
|
39087
39266
|
if (isSyncIgnored(name)) continue;
|
|
39088
39267
|
let isDir = false;
|
|
39089
39268
|
let size = 0;
|
|
39090
39269
|
try {
|
|
39091
|
-
const st2 = statSync7(
|
|
39270
|
+
const st2 = statSync7(join16(d, name));
|
|
39092
39271
|
isDir = st2.isDirectory();
|
|
39093
39272
|
size = st2.isFile() ? st2.size : 0;
|
|
39094
39273
|
} catch {
|
|
@@ -39097,7 +39276,7 @@ function localFsFallback(dir) {
|
|
|
39097
39276
|
if (isDir) {
|
|
39098
39277
|
folderCount++;
|
|
39099
39278
|
if (depth === 0) topLevelEntries.push(`${name}/`);
|
|
39100
|
-
walk(
|
|
39279
|
+
walk(join16(d, name), depth + 1);
|
|
39101
39280
|
} else {
|
|
39102
39281
|
fileCount++;
|
|
39103
39282
|
totalBytes += size;
|
|
@@ -39130,8 +39309,8 @@ function hasStreamJsonFlag(args) {
|
|
|
39130
39309
|
}
|
|
39131
39310
|
function markFolderTrusted(dir) {
|
|
39132
39311
|
try {
|
|
39133
|
-
const file =
|
|
39134
|
-
const cfg = existsSync14(file) ? JSON.parse(
|
|
39312
|
+
const file = join16(homedir12(), ".claude.json");
|
|
39313
|
+
const cfg = existsSync14(file) ? JSON.parse(readFileSync18(file, "utf-8")) : {};
|
|
39135
39314
|
if (typeof cfg !== "object" || cfg === null) return;
|
|
39136
39315
|
if (typeof cfg.projects !== "object" || cfg.projects === null) cfg.projects = {};
|
|
39137
39316
|
const entry = typeof cfg.projects[dir] === "object" && cfg.projects[dir] !== null ? cfg.projects[dir] : {};
|
|
@@ -39256,9 +39435,9 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39256
39435
|
let existing = getConfig();
|
|
39257
39436
|
let forceAdoptCwd = false;
|
|
39258
39437
|
if (existing && !opts.newProject && !opts.project && !nonInteractive) {
|
|
39259
|
-
const cwdHasConfig = existsSync14(
|
|
39438
|
+
const cwdHasConfig = existsSync14(resolve13(process.cwd(), ".gipity.json"));
|
|
39260
39439
|
if (!cwdHasConfig && isLikelyEmpty(process.cwd())) {
|
|
39261
|
-
const ancestorRoot =
|
|
39440
|
+
const ancestorRoot = dirname9(getConfigPath());
|
|
39262
39441
|
console.log(` ${bold("You are inside an existing Gipity project.")}
|
|
39263
39442
|
`);
|
|
39264
39443
|
console.log(` ${bold("1.")} Continue in ${brand(existing.projectSlug)} ${muted(`(rooted at ${formatCwdLabel(ancestorRoot)})`)}`);
|
|
@@ -39305,7 +39484,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39305
39484
|
}
|
|
39306
39485
|
project = found;
|
|
39307
39486
|
}
|
|
39308
|
-
const projectDir2 = opts.here ? process.cwd() :
|
|
39487
|
+
const projectDir2 = opts.here ? process.cwd() : join16(getProjectsRoot(), project.slug);
|
|
39309
39488
|
mkdirSync12(projectDir2, { recursive: true });
|
|
39310
39489
|
process.chdir(projectDir2);
|
|
39311
39490
|
clearConfigCache();
|
|
@@ -39347,7 +39526,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39347
39526
|
`);
|
|
39348
39527
|
setupProjectTools();
|
|
39349
39528
|
let doSync = true;
|
|
39350
|
-
const projectRoot =
|
|
39529
|
+
const projectRoot = dirname9(getConfigPath());
|
|
39351
39530
|
const scan = scanForAdoption(projectRoot);
|
|
39352
39531
|
if (scan.tier === "refuse" && !nonInteractive) {
|
|
39353
39532
|
const sizeStr = scan.truncated ? `>${formatBytes(ADOPT_THRESHOLDS.REFUSE_BYTES)}` : formatBytes(scan.bytes);
|
|
@@ -39428,7 +39607,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39428
39607
|
} else {
|
|
39429
39608
|
project = result.project;
|
|
39430
39609
|
isNewProject = result.kind === "create-new";
|
|
39431
|
-
const projectDir2 =
|
|
39610
|
+
const projectDir2 = join16(getProjectsRoot(), project.slug);
|
|
39432
39611
|
mkdirSync12(projectDir2, { recursive: true });
|
|
39433
39612
|
process.chdir(projectDir2);
|
|
39434
39613
|
clearConfigCache();
|
|
@@ -39810,7 +39989,7 @@ ${doneLine}
|
|
|
39810
39989
|
});
|
|
39811
39990
|
}
|
|
39812
39991
|
function flushHeadlessCapture(adapter, sessionId, convGuid) {
|
|
39813
|
-
const runner =
|
|
39992
|
+
const runner = join16(__clDir, "..", "hooks", "capture-runner.js");
|
|
39814
39993
|
if (!existsSync14(runner)) return;
|
|
39815
39994
|
const payload = JSON.stringify({ session_id: sessionId, cwd: process.cwd() });
|
|
39816
39995
|
const env = { ...process.env, GIPITY_CONVERSATION_GUID: convGuid };
|
|
@@ -39831,32 +40010,39 @@ async function pickOrCreateProject(projects, existingSlugs) {
|
|
|
39831
40010
|
const cwd = process.cwd();
|
|
39832
40011
|
const showAdopt = canAdoptCwd(cwd);
|
|
39833
40012
|
const reserved = showAdopt ? 2 : 1;
|
|
39834
|
-
const
|
|
39835
|
-
const
|
|
39836
|
-
const
|
|
40013
|
+
const fitsAll = filtered.length <= 9 - reserved;
|
|
40014
|
+
const hasMore = !fitsAll;
|
|
40015
|
+
const searchSlot = hasMore ? reserved + 1 : 0;
|
|
40016
|
+
const projectBase = reserved + (hasMore ? 1 : 0);
|
|
40017
|
+
const recent = fitsAll ? filtered : filtered.slice(0, 9 - reserved - 2);
|
|
40018
|
+
const moreSlot = hasMore ? projectBase + recent.length + 1 : 0;
|
|
39837
40019
|
while (true) {
|
|
39838
40020
|
const newProjectLabel = formatNewProjectLabel(existingSlugs);
|
|
39839
40021
|
console.log(` ${bold("Choose project to open:")}
|
|
39840
40022
|
`);
|
|
39841
|
-
console.log(` ${bold("1.")} Create new project ${muted(`(${newProjectLabel})`)}`);
|
|
40023
|
+
console.log(` ${bold("1.")} ${info("Create new project")} ${muted(`(${newProjectLabel})`)}`);
|
|
39842
40024
|
if (showAdopt) {
|
|
39843
|
-
console.log(` ${bold("2.")} Use this directory ${muted(`(${formatCwdLabel(cwd)})`)}`);
|
|
40025
|
+
console.log(` ${bold("2.")} ${info("Use this directory")} ${muted(`(${formatCwdLabel(cwd)})`)}`);
|
|
40026
|
+
}
|
|
40027
|
+
if (hasMore) {
|
|
40028
|
+
console.log(` ${bold(`${searchSlot}.`)} ${info("Search projects")} ${muted(`(${filtered.length} total)`)}`);
|
|
39844
40029
|
}
|
|
39845
40030
|
recent.forEach(
|
|
39846
|
-
(p, i) => console.log(` ${bold(`${
|
|
40031
|
+
(p, i) => console.log(` ${bold(`${projectBase + i + 1}.`)} ${p.name} ${muted(`(${p.slug})`)}`)
|
|
39847
40032
|
);
|
|
39848
40033
|
if (hasMore) {
|
|
39849
|
-
console.log(` ${bold(`${
|
|
40034
|
+
console.log(` ${bold(`${moreSlot}.`)} ${info("Show more")}`);
|
|
39850
40035
|
}
|
|
39851
40036
|
console.log("");
|
|
39852
|
-
const maxOption = recent.length +
|
|
40037
|
+
const maxOption = projectBase + recent.length + (hasMore ? 1 : 0);
|
|
39853
40038
|
const idx = await pickOne("Choose", maxOption, 1);
|
|
39854
|
-
if (idx >
|
|
39855
|
-
return { kind: "pick", project: recent[idx -
|
|
40039
|
+
if (idx > projectBase && idx <= projectBase + recent.length) {
|
|
40040
|
+
return { kind: "pick", project: recent[idx - projectBase - 1] };
|
|
39856
40041
|
}
|
|
39857
|
-
if (hasMore && idx ===
|
|
39858
|
-
const picked = await
|
|
40042
|
+
if (hasMore && (idx === searchSlot || idx === moreSlot)) {
|
|
40043
|
+
const picked = await browseProjects(filtered, { search: idx === searchSlot });
|
|
39859
40044
|
if (picked) return { kind: "pick", project: picked };
|
|
40045
|
+
console.log("");
|
|
39860
40046
|
continue;
|
|
39861
40047
|
}
|
|
39862
40048
|
if (showAdopt && idx === 2) {
|
|
@@ -39871,17 +40057,62 @@ async function pickOrCreateProject(projects, existingSlugs) {
|
|
|
39871
40057
|
return { kind: "create-new", project };
|
|
39872
40058
|
}
|
|
39873
40059
|
}
|
|
39874
|
-
|
|
40060
|
+
var PROJECT_PAGE_SIZE = 10;
|
|
40061
|
+
function searchProjects(all, query) {
|
|
40062
|
+
const q = query.trim().toLowerCase();
|
|
40063
|
+
if (!q) return all;
|
|
40064
|
+
return all.filter((p) => p.name.toLowerCase().includes(q) || p.slug.toLowerCase().includes(q));
|
|
40065
|
+
}
|
|
40066
|
+
async function browseProjects(all, opts = {}) {
|
|
40067
|
+
let items = all;
|
|
40068
|
+
let heading = "All projects";
|
|
40069
|
+
const runSearch = async () => {
|
|
40070
|
+
const q = await prompt(` ${bold("Search projects:")} `);
|
|
40071
|
+
if (!q) return false;
|
|
40072
|
+
const matches = searchProjects(all, q);
|
|
40073
|
+
if (matches.length === 0) {
|
|
40074
|
+
console.log(` ${muted(`No projects match "${q}".`)}`);
|
|
40075
|
+
return false;
|
|
40076
|
+
}
|
|
40077
|
+
items = matches;
|
|
40078
|
+
heading = `Projects matching "${q}"`;
|
|
40079
|
+
return true;
|
|
40080
|
+
};
|
|
39875
40081
|
console.log("");
|
|
39876
|
-
|
|
40082
|
+
if (opts.search && !await runSearch()) return null;
|
|
40083
|
+
let shown = 0;
|
|
40084
|
+
while (true) {
|
|
40085
|
+
if (shown < items.length) {
|
|
40086
|
+
const page = items.slice(shown, shown + PROJECT_PAGE_SIZE);
|
|
40087
|
+
if (shown === 0) console.log(` ${bold(`${heading}`)} ${muted(`(${items.length})`)}
|
|
39877
40088
|
`);
|
|
39878
|
-
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
39882
|
-
|
|
39883
|
-
|
|
39884
|
-
|
|
40089
|
+
page.forEach(
|
|
40090
|
+
(p, i) => console.log(` ${bold(`${String(shown + i + 1).padStart(3)}.`)} ${p.name} ${muted(`(${p.slug})`)}`)
|
|
40091
|
+
);
|
|
40092
|
+
shown += page.length;
|
|
40093
|
+
console.log("");
|
|
40094
|
+
}
|
|
40095
|
+
const more = shown < items.length;
|
|
40096
|
+
const hints = [
|
|
40097
|
+
more ? `Enter = next ${Math.min(PROJECT_PAGE_SIZE, items.length - shown)}` : null,
|
|
40098
|
+
"number = open",
|
|
40099
|
+
"s = search",
|
|
40100
|
+
"q = back"
|
|
40101
|
+
].filter(Boolean).join(" \xB7 ");
|
|
40102
|
+
const answer = (await prompt(` ${bold("Choose")} ${muted(`(${hints})`)}: `)).toLowerCase();
|
|
40103
|
+
if (answer === "" && more) continue;
|
|
40104
|
+
if (answer === "" || answer === "q") return null;
|
|
40105
|
+
if (answer === "s") {
|
|
40106
|
+
if (await runSearch()) {
|
|
40107
|
+
shown = 0;
|
|
40108
|
+
console.log("");
|
|
40109
|
+
}
|
|
40110
|
+
continue;
|
|
40111
|
+
}
|
|
40112
|
+
const n = parseInt(answer, 10);
|
|
40113
|
+
if (Number.isInteger(n) && n >= 1 && n <= shown) return items[n - 1];
|
|
40114
|
+
console.log(` ${muted("Type one of the numbers above, Enter for more, s to search, or q to go back.")}`);
|
|
40115
|
+
}
|
|
39885
40116
|
}
|
|
39886
40117
|
function formatNewProjectLabel(existingSlugs) {
|
|
39887
40118
|
const slug = suggestProjectName(existingSlugs);
|
|
@@ -39990,12 +40221,10 @@ var KITS = [
|
|
|
39990
40221
|
{ key: "chatbot", hint: "drop-in chatbot - persona, guardrails, streaming responses" },
|
|
39991
40222
|
{ key: "audio-align", hint: "audio + lyrics -> word-level timing JSON (GPU job)" },
|
|
39992
40223
|
{ key: "i18n", hint: "multi-language web apps - language picker, RTL, translations" },
|
|
39993
|
-
{ key: "records", hint: "registry-driven data plane - generic CRUD, validation, search, audit spine" },
|
|
39994
|
-
{ key: "views", hint: "registry-driven UI over records - table, forms, kanban" },
|
|
39995
|
-
{ key: "agent-api", hint: "named API keys for agent/script writes through the records kit" },
|
|
39996
40224
|
{ key: "contacts", hint: "multi-source contact layer - LinkedIn/Gmail import, dedupe with provenance" },
|
|
39997
40225
|
{ key: "stripe", hint: "charge your users - Stripe checkout, subscriptions, brokered webhooks" },
|
|
39998
|
-
{ key: "notify", hint: "web push notifications - platform-owned keys, works on iOS home screen" }
|
|
40226
|
+
{ key: "notify", hint: "web push notifications - platform-owned keys, works on iOS home screen" },
|
|
40227
|
+
{ key: "servicenow", hint: "ServiceNow tables as a data source - OAuth pull/write-back/real-time sync" }
|
|
39999
40228
|
];
|
|
40000
40229
|
|
|
40001
40230
|
// src/commands/add.ts
|
|
@@ -40958,7 +41187,7 @@ init_api();
|
|
|
40958
41187
|
init_config();
|
|
40959
41188
|
init_colors();
|
|
40960
41189
|
init_utils();
|
|
40961
|
-
var recordsCommand = new Command("records").description("Manage records
|
|
41190
|
+
var recordsCommand = new Command("records").description("Manage app records (Gipity Records - validated CRUD with audit history)");
|
|
40962
41191
|
recordsCommand.command("list").description("List record tables").option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
40963
41192
|
const config = requireConfig();
|
|
40964
41193
|
const res = await get(`/api/${config.projectGuid}/records-config`);
|
|
@@ -41017,6 +41246,16 @@ recordsCommand.command("get <table> <id>").description("Get a record").option("-
|
|
|
41017
41246
|
const res = await get(`/api/${config.projectGuid}/records/${table}/${id2}`);
|
|
41018
41247
|
console.log(opts.json ? JSON.stringify(res.data) : JSON.stringify(res.data, null, 2));
|
|
41019
41248
|
}));
|
|
41249
|
+
recordsCommand.command("history <table> <id>").description("Audit history for a record (who/what changed it, with English summaries)").option("--limit <n>", "Max events", "20").option("--json", "Output as JSON").action((table, id2, opts) => run("History", async () => {
|
|
41250
|
+
const config = requireConfig();
|
|
41251
|
+
const res = await get(
|
|
41252
|
+
`/api/${config.projectGuid}/records/${table}/${id2}/history?limit=${encodeURIComponent(opts.limit)}`
|
|
41253
|
+
);
|
|
41254
|
+
printList(res.data, opts, "No history for this record.", (e) => {
|
|
41255
|
+
const summary = e.detail?.summary || `${e.action} ${e.entity_type} ${e.entity_id}`;
|
|
41256
|
+
return `${muted(e.created_at)} ${bold(e.source || "-")} ${summary}`;
|
|
41257
|
+
});
|
|
41258
|
+
}));
|
|
41020
41259
|
recordsCommand.command("create <table>").description("Create a record").requiredOption("--data <json>", "JSON object with field values").option("--json", "Output as JSON").action((table, opts) => run("Create", async () => {
|
|
41021
41260
|
const config = requireConfig();
|
|
41022
41261
|
const data = JSON.parse(opts.data);
|
|
@@ -41055,7 +41294,7 @@ fnCommand.command("list").description("List functions").option("--json", "Output
|
|
|
41055
41294
|
${muted(f.description)}` : line;
|
|
41056
41295
|
});
|
|
41057
41296
|
if (!opts.json && res.data.length > 0) {
|
|
41058
|
-
console.log(muted(`Endpoint: POST ${
|
|
41297
|
+
console.log(muted(`Endpoint: POST ${resolveApiBase()}/api/${config.projectGuid}/fn/<name> (same on dev and prod; public path: gipity fn call <name> --anon)`));
|
|
41059
41298
|
}
|
|
41060
41299
|
}));
|
|
41061
41300
|
fnCommand.command("logs <name>").description("Show recent logs").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON").action((name, opts) => run("Logs", async () => {
|
|
@@ -41094,10 +41333,9 @@ async function callAnon(projectGuid, name, body) {
|
|
|
41094
41333
|
appToken ? { "X-App-Token": appToken } : void 0
|
|
41095
41334
|
);
|
|
41096
41335
|
}
|
|
41097
|
-
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 () => {
|
|
41336
|
+
fnCommand.command("call <name> [body]").description("Call a function").option("-d, --data <json>", "JSON request body: inline JSON, @file to read a file, or @- / - for stdin").option("--file <field=@path>", "Attach a file as { data, media_type } under <field> (the vision/media service shape), repeatable, e.g. --file image=@receipt.png", (v7, acc) => (acc || []).concat(v7)).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 () => {
|
|
41098
41337
|
const config = requireConfig();
|
|
41099
|
-
const
|
|
41100
|
-
const body = JSON.parse(raw);
|
|
41338
|
+
const body = resolveBody(bodyArg || opts.data, opts.file);
|
|
41101
41339
|
const path5 = `/api/${config.projectGuid}/fn/${encodeURIComponent(name)}`;
|
|
41102
41340
|
if (opts.anon) {
|
|
41103
41341
|
console.error(muted("Auth: anonymous visitor (the public path a signed-out user hits)"));
|
|
@@ -41146,8 +41384,12 @@ var SERVICES = [
|
|
|
41146
41384
|
{ name: "location/geocode", method: "POST", desc: "Reverse geocode ({ lat, lon })" }
|
|
41147
41385
|
];
|
|
41148
41386
|
var serviceCommand = new Command("service").description("Call an app service (llm, tts, image, transcribe, ...)").addHelpText("after", "\nCall a Gipity app service: LLM, image, music, TTS, and more.");
|
|
41149
|
-
serviceCommand.command("list").description("List callable app services").action(() => run("Services", async () => {
|
|
41387
|
+
serviceCommand.command("list").description("List callable app services").option("--json", "Output the service list as JSON").action((opts) => run("Services", async () => {
|
|
41150
41388
|
requireConfig();
|
|
41389
|
+
if (opts.json) {
|
|
41390
|
+
console.log(JSON.stringify(SERVICES));
|
|
41391
|
+
return;
|
|
41392
|
+
}
|
|
41151
41393
|
const width = SERVICES.reduce((m, s) => Math.max(m, s.name.length), 0);
|
|
41152
41394
|
console.log('Call one with `gipity service call <name> \'{"json":"body"}\'`');
|
|
41153
41395
|
console.log(muted("(GET endpoints like llm/models, tts/voices take --get and no body)"));
|
|
@@ -41466,10 +41708,9 @@ jobCommand.command("list").description("List jobs").option("--json", "Output as
|
|
|
41466
41708
|
${muted(j7.description)}` : line;
|
|
41467
41709
|
});
|
|
41468
41710
|
}));
|
|
41469
|
-
jobCommand.command("submit <name> [body]").description("Submit a job (returns a run guid;
|
|
41711
|
+
jobCommand.command("submit <name> [body]").description("Submit a job (returns a run guid; block on it with `job wait <guid>`)").option("-d, --data <json>", "JSON input body: inline JSON, @file to read a file, or @- / - for stdin").option("--file <field=@path>", "Attach a file as { data, media_type } under <field> (the vision/media service shape), repeatable, e.g. --file image=@scan.png", (v7, acc) => (acc || []).concat(v7)).option("--idempotency-key <key>", "Idempotency key (replays return the existing run)").option("--json", "Output as JSON").action((name, bodyArg, opts) => run("Submit", async () => {
|
|
41470
41712
|
const config = requireConfig();
|
|
41471
|
-
const
|
|
41472
|
-
const input = JSON.parse(raw);
|
|
41713
|
+
const input = resolveBody(bodyArg || opts.data, opts.file);
|
|
41473
41714
|
const body = { input };
|
|
41474
41715
|
if (opts.idempotencyKey) body.idempotency_key = opts.idempotencyKey;
|
|
41475
41716
|
const res = await post(
|
|
@@ -41481,6 +41722,10 @@ jobCommand.command("submit <name> [body]").description("Submit a job (returns a
|
|
|
41481
41722
|
} else {
|
|
41482
41723
|
const tag = res.data.replayed ? warning(" (replayed)") : "";
|
|
41483
41724
|
console.log(`${bold(res.data.run_guid)} ${muted(res.data.status)}${tag}`);
|
|
41725
|
+
const TERMINAL = /* @__PURE__ */ new Set(["success", "failed", "cancelled", "canceled", "error"]);
|
|
41726
|
+
if (!TERMINAL.has(res.data.status)) {
|
|
41727
|
+
console.error(muted(`Block until it finishes: gipity job wait ${res.data.run_guid}`));
|
|
41728
|
+
}
|
|
41484
41729
|
}
|
|
41485
41730
|
}));
|
|
41486
41731
|
jobCommand.command("status <runGuid>").description("Show status of a job run").option("--json", "Output as JSON").action((runGuid, opts) => run("Status", async () => {
|
|
@@ -41498,6 +41743,47 @@ jobCommand.command("status <runGuid>").description("Show status of a job run").o
|
|
|
41498
41743
|
if (r.error) console.log(`${error("error:")} ${r.error}`);
|
|
41499
41744
|
if (r.output) console.log(`output: ${JSON.stringify(r.output)}`);
|
|
41500
41745
|
}));
|
|
41746
|
+
jobCommand.command("wait <runGuid>").description("Block until a job run finishes, or exit at --timeout so the shell never hangs (poll again to keep waiting)").option("--timeout <seconds>", "Max seconds to wait before giving up and reporting current progress. Bare number = seconds; an explicit unit (90s) means the same here and on `sandbox run --timeout`.", "90").option("--interval <seconds>", "Seconds between polls", "3").option("--json", "Output as JSON").action((runGuid, opts) => run("Wait", async () => {
|
|
41747
|
+
const config = requireConfig();
|
|
41748
|
+
const TERMINAL = /* @__PURE__ */ new Set(["success", "failed", "cancelled", "canceled", "error"]);
|
|
41749
|
+
const durOpt = parseDuration(opts.timeout, "s");
|
|
41750
|
+
const timeoutSec = durOpt ? Math.max(1, Math.round(durOpt.value)) : parseInt(opts.timeout, 10);
|
|
41751
|
+
const timeout = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : 90;
|
|
41752
|
+
const interval = Math.max(1, parseInt(opts.interval, 10) || 3);
|
|
41753
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
41754
|
+
const fetchRun = async () => (await get(`/projects/${config.projectGuid}/jobs/runs/${encodeURIComponent(runGuid)}`)).data;
|
|
41755
|
+
let r = await fetchRun();
|
|
41756
|
+
let lastProgress = "";
|
|
41757
|
+
while (!TERMINAL.has(r.status) && Date.now() < deadline) {
|
|
41758
|
+
if (!opts.json) {
|
|
41759
|
+
const prog = r.progress_pct != null ? `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}` : r.status;
|
|
41760
|
+
if (prog !== lastProgress) {
|
|
41761
|
+
console.error(muted(`\u2026 ${prog}`));
|
|
41762
|
+
lastProgress = prog;
|
|
41763
|
+
}
|
|
41764
|
+
}
|
|
41765
|
+
const remaining = deadline - Date.now();
|
|
41766
|
+
if (remaining <= 0) break;
|
|
41767
|
+
await new Promise((res) => setTimeout(res, Math.min(interval * 1e3, remaining)));
|
|
41768
|
+
r = await fetchRun();
|
|
41769
|
+
}
|
|
41770
|
+
const done = TERMINAL.has(r.status);
|
|
41771
|
+
if (opts.json) {
|
|
41772
|
+
console.log(JSON.stringify(done ? r : { ...r, waiting: true, timed_out: true }));
|
|
41773
|
+
} else if (!done) {
|
|
41774
|
+
const prog = r.progress_pct != null ? ` ${Math.round(r.progress_pct * 100)}%` : "";
|
|
41775
|
+
console.log(`${warning("still running")}${prog}${r.progress_message ? ` (${r.progress_message})` : ""} ${muted(runGuid)}`);
|
|
41776
|
+
console.log(muted(`Not finished after ${timeout}s. Run \`gipity job wait ${runGuid}\` again to keep waiting.`));
|
|
41777
|
+
} else {
|
|
41778
|
+
const statusColor = r.status === "success" ? success : error;
|
|
41779
|
+
console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
|
|
41780
|
+
if (r.duration_ms != null) console.log(`duration: ${r.duration_ms}ms`);
|
|
41781
|
+
if (r.error) console.log(`${error("error:")} ${r.error}`);
|
|
41782
|
+
if (r.output) console.log(`output: ${JSON.stringify(r.output)}`);
|
|
41783
|
+
}
|
|
41784
|
+
if (!done) process.exit(2);
|
|
41785
|
+
if (r.status !== "success") process.exit(1);
|
|
41786
|
+
}));
|
|
41501
41787
|
jobCommand.command("runs <name>").description("List recent runs for a job").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON").action((name, opts) => run("Runs", async () => {
|
|
41502
41788
|
const config = requireConfig();
|
|
41503
41789
|
const res = await get(
|
|
@@ -41512,19 +41798,44 @@ jobCommand.command("runs <name>").description("List recent runs for a job").opti
|
|
|
41512
41798
|
${error(`error: ${r.error}`)}` : line;
|
|
41513
41799
|
});
|
|
41514
41800
|
}));
|
|
41515
|
-
jobCommand.command("logs <runGuid>").description("Stream live logs for a job run (--no-follow for a one-shot snapshot)").option("--follow", "Stream via SSE (default)", true).option("--no-follow", "One-shot snapshot of run state instead of streaming").option("--json", "Output as JSON").action((runGuid, opts) => run("Logs", async () => {
|
|
41801
|
+
jobCommand.command("logs <runGuid>").description("Stream live logs for a job run (--no-follow for a one-shot snapshot)").option("--follow", "Stream via SSE (default)", true).option("--no-follow", "One-shot snapshot of run state instead of streaming").option("--timeout <seconds>", "Stop streaming after N seconds and exit cleanly, so a bounded peek at the live log never hangs the shell (no shell `timeout` wrapper needed). Bare number = seconds; unit (20s) also accepted.").option("--json", "Output as JSON").action((runGuid, opts) => run("Logs", async () => {
|
|
41516
41802
|
const config = requireConfig();
|
|
41517
41803
|
if (!opts.follow) {
|
|
41518
41804
|
const res2 = await get(`/projects/${config.projectGuid}/jobs/runs/${encodeURIComponent(runGuid)}`);
|
|
41519
41805
|
console.log(opts.json ? JSON.stringify(res2.data) : JSON.stringify(res2.data, null, 2));
|
|
41520
41806
|
return;
|
|
41521
41807
|
}
|
|
41808
|
+
let peekSeconds = 0;
|
|
41809
|
+
if (opts.timeout != null) {
|
|
41810
|
+
const dur = parseDuration(String(opts.timeout), "s");
|
|
41811
|
+
const secs = dur ? dur.value : parseFloat(String(opts.timeout));
|
|
41812
|
+
peekSeconds = Number.isFinite(secs) && secs > 0 ? secs : 0;
|
|
41813
|
+
}
|
|
41522
41814
|
const url = `${getBaseUrl()}/projects/${config.projectGuid}/jobs/runs/${encodeURIComponent(runGuid)}/logs/stream`;
|
|
41523
41815
|
const authHeader = await getAuthHeader();
|
|
41524
41816
|
const headers = { "Accept": "text/event-stream" };
|
|
41525
41817
|
if (authHeader) headers.Authorization = authHeader;
|
|
41526
|
-
const
|
|
41818
|
+
const controller = new AbortController();
|
|
41819
|
+
let peekTimer;
|
|
41820
|
+
let peekedOut = false;
|
|
41821
|
+
if (peekSeconds > 0) {
|
|
41822
|
+
peekTimer = setTimeout(() => {
|
|
41823
|
+
peekedOut = true;
|
|
41824
|
+
controller.abort();
|
|
41825
|
+
}, peekSeconds * 1e3);
|
|
41826
|
+
}
|
|
41827
|
+
let res;
|
|
41828
|
+
try {
|
|
41829
|
+
res = await fetch(url, { headers, signal: controller.signal });
|
|
41830
|
+
} catch (e) {
|
|
41831
|
+
if (peekedOut) {
|
|
41832
|
+
console.error(muted(`\u2026 still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
|
|
41833
|
+
return;
|
|
41834
|
+
}
|
|
41835
|
+
throw e;
|
|
41836
|
+
}
|
|
41527
41837
|
if (!res.ok || !res.body) {
|
|
41838
|
+
if (peekTimer) clearTimeout(peekTimer);
|
|
41528
41839
|
console.error(error(`Failed to open stream: HTTP ${res.status}`));
|
|
41529
41840
|
process.exit(1);
|
|
41530
41841
|
}
|
|
@@ -41533,8 +41844,21 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
|
|
|
41533
41844
|
let buffer = "";
|
|
41534
41845
|
let currentEvent = "";
|
|
41535
41846
|
while (true) {
|
|
41536
|
-
|
|
41537
|
-
|
|
41847
|
+
let chunk;
|
|
41848
|
+
try {
|
|
41849
|
+
chunk = await reader.read();
|
|
41850
|
+
} catch (e) {
|
|
41851
|
+
if (peekedOut) {
|
|
41852
|
+
console.error(muted(`\u2026 still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
|
|
41853
|
+
return;
|
|
41854
|
+
}
|
|
41855
|
+
throw e;
|
|
41856
|
+
}
|
|
41857
|
+
const { done, value } = chunk;
|
|
41858
|
+
if (done) {
|
|
41859
|
+
if (peekTimer) clearTimeout(peekTimer);
|
|
41860
|
+
break;
|
|
41861
|
+
}
|
|
41538
41862
|
buffer += decoder.decode(value, { stream: true });
|
|
41539
41863
|
let nl2;
|
|
41540
41864
|
while ((nl2 = buffer.indexOf("\n")) >= 0) {
|
|
@@ -41546,9 +41870,9 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
|
|
|
41546
41870
|
const dataRaw = line.slice(5).trim();
|
|
41547
41871
|
if (currentEvent === "log") {
|
|
41548
41872
|
try {
|
|
41549
|
-
const { type, chunk } = JSON.parse(dataRaw);
|
|
41550
|
-
if (type === "stderr") process.stderr.write(error(
|
|
41551
|
-
else process.stdout.write(
|
|
41873
|
+
const { type, chunk: chunk2 } = JSON.parse(dataRaw);
|
|
41874
|
+
if (type === "stderr") process.stderr.write(error(chunk2));
|
|
41875
|
+
else process.stdout.write(chunk2);
|
|
41552
41876
|
} catch {
|
|
41553
41877
|
}
|
|
41554
41878
|
} else if (currentEvent === "status") {
|
|
@@ -41588,11 +41912,11 @@ jobCommand.command("cancel <runGuid>").description("Cancel a queued or running j
|
|
|
41588
41912
|
}
|
|
41589
41913
|
}));
|
|
41590
41914
|
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) => {
|
|
41591
|
-
const { existsSync: existsSync24, statSync:
|
|
41592
|
-
const { resolve:
|
|
41915
|
+
const { existsSync: existsSync24, statSync: statSync11 } = await import("node:fs");
|
|
41916
|
+
const { resolve: resolve19, join: join26 } = await import("node:path");
|
|
41593
41917
|
const { spawnCommand: spawnCommand2, spawnSyncCommand: spawnSyncCommand2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
41594
|
-
const jobDir =
|
|
41595
|
-
if (!existsSync24(jobDir) || !
|
|
41918
|
+
const jobDir = resolve19(process.cwd(), "jobs", name);
|
|
41919
|
+
if (!existsSync24(jobDir) || !statSync11(jobDir).isDirectory()) {
|
|
41596
41920
|
console.error(error(`jobs/${name}/ not found in current directory`));
|
|
41597
41921
|
process.exit(1);
|
|
41598
41922
|
}
|
|
@@ -41813,8 +42137,8 @@ emailCommand.command("log").description("Recent email() sends from your app").op
|
|
|
41813
42137
|
init_api();
|
|
41814
42138
|
init_config();
|
|
41815
42139
|
init_colors();
|
|
41816
|
-
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
41817
|
-
import { resolve as resolvePath2, dirname as
|
|
42140
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, readFileSync as readFileSync19 } from "fs";
|
|
42141
|
+
import { resolve as resolvePath2, dirname as dirname10, relative as relative5, isAbsolute, basename as basename5 } from "path";
|
|
41818
42142
|
|
|
41819
42143
|
// src/provider-docs.ts
|
|
41820
42144
|
var IMAGE_GEMINI_ASPECT_RATIOS = `1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9`;
|
|
@@ -41828,11 +42152,12 @@ var TTS_PROVIDER_DESCRIPTIONS = {
|
|
|
41828
42152
|
var VIDEO_MODELS_DOC = `veo-3.1-generate-preview (best quality, up to 4K, ~$0.40/sec), veo-3.1-fast-generate-preview (faster, ~$0.10/sec), veo-3.1-lite-generate-preview (budget, ~$0.05/sec), gemini-omni-flash-preview (3-10s @ 720p, conversational editing, ~$0.10/sec)`;
|
|
41829
42153
|
|
|
41830
42154
|
// src/commands/generate.ts
|
|
41831
|
-
async function downloadFile(url, filename) {
|
|
42155
|
+
async function downloadFile(url, filename, explicit) {
|
|
41832
42156
|
const res = await fetch(url);
|
|
41833
42157
|
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
41834
42158
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
41835
|
-
const outPath = correctExtension(filename, buffer);
|
|
42159
|
+
const outPath = explicit ? filename : correctExtension(filename, buffer);
|
|
42160
|
+
if (explicit) noteFormatMismatch(filename, buffer);
|
|
41836
42161
|
ensureOutputDir(outPath);
|
|
41837
42162
|
writeFileSync13(outPath, buffer);
|
|
41838
42163
|
const savedPath = resolvePath2(outPath);
|
|
@@ -41867,8 +42192,18 @@ function correctExtension(filename, buf) {
|
|
|
41867
42192
|
));
|
|
41868
42193
|
return corrected;
|
|
41869
42194
|
}
|
|
42195
|
+
function noteFormatMismatch(filename, buf) {
|
|
42196
|
+
const actual = sniffExt(buf);
|
|
42197
|
+
if (!actual) return;
|
|
42198
|
+
const dot = filename.lastIndexOf(".");
|
|
42199
|
+
const asked = dot > 0 ? filename.slice(dot + 1).toLowerCase() : "";
|
|
42200
|
+
if (!asked || canonicalExt(asked) === actual) return;
|
|
42201
|
+
console.error(muted(
|
|
42202
|
+
`Note: ${basename5(filename)} holds ${formatName(actual)} bytes though its name ends .${asked} \u2014 that's fine (browsers and image/video/edit tools sniff the bytes, not the name). Saved at the exact path you requested; reference it as-is.`
|
|
42203
|
+
));
|
|
42204
|
+
}
|
|
41870
42205
|
function ensureOutputDir(output) {
|
|
41871
|
-
const dir =
|
|
42206
|
+
const dir = dirname10(resolvePath2(output));
|
|
41872
42207
|
try {
|
|
41873
42208
|
mkdirSync13(dir, { recursive: true });
|
|
41874
42209
|
} catch (err) {
|
|
@@ -41878,7 +42213,7 @@ function ensureOutputDir(output) {
|
|
|
41878
42213
|
async function pushGenerated(savedPath) {
|
|
41879
42214
|
const configPath = getConfigPath();
|
|
41880
42215
|
if (!configPath) return;
|
|
41881
|
-
const rel2 = relative5(
|
|
42216
|
+
const rel2 = relative5(dirname10(configPath), savedPath);
|
|
41882
42217
|
if (rel2.startsWith("..") || isAbsolute(rel2)) return;
|
|
41883
42218
|
try {
|
|
41884
42219
|
await pushFile(savedPath);
|
|
@@ -41886,22 +42221,46 @@ async function pushGenerated(savedPath) {
|
|
|
41886
42221
|
console.error(muted(`Note: couldn't sync to the cloud automatically (${err.message}). Run \`gipity sync\` before referencing this file in \`gipity sandbox run\`.`));
|
|
41887
42222
|
}
|
|
41888
42223
|
}
|
|
41889
|
-
|
|
42224
|
+
function readInputImages(paths) {
|
|
42225
|
+
return paths.map((p) => {
|
|
42226
|
+
let buf;
|
|
42227
|
+
try {
|
|
42228
|
+
buf = readFileSync19(p);
|
|
42229
|
+
} catch (e) {
|
|
42230
|
+
throw new Error(`can't read --input image ${p} (${e.code || e.message}).`);
|
|
42231
|
+
}
|
|
42232
|
+
const mime = guessMime(p);
|
|
42233
|
+
if (!mime.startsWith("image/")) {
|
|
42234
|
+
throw new Error(`--input ${p} is not an image (${mime}). Edit inputs must be image files.`);
|
|
42235
|
+
}
|
|
42236
|
+
return { data: buf.toString("base64"), mime_type: mime };
|
|
42237
|
+
});
|
|
42238
|
+
}
|
|
42239
|
+
var imageCommand = new Command("image").description(`Generate an image from a text prompt, or EDIT existing images with --input.
|
|
41890
42240
|
|
|
41891
42241
|
Models: ${IMAGE_MODELS_DOC}
|
|
41892
42242
|
|
|
42243
|
+
Editing (--input): pass one or more source images and the prompt becomes an edit
|
|
42244
|
+
instruction applied to them - "make it night time", "add a hat", "remove the car
|
|
42245
|
+
in the background", or compose several inputs together. Editing routes to Gemini
|
|
42246
|
+
automatically. This is the first-party way to exercise the image-edit flow from
|
|
42247
|
+
the CLI; the CLI reads the file bytes itself, so there is no base64/argv fiddling.
|
|
42248
|
+
|
|
41893
42249
|
Gemini-specific options:
|
|
41894
42250
|
--aspect-ratio Control output shape: ${IMAGE_GEMINI_ASPECT_RATIOS}
|
|
41895
42251
|
--image-size Control resolution: ${IMAGE_GEMINI_SIZES} (default: 1K)
|
|
41896
42252
|
|
|
41897
42253
|
Examples:
|
|
41898
42254
|
gipity generate image "a cat wearing a top hat"
|
|
42255
|
+
gipity generate image "make it night time" --input photo.jpg -o edited.png
|
|
42256
|
+
gipity generate image "put the person in the first photo into the second scene" --input person.png --input scene.png
|
|
41899
42257
|
gipity generate image "landscape sunset" --provider gemini --aspect-ratio 16:9 --image-size 2K
|
|
41900
42258
|
gipity generate image "product photo" --provider openai --model gpt-image-2 --size 1536x1024 --quality high
|
|
41901
|
-
gipity generate image "abstract art" --provider bfl --model flux-2-pro -o art.png`).argument("<prompt>", "Text description of the image to
|
|
42259
|
+
gipity generate image "abstract art" --provider bfl --model flux-2-pro -o art.png`).argument("<prompt>", "Text description of the image, or (with --input) the edit instruction to apply").option("--input <file>", "Source image to edit/compose (repeatable). With --input the prompt is an edit instruction; routes to Gemini.", (v7, acc) => (acc || []).concat(v7)).option("--provider <provider>", "Image provider: openai, bfl, or gemini (default: bfl)").option("--model <model>", "Model ID (see provider list above)").option("--size <size>", 'Dimensions as WxH, e.g. "1024x1024" (OpenAI/BFL)').option("--quality <quality>", "Quality: low|medium|high|auto (gpt-image-2)").option("--aspect-ratio <ratio>", "Aspect ratio (Gemini only): 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9").option("--image-size <size>", "Output resolution (Gemini only): 512, 1K, 2K, 4K").option("--seed <n>", "Deterministic seed (BFL only): reuse one seed to keep a set of images visually coherent", (v7) => parseInt(v7, 10)).option("-o, --output <file>", "Output path (default ./generated.png). For an image your app ships, write it into the source tree so it deploys, e.g. -o src/assets/images/hero.png; the cwd default is fine for one-off generation.").option("--json", "Output as JSON").action(async (prompt2, opts) => {
|
|
41902
42260
|
try {
|
|
41903
42261
|
const { config } = await resolveProjectContext();
|
|
41904
42262
|
if (opts.output) ensureOutputDir(opts.output);
|
|
42263
|
+
const inputImages = opts.input ? readInputImages(opts.input) : void 0;
|
|
41905
42264
|
const doGenerate = () => post(`/projects/${config.projectGuid}/generate/image`, {
|
|
41906
42265
|
prompt: prompt2,
|
|
41907
42266
|
provider: opts.provider,
|
|
@@ -41910,12 +42269,14 @@ Examples:
|
|
|
41910
42269
|
quality: opts.quality,
|
|
41911
42270
|
aspect_ratio: opts.aspectRatio,
|
|
41912
42271
|
image_size: opts.imageSize,
|
|
41913
|
-
seed: Number.isFinite(opts.seed) ? opts.seed : void 0
|
|
42272
|
+
seed: Number.isFinite(opts.seed) ? opts.seed : void 0,
|
|
42273
|
+
input_images: inputImages
|
|
41914
42274
|
});
|
|
41915
|
-
const
|
|
42275
|
+
const verb = inputImages ? "Editing image\u2026" : "Generating image\u2026";
|
|
42276
|
+
const result = opts.json ? await doGenerate() : await withSpinner(verb, doGenerate, { done: null });
|
|
41916
42277
|
const ext = result.content_type.includes("png") ? "png" : "jpg";
|
|
41917
42278
|
const filename = opts.output || `generated.${ext}`;
|
|
41918
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42279
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
41919
42280
|
if (opts.json) {
|
|
41920
42281
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
41921
42282
|
} else {
|
|
@@ -41959,7 +42320,7 @@ Examples:
|
|
|
41959
42320
|
});
|
|
41960
42321
|
const result = opts.json ? await doGenerate() : await withSpinner("Generating video\u2026", doGenerate, { done: null });
|
|
41961
42322
|
const filename = opts.output || "generated.mp4";
|
|
41962
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42323
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
41963
42324
|
if (opts.json) {
|
|
41964
42325
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
41965
42326
|
} else {
|
|
@@ -42007,7 +42368,7 @@ Examples:
|
|
|
42007
42368
|
});
|
|
42008
42369
|
const result = opts.json ? await doGenerate() : await withSpinner("Generating speech\u2026", doGenerate, { done: null });
|
|
42009
42370
|
const filename = opts.output || "speech.mp3";
|
|
42010
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42371
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42011
42372
|
if (opts.json) {
|
|
42012
42373
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
42013
42374
|
} else {
|
|
@@ -42046,7 +42407,7 @@ Examples:
|
|
|
42046
42407
|
});
|
|
42047
42408
|
const result = opts.json ? await doGenerate() : await withSpinner("Generating music\u2026", doGenerate, { done: null });
|
|
42048
42409
|
const filename = opts.output || "music.mp3";
|
|
42049
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42410
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42050
42411
|
if (opts.json) {
|
|
42051
42412
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
42052
42413
|
} else {
|
|
@@ -42084,7 +42445,7 @@ Examples:
|
|
|
42084
42445
|
});
|
|
42085
42446
|
const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect\u2026", doGenerate, { done: null });
|
|
42086
42447
|
const filename = opts.output || "sound.mp3";
|
|
42087
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42448
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42088
42449
|
if (opts.json) {
|
|
42089
42450
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
42090
42451
|
} else {
|
|
@@ -42103,14 +42464,14 @@ var generateCommand = new Command("generate").description("Generate images, vide
|
|
|
42103
42464
|
init_api();
|
|
42104
42465
|
init_config();
|
|
42105
42466
|
init_colors();
|
|
42106
|
-
import { existsSync as existsSync15, readFileSync as
|
|
42107
|
-
import { join as
|
|
42467
|
+
import { existsSync as existsSync15, readFileSync as readFileSync20 } from "fs";
|
|
42468
|
+
import { join as join17 } from "path";
|
|
42108
42469
|
function readInstalledKitReadme(name) {
|
|
42109
42470
|
const root = getProjectRoot() ?? process.cwd();
|
|
42110
|
-
const kitDir =
|
|
42111
|
-
if (!existsSync15(
|
|
42112
|
-
const readme =
|
|
42113
|
-
return existsSync15(readme) ?
|
|
42471
|
+
const kitDir = join17(root, "src", "packages", name);
|
|
42472
|
+
if (!existsSync15(join17(kitDir, "package.json"))) return null;
|
|
42473
|
+
const readme = join17(kitDir, "README.md");
|
|
42474
|
+
return existsSync15(readme) ? readFileSync20(readme, "utf-8") : null;
|
|
42114
42475
|
}
|
|
42115
42476
|
var skillCommand = new Command("skill").description("Task docs - read the matching skill before building");
|
|
42116
42477
|
skillCommand.command("list").description("List skills").option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
@@ -42284,14 +42645,14 @@ var domainCommand = new Command("domain").description("Manage custom domains").a
|
|
|
42284
42645
|
|
|
42285
42646
|
// src/commands/realtime.ts
|
|
42286
42647
|
import { existsSync as existsSync16 } from "fs";
|
|
42287
|
-
import { dirname as
|
|
42648
|
+
import { dirname as dirname11, resolve as resolve14 } from "path";
|
|
42288
42649
|
init_api();
|
|
42289
42650
|
init_config();
|
|
42290
42651
|
init_colors();
|
|
42291
42652
|
function hasDeployManifest() {
|
|
42292
42653
|
const cfgPath = getConfigPath();
|
|
42293
42654
|
if (!cfgPath) return false;
|
|
42294
|
-
return existsSync16(
|
|
42655
|
+
return existsSync16(resolve14(dirname11(cfgPath), "gipity.yaml"));
|
|
42295
42656
|
}
|
|
42296
42657
|
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 () => {
|
|
42297
42658
|
const config = requireConfig();
|
|
@@ -42391,6 +42752,16 @@ function printSkippedCandidates(skipped) {
|
|
|
42391
42752
|
if (skipped.length > 10) console.log(muted(` ... and ${skipped.length - 10} more`));
|
|
42392
42753
|
console.log(muted('Run frontend/kit module tests in the sandbox: gipity sandbox run bash "node <file>"'));
|
|
42393
42754
|
}
|
|
42755
|
+
function undeployedFunctionsFromFailures(results) {
|
|
42756
|
+
const names = /* @__PURE__ */ new Set();
|
|
42757
|
+
for (const r of results) {
|
|
42758
|
+
if (r.status !== "failed" || !r.error) continue;
|
|
42759
|
+
if (!/404\b/.test(r.error) || !/not\s+found/i.test(r.error)) continue;
|
|
42760
|
+
const m = r.error.match(/Function ['"]?([\w-]+)['"]? (?:not found|failed)/i);
|
|
42761
|
+
if (m) names.add(m[1]);
|
|
42762
|
+
}
|
|
42763
|
+
return [...names];
|
|
42764
|
+
}
|
|
42394
42765
|
var LONG_RUN_MS = 6e4;
|
|
42395
42766
|
async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
42396
42767
|
const startTime = Date.now();
|
|
@@ -42467,7 +42838,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
42467
42838
|
}
|
|
42468
42839
|
}
|
|
42469
42840
|
}
|
|
42470
|
-
await new Promise((
|
|
42841
|
+
await new Promise((resolve19) => setTimeout(resolve19, getPollInterval()));
|
|
42471
42842
|
}
|
|
42472
42843
|
}
|
|
42473
42844
|
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 () => {
|
|
@@ -42532,6 +42903,15 @@ var testCommand = new Command("test").description("Run tests").argument("[path]"
|
|
|
42532
42903
|
}
|
|
42533
42904
|
console.log("");
|
|
42534
42905
|
}
|
|
42906
|
+
const undeployed = undeployedFunctionsFromFailures(data.results);
|
|
42907
|
+
if (undeployed.length > 0) {
|
|
42908
|
+
const list = undeployed.map((n) => `'${n}'`).join(", ");
|
|
42909
|
+
console.log(warning(
|
|
42910
|
+
`${undeployed.length === 1 ? "Function" : "Functions"} ${list} 404'd: tests call functions against the DEPLOYED build, and ${undeployed.length === 1 ? "this one is" : "these are"} not deployed yet.`
|
|
42911
|
+
));
|
|
42912
|
+
console.log(muted("Deploy first, then re-run: gipity deploy dev && gipity test"));
|
|
42913
|
+
console.log("");
|
|
42914
|
+
}
|
|
42535
42915
|
const parts = [];
|
|
42536
42916
|
if (data.passed > 0) parts.push(success(`${data.passed} passed`));
|
|
42537
42917
|
if (data.failed > 0) parts.push(error(`${data.failed} failed`));
|
|
@@ -42681,21 +43061,21 @@ var locationCommand = new Command("location").description("Show location").argum
|
|
|
42681
43061
|
}));
|
|
42682
43062
|
|
|
42683
43063
|
// src/commands/doctor.ts
|
|
42684
|
-
import { existsSync as existsSync18, readFileSync as
|
|
42685
|
-
import { join as
|
|
43064
|
+
import { existsSync as existsSync18, readFileSync as readFileSync22, statSync as statSync8 } from "fs";
|
|
43065
|
+
import { join as join19, resolve as resolve15 } from "path";
|
|
42686
43066
|
import { homedir as homedir14 } from "os";
|
|
42687
43067
|
|
|
42688
43068
|
// src/updater/state.ts
|
|
42689
|
-
import { readFileSync as
|
|
42690
|
-
import { join as
|
|
43069
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync17 } from "fs";
|
|
43070
|
+
import { join as join18 } from "path";
|
|
42691
43071
|
import { homedir as homedir13 } from "os";
|
|
42692
|
-
var GIPITY_DIR =
|
|
42693
|
-
var LOCAL_DIR =
|
|
42694
|
-
var LOCAL_PKG_DIR =
|
|
42695
|
-
var LOCAL_ENTRY =
|
|
42696
|
-
var STATE_FILE =
|
|
42697
|
-
var SETTINGS_FILE =
|
|
42698
|
-
var UPDATE_LOG =
|
|
43072
|
+
var GIPITY_DIR = join18(homedir13(), ".gipity");
|
|
43073
|
+
var LOCAL_DIR = join18(GIPITY_DIR, "local");
|
|
43074
|
+
var LOCAL_PKG_DIR = join18(LOCAL_DIR, "node_modules", "gipity");
|
|
43075
|
+
var LOCAL_ENTRY = join18(LOCAL_PKG_DIR, "dist", "index.js");
|
|
43076
|
+
var STATE_FILE = join18(GIPITY_DIR, "update-state.json");
|
|
43077
|
+
var SETTINGS_FILE = join18(GIPITY_DIR, "settings.json");
|
|
43078
|
+
var UPDATE_LOG = join18(GIPITY_DIR, "update.log");
|
|
42699
43079
|
var DEFAULT_STATE = {
|
|
42700
43080
|
installedVersion: null,
|
|
42701
43081
|
lastCheckAt: 0,
|
|
@@ -42711,7 +43091,7 @@ function ensureDir() {
|
|
|
42711
43091
|
function readState() {
|
|
42712
43092
|
if (!existsSync17(STATE_FILE)) return { ...DEFAULT_STATE };
|
|
42713
43093
|
try {
|
|
42714
|
-
return { ...DEFAULT_STATE, ...JSON.parse(
|
|
43094
|
+
return { ...DEFAULT_STATE, ...JSON.parse(readFileSync21(STATE_FILE, "utf-8")) };
|
|
42715
43095
|
} catch {
|
|
42716
43096
|
return { ...DEFAULT_STATE };
|
|
42717
43097
|
}
|
|
@@ -42723,7 +43103,7 @@ function writeState(state) {
|
|
|
42723
43103
|
function readSettings() {
|
|
42724
43104
|
if (!existsSync17(SETTINGS_FILE)) return { ...DEFAULT_SETTINGS };
|
|
42725
43105
|
try {
|
|
42726
|
-
return { ...DEFAULT_SETTINGS, ...JSON.parse(
|
|
43106
|
+
return { ...DEFAULT_SETTINGS, ...JSON.parse(readFileSync21(SETTINGS_FILE, "utf-8")) };
|
|
42727
43107
|
} catch {
|
|
42728
43108
|
return { ...DEFAULT_SETTINGS };
|
|
42729
43109
|
}
|
|
@@ -42756,10 +43136,10 @@ function versionManagerNode(execPath, env = process.env, home = homedir14()) {
|
|
|
42756
43136
|
return null;
|
|
42757
43137
|
}
|
|
42758
43138
|
function localVersion() {
|
|
42759
|
-
const pkgPath =
|
|
43139
|
+
const pkgPath = join19(LOCAL_PKG_DIR, "package.json");
|
|
42760
43140
|
if (!existsSync18(pkgPath)) return null;
|
|
42761
43141
|
try {
|
|
42762
|
-
return JSON.parse(
|
|
43142
|
+
return JSON.parse(readFileSync22(pkgPath, "utf-8")).version ?? null;
|
|
42763
43143
|
} catch {
|
|
42764
43144
|
return null;
|
|
42765
43145
|
}
|
|
@@ -42767,7 +43147,7 @@ function localVersion() {
|
|
|
42767
43147
|
function shimVersion() {
|
|
42768
43148
|
try {
|
|
42769
43149
|
const url = new URL("../../package.json", import.meta.url);
|
|
42770
|
-
return JSON.parse(
|
|
43150
|
+
return JSON.parse(readFileSync22(url, "utf-8")).version;
|
|
42771
43151
|
} catch {
|
|
42772
43152
|
return "unknown";
|
|
42773
43153
|
}
|
|
@@ -42850,7 +43230,7 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
42850
43230
|
console.log(`${muted("claude code ")} installed ${yn(env.claude.installed)} \xB7 authenticated ${yn(env.claude.authenticated)}`);
|
|
42851
43231
|
const autostartLabel = env.relay.autostart === null ? muted("n/a") : yn(env.relay.autostart);
|
|
42852
43232
|
console.log(`${muted("relay ")} paired ${yn(env.relay.paired)} \xB7 running ${yn(env.relay.running)} \xB7 autostart ${autostartLabel}${env.relay.paused ? warning(" \xB7 paused") : ""}${env.relay.device ? muted(` (${env.relay.device.name})`) : ""}`);
|
|
42853
|
-
if (existsSync18(
|
|
43233
|
+
if (existsSync18(resolve15(process.cwd(), ".codex", "hooks.json"))) {
|
|
42854
43234
|
console.log(`${muted("codex hooks ")} ${warning("written")} - session capture needs a one-time approval: run /hooks inside Codex in this project`);
|
|
42855
43235
|
}
|
|
42856
43236
|
console.log(`${muted("ready ")} ${env.ready ? success("yes") : warning("no - run `gipity build` (or the desktop app) to finish setup")}`);
|
|
@@ -42876,8 +43256,62 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
42876
43256
|
});
|
|
42877
43257
|
|
|
42878
43258
|
// src/updater/check.ts
|
|
42879
|
-
import { appendFileSync
|
|
43259
|
+
import { appendFileSync } from "fs";
|
|
43260
|
+
|
|
43261
|
+
// src/updater/install.ts
|
|
43262
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync15, rmSync as rmSync2, statSync as statSync9, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
|
|
43263
|
+
import { dirname as dirname12, join as join20 } from "path";
|
|
42880
43264
|
init_platform();
|
|
43265
|
+
var UPDATE_LOCK = join20(GIPITY_DIR, "update.lock");
|
|
43266
|
+
var LOCK_STALE_MS2 = 10 * 60 * 1e3;
|
|
43267
|
+
function npmInstallGipity(version) {
|
|
43268
|
+
const res = spawnSyncCommand(resolveCommand("npm"), ["install", "--no-audit", "--no-fund", "--ignore-scripts", `gipity@${version}`], {
|
|
43269
|
+
cwd: LOCAL_DIR,
|
|
43270
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
43271
|
+
encoding: "utf-8"
|
|
43272
|
+
});
|
|
43273
|
+
return {
|
|
43274
|
+
ok: res.status === 0 && existsSync19(LOCAL_ENTRY),
|
|
43275
|
+
status: res.status,
|
|
43276
|
+
stderr: (res.stderr || "").toString(),
|
|
43277
|
+
spawnError: res.error
|
|
43278
|
+
};
|
|
43279
|
+
}
|
|
43280
|
+
var WEDGE_CODES = /\b(ENOTEMPTY|EEXIST|ENOTDIR|EISDIR|EJSONPARSE)\b/;
|
|
43281
|
+
function isWedged(stderr) {
|
|
43282
|
+
return WEDGE_CODES.test(stderr);
|
|
43283
|
+
}
|
|
43284
|
+
function resetLocalTree(dir = LOCAL_DIR) {
|
|
43285
|
+
mkdirSync15(dir, { recursive: true });
|
|
43286
|
+
rmSync2(join20(dir, "node_modules"), { recursive: true, force: true });
|
|
43287
|
+
rmSync2(join20(dir, "package-lock.json"), { force: true });
|
|
43288
|
+
writeFileSync15(join20(dir, "package.json"), JSON.stringify({ name: "gipity-local", private: true, version: "0.0.0" }, null, 2));
|
|
43289
|
+
}
|
|
43290
|
+
function acquireUpdateLock(lockPath2 = UPDATE_LOCK) {
|
|
43291
|
+
mkdirSync15(dirname12(lockPath2), { recursive: true });
|
|
43292
|
+
try {
|
|
43293
|
+
writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
|
|
43294
|
+
return true;
|
|
43295
|
+
} catch {
|
|
43296
|
+
try {
|
|
43297
|
+
if (Date.now() - statSync9(lockPath2).mtimeMs > LOCK_STALE_MS2) {
|
|
43298
|
+
unlinkSync5(lockPath2);
|
|
43299
|
+
writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
|
|
43300
|
+
return true;
|
|
43301
|
+
}
|
|
43302
|
+
} catch {
|
|
43303
|
+
}
|
|
43304
|
+
return false;
|
|
43305
|
+
}
|
|
43306
|
+
}
|
|
43307
|
+
function releaseUpdateLock(lockPath2 = UPDATE_LOCK) {
|
|
43308
|
+
try {
|
|
43309
|
+
unlinkSync5(lockPath2);
|
|
43310
|
+
} catch {
|
|
43311
|
+
}
|
|
43312
|
+
}
|
|
43313
|
+
|
|
43314
|
+
// src/updater/check.ts
|
|
42881
43315
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
42882
43316
|
function log(line) {
|
|
42883
43317
|
try {
|
|
@@ -42906,12 +43340,24 @@ async function fetchLatestVersion() {
|
|
|
42906
43340
|
return json.version;
|
|
42907
43341
|
}
|
|
42908
43342
|
function installVersion(version) {
|
|
42909
|
-
|
|
42910
|
-
|
|
42911
|
-
|
|
42912
|
-
|
|
42913
|
-
|
|
42914
|
-
|
|
43343
|
+
let res = npmInstallGipity(version);
|
|
43344
|
+
if (!res.ok && !res.spawnError && isWedged(res.stderr)) {
|
|
43345
|
+
log(`wedged install tree detected; wiping node_modules and retrying:
|
|
43346
|
+
${res.stderr.trim().slice(-2e3)}`);
|
|
43347
|
+
resetLocalTree();
|
|
43348
|
+
res = npmInstallGipity(version);
|
|
43349
|
+
if (res.ok) log("clean reinstall succeeded");
|
|
43350
|
+
}
|
|
43351
|
+
if (res.ok) return { ok: true };
|
|
43352
|
+
if (res.spawnError) {
|
|
43353
|
+
log(`npm spawn failed: ${res.spawnError.message}`);
|
|
43354
|
+
return { ok: false, detail: res.spawnError.message };
|
|
43355
|
+
}
|
|
43356
|
+
if (res.stderr.trim()) log(`npm stderr (exit ${res.status}):
|
|
43357
|
+
${res.stderr.trim().slice(-2e3)}`);
|
|
43358
|
+
if (res.status === 0) return { ok: false, detail: "npm succeeded but the installed package is missing dist/index.js" };
|
|
43359
|
+
const firstLine = res.stderr.split("\n").map((l7) => l7.trim()).find((l7) => l7.length > 0) || `npm exit ${res.status}`;
|
|
43360
|
+
return { ok: false, detail: firstLine.length > 160 ? firstLine.slice(0, 157) + "..." : firstLine };
|
|
42915
43361
|
}
|
|
42916
43362
|
async function runCheck(opts = {}) {
|
|
42917
43363
|
const state = readState();
|
|
@@ -42944,17 +43390,27 @@ async function runCheck(opts = {}) {
|
|
|
42944
43390
|
log(`up-to-date (current=${current}, latest=${latest})`);
|
|
42945
43391
|
return { updated: false, reason: "up-to-date" };
|
|
42946
43392
|
}
|
|
43393
|
+
if (!acquireUpdateLock()) {
|
|
43394
|
+
log("skipped: another update is already in progress");
|
|
43395
|
+
return { updated: false, reason: "another update is already in progress" };
|
|
43396
|
+
}
|
|
42947
43397
|
log(`upgrading ${current} \u2192 ${latest}`);
|
|
42948
|
-
const ok = installVersion(latest);
|
|
42949
43398
|
state.lastCheckAt = Date.now();
|
|
42950
|
-
|
|
43399
|
+
writeState(state);
|
|
43400
|
+
let install;
|
|
43401
|
+
try {
|
|
43402
|
+
install = installVersion(latest);
|
|
43403
|
+
} finally {
|
|
43404
|
+
releaseUpdateLock();
|
|
43405
|
+
}
|
|
43406
|
+
if (install.ok) {
|
|
42951
43407
|
state.installedVersion = latest;
|
|
42952
43408
|
state.lastError = null;
|
|
42953
43409
|
writeState(state);
|
|
42954
43410
|
log(`upgraded to ${latest}`);
|
|
42955
43411
|
return { updated: true, from: current, to: latest };
|
|
42956
43412
|
}
|
|
42957
|
-
state.lastError = `npm install gipity@${latest} failed
|
|
43413
|
+
state.lastError = `npm install gipity@${latest} failed` + (install.detail ? `: ${install.detail}` : "");
|
|
42958
43414
|
writeState(state);
|
|
42959
43415
|
log(state.lastError);
|
|
42960
43416
|
return { updated: false, reason: state.lastError };
|
|
@@ -42978,6 +43434,10 @@ var updateCommand = new Command("update").description("Update the CLI").action(a
|
|
|
42978
43434
|
console.log(success("Already on the latest version."));
|
|
42979
43435
|
} else {
|
|
42980
43436
|
console.log(warning(`No update applied: ${result.reason}`));
|
|
43437
|
+
if (result.reason?.startsWith("npm install")) {
|
|
43438
|
+
console.log(dim("Full npm output: ~/.gipity/update.log"));
|
|
43439
|
+
console.log(dim("If it keeps failing, delete ~/.gipity/local and run any gipity command to reinstall."));
|
|
43440
|
+
}
|
|
42981
43441
|
}
|
|
42982
43442
|
});
|
|
42983
43443
|
|
|
@@ -42985,13 +43445,13 @@ var updateCommand = new Command("update").description("Update the CLI").action(a
|
|
|
42985
43445
|
init_api();
|
|
42986
43446
|
init_utils();
|
|
42987
43447
|
init_colors();
|
|
42988
|
-
import { existsSync as existsSync22, readFileSync as
|
|
43448
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25, unlinkSync as unlinkSync8 } from "fs";
|
|
42989
43449
|
import { spawn as spawn2 } from "child_process";
|
|
42990
43450
|
|
|
42991
43451
|
// src/relay/daemon.ts
|
|
42992
43452
|
init_platform();
|
|
42993
43453
|
init_config();
|
|
42994
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
43454
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync17, existsSync as existsSync21, readFileSync as readFileSync24, writeFileSync as writeFileSync16, chmodSync as chmodSync3, closeSync as closeSync4, openSync as openSync4, unlinkSync as unlinkSync7 } from "fs";
|
|
42995
43455
|
import { stat, readFile } from "fs/promises";
|
|
42996
43456
|
import { createInterface as createInterface2 } from "readline";
|
|
42997
43457
|
import { homedir as homedir16, hostname as hostname5, platform as osPlatform3, loadavg as loadavg2, freemem as freemem2, totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
@@ -43751,7 +44211,7 @@ async function revokeRelayAgentToken() {
|
|
|
43751
44211
|
init_platform();
|
|
43752
44212
|
init_client_context();
|
|
43753
44213
|
import { cpus, freemem, totalmem, loadavg, release, uptime, homedir as homedir15 } from "os";
|
|
43754
|
-
import { statfsSync, readdirSync as
|
|
44214
|
+
import { statfsSync, readdirSync as readdirSync8 } from "fs";
|
|
43755
44215
|
import { join as join21 } from "path";
|
|
43756
44216
|
var PROBE_TIMEOUT_MS = 4e3;
|
|
43757
44217
|
function parseVersion(out) {
|
|
@@ -43759,13 +44219,13 @@ function parseVersion(out) {
|
|
|
43759
44219
|
return m ? m[0] : void 0;
|
|
43760
44220
|
}
|
|
43761
44221
|
function runProbe(cmd, args) {
|
|
43762
|
-
return new Promise((
|
|
44222
|
+
return new Promise((resolve19) => {
|
|
43763
44223
|
let out = "";
|
|
43764
44224
|
let settled = false;
|
|
43765
44225
|
const done = (s) => {
|
|
43766
44226
|
if (!settled) {
|
|
43767
44227
|
settled = true;
|
|
43768
|
-
|
|
44228
|
+
resolve19(s);
|
|
43769
44229
|
}
|
|
43770
44230
|
};
|
|
43771
44231
|
let child;
|
|
@@ -43833,7 +44293,7 @@ function diskUsage() {
|
|
|
43833
44293
|
}
|
|
43834
44294
|
function localProjectCount() {
|
|
43835
44295
|
try {
|
|
43836
|
-
return
|
|
44296
|
+
return readdirSync8(join21(homedir15(), "GipityProjects"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
|
|
43837
44297
|
} catch {
|
|
43838
44298
|
return void 0;
|
|
43839
44299
|
}
|
|
@@ -43964,8 +44424,8 @@ var SessionPool = class {
|
|
|
43964
44424
|
const done = new Promise((r) => {
|
|
43965
44425
|
markDone = r;
|
|
43966
44426
|
});
|
|
43967
|
-
return new Promise((
|
|
43968
|
-
const turn = { onMessage: p.onMessage, resolve:
|
|
44427
|
+
return new Promise((resolve19, reject) => {
|
|
44428
|
+
const turn = { onMessage: p.onMessage, resolve: resolve19, reject, interruptRequested: false, wasHot, done, markDone };
|
|
43969
44429
|
session.current = turn;
|
|
43970
44430
|
session.state = "running";
|
|
43971
44431
|
session.lastActivityAt = Date.now();
|
|
@@ -44260,7 +44720,7 @@ function log2(level, msg, extra) {
|
|
|
44260
44720
|
process.stderr.write(pretty + "\n");
|
|
44261
44721
|
try {
|
|
44262
44722
|
const dir = join23(homedir16(), ".gipity");
|
|
44263
|
-
|
|
44723
|
+
mkdirSync17(dir, { recursive: true });
|
|
44264
44724
|
lockLogPerms(dir, RELAY_LOG_PATH);
|
|
44265
44725
|
const json = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...extra ?? {} });
|
|
44266
44726
|
appendFileSync3(RELAY_LOG_PATH, json + "\n");
|
|
@@ -44384,10 +44844,10 @@ async function heartbeatLoop(ctx) {
|
|
|
44384
44844
|
}
|
|
44385
44845
|
await Promise.race([
|
|
44386
44846
|
sleep4(HEARTBEAT_INTERVAL_MS, ctx.abort.signal),
|
|
44387
|
-
new Promise((
|
|
44847
|
+
new Promise((resolve19) => {
|
|
44388
44848
|
heartbeatPoke = () => {
|
|
44389
44849
|
heartbeatPoke = null;
|
|
44390
|
-
|
|
44850
|
+
resolve19();
|
|
44391
44851
|
};
|
|
44392
44852
|
})
|
|
44393
44853
|
]);
|
|
@@ -44431,9 +44891,9 @@ async function dispatchLoop(ctx, opts) {
|
|
|
44431
44891
|
while (inflight.size >= MAX_CONCURRENT_DISPATCHES && !ctx.abort.signal.aborted) {
|
|
44432
44892
|
await Promise.race([
|
|
44433
44893
|
...inflight,
|
|
44434
|
-
new Promise((
|
|
44435
|
-
if (ctx.abort.signal.aborted) return
|
|
44436
|
-
ctx.abort.signal.addEventListener("abort", () =>
|
|
44894
|
+
new Promise((resolve19) => {
|
|
44895
|
+
if (ctx.abort.signal.aborted) return resolve19();
|
|
44896
|
+
ctx.abort.signal.addEventListener("abort", () => resolve19(), { once: true });
|
|
44437
44897
|
})
|
|
44438
44898
|
]);
|
|
44439
44899
|
}
|
|
@@ -45118,7 +45578,7 @@ async function resolveCwdForProject(d) {
|
|
|
45118
45578
|
const configPath = join23(path5, ".gipity.json");
|
|
45119
45579
|
if (existsSync21(configPath)) {
|
|
45120
45580
|
try {
|
|
45121
|
-
const cfg = JSON.parse(
|
|
45581
|
+
const cfg = JSON.parse(readFileSync24(configPath, "utf-8"));
|
|
45122
45582
|
if (cfg.projectGuid === d.project_guid) return { cwd: path5, bootstrapped: false };
|
|
45123
45583
|
log2("warn", "project dir exists but guid mismatch - using it anyway", {
|
|
45124
45584
|
path: path5,
|
|
@@ -45130,9 +45590,9 @@ async function resolveCwdForProject(d) {
|
|
|
45130
45590
|
}
|
|
45131
45591
|
}
|
|
45132
45592
|
log2("info", "bootstrapping new project dir", { slug: d.project_slug, path: path5 });
|
|
45133
|
-
|
|
45593
|
+
mkdirSync17(path5, { recursive: true });
|
|
45134
45594
|
const apiBase2 = getApiBaseOverride() || DEFAULT_API_BASE;
|
|
45135
|
-
|
|
45595
|
+
writeFileSync16(configPath, JSON.stringify({
|
|
45136
45596
|
projectGuid: d.project_guid,
|
|
45137
45597
|
projectSlug: d.project_slug,
|
|
45138
45598
|
accountSlug: d.account_slug,
|
|
@@ -45209,7 +45669,7 @@ async function killRunningForConv(convGuid) {
|
|
|
45209
45669
|
}
|
|
45210
45670
|
}
|
|
45211
45671
|
const graceTimers = [];
|
|
45212
|
-
const escalate = new Promise((
|
|
45672
|
+
const escalate = new Promise((resolve19) => {
|
|
45213
45673
|
const t = setTimeout(() => {
|
|
45214
45674
|
for (const e of matches) {
|
|
45215
45675
|
log2("warn", "previous dispatch ignored SIGTERM - escalating to SIGKILL", { conv: convGuid });
|
|
@@ -45218,7 +45678,7 @@ async function killRunningForConv(convGuid) {
|
|
|
45218
45678
|
} catch {
|
|
45219
45679
|
}
|
|
45220
45680
|
}
|
|
45221
|
-
|
|
45681
|
+
resolve19();
|
|
45222
45682
|
}, KILL_GRACE_MS);
|
|
45223
45683
|
graceTimers.push(t);
|
|
45224
45684
|
});
|
|
@@ -45229,7 +45689,7 @@ async function killRunningForConv(convGuid) {
|
|
|
45229
45689
|
}
|
|
45230
45690
|
async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
45231
45691
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
45232
|
-
return new Promise((
|
|
45692
|
+
return new Promise((resolve19, reject) => {
|
|
45233
45693
|
const child = spawnCommand(cmd, ["sync", "--json"], {
|
|
45234
45694
|
cwd,
|
|
45235
45695
|
env: childEnv(),
|
|
@@ -45253,7 +45713,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
45253
45713
|
} catch {
|
|
45254
45714
|
}
|
|
45255
45715
|
try {
|
|
45256
|
-
|
|
45716
|
+
unlinkSync7(join23(cwd, ".gipity", "sync.lock"));
|
|
45257
45717
|
} catch {
|
|
45258
45718
|
}
|
|
45259
45719
|
finish(() => reject(new Error(`timed out after ${Math.round(timeoutMs / 1e3)}s`)));
|
|
@@ -45269,7 +45729,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
45269
45729
|
child.on("exit", (code) => finish(() => {
|
|
45270
45730
|
if (code === 0) {
|
|
45271
45731
|
log2("info", "sync done", { cwd, stdoutLen });
|
|
45272
|
-
|
|
45732
|
+
resolve19();
|
|
45273
45733
|
} else {
|
|
45274
45734
|
reject(new Error(`gipity sync exited ${code}${stderrBuf ? `: ${stderrBuf.trim().slice(0, 300)}` : ""}`));
|
|
45275
45735
|
}
|
|
@@ -45281,7 +45741,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
45281
45741
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
45282
45742
|
const fullArgs = streamJson ? [...args, "--output-format", "stream-json", "--verbose", "--include-partial-messages"] : [...args];
|
|
45283
45743
|
const env = streamJson ? childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid, GIPITY_CAPTURE: "off" }) : childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid });
|
|
45284
|
-
return new Promise((
|
|
45744
|
+
return new Promise((resolve19, reject) => {
|
|
45285
45745
|
const child = spawnCommand(cmd, fullArgs, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
45286
45746
|
let resolveExited = () => {
|
|
45287
45747
|
};
|
|
@@ -45461,7 +45921,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
45461
45921
|
void postProgress(d.conversation_guid, buildProgressPayload(false));
|
|
45462
45922
|
if (ownQueue) await q.close();
|
|
45463
45923
|
cleanup();
|
|
45464
|
-
|
|
45924
|
+
resolve19({
|
|
45465
45925
|
exitCode: code ?? 1,
|
|
45466
45926
|
killed,
|
|
45467
45927
|
runtimeLimit,
|
|
@@ -45475,10 +45935,10 @@ async function runDispatchSync(d, cwd) {
|
|
|
45475
45935
|
let killed = false;
|
|
45476
45936
|
try {
|
|
45477
45937
|
await spawnSync2(cwd, PROJECT_SYNC_TIMEOUT_MS, (child) => {
|
|
45478
|
-
const exited = new Promise((
|
|
45938
|
+
const exited = new Promise((resolve19) => {
|
|
45479
45939
|
child.once("exit", (_code, signal) => {
|
|
45480
45940
|
if (signal === "SIGTERM") killed = true;
|
|
45481
|
-
|
|
45941
|
+
resolve19();
|
|
45482
45942
|
});
|
|
45483
45943
|
});
|
|
45484
45944
|
running.set(d.short_guid, { child, convGuid: d.conversation_guid, exited });
|
|
@@ -45509,18 +45969,18 @@ function killDispatch(shortGuid) {
|
|
|
45509
45969
|
return false;
|
|
45510
45970
|
}
|
|
45511
45971
|
function sleep4(ms2, signal) {
|
|
45512
|
-
return new Promise((
|
|
45513
|
-
if (signal?.aborted) return
|
|
45972
|
+
return new Promise((resolve19) => {
|
|
45973
|
+
if (signal?.aborted) return resolve19();
|
|
45514
45974
|
let onAbort = null;
|
|
45515
45975
|
const t = setTimeout(() => {
|
|
45516
45976
|
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
45517
|
-
|
|
45977
|
+
resolve19();
|
|
45518
45978
|
}, ms2);
|
|
45519
45979
|
if (signal) {
|
|
45520
45980
|
onAbort = () => {
|
|
45521
45981
|
clearTimeout(t);
|
|
45522
45982
|
signal.removeEventListener("abort", onAbort);
|
|
45523
|
-
|
|
45983
|
+
resolve19();
|
|
45524
45984
|
};
|
|
45525
45985
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
45526
45986
|
}
|
|
@@ -45694,7 +46154,7 @@ relayCommand.command("stop").description("Stop the background service").option("
|
|
|
45694
46154
|
console.log(muted("Background service isn't running."));
|
|
45695
46155
|
return;
|
|
45696
46156
|
}
|
|
45697
|
-
const pid = parseInt(
|
|
46157
|
+
const pid = parseInt(readFileSync25(pidPath, "utf-8").trim(), 10);
|
|
45698
46158
|
if (!pid || isNaN(pid)) {
|
|
45699
46159
|
console.error(error("PID file is empty or malformed."));
|
|
45700
46160
|
process.exit(1);
|
|
@@ -45705,7 +46165,7 @@ relayCommand.command("stop").description("Stop the background service").option("
|
|
|
45705
46165
|
if (err?.code === "ESRCH") {
|
|
45706
46166
|
console.log(muted(`PID ${pid} not running - cleaning up stale PID file.`));
|
|
45707
46167
|
try {
|
|
45708
|
-
|
|
46168
|
+
unlinkSync8(pidPath);
|
|
45709
46169
|
} catch {
|
|
45710
46170
|
}
|
|
45711
46171
|
return;
|
|
@@ -45819,7 +46279,7 @@ relayCommand.command("revoke").description("Revoke and forget this device").acti
|
|
|
45819
46279
|
}
|
|
45820
46280
|
const pidPath = getDaemonPidPath();
|
|
45821
46281
|
if (existsSync22(pidPath)) {
|
|
45822
|
-
const pid = parseInt(
|
|
46282
|
+
const pid = parseInt(readFileSync25(pidPath, "utf-8").trim(), 10);
|
|
45823
46283
|
if (pid && !isNaN(pid)) {
|
|
45824
46284
|
try {
|
|
45825
46285
|
process.kill(pid, "SIGTERM");
|
|
@@ -45838,7 +46298,7 @@ relayCommand.command("log").description("Tail the service log").option("-n, --li
|
|
|
45838
46298
|
}
|
|
45839
46299
|
const lines = parseInt(opts.lines, 10) || 100;
|
|
45840
46300
|
try {
|
|
45841
|
-
const all =
|
|
46301
|
+
const all = readFileSync25(path5, "utf-8").split("\n");
|
|
45842
46302
|
const tail = all.slice(-lines - 1).join("\n");
|
|
45843
46303
|
process.stdout.write(tail);
|
|
45844
46304
|
} catch (err) {
|
|
@@ -45911,15 +46371,15 @@ init_api();
|
|
|
45911
46371
|
init_auth();
|
|
45912
46372
|
init_utils();
|
|
45913
46373
|
init_colors();
|
|
45914
|
-
import { existsSync as existsSync23, rmSync as
|
|
46374
|
+
import { existsSync as existsSync23, rmSync as rmSync4, unlinkSync as unlinkSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync17 } from "fs";
|
|
45915
46375
|
import { homedir as homedir17, platform as osPlatform4 } from "os";
|
|
45916
|
-
import { join as join24, resolve as
|
|
46376
|
+
import { join as join24, resolve as resolve17 } from "path";
|
|
45917
46377
|
function removeGipityPluginConfig() {
|
|
45918
46378
|
const settingsPath = join24(homedir17(), ".claude", "settings.json");
|
|
45919
46379
|
if (!existsSync23(settingsPath)) return false;
|
|
45920
46380
|
let settings;
|
|
45921
46381
|
try {
|
|
45922
|
-
settings = JSON.parse(
|
|
46382
|
+
settings = JSON.parse(readFileSync26(settingsPath, "utf-8"));
|
|
45923
46383
|
} catch {
|
|
45924
46384
|
return false;
|
|
45925
46385
|
}
|
|
@@ -45934,7 +46394,7 @@ function removeGipityPluginConfig() {
|
|
|
45934
46394
|
if (Object.keys(settings.extraKnownMarketplaces).length === 0) delete settings.extraKnownMarketplaces;
|
|
45935
46395
|
changed = true;
|
|
45936
46396
|
}
|
|
45937
|
-
if (changed)
|
|
46397
|
+
if (changed) writeFileSync17(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
45938
46398
|
return changed;
|
|
45939
46399
|
}
|
|
45940
46400
|
function removeInstallerPathLines() {
|
|
@@ -45946,7 +46406,7 @@ function removeInstallerPathLines() {
|
|
|
45946
46406
|
if (!existsSync23(rc2)) continue;
|
|
45947
46407
|
let text;
|
|
45948
46408
|
try {
|
|
45949
|
-
text =
|
|
46409
|
+
text = readFileSync26(rc2, "utf-8");
|
|
45950
46410
|
} catch {
|
|
45951
46411
|
continue;
|
|
45952
46412
|
}
|
|
@@ -45956,7 +46416,7 @@ function removeInstallerPathLines() {
|
|
|
45956
46416
|
);
|
|
45957
46417
|
if (kept.length === lines.length) continue;
|
|
45958
46418
|
try {
|
|
45959
|
-
|
|
46419
|
+
writeFileSync17(rc2, kept.join("\n"));
|
|
45960
46420
|
touched.push(rc2);
|
|
45961
46421
|
} catch {
|
|
45962
46422
|
}
|
|
@@ -45968,7 +46428,7 @@ function tildify2(p) {
|
|
|
45968
46428
|
return p === home || p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
|
|
45969
46429
|
}
|
|
45970
46430
|
function resolveCliPath2() {
|
|
45971
|
-
return
|
|
46431
|
+
return resolve17(process.argv[1] ?? "gipity");
|
|
45972
46432
|
}
|
|
45973
46433
|
async function stopDaemon() {
|
|
45974
46434
|
if (!isDaemonRunning()) return;
|
|
@@ -46004,7 +46464,7 @@ function removeServiceUnit() {
|
|
|
46004
46464
|
}
|
|
46005
46465
|
if (existsSync23(plan2.path)) {
|
|
46006
46466
|
try {
|
|
46007
|
-
|
|
46467
|
+
unlinkSync9(plan2.path);
|
|
46008
46468
|
} catch {
|
|
46009
46469
|
}
|
|
46010
46470
|
}
|
|
@@ -46073,7 +46533,7 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
|
|
|
46073
46533
|
if (agentSkills.skills.length) {
|
|
46074
46534
|
for (const name of agentSkills.skills) {
|
|
46075
46535
|
try {
|
|
46076
|
-
|
|
46536
|
+
rmSync4(join24(AGENTS_SKILLS_DIR, name), { recursive: true, force: true });
|
|
46077
46537
|
} catch {
|
|
46078
46538
|
}
|
|
46079
46539
|
}
|
|
@@ -46081,7 +46541,7 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
|
|
|
46081
46541
|
}
|
|
46082
46542
|
if (existsSync23(gipityDir)) {
|
|
46083
46543
|
try {
|
|
46084
|
-
|
|
46544
|
+
rmSync4(gipityDir, { recursive: true, force: true });
|
|
46085
46545
|
console.log(`${success(`Removed ${gipityDir}/`)}`);
|
|
46086
46546
|
} catch (err) {
|
|
46087
46547
|
console.error(`${error(`Could not remove ${gipityDir}: ${err?.message || err}`)}`);
|
|
@@ -46242,7 +46702,7 @@ gmailCommand.command("reply").description("Reply to a Gmail thread. Get thread-i
|
|
|
46242
46702
|
}));
|
|
46243
46703
|
|
|
46244
46704
|
// src/commands/text.ts
|
|
46245
|
-
import { readFileSync as
|
|
46705
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
46246
46706
|
init_colors();
|
|
46247
46707
|
|
|
46248
46708
|
// src/helpers/text-analysis.ts
|
|
@@ -46397,10 +46857,10 @@ function areAnagrams(a, b7) {
|
|
|
46397
46857
|
|
|
46398
46858
|
// src/commands/text.ts
|
|
46399
46859
|
function resolveInput(args, opts) {
|
|
46400
|
-
if (opts.file) return
|
|
46860
|
+
if (opts.file) return readFileSync27(opts.file, "utf-8");
|
|
46401
46861
|
if (args.length) return args.join(" ");
|
|
46402
46862
|
try {
|
|
46403
|
-
return
|
|
46863
|
+
return readFileSync27(0, "utf-8");
|
|
46404
46864
|
} catch {
|
|
46405
46865
|
return "";
|
|
46406
46866
|
}
|
|
@@ -46606,7 +47066,7 @@ function collectRealFlags(argv2, program3) {
|
|
|
46606
47066
|
}
|
|
46607
47067
|
|
|
46608
47068
|
// src/trace.ts
|
|
46609
|
-
import { openSync as openSync5, writeSync, mkdirSync as
|
|
47069
|
+
import { openSync as openSync5, writeSync, mkdirSync as mkdirSync18 } from "fs";
|
|
46610
47070
|
import { join as join25 } from "path";
|
|
46611
47071
|
import { homedir as homedir18 } from "os";
|
|
46612
47072
|
var TRACE_KEY = /* @__PURE__ */ Symbol.for("gipity.traceOutput");
|
|
@@ -46620,7 +47080,7 @@ function installOutputTrace(label2) {
|
|
|
46620
47080
|
return;
|
|
46621
47081
|
}
|
|
46622
47082
|
const dir = join25(process.env.GIPITY_DIR || join25(homedir18(), ".gipity"), "trace");
|
|
46623
|
-
|
|
47083
|
+
mkdirSync18(dir, { recursive: true });
|
|
46624
47084
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
|
|
46625
47085
|
const fd2 = openSync5(join25(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
|
|
46626
47086
|
const emit = (rec) => {
|
|
@@ -46658,11 +47118,11 @@ function installOutputTrace(label2) {
|
|
|
46658
47118
|
// src/index.ts
|
|
46659
47119
|
installOutputTrace("index");
|
|
46660
47120
|
var __dirname = dirname13(fileURLToPath3(import.meta.url));
|
|
46661
|
-
var pkg = JSON.parse(
|
|
47121
|
+
var pkg = JSON.parse(readFileSync28(resolve18(__dirname, "../package.json"), "utf-8"));
|
|
46662
47122
|
function versionLabel() {
|
|
46663
47123
|
const base = `v${pkg.version}`;
|
|
46664
47124
|
try {
|
|
46665
|
-
const info2 = JSON.parse(
|
|
47125
|
+
const info2 = JSON.parse(readFileSync28(resolve18(__dirname, "build-info.json"), "utf-8"));
|
|
46666
47126
|
if (info2?.sha) return `${base} (dev ${info2.sha}${info2.dirty ? ", modified" : ""})`;
|
|
46667
47127
|
} catch {
|
|
46668
47128
|
}
|
|
@@ -46867,6 +47327,19 @@ function resolveTargetCommand(argv2) {
|
|
|
46867
47327
|
}
|
|
46868
47328
|
return cmd;
|
|
46869
47329
|
}
|
|
47330
|
+
Command.prototype._excessArguments = function(receivedArgs) {
|
|
47331
|
+
if (this._allowExcessArguments) return;
|
|
47332
|
+
const expected = this.registeredArguments.length;
|
|
47333
|
+
const excess = receivedArgs.slice(expected);
|
|
47334
|
+
const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
|
|
47335
|
+
const list = excess.map((a) => `'${a}'`).join(", ");
|
|
47336
|
+
let message = `error: unexpected extra argument${excess.length === 1 ? "" : "s"} ${list}${forSubcommand}.`;
|
|
47337
|
+
const kv2 = excess.map((a) => /^([A-Za-z][\w-]*)=/.exec(a)).find(Boolean);
|
|
47338
|
+
if (kv2) {
|
|
47339
|
+
message += ` Options are passed as \`--${kv2[1]} <value>\`, not \`${kv2[1]}=\u2026\` - did you mean \`--${kv2[1]}\`?`;
|
|
47340
|
+
}
|
|
47341
|
+
this.error(message, { code: "commander.excessArguments" });
|
|
47342
|
+
};
|
|
46870
47343
|
function enableHelpAfterError(cmd) {
|
|
46871
47344
|
cmd.configureOutput({
|
|
46872
47345
|
// Render the offending command's full help (via outputHelp, so addHelpText
|