gipity 1.0.427 → 1.0.429
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/commands/deploy.js +19 -1
- package/dist/commands/generate.js +61 -4
- package/dist/commands/page-eval.js +387 -50
- package/dist/commands/page-inspect.js +8 -2
- package/dist/commands/page-screenshot.js +209 -43
- package/dist/hooks/capture-runner.js +9 -1
- package/dist/index.js +1073 -496
- package/dist/page-fixtures.js +92 -3
- package/dist/provider-docs.js +10 -10
- package/dist/relay/daemon.js +43 -14
- package/dist/relay/device-http.js +22 -0
- package/dist/relay/media-upload.js +184 -0
- package/dist/setup.js +4 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1166,7 +1166,7 @@ var require_command = __commonJS({
|
|
|
1166
1166
|
"node_modules/commander/lib/command.js"(exports) {
|
|
1167
1167
|
var EventEmitter = __require("node:events").EventEmitter;
|
|
1168
1168
|
var childProcess = __require("node:child_process");
|
|
1169
|
-
var
|
|
1169
|
+
var path5 = __require("node:path");
|
|
1170
1170
|
var fs6 = __require("node:fs");
|
|
1171
1171
|
var process2 = __require("node:process");
|
|
1172
1172
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
@@ -2166,9 +2166,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2166
2166
|
let launchWithNode = false;
|
|
2167
2167
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
2168
2168
|
function findFile(baseDir, baseName) {
|
|
2169
|
-
const localBin =
|
|
2169
|
+
const localBin = path5.resolve(baseDir, baseName);
|
|
2170
2170
|
if (fs6.existsSync(localBin)) return localBin;
|
|
2171
|
-
if (sourceExt.includes(
|
|
2171
|
+
if (sourceExt.includes(path5.extname(baseName))) return void 0;
|
|
2172
2172
|
const foundExt = sourceExt.find(
|
|
2173
2173
|
(ext) => fs6.existsSync(`${localBin}${ext}`)
|
|
2174
2174
|
);
|
|
@@ -2186,17 +2186,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2186
2186
|
} catch {
|
|
2187
2187
|
resolvedScriptPath = this._scriptPath;
|
|
2188
2188
|
}
|
|
2189
|
-
executableDir =
|
|
2190
|
-
|
|
2189
|
+
executableDir = path5.resolve(
|
|
2190
|
+
path5.dirname(resolvedScriptPath),
|
|
2191
2191
|
executableDir
|
|
2192
2192
|
);
|
|
2193
2193
|
}
|
|
2194
2194
|
if (executableDir) {
|
|
2195
2195
|
let localFile = findFile(executableDir, executableFile);
|
|
2196
2196
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
2197
|
-
const legacyName =
|
|
2197
|
+
const legacyName = path5.basename(
|
|
2198
2198
|
this._scriptPath,
|
|
2199
|
-
|
|
2199
|
+
path5.extname(this._scriptPath)
|
|
2200
2200
|
);
|
|
2201
2201
|
if (legacyName !== this._name) {
|
|
2202
2202
|
localFile = findFile(
|
|
@@ -2207,7 +2207,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2207
2207
|
}
|
|
2208
2208
|
executableFile = localFile || executableFile;
|
|
2209
2209
|
}
|
|
2210
|
-
launchWithNode = sourceExt.includes(
|
|
2210
|
+
launchWithNode = sourceExt.includes(path5.extname(executableFile));
|
|
2211
2211
|
let proc;
|
|
2212
2212
|
if (process2.platform !== "win32") {
|
|
2213
2213
|
if (launchWithNode) {
|
|
@@ -3054,7 +3054,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3054
3054
|
* @return {Command}
|
|
3055
3055
|
*/
|
|
3056
3056
|
nameFromFilename(filename) {
|
|
3057
|
-
this._name =
|
|
3057
|
+
this._name = path5.basename(filename, path5.extname(filename));
|
|
3058
3058
|
return this;
|
|
3059
3059
|
}
|
|
3060
3060
|
/**
|
|
@@ -3068,9 +3068,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3068
3068
|
* @param {string} [path]
|
|
3069
3069
|
* @return {(string|null|Command)}
|
|
3070
3070
|
*/
|
|
3071
|
-
executableDir(
|
|
3072
|
-
if (
|
|
3073
|
-
this._executableDir =
|
|
3071
|
+
executableDir(path6) {
|
|
3072
|
+
if (path6 === void 0) return this._executableDir;
|
|
3073
|
+
this._executableDir = path6;
|
|
3074
3074
|
return this;
|
|
3075
3075
|
}
|
|
3076
3076
|
/**
|
|
@@ -3639,7 +3639,7 @@ var require_ignore = __commonJS({
|
|
|
3639
3639
|
// path matching.
|
|
3640
3640
|
// - check `string` either `MODE_IGNORE` or `MODE_CHECK_IGNORE`
|
|
3641
3641
|
// @returns {TestResult} true if a file is ignored
|
|
3642
|
-
test(
|
|
3642
|
+
test(path5, checkUnignored, mode) {
|
|
3643
3643
|
let ignored = false;
|
|
3644
3644
|
let unignored = false;
|
|
3645
3645
|
let matchedRule;
|
|
@@ -3648,7 +3648,7 @@ var require_ignore = __commonJS({
|
|
|
3648
3648
|
if (unignored === negative && ignored !== unignored || negative && !ignored && !unignored && !checkUnignored) {
|
|
3649
3649
|
return;
|
|
3650
3650
|
}
|
|
3651
|
-
const matched = rule[mode].test(
|
|
3651
|
+
const matched = rule[mode].test(path5);
|
|
3652
3652
|
if (!matched) {
|
|
3653
3653
|
return;
|
|
3654
3654
|
}
|
|
@@ -3669,17 +3669,17 @@ var require_ignore = __commonJS({
|
|
|
3669
3669
|
var throwError = (message, Ctor) => {
|
|
3670
3670
|
throw new Ctor(message);
|
|
3671
3671
|
};
|
|
3672
|
-
var checkPath = (
|
|
3673
|
-
if (!isString(
|
|
3672
|
+
var checkPath = (path5, originalPath, doThrow) => {
|
|
3673
|
+
if (!isString(path5)) {
|
|
3674
3674
|
return doThrow(
|
|
3675
3675
|
`path must be a string, but got \`${originalPath}\``,
|
|
3676
3676
|
TypeError
|
|
3677
3677
|
);
|
|
3678
3678
|
}
|
|
3679
|
-
if (!
|
|
3679
|
+
if (!path5) {
|
|
3680
3680
|
return doThrow(`path must not be empty`, TypeError);
|
|
3681
3681
|
}
|
|
3682
|
-
if (checkPath.isNotRelative(
|
|
3682
|
+
if (checkPath.isNotRelative(path5)) {
|
|
3683
3683
|
const r = "`path.relative()`d";
|
|
3684
3684
|
return doThrow(
|
|
3685
3685
|
`path should be a ${r} string, but got "${originalPath}"`,
|
|
@@ -3688,7 +3688,7 @@ var require_ignore = __commonJS({
|
|
|
3688
3688
|
}
|
|
3689
3689
|
return true;
|
|
3690
3690
|
};
|
|
3691
|
-
var isNotRelative = (
|
|
3691
|
+
var isNotRelative = (path5) => REGEX_TEST_INVALID_PATH.test(path5);
|
|
3692
3692
|
checkPath.isNotRelative = isNotRelative;
|
|
3693
3693
|
checkPath.convert = (p) => p;
|
|
3694
3694
|
var Ignore = class {
|
|
@@ -3718,19 +3718,19 @@ var require_ignore = __commonJS({
|
|
|
3718
3718
|
}
|
|
3719
3719
|
// @returns {TestResult}
|
|
3720
3720
|
_test(originalPath, cache, checkUnignored, slices) {
|
|
3721
|
-
const
|
|
3721
|
+
const path5 = originalPath && checkPath.convert(originalPath);
|
|
3722
3722
|
checkPath(
|
|
3723
|
-
|
|
3723
|
+
path5,
|
|
3724
3724
|
originalPath,
|
|
3725
3725
|
this._strictPathCheck ? throwError : RETURN_FALSE
|
|
3726
3726
|
);
|
|
3727
|
-
return this._t(
|
|
3727
|
+
return this._t(path5, cache, checkUnignored, slices);
|
|
3728
3728
|
}
|
|
3729
|
-
checkIgnore(
|
|
3730
|
-
if (!REGEX_TEST_TRAILING_SLASH.test(
|
|
3731
|
-
return this.test(
|
|
3729
|
+
checkIgnore(path5) {
|
|
3730
|
+
if (!REGEX_TEST_TRAILING_SLASH.test(path5)) {
|
|
3731
|
+
return this.test(path5);
|
|
3732
3732
|
}
|
|
3733
|
-
const slices =
|
|
3733
|
+
const slices = path5.split(SLASH).filter(Boolean);
|
|
3734
3734
|
slices.pop();
|
|
3735
3735
|
if (slices.length) {
|
|
3736
3736
|
const parent = this._t(
|
|
@@ -3743,18 +3743,18 @@ var require_ignore = __commonJS({
|
|
|
3743
3743
|
return parent;
|
|
3744
3744
|
}
|
|
3745
3745
|
}
|
|
3746
|
-
return this._rules.test(
|
|
3746
|
+
return this._rules.test(path5, false, MODE_CHECK_IGNORE);
|
|
3747
3747
|
}
|
|
3748
|
-
_t(
|
|
3749
|
-
if (
|
|
3750
|
-
return cache[
|
|
3748
|
+
_t(path5, cache, checkUnignored, slices) {
|
|
3749
|
+
if (path5 in cache) {
|
|
3750
|
+
return cache[path5];
|
|
3751
3751
|
}
|
|
3752
3752
|
if (!slices) {
|
|
3753
|
-
slices =
|
|
3753
|
+
slices = path5.split(SLASH).filter(Boolean);
|
|
3754
3754
|
}
|
|
3755
3755
|
slices.pop();
|
|
3756
3756
|
if (!slices.length) {
|
|
3757
|
-
return cache[
|
|
3757
|
+
return cache[path5] = this._rules.test(path5, checkUnignored, MODE_IGNORE);
|
|
3758
3758
|
}
|
|
3759
3759
|
const parent = this._t(
|
|
3760
3760
|
slices.join(SLASH) + SLASH,
|
|
@@ -3762,29 +3762,29 @@ var require_ignore = __commonJS({
|
|
|
3762
3762
|
checkUnignored,
|
|
3763
3763
|
slices
|
|
3764
3764
|
);
|
|
3765
|
-
return cache[
|
|
3765
|
+
return cache[path5] = parent.ignored ? parent : this._rules.test(path5, checkUnignored, MODE_IGNORE);
|
|
3766
3766
|
}
|
|
3767
|
-
ignores(
|
|
3768
|
-
return this._test(
|
|
3767
|
+
ignores(path5) {
|
|
3768
|
+
return this._test(path5, this._ignoreCache, false).ignored;
|
|
3769
3769
|
}
|
|
3770
3770
|
createFilter() {
|
|
3771
|
-
return (
|
|
3771
|
+
return (path5) => !this.ignores(path5);
|
|
3772
3772
|
}
|
|
3773
3773
|
filter(paths) {
|
|
3774
3774
|
return makeArray(paths).filter(this.createFilter());
|
|
3775
3775
|
}
|
|
3776
3776
|
// @returns {TestResult}
|
|
3777
|
-
test(
|
|
3778
|
-
return this._test(
|
|
3777
|
+
test(path5) {
|
|
3778
|
+
return this._test(path5, this._testCache, true);
|
|
3779
3779
|
}
|
|
3780
3780
|
};
|
|
3781
3781
|
var factory = (options) => new Ignore(options);
|
|
3782
|
-
var isPathValid = (
|
|
3782
|
+
var isPathValid = (path5) => checkPath(path5 && checkPath.convert(path5), path5, RETURN_FALSE);
|
|
3783
3783
|
var setupWindows = () => {
|
|
3784
3784
|
const makePosix = (str) => /^\\\\\?\\/.test(str) || /["<>|\u0000-\u001F]+/u.test(str) ? str : str.replace(/\\/g, "/");
|
|
3785
3785
|
checkPath.convert = makePosix;
|
|
3786
3786
|
const REGEX_TEST_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i;
|
|
3787
|
-
checkPath.isNotRelative = (
|
|
3787
|
+
checkPath.isNotRelative = (path5) => REGEX_TEST_WINDOWS_PATH_ABSOLUTE.test(path5) || isNotRelative(path5);
|
|
3788
3788
|
};
|
|
3789
3789
|
if (
|
|
3790
3790
|
// Detect `process` so that it can run in browsers.
|
|
@@ -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
|
}
|
|
@@ -6662,16 +6662,16 @@ async function getAuthHeader() {
|
|
|
6662
6662
|
const headers = await getHeaders();
|
|
6663
6663
|
return headers.Authorization;
|
|
6664
6664
|
}
|
|
6665
|
-
async function request(method,
|
|
6665
|
+
async function request(method, path5, body, retrying = false) {
|
|
6666
6666
|
const headers = await getHeaders();
|
|
6667
|
-
const url = `${baseUrl()}${
|
|
6667
|
+
const url = `${baseUrl()}${path5}`;
|
|
6668
6668
|
const res = await fetchWithTimeout(url, {
|
|
6669
6669
|
method,
|
|
6670
6670
|
headers,
|
|
6671
6671
|
body: body ? JSON.stringify(body) : void 0
|
|
6672
|
-
}, REQUEST_TIMEOUT_MS, `${method} ${
|
|
6672
|
+
}, REQUEST_TIMEOUT_MS, `${method} ${path5}`);
|
|
6673
6673
|
if (await shouldRetryAfter401(res.status, retrying)) {
|
|
6674
|
-
return request(method,
|
|
6674
|
+
return request(method, path5, body, true);
|
|
6675
6675
|
}
|
|
6676
6676
|
if (!res.ok) {
|
|
6677
6677
|
const json = await res.json().catch(() => ({ error: { code: "UNKNOWN", message: res.statusText } }));
|
|
@@ -6680,22 +6680,22 @@ async function request(method, path4, body, retrying = false) {
|
|
|
6680
6680
|
}
|
|
6681
6681
|
return res.json();
|
|
6682
6682
|
}
|
|
6683
|
-
function get(
|
|
6684
|
-
return request("GET",
|
|
6683
|
+
function get(path5) {
|
|
6684
|
+
return request("GET", path5);
|
|
6685
6685
|
}
|
|
6686
|
-
function post(
|
|
6687
|
-
return request("POST",
|
|
6686
|
+
function post(path5, body) {
|
|
6687
|
+
return request("POST", path5, body);
|
|
6688
6688
|
}
|
|
6689
|
-
async function postForTarEntries(
|
|
6689
|
+
async function postForTarEntries(path5, body, retried = false) {
|
|
6690
6690
|
const headers = await getHeaders();
|
|
6691
|
-
const url = `${baseUrl()}${
|
|
6691
|
+
const url = `${baseUrl()}${path5}`;
|
|
6692
6692
|
const res = await fetch(url, {
|
|
6693
6693
|
method: "POST",
|
|
6694
6694
|
headers,
|
|
6695
6695
|
body: body ? JSON.stringify(body) : void 0
|
|
6696
6696
|
});
|
|
6697
6697
|
if (await shouldRetryAfter401(res.status, retried)) {
|
|
6698
|
-
return postForTarEntries(
|
|
6698
|
+
return postForTarEntries(path5, body, true);
|
|
6699
6699
|
}
|
|
6700
6700
|
if (!res.ok) {
|
|
6701
6701
|
const json = await res.json().catch(() => ({ error: { code: "UNKNOWN", message: res.statusText } }));
|
|
@@ -6705,7 +6705,7 @@ async function postForTarEntries(path4, body, retried = false) {
|
|
|
6705
6705
|
if (!res.body) throw new ApiError(500, "EMPTY_RESPONSE", "Server returned no body");
|
|
6706
6706
|
const extract3 = tar.extract();
|
|
6707
6707
|
const entries = [];
|
|
6708
|
-
const done = new Promise((
|
|
6708
|
+
const done = new Promise((resolve19, reject) => {
|
|
6709
6709
|
extract3.on("entry", (header, stream, next) => {
|
|
6710
6710
|
const chunks = [];
|
|
6711
6711
|
stream.on("data", (c) => chunks.push(c));
|
|
@@ -6716,21 +6716,21 @@ async function postForTarEntries(path4, body, retried = false) {
|
|
|
6716
6716
|
stream.on("error", reject);
|
|
6717
6717
|
stream.resume();
|
|
6718
6718
|
});
|
|
6719
|
-
extract3.on("finish", () =>
|
|
6719
|
+
extract3.on("finish", () => resolve19());
|
|
6720
6720
|
extract3.on("error", reject);
|
|
6721
6721
|
});
|
|
6722
6722
|
Readable.fromWeb(res.body).pipe(extract3);
|
|
6723
6723
|
await done;
|
|
6724
6724
|
return entries;
|
|
6725
6725
|
}
|
|
6726
|
-
function put(
|
|
6727
|
-
return request("PUT",
|
|
6726
|
+
function put(path5, body) {
|
|
6727
|
+
return request("PUT", path5, body);
|
|
6728
6728
|
}
|
|
6729
|
-
function patch(
|
|
6730
|
-
return request("PATCH",
|
|
6729
|
+
function patch(path5, body) {
|
|
6730
|
+
return request("PATCH", path5, body);
|
|
6731
6731
|
}
|
|
6732
|
-
function del(
|
|
6733
|
-
return request("DELETE",
|
|
6732
|
+
function del(path5, body) {
|
|
6733
|
+
return request("DELETE", path5, body);
|
|
6734
6734
|
}
|
|
6735
6735
|
async function sendMessage(message) {
|
|
6736
6736
|
const config = requireConfig();
|
|
@@ -6743,26 +6743,26 @@ async function sendMessage(message) {
|
|
|
6743
6743
|
}
|
|
6744
6744
|
return res.data.content;
|
|
6745
6745
|
}
|
|
6746
|
-
async function download(
|
|
6747
|
-
const url = `${baseUrl()}${
|
|
6746
|
+
async function download(path5, retried = false) {
|
|
6747
|
+
const url = `${baseUrl()}${path5}`;
|
|
6748
6748
|
const res = await fetch(url, {
|
|
6749
6749
|
headers: { ...clientHeaders(), "Authorization": `Bearer ${await bearerToken()}` }
|
|
6750
6750
|
});
|
|
6751
6751
|
if (await shouldRetryAfter401(res.status, retried)) {
|
|
6752
|
-
return download(
|
|
6752
|
+
return download(path5, true);
|
|
6753
6753
|
}
|
|
6754
6754
|
if (!res.ok) {
|
|
6755
6755
|
throw new ApiError(res.status, "DOWNLOAD_ERROR", with401Hint(res.status, `Download failed: ${res.statusText}`));
|
|
6756
6756
|
}
|
|
6757
6757
|
return Buffer.from(await res.arrayBuffer());
|
|
6758
6758
|
}
|
|
6759
|
-
async function downloadWithHeaders(
|
|
6760
|
-
const url = `${baseUrl()}${
|
|
6759
|
+
async function downloadWithHeaders(path5, retried = false) {
|
|
6760
|
+
const url = `${baseUrl()}${path5}`;
|
|
6761
6761
|
const res = await fetch(url, {
|
|
6762
6762
|
headers: { ...clientHeaders(), "Authorization": `Bearer ${await bearerToken()}` }
|
|
6763
6763
|
});
|
|
6764
6764
|
if (await shouldRetryAfter401(res.status, retried)) {
|
|
6765
|
-
return downloadWithHeaders(
|
|
6765
|
+
return downloadWithHeaders(path5, true);
|
|
6766
6766
|
}
|
|
6767
6767
|
if (!res.ok) {
|
|
6768
6768
|
const json = await res.json().catch(() => ({ error: { code: "UNKNOWN", message: res.statusText } }));
|
|
@@ -6771,8 +6771,8 @@ async function downloadWithHeaders(path4, retried = false) {
|
|
|
6771
6771
|
}
|
|
6772
6772
|
return { buffer: Buffer.from(await res.arrayBuffer()), headers: res.headers };
|
|
6773
6773
|
}
|
|
6774
|
-
async function postBinary(
|
|
6775
|
-
const url = `${baseUrl()}${
|
|
6774
|
+
async function postBinary(path5, body, contentType = "application/zip", retried = false) {
|
|
6775
|
+
const url = `${baseUrl()}${path5}`;
|
|
6776
6776
|
const headers = {
|
|
6777
6777
|
...clientHeaders(),
|
|
6778
6778
|
"Authorization": `Bearer ${await bearerToken()}`,
|
|
@@ -6782,9 +6782,9 @@ async function postBinary(path4, body, contentType = "application/zip", retried
|
|
|
6782
6782
|
method: "POST",
|
|
6783
6783
|
headers,
|
|
6784
6784
|
body: new Uint8Array(body)
|
|
6785
|
-
}, putTimeoutMs(body.length), `POST ${
|
|
6785
|
+
}, putTimeoutMs(body.length), `POST ${path5}`);
|
|
6786
6786
|
if (await shouldRetryAfter401(res.status, retried)) {
|
|
6787
|
-
return postBinary(
|
|
6787
|
+
return postBinary(path5, body, contentType, true);
|
|
6788
6788
|
}
|
|
6789
6789
|
if (!res.ok) {
|
|
6790
6790
|
const json = await res.json().catch(() => ({ error: { code: "UNKNOWN", message: res.statusText } }));
|
|
@@ -6793,9 +6793,9 @@ async function postBinary(path4, body, contentType = "application/zip", retried
|
|
|
6793
6793
|
}
|
|
6794
6794
|
return { status: res.status, json: await res.json() };
|
|
6795
6795
|
}
|
|
6796
|
-
async function downloadStream(
|
|
6796
|
+
async function downloadStream(path5, retried = false) {
|
|
6797
6797
|
const { Readable: Readable2 } = await import("stream");
|
|
6798
|
-
const url = `${baseUrl()}${
|
|
6798
|
+
const url = `${baseUrl()}${path5}`;
|
|
6799
6799
|
const ac2 = new AbortController();
|
|
6800
6800
|
const headerTimer = setTimeout(() => ac2.abort(), DOWNLOAD_HEADER_TIMEOUT_MS);
|
|
6801
6801
|
let res;
|
|
@@ -6813,7 +6813,7 @@ async function downloadStream(path4, retried = false) {
|
|
|
6813
6813
|
clearTimeout(headerTimer);
|
|
6814
6814
|
}
|
|
6815
6815
|
if (await shouldRetryAfter401(res.status, retried)) {
|
|
6816
|
-
return downloadStream(
|
|
6816
|
+
return downloadStream(path5, true);
|
|
6817
6817
|
}
|
|
6818
6818
|
if (!res.ok) {
|
|
6819
6819
|
throw new ApiError(res.status, "DOWNLOAD_ERROR", with401Hint(res.status, `Download failed: ${res.statusText}`));
|
|
@@ -6862,8 +6862,8 @@ async function getAccountSlug() {
|
|
|
6862
6862
|
accountSlugCache = res.data.accountSlug;
|
|
6863
6863
|
return accountSlugCache;
|
|
6864
6864
|
}
|
|
6865
|
-
async function publicPost(
|
|
6866
|
-
const url = `${baseUrl()}${
|
|
6865
|
+
async function publicPost(path5, body, extraHeaders) {
|
|
6866
|
+
const url = `${baseUrl()}${path5}`;
|
|
6867
6867
|
const res = await fetch(url, {
|
|
6868
6868
|
method: "POST",
|
|
6869
6869
|
headers: { ...clientHeaders(), "Content-Type": "application/json", ...extraHeaders },
|
|
@@ -6973,15 +6973,15 @@ function getConfigPath() {
|
|
|
6973
6973
|
return cachedPath;
|
|
6974
6974
|
}
|
|
6975
6975
|
function getProjectRoot() {
|
|
6976
|
-
const
|
|
6977
|
-
return
|
|
6976
|
+
const path5 = getConfigPath();
|
|
6977
|
+
return path5 ? dirname2(path5) : null;
|
|
6978
6978
|
}
|
|
6979
6979
|
function getConfig() {
|
|
6980
6980
|
if (cached3) return cached3;
|
|
6981
|
-
const
|
|
6982
|
-
if (!
|
|
6981
|
+
const path5 = getConfigPath();
|
|
6982
|
+
if (!path5) return null;
|
|
6983
6983
|
try {
|
|
6984
|
-
cached3 = JSON.parse(readFileSync3(
|
|
6984
|
+
cached3 = JSON.parse(readFileSync3(path5, "utf-8"));
|
|
6985
6985
|
return cached3;
|
|
6986
6986
|
} catch {
|
|
6987
6987
|
return null;
|
|
@@ -7063,21 +7063,21 @@ function clearConfigCache() {
|
|
|
7063
7063
|
cachedPath = null;
|
|
7064
7064
|
}
|
|
7065
7065
|
function saveConfig(data) {
|
|
7066
|
-
const
|
|
7067
|
-
if (!
|
|
7066
|
+
const path5 = getConfigPath();
|
|
7067
|
+
if (!path5) {
|
|
7068
7068
|
throw new Error(
|
|
7069
7069
|
"saveConfig: no .gipity.json found to update. Use saveConfigAt(dir, \u2026) to create a new project config."
|
|
7070
7070
|
);
|
|
7071
7071
|
}
|
|
7072
|
-
writeFileSync2(
|
|
7072
|
+
writeFileSync2(path5, JSON.stringify(data, null, 2) + "\n");
|
|
7073
7073
|
cached3 = data;
|
|
7074
|
-
cachedPath =
|
|
7074
|
+
cachedPath = path5;
|
|
7075
7075
|
}
|
|
7076
7076
|
function saveConfigAt(dir, data) {
|
|
7077
|
-
const
|
|
7078
|
-
writeFileSync2(
|
|
7077
|
+
const path5 = resolve2(dir, CONFIG_FILE);
|
|
7078
|
+
writeFileSync2(path5, JSON.stringify(data, null, 2) + "\n");
|
|
7079
7079
|
cached3 = data;
|
|
7080
|
-
cachedPath =
|
|
7080
|
+
cachedPath = path5;
|
|
7081
7081
|
}
|
|
7082
7082
|
function shouldIgnore(filePath, ignorePatterns) {
|
|
7083
7083
|
if (ignorePatterns.length === 0) return false;
|
|
@@ -31860,7 +31860,7 @@ init_config();
|
|
|
31860
31860
|
init_utils();
|
|
31861
31861
|
import { readFileSync as readFileSync25 } from "fs";
|
|
31862
31862
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31863
|
-
import { dirname as dirname13, resolve as
|
|
31863
|
+
import { dirname as dirname13, resolve as resolve18 } from "path";
|
|
31864
31864
|
|
|
31865
31865
|
// src/helpers/output.ts
|
|
31866
31866
|
var frameOpen = false;
|
|
@@ -31900,16 +31900,16 @@ function printResult(text, opts, jsonData) {
|
|
|
31900
31900
|
}
|
|
31901
31901
|
console.log(text);
|
|
31902
31902
|
}
|
|
31903
|
-
function pluckField(data,
|
|
31904
|
-
return
|
|
31903
|
+
function pluckField(data, path5) {
|
|
31904
|
+
return path5.split(".").reduce(
|
|
31905
31905
|
(acc, key) => acc == null ? void 0 : acc[key],
|
|
31906
31906
|
data
|
|
31907
31907
|
);
|
|
31908
31908
|
}
|
|
31909
|
-
function emitField(data,
|
|
31910
|
-
const value = pluckField(data,
|
|
31909
|
+
function emitField(data, path5) {
|
|
31910
|
+
const value = pluckField(data, path5);
|
|
31911
31911
|
if (value === void 0) {
|
|
31912
|
-
console.error(`Field not found: ${
|
|
31912
|
+
console.error(`Field not found: ${path5}`);
|
|
31913
31913
|
process.exit(1);
|
|
31914
31914
|
}
|
|
31915
31915
|
console.log(typeof value === "object" && value !== null ? JSON.stringify(value) : String(value));
|
|
@@ -32171,13 +32171,13 @@ async function flushBugQueue() {
|
|
|
32171
32171
|
let delivered = 0;
|
|
32172
32172
|
for (const file of readdirSync(QUEUE_DIR)) {
|
|
32173
32173
|
if (!file.endsWith(".json")) continue;
|
|
32174
|
-
const
|
|
32174
|
+
const path5 = join2(QUEUE_DIR, file);
|
|
32175
32175
|
let report;
|
|
32176
32176
|
try {
|
|
32177
|
-
report = JSON.parse(readFileSync4(
|
|
32177
|
+
report = JSON.parse(readFileSync4(path5, "utf-8"));
|
|
32178
32178
|
} catch {
|
|
32179
32179
|
try {
|
|
32180
|
-
unlinkSync2(
|
|
32180
|
+
unlinkSync2(path5);
|
|
32181
32181
|
} catch {
|
|
32182
32182
|
}
|
|
32183
32183
|
continue;
|
|
@@ -32189,7 +32189,7 @@ async function flushBugQueue() {
|
|
|
32189
32189
|
summary: report.summary,
|
|
32190
32190
|
detail: report.detail
|
|
32191
32191
|
});
|
|
32192
|
-
unlinkSync2(
|
|
32192
|
+
unlinkSync2(path5);
|
|
32193
32193
|
delivered++;
|
|
32194
32194
|
} catch {
|
|
32195
32195
|
}
|
|
@@ -32347,32 +32347,32 @@ var MIME_BY_EXT = {
|
|
|
32347
32347
|
".xlsx": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
32348
32348
|
".pptx": "application/vnd.openxmlformats-officedocument.presentationml.presentation"
|
|
32349
32349
|
};
|
|
32350
|
-
function guessMime(
|
|
32351
|
-
return MIME_BY_EXT[extname(
|
|
32350
|
+
function guessMime(path5) {
|
|
32351
|
+
return MIME_BY_EXT[extname(path5).toLowerCase()] ?? "application/octet-stream";
|
|
32352
32352
|
}
|
|
32353
|
-
async function hashFile(
|
|
32353
|
+
async function hashFile(path5) {
|
|
32354
32354
|
const hash = createHash("sha256");
|
|
32355
32355
|
let size = 0;
|
|
32356
|
-
const stream = createReadStream(
|
|
32357
|
-
await new Promise((
|
|
32356
|
+
const stream = createReadStream(path5);
|
|
32357
|
+
await new Promise((resolve19, reject) => {
|
|
32358
32358
|
stream.on("data", (chunk) => {
|
|
32359
32359
|
const buf = chunk;
|
|
32360
32360
|
hash.update(buf);
|
|
32361
32361
|
size += buf.length;
|
|
32362
32362
|
});
|
|
32363
|
-
stream.on("end", () =>
|
|
32363
|
+
stream.on("end", () => resolve19());
|
|
32364
32364
|
stream.on("error", reject);
|
|
32365
32365
|
});
|
|
32366
32366
|
return { sha256: hash.digest("hex"), size };
|
|
32367
32367
|
}
|
|
32368
|
-
function readRange(
|
|
32369
|
-
return new Promise((
|
|
32368
|
+
function readRange(path5, start, end) {
|
|
32369
|
+
return new Promise((resolve19, reject) => {
|
|
32370
32370
|
const chunks = [];
|
|
32371
|
-
const stream = createReadStream(
|
|
32371
|
+
const stream = createReadStream(path5, { start, end });
|
|
32372
32372
|
stream.on("data", (c) => {
|
|
32373
32373
|
chunks.push(c);
|
|
32374
32374
|
});
|
|
32375
|
-
stream.on("end", () =>
|
|
32375
|
+
stream.on("end", () => resolve19(Buffer.concat(chunks)));
|
|
32376
32376
|
stream.on("error", reject);
|
|
32377
32377
|
});
|
|
32378
32378
|
}
|
|
@@ -32395,10 +32395,10 @@ async function withRetry(label2, fn, attempts = 3) {
|
|
|
32395
32395
|
throw lastErr;
|
|
32396
32396
|
}
|
|
32397
32397
|
var UploadConflictError = class extends Error {
|
|
32398
|
-
constructor(currentServerVersion,
|
|
32399
|
-
super(`Version mismatch for ${
|
|
32398
|
+
constructor(currentServerVersion, path5) {
|
|
32399
|
+
super(`Version mismatch for ${path5}: current serverVersion is ${currentServerVersion}`);
|
|
32400
32400
|
this.currentServerVersion = currentServerVersion;
|
|
32401
|
-
this.path =
|
|
32401
|
+
this.path = path5;
|
|
32402
32402
|
this.name = "UploadConflictError";
|
|
32403
32403
|
}
|
|
32404
32404
|
currentServerVersion;
|
|
@@ -32645,10 +32645,10 @@ function stripGipityHooks(settings) {
|
|
|
32645
32645
|
if (Object.keys(hooks).length === 0) delete settings.hooks;
|
|
32646
32646
|
return changed;
|
|
32647
32647
|
}
|
|
32648
|
-
function readSettingsFile(
|
|
32649
|
-
if (!existsSync4(
|
|
32648
|
+
function readSettingsFile(path5) {
|
|
32649
|
+
if (!existsSync4(path5)) return {};
|
|
32650
32650
|
try {
|
|
32651
|
-
return JSON.parse(readFileSync5(
|
|
32651
|
+
return JSON.parse(readFileSync5(path5, "utf-8"));
|
|
32652
32652
|
} catch {
|
|
32653
32653
|
return {};
|
|
32654
32654
|
}
|
|
@@ -32767,12 +32767,12 @@ function applySkillsBlock(existing) {
|
|
|
32767
32767
|
return next.endsWith("\n") ? next : next + "\n";
|
|
32768
32768
|
}
|
|
32769
32769
|
function writeSkillsFile(relPath, wrap) {
|
|
32770
|
-
const
|
|
32771
|
-
mkdirSync3(dirname3(
|
|
32772
|
-
const existing = existsSync4(
|
|
32770
|
+
const path5 = resolve3(process.cwd(), relPath);
|
|
32771
|
+
mkdirSync3(dirname3(path5), { recursive: true });
|
|
32772
|
+
const existing = existsSync4(path5) ? readFileSync5(path5, "utf-8") : null;
|
|
32773
32773
|
const baseNext = applySkillsBlock(existing);
|
|
32774
32774
|
const next = wrap && existing === null ? wrap(baseNext) : baseNext;
|
|
32775
|
-
if (next !== existing) writeFileSync4(
|
|
32775
|
+
if (next !== existing) writeFileSync4(path5, next);
|
|
32776
32776
|
}
|
|
32777
32777
|
function setupClaudeMd() {
|
|
32778
32778
|
writeSkillsFile(PRIMER_FILES.claude);
|
|
@@ -32811,10 +32811,10 @@ function applyAiderConf(existing) {
|
|
|
32811
32811
|
}
|
|
32812
32812
|
function setupAiderMd() {
|
|
32813
32813
|
writeSkillsFile(PRIMER_FILES.aider);
|
|
32814
|
-
const
|
|
32815
|
-
const existing = existsSync4(
|
|
32814
|
+
const path5 = resolve3(process.cwd(), AIDER_CONF_FILE);
|
|
32815
|
+
const existing = existsSync4(path5) ? readFileSync5(path5, "utf-8") : null;
|
|
32816
32816
|
const next = applyAiderConf(existing);
|
|
32817
|
-
if (next !== null) writeFileSync4(
|
|
32817
|
+
if (next !== null) writeFileSync4(path5, next);
|
|
32818
32818
|
}
|
|
32819
32819
|
function setupGeminiMd() {
|
|
32820
32820
|
writeSkillsFile(PRIMER_FILES.gemini);
|
|
@@ -32888,12 +32888,12 @@ var LOCK_WAIT_MS2 = 3e4;
|
|
|
32888
32888
|
var LOCK_POLL_MS2 = 500;
|
|
32889
32889
|
var LOCK_HEARTBEAT_MS = 15e3;
|
|
32890
32890
|
var LOCK_STALE_MS = 9e4;
|
|
32891
|
-
function isLockReclaimable(
|
|
32891
|
+
function isLockReclaimable(path5, now = Date.now()) {
|
|
32892
32892
|
let raw;
|
|
32893
32893
|
let mtimeMs;
|
|
32894
32894
|
try {
|
|
32895
|
-
raw = readFileSync6(
|
|
32896
|
-
mtimeMs = statSync(
|
|
32895
|
+
raw = readFileSync6(path5, "utf-8").trim();
|
|
32896
|
+
mtimeMs = statSync(path5).mtimeMs;
|
|
32897
32897
|
} catch {
|
|
32898
32898
|
return false;
|
|
32899
32899
|
}
|
|
@@ -32907,8 +32907,8 @@ function isLockReclaimable(path4, now = Date.now()) {
|
|
|
32907
32907
|
return now - mtimeMs > LOCK_STALE_MS;
|
|
32908
32908
|
}
|
|
32909
32909
|
async function acquireLock(progress) {
|
|
32910
|
-
const
|
|
32911
|
-
mkdirSync4(dirname4(
|
|
32910
|
+
const path5 = lockPath();
|
|
32911
|
+
mkdirSync4(dirname4(path5), { recursive: true });
|
|
32912
32912
|
const start = Date.now();
|
|
32913
32913
|
let waitSpinner = null;
|
|
32914
32914
|
const settleWait = (ok) => {
|
|
@@ -32919,12 +32919,12 @@ async function acquireLock(progress) {
|
|
|
32919
32919
|
};
|
|
32920
32920
|
while (true) {
|
|
32921
32921
|
try {
|
|
32922
|
-
const fd2 = openSync2(
|
|
32922
|
+
const fd2 = openSync2(path5, "wx");
|
|
32923
32923
|
writeFileSync5(fd2, String(process.pid));
|
|
32924
32924
|
closeSync2(fd2);
|
|
32925
32925
|
const beat = setInterval(() => {
|
|
32926
32926
|
try {
|
|
32927
|
-
utimesSync(
|
|
32927
|
+
utimesSync(path5, /* @__PURE__ */ new Date(), /* @__PURE__ */ new Date());
|
|
32928
32928
|
} catch {
|
|
32929
32929
|
}
|
|
32930
32930
|
}, LOCK_HEARTBEAT_MS);
|
|
@@ -32933,14 +32933,14 @@ async function acquireLock(progress) {
|
|
|
32933
32933
|
return () => {
|
|
32934
32934
|
clearInterval(beat);
|
|
32935
32935
|
try {
|
|
32936
|
-
unlinkSync3(
|
|
32936
|
+
unlinkSync3(path5);
|
|
32937
32937
|
} catch {
|
|
32938
32938
|
}
|
|
32939
32939
|
};
|
|
32940
32940
|
} catch {
|
|
32941
|
-
if (isLockReclaimable(
|
|
32941
|
+
if (isLockReclaimable(path5)) {
|
|
32942
32942
|
try {
|
|
32943
|
-
unlinkSync3(
|
|
32943
|
+
unlinkSync3(path5);
|
|
32944
32944
|
} catch {
|
|
32945
32945
|
}
|
|
32946
32946
|
continue;
|
|
@@ -32948,7 +32948,7 @@ async function acquireLock(progress) {
|
|
|
32948
32948
|
if (Date.now() - start > LOCK_WAIT_MS2) {
|
|
32949
32949
|
settleWait(false);
|
|
32950
32950
|
throw new Error(
|
|
32951
|
-
`Another sync is in progress (${
|
|
32951
|
+
`Another sync is in progress (${path5}). Waited ${LOCK_WAIT_MS2 / 1e3}s. Remove the file manually if you're sure no sync is running.`
|
|
32952
32952
|
);
|
|
32953
32953
|
}
|
|
32954
32954
|
if (!waitSpinner) {
|
|
@@ -32959,10 +32959,10 @@ async function acquireLock(progress) {
|
|
|
32959
32959
|
}
|
|
32960
32960
|
}
|
|
32961
32961
|
function readBaseline(projectGuid) {
|
|
32962
|
-
const
|
|
32963
|
-
if (!existsSync5(
|
|
32962
|
+
const path5 = syncStatePath();
|
|
32963
|
+
if (!existsSync5(path5)) return { projectGuid, files: {}, lastFullSync: null };
|
|
32964
32964
|
try {
|
|
32965
|
-
const parsed = JSON.parse(readFileSync6(
|
|
32965
|
+
const parsed = JSON.parse(readFileSync6(path5, "utf-8"));
|
|
32966
32966
|
if (parsed.projectGuid !== projectGuid) {
|
|
32967
32967
|
return { projectGuid, files: {}, lastFullSync: null };
|
|
32968
32968
|
}
|
|
@@ -32977,9 +32977,9 @@ function readBaseline(projectGuid) {
|
|
|
32977
32977
|
}
|
|
32978
32978
|
}
|
|
32979
32979
|
function writeBaseline(b7) {
|
|
32980
|
-
const
|
|
32981
|
-
mkdirSync4(dirname4(
|
|
32982
|
-
writeFileSync5(
|
|
32980
|
+
const path5 = syncStatePath();
|
|
32981
|
+
mkdirSync4(dirname4(path5), { recursive: true });
|
|
32982
|
+
writeFileSync5(path5, JSON.stringify(b7, null, 2));
|
|
32983
32983
|
}
|
|
32984
32984
|
function walkLocal(root, ignorePatterns, baseline) {
|
|
32985
32985
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -33014,11 +33014,11 @@ function walkLocal(root, ignorePatterns, baseline) {
|
|
|
33014
33014
|
return result;
|
|
33015
33015
|
}
|
|
33016
33016
|
async function ensureLocalHashes(root, local, paths) {
|
|
33017
|
-
for (const
|
|
33018
|
-
const info2 = local.get(
|
|
33017
|
+
for (const path5 of paths) {
|
|
33018
|
+
const info2 = local.get(path5);
|
|
33019
33019
|
if (!info2 || info2.sha256) continue;
|
|
33020
33020
|
try {
|
|
33021
|
-
const { sha256: sha2562 } = await hashFile(join4(root,
|
|
33021
|
+
const { sha256: sha2562 } = await hashFile(join4(root, path5));
|
|
33022
33022
|
info2.sha256 = sha2562;
|
|
33023
33023
|
} catch {
|
|
33024
33024
|
}
|
|
@@ -33051,9 +33051,9 @@ async function fetchRemote(projectGuid) {
|
|
|
33051
33051
|
const out = /* @__PURE__ */ new Map();
|
|
33052
33052
|
for (const f of res.data) {
|
|
33053
33053
|
if (f.type !== "file") continue;
|
|
33054
|
-
const
|
|
33055
|
-
out.set(
|
|
33056
|
-
path:
|
|
33054
|
+
const path5 = normalizeTreePath(f.path);
|
|
33055
|
+
out.set(path5, {
|
|
33056
|
+
path: path5,
|
|
33057
33057
|
size: f.size,
|
|
33058
33058
|
sha256: f.contentHash,
|
|
33059
33059
|
serverVersion: f.serverVersion,
|
|
@@ -33065,7 +33065,7 @@ async function fetchRemote(projectGuid) {
|
|
|
33065
33065
|
function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
33066
33066
|
const extract3 = tar2.extract();
|
|
33067
33067
|
const files = /* @__PURE__ */ new Map();
|
|
33068
|
-
return new Promise((
|
|
33068
|
+
return new Promise((resolve19, reject) => {
|
|
33069
33069
|
let settled = false;
|
|
33070
33070
|
let idle;
|
|
33071
33071
|
const arm = () => {
|
|
@@ -33101,7 +33101,7 @@ function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
|
33101
33101
|
});
|
|
33102
33102
|
entryStream.resume();
|
|
33103
33103
|
});
|
|
33104
|
-
extract3.on("finish", () => done(
|
|
33104
|
+
extract3.on("finish", () => done(resolve19, files));
|
|
33105
33105
|
extract3.on("error", (e) => done(reject, e));
|
|
33106
33106
|
stream.on("error", (e) => done(reject, e));
|
|
33107
33107
|
arm();
|
|
@@ -33112,12 +33112,12 @@ async function downloadAll(projectGuid, onBytes) {
|
|
|
33112
33112
|
const stream = await downloadStream(`/projects/${projectGuid}/files/tree?content=tar`);
|
|
33113
33113
|
return extractTarToMap(stream, DOWNLOAD_IDLE_MS, onBytes);
|
|
33114
33114
|
}
|
|
33115
|
-
async function fetchOneOutcome(projectGuid,
|
|
33115
|
+
async function fetchOneOutcome(projectGuid, path5, expectedSha) {
|
|
33116
33116
|
try {
|
|
33117
33117
|
const stream = await downloadStream(
|
|
33118
|
-
`/projects/${projectGuid}/files/tree?content=tar&path=${encodeURIComponent(
|
|
33118
|
+
`/projects/${projectGuid}/files/tree?content=tar&path=${encodeURIComponent(path5)}`
|
|
33119
33119
|
);
|
|
33120
|
-
const want = normalizeTreePath(
|
|
33120
|
+
const want = normalizeTreePath(path5);
|
|
33121
33121
|
const files = await extractTarToMap(stream, DOWNLOAD_IDLE_MS, void 0, (p) => p === want);
|
|
33122
33122
|
const buf = files.get(want) ?? null;
|
|
33123
33123
|
if (!buf) return { kind: "transient" };
|
|
@@ -33131,8 +33131,8 @@ async function fetchOneOutcome(projectGuid, path4, expectedSha) {
|
|
|
33131
33131
|
return { kind: "transient" };
|
|
33132
33132
|
}
|
|
33133
33133
|
}
|
|
33134
|
-
async function fetchOne(projectGuid,
|
|
33135
|
-
const out = await fetchOneOutcome(projectGuid,
|
|
33134
|
+
async function fetchOne(projectGuid, path5, expectedSha) {
|
|
33135
|
+
const out = await fetchOneOutcome(projectGuid, path5, expectedSha);
|
|
33136
33136
|
return out.kind === "ok" ? out.buf : null;
|
|
33137
33137
|
}
|
|
33138
33138
|
function classifyLocal(info2, base) {
|
|
@@ -33153,11 +33153,11 @@ function classifyRemote(info2, base) {
|
|
|
33153
33153
|
if (iSha && bSha) return iSha === bSha ? "unchanged" : "modified";
|
|
33154
33154
|
return info2.size === base.size ? "unchanged" : "modified";
|
|
33155
33155
|
}
|
|
33156
|
-
function conflictedCopyName(
|
|
33156
|
+
function conflictedCopyName(path5) {
|
|
33157
33157
|
const ts2 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:T]/g, "-").replace(/\..+$/, "");
|
|
33158
33158
|
const host = hostname().replace(/[^A-Za-z0-9._-]/g, "_");
|
|
33159
|
-
const ext = extname2(
|
|
33160
|
-
const base = ext ?
|
|
33159
|
+
const ext = extname2(path5);
|
|
33160
|
+
const base = ext ? path5.slice(0, -ext.length) : path5;
|
|
33161
33161
|
return `${base} (conflict from ${host} ${ts2})${ext}`;
|
|
33162
33162
|
}
|
|
33163
33163
|
function plan(local, remote, baseline) {
|
|
@@ -33177,47 +33177,47 @@ function plan(local, remote, baseline) {
|
|
|
33177
33177
|
}
|
|
33178
33178
|
};
|
|
33179
33179
|
const allPaths = /* @__PURE__ */ new Set([...local.keys(), ...remote.keys(), ...Object.keys(baseline)]);
|
|
33180
|
-
for (const
|
|
33181
|
-
const L7 = local.get(
|
|
33182
|
-
const R = remote.get(
|
|
33183
|
-
const B7 = baseline[
|
|
33180
|
+
for (const path5 of allPaths) {
|
|
33181
|
+
const L7 = local.get(path5);
|
|
33182
|
+
const R = remote.get(path5);
|
|
33183
|
+
const B7 = baseline[path5];
|
|
33184
33184
|
const lSide = classifyLocal(L7, B7);
|
|
33185
33185
|
const rSide = classifyRemote(R, B7);
|
|
33186
33186
|
if (lSide === "absent" && rSide === "absent") continue;
|
|
33187
33187
|
const shasMatch = L7?.sha256 && R?.sha256 && L7.sha256 === R.sha256;
|
|
33188
33188
|
if (lSide === "absent" && rSide === "added") {
|
|
33189
|
-
actions.push({ path:
|
|
33189
|
+
actions.push({ path: path5, kind: "download", remoteSize: R.size });
|
|
33190
33190
|
continue;
|
|
33191
33191
|
}
|
|
33192
33192
|
if (lSide === "added" && rSide === "absent") {
|
|
33193
|
-
actions.push({ path:
|
|
33193
|
+
actions.push({ path: path5, kind: "upload", localSize: L7.size, expectedServerVersion: null });
|
|
33194
33194
|
continue;
|
|
33195
33195
|
}
|
|
33196
33196
|
if (lSide === "unchanged" && rSide === "unchanged") continue;
|
|
33197
33197
|
if (lSide === "unchanged" && rSide === "modified") {
|
|
33198
33198
|
if (B7 && R.serverVersion === B7.serverVersion && R.sha256 === B7.sha256) continue;
|
|
33199
|
-
actions.push({ path:
|
|
33199
|
+
actions.push({ path: path5, kind: "download", remoteSize: R.size });
|
|
33200
33200
|
continue;
|
|
33201
33201
|
}
|
|
33202
33202
|
if (lSide === "unchanged" && rSide === "deleted") {
|
|
33203
|
-
actions.push({ path:
|
|
33203
|
+
actions.push({ path: path5, kind: "delete-local", localSize: L7.size });
|
|
33204
33204
|
continue;
|
|
33205
33205
|
}
|
|
33206
33206
|
if (lSide === "modified" && rSide === "unchanged") {
|
|
33207
|
-
actions.push({ path:
|
|
33207
|
+
actions.push({ path: path5, kind: "upload", localSize: L7.size, expectedServerVersion: B7.serverVersion });
|
|
33208
33208
|
continue;
|
|
33209
33209
|
}
|
|
33210
33210
|
if (lSide === "modified" && rSide === "modified") {
|
|
33211
33211
|
if (shasMatch) {
|
|
33212
|
-
adoptIfNeeded(
|
|
33212
|
+
adoptIfNeeded(path5, L7, R);
|
|
33213
33213
|
continue;
|
|
33214
33214
|
}
|
|
33215
33215
|
actions.push({
|
|
33216
|
-
path:
|
|
33216
|
+
path: path5,
|
|
33217
33217
|
kind: "conflict",
|
|
33218
33218
|
localSize: L7.size,
|
|
33219
33219
|
remoteSize: R.size,
|
|
33220
|
-
renamedLocalTo: conflictedCopyName(
|
|
33220
|
+
renamedLocalTo: conflictedCopyName(path5),
|
|
33221
33221
|
expectedServerVersion: B7.serverVersion,
|
|
33222
33222
|
reason: "both sides modified since last sync"
|
|
33223
33223
|
});
|
|
@@ -33225,7 +33225,7 @@ function plan(local, remote, baseline) {
|
|
|
33225
33225
|
}
|
|
33226
33226
|
if (lSide === "modified" && rSide === "deleted") {
|
|
33227
33227
|
actions.push({
|
|
33228
|
-
path:
|
|
33228
|
+
path: path5,
|
|
33229
33229
|
kind: "upload",
|
|
33230
33230
|
localSize: L7.size,
|
|
33231
33231
|
expectedServerVersion: null,
|
|
@@ -33235,32 +33235,32 @@ function plan(local, remote, baseline) {
|
|
|
33235
33235
|
}
|
|
33236
33236
|
if (lSide === "added" && rSide === "added") {
|
|
33237
33237
|
if (shasMatch) {
|
|
33238
|
-
adoptIfNeeded(
|
|
33238
|
+
adoptIfNeeded(path5, L7, R);
|
|
33239
33239
|
continue;
|
|
33240
33240
|
}
|
|
33241
33241
|
actions.push({
|
|
33242
|
-
path:
|
|
33242
|
+
path: path5,
|
|
33243
33243
|
kind: "conflict",
|
|
33244
33244
|
localSize: L7.size,
|
|
33245
33245
|
remoteSize: R.size,
|
|
33246
|
-
renamedLocalTo: conflictedCopyName(
|
|
33246
|
+
renamedLocalTo: conflictedCopyName(path5),
|
|
33247
33247
|
expectedServerVersion: null,
|
|
33248
33248
|
reason: "both sides created the same path with different content"
|
|
33249
33249
|
});
|
|
33250
33250
|
continue;
|
|
33251
33251
|
}
|
|
33252
33252
|
if (lSide === "deleted" && rSide === "absent") {
|
|
33253
|
-
baselineDrops.push(
|
|
33253
|
+
baselineDrops.push(path5);
|
|
33254
33254
|
continue;
|
|
33255
33255
|
}
|
|
33256
33256
|
if (lSide === "deleted" && rSide === "unchanged") {
|
|
33257
|
-
actions.push({ path:
|
|
33257
|
+
actions.push({ path: path5, kind: "delete-remote", remoteSize: R.size, expectedServerVersion: R.serverVersion });
|
|
33258
33258
|
continue;
|
|
33259
33259
|
}
|
|
33260
33260
|
if (lSide === "deleted" && rSide === "modified") {
|
|
33261
33261
|
if (B7 && R.serverVersion === B7.serverVersion && R.sha256 === B7.sha256) continue;
|
|
33262
33262
|
actions.push({
|
|
33263
|
-
path:
|
|
33263
|
+
path: path5,
|
|
33264
33264
|
kind: "download",
|
|
33265
33265
|
remoteSize: R.size,
|
|
33266
33266
|
reason: "local deleted but remote modified - remote preserved"
|
|
@@ -33268,7 +33268,7 @@ function plan(local, remote, baseline) {
|
|
|
33268
33268
|
continue;
|
|
33269
33269
|
}
|
|
33270
33270
|
if (lSide === "deleted" && rSide === "deleted") {
|
|
33271
|
-
baselineDrops.push(
|
|
33271
|
+
baselineDrops.push(path5);
|
|
33272
33272
|
continue;
|
|
33273
33273
|
}
|
|
33274
33274
|
}
|
|
@@ -33325,9 +33325,9 @@ Plan deletes ${totalDeletes} files (${Math.round(fraction * 100)}% of the tree).
|
|
|
33325
33325
|
}
|
|
33326
33326
|
var GIPITY_IGNORE_FILE = ".gipityignore";
|
|
33327
33327
|
function readGipityIgnore(root) {
|
|
33328
|
-
const
|
|
33329
|
-
if (!existsSync5(
|
|
33330
|
-
return readFileSync6(
|
|
33328
|
+
const path5 = join4(root, GIPITY_IGNORE_FILE);
|
|
33329
|
+
if (!existsSync5(path5)) return [];
|
|
33330
|
+
return readFileSync6(path5, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\.\//, "").replace(/^\//, ""));
|
|
33331
33331
|
}
|
|
33332
33332
|
function effectiveIgnore(root, configIgnore) {
|
|
33333
33333
|
const base = configIgnore && configIgnore.length ? configIgnore : DEFAULT_SYNC_IGNORE;
|
|
@@ -33343,8 +33343,8 @@ function isLocalTreeClean() {
|
|
|
33343
33343
|
const local = walkLocal(root, effectiveIgnore(root, config.ignore), baseline.files);
|
|
33344
33344
|
const baselineFiles = baseline.files;
|
|
33345
33345
|
if (local.size !== Object.keys(baselineFiles).length) return false;
|
|
33346
|
-
for (const [
|
|
33347
|
-
const prior = baselineFiles[
|
|
33346
|
+
for (const [path5, info2] of local) {
|
|
33347
|
+
const prior = baselineFiles[path5];
|
|
33348
33348
|
if (!prior || prior.size !== info2.size || prior.mtime !== info2.mtime) return false;
|
|
33349
33349
|
}
|
|
33350
33350
|
return true;
|
|
@@ -33372,14 +33372,14 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33372
33372
|
const local = walkLocal(root, ignore2, baseline.files);
|
|
33373
33373
|
p?.phase("Checking Gipity for changes\u2026");
|
|
33374
33374
|
const remote = await fetchRemote(projectGuid);
|
|
33375
|
-
for (const
|
|
33376
|
-
if (shouldIgnore(
|
|
33375
|
+
for (const path5 of [...remote.keys()]) {
|
|
33376
|
+
if (shouldIgnore(path5, ignore2)) remote.delete(path5);
|
|
33377
33377
|
}
|
|
33378
33378
|
const needHash = [];
|
|
33379
|
-
for (const [
|
|
33379
|
+
for (const [path5, l7] of local) {
|
|
33380
33380
|
if (l7.sha256) continue;
|
|
33381
|
-
const r = remote.get(
|
|
33382
|
-
if (r?.sha256 || baseline.files[
|
|
33381
|
+
const r = remote.get(path5);
|
|
33382
|
+
if (r?.sha256 || baseline.files[path5]) needHash.push(path5);
|
|
33383
33383
|
}
|
|
33384
33384
|
if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}\u2026`);
|
|
33385
33385
|
await ensureLocalHashes(root, local, needHash);
|
|
@@ -33843,8 +33843,8 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33843
33843
|
delete baseline.deletesSkippedStreak;
|
|
33844
33844
|
}
|
|
33845
33845
|
cleanupEmptyDirs(root, dirsTouchedByDelete);
|
|
33846
|
-
for (const { path:
|
|
33847
|
-
for (const
|
|
33846
|
+
for (const { path: path5, entry } of planned.baselineAdopts) baseline.files[path5] = entry;
|
|
33847
|
+
for (const path5 of planned.baselineDrops) delete baseline.files[path5];
|
|
33848
33848
|
baseline.lastFullSync = (/* @__PURE__ */ new Date()).toISOString();
|
|
33849
33849
|
writeBaseline(baseline);
|
|
33850
33850
|
p?.finish();
|
|
@@ -34837,11 +34837,11 @@ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
|
|
|
34837
34837
|
import { join as join9, resolve as resolve8 } from "path";
|
|
34838
34838
|
import { homedir as homedir7 } from "os";
|
|
34839
34839
|
function checkGipityPlugin() {
|
|
34840
|
-
const
|
|
34840
|
+
const path5 = join9(homedir7(), ".claude", "settings.json");
|
|
34841
34841
|
let settings = {};
|
|
34842
|
-
if (existsSync9(
|
|
34842
|
+
if (existsSync9(path5)) {
|
|
34843
34843
|
try {
|
|
34844
|
-
settings = JSON.parse(readFileSync10(
|
|
34844
|
+
settings = JSON.parse(readFileSync10(path5, "utf-8"));
|
|
34845
34845
|
} catch {
|
|
34846
34846
|
}
|
|
34847
34847
|
}
|
|
@@ -35104,9 +35104,48 @@ init_config();
|
|
|
35104
35104
|
|
|
35105
35105
|
// src/page-fixtures.ts
|
|
35106
35106
|
init_api();
|
|
35107
|
-
|
|
35108
|
-
import {
|
|
35109
|
-
|
|
35107
|
+
init_config();
|
|
35108
|
+
import { readFileSync as readFileSync11, statSync as statSync5, existsSync as existsSync10, readdirSync as readdirSync6 } from "node:fs";
|
|
35109
|
+
import { basename as basename3, resolve as resolve11, relative as relative3, join as join11 } from "node:path";
|
|
35110
|
+
var FIND_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".gipity", "dist", "build", ".next", "coverage"]);
|
|
35111
|
+
function findByBasename(root, name, limit = 3) {
|
|
35112
|
+
const hits = [];
|
|
35113
|
+
const queue = [root];
|
|
35114
|
+
let visited = 0;
|
|
35115
|
+
while (queue.length && hits.length < limit && visited < 500) {
|
|
35116
|
+
const dir = queue.shift();
|
|
35117
|
+
visited++;
|
|
35118
|
+
let entries;
|
|
35119
|
+
try {
|
|
35120
|
+
entries = readdirSync6(dir, { withFileTypes: true });
|
|
35121
|
+
} catch {
|
|
35122
|
+
continue;
|
|
35123
|
+
}
|
|
35124
|
+
for (const e of entries) {
|
|
35125
|
+
if (e.isDirectory()) {
|
|
35126
|
+
if (!FIND_SKIP.has(e.name)) queue.push(join11(dir, e.name));
|
|
35127
|
+
} else if (e.name === name && hits.length < limit) {
|
|
35128
|
+
hits.push(join11(dir, e.name));
|
|
35129
|
+
}
|
|
35130
|
+
}
|
|
35131
|
+
}
|
|
35132
|
+
return hits;
|
|
35133
|
+
}
|
|
35134
|
+
function assertLocalAsset(flag, localPath) {
|
|
35135
|
+
const abs = resolve11(localPath);
|
|
35136
|
+
if (existsSync10(abs)) return;
|
|
35137
|
+
const root = getProjectRoot() ?? process.cwd();
|
|
35138
|
+
const elsewhere = findByBasename(root, basename3(localPath)).filter((p) => p !== abs);
|
|
35139
|
+
const found = elsewhere.length ? `
|
|
35140
|
+
That file DOES exist here \u2014 pass this path instead:
|
|
35141
|
+
${elsewhere.map((p) => ` ${flag} ${relative3(process.cwd(), p) || p}`).join("\n")}` : `
|
|
35142
|
+
Nothing named "${basename3(localPath)}" under ${root} either. Generate a frame with \`gipity generate image "<description>" -o ${localPath}\`, or check the path.`;
|
|
35143
|
+
throw new Error(
|
|
35144
|
+
`${flag} ${localPath}: no such file \u2014 looked for ${abs} (relative paths resolve against the current directory, ${process.cwd()}).${found}`
|
|
35145
|
+
);
|
|
35146
|
+
}
|
|
35147
|
+
async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
|
|
35148
|
+
assertLocalAsset(flag, localPath);
|
|
35110
35149
|
const name = basename3(localPath);
|
|
35111
35150
|
const size = statSync5(localPath).size;
|
|
35112
35151
|
const contentType = guessMime(localPath);
|
|
@@ -35134,6 +35173,21 @@ async function uploadPublicFixture(projectGuid, localPath) {
|
|
|
35134
35173
|
async function deleteFixture(projectGuid, guid) {
|
|
35135
35174
|
await del(`/api/${projectGuid}/uploads/${guid}`);
|
|
35136
35175
|
}
|
|
35176
|
+
var CAMERA_EXTS = [".png", ".jpg", ".jpeg", ".webp", ".mp4", ".webm", ".y4m", ".mjpeg"];
|
|
35177
|
+
function assertCameraFile(localPath) {
|
|
35178
|
+
assertLocalAsset("--camera", localPath);
|
|
35179
|
+
const ext = localPath.slice(localPath.lastIndexOf(".")).toLowerCase();
|
|
35180
|
+
if (CAMERA_EXTS.includes(ext)) return;
|
|
35181
|
+
throw new Error(
|
|
35182
|
+
`--camera ${basename3(localPath)}: unsupported file type "${ext || "(none)"}" \u2014 the camera feed must be an image or video (${CAMERA_EXTS.join(", ")}).
|
|
35183
|
+
A still image is played as a looping single-frame feed, which is what a gesture/pose/object model needs.
|
|
35184
|
+
No frame to hand? Generate one: gipity generate image "a hand making a closed fist, palm to camera, plain background"`
|
|
35185
|
+
);
|
|
35186
|
+
}
|
|
35187
|
+
async function uploadCameraFeed(projectGuid, localPath) {
|
|
35188
|
+
assertCameraFile(localPath);
|
|
35189
|
+
return uploadPublicFixture(projectGuid, localPath, "--camera");
|
|
35190
|
+
}
|
|
35137
35191
|
|
|
35138
35192
|
// src/commands/page-eval.ts
|
|
35139
35193
|
var EVAL_NO_VALUE_HINT = "The eval ran but returned no JSON-serializable value. A statement body with no `return`, an assignment, a void call, or a DOM node/function all serialize to null. End the script with an expression \u2014 or an explicit `return` \u2014 that yields plain data, e.g. `return { label: input.value, count: items.length }` or `return JSON.stringify(payload)`.";
|
|
@@ -35156,6 +35210,21 @@ function normalizeEvalResult(raw) {
|
|
|
35156
35210
|
}
|
|
35157
35211
|
return { result: raw, noValue: false };
|
|
35158
35212
|
}
|
|
35213
|
+
var EVAL_EMPTY_STATE_HINT = "The eval returned an empty value \u2014 the script ran, but the state it read was not there at that instant. A fixed --wait samples ONE moment; transient state (a round result, a toast, a detection) can be gone by then. Poll INSIDE the body and return the moment the condition holds (raise --timeout if the sequence needs longer), or gate the read on the app's own signal with --wait-for '<selector>'.";
|
|
35214
|
+
var CAMERA_FRAME_CHECK = "in the eval body, run the model on the frame YOURSELF (grab the app's <video> element, or the app's own detector on it) and return its RAW output alongside the app's state. Model saw nothing => the frame is at fault: regenerate it (subject filling the frame, plain background, even light), do not re-run the same one. Model saw it but the app's state is empty => the bug is in the app's wiring, not the picture.";
|
|
35215
|
+
var EVAL_CAMERA_EMPTY_HINT = `The eval returned an empty value on a --camera run. --camera loops ONE still image, and the model is deterministic on it, so a longer --wait/--timeout re-runs the identical inference and returns the identical nothing \u2014 do not escalate. Two suspects: the frame, or the app's wiring. Tell them apart in one run: ${CAMERA_FRAME_CHECK} (If the app only starts its pipeline on a click, a headless run never clicks \u2014 start it on load and gate this eval on the app's own ready signal with --wait-for '<selector>'.)`;
|
|
35216
|
+
function isEmptyStateResult(result) {
|
|
35217
|
+
let parsed;
|
|
35218
|
+
try {
|
|
35219
|
+
parsed = JSON.parse(result);
|
|
35220
|
+
} catch {
|
|
35221
|
+
return false;
|
|
35222
|
+
}
|
|
35223
|
+
if (Array.isArray(parsed)) return parsed.length === 0;
|
|
35224
|
+
if (!parsed || typeof parsed !== "object") return false;
|
|
35225
|
+
const values = Object.values(parsed);
|
|
35226
|
+
return values.every((v7) => v7 === null || v7 === void 0 || v7 === "");
|
|
35227
|
+
}
|
|
35159
35228
|
var EXPR_ECHO_MAX_CHARS = 120;
|
|
35160
35229
|
function summarizeExpr(expr) {
|
|
35161
35230
|
const lines = expr.split("\n");
|
|
@@ -35178,6 +35247,71 @@ function capWaitMs(rawWait, url) {
|
|
|
35178
35247
|
));
|
|
35179
35248
|
return MAX_WAIT_MS;
|
|
35180
35249
|
}
|
|
35250
|
+
var MAX_EVAL_STEPS = 4;
|
|
35251
|
+
var WAIT_FOR_MAX_MS = 3e4;
|
|
35252
|
+
function capWaitForTimeoutMs(raw) {
|
|
35253
|
+
const parsed = parseInt(raw, 10);
|
|
35254
|
+
const ms2 = Number.isFinite(parsed) && parsed >= 0 ? parsed : 5e3;
|
|
35255
|
+
if (ms2 <= WAIT_FOR_MAX_MS) return ms2;
|
|
35256
|
+
console.error(warning(
|
|
35257
|
+
`--wait-timeout ${ms2}ms exceeds the ${WAIT_FOR_MAX_MS}ms cap on the selector gate \u2014 using ${WAIT_FOR_MAX_MS}ms. A state that takes longer than that to appear is not a settling delay: have the page reach it on load, or watch for it INSIDE the script (--timeout ${EVAL_SCRIPT_BUDGET_MAX_MS}).`
|
|
35258
|
+
));
|
|
35259
|
+
return WAIT_FOR_MAX_MS;
|
|
35260
|
+
}
|
|
35261
|
+
function parsesAsExpression(s) {
|
|
35262
|
+
try {
|
|
35263
|
+
new Function(`return (${s}
|
|
35264
|
+
);`);
|
|
35265
|
+
return true;
|
|
35266
|
+
} catch {
|
|
35267
|
+
return false;
|
|
35268
|
+
}
|
|
35269
|
+
}
|
|
35270
|
+
function withPrelude(script, prelude) {
|
|
35271
|
+
if (!prelude) return script;
|
|
35272
|
+
return parsesAsExpression(script) ? `${prelude}
|
|
35273
|
+
return (${script});` : `${prelude}
|
|
35274
|
+
${script}`;
|
|
35275
|
+
}
|
|
35276
|
+
var CAMERA_DEFAULT_WAIT_MS = 15e3;
|
|
35277
|
+
var CAMERA_SETUP_BUDGET_MS = 3e4;
|
|
35278
|
+
var EVAL_SCRIPT_BUDGET_MS = 3e4;
|
|
35279
|
+
var EVAL_SCRIPT_BUDGET_CAMERA_MS = 45e3;
|
|
35280
|
+
var EVAL_SCRIPT_BUDGET_MAX_MS = 9e4;
|
|
35281
|
+
function capScriptBudgetMs(rawTimeout, hasMedia) {
|
|
35282
|
+
const fallback = hasMedia ? EVAL_SCRIPT_BUDGET_CAMERA_MS : EVAL_SCRIPT_BUDGET_MS;
|
|
35283
|
+
if (rawTimeout === void 0) return fallback;
|
|
35284
|
+
const parsed = parseInt(rawTimeout, 10);
|
|
35285
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return fallback;
|
|
35286
|
+
if (parsed > EVAL_SCRIPT_BUDGET_MAX_MS) {
|
|
35287
|
+
console.error(warning(
|
|
35288
|
+
`--timeout ${parsed}ms exceeds the ${EVAL_SCRIPT_BUDGET_MAX_MS}ms max in-page budget \u2014 using ${EVAL_SCRIPT_BUDGET_MAX_MS}ms. A body that needs longer than that is doing its waiting in the wrong place: start the slow work on page load and absorb it in the pre-eval window (--wait-for '<ready-selector>'), keeping the body to a quick read.`
|
|
35289
|
+
));
|
|
35290
|
+
return EVAL_SCRIPT_BUDGET_MAX_MS;
|
|
35291
|
+
}
|
|
35292
|
+
return Math.max(1e3, parsed);
|
|
35293
|
+
}
|
|
35294
|
+
function evalWorkBudgetMs(o) {
|
|
35295
|
+
const script = o.scriptBudgetMs ?? (o.hasCamera ? EVAL_SCRIPT_BUDGET_CAMERA_MS : EVAL_SCRIPT_BUDGET_MS);
|
|
35296
|
+
const legs = Math.max(1, o.legs ?? 1);
|
|
35297
|
+
const settle = o.waitMs + (o.waitForTimeoutMs ?? 0);
|
|
35298
|
+
const firstPass = settle + script * legs;
|
|
35299
|
+
return firstPass + (o.hasReload ? settle + script : 0) + (o.hasCamera ? CAMERA_SETUP_BUDGET_MS : 0);
|
|
35300
|
+
}
|
|
35301
|
+
function budgetOverrunHint(reason, usedBudgetMs) {
|
|
35302
|
+
if (!/in-page budget/i.test(reason)) return null;
|
|
35303
|
+
if (usedBudgetMs >= EVAL_SCRIPT_BUDGET_MAX_MS) {
|
|
35304
|
+
return `This run already used the maximum in-page budget (--timeout ${EVAL_SCRIPT_BUDGET_MAX_MS}). More time is not the answer: have the page start its slow work on load, wait it out in the pre-eval window (--wait-for '<ready-selector>' --wait-timeout ${MAX_WAIT_MS}), and keep the body to a quick read.`;
|
|
35305
|
+
}
|
|
35306
|
+
return `That budget was ${Math.round(usedBudgetMs / 1e3)}s. Raise it with --timeout <ms> (up to ${EVAL_SCRIPT_BUDGET_MAX_MS}), e.g. --timeout ${EVAL_SCRIPT_BUDGET_MAX_MS} \u2014 or gate on a ready signal instead: --wait-for '<selector>' --wait-timeout ${MAX_WAIT_MS}.`;
|
|
35307
|
+
}
|
|
35308
|
+
function slowRenderMessage(fps, o) {
|
|
35309
|
+
if (o.camera) {
|
|
35310
|
+
const frames = Math.max(1, Math.round(fps * (o.waitMs / 1e3)));
|
|
35311
|
+
return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps, so the app's vision pipeline ran on roughly ${bold(`${frames} frame${frames === 1 ? "" : "s"}`)} during the ${Math.round(o.waitMs / 1e3)}s before this eval (it infers once per painted frame). ${bold("That is enough:")} --camera loops your still image, so every one of those frames is the SAME pixels and the model returns the SAME answer on each \u2014 re-running with a bigger --wait/--timeout cannot change the result. ${bold("Do not escalate the wait.")} If a detection landed, it is real. If nothing was detected, the suspect is the ${bold("frame")} (a model needs the whole subject in shot \u2014 a tight crop, an odd angle or a busy background reads as nothing) or the ${bold("app")} (frames never reach the model) \u2014 never the frame rate. Settle it in ONE run: ${CAMERA_FRAME_CHECK}`;
|
|
35312
|
+
}
|
|
35313
|
+
return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps. Waiting on real time (setTimeout) advances animation/physics time far slower than it looks \u2014 assertions after a wall-clock wait can report a false negative. Step the app's own loop deterministically instead (3D templates: ${bold("core.advance(seconds)")}).`;
|
|
35314
|
+
}
|
|
35181
35315
|
async function pollEvalResult(evalJobId, expectedWorkMs) {
|
|
35182
35316
|
const deadline = Date.now() + expectedWorkMs + 6e4;
|
|
35183
35317
|
let missCount = 0;
|
|
@@ -35196,10 +35330,13 @@ async function pollEvalResult(evalJobId, expectedWorkMs) {
|
|
|
35196
35330
|
if (rec.status === "error") throw new ApiError(rec.httpStatus, rec.code, rec.reason);
|
|
35197
35331
|
await sleep(1e3);
|
|
35198
35332
|
}
|
|
35199
|
-
throw new ApiError(
|
|
35333
|
+
throw new ApiError(
|
|
35334
|
+
504,
|
|
35335
|
+
"EVAL_TIMEOUT",
|
|
35336
|
+
`the browser did not report back within ${Math.round((expectedWorkMs + 6e4) / 1e3)}s \u2014 that budget already covers --wait, --wait-for, the eval body and any --camera setup, so this is the browser itself being slow or stuck (a cold sandbox, a heavy page), not your expression being too big. Lowering --wait does NOT help and on a --camera run actively hurts (the wait is what lets the vision model load \u2014 cut it and the eval reads an empty page instead). Just run the same command again: the sandbox is warm on the second hit.`
|
|
35337
|
+
);
|
|
35200
35338
|
}
|
|
35201
|
-
|
|
35202
|
-
function evalExecTimeoutMessage(result) {
|
|
35339
|
+
function evalExecTimeoutMessage(result, budgetMs) {
|
|
35203
35340
|
let parsed;
|
|
35204
35341
|
try {
|
|
35205
35342
|
parsed = JSON.parse(result);
|
|
@@ -35208,10 +35345,18 @@ function evalExecTimeoutMessage(result) {
|
|
|
35208
35345
|
}
|
|
35209
35346
|
if (!parsed || parsed.success !== false || typeof parsed.error !== "string") return null;
|
|
35210
35347
|
if (!/CDP command timed out:\s*Runtime\.evaluate/i.test(parsed.error)) return null;
|
|
35211
|
-
|
|
35348
|
+
const budget = Math.round(budgetMs / 1e3);
|
|
35349
|
+
return `the script hit its ${budget}s in-page budget \u2014 the body (including its own await/setTimeout pauses) ran longer than that. --wait sleeps BEFORE the script and does not extend it.
|
|
35350
|
+
` + (budgetOverrunHint("in-page budget", budgetMs) ?? "") + `
|
|
35351
|
+
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.`;
|
|
35212
35352
|
}
|
|
35213
35353
|
var JS_DECOY_FLAGS = ["--js", "--javascript", "--script", "--code", "--expr", "--eval", "--exec"];
|
|
35214
|
-
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]",
|
|
35354
|
+
var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script). ONE client per call - to verify realtime/presence across concurrent clients use `page test --observe` instead").argument("<url>", "URL to load").argument("[expr]", `JavaScript to evaluate in page context (inline expression or statement body with return/await; result is JSON-serialized). Omit when using --file. Time budget: the body has ${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(
|
|
35355
|
+
"--step <expr>",
|
|
35356
|
+
`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.`,
|
|
35357
|
+
(val, prev) => [...prev, val],
|
|
35358
|
+
[]
|
|
35359
|
+
).option(
|
|
35215
35360
|
"--fixture <path>",
|
|
35216
35361
|
"Host a local file and expose it to the eval as `fixtureUrl` (and under `fixtures` by basename) to fetch in-page. For verifying a render/parse path against a real binary (an MP3, an image) - no size limit, auto-deleted after the run. Repeat for several files (single-value so it never swallows the inline <expr>).",
|
|
35217
35362
|
(val, prev) => [...prev, val],
|
|
@@ -35219,7 +35364,13 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35219
35364
|
).option(
|
|
35220
35365
|
"--reload <expr>",
|
|
35221
35366
|
"After the first eval, reload the page IN PLACE (localStorage/sessionStorage/cookies preserved) and evaluate this second expression against the post-reload DOM. One command verifies persisted state survives a reload: seed/assert state with <expr>, then assert the restored UI here."
|
|
35222
|
-
).option("--reload-file <path>", "Read the post-reload expression from a file instead of inline --reload (mutually exclusive)").option(
|
|
35367
|
+
).option("--reload-file <path>", "Read the post-reload expression from a file instead of inline --reload (mutually exclusive)").option(
|
|
35368
|
+
"--camera <path>",
|
|
35369
|
+
`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".`
|
|
35370
|
+
).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(
|
|
35371
|
+
"--timeout <ms>",
|
|
35372
|
+
`How long the script itself may run IN the page - its own await/setTimeout pauses count (default ${EVAL_SCRIPT_BUDGET_MS}, ${EVAL_SCRIPT_BUDGET_CAMERA_MS} with --camera/--fake-media; 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.`
|
|
35373
|
+
).option("--auth", "Evaluate signed in as you (your Gipity account), so a page behind a Sign-in-with-Gipity login is reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai.").option("--json", "Output as JSON").action((url, exprArg, opts) => run("Page eval", async () => {
|
|
35223
35374
|
const decoy = JS_DECOY_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
35224
35375
|
if (decoy) {
|
|
35225
35376
|
pageEvalCommand.error(
|
|
@@ -35258,17 +35409,44 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35258
35409
|
pageEvalCommand.error(`error: Cannot read file: ${opts.reloadFile}`);
|
|
35259
35410
|
}
|
|
35260
35411
|
}
|
|
35261
|
-
const
|
|
35262
|
-
|
|
35263
|
-
|
|
35412
|
+
const steps = opts.step ?? [];
|
|
35413
|
+
if (steps.length > MAX_EVAL_STEPS) {
|
|
35414
|
+
pageEvalCommand.error(
|
|
35415
|
+
`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`
|
|
35416
|
+
);
|
|
35417
|
+
}
|
|
35418
|
+
let waitMs = capWaitMs(opts.wait, url);
|
|
35419
|
+
const waitForTimeoutMs = capWaitForTimeoutMs(opts.waitTimeout);
|
|
35420
|
+
const hasMedia = !!opts.camera || !!opts.fakeMedia;
|
|
35421
|
+
const scriptBudgetMs = capScriptBudgetMs(opts.timeout, hasMedia);
|
|
35422
|
+
const explicitWait = pageEvalCommand.getOptionValueSource("wait") !== "default";
|
|
35423
|
+
const chosenWait = explicitWait || !!opts.waitFor;
|
|
35424
|
+
if (opts.camera && !chosenWait) {
|
|
35425
|
+
waitMs = CAMERA_DEFAULT_WAIT_MS;
|
|
35426
|
+
console.error(muted(
|
|
35427
|
+
`--camera: waiting ${CAMERA_DEFAULT_WAIT_MS / 1e3}s before the eval so the camera and the app's vision model finish loading (they cannot warm up inside the eval body \u2014 that has a ${scriptBudgetMs / 1e3}s budget of its own, --timeout to change it). Override with --wait <ms>, or use --wait-for '<ready-selector>' to stop as soon as the app is ready.`
|
|
35428
|
+
));
|
|
35429
|
+
}
|
|
35430
|
+
const settleAfterGateMs = opts.waitFor && explicitWait ? waitMs : 0;
|
|
35431
|
+
const sentWaitMs = settleAfterGateMs > 0 ? 0 : waitMs;
|
|
35432
|
+
const sentScriptBudgetMs = Math.min(scriptBudgetMs + settleAfterGateMs, EVAL_SCRIPT_BUDGET_MAX_MS);
|
|
35264
35433
|
const fixturePaths = opts.fixture ?? [];
|
|
35265
35434
|
const hosted = [];
|
|
35435
|
+
let camera;
|
|
35266
35436
|
let projectGuid;
|
|
35267
35437
|
let sentExpr = expr;
|
|
35438
|
+
let sentSteps = steps;
|
|
35439
|
+
if (opts.camera) assertCameraFile(opts.camera);
|
|
35268
35440
|
try {
|
|
35269
|
-
if (fixturePaths.length) {
|
|
35441
|
+
if (fixturePaths.length || opts.camera) {
|
|
35270
35442
|
const { config } = await resolveProjectContext({});
|
|
35271
35443
|
projectGuid = config.projectGuid;
|
|
35444
|
+
}
|
|
35445
|
+
if (opts.camera) {
|
|
35446
|
+
console.log(muted(`Hosting camera feed ${opts.camera}\u2026`));
|
|
35447
|
+
camera = await uploadCameraFeed(projectGuid, opts.camera);
|
|
35448
|
+
}
|
|
35449
|
+
if (fixturePaths.length) {
|
|
35272
35450
|
for (const p of fixturePaths) {
|
|
35273
35451
|
console.log(muted(`Hosting fixture ${p}\u2026`));
|
|
35274
35452
|
hosted.push(await uploadPublicFixture(projectGuid, p));
|
|
@@ -35276,30 +35454,63 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35276
35454
|
const map = {};
|
|
35277
35455
|
for (const h of hosted) map[h.name] = h.url;
|
|
35278
35456
|
const prelude = `const fixtures=${JSON.stringify(map)};const fixtureUrl=${JSON.stringify(hosted[0].url)};`;
|
|
35279
|
-
sentExpr =
|
|
35280
|
-
|
|
35281
|
-
|
|
35457
|
+
sentExpr = withPrelude(sentExpr, prelude);
|
|
35458
|
+
sentSteps = sentSteps.map((s) => withPrelude(s, prelude));
|
|
35459
|
+
}
|
|
35460
|
+
if (settleAfterGateMs > 0) {
|
|
35461
|
+
sentExpr = withPrelude(sentExpr, `await new Promise((r) => setTimeout(r, ${settleAfterGateMs}));`);
|
|
35282
35462
|
}
|
|
35283
35463
|
const kickoff = await post("/tools/browser/eval", {
|
|
35284
35464
|
url,
|
|
35285
35465
|
expr: sentExpr,
|
|
35286
|
-
waitMs,
|
|
35466
|
+
waitMs: sentWaitMs,
|
|
35467
|
+
steps: sentSteps.length ? sentSteps : void 0,
|
|
35287
35468
|
reloadExpr,
|
|
35288
35469
|
waitForSelector: opts.waitFor || void 0,
|
|
35289
35470
|
waitForTimeoutMs: opts.waitFor ? waitForTimeoutMs : void 0,
|
|
35290
|
-
|
|
35471
|
+
timeoutMs: sentScriptBudgetMs,
|
|
35472
|
+
auth: opts.auth || void 0,
|
|
35473
|
+
// A camera feed needs the synthetic devices in place to be played into,
|
|
35474
|
+
// so --camera implies --fake-media rather than failing on the pairing.
|
|
35475
|
+
fakeMedia: opts.fakeMedia || !!camera || void 0,
|
|
35476
|
+
cameraUrl: camera?.url
|
|
35291
35477
|
});
|
|
35292
|
-
|
|
35478
|
+
let d;
|
|
35479
|
+
try {
|
|
35480
|
+
d = await pollEvalResult(kickoff.data.evalJobId, evalWorkBudgetMs({
|
|
35481
|
+
waitMs: sentWaitMs,
|
|
35482
|
+
waitForTimeoutMs: opts.waitFor ? waitForTimeoutMs : 0,
|
|
35483
|
+
hasReload: reloadExpr !== void 0,
|
|
35484
|
+
hasCamera: !!camera,
|
|
35485
|
+
scriptBudgetMs: sentScriptBudgetMs,
|
|
35486
|
+
legs: 1 + sentSteps.length
|
|
35487
|
+
}));
|
|
35488
|
+
} catch (err) {
|
|
35489
|
+
const msg = err?.message ?? "";
|
|
35490
|
+
const hint = budgetOverrunHint(msg, scriptBudgetMs);
|
|
35491
|
+
if (!hint) throw err;
|
|
35492
|
+
throw new Error(`${msg}
|
|
35493
|
+
${hint}`);
|
|
35494
|
+
}
|
|
35293
35495
|
const { result, noValue } = normalizeEvalResult(d.result);
|
|
35294
35496
|
const reload = d.reloadResult !== void 0 ? normalizeEvalResult(d.reloadResult) : void 0;
|
|
35295
|
-
const execTimeout = evalExecTimeoutMessage(d.result);
|
|
35497
|
+
const execTimeout = evalExecTimeoutMessage(d.result, scriptBudgetMs);
|
|
35296
35498
|
if (execTimeout) throw new Error(execTimeout);
|
|
35499
|
+
const stepOut = steps.map((stepExpr, i) => {
|
|
35500
|
+
const raw = d.stepResults?.[i];
|
|
35501
|
+
const norm = raw !== void 0 ? normalizeEvalResult(raw) : void 0;
|
|
35502
|
+
return { expr: stepExpr, result: norm?.result ?? "", noValue: norm?.noValue ?? true };
|
|
35503
|
+
});
|
|
35504
|
+
const emptyState2 = !noValue && isEmptyStateResult(result);
|
|
35505
|
+
const emptyHint = camera ? EVAL_CAMERA_EMPTY_HINT : EVAL_EMPTY_STATE_HINT;
|
|
35297
35506
|
if (opts.json) {
|
|
35298
35507
|
console.log(JSON.stringify({
|
|
35299
35508
|
...d,
|
|
35300
35509
|
result,
|
|
35510
|
+
...stepOut.length ? { stepResults: stepOut.map((s) => s.result) } : {},
|
|
35301
35511
|
...reload ? { reloadResult: reload.result } : {},
|
|
35302
|
-
...noValue ? { hint: EVAL_NO_VALUE_HINT } : {}
|
|
35512
|
+
...noValue ? { hint: EVAL_NO_VALUE_HINT } : {},
|
|
35513
|
+
...emptyState2 ? { hint: emptyHint } : {}
|
|
35303
35514
|
}));
|
|
35304
35515
|
return;
|
|
35305
35516
|
}
|
|
@@ -35308,19 +35519,29 @@ return (${expr});`;
|
|
|
35308
35519
|
console.log(`${warning("\u26A0 Navigation incomplete:")} ${d.note || "page did not reach full load"}`);
|
|
35309
35520
|
}
|
|
35310
35521
|
if (d.slowRender) {
|
|
35311
|
-
console.log(
|
|
35522
|
+
console.log(slowRenderMessage(d.slowRender.fps, { camera: !!camera, waitMs }));
|
|
35312
35523
|
}
|
|
35313
35524
|
if (d.auth?.requested) {
|
|
35314
35525
|
const who = getAuth()?.email;
|
|
35315
35526
|
console.log(d.auth.established ? `${muted("Auth:")} ${success("session established")}${who ? muted(` as ${who}`) : ""} ${muted("(what the page renders with it is app-defined)")}` : `${warning("Auth: session NOT established")}${d.auth.detail ? ` \u2014 ${d.auth.detail}` : ""} ${muted("(this is the anonymous view)")}`);
|
|
35316
35527
|
}
|
|
35528
|
+
if (camera) console.log(`${muted("Camera:")} ${camera.name} ${muted("(played as the webcam feed; getUserMedia resolves)")}`);
|
|
35317
35529
|
if (hosted.length) console.log(`${muted("Fixtures:")} ${hosted.map((h) => h.name).join(", ")}`);
|
|
35318
35530
|
console.log(opts.file ? `${muted("Script:")} ${opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
35319
35531
|
console.log(`
|
|
35320
35532
|
${result.trim() ? result : muted("(empty result)")}`);
|
|
35321
35533
|
if (noValue) console.log(muted(`
|
|
35322
35534
|
${EVAL_NO_VALUE_HINT}`));
|
|
35535
|
+
if (emptyState2) console.log(muted(`
|
|
35536
|
+
${emptyHint}`));
|
|
35323
35537
|
if (d.truncated) console.log(muted("\n(result truncated to fit context - narrow the expression for the full value)"));
|
|
35538
|
+
for (let i = 0; i < stepOut.length; i++) {
|
|
35539
|
+
const s = stepOut[i];
|
|
35540
|
+
console.log(`
|
|
35541
|
+
${bold(`Step ${i + 1}`)} ${muted(summarizeExpr(s.expr))} ${muted("(same page load)")}`);
|
|
35542
|
+
console.log(s.result.trim() ? s.result : muted("(empty result)"));
|
|
35543
|
+
if (s.noValue) console.log(muted(EVAL_NO_VALUE_HINT));
|
|
35544
|
+
}
|
|
35324
35545
|
if (reload) {
|
|
35325
35546
|
console.log(`
|
|
35326
35547
|
${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserved)")}`);
|
|
@@ -35329,7 +35550,7 @@ ${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserve
|
|
|
35329
35550
|
if (d.reloadTruncated) console.log(muted("(reload result truncated to fit context - narrow the expression for the full value)"));
|
|
35330
35551
|
}
|
|
35331
35552
|
} finally {
|
|
35332
|
-
for (const h of hosted) {
|
|
35553
|
+
for (const h of [...hosted, ...camera ? [camera] : []]) {
|
|
35333
35554
|
try {
|
|
35334
35555
|
await deleteFixture(projectGuid, h.guid);
|
|
35335
35556
|
} catch (err) {
|
|
@@ -35355,15 +35576,52 @@ Examples:
|
|
|
35355
35576
|
"localStorage.setItem('todo','milk'); document.title" \\
|
|
35356
35577
|
--reload "({ restored: localStorage.getItem('todo'), heading: document.querySelector('h1')?.textContent })"
|
|
35357
35578
|
|
|
35579
|
+
# Camera app (MediaPipe / YOLOX / any getUserMedia app): play a real image as
|
|
35580
|
+
# the webcam so the app's OWN pipeline runs on a frame you control \u2014 no need to
|
|
35581
|
+
# stub the model or export internals just to test around a missing camera.
|
|
35582
|
+
# --camera waits ${CAMERA_DEFAULT_WAIT_MS / 1e3}s before evaluating (model load + first frame); the script
|
|
35583
|
+
# itself then gets ${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s in the page (--timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s).
|
|
35584
|
+
gipity generate image "a hand making a closed fist, palm to camera, plain background" -o fist.png
|
|
35585
|
+
gipity page eval "https://dev.gipity.ai/me/app/" --camera fist.png \\
|
|
35586
|
+
"document.querySelector('#detected-gesture').textContent"
|
|
35587
|
+
# Camera app that only starts on a click? Start it on page load instead (a headless
|
|
35588
|
+
# run has no user), and expose a ready signal so the wait ends the moment it's up:
|
|
35589
|
+
gipity page eval "https://dev.gipity.ai/me/app/" --camera fist.png \\
|
|
35590
|
+
--wait-for "#detected-gesture:not(:empty)" --wait-timeout ${WAIT_FOR_MAX_MS} \\
|
|
35591
|
+
"document.querySelector('#detected-gesture').textContent"
|
|
35592
|
+
# Several assertions about the SAME page? Use --step, not several commands: a camera
|
|
35593
|
+
# page load pays for getUserMedia + the vision model ONCE and every step reuses it.
|
|
35594
|
+
gipity page eval "https://dev.gipity.ai/me/app/" --camera fist.png \\
|
|
35595
|
+
--wait-for '[data-vision="ready"]' \\
|
|
35596
|
+
"window.__vision.gesture()" \\
|
|
35597
|
+
--step "document.getElementById('see').textContent" \\
|
|
35598
|
+
--step "({ score: score.textContent, verdict: verdict.textContent })"
|
|
35599
|
+
|
|
35358
35600
|
Module resolution: dynamic import() specifiers starting with ./ or ../ resolve
|
|
35359
35601
|
against the PAGE URL, so import('./packages/i18n/index.js') loads the app's own
|
|
35360
35602
|
module without hand-building the deployed /account/project/ path. Absolute paths
|
|
35361
35603
|
and full URLs pass through unchanged.
|
|
35362
35604
|
|
|
35363
|
-
|
|
35364
|
-
|
|
35365
|
-
|
|
35366
|
-
|
|
35605
|
+
Time budget: the script runs under a ${EVAL_SCRIPT_BUDGET_MS / 1e3}s in-page budget (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera/--fake-media),
|
|
35606
|
+
counting its own await/setTimeout pauses. Two separate knobs:
|
|
35607
|
+
--timeout <ms> how long the SCRIPT may run in the page (max ${EVAL_SCRIPT_BUDGET_MAX_MS}) \u2014 raise this to
|
|
35608
|
+
trace a sequence that unfolds over time (a game round, an animation)
|
|
35609
|
+
--wait / --wait-for how long to settle BEFORE the script runs \u2014 a blind sleep, a
|
|
35610
|
+
selector gate, or both (gate first, then sleep). Neither extends --timeout.
|
|
35611
|
+
Which one:
|
|
35612
|
+
- waiting for the app to REACH a state before you read it \u2192 --wait-for '<selector>'
|
|
35613
|
+
(gate on the end state \u2014 '#verdict:not(:empty)' \u2014 not just a ready flag; a bare
|
|
35614
|
+
--wait samples one arbitrary instant and can land mid-sequence). Passing both means
|
|
35615
|
+
gate first, then settle --wait ms.
|
|
35616
|
+
- watching state evolve (poll until a result lands, record a trace) \u2192 --timeout
|
|
35617
|
+
- a slow ONE-TIME init (WASM/model download, big asset) \u2192 do NOT split the body across
|
|
35618
|
+
calls; every call re-loads the page and re-pays it. Start that work on page load and
|
|
35619
|
+
absorb it in the pre-eval window: --wait <ms> (max ${MAX_WAIT_MS}) or, better,
|
|
35620
|
+
--wait-for '<ready-selector>' --wait-timeout ${WAIT_FOR_MAX_MS}.
|
|
35621
|
+
|
|
35622
|
+
Several things to check on one page? Pass --step (max ${MAX_EVAL_STEPS}) instead of running
|
|
35623
|
+
'page eval' N times: the steps run in order against the ONE loaded page, so a slow
|
|
35624
|
+
boot (vision model, game, socket) is paid once, not once per assertion.
|
|
35367
35625
|
|
|
35368
35626
|
Testing realtime/shared state across clients?
|
|
35369
35627
|
Separate 'page eval' calls run sequentially (one finishes before the next
|
|
@@ -35378,7 +35636,7 @@ init_auth();
|
|
|
35378
35636
|
init_colors();
|
|
35379
35637
|
init_utils();
|
|
35380
35638
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
35381
|
-
import { dirname as dirname7, join as
|
|
35639
|
+
import { dirname as dirname7, join as join12, resolve as resolvePath } from "path";
|
|
35382
35640
|
var DEVICE_PRESETS = {
|
|
35383
35641
|
default: { width: 1280, height: 720 },
|
|
35384
35642
|
desktop: { width: 1920, height: 1080 },
|
|
@@ -35401,8 +35659,17 @@ function printAuthLine(auth) {
|
|
|
35401
35659
|
}
|
|
35402
35660
|
function printActionErrorLine(actionError) {
|
|
35403
35661
|
if (!actionError) return;
|
|
35662
|
+
if (/wait-for:/.test(actionError)) {
|
|
35663
|
+
console.log(`${warning("\u26A0 --wait-for never matched:")} ${actionError.replace(/^EvalError:\s*/, "")} ${muted("(the image below is the state the page WAS in when the gate gave up)")}`);
|
|
35664
|
+
return;
|
|
35665
|
+
}
|
|
35404
35666
|
console.log(`${warning("\u26A0 --action failed:")} ${actionError} ${muted("(this image shows the page BEFORE the action ran)")}`);
|
|
35405
35667
|
}
|
|
35668
|
+
function augmentSandboxTimeout(message) {
|
|
35669
|
+
if (!/did not respond within/i.test(message)) return message;
|
|
35670
|
+
return `${message}
|
|
35671
|
+
A pre-capture script was set (--action and/or the --wait-for gate), and its runtime is spent INSIDE that same sandbox budget \u2014 a script that waits on slow work (a WASM/model download, a network fetch, a long animation) can exhaust the budget on its own. Keep --action to the interaction itself (a click, a keypress), let the page do the slow work on load, and absorb that with --wait <ms> or a tighter --wait-for '<selector>' gate instead.`;
|
|
35672
|
+
}
|
|
35406
35673
|
function fmtPerformance(p) {
|
|
35407
35674
|
const parts = [
|
|
35408
35675
|
`TTFB ${fmtMs(p.ttfb)}`,
|
|
@@ -35430,7 +35697,7 @@ function dimSuffix(vp2) {
|
|
|
35430
35697
|
}
|
|
35431
35698
|
function defaultScreenshotDir() {
|
|
35432
35699
|
const root = getProjectRoot();
|
|
35433
|
-
return
|
|
35700
|
+
return join12(root ?? ".", "screenshots");
|
|
35434
35701
|
}
|
|
35435
35702
|
function timestampSlug(d = /* @__PURE__ */ new Date()) {
|
|
35436
35703
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -35485,17 +35752,51 @@ function appendOption(value, previous = []) {
|
|
|
35485
35752
|
return [...previous, value];
|
|
35486
35753
|
}
|
|
35487
35754
|
var ACTION_DECOY_FLAGS = ["--eval", "--js", "--javascript", "--script", "--code", "--exec"];
|
|
35488
|
-
|
|
35755
|
+
function buildWaitForGate(selector, timeoutMs) {
|
|
35756
|
+
const sel = JSON.stringify(selector);
|
|
35757
|
+
return `await (async () => { const __t0 = Date.now(); while (Date.now() - __t0 < ${timeoutMs}) { try { if (document.querySelector(${sel})) return; } catch (e) { throw new Error('wait-for: invalid selector ' + ${sel}); } await new Promise((r) => setTimeout(r, 100)); } throw new Error('wait-for: nothing matched ' + ${sel} + ' within ${timeoutMs}ms \u2014 the page never reached that state (raise --wait-timeout, fix the selector, or check the app actually gets there headlessly)'); })();`;
|
|
35758
|
+
}
|
|
35759
|
+
var WAIT_FOR_MAX_MS2 = 15e3;
|
|
35760
|
+
var WAIT_FOR_DEFAULT_MS = 15e3;
|
|
35761
|
+
var MAX_POST_LOAD_DELAY_MS = 3e4;
|
|
35762
|
+
var CAMERA_DEFAULT_DELAY_MS = 15e3;
|
|
35763
|
+
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").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("--device <names>", `Device preset(s): ${Object.keys(DEVICE_PRESETS).join(", ")} (comma-separated or repeat flag). mobile/tablet emulate a real touch device \u2014 touch events, mobile user-agent, DPR \u2014 so touch-gated mobile UI actually renders.`, appendOption, []).option("--viewport <dims>", "Raw viewport(s): WxH or WxH@dpr (comma-separated or repeat flag)", appendOption, []).option("--no-reload-between", "Skip reload between viewports (faster, lower fidelity - only safe for static pages)").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps render headlessly. 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.").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()).action((url, opts) => run("Page screenshot", async () => {
|
|
35489
35764
|
const decoy = ACTION_DECOY_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
35490
35765
|
if (decoy) {
|
|
35491
35766
|
pageScreenshotCommand.error(
|
|
35492
35767
|
`error: ${decoy} is not a flag on screenshot \u2014 use --action "<js>" to run JavaScript in the page before the capture, e.g. gipity page screenshot "<url>" --action "document.getElementById('play').click()"`
|
|
35493
35768
|
);
|
|
35494
35769
|
}
|
|
35495
|
-
const delayRaw = opts.
|
|
35496
|
-
const
|
|
35497
|
-
|
|
35498
|
-
|
|
35770
|
+
const delayRaw = opts.wait ?? opts.postLoadDelay;
|
|
35771
|
+
const chosenDelay = delayRaw !== void 0;
|
|
35772
|
+
let postLoadDelayMs = chosenDelay ? parseInt(String(delayRaw), 10) : 1e3;
|
|
35773
|
+
if (!Number.isFinite(postLoadDelayMs) || postLoadDelayMs < 0) {
|
|
35774
|
+
throw new Error("--wait must be a non-negative integer (ms)");
|
|
35775
|
+
}
|
|
35776
|
+
if (postLoadDelayMs > MAX_POST_LOAD_DELAY_MS) {
|
|
35777
|
+
console.error(warning(
|
|
35778
|
+
`--wait ${postLoadDelayMs}ms exceeds the ${MAX_POST_LOAD_DELAY_MS}ms cap \u2014 using ${MAX_POST_LOAD_DELAY_MS}ms. Waiting longer is rarely the fix: gate on the state you want with --wait-for '<selector>' instead of guessing a duration.`
|
|
35779
|
+
));
|
|
35780
|
+
postLoadDelayMs = MAX_POST_LOAD_DELAY_MS;
|
|
35781
|
+
}
|
|
35782
|
+
const waitForTimeoutRaw = opts.waitTimeout !== void 0 ? parseInt(String(opts.waitTimeout), 10) : WAIT_FOR_DEFAULT_MS;
|
|
35783
|
+
if (!Number.isFinite(waitForTimeoutRaw) || waitForTimeoutRaw < 0) {
|
|
35784
|
+
throw new Error("--wait-timeout must be a non-negative integer (ms)");
|
|
35785
|
+
}
|
|
35786
|
+
const waitForTimeoutMs = Math.min(waitForTimeoutRaw, WAIT_FOR_MAX_MS2);
|
|
35787
|
+
if (waitForTimeoutRaw > WAIT_FOR_MAX_MS2) {
|
|
35788
|
+
console.error(warning(
|
|
35789
|
+
`--wait-timeout ${waitForTimeoutRaw}ms exceeds the ${WAIT_FOR_MAX_MS2}ms the capture's browser budget covers \u2014 using ${WAIT_FOR_MAX_MS2}ms. A state that takes longer than that to appear is not a screenshot problem: watch for it with \`gipity page eval <url> --wait-for '<selector>' --wait-timeout 30000\` (a wider budget), then capture it.`
|
|
35790
|
+
));
|
|
35791
|
+
}
|
|
35792
|
+
if (opts.waitTimeout !== void 0 && !opts.waitFor) {
|
|
35793
|
+
throw new Error("--wait-timeout only means something with --wait-for '<selector>' (it bounds that gate)");
|
|
35794
|
+
}
|
|
35795
|
+
if (opts.camera && !chosenDelay && !opts.waitFor) {
|
|
35796
|
+
postLoadDelayMs = CAMERA_DEFAULT_DELAY_MS;
|
|
35797
|
+
console.error(muted(
|
|
35798
|
+
`--camera: waiting ${CAMERA_DEFAULT_DELAY_MS / 1e3}s before the capture so the camera and the app's vision model finish loading. Override with --wait <ms>, or use --wait-for '<ready-selector>' to shoot the moment it's ready.`
|
|
35799
|
+
));
|
|
35499
35800
|
}
|
|
35500
35801
|
const deviceNames = splitCsv(opts.device);
|
|
35501
35802
|
const viewportStrs = splitCsv(opts.viewport);
|
|
@@ -35507,18 +35808,54 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
35507
35808
|
throw new Error("--output can only be used with a single viewport");
|
|
35508
35809
|
}
|
|
35509
35810
|
const userSpecifiedViewports = customViewports.length > 0;
|
|
35811
|
+
const slug = slugFromUrl(url);
|
|
35812
|
+
const ts2 = timestampSlug();
|
|
35813
|
+
const shotName = (vp2) => defaultFilename(slug, ts2, vp2 && userSpecifiedViewports ? dimSuffix(vp2) : void 0);
|
|
35814
|
+
const names = userSpecifiedViewports ? customViewports.map(shotName) : [shotName()];
|
|
35815
|
+
const projectGuid = getConfig()?.projectGuid;
|
|
35816
|
+
const save = !opts.ephemeral && projectGuid ? { project_guid: projectGuid, names } : void 0;
|
|
35817
|
+
let camera;
|
|
35818
|
+
if (opts.camera) {
|
|
35819
|
+
assertCameraFile(opts.camera);
|
|
35820
|
+
if (!projectGuid) throw new Error("--camera needs a linked project (the frame is hosted for the browser to fetch) \u2014 run `gipity link` first.");
|
|
35821
|
+
console.log(muted(`Hosting camera feed ${opts.camera}\u2026`));
|
|
35822
|
+
camera = await uploadCameraFeed(projectGuid, opts.camera);
|
|
35823
|
+
}
|
|
35824
|
+
const preCapture = [
|
|
35825
|
+
opts.waitFor ? buildWaitForGate(opts.waitFor, waitForTimeoutMs) : "",
|
|
35826
|
+
opts.action ?? ""
|
|
35827
|
+
].filter(Boolean).join("\n");
|
|
35510
35828
|
const body = {
|
|
35511
35829
|
url,
|
|
35512
35830
|
postLoadDelayMs,
|
|
35513
35831
|
full: !!(opts.full || opts.fullPage),
|
|
35514
35832
|
reloadBetween: opts.reloadBetween !== false,
|
|
35515
35833
|
...userSpecifiedViewports ? { viewports: customViewports } : {},
|
|
35516
|
-
|
|
35834
|
+
// A camera feed is played into the synthetic devices, so --camera implies
|
|
35835
|
+
// --fake-media rather than failing on the pairing.
|
|
35836
|
+
...opts.fakeMedia || camera ? { fakeMedia: true } : {},
|
|
35837
|
+
...camera ? { cameraUrl: camera.url } : {},
|
|
35517
35838
|
...opts.auth ? { auth: true } : {},
|
|
35518
|
-
...
|
|
35839
|
+
...preCapture ? { action: preCapture } : {},
|
|
35840
|
+
...save ? { save } : {}
|
|
35519
35841
|
};
|
|
35520
35842
|
const doShoot = () => postForTarEntries("/tools/browser/screenshot", body);
|
|
35521
|
-
|
|
35843
|
+
let entries;
|
|
35844
|
+
try {
|
|
35845
|
+
try {
|
|
35846
|
+
entries = opts.json ? await doShoot() : await withSpinner("Capturing\u2026", doShoot, { done: null });
|
|
35847
|
+
} catch (err) {
|
|
35848
|
+
throw preCapture ? new Error(augmentSandboxTimeout(err.message)) : err;
|
|
35849
|
+
}
|
|
35850
|
+
} finally {
|
|
35851
|
+
if (camera) {
|
|
35852
|
+
try {
|
|
35853
|
+
await deleteFixture(projectGuid, camera.guid);
|
|
35854
|
+
} catch (err) {
|
|
35855
|
+
console.error(warning(`\u26A0 Could not auto-delete camera feed "${camera.name}" (${camera.guid}) \u2014 still hosted at ${camera.url}: ${err.message}`));
|
|
35856
|
+
}
|
|
35857
|
+
}
|
|
35858
|
+
}
|
|
35522
35859
|
const metaEntry = entries.find((e) => e.name === "meta.json");
|
|
35523
35860
|
if (!metaEntry) throw new Error("Server response missing meta.json");
|
|
35524
35861
|
const meta = JSON.parse(metaEntry.buffer.toString("utf8"));
|
|
@@ -35526,14 +35863,10 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
35526
35863
|
if (pngs.length !== meta.screenshots.length) {
|
|
35527
35864
|
throw new Error(`Server returned ${pngs.length} PNGs but ${meta.screenshots.length} metadata entries`);
|
|
35528
35865
|
}
|
|
35529
|
-
const slug = slugFromUrl(url);
|
|
35530
|
-
const ts2 = timestampSlug();
|
|
35531
35866
|
const dir = defaultScreenshotDir();
|
|
35532
35867
|
const savedFiles = [];
|
|
35533
35868
|
for (let i = 0; i < pngs.length; i++) {
|
|
35534
|
-
const
|
|
35535
|
-
const suffix = userSpecifiedViewports ? dimSuffix(shot.viewport) : void 0;
|
|
35536
|
-
const target = opts.output ? opts.output : join11(dir, defaultFilename(slug, ts2, suffix));
|
|
35869
|
+
const target = opts.output ? opts.output : join12(dir, names[i] ?? shotName(meta.screenshots[i].viewport));
|
|
35537
35870
|
mkdirSync7(dirname7(target), { recursive: true });
|
|
35538
35871
|
writeFileSync8(target, pngs[i].buffer);
|
|
35539
35872
|
savedFiles.push(resolvePath(target));
|
|
@@ -35561,11 +35894,20 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
35561
35894
|
height: s.height,
|
|
35562
35895
|
size_bytes: s.screenshotSizeBytes,
|
|
35563
35896
|
full_page: meta.full,
|
|
35564
|
-
phase: s.phase
|
|
35897
|
+
phase: s.phase,
|
|
35898
|
+
...s.vfs ? { gipity: s.vfs } : {}
|
|
35565
35899
|
}))
|
|
35566
35900
|
}));
|
|
35567
35901
|
return;
|
|
35568
35902
|
}
|
|
35903
|
+
const printVfsLine = (s) => {
|
|
35904
|
+
if (!s.vfs) return;
|
|
35905
|
+
if ("error" in s.vfs) {
|
|
35906
|
+
console.log(`${warning("\u26A0 Gipity save failed:")} ${s.vfs.error} ${muted("(local file is fine)")}`);
|
|
35907
|
+
} else {
|
|
35908
|
+
console.log(`${label("Gipity history")} ${s.vfs.path} ${muted("(synced, not deployed)")}`);
|
|
35909
|
+
}
|
|
35910
|
+
};
|
|
35569
35911
|
if (meta.screenshots.length === 1) {
|
|
35570
35912
|
const s = meta.screenshots[0];
|
|
35571
35913
|
console.log(`${brand("Screenshot")} ${bold(url)}`);
|
|
@@ -35580,6 +35922,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
35580
35922
|
console.log(`${label("Screenshot size")} ${sizePart}`);
|
|
35581
35923
|
if (s.width && s.height) console.log(`${label("Screenshot dims")} ${s.width} \xD7 ${s.height}`);
|
|
35582
35924
|
console.log(`${label("Screenshot file")} ${success(savedFiles[0])}`);
|
|
35925
|
+
printVfsLine(s);
|
|
35583
35926
|
return;
|
|
35584
35927
|
}
|
|
35585
35928
|
console.log(`${brand("Loading")} ${bold(url)} ${muted(`once \u2192 ${meta.screenshots.length} viewports`)}`);
|
|
@@ -35599,6 +35942,7 @@ ${brand("@ " + dims)}${deviceTag}`);
|
|
|
35599
35942
|
console.log(`${label("Screenshot size")} ${sizePart}`);
|
|
35600
35943
|
if (s.width && s.height) console.log(`${label("Screenshot dims")} ${s.width} \xD7 ${s.height}`);
|
|
35601
35944
|
console.log(`${label("Screenshot file")} ${success(savedFiles[i])}`);
|
|
35945
|
+
printVfsLine(s);
|
|
35602
35946
|
}
|
|
35603
35947
|
}));
|
|
35604
35948
|
for (const f of ACTION_DECOY_FLAGS) pageScreenshotCommand.addOption(new Option(`${f} <value>`).hideHelp());
|
|
@@ -35609,11 +35953,21 @@ Examples:
|
|
|
35609
35953
|
gipity page screenshot "https://dev.gipity.ai/me/app/" --device mobile,desktop
|
|
35610
35954
|
gipity page screenshot "https://dev.gipity.ai/me/app/" \\
|
|
35611
35955
|
--action "document.getElementById('play').click()" # capture an in-game frame
|
|
35956
|
+
# Camera / vision app: play a real frame in as the webcam and shoot once the app
|
|
35957
|
+
# says it's ready \u2014 same --camera / --wait-for flags as 'page eval', no guessed delay:
|
|
35958
|
+
gipity page screenshot "https://dev.gipity.ai/me/app/" --camera fist.png \\
|
|
35959
|
+
--wait-for '[data-vision="ready"]'
|
|
35960
|
+
|
|
35961
|
+
Waiting for the page to reach a state before the shot?
|
|
35962
|
+
--wait-for '<selector>' gates the capture on the app's own signal (deterministic).
|
|
35963
|
+
Reach for --wait <ms> only when there is nothing to gate on \u2014 a guessed duration
|
|
35964
|
+
either shoots too early or wastes the difference.
|
|
35612
35965
|
|
|
35613
35966
|
Capturing a state that needs an interaction (start a game, open a menu, dismiss a modal)?
|
|
35614
|
-
Use --action to run JS in the page before the shot \u2014 it fires after the
|
|
35615
|
-
|
|
35616
|
-
that returns a base64 image: the eval result is capped
|
|
35967
|
+
Use --action to run JS in the page before the shot \u2014 it fires after the wait
|
|
35968
|
+
(and after any --wait-for gate), then settles again so the result has painted. Do
|
|
35969
|
+
NOT hand-roll a 'page eval' that returns a base64 image: the eval result is capped
|
|
35970
|
+
(~16KB) and truncates the PNG.
|
|
35617
35971
|
|
|
35618
35972
|
Capturing an off-screen region or reading element data?
|
|
35619
35973
|
\u2022 --full captures the ENTIRE scrollable page (then crop to the region).
|
|
@@ -35638,7 +35992,7 @@ function shortUrl(url, truncate = true, maxLen = 100) {
|
|
|
35638
35992
|
const tailLen = Math.floor(keep / 2);
|
|
35639
35993
|
return result.slice(0, headLen) + "\u2026" + result.slice(-tailLen);
|
|
35640
35994
|
}
|
|
35641
|
-
var pageInspectCommand = new Command("inspect").description("Inspect a web page (console, failed resources, timing, layout overflow). ONE passive load - to verify realtime/presence across concurrent clients use `page test --observe`").argument("<url>", "URL to inspect").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before capturing (lets late async/LCP work settle; max 30000)", "500").option("--wait-for <selector>", "Wait until this CSS selector appears before capturing (deterministic; replaces --wait)").option("--wait-timeout <ms>", "Max ms to wait for --wait-for before giving up", "5000").option("--json", "Output as JSON").option("--no-reprobe", "Skip the automatic second probe that filters one-time transient console errors - roughly halves inspect time on a page with any console error, at the cost of possibly reporting a cold-load transient as real").option("--no-truncate", "Show full URLs instead of truncating long ones with middle-ellipsis").option("--all", "Include render-blocking, large resources, oversized images, overflow culprits, and LCP detail").option("--fake-media", "
|
|
35995
|
+
var pageInspectCommand = new Command("inspect").description("Inspect a web page (console, failed resources, timing, layout overflow). ONE passive load - to verify realtime/presence across concurrent clients use `page test --observe`").argument("<url>", "URL to inspect").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before capturing (lets late async/LCP work settle; max 30000)", "500").option("--wait-for <selector>", "Wait until this CSS selector appears before capturing (deterministic; replaces --wait)").option("--wait-timeout <ms>", "Max ms to wait for --wait-for before giving up", "5000").option("--json", "Output as JSON").option("--no-reprobe", "Skip the automatic second probe that filters one-time transient console errors - roughly halves inspect time on a page with any console error, at the cost of possibly reporting a cold-load transient as real").option("--no-truncate", "Show full URLs instead of truncating long ones with middle-ellipsis").option("--all", "Include render-blocking, large resources, oversized images, overflow culprits, and LCP detail").option("--no-fake-media", "Inspect with NO camera or microphone present, so a getUserMedia app takes its no-device error path (use this only when that fallback IS what you're inspecting)").option("--device <name>", `Inspect as a real touch device: ${TOUCH_DEVICES.join(", ")}. Emulates touch events, mobile user-agent and DPR, so a touch-gated mobile layout is what gets inspected.`).option("--auth", "Load the page signed in as you (your Gipity account), so pages behind a Sign-in-with-Gipity login are reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai.").addOption(new Option("--screenshot [path]", "Capture a screenshot").hideHelp()).action((url, opts) => {
|
|
35642
35996
|
if (opts.screenshot !== void 0) {
|
|
35643
35997
|
console.error(error("page inspect does not capture screenshots. Use page screenshot:"));
|
|
35644
35998
|
console.error(` gipity page screenshot ${url}${typeof opts.screenshot === "string" ? ` -o ${opts.screenshot}` : ""}`);
|
|
@@ -35657,7 +36011,7 @@ async function inspectPage(url, opts = {}) {
|
|
|
35657
36011
|
waitMs,
|
|
35658
36012
|
waitForSelector: opts.waitFor || void 0,
|
|
35659
36013
|
waitForTimeoutMs: opts.waitFor ? waitForTimeoutMs : void 0,
|
|
35660
|
-
fakeMedia: opts.fakeMedia
|
|
36014
|
+
fakeMedia: opts.fakeMedia !== false ? true : void 0,
|
|
35661
36015
|
device: opts.device ? resolveTouchDevice(opts.device) : void 0,
|
|
35662
36016
|
auth: opts.auth || void 0
|
|
35663
36017
|
};
|
|
@@ -35818,11 +36172,14 @@ function statusIcon(status) {
|
|
|
35818
36172
|
if (status === "warning") return warning("\u26A0");
|
|
35819
36173
|
return muted("\u2192");
|
|
35820
36174
|
}
|
|
35821
|
-
var deployCommand = new Command("deploy").description("Deploy to dev or prod").argument("[target]", "dev or prod", "dev").option("--source-dir <dir>", "Source directory to deploy from").option("--only <phases>", "Run only specific phases (comma-separated)").option("--force", "Re-run all phases (ignore checksums) and bypass the sync bulk-deletion guard").option("--no-sync", "Skip sync-up before deploy").option("--optimize", "Force Vite build optimization on (default for prod; use this to optimize a dev deploy too)").option("--no-optimize", 'Skip build optimization and upload files as-is - the escape hatch for plain-HTML apps whose <script src> tags are not type="module"').option("--json", "Output as JSON").option("--inspect [path]", "After a successful deploy, run `page inspect` on the deployed URL (or URL + path) in the same command - one build-loop step instead of two. With --json, emits two JSON lines: deploy, then inspect.").action((target, opts) => run("Deploy", async () => {
|
|
36175
|
+
var deployCommand = new Command("deploy").description("Deploy to dev or prod").argument("[target]", "dev or prod", "dev").option("--source-dir <dir>", "Source directory to deploy from").option("--only <phases>", "Run only specific phases (comma-separated)").option("--force", "Re-run all phases (ignore checksums) and bypass the sync bulk-deletion guard").option("--no-sync", "Skip sync-up before deploy").option("--optimize", "Force Vite build optimization on (default for prod; use this to optimize a dev deploy too)").option("--no-optimize", 'Skip build optimization and upload files as-is - the escape hatch for plain-HTML apps whose <script src> tags are not type="module"').option("--json", "Output as JSON").option("--inspect [path]", "After a successful deploy, run `page inspect` on the deployed URL (or URL + path) in the same command - one build-loop step instead of two. With --json, emits two JSON lines: deploy, then inspect.").option("--wait <ms>", "With --inspect: sleep this many ms before capturing (max 30000)").option("--wait-for <selector>", "With --inspect: wait until this CSS selector appears before capturing (deterministic; the app's own ready signal)").option("--wait-timeout <ms>", "With --inspect: max ms to wait for --wait-for before giving up", "5000").action((target, opts) => run("Deploy", async () => {
|
|
35822
36176
|
if (target !== "dev" && target !== "prod") {
|
|
35823
36177
|
console.error(error('Target must be "dev" or "prod"'));
|
|
35824
36178
|
process.exit(1);
|
|
35825
36179
|
}
|
|
36180
|
+
if (!opts.inspect && (opts.waitFor || deployCommand.getOptionValueSource("wait") !== void 0)) {
|
|
36181
|
+
opts.inspect = true;
|
|
36182
|
+
}
|
|
35826
36183
|
const config = requireConfig();
|
|
35827
36184
|
await syncBeforeAction(opts);
|
|
35828
36185
|
const doDeploy = () => post(`/projects/${config.projectGuid}/deploy`, {
|
|
@@ -35843,7 +36200,12 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
|
|
|
35843
36200
|
const url = typeof opts.inspect === "string" ? new URL(opts.inspect, d.url).toString() : d.url;
|
|
35844
36201
|
if (!opts.json) console.log("");
|
|
35845
36202
|
try {
|
|
35846
|
-
await inspectPage(url, {
|
|
36203
|
+
await inspectPage(url, {
|
|
36204
|
+
json: opts.json,
|
|
36205
|
+
wait: opts.wait,
|
|
36206
|
+
waitFor: opts.waitFor,
|
|
36207
|
+
waitTimeout: opts.waitTimeout
|
|
36208
|
+
});
|
|
35847
36209
|
} catch (err) {
|
|
35848
36210
|
console.error(warning(`Inspect failed (deploy itself succeeded): ${err?.message ?? err}`));
|
|
35849
36211
|
}
|
|
@@ -36066,8 +36428,8 @@ memoryCommand.command("delete <topic>").description("Delete a topic").option("--
|
|
|
36066
36428
|
// src/commands/sandbox.ts
|
|
36067
36429
|
init_api();
|
|
36068
36430
|
init_config();
|
|
36069
|
-
import { readFileSync as readFileSync13, existsSync as
|
|
36070
|
-
import { dirname as dirname8, extname as extname4, relative as
|
|
36431
|
+
import { readFileSync as readFileSync13, existsSync as existsSync11, statSync as statSync6 } from "fs";
|
|
36432
|
+
import { dirname as dirname8, extname as extname4, relative as relative4, resolve as resolve12 } from "path";
|
|
36071
36433
|
init_colors();
|
|
36072
36434
|
var LANG_MAP = {
|
|
36073
36435
|
js: "javascript",
|
|
@@ -36180,7 +36542,7 @@ function resolveRelativeCwd() {
|
|
|
36180
36542
|
const configPath = getConfigPath();
|
|
36181
36543
|
if (!configPath) return void 0;
|
|
36182
36544
|
const projectRoot = dirname8(configPath);
|
|
36183
|
-
const rel2 =
|
|
36545
|
+
const rel2 = relative4(projectRoot, process.cwd());
|
|
36184
36546
|
if (!rel2 || rel2.startsWith("..")) return void 0;
|
|
36185
36547
|
return rel2.split(/[\\/]/).filter(Boolean).join("/");
|
|
36186
36548
|
}
|
|
@@ -36239,7 +36601,7 @@ GCC/Rust).
|
|
|
36239
36601
|
if (args.length >= 2 && INTERPRETERS[args[0].toLowerCase()] !== void 0) {
|
|
36240
36602
|
langFromInterp = INTERPRETERS[args[0].toLowerCase()];
|
|
36241
36603
|
const rest = args.slice(1).join(" ");
|
|
36242
|
-
if (
|
|
36604
|
+
if (existsSync11(rest) && statSync6(rest).isFile()) filePath = rest;
|
|
36243
36605
|
else inlineCode = rest;
|
|
36244
36606
|
} else if (args.length === 1) {
|
|
36245
36607
|
inlineCode = args[0];
|
|
@@ -36319,7 +36681,7 @@ GCC/Rust).
|
|
|
36319
36681
|
}
|
|
36320
36682
|
if (res.data.outputFiles && res.data.outputFiles.length > 0) {
|
|
36321
36683
|
const projectRoot = getProjectRoot();
|
|
36322
|
-
const landed = (f) => pulledLocal && !!projectRoot &&
|
|
36684
|
+
const landed = (f) => pulledLocal && !!projectRoot && existsSync11(resolve12(projectRoot, f));
|
|
36323
36685
|
const onDisk = res.data.outputFiles.filter(landed);
|
|
36324
36686
|
const notOnDisk = res.data.outputFiles.filter((f) => !landed(f));
|
|
36325
36687
|
if (onDisk.length > 0) {
|
|
@@ -36436,7 +36798,7 @@ chatCommand.command("delete <guid>").description("Delete a chat").option("--json
|
|
|
36436
36798
|
// src/commands/project.ts
|
|
36437
36799
|
init_api();
|
|
36438
36800
|
init_config();
|
|
36439
|
-
import { join as
|
|
36801
|
+
import { join as join13 } from "path";
|
|
36440
36802
|
import { mkdirSync as mkdirSync8 } from "fs";
|
|
36441
36803
|
init_colors();
|
|
36442
36804
|
init_utils();
|
|
@@ -36494,7 +36856,7 @@ projectCommand.command("create <name>").description("Create a project").option("
|
|
|
36494
36856
|
}
|
|
36495
36857
|
const res = await post("/projects", body);
|
|
36496
36858
|
const project = res.data;
|
|
36497
|
-
const dir =
|
|
36859
|
+
const dir = join13(getProjectsRoot(), project.slug);
|
|
36498
36860
|
mkdirSync8(dir, { recursive: true });
|
|
36499
36861
|
const accountSlug = await getAccountSlug();
|
|
36500
36862
|
let agentGuid = "";
|
|
@@ -36750,7 +37112,7 @@ function printStepRuns(steps, emptyNote) {
|
|
|
36750
37112
|
}
|
|
36751
37113
|
}
|
|
36752
37114
|
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
36753
|
-
var sleep2 = (ms2) => new Promise((
|
|
37115
|
+
var sleep2 = (ms2) => new Promise((resolve19) => setTimeout(resolve19, ms2));
|
|
36754
37116
|
async function waitForRun(wfGuid, prevGuid, timeoutSec) {
|
|
36755
37117
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
36756
37118
|
let runGuid;
|
|
@@ -37189,9 +37551,9 @@ init_config();
|
|
|
37189
37551
|
init_utils();
|
|
37190
37552
|
init_colors();
|
|
37191
37553
|
var fileCommand = new Command("file").description("Browse remote files");
|
|
37192
|
-
fileCommand.command("ls [path]").description("List files").option("--json", "Output as JSON").action((
|
|
37554
|
+
fileCommand.command("ls [path]").description("List files").option("--json", "Output as JSON").action((path5, opts) => run("List", async () => {
|
|
37193
37555
|
const { config } = await resolveProjectContext();
|
|
37194
|
-
const query =
|
|
37556
|
+
const query = path5 ? `?path=${encodeURIComponent(path5)}` : "";
|
|
37195
37557
|
const res = await get(`/projects/${config.projectGuid}/files${query}`);
|
|
37196
37558
|
printList(res.data, opts, "(empty)", (f) => {
|
|
37197
37559
|
const size = f.type === "directory" ? "<DIR>" : formatSize(Number(f.size));
|
|
@@ -37199,10 +37561,10 @@ fileCommand.command("ls [path]").description("List files").option("--json", "Out
|
|
|
37199
37561
|
return `${muted(size.padStart(10))} ${name}`;
|
|
37200
37562
|
});
|
|
37201
37563
|
}));
|
|
37202
|
-
fileCommand.command("cat <path>").description("Read a file").option("--json", "Output as JSON").action((
|
|
37564
|
+
fileCommand.command("cat <path>").description("Read a file").option("--json", "Output as JSON").action((path5, opts) => run("Read", async () => {
|
|
37203
37565
|
const { config } = await resolveProjectContext();
|
|
37204
37566
|
const res = await get(
|
|
37205
|
-
`/projects/${config.projectGuid}/files/read?path=${encodeURIComponent(
|
|
37567
|
+
`/projects/${config.projectGuid}/files/read?path=${encodeURIComponent(path5)}`
|
|
37206
37568
|
);
|
|
37207
37569
|
if (opts.json) {
|
|
37208
37570
|
console.log(JSON.stringify(res.data));
|
|
@@ -37210,9 +37572,9 @@ fileCommand.command("cat <path>").description("Read a file").option("--json", "O
|
|
|
37210
37572
|
process.stdout.write(res.data.content);
|
|
37211
37573
|
}
|
|
37212
37574
|
}));
|
|
37213
|
-
fileCommand.command("tree [path]").description("Show the file tree").option("--json", "Output as JSON").action((
|
|
37575
|
+
fileCommand.command("tree [path]").description("Show the file tree").option("--json", "Output as JSON").action((path5, opts) => run("Tree", async () => {
|
|
37214
37576
|
const { config } = await resolveProjectContext();
|
|
37215
|
-
const query =
|
|
37577
|
+
const query = path5 ? `?path=${encodeURIComponent(path5)}` : "";
|
|
37216
37578
|
const res = await get(
|
|
37217
37579
|
`/projects/${config.projectGuid}/files/tree${query}`
|
|
37218
37580
|
);
|
|
@@ -37221,16 +37583,16 @@ fileCommand.command("tree [path]").description("Show the file tree").option("--j
|
|
|
37221
37583
|
return `${f.path}${size}`;
|
|
37222
37584
|
});
|
|
37223
37585
|
}));
|
|
37224
|
-
fileCommand.command("rm <path>").description("Delete a file or folder").option("--json", "Output as JSON").action((
|
|
37586
|
+
fileCommand.command("rm <path>").description("Delete a file or folder").option("--json", "Output as JSON").action((path5, opts) => run("Delete", async () => {
|
|
37225
37587
|
const { config } = await resolveProjectContext();
|
|
37226
37588
|
const res = await del(
|
|
37227
|
-
`/projects/${config.projectGuid}/files?path=${encodeURIComponent(
|
|
37589
|
+
`/projects/${config.projectGuid}/files?path=${encodeURIComponent(path5)}`
|
|
37228
37590
|
);
|
|
37229
|
-
printResult(`Deleted: ${
|
|
37591
|
+
printResult(`Deleted: ${path5}`, opts, res);
|
|
37230
37592
|
}));
|
|
37231
|
-
fileCommand.command("versions <path>").description("List file versions").option("--limit <n>", "Max versions to return", "20").option("--json", "Output as JSON").action((
|
|
37593
|
+
fileCommand.command("versions <path>").description("List file versions").option("--limit <n>", "Max versions to return", "20").option("--json", "Output as JSON").action((path5, opts) => run("Versions", async () => {
|
|
37232
37594
|
const { config } = await resolveProjectContext();
|
|
37233
|
-
const query = `?path=${encodeURIComponent(
|
|
37595
|
+
const query = `?path=${encodeURIComponent(path5)}&limit=${opts.limit}`;
|
|
37234
37596
|
const res = await get(
|
|
37235
37597
|
`/projects/${config.projectGuid}/files/versions${query}`
|
|
37236
37598
|
);
|
|
@@ -37240,11 +37602,11 @@ fileCommand.command("versions <path>").description("List file versions").option(
|
|
|
37240
37602
|
return `${info(`v${v7.version}`)} ${muted(date)} ${formatSize(v7.size).padStart(8)} [${v7.source}]${marker}`;
|
|
37241
37603
|
});
|
|
37242
37604
|
}));
|
|
37243
|
-
fileCommand.command("restore <path> <version>").description("Restore a file to a version").option("--json", "Output as JSON").action((
|
|
37605
|
+
fileCommand.command("restore <path> <version>").description("Restore a file to a version").option("--json", "Output as JSON").action((path5, version, opts) => run("Restore", async () => {
|
|
37244
37606
|
const { config } = await resolveProjectContext();
|
|
37245
37607
|
const res = await post(
|
|
37246
37608
|
`/projects/${config.projectGuid}/files/version-restore`,
|
|
37247
|
-
{ path:
|
|
37609
|
+
{ path: path5, version: Number(version) }
|
|
37248
37610
|
);
|
|
37249
37611
|
printResult(`Restored ${res.data.path} to v${res.data.version} (${formatSize(res.data.size)}).`, opts, res.data);
|
|
37250
37612
|
}));
|
|
@@ -37371,8 +37733,8 @@ storageCommand.command("retention").description("View or adjust your file versio
|
|
|
37371
37733
|
init_platform();
|
|
37372
37734
|
init_auth();
|
|
37373
37735
|
init_api();
|
|
37374
|
-
import { join as
|
|
37375
|
-
import { existsSync as
|
|
37736
|
+
import { join as join16, dirname as dirname10, resolve as resolve14, basename as basename4, sep as sep4 } from "path";
|
|
37737
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync10, readFileSync as readFileSync16, writeFileSync as writeFileSync10, renameSync as renameSync2, readdirSync as readdirSync7, statSync as statSync7 } from "fs";
|
|
37376
37738
|
import { homedir as homedir11 } from "os";
|
|
37377
37739
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
37378
37740
|
|
|
@@ -37523,7 +37885,7 @@ import { readFileSync as readFileSync15 } from "node:fs";
|
|
|
37523
37885
|
|
|
37524
37886
|
// src/relay/installers.ts
|
|
37525
37887
|
import { homedir as homedir8, platform as osPlatform } from "os";
|
|
37526
|
-
import { join as
|
|
37888
|
+
import { join as join14 } from "path";
|
|
37527
37889
|
var UnsupportedPlatformError = class extends Error {
|
|
37528
37890
|
constructor(plat) {
|
|
37529
37891
|
super(`gipity relay install does not support ${plat}`);
|
|
@@ -37541,8 +37903,8 @@ function display(argvs) {
|
|
|
37541
37903
|
}
|
|
37542
37904
|
function launchdPlan(cliPath) {
|
|
37543
37905
|
const label2 = "ai.gipity.relay";
|
|
37544
|
-
const
|
|
37545
|
-
const logDir =
|
|
37906
|
+
const path5 = join14(homedir8(), "Library", "LaunchAgents", `${label2}.plist`);
|
|
37907
|
+
const logDir = join14(homedir8(), "Library", "Logs", "Gipity");
|
|
37546
37908
|
const uid = process.getuid?.() ?? 0;
|
|
37547
37909
|
const target = `gui/${uid}`;
|
|
37548
37910
|
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -37566,18 +37928,18 @@ function launchdPlan(cliPath) {
|
|
|
37566
37928
|
<dict>
|
|
37567
37929
|
<key>SuccessfulExit</key><false/>
|
|
37568
37930
|
</dict>
|
|
37569
|
-
<key>StandardOutPath</key><string>${
|
|
37570
|
-
<key>StandardErrorPath</key><string>${
|
|
37931
|
+
<key>StandardOutPath</key><string>${join14(logDir, "relay.out.log")}</string>
|
|
37932
|
+
<key>StandardErrorPath</key><string>${join14(logDir, "relay.err.log")}</string>
|
|
37571
37933
|
<key>ProcessType</key><string>Background</string>
|
|
37572
37934
|
</dict>
|
|
37573
37935
|
</plist>
|
|
37574
37936
|
`;
|
|
37575
|
-
const enableCmds = [["launchctl", "bootstrap", target,
|
|
37576
|
-
const disableCmds = [["launchctl", "bootout", target,
|
|
37937
|
+
const enableCmds = [["launchctl", "bootstrap", target, path5]];
|
|
37938
|
+
const disableCmds = [["launchctl", "bootout", target, path5]];
|
|
37577
37939
|
const statusCmd = ["launchctl", "print", `${target}/${label2}`];
|
|
37578
37940
|
return {
|
|
37579
37941
|
platform: "darwin",
|
|
37580
|
-
path:
|
|
37942
|
+
path: path5,
|
|
37581
37943
|
content,
|
|
37582
37944
|
enableCmds,
|
|
37583
37945
|
disableCmds,
|
|
@@ -37590,7 +37952,7 @@ function launchdPlan(cliPath) {
|
|
|
37590
37952
|
}
|
|
37591
37953
|
function systemdUserPlan(cliPath) {
|
|
37592
37954
|
const unitName = "gipity-relay.service";
|
|
37593
|
-
const
|
|
37955
|
+
const path5 = join14(homedir8(), ".config", "systemd", "user", unitName);
|
|
37594
37956
|
const content = `[Unit]
|
|
37595
37957
|
Description=Gipity relay - local Claude Code control from the Gipity web CLI
|
|
37596
37958
|
After=network-online.target
|
|
@@ -37615,7 +37977,7 @@ WantedBy=default.target
|
|
|
37615
37977
|
const statusCmd = ["systemctl", "--user", "status", unitName];
|
|
37616
37978
|
return {
|
|
37617
37979
|
platform: "linux",
|
|
37618
|
-
path:
|
|
37980
|
+
path: path5,
|
|
37619
37981
|
content,
|
|
37620
37982
|
enableCmds,
|
|
37621
37983
|
disableCmds,
|
|
@@ -37628,7 +37990,7 @@ WantedBy=default.target
|
|
|
37628
37990
|
}
|
|
37629
37991
|
function windowsTaskPlan(cliPath) {
|
|
37630
37992
|
const taskName = "GipityRelay";
|
|
37631
|
-
const
|
|
37993
|
+
const path5 = join14(homedir8(), "AppData", "Local", "Gipity", "relay-task.xml");
|
|
37632
37994
|
const runsViaNode = /\.[cm]?js$/i.test(cliPath);
|
|
37633
37995
|
const command = runsViaNode ? process.execPath : cliPath;
|
|
37634
37996
|
const argLine = runsViaNode ? `"${cliPath}" relay run` : "relay run";
|
|
@@ -37664,7 +38026,7 @@ function windowsTaskPlan(cliPath) {
|
|
|
37664
38026
|
</Task>
|
|
37665
38027
|
`;
|
|
37666
38028
|
const enableCmds = [
|
|
37667
|
-
["schtasks", "/Create", "/TN", taskName, "/XML",
|
|
38029
|
+
["schtasks", "/Create", "/TN", taskName, "/XML", path5, "/F"],
|
|
37668
38030
|
["schtasks", "/Run", "/TN", taskName]
|
|
37669
38031
|
];
|
|
37670
38032
|
const disableCmds = [
|
|
@@ -37674,7 +38036,7 @@ function windowsTaskPlan(cliPath) {
|
|
|
37674
38036
|
const statusCmd = ["schtasks", "/Query", "/TN", taskName, "/V", "/FO", "LIST"];
|
|
37675
38037
|
return {
|
|
37676
38038
|
platform: "win32",
|
|
37677
|
-
path:
|
|
38039
|
+
path: path5,
|
|
37678
38040
|
content,
|
|
37679
38041
|
enableCmds,
|
|
37680
38042
|
disableCmds,
|
|
@@ -37690,7 +38052,7 @@ function windowsTaskPlan(cliPath) {
|
|
|
37690
38052
|
init_platform();
|
|
37691
38053
|
init_api();
|
|
37692
38054
|
import { hostname as hostname3, userInfo, platform as osPlatform2 } from "os";
|
|
37693
|
-
import { resolve as
|
|
38055
|
+
import { resolve as resolve13, dirname as dirname9 } from "path";
|
|
37694
38056
|
import { mkdirSync as mkdirSync9, writeFileSync as writeFileSync9 } from "fs";
|
|
37695
38057
|
|
|
37696
38058
|
// src/relay/machine-id.ts
|
|
@@ -37703,9 +38065,9 @@ function rawMachineId() {
|
|
|
37703
38065
|
if (override) return `override:${override}`;
|
|
37704
38066
|
try {
|
|
37705
38067
|
if (platform() === "linux") {
|
|
37706
|
-
for (const
|
|
38068
|
+
for (const path5 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
37707
38069
|
try {
|
|
37708
|
-
const id2 = readFileSync14(
|
|
38070
|
+
const id2 = readFileSync14(path5, "utf-8").trim();
|
|
37709
38071
|
if (id2) return `linux:${id2}`;
|
|
37710
38072
|
} catch {
|
|
37711
38073
|
}
|
|
@@ -37752,7 +38114,7 @@ function friendlyDeviceName() {
|
|
|
37752
38114
|
return host || `My ${kind}`;
|
|
37753
38115
|
}
|
|
37754
38116
|
function resolveCliPath() {
|
|
37755
|
-
return
|
|
38117
|
+
return resolve13(process.argv[1] ?? "gipity");
|
|
37756
38118
|
}
|
|
37757
38119
|
async function pairDevice(opts = {}) {
|
|
37758
38120
|
const existing = getDevice();
|
|
@@ -38204,9 +38566,9 @@ function printFull(opts, outerW) {
|
|
|
38204
38566
|
|
|
38205
38567
|
// src/claude-setup.ts
|
|
38206
38568
|
import { execSync as execSync2 } from "child_process";
|
|
38207
|
-
import { existsSync as
|
|
38569
|
+
import { existsSync as existsSync12 } from "fs";
|
|
38208
38570
|
import { homedir as homedir10 } from "os";
|
|
38209
|
-
import { join as
|
|
38571
|
+
import { join as join15 } from "path";
|
|
38210
38572
|
var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
|
|
38211
38573
|
function claudeInstallPlan(platformOverride) {
|
|
38212
38574
|
const plat = platformOverride ?? process.platform;
|
|
@@ -38225,7 +38587,7 @@ function isClaudeInstalled(platformOverride) {
|
|
|
38225
38587
|
}
|
|
38226
38588
|
function isClaudeAuthenticated() {
|
|
38227
38589
|
if (process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY) return true;
|
|
38228
|
-
return
|
|
38590
|
+
return existsSync12(join15(homedir10(), ".claude", ".credentials.json"));
|
|
38229
38591
|
}
|
|
38230
38592
|
function probeClaudeAuthenticated() {
|
|
38231
38593
|
if (!isClaudeInstalled()) return false;
|
|
@@ -38255,8 +38617,8 @@ function ensureClaudeInstalled(opts = {}) {
|
|
|
38255
38617
|
var __clDir = dirname10(fileURLToPath2(import.meta.url));
|
|
38256
38618
|
function readOwnPkg(fromDir) {
|
|
38257
38619
|
for (let d = fromDir; ; d = dirname10(d)) {
|
|
38258
|
-
const p =
|
|
38259
|
-
if (
|
|
38620
|
+
const p = join16(d, "package.json");
|
|
38621
|
+
if (existsSync13(p)) {
|
|
38260
38622
|
try {
|
|
38261
38623
|
const pkg2 = JSON.parse(readFileSync16(p, "utf-8"));
|
|
38262
38624
|
if (pkg2.name === "gipity") return pkg2;
|
|
@@ -38307,12 +38669,12 @@ function localFsFallback(dir) {
|
|
|
38307
38669
|
const topLevelEntries = [];
|
|
38308
38670
|
const walk = (d, depth) => {
|
|
38309
38671
|
try {
|
|
38310
|
-
for (const name of
|
|
38672
|
+
for (const name of readdirSync7(d).sort()) {
|
|
38311
38673
|
if (isSyncIgnored(name)) continue;
|
|
38312
38674
|
let isDir = false;
|
|
38313
38675
|
let size = 0;
|
|
38314
38676
|
try {
|
|
38315
|
-
const st2 = statSync7(
|
|
38677
|
+
const st2 = statSync7(join16(d, name));
|
|
38316
38678
|
isDir = st2.isDirectory();
|
|
38317
38679
|
size = st2.isFile() ? st2.size : 0;
|
|
38318
38680
|
} catch {
|
|
@@ -38321,7 +38683,7 @@ function localFsFallback(dir) {
|
|
|
38321
38683
|
if (isDir) {
|
|
38322
38684
|
folderCount++;
|
|
38323
38685
|
if (depth === 0) topLevelEntries.push(`${name}/`);
|
|
38324
|
-
walk(
|
|
38686
|
+
walk(join16(d, name), depth + 1);
|
|
38325
38687
|
} else {
|
|
38326
38688
|
fileCount++;
|
|
38327
38689
|
totalBytes += size;
|
|
@@ -38354,8 +38716,8 @@ function hasStreamJsonFlag(args) {
|
|
|
38354
38716
|
}
|
|
38355
38717
|
function markFolderTrusted(dir) {
|
|
38356
38718
|
try {
|
|
38357
|
-
const file =
|
|
38358
|
-
const cfg =
|
|
38719
|
+
const file = join16(homedir11(), ".claude.json");
|
|
38720
|
+
const cfg = existsSync13(file) ? JSON.parse(readFileSync16(file, "utf-8")) : {};
|
|
38359
38721
|
if (typeof cfg !== "object" || cfg === null) return;
|
|
38360
38722
|
if (typeof cfg.projects !== "object" || cfg.projects === null) cfg.projects = {};
|
|
38361
38723
|
const entry = typeof cfg.projects[dir] === "object" && cfg.projects[dir] !== null ? cfg.projects[dir] : {};
|
|
@@ -38468,7 +38830,7 @@ var claudeCommand = new Command("claude").description("Set up and run Claude Cod
|
|
|
38468
38830
|
let existing = getConfig();
|
|
38469
38831
|
let forceAdoptCwd = false;
|
|
38470
38832
|
if (existing && !opts.newProject && !opts.project && !nonInteractive) {
|
|
38471
|
-
const cwdHasConfig =
|
|
38833
|
+
const cwdHasConfig = existsSync13(resolve14(process.cwd(), ".gipity.json"));
|
|
38472
38834
|
if (!cwdHasConfig && isLikelyEmpty(process.cwd())) {
|
|
38473
38835
|
const ancestorRoot = dirname10(getConfigPath());
|
|
38474
38836
|
console.log(` ${bold("You are inside an existing Gipity project.")}
|
|
@@ -38517,7 +38879,7 @@ var claudeCommand = new Command("claude").description("Set up and run Claude Cod
|
|
|
38517
38879
|
}
|
|
38518
38880
|
project = found;
|
|
38519
38881
|
}
|
|
38520
|
-
const projectDir2 = opts.here ? process.cwd() :
|
|
38882
|
+
const projectDir2 = opts.here ? process.cwd() : join16(getProjectsRoot(), project.slug);
|
|
38521
38883
|
mkdirSync10(projectDir2, { recursive: true });
|
|
38522
38884
|
process.chdir(projectDir2);
|
|
38523
38885
|
clearConfigCache();
|
|
@@ -38646,7 +39008,7 @@ var claudeCommand = new Command("claude").description("Set up and run Claude Cod
|
|
|
38646
39008
|
} else {
|
|
38647
39009
|
project = result.project;
|
|
38648
39010
|
isNewProject = result.kind === "create-new";
|
|
38649
|
-
const projectDir2 =
|
|
39011
|
+
const projectDir2 = join16(getProjectsRoot(), project.slug);
|
|
38650
39012
|
mkdirSync10(projectDir2, { recursive: true });
|
|
38651
39013
|
process.chdir(projectDir2);
|
|
38652
39014
|
clearConfigCache();
|
|
@@ -39813,8 +40175,8 @@ Examples:
|
|
|
39813
40175
|
init_colors();
|
|
39814
40176
|
init_utils();
|
|
39815
40177
|
import { createHash as createHash5 } from "crypto";
|
|
39816
|
-
function expectedType(
|
|
39817
|
-
const p =
|
|
40178
|
+
function expectedType(path5) {
|
|
40179
|
+
const p = path5.toLowerCase().split("?")[0].split("#")[0];
|
|
39818
40180
|
if (p.endsWith(".json")) return "json";
|
|
39819
40181
|
if (p.endsWith(".xml")) return "xml";
|
|
39820
40182
|
if (p.endsWith(".html") || p.endsWith(".htm")) return "html";
|
|
@@ -39845,8 +40207,8 @@ function sha256(s) {
|
|
|
39845
40207
|
function baseWithSlash(url) {
|
|
39846
40208
|
return url.endsWith("/") ? url : url + "/";
|
|
39847
40209
|
}
|
|
39848
|
-
function resolveUrl(base,
|
|
39849
|
-
return new URL(
|
|
40210
|
+
function resolveUrl(base, path5) {
|
|
40211
|
+
return new URL(path5.replace(/^\/+/, ""), baseWithSlash(base)).toString();
|
|
39850
40212
|
}
|
|
39851
40213
|
async function fetchRaw(url) {
|
|
39852
40214
|
try {
|
|
@@ -39868,12 +40230,12 @@ async function probeShell(base) {
|
|
|
39868
40230
|
isHtml: looksLikeHtml(r.body)
|
|
39869
40231
|
};
|
|
39870
40232
|
}
|
|
39871
|
-
function classify(
|
|
40233
|
+
function classify(path5, r, shell) {
|
|
39872
40234
|
if (!r) return { status: null, contentType: null, bytes: 0, verdict: "MISSING", detail: "fetch failed (host unreachable)" };
|
|
39873
40235
|
const bytes = Buffer.byteLength(r.body);
|
|
39874
40236
|
const base = { status: r.status, contentType: r.contentType, bytes };
|
|
39875
40237
|
if (r.status >= 400) return { ...base, verdict: "MISSING", detail: `HTTP ${r.status}` };
|
|
39876
|
-
const expect = expectedType(
|
|
40238
|
+
const expect = expectedType(path5);
|
|
39877
40239
|
if (shell.served && shell.sha256 && sha256(r.body) === shell.sha256) {
|
|
39878
40240
|
return { ...base, verdict: "MISSING", detail: `HTTP ${r.status} but served the SPA shell (file not deployed)` };
|
|
39879
40241
|
}
|
|
@@ -40073,8 +40435,8 @@ fnCommand.command("call <name> [body]").description("Call a function").option("-
|
|
|
40073
40435
|
const config = requireConfig();
|
|
40074
40436
|
const raw = bodyArg || opts.data || "{}";
|
|
40075
40437
|
const body = JSON.parse(raw);
|
|
40076
|
-
const
|
|
40077
|
-
const res = opts.anon ? await callAnon(config.projectGuid, name, body) : await post(
|
|
40438
|
+
const path5 = `/api/${config.projectGuid}/fn/${encodeURIComponent(name)}`;
|
|
40439
|
+
const res = opts.anon ? await callAnon(config.projectGuid, name, body) : await post(path5, body);
|
|
40078
40440
|
if (opts.field) {
|
|
40079
40441
|
emitField(res.data, opts.field);
|
|
40080
40442
|
return;
|
|
@@ -40127,8 +40489,8 @@ serviceCommand.command("list").description("List callable app services").action(
|
|
|
40127
40489
|
}));
|
|
40128
40490
|
serviceCommand.command("call <name> [body]").description("Call an app service by name (e.g. llm, image, music). Uses your logged-in session - no token wrangling.").option("--data <json>", "JSON request body (alternative to the positional [body])").option("--get", "Issue a GET (for listing endpoints like llm/models, tts/voices)").option("--json", "Output compact JSON").action((name, bodyArg, opts) => run("Call", async () => {
|
|
40129
40491
|
const config = requireConfig();
|
|
40130
|
-
const
|
|
40131
|
-
const url = `/api/${config.projectGuid}/services/${
|
|
40492
|
+
const path5 = name.split("/").map(encodeURIComponent).join("/");
|
|
40493
|
+
const url = `/api/${config.projectGuid}/services/${path5}`;
|
|
40132
40494
|
const res = opts.get ? await get(url) : await post(url, JSON.parse(bodyArg || opts.data || "{}"));
|
|
40133
40495
|
console.log(opts.json ? JSON.stringify(res) : JSON.stringify(res, null, 2));
|
|
40134
40496
|
}));
|
|
@@ -40557,11 +40919,11 @@ jobCommand.command("cancel <runGuid>").description("Cancel a queued or running j
|
|
|
40557
40919
|
}
|
|
40558
40920
|
}));
|
|
40559
40921
|
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) => {
|
|
40560
|
-
const { existsSync:
|
|
40561
|
-
const { resolve:
|
|
40922
|
+
const { existsSync: existsSync23, statSync: statSync10 } = await import("node:fs");
|
|
40923
|
+
const { resolve: resolve19, join: join25 } = await import("node:path");
|
|
40562
40924
|
const { spawnCommand: spawnCommand2, spawnSyncCommand: spawnSyncCommand2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
40563
|
-
const jobDir =
|
|
40564
|
-
if (!
|
|
40925
|
+
const jobDir = resolve19(process.cwd(), "jobs", name);
|
|
40926
|
+
if (!existsSync23(jobDir) || !statSync10(jobDir).isDirectory()) {
|
|
40565
40927
|
console.error(error(`jobs/${name}/ not found in current directory`));
|
|
40566
40928
|
process.exit(1);
|
|
40567
40929
|
}
|
|
@@ -40582,7 +40944,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
40582
40944
|
},
|
|
40583
40945
|
{ file: "main.sh", runtime: "bash", entry: "bash" }
|
|
40584
40946
|
];
|
|
40585
|
-
const picked = variants.find((v7) =>
|
|
40947
|
+
const picked = variants.find((v7) => existsSync23(join25(jobDir, v7.file)));
|
|
40586
40948
|
if (!picked) {
|
|
40587
40949
|
console.error(error(`No main.py / main.js / main.sh found under jobs/${name}/`));
|
|
40588
40950
|
process.exit(1);
|
|
@@ -40607,7 +40969,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
40607
40969
|
"-v",
|
|
40608
40970
|
`${jobDir}:/work`
|
|
40609
40971
|
];
|
|
40610
|
-
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd &&
|
|
40972
|
+
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync23(join25(jobDir, picked.depsFile));
|
|
40611
40973
|
const handlerCmd = `${picked.entry} /work/${picked.file}`;
|
|
40612
40974
|
let shellCmd;
|
|
40613
40975
|
if (needsDeps && picked.installCmd) {
|
|
@@ -40783,30 +41145,59 @@ init_api();
|
|
|
40783
41145
|
init_config();
|
|
40784
41146
|
init_colors();
|
|
40785
41147
|
import { mkdirSync as mkdirSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
40786
|
-
import { resolve as resolvePath2, dirname as dirname11, relative as
|
|
41148
|
+
import { resolve as resolvePath2, dirname as dirname11, relative as relative5, isAbsolute } from "path";
|
|
40787
41149
|
|
|
40788
41150
|
// src/provider-docs.ts
|
|
40789
41151
|
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`;
|
|
40790
41152
|
var IMAGE_GEMINI_SIZES = `512, 1K, 2K, 4K`;
|
|
40791
|
-
var IMAGE_MODELS_DOC = `openai: gpt-image-2
|
|
41153
|
+
var IMAGE_MODELS_DOC = `openai: gpt-image-2. bfl: flux-2-pro, flux-2-flex, flux-2-max, flux-2-klein-9b, flux-2-klein-4b. gemini: gemini-3.1-flash-lite-image, gemini-2.5-flash-image, gemini-3.1-flash-image, gemini-3-pro-image`;
|
|
40792
41154
|
var TTS_PROVIDER_DESCRIPTIONS = {
|
|
40793
41155
|
"elevenlabs": `ElevenLabs (many voices - use voice_set list to discover)`,
|
|
40794
41156
|
"openai": `OpenAI (alloy, ash, ballad, coral, echo, fable, nova, onyx, sage, shimmer, verse)`,
|
|
40795
41157
|
"gemini": `Gemini (30 voices: Kore, Puck, Zephyr, Charon, Fenrir, Leda, Orus, Aoede, and 22 more). Multi-speaker (up to 2) and 60+ languages`
|
|
40796
41158
|
};
|
|
40797
|
-
var VIDEO_MODELS_DOC = `veo-3.1-generate-preview (best quality, ~$0.40/sec), veo-3.1-fast-generate-preview (faster, ~$0.
|
|
41159
|
+
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)`;
|
|
40798
41160
|
|
|
40799
41161
|
// src/commands/generate.ts
|
|
40800
41162
|
async function downloadFile(url, filename) {
|
|
40801
41163
|
const res = await fetch(url);
|
|
40802
41164
|
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
40803
41165
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
40804
|
-
|
|
40805
|
-
|
|
40806
|
-
|
|
41166
|
+
const outPath = correctExtension(filename, buffer);
|
|
41167
|
+
ensureOutputDir(outPath);
|
|
41168
|
+
writeFileSync11(outPath, buffer);
|
|
41169
|
+
const savedPath = resolvePath2(outPath);
|
|
40807
41170
|
await pushGenerated(savedPath);
|
|
40808
41171
|
return savedPath;
|
|
40809
41172
|
}
|
|
41173
|
+
function sniffExt(buf) {
|
|
41174
|
+
const ascii = (start, end) => buf.subarray(start, end).toString("latin1");
|
|
41175
|
+
if (buf.length < 4) return void 0;
|
|
41176
|
+
if (buf[0] === 255 && buf[1] === 216 && buf[2] === 255) return "jpg";
|
|
41177
|
+
if (ascii(0, 8) === "\x89PNG\r\n\n") return "png";
|
|
41178
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WEBP") return "webp";
|
|
41179
|
+
if (ascii(0, 4) === "RIFF" && ascii(8, 12) === "WAVE") return "wav";
|
|
41180
|
+
if (ascii(0, 3) === "GIF") return "gif";
|
|
41181
|
+
if (ascii(4, 8) === "ftyp") return "mp4";
|
|
41182
|
+
if (buf[0] === 26 && buf[1] === 69 && buf[2] === 223 && buf[3] === 163) return "webm";
|
|
41183
|
+
if (ascii(0, 3) === "ID3" || buf[0] === 255 && (buf[1] & 224) === 224) return "mp3";
|
|
41184
|
+
return void 0;
|
|
41185
|
+
}
|
|
41186
|
+
var EXT_ALIASES = { jpeg: "jpg", htm: "html", yml: "yaml" };
|
|
41187
|
+
var canonicalExt = (ext) => EXT_ALIASES[ext] ?? ext;
|
|
41188
|
+
var formatName = (ext) => canonicalExt(ext) === "jpg" ? "JPEG" : ext.toUpperCase();
|
|
41189
|
+
function correctExtension(filename, buf) {
|
|
41190
|
+
const actual = sniffExt(buf);
|
|
41191
|
+
if (!actual) return filename;
|
|
41192
|
+
const dot = filename.lastIndexOf(".");
|
|
41193
|
+
const asked = dot > 0 ? filename.slice(dot + 1).toLowerCase() : "";
|
|
41194
|
+
if (canonicalExt(asked) === actual) return filename;
|
|
41195
|
+
const corrected = `${dot > 0 ? filename.slice(0, dot) : filename}.${actual}`;
|
|
41196
|
+
console.error(muted(
|
|
41197
|
+
`Note: the model returned ${formatName(actual)}, not ${asked ? formatName(asked) : "the requested format"} \u2014 saving as ${corrected} so the extension matches the actual bytes.`
|
|
41198
|
+
));
|
|
41199
|
+
return corrected;
|
|
41200
|
+
}
|
|
40810
41201
|
function ensureOutputDir(output) {
|
|
40811
41202
|
const dir = dirname11(resolvePath2(output));
|
|
40812
41203
|
try {
|
|
@@ -40818,7 +41209,7 @@ function ensureOutputDir(output) {
|
|
|
40818
41209
|
async function pushGenerated(savedPath) {
|
|
40819
41210
|
const configPath = getConfigPath();
|
|
40820
41211
|
if (!configPath) return;
|
|
40821
|
-
const rel2 =
|
|
41212
|
+
const rel2 = relative5(dirname11(configPath), savedPath);
|
|
40822
41213
|
if (rel2.startsWith("..") || isAbsolute(rel2)) return;
|
|
40823
41214
|
try {
|
|
40824
41215
|
await pushFile(savedPath);
|
|
@@ -40861,10 +41252,10 @@ Examples:
|
|
|
40861
41252
|
} else {
|
|
40862
41253
|
const sizeKb = Math.round(result.size_bytes / 1024);
|
|
40863
41254
|
console.log(`${muted(`Generated with ${result.provider}/${result.model} (${sizeKb}KB)`)}`);
|
|
40864
|
-
console.log(success(`Saved to ${savedPath}`));
|
|
40865
41255
|
if (result.seed !== void 0) {
|
|
40866
41256
|
console.log(muted(`Seed ${result.seed} \u2014 pass --seed ${result.seed} to keep the next image coherent`));
|
|
40867
41257
|
}
|
|
41258
|
+
console.log(success(`Saved to ${savedPath}`));
|
|
40868
41259
|
}
|
|
40869
41260
|
} catch (err) {
|
|
40870
41261
|
printCommandError("Image generation", err);
|
|
@@ -41043,14 +41434,14 @@ var generateCommand = new Command("generate").description("Generate images, vide
|
|
|
41043
41434
|
init_api();
|
|
41044
41435
|
init_config();
|
|
41045
41436
|
init_colors();
|
|
41046
|
-
import { existsSync as
|
|
41047
|
-
import { join as
|
|
41437
|
+
import { existsSync as existsSync14, readFileSync as readFileSync17 } from "fs";
|
|
41438
|
+
import { join as join17 } from "path";
|
|
41048
41439
|
function readInstalledKitReadme(name) {
|
|
41049
41440
|
const root = getProjectRoot() ?? process.cwd();
|
|
41050
|
-
const kitDir =
|
|
41051
|
-
if (!
|
|
41052
|
-
const readme =
|
|
41053
|
-
return
|
|
41441
|
+
const kitDir = join17(root, "src", "packages", name);
|
|
41442
|
+
if (!existsSync14(join17(kitDir, "package.json"))) return null;
|
|
41443
|
+
const readme = join17(kitDir, "README.md");
|
|
41444
|
+
return existsSync14(readme) ? readFileSync17(readme, "utf-8") : null;
|
|
41054
41445
|
}
|
|
41055
41446
|
var skillCommand = new Command("skill").description("Task docs - read the matching skill before building");
|
|
41056
41447
|
skillCommand.command("list").description("List skills").option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
@@ -41223,15 +41614,15 @@ var domainCommand = new Command("domain").description("Manage custom domains").a
|
|
|
41223
41614
|
}));
|
|
41224
41615
|
|
|
41225
41616
|
// src/commands/realtime.ts
|
|
41226
|
-
import { existsSync as
|
|
41227
|
-
import { dirname as dirname12, resolve as
|
|
41617
|
+
import { existsSync as existsSync15 } from "fs";
|
|
41618
|
+
import { dirname as dirname12, resolve as resolve15 } from "path";
|
|
41228
41619
|
init_api();
|
|
41229
41620
|
init_config();
|
|
41230
41621
|
init_colors();
|
|
41231
41622
|
function hasDeployManifest() {
|
|
41232
41623
|
const cfgPath = getConfigPath();
|
|
41233
41624
|
if (!cfgPath) return false;
|
|
41234
|
-
return
|
|
41625
|
+
return existsSync15(resolve15(dirname12(cfgPath), "gipity.yaml"));
|
|
41235
41626
|
}
|
|
41236
41627
|
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 () => {
|
|
41237
41628
|
const config = requireConfig();
|
|
@@ -41364,10 +41755,10 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
41364
41755
|
if (!groups.has(key)) groups.set(key, []);
|
|
41365
41756
|
groups.get(key).push(r);
|
|
41366
41757
|
}
|
|
41367
|
-
for (const [
|
|
41758
|
+
for (const [path5, results] of groups) {
|
|
41368
41759
|
const existingPaths = new Set(data.results.slice(0, lastResultCount).map((r) => r.path));
|
|
41369
|
-
if (!existingPaths.has(
|
|
41370
|
-
console.log(` ${dim(
|
|
41760
|
+
if (!existingPaths.has(path5)) {
|
|
41761
|
+
console.log(` ${dim(path5)}`);
|
|
41371
41762
|
}
|
|
41372
41763
|
for (const r of results) {
|
|
41373
41764
|
const icon = r.isFlaky ? warning("~") : statusIcon2(r.status);
|
|
@@ -41407,7 +41798,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
41407
41798
|
}
|
|
41408
41799
|
}
|
|
41409
41800
|
}
|
|
41410
|
-
await new Promise((
|
|
41801
|
+
await new Promise((resolve19) => setTimeout(resolve19, getPollInterval()));
|
|
41411
41802
|
}
|
|
41412
41803
|
}
|
|
41413
41804
|
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 () => {
|
|
@@ -41617,21 +42008,21 @@ var locationCommand = new Command("location").description("Show location").argum
|
|
|
41617
42008
|
}));
|
|
41618
42009
|
|
|
41619
42010
|
// src/commands/doctor.ts
|
|
41620
|
-
import { existsSync as
|
|
41621
|
-
import { join as
|
|
42011
|
+
import { existsSync as existsSync17, readFileSync as readFileSync19, statSync as statSync8 } from "fs";
|
|
42012
|
+
import { join as join19 } from "path";
|
|
41622
42013
|
import { homedir as homedir13 } from "os";
|
|
41623
42014
|
|
|
41624
42015
|
// src/updater/state.ts
|
|
41625
|
-
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, existsSync as
|
|
41626
|
-
import { join as
|
|
42016
|
+
import { readFileSync as readFileSync18, writeFileSync as writeFileSync12, mkdirSync as mkdirSync12, existsSync as existsSync16 } from "fs";
|
|
42017
|
+
import { join as join18 } from "path";
|
|
41627
42018
|
import { homedir as homedir12 } from "os";
|
|
41628
|
-
var GIPITY_DIR =
|
|
41629
|
-
var LOCAL_DIR =
|
|
41630
|
-
var LOCAL_PKG_DIR =
|
|
41631
|
-
var LOCAL_ENTRY =
|
|
41632
|
-
var STATE_FILE =
|
|
41633
|
-
var SETTINGS_FILE =
|
|
41634
|
-
var UPDATE_LOG =
|
|
42019
|
+
var GIPITY_DIR = join18(homedir12(), ".gipity");
|
|
42020
|
+
var LOCAL_DIR = join18(GIPITY_DIR, "local");
|
|
42021
|
+
var LOCAL_PKG_DIR = join18(LOCAL_DIR, "node_modules", "gipity");
|
|
42022
|
+
var LOCAL_ENTRY = join18(LOCAL_PKG_DIR, "dist", "index.js");
|
|
42023
|
+
var STATE_FILE = join18(GIPITY_DIR, "update-state.json");
|
|
42024
|
+
var SETTINGS_FILE = join18(GIPITY_DIR, "settings.json");
|
|
42025
|
+
var UPDATE_LOG = join18(GIPITY_DIR, "update.log");
|
|
41635
42026
|
var DEFAULT_STATE = {
|
|
41636
42027
|
installedVersion: null,
|
|
41637
42028
|
lastCheckAt: 0,
|
|
@@ -41645,7 +42036,7 @@ function ensureDir() {
|
|
|
41645
42036
|
mkdirSync12(GIPITY_DIR, { recursive: true });
|
|
41646
42037
|
}
|
|
41647
42038
|
function readState() {
|
|
41648
|
-
if (!
|
|
42039
|
+
if (!existsSync16(STATE_FILE)) return { ...DEFAULT_STATE };
|
|
41649
42040
|
try {
|
|
41650
42041
|
return { ...DEFAULT_STATE, ...JSON.parse(readFileSync18(STATE_FILE, "utf-8")) };
|
|
41651
42042
|
} catch {
|
|
@@ -41657,7 +42048,7 @@ function writeState(state) {
|
|
|
41657
42048
|
writeFileSync12(STATE_FILE, JSON.stringify(state, null, 2));
|
|
41658
42049
|
}
|
|
41659
42050
|
function readSettings() {
|
|
41660
|
-
if (!
|
|
42051
|
+
if (!existsSync16(SETTINGS_FILE)) return { ...DEFAULT_SETTINGS };
|
|
41661
42052
|
try {
|
|
41662
42053
|
return { ...DEFAULT_SETTINGS, ...JSON.parse(readFileSync18(SETTINGS_FILE, "utf-8")) };
|
|
41663
42054
|
} catch {
|
|
@@ -41692,8 +42083,8 @@ function versionManagerNode(execPath, env = process.env, home = homedir13()) {
|
|
|
41692
42083
|
return null;
|
|
41693
42084
|
}
|
|
41694
42085
|
function localVersion() {
|
|
41695
|
-
const pkgPath =
|
|
41696
|
-
if (!
|
|
42086
|
+
const pkgPath = join19(LOCAL_PKG_DIR, "package.json");
|
|
42087
|
+
if (!existsSync17(pkgPath)) return null;
|
|
41697
42088
|
try {
|
|
41698
42089
|
return JSON.parse(readFileSync19(pkgPath, "utf-8")).version ?? null;
|
|
41699
42090
|
} catch {
|
|
@@ -41718,7 +42109,7 @@ function rel(t) {
|
|
|
41718
42109
|
}
|
|
41719
42110
|
function relayAutostartInstalled() {
|
|
41720
42111
|
try {
|
|
41721
|
-
return
|
|
42112
|
+
return existsSync17(planFor({ cliPath: resolveCliPath() }).path);
|
|
41722
42113
|
} catch (err) {
|
|
41723
42114
|
if (err instanceof UnsupportedPlatformError) return null;
|
|
41724
42115
|
return null;
|
|
@@ -41756,7 +42147,7 @@ function gatherEnv(opts = {}) {
|
|
|
41756
42147
|
const cli = {
|
|
41757
42148
|
shim_version: shimVersion(),
|
|
41758
42149
|
local_version: localVersion(),
|
|
41759
|
-
local_install_ok:
|
|
42150
|
+
local_install_ok: existsSync17(LOCAL_ENTRY),
|
|
41760
42151
|
auto_updates: !dis.disabled,
|
|
41761
42152
|
updates_disabled_reason: dis.disabled ? dis.reason ?? null : null,
|
|
41762
42153
|
last_check_at: updState.lastCheckAt,
|
|
@@ -41791,7 +42182,7 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
41791
42182
|
const settings = readSettings();
|
|
41792
42183
|
const dis = updatesDisabled();
|
|
41793
42184
|
const local = localVersion();
|
|
41794
|
-
const localOk =
|
|
42185
|
+
const localOk = existsSync17(LOCAL_ENTRY);
|
|
41795
42186
|
console.log("");
|
|
41796
42187
|
console.log(bold("CLI install"));
|
|
41797
42188
|
console.log(`${muted("shim version ")} ${shimVersion()}`);
|
|
@@ -41799,17 +42190,17 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
41799
42190
|
console.log(`${muted("local install ")} ${LOCAL_PKG_DIR}`);
|
|
41800
42191
|
console.log("");
|
|
41801
42192
|
console.log(`${muted("auto-updates ")} ${dis.disabled ? warning(`disabled (${dis.reason})`) : success("enabled")}`);
|
|
41802
|
-
console.log(`${muted("settings file ")} ${
|
|
42193
|
+
console.log(`${muted("settings file ")} ${existsSync17(SETTINGS_FILE) ? SETTINGS_FILE : dim("(default)")} autoUpdates=${settings.autoUpdates}`);
|
|
41803
42194
|
console.log(`${muted("last check ")} ${rel(state.lastCheckAt)}`);
|
|
41804
42195
|
console.log(`${muted("last error ")} ${state.lastError ? error(state.lastError) : dim("none")}`);
|
|
41805
|
-
console.log(`${muted("state file ")} ${
|
|
41806
|
-
console.log(`${muted("update log ")} ${
|
|
42196
|
+
console.log(`${muted("state file ")} ${existsSync17(STATE_FILE) ? STATE_FILE : dim("(none yet)")}`);
|
|
42197
|
+
console.log(`${muted("update log ")} ${existsSync17(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync8(UPDATE_LOG).size} bytes)` : dim("(none yet)")}`);
|
|
41807
42198
|
console.log("");
|
|
41808
42199
|
console.log(dim("Force an update with: gipity update"));
|
|
41809
42200
|
});
|
|
41810
42201
|
|
|
41811
42202
|
// src/updater/check.ts
|
|
41812
|
-
import { appendFileSync, existsSync as
|
|
42203
|
+
import { appendFileSync, existsSync as existsSync18 } from "fs";
|
|
41813
42204
|
init_platform();
|
|
41814
42205
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
41815
42206
|
function log(line) {
|
|
@@ -41844,7 +42235,7 @@ function installVersion(version) {
|
|
|
41844
42235
|
stdio: "ignore"
|
|
41845
42236
|
});
|
|
41846
42237
|
if (res.error) log(`npm spawn failed: ${res.error.message}`);
|
|
41847
|
-
return res.status === 0 &&
|
|
42238
|
+
return res.status === 0 && existsSync18(LOCAL_ENTRY);
|
|
41848
42239
|
}
|
|
41849
42240
|
async function runCheck(opts = {}) {
|
|
41850
42241
|
const state = readState();
|
|
@@ -41918,17 +42309,17 @@ var updateCommand = new Command("update").description("Update the CLI").action(a
|
|
|
41918
42309
|
init_api();
|
|
41919
42310
|
init_utils();
|
|
41920
42311
|
init_colors();
|
|
41921
|
-
import { existsSync as
|
|
42312
|
+
import { existsSync as existsSync21, readFileSync as readFileSync22, unlinkSync as unlinkSync7 } from "fs";
|
|
41922
42313
|
import { spawn as spawn2 } from "child_process";
|
|
41923
42314
|
|
|
41924
42315
|
// src/relay/daemon.ts
|
|
41925
42316
|
init_platform();
|
|
41926
42317
|
init_config();
|
|
41927
|
-
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, existsSync as
|
|
42318
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync14, existsSync as existsSync20, readFileSync as readFileSync21, writeFileSync as writeFileSync13, chmodSync as chmodSync3, closeSync as closeSync4, openSync as openSync4, unlinkSync as unlinkSync6 } from "fs";
|
|
41928
42319
|
import { stat, readFile } from "fs/promises";
|
|
41929
42320
|
import { createInterface as createInterface2 } from "readline";
|
|
41930
42321
|
import { homedir as homedir15, hostname as hostname5, platform as osPlatform3, loadavg as loadavg2, freemem as freemem2, totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
41931
|
-
import { join as
|
|
42322
|
+
import { join as join22 } from "path";
|
|
41932
42323
|
init_auth();
|
|
41933
42324
|
init_api();
|
|
41934
42325
|
|
|
@@ -42218,6 +42609,214 @@ var IngestQueue = class {
|
|
|
42218
42609
|
}
|
|
42219
42610
|
};
|
|
42220
42611
|
|
|
42612
|
+
// src/relay/media-upload.ts
|
|
42613
|
+
import path4 from "path";
|
|
42614
|
+
|
|
42615
|
+
// src/relay/device-http.ts
|
|
42616
|
+
init_config();
|
|
42617
|
+
function apiBase() {
|
|
42618
|
+
return resolveApiBase();
|
|
42619
|
+
}
|
|
42620
|
+
function deviceToken() {
|
|
42621
|
+
const d = getDevice();
|
|
42622
|
+
if (!d) throw new Error("No device registered. Run: gipity login");
|
|
42623
|
+
return d.token;
|
|
42624
|
+
}
|
|
42625
|
+
function bridgeAbort(outer, inner) {
|
|
42626
|
+
if (outer.aborted) {
|
|
42627
|
+
inner.abort(outer.reason);
|
|
42628
|
+
return () => {
|
|
42629
|
+
};
|
|
42630
|
+
}
|
|
42631
|
+
const onAbort = () => {
|
|
42632
|
+
inner.abort(outer.reason);
|
|
42633
|
+
};
|
|
42634
|
+
outer.addEventListener("abort", onAbort, { once: true });
|
|
42635
|
+
return () => outer.removeEventListener("abort", onAbort);
|
|
42636
|
+
}
|
|
42637
|
+
async function deviceFetchBinary(method, path5, body, contentType, timeoutMs) {
|
|
42638
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
42639
|
+
const timer = controller ? setTimeout(() => controller.abort("timeout"), timeoutMs) : null;
|
|
42640
|
+
try {
|
|
42641
|
+
return await fetch(`${apiBase()}${path5}`, {
|
|
42642
|
+
method,
|
|
42643
|
+
headers: {
|
|
42644
|
+
"Authorization": `Bearer ${deviceToken()}`,
|
|
42645
|
+
"Content-Type": contentType
|
|
42646
|
+
},
|
|
42647
|
+
body: new Uint8Array(body),
|
|
42648
|
+
signal: controller?.signal
|
|
42649
|
+
});
|
|
42650
|
+
} finally {
|
|
42651
|
+
if (timer) clearTimeout(timer);
|
|
42652
|
+
}
|
|
42653
|
+
}
|
|
42654
|
+
async function deviceFetch(method, path5, body, timeoutMs, signal) {
|
|
42655
|
+
const controller = timeoutMs ? new AbortController() : null;
|
|
42656
|
+
const timer = controller ? setTimeout(() => controller.abort("timeout"), timeoutMs) : null;
|
|
42657
|
+
const detach = signal && controller ? bridgeAbort(signal, controller) : null;
|
|
42658
|
+
try {
|
|
42659
|
+
return await fetch(`${apiBase()}${path5}`, {
|
|
42660
|
+
method,
|
|
42661
|
+
headers: {
|
|
42662
|
+
"Authorization": `Bearer ${deviceToken()}`,
|
|
42663
|
+
"Content-Type": "application/json"
|
|
42664
|
+
},
|
|
42665
|
+
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
42666
|
+
signal: controller?.signal ?? signal
|
|
42667
|
+
});
|
|
42668
|
+
} finally {
|
|
42669
|
+
if (timer) clearTimeout(timer);
|
|
42670
|
+
if (detach) detach();
|
|
42671
|
+
}
|
|
42672
|
+
}
|
|
42673
|
+
|
|
42674
|
+
// src/relay/media-upload.ts
|
|
42675
|
+
var MIN_UPLOAD_BYTES = 4 * 1024;
|
|
42676
|
+
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
42677
|
+
var INLINE_SAFE_B64_CHARS = 12e4;
|
|
42678
|
+
var PATH_MAP_CAP = 500;
|
|
42679
|
+
var uploadViaDevice = async (convGuid, buf, opts) => {
|
|
42680
|
+
const qs = new URLSearchParams({ filename: opts.filename, media_type: opts.mediaType });
|
|
42681
|
+
if (opts.suggestedPath) qs.set("suggested_path", opts.suggestedPath);
|
|
42682
|
+
const res = await deviceFetchBinary(
|
|
42683
|
+
"POST",
|
|
42684
|
+
`/remote-sessions/${encodeURIComponent(convGuid)}/media?${qs.toString()}`,
|
|
42685
|
+
buf,
|
|
42686
|
+
opts.mediaType,
|
|
42687
|
+
3e4
|
|
42688
|
+
);
|
|
42689
|
+
if (!res.ok) {
|
|
42690
|
+
const body = await res.text().catch(() => "");
|
|
42691
|
+
throw new Error(`HTTP ${res.status} ${body.slice(0, 120)}`);
|
|
42692
|
+
}
|
|
42693
|
+
const json = await res.json();
|
|
42694
|
+
return json.data;
|
|
42695
|
+
};
|
|
42696
|
+
function extForMime(mime) {
|
|
42697
|
+
switch (mime) {
|
|
42698
|
+
case "image/jpeg":
|
|
42699
|
+
return ".jpg";
|
|
42700
|
+
case "image/gif":
|
|
42701
|
+
return ".gif";
|
|
42702
|
+
case "image/webp":
|
|
42703
|
+
return ".webp";
|
|
42704
|
+
case "image/svg+xml":
|
|
42705
|
+
return ".svg";
|
|
42706
|
+
default:
|
|
42707
|
+
return ".png";
|
|
42708
|
+
}
|
|
42709
|
+
}
|
|
42710
|
+
function isBase64ImageBlock(b7) {
|
|
42711
|
+
return b7?.type === "image" && b7.source?.type === "base64" && typeof b7.source.data === "string" && b7.source.data.length > 0;
|
|
42712
|
+
}
|
|
42713
|
+
var ImageBlockRewriter = class {
|
|
42714
|
+
constructor(convGuid, onWarn, upload = uploadViaDevice) {
|
|
42715
|
+
this.convGuid = convGuid;
|
|
42716
|
+
this.onWarn = onWarn;
|
|
42717
|
+
this.upload = upload;
|
|
42718
|
+
}
|
|
42719
|
+
convGuid;
|
|
42720
|
+
onWarn;
|
|
42721
|
+
upload;
|
|
42722
|
+
/** tool_use_id → the file_path the tool was asked to read (for naming +
|
|
42723
|
+
* server-side dedup against the synced node at that path). */
|
|
42724
|
+
pathsByToolUseId = /* @__PURE__ */ new Map();
|
|
42725
|
+
/** Project root on this machine, once the dispatch resolves it — lets an
|
|
42726
|
+
* absolute Read path map to its project-relative VFS path. */
|
|
42727
|
+
cwd = null;
|
|
42728
|
+
setCwd(cwd) {
|
|
42729
|
+
this.cwd = cwd;
|
|
42730
|
+
}
|
|
42731
|
+
/** Rewrite every base64 image block in the batch to an image_ref. Never
|
|
42732
|
+
* throws — per-image failures degrade per the policy above. */
|
|
42733
|
+
async rewrite(entries) {
|
|
42734
|
+
const out = [];
|
|
42735
|
+
for (const entry of entries) {
|
|
42736
|
+
if (entry.kind === "tool_use") {
|
|
42737
|
+
this.recordToolPath(entry);
|
|
42738
|
+
out.push(entry);
|
|
42739
|
+
continue;
|
|
42740
|
+
}
|
|
42741
|
+
if (entry.kind !== "tool_result" || !Array.isArray(entry.content) || typeof entry.tool_use_id !== "string") {
|
|
42742
|
+
out.push(entry);
|
|
42743
|
+
continue;
|
|
42744
|
+
}
|
|
42745
|
+
const blocks = [];
|
|
42746
|
+
for (const block of entry.content) {
|
|
42747
|
+
blocks.push(isBase64ImageBlock(block) ? await this.rewriteBlock(block, entry.tool_use_id) : block);
|
|
42748
|
+
}
|
|
42749
|
+
out.push({ ...entry, content: blocks });
|
|
42750
|
+
}
|
|
42751
|
+
return out;
|
|
42752
|
+
}
|
|
42753
|
+
recordToolPath(entry) {
|
|
42754
|
+
if (typeof entry.tool_use_id !== "string") return;
|
|
42755
|
+
const input = entry.tool_input;
|
|
42756
|
+
const p = input && typeof input === "object" ? input.file_path ?? input.path ?? input.notebook_path : void 0;
|
|
42757
|
+
if (typeof p !== "string" || !p) return;
|
|
42758
|
+
if (this.pathsByToolUseId.size >= PATH_MAP_CAP) {
|
|
42759
|
+
const oldest = this.pathsByToolUseId.keys().next().value;
|
|
42760
|
+
if (oldest !== void 0) this.pathsByToolUseId.delete(oldest);
|
|
42761
|
+
}
|
|
42762
|
+
this.pathsByToolUseId.set(entry.tool_use_id, p);
|
|
42763
|
+
}
|
|
42764
|
+
async rewriteBlock(block, toolUseId) {
|
|
42765
|
+
const b64 = block.source.data;
|
|
42766
|
+
let buf;
|
|
42767
|
+
try {
|
|
42768
|
+
buf = Buffer.from(b64, "base64");
|
|
42769
|
+
} catch {
|
|
42770
|
+
return block;
|
|
42771
|
+
}
|
|
42772
|
+
if (buf.length < MIN_UPLOAD_BYTES) return block;
|
|
42773
|
+
const mediaType = block.source.media_type || "image/png";
|
|
42774
|
+
const sourcePath = this.pathsByToolUseId.get(toolUseId);
|
|
42775
|
+
const filename = sourcePath ? path4.basename(sourcePath) : `image${extForMime(mediaType)}`;
|
|
42776
|
+
const suggested = this.projectRelative(sourcePath);
|
|
42777
|
+
if (buf.length > MAX_UPLOAD_BYTES) {
|
|
42778
|
+
this.onWarn?.("transcript image exceeds upload cap - stubbed", { bytes: buf.length, filename });
|
|
42779
|
+
return this.stub(filename, buf.length, "too large to store");
|
|
42780
|
+
}
|
|
42781
|
+
try {
|
|
42782
|
+
const ref = await this.upload(this.convGuid, buf, {
|
|
42783
|
+
filename,
|
|
42784
|
+
mediaType,
|
|
42785
|
+
...suggested ? { suggestedPath: suggested } : {}
|
|
42786
|
+
});
|
|
42787
|
+
return {
|
|
42788
|
+
type: "image_ref",
|
|
42789
|
+
url: ref.url,
|
|
42790
|
+
...ref.thumb_url ? { thumb_url: ref.thumb_url } : {},
|
|
42791
|
+
media_type: mediaType,
|
|
42792
|
+
path: ref.path,
|
|
42793
|
+
...ref.width != null ? { width: ref.width } : {},
|
|
42794
|
+
...ref.height != null ? { height: ref.height } : {},
|
|
42795
|
+
bytes: ref.bytes
|
|
42796
|
+
};
|
|
42797
|
+
} catch (err) {
|
|
42798
|
+
this.onWarn?.("transcript image upload failed", { filename, err: err?.message });
|
|
42799
|
+
if (b64.length <= INLINE_SAFE_B64_CHARS) return block;
|
|
42800
|
+
return this.stub(filename, buf.length, `upload failed: ${err?.message || "unknown error"}`);
|
|
42801
|
+
}
|
|
42802
|
+
}
|
|
42803
|
+
stub(filename, bytes, reason) {
|
|
42804
|
+
return {
|
|
42805
|
+
type: "text",
|
|
42806
|
+
text: `[image ${filename} (${Math.round(bytes / 1024)} KB) omitted: ${reason}]`
|
|
42807
|
+
};
|
|
42808
|
+
}
|
|
42809
|
+
/** Map an absolute local path to its project-relative VFS path, when it
|
|
42810
|
+
* falls inside the dispatch's project root. */
|
|
42811
|
+
projectRelative(p) {
|
|
42812
|
+
if (!p || !this.cwd) return void 0;
|
|
42813
|
+
const abs = path4.resolve(p);
|
|
42814
|
+
const root = path4.resolve(this.cwd);
|
|
42815
|
+
if (!abs.startsWith(root + path4.sep)) return void 0;
|
|
42816
|
+
return abs.slice(root.length + 1).split(path4.sep).join("/");
|
|
42817
|
+
}
|
|
42818
|
+
};
|
|
42819
|
+
|
|
42221
42820
|
// src/relay/redact.ts
|
|
42222
42821
|
var REDACTION_MARKER = "[redacted]";
|
|
42223
42822
|
var MIN_SECRET_LEN = 12;
|
|
@@ -42428,48 +43027,6 @@ var DeltaBatcher = class {
|
|
|
42428
43027
|
// src/relay/daemon.ts
|
|
42429
43028
|
import { randomUUID } from "crypto";
|
|
42430
43029
|
|
|
42431
|
-
// src/relay/device-http.ts
|
|
42432
|
-
init_config();
|
|
42433
|
-
function apiBase() {
|
|
42434
|
-
return resolveApiBase();
|
|
42435
|
-
}
|
|
42436
|
-
function deviceToken() {
|
|
42437
|
-
const d = getDevice();
|
|
42438
|
-
if (!d) throw new Error("No device registered. Run: gipity login");
|
|
42439
|
-
return d.token;
|
|
42440
|
-
}
|
|
42441
|
-
function bridgeAbort(outer, inner) {
|
|
42442
|
-
if (outer.aborted) {
|
|
42443
|
-
inner.abort(outer.reason);
|
|
42444
|
-
return () => {
|
|
42445
|
-
};
|
|
42446
|
-
}
|
|
42447
|
-
const onAbort = () => {
|
|
42448
|
-
inner.abort(outer.reason);
|
|
42449
|
-
};
|
|
42450
|
-
outer.addEventListener("abort", onAbort, { once: true });
|
|
42451
|
-
return () => outer.removeEventListener("abort", onAbort);
|
|
42452
|
-
}
|
|
42453
|
-
async function deviceFetch(method, path4, body, timeoutMs, signal) {
|
|
42454
|
-
const controller = timeoutMs ? new AbortController() : null;
|
|
42455
|
-
const timer = controller ? setTimeout(() => controller.abort("timeout"), timeoutMs) : null;
|
|
42456
|
-
const detach = signal && controller ? bridgeAbort(signal, controller) : null;
|
|
42457
|
-
try {
|
|
42458
|
-
return await fetch(`${apiBase()}${path4}`, {
|
|
42459
|
-
method,
|
|
42460
|
-
headers: {
|
|
42461
|
-
"Authorization": `Bearer ${deviceToken()}`,
|
|
42462
|
-
"Content-Type": "application/json"
|
|
42463
|
-
},
|
|
42464
|
-
body: body !== void 0 ? JSON.stringify(body) : void 0,
|
|
42465
|
-
signal: controller?.signal ?? signal
|
|
42466
|
-
});
|
|
42467
|
-
} finally {
|
|
42468
|
-
if (timer) clearTimeout(timer);
|
|
42469
|
-
if (detach) detach();
|
|
42470
|
-
}
|
|
42471
|
-
}
|
|
42472
|
-
|
|
42473
43030
|
// src/relay/agent-token.ts
|
|
42474
43031
|
init_api();
|
|
42475
43032
|
init_config();
|
|
@@ -42518,21 +43075,21 @@ async function revokeRelayAgentToken() {
|
|
|
42518
43075
|
init_platform();
|
|
42519
43076
|
init_client_context();
|
|
42520
43077
|
import { cpus, freemem, totalmem, loadavg, release, uptime, homedir as homedir14 } from "os";
|
|
42521
|
-
import { statfsSync, readdirSync as
|
|
42522
|
-
import { join as
|
|
43078
|
+
import { statfsSync, readdirSync as readdirSync8 } from "fs";
|
|
43079
|
+
import { join as join20 } from "path";
|
|
42523
43080
|
var PROBE_TIMEOUT_MS = 4e3;
|
|
42524
43081
|
function parseVersion(out) {
|
|
42525
43082
|
const m = out.match(/\d+\.\d+(?:\.\d+)?(?:[-.\w]*)?/);
|
|
42526
43083
|
return m ? m[0] : void 0;
|
|
42527
43084
|
}
|
|
42528
43085
|
function runProbe(cmd, args) {
|
|
42529
|
-
return new Promise((
|
|
43086
|
+
return new Promise((resolve19) => {
|
|
42530
43087
|
let out = "";
|
|
42531
43088
|
let settled = false;
|
|
42532
43089
|
const done = (s) => {
|
|
42533
43090
|
if (!settled) {
|
|
42534
43091
|
settled = true;
|
|
42535
|
-
|
|
43092
|
+
resolve19(s);
|
|
42536
43093
|
}
|
|
42537
43094
|
};
|
|
42538
43095
|
let child;
|
|
@@ -42586,7 +43143,7 @@ async function detectGpu() {
|
|
|
42586
43143
|
function diskUsage() {
|
|
42587
43144
|
try {
|
|
42588
43145
|
if (typeof statfsSync !== "function") return void 0;
|
|
42589
|
-
const st2 = statfsSync(
|
|
43146
|
+
const st2 = statfsSync(join20(homedir14(), "GipityProjects"), { bigint: false });
|
|
42590
43147
|
if (!st2 || !st2.bsize) return void 0;
|
|
42591
43148
|
return { total: st2.bsize * st2.blocks, free: st2.bsize * st2.bavail };
|
|
42592
43149
|
} catch {
|
|
@@ -42600,7 +43157,7 @@ function diskUsage() {
|
|
|
42600
43157
|
}
|
|
42601
43158
|
function localProjectCount() {
|
|
42602
43159
|
try {
|
|
42603
|
-
return
|
|
43160
|
+
return readdirSync8(join20(homedir14(), "GipityProjects"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
|
|
42604
43161
|
} catch {
|
|
42605
43162
|
return void 0;
|
|
42606
43163
|
}
|
|
@@ -42729,8 +43286,8 @@ var SessionPool = class {
|
|
|
42729
43286
|
const done = new Promise((r) => {
|
|
42730
43287
|
markDone = r;
|
|
42731
43288
|
});
|
|
42732
|
-
return new Promise((
|
|
42733
|
-
const turn = { onMessage: p.onMessage, resolve:
|
|
43289
|
+
return new Promise((resolve19, reject) => {
|
|
43290
|
+
const turn = { onMessage: p.onMessage, resolve: resolve19, reject, interruptRequested: false, wasHot, done, markDone };
|
|
42734
43291
|
session.current = turn;
|
|
42735
43292
|
session.state = "running";
|
|
42736
43293
|
session.lastActivityAt = Date.now();
|
|
@@ -42907,7 +43464,7 @@ var SessionPool = class {
|
|
|
42907
43464
|
// src/relay/daemon.ts
|
|
42908
43465
|
init_config();
|
|
42909
43466
|
init_api();
|
|
42910
|
-
var RELAY_LOG_PATH =
|
|
43467
|
+
var RELAY_LOG_PATH = join22(homedir15(), ".gipity", "relay.log");
|
|
42911
43468
|
var HEARTBEAT_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_HEARTBEAT_MS || "60000", 10);
|
|
42912
43469
|
var DIAGNOSTICS_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_DIAGNOSTICS_MS || String(24 * 60 * 60 * 1e3), 10);
|
|
42913
43470
|
var LONG_POLL_TIMEOUT_MS = parseInt(process.env.GIPITY_RELAY_POLL_TIMEOUT_MS || "35000", 10);
|
|
@@ -43007,7 +43564,7 @@ function lockLogPerms(dir, file) {
|
|
|
43007
43564
|
chmodSync3(dir, 448);
|
|
43008
43565
|
} catch {
|
|
43009
43566
|
}
|
|
43010
|
-
if (!
|
|
43567
|
+
if (!existsSync20(file)) {
|
|
43011
43568
|
try {
|
|
43012
43569
|
closeSync4(openSync4(file, "a", 384));
|
|
43013
43570
|
} catch {
|
|
@@ -43024,7 +43581,7 @@ function log2(level, msg, extra) {
|
|
|
43024
43581
|
const pretty = `${C7.dim(hhmmss())} ${badge(level)} ${C7.bold(msg)}${formatExtra(extra)}`;
|
|
43025
43582
|
process.stderr.write(pretty + "\n");
|
|
43026
43583
|
try {
|
|
43027
|
-
const dir =
|
|
43584
|
+
const dir = join22(homedir15(), ".gipity");
|
|
43028
43585
|
mkdirSync14(dir, { recursive: true });
|
|
43029
43586
|
lockLogPerms(dir, RELAY_LOG_PATH);
|
|
43030
43587
|
const json = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...extra ?? {} });
|
|
@@ -43149,10 +43706,10 @@ async function heartbeatLoop(ctx) {
|
|
|
43149
43706
|
}
|
|
43150
43707
|
await Promise.race([
|
|
43151
43708
|
sleep4(HEARTBEAT_INTERVAL_MS, ctx.abort.signal),
|
|
43152
|
-
new Promise((
|
|
43709
|
+
new Promise((resolve19) => {
|
|
43153
43710
|
heartbeatPoke = () => {
|
|
43154
43711
|
heartbeatPoke = null;
|
|
43155
|
-
|
|
43712
|
+
resolve19();
|
|
43156
43713
|
};
|
|
43157
43714
|
})
|
|
43158
43715
|
]);
|
|
@@ -43196,9 +43753,9 @@ async function dispatchLoop(ctx, opts) {
|
|
|
43196
43753
|
while (inflight.size >= MAX_CONCURRENT_DISPATCHES && !ctx.abort.signal.aborted) {
|
|
43197
43754
|
await Promise.race([
|
|
43198
43755
|
...inflight,
|
|
43199
|
-
new Promise((
|
|
43200
|
-
if (ctx.abort.signal.aborted) return
|
|
43201
|
-
ctx.abort.signal.addEventListener("abort", () =>
|
|
43756
|
+
new Promise((resolve19) => {
|
|
43757
|
+
if (ctx.abort.signal.aborted) return resolve19();
|
|
43758
|
+
ctx.abort.signal.addEventListener("abort", () => resolve19(), { once: true });
|
|
43202
43759
|
})
|
|
43203
43760
|
]);
|
|
43204
43761
|
}
|
|
@@ -43319,8 +43876,8 @@ async function postStreamDelta(convGuid, dispatchGuid, flush) {
|
|
|
43319
43876
|
function toolHint(name, input) {
|
|
43320
43877
|
if (!input || typeof input !== "object") return void 0;
|
|
43321
43878
|
if (name === "Bash" && typeof input.command === "string") return input.command;
|
|
43322
|
-
const
|
|
43323
|
-
return typeof
|
|
43879
|
+
const path5 = input.file_path ?? input.path ?? input.pattern ?? input.query ?? input.url ?? input.description;
|
|
43880
|
+
return typeof path5 === "string" ? path5 : void 0;
|
|
43324
43881
|
}
|
|
43325
43882
|
var PhaseTracker = class {
|
|
43326
43883
|
phase = "starting";
|
|
@@ -43425,7 +43982,7 @@ function isSafeSessionId(s) {
|
|
|
43425
43982
|
function transcriptPathFor(cwd, sessionId) {
|
|
43426
43983
|
if (!isSafeSessionId(sessionId)) return null;
|
|
43427
43984
|
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
43428
|
-
return
|
|
43985
|
+
return join22(homedir15(), ".claude", "projects", slug, `${sessionId}.jsonl`);
|
|
43429
43986
|
}
|
|
43430
43987
|
function formatDuration(ms2) {
|
|
43431
43988
|
const totalSec = ms2 / 1e3;
|
|
@@ -43485,8 +44042,12 @@ async function handleDispatch(claimed) {
|
|
|
43485
44042
|
d = { ...d, kind: "resume", remote_session_id: sid };
|
|
43486
44043
|
}
|
|
43487
44044
|
}
|
|
44045
|
+
const rewriter = new ImageBlockRewriter(
|
|
44046
|
+
d.conversation_guid,
|
|
44047
|
+
(msg, meta) => log2("warn", msg, { id: d.short_guid, ...meta })
|
|
44048
|
+
);
|
|
43488
44049
|
const queue = new IngestQueue(
|
|
43489
|
-
(entries) => postIngest(d.conversation_guid, entries),
|
|
44050
|
+
async (entries) => postIngest(d.conversation_guid, await rewriter.rewrite(entries)),
|
|
43490
44051
|
{ onWarn: (msg, meta) => log2("warn", msg, { id: d.short_guid, ...meta }) }
|
|
43491
44052
|
);
|
|
43492
44053
|
const pushSystem = (content) => {
|
|
@@ -43499,24 +44060,33 @@ async function handleDispatch(claimed) {
|
|
|
43499
44060
|
let bootstrapped;
|
|
43500
44061
|
try {
|
|
43501
44062
|
({ cwd, bootstrapped } = await resolveCwdForProject(d));
|
|
44063
|
+
rewriter.setCwd(cwd);
|
|
43502
44064
|
log2("debug", "resolved project cwd", { id: d.short_guid, project: d.project_slug, cwd, bootstrapped });
|
|
43503
44065
|
} catch (err) {
|
|
43504
44066
|
log2("error", "could not resolve project cwd", { id: d.short_guid, err: err?.message });
|
|
43505
44067
|
await ack(d.short_guid, "error", `Could not materialize project locally: ${err?.message || err}`);
|
|
43506
44068
|
return;
|
|
43507
44069
|
}
|
|
43508
|
-
|
|
43509
|
-
|
|
44070
|
+
const hasAttachments = (d.attachments?.length ?? 0) > 0;
|
|
44071
|
+
if (bootstrapped || hasAttachments) {
|
|
44072
|
+
pushSystem(bootstrapped ? "Syncing project files\u2026" : "Syncing attached files\u2026");
|
|
43510
44073
|
let syncKilled = false;
|
|
44074
|
+
let syncFailed = false;
|
|
43511
44075
|
try {
|
|
43512
44076
|
syncKilled = (await runDispatchSync(d, cwd)).killed;
|
|
43513
44077
|
} catch (err) {
|
|
43514
44078
|
const msg = `project sync ${err?.message || "failed"}`;
|
|
43515
|
-
|
|
43516
|
-
|
|
43517
|
-
|
|
43518
|
-
|
|
43519
|
-
|
|
44079
|
+
if (!bootstrapped) {
|
|
44080
|
+
syncFailed = true;
|
|
44081
|
+
log2("error", "attachment pre-sync failed - continuing", { id: d.short_guid, err: err?.message });
|
|
44082
|
+
pushSystem(`Attached-file sync failed (${err?.message || "failed"}) - Claude will sync on demand.`);
|
|
44083
|
+
} else {
|
|
44084
|
+
log2("error", "project sync failed - aborting dispatch", { id: d.short_guid, err: err?.message });
|
|
44085
|
+
pushSystem(`Claude Code not started - ${msg}`);
|
|
44086
|
+
await flushQueue();
|
|
44087
|
+
await ack(d.short_guid, "error", msg);
|
|
44088
|
+
return;
|
|
44089
|
+
}
|
|
43520
44090
|
}
|
|
43521
44091
|
if (syncKilled) {
|
|
43522
44092
|
log2("info", "dispatch cancelled during project sync", { id: d.short_guid });
|
|
@@ -43525,7 +44095,7 @@ async function handleDispatch(claimed) {
|
|
|
43525
44095
|
await ack(d.short_guid, "cancelled");
|
|
43526
44096
|
return;
|
|
43527
44097
|
}
|
|
43528
|
-
pushSystem("Project files synced.");
|
|
44098
|
+
if (!syncFailed) pushSystem(bootstrapped ? "Project files synced." : "Attached files synced.");
|
|
43529
44099
|
}
|
|
43530
44100
|
if (SESSION_POOL_ENABLED) {
|
|
43531
44101
|
const handled = await tryHandleViaPool(d, cwd, queue, pushSystem, flushQueue);
|
|
@@ -43807,23 +44377,23 @@ async function resolveCwdForProject(d) {
|
|
|
43807
44377
|
throw new Error(`Invalid project slug: ${JSON.stringify(d.project_slug)}`);
|
|
43808
44378
|
}
|
|
43809
44379
|
const root = getProjectsRoot();
|
|
43810
|
-
const
|
|
43811
|
-
const configPath =
|
|
43812
|
-
if (
|
|
44380
|
+
const path5 = join22(root, d.project_slug);
|
|
44381
|
+
const configPath = join22(path5, ".gipity.json");
|
|
44382
|
+
if (existsSync20(configPath)) {
|
|
43813
44383
|
try {
|
|
43814
44384
|
const cfg = JSON.parse(readFileSync21(configPath, "utf-8"));
|
|
43815
|
-
if (cfg.projectGuid === d.project_guid) return { cwd:
|
|
44385
|
+
if (cfg.projectGuid === d.project_guid) return { cwd: path5, bootstrapped: false };
|
|
43816
44386
|
log2("warn", "project dir exists but guid mismatch - using it anyway", {
|
|
43817
|
-
path:
|
|
44387
|
+
path: path5,
|
|
43818
44388
|
expected: d.project_guid,
|
|
43819
44389
|
found: cfg.projectGuid
|
|
43820
44390
|
});
|
|
43821
|
-
return { cwd:
|
|
44391
|
+
return { cwd: path5, bootstrapped: false };
|
|
43822
44392
|
} catch {
|
|
43823
44393
|
}
|
|
43824
44394
|
}
|
|
43825
|
-
log2("info", "bootstrapping new project dir", { slug: d.project_slug, path:
|
|
43826
|
-
mkdirSync14(
|
|
44395
|
+
log2("info", "bootstrapping new project dir", { slug: d.project_slug, path: path5 });
|
|
44396
|
+
mkdirSync14(path5, { recursive: true });
|
|
43827
44397
|
const apiBase2 = getApiBaseOverride() || DEFAULT_API_BASE;
|
|
43828
44398
|
writeFileSync13(configPath, JSON.stringify({
|
|
43829
44399
|
projectGuid: d.project_guid,
|
|
@@ -43836,7 +44406,7 @@ async function resolveCwdForProject(d) {
|
|
|
43836
44406
|
}, null, 2) + "\n");
|
|
43837
44407
|
const origCwd = process.cwd();
|
|
43838
44408
|
try {
|
|
43839
|
-
process.chdir(
|
|
44409
|
+
process.chdir(path5);
|
|
43840
44410
|
setupClaudeHooks();
|
|
43841
44411
|
setupClaudeMd();
|
|
43842
44412
|
setupAgentsMd();
|
|
@@ -43844,7 +44414,7 @@ async function resolveCwdForProject(d) {
|
|
|
43844
44414
|
} finally {
|
|
43845
44415
|
process.chdir(origCwd);
|
|
43846
44416
|
}
|
|
43847
|
-
return { cwd:
|
|
44417
|
+
return { cwd: path5, bootstrapped: true };
|
|
43848
44418
|
}
|
|
43849
44419
|
var running = /* @__PURE__ */ new Map();
|
|
43850
44420
|
var LAST_SESSION_TTL_MS = 10 * 6e4;
|
|
@@ -43905,7 +44475,7 @@ async function killRunningForConv(convGuid) {
|
|
|
43905
44475
|
}
|
|
43906
44476
|
}
|
|
43907
44477
|
const graceTimers = [];
|
|
43908
|
-
const escalate = new Promise((
|
|
44478
|
+
const escalate = new Promise((resolve19) => {
|
|
43909
44479
|
const t = setTimeout(() => {
|
|
43910
44480
|
for (const e of matches) {
|
|
43911
44481
|
log2("warn", "previous dispatch ignored SIGTERM - escalating to SIGKILL", { conv: convGuid });
|
|
@@ -43914,7 +44484,7 @@ async function killRunningForConv(convGuid) {
|
|
|
43914
44484
|
} catch {
|
|
43915
44485
|
}
|
|
43916
44486
|
}
|
|
43917
|
-
|
|
44487
|
+
resolve19();
|
|
43918
44488
|
}, KILL_GRACE_MS);
|
|
43919
44489
|
graceTimers.push(t);
|
|
43920
44490
|
});
|
|
@@ -43925,7 +44495,7 @@ async function killRunningForConv(convGuid) {
|
|
|
43925
44495
|
}
|
|
43926
44496
|
async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
43927
44497
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
43928
|
-
return new Promise((
|
|
44498
|
+
return new Promise((resolve19, reject) => {
|
|
43929
44499
|
const child = spawnCommand(cmd, ["sync", "--json"], {
|
|
43930
44500
|
cwd,
|
|
43931
44501
|
env: childEnv(),
|
|
@@ -43949,7 +44519,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
43949
44519
|
} catch {
|
|
43950
44520
|
}
|
|
43951
44521
|
try {
|
|
43952
|
-
unlinkSync6(
|
|
44522
|
+
unlinkSync6(join22(cwd, ".gipity", "sync.lock"));
|
|
43953
44523
|
} catch {
|
|
43954
44524
|
}
|
|
43955
44525
|
finish(() => reject(new Error(`timed out after ${Math.round(timeoutMs / 1e3)}s`)));
|
|
@@ -43965,7 +44535,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
43965
44535
|
child.on("exit", (code) => finish(() => {
|
|
43966
44536
|
if (code === 0) {
|
|
43967
44537
|
log2("info", "sync done", { cwd, stdoutLen });
|
|
43968
|
-
|
|
44538
|
+
resolve19();
|
|
43969
44539
|
} else {
|
|
43970
44540
|
reject(new Error(`gipity sync exited ${code}${stderrBuf ? `: ${stderrBuf.trim().slice(0, 300)}` : ""}`));
|
|
43971
44541
|
}
|
|
@@ -43976,7 +44546,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
43976
44546
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
43977
44547
|
const fullArgs = [...args, "--output-format", "stream-json", "--verbose", "--include-partial-messages"];
|
|
43978
44548
|
const env = childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid, GIPITY_CAPTURE: "off" });
|
|
43979
|
-
return new Promise((
|
|
44549
|
+
return new Promise((resolve19, reject) => {
|
|
43980
44550
|
const child = spawnCommand(cmd, fullArgs, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
43981
44551
|
let resolveExited = () => {
|
|
43982
44552
|
};
|
|
@@ -43984,10 +44554,17 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
43984
44554
|
resolveExited = r;
|
|
43985
44555
|
});
|
|
43986
44556
|
running.set(d.short_guid, { child, convGuid: d.conversation_guid, exited });
|
|
43987
|
-
const q = queue ??
|
|
43988
|
-
|
|
43989
|
-
|
|
43990
|
-
|
|
44557
|
+
const q = queue ?? (() => {
|
|
44558
|
+
const rw = new ImageBlockRewriter(
|
|
44559
|
+
d.conversation_guid,
|
|
44560
|
+
(msg, meta2) => log2("warn", msg, { id: d.short_guid, ...meta2 })
|
|
44561
|
+
);
|
|
44562
|
+
rw.setCwd(cwd);
|
|
44563
|
+
return new IngestQueue(
|
|
44564
|
+
async (entries) => postIngest(d.conversation_guid, await rw.rewrite(entries)),
|
|
44565
|
+
{ onWarn: (msg, meta2) => log2("warn", msg, { id: d.short_guid, ...meta2 }) }
|
|
44566
|
+
);
|
|
44567
|
+
})();
|
|
43991
44568
|
const ownQueue = !queue;
|
|
43992
44569
|
const dispatchStartedAt = Date.now();
|
|
43993
44570
|
let stdoutBytesTotal = 0;
|
|
@@ -44147,7 +44724,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
44147
44724
|
void postProgress(d.conversation_guid, buildProgressPayload(false));
|
|
44148
44725
|
if (ownQueue) await q.close();
|
|
44149
44726
|
cleanup();
|
|
44150
|
-
|
|
44727
|
+
resolve19({
|
|
44151
44728
|
exitCode: code ?? 1,
|
|
44152
44729
|
killed,
|
|
44153
44730
|
runtimeLimit,
|
|
@@ -44161,10 +44738,10 @@ async function runDispatchSync(d, cwd) {
|
|
|
44161
44738
|
let killed = false;
|
|
44162
44739
|
try {
|
|
44163
44740
|
await spawnSync2(cwd, PROJECT_SYNC_TIMEOUT_MS, (child) => {
|
|
44164
|
-
const exited = new Promise((
|
|
44741
|
+
const exited = new Promise((resolve19) => {
|
|
44165
44742
|
child.once("exit", (_code, signal) => {
|
|
44166
44743
|
if (signal === "SIGTERM") killed = true;
|
|
44167
|
-
|
|
44744
|
+
resolve19();
|
|
44168
44745
|
});
|
|
44169
44746
|
});
|
|
44170
44747
|
running.set(d.short_guid, { child, convGuid: d.conversation_guid, exited });
|
|
@@ -44195,18 +44772,18 @@ function killDispatch(shortGuid) {
|
|
|
44195
44772
|
return false;
|
|
44196
44773
|
}
|
|
44197
44774
|
function sleep4(ms2, signal) {
|
|
44198
|
-
return new Promise((
|
|
44199
|
-
if (signal?.aborted) return
|
|
44775
|
+
return new Promise((resolve19) => {
|
|
44776
|
+
if (signal?.aborted) return resolve19();
|
|
44200
44777
|
let onAbort = null;
|
|
44201
44778
|
const t = setTimeout(() => {
|
|
44202
44779
|
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
44203
|
-
|
|
44780
|
+
resolve19();
|
|
44204
44781
|
}, ms2);
|
|
44205
44782
|
if (signal) {
|
|
44206
44783
|
onAbort = () => {
|
|
44207
44784
|
clearTimeout(t);
|
|
44208
44785
|
signal.removeEventListener("abort", onAbort);
|
|
44209
|
-
|
|
44786
|
+
resolve19();
|
|
44210
44787
|
};
|
|
44211
44788
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
44212
44789
|
}
|
|
@@ -44376,7 +44953,7 @@ relayCommand.command("run").description("Run the background service").option("-v
|
|
|
44376
44953
|
});
|
|
44377
44954
|
relayCommand.command("stop").description("Stop the background service").option("--force", "Force-stop if it doesn't exit cleanly within 5s").action(async (opts) => {
|
|
44378
44955
|
const pidPath = getDaemonPidPath();
|
|
44379
|
-
if (!
|
|
44956
|
+
if (!existsSync21(pidPath)) {
|
|
44380
44957
|
console.log(muted("Background service isn't running."));
|
|
44381
44958
|
return;
|
|
44382
44959
|
}
|
|
@@ -44504,7 +45081,7 @@ relayCommand.command("revoke").description("Revoke and forget this device").acti
|
|
|
44504
45081
|
} catch {
|
|
44505
45082
|
}
|
|
44506
45083
|
const pidPath = getDaemonPidPath();
|
|
44507
|
-
if (
|
|
45084
|
+
if (existsSync21(pidPath)) {
|
|
44508
45085
|
const pid = parseInt(readFileSync22(pidPath, "utf-8").trim(), 10);
|
|
44509
45086
|
if (pid && !isNaN(pid)) {
|
|
44510
45087
|
try {
|
|
@@ -44517,14 +45094,14 @@ relayCommand.command("revoke").description("Revoke and forget this device").acti
|
|
|
44517
45094
|
console.log(muted(autostartRemoved ? "Auto-start removed and the background service was signalled to stop." : "The background service was signalled to stop."));
|
|
44518
45095
|
});
|
|
44519
45096
|
relayCommand.command("log").description("Tail the service log").option("-n, --lines <n>", "How many lines to print (default 100)", "100").option("-f, --follow", "Follow the log like `tail -f`").action((opts) => {
|
|
44520
|
-
const
|
|
44521
|
-
if (!
|
|
45097
|
+
const path5 = RELAY_LOG_PATH;
|
|
45098
|
+
if (!existsSync21(path5)) {
|
|
44522
45099
|
console.log(muted("No log file yet. Start the service with `gipity relay run` (or install it)."));
|
|
44523
45100
|
return;
|
|
44524
45101
|
}
|
|
44525
45102
|
const lines = parseInt(opts.lines, 10) || 100;
|
|
44526
45103
|
try {
|
|
44527
|
-
const all = readFileSync22(
|
|
45104
|
+
const all = readFileSync22(path5, "utf-8").split("\n");
|
|
44528
45105
|
const tail = all.slice(-lines - 1).join("\n");
|
|
44529
45106
|
process.stdout.write(tail);
|
|
44530
45107
|
} catch (err) {
|
|
@@ -44537,7 +45114,7 @@ relayCommand.command("log").description("Tail the service log").option("-n, --li
|
|
|
44537
45114
|
console.error(error("--follow is not supported on this platform yet."));
|
|
44538
45115
|
process.exit(1);
|
|
44539
45116
|
}
|
|
44540
|
-
const child = spawn2(tailCmd, ["-f", "-n", "0",
|
|
45117
|
+
const child = spawn2(tailCmd, ["-f", "-n", "0", path5], { stdio: "inherit" });
|
|
44541
45118
|
child.on("error", (err) => {
|
|
44542
45119
|
console.error(error(err.code === "ENOENT" ? "`tail` not found - can't follow the log on this system." : `Failed to follow log: ${err.message}`));
|
|
44543
45120
|
process.exit(1);
|
|
@@ -44597,12 +45174,12 @@ init_api();
|
|
|
44597
45174
|
init_auth();
|
|
44598
45175
|
init_utils();
|
|
44599
45176
|
init_colors();
|
|
44600
|
-
import { existsSync as
|
|
45177
|
+
import { existsSync as existsSync22, rmSync as rmSync2, unlinkSync as unlinkSync8, readFileSync as readFileSync23, writeFileSync as writeFileSync14 } from "fs";
|
|
44601
45178
|
import { homedir as homedir16, platform as osPlatform4 } from "os";
|
|
44602
|
-
import { join as
|
|
45179
|
+
import { join as join23, resolve as resolve17 } from "path";
|
|
44603
45180
|
function removeGipityPluginConfig() {
|
|
44604
|
-
const settingsPath =
|
|
44605
|
-
if (!
|
|
45181
|
+
const settingsPath = join23(homedir16(), ".claude", "settings.json");
|
|
45182
|
+
if (!existsSync22(settingsPath)) return false;
|
|
44606
45183
|
let settings;
|
|
44607
45184
|
try {
|
|
44608
45185
|
settings = JSON.parse(readFileSync23(settingsPath, "utf-8"));
|
|
@@ -44628,8 +45205,8 @@ function removeInstallerPathLines() {
|
|
|
44628
45205
|
const marker = "# Added by the Gipity installer";
|
|
44629
45206
|
const touched = [];
|
|
44630
45207
|
for (const name of [".bashrc", ".zshrc", ".profile"]) {
|
|
44631
|
-
const rc2 =
|
|
44632
|
-
if (!
|
|
45208
|
+
const rc2 = join23(homedir16(), name);
|
|
45209
|
+
if (!existsSync22(rc2)) continue;
|
|
44633
45210
|
let text;
|
|
44634
45211
|
try {
|
|
44635
45212
|
text = readFileSync23(rc2, "utf-8");
|
|
@@ -44654,7 +45231,7 @@ function tildify2(p) {
|
|
|
44654
45231
|
return p === home || p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
|
|
44655
45232
|
}
|
|
44656
45233
|
function resolveCliPath2() {
|
|
44657
|
-
return
|
|
45234
|
+
return resolve17(process.argv[1] ?? "gipity");
|
|
44658
45235
|
}
|
|
44659
45236
|
async function stopDaemon() {
|
|
44660
45237
|
if (!isDaemonRunning()) return;
|
|
@@ -44688,7 +45265,7 @@ function removeServiceUnit() {
|
|
|
44688
45265
|
const r = spawnSyncCommand(argv2[0], argv2.slice(1), { stdio: "ignore" });
|
|
44689
45266
|
if (r.status !== 0) allOk = false;
|
|
44690
45267
|
}
|
|
44691
|
-
if (
|
|
45268
|
+
if (existsSync22(plan2.path)) {
|
|
44692
45269
|
try {
|
|
44693
45270
|
unlinkSync8(plan2.path);
|
|
44694
45271
|
} catch {
|
|
@@ -44711,9 +45288,9 @@ async function revokeDeviceBestEffort() {
|
|
|
44711
45288
|
}
|
|
44712
45289
|
var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").option("--yes", "Skip confirmation prompts").action(async (opts) => {
|
|
44713
45290
|
const autoYes = opts.yes || getAutoConfirm();
|
|
44714
|
-
const gipityDir =
|
|
44715
|
-
const launcherBin =
|
|
44716
|
-
const installedViaLauncher =
|
|
45291
|
+
const gipityDir = join23(homedir16(), ".gipity");
|
|
45292
|
+
const launcherBin = join23(gipityDir, "launcher", "bin", "gipity");
|
|
45293
|
+
const installedViaLauncher = existsSync22(launcherBin);
|
|
44717
45294
|
console.log(`${bold("Gipity uninstall")} - this will:`);
|
|
44718
45295
|
console.log(`\u2022 Stop the running relay daemon (if any)`);
|
|
44719
45296
|
console.log(`\u2022 Remove the OS autostart service (launchd / systemd / Task Scheduler)`);
|
|
@@ -44748,7 +45325,7 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
|
|
|
44748
45325
|
} else {
|
|
44749
45326
|
console.log(`${muted("No Gipity entries in Claude Code settings.")}`);
|
|
44750
45327
|
}
|
|
44751
|
-
if (
|
|
45328
|
+
if (existsSync22(gipityDir)) {
|
|
44752
45329
|
try {
|
|
44753
45330
|
rmSync2(gipityDir, { recursive: true, force: true });
|
|
44754
45331
|
console.log(`${success(`Removed ${gipityDir}/`)}`);
|
|
@@ -45275,7 +45852,7 @@ function collectRealFlags(argv2, program3) {
|
|
|
45275
45852
|
|
|
45276
45853
|
// src/trace.ts
|
|
45277
45854
|
import { openSync as openSync5, writeSync, mkdirSync as mkdirSync15 } from "fs";
|
|
45278
|
-
import { join as
|
|
45855
|
+
import { join as join24 } from "path";
|
|
45279
45856
|
import { homedir as homedir17 } from "os";
|
|
45280
45857
|
var TRACE_KEY = /* @__PURE__ */ Symbol.for("gipity.traceOutput");
|
|
45281
45858
|
function installOutputTrace(label2) {
|
|
@@ -45287,10 +45864,10 @@ function installOutputTrace(label2) {
|
|
|
45287
45864
|
existing.emit({ event: "reenter", label: label2 });
|
|
45288
45865
|
return;
|
|
45289
45866
|
}
|
|
45290
|
-
const dir =
|
|
45867
|
+
const dir = join24(process.env.GIPITY_DIR || join24(homedir17(), ".gipity"), "trace");
|
|
45291
45868
|
mkdirSync15(dir, { recursive: true });
|
|
45292
45869
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
|
|
45293
|
-
const fd2 = openSync5(
|
|
45870
|
+
const fd2 = openSync5(join24(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
|
|
45294
45871
|
const emit = (rec) => {
|
|
45295
45872
|
try {
|
|
45296
45873
|
writeSync(fd2, JSON.stringify({ t: (/* @__PURE__ */ new Date()).toISOString(), ...rec }) + "\n");
|
|
@@ -45326,11 +45903,11 @@ function installOutputTrace(label2) {
|
|
|
45326
45903
|
// src/index.ts
|
|
45327
45904
|
installOutputTrace("index");
|
|
45328
45905
|
var __dirname = dirname13(fileURLToPath3(import.meta.url));
|
|
45329
|
-
var pkg = JSON.parse(readFileSync25(
|
|
45906
|
+
var pkg = JSON.parse(readFileSync25(resolve18(__dirname, "../package.json"), "utf-8"));
|
|
45330
45907
|
function versionLabel() {
|
|
45331
45908
|
const base = `v${pkg.version}`;
|
|
45332
45909
|
try {
|
|
45333
|
-
const info2 = JSON.parse(readFileSync25(
|
|
45910
|
+
const info2 = JSON.parse(readFileSync25(resolve18(__dirname, "build-info.json"), "utf-8"));
|
|
45334
45911
|
if (info2?.sha) return `${base} (dev ${info2.sha}${info2.dirty ? ", modified" : ""})`;
|
|
45335
45912
|
} catch {
|
|
45336
45913
|
}
|