gipity 1.1.3 → 1.1.5
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/adopt-cwd.js +1 -1
- package/dist/banner.js +3 -5
- package/dist/catalog.js +1 -3
- package/dist/commands/add.js +1 -1
- package/dist/commands/build.js +111 -40
- package/dist/commands/chat.js +1 -1
- package/dist/commands/deploy.js +7 -3
- package/dist/commands/file.js +18 -0
- package/dist/commands/fn.js +6 -6
- package/dist/commands/generate.js +80 -16
- package/dist/commands/gmail.js +1 -1
- package/dist/commands/job.js +132 -10
- package/dist/commands/load.js +2 -2
- package/dist/commands/login.js +7 -2
- package/dist/commands/page-eval.js +82 -13
- package/dist/commands/page-fetch.js +14 -10
- package/dist/commands/page-inspect.js +28 -3
- package/dist/commands/page-screenshot.js +36 -7
- package/dist/commands/page-test.js +1 -1
- package/dist/commands/records.js +14 -1
- package/dist/commands/remove.js +1 -1
- package/dist/commands/sandbox.js +182 -10
- package/dist/commands/save.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/service.js +7 -2
- package/dist/commands/setup.js +4 -6
- package/dist/commands/status.js +41 -12
- package/dist/commands/storage.js +2 -2
- package/dist/commands/sync.js +15 -0
- package/dist/commands/test.js +33 -1
- package/dist/commands/update.js +4 -0
- package/dist/commands/upload.js +69 -121
- package/dist/commands/workflow.js +1 -1
- package/dist/helpers/body.js +117 -0
- package/dist/helpers/duration.js +34 -0
- package/dist/helpers/index.js +2 -0
- package/dist/index.js +1173 -620
- package/dist/knowledge.js +7 -7
- package/dist/login-flow.js +44 -2
- package/dist/relay/onboarding.js +26 -44
- package/dist/sync.js +6 -6
- package/dist/updater/bootstrap.js +27 -16
- package/dist/updater/check.js +86 -10
- package/dist/updater/install.js +82 -0
- package/dist/updater/shim.js +90 -27
- package/dist/utils.js +60 -3
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -3929,6 +3929,41 @@ var init_colors = __esm({
|
|
|
3929
3929
|
|
|
3930
3930
|
// src/utils.ts
|
|
3931
3931
|
import { createInterface } from "readline";
|
|
3932
|
+
import { readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
3933
|
+
import { basename, join } from "node:path";
|
|
3934
|
+
function isWsl() {
|
|
3935
|
+
if (process.platform !== "linux") return false;
|
|
3936
|
+
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
3937
|
+
try {
|
|
3938
|
+
return /microsoft/i.test(readFileSync("/proc/version", "utf-8"));
|
|
3939
|
+
} catch {
|
|
3940
|
+
return false;
|
|
3941
|
+
}
|
|
3942
|
+
}
|
|
3943
|
+
function findWindowsTwinProject(projectRoot, usersBase = "/mnt/c/Users") {
|
|
3944
|
+
try {
|
|
3945
|
+
const name = basename(projectRoot);
|
|
3946
|
+
if (!name) return null;
|
|
3947
|
+
const realRoot = realpathSync(projectRoot);
|
|
3948
|
+
for (const user of readdirSync(usersBase)) {
|
|
3949
|
+
if (WINDOWS_PSEUDO_USERS.has(user)) continue;
|
|
3950
|
+
const candidate = join(usersBase, user, "GipityProjects", name);
|
|
3951
|
+
try {
|
|
3952
|
+
if (!statSync(candidate).isDirectory()) continue;
|
|
3953
|
+
if (realpathSync(candidate) === realRoot) continue;
|
|
3954
|
+
return candidate;
|
|
3955
|
+
} catch {
|
|
3956
|
+
}
|
|
3957
|
+
}
|
|
3958
|
+
} catch {
|
|
3959
|
+
}
|
|
3960
|
+
return null;
|
|
3961
|
+
}
|
|
3962
|
+
function wslPathToWindows(p) {
|
|
3963
|
+
const m = p.match(/^\/mnt\/([a-z])\/(.*)$/);
|
|
3964
|
+
if (!m) return p;
|
|
3965
|
+
return `${m[1].toUpperCase()}:\\${m[2].replace(/\//g, "\\")}`;
|
|
3966
|
+
}
|
|
3932
3967
|
function decodeJwtExp(token) {
|
|
3933
3968
|
try {
|
|
3934
3969
|
const parts = token.split(".");
|
|
@@ -3944,10 +3979,10 @@ function prompt(question) {
|
|
|
3944
3979
|
return Promise.reject(new Error(`prompt() called without a TTY: ${question.trim()}`));
|
|
3945
3980
|
}
|
|
3946
3981
|
const rl2 = createInterface({ input: process.stdin, output: process.stdout });
|
|
3947
|
-
return new Promise((
|
|
3982
|
+
return new Promise((resolve19) => {
|
|
3948
3983
|
rl2.question(question, (answer) => {
|
|
3949
3984
|
rl2.close();
|
|
3950
|
-
|
|
3985
|
+
resolve19(answer.trim());
|
|
3951
3986
|
});
|
|
3952
3987
|
});
|
|
3953
3988
|
}
|
|
@@ -3972,12 +4007,12 @@ async function confirm(question, opts = {}) {
|
|
|
3972
4007
|
return false;
|
|
3973
4008
|
}
|
|
3974
4009
|
const hint = defaultYes ? dim("[Y/n]") : dim("[y/N]");
|
|
3975
|
-
process.stdout.write(`${question} ${hint} `);
|
|
4010
|
+
process.stdout.write(`${question} ${hint}: `);
|
|
3976
4011
|
const { stdin } = process;
|
|
3977
4012
|
const wasRaw = stdin.isRaw ?? false;
|
|
3978
4013
|
stdin.setRawMode(true);
|
|
3979
4014
|
stdin.resume();
|
|
3980
|
-
return new Promise((
|
|
4015
|
+
return new Promise((resolve19) => {
|
|
3981
4016
|
stdin.once("data", (key) => {
|
|
3982
4017
|
stdin.setRawMode(wasRaw);
|
|
3983
4018
|
stdin.pause();
|
|
@@ -3992,12 +4027,12 @@ async function confirm(question, opts = {}) {
|
|
|
3992
4027
|
else if (k7 === "n") answer = false;
|
|
3993
4028
|
else answer = defaultYes;
|
|
3994
4029
|
console.log(answer ? "y" : "n");
|
|
3995
|
-
|
|
4030
|
+
resolve19(answer);
|
|
3996
4031
|
});
|
|
3997
4032
|
});
|
|
3998
4033
|
}
|
|
3999
4034
|
function pickOne(label2, max, defaultIdx = 1) {
|
|
4000
|
-
return new Promise((
|
|
4035
|
+
return new Promise((resolve19) => {
|
|
4001
4036
|
process.stdout.write(` ${bold(label2)} (1-${max}) [${bold(String(defaultIdx))}]: `);
|
|
4002
4037
|
const { stdin } = process;
|
|
4003
4038
|
const wasRaw = stdin.isRaw ?? false;
|
|
@@ -4013,15 +4048,15 @@ function pickOne(label2, max, defaultIdx = 1) {
|
|
|
4013
4048
|
}
|
|
4014
4049
|
if (ch === "\r" || ch === "\n") {
|
|
4015
4050
|
console.log(String(defaultIdx));
|
|
4016
|
-
return
|
|
4051
|
+
return resolve19(defaultIdx);
|
|
4017
4052
|
}
|
|
4018
4053
|
const n = parseInt(ch, 10);
|
|
4019
4054
|
if (n >= 1 && n <= max) {
|
|
4020
4055
|
console.log(String(n));
|
|
4021
|
-
return
|
|
4056
|
+
return resolve19(n);
|
|
4022
4057
|
}
|
|
4023
4058
|
console.log(String(defaultIdx));
|
|
4024
|
-
|
|
4059
|
+
resolve19(defaultIdx);
|
|
4025
4060
|
});
|
|
4026
4061
|
});
|
|
4027
4062
|
}
|
|
@@ -4030,11 +4065,12 @@ function formatSize(bytes) {
|
|
|
4030
4065
|
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
4031
4066
|
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4032
4067
|
}
|
|
4033
|
-
var _autoConfirm;
|
|
4068
|
+
var WINDOWS_PSEUDO_USERS, _autoConfirm;
|
|
4034
4069
|
var init_utils = __esm({
|
|
4035
4070
|
"src/utils.ts"() {
|
|
4036
4071
|
"use strict";
|
|
4037
4072
|
init_colors();
|
|
4073
|
+
WINDOWS_PSEUDO_USERS = /* @__PURE__ */ new Set(["All Users", "Default", "Default User", "Public", "desktop.ini"]);
|
|
4038
4074
|
_autoConfirm = false;
|
|
4039
4075
|
}
|
|
4040
4076
|
});
|
|
@@ -4052,14 +4088,14 @@ __export(auth_exports, {
|
|
|
4052
4088
|
saveAuth: () => saveAuth,
|
|
4053
4089
|
sessionExpired: () => sessionExpired
|
|
4054
4090
|
});
|
|
4055
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync, openSync, closeSync } from "fs";
|
|
4056
|
-
import { join } from "path";
|
|
4091
|
+
import { readFileSync as readFileSync2, writeFileSync, mkdirSync, existsSync, unlinkSync, chmodSync, openSync, closeSync } from "fs";
|
|
4092
|
+
import { join as join2 } from "path";
|
|
4057
4093
|
import { homedir } from "os";
|
|
4058
4094
|
function getAuth() {
|
|
4059
4095
|
if (cached) return cached;
|
|
4060
4096
|
if (!existsSync(AUTH_FILE)) return null;
|
|
4061
4097
|
try {
|
|
4062
|
-
cached = JSON.parse(
|
|
4098
|
+
cached = JSON.parse(readFileSync2(AUTH_FILE, "utf-8"));
|
|
4063
4099
|
return cached;
|
|
4064
4100
|
} catch {
|
|
4065
4101
|
return null;
|
|
@@ -4068,7 +4104,7 @@ function getAuth() {
|
|
|
4068
4104
|
function readAuthFresh() {
|
|
4069
4105
|
if (!existsSync(AUTH_FILE)) return null;
|
|
4070
4106
|
try {
|
|
4071
|
-
return JSON.parse(
|
|
4107
|
+
return JSON.parse(readFileSync2(AUTH_FILE, "utf-8"));
|
|
4072
4108
|
} catch {
|
|
4073
4109
|
return null;
|
|
4074
4110
|
}
|
|
@@ -4119,7 +4155,7 @@ async function acquireRefreshLock() {
|
|
|
4119
4155
|
};
|
|
4120
4156
|
} catch {
|
|
4121
4157
|
try {
|
|
4122
|
-
const pid = parseInt(
|
|
4158
|
+
const pid = parseInt(readFileSync2(AUTH_LOCK_FILE, "utf-8").trim(), 10);
|
|
4123
4159
|
if (pid && !isNaN(pid)) {
|
|
4124
4160
|
try {
|
|
4125
4161
|
process.kill(pid, 0);
|
|
@@ -4209,9 +4245,9 @@ var init_auth = __esm({
|
|
|
4209
4245
|
"src/auth.ts"() {
|
|
4210
4246
|
"use strict";
|
|
4211
4247
|
init_utils();
|
|
4212
|
-
AUTH_DIR = process.env.GIPITY_DIR ||
|
|
4213
|
-
AUTH_FILE =
|
|
4214
|
-
AUTH_LOCK_FILE =
|
|
4248
|
+
AUTH_DIR = process.env.GIPITY_DIR || join2(homedir(), ".gipity");
|
|
4249
|
+
AUTH_FILE = join2(AUTH_DIR, "auth.json");
|
|
4250
|
+
AUTH_LOCK_FILE = join2(AUTH_DIR, "auth.lock");
|
|
4215
4251
|
cached = null;
|
|
4216
4252
|
delay = (ms2) => new Promise((r) => setTimeout(r, ms2));
|
|
4217
4253
|
LOCK_WAIT_MS = 1e4;
|
|
@@ -5370,8 +5406,8 @@ var require_streamx = __commonJS({
|
|
|
5370
5406
|
return this;
|
|
5371
5407
|
},
|
|
5372
5408
|
next() {
|
|
5373
|
-
return new Promise(function(
|
|
5374
|
-
promiseResolve =
|
|
5409
|
+
return new Promise(function(resolve19, reject) {
|
|
5410
|
+
promiseResolve = resolve19;
|
|
5375
5411
|
promiseReject = reject;
|
|
5376
5412
|
const data = stream.read();
|
|
5377
5413
|
if (data !== null) ondata(data);
|
|
@@ -5401,11 +5437,11 @@ var require_streamx = __commonJS({
|
|
|
5401
5437
|
}
|
|
5402
5438
|
function destroy(err) {
|
|
5403
5439
|
stream.destroy(err);
|
|
5404
|
-
return new Promise((
|
|
5405
|
-
if (stream._duplexState & DESTROYED) return
|
|
5440
|
+
return new Promise((resolve19, reject) => {
|
|
5441
|
+
if (stream._duplexState & DESTROYED) return resolve19({ value: void 0, done: true });
|
|
5406
5442
|
stream.once("close", function() {
|
|
5407
5443
|
if (err) reject(err);
|
|
5408
|
-
else
|
|
5444
|
+
else resolve19({ value: void 0, done: true });
|
|
5409
5445
|
});
|
|
5410
5446
|
});
|
|
5411
5447
|
}
|
|
@@ -5449,8 +5485,8 @@ var require_streamx = __commonJS({
|
|
|
5449
5485
|
const writes = pending + (ws2._duplexState & WRITE_WRITING ? 1 : 0);
|
|
5450
5486
|
if (writes === 0) return Promise.resolve(true);
|
|
5451
5487
|
if (state.drains === null) state.drains = [];
|
|
5452
|
-
return new Promise((
|
|
5453
|
-
state.drains.push({ writes, resolve:
|
|
5488
|
+
return new Promise((resolve19) => {
|
|
5489
|
+
state.drains.push({ writes, resolve: resolve19 });
|
|
5454
5490
|
});
|
|
5455
5491
|
}
|
|
5456
5492
|
write(data) {
|
|
@@ -5555,10 +5591,10 @@ var require_streamx = __commonJS({
|
|
|
5555
5591
|
cb2(null);
|
|
5556
5592
|
}
|
|
5557
5593
|
function pipelinePromise(...streams) {
|
|
5558
|
-
return new Promise((
|
|
5594
|
+
return new Promise((resolve19, reject) => {
|
|
5559
5595
|
return pipeline(...streams, (err) => {
|
|
5560
5596
|
if (err) return reject(err);
|
|
5561
|
-
|
|
5597
|
+
resolve19();
|
|
5562
5598
|
});
|
|
5563
5599
|
});
|
|
5564
5600
|
}
|
|
@@ -6213,16 +6249,16 @@ var require_extract = __commonJS({
|
|
|
6213
6249
|
entryCallback = null;
|
|
6214
6250
|
cb2(err);
|
|
6215
6251
|
}
|
|
6216
|
-
function onnext(
|
|
6252
|
+
function onnext(resolve19, reject) {
|
|
6217
6253
|
if (error2) {
|
|
6218
6254
|
return reject(error2);
|
|
6219
6255
|
}
|
|
6220
6256
|
if (entryStream) {
|
|
6221
|
-
|
|
6257
|
+
resolve19({ value: entryStream, done: false });
|
|
6222
6258
|
entryStream = null;
|
|
6223
6259
|
return;
|
|
6224
6260
|
}
|
|
6225
|
-
promiseResolve =
|
|
6261
|
+
promiseResolve = resolve19;
|
|
6226
6262
|
promiseReject = reject;
|
|
6227
6263
|
consumeCallback(null);
|
|
6228
6264
|
if (extract3._finished && promiseResolve) {
|
|
@@ -6250,11 +6286,11 @@ var require_extract = __commonJS({
|
|
|
6250
6286
|
function destroy(err) {
|
|
6251
6287
|
extract3.destroy(err);
|
|
6252
6288
|
consumeCallback(err);
|
|
6253
|
-
return new Promise((
|
|
6254
|
-
if (extract3.destroyed) return
|
|
6289
|
+
return new Promise((resolve19, reject) => {
|
|
6290
|
+
if (extract3.destroyed) return resolve19({ value: void 0, done: true });
|
|
6255
6291
|
extract3.once("close", function() {
|
|
6256
6292
|
if (err) reject(err);
|
|
6257
|
-
else
|
|
6293
|
+
else resolve19({ value: void 0, done: true });
|
|
6258
6294
|
});
|
|
6259
6295
|
});
|
|
6260
6296
|
}
|
|
@@ -6535,13 +6571,13 @@ var require_tar_stream = __commonJS({
|
|
|
6535
6571
|
});
|
|
6536
6572
|
|
|
6537
6573
|
// src/client-context.ts
|
|
6538
|
-
import { readFileSync as
|
|
6574
|
+
import { readFileSync as readFileSync3 } from "fs";
|
|
6539
6575
|
import { resolve, dirname } from "path";
|
|
6540
6576
|
import { fileURLToPath } from "url";
|
|
6541
6577
|
function cliVersion() {
|
|
6542
6578
|
try {
|
|
6543
6579
|
const dir = dirname(fileURLToPath(import.meta.url));
|
|
6544
|
-
const pkg2 = JSON.parse(
|
|
6580
|
+
const pkg2 = JSON.parse(readFileSync3(resolve(dir, "../package.json"), "utf-8"));
|
|
6545
6581
|
return typeof pkg2.version === "string" ? pkg2.version : "unknown";
|
|
6546
6582
|
} catch {
|
|
6547
6583
|
return "unknown";
|
|
@@ -6707,7 +6743,7 @@ async function postForTarEntries(path5, body, retried = false) {
|
|
|
6707
6743
|
if (!res.body) throw new ApiError(500, "EMPTY_RESPONSE", "Server returned no body");
|
|
6708
6744
|
const extract3 = tar.extract();
|
|
6709
6745
|
const entries = [];
|
|
6710
|
-
const done = new Promise((
|
|
6746
|
+
const done = new Promise((resolve19, reject) => {
|
|
6711
6747
|
extract3.on("entry", (header, stream, next) => {
|
|
6712
6748
|
const chunks = [];
|
|
6713
6749
|
stream.on("data", (c) => chunks.push(c));
|
|
@@ -6718,7 +6754,7 @@ async function postForTarEntries(path5, body, retried = false) {
|
|
|
6718
6754
|
stream.on("error", reject);
|
|
6719
6755
|
stream.resume();
|
|
6720
6756
|
});
|
|
6721
|
-
extract3.on("finish", () =>
|
|
6757
|
+
extract3.on("finish", () => resolve19());
|
|
6722
6758
|
extract3.on("error", reject);
|
|
6723
6759
|
});
|
|
6724
6760
|
Readable.fromWeb(res.body).pipe(extract3);
|
|
@@ -6928,7 +6964,7 @@ __export(config_exports, {
|
|
|
6928
6964
|
setApiBaseOverride: () => setApiBaseOverride,
|
|
6929
6965
|
shouldIgnore: () => shouldIgnore
|
|
6930
6966
|
});
|
|
6931
|
-
import { readFileSync as
|
|
6967
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
6932
6968
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
6933
6969
|
function setApiBaseOverride(url) {
|
|
6934
6970
|
apiBaseOverride = url;
|
|
@@ -6986,7 +7022,7 @@ function getConfig() {
|
|
|
6986
7022
|
const path5 = getConfigPath();
|
|
6987
7023
|
if (!path5) return null;
|
|
6988
7024
|
try {
|
|
6989
|
-
cached3 = JSON.parse(
|
|
7025
|
+
cached3 = JSON.parse(readFileSync4(path5, "utf-8"));
|
|
6990
7026
|
return cached3;
|
|
6991
7027
|
} catch {
|
|
6992
7028
|
return null;
|
|
@@ -31863,9 +31899,9 @@ var {
|
|
|
31863
31899
|
// src/index.ts
|
|
31864
31900
|
init_config();
|
|
31865
31901
|
init_utils();
|
|
31866
|
-
import { readFileSync as
|
|
31902
|
+
import { readFileSync as readFileSync28 } from "fs";
|
|
31867
31903
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31868
|
-
import { dirname as
|
|
31904
|
+
import { dirname as dirname14, resolve as resolve18 } from "path";
|
|
31869
31905
|
|
|
31870
31906
|
// src/helpers/output.ts
|
|
31871
31907
|
var frameOpen = false;
|
|
@@ -31970,12 +32006,10 @@ Kits are reusable building blocks added to an existing app, not whole templates
|
|
|
31970
32006
|
- \`gipity add chatbot\` - Drop-in chatbot - configurable persona, scope guardrails, static knowledge (20k budget), streaming responses. Headless engine + bubble widget; bring your own UI if you want. Works in any app.
|
|
31971
32007
|
- \`gipity add audio-align\` - Audio + lyrics -> word-level timing JSON. Demucs vocal isolation + MMS_FA forced alignment, runs as a Modal L4 GPU job (~$0.01 per 3-min song). For karaoke captions, subtitling, language learning, dubbing alignment.
|
|
31972
32008
|
- \`gipity add i18n\` - Multi-language for web apps - language picker, locale persistence, RTL, plural/translation lookup. Scaffolds src/js/strings.js and wires it up; move your copy there and read it with t('key'). Web only.
|
|
31973
|
-
- \`gipity add records\` - Registry-driven records: declare objects/fields as data, get generic CRUD functions with validation, full-text search, soft delete, ACTOR provenance, and an audit event spine - every write is transactional (row + event). Field types include relations ({id,label}), currency, emails/phones/links composites. Ships backend functions + migrations. Needs a database (web-fullstack/api template). See the \`app-records\` skill.
|
|
31974
|
-
- \`gipity add views\` - Generic UI over records-kit objects: sortable/filterable table with full-text search, create/edit/delete forms with type-appropriate widgets, kanban board with drag-to-update. Renders entirely from the field registry - zero per-object UI code. Requires the records kit. See the \`app-records\` skill.
|
|
31975
|
-
- \`gipity add agent-api\` - Make your app agent-operable: named API keys (kit_api_keys) let agents and scripts write through the records kit's single write path with AGENT/API actor attribution - machine writes land on the same audit spine as human edits. Requires the records kit. See the \`app-records\` skill.
|
|
31976
32009
|
- \`gipity add contacts\` - Source-agnostic contact data layer for lead-gen/CRM apps: import people from LinkedIn CSV + Gmail + pasted lists, resolve duplicates into one person while keeping EVERY value from every source with provenance (multi-valued attributes, never overwrites). Exact email/URL auto-merge; fuzzy name+company goes to a human merge-review queue (reversible). Re-imports detect job changes and emit signals. User-definable tags, full-text search, and a transactional event spine. Ships backend functions + migrations. Needs a database (web-fullstack/api template).
|
|
31977
32010
|
- \`gipity add stripe\` - Charge your app's end-users for one-time purchases and subscriptions via Stripe. Owner connects their own Stripe account through Gipity-hosted onboarding (no API keys to paste); money lands in their account, Gipity takes a small platform fee. Ships a buy-button / pricing component, a subscription-status helper for gating UI, a webhook-verified fulfillment function, and the payments/subscriptions tables. The platform brokers checkout + signature-verified webhooks. Needs a database (web-fullstack/api template).
|
|
31978
|
-
- \`gipity add notify\` - Web push notifications for any web app, including iOS home-screen web apps (iOS 16.4+). The platform owns the VAPID keys, encryption, and delivery \u2014 no keys to paste, no crypto, no server. Ships a <gipity-notify-button>, a service worker, and a PWA manifest; the device subscribes itself and self-heals stale subscriptions. Send from a function with the injected notify() service (one flat credit per send, owner-billed) or test with \`gipity notify test\`. Works in any template \u2014 no database required
|
|
32011
|
+
- \`gipity add notify\` - Web push notifications for any web app, including iOS home-screen web apps (iOS 16.4+). The platform owns the VAPID keys, encryption, and delivery \u2014 no keys to paste, no crypto, no server. Ships a <gipity-notify-button>, a service worker, and a PWA manifest; the device subscribes itself and self-heals stale subscriptions. Send from a function with the injected notify() service (one flat credit per send, owner-billed) or test with \`gipity notify test\`. Works in any template \u2014 no database required.
|
|
32012
|
+
- \`gipity add servicenow\` - Use ServiceNow tables as a data source: OAuth (client-credentials) authenticated polling pull into a local Postgres mirror (sn_records, any table, configurable), plus write-back into ServiceNow via the Table API. Optional real-time sync layers a ServiceNow Business Rule + Script Include on top, pushing changes to a webhook the instant they happen. Ships a cron workflow for the pull cycle, sn-pull/sn-write/sn-webhook functions, and setup scripts that automate the ServiceNow-side configuration. Needs a database (web-fullstack/api template).`;
|
|
31979
32013
|
var SKILLS_CONTENT = `# Gipity Integration
|
|
31980
32014
|
|
|
31981
32015
|
Gipity is the cloud platform your project runs on - hosting, databases, deployment, file storage, code execution, workflows, and monitoring. Gip is the cloud agent that runs on Gipity.
|
|
@@ -32083,12 +32117,14 @@ Write files locally - Gipity's editor hooks (installed into Claude Code, Grok, a
|
|
|
32083
32117
|
|
|
32084
32118
|
To keep local-only material (research clones, scratch data, vendored references) in the project directory without syncing or deploying it, list it in a \`.gipityignore\` at the project root - gitignore-style, one pattern per line, \`#\` comments. Ignored paths are invisible to sync in both directions; anything that already synced before being ignored stays on the server until you delete it.
|
|
32085
32119
|
|
|
32120
|
+
**WSL trap: user-dropped files may be in a Windows-side twin.** On WSL, a same-named folder often exists at \`C:\\Users\\<name>\\GipityProjects\\<project>\` (visible as \`/mnt/c/Users/<name>/GipityProjects/<project>\`) - from Windows Explorer it looks like "the project folder", so users drop new files (audio, images) there. Nothing syncs from it. If the user says files are "in the project" but you can't find them, check that path before searching wider; \`gipity sync\` prints a notice when such a twin exists.
|
|
32121
|
+
|
|
32086
32122
|
### Where files go: deploy only ships \`src/\`
|
|
32087
32123
|
|
|
32088
32124
|
Deploy is opt-in, not opt-out: the \`files\` phase uploads **only** what's under \`src/\` (plus \`functions/\` and \`migrations/\` as backend, not CDN files). Anything else at the project root is kept but never deployed. Put each kind of file in the right bucket so scratch and reference material can't bloat a deploy:
|
|
32089
32125
|
|
|
32090
32126
|
- **\`src/\`** - the app itself. Synced **and** deployed to the CDN. Only app code, assets, and pages belong here.
|
|
32091
|
-
- **\`tmp/\`** - ephemeral scratch: file conversions, intermediate outputs, design staging. **Already ignored** (never synced, never deployed) - the one place to do throwaway work. Use this single root. (\`*_tmp/\` dirs and \`.gipityscratch/\` are auto-ignored too, as a safety net, so legacy scattered scratch like \`_vsd_tmp/\` can't leak - but write new scratch to \`tmp/\`, not scattered dirs.) Because it never syncs, \`tmp/\` is also never mirrored into the sandbox: **don't stage \`gipity sandbox run\` inputs here** - the sandbox won't see them. Stage inputs under \`src/\`/\`docs/\` and delete them after.
|
|
32127
|
+
- **\`tmp/\`** - ephemeral scratch: file conversions, intermediate outputs, design staging. **Already ignored** (never synced, never deployed) - the one place to do throwaway work. Use this single root. Leave scratch files where they are when you're done; there's nothing to clean up (no \`rm\` turn) - they're invisible to sync and deploy. (\`*_tmp/\` dirs and \`.gipityscratch/\` are auto-ignored too, as a safety net, so legacy scattered scratch like \`_vsd_tmp/\` can't leak - but write new scratch to \`tmp/\`, not scattered dirs.) Because it never syncs, \`tmp/\` is also never mirrored into the sandbox: **don't stage \`gipity sandbox run\` inputs here** - the sandbox won't see them. Stage inputs under \`src/\`/\`docs/\` and delete them after.
|
|
32092
32128
|
- **\`docs/\`** - reference material you want to keep: UI/architecture diagrams, design decks, notes, ADRs. Synced and versioned on the server (backed up, rollback-able) but **never deployed**, because it's outside \`src/\`. This is the home for "keep forever, don't ship" artifacts.
|
|
32093
32129
|
- **\`tests/\`** - \`*.test.js\` suites. Synced, run by \`gipity test\`, never deployed. \`gipity test list [path]\` lists the test files (and what a filter selects) without running them.
|
|
32094
32130
|
|
|
@@ -32104,7 +32140,7 @@ App services skills (load before calling \`/services/*\` endpoints):
|
|
|
32104
32140
|
- \`app-audio\` - sound effects, music, transcription
|
|
32105
32141
|
- \`app-auth\` - sign in with Gipity, popup vs redirect
|
|
32106
32142
|
- \`app-files\` - uploads, variants, file listing
|
|
32107
|
-
- \`app-image\` - text-to-image
|
|
32143
|
+
- \`app-image\` - text-to-image AND instruction editing (pass \`images\` to edit an uploaded photo); providers, sizes, aspect ratios
|
|
32108
32144
|
- \`app-llm\` - chat completions, streaming, image input
|
|
32109
32145
|
- \`app-location\` - user location & reverse geocoding for deployed apps (first-party - no third-party geocoder)
|
|
32110
32146
|
- \`app-notify\` - web push notifications for deployed apps (incl. iOS home-screen web apps) - notify kit + injected notify() service, platform owns the keys
|
|
@@ -32119,6 +32155,7 @@ App development skills:
|
|
|
32119
32155
|
- \`app-debugging\` - debug a deployed app: page inspect/eval, screenshots, function logs
|
|
32120
32156
|
- \`app-development\` - functions, database, and API
|
|
32121
32157
|
- \`app-import\` - import apps from GitHub/.gip bundles (incl. Vercel/Replit/Lovable porting) and export any project as a portable .gip - app_import tool, gipity save/load
|
|
32158
|
+
- \`app-records\` - Gipity Records: declared tables \u2192 validated CRUD, provenance, workflows, auto UI
|
|
32122
32159
|
- \`app-testing\` - testing deployed app functions (ctx.fn.call/callAs, the isolated test DB)
|
|
32123
32160
|
- \`deploy\` - the deploy pipeline & gipity.yaml manifest
|
|
32124
32161
|
- \`jobs\` - long-running CPU + GPU compute jobs (Python / Node / bash)
|
|
@@ -32127,7 +32164,6 @@ App development skills:
|
|
|
32127
32164
|
- \`web-ui-patterns\` - default Gipity look (theme tokens) + web UI recipes - feeds, copy-to-clipboard
|
|
32128
32165
|
|
|
32129
32166
|
Kit skills (reusable building blocks - \`gipity add <kit>\`):
|
|
32130
|
-
- \`app-records\` - the records + views kits: registry-driven CRUD with search + audit history, and generated tables/kanban/forms (gipity add records)
|
|
32131
32167
|
- \`audio-align\` - the audio-align kit: forced alignment of audio + lyrics into word-level timing JSON
|
|
32132
32168
|
- \`chatbot\` - the chatbot kit: persona + scope guardrails + static knowledge, bubble widget or headless engine
|
|
32133
32169
|
|
|
@@ -32156,13 +32192,13 @@ init_colors();
|
|
|
32156
32192
|
|
|
32157
32193
|
// src/bug-queue.ts
|
|
32158
32194
|
init_api();
|
|
32159
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as
|
|
32160
|
-
import { join as
|
|
32195
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
32196
|
+
import { join as join3 } from "path";
|
|
32161
32197
|
import { homedir as homedir2 } from "os";
|
|
32162
|
-
var QUEUE_DIR =
|
|
32198
|
+
var QUEUE_DIR = join3(process.env.GIPITY_DIR || join3(homedir2(), ".gipity"), "bug-queue");
|
|
32163
32199
|
function queueBugReport(report) {
|
|
32164
32200
|
mkdirSync2(QUEUE_DIR, { recursive: true, mode: 448 });
|
|
32165
|
-
const file =
|
|
32201
|
+
const file = join3(QUEUE_DIR, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
32166
32202
|
writeFileSync3(file, JSON.stringify(report, null, 2), { mode: 384 });
|
|
32167
32203
|
}
|
|
32168
32204
|
function isRetryableFailure(err) {
|
|
@@ -32174,12 +32210,12 @@ function isRetryableFailure(err) {
|
|
|
32174
32210
|
async function flushBugQueue() {
|
|
32175
32211
|
if (!existsSync3(QUEUE_DIR)) return 0;
|
|
32176
32212
|
let delivered = 0;
|
|
32177
|
-
for (const file of
|
|
32213
|
+
for (const file of readdirSync2(QUEUE_DIR)) {
|
|
32178
32214
|
if (!file.endsWith(".json")) continue;
|
|
32179
|
-
const path5 =
|
|
32215
|
+
const path5 = join3(QUEUE_DIR, file);
|
|
32180
32216
|
let report;
|
|
32181
32217
|
try {
|
|
32182
|
-
report = JSON.parse(
|
|
32218
|
+
report = JSON.parse(readFileSync5(path5, "utf-8"));
|
|
32183
32219
|
} catch {
|
|
32184
32220
|
try {
|
|
32185
32221
|
unlinkSync2(path5);
|
|
@@ -32202,6 +32238,70 @@ async function flushBugQueue() {
|
|
|
32202
32238
|
return delivered;
|
|
32203
32239
|
}
|
|
32204
32240
|
|
|
32241
|
+
// src/login-flow.ts
|
|
32242
|
+
init_api();
|
|
32243
|
+
init_auth();
|
|
32244
|
+
init_config();
|
|
32245
|
+
init_utils();
|
|
32246
|
+
init_colors();
|
|
32247
|
+
function newAccountWouldBeUnexpected(email, priorAuth) {
|
|
32248
|
+
return !!getConfig() || priorAuth?.email.toLowerCase() === email.toLowerCase().trim();
|
|
32249
|
+
}
|
|
32250
|
+
function warnBeforeCodeIfUnexpectedNewAccount(isNewUser, email, indent = "") {
|
|
32251
|
+
if (isNewUser !== true) return;
|
|
32252
|
+
const priorAuth = getAuth();
|
|
32253
|
+
if (!newAccountWouldBeUnexpected(email, priorAuth)) return;
|
|
32254
|
+
const config = getConfig();
|
|
32255
|
+
console.log(`${indent}${warning(`No existing Gipity account for ${email} \u2014 entering the code will CREATE a new one.`)}`);
|
|
32256
|
+
if (config) {
|
|
32257
|
+
console.log(`${indent}${muted(`This directory is linked to project ${config.projectSlug} (account ${config.accountSlug}). If you meant to log into that account, stop and re-check the email before entering the code.`)}`);
|
|
32258
|
+
}
|
|
32259
|
+
}
|
|
32260
|
+
function warnIfUnexpectedNewAccount(isNewUser, email, priorAuth, indent = "") {
|
|
32261
|
+
if (isNewUser !== true) return;
|
|
32262
|
+
if (!newAccountWouldBeUnexpected(email, priorAuth)) return;
|
|
32263
|
+
const config = getConfig();
|
|
32264
|
+
console.log(`${indent}${warning(`Logged into a NEW, empty account for ${email} \u2014 no prior account existed for this email.`)}`);
|
|
32265
|
+
if (config) {
|
|
32266
|
+
console.log(`${indent}${muted(`This directory is linked to project ${config.projectSlug} (account ${config.accountSlug}), which the new account does not own \u2014 project / skill / sync commands will fail with "not found".`)}`);
|
|
32267
|
+
}
|
|
32268
|
+
console.log(`${indent}${muted("If you expected an existing account, run: gipity status \u2014 then gipity login again with the correct email.")}`);
|
|
32269
|
+
}
|
|
32270
|
+
async function interactiveLogin() {
|
|
32271
|
+
const email = await prompt(" Email: ");
|
|
32272
|
+
if (!email) {
|
|
32273
|
+
console.error(`
|
|
32274
|
+
${error("Email required.")}`);
|
|
32275
|
+
process.exit(1);
|
|
32276
|
+
}
|
|
32277
|
+
const sendRes = await publicPost("/auth/login", { email });
|
|
32278
|
+
console.log(" Check your email for a 6-digit code.\n");
|
|
32279
|
+
warnBeforeCodeIfUnexpectedNewAccount(sendRes.isNewUser, email, " ");
|
|
32280
|
+
const code = await prompt(" Code: ");
|
|
32281
|
+
if (!code) {
|
|
32282
|
+
console.error(`
|
|
32283
|
+
${error("Code required.")}`);
|
|
32284
|
+
process.exit(1);
|
|
32285
|
+
}
|
|
32286
|
+
const priorAuth = getAuth();
|
|
32287
|
+
const res = await publicPost("/auth/verify", { email, code });
|
|
32288
|
+
const exp = decodeJwtExp(res.accessToken);
|
|
32289
|
+
if (!exp) {
|
|
32290
|
+
console.error(`
|
|
32291
|
+
${error("Invalid token received.")}`);
|
|
32292
|
+
process.exit(1);
|
|
32293
|
+
}
|
|
32294
|
+
const expiresAt = new Date(exp * 1e3).toISOString();
|
|
32295
|
+
saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
|
|
32296
|
+
console.log(` ${success(`Logged in (${email}).`)}`);
|
|
32297
|
+
warnIfUnexpectedNewAccount(res.isNewUser, email, priorAuth, " ");
|
|
32298
|
+
const delivered = await flushBugQueue().catch(() => 0);
|
|
32299
|
+
if (delivered > 0) {
|
|
32300
|
+
console.log(` ${muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`)}`);
|
|
32301
|
+
}
|
|
32302
|
+
return getAuth();
|
|
32303
|
+
}
|
|
32304
|
+
|
|
32205
32305
|
// src/commands/login.ts
|
|
32206
32306
|
var loginCommand = new Command("login").description("Log in or sign up").option("--email <email>", "Email address").option("--code <code>", "Verification code").action(async (opts) => {
|
|
32207
32307
|
try {
|
|
@@ -32212,9 +32312,10 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
|
|
|
32212
32312
|
return;
|
|
32213
32313
|
}
|
|
32214
32314
|
if (email && !code) {
|
|
32215
|
-
await publicPost("/auth/login", { email });
|
|
32315
|
+
const sendRes2 = await publicPost("/auth/login", { email });
|
|
32216
32316
|
console.log("Check your email for a 6-digit code.");
|
|
32217
32317
|
console.log(muted(`Then run: gipity login --email ${email} --code <code>`));
|
|
32318
|
+
warnBeforeCodeIfUnexpectedNewAccount(sendRes2.isNewUser, email);
|
|
32218
32319
|
return;
|
|
32219
32320
|
}
|
|
32220
32321
|
console.log("Enter your email to log in or create an account.");
|
|
@@ -32225,9 +32326,10 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
|
|
|
32225
32326
|
console.error(error("Email required."));
|
|
32226
32327
|
process.exit(1);
|
|
32227
32328
|
}
|
|
32228
|
-
await publicPost("/auth/login", { email });
|
|
32329
|
+
const sendRes = await publicPost("/auth/login", { email });
|
|
32229
32330
|
console.log("");
|
|
32230
32331
|
console.log("Check your email for a 6-digit code.");
|
|
32332
|
+
warnBeforeCodeIfUnexpectedNewAccount(sendRes.isNewUser, email);
|
|
32231
32333
|
code = await prompt("Code: ");
|
|
32232
32334
|
await verify(email, code);
|
|
32233
32335
|
} catch (err) {
|
|
@@ -32236,6 +32338,7 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
|
|
|
32236
32338
|
}
|
|
32237
32339
|
});
|
|
32238
32340
|
async function verify(email, code) {
|
|
32341
|
+
const priorAuth = getAuth();
|
|
32239
32342
|
const res = await publicPost("/auth/verify", { email, code });
|
|
32240
32343
|
const exp = decodeJwtExp(res.accessToken);
|
|
32241
32344
|
if (!exp) {
|
|
@@ -32250,6 +32353,7 @@ async function verify(email, code) {
|
|
|
32250
32353
|
expiresAt
|
|
32251
32354
|
});
|
|
32252
32355
|
console.log(success(`Logged in (${email}).`));
|
|
32356
|
+
warnIfUnexpectedNewAccount(res.isNewUser, email, priorAuth);
|
|
32253
32357
|
const delivered = await flushBugQueue().catch(() => 0);
|
|
32254
32358
|
if (delivered > 0) {
|
|
32255
32359
|
console.log(muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`));
|
|
@@ -32295,8 +32399,8 @@ function run(label2, action) {
|
|
|
32295
32399
|
init_api();
|
|
32296
32400
|
init_config();
|
|
32297
32401
|
init_utils();
|
|
32298
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, statSync, lstatSync, unlinkSync as unlinkSync3, readdirSync as
|
|
32299
|
-
import { join as
|
|
32402
|
+
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, statSync as statSync2, lstatSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, readFileSync as readFileSync7, renameSync, openSync as openSync2, closeSync as closeSync2, utimesSync, realpathSync as realpathSync2 } from "fs";
|
|
32403
|
+
import { join as join5, relative, dirname as dirname4, extname as extname2, resolve as resolve4, sep } from "path";
|
|
32300
32404
|
import { hostname } from "os";
|
|
32301
32405
|
import { createHash as createHash2 } from "crypto";
|
|
32302
32406
|
|
|
@@ -32359,25 +32463,25 @@ async function hashFile(path5) {
|
|
|
32359
32463
|
const hash = createHash("sha256");
|
|
32360
32464
|
let size = 0;
|
|
32361
32465
|
const stream = createReadStream(path5);
|
|
32362
|
-
await new Promise((
|
|
32466
|
+
await new Promise((resolve19, reject) => {
|
|
32363
32467
|
stream.on("data", (chunk) => {
|
|
32364
32468
|
const buf = chunk;
|
|
32365
32469
|
hash.update(buf);
|
|
32366
32470
|
size += buf.length;
|
|
32367
32471
|
});
|
|
32368
|
-
stream.on("end", () =>
|
|
32472
|
+
stream.on("end", () => resolve19());
|
|
32369
32473
|
stream.on("error", reject);
|
|
32370
32474
|
});
|
|
32371
32475
|
return { sha256: hash.digest("hex"), size };
|
|
32372
32476
|
}
|
|
32373
32477
|
function readRange(path5, start, end) {
|
|
32374
|
-
return new Promise((
|
|
32478
|
+
return new Promise((resolve19, reject) => {
|
|
32375
32479
|
const chunks = [];
|
|
32376
32480
|
const stream = createReadStream(path5, { start, end });
|
|
32377
32481
|
stream.on("data", (c) => {
|
|
32378
32482
|
chunks.push(c);
|
|
32379
32483
|
});
|
|
32380
|
-
stream.on("end", () =>
|
|
32484
|
+
stream.on("end", () => resolve19(Buffer.concat(chunks)));
|
|
32381
32485
|
stream.on("error", reject);
|
|
32382
32486
|
});
|
|
32383
32487
|
}
|
|
@@ -32410,9 +32514,9 @@ var UploadConflictError = class extends Error {
|
|
|
32410
32514
|
path;
|
|
32411
32515
|
};
|
|
32412
32516
|
async function uploadOneFile(projectGuid, localPath, virtualPath, opts = {}) {
|
|
32413
|
-
const { sha256
|
|
32517
|
+
const { sha256, size } = await hashFile(localPath);
|
|
32414
32518
|
const mime = opts.mime ?? guessMime(virtualPath);
|
|
32415
|
-
const initBody = { path: virtualPath, size, sha256
|
|
32519
|
+
const initBody = { path: virtualPath, size, sha256, mime };
|
|
32416
32520
|
if (opts.expectedServerVersion !== void 0) {
|
|
32417
32521
|
initBody.expected_server_version = opts.expectedServerVersion;
|
|
32418
32522
|
}
|
|
@@ -32540,9 +32644,9 @@ async function uploadCompleteBatch(projectGuid, items) {
|
|
|
32540
32644
|
|
|
32541
32645
|
// src/setup.ts
|
|
32542
32646
|
init_platform();
|
|
32543
|
-
import { resolve as resolve3, join as
|
|
32647
|
+
import { resolve as resolve3, join as join4, dirname as dirname3 } from "path";
|
|
32544
32648
|
import { homedir as homedir3, tmpdir } from "os";
|
|
32545
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as
|
|
32649
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync6, mkdtempSync, rmSync, cpSync, readdirSync as readdirSync3 } from "fs";
|
|
32546
32650
|
init_config();
|
|
32547
32651
|
var PRIMER_FILES = {
|
|
32548
32652
|
claude: "CLAUDE.md",
|
|
@@ -32657,14 +32761,14 @@ function stripGipityHooks(settings) {
|
|
|
32657
32761
|
function readSettingsFile(path5) {
|
|
32658
32762
|
if (!existsSync4(path5)) return {};
|
|
32659
32763
|
try {
|
|
32660
|
-
return JSON.parse(
|
|
32764
|
+
return JSON.parse(readFileSync6(path5, "utf-8"));
|
|
32661
32765
|
} catch {
|
|
32662
32766
|
return {};
|
|
32663
32767
|
}
|
|
32664
32768
|
}
|
|
32665
32769
|
function ensureGipityPlugin(force = false) {
|
|
32666
|
-
const claudeDir =
|
|
32667
|
-
const settingsPath =
|
|
32770
|
+
const claudeDir = join4(homedir3(), ".claude");
|
|
32771
|
+
const settingsPath = join4(claudeDir, "settings.json");
|
|
32668
32772
|
const settings = readSettingsFile(settingsPath);
|
|
32669
32773
|
let changed = stripGipityHooks(settings);
|
|
32670
32774
|
const marketplaces = settings.extraKnownMarketplaces ?? (settings.extraKnownMarketplaces = {});
|
|
@@ -32696,8 +32800,8 @@ function versionGte(have, want) {
|
|
|
32696
32800
|
}
|
|
32697
32801
|
function userScopeInstallState() {
|
|
32698
32802
|
try {
|
|
32699
|
-
const p =
|
|
32700
|
-
const data = JSON.parse(
|
|
32803
|
+
const p = join4(homedir3(), ".claude", "plugins", "installed_plugins.json");
|
|
32804
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
32701
32805
|
const entries = data?.plugins?.[GIPITY_PLUGIN_ID];
|
|
32702
32806
|
if (!Array.isArray(entries)) return { exists: false, current: false };
|
|
32703
32807
|
const userEntries = entries.filter((e) => e?.scope === "user");
|
|
@@ -32734,8 +32838,8 @@ function ensureGipityPluginInstalled() {
|
|
|
32734
32838
|
}
|
|
32735
32839
|
function grokInstallState() {
|
|
32736
32840
|
try {
|
|
32737
|
-
const p =
|
|
32738
|
-
const data = JSON.parse(
|
|
32841
|
+
const p = join4(homedir3(), ".grok", "installed-plugins", "registry.json");
|
|
32842
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
32739
32843
|
const versions = [];
|
|
32740
32844
|
for (const repo of Object.values(data?.repos ?? {})) {
|
|
32741
32845
|
const v7 = repo?.plugins?.gipity?.version;
|
|
@@ -32760,12 +32864,12 @@ function ensureGrokPluginInstalled() {
|
|
|
32760
32864
|
console.log("Installed the Gipity plugin for Grok (skills + file-sync hooks).");
|
|
32761
32865
|
}
|
|
32762
32866
|
}
|
|
32763
|
-
var AGENTS_SKILLS_DIR =
|
|
32764
|
-
var AGENT_HOOKS_DIR =
|
|
32765
|
-
var AGENT_SKILLS_MANIFEST =
|
|
32867
|
+
var AGENTS_SKILLS_DIR = join4(homedir3(), ".agents", "skills");
|
|
32868
|
+
var AGENT_HOOKS_DIR = join4(homedir3(), ".gipity", "agent-hooks");
|
|
32869
|
+
var AGENT_SKILLS_MANIFEST = join4(homedir3(), ".gipity", "agent-skills.json");
|
|
32766
32870
|
function agentSkillsState() {
|
|
32767
32871
|
try {
|
|
32768
|
-
const m = JSON.parse(
|
|
32872
|
+
const m = JSON.parse(readFileSync6(AGENT_SKILLS_MANIFEST, "utf-8"));
|
|
32769
32873
|
return {
|
|
32770
32874
|
current: typeof m?.version === "string" && versionGte(m.version, GIPITY_PLUGIN_VERSION),
|
|
32771
32875
|
skills: Array.isArray(m?.skills) ? m.skills : []
|
|
@@ -32777,36 +32881,36 @@ function agentSkillsState() {
|
|
|
32777
32881
|
function ensureAgentSkillsInstalled() {
|
|
32778
32882
|
if (agentSkillsState().current) return;
|
|
32779
32883
|
if (!binaryOnPath("git")) return;
|
|
32780
|
-
const tmp = mkdtempSync(
|
|
32884
|
+
const tmp = mkdtempSync(join4(tmpdir(), "gipity-skills-"));
|
|
32781
32885
|
try {
|
|
32782
32886
|
const clone = spawnSyncCommand(
|
|
32783
32887
|
resolveCommand("git"),
|
|
32784
|
-
["clone", "--depth", "1", `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`,
|
|
32888
|
+
["clone", "--depth", "1", `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`, join4(tmp, "repo")],
|
|
32785
32889
|
{ stdio: "ignore", timeout: 12e4 }
|
|
32786
32890
|
);
|
|
32787
32891
|
if (clone.status !== 0) return;
|
|
32788
|
-
const repo =
|
|
32892
|
+
const repo = join4(tmp, "repo");
|
|
32789
32893
|
let version = GIPITY_PLUGIN_VERSION;
|
|
32790
32894
|
try {
|
|
32791
|
-
const manifest = JSON.parse(
|
|
32895
|
+
const manifest = JSON.parse(readFileSync6(join4(repo, ".claude-plugin", "plugin.json"), "utf-8"));
|
|
32792
32896
|
if (typeof manifest?.version === "string") version = manifest.version;
|
|
32793
32897
|
} catch {
|
|
32794
32898
|
}
|
|
32795
|
-
const skillsSrc =
|
|
32899
|
+
const skillsSrc = join4(repo, "skills");
|
|
32796
32900
|
const names = [];
|
|
32797
|
-
for (const entry of
|
|
32901
|
+
for (const entry of readdirSync3(skillsSrc, { withFileTypes: true })) {
|
|
32798
32902
|
if (!entry.isDirectory()) continue;
|
|
32799
|
-
if (!existsSync4(
|
|
32903
|
+
if (!existsSync4(join4(skillsSrc, entry.name, "SKILL.md"))) continue;
|
|
32800
32904
|
mkdirSync3(AGENTS_SKILLS_DIR, { recursive: true });
|
|
32801
|
-
cpSync(
|
|
32905
|
+
cpSync(join4(skillsSrc, entry.name), join4(AGENTS_SKILLS_DIR, entry.name), {
|
|
32802
32906
|
recursive: true,
|
|
32803
32907
|
force: true
|
|
32804
32908
|
});
|
|
32805
32909
|
names.push(entry.name);
|
|
32806
32910
|
}
|
|
32807
32911
|
mkdirSync3(AGENT_HOOKS_DIR, { recursive: true });
|
|
32808
|
-
for (const script of
|
|
32809
|
-
cpSync(
|
|
32912
|
+
for (const script of readdirSync3(join4(repo, "hooks", "scripts"))) {
|
|
32913
|
+
cpSync(join4(repo, "hooks", "scripts", script), join4(AGENT_HOOKS_DIR, script), { force: true });
|
|
32810
32914
|
}
|
|
32811
32915
|
writeFileSync4(AGENT_SKILLS_MANIFEST, JSON.stringify({ version, skills: names }, null, 2) + "\n");
|
|
32812
32916
|
console.log(`Installed ${names.length} Gipity skills for Codex (~/.agents/skills).`);
|
|
@@ -32816,8 +32920,8 @@ function ensureAgentSkillsInstalled() {
|
|
|
32816
32920
|
}
|
|
32817
32921
|
}
|
|
32818
32922
|
function applyCodexHooks(existing) {
|
|
32819
|
-
const launcher =
|
|
32820
|
-
const cmd = (script, ...args) => [`sh "${launcher}" "${
|
|
32923
|
+
const launcher = join4(AGENT_HOOKS_DIR, "launch.sh");
|
|
32924
|
+
const cmd = (script, ...args) => [`sh "${launcher}" "${join4(AGENT_HOOKS_DIR, script)}"`, ...args].join(" ");
|
|
32821
32925
|
const wanted = [
|
|
32822
32926
|
{ event: "PostToolUse", matcher: "Edit|Write", command: cmd("sync-push.cjs"), timeout: 30 },
|
|
32823
32927
|
{ event: "UserPromptSubmit", command: cmd("sync-pull.cjs"), timeout: 300 },
|
|
@@ -32855,8 +32959,8 @@ function setupCodexHooks() {
|
|
|
32855
32959
|
if (process.platform === "win32") return;
|
|
32856
32960
|
const cwd = resolve3(process.cwd());
|
|
32857
32961
|
if (cwd === resolve3(homedir3())) return;
|
|
32858
|
-
const path5 =
|
|
32859
|
-
const existing = existsSync4(path5) ?
|
|
32962
|
+
const path5 = join4(cwd, ".codex", "hooks.json");
|
|
32963
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
32860
32964
|
const next = applyCodexHooks(existing);
|
|
32861
32965
|
if (next === null) return;
|
|
32862
32966
|
mkdirSync3(dirname3(path5), { recursive: true });
|
|
@@ -32876,9 +32980,9 @@ function setupClaudeHooks() {
|
|
|
32876
32980
|
ensureGipityPlugin();
|
|
32877
32981
|
const cwd = resolve3(process.cwd());
|
|
32878
32982
|
if (cwd === resolve3(homedir3())) return;
|
|
32879
|
-
const claudeDir =
|
|
32983
|
+
const claudeDir = join4(cwd, ".claude");
|
|
32880
32984
|
mkdirSync3(claudeDir, { recursive: true });
|
|
32881
|
-
const settingsPath =
|
|
32985
|
+
const settingsPath = join4(claudeDir, "settings.json");
|
|
32882
32986
|
const settings = readSettingsFile(settingsPath);
|
|
32883
32987
|
stripGipityHooks(settings);
|
|
32884
32988
|
const perms = settings.permissions || {};
|
|
@@ -32925,7 +33029,7 @@ function applySkillsBlock(existing, apiBase2 = DEFAULT_API_BASE) {
|
|
|
32925
33029
|
function writeSkillsFile(relPath, wrap) {
|
|
32926
33030
|
const path5 = resolve3(process.cwd(), relPath);
|
|
32927
33031
|
mkdirSync3(dirname3(path5), { recursive: true });
|
|
32928
|
-
const existing = existsSync4(path5) ?
|
|
33032
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
32929
33033
|
const baseNext = applySkillsBlock(existing, resolveApiBase());
|
|
32930
33034
|
const next = wrap && existing === null ? wrap(baseNext) : baseNext;
|
|
32931
33035
|
if (next !== existing) writeFileSync4(path5, next);
|
|
@@ -32968,7 +33072,7 @@ function applyAiderConf(existing) {
|
|
|
32968
33072
|
function setupAiderMd() {
|
|
32969
33073
|
writeSkillsFile(PRIMER_FILES.aider);
|
|
32970
33074
|
const path5 = resolve3(process.cwd(), AIDER_CONF_FILE);
|
|
32971
|
-
const existing = existsSync4(path5) ?
|
|
33075
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
32972
33076
|
const next = applyAiderConf(existing);
|
|
32973
33077
|
if (next !== null) writeFileSync4(path5, next);
|
|
32974
33078
|
}
|
|
@@ -33010,7 +33114,7 @@ function setupGitignore() {
|
|
|
33010
33114
|
const gitignorePath = resolve3(process.cwd(), ".gitignore");
|
|
33011
33115
|
const entries = [".gipity/", ".gipity.json", ...SCRATCH_IGNORE];
|
|
33012
33116
|
if (existsSync4(gitignorePath)) {
|
|
33013
|
-
let content =
|
|
33117
|
+
let content = readFileSync6(gitignorePath, "utf-8");
|
|
33014
33118
|
const lines = content.split(/\r?\n/);
|
|
33015
33119
|
const toAdd = entries.filter((e) => !lines.includes(e));
|
|
33016
33120
|
if (toAdd.length > 0) {
|
|
@@ -33038,11 +33142,11 @@ var BULK_DELETE_FRACTION = 0.25;
|
|
|
33038
33142
|
var DOWNLOAD_IDLE_MS = 3e4;
|
|
33039
33143
|
function syncStatePath() {
|
|
33040
33144
|
const configPath = getConfigPath();
|
|
33041
|
-
return
|
|
33145
|
+
return join5(dirname4(configPath), ".gipity", "sync-state.json");
|
|
33042
33146
|
}
|
|
33043
33147
|
function lockPath() {
|
|
33044
33148
|
const configPath = getConfigPath();
|
|
33045
|
-
return
|
|
33149
|
+
return join5(dirname4(configPath), ".gipity", "sync.lock");
|
|
33046
33150
|
}
|
|
33047
33151
|
function projectDir() {
|
|
33048
33152
|
const configPath = getConfigPath();
|
|
@@ -33056,8 +33160,8 @@ function isLockReclaimable(path5, now = Date.now()) {
|
|
|
33056
33160
|
let raw;
|
|
33057
33161
|
let mtimeMs;
|
|
33058
33162
|
try {
|
|
33059
|
-
raw =
|
|
33060
|
-
mtimeMs =
|
|
33163
|
+
raw = readFileSync7(path5, "utf-8").trim();
|
|
33164
|
+
mtimeMs = statSync2(path5).mtimeMs;
|
|
33061
33165
|
} catch {
|
|
33062
33166
|
return false;
|
|
33063
33167
|
}
|
|
@@ -33116,7 +33220,7 @@ async function acquireLock(progress) {
|
|
|
33116
33220
|
);
|
|
33117
33221
|
}
|
|
33118
33222
|
if (!waitSpinner) {
|
|
33119
|
-
waitSpinner = progress?.spinner("Waiting for another sync to finish
|
|
33223
|
+
waitSpinner = progress?.spinner("Waiting for another sync to finish...") ?? null;
|
|
33120
33224
|
}
|
|
33121
33225
|
await new Promise((r) => setTimeout(r, LOCK_POLL_MS2));
|
|
33122
33226
|
}
|
|
@@ -33126,7 +33230,7 @@ function readBaseline(projectGuid) {
|
|
|
33126
33230
|
const path5 = syncStatePath();
|
|
33127
33231
|
if (!existsSync5(path5)) return { projectGuid, files: {}, lastFullSync: null };
|
|
33128
33232
|
try {
|
|
33129
|
-
const parsed = JSON.parse(
|
|
33233
|
+
const parsed = JSON.parse(readFileSync7(path5, "utf-8"));
|
|
33130
33234
|
if (parsed.projectGuid !== projectGuid) {
|
|
33131
33235
|
return { projectGuid, files: {}, lastFullSync: null };
|
|
33132
33236
|
}
|
|
@@ -33150,25 +33254,25 @@ function walkLocal(root, ignorePatterns, baseline) {
|
|
|
33150
33254
|
function walk(dir) {
|
|
33151
33255
|
let entries;
|
|
33152
33256
|
try {
|
|
33153
|
-
entries =
|
|
33257
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
33154
33258
|
} catch {
|
|
33155
33259
|
return;
|
|
33156
33260
|
}
|
|
33157
33261
|
for (const entry of entries) {
|
|
33158
|
-
const full =
|
|
33262
|
+
const full = join5(dir, entry.name);
|
|
33159
33263
|
const rel2 = relative(root, full).replace(/\\/g, "/");
|
|
33160
33264
|
if (shouldIgnore(rel2, ignorePatterns)) continue;
|
|
33161
33265
|
if (entry.isDirectory()) {
|
|
33162
|
-
if (existsSync5(
|
|
33266
|
+
if (existsSync5(join5(full, CONFIG_FILE2))) continue;
|
|
33163
33267
|
walk(full);
|
|
33164
33268
|
} else if (entry.isFile()) {
|
|
33165
33269
|
try {
|
|
33166
|
-
const stat2 =
|
|
33270
|
+
const stat2 = statSync2(full);
|
|
33167
33271
|
const size = stat2.size;
|
|
33168
33272
|
const mtime = stat2.mtime.toISOString();
|
|
33169
33273
|
const prior = baseline[rel2];
|
|
33170
|
-
const
|
|
33171
|
-
result.set(rel2, { size, mtime, sha256
|
|
33274
|
+
const sha256 = prior && prior.size === size && prior.mtime === mtime ? prior.sha256 : void 0;
|
|
33275
|
+
result.set(rel2, { size, mtime, sha256 });
|
|
33172
33276
|
} catch {
|
|
33173
33277
|
}
|
|
33174
33278
|
}
|
|
@@ -33182,8 +33286,8 @@ async function ensureLocalHashes(root, local, paths) {
|
|
|
33182
33286
|
const info2 = local.get(path5);
|
|
33183
33287
|
if (!info2 || info2.sha256) continue;
|
|
33184
33288
|
try {
|
|
33185
|
-
const { sha256
|
|
33186
|
-
info2.sha256 =
|
|
33289
|
+
const { sha256 } = await hashFile(join5(root, path5));
|
|
33290
|
+
info2.sha256 = sha256;
|
|
33187
33291
|
} catch {
|
|
33188
33292
|
}
|
|
33189
33293
|
}
|
|
@@ -33198,10 +33302,10 @@ function resolveInRoot(root, relPath) {
|
|
|
33198
33302
|
throw new Error(`Refusing path outside project root: ${relPath}`);
|
|
33199
33303
|
}
|
|
33200
33304
|
try {
|
|
33201
|
-
const rootReal =
|
|
33305
|
+
const rootReal = realpathSync2(rootResolved);
|
|
33202
33306
|
let ancestor = full;
|
|
33203
33307
|
while (!existsSync5(ancestor) && dirname4(ancestor) !== ancestor) ancestor = dirname4(ancestor);
|
|
33204
|
-
const ancestorReal =
|
|
33308
|
+
const ancestorReal = realpathSync2(ancestor);
|
|
33205
33309
|
if (ancestorReal !== rootReal && !ancestorReal.startsWith(rootReal + sep)) {
|
|
33206
33310
|
throw new Error(`Refusing path outside project root (symlink escape): ${relPath}`);
|
|
33207
33311
|
}
|
|
@@ -33229,7 +33333,7 @@ async function fetchRemote(projectGuid) {
|
|
|
33229
33333
|
function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
33230
33334
|
const extract3 = tar2.extract();
|
|
33231
33335
|
const files = /* @__PURE__ */ new Map();
|
|
33232
|
-
return new Promise((
|
|
33336
|
+
return new Promise((resolve19, reject) => {
|
|
33233
33337
|
let settled = false;
|
|
33234
33338
|
let idle;
|
|
33235
33339
|
const arm = () => {
|
|
@@ -33265,7 +33369,7 @@ function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
|
33265
33369
|
});
|
|
33266
33370
|
entryStream.resume();
|
|
33267
33371
|
});
|
|
33268
|
-
extract3.on("finish", () => done(
|
|
33372
|
+
extract3.on("finish", () => done(resolve19, files));
|
|
33269
33373
|
extract3.on("error", (e) => done(reject, e));
|
|
33270
33374
|
stream.on("error", (e) => done(reject, e));
|
|
33271
33375
|
arm();
|
|
@@ -33489,9 +33593,9 @@ Plan deletes ${totalDeletes} files (${Math.round(fraction * 100)}% of the tree).
|
|
|
33489
33593
|
}
|
|
33490
33594
|
var GIPITY_IGNORE_FILE = ".gipityignore";
|
|
33491
33595
|
function readGipityIgnore(root) {
|
|
33492
|
-
const path5 =
|
|
33596
|
+
const path5 = join5(root, GIPITY_IGNORE_FILE);
|
|
33493
33597
|
if (!existsSync5(path5)) return [];
|
|
33494
|
-
return
|
|
33598
|
+
return readFileSync7(path5, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\.\//, "").replace(/^\//, ""));
|
|
33495
33599
|
}
|
|
33496
33600
|
function effectiveIgnore(root, configIgnore) {
|
|
33497
33601
|
const base = configIgnore && configIgnore.length ? configIgnore : DEFAULT_SYNC_IGNORE;
|
|
@@ -33532,9 +33636,9 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33532
33636
|
const config = { projectGuid, ignore: ignore2 };
|
|
33533
33637
|
const p = opts.progress;
|
|
33534
33638
|
const baseline = readBaseline(projectGuid);
|
|
33535
|
-
p?.phase("Scanning local files
|
|
33639
|
+
p?.phase("Scanning local files...");
|
|
33536
33640
|
const local = walkLocal(root, ignore2, baseline.files);
|
|
33537
|
-
p?.phase("Checking Gipity for changes
|
|
33641
|
+
p?.phase("Checking Gipity for changes...");
|
|
33538
33642
|
const remote = await fetchRemote(projectGuid);
|
|
33539
33643
|
for (const path5 of [...remote.keys()]) {
|
|
33540
33644
|
if (shouldIgnore(path5, ignore2)) remote.delete(path5);
|
|
@@ -33545,7 +33649,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33545
33649
|
const r = remote.get(path5);
|
|
33546
33650
|
if (r?.sha256 || baseline.files[path5]) needHash.push(path5);
|
|
33547
33651
|
}
|
|
33548
|
-
if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}
|
|
33652
|
+
if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}...`);
|
|
33549
33653
|
await ensureLocalHashes(root, local, needHash);
|
|
33550
33654
|
const planned = plan(local, remote, baseline.files);
|
|
33551
33655
|
if (opts.plan) {
|
|
@@ -33631,13 +33735,13 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33631
33735
|
if (buf) downloadedBytes.set(a.path, buf);
|
|
33632
33736
|
}
|
|
33633
33737
|
} catch (err) {
|
|
33634
|
-
errors.push(`Bulk download incomplete (${err.message}); recovering files individually
|
|
33738
|
+
errors.push(`Bulk download incomplete (${err.message}); recovering files individually...`);
|
|
33635
33739
|
} finally {
|
|
33636
33740
|
p?.finish();
|
|
33637
33741
|
}
|
|
33638
33742
|
const missing = wantedDownloads.filter((a) => !downloadedBytes.has(a.path));
|
|
33639
33743
|
if (missing.length) {
|
|
33640
|
-
p?.phase(`Recovering ${missing.length} file${missing.length === 1 ? "" : "s"} the bulk download dropped
|
|
33744
|
+
p?.phase(`Recovering ${missing.length} file${missing.length === 1 ? "" : "s"} the bulk download dropped...`);
|
|
33641
33745
|
for (const a of missing) {
|
|
33642
33746
|
const expectedSha = remote.get(a.path)?.sha256 ?? void 0;
|
|
33643
33747
|
let buf = null;
|
|
@@ -33679,7 +33783,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33679
33783
|
if (currentBytes) {
|
|
33680
33784
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33681
33785
|
writeFileSync5(full, currentBytes);
|
|
33682
|
-
const stat2 =
|
|
33786
|
+
const stat2 = statSync2(full);
|
|
33683
33787
|
baseline.files[a.path] = {
|
|
33684
33788
|
size: stat2.size,
|
|
33685
33789
|
mtime: stat2.mtime.toISOString(),
|
|
@@ -33695,12 +33799,12 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33695
33799
|
renamedRel,
|
|
33696
33800
|
{ expectedServerVersion: null }
|
|
33697
33801
|
);
|
|
33698
|
-
const stat2 =
|
|
33699
|
-
const { sha256
|
|
33802
|
+
const stat2 = statSync2(renamedFull);
|
|
33803
|
+
const { sha256 } = await hashFile(renamedFull);
|
|
33700
33804
|
baseline.files[renamedRel] = {
|
|
33701
33805
|
size: stat2.size,
|
|
33702
33806
|
mtime: stat2.mtime.toISOString(),
|
|
33703
|
-
sha256
|
|
33807
|
+
sha256,
|
|
33704
33808
|
serverVersion: result.serverVersion
|
|
33705
33809
|
};
|
|
33706
33810
|
} catch (e) {
|
|
@@ -33736,7 +33840,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33736
33840
|
}
|
|
33737
33841
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33738
33842
|
writeFileSync5(full, buf);
|
|
33739
|
-
const stat2 =
|
|
33843
|
+
const stat2 = statSync2(full);
|
|
33740
33844
|
local.set(a.path, { size: stat2.size, mtime: stat2.mtime.toISOString(), sha256: void 0 });
|
|
33741
33845
|
baseline.files[a.path] = {
|
|
33742
33846
|
size: stat2.size,
|
|
@@ -33767,7 +33871,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33767
33871
|
if (buf) {
|
|
33768
33872
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33769
33873
|
writeFileSync5(full, buf);
|
|
33770
|
-
const stat2 =
|
|
33874
|
+
const stat2 = statSync2(full);
|
|
33771
33875
|
baseline.files[a.path] = {
|
|
33772
33876
|
size: stat2.size,
|
|
33773
33877
|
mtime: stat2.mtime.toISOString(),
|
|
@@ -33781,12 +33885,12 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33781
33885
|
const result = await uploadOneFile(config.projectGuid, renamed, a.renamedLocalTo, {
|
|
33782
33886
|
expectedServerVersion: null
|
|
33783
33887
|
});
|
|
33784
|
-
const stat2 =
|
|
33785
|
-
const { sha256
|
|
33888
|
+
const stat2 = statSync2(renamed);
|
|
33889
|
+
const { sha256 } = await hashFile(renamed);
|
|
33786
33890
|
baseline.files[a.renamedLocalTo] = {
|
|
33787
33891
|
size: stat2.size,
|
|
33788
33892
|
mtime: stat2.mtime.toISOString(),
|
|
33789
|
-
sha256
|
|
33893
|
+
sha256,
|
|
33790
33894
|
serverVersion: result.serverVersion
|
|
33791
33895
|
};
|
|
33792
33896
|
} catch (err) {
|
|
@@ -33820,14 +33924,14 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33820
33924
|
continue;
|
|
33821
33925
|
}
|
|
33822
33926
|
try {
|
|
33823
|
-
const stat2 =
|
|
33927
|
+
const stat2 = statSync2(full);
|
|
33824
33928
|
if (stat2.size > UPLOAD_MAX_BYTES) {
|
|
33825
33929
|
errors.push(`Upload failed for ${a.path}: file exceeds the 30 GB upload limit`);
|
|
33826
33930
|
onBytes?.(a.localSize ?? 0);
|
|
33827
33931
|
continue;
|
|
33828
33932
|
}
|
|
33829
|
-
const
|
|
33830
|
-
prepared.push({ a, full, size: stat2.size, mtime: stat2.mtime.toISOString(), sha256
|
|
33933
|
+
const sha256 = local.get(a.path)?.sha256 ?? (await hashFile(full)).sha256;
|
|
33934
|
+
prepared.push({ a, full, size: stat2.size, mtime: stat2.mtime.toISOString(), sha256 });
|
|
33831
33935
|
} catch (e) {
|
|
33832
33936
|
errors.push(`Upload failed for ${a.path}: ${e.message}`);
|
|
33833
33937
|
onBytes?.(a.localSize ?? 0);
|
|
@@ -33974,13 +34078,13 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33974
34078
|
const full = resolveInRoot(root, a.path);
|
|
33975
34079
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33976
34080
|
writeFileSync5(full, buf);
|
|
33977
|
-
const stat2 =
|
|
33978
|
-
const { sha256
|
|
34081
|
+
const stat2 = statSync2(full);
|
|
34082
|
+
const { sha256 } = await hashFile(full);
|
|
33979
34083
|
const current = typeof err.data?.current_server_version === "number" ? err.data.current_server_version : null;
|
|
33980
34084
|
baseline.files[a.path] = {
|
|
33981
34085
|
size: stat2.size,
|
|
33982
34086
|
mtime: stat2.mtime.toISOString(),
|
|
33983
|
-
sha256
|
|
34087
|
+
sha256,
|
|
33984
34088
|
serverVersion: current ?? a.expectedServerVersion ?? 0
|
|
33985
34089
|
};
|
|
33986
34090
|
errors.push(`Could not delete ${a.path}: server has a newer version - restored the server copy locally (delete it again and re-sync to confirm)`);
|
|
@@ -34024,7 +34128,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
34024
34128
|
function cleanupEmptyDirs(root, emptiedDirs) {
|
|
34025
34129
|
const isEmpty = (dir) => {
|
|
34026
34130
|
try {
|
|
34027
|
-
return
|
|
34131
|
+
return readdirSync4(dir).length === 0;
|
|
34028
34132
|
} catch {
|
|
34029
34133
|
return false;
|
|
34030
34134
|
}
|
|
@@ -34059,12 +34163,12 @@ async function pushFile(filePath) {
|
|
|
34059
34163
|
const result = await uploadOneFile(config.projectGuid, filePath, rel2, {
|
|
34060
34164
|
expectedServerVersion: baseEntry ? baseEntry.serverVersion : null
|
|
34061
34165
|
});
|
|
34062
|
-
const stat2 =
|
|
34063
|
-
const { sha256
|
|
34166
|
+
const stat2 = statSync2(filePath);
|
|
34167
|
+
const { sha256 } = await hashFile(filePath);
|
|
34064
34168
|
baseline.files[rel2] = {
|
|
34065
34169
|
size: stat2.size,
|
|
34066
34170
|
mtime: stat2.mtime.toISOString(),
|
|
34067
|
-
sha256
|
|
34171
|
+
sha256,
|
|
34068
34172
|
serverVersion: result.serverVersion
|
|
34069
34173
|
};
|
|
34070
34174
|
writeBaseline(baseline);
|
|
@@ -34251,6 +34355,89 @@ async function syncBeforeAction(opts) {
|
|
|
34251
34355
|
}
|
|
34252
34356
|
}
|
|
34253
34357
|
|
|
34358
|
+
// src/helpers/body.ts
|
|
34359
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
34360
|
+
function readStdin() {
|
|
34361
|
+
if (process.stdin.isTTY) return "";
|
|
34362
|
+
try {
|
|
34363
|
+
return readFileSync8(0, "utf-8");
|
|
34364
|
+
} catch {
|
|
34365
|
+
return "";
|
|
34366
|
+
}
|
|
34367
|
+
}
|
|
34368
|
+
function readFileField(spec) {
|
|
34369
|
+
const eq2 = spec.indexOf("=");
|
|
34370
|
+
if (eq2 === -1) {
|
|
34371
|
+
throw new Error(`Invalid --file '${spec}': expected field=@path (e.g. --file image=@receipt.png).`);
|
|
34372
|
+
}
|
|
34373
|
+
const field = spec.slice(0, eq2).trim();
|
|
34374
|
+
let path5 = spec.slice(eq2 + 1);
|
|
34375
|
+
if (path5.startsWith("@")) path5 = path5.slice(1);
|
|
34376
|
+
if (!field) throw new Error(`Invalid --file '${spec}': missing field name before '='.`);
|
|
34377
|
+
if (!path5) throw new Error(`Invalid --file '${spec}': missing file path after '='.`);
|
|
34378
|
+
try {
|
|
34379
|
+
return [field, { data: readFileSync8(path5).toString("base64"), media_type: guessMime(path5) }];
|
|
34380
|
+
} catch (e) {
|
|
34381
|
+
throw new Error(`Cannot read --file '${path5}': ${e.message}`);
|
|
34382
|
+
}
|
|
34383
|
+
}
|
|
34384
|
+
function resolveBody(raw, fileSpecs) {
|
|
34385
|
+
const body = resolveJsonBody(raw);
|
|
34386
|
+
if (!fileSpecs || fileSpecs.length === 0) return body;
|
|
34387
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) {
|
|
34388
|
+
throw new Error("--file needs an object body to attach into (got a non-object JSON body).");
|
|
34389
|
+
}
|
|
34390
|
+
const obj = body;
|
|
34391
|
+
for (const spec of fileSpecs) {
|
|
34392
|
+
const [field, attachment] = readFileField(spec);
|
|
34393
|
+
obj[field] = attachment;
|
|
34394
|
+
}
|
|
34395
|
+
return obj;
|
|
34396
|
+
}
|
|
34397
|
+
function resolveJsonBody(raw) {
|
|
34398
|
+
if (raw == null || raw === "") return {};
|
|
34399
|
+
let source;
|
|
34400
|
+
let origin;
|
|
34401
|
+
if (raw === "-" || raw === "@-") {
|
|
34402
|
+
source = readStdin();
|
|
34403
|
+
origin = "stdin";
|
|
34404
|
+
if (source.trim() === "") {
|
|
34405
|
+
throw new Error("No JSON on stdin (pipe a body in, e.g. `cat body.json | gipity fn call foo -d -`).");
|
|
34406
|
+
}
|
|
34407
|
+
} else if (raw.startsWith("@")) {
|
|
34408
|
+
const path5 = raw.slice(1);
|
|
34409
|
+
try {
|
|
34410
|
+
source = readFileSync8(path5, "utf-8");
|
|
34411
|
+
} catch (e) {
|
|
34412
|
+
throw new Error(`Cannot read body file '${path5}': ${e.message}`);
|
|
34413
|
+
}
|
|
34414
|
+
origin = `file '${path5}'`;
|
|
34415
|
+
} else {
|
|
34416
|
+
source = raw;
|
|
34417
|
+
origin = "inline JSON";
|
|
34418
|
+
}
|
|
34419
|
+
try {
|
|
34420
|
+
return JSON.parse(source);
|
|
34421
|
+
} catch (e) {
|
|
34422
|
+
throw new Error(`Invalid JSON in ${origin}: ${e.message}`);
|
|
34423
|
+
}
|
|
34424
|
+
}
|
|
34425
|
+
|
|
34426
|
+
// src/helpers/duration.ts
|
|
34427
|
+
var UNIT_TO_MS = { ms: 1, s: 1e3, m: 6e4 };
|
|
34428
|
+
function parseDuration(raw, unit) {
|
|
34429
|
+
if (raw === void 0) return null;
|
|
34430
|
+
const m = raw.trim().match(/^(\d+(?:\.\d+)?)\s*(ms|s|m)?$/i);
|
|
34431
|
+
if (!m) return null;
|
|
34432
|
+
const n = parseFloat(m[1]);
|
|
34433
|
+
if (!Number.isFinite(n)) return null;
|
|
34434
|
+
const suffix = m[2]?.toLowerCase();
|
|
34435
|
+
if (!suffix) return { value: n, hadSuffix: false };
|
|
34436
|
+
const ms2 = n * UNIT_TO_MS[suffix];
|
|
34437
|
+
const value = unit === "ms" ? ms2 : ms2 / 1e3;
|
|
34438
|
+
return { value, hadSuffix: true };
|
|
34439
|
+
}
|
|
34440
|
+
|
|
34254
34441
|
// src/commands/token.ts
|
|
34255
34442
|
var tokenCommand = new Command("token").description("Manage API tokens").addHelpText("after", "\nLong-lived agent API tokens (gip_at_*) for headless agents and CI.");
|
|
34256
34443
|
var fmtDate = (d) => d ? new Date(d).toLocaleDateString() : "never";
|
|
@@ -34301,23 +34488,23 @@ tokenCommand.command("revoke <short_guid>").alias("rm").description("Revoke an a
|
|
|
34301
34488
|
init_api();
|
|
34302
34489
|
init_config();
|
|
34303
34490
|
init_auth();
|
|
34304
|
-
import { basename, resolve as resolve7, dirname as dirname5 } from "path";
|
|
34305
|
-
import { existsSync as existsSync8, readFileSync as
|
|
34491
|
+
import { basename as basename2, resolve as resolve7, dirname as dirname5 } from "path";
|
|
34492
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
34306
34493
|
init_colors();
|
|
34307
34494
|
init_utils();
|
|
34308
34495
|
|
|
34309
34496
|
// src/adopt-cwd.ts
|
|
34310
34497
|
init_api();
|
|
34311
|
-
import { readdirSync as
|
|
34312
|
-
import { join as
|
|
34498
|
+
import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
34499
|
+
import { join as join9, resolve as resolve6, sep as sep2, parse as parsePath } from "path";
|
|
34313
34500
|
import { homedir as homedir6 } from "os";
|
|
34314
34501
|
|
|
34315
34502
|
// src/project-setup.ts
|
|
34316
34503
|
init_config();
|
|
34317
34504
|
|
|
34318
34505
|
// src/template-vars.ts
|
|
34319
|
-
import { promises as fs, readdirSync as
|
|
34320
|
-
import { join as
|
|
34506
|
+
import { promises as fs, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
34507
|
+
import { join as join6, relative as relative2, extname as extname3 } from "path";
|
|
34321
34508
|
var SUBSTITUTABLE_EXTS = /* @__PURE__ */ new Set([
|
|
34322
34509
|
".html",
|
|
34323
34510
|
".htm",
|
|
@@ -34383,18 +34570,18 @@ function* walkTextFiles(root) {
|
|
|
34383
34570
|
const dir = stack.pop();
|
|
34384
34571
|
let entries;
|
|
34385
34572
|
try {
|
|
34386
|
-
entries =
|
|
34573
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
34387
34574
|
} catch {
|
|
34388
34575
|
continue;
|
|
34389
34576
|
}
|
|
34390
34577
|
for (const entry of entries) {
|
|
34391
34578
|
if (isSyncIgnored(entry.name)) continue;
|
|
34392
|
-
const full =
|
|
34579
|
+
const full = join6(dir, entry.name);
|
|
34393
34580
|
if (entry.isDirectory()) {
|
|
34394
34581
|
stack.push(full);
|
|
34395
34582
|
} else if (entry.isFile()) {
|
|
34396
34583
|
try {
|
|
34397
|
-
if (!
|
|
34584
|
+
if (!statSync3(full).isFile()) continue;
|
|
34398
34585
|
} catch {
|
|
34399
34586
|
continue;
|
|
34400
34587
|
}
|
|
@@ -34411,7 +34598,7 @@ async function substituteDir(dir, vars) {
|
|
|
34411
34598
|
for (const rel2 of walkTextFiles(dir)) {
|
|
34412
34599
|
const ext = extname3(rel2).toLowerCase();
|
|
34413
34600
|
if (!SUBSTITUTABLE_EXTS.has(ext)) continue;
|
|
34414
|
-
const abs =
|
|
34601
|
+
const abs = join6(dir, rel2);
|
|
34415
34602
|
let content;
|
|
34416
34603
|
try {
|
|
34417
34604
|
content = await fs.readFile(abs, "utf-8");
|
|
@@ -34477,18 +34664,18 @@ async function finalizeLocalProject(opts) {
|
|
|
34477
34664
|
}
|
|
34478
34665
|
|
|
34479
34666
|
// src/relay/paths.ts
|
|
34480
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as
|
|
34667
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
34481
34668
|
import { homedir as homedir4 } from "os";
|
|
34482
|
-
import { join as
|
|
34669
|
+
import { join as join7, resolve as resolve5 } from "path";
|
|
34483
34670
|
function getProjectsRoot() {
|
|
34484
|
-
const settingsPath =
|
|
34485
|
-
const defaultDir =
|
|
34671
|
+
const settingsPath = join7(homedir4(), ".gipity", "settings.json");
|
|
34672
|
+
const defaultDir = join7(homedir4(), "GipityProjects");
|
|
34486
34673
|
try {
|
|
34487
34674
|
if (existsSync6(settingsPath)) {
|
|
34488
|
-
const settings = JSON.parse(
|
|
34675
|
+
const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
34489
34676
|
if (settings.projectsDir) return resolve5(settings.projectsDir);
|
|
34490
34677
|
} else {
|
|
34491
|
-
mkdirSync5(
|
|
34678
|
+
mkdirSync5(join7(homedir4(), ".gipity"), { recursive: true });
|
|
34492
34679
|
writeFileSync6(settingsPath, JSON.stringify({ projectsDir: defaultDir }, null, 2) + "\n");
|
|
34493
34680
|
}
|
|
34494
34681
|
} catch {
|
|
@@ -34497,11 +34684,11 @@ function getProjectsRoot() {
|
|
|
34497
34684
|
}
|
|
34498
34685
|
|
|
34499
34686
|
// src/relay/state.ts
|
|
34500
|
-
import { readFileSync as
|
|
34501
|
-
import { join as
|
|
34687
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
34688
|
+
import { join as join8 } from "path";
|
|
34502
34689
|
import { homedir as homedir5 } from "os";
|
|
34503
|
-
var RELAY_DIR = process.env.GIPITY_DIR ||
|
|
34504
|
-
var RELAY_FILE =
|
|
34690
|
+
var RELAY_DIR = process.env.GIPITY_DIR || join8(homedir5(), ".gipity");
|
|
34691
|
+
var RELAY_FILE = join8(RELAY_DIR, "relay.json");
|
|
34505
34692
|
var FILE_MODE = 384;
|
|
34506
34693
|
function emptyState() {
|
|
34507
34694
|
return { device: null, paused: false };
|
|
@@ -34509,7 +34696,7 @@ function emptyState() {
|
|
|
34509
34696
|
function loadState() {
|
|
34510
34697
|
if (!existsSync7(RELAY_FILE)) return emptyState();
|
|
34511
34698
|
try {
|
|
34512
|
-
const raw = JSON.parse(
|
|
34699
|
+
const raw = JSON.parse(readFileSync10(RELAY_FILE, "utf-8"));
|
|
34513
34700
|
return {
|
|
34514
34701
|
device: raw.device ?? null,
|
|
34515
34702
|
paused: Boolean(raw.paused),
|
|
@@ -34594,7 +34781,7 @@ function diagnosticsConsented() {
|
|
|
34594
34781
|
if (env && env !== "0" && env.toLowerCase() !== "false") return false;
|
|
34595
34782
|
return loadState().diagnostics_consent !== false;
|
|
34596
34783
|
}
|
|
34597
|
-
var RELAY_PID_FILE =
|
|
34784
|
+
var RELAY_PID_FILE = join8(RELAY_DIR, "relay.pid");
|
|
34598
34785
|
function getDaemonPidPath() {
|
|
34599
34786
|
return RELAY_PID_FILE;
|
|
34600
34787
|
}
|
|
@@ -34611,7 +34798,7 @@ function clearDaemonPid() {
|
|
|
34611
34798
|
function isDaemonRunning() {
|
|
34612
34799
|
if (!existsSync7(RELAY_PID_FILE)) return false;
|
|
34613
34800
|
try {
|
|
34614
|
-
const raw =
|
|
34801
|
+
const raw = readFileSync10(RELAY_PID_FILE, "utf-8").trim();
|
|
34615
34802
|
const pid = parseInt(raw, 10);
|
|
34616
34803
|
if (!pid || isNaN(pid)) {
|
|
34617
34804
|
try {
|
|
@@ -34651,17 +34838,17 @@ function scanForAdoption(cwd) {
|
|
|
34651
34838
|
if (truncated) return;
|
|
34652
34839
|
let entries;
|
|
34653
34840
|
try {
|
|
34654
|
-
entries =
|
|
34841
|
+
entries = readdirSync6(dir);
|
|
34655
34842
|
} catch {
|
|
34656
34843
|
return;
|
|
34657
34844
|
}
|
|
34658
34845
|
for (const name of entries) {
|
|
34659
34846
|
if (truncated) return;
|
|
34660
34847
|
if (isSyncIgnored(name)) continue;
|
|
34661
|
-
const full =
|
|
34848
|
+
const full = join9(dir, name);
|
|
34662
34849
|
let st2;
|
|
34663
34850
|
try {
|
|
34664
|
-
st2 =
|
|
34851
|
+
st2 = statSync4(full);
|
|
34665
34852
|
} catch {
|
|
34666
34853
|
continue;
|
|
34667
34854
|
}
|
|
@@ -34687,7 +34874,7 @@ function scanForAdoption(cwd) {
|
|
|
34687
34874
|
function isLikelyEmpty(cwd) {
|
|
34688
34875
|
let entries;
|
|
34689
34876
|
try {
|
|
34690
|
-
entries =
|
|
34877
|
+
entries = readdirSync6(cwd);
|
|
34691
34878
|
} catch {
|
|
34692
34879
|
return true;
|
|
34693
34880
|
}
|
|
@@ -34725,18 +34912,18 @@ function canAdoptCwd(cwd) {
|
|
|
34725
34912
|
function countGitRepoChildren(dir) {
|
|
34726
34913
|
let entries;
|
|
34727
34914
|
try {
|
|
34728
|
-
entries =
|
|
34915
|
+
entries = readdirSync6(dir);
|
|
34729
34916
|
} catch {
|
|
34730
34917
|
return 0;
|
|
34731
34918
|
}
|
|
34732
34919
|
let count = 0;
|
|
34733
34920
|
for (const name of entries.slice(0, 50)) {
|
|
34734
34921
|
if (name.startsWith(".")) continue;
|
|
34735
|
-
const child =
|
|
34922
|
+
const child = join9(dir, name);
|
|
34736
34923
|
try {
|
|
34737
|
-
if (!
|
|
34738
|
-
const gitPath =
|
|
34739
|
-
if (
|
|
34924
|
+
if (!statSync4(child).isDirectory()) continue;
|
|
34925
|
+
const gitPath = join9(child, ".git");
|
|
34926
|
+
if (statSync4(gitPath)) count++;
|
|
34740
34927
|
} catch {
|
|
34741
34928
|
}
|
|
34742
34929
|
if (count >= 3) return count;
|
|
@@ -34750,7 +34937,7 @@ function formatCwdLabel(cwd) {
|
|
|
34750
34937
|
if (norm.startsWith(home + sep2)) {
|
|
34751
34938
|
const tail = norm.slice(home.length + 1).split(sep2);
|
|
34752
34939
|
if (tail.length <= 2) return "~/" + tail.join("/");
|
|
34753
|
-
return "
|
|
34940
|
+
return "~/.../" + tail.slice(-2).join("/");
|
|
34754
34941
|
}
|
|
34755
34942
|
const parts = norm.split(sep2).filter(Boolean);
|
|
34756
34943
|
if (parts.length <= 1) return norm;
|
|
@@ -34915,7 +35102,7 @@ Working with an existing Gipity project:
|
|
|
34915
35102
|
process.exit(1);
|
|
34916
35103
|
}
|
|
34917
35104
|
}
|
|
34918
|
-
const projectName = (name ||
|
|
35105
|
+
const projectName = (name || basename2(cwd)).slice(0, 100);
|
|
34919
35106
|
const projectSlug = slugify(projectName);
|
|
34920
35107
|
if (!projectSlug) {
|
|
34921
35108
|
console.error(error("Could not derive a valid project slug. Provide a name: gipity init my-app"));
|
|
@@ -34957,7 +35144,7 @@ Working with an existing Gipity project:
|
|
|
34957
35144
|
if (adopted.applied > 0) console.log(`Synced ${adopted.applied} change${adopted.applied > 1 ? "s" : ""} with Gipity.`);
|
|
34958
35145
|
if (opts.capture === false) {
|
|
34959
35146
|
try {
|
|
34960
|
-
const cfg = JSON.parse(
|
|
35147
|
+
const cfg = JSON.parse(readFileSync11(resolve7(cwd, ".gipity.json"), "utf-8"));
|
|
34961
35148
|
cfg.captureHooks = false;
|
|
34962
35149
|
saveConfigAt(cwd, cfg);
|
|
34963
35150
|
} catch {
|
|
@@ -34991,15 +35178,15 @@ init_auth();
|
|
|
34991
35178
|
init_api();
|
|
34992
35179
|
init_config();
|
|
34993
35180
|
init_colors();
|
|
34994
|
-
import { existsSync as existsSync9, readFileSync as
|
|
34995
|
-
import { join as
|
|
35181
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
|
|
35182
|
+
import { join as join10, resolve as resolve8 } from "path";
|
|
34996
35183
|
import { homedir as homedir7 } from "os";
|
|
34997
35184
|
function checkGipityPlugin() {
|
|
34998
|
-
const path5 =
|
|
35185
|
+
const path5 = join10(homedir7(), ".claude", "settings.json");
|
|
34999
35186
|
let settings = {};
|
|
35000
35187
|
if (existsSync9(path5)) {
|
|
35001
35188
|
try {
|
|
35002
|
-
settings = JSON.parse(
|
|
35189
|
+
settings = JSON.parse(readFileSync12(path5, "utf-8"));
|
|
35003
35190
|
} catch {
|
|
35004
35191
|
}
|
|
35005
35192
|
}
|
|
@@ -35012,12 +35199,14 @@ function checkGipityPlugin() {
|
|
|
35012
35199
|
return { missing, ok: missing.length === 0, stale };
|
|
35013
35200
|
}
|
|
35014
35201
|
async function probeAuth(loggedIn) {
|
|
35015
|
-
if (!loggedIn && !usingEnvToken()) return "none";
|
|
35016
|
-
if (!usingEnvToken() && sessionExpired()) return "expired";
|
|
35017
|
-
const timeout = new Promise(
|
|
35202
|
+
if (!loggedIn && !usingEnvToken()) return { state: "none", account: null };
|
|
35203
|
+
if (!usingEnvToken() && sessionExpired()) return { state: "expired", account: null };
|
|
35204
|
+
const timeout = new Promise(
|
|
35205
|
+
(res) => setTimeout(() => res({ state: "unreachable", account: null }), 5e3).unref?.()
|
|
35206
|
+
);
|
|
35018
35207
|
const call = get("/users/me").then(
|
|
35019
|
-
() => "ok",
|
|
35020
|
-
(err) => err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable"
|
|
35208
|
+
(res) => ({ state: "ok", account: res.data?.accountSlug ?? null }),
|
|
35209
|
+
(err) => ({ state: err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable", account: null })
|
|
35021
35210
|
);
|
|
35022
35211
|
return Promise.race([call, timeout]);
|
|
35023
35212
|
}
|
|
@@ -35029,6 +35218,8 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35029
35218
|
const hookCheck = config ? checkGipityPlugin() : null;
|
|
35030
35219
|
const queueDelivered = auth && !sessionExpired() ? await flushBugQueue().catch(() => 0) : 0;
|
|
35031
35220
|
const probe = await probeAuth(!!auth);
|
|
35221
|
+
const accountMismatch = probe.state === "ok" && !!config && !!probe.account && probe.account !== config.accountSlug;
|
|
35222
|
+
const apiBaseInUse = resolveApiBase();
|
|
35032
35223
|
if (opts.json) {
|
|
35033
35224
|
console.log(JSON.stringify({
|
|
35034
35225
|
project: config ? {
|
|
@@ -35036,17 +35227,21 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35036
35227
|
slug: config.projectSlug,
|
|
35037
35228
|
account: config.accountSlug,
|
|
35038
35229
|
apiBase: config.apiBase,
|
|
35230
|
+
apiBaseInUse,
|
|
35039
35231
|
url: liveUrl(config)
|
|
35040
35232
|
} : null,
|
|
35041
35233
|
// `valid` reflects the refresh token (the real session) - access
|
|
35042
35234
|
// tokens auto-renew, so their expiry must not read as "invalid".
|
|
35043
35235
|
// `probe` is what one live call just proved: 'rejected' means every
|
|
35044
35236
|
// authenticated command will fail even though `valid` reads true.
|
|
35237
|
+
// 'mismatch' overrides 'ok' when the live account isn't the one that
|
|
35238
|
+
// owns this project - `valid` still reads true (the token IS valid).
|
|
35045
35239
|
auth: auth || usingEnvToken() ? {
|
|
35046
35240
|
email: auth?.email,
|
|
35241
|
+
account: probe.account,
|
|
35047
35242
|
source: usingEnvToken() ? "agent-token" : "session",
|
|
35048
|
-
valid: usingEnvToken() ? probe !== "rejected" : !sessionExpired(),
|
|
35049
|
-
probe
|
|
35243
|
+
valid: usingEnvToken() ? probe.state !== "rejected" : !sessionExpired(),
|
|
35244
|
+
probe: accountMismatch ? "mismatch" : probe.state
|
|
35050
35245
|
} : null,
|
|
35051
35246
|
plugin: hookCheck
|
|
35052
35247
|
}, null, 2));
|
|
@@ -35059,18 +35254,25 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35059
35254
|
console.log(`${muted("Account:")} ${config.accountSlug}`);
|
|
35060
35255
|
console.log(`${muted("Live:")} ${liveUrl(config)}`);
|
|
35061
35256
|
console.log(`${muted("API:")} ${config.apiBase}`);
|
|
35257
|
+
if (apiBaseInUse !== config.apiBase) {
|
|
35258
|
+
console.log(`${muted("API (in use):")} ${warning(apiBaseInUse)} ${muted("(overrides .gipity.json \u2014 GIPITY_API_BASE / --api-base / allowlist)")}`);
|
|
35259
|
+
}
|
|
35062
35260
|
if (config.agentGuid) console.log(`${muted("Agent:")} ${config.agentGuid}`);
|
|
35063
35261
|
}
|
|
35064
35262
|
if (usingEnvToken()) {
|
|
35065
|
-
console.log(`${muted("Auth:")} ${probe === "rejected" ? warning("agent API token (GIPITY_TOKEN) rejected by the server \u2014 mint a new one: gipity skill read agent-deploy") : success("agent API token (GIPITY_TOKEN)")}${probe === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35263
|
+
console.log(`${muted("Auth:")} ${probe.state === "rejected" ? warning("agent API token (GIPITY_TOKEN) rejected by the server \u2014 mint a new one: gipity skill read agent-deploy") : success("agent API token (GIPITY_TOKEN)")}${probe.state === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35066
35264
|
} else if (!auth) {
|
|
35067
35265
|
console.log(`${muted("Auth:")} ${warning("not logged in. Run: gipity login")}`);
|
|
35068
|
-
} else if (probe === "expired") {
|
|
35266
|
+
} else if (probe.state === "expired") {
|
|
35069
35267
|
console.log(`${muted("Auth:")} ${warning(`session expired for ${auth.email}. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
|
|
35070
|
-
} else if (probe === "rejected") {
|
|
35268
|
+
} else if (probe.state === "rejected") {
|
|
35071
35269
|
console.log(`${muted("Auth:")} ${warning(`session for ${auth.email} was rejected by the server. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
|
|
35072
35270
|
} else {
|
|
35073
|
-
console.log(`${muted("Auth:")} ${success(auth.email)}${probe === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35271
|
+
console.log(`${muted("Auth:")} ${success(auth.email)}${probe.state === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35272
|
+
}
|
|
35273
|
+
if (accountMismatch) {
|
|
35274
|
+
console.log(`${muted("Account:")} ${warning(`logged-in account (${probe.account}) differs from this project's account (${config.accountSlug}). If you didn't expect this you may be logged into the wrong account \u2014 run: gipity login`)}`);
|
|
35275
|
+
console.log(muted("(If this project was shared with you via gipity rbac, this is expected.)"));
|
|
35074
35276
|
}
|
|
35075
35277
|
if (queueDelivered > 0) {
|
|
35076
35278
|
console.log(`${muted("Bug queue:")} ${success(`delivered ${queueDelivered} queued bug report${queueDelivered === 1 ? "" : "s"}`)}`);
|
|
@@ -35101,7 +35303,10 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35101
35303
|
});
|
|
35102
35304
|
|
|
35103
35305
|
// src/commands/sync.ts
|
|
35306
|
+
import { dirname as dirname6 } from "node:path";
|
|
35104
35307
|
init_colors();
|
|
35308
|
+
init_utils();
|
|
35309
|
+
init_config();
|
|
35105
35310
|
var syncCommand = new Command("sync").description("Sync files").addHelpText("after", "\nAdd a .gipityignore at the project root to exclude paths (gitignore-style).").option("--plan", "Print the plan without applying any changes").option("--force", "Bypass the bulk-deletion guard").option("--prune", "Remove files that exist on Gipity but not locally (applies the bulk deletes the guard defers)").option("--json", "Output as JSON").action(async (opts) => {
|
|
35106
35311
|
try {
|
|
35107
35312
|
const progress = opts.json ? void 0 : createProgressReporter();
|
|
@@ -35114,6 +35319,16 @@ var syncCommand = new Command("sync").description("Sync files").addHelpText("aft
|
|
|
35114
35319
|
console.log(`
|
|
35115
35320
|
${result.applied} action${result.applied > 1 ? "s" : ""} applied.`);
|
|
35116
35321
|
}
|
|
35322
|
+
if (isWsl()) {
|
|
35323
|
+
const configPath = getConfigPath();
|
|
35324
|
+
const twin = configPath ? findWindowsTwinProject(dirname6(configPath)) : null;
|
|
35325
|
+
if (twin) {
|
|
35326
|
+
console.log(muted(
|
|
35327
|
+
`
|
|
35328
|
+
Note: a Windows-side copy of this project exists at ${wslPathToWindows(twin)} and is NOT synced. If you added files there, move them into this folder so they sync.`
|
|
35329
|
+
));
|
|
35330
|
+
}
|
|
35331
|
+
}
|
|
35117
35332
|
if (result.deferredDeletes > 0) {
|
|
35118
35333
|
console.log(muted(
|
|
35119
35334
|
`
|
|
@@ -35159,106 +35374,60 @@ var pushCommand = new Command("push").description("Push one or more files").argu
|
|
|
35159
35374
|
});
|
|
35160
35375
|
|
|
35161
35376
|
// src/commands/upload.ts
|
|
35377
|
+
init_api();
|
|
35162
35378
|
init_config();
|
|
35163
|
-
import { statSync as
|
|
35164
|
-
import {
|
|
35379
|
+
import { statSync as statSync5 } from "fs";
|
|
35380
|
+
import { basename as basename3 } from "path";
|
|
35165
35381
|
init_utils();
|
|
35166
35382
|
init_colors();
|
|
35167
|
-
|
|
35168
|
-
|
|
35169
|
-
|
|
35170
|
-
|
|
35171
|
-
|
|
35172
|
-
|
|
35173
|
-
|
|
35174
|
-
|
|
35175
|
-
|
|
35176
|
-
|
|
35177
|
-
|
|
35178
|
-
|
|
35179
|
-
|
|
35180
|
-
function destFor(localFile, srcRoot, destRoot) {
|
|
35181
|
-
const rel2 = localFile.slice(srcRoot.length).replace(/\\/g, "/").replace(/^\/+/, "");
|
|
35182
|
-
return posix.join(destRoot.replace(/\\/g, "/").replace(/\/$/, ""), rel2);
|
|
35183
|
-
}
|
|
35184
|
-
var uploadCommand = new Command("upload").description("Upload files").argument("<src>", "Local source file or directory").argument("[dest]", "Destination path in the project (defaults to /)").option("-r, --recursive", "Upload a directory recursively").option("--mime <type>", "Override the content-type (default: detect from extension)").option("--concurrency <n>", `Parallel files (default ${UPLOAD_CONCURRENCY})`).option("--dry-run", "Print what would be uploaded; do not call the network").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").action(async (src, destArg, opts) => {
|
|
35383
|
+
var uploadCommand = new Command("upload").description(`Upload a local file and print a durable, worker-reachable URL for it.
|
|
35384
|
+
|
|
35385
|
+
The URL works immediately - no \`gipity deploy\` - and any job or function can
|
|
35386
|
+
fetch it. Use it to hand a GPU/CPU job a real input file when testing end-to-end:
|
|
35387
|
+
|
|
35388
|
+
gipity upload song.mp3
|
|
35389
|
+
gipity job submit split-stems --data '{"audio_url":"<printed url>"}'
|
|
35390
|
+
|
|
35391
|
+
By default the file is PUBLIC: a plain \`media.gipity.ai\` CDN url that resolves
|
|
35392
|
+
from anywhere, so a cloud worker can always fetch it. Pass --private for a
|
|
35393
|
+
token-signed serve url instead (reachable only by holders of the url).`).argument("<file>", "Local file to upload").option("--private", "Store as a private token-signed serve URL instead of a public CDN url").option("--content-type <mime>", "Override the content type (default: detected from the file extension)").option("--json", "Output as JSON").action((file, opts) => run("Upload", async () => {
|
|
35394
|
+
const { config } = await resolveProjectContext();
|
|
35395
|
+
let size;
|
|
35185
35396
|
try {
|
|
35186
|
-
const
|
|
35187
|
-
|
|
35188
|
-
|
|
35189
|
-
const planned = [];
|
|
35190
|
-
if (srcStat.isDirectory()) {
|
|
35191
|
-
if (!opts.recursive) {
|
|
35192
|
-
throw new Error(`${src} is a directory - pass -r/--recursive to upload it`);
|
|
35193
|
-
}
|
|
35194
|
-
const srcAbs = resolve10(src);
|
|
35195
|
-
const sliceRoot = dirname6(srcAbs);
|
|
35196
|
-
for (const file of walkFiles(src)) {
|
|
35197
|
-
planned.push({
|
|
35198
|
-
localPath: file,
|
|
35199
|
-
virtualPath: destFor(resolve10(file), sliceRoot, dest),
|
|
35200
|
-
size: statSync4(file).size
|
|
35201
|
-
});
|
|
35202
|
-
}
|
|
35203
|
-
} else if (srcStat.isFile()) {
|
|
35204
|
-
const looksLikeDir = dest.endsWith("/") || opts.recursive === true;
|
|
35205
|
-
const virtualPath = looksLikeDir ? posix.join(dest.replace(/\/$/, ""), basename2(src)) : dest;
|
|
35206
|
-
planned.push({ localPath: src, virtualPath, size: srcStat.size });
|
|
35207
|
-
} else {
|
|
35208
|
-
throw new Error(`${src} is neither a regular file nor a directory`);
|
|
35209
|
-
}
|
|
35210
|
-
if (planned.length === 0) {
|
|
35211
|
-
console.log("Nothing to upload (0 files).");
|
|
35212
|
-
return;
|
|
35213
|
-
}
|
|
35214
|
-
const totalBytes = planned.reduce((s, f) => s + f.size, 0);
|
|
35215
|
-
console.log(`Plan: ${planned.length} file${planned.length > 1 ? "s" : ""}, ${formatSize(totalBytes)}`);
|
|
35216
|
-
for (const f of planned) {
|
|
35217
|
-
console.log(`${f.localPath} ${dim("\u2192")} ${f.virtualPath} (${formatSize(f.size)})`);
|
|
35218
|
-
}
|
|
35219
|
-
if (opts.dryRun) {
|
|
35220
|
-
console.log("\n--dry-run: skipping all network calls.");
|
|
35221
|
-
return;
|
|
35222
|
-
}
|
|
35223
|
-
const concurrency = Math.max(1, parseInt(opts.concurrency ?? String(UPLOAD_CONCURRENCY), 10));
|
|
35224
|
-
const uploadOpts = { mime: opts.mime };
|
|
35225
|
-
let cursor = 0;
|
|
35226
|
-
let uploaded = 0, skipped = 0, resumed = 0, failed = 0;
|
|
35227
|
-
const workers = [];
|
|
35228
|
-
for (let w = 0; w < Math.min(concurrency, planned.length); w++) {
|
|
35229
|
-
workers.push((async () => {
|
|
35230
|
-
while (true) {
|
|
35231
|
-
const idx = cursor++;
|
|
35232
|
-
if (idx >= planned.length) return;
|
|
35233
|
-
const f = planned[idx];
|
|
35234
|
-
try {
|
|
35235
|
-
const result = await uploadOneFile(config.projectGuid, f.localPath, f.virtualPath, uploadOpts);
|
|
35236
|
-
if (result.status === "skipped") {
|
|
35237
|
-
skipped++;
|
|
35238
|
-
console.log(`${dim("skip")} ${f.virtualPath} (already current)`);
|
|
35239
|
-
} else if (result.status === "resumed") {
|
|
35240
|
-
resumed++;
|
|
35241
|
-
console.log(`${dim("resumed")} ${f.virtualPath} v${result.version}`);
|
|
35242
|
-
} else {
|
|
35243
|
-
uploaded++;
|
|
35244
|
-
console.log(`${dim("uploaded")} ${f.virtualPath} v${result.version}`);
|
|
35245
|
-
}
|
|
35246
|
-
} catch (err) {
|
|
35247
|
-
failed++;
|
|
35248
|
-
console.error(error(` fail ${f.virtualPath}: ${err.message}`));
|
|
35249
|
-
}
|
|
35250
|
-
}
|
|
35251
|
-
})());
|
|
35252
|
-
}
|
|
35253
|
-
await Promise.all(workers);
|
|
35254
|
-
console.log(`
|
|
35255
|
-
Uploaded: ${uploaded}, Resumed: ${resumed}, Skipped: ${skipped}, Failed: ${failed}`);
|
|
35256
|
-
if (failed > 0) process.exit(1);
|
|
35397
|
+
const st2 = statSync5(file);
|
|
35398
|
+
if (!st2.isFile()) throw new Error("not a regular file");
|
|
35399
|
+
size = st2.size;
|
|
35257
35400
|
} catch (err) {
|
|
35258
|
-
|
|
35259
|
-
|
|
35401
|
+
throw new Error(`can't read ${file}: ${err.message}`);
|
|
35402
|
+
}
|
|
35403
|
+
if (size === 0) throw new Error(`${file} is empty - nothing to upload.`);
|
|
35404
|
+
const filename = basename3(file);
|
|
35405
|
+
const contentType = opts.contentType || guessMime(file);
|
|
35406
|
+
const doUpload = async () => {
|
|
35407
|
+
const init = await post(`/api/${config.projectGuid}/uploads/init`, {
|
|
35408
|
+
filename,
|
|
35409
|
+
content_type: contentType,
|
|
35410
|
+
size,
|
|
35411
|
+
public: !opts.private
|
|
35412
|
+
});
|
|
35413
|
+
const fields = await transferToS3(file, size, contentType, init.data);
|
|
35414
|
+
const completeBody = {
|
|
35415
|
+
upload_guid: init.data.upload_guid
|
|
35416
|
+
};
|
|
35417
|
+
if ("parts" in fields) completeBody.parts = fields.parts;
|
|
35418
|
+
const comp = await post(`/api/${config.projectGuid}/uploads/complete`, completeBody);
|
|
35419
|
+
return comp.data;
|
|
35420
|
+
};
|
|
35421
|
+
const data = opts.json ? await doUpload() : await withSpinner(`Uploading ${filename} (${formatSize(size)})...`, doUpload, { done: null });
|
|
35422
|
+
if (opts.json) {
|
|
35423
|
+
console.log(JSON.stringify(data));
|
|
35424
|
+
return;
|
|
35260
35425
|
}
|
|
35261
|
-
});
|
|
35426
|
+
console.log(success(`Uploaded ${data.name} (${formatSize(data.size)})`));
|
|
35427
|
+
console.log(` ${brand(data.url)}`);
|
|
35428
|
+
console.log(muted(` Durable, worker-reachable URL - pass it straight to a job/function as an input URL.`));
|
|
35429
|
+
console.log(muted(` ${data.is_public ? "public (CDN)" : "private (token-signed)"} \xB7 guid: ${data.guid}`));
|
|
35430
|
+
}));
|
|
35262
35431
|
|
|
35263
35432
|
// src/commands/deploy.ts
|
|
35264
35433
|
init_api();
|
|
@@ -35273,7 +35442,7 @@ init_colors();
|
|
|
35273
35442
|
init_auth();
|
|
35274
35443
|
|
|
35275
35444
|
// src/commands/page-eval.ts
|
|
35276
|
-
import { readFileSync as
|
|
35445
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
35277
35446
|
init_api();
|
|
35278
35447
|
init_colors();
|
|
35279
35448
|
init_auth();
|
|
@@ -35282,8 +35451,8 @@ init_config();
|
|
|
35282
35451
|
// src/page-fixtures.ts
|
|
35283
35452
|
init_api();
|
|
35284
35453
|
init_config();
|
|
35285
|
-
import { readFileSync as
|
|
35286
|
-
import { basename as
|
|
35454
|
+
import { readFileSync as readFileSync13, statSync as statSync6, existsSync as existsSync10, readdirSync as readdirSync7 } from "node:fs";
|
|
35455
|
+
import { basename as basename4, resolve as resolve10, relative as relative3, join as join11 } from "node:path";
|
|
35287
35456
|
var FIND_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".gipity", "dist", "build", ".next", "coverage"]);
|
|
35288
35457
|
function findByBasename(root, name, limit = 3) {
|
|
35289
35458
|
const hits = [];
|
|
@@ -35309,22 +35478,22 @@ function findByBasename(root, name, limit = 3) {
|
|
|
35309
35478
|
return hits;
|
|
35310
35479
|
}
|
|
35311
35480
|
function assertLocalAsset(flag, localPath) {
|
|
35312
|
-
const abs =
|
|
35481
|
+
const abs = resolve10(localPath);
|
|
35313
35482
|
if (existsSync10(abs)) return;
|
|
35314
35483
|
const root = getProjectRoot() ?? process.cwd();
|
|
35315
|
-
const elsewhere = findByBasename(root,
|
|
35484
|
+
const elsewhere = findByBasename(root, basename4(localPath)).filter((p) => p !== abs);
|
|
35316
35485
|
const found = elsewhere.length ? `
|
|
35317
35486
|
That file DOES exist here \u2014 pass this path instead:
|
|
35318
35487
|
${elsewhere.map((p) => ` ${flag} ${relative3(process.cwd(), p) || p}`).join("\n")}` : `
|
|
35319
|
-
Nothing named "${
|
|
35488
|
+
Nothing named "${basename4(localPath)}" under ${root} either. Generate a frame with \`gipity generate image "<description>" -o ${localPath}\`, or check the path.`;
|
|
35320
35489
|
throw new Error(
|
|
35321
35490
|
`${flag} ${localPath}: no such file \u2014 looked for ${abs} (relative paths resolve against the current directory, ${process.cwd()}).${found}`
|
|
35322
35491
|
);
|
|
35323
35492
|
}
|
|
35324
35493
|
async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
|
|
35325
35494
|
assertLocalAsset(flag, localPath);
|
|
35326
|
-
const name =
|
|
35327
|
-
const size =
|
|
35495
|
+
const name = basename4(localPath);
|
|
35496
|
+
const size = statSync6(localPath).size;
|
|
35328
35497
|
const contentType = guessMime(localPath);
|
|
35329
35498
|
const init = await post(
|
|
35330
35499
|
`/api/${projectGuid}/uploads/init`,
|
|
@@ -35336,7 +35505,7 @@ async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
|
|
|
35336
35505
|
const res = await fetch(init.data.url, {
|
|
35337
35506
|
method: "PUT",
|
|
35338
35507
|
headers: { "Content-Type": contentType },
|
|
35339
|
-
body:
|
|
35508
|
+
body: readFileSync13(localPath)
|
|
35340
35509
|
});
|
|
35341
35510
|
if (!res.ok) {
|
|
35342
35511
|
throw new Error(`upload of fixture "${name}" failed: ${res.status} ${res.statusText}`);
|
|
@@ -35356,7 +35525,7 @@ function assertCameraFile(localPath) {
|
|
|
35356
35525
|
const ext = localPath.slice(localPath.lastIndexOf(".")).toLowerCase();
|
|
35357
35526
|
if (CAMERA_EXTS.includes(ext)) return;
|
|
35358
35527
|
throw new Error(
|
|
35359
|
-
`--camera ${
|
|
35528
|
+
`--camera ${basename4(localPath)}: unsupported file type "${ext || "(none)"}" \u2014 the camera feed must be an image or video (${CAMERA_EXTS.join(", ")}).
|
|
35360
35529
|
A still image is played as a looping single-frame feed, which is what a gesture/pose/object model needs.
|
|
35361
35530
|
No frame to hand? Generate one: gipity generate image "a hand making a closed fist, palm to camera, plain background"`
|
|
35362
35531
|
);
|
|
@@ -35409,7 +35578,7 @@ function summarizeExpr(expr) {
|
|
|
35409
35578
|
const oneLine = meaningful.length <= 1;
|
|
35410
35579
|
if (oneLine && expr.trim().length <= EXPR_ECHO_MAX_CHARS) return expr.trim();
|
|
35411
35580
|
const first = (meaningful[0] ?? "").trim();
|
|
35412
|
-
const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS -
|
|
35581
|
+
const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS - 3)}...` : first;
|
|
35413
35582
|
const shape = oneLine ? `(${expr.trim().length} chars)` : `(+${meaningful.length - 1} more ${meaningful.length - 1 === 1 ? "line" : "lines"}, ${expr.trim().length} chars)`;
|
|
35414
35583
|
return `${head} ${shape}`;
|
|
35415
35584
|
}
|
|
@@ -35435,6 +35604,9 @@ function capWaitForTimeoutMs(raw) {
|
|
|
35435
35604
|
));
|
|
35436
35605
|
return WAIT_FOR_MAX_MS;
|
|
35437
35606
|
}
|
|
35607
|
+
function readScriptFile(pathArg) {
|
|
35608
|
+
return readFileSync14(pathArg === "-" ? 0 : pathArg, "utf8");
|
|
35609
|
+
}
|
|
35438
35610
|
function parsesAsExpression(s) {
|
|
35439
35611
|
try {
|
|
35440
35612
|
new Function(`return (${s}
|
|
@@ -35455,6 +35627,7 @@ var CAMERA_SETUP_BUDGET_MS = 3e4;
|
|
|
35455
35627
|
var EVAL_SCRIPT_BUDGET_MS = 3e4;
|
|
35456
35628
|
var EVAL_SCRIPT_BUDGET_CAMERA_MS = 45e3;
|
|
35457
35629
|
var EVAL_SCRIPT_BUDGET_MAX_MS = 9e4;
|
|
35630
|
+
var EVAL_SCRIPT_BUDGET_MIN_MS = 1e3;
|
|
35458
35631
|
function capScriptBudgetMs(rawTimeout, hasMedia) {
|
|
35459
35632
|
const fallback = hasMedia ? EVAL_SCRIPT_BUDGET_CAMERA_MS : EVAL_SCRIPT_BUDGET_MS;
|
|
35460
35633
|
if (rawTimeout === void 0) return fallback;
|
|
@@ -35466,7 +35639,7 @@ function capScriptBudgetMs(rawTimeout, hasMedia) {
|
|
|
35466
35639
|
));
|
|
35467
35640
|
return EVAL_SCRIPT_BUDGET_MAX_MS;
|
|
35468
35641
|
}
|
|
35469
|
-
return Math.max(
|
|
35642
|
+
return Math.max(EVAL_SCRIPT_BUDGET_MIN_MS, parsed);
|
|
35470
35643
|
}
|
|
35471
35644
|
function evalWorkBudgetMs(o) {
|
|
35472
35645
|
const script = o.scriptBudgetMs ?? (o.hasCamera ? EVAL_SCRIPT_BUDGET_CAMERA_MS : EVAL_SCRIPT_BUDGET_MS);
|
|
@@ -35528,14 +35701,14 @@ function evalExecTimeoutMessage(result, budgetMs) {
|
|
|
35528
35701
|
If the slow part is a ONE-TIME page init (a WASM/model download, a big asset, a first-frame pipeline warm-up), do NOT split the body across several 'page eval' calls \u2014 every call is a fresh page load that re-pays that init, so each one hits this same wall. Have the page kick it off on load (not behind a click) and absorb it in the pre-eval window instead.`;
|
|
35529
35702
|
}
|
|
35530
35703
|
var JS_DECOY_FLAGS = ["--js", "--javascript", "--script", "--code", "--expr", "--eval", "--exec", "--action"];
|
|
35531
|
-
var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script). ONE client per call - to verify realtime/presence across concurrent clients use `page test --observe` instead").argument("<url>", "URL to load").argument("[expr]", `JavaScript to evaluate in page context (inline expression or statement body; await works, and the trailing expression is returned automatically \u2014 REPL-style \u2014 so no explicit return is needed; result is JSON-serialized). Omit when using --file. Time budget: the body has ${EVAL_SCRIPT_BUDGET_MS / 1e3}s to finish after page load (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera) - raise it with --timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s.`).option("--file <path>", `Read the script body from a file instead of the inline <expr> arg (mutually exclusive). Runs as an async function body, so top-level return/await work. Same post-load budget as <expr> (--timeout).`).option(
|
|
35704
|
+
var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script). ONE client per call - to verify realtime/presence across concurrent clients use `page test --observe` instead").argument("<url>", "URL to load").argument("[expr]", `JavaScript to evaluate in page context (inline expression or statement body; await works, and the trailing expression is returned automatically \u2014 REPL-style \u2014 so no explicit return is needed; result is JSON-serialized). Omit when using --file. Time budget: the body has ${EVAL_SCRIPT_BUDGET_MS / 1e3}s to finish after page load (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera) - raise it with --timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s.`).option("--file <path>", `Read the script body from a file instead of the inline <expr> arg (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' ... EOF) with no tmp file. Runs as an async function body, so top-level return/await work. Same post-load budget as <expr> (--timeout).`).option(
|
|
35532
35705
|
"--step <expr>",
|
|
35533
35706
|
`Run another expression against the SAME loaded page, after <expr> (repeat, max ${MAX_EVAL_STEPS}). Whatever that page load paid for \u2014 a vision model coming up, a game booting, a socket connecting \u2014 stays up for every step, so an N-part check costs ONE page load instead of N. Each step gets its own in-page budget and its own reported result.`,
|
|
35534
35707
|
(val, prev) => [...prev, val],
|
|
35535
35708
|
[]
|
|
35536
35709
|
).option(
|
|
35537
35710
|
"--fixture <path>",
|
|
35538
|
-
|
|
35711
|
+
'Host a local file and expose it to the eval as `fixtureUrl` (and under `fixtures` by basename) to fetch in-page. For verifying a render/parse path against a real binary (an MP3, an image) - no size limit, auto-deleted after the run. The hosted URL has permissive CORS, so `new Image(); img.crossOrigin="anonymous"; img.src=fixtureUrl` decodes untainted for a canvas/pixel read - this is the UPLOAD analog of --camera: feed a known photo/video to a file-upload vision app (web-vision-detect) and run its detector, no need to deploy a throwaway test asset into the app. Repeat for several files (single-value so it never swallows the inline <expr>).',
|
|
35539
35712
|
(val, prev) => [...prev, val],
|
|
35540
35713
|
[]
|
|
35541
35714
|
).option(
|
|
@@ -35546,7 +35719,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35546
35719
|
`Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, so a camera app's real pipeline (getUserMedia \u2192 MediaPipe/YOLOX \u2192 your app logic) runs headlessly on a frame you choose. Implies --fake-media and waits ${CAMERA_DEFAULT_WAIT_MS / 1e3}s for the vision model to load. No frame handy? gipity generate image "a hand making a closed fist, palm to camera".`
|
|
35547
35720
|
).option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so a voice/camera app runs headlessly instead of hitting its no-camera path. The feed is a built-in test pattern (and a tone) \u2014 nothing a vision model can recognize; to drive a vision app use --camera <path> instead.").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before evaluating (lets late async work settle; max 30000). With --wait-for, it elapses AFTER the selector appears - gate, then settle.", "500").option("--wait-for <selector>", `Wait until this CSS selector appears before evaluating, then evaluate (deterministic - beats guessing a --wait). Gate on the state you are ASSERTING, not just a ready flag: '#verdict:not(:empty)' returns the instant the round lands, where a fixed wait snapshots mid-sequence. Same flag on page inspect/screenshot. Max ${WAIT_FOR_MAX_MS}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (max ${WAIT_FOR_MAX_MS})`, "5000").option(
|
|
35548
35721
|
"--timeout <ms>",
|
|
35549
|
-
`How long the script itself may run IN the page
|
|
35722
|
+
`How long the script itself may run IN the page. Bare number = MILLISECONDS (so 90s = 90000, NOT 90); or pass an explicit unit that means the same on both this and \`sandbox run --timeout\` \u2014 --timeout 90s. Its own await/setTimeout pauses count (default ${EVAL_SCRIPT_BUDGET_MS}, ${EVAL_SCRIPT_BUDGET_CAMERA_MS} with --camera/--fake-media; floor 1000, max ${EVAL_SCRIPT_BUDGET_MAX_MS}). Raise it to trace a sequence that unfolds over time (a game round, an animation). Distinct from --wait, which only sleeps BEFORE the script.`
|
|
35550
35723
|
).option("--auth", "Evaluate signed in as you (your Gipity account), so a page behind a Sign-in-with-Gipity login is reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--json", "Output as JSON").action((url, exprArg, opts) => run("Page eval", async () => {
|
|
35551
35724
|
const decoy = JS_DECOY_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
35552
35725
|
if (decoy) {
|
|
@@ -35570,9 +35743,9 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35570
35743
|
let expr = exprArg;
|
|
35571
35744
|
if (opts.file) {
|
|
35572
35745
|
try {
|
|
35573
|
-
expr =
|
|
35746
|
+
expr = readScriptFile(opts.file);
|
|
35574
35747
|
} catch {
|
|
35575
|
-
pageEvalCommand.error(`error: Cannot read file: ${opts.file}`);
|
|
35748
|
+
pageEvalCommand.error(opts.file === "-" ? `error: --file - reads the script from stdin, but stdin was empty/unreadable - pipe it in, e.g. gipity page eval "<url>" --file - <<'EOF' ... EOF` : `error: Cannot read file: ${opts.file}`);
|
|
35576
35749
|
}
|
|
35577
35750
|
}
|
|
35578
35751
|
if (opts.reload !== void 0 && opts.reloadFile) {
|
|
@@ -35581,7 +35754,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35581
35754
|
let reloadExpr = opts.reload;
|
|
35582
35755
|
if (opts.reloadFile) {
|
|
35583
35756
|
try {
|
|
35584
|
-
reloadExpr =
|
|
35757
|
+
reloadExpr = readScriptFile(opts.reloadFile);
|
|
35585
35758
|
} catch {
|
|
35586
35759
|
pageEvalCommand.error(`error: Cannot read file: ${opts.reloadFile}`);
|
|
35587
35760
|
}
|
|
@@ -35592,6 +35765,20 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35592
35765
|
`error: at most ${MAX_EVAL_STEPS} --step expressions ride on one page load (got ${steps.length}) \u2014 do more per step, or split this into two evals`
|
|
35593
35766
|
);
|
|
35594
35767
|
}
|
|
35768
|
+
if (opts.timeout !== void 0) {
|
|
35769
|
+
const dur = parseDuration(opts.timeout, "ms");
|
|
35770
|
+
if (dur?.hadSuffix) opts.timeout = String(Math.round(dur.value));
|
|
35771
|
+
}
|
|
35772
|
+
if (opts.timeout !== void 0) {
|
|
35773
|
+
const t = parseInt(opts.timeout, 10);
|
|
35774
|
+
if (Number.isFinite(t) && t > 0 && t < EVAL_SCRIPT_BUDGET_MIN_MS) {
|
|
35775
|
+
const overMax = t * 1e3 > EVAL_SCRIPT_BUDGET_MAX_MS;
|
|
35776
|
+
const asMs = Math.min(t * 1e3, EVAL_SCRIPT_BUDGET_MAX_MS);
|
|
35777
|
+
throw new Error(
|
|
35778
|
+
`--timeout is in MILLISECONDS (got ${t}, under the ${EVAL_SCRIPT_BUDGET_MIN_MS}ms in-page floor). Unlike \`sandbox run --timeout\` (seconds), page eval's is ms \u2014 or pass an explicit unit that means the same on both: --timeout ${t}s. ${t} looks like seconds, so for ${t} second${t === 1 ? "" : "s"} pass --timeout ${t}s (= ${asMs}ms)${overMax ? ` (${t}s is over the ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s max, so this is the ceiling)` : ""}.`
|
|
35779
|
+
);
|
|
35780
|
+
}
|
|
35781
|
+
}
|
|
35595
35782
|
let waitMs = capWaitMs(opts.wait, url);
|
|
35596
35783
|
const waitForTimeoutMs = capWaitForTimeoutMs(opts.waitTimeout);
|
|
35597
35784
|
const hasMedia = !!opts.camera || !!opts.fakeMedia;
|
|
@@ -35620,12 +35807,12 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35620
35807
|
projectGuid = config.projectGuid;
|
|
35621
35808
|
}
|
|
35622
35809
|
if (opts.camera) {
|
|
35623
|
-
console.log(muted(`Hosting camera feed ${opts.camera}
|
|
35810
|
+
console.log(muted(`Hosting camera feed ${opts.camera}...`));
|
|
35624
35811
|
camera = await uploadCameraFeed(projectGuid, opts.camera);
|
|
35625
35812
|
}
|
|
35626
35813
|
if (fixturePaths.length) {
|
|
35627
35814
|
for (const p of fixturePaths) {
|
|
35628
|
-
console.log(muted(`Hosting fixture ${p}
|
|
35815
|
+
console.log(muted(`Hosting fixture ${p}...`));
|
|
35629
35816
|
hosted.push(await uploadPublicFixture(projectGuid, p));
|
|
35630
35817
|
}
|
|
35631
35818
|
const map = {};
|
|
@@ -35706,7 +35893,7 @@ ${hint}`);
|
|
|
35706
35893
|
}
|
|
35707
35894
|
if (camera) console.log(`${muted("Camera:")} ${camera.name} ${muted("(played as the webcam feed; getUserMedia resolves)")}`);
|
|
35708
35895
|
if (hosted.length) console.log(`${muted("Fixtures:")} ${hosted.map((h) => h.name).join(", ")}`);
|
|
35709
|
-
console.error(opts.file ? `${muted("Script:")} ${opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
35896
|
+
console.error(opts.file ? `${muted("Script:")} ${opts.file === "-" ? "(stdin)" : opts.file}` : `${muted("Expression:")} ${summarizeExpr(expr)}`);
|
|
35710
35897
|
console.log(`
|
|
35711
35898
|
${result.trim() ? result : muted("(empty result)")}`);
|
|
35712
35899
|
if (noValue) console.log(muted(`
|
|
@@ -35739,7 +35926,7 @@ ${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserve
|
|
|
35739
35926
|
}
|
|
35740
35927
|
}));
|
|
35741
35928
|
for (const f of JS_DECOY_FLAGS) pageEvalCommand.addOption(new Option(`${f} <value>`).hideHelp());
|
|
35742
|
-
pageEvalCommand.addHelpText("after", `
|
|
35929
|
+
pageEvalCommand.addHelpText("after", (context) => context.error ? "" : `
|
|
35743
35930
|
Examples:
|
|
35744
35931
|
gipity page eval "https://dev.gipity.ai/me/app/" "document.title"
|
|
35745
35932
|
# Functionally test a page's own code paths: save a script that drives the UI
|
|
@@ -35775,6 +35962,16 @@ Examples:
|
|
|
35775
35962
|
"window.__vision.gesture()" \\
|
|
35776
35963
|
--step "document.getElementById('see').textContent" \\
|
|
35777
35964
|
--step "({ score: score.textContent, verdict: verdict.textContent })"
|
|
35965
|
+
# File-UPLOAD vision app (web-vision-detect, no camera)? --fixture is the analog of
|
|
35966
|
+
# --camera: it hosts your test photo at a CORS-permissive URL, so you feed the kit's
|
|
35967
|
+
# detector a known image and read back the labels - no deploying a throwaway asset into
|
|
35968
|
+
# the app tree (and no cleanup redeploy). crossOrigin='anonymous' keeps the canvas clean.
|
|
35969
|
+
gipity generate image "street scene: three people, two cars, one dog" -o street.jpg
|
|
35970
|
+
gipity page eval "https://dev.gipity.ai/me/app/" --fixture street.jpg \\
|
|
35971
|
+
"const { createDetector } = await import('./packages/web-vision-detect/index.js'); \\
|
|
35972
|
+
const det = await createDetector({ model: 'nano' }); \\
|
|
35973
|
+
const img = new Image(); img.crossOrigin = 'anonymous'; img.src = fixtureUrl; await img.decode(); \\
|
|
35974
|
+
const { detections } = await det.detect(img); return detections.map(d => d.label);"
|
|
35778
35975
|
|
|
35779
35976
|
Module resolution: dynamic import() specifiers starting with ./ or ../ resolve
|
|
35780
35977
|
against the PAGE URL, so import('./packages/i18n/index.js') loads the app's own
|
|
@@ -35942,14 +36139,25 @@ var WAIT_FOR_MAX_MS2 = 15e3;
|
|
|
35942
36139
|
var WAIT_FOR_DEFAULT_MS = 15e3;
|
|
35943
36140
|
var MAX_POST_LOAD_DELAY_MS = 3e4;
|
|
35944
36141
|
var CAMERA_DEFAULT_DELAY_MS = 15e3;
|
|
35945
|
-
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").option("--device <names>", `Device preset(s): ${Object.keys(DEVICE_PRESETS).join(", ")} (comma-separated or repeat flag). mobile/tablet emulate a real touch device \u2014 touch events, mobile user-agent, DPR \u2014 so touch-gated mobile UI actually renders.`, appendOption, []).option("--viewport <dims>", "Raw viewport(s): WxH or WxH@dpr (comma-separated or repeat flag), e.g. --viewport 390x844 for a phone-sized window", appendOption, []).option("--wait <ms>", `Sleep this many ms after DOMContentLoaded before capturing (default 1000, max ${MAX_POST_LOAD_DELAY_MS}). With --camera it defaults to ${CAMERA_DEFAULT_DELAY_MS} so the vision model is up. Same flag as page eval/inspect.`).option("--wait-for <selector>", `Wait until this CSS selector appears before capturing, then capture (deterministic - beats guessing a --wait). Same flag as page eval/inspect. Gate on what you are photographing ('[data-vision="ready"]', '#verdict:not(:empty)'). Max ${WAIT_FOR_MAX_MS2}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (default ${WAIT_FOR_DEFAULT_MS}, max ${WAIT_FOR_MAX_MS2}). Timing out is reported - the capture still happens, so you see the state it got stuck in.`).option("--action <js>", `Run JS in the page before capturing
|
|
36142
|
+
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").option("--device <names>", `Device preset(s): ${Object.keys(DEVICE_PRESETS).join(", ")} (comma-separated or repeat flag). mobile/tablet emulate a real touch device \u2014 touch events, mobile user-agent, DPR \u2014 so touch-gated mobile UI actually renders.`, appendOption, []).option("--viewport <dims>", "Raw viewport(s): WxH or WxH@dpr (comma-separated or repeat flag), e.g. --viewport 390x844 for a phone-sized window", appendOption, []).option("--wait <ms>", `Sleep this many ms after DOMContentLoaded before capturing (default 1000, max ${MAX_POST_LOAD_DELAY_MS}). With --camera it defaults to ${CAMERA_DEFAULT_DELAY_MS} so the vision model is up. Same flag as page eval/inspect.`).option("--wait-for <selector>", `Wait until this CSS selector appears before capturing, then capture (deterministic - beats guessing a --wait). Same flag as page eval/inspect. Gate on what you are photographing ('[data-vision="ready"]', '#verdict:not(:empty)'). Max ${WAIT_FOR_MAX_MS2}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (default ${WAIT_FOR_DEFAULT_MS}, max ${WAIT_FOR_MAX_MS2}). Timing out is reported - the capture still happens, so you see the state it got stuck in.`).option("--action <js>", `Run JS in the page before capturing - 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('./...') work. Runs after the post-load delay, then settles again before the shot. If it throws, the capture still happens and the failure is reported.`).option("--file <path>", "Read the pre-capture script from a file instead of inline --action (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' ... EOF) with no tmp file. For a multi-step driver - click through a flow, wait for it to render - then capture. Same async-function-body semantics as --action; same --file flag as page eval.").option("--full", "Capture the full scrollable page (default: viewport only). Scrolls the page through first so scroll-reveal/fade-in-on-scroll (IntersectionObserver) sections render into the shot instead of capturing blank.").option("-o, --output <file>", "Output path (single viewport only; default .gipity/screenshots/ss-<host>-<timestamp>.png)").option("--no-reload-between", "Skip reload between viewports (faster, lower fidelity - only safe for static pages)").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps render headlessly. The video feed is a built-in test pattern \u2014 to capture what the app does with a REAL frame (a hand, a face, an object), use --camera <path> instead.").option("--camera <path>", "Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, then capture \u2014 so the shot shows the app reacting to a frame you chose (detected gesture, boxes, labels). Implies --fake-media.").option("--auth", "Capture the page signed in as you (your Gipity account), so UI behind a Sign-in-with-Gipity login is shown. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--ephemeral", "Skip the project screenshot history: do not persist this capture to Gipity (screenshots/ in the project). Local file is still written.").option("--json", "Output JSON metadata instead of a friendly summary").addOption(new Option("--post-load-delay <ms>", "Alias for --wait").hideHelp()).addOption(new Option("--full-page", "Alias for --full").hideHelp()).addOption(new Option("--width <px>", "Alias: --viewport WxH").hideHelp()).addOption(new Option("--height <px>", "Alias: --viewport WxH").hideHelp()).action((url, opts) => run("Page screenshot", async () => {
|
|
35946
36143
|
const aliasFlag = ACTION_ALIAS_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
35947
36144
|
if (aliasFlag) {
|
|
35948
36145
|
console.error(muted(
|
|
35949
36146
|
`Note: treating ${aliasFlag} as --action \u2014 it runs your JS in the page before the capture. --action is the canonical flag.`
|
|
35950
36147
|
));
|
|
35951
36148
|
}
|
|
35952
|
-
const
|
|
36149
|
+
const inlineAction = [opts.action, ...ACTION_ALIAS_FLAGS.map((f) => opts[f.slice(2)])].filter(Boolean).join("\n");
|
|
36150
|
+
if (opts.file && inlineAction) {
|
|
36151
|
+
throw new Error("Pass either --file <path> or an inline --action script, not both");
|
|
36152
|
+
}
|
|
36153
|
+
let actionScript = inlineAction;
|
|
36154
|
+
if (opts.file) {
|
|
36155
|
+
try {
|
|
36156
|
+
actionScript = readScriptFile(opts.file);
|
|
36157
|
+
} catch {
|
|
36158
|
+
throw new Error(opts.file === "-" ? `--file - reads the pre-capture script from stdin, but stdin was empty/unreadable - pipe it in, e.g. gipity page screenshot "<url>" --file - <<'EOF' ... EOF` : `Cannot read file: ${opts.file}`);
|
|
36159
|
+
}
|
|
36160
|
+
}
|
|
35953
36161
|
const delayRaw = opts.wait ?? opts.postLoadDelay;
|
|
35954
36162
|
const chosenDelay = delayRaw !== void 0;
|
|
35955
36163
|
let postLoadDelayMs = chosenDelay ? parseInt(String(delayRaw), 10) : 1e3;
|
|
@@ -36015,7 +36223,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36015
36223
|
if (opts.camera) {
|
|
36016
36224
|
assertCameraFile(opts.camera);
|
|
36017
36225
|
if (!projectGuid) throw new Error("--camera needs a linked project (the frame is hosted for the browser to fetch) \u2014 run `gipity link` first.");
|
|
36018
|
-
console.log(muted(`Hosting camera feed ${opts.camera}
|
|
36226
|
+
console.log(muted(`Hosting camera feed ${opts.camera}...`));
|
|
36019
36227
|
camera = await uploadCameraFeed(projectGuid, opts.camera);
|
|
36020
36228
|
}
|
|
36021
36229
|
const preCapture = [
|
|
@@ -36040,7 +36248,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36040
36248
|
let entries;
|
|
36041
36249
|
try {
|
|
36042
36250
|
try {
|
|
36043
|
-
entries = opts.json ? await doShoot() : await withSpinner("Capturing
|
|
36251
|
+
entries = opts.json ? await doShoot() : await withSpinner("Capturing...", doShoot, { done: null });
|
|
36044
36252
|
} catch (err) {
|
|
36045
36253
|
throw preCapture ? new Error(augmentSandboxTimeout(err.message)) : err;
|
|
36046
36254
|
}
|
|
@@ -36162,9 +36370,17 @@ Waiting for the page to reach a state before the shot?
|
|
|
36162
36370
|
|
|
36163
36371
|
Capturing a state that needs an interaction (start a game, open a menu, dismiss a modal)?
|
|
36164
36372
|
Use --action to run JS in the page before the shot \u2014 it fires after the wait
|
|
36165
|
-
(and after any --wait-for gate), then settles again so the result has painted.
|
|
36166
|
-
|
|
36167
|
-
|
|
36373
|
+
(and after any --wait-for gate), then settles again so the result has painted. For
|
|
36374
|
+
a multi-step driver (click, wait, click) pipe it as a heredoc with --file - (same
|
|
36375
|
+
as 'page eval') instead of cramming it into one inline string:
|
|
36376
|
+
gipity page screenshot "https://dev.gipity.ai/me/app/" --file - <<'EOF'
|
|
36377
|
+
document.getElementById('load-sample').click();
|
|
36378
|
+
await new Promise(r => setTimeout(r, 500));
|
|
36379
|
+
document.getElementById('run').click();
|
|
36380
|
+
await new Promise(r => setTimeout(r, 2000));
|
|
36381
|
+
EOF
|
|
36382
|
+
Do NOT hand-roll a 'page eval' that returns a base64 image: the eval result is
|
|
36383
|
+
capped (~16KB) and truncates the PNG.
|
|
36168
36384
|
|
|
36169
36385
|
Capturing an off-screen region or reading element data?
|
|
36170
36386
|
\u2022 --full captures the ENTIRE scrollable page (then crop to the region).
|
|
@@ -36184,10 +36400,10 @@ function shortUrl(url, truncate = true, maxLen = 100) {
|
|
|
36184
36400
|
result = url;
|
|
36185
36401
|
}
|
|
36186
36402
|
if (!truncate || result.length <= maxLen) return result;
|
|
36187
|
-
const keep = maxLen -
|
|
36403
|
+
const keep = maxLen - 3;
|
|
36188
36404
|
const headLen = Math.ceil(keep / 2);
|
|
36189
36405
|
const tailLen = Math.floor(keep / 2);
|
|
36190
|
-
return result.slice(0, headLen) + "
|
|
36406
|
+
return result.slice(0, headLen) + "..." + result.slice(-tailLen);
|
|
36191
36407
|
}
|
|
36192
36408
|
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. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").addOption(new Option("--screenshot [path]", "Capture a screenshot").hideHelp()).action((url, opts) => {
|
|
36193
36409
|
if (opts.screenshot !== void 0) {
|
|
@@ -36213,7 +36429,17 @@ async function inspectPage(url, opts = {}) {
|
|
|
36213
36429
|
auth: opts.auth || void 0
|
|
36214
36430
|
};
|
|
36215
36431
|
const res = await post(`/tools/browser/inspect`, inspectBody);
|
|
36216
|
-
const b7 = res
|
|
36432
|
+
const b7 = res?.data;
|
|
36433
|
+
if (!b7 || typeof b7 !== "object") {
|
|
36434
|
+
if (opts.json) {
|
|
36435
|
+
console.log(JSON.stringify({ url, captured: false, note: "inspector returned no page data" }));
|
|
36436
|
+
return;
|
|
36437
|
+
}
|
|
36438
|
+
console.log(`${brand("Inspecting")} ${bold(url)}`);
|
|
36439
|
+
console.log(warning("Captured no page data \u2014 the page returned an empty response."));
|
|
36440
|
+
console.log(muted("The page may have failed to load, redirected away, or rendered nothing server-side. Retry once; if it persists pass --wait-for <selector> to wait for a specific element to appear, or --auth if it is behind Sign in with Gipity."));
|
|
36441
|
+
return;
|
|
36442
|
+
}
|
|
36217
36443
|
const isPlatformLog = (entry) => {
|
|
36218
36444
|
const urlPart = entry.replace(/\s*\([^)]*\)\s*$/, "");
|
|
36219
36445
|
try {
|
|
@@ -36269,6 +36495,9 @@ async function inspectPage(url, opts = {}) {
|
|
|
36269
36495
|
console.log(`${muted("Title:")} ${b7.title || "(none)"}`);
|
|
36270
36496
|
console.log(`${muted("Elements:")} ${b7.elementCount || 0}`);
|
|
36271
36497
|
console.log(`${muted("Page weight:")} ${info(formatSize(b7.totalBytes || 0))}`);
|
|
36498
|
+
if ((b7.elementCount || 0) === 0 && !b7.title) {
|
|
36499
|
+
console.log(`${warning("\u26A0 Rendered no DOM (0 elements, no title)")} ${muted("\u2014 the client-side render likely had not run yet (try --wait-for <selector>), the page failed to load, or it is behind a sign-in gate (try --auth).")}`);
|
|
36500
|
+
}
|
|
36272
36501
|
console.log(`
|
|
36273
36502
|
${bold("Timing:")}`);
|
|
36274
36503
|
console.log(`${muted("TTFB:")} ${timing.ttfb}ms`);
|
|
@@ -36388,7 +36617,7 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
|
|
|
36388
36617
|
force: opts.force,
|
|
36389
36618
|
only: opts.only?.split(",").map((s) => s.trim())
|
|
36390
36619
|
});
|
|
36391
|
-
const res = opts.json ? await doDeploy() : await withSpinner(`Deploying to ${target}
|
|
36620
|
+
const res = opts.json ? await doDeploy() : await withSpinner(`Deploying to ${target}...`, doDeploy, { done: null });
|
|
36392
36621
|
const d = res.data;
|
|
36393
36622
|
const inspectAfter = async () => {
|
|
36394
36623
|
if (!opts.inspect) return;
|
|
@@ -36455,7 +36684,7 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
|
|
|
36455
36684
|
console.log(success(`\u2713 Deployed to ${target}`) + muted(` (${d.elapsedMs}ms)`));
|
|
36456
36685
|
if (d.url) console.log(`${muted("Live:")} ${brand(d.url)}`);
|
|
36457
36686
|
if (d.phases?.some((p) => p.name === "functions")) {
|
|
36458
|
-
console.log(`${muted("Functions:")} POST ${brand(`${
|
|
36687
|
+
console.log(`${muted("Functions:")} POST ${brand(`${resolveApiBase()}/api/${config.projectGuid}/fn/<name>`)}`);
|
|
36459
36688
|
console.log(muted(" (same address on dev and prod; names: gipity fn list; verify the public path: gipity fn call <name> --anon)"));
|
|
36460
36689
|
}
|
|
36461
36690
|
await inspectAfter();
|
|
@@ -36640,8 +36869,8 @@ memoryCommand.command("delete <topic>").description("Delete a topic").option("--
|
|
|
36640
36869
|
// src/commands/sandbox.ts
|
|
36641
36870
|
init_api();
|
|
36642
36871
|
init_config();
|
|
36643
|
-
import { readFileSync as
|
|
36644
|
-
import { dirname as dirname8, extname as extname4, relative as relative4, resolve as
|
|
36872
|
+
import { readFileSync as readFileSync15, existsSync as existsSync11, statSync as statSync7, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
36873
|
+
import { dirname as dirname8, extname as extname4, relative as relative4, resolve as resolve11, sep as sep3 } from "path";
|
|
36645
36874
|
init_colors();
|
|
36646
36875
|
var LANG_MAP = {
|
|
36647
36876
|
js: "javascript",
|
|
@@ -36750,6 +36979,32 @@ function explainSplitArgs(args) {
|
|
|
36750
36979
|
);
|
|
36751
36980
|
return lines.join("\n");
|
|
36752
36981
|
}
|
|
36982
|
+
var SCRATCH_DIR_PATTERNS = SCRATCH_IGNORE.map((p) => p.replace(/\/$/, "")).filter(Boolean).map((p) => p.replace(/[.]/g, "\\.").replace(/\*/g, "[A-Za-z0-9._-]*"));
|
|
36983
|
+
function scratchRefsInCode(code) {
|
|
36984
|
+
const dirs = SCRATCH_DIR_PATTERNS.join("|");
|
|
36985
|
+
if (!dirs) return [];
|
|
36986
|
+
const re2 = new RegExp(`(?:\\/work\\/|(?<![\\w/.]))(?:${dirs})\\/[A-Za-z0-9._\\-\\/]+`, "g");
|
|
36987
|
+
return [...new Set((code.match(re2) ?? []).map((m) => m.replace(/^\/work\//, "")))];
|
|
36988
|
+
}
|
|
36989
|
+
function isWriteTarget(code, p) {
|
|
36990
|
+
const path5 = p.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
36991
|
+
const m = new RegExp(`(?:\\/work\\/)?${path5}(?![\\w./-])`).exec(code);
|
|
36992
|
+
if (!m) return false;
|
|
36993
|
+
const idx = m.index;
|
|
36994
|
+
const before = code.slice(Math.max(0, idx - 40), idx);
|
|
36995
|
+
if (/(?:^|[\s'"`;(])(?:-o|-of|--out(?:put)?[= ]|>>?|tee(?:\s+-a)?)\s*['"`]?(?:\/work\/)?$/.test(before)) return true;
|
|
36996
|
+
const segStart = Math.max(
|
|
36997
|
+
code.lastIndexOf("&&", idx),
|
|
36998
|
+
code.lastIndexOf("||", idx),
|
|
36999
|
+
code.lastIndexOf(";", idx),
|
|
37000
|
+
code.lastIndexOf("|", idx),
|
|
37001
|
+
code.lastIndexOf("\n", idx)
|
|
37002
|
+
);
|
|
37003
|
+
const seg = code.slice(segStart + 1, idx).replace(/^[\s&|;]+/, "");
|
|
37004
|
+
const cmd = /^\s*([A-Za-z0-9_./-]+)/.exec(seg)?.[1]?.split("/").pop()?.toLowerCase();
|
|
37005
|
+
const afterInputFlag = /(?:^|[\s'"`])(?:-i|--input)[= ]\s*['"`]?$/.test(before);
|
|
37006
|
+
return cmd === "ffmpeg" && !afterInputFlag;
|
|
37007
|
+
}
|
|
36753
37008
|
function resolveRelativeCwd() {
|
|
36754
37009
|
const configPath = getConfigPath();
|
|
36755
37010
|
if (!configPath) return void 0;
|
|
@@ -36759,7 +37014,7 @@ function resolveRelativeCwd() {
|
|
|
36759
37014
|
return rel2.split(/[\\/]/).filter(Boolean).join("/");
|
|
36760
37015
|
}
|
|
36761
37016
|
var sandboxCommand = new Command("sandbox").description("Run code in a sandbox");
|
|
36762
|
-
sandboxCommand.command("run [args...]").description("Run code").option("--language <language>", "Language: js, py, or bash (required unless pinned by an interpreter token or --file extension)").option("--file <path>", "Read the code body from a file instead of the inline <code> arg; --language is inferred from the extension when not given").addOption(new Option("--code <code>", "Alias for the positional inline <code> arg").hideHelp()).option("--timeout <seconds>", "Execution timeout
|
|
37017
|
+
sandboxCommand.command("run [args...]").description("Run code").option("--language <language>", "Language: js, py, or bash (required unless pinned by an interpreter token or --file extension)").option("--file <path>", "Read the code body from a file instead of the inline <code> arg; --language is inferred from the extension when not given").addOption(new Option("--code <code>", "Alias for the positional inline <code> arg").hideHelp()).addOption(new Option("-c, --cmd <code>", 'Inline code (bash idiom: `sandbox run bash -c "<code>"`)').hideHelp()).addOption(new Option("-e, --eval <code>", 'Inline code (node idiom: `sandbox run node -e "<code>"`)').hideHelp()).option("--timeout <seconds>", "Execution timeout. Bare number = seconds; or pass an explicit unit that means the same on both this and `page eval --timeout`, e.g. --timeout 90s.", "30").option(
|
|
36763
37018
|
"--input <path>",
|
|
36764
37019
|
"Narrow to specific project files instead of auto-mirroring the whole tree (repeatable). Use this only for >1 GB projects or when you want surgical control.",
|
|
36765
37020
|
(v7, prev) => [...prev ?? [], v7]
|
|
@@ -36803,6 +37058,8 @@ Examples:
|
|
|
36803
37058
|
$ gipity sandbox run --file build_report.py
|
|
36804
37059
|
$ gipity sandbox run python build_report.py # same thing, interpreter shorthand
|
|
36805
37060
|
$ gipity sandbox run bash "echo hi; ffmpeg -version" # inline, language pinned
|
|
37061
|
+
$ gipity sandbox run bash -c "ffmpeg -version" # the bash -c idiom also works
|
|
37062
|
+
$ gipity sandbox run node -e "console.log(process.version)" # and node -e
|
|
36806
37063
|
|
|
36807
37064
|
# Surgical: only these files are mirrored in
|
|
36808
37065
|
$ gipity sandbox run --language bash \\
|
|
@@ -36820,17 +37077,20 @@ GCC/Rust).
|
|
|
36820
37077
|
let inlineCode;
|
|
36821
37078
|
let filePath = opts.file;
|
|
36822
37079
|
let langFromInterp;
|
|
36823
|
-
|
|
36824
|
-
|
|
36825
|
-
|
|
37080
|
+
const flagCode = opts.code ?? opts.cmd ?? opts.eval;
|
|
37081
|
+
if (flagCode !== void 0) {
|
|
37082
|
+
const leadIsInterp = args.length === 1 && INTERPRETERS[args[0].toLowerCase()] !== void 0;
|
|
37083
|
+
if (args.length > (leadIsInterp ? 1 : 0)) {
|
|
37084
|
+
console.error(error("Pass the code once: either positionally or via --code/--cmd/-c/--eval/-e, not both"));
|
|
36826
37085
|
process.exit(1);
|
|
36827
37086
|
}
|
|
36828
|
-
|
|
37087
|
+
const interp = leadIsInterp ? args[0] : opts.cmd !== void 0 ? "bash" : opts.eval !== void 0 ? "node" : void 0;
|
|
37088
|
+
args = interp ? [interp, flagCode] : [flagCode];
|
|
36829
37089
|
}
|
|
36830
37090
|
if (args.length >= 2 && INTERPRETERS[args[0].toLowerCase()] !== void 0) {
|
|
36831
37091
|
langFromInterp = INTERPRETERS[args[0].toLowerCase()];
|
|
36832
37092
|
const rest = args.slice(1).join(" ");
|
|
36833
|
-
if (existsSync11(rest) &&
|
|
37093
|
+
if (existsSync11(rest) && statSync7(rest).isFile()) filePath = rest;
|
|
36834
37094
|
else inlineCode = rest;
|
|
36835
37095
|
} else if (args.length === 1) {
|
|
36836
37096
|
inlineCode = args[0];
|
|
@@ -36839,7 +37099,9 @@ GCC/Rust).
|
|
|
36839
37099
|
process.exit(1);
|
|
36840
37100
|
}
|
|
36841
37101
|
if (inlineCode !== void 0 && filePath) {
|
|
36842
|
-
console.error(error("Pass
|
|
37102
|
+
console.error(error("Pass the code ONE way, not both: an inline <code> arg OR --file <path>."));
|
|
37103
|
+
console.error(dim(" inline: gipity sandbox run bash 'echo hi'"));
|
|
37104
|
+
console.error(dim(" file: gipity sandbox run --file script.sh"));
|
|
36843
37105
|
process.exit(1);
|
|
36844
37106
|
}
|
|
36845
37107
|
if (inlineCode === void 0 && !filePath) {
|
|
@@ -36849,7 +37111,7 @@ GCC/Rust).
|
|
|
36849
37111
|
let source = inlineCode;
|
|
36850
37112
|
if (filePath) {
|
|
36851
37113
|
try {
|
|
36852
|
-
source =
|
|
37114
|
+
source = readFileSync15(filePath, "utf8");
|
|
36853
37115
|
} catch {
|
|
36854
37116
|
console.error(error(`Cannot read file: ${filePath}`));
|
|
36855
37117
|
process.exit(1);
|
|
@@ -36862,7 +37124,8 @@ GCC/Rust).
|
|
|
36862
37124
|
process.exit(1);
|
|
36863
37125
|
}
|
|
36864
37126
|
const { config } = await resolveProjectContext();
|
|
36865
|
-
const
|
|
37127
|
+
const durOpt = parseDuration(opts.timeout, "s");
|
|
37128
|
+
const timeout = durOpt ? Math.max(1, Math.round(durOpt.value)) : parseInt(opts.timeout, 10);
|
|
36866
37129
|
const cwd = resolveRelativeCwd();
|
|
36867
37130
|
const scratchInputs = (opts.input ?? []).filter(
|
|
36868
37131
|
(p) => shouldIgnore(p.replace(/\\/g, "/").replace(/^\.\//, ""), SCRATCH_IGNORE)
|
|
@@ -36873,6 +37136,18 @@ GCC/Rust).
|
|
|
36873
37136
|
console.error(dim(" Stage inputs at a real project path (src/, docs/, assets/) and delete them afterward."));
|
|
36874
37137
|
process.exit(1);
|
|
36875
37138
|
}
|
|
37139
|
+
if (source) {
|
|
37140
|
+
const scratchReads = scratchRefsInCode(source).filter(
|
|
37141
|
+
(p) => shouldIgnore(p, SCRATCH_IGNORE) && existsSync11(resolve11(process.cwd(), p)) && !isWriteTarget(source, p)
|
|
37142
|
+
);
|
|
37143
|
+
if (scratchReads.length) {
|
|
37144
|
+
console.error(error(`Scratch files are never mirrored into the sandbox, so it can't read: ${scratchReads.join(", ")}`));
|
|
37145
|
+
console.error(dim(` ${SCRATCH_IGNORE.join(", ")} are ignored by sync, so the sandbox never sees them.`));
|
|
37146
|
+
console.error(dim(" Stage the input at a real project path (src/, docs/, assets/) and delete it afterward."));
|
|
37147
|
+
console.error(dim(" (Writing OUTPUT under tmp/ is fine \u2014 scratch outputs come back to your local tmp/.)"));
|
|
37148
|
+
process.exit(1);
|
|
37149
|
+
}
|
|
37150
|
+
}
|
|
36876
37151
|
if (getConfigPath()) {
|
|
36877
37152
|
await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
36878
37153
|
}
|
|
@@ -36888,17 +37163,17 @@ GCC/Rust).
|
|
|
36888
37163
|
// wire name for --discard-output.)
|
|
36889
37164
|
noSyncOutput: discardOutput.length ? discardOutput : void 0
|
|
36890
37165
|
});
|
|
36891
|
-
const res = opts.json ? await doRun() : await withSpinner("Running in sandbox
|
|
37166
|
+
const res = opts.json ? await doRun() : await withSpinner("Running in sandbox...", doRun, { done: null });
|
|
36892
37167
|
const pulledLocal = !!(res.data.outputFiles?.length && getConfigPath());
|
|
36893
37168
|
if (pulledLocal) {
|
|
36894
37169
|
await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
36895
37170
|
}
|
|
36896
37171
|
const scratchWritten = [];
|
|
36897
37172
|
if (res.data.scratchFiles?.length) {
|
|
36898
|
-
const base =
|
|
37173
|
+
const base = resolve11(getProjectRoot() ?? process.cwd());
|
|
36899
37174
|
for (const f of res.data.scratchFiles) {
|
|
36900
37175
|
const rel2 = f.path.replace(/\\/g, "/");
|
|
36901
|
-
const abs =
|
|
37176
|
+
const abs = resolve11(base, rel2);
|
|
36902
37177
|
if (abs !== base && !abs.startsWith(base + sep3)) continue;
|
|
36903
37178
|
mkdirSync8(dirname8(abs), { recursive: true });
|
|
36904
37179
|
writeFileSync9(abs, Buffer.from(f.contentBase64, "base64"));
|
|
@@ -36924,7 +37199,7 @@ GCC/Rust).
|
|
|
36924
37199
|
}
|
|
36925
37200
|
if (res.data.outputFiles && res.data.outputFiles.length > 0) {
|
|
36926
37201
|
const projectRoot = getProjectRoot();
|
|
36927
|
-
const landed = (f) => pulledLocal && !!projectRoot && existsSync11(
|
|
37202
|
+
const landed = (f) => pulledLocal && !!projectRoot && existsSync11(resolve11(projectRoot, f));
|
|
36928
37203
|
const onDisk = res.data.outputFiles.filter(landed);
|
|
36929
37204
|
const notOnDisk = res.data.outputFiles.filter((f) => !landed(f));
|
|
36930
37205
|
if (onDisk.length > 0) {
|
|
@@ -36936,6 +37211,12 @@ GCC/Rust).
|
|
|
36936
37211
|
for (const f of notOnDisk) console.log(`${f}`);
|
|
36937
37212
|
if (pulledLocal) console.log(dim("Not pulled locally - run 'gipity sync' to fetch them."));
|
|
36938
37213
|
}
|
|
37214
|
+
const rootStrays = [...onDisk, ...notOnDisk].filter((f) => !f.includes("/"));
|
|
37215
|
+
if (rootStrays.length > 0) {
|
|
37216
|
+
console.log(dim(`
|
|
37217
|
+
Note: ${rootStrays.join(", ")} landed at the project root and will deploy with the files phase.`));
|
|
37218
|
+
console.log(dim("For a throwaway probe or fixture, write it under tmp/ instead - scratch outputs stay on local disk and never sync or deploy."));
|
|
37219
|
+
}
|
|
36939
37220
|
}
|
|
36940
37221
|
if (scratchWritten.length > 0) {
|
|
36941
37222
|
console.log("\nScratch outputs (local only - never synced or deployed):");
|
|
@@ -36945,7 +37226,27 @@ GCC/Rust).
|
|
|
36945
37226
|
console.log(dim("\nDiscarded (--discard-output):"));
|
|
36946
37227
|
for (const f of res.data.skippedOutputFiles) console.log(dim(`${f}`));
|
|
36947
37228
|
}
|
|
37229
|
+
if (res.data.exitCode === 0) {
|
|
37230
|
+
const emptyScratch = (res.data.scratchFiles ?? []).filter((f) => f.contentBase64.length === 0).map((f) => f.path.replace(/\\/g, "/"));
|
|
37231
|
+
const root = getProjectRoot();
|
|
37232
|
+
const emptyOutputs = pulledLocal && root ? (res.data.outputFiles ?? []).filter((f) => {
|
|
37233
|
+
try {
|
|
37234
|
+
return statSync7(resolve11(root, f)).size === 0;
|
|
37235
|
+
} catch {
|
|
37236
|
+
return false;
|
|
37237
|
+
}
|
|
37238
|
+
}) : [];
|
|
37239
|
+
const empties = [.../* @__PURE__ */ new Set([...emptyScratch, ...emptyOutputs])];
|
|
37240
|
+
if (empties.length > 0) {
|
|
37241
|
+
console.error(dim(`Note: ${empties.join(", ")} was written but is empty (0 bytes) - the command exited 0 but likely failed silently (a broken filter, or an out-of-memory kill the shell swallowed). Re-check the command, or shrink the job if it may have run out of memory.`));
|
|
37242
|
+
}
|
|
37243
|
+
}
|
|
36948
37244
|
if (res.data.exitCode !== 0) {
|
|
37245
|
+
if (res.data.exitCode === 137) {
|
|
37246
|
+
console.error(error("The sandbox process was killed (exit 137) - it ran out of memory."));
|
|
37247
|
+
console.error(dim(" Shrink the job: for ffmpeg, downscale (-vf scale=640:-2) and add -preset ultrafast;"));
|
|
37248
|
+
console.error(dim(" for ImageMagick, lower -density/-resize. Process in smaller chunks if it still OOMs."));
|
|
37249
|
+
}
|
|
36949
37250
|
process.exit(res.data.exitCode);
|
|
36950
37251
|
}
|
|
36951
37252
|
}
|
|
@@ -36962,7 +37263,7 @@ var chatCommand = new Command("chat").description("Send a message to your agent"
|
|
|
36962
37263
|
const endpoint = useExisting ? `/conversations/${config.conversationGuid}/messages` : "/conversations";
|
|
36963
37264
|
const body = useExisting ? { content: message, projectGuid: config.projectGuid } : { agentGuid: config.agentGuid, content: message, projectGuid: config.projectGuid };
|
|
36964
37265
|
const doChat = () => post(endpoint, body);
|
|
36965
|
-
const res = opts.json ? await doChat() : await withSpinner("Thinking
|
|
37266
|
+
const res = opts.json ? await doChat() : await withSpinner("Thinking...", doChat, { done: null });
|
|
36966
37267
|
if (!oneOff && res.data.conversationGuid !== config.conversationGuid) {
|
|
36967
37268
|
saveConfig({ ...config, conversationGuid: res.data.conversationGuid });
|
|
36968
37269
|
}
|
|
@@ -37361,7 +37662,7 @@ function printStepRuns(steps, emptyNote) {
|
|
|
37361
37662
|
}
|
|
37362
37663
|
}
|
|
37363
37664
|
var TERMINAL_RUN_STATUSES = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
37364
|
-
var sleep2 = (ms2) => new Promise((
|
|
37665
|
+
var sleep2 = (ms2) => new Promise((resolve19) => setTimeout(resolve19, ms2));
|
|
37365
37666
|
async function waitForRun(wfGuid, prevGuid, timeoutSec) {
|
|
37366
37667
|
const deadline = Date.now() + timeoutSec * 1e3;
|
|
37367
37668
|
let runGuid;
|
|
@@ -37448,7 +37749,7 @@ workflowCommand.command("run <name>").description("Trigger a workflow (add --wai
|
|
|
37448
37749
|
}
|
|
37449
37750
|
if (r.status !== "completed") process.exit(1);
|
|
37450
37751
|
}));
|
|
37451
|
-
workflowCommand.command("runs <name> [runGuid]").description("List recent runs, or pass a run guid (wr_
|
|
37752
|
+
workflowCommand.command("runs <name> [runGuid]").description("List recent runs, or pass a run guid (wr_...) to see that run's per-step outputs").option("--json", "Output as JSON").action((name, runGuid, _opts, cmd) => run("Runs", async () => {
|
|
37452
37753
|
const opts = mergedOpts(cmd);
|
|
37453
37754
|
const wf2 = await resolveWorkflow(name);
|
|
37454
37755
|
if (runGuid) {
|
|
@@ -37821,6 +38122,19 @@ fileCommand.command("cat <path>").description("Read a file").option("--json", "O
|
|
|
37821
38122
|
process.stdout.write(res.data.content);
|
|
37822
38123
|
}
|
|
37823
38124
|
}));
|
|
38125
|
+
fileCommand.command("url <path>").description("Durable public URL for a project file - no deploy needed. Reachable by browsers, GPU jobs, and external services the moment the file syncs; ideal for throwaway test fixtures (delete the file to revoke the link).").option("--download", "URL forces a download (Content-Disposition: attachment)").option("--json", "Output as JSON").action((path5, opts) => run("Url", async () => {
|
|
38126
|
+
const { config } = await resolveProjectContext();
|
|
38127
|
+
const qs = new URLSearchParams({ path: path5 });
|
|
38128
|
+
if (opts.download) qs.set("download", "1");
|
|
38129
|
+
const res = await get(
|
|
38130
|
+
`/projects/${config.projectGuid}/files/url?${qs.toString()}`
|
|
38131
|
+
);
|
|
38132
|
+
if (opts.json) {
|
|
38133
|
+
console.log(JSON.stringify(res.data));
|
|
38134
|
+
} else {
|
|
38135
|
+
console.log(res.data.url);
|
|
38136
|
+
}
|
|
38137
|
+
}));
|
|
37824
38138
|
fileCommand.command("tree [path]").description("Show the file tree").option("--json", "Output as JSON").action((path5, opts) => run("Tree", async () => {
|
|
37825
38139
|
const { config } = await resolveProjectContext();
|
|
37826
38140
|
const query = path5 ? `?path=${encodeURIComponent(path5)}` : "";
|
|
@@ -37921,7 +38235,7 @@ function printUsage(d) {
|
|
|
37921
38235
|
for (const p of d.projects) {
|
|
37922
38236
|
const name = p.projectName ?? "(no project)";
|
|
37923
38237
|
row(
|
|
37924
|
-
name.length > 16 ? `${name.slice(0,
|
|
38238
|
+
name.length > 16 ? `${name.slice(0, 13)}...` : name,
|
|
37925
38239
|
formatBytes(p.liveBytes),
|
|
37926
38240
|
`${p.liveFiles.toLocaleString()} files`
|
|
37927
38241
|
);
|
|
@@ -37930,7 +38244,7 @@ function printUsage(d) {
|
|
|
37930
38244
|
const shownBytes = d.projects.reduce((n, p) => n + p.liveBytes, 0);
|
|
37931
38245
|
const shownFiles = d.projects.reduce((n, p) => n + p.liveFiles, 0);
|
|
37932
38246
|
row(
|
|
37933
|
-
|
|
38247
|
+
`...and ${hidden.toLocaleString()} more`,
|
|
37934
38248
|
formatBytes(totals.liveBytes - shownBytes),
|
|
37935
38249
|
`${(totals.liveFiles - shownFiles).toLocaleString()} files`
|
|
37936
38250
|
);
|
|
@@ -37982,50 +38296,11 @@ storageCommand.command("retention").description("View or adjust your file versio
|
|
|
37982
38296
|
init_platform();
|
|
37983
38297
|
init_auth();
|
|
37984
38298
|
init_api();
|
|
37985
|
-
import { join as join17, dirname as dirname10, resolve as
|
|
37986
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as
|
|
38299
|
+
import { join as join17, dirname as dirname10, resolve as resolve13, basename as basename5, sep as sep5 } from "path";
|
|
38300
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync2, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
|
|
37987
38301
|
import { homedir as homedir12 } from "os";
|
|
37988
38302
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
37989
38303
|
import { randomUUID } from "crypto";
|
|
37990
|
-
|
|
37991
|
-
// src/login-flow.ts
|
|
37992
|
-
init_api();
|
|
37993
|
-
init_auth();
|
|
37994
|
-
init_utils();
|
|
37995
|
-
init_colors();
|
|
37996
|
-
async function interactiveLogin() {
|
|
37997
|
-
const email = await prompt(" Email: ");
|
|
37998
|
-
if (!email) {
|
|
37999
|
-
console.error(`
|
|
38000
|
-
${error("Email required.")}`);
|
|
38001
|
-
process.exit(1);
|
|
38002
|
-
}
|
|
38003
|
-
await publicPost("/auth/login", { email });
|
|
38004
|
-
console.log(" Check your email for a 6-digit code.\n");
|
|
38005
|
-
const code = await prompt(" Code: ");
|
|
38006
|
-
if (!code) {
|
|
38007
|
-
console.error(`
|
|
38008
|
-
${error("Code required.")}`);
|
|
38009
|
-
process.exit(1);
|
|
38010
|
-
}
|
|
38011
|
-
const res = await publicPost("/auth/verify", { email, code });
|
|
38012
|
-
const exp = decodeJwtExp(res.accessToken);
|
|
38013
|
-
if (!exp) {
|
|
38014
|
-
console.error(`
|
|
38015
|
-
${error("Invalid token received.")}`);
|
|
38016
|
-
process.exit(1);
|
|
38017
|
-
}
|
|
38018
|
-
const expiresAt = new Date(exp * 1e3).toISOString();
|
|
38019
|
-
saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
|
|
38020
|
-
console.log(` ${success(`Logged in (${email}).`)}`);
|
|
38021
|
-
const delivered = await flushBugQueue().catch(() => 0);
|
|
38022
|
-
if (delivered > 0) {
|
|
38023
|
-
console.log(` ${muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`)}`);
|
|
38024
|
-
}
|
|
38025
|
-
return getAuth();
|
|
38026
|
-
}
|
|
38027
|
-
|
|
38028
|
-
// src/commands/build.ts
|
|
38029
38304
|
init_config();
|
|
38030
38305
|
|
|
38031
38306
|
// src/prompts.ts
|
|
@@ -38131,7 +38406,6 @@ function buildFreshWrap(contextBlock, userMsg) {
|
|
|
38131
38406
|
// src/relay/onboarding.ts
|
|
38132
38407
|
init_utils();
|
|
38133
38408
|
init_colors();
|
|
38134
|
-
import { readFileSync as readFileSync15 } from "node:fs";
|
|
38135
38409
|
|
|
38136
38410
|
// src/relay/installers.ts
|
|
38137
38411
|
import { homedir as homedir8, platform as osPlatform } from "os";
|
|
@@ -38302,13 +38576,13 @@ function windowsTaskPlan(cliPath) {
|
|
|
38302
38576
|
init_platform();
|
|
38303
38577
|
init_api();
|
|
38304
38578
|
import { hostname as hostname3, userInfo, platform as osPlatform2 } from "os";
|
|
38305
|
-
import { resolve as
|
|
38579
|
+
import { resolve as resolve12, dirname as dirname9 } from "path";
|
|
38306
38580
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
38307
38581
|
|
|
38308
38582
|
// src/relay/machine-id.ts
|
|
38309
38583
|
import { createHash as createHash3 } from "crypto";
|
|
38310
38584
|
import { execFileSync } from "child_process";
|
|
38311
|
-
import { readFileSync as
|
|
38585
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
38312
38586
|
import { hostname as hostname2, platform, arch } from "os";
|
|
38313
38587
|
function rawMachineId() {
|
|
38314
38588
|
const override = process.env.GIPITY_RELAY_MACHINE_ID?.trim();
|
|
@@ -38317,7 +38591,7 @@ function rawMachineId() {
|
|
|
38317
38591
|
if (platform() === "linux") {
|
|
38318
38592
|
for (const path5 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
38319
38593
|
try {
|
|
38320
|
-
const id2 =
|
|
38594
|
+
const id2 = readFileSync16(path5, "utf-8").trim();
|
|
38321
38595
|
if (id2) return `linux:${id2}`;
|
|
38322
38596
|
} catch {
|
|
38323
38597
|
}
|
|
@@ -38364,7 +38638,7 @@ function friendlyDeviceName() {
|
|
|
38364
38638
|
return host || `My ${kind}`;
|
|
38365
38639
|
}
|
|
38366
38640
|
function resolveCliPath() {
|
|
38367
|
-
return
|
|
38641
|
+
return resolve12(process.argv[1] ?? "gipity");
|
|
38368
38642
|
}
|
|
38369
38643
|
async function pairDevice(opts = {}) {
|
|
38370
38644
|
const existing = getDevice();
|
|
@@ -38437,15 +38711,6 @@ function removeAutostart(opts = {}) {
|
|
|
38437
38711
|
|
|
38438
38712
|
// src/relay/onboarding.ts
|
|
38439
38713
|
var ensureDaemonRunning = startDaemon;
|
|
38440
|
-
function isWsl() {
|
|
38441
|
-
if (process.platform !== "linux") return false;
|
|
38442
|
-
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
38443
|
-
try {
|
|
38444
|
-
return /microsoft/i.test(readFileSync15("/proc/version", "utf-8"));
|
|
38445
|
-
} catch {
|
|
38446
|
-
return false;
|
|
38447
|
-
}
|
|
38448
|
-
}
|
|
38449
38714
|
async function runRelaySetup(opts) {
|
|
38450
38715
|
const alreadyAnswered = getRelayEnabled() !== void 0;
|
|
38451
38716
|
if (opts.mode === "offer-once" && alreadyAnswered) {
|
|
@@ -38461,12 +38726,12 @@ async function runRelaySetup(opts) {
|
|
|
38461
38726
|
console.log("");
|
|
38462
38727
|
console.log(` ${success("Registered as")} ${bold(existingDevice.name)} ${muted(`(${existingDevice.guid})`)}`);
|
|
38463
38728
|
if (isPaused()) {
|
|
38464
|
-
console.log(` ${dim("The relay is paused
|
|
38729
|
+
console.log(` ${dim("The relay is paused - resume it with")} ${brand("gipity relay resume")}${dim(".")}`);
|
|
38465
38730
|
} else {
|
|
38466
38731
|
console.log(` ${dim("The relay is running. Open")} ${brand("gipity.ai")}${dim(" and type `/claude` to drive Claude Code here.")}`);
|
|
38467
38732
|
}
|
|
38468
38733
|
console.log("");
|
|
38469
|
-
console.log(` ${dim("To register this computer again
|
|
38734
|
+
console.log(` ${dim("To register this computer again (for example under a different name),")}`);
|
|
38470
38735
|
console.log(` ${dim("unregister it first, then re-run setup:")}`);
|
|
38471
38736
|
console.log(` ${brand("gipity relay revoke")} ${dim("# unpairs this computer and removes the login service")}`);
|
|
38472
38737
|
console.log(` ${brand("gipity connect")} ${dim("# register it again (asks for a new name)")}`);
|
|
@@ -38476,28 +38741,29 @@ async function runRelaySetup(opts) {
|
|
|
38476
38741
|
}
|
|
38477
38742
|
if (opts.mode === "run-now") {
|
|
38478
38743
|
console.log(` ${bold("Set up this computer as a relay")}`);
|
|
38479
|
-
console.log(` ${dim("
|
|
38480
|
-
console.log(` ${dim("
|
|
38744
|
+
console.log(` ${dim("Gipity drives the coding agents on this computer from the web (")}${brand("gipity.ai")}${dim(")")}`);
|
|
38745
|
+
console.log(` ${dim("on any browser, desktop or phone. Uses your Claude, Codex, or Grok")}`);
|
|
38746
|
+
console.log(` ${dim("subscription - the cheapest way to pay for tokens.")}`);
|
|
38481
38747
|
} else {
|
|
38482
|
-
console.log(` ${bold("
|
|
38483
|
-
console.log(` ${dim("
|
|
38484
|
-
console.log("");
|
|
38485
|
-
console.log(` ${dim("Enable now (takes 2 seconds) or turn on later with")} ${brand("gipity connect")}`);
|
|
38748
|
+
console.log(` ${bold("Optional:")} ${dim("Gipity can drive the coding agents on this computer from the")}`);
|
|
38749
|
+
console.log(` ${dim("web (")}${brand("gipity.ai")}${dim(") on any browser, desktop or phone. Like Claude Code remote")}`);
|
|
38750
|
+
console.log(` ${dim("control, but one pane for all your coding agents.")}`);
|
|
38486
38751
|
}
|
|
38487
38752
|
console.log("");
|
|
38488
|
-
const promptText = opts.mode === "run-now" ? " Set up remote control on this computer
|
|
38753
|
+
const promptText = opts.mode === "run-now" ? " Set up remote control on this computer" : " Enable remote control";
|
|
38489
38754
|
const enable = await confirm(promptText, { default: "yes" });
|
|
38490
38755
|
if (!enable) {
|
|
38491
38756
|
setRelayEnabled(false);
|
|
38492
|
-
console.log(` ${muted("Skipped.")}`);
|
|
38757
|
+
console.log(` ${muted("Skipped. Turn on later with")} ${brand("gipity connect")}${muted(".")}`);
|
|
38493
38758
|
console.log("");
|
|
38494
38759
|
return false;
|
|
38495
38760
|
}
|
|
38761
|
+
console.log("");
|
|
38496
38762
|
const defaultName = friendlyDeviceName();
|
|
38497
38763
|
const rawName = await prompt(` Device name [${bold(defaultName)}]: `);
|
|
38498
38764
|
const name = (rawName || defaultName).trim();
|
|
38499
38765
|
if (!name || name.length > 100) {
|
|
38500
|
-
console.error(` ${error("Device name must be 1
|
|
38766
|
+
console.error(` ${error("Device name must be 1-100 non-whitespace characters. Skipping.")}`);
|
|
38501
38767
|
setRelayEnabled(false);
|
|
38502
38768
|
return false;
|
|
38503
38769
|
}
|
|
@@ -38510,11 +38776,9 @@ async function runRelaySetup(opts) {
|
|
|
38510
38776
|
console.error(` ${dim("Skipping for now - we'll offer again next time. Or turn it on with `gipity connect`.")}`);
|
|
38511
38777
|
return false;
|
|
38512
38778
|
}
|
|
38513
|
-
|
|
38514
|
-
|
|
38515
|
-
|
|
38516
|
-
}
|
|
38517
|
-
const autostartOs = await confirm(" Also start at OS login (auto-start with Windows / macOS / Linux)?", { default: "yes" });
|
|
38779
|
+
startDaemon();
|
|
38780
|
+
console.log("");
|
|
38781
|
+
const autostartOs = await confirm(" Also start at OS login", { default: "yes" });
|
|
38518
38782
|
if (autostartOs) {
|
|
38519
38783
|
try {
|
|
38520
38784
|
const res = installAutostart();
|
|
@@ -38526,8 +38790,6 @@ async function runRelaySetup(opts) {
|
|
|
38526
38790
|
} else {
|
|
38527
38791
|
console.log(` ${muted("Autostart install returned non-zero - you can run")} ${brand("gipity relay install")} ${muted("later.")}`);
|
|
38528
38792
|
}
|
|
38529
|
-
} else {
|
|
38530
|
-
console.log(` ${success("Auto-start installed.")} ${dim(res.summary)}`);
|
|
38531
38793
|
}
|
|
38532
38794
|
} catch (err) {
|
|
38533
38795
|
if (err instanceof UnsupportedPlatformError) {
|
|
@@ -38538,16 +38800,12 @@ async function runRelaySetup(opts) {
|
|
|
38538
38800
|
}
|
|
38539
38801
|
}
|
|
38540
38802
|
if (getDiagnosticsConsent() === void 0) {
|
|
38541
|
-
|
|
38542
|
-
|
|
38543
|
-
{ default: "yes" }
|
|
38544
|
-
);
|
|
38803
|
+
console.log("");
|
|
38804
|
+
const diag = await confirm(" Share anonymous diagnostics info", { default: "yes" });
|
|
38545
38805
|
setDiagnosticsConsent(diag);
|
|
38546
|
-
console.log(` ${dim(diag ? "Thanks - no personal data or file paths are ever sent. Turn off anytime with `gipity relay diagnostics off`." : "Diagnostics off. Turn on anytime with `gipity relay diagnostics on`.")}`);
|
|
38547
38806
|
}
|
|
38548
38807
|
console.log("");
|
|
38549
|
-
console.log(` ${success(`
|
|
38550
|
-
console.log(` ${dim("In the Gipity web CLI, type `/claude` to dispatch messages to this PC.")}`);
|
|
38808
|
+
console.log(` ${success(`Done! ${bold(device.name)} is set up. New chats from gipity.ai can now execute here.`)}`);
|
|
38551
38809
|
console.log("");
|
|
38552
38810
|
return true;
|
|
38553
38811
|
}
|
|
@@ -38690,9 +38948,8 @@ function padR(s, width) {
|
|
|
38690
38948
|
}
|
|
38691
38949
|
function leadingEllipsify(s, maxW) {
|
|
38692
38950
|
if (s.length <= maxW) return s;
|
|
38693
|
-
if (maxW <=
|
|
38694
|
-
|
|
38695
|
-
return "\u2026" + s.slice(-(maxW - 1));
|
|
38951
|
+
if (maxW <= 4) return s.slice(-maxW);
|
|
38952
|
+
return "..." + s.slice(-(maxW - 3));
|
|
38696
38953
|
}
|
|
38697
38954
|
function center(s, width) {
|
|
38698
38955
|
const gap = width - visLen(s);
|
|
@@ -39002,14 +39259,14 @@ function getAdapterBySource(source) {
|
|
|
39002
39259
|
}
|
|
39003
39260
|
|
|
39004
39261
|
// src/prefs.ts
|
|
39005
|
-
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as
|
|
39262
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
39006
39263
|
import { homedir as homedir11 } from "os";
|
|
39007
39264
|
import { join as join16 } from "path";
|
|
39008
39265
|
var PREFS_PATH = join16(homedir11(), ".gipity", "prefs.json");
|
|
39009
39266
|
function readPrefs() {
|
|
39010
39267
|
if (!existsSync13(PREFS_PATH)) return {};
|
|
39011
39268
|
try {
|
|
39012
|
-
const parsed = JSON.parse(
|
|
39269
|
+
const parsed = JSON.parse(readFileSync17(PREFS_PATH, "utf-8"));
|
|
39013
39270
|
return {
|
|
39014
39271
|
lastAgent: typeof parsed.lastAgent === "string" ? parsed.lastAgent : void 0
|
|
39015
39272
|
};
|
|
@@ -39034,7 +39291,7 @@ function readOwnPkg(fromDir) {
|
|
|
39034
39291
|
const p = join17(d, "package.json");
|
|
39035
39292
|
if (existsSync14(p)) {
|
|
39036
39293
|
try {
|
|
39037
|
-
const pkg2 = JSON.parse(
|
|
39294
|
+
const pkg2 = JSON.parse(readFileSync18(p, "utf-8"));
|
|
39038
39295
|
if (pkg2.name === "gipity") return pkg2;
|
|
39039
39296
|
} catch {
|
|
39040
39297
|
}
|
|
@@ -39045,7 +39302,7 @@ function readOwnPkg(fromDir) {
|
|
|
39045
39302
|
var __clPkg = readOwnPkg(__clDir);
|
|
39046
39303
|
function reportSyncResult(result) {
|
|
39047
39304
|
if (result.aborted) {
|
|
39048
|
-
console.log(` ${warning("Merge cancelled
|
|
39305
|
+
console.log(` ${warning("Merge cancelled - this folder was NOT synced with the project.")}`);
|
|
39049
39306
|
console.log(` ${muted("For a clean copy, quit and open the project in an empty folder. To merge anyway, run `gipity sync`.")}`);
|
|
39050
39307
|
return;
|
|
39051
39308
|
}
|
|
@@ -39053,9 +39310,9 @@ function reportSyncResult(result) {
|
|
|
39053
39310
|
console.log(` Synced ${result.applied} change${result.applied > 1 ? "s" : ""} with Gipity.`);
|
|
39054
39311
|
}
|
|
39055
39312
|
if (result.errors.length) {
|
|
39056
|
-
console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? "" : "s"}
|
|
39313
|
+
console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? "" : "s"} - your local copy may be incomplete:`)}`);
|
|
39057
39314
|
for (const e of result.errors.slice(0, 8)) console.log(` - ${e}`);
|
|
39058
|
-
if (result.errors.length > 8) console.log(`
|
|
39315
|
+
if (result.errors.length > 8) console.log(` ...and ${result.errors.length - 8} more.`);
|
|
39059
39316
|
console.log(` ${muted("Nothing was deleted. Re-run `gipity sync` to finish the pull.")}`);
|
|
39060
39317
|
}
|
|
39061
39318
|
}
|
|
@@ -39088,7 +39345,7 @@ function localFsFallback(dir) {
|
|
|
39088
39345
|
let isDir = false;
|
|
39089
39346
|
let size = 0;
|
|
39090
39347
|
try {
|
|
39091
|
-
const st2 =
|
|
39348
|
+
const st2 = statSync8(join17(d, name));
|
|
39092
39349
|
isDir = st2.isDirectory();
|
|
39093
39350
|
size = st2.isFile() ? st2.size : 0;
|
|
39094
39351
|
} catch {
|
|
@@ -39108,7 +39365,7 @@ function localFsFallback(dir) {
|
|
|
39108
39365
|
}
|
|
39109
39366
|
};
|
|
39110
39367
|
walk(dir, 0);
|
|
39111
|
-
const topLevel = topLevelEntries.length ? topLevelEntries.slice(0, 20).join(", ") + (topLevelEntries.length > 20 ? ",
|
|
39368
|
+
const topLevel = topLevelEntries.length ? topLevelEntries.slice(0, 20).join(", ") + (topLevelEntries.length > 20 ? ", ..." : "") : "(empty directory)";
|
|
39112
39369
|
return { fileCount, folderCount, totalBytes, topLevel };
|
|
39113
39370
|
}
|
|
39114
39371
|
async function buildProjectContextBlock2(opts) {
|
|
@@ -39131,7 +39388,7 @@ function hasStreamJsonFlag(args) {
|
|
|
39131
39388
|
function markFolderTrusted(dir) {
|
|
39132
39389
|
try {
|
|
39133
39390
|
const file = join17(homedir12(), ".claude.json");
|
|
39134
|
-
const cfg = existsSync14(file) ? JSON.parse(
|
|
39391
|
+
const cfg = existsSync14(file) ? JSON.parse(readFileSync18(file, "utf-8")) : {};
|
|
39135
39392
|
if (typeof cfg !== "object" || cfg === null) return;
|
|
39136
39393
|
if (typeof cfg.projects !== "object" || cfg.projects === null) cfg.projects = {};
|
|
39137
39394
|
const entry = typeof cfg.projects[dir] === "object" && cfg.projects[dir] !== null ? cfg.projects[dir] : {};
|
|
@@ -39256,7 +39513,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39256
39513
|
let existing = getConfig();
|
|
39257
39514
|
let forceAdoptCwd = false;
|
|
39258
39515
|
if (existing && !opts.newProject && !opts.project && !nonInteractive) {
|
|
39259
|
-
const cwdHasConfig = existsSync14(
|
|
39516
|
+
const cwdHasConfig = existsSync14(resolve13(process.cwd(), ".gipity.json"));
|
|
39260
39517
|
if (!cwdHasConfig && isLikelyEmpty(process.cwd())) {
|
|
39261
39518
|
const ancestorRoot = dirname10(getConfigPath());
|
|
39262
39519
|
console.log(` ${bold("You are inside an existing Gipity project.")}
|
|
@@ -39334,6 +39591,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39334
39591
|
console.log(` ${warning("Could not sync files (will retry on next prompt):")} ${err.message}`);
|
|
39335
39592
|
}
|
|
39336
39593
|
setupProjectTools();
|
|
39594
|
+
if (!nonInteractive) console.log(` ${muted("Installed Gipity plugin, skills, and hooks.")}`);
|
|
39337
39595
|
if (nonInteractive) {
|
|
39338
39596
|
headlessNewProject = isNewProject;
|
|
39339
39597
|
} else {
|
|
@@ -39406,7 +39664,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39406
39664
|
let agentGuid = "";
|
|
39407
39665
|
const result = forceAdoptCwd ? { kind: "adopt-cwd" } : projects.length > 0 ? await pickOrCreateProject(projects, existingSlugs) : { kind: "create-new", project: await createNewProject(existingSlugs) };
|
|
39408
39666
|
if (result.kind === "adopt-cwd") {
|
|
39409
|
-
const cwdBase =
|
|
39667
|
+
const cwdBase = basename5(process.cwd());
|
|
39410
39668
|
const adoptSlug = slugify(cwdBase) || "project";
|
|
39411
39669
|
const adoptName = (cwdBase || adoptSlug).slice(0, 100);
|
|
39412
39670
|
accountSlug = await getAccountSlug();
|
|
@@ -39458,6 +39716,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39458
39716
|
}
|
|
39459
39717
|
initialPrompt = "";
|
|
39460
39718
|
setupProjectTools();
|
|
39719
|
+
if (!nonInteractive) console.log(` ${muted("Installed Gipity plugin, skills, and hooks.")}`);
|
|
39461
39720
|
console.log(` ${success(`Project "${project.name}" ready.`)}
|
|
39462
39721
|
`);
|
|
39463
39722
|
}
|
|
@@ -39553,9 +39812,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39553
39812
|
}
|
|
39554
39813
|
if (!nonInteractive) {
|
|
39555
39814
|
console.log(` ${bold("Launching Claude Code, powered by Gipity.")}`);
|
|
39556
|
-
console.log(` ${muted("Just tell Claude what you'd like to build or do - everything Claude can do,")}`);
|
|
39557
|
-
console.log(` ${muted("plus hosting, databases, and live deploys on Gipity.")}`);
|
|
39558
39815
|
if (convGuidForHooks) {
|
|
39816
|
+
console.log("");
|
|
39559
39817
|
console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand("prompt.gipity.ai")}. (--no-capture on init to disable.)`)}`);
|
|
39560
39818
|
}
|
|
39561
39819
|
console.log("");
|
|
@@ -39653,10 +39911,8 @@ async function pickAgent(lastUsed) {
|
|
|
39653
39911
|
console.log(` ${bold("Which coding agent?")}
|
|
39654
39912
|
`);
|
|
39655
39913
|
AGENT_ADAPTERS.forEach((a, i) => {
|
|
39656
|
-
const installed = binaryOnPath(a.binary) || a.key === "claude" && isClaudeInstalled();
|
|
39657
39914
|
const notes = [];
|
|
39658
39915
|
if (a.key === lastUsed) notes.push("last used");
|
|
39659
|
-
if (!installed) notes.push(a.ensureInstalled ? "not installed - we'll install it" : "not installed");
|
|
39660
39916
|
if (a.key === "codex" && !a.hooksSupportedOnPlatform(process.platform)) {
|
|
39661
39917
|
notes.push("session recording unavailable on Windows");
|
|
39662
39918
|
}
|
|
@@ -39769,9 +40025,8 @@ Sending to ${adapter.displayName}: ${message}
|
|
|
39769
40025
|
} else {
|
|
39770
40026
|
agentArgs = [...adapter.buildInteractiveArgs({ resume, model }), ...extras];
|
|
39771
40027
|
console.log(` ${bold(`Launching ${adapter.displayName}, powered by Gipity.`)}`);
|
|
39772
|
-
console.log(` ${muted(`Just tell ${adapter.displayName} what you'd like to build or do - plus hosting,`)}`);
|
|
39773
|
-
console.log(` ${muted("databases, and live deploys on Gipity.")}`);
|
|
39774
40028
|
if (convGuid) {
|
|
40029
|
+
console.log("");
|
|
39775
40030
|
console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand("prompt.gipity.ai")}. (--no-capture on init to disable.)`)}`);
|
|
39776
40031
|
}
|
|
39777
40032
|
if (adapter.oneTimeSetupNote) {
|
|
@@ -39831,32 +40086,39 @@ async function pickOrCreateProject(projects, existingSlugs) {
|
|
|
39831
40086
|
const cwd = process.cwd();
|
|
39832
40087
|
const showAdopt = canAdoptCwd(cwd);
|
|
39833
40088
|
const reserved = showAdopt ? 2 : 1;
|
|
39834
|
-
const
|
|
39835
|
-
const
|
|
39836
|
-
const
|
|
40089
|
+
const fitsAll = filtered.length <= 9 - reserved;
|
|
40090
|
+
const hasMore = !fitsAll;
|
|
40091
|
+
const searchSlot = hasMore ? reserved + 1 : 0;
|
|
40092
|
+
const projectBase = reserved + (hasMore ? 1 : 0);
|
|
40093
|
+
const recent = fitsAll ? filtered : filtered.slice(0, 9 - reserved - 2);
|
|
40094
|
+
const moreSlot = hasMore ? projectBase + recent.length + 1 : 0;
|
|
39837
40095
|
while (true) {
|
|
39838
40096
|
const newProjectLabel = formatNewProjectLabel(existingSlugs);
|
|
39839
40097
|
console.log(` ${bold("Choose project to open:")}
|
|
39840
40098
|
`);
|
|
39841
|
-
console.log(` ${bold("1.")} Create new project ${muted(`(${newProjectLabel})`)}`);
|
|
40099
|
+
console.log(` ${bold("1.")} ${info("Create new project")} ${muted(`(${newProjectLabel})`)}`);
|
|
39842
40100
|
if (showAdopt) {
|
|
39843
|
-
console.log(` ${bold("2.")} Use this directory ${muted(`(${formatCwdLabel(cwd)})`)}`);
|
|
40101
|
+
console.log(` ${bold("2.")} ${info("Use this directory")} ${muted(`(${formatCwdLabel(cwd)})`)}`);
|
|
40102
|
+
}
|
|
40103
|
+
if (hasMore) {
|
|
40104
|
+
console.log(` ${bold(`${searchSlot}.`)} ${info("Search projects")} ${muted(`(${filtered.length} total)`)}`);
|
|
39844
40105
|
}
|
|
39845
40106
|
recent.forEach(
|
|
39846
|
-
(p, i) => console.log(` ${bold(`${
|
|
40107
|
+
(p, i) => console.log(` ${bold(`${projectBase + i + 1}.`)} ${p.name} ${muted(`(${p.slug})`)}`)
|
|
39847
40108
|
);
|
|
39848
40109
|
if (hasMore) {
|
|
39849
|
-
console.log(` ${bold(`${
|
|
40110
|
+
console.log(` ${bold(`${moreSlot}.`)} ${info("Show more")}`);
|
|
39850
40111
|
}
|
|
39851
40112
|
console.log("");
|
|
39852
|
-
const maxOption = recent.length +
|
|
40113
|
+
const maxOption = projectBase + recent.length + (hasMore ? 1 : 0);
|
|
39853
40114
|
const idx = await pickOne("Choose", maxOption, 1);
|
|
39854
|
-
if (idx >
|
|
39855
|
-
return { kind: "pick", project: recent[idx -
|
|
40115
|
+
if (idx > projectBase && idx <= projectBase + recent.length) {
|
|
40116
|
+
return { kind: "pick", project: recent[idx - projectBase - 1] };
|
|
39856
40117
|
}
|
|
39857
|
-
if (hasMore && idx ===
|
|
39858
|
-
const picked = await
|
|
40118
|
+
if (hasMore && (idx === searchSlot || idx === moreSlot)) {
|
|
40119
|
+
const picked = await browseProjects(filtered, { search: idx === searchSlot });
|
|
39859
40120
|
if (picked) return { kind: "pick", project: picked };
|
|
40121
|
+
console.log("");
|
|
39860
40122
|
continue;
|
|
39861
40123
|
}
|
|
39862
40124
|
if (showAdopt && idx === 2) {
|
|
@@ -39871,17 +40133,62 @@ async function pickOrCreateProject(projects, existingSlugs) {
|
|
|
39871
40133
|
return { kind: "create-new", project };
|
|
39872
40134
|
}
|
|
39873
40135
|
}
|
|
39874
|
-
|
|
40136
|
+
var PROJECT_PAGE_SIZE = 10;
|
|
40137
|
+
function searchProjects(all, query) {
|
|
40138
|
+
const q = query.trim().toLowerCase();
|
|
40139
|
+
if (!q) return all;
|
|
40140
|
+
return all.filter((p) => p.name.toLowerCase().includes(q) || p.slug.toLowerCase().includes(q));
|
|
40141
|
+
}
|
|
40142
|
+
async function browseProjects(all, opts = {}) {
|
|
40143
|
+
let items = all;
|
|
40144
|
+
let heading = "All projects";
|
|
40145
|
+
const runSearch = async () => {
|
|
40146
|
+
const q = await prompt(` ${bold("Search projects:")} `);
|
|
40147
|
+
if (!q) return false;
|
|
40148
|
+
const matches = searchProjects(all, q);
|
|
40149
|
+
if (matches.length === 0) {
|
|
40150
|
+
console.log(` ${muted(`No projects match "${q}".`)}`);
|
|
40151
|
+
return false;
|
|
40152
|
+
}
|
|
40153
|
+
items = matches;
|
|
40154
|
+
heading = `Projects matching "${q}"`;
|
|
40155
|
+
return true;
|
|
40156
|
+
};
|
|
39875
40157
|
console.log("");
|
|
39876
|
-
|
|
40158
|
+
if (opts.search && !await runSearch()) return null;
|
|
40159
|
+
let shown = 0;
|
|
40160
|
+
while (true) {
|
|
40161
|
+
if (shown < items.length) {
|
|
40162
|
+
const page = items.slice(shown, shown + PROJECT_PAGE_SIZE);
|
|
40163
|
+
if (shown === 0) console.log(` ${bold(`${heading}`)} ${muted(`(${items.length})`)}
|
|
39877
40164
|
`);
|
|
39878
|
-
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
39882
|
-
|
|
39883
|
-
|
|
39884
|
-
|
|
40165
|
+
page.forEach(
|
|
40166
|
+
(p, i) => console.log(` ${bold(`${String(shown + i + 1).padStart(3)}.`)} ${p.name} ${muted(`(${p.slug})`)}`)
|
|
40167
|
+
);
|
|
40168
|
+
shown += page.length;
|
|
40169
|
+
console.log("");
|
|
40170
|
+
}
|
|
40171
|
+
const more = shown < items.length;
|
|
40172
|
+
const hints = [
|
|
40173
|
+
more ? `Enter = next ${Math.min(PROJECT_PAGE_SIZE, items.length - shown)}` : null,
|
|
40174
|
+
"number = open",
|
|
40175
|
+
"s = search",
|
|
40176
|
+
"q = back"
|
|
40177
|
+
].filter(Boolean).join(" \xB7 ");
|
|
40178
|
+
const answer = (await prompt(` ${bold("Choose")} ${muted(`(${hints})`)}: `)).toLowerCase();
|
|
40179
|
+
if (answer === "" && more) continue;
|
|
40180
|
+
if (answer === "" || answer === "q") return null;
|
|
40181
|
+
if (answer === "s") {
|
|
40182
|
+
if (await runSearch()) {
|
|
40183
|
+
shown = 0;
|
|
40184
|
+
console.log("");
|
|
40185
|
+
}
|
|
40186
|
+
continue;
|
|
40187
|
+
}
|
|
40188
|
+
const n = parseInt(answer, 10);
|
|
40189
|
+
if (Number.isInteger(n) && n >= 1 && n <= shown) return items[n - 1];
|
|
40190
|
+
console.log(` ${muted("Type one of the numbers above, Enter for more, s to search, or q to go back.")}`);
|
|
40191
|
+
}
|
|
39885
40192
|
}
|
|
39886
40193
|
function formatNewProjectLabel(existingSlugs) {
|
|
39887
40194
|
const slug = suggestProjectName(existingSlugs);
|
|
@@ -39990,12 +40297,10 @@ var KITS = [
|
|
|
39990
40297
|
{ key: "chatbot", hint: "drop-in chatbot - persona, guardrails, streaming responses" },
|
|
39991
40298
|
{ key: "audio-align", hint: "audio + lyrics -> word-level timing JSON (GPU job)" },
|
|
39992
40299
|
{ key: "i18n", hint: "multi-language web apps - language picker, RTL, translations" },
|
|
39993
|
-
{ key: "records", hint: "registry-driven data plane - generic CRUD, validation, search, audit spine" },
|
|
39994
|
-
{ key: "views", hint: "registry-driven UI over records - table, forms, kanban" },
|
|
39995
|
-
{ key: "agent-api", hint: "named API keys for agent/script writes through the records kit" },
|
|
39996
40300
|
{ key: "contacts", hint: "multi-source contact layer - LinkedIn/Gmail import, dedupe with provenance" },
|
|
39997
40301
|
{ key: "stripe", hint: "charge your users - Stripe checkout, subscriptions, brokered webhooks" },
|
|
39998
|
-
{ key: "notify", hint: "web push notifications - platform-owned keys, works on iOS home screen" }
|
|
40302
|
+
{ key: "notify", hint: "web push notifications - platform-owned keys, works on iOS home screen" },
|
|
40303
|
+
{ key: "servicenow", hint: "ServiceNow tables as a data source - OAuth pull/write-back/real-time sync" }
|
|
39999
40304
|
];
|
|
40000
40305
|
|
|
40001
40306
|
// src/commands/add.ts
|
|
@@ -40151,7 +40456,7 @@ var addCommand = new Command("add").description("Add a template or kit").argumen
|
|
|
40151
40456
|
console.error(muted(isKit ? "Installing kit (server-side install pipeline)..." : "Installing (server writes files + generates favicons; first add for a title can take ~10s)..."));
|
|
40152
40457
|
}
|
|
40153
40458
|
const doAdd = () => post(`/projects/${config.projectGuid}/add`, body);
|
|
40154
|
-
const res = opts.json ? await doAdd() : await withSpinner("Installing
|
|
40459
|
+
const res = opts.json ? await doAdd() : await withSpinner("Installing...", doAdd, { done: null });
|
|
40155
40460
|
const syncResult = await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
40156
40461
|
const data = res.data;
|
|
40157
40462
|
if (opts.json) {
|
|
@@ -40199,7 +40504,7 @@ var removeCommand = new Command("remove").description("Remove a kit").argument("
|
|
|
40199
40504
|
}
|
|
40200
40505
|
}
|
|
40201
40506
|
const doRemove = () => post(`/projects/${config.projectGuid}/remove`, { name: kit });
|
|
40202
|
-
const res = opts.json ? await doRemove() : await withSpinner("Removing
|
|
40507
|
+
const res = opts.json ? await doRemove() : await withSpinner("Removing...", doRemove, { done: null });
|
|
40203
40508
|
const data = res.data;
|
|
40204
40509
|
const syncResult = await sync({
|
|
40205
40510
|
interactive: false,
|
|
@@ -40249,7 +40554,7 @@ function dispositionFilename(headers) {
|
|
|
40249
40554
|
var saveCommand = new Command("save").description("Save this app as a portable .gip bundle").argument("[output]", "Output .gip path, or a directory to write <slug>.gip into (default: ./<slug>.gip)").option("--json", "Output as JSON").addHelpText("after", "\nRestore anywhere with `gipity load <file>.gip` - it always creates a NEW project.").action((output, opts) => run("Save", async () => {
|
|
40250
40555
|
const config = requireConfig();
|
|
40251
40556
|
const doExport = () => downloadWithHeaders(`/projects/${config.projectGuid}/export`);
|
|
40252
|
-
const { buffer, headers } = opts.json ? await doExport() : await withSpinner("Exporting
|
|
40557
|
+
const { buffer, headers } = opts.json ? await doExport() : await withSpinner("Exporting...", doExport, { done: null });
|
|
40253
40558
|
const filename = dispositionFilename(headers) ?? `${config.projectSlug}.gip`;
|
|
40254
40559
|
let dest = path2.resolve(output ?? filename);
|
|
40255
40560
|
if (output && fs3.existsSync(dest) && fs3.statSync(dest).isDirectory()) {
|
|
@@ -40257,14 +40562,14 @@ var saveCommand = new Command("save").description("Save this app as a portable .
|
|
|
40257
40562
|
}
|
|
40258
40563
|
fs3.mkdirSync(path2.dirname(dest), { recursive: true });
|
|
40259
40564
|
fs3.writeFileSync(dest, buffer);
|
|
40260
|
-
const
|
|
40565
|
+
const sha256 = createHash4("sha256").update(buffer).digest("hex");
|
|
40261
40566
|
const skipped = Number(headers.get("x-gip-skipped") ?? 0);
|
|
40262
40567
|
const entries = zipEntryCount(buffer);
|
|
40263
40568
|
if (opts.json) {
|
|
40264
40569
|
console.log(JSON.stringify({
|
|
40265
40570
|
path: dest,
|
|
40266
40571
|
bytes: buffer.length,
|
|
40267
|
-
sha256
|
|
40572
|
+
sha256,
|
|
40268
40573
|
...entries !== null ? { entries } : {},
|
|
40269
40574
|
skipped
|
|
40270
40575
|
}));
|
|
@@ -40274,7 +40579,7 @@ var saveCommand = new Command("save").description("Save this app as a portable .
|
|
|
40274
40579
|
console.log(` Path: ${dest}`);
|
|
40275
40580
|
console.log(` Size: ${formatBytes(buffer.length)}`);
|
|
40276
40581
|
if (entries !== null) console.log(` Entries: ${entries} (including the bundle manifest)`);
|
|
40277
|
-
console.log(` SHA-256: ${
|
|
40582
|
+
console.log(` SHA-256: ${sha256}`);
|
|
40278
40583
|
if (skipped > 0) {
|
|
40279
40584
|
console.log("");
|
|
40280
40585
|
console.warn(warning(`${skipped} file(s) were skipped (unreadable or over 100 MB) - this bundle is partial.`));
|
|
@@ -40391,7 +40696,7 @@ var loadCommand = new Command("load").description("Create a new app from a .gip
|
|
|
40391
40696
|
};
|
|
40392
40697
|
}
|
|
40393
40698
|
if (opts.inspect) {
|
|
40394
|
-
const res = opts.json ? await doInspect() : await withSpinner("Inspecting
|
|
40699
|
+
const res = opts.json ? await doInspect() : await withSpinner("Inspecting...", doInspect, { done: null });
|
|
40395
40700
|
if (opts.json) {
|
|
40396
40701
|
console.log(JSON.stringify(res.data));
|
|
40397
40702
|
return;
|
|
@@ -40399,7 +40704,7 @@ var loadCommand = new Command("load").description("Create a new app from a .gip
|
|
|
40399
40704
|
printInspect(res.data, source);
|
|
40400
40705
|
return;
|
|
40401
40706
|
}
|
|
40402
|
-
const { data, partial } = opts.json ? await doImport() : await withSpinner("Importing
|
|
40707
|
+
const { data, partial } = opts.json ? await doImport() : await withSpinner("Importing...", doImport, { done: null });
|
|
40403
40708
|
const project = data.project;
|
|
40404
40709
|
const dir = path3.join(getProjectsRoot(), project.slug);
|
|
40405
40710
|
fs4.mkdirSync(dir, { recursive: true });
|
|
@@ -40800,7 +41105,7 @@ ${error(`\u26A0 ${problems} error/crash line(s) flagged above`)}`
|
|
|
40800
41105
|
);
|
|
40801
41106
|
if (problems > 0) process.exitCode = 1;
|
|
40802
41107
|
}
|
|
40803
|
-
var pageTestCommand = new Command("test").description("Multi-client realtime check: load a URL in N concurrent headless clients; flag console errors, or drive an action and observe shared state (--observe)").argument("<url>", "Deployed URL to load in every client. {{label}}/{{i}} substitute per client in both modes (e.g. ?name=Bot{{i}}, or ?role={{label}} with --labels host,join), so one invocation can give each client a distinct role.").option("--clients <n>", "Number of headless clients to launch", "2").option("--stagger <s>", "Seconds between client starts (passive default 12; interactive default 0)").option("--wait <ms>", "Passive mode: ms each client stays open after load (max 30000)", "24000").option("--observe <expr>", "Interactive: JS expression sampled in each client to read shared state (e.g. presence count). Switches on interactive mode.").option("--action <expr>", "Interactive: one-time JS run in each client before observing (e.g. fill a name + submit). {{label}}/{{i}} are substituted per client.").option("--labels <csv>", "Per-client labels substituted for {{label}} in the URL/--action/--observe (default client-0, client-1,
|
|
41108
|
+
var pageTestCommand = new Command("test").description("Multi-client realtime check: load a URL in N concurrent headless clients; flag console errors, or drive an action and observe shared state (--observe)").argument("<url>", "Deployed URL to load in every client. {{label}}/{{i}} substitute per client in both modes (e.g. ?name=Bot{{i}}, or ?role={{label}} with --labels host,join), so one invocation can give each client a distinct role.").option("--clients <n>", "Number of headless clients to launch", "2").option("--stagger <s>", "Seconds between client starts (passive default 12; interactive default 0)").option("--wait <ms>", "Passive mode: ms each client stays open after load (max 30000)", "24000").option("--observe <expr>", "Interactive: JS expression sampled in each client to read shared state (e.g. presence count). Switches on interactive mode.").option("--action <expr>", "Interactive: one-time JS run in each client before observing (e.g. fill a name + submit). {{label}}/{{i}} are substituted per client.").option("--labels <csv>", "Per-client labels substituted for {{label}} in the URL/--action/--observe (default client-0, client-1, ...)").option("--hold <ms>", `Interactive: total observe window per client (${MIN_HOLD_MS}-${MAX_HOLD_MS}ms)`, "8000").option("--samples <k>", "Interactive: number of observations across the hold window (2-30)", "6").option("--wait-for <selector>", "Interactive: wait for this CSS selector before running --action (deterministic readiness gate)").option("--json", "Output as JSON").addHelpText("after", `
|
|
40804
41109
|
Examples:
|
|
40805
41110
|
# Passive: load in 3 staggered clients, flag console errors
|
|
40806
41111
|
gipity page test "https://dev.gipity.ai/me/app/" --clients 3 --stagger 8
|
|
@@ -40858,9 +41163,6 @@ function looksLikeHtml(body) {
|
|
|
40858
41163
|
const head = body.slice(0, 300).toLowerCase();
|
|
40859
41164
|
return /<!doctype html|<html[\s>]/.test(head);
|
|
40860
41165
|
}
|
|
40861
|
-
function sha256(s) {
|
|
40862
|
-
return createHash5("sha256").update(s).digest("hex");
|
|
40863
|
-
}
|
|
40864
41166
|
function baseWithSlash(url) {
|
|
40865
41167
|
return url.endsWith("/") ? url : url + "/";
|
|
40866
41168
|
}
|
|
@@ -40870,8 +41172,14 @@ function resolveUrl(base, path5) {
|
|
|
40870
41172
|
async function fetchRaw(url) {
|
|
40871
41173
|
try {
|
|
40872
41174
|
const res = await fetch(url, { redirect: "follow" });
|
|
40873
|
-
const
|
|
40874
|
-
return {
|
|
41175
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
41176
|
+
return {
|
|
41177
|
+
status: res.status,
|
|
41178
|
+
contentType: res.headers.get("content-type"),
|
|
41179
|
+
body: buf.toString("utf-8"),
|
|
41180
|
+
bytes: buf.length,
|
|
41181
|
+
sha256: createHash5("sha256").update(buf).digest("hex")
|
|
41182
|
+
};
|
|
40875
41183
|
} catch {
|
|
40876
41184
|
return null;
|
|
40877
41185
|
}
|
|
@@ -40883,17 +41191,16 @@ async function probeShell(base) {
|
|
|
40883
41191
|
return {
|
|
40884
41192
|
served: r.status >= 200 && r.status < 300,
|
|
40885
41193
|
status: r.status,
|
|
40886
|
-
sha256:
|
|
41194
|
+
sha256: r.sha256,
|
|
40887
41195
|
isHtml: looksLikeHtml(r.body)
|
|
40888
41196
|
};
|
|
40889
41197
|
}
|
|
40890
41198
|
function classify(path5, r, shell) {
|
|
40891
41199
|
if (!r) return { status: null, contentType: null, bytes: 0, verdict: "MISSING", detail: "fetch failed (host unreachable)" };
|
|
40892
|
-
const
|
|
40893
|
-
const base = { status: r.status, contentType: r.contentType, bytes };
|
|
41200
|
+
const base = { status: r.status, contentType: r.contentType, bytes: r.bytes };
|
|
40894
41201
|
if (r.status >= 400) return { ...base, verdict: "MISSING", detail: `HTTP ${r.status}` };
|
|
40895
41202
|
const expect = expectedType(path5);
|
|
40896
|
-
if (shell.served && shell.sha256 &&
|
|
41203
|
+
if (shell.served && shell.sha256 && r.sha256 === shell.sha256) {
|
|
40897
41204
|
return { ...base, verdict: "MISSING", detail: `HTTP ${r.status} but served the SPA shell (file not deployed)` };
|
|
40898
41205
|
}
|
|
40899
41206
|
if (expect && expect !== "html" && looksLikeHtml(r.body)) {
|
|
@@ -40909,7 +41216,7 @@ var TAG = {
|
|
|
40909
41216
|
"MISSING": error("MISSING "),
|
|
40910
41217
|
"WRONG-TYPE": warning("WRONG-TYPE")
|
|
40911
41218
|
};
|
|
40912
|
-
var pageFetchCommand = new Command("fetch").description("Verify deployed non-rendered files (llms.txt, AGENTS.md, robots.txt, served JSON
|
|
41219
|
+
var pageFetchCommand = new Command("fetch").description("Verify deployed non-rendered files (llms.txt, AGENTS.md, robots.txt, served JSON...) really exist - catches the static-host trap where a missing file returns 200 with the SPA shell instead of a 404").argument("<url>", "Deployed app base URL; file paths resolve relative to it").argument("<paths...>", "One or more file paths to verify, e.g. llms.txt AGENTS.md robots.txt").option("--json", "Output as JSON").action((url, paths, opts) => run("Page fetch", async () => {
|
|
40913
41220
|
const shell = await probeShell(url);
|
|
40914
41221
|
const results = [];
|
|
40915
41222
|
for (const p of paths) {
|
|
@@ -40958,7 +41265,7 @@ init_api();
|
|
|
40958
41265
|
init_config();
|
|
40959
41266
|
init_colors();
|
|
40960
41267
|
init_utils();
|
|
40961
|
-
var recordsCommand = new Command("records").description("Manage records
|
|
41268
|
+
var recordsCommand = new Command("records").description("Manage app records (Gipity Records - validated CRUD with audit history)");
|
|
40962
41269
|
recordsCommand.command("list").description("List record tables").option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
40963
41270
|
const config = requireConfig();
|
|
40964
41271
|
const res = await get(`/api/${config.projectGuid}/records-config`);
|
|
@@ -41017,6 +41324,16 @@ recordsCommand.command("get <table> <id>").description("Get a record").option("-
|
|
|
41017
41324
|
const res = await get(`/api/${config.projectGuid}/records/${table}/${id2}`);
|
|
41018
41325
|
console.log(opts.json ? JSON.stringify(res.data) : JSON.stringify(res.data, null, 2));
|
|
41019
41326
|
}));
|
|
41327
|
+
recordsCommand.command("history <table> <id>").description("Audit history for a record (who/what changed it, with English summaries)").option("--limit <n>", "Max events", "20").option("--json", "Output as JSON").action((table, id2, opts) => run("History", async () => {
|
|
41328
|
+
const config = requireConfig();
|
|
41329
|
+
const res = await get(
|
|
41330
|
+
`/api/${config.projectGuid}/records/${table}/${id2}/history?limit=${encodeURIComponent(opts.limit)}`
|
|
41331
|
+
);
|
|
41332
|
+
printList(res.data, opts, "No history for this record.", (e) => {
|
|
41333
|
+
const summary = e.detail?.summary || `${e.action} ${e.entity_type} ${e.entity_id}`;
|
|
41334
|
+
return `${muted(e.created_at)} ${bold(e.source || "-")} ${summary}`;
|
|
41335
|
+
});
|
|
41336
|
+
}));
|
|
41020
41337
|
recordsCommand.command("create <table>").description("Create a record").requiredOption("--data <json>", "JSON object with field values").option("--json", "Output as JSON").action((table, opts) => run("Create", async () => {
|
|
41021
41338
|
const config = requireConfig();
|
|
41022
41339
|
const data = JSON.parse(opts.data);
|
|
@@ -41055,7 +41372,7 @@ fnCommand.command("list").description("List functions").option("--json", "Output
|
|
|
41055
41372
|
${muted(f.description)}` : line;
|
|
41056
41373
|
});
|
|
41057
41374
|
if (!opts.json && res.data.length > 0) {
|
|
41058
|
-
console.log(muted(`Endpoint: POST ${
|
|
41375
|
+
console.log(muted(`Endpoint: POST ${resolveApiBase()}/api/${config.projectGuid}/fn/<name> (same on dev and prod; public path: gipity fn call <name> --anon)`));
|
|
41059
41376
|
}
|
|
41060
41377
|
}));
|
|
41061
41378
|
fnCommand.command("logs <name>").description("Show recent logs").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON").action((name, opts) => run("Logs", async () => {
|
|
@@ -41094,10 +41411,9 @@ async function callAnon(projectGuid, name, body) {
|
|
|
41094
41411
|
appToken ? { "X-App-Token": appToken } : void 0
|
|
41095
41412
|
);
|
|
41096
41413
|
}
|
|
41097
|
-
fnCommand.command("call <name> [body]").description("Call a function").option("--data <json>", "JSON request body").option("--anon", "Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account").option("--field <path>", "Print only this field of the result (dot path, e.g. items.0.short_guid)").option("--json", "Output as JSON").action((name, bodyArg, opts) => run("Call", async () => {
|
|
41414
|
+
fnCommand.command("call <name> [body]").description("Call a function").option("-d, --data <json>", "JSON request body: inline JSON, @file to read a file, or @- / - for stdin").option("--file <field=@path>", "Attach a file as { data, media_type } under <field> (the vision/media service shape), repeatable, e.g. --file image=@receipt.png", (v7, acc) => (acc || []).concat(v7)).option("--anon", "Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account").option("--field <path>", "Print only this field of the result (dot path, e.g. items.0.short_guid)").option("--json", "Output as JSON").action((name, bodyArg, opts) => run("Call", async () => {
|
|
41098
41415
|
const config = requireConfig();
|
|
41099
|
-
const
|
|
41100
|
-
const body = JSON.parse(raw);
|
|
41416
|
+
const body = resolveBody(bodyArg || opts.data, opts.file);
|
|
41101
41417
|
const path5 = `/api/${config.projectGuid}/fn/${encodeURIComponent(name)}`;
|
|
41102
41418
|
if (opts.anon) {
|
|
41103
41419
|
console.error(muted("Auth: anonymous visitor (the public path a signed-out user hits)"));
|
|
@@ -41145,9 +41461,13 @@ var SERVICES = [
|
|
|
41145
41461
|
{ name: "location/ip", method: "POST", desc: "IP geolocation ({ ip? })" },
|
|
41146
41462
|
{ name: "location/geocode", method: "POST", desc: "Reverse geocode ({ lat, lon })" }
|
|
41147
41463
|
];
|
|
41148
|
-
var serviceCommand = new Command("service").description("Call an app service (llm, tts, image, transcribe, ...)").addHelpText("after", "\nCall a Gipity app service: LLM, image, music, TTS, and more
|
|
41149
|
-
serviceCommand.command("list").description("List callable app services").action(() => run("Services", async () => {
|
|
41464
|
+
var serviceCommand = new Command("service").description("Call an app service (llm, tts, image, transcribe, ...)").addHelpText("after", "\nCall a Gipity app service: LLM, image, music, TTS, and more.\nPer-service docs: gipity skill read app-llm | app-tts | app-image | app-video | app-audio (transcribe, sound, music) | app-location");
|
|
41465
|
+
serviceCommand.command("list").description("List callable app services").option("--json", "Output the service list as JSON").action((opts) => run("Services", async () => {
|
|
41150
41466
|
requireConfig();
|
|
41467
|
+
if (opts.json) {
|
|
41468
|
+
console.log(JSON.stringify(SERVICES));
|
|
41469
|
+
return;
|
|
41470
|
+
}
|
|
41151
41471
|
const width = SERVICES.reduce((m, s) => Math.max(m, s.name.length), 0);
|
|
41152
41472
|
console.log('Call one with `gipity service call <name> \'{"json":"body"}\'`');
|
|
41153
41473
|
console.log(muted("(GET endpoints like llm/models, tts/voices take --get and no body)"));
|
|
@@ -41187,7 +41507,7 @@ secretsCommand.command("list").alias("ls").description("List secret names (never
|
|
|
41187
41507
|
}
|
|
41188
41508
|
console.log(bold(`${res.data.length} ${scope} secret${res.data.length === 1 ? "" : "s"}:`));
|
|
41189
41509
|
for (const s of res.data) {
|
|
41190
|
-
const masked = s.preview ? muted(
|
|
41510
|
+
const masked = s.preview ? muted(`...${s.preview}`) : muted("(hidden)");
|
|
41191
41511
|
console.log(` ${s.name} ${masked} ${muted(`updated ${new Date(s.updated_at).toLocaleDateString()}`)}`);
|
|
41192
41512
|
}
|
|
41193
41513
|
}));
|
|
@@ -41466,10 +41786,9 @@ jobCommand.command("list").description("List jobs").option("--json", "Output as
|
|
|
41466
41786
|
${muted(j7.description)}` : line;
|
|
41467
41787
|
});
|
|
41468
41788
|
}));
|
|
41469
|
-
jobCommand.command("submit <name> [body]").description("Submit a job (returns a run guid;
|
|
41789
|
+
jobCommand.command("submit <name> [body]").description("Submit a job (returns a run guid; block on it with `job wait <guid>`)").option("-d, --data <json>", "JSON input body: inline JSON, @file to read a file, or @- / - for stdin").option("--file <field=@path>", "Attach a file as { data, media_type } under <field> (the vision/media service shape), repeatable, e.g. --file image=@scan.png", (v7, acc) => (acc || []).concat(v7)).option("--idempotency-key <key>", "Idempotency key (replays return the existing run)").option("--json", "Output as JSON").action((name, bodyArg, opts) => run("Submit", async () => {
|
|
41470
41790
|
const config = requireConfig();
|
|
41471
|
-
const
|
|
41472
|
-
const input = JSON.parse(raw);
|
|
41791
|
+
const input = resolveBody(bodyArg || opts.data, opts.file);
|
|
41473
41792
|
const body = { input };
|
|
41474
41793
|
if (opts.idempotencyKey) body.idempotency_key = opts.idempotencyKey;
|
|
41475
41794
|
const res = await post(
|
|
@@ -41481,6 +41800,10 @@ jobCommand.command("submit <name> [body]").description("Submit a job (returns a
|
|
|
41481
41800
|
} else {
|
|
41482
41801
|
const tag = res.data.replayed ? warning(" (replayed)") : "";
|
|
41483
41802
|
console.log(`${bold(res.data.run_guid)} ${muted(res.data.status)}${tag}`);
|
|
41803
|
+
const TERMINAL = /* @__PURE__ */ new Set(["success", "failed", "cancelled", "canceled", "error"]);
|
|
41804
|
+
if (!TERMINAL.has(res.data.status)) {
|
|
41805
|
+
console.error(muted(`Block until it finishes: gipity job wait ${res.data.run_guid}`));
|
|
41806
|
+
}
|
|
41484
41807
|
}
|
|
41485
41808
|
}));
|
|
41486
41809
|
jobCommand.command("status <runGuid>").description("Show status of a job run").option("--json", "Output as JSON").action((runGuid, opts) => run("Status", async () => {
|
|
@@ -41493,11 +41816,56 @@ jobCommand.command("status <runGuid>").description("Show status of a job run").o
|
|
|
41493
41816
|
const r = res.data;
|
|
41494
41817
|
const statusColor = r.status === "success" ? success : r.status === "failed" ? error : muted;
|
|
41495
41818
|
console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
|
|
41496
|
-
if (r.progress_pct != null)
|
|
41819
|
+
if (r.progress_pct != null) {
|
|
41820
|
+
const prog = `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}`;
|
|
41821
|
+
const failedish = r.status === "failed" || r.status === "cancelled" || r.status === "canceled" || r.status === "error";
|
|
41822
|
+
console.log(failedish ? `last progress before ${r.status}: ${prog}` : `progress: ${prog}`);
|
|
41823
|
+
}
|
|
41497
41824
|
if (r.duration_ms != null) console.log(`duration: ${r.duration_ms}ms`);
|
|
41498
41825
|
if (r.error) console.log(`${error("error:")} ${r.error}`);
|
|
41499
41826
|
if (r.output) console.log(`output: ${JSON.stringify(r.output)}`);
|
|
41500
41827
|
}));
|
|
41828
|
+
jobCommand.command("wait <runGuid>").description("Block until a job run finishes, or exit at --timeout so the shell never hangs (poll again to keep waiting)").option("--timeout <seconds>", "Max seconds to wait before giving up and reporting current progress. Bare number = seconds; an explicit unit (90s) means the same here and on `sandbox run --timeout`.", "90").option("--interval <seconds>", "Seconds between polls", "3").option("--json", "Output as JSON").action((runGuid, opts) => run("Wait", async () => {
|
|
41829
|
+
const config = requireConfig();
|
|
41830
|
+
const TERMINAL = /* @__PURE__ */ new Set(["success", "failed", "cancelled", "canceled", "error"]);
|
|
41831
|
+
const durOpt = parseDuration(opts.timeout, "s");
|
|
41832
|
+
const timeoutSec = durOpt ? Math.max(1, Math.round(durOpt.value)) : parseInt(opts.timeout, 10);
|
|
41833
|
+
const timeout = Number.isFinite(timeoutSec) && timeoutSec > 0 ? timeoutSec : 90;
|
|
41834
|
+
const interval = Math.max(1, parseInt(opts.interval, 10) || 3);
|
|
41835
|
+
const deadline = Date.now() + timeout * 1e3;
|
|
41836
|
+
const fetchRun = async () => (await get(`/projects/${config.projectGuid}/jobs/runs/${encodeURIComponent(runGuid)}`)).data;
|
|
41837
|
+
let r = await fetchRun();
|
|
41838
|
+
let lastProgress = "";
|
|
41839
|
+
while (!TERMINAL.has(r.status) && Date.now() < deadline) {
|
|
41840
|
+
if (!opts.json) {
|
|
41841
|
+
const prog = r.progress_pct != null ? `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}` : r.status;
|
|
41842
|
+
if (prog !== lastProgress) {
|
|
41843
|
+
console.error(muted(`... ${prog}`));
|
|
41844
|
+
lastProgress = prog;
|
|
41845
|
+
}
|
|
41846
|
+
}
|
|
41847
|
+
const remaining = deadline - Date.now();
|
|
41848
|
+
if (remaining <= 0) break;
|
|
41849
|
+
await new Promise((res) => setTimeout(res, Math.min(interval * 1e3, remaining)));
|
|
41850
|
+
r = await fetchRun();
|
|
41851
|
+
}
|
|
41852
|
+
const done = TERMINAL.has(r.status);
|
|
41853
|
+
if (opts.json) {
|
|
41854
|
+
console.log(JSON.stringify(done ? r : { ...r, waiting: true, timed_out: true }));
|
|
41855
|
+
} else if (!done) {
|
|
41856
|
+
const prog = r.progress_pct != null ? ` ${Math.round(r.progress_pct * 100)}%` : "";
|
|
41857
|
+
console.log(`${warning("still running")}${prog}${r.progress_message ? ` (${r.progress_message})` : ""} ${muted(runGuid)}`);
|
|
41858
|
+
console.log(muted(`Not finished after ${timeout}s. Run \`gipity job wait ${runGuid}\` again to keep waiting.`));
|
|
41859
|
+
} else {
|
|
41860
|
+
const statusColor = r.status === "success" ? success : error;
|
|
41861
|
+
console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
|
|
41862
|
+
if (r.duration_ms != null) console.log(`duration: ${r.duration_ms}ms`);
|
|
41863
|
+
if (r.error) console.log(`${error("error:")} ${r.error}`);
|
|
41864
|
+
if (r.output) console.log(`output: ${JSON.stringify(r.output)}`);
|
|
41865
|
+
}
|
|
41866
|
+
if (!done) process.exit(2);
|
|
41867
|
+
if (r.status !== "success") process.exit(1);
|
|
41868
|
+
}));
|
|
41501
41869
|
jobCommand.command("runs <name>").description("List recent runs for a job").option("--limit <n>", "Max entries", "20").option("--json", "Output as JSON").action((name, opts) => run("Runs", async () => {
|
|
41502
41870
|
const config = requireConfig();
|
|
41503
41871
|
const res = await get(
|
|
@@ -41512,19 +41880,44 @@ jobCommand.command("runs <name>").description("List recent runs for a job").opti
|
|
|
41512
41880
|
${error(`error: ${r.error}`)}` : line;
|
|
41513
41881
|
});
|
|
41514
41882
|
}));
|
|
41515
|
-
jobCommand.command("logs <runGuid>").description("Stream live logs for a job run (--no-follow for a one-shot snapshot)").option("--follow", "Stream via SSE (default)", true).option("--no-follow", "One-shot snapshot of run state instead of streaming").option("--json", "Output as JSON").action((runGuid, opts) => run("Logs", async () => {
|
|
41883
|
+
jobCommand.command("logs <runGuid>").description("Stream live logs for a job run (--no-follow for a one-shot snapshot)").option("--follow", "Stream via SSE (default)", true).option("--no-follow", "One-shot snapshot of run state instead of streaming").option("--timeout <seconds>", "Stop streaming after N seconds and exit cleanly, so a bounded peek at the live log never hangs the shell (no shell `timeout` wrapper needed). Bare number = seconds; unit (20s) also accepted.").option("--json", "Output as JSON").action((runGuid, opts) => run("Logs", async () => {
|
|
41516
41884
|
const config = requireConfig();
|
|
41517
41885
|
if (!opts.follow) {
|
|
41518
41886
|
const res2 = await get(`/projects/${config.projectGuid}/jobs/runs/${encodeURIComponent(runGuid)}`);
|
|
41519
41887
|
console.log(opts.json ? JSON.stringify(res2.data) : JSON.stringify(res2.data, null, 2));
|
|
41520
41888
|
return;
|
|
41521
41889
|
}
|
|
41890
|
+
let peekSeconds = 0;
|
|
41891
|
+
if (opts.timeout != null) {
|
|
41892
|
+
const dur = parseDuration(String(opts.timeout), "s");
|
|
41893
|
+
const secs = dur ? dur.value : parseFloat(String(opts.timeout));
|
|
41894
|
+
peekSeconds = Number.isFinite(secs) && secs > 0 ? secs : 0;
|
|
41895
|
+
}
|
|
41522
41896
|
const url = `${getBaseUrl()}/projects/${config.projectGuid}/jobs/runs/${encodeURIComponent(runGuid)}/logs/stream`;
|
|
41523
41897
|
const authHeader = await getAuthHeader();
|
|
41524
41898
|
const headers = { "Accept": "text/event-stream" };
|
|
41525
41899
|
if (authHeader) headers.Authorization = authHeader;
|
|
41526
|
-
const
|
|
41900
|
+
const controller = new AbortController();
|
|
41901
|
+
let peekTimer;
|
|
41902
|
+
let peekedOut = false;
|
|
41903
|
+
if (peekSeconds > 0) {
|
|
41904
|
+
peekTimer = setTimeout(() => {
|
|
41905
|
+
peekedOut = true;
|
|
41906
|
+
controller.abort();
|
|
41907
|
+
}, peekSeconds * 1e3);
|
|
41908
|
+
}
|
|
41909
|
+
let res;
|
|
41910
|
+
try {
|
|
41911
|
+
res = await fetch(url, { headers, signal: controller.signal });
|
|
41912
|
+
} catch (e) {
|
|
41913
|
+
if (peekedOut) {
|
|
41914
|
+
console.error(muted(`... still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
|
|
41915
|
+
return;
|
|
41916
|
+
}
|
|
41917
|
+
throw e;
|
|
41918
|
+
}
|
|
41527
41919
|
if (!res.ok || !res.body) {
|
|
41920
|
+
if (peekTimer) clearTimeout(peekTimer);
|
|
41528
41921
|
console.error(error(`Failed to open stream: HTTP ${res.status}`));
|
|
41529
41922
|
process.exit(1);
|
|
41530
41923
|
}
|
|
@@ -41533,8 +41926,21 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
|
|
|
41533
41926
|
let buffer = "";
|
|
41534
41927
|
let currentEvent = "";
|
|
41535
41928
|
while (true) {
|
|
41536
|
-
|
|
41537
|
-
|
|
41929
|
+
let chunk;
|
|
41930
|
+
try {
|
|
41931
|
+
chunk = await reader.read();
|
|
41932
|
+
} catch (e) {
|
|
41933
|
+
if (peekedOut) {
|
|
41934
|
+
console.error(muted(`... still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
|
|
41935
|
+
return;
|
|
41936
|
+
}
|
|
41937
|
+
throw e;
|
|
41938
|
+
}
|
|
41939
|
+
const { done, value } = chunk;
|
|
41940
|
+
if (done) {
|
|
41941
|
+
if (peekTimer) clearTimeout(peekTimer);
|
|
41942
|
+
break;
|
|
41943
|
+
}
|
|
41538
41944
|
buffer += decoder.decode(value, { stream: true });
|
|
41539
41945
|
let nl2;
|
|
41540
41946
|
while ((nl2 = buffer.indexOf("\n")) >= 0) {
|
|
@@ -41546,9 +41952,9 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
|
|
|
41546
41952
|
const dataRaw = line.slice(5).trim();
|
|
41547
41953
|
if (currentEvent === "log") {
|
|
41548
41954
|
try {
|
|
41549
|
-
const { type, chunk } = JSON.parse(dataRaw);
|
|
41550
|
-
if (type === "stderr") process.stderr.write(error(
|
|
41551
|
-
else process.stdout.write(
|
|
41955
|
+
const { type, chunk: chunk2 } = JSON.parse(dataRaw);
|
|
41956
|
+
if (type === "stderr") process.stderr.write(error(chunk2));
|
|
41957
|
+
else process.stdout.write(chunk2);
|
|
41552
41958
|
} catch {
|
|
41553
41959
|
}
|
|
41554
41960
|
} else if (currentEvent === "status") {
|
|
@@ -41588,11 +41994,11 @@ jobCommand.command("cancel <runGuid>").description("Cancel a queued or running j
|
|
|
41588
41994
|
}
|
|
41589
41995
|
}));
|
|
41590
41996
|
jobCommand.command("run-local <name>").description("Run the job in a local Docker container against your local filesystem (no platform round-trip)").option("--image <image>", "Docker image to run inside", "easyclaw/sandbox:latest").option("--no-deps", "Skip pip/npm install even if a deps file is present").action(async (name, opts) => {
|
|
41591
|
-
const { existsSync: existsSync24, statSync:
|
|
41592
|
-
const { resolve:
|
|
41997
|
+
const { existsSync: existsSync24, statSync: statSync12 } = await import("node:fs");
|
|
41998
|
+
const { resolve: resolve19, join: join27 } = await import("node:path");
|
|
41593
41999
|
const { spawnCommand: spawnCommand2, spawnSyncCommand: spawnSyncCommand2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
41594
|
-
const jobDir =
|
|
41595
|
-
if (!existsSync24(jobDir) || !
|
|
42000
|
+
const jobDir = resolve19(process.cwd(), "jobs", name);
|
|
42001
|
+
if (!existsSync24(jobDir) || !statSync12(jobDir).isDirectory()) {
|
|
41596
42002
|
console.error(error(`jobs/${name}/ not found in current directory`));
|
|
41597
42003
|
process.exit(1);
|
|
41598
42004
|
}
|
|
@@ -41613,7 +42019,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
41613
42019
|
},
|
|
41614
42020
|
{ file: "main.sh", runtime: "bash", entry: "bash" }
|
|
41615
42021
|
];
|
|
41616
|
-
const picked = variants.find((v7) => existsSync24(
|
|
42022
|
+
const picked = variants.find((v7) => existsSync24(join27(jobDir, v7.file)));
|
|
41617
42023
|
if (!picked) {
|
|
41618
42024
|
console.error(error(`No main.py / main.js / main.sh found under jobs/${name}/`));
|
|
41619
42025
|
process.exit(1);
|
|
@@ -41638,7 +42044,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
41638
42044
|
"-v",
|
|
41639
42045
|
`${jobDir}:/work`
|
|
41640
42046
|
];
|
|
41641
|
-
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync24(
|
|
42047
|
+
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync24(join27(jobDir, picked.depsFile));
|
|
41642
42048
|
const handlerCmd = `${picked.entry} /work/${picked.file}`;
|
|
41643
42049
|
let shellCmd;
|
|
41644
42050
|
if (needsDeps && picked.installCmd) {
|
|
@@ -41813,8 +42219,8 @@ emailCommand.command("log").description("Recent email() sends from your app").op
|
|
|
41813
42219
|
init_api();
|
|
41814
42220
|
init_config();
|
|
41815
42221
|
init_colors();
|
|
41816
|
-
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13 } from "fs";
|
|
41817
|
-
import { resolve as resolvePath2, dirname as dirname11, relative as relative5, isAbsolute } from "path";
|
|
42222
|
+
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, readFileSync as readFileSync19 } from "fs";
|
|
42223
|
+
import { resolve as resolvePath2, dirname as dirname11, relative as relative5, isAbsolute, basename as basename6 } from "path";
|
|
41818
42224
|
|
|
41819
42225
|
// src/provider-docs.ts
|
|
41820
42226
|
var IMAGE_GEMINI_ASPECT_RATIOS = `1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9`;
|
|
@@ -41828,11 +42234,12 @@ var TTS_PROVIDER_DESCRIPTIONS = {
|
|
|
41828
42234
|
var VIDEO_MODELS_DOC = `veo-3.1-generate-preview (best quality, up to 4K, ~$0.40/sec), veo-3.1-fast-generate-preview (faster, ~$0.10/sec), veo-3.1-lite-generate-preview (budget, ~$0.05/sec), gemini-omni-flash-preview (3-10s @ 720p, conversational editing, ~$0.10/sec)`;
|
|
41829
42235
|
|
|
41830
42236
|
// src/commands/generate.ts
|
|
41831
|
-
async function downloadFile(url, filename) {
|
|
42237
|
+
async function downloadFile(url, filename, explicit) {
|
|
41832
42238
|
const res = await fetch(url);
|
|
41833
42239
|
if (!res.ok) throw new Error(`Download failed: ${res.status}`);
|
|
41834
42240
|
const buffer = Buffer.from(await res.arrayBuffer());
|
|
41835
|
-
const outPath = correctExtension(filename, buffer);
|
|
42241
|
+
const outPath = explicit ? filename : correctExtension(filename, buffer);
|
|
42242
|
+
if (explicit) noteFormatMismatch(filename, buffer);
|
|
41836
42243
|
ensureOutputDir(outPath);
|
|
41837
42244
|
writeFileSync13(outPath, buffer);
|
|
41838
42245
|
const savedPath = resolvePath2(outPath);
|
|
@@ -41867,6 +42274,16 @@ function correctExtension(filename, buf) {
|
|
|
41867
42274
|
));
|
|
41868
42275
|
return corrected;
|
|
41869
42276
|
}
|
|
42277
|
+
function noteFormatMismatch(filename, buf) {
|
|
42278
|
+
const actual = sniffExt(buf);
|
|
42279
|
+
if (!actual) return;
|
|
42280
|
+
const dot = filename.lastIndexOf(".");
|
|
42281
|
+
const asked = dot > 0 ? filename.slice(dot + 1).toLowerCase() : "";
|
|
42282
|
+
if (!asked || canonicalExt(asked) === actual) return;
|
|
42283
|
+
console.error(muted(
|
|
42284
|
+
`Note: ${basename6(filename)} holds ${formatName(actual)} bytes though its name ends .${asked} \u2014 that's fine (browsers and image/video/edit tools sniff the bytes, not the name). Saved at the exact path you requested; reference it as-is.`
|
|
42285
|
+
));
|
|
42286
|
+
}
|
|
41870
42287
|
function ensureOutputDir(output) {
|
|
41871
42288
|
const dir = dirname11(resolvePath2(output));
|
|
41872
42289
|
try {
|
|
@@ -41886,22 +42303,46 @@ async function pushGenerated(savedPath) {
|
|
|
41886
42303
|
console.error(muted(`Note: couldn't sync to the cloud automatically (${err.message}). Run \`gipity sync\` before referencing this file in \`gipity sandbox run\`.`));
|
|
41887
42304
|
}
|
|
41888
42305
|
}
|
|
41889
|
-
|
|
42306
|
+
function readInputImages(paths) {
|
|
42307
|
+
return paths.map((p) => {
|
|
42308
|
+
let buf;
|
|
42309
|
+
try {
|
|
42310
|
+
buf = readFileSync19(p);
|
|
42311
|
+
} catch (e) {
|
|
42312
|
+
throw new Error(`can't read --input image ${p} (${e.code || e.message}).`);
|
|
42313
|
+
}
|
|
42314
|
+
const mime = guessMime(p);
|
|
42315
|
+
if (!mime.startsWith("image/")) {
|
|
42316
|
+
throw new Error(`--input ${p} is not an image (${mime}). Edit inputs must be image files.`);
|
|
42317
|
+
}
|
|
42318
|
+
return { data: buf.toString("base64"), mime_type: mime };
|
|
42319
|
+
});
|
|
42320
|
+
}
|
|
42321
|
+
var imageCommand = new Command("image").description(`Generate an image from a text prompt, or EDIT existing images with --input.
|
|
41890
42322
|
|
|
41891
42323
|
Models: ${IMAGE_MODELS_DOC}
|
|
41892
42324
|
|
|
42325
|
+
Editing (--input): pass one or more source images and the prompt becomes an edit
|
|
42326
|
+
instruction applied to them - "make it night time", "add a hat", "remove the car
|
|
42327
|
+
in the background", or compose several inputs together. Editing routes to Gemini
|
|
42328
|
+
automatically. This is the first-party way to exercise the image-edit flow from
|
|
42329
|
+
the CLI; the CLI reads the file bytes itself, so there is no base64/argv fiddling.
|
|
42330
|
+
|
|
41893
42331
|
Gemini-specific options:
|
|
41894
42332
|
--aspect-ratio Control output shape: ${IMAGE_GEMINI_ASPECT_RATIOS}
|
|
41895
42333
|
--image-size Control resolution: ${IMAGE_GEMINI_SIZES} (default: 1K)
|
|
41896
42334
|
|
|
41897
42335
|
Examples:
|
|
41898
42336
|
gipity generate image "a cat wearing a top hat"
|
|
42337
|
+
gipity generate image "make it night time" --input photo.jpg -o edited.png
|
|
42338
|
+
gipity generate image "put the person in the first photo into the second scene" --input person.png --input scene.png
|
|
41899
42339
|
gipity generate image "landscape sunset" --provider gemini --aspect-ratio 16:9 --image-size 2K
|
|
41900
42340
|
gipity generate image "product photo" --provider openai --model gpt-image-2 --size 1536x1024 --quality high
|
|
41901
|
-
gipity generate image "abstract art" --provider bfl --model flux-2-pro -o art.png`).argument("<prompt>", "Text description of the image to
|
|
42341
|
+
gipity generate image "abstract art" --provider bfl --model flux-2-pro -o art.png`).argument("<prompt>", "Text description of the image, or (with --input) the edit instruction to apply").option("--input <file>", "Source image to edit/compose (repeatable). With --input the prompt is an edit instruction; routes to Gemini.", (v7, acc) => (acc || []).concat(v7)).option("--provider <provider>", "Image provider: openai, bfl, or gemini (default: bfl)").option("--model <model>", "Model ID (see provider list above)").option("--size <size>", 'Dimensions as WxH, e.g. "1024x1024" (OpenAI/BFL)').option("--quality <quality>", "Quality: low|medium|high|auto (gpt-image-2)").option("--aspect-ratio <ratio>", "Aspect ratio (Gemini only): 1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9").option("--image-size <size>", "Output resolution (Gemini only): 512, 1K, 2K, 4K").option("--seed <n>", "Deterministic seed (BFL only): reuse one seed to keep a set of images visually coherent", (v7) => parseInt(v7, 10)).option("-o, --output <file>", "Output path (default ./generated.png). For an image your app ships, write it into the source tree so it deploys, e.g. -o src/assets/images/hero.png; the cwd default is fine for one-off generation.").option("--json", "Output as JSON").action(async (prompt2, opts) => {
|
|
41902
42342
|
try {
|
|
41903
42343
|
const { config } = await resolveProjectContext();
|
|
41904
42344
|
if (opts.output) ensureOutputDir(opts.output);
|
|
42345
|
+
const inputImages = opts.input ? readInputImages(opts.input) : void 0;
|
|
41905
42346
|
const doGenerate = () => post(`/projects/${config.projectGuid}/generate/image`, {
|
|
41906
42347
|
prompt: prompt2,
|
|
41907
42348
|
provider: opts.provider,
|
|
@@ -41910,12 +42351,14 @@ Examples:
|
|
|
41910
42351
|
quality: opts.quality,
|
|
41911
42352
|
aspect_ratio: opts.aspectRatio,
|
|
41912
42353
|
image_size: opts.imageSize,
|
|
41913
|
-
seed: Number.isFinite(opts.seed) ? opts.seed : void 0
|
|
42354
|
+
seed: Number.isFinite(opts.seed) ? opts.seed : void 0,
|
|
42355
|
+
input_images: inputImages
|
|
41914
42356
|
});
|
|
41915
|
-
const
|
|
42357
|
+
const verb = inputImages ? "Editing image..." : "Generating image...";
|
|
42358
|
+
const result = opts.json ? await doGenerate() : await withSpinner(verb, doGenerate, { done: null });
|
|
41916
42359
|
const ext = result.content_type.includes("png") ? "png" : "jpg";
|
|
41917
42360
|
const filename = opts.output || `generated.${ext}`;
|
|
41918
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42361
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
41919
42362
|
if (opts.json) {
|
|
41920
42363
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
41921
42364
|
} else {
|
|
@@ -41957,9 +42400,9 @@ Examples:
|
|
|
41957
42400
|
aspect_ratio: opts.aspect,
|
|
41958
42401
|
resolution: opts.resolution
|
|
41959
42402
|
});
|
|
41960
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating video
|
|
42403
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating video...", doGenerate, { done: null });
|
|
41961
42404
|
const filename = opts.output || "generated.mp4";
|
|
41962
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42405
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
41963
42406
|
if (opts.json) {
|
|
41964
42407
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
41965
42408
|
} else {
|
|
@@ -42005,9 +42448,9 @@ Examples:
|
|
|
42005
42448
|
language: opts.language,
|
|
42006
42449
|
speakers
|
|
42007
42450
|
});
|
|
42008
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating speech
|
|
42451
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating speech...", doGenerate, { done: null });
|
|
42009
42452
|
const filename = opts.output || "speech.mp3";
|
|
42010
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42453
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42011
42454
|
if (opts.json) {
|
|
42012
42455
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
42013
42456
|
} else {
|
|
@@ -42044,9 +42487,9 @@ Examples:
|
|
|
42044
42487
|
model: opts.model,
|
|
42045
42488
|
instrumental: !opts.vocals
|
|
42046
42489
|
});
|
|
42047
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating music
|
|
42490
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating music...", doGenerate, { done: null });
|
|
42048
42491
|
const filename = opts.output || "music.mp3";
|
|
42049
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42492
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42050
42493
|
if (opts.json) {
|
|
42051
42494
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
42052
42495
|
} else {
|
|
@@ -42082,9 +42525,9 @@ Examples:
|
|
|
42082
42525
|
duration_seconds: opts.duration,
|
|
42083
42526
|
prompt_influence: opts.influence
|
|
42084
42527
|
});
|
|
42085
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect
|
|
42528
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect...", doGenerate, { done: null });
|
|
42086
42529
|
const filename = opts.output || "sound.mp3";
|
|
42087
|
-
const savedPath = await downloadFile(result.url, filename);
|
|
42530
|
+
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42088
42531
|
if (opts.json) {
|
|
42089
42532
|
console.log(JSON.stringify({ ...result, saved: savedPath }));
|
|
42090
42533
|
} else {
|
|
@@ -42103,14 +42546,14 @@ var generateCommand = new Command("generate").description("Generate images, vide
|
|
|
42103
42546
|
init_api();
|
|
42104
42547
|
init_config();
|
|
42105
42548
|
init_colors();
|
|
42106
|
-
import { existsSync as existsSync15, readFileSync as
|
|
42549
|
+
import { existsSync as existsSync15, readFileSync as readFileSync20 } from "fs";
|
|
42107
42550
|
import { join as join18 } from "path";
|
|
42108
42551
|
function readInstalledKitReadme(name) {
|
|
42109
42552
|
const root = getProjectRoot() ?? process.cwd();
|
|
42110
42553
|
const kitDir = join18(root, "src", "packages", name);
|
|
42111
42554
|
if (!existsSync15(join18(kitDir, "package.json"))) return null;
|
|
42112
42555
|
const readme = join18(kitDir, "README.md");
|
|
42113
|
-
return existsSync15(readme) ?
|
|
42556
|
+
return existsSync15(readme) ? readFileSync20(readme, "utf-8") : null;
|
|
42114
42557
|
}
|
|
42115
42558
|
var skillCommand = new Command("skill").description("Task docs - read the matching skill before building");
|
|
42116
42559
|
skillCommand.command("list").description("List skills").option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
@@ -42284,14 +42727,14 @@ var domainCommand = new Command("domain").description("Manage custom domains").a
|
|
|
42284
42727
|
|
|
42285
42728
|
// src/commands/realtime.ts
|
|
42286
42729
|
import { existsSync as existsSync16 } from "fs";
|
|
42287
|
-
import { dirname as dirname12, resolve as
|
|
42730
|
+
import { dirname as dirname12, resolve as resolve14 } from "path";
|
|
42288
42731
|
init_api();
|
|
42289
42732
|
init_config();
|
|
42290
42733
|
init_colors();
|
|
42291
42734
|
function hasDeployManifest() {
|
|
42292
42735
|
const cfgPath = getConfigPath();
|
|
42293
42736
|
if (!cfgPath) return false;
|
|
42294
|
-
return existsSync16(
|
|
42737
|
+
return existsSync16(resolve14(dirname12(cfgPath), "gipity.yaml"));
|
|
42295
42738
|
}
|
|
42296
42739
|
var roomCommand = new Command("room").description("Manage realtime rooms").argument("[action]", "list | create | delete | info", "list").argument("[name]", "room name (for create | delete | info)").option("--type <type>", "room type for create: state | relay", "state").option("--auth <level>", "auth level for create: public | user", "public").option("--max-clients <n>", "max clients for create (1-200)").option("--json", "Output as JSON").action((action, name, opts) => run("Realtime room", async () => {
|
|
42297
42740
|
const config = requireConfig();
|
|
@@ -42391,6 +42834,16 @@ function printSkippedCandidates(skipped) {
|
|
|
42391
42834
|
if (skipped.length > 10) console.log(muted(` ... and ${skipped.length - 10} more`));
|
|
42392
42835
|
console.log(muted('Run frontend/kit module tests in the sandbox: gipity sandbox run bash "node <file>"'));
|
|
42393
42836
|
}
|
|
42837
|
+
function undeployedFunctionsFromFailures(results) {
|
|
42838
|
+
const names = /* @__PURE__ */ new Set();
|
|
42839
|
+
for (const r of results) {
|
|
42840
|
+
if (r.status !== "failed" || !r.error) continue;
|
|
42841
|
+
if (!/404\b/.test(r.error) || !/not\s+found/i.test(r.error)) continue;
|
|
42842
|
+
const m = r.error.match(/Function ['"]?([\w-]+)['"]? (?:not found|failed)/i);
|
|
42843
|
+
if (m) names.add(m[1]);
|
|
42844
|
+
}
|
|
42845
|
+
return [...names];
|
|
42846
|
+
}
|
|
42394
42847
|
var LONG_RUN_MS = 6e4;
|
|
42395
42848
|
async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
42396
42849
|
const startTime = Date.now();
|
|
@@ -42459,7 +42912,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
42459
42912
|
const elapsed = Math.round((now - startTime) / 1e3);
|
|
42460
42913
|
const progress = data.totalFiles === 0 ? "starting up" : `${data.completedFiles}/${data.totalFiles} files`;
|
|
42461
42914
|
const tally = data.passed + data.failed > 0 ? ` (${data.passed} passed${data.failed > 0 ? `, ${data.failed} failed` : ""} so far)` : "";
|
|
42462
|
-
console.log(muted(`
|
|
42915
|
+
console.log(muted(` ... still running - ${progress}${tally}, ${elapsed}s elapsed`));
|
|
42463
42916
|
if (now - startTime >= LONG_RUN_MS && !longRunHintShown) {
|
|
42464
42917
|
longRunHintShown = true;
|
|
42465
42918
|
console.log(muted(" Note: progressing, not hung. LLM-backed tests can take minutes. To verify one function fast, use `gipity fn call <name>`; narrow this suite with `gipity test <path>`."));
|
|
@@ -42467,7 +42920,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
42467
42920
|
}
|
|
42468
42921
|
}
|
|
42469
42922
|
}
|
|
42470
|
-
await new Promise((
|
|
42923
|
+
await new Promise((resolve19) => setTimeout(resolve19, getPollInterval()));
|
|
42471
42924
|
}
|
|
42472
42925
|
}
|
|
42473
42926
|
var testCommand = new Command("test").description("Run tests").argument("[path]", 'Test path filter (e.g. "api", "e2e/portal")').option("--filter <path>", "Alias for the positional test path filter").option("--timeout <ms>", "Per-test timeout in ms", "30000").option("--retry <n>", "Retry failed tests N times", "0").option("--no-sync", "Skip sync-up before tests").option("--json", "Output as JSON").action((pathFilter, opts) => run("Test", async () => {
|
|
@@ -42532,6 +42985,15 @@ var testCommand = new Command("test").description("Run tests").argument("[path]"
|
|
|
42532
42985
|
}
|
|
42533
42986
|
console.log("");
|
|
42534
42987
|
}
|
|
42988
|
+
const undeployed = undeployedFunctionsFromFailures(data.results);
|
|
42989
|
+
if (undeployed.length > 0) {
|
|
42990
|
+
const list = undeployed.map((n) => `'${n}'`).join(", ");
|
|
42991
|
+
console.log(warning(
|
|
42992
|
+
`${undeployed.length === 1 ? "Function" : "Functions"} ${list} 404'd: tests call functions against the DEPLOYED build, and ${undeployed.length === 1 ? "this one is" : "these are"} not deployed yet.`
|
|
42993
|
+
));
|
|
42994
|
+
console.log(muted("Deploy first, then re-run: gipity deploy dev && gipity test"));
|
|
42995
|
+
console.log("");
|
|
42996
|
+
}
|
|
42535
42997
|
const parts = [];
|
|
42536
42998
|
if (data.passed > 0) parts.push(success(`${data.passed} passed`));
|
|
42537
42999
|
if (data.failed > 0) parts.push(error(`${data.failed} failed`));
|
|
@@ -42681,12 +43143,12 @@ var locationCommand = new Command("location").description("Show location").argum
|
|
|
42681
43143
|
}));
|
|
42682
43144
|
|
|
42683
43145
|
// src/commands/doctor.ts
|
|
42684
|
-
import { existsSync as existsSync18, readFileSync as
|
|
42685
|
-
import { join as join20, resolve as
|
|
43146
|
+
import { existsSync as existsSync18, readFileSync as readFileSync22, statSync as statSync9 } from "fs";
|
|
43147
|
+
import { join as join20, resolve as resolve15 } from "path";
|
|
42686
43148
|
import { homedir as homedir14 } from "os";
|
|
42687
43149
|
|
|
42688
43150
|
// src/updater/state.ts
|
|
42689
|
-
import { readFileSync as
|
|
43151
|
+
import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync17 } from "fs";
|
|
42690
43152
|
import { join as join19 } from "path";
|
|
42691
43153
|
import { homedir as homedir13 } from "os";
|
|
42692
43154
|
var GIPITY_DIR = join19(homedir13(), ".gipity");
|
|
@@ -42711,7 +43173,7 @@ function ensureDir() {
|
|
|
42711
43173
|
function readState() {
|
|
42712
43174
|
if (!existsSync17(STATE_FILE)) return { ...DEFAULT_STATE };
|
|
42713
43175
|
try {
|
|
42714
|
-
return { ...DEFAULT_STATE, ...JSON.parse(
|
|
43176
|
+
return { ...DEFAULT_STATE, ...JSON.parse(readFileSync21(STATE_FILE, "utf-8")) };
|
|
42715
43177
|
} catch {
|
|
42716
43178
|
return { ...DEFAULT_STATE };
|
|
42717
43179
|
}
|
|
@@ -42723,7 +43185,7 @@ function writeState(state) {
|
|
|
42723
43185
|
function readSettings() {
|
|
42724
43186
|
if (!existsSync17(SETTINGS_FILE)) return { ...DEFAULT_SETTINGS };
|
|
42725
43187
|
try {
|
|
42726
|
-
return { ...DEFAULT_SETTINGS, ...JSON.parse(
|
|
43188
|
+
return { ...DEFAULT_SETTINGS, ...JSON.parse(readFileSync21(SETTINGS_FILE, "utf-8")) };
|
|
42727
43189
|
} catch {
|
|
42728
43190
|
return { ...DEFAULT_SETTINGS };
|
|
42729
43191
|
}
|
|
@@ -42759,7 +43221,7 @@ function localVersion() {
|
|
|
42759
43221
|
const pkgPath = join20(LOCAL_PKG_DIR, "package.json");
|
|
42760
43222
|
if (!existsSync18(pkgPath)) return null;
|
|
42761
43223
|
try {
|
|
42762
|
-
return JSON.parse(
|
|
43224
|
+
return JSON.parse(readFileSync22(pkgPath, "utf-8")).version ?? null;
|
|
42763
43225
|
} catch {
|
|
42764
43226
|
return null;
|
|
42765
43227
|
}
|
|
@@ -42767,7 +43229,7 @@ function localVersion() {
|
|
|
42767
43229
|
function shimVersion() {
|
|
42768
43230
|
try {
|
|
42769
43231
|
const url = new URL("../../package.json", import.meta.url);
|
|
42770
|
-
return JSON.parse(
|
|
43232
|
+
return JSON.parse(readFileSync22(url, "utf-8")).version;
|
|
42771
43233
|
} catch {
|
|
42772
43234
|
return "unknown";
|
|
42773
43235
|
}
|
|
@@ -42850,7 +43312,7 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
42850
43312
|
console.log(`${muted("claude code ")} installed ${yn(env.claude.installed)} \xB7 authenticated ${yn(env.claude.authenticated)}`);
|
|
42851
43313
|
const autostartLabel = env.relay.autostart === null ? muted("n/a") : yn(env.relay.autostart);
|
|
42852
43314
|
console.log(`${muted("relay ")} paired ${yn(env.relay.paired)} \xB7 running ${yn(env.relay.running)} \xB7 autostart ${autostartLabel}${env.relay.paused ? warning(" \xB7 paused") : ""}${env.relay.device ? muted(` (${env.relay.device.name})`) : ""}`);
|
|
42853
|
-
if (existsSync18(
|
|
43315
|
+
if (existsSync18(resolve15(process.cwd(), ".codex", "hooks.json"))) {
|
|
42854
43316
|
console.log(`${muted("codex hooks ")} ${warning("written")} - session capture needs a one-time approval: run /hooks inside Codex in this project`);
|
|
42855
43317
|
}
|
|
42856
43318
|
console.log(`${muted("ready ")} ${env.ready ? success("yes") : warning("no - run `gipity build` (or the desktop app) to finish setup")}`);
|
|
@@ -42870,14 +43332,68 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
42870
43332
|
console.log(`${muted("last check ")} ${rel(state.lastCheckAt)}`);
|
|
42871
43333
|
console.log(`${muted("last error ")} ${state.lastError ? error(state.lastError) : dim("none")}`);
|
|
42872
43334
|
console.log(`${muted("state file ")} ${existsSync18(STATE_FILE) ? STATE_FILE : dim("(none yet)")}`);
|
|
42873
|
-
console.log(`${muted("update log ")} ${existsSync18(UPDATE_LOG) ? `${UPDATE_LOG} (${
|
|
43335
|
+
console.log(`${muted("update log ")} ${existsSync18(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync9(UPDATE_LOG).size} bytes)` : dim("(none yet)")}`);
|
|
42874
43336
|
console.log("");
|
|
42875
43337
|
console.log(dim("Force an update with: gipity update"));
|
|
42876
43338
|
});
|
|
42877
43339
|
|
|
42878
43340
|
// src/updater/check.ts
|
|
42879
|
-
import { appendFileSync
|
|
43341
|
+
import { appendFileSync } from "fs";
|
|
43342
|
+
|
|
43343
|
+
// src/updater/install.ts
|
|
43344
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync15, rmSync as rmSync2, statSync as statSync10, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
|
|
43345
|
+
import { dirname as dirname13, join as join21 } from "path";
|
|
42880
43346
|
init_platform();
|
|
43347
|
+
var UPDATE_LOCK = join21(GIPITY_DIR, "update.lock");
|
|
43348
|
+
var LOCK_STALE_MS2 = 10 * 60 * 1e3;
|
|
43349
|
+
function npmInstallGipity(version) {
|
|
43350
|
+
const res = spawnSyncCommand(resolveCommand("npm"), ["install", "--no-audit", "--no-fund", "--ignore-scripts", `gipity@${version}`], {
|
|
43351
|
+
cwd: LOCAL_DIR,
|
|
43352
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
43353
|
+
encoding: "utf-8"
|
|
43354
|
+
});
|
|
43355
|
+
return {
|
|
43356
|
+
ok: res.status === 0 && existsSync19(LOCAL_ENTRY),
|
|
43357
|
+
status: res.status,
|
|
43358
|
+
stderr: (res.stderr || "").toString(),
|
|
43359
|
+
spawnError: res.error
|
|
43360
|
+
};
|
|
43361
|
+
}
|
|
43362
|
+
var WEDGE_CODES = /\b(ENOTEMPTY|EEXIST|ENOTDIR|EISDIR|EJSONPARSE)\b/;
|
|
43363
|
+
function isWedged(stderr) {
|
|
43364
|
+
return WEDGE_CODES.test(stderr);
|
|
43365
|
+
}
|
|
43366
|
+
function resetLocalTree(dir = LOCAL_DIR) {
|
|
43367
|
+
mkdirSync15(dir, { recursive: true });
|
|
43368
|
+
rmSync2(join21(dir, "node_modules"), { recursive: true, force: true });
|
|
43369
|
+
rmSync2(join21(dir, "package-lock.json"), { force: true });
|
|
43370
|
+
writeFileSync15(join21(dir, "package.json"), JSON.stringify({ name: "gipity-local", private: true, version: "0.0.0" }, null, 2));
|
|
43371
|
+
}
|
|
43372
|
+
function acquireUpdateLock(lockPath2 = UPDATE_LOCK) {
|
|
43373
|
+
mkdirSync15(dirname13(lockPath2), { recursive: true });
|
|
43374
|
+
try {
|
|
43375
|
+
writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
|
|
43376
|
+
return true;
|
|
43377
|
+
} catch {
|
|
43378
|
+
try {
|
|
43379
|
+
if (Date.now() - statSync10(lockPath2).mtimeMs > LOCK_STALE_MS2) {
|
|
43380
|
+
unlinkSync5(lockPath2);
|
|
43381
|
+
writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
|
|
43382
|
+
return true;
|
|
43383
|
+
}
|
|
43384
|
+
} catch {
|
|
43385
|
+
}
|
|
43386
|
+
return false;
|
|
43387
|
+
}
|
|
43388
|
+
}
|
|
43389
|
+
function releaseUpdateLock(lockPath2 = UPDATE_LOCK) {
|
|
43390
|
+
try {
|
|
43391
|
+
unlinkSync5(lockPath2);
|
|
43392
|
+
} catch {
|
|
43393
|
+
}
|
|
43394
|
+
}
|
|
43395
|
+
|
|
43396
|
+
// src/updater/check.ts
|
|
42881
43397
|
var CHECK_INTERVAL_MS = 4 * 60 * 60 * 1e3;
|
|
42882
43398
|
function log(line) {
|
|
42883
43399
|
try {
|
|
@@ -42906,12 +43422,24 @@ async function fetchLatestVersion() {
|
|
|
42906
43422
|
return json.version;
|
|
42907
43423
|
}
|
|
42908
43424
|
function installVersion(version) {
|
|
42909
|
-
|
|
42910
|
-
|
|
42911
|
-
|
|
42912
|
-
|
|
42913
|
-
|
|
42914
|
-
|
|
43425
|
+
let res = npmInstallGipity(version);
|
|
43426
|
+
if (!res.ok && !res.spawnError && isWedged(res.stderr)) {
|
|
43427
|
+
log(`wedged install tree detected; wiping node_modules and retrying:
|
|
43428
|
+
${res.stderr.trim().slice(-2e3)}`);
|
|
43429
|
+
resetLocalTree();
|
|
43430
|
+
res = npmInstallGipity(version);
|
|
43431
|
+
if (res.ok) log("clean reinstall succeeded");
|
|
43432
|
+
}
|
|
43433
|
+
if (res.ok) return { ok: true };
|
|
43434
|
+
if (res.spawnError) {
|
|
43435
|
+
log(`npm spawn failed: ${res.spawnError.message}`);
|
|
43436
|
+
return { ok: false, detail: res.spawnError.message };
|
|
43437
|
+
}
|
|
43438
|
+
if (res.stderr.trim()) log(`npm stderr (exit ${res.status}):
|
|
43439
|
+
${res.stderr.trim().slice(-2e3)}`);
|
|
43440
|
+
if (res.status === 0) return { ok: false, detail: "npm succeeded but the installed package is missing dist/index.js" };
|
|
43441
|
+
const firstLine = res.stderr.split("\n").map((l7) => l7.trim()).find((l7) => l7.length > 0) || `npm exit ${res.status}`;
|
|
43442
|
+
return { ok: false, detail: firstLine.length > 160 ? firstLine.slice(0, 157) + "..." : firstLine };
|
|
42915
43443
|
}
|
|
42916
43444
|
async function runCheck(opts = {}) {
|
|
42917
43445
|
const state = readState();
|
|
@@ -42944,17 +43472,27 @@ async function runCheck(opts = {}) {
|
|
|
42944
43472
|
log(`up-to-date (current=${current}, latest=${latest})`);
|
|
42945
43473
|
return { updated: false, reason: "up-to-date" };
|
|
42946
43474
|
}
|
|
43475
|
+
if (!acquireUpdateLock()) {
|
|
43476
|
+
log("skipped: another update is already in progress");
|
|
43477
|
+
return { updated: false, reason: "another update is already in progress" };
|
|
43478
|
+
}
|
|
42947
43479
|
log(`upgrading ${current} \u2192 ${latest}`);
|
|
42948
|
-
const ok = installVersion(latest);
|
|
42949
43480
|
state.lastCheckAt = Date.now();
|
|
42950
|
-
|
|
43481
|
+
writeState(state);
|
|
43482
|
+
let install;
|
|
43483
|
+
try {
|
|
43484
|
+
install = installVersion(latest);
|
|
43485
|
+
} finally {
|
|
43486
|
+
releaseUpdateLock();
|
|
43487
|
+
}
|
|
43488
|
+
if (install.ok) {
|
|
42951
43489
|
state.installedVersion = latest;
|
|
42952
43490
|
state.lastError = null;
|
|
42953
43491
|
writeState(state);
|
|
42954
43492
|
log(`upgraded to ${latest}`);
|
|
42955
43493
|
return { updated: true, from: current, to: latest };
|
|
42956
43494
|
}
|
|
42957
|
-
state.lastError = `npm install gipity@${latest} failed
|
|
43495
|
+
state.lastError = `npm install gipity@${latest} failed` + (install.detail ? `: ${install.detail}` : "");
|
|
42958
43496
|
writeState(state);
|
|
42959
43497
|
log(state.lastError);
|
|
42960
43498
|
return { updated: false, reason: state.lastError };
|
|
@@ -42978,6 +43516,10 @@ var updateCommand = new Command("update").description("Update the CLI").action(a
|
|
|
42978
43516
|
console.log(success("Already on the latest version."));
|
|
42979
43517
|
} else {
|
|
42980
43518
|
console.log(warning(`No update applied: ${result.reason}`));
|
|
43519
|
+
if (result.reason?.startsWith("npm install")) {
|
|
43520
|
+
console.log(dim("Full npm output: ~/.gipity/update.log"));
|
|
43521
|
+
console.log(dim("If it keeps failing, delete ~/.gipity/local and run any gipity command to reinstall."));
|
|
43522
|
+
}
|
|
42981
43523
|
}
|
|
42982
43524
|
});
|
|
42983
43525
|
|
|
@@ -42985,17 +43527,17 @@ var updateCommand = new Command("update").description("Update the CLI").action(a
|
|
|
42985
43527
|
init_api();
|
|
42986
43528
|
init_utils();
|
|
42987
43529
|
init_colors();
|
|
42988
|
-
import { existsSync as existsSync22, readFileSync as
|
|
43530
|
+
import { existsSync as existsSync22, readFileSync as readFileSync25, unlinkSync as unlinkSync8 } from "fs";
|
|
42989
43531
|
import { spawn as spawn2 } from "child_process";
|
|
42990
43532
|
|
|
42991
43533
|
// src/relay/daemon.ts
|
|
42992
43534
|
init_platform();
|
|
42993
43535
|
init_config();
|
|
42994
|
-
import { appendFileSync as appendFileSync3, mkdirSync as
|
|
43536
|
+
import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync17, existsSync as existsSync21, readFileSync as readFileSync24, writeFileSync as writeFileSync16, chmodSync as chmodSync3, closeSync as closeSync4, openSync as openSync4, unlinkSync as unlinkSync7 } from "fs";
|
|
42995
43537
|
import { stat, readFile } from "fs/promises";
|
|
42996
43538
|
import { createInterface as createInterface2 } from "readline";
|
|
42997
43539
|
import { homedir as homedir16, hostname as hostname5, platform as osPlatform3, loadavg as loadavg2, freemem as freemem2, totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
42998
|
-
import { join as
|
|
43540
|
+
import { join as join24 } from "path";
|
|
42999
43541
|
init_auth();
|
|
43000
43542
|
init_api();
|
|
43001
43543
|
|
|
@@ -43752,20 +44294,20 @@ init_platform();
|
|
|
43752
44294
|
init_client_context();
|
|
43753
44295
|
import { cpus, freemem, totalmem, loadavg, release, uptime, homedir as homedir15 } from "os";
|
|
43754
44296
|
import { statfsSync, readdirSync as readdirSync9 } from "fs";
|
|
43755
|
-
import { join as
|
|
44297
|
+
import { join as join22 } from "path";
|
|
43756
44298
|
var PROBE_TIMEOUT_MS = 4e3;
|
|
43757
44299
|
function parseVersion(out) {
|
|
43758
44300
|
const m = out.match(/\d+\.\d+(?:\.\d+)?(?:[-.\w]*)?/);
|
|
43759
44301
|
return m ? m[0] : void 0;
|
|
43760
44302
|
}
|
|
43761
44303
|
function runProbe(cmd, args) {
|
|
43762
|
-
return new Promise((
|
|
44304
|
+
return new Promise((resolve19) => {
|
|
43763
44305
|
let out = "";
|
|
43764
44306
|
let settled = false;
|
|
43765
44307
|
const done = (s) => {
|
|
43766
44308
|
if (!settled) {
|
|
43767
44309
|
settled = true;
|
|
43768
|
-
|
|
44310
|
+
resolve19(s);
|
|
43769
44311
|
}
|
|
43770
44312
|
};
|
|
43771
44313
|
let child;
|
|
@@ -43819,7 +44361,7 @@ async function detectGpu() {
|
|
|
43819
44361
|
function diskUsage() {
|
|
43820
44362
|
try {
|
|
43821
44363
|
if (typeof statfsSync !== "function") return void 0;
|
|
43822
|
-
const st2 = statfsSync(
|
|
44364
|
+
const st2 = statfsSync(join22(homedir15(), "GipityProjects"), { bigint: false });
|
|
43823
44365
|
if (!st2 || !st2.bsize) return void 0;
|
|
43824
44366
|
return { total: st2.bsize * st2.blocks, free: st2.bsize * st2.bavail };
|
|
43825
44367
|
} catch {
|
|
@@ -43833,7 +44375,7 @@ function diskUsage() {
|
|
|
43833
44375
|
}
|
|
43834
44376
|
function localProjectCount() {
|
|
43835
44377
|
try {
|
|
43836
|
-
return readdirSync9(
|
|
44378
|
+
return readdirSync9(join22(homedir15(), "GipityProjects"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
|
|
43837
44379
|
} catch {
|
|
43838
44380
|
return void 0;
|
|
43839
44381
|
}
|
|
@@ -43964,8 +44506,8 @@ var SessionPool = class {
|
|
|
43964
44506
|
const done = new Promise((r) => {
|
|
43965
44507
|
markDone = r;
|
|
43966
44508
|
});
|
|
43967
|
-
return new Promise((
|
|
43968
|
-
const turn = { onMessage: p.onMessage, resolve:
|
|
44509
|
+
return new Promise((resolve19, reject) => {
|
|
44510
|
+
const turn = { onMessage: p.onMessage, resolve: resolve19, reject, interruptRequested: false, wasHot, done, markDone };
|
|
43969
44511
|
session.current = turn;
|
|
43970
44512
|
session.state = "running";
|
|
43971
44513
|
session.lastActivityAt = Date.now();
|
|
@@ -44142,7 +44684,7 @@ var SessionPool = class {
|
|
|
44142
44684
|
// src/relay/daemon.ts
|
|
44143
44685
|
init_config();
|
|
44144
44686
|
init_api();
|
|
44145
|
-
var RELAY_LOG_PATH =
|
|
44687
|
+
var RELAY_LOG_PATH = join24(homedir16(), ".gipity", "relay.log");
|
|
44146
44688
|
var HEARTBEAT_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_HEARTBEAT_MS || "60000", 10);
|
|
44147
44689
|
var DIAGNOSTICS_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_DIAGNOSTICS_MS || String(24 * 60 * 60 * 1e3), 10);
|
|
44148
44690
|
var LONG_POLL_TIMEOUT_MS = parseInt(process.env.GIPITY_RELAY_POLL_TIMEOUT_MS || "35000", 10);
|
|
@@ -44259,8 +44801,8 @@ function log2(level, msg, extra) {
|
|
|
44259
44801
|
const pretty = `${C7.dim(hhmmss())} ${badge(level)} ${C7.bold(msg)}${formatExtra(extra)}`;
|
|
44260
44802
|
process.stderr.write(pretty + "\n");
|
|
44261
44803
|
try {
|
|
44262
|
-
const dir =
|
|
44263
|
-
|
|
44804
|
+
const dir = join24(homedir16(), ".gipity");
|
|
44805
|
+
mkdirSync17(dir, { recursive: true });
|
|
44264
44806
|
lockLogPerms(dir, RELAY_LOG_PATH);
|
|
44265
44807
|
const json = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...extra ?? {} });
|
|
44266
44808
|
appendFileSync3(RELAY_LOG_PATH, json + "\n");
|
|
@@ -44384,10 +44926,10 @@ async function heartbeatLoop(ctx) {
|
|
|
44384
44926
|
}
|
|
44385
44927
|
await Promise.race([
|
|
44386
44928
|
sleep4(HEARTBEAT_INTERVAL_MS, ctx.abort.signal),
|
|
44387
|
-
new Promise((
|
|
44929
|
+
new Promise((resolve19) => {
|
|
44388
44930
|
heartbeatPoke = () => {
|
|
44389
44931
|
heartbeatPoke = null;
|
|
44390
|
-
|
|
44932
|
+
resolve19();
|
|
44391
44933
|
};
|
|
44392
44934
|
})
|
|
44393
44935
|
]);
|
|
@@ -44431,9 +44973,9 @@ async function dispatchLoop(ctx, opts) {
|
|
|
44431
44973
|
while (inflight.size >= MAX_CONCURRENT_DISPATCHES && !ctx.abort.signal.aborted) {
|
|
44432
44974
|
await Promise.race([
|
|
44433
44975
|
...inflight,
|
|
44434
|
-
new Promise((
|
|
44435
|
-
if (ctx.abort.signal.aborted) return
|
|
44436
|
-
ctx.abort.signal.addEventListener("abort", () =>
|
|
44976
|
+
new Promise((resolve19) => {
|
|
44977
|
+
if (ctx.abort.signal.aborted) return resolve19();
|
|
44978
|
+
ctx.abort.signal.addEventListener("abort", () => resolve19(), { once: true });
|
|
44437
44979
|
})
|
|
44438
44980
|
]);
|
|
44439
44981
|
}
|
|
@@ -44660,7 +45202,7 @@ function isSafeSessionId(s) {
|
|
|
44660
45202
|
function transcriptPathFor(cwd, sessionId) {
|
|
44661
45203
|
if (!isSafeSessionId(sessionId)) return null;
|
|
44662
45204
|
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
44663
|
-
return
|
|
45205
|
+
return join24(homedir16(), ".claude", "projects", slug, `${sessionId}.jsonl`);
|
|
44664
45206
|
}
|
|
44665
45207
|
function formatDuration(ms2) {
|
|
44666
45208
|
const totalSec = ms2 / 1e3;
|
|
@@ -45114,11 +45656,11 @@ async function resolveCwdForProject(d) {
|
|
|
45114
45656
|
throw new Error(`Invalid project slug: ${JSON.stringify(d.project_slug)}`);
|
|
45115
45657
|
}
|
|
45116
45658
|
const root = getProjectsRoot();
|
|
45117
|
-
const path5 =
|
|
45118
|
-
const configPath =
|
|
45659
|
+
const path5 = join24(root, d.project_slug);
|
|
45660
|
+
const configPath = join24(path5, ".gipity.json");
|
|
45119
45661
|
if (existsSync21(configPath)) {
|
|
45120
45662
|
try {
|
|
45121
|
-
const cfg = JSON.parse(
|
|
45663
|
+
const cfg = JSON.parse(readFileSync24(configPath, "utf-8"));
|
|
45122
45664
|
if (cfg.projectGuid === d.project_guid) return { cwd: path5, bootstrapped: false };
|
|
45123
45665
|
log2("warn", "project dir exists but guid mismatch - using it anyway", {
|
|
45124
45666
|
path: path5,
|
|
@@ -45130,9 +45672,9 @@ async function resolveCwdForProject(d) {
|
|
|
45130
45672
|
}
|
|
45131
45673
|
}
|
|
45132
45674
|
log2("info", "bootstrapping new project dir", { slug: d.project_slug, path: path5 });
|
|
45133
|
-
|
|
45675
|
+
mkdirSync17(path5, { recursive: true });
|
|
45134
45676
|
const apiBase2 = getApiBaseOverride() || DEFAULT_API_BASE;
|
|
45135
|
-
|
|
45677
|
+
writeFileSync16(configPath, JSON.stringify({
|
|
45136
45678
|
projectGuid: d.project_guid,
|
|
45137
45679
|
projectSlug: d.project_slug,
|
|
45138
45680
|
accountSlug: d.account_slug,
|
|
@@ -45209,7 +45751,7 @@ async function killRunningForConv(convGuid) {
|
|
|
45209
45751
|
}
|
|
45210
45752
|
}
|
|
45211
45753
|
const graceTimers = [];
|
|
45212
|
-
const escalate = new Promise((
|
|
45754
|
+
const escalate = new Promise((resolve19) => {
|
|
45213
45755
|
const t = setTimeout(() => {
|
|
45214
45756
|
for (const e of matches) {
|
|
45215
45757
|
log2("warn", "previous dispatch ignored SIGTERM - escalating to SIGKILL", { conv: convGuid });
|
|
@@ -45218,7 +45760,7 @@ async function killRunningForConv(convGuid) {
|
|
|
45218
45760
|
} catch {
|
|
45219
45761
|
}
|
|
45220
45762
|
}
|
|
45221
|
-
|
|
45763
|
+
resolve19();
|
|
45222
45764
|
}, KILL_GRACE_MS);
|
|
45223
45765
|
graceTimers.push(t);
|
|
45224
45766
|
});
|
|
@@ -45229,7 +45771,7 @@ async function killRunningForConv(convGuid) {
|
|
|
45229
45771
|
}
|
|
45230
45772
|
async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
45231
45773
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
45232
|
-
return new Promise((
|
|
45774
|
+
return new Promise((resolve19, reject) => {
|
|
45233
45775
|
const child = spawnCommand(cmd, ["sync", "--json"], {
|
|
45234
45776
|
cwd,
|
|
45235
45777
|
env: childEnv(),
|
|
@@ -45253,7 +45795,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
45253
45795
|
} catch {
|
|
45254
45796
|
}
|
|
45255
45797
|
try {
|
|
45256
|
-
|
|
45798
|
+
unlinkSync7(join24(cwd, ".gipity", "sync.lock"));
|
|
45257
45799
|
} catch {
|
|
45258
45800
|
}
|
|
45259
45801
|
finish(() => reject(new Error(`timed out after ${Math.round(timeoutMs / 1e3)}s`)));
|
|
@@ -45269,7 +45811,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
45269
45811
|
child.on("exit", (code) => finish(() => {
|
|
45270
45812
|
if (code === 0) {
|
|
45271
45813
|
log2("info", "sync done", { cwd, stdoutLen });
|
|
45272
|
-
|
|
45814
|
+
resolve19();
|
|
45273
45815
|
} else {
|
|
45274
45816
|
reject(new Error(`gipity sync exited ${code}${stderrBuf ? `: ${stderrBuf.trim().slice(0, 300)}` : ""}`));
|
|
45275
45817
|
}
|
|
@@ -45281,7 +45823,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
45281
45823
|
const cmd = process.env.GIPITY_RELAY_CLAUDE_CMD || resolveCommand("gipity");
|
|
45282
45824
|
const fullArgs = streamJson ? [...args, "--output-format", "stream-json", "--verbose", "--include-partial-messages"] : [...args];
|
|
45283
45825
|
const env = streamJson ? childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid, GIPITY_CAPTURE: "off" }) : childEnv({ GIPITY_CONVERSATION_GUID: d.conversation_guid });
|
|
45284
|
-
return new Promise((
|
|
45826
|
+
return new Promise((resolve19, reject) => {
|
|
45285
45827
|
const child = spawnCommand(cmd, fullArgs, { cwd, env, stdio: ["ignore", "pipe", "pipe"] });
|
|
45286
45828
|
let resolveExited = () => {
|
|
45287
45829
|
};
|
|
@@ -45461,7 +46003,7 @@ async function spawnGipityClaude(args, cwd, d, queue, meta) {
|
|
|
45461
46003
|
void postProgress(d.conversation_guid, buildProgressPayload(false));
|
|
45462
46004
|
if (ownQueue) await q.close();
|
|
45463
46005
|
cleanup();
|
|
45464
|
-
|
|
46006
|
+
resolve19({
|
|
45465
46007
|
exitCode: code ?? 1,
|
|
45466
46008
|
killed,
|
|
45467
46009
|
runtimeLimit,
|
|
@@ -45475,10 +46017,10 @@ async function runDispatchSync(d, cwd) {
|
|
|
45475
46017
|
let killed = false;
|
|
45476
46018
|
try {
|
|
45477
46019
|
await spawnSync2(cwd, PROJECT_SYNC_TIMEOUT_MS, (child) => {
|
|
45478
|
-
const exited = new Promise((
|
|
46020
|
+
const exited = new Promise((resolve19) => {
|
|
45479
46021
|
child.once("exit", (_code, signal) => {
|
|
45480
46022
|
if (signal === "SIGTERM") killed = true;
|
|
45481
|
-
|
|
46023
|
+
resolve19();
|
|
45482
46024
|
});
|
|
45483
46025
|
});
|
|
45484
46026
|
running.set(d.short_guid, { child, convGuid: d.conversation_guid, exited });
|
|
@@ -45509,18 +46051,18 @@ function killDispatch(shortGuid) {
|
|
|
45509
46051
|
return false;
|
|
45510
46052
|
}
|
|
45511
46053
|
function sleep4(ms2, signal) {
|
|
45512
|
-
return new Promise((
|
|
45513
|
-
if (signal?.aborted) return
|
|
46054
|
+
return new Promise((resolve19) => {
|
|
46055
|
+
if (signal?.aborted) return resolve19();
|
|
45514
46056
|
let onAbort = null;
|
|
45515
46057
|
const t = setTimeout(() => {
|
|
45516
46058
|
if (onAbort && signal) signal.removeEventListener("abort", onAbort);
|
|
45517
|
-
|
|
46059
|
+
resolve19();
|
|
45518
46060
|
}, ms2);
|
|
45519
46061
|
if (signal) {
|
|
45520
46062
|
onAbort = () => {
|
|
45521
46063
|
clearTimeout(t);
|
|
45522
46064
|
signal.removeEventListener("abort", onAbort);
|
|
45523
|
-
|
|
46065
|
+
resolve19();
|
|
45524
46066
|
};
|
|
45525
46067
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
45526
46068
|
}
|
|
@@ -45694,7 +46236,7 @@ relayCommand.command("stop").description("Stop the background service").option("
|
|
|
45694
46236
|
console.log(muted("Background service isn't running."));
|
|
45695
46237
|
return;
|
|
45696
46238
|
}
|
|
45697
|
-
const pid = parseInt(
|
|
46239
|
+
const pid = parseInt(readFileSync25(pidPath, "utf-8").trim(), 10);
|
|
45698
46240
|
if (!pid || isNaN(pid)) {
|
|
45699
46241
|
console.error(error("PID file is empty or malformed."));
|
|
45700
46242
|
process.exit(1);
|
|
@@ -45705,7 +46247,7 @@ relayCommand.command("stop").description("Stop the background service").option("
|
|
|
45705
46247
|
if (err?.code === "ESRCH") {
|
|
45706
46248
|
console.log(muted(`PID ${pid} not running - cleaning up stale PID file.`));
|
|
45707
46249
|
try {
|
|
45708
|
-
|
|
46250
|
+
unlinkSync8(pidPath);
|
|
45709
46251
|
} catch {
|
|
45710
46252
|
}
|
|
45711
46253
|
return;
|
|
@@ -45819,7 +46361,7 @@ relayCommand.command("revoke").description("Revoke and forget this device").acti
|
|
|
45819
46361
|
}
|
|
45820
46362
|
const pidPath = getDaemonPidPath();
|
|
45821
46363
|
if (existsSync22(pidPath)) {
|
|
45822
|
-
const pid = parseInt(
|
|
46364
|
+
const pid = parseInt(readFileSync25(pidPath, "utf-8").trim(), 10);
|
|
45823
46365
|
if (pid && !isNaN(pid)) {
|
|
45824
46366
|
try {
|
|
45825
46367
|
process.kill(pid, "SIGTERM");
|
|
@@ -45838,7 +46380,7 @@ relayCommand.command("log").description("Tail the service log").option("-n, --li
|
|
|
45838
46380
|
}
|
|
45839
46381
|
const lines = parseInt(opts.lines, 10) || 100;
|
|
45840
46382
|
try {
|
|
45841
|
-
const all =
|
|
46383
|
+
const all = readFileSync25(path5, "utf-8").split("\n");
|
|
45842
46384
|
const tail = all.slice(-lines - 1).join("\n");
|
|
45843
46385
|
process.stdout.write(tail);
|
|
45844
46386
|
} catch (err) {
|
|
@@ -45891,9 +46433,7 @@ var connectCommand = new Command("connect").alias("setup").description("Connect
|
|
|
45891
46433
|
console.log("");
|
|
45892
46434
|
const enabled = await runRelaySetup({ mode: "run-now" });
|
|
45893
46435
|
if (enabled) {
|
|
45894
|
-
|
|
45895
|
-
console.log(` ${success("Done")} \u2014 your relay ${running2 ? "is running in the background" : "is set up"} and will start with your computer.`);
|
|
45896
|
-
console.log(` ${muted("Open")} ${brand("gipity.ai")} ${muted("and start a chat to drive your coding agent here. Manage it with `gipity relay status`.")}`);
|
|
46436
|
+
console.log(` ${muted("Manage it anytime with `gipity relay status`.")}`);
|
|
45897
46437
|
} else {
|
|
45898
46438
|
console.log(` ${muted("No relay set up. Run `gipity connect` again anytime, or `gipity build` to start building.")}`);
|
|
45899
46439
|
}
|
|
@@ -45911,15 +46451,15 @@ init_api();
|
|
|
45911
46451
|
init_auth();
|
|
45912
46452
|
init_utils();
|
|
45913
46453
|
init_colors();
|
|
45914
|
-
import { existsSync as existsSync23, rmSync as
|
|
46454
|
+
import { existsSync as existsSync23, rmSync as rmSync4, unlinkSync as unlinkSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync17 } from "fs";
|
|
45915
46455
|
import { homedir as homedir17, platform as osPlatform4 } from "os";
|
|
45916
|
-
import { join as
|
|
46456
|
+
import { join as join25, resolve as resolve17 } from "path";
|
|
45917
46457
|
function removeGipityPluginConfig() {
|
|
45918
|
-
const settingsPath =
|
|
46458
|
+
const settingsPath = join25(homedir17(), ".claude", "settings.json");
|
|
45919
46459
|
if (!existsSync23(settingsPath)) return false;
|
|
45920
46460
|
let settings;
|
|
45921
46461
|
try {
|
|
45922
|
-
settings = JSON.parse(
|
|
46462
|
+
settings = JSON.parse(readFileSync26(settingsPath, "utf-8"));
|
|
45923
46463
|
} catch {
|
|
45924
46464
|
return false;
|
|
45925
46465
|
}
|
|
@@ -45934,7 +46474,7 @@ function removeGipityPluginConfig() {
|
|
|
45934
46474
|
if (Object.keys(settings.extraKnownMarketplaces).length === 0) delete settings.extraKnownMarketplaces;
|
|
45935
46475
|
changed = true;
|
|
45936
46476
|
}
|
|
45937
|
-
if (changed)
|
|
46477
|
+
if (changed) writeFileSync17(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
45938
46478
|
return changed;
|
|
45939
46479
|
}
|
|
45940
46480
|
function removeInstallerPathLines() {
|
|
@@ -45942,11 +46482,11 @@ function removeInstallerPathLines() {
|
|
|
45942
46482
|
const marker = "# Added by the Gipity installer";
|
|
45943
46483
|
const touched = [];
|
|
45944
46484
|
for (const name of [".bashrc", ".zshrc", ".profile"]) {
|
|
45945
|
-
const rc2 =
|
|
46485
|
+
const rc2 = join25(homedir17(), name);
|
|
45946
46486
|
if (!existsSync23(rc2)) continue;
|
|
45947
46487
|
let text;
|
|
45948
46488
|
try {
|
|
45949
|
-
text =
|
|
46489
|
+
text = readFileSync26(rc2, "utf-8");
|
|
45950
46490
|
} catch {
|
|
45951
46491
|
continue;
|
|
45952
46492
|
}
|
|
@@ -45956,7 +46496,7 @@ function removeInstallerPathLines() {
|
|
|
45956
46496
|
);
|
|
45957
46497
|
if (kept.length === lines.length) continue;
|
|
45958
46498
|
try {
|
|
45959
|
-
|
|
46499
|
+
writeFileSync17(rc2, kept.join("\n"));
|
|
45960
46500
|
touched.push(rc2);
|
|
45961
46501
|
} catch {
|
|
45962
46502
|
}
|
|
@@ -45968,7 +46508,7 @@ function tildify2(p) {
|
|
|
45968
46508
|
return p === home || p.startsWith(home + "/") ? "~" + p.slice(home.length) : p;
|
|
45969
46509
|
}
|
|
45970
46510
|
function resolveCliPath2() {
|
|
45971
|
-
return
|
|
46511
|
+
return resolve17(process.argv[1] ?? "gipity");
|
|
45972
46512
|
}
|
|
45973
46513
|
async function stopDaemon() {
|
|
45974
46514
|
if (!isDaemonRunning()) return;
|
|
@@ -46004,7 +46544,7 @@ function removeServiceUnit() {
|
|
|
46004
46544
|
}
|
|
46005
46545
|
if (existsSync23(plan2.path)) {
|
|
46006
46546
|
try {
|
|
46007
|
-
|
|
46547
|
+
unlinkSync9(plan2.path);
|
|
46008
46548
|
} catch {
|
|
46009
46549
|
}
|
|
46010
46550
|
}
|
|
@@ -46025,8 +46565,8 @@ async function revokeDeviceBestEffort() {
|
|
|
46025
46565
|
}
|
|
46026
46566
|
var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").option("--yes", "Skip confirmation prompts").action(async (opts) => {
|
|
46027
46567
|
const autoYes = opts.yes || getAutoConfirm();
|
|
46028
|
-
const gipityDir =
|
|
46029
|
-
const launcherBin =
|
|
46568
|
+
const gipityDir = join25(homedir17(), ".gipity");
|
|
46569
|
+
const launcherBin = join25(gipityDir, "launcher", "bin", "gipity");
|
|
46030
46570
|
const installedViaLauncher = existsSync23(launcherBin);
|
|
46031
46571
|
console.log(`${bold("Gipity uninstall")} - this will:`);
|
|
46032
46572
|
console.log(`\u2022 Stop the running relay daemon (if any)`);
|
|
@@ -46073,7 +46613,7 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
|
|
|
46073
46613
|
if (agentSkills.skills.length) {
|
|
46074
46614
|
for (const name of agentSkills.skills) {
|
|
46075
46615
|
try {
|
|
46076
|
-
|
|
46616
|
+
rmSync4(join25(AGENTS_SKILLS_DIR, name), { recursive: true, force: true });
|
|
46077
46617
|
} catch {
|
|
46078
46618
|
}
|
|
46079
46619
|
}
|
|
@@ -46081,7 +46621,7 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
|
|
|
46081
46621
|
}
|
|
46082
46622
|
if (existsSync23(gipityDir)) {
|
|
46083
46623
|
try {
|
|
46084
|
-
|
|
46624
|
+
rmSync4(gipityDir, { recursive: true, force: true });
|
|
46085
46625
|
console.log(`${success(`Removed ${gipityDir}/`)}`);
|
|
46086
46626
|
} catch (err) {
|
|
46087
46627
|
console.error(`${error(`Could not remove ${gipityDir}: ${err?.message || err}`)}`);
|
|
@@ -46225,7 +46765,7 @@ gmailCommand.command("send").description("Send a new email from your Gmail").req
|
|
|
46225
46765
|
const recap = opts.to.join(", ") + (opts.cc.length ? `, cc: ${opts.cc.join(", ")}` : "");
|
|
46226
46766
|
printResult(`Sent from your Gmail to ${recap}.`, opts, { sent: true, to: opts.to });
|
|
46227
46767
|
}));
|
|
46228
|
-
gmailCommand.command("reply").description("Reply to a Gmail thread. Get thread-id + message-id from `gmail search`.").requiredOption("--thread-id <id>", "Thread ID (from `gmail search`)").requiredOption("--message-id <id>", "Message ID being replied to (In-Reply-To)").requiredOption("--to <email>", "Recipient (repeatable)", collect2, []).option("--cc <email>", "Cc recipient (repeatable)", collect2, []).option("--bcc <email>", "Bcc recipient (repeatable)", collect2, []).option("--reply-to <email>", "Reply-To header address").requiredOption("--subject <s>", 'Subject (usually "Re:
|
|
46768
|
+
gmailCommand.command("reply").description("Reply to a Gmail thread. Get thread-id + message-id from `gmail search`.").requiredOption("--thread-id <id>", "Thread ID (from `gmail search`)").requiredOption("--message-id <id>", "Message ID being replied to (In-Reply-To)").requiredOption("--to <email>", "Recipient (repeatable)", collect2, []).option("--cc <email>", "Cc recipient (repeatable)", collect2, []).option("--bcc <email>", "Bcc recipient (repeatable)", collect2, []).option("--reply-to <email>", "Reply-To header address").requiredOption("--subject <s>", 'Subject (usually "Re: ..." of the original)').requiredOption("--body <text>", "Reply body (you compose the quote header)").option("--json", "Output as JSON").action((opts) => run("Reply", async () => {
|
|
46229
46769
|
if (!opts.to.length) throw new Error("--to is required (pass at least one)");
|
|
46230
46770
|
const payload = {
|
|
46231
46771
|
thread_id: opts.threadId,
|
|
@@ -46242,7 +46782,7 @@ gmailCommand.command("reply").description("Reply to a Gmail thread. Get thread-i
|
|
|
46242
46782
|
}));
|
|
46243
46783
|
|
|
46244
46784
|
// src/commands/text.ts
|
|
46245
|
-
import { readFileSync as
|
|
46785
|
+
import { readFileSync as readFileSync27 } from "fs";
|
|
46246
46786
|
init_colors();
|
|
46247
46787
|
|
|
46248
46788
|
// src/helpers/text-analysis.ts
|
|
@@ -46397,10 +46937,10 @@ function areAnagrams(a, b7) {
|
|
|
46397
46937
|
|
|
46398
46938
|
// src/commands/text.ts
|
|
46399
46939
|
function resolveInput(args, opts) {
|
|
46400
|
-
if (opts.file) return
|
|
46940
|
+
if (opts.file) return readFileSync27(opts.file, "utf-8");
|
|
46401
46941
|
if (args.length) return args.join(" ");
|
|
46402
46942
|
try {
|
|
46403
|
-
return
|
|
46943
|
+
return readFileSync27(0, "utf-8");
|
|
46404
46944
|
} catch {
|
|
46405
46945
|
return "";
|
|
46406
46946
|
}
|
|
@@ -46606,8 +47146,8 @@ function collectRealFlags(argv2, program3) {
|
|
|
46606
47146
|
}
|
|
46607
47147
|
|
|
46608
47148
|
// src/trace.ts
|
|
46609
|
-
import { openSync as openSync5, writeSync, mkdirSync as
|
|
46610
|
-
import { join as
|
|
47149
|
+
import { openSync as openSync5, writeSync, mkdirSync as mkdirSync18 } from "fs";
|
|
47150
|
+
import { join as join26 } from "path";
|
|
46611
47151
|
import { homedir as homedir18 } from "os";
|
|
46612
47152
|
var TRACE_KEY = /* @__PURE__ */ Symbol.for("gipity.traceOutput");
|
|
46613
47153
|
function installOutputTrace(label2) {
|
|
@@ -46619,10 +47159,10 @@ function installOutputTrace(label2) {
|
|
|
46619
47159
|
existing.emit({ event: "reenter", label: label2 });
|
|
46620
47160
|
return;
|
|
46621
47161
|
}
|
|
46622
|
-
const dir =
|
|
46623
|
-
|
|
47162
|
+
const dir = join26(process.env.GIPITY_DIR || join26(homedir18(), ".gipity"), "trace");
|
|
47163
|
+
mkdirSync18(dir, { recursive: true });
|
|
46624
47164
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
|
|
46625
|
-
const fd2 = openSync5(
|
|
47165
|
+
const fd2 = openSync5(join26(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
|
|
46626
47166
|
const emit = (rec) => {
|
|
46627
47167
|
try {
|
|
46628
47168
|
writeSync(fd2, JSON.stringify({ t: (/* @__PURE__ */ new Date()).toISOString(), ...rec }) + "\n");
|
|
@@ -46657,12 +47197,12 @@ function installOutputTrace(label2) {
|
|
|
46657
47197
|
|
|
46658
47198
|
// src/index.ts
|
|
46659
47199
|
installOutputTrace("index");
|
|
46660
|
-
var __dirname =
|
|
46661
|
-
var pkg = JSON.parse(
|
|
47200
|
+
var __dirname = dirname14(fileURLToPath3(import.meta.url));
|
|
47201
|
+
var pkg = JSON.parse(readFileSync28(resolve18(__dirname, "../package.json"), "utf-8"));
|
|
46662
47202
|
function versionLabel() {
|
|
46663
47203
|
const base = `v${pkg.version}`;
|
|
46664
47204
|
try {
|
|
46665
|
-
const info2 = JSON.parse(
|
|
47205
|
+
const info2 = JSON.parse(readFileSync28(resolve18(__dirname, "build-info.json"), "utf-8"));
|
|
46666
47206
|
if (info2?.sha) return `${base} (dev ${info2.sha}${info2.dirty ? ", modified" : ""})`;
|
|
46667
47207
|
} catch {
|
|
46668
47208
|
}
|
|
@@ -46867,6 +47407,19 @@ function resolveTargetCommand(argv2) {
|
|
|
46867
47407
|
}
|
|
46868
47408
|
return cmd;
|
|
46869
47409
|
}
|
|
47410
|
+
Command.prototype._excessArguments = function(receivedArgs) {
|
|
47411
|
+
if (this._allowExcessArguments) return;
|
|
47412
|
+
const expected = this.registeredArguments.length;
|
|
47413
|
+
const excess = receivedArgs.slice(expected);
|
|
47414
|
+
const forSubcommand = this.parent ? ` for '${this.name()}'` : "";
|
|
47415
|
+
const list = excess.map((a) => `'${a}'`).join(", ");
|
|
47416
|
+
let message = `error: unexpected extra argument${excess.length === 1 ? "" : "s"} ${list}${forSubcommand}.`;
|
|
47417
|
+
const kv2 = excess.map((a) => /^([A-Za-z][\w-]*)=/.exec(a)).find(Boolean);
|
|
47418
|
+
if (kv2) {
|
|
47419
|
+
message += ` Options are passed as \`--${kv2[1]} <value>\`, not \`${kv2[1]}=...\` - did you mean \`--${kv2[1]}\`?`;
|
|
47420
|
+
}
|
|
47421
|
+
this.error(message, { code: "commander.excessArguments" });
|
|
47422
|
+
};
|
|
46870
47423
|
function enableHelpAfterError(cmd) {
|
|
46871
47424
|
cmd.configureOutput({
|
|
46872
47425
|
// Render the offending command's full help (via outputHelp, so addHelpText
|