gipity 1.1.4 → 1.1.6
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/agents/agy.js +52 -0
- package/dist/agents/index.js +2 -0
- package/dist/api.js +19 -3
- package/dist/banner.js +3 -5
- package/dist/capture/sources/agy.js +88 -0
- package/dist/client-context.js +9 -0
- package/dist/commands/add.js +1 -1
- package/dist/commands/brand.js +125 -0
- package/dist/commands/build.js +13 -12
- package/dist/commands/chat.js +1 -1
- package/dist/commands/deploy.js +1 -1
- package/dist/commands/fn.js +2 -8
- package/dist/commands/generate.js +5 -5
- package/dist/commands/gmail.js +1 -1
- package/dist/commands/init.js +2 -2
- package/dist/commands/job.js +10 -5
- package/dist/commands/key.js +91 -0
- package/dist/commands/load.js +2 -2
- package/dist/commands/login.js +7 -2
- package/dist/commands/page-eval.js +47 -8
- package/dist/commands/page-fetch.js +14 -10
- package/dist/commands/page-inspect.js +2 -2
- package/dist/commands/page-screenshot.js +5 -5
- package/dist/commands/page-test.js +1 -1
- package/dist/commands/records.js +57 -19
- package/dist/commands/remove.js +1 -1
- package/dist/commands/sandbox.js +1 -1
- package/dist/commands/save.js +1 -1
- package/dist/commands/secrets.js +1 -1
- package/dist/commands/service.js +1 -1
- 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 +1 -1
- package/dist/commands/token.js +6 -1
- package/dist/commands/uninstall.js +14 -1
- package/dist/commands/upload.js +1 -1
- package/dist/commands/workflow.js +1 -1
- package/dist/hooks/capture-runner.js +20 -8
- package/dist/index.js +1046 -518
- package/dist/knowledge.js +4 -1
- package/dist/login-flow.js +44 -2
- package/dist/project-setup.js +2 -0
- package/dist/relay/diagnostics.js +4 -2
- package/dist/relay/onboarding.js +26 -44
- package/dist/setup.js +198 -16
- package/dist/sync.js +6 -6
- package/dist/template-vars.js +74 -0
- 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(".");
|
|
@@ -3972,7 +4007,7 @@ 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);
|
|
@@ -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;
|
|
@@ -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";
|
|
@@ -6561,6 +6597,9 @@ function detectHarness() {
|
|
|
6561
6597
|
}
|
|
6562
6598
|
if (hasEnvPrefix("CODEX_")) return { harness: "codex" };
|
|
6563
6599
|
if (hasEnvPrefix("GROK_")) return { harness: "grok", harnessSession: process.env.GROK_SESSION_ID };
|
|
6600
|
+
if (process.env.ANTIGRAVITY_CONVERSATION_ID) {
|
|
6601
|
+
return { harness: "agy", harnessSession: process.env.ANTIGRAVITY_CONVERSATION_ID };
|
|
6602
|
+
}
|
|
6564
6603
|
if (process.env.CURSOR_TRACE_ID || hasEnvPrefix("CURSOR_") || (process.env.TERM_PROGRAM ?? "").toLowerCase().includes("cursor")) {
|
|
6565
6604
|
return { harness: "cursor" };
|
|
6566
6605
|
}
|
|
@@ -6607,11 +6646,13 @@ __export(api_exports, {
|
|
|
6607
6646
|
getAccountSlug: () => getAccountSlug,
|
|
6608
6647
|
getAuthHeader: () => getAuthHeader,
|
|
6609
6648
|
getBaseUrl: () => getBaseUrl,
|
|
6649
|
+
mintAppToken: () => mintAppToken,
|
|
6610
6650
|
patch: () => patch,
|
|
6611
6651
|
post: () => post,
|
|
6612
6652
|
postBinary: () => postBinary,
|
|
6613
6653
|
postForTarEntries: () => postForTarEntries,
|
|
6614
6654
|
publicPost: () => publicPost,
|
|
6655
|
+
publicRequest: () => publicRequest,
|
|
6615
6656
|
put: () => put,
|
|
6616
6657
|
putTimeoutMs: () => putTimeoutMs,
|
|
6617
6658
|
putToPresignedUrl: () => putToPresignedUrl,
|
|
@@ -6867,12 +6908,12 @@ async function getAccountSlug() {
|
|
|
6867
6908
|
accountSlugCache = res.data.accountSlug;
|
|
6868
6909
|
return accountSlugCache;
|
|
6869
6910
|
}
|
|
6870
|
-
async function
|
|
6911
|
+
async function publicRequest(method, path5, body, extraHeaders) {
|
|
6871
6912
|
const url = `${baseUrl()}${path5}`;
|
|
6872
6913
|
const res = await fetch(url, {
|
|
6873
|
-
method
|
|
6914
|
+
method,
|
|
6874
6915
|
headers: { ...clientHeaders(), "Content-Type": "application/json", ...extraHeaders },
|
|
6875
|
-
body: JSON.stringify(body)
|
|
6916
|
+
body: body === void 0 ? void 0 : JSON.stringify(body)
|
|
6876
6917
|
});
|
|
6877
6918
|
if (!res.ok) {
|
|
6878
6919
|
const json = await res.json().catch(() => ({ error: { code: "UNKNOWN", message: res.statusText } }));
|
|
@@ -6881,6 +6922,17 @@ async function publicPost(path5, body, extraHeaders) {
|
|
|
6881
6922
|
}
|
|
6882
6923
|
return res.json();
|
|
6883
6924
|
}
|
|
6925
|
+
function publicPost(path5, body, extraHeaders) {
|
|
6926
|
+
return publicRequest("POST", path5, body, extraHeaders);
|
|
6927
|
+
}
|
|
6928
|
+
async function mintAppToken(projectGuid2) {
|
|
6929
|
+
try {
|
|
6930
|
+
const minted = await publicPost("/api/token", { app: projectGuid2 });
|
|
6931
|
+
return { "X-App-Token": minted.data.token };
|
|
6932
|
+
} catch {
|
|
6933
|
+
return void 0;
|
|
6934
|
+
}
|
|
6935
|
+
}
|
|
6884
6936
|
var tar, ApiError, REQUEST_TIMEOUT_MS, DOWNLOAD_HEADER_TIMEOUT_MS, PUT_TIMEOUT_FLOOR_MS, PUT_MIN_BYTES_PER_SEC, accountSlugCache;
|
|
6885
6937
|
var init_api = __esm({
|
|
6886
6938
|
"src/api.ts"() {
|
|
@@ -6928,7 +6980,7 @@ __export(config_exports, {
|
|
|
6928
6980
|
setApiBaseOverride: () => setApiBaseOverride,
|
|
6929
6981
|
shouldIgnore: () => shouldIgnore
|
|
6930
6982
|
});
|
|
6931
|
-
import { readFileSync as
|
|
6983
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
|
|
6932
6984
|
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
6933
6985
|
function setApiBaseOverride(url) {
|
|
6934
6986
|
apiBaseOverride = url;
|
|
@@ -6986,7 +7038,7 @@ function getConfig() {
|
|
|
6986
7038
|
const path5 = getConfigPath();
|
|
6987
7039
|
if (!path5) return null;
|
|
6988
7040
|
try {
|
|
6989
|
-
cached3 = JSON.parse(
|
|
7041
|
+
cached3 = JSON.parse(readFileSync4(path5, "utf-8"));
|
|
6990
7042
|
return cached3;
|
|
6991
7043
|
} catch {
|
|
6992
7044
|
return null;
|
|
@@ -31865,7 +31917,7 @@ init_config();
|
|
|
31865
31917
|
init_utils();
|
|
31866
31918
|
import { readFileSync as readFileSync28 } from "fs";
|
|
31867
31919
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
31868
|
-
import { dirname as
|
|
31920
|
+
import { dirname as dirname14, resolve as resolve18 } from "path";
|
|
31869
31921
|
|
|
31870
31922
|
// src/helpers/output.ts
|
|
31871
31923
|
var frameOpen = false;
|
|
@@ -32039,7 +32091,7 @@ mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <s
|
|
|
32039
32091
|
|
|
32040
32092
|
## CLI quick reference
|
|
32041
32093
|
|
|
32042
|
-
Key commands: \`gipity add <template|kit>\`, \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity secrets set <NAME> <value> [--account]\` (store an API key/token encrypted; read in functions via \`secrets.get('NAME')\` \u2014 never hardcode keys), \`gipity email send --to <addr> --subject <s> --body <b>\` (sends as \`gipity@gipity.ai\`; omit \`--to\` to self-send), \`gipity skill read <name>\`.
|
|
32094
|
+
Key commands: \`gipity add <template|kit>\`, \`gipity brand set --emoji <e>|--color <hex>\` (regenerate the app's icons + social share card), \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity secrets set <NAME> <value> [--account]\` (store an API key/token encrypted; read in functions via \`secrets.get('NAME')\` \u2014 never hardcode keys), \`gipity email send --to <addr> --subject <s> --body <b>\` (sends as \`gipity@gipity.ai\`; omit \`--to\` to self-send), \`gipity skill read <name>\`.
|
|
32043
32095
|
Rename for findability: \`gipity project rename <name>\` renames the current project's display name (the slug and deployed URLs never change); \`gipity chat rename <title>\` renames the current chat's tab title. Both are the display label users scan to switch between tabs \u2014 retitle a chat when the conversation clearly shifts to a new topic (sparingly, not every turn), and keep every project/chat title SHORT: 2-4 words, \u226440 characters, no trailing punctuation (e.g. "Stripe checkout", "Tetris game").
|
|
32044
32096
|
Pull an existing remote project local (given its URL/slug): \`mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <slug>\` (adopts the matching project and syncs files down - this is the "clone").
|
|
32045
32097
|
Move whole apps in/out: \`gipity save\` (export this project as a portable \`.gip\` bundle), \`gipity load <file.gip | github:owner/repo>\` (import as a NEW project; \`--inspect\` to preview), \`gipity github connect\` (1-2 click GitHub access for imports). Porting a Vercel/Replit/Lovable app? Load the \`app-import\` skill first.
|
|
@@ -32081,6 +32133,8 @@ Write files locally - Gipity's editor hooks (installed into Claude Code, Grok, a
|
|
|
32081
32133
|
|
|
32082
32134
|
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.
|
|
32083
32135
|
|
|
32136
|
+
**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.
|
|
32137
|
+
|
|
32084
32138
|
### Where files go: deploy only ships \`src/\`
|
|
32085
32139
|
|
|
32086
32140
|
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:
|
|
@@ -32117,6 +32171,7 @@ App development skills:
|
|
|
32117
32171
|
- \`app-debugging\` - debug a deployed app: page inspect/eval, screenshots, function logs
|
|
32118
32172
|
- \`app-development\` - functions, database, and API
|
|
32119
32173
|
- \`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
|
|
32174
|
+
- \`app-records\` - Gipity Records: declared tables \u2192 validated CRUD, provenance, workflows, auto UI
|
|
32120
32175
|
- \`app-testing\` - testing deployed app functions (ctx.fn.call/callAs, the isolated test DB)
|
|
32121
32176
|
- \`deploy\` - the deploy pipeline & gipity.yaml manifest
|
|
32122
32177
|
- \`jobs\` - long-running CPU + GPU compute jobs (Python / Node / bash)
|
|
@@ -32153,13 +32208,13 @@ init_colors();
|
|
|
32153
32208
|
|
|
32154
32209
|
// src/bug-queue.ts
|
|
32155
32210
|
init_api();
|
|
32156
|
-
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as
|
|
32157
|
-
import { join as
|
|
32211
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
|
|
32212
|
+
import { join as join3 } from "path";
|
|
32158
32213
|
import { homedir as homedir2 } from "os";
|
|
32159
|
-
var QUEUE_DIR =
|
|
32214
|
+
var QUEUE_DIR = join3(process.env.GIPITY_DIR || join3(homedir2(), ".gipity"), "bug-queue");
|
|
32160
32215
|
function queueBugReport(report) {
|
|
32161
32216
|
mkdirSync2(QUEUE_DIR, { recursive: true, mode: 448 });
|
|
32162
|
-
const file =
|
|
32217
|
+
const file = join3(QUEUE_DIR, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
|
|
32163
32218
|
writeFileSync3(file, JSON.stringify(report, null, 2), { mode: 384 });
|
|
32164
32219
|
}
|
|
32165
32220
|
function isRetryableFailure(err) {
|
|
@@ -32171,12 +32226,12 @@ function isRetryableFailure(err) {
|
|
|
32171
32226
|
async function flushBugQueue() {
|
|
32172
32227
|
if (!existsSync3(QUEUE_DIR)) return 0;
|
|
32173
32228
|
let delivered = 0;
|
|
32174
|
-
for (const file of
|
|
32229
|
+
for (const file of readdirSync2(QUEUE_DIR)) {
|
|
32175
32230
|
if (!file.endsWith(".json")) continue;
|
|
32176
|
-
const path5 =
|
|
32231
|
+
const path5 = join3(QUEUE_DIR, file);
|
|
32177
32232
|
let report;
|
|
32178
32233
|
try {
|
|
32179
|
-
report = JSON.parse(
|
|
32234
|
+
report = JSON.parse(readFileSync5(path5, "utf-8"));
|
|
32180
32235
|
} catch {
|
|
32181
32236
|
try {
|
|
32182
32237
|
unlinkSync2(path5);
|
|
@@ -32199,6 +32254,70 @@ async function flushBugQueue() {
|
|
|
32199
32254
|
return delivered;
|
|
32200
32255
|
}
|
|
32201
32256
|
|
|
32257
|
+
// src/login-flow.ts
|
|
32258
|
+
init_api();
|
|
32259
|
+
init_auth();
|
|
32260
|
+
init_config();
|
|
32261
|
+
init_utils();
|
|
32262
|
+
init_colors();
|
|
32263
|
+
function newAccountWouldBeUnexpected(email, priorAuth) {
|
|
32264
|
+
return !!getConfig() || priorAuth?.email.toLowerCase() === email.toLowerCase().trim();
|
|
32265
|
+
}
|
|
32266
|
+
function warnBeforeCodeIfUnexpectedNewAccount(isNewUser, email, indent = "") {
|
|
32267
|
+
if (isNewUser !== true) return;
|
|
32268
|
+
const priorAuth = getAuth();
|
|
32269
|
+
if (!newAccountWouldBeUnexpected(email, priorAuth)) return;
|
|
32270
|
+
const config = getConfig();
|
|
32271
|
+
console.log(`${indent}${warning(`No existing Gipity account for ${email} \u2014 entering the code will CREATE a new one.`)}`);
|
|
32272
|
+
if (config) {
|
|
32273
|
+
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.`)}`);
|
|
32274
|
+
}
|
|
32275
|
+
}
|
|
32276
|
+
function warnIfUnexpectedNewAccount(isNewUser, email, priorAuth, indent = "") {
|
|
32277
|
+
if (isNewUser !== true) return;
|
|
32278
|
+
if (!newAccountWouldBeUnexpected(email, priorAuth)) return;
|
|
32279
|
+
const config = getConfig();
|
|
32280
|
+
console.log(`${indent}${warning(`Logged into a NEW, empty account for ${email} \u2014 no prior account existed for this email.`)}`);
|
|
32281
|
+
if (config) {
|
|
32282
|
+
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".`)}`);
|
|
32283
|
+
}
|
|
32284
|
+
console.log(`${indent}${muted("If you expected an existing account, run: gipity status \u2014 then gipity login again with the correct email.")}`);
|
|
32285
|
+
}
|
|
32286
|
+
async function interactiveLogin() {
|
|
32287
|
+
const email = await prompt(" Email: ");
|
|
32288
|
+
if (!email) {
|
|
32289
|
+
console.error(`
|
|
32290
|
+
${error("Email required.")}`);
|
|
32291
|
+
process.exit(1);
|
|
32292
|
+
}
|
|
32293
|
+
const sendRes = await publicPost("/auth/login", { email });
|
|
32294
|
+
console.log(" Check your email for a 6-digit code.\n");
|
|
32295
|
+
warnBeforeCodeIfUnexpectedNewAccount(sendRes.isNewUser, email, " ");
|
|
32296
|
+
const code = await prompt(" Code: ");
|
|
32297
|
+
if (!code) {
|
|
32298
|
+
console.error(`
|
|
32299
|
+
${error("Code required.")}`);
|
|
32300
|
+
process.exit(1);
|
|
32301
|
+
}
|
|
32302
|
+
const priorAuth = getAuth();
|
|
32303
|
+
const res = await publicPost("/auth/verify", { email, code });
|
|
32304
|
+
const exp = decodeJwtExp(res.accessToken);
|
|
32305
|
+
if (!exp) {
|
|
32306
|
+
console.error(`
|
|
32307
|
+
${error("Invalid token received.")}`);
|
|
32308
|
+
process.exit(1);
|
|
32309
|
+
}
|
|
32310
|
+
const expiresAt = new Date(exp * 1e3).toISOString();
|
|
32311
|
+
saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
|
|
32312
|
+
console.log(` ${success(`Logged in (${email}).`)}`);
|
|
32313
|
+
warnIfUnexpectedNewAccount(res.isNewUser, email, priorAuth, " ");
|
|
32314
|
+
const delivered = await flushBugQueue().catch(() => 0);
|
|
32315
|
+
if (delivered > 0) {
|
|
32316
|
+
console.log(` ${muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`)}`);
|
|
32317
|
+
}
|
|
32318
|
+
return getAuth();
|
|
32319
|
+
}
|
|
32320
|
+
|
|
32202
32321
|
// src/commands/login.ts
|
|
32203
32322
|
var loginCommand = new Command("login").description("Log in or sign up").option("--email <email>", "Email address").option("--code <code>", "Verification code").action(async (opts) => {
|
|
32204
32323
|
try {
|
|
@@ -32209,9 +32328,10 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
|
|
|
32209
32328
|
return;
|
|
32210
32329
|
}
|
|
32211
32330
|
if (email && !code) {
|
|
32212
|
-
await publicPost("/auth/login", { email });
|
|
32331
|
+
const sendRes2 = await publicPost("/auth/login", { email });
|
|
32213
32332
|
console.log("Check your email for a 6-digit code.");
|
|
32214
32333
|
console.log(muted(`Then run: gipity login --email ${email} --code <code>`));
|
|
32334
|
+
warnBeforeCodeIfUnexpectedNewAccount(sendRes2.isNewUser, email);
|
|
32215
32335
|
return;
|
|
32216
32336
|
}
|
|
32217
32337
|
console.log("Enter your email to log in or create an account.");
|
|
@@ -32222,9 +32342,10 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
|
|
|
32222
32342
|
console.error(error("Email required."));
|
|
32223
32343
|
process.exit(1);
|
|
32224
32344
|
}
|
|
32225
|
-
await publicPost("/auth/login", { email });
|
|
32345
|
+
const sendRes = await publicPost("/auth/login", { email });
|
|
32226
32346
|
console.log("");
|
|
32227
32347
|
console.log("Check your email for a 6-digit code.");
|
|
32348
|
+
warnBeforeCodeIfUnexpectedNewAccount(sendRes.isNewUser, email);
|
|
32228
32349
|
code = await prompt("Code: ");
|
|
32229
32350
|
await verify(email, code);
|
|
32230
32351
|
} catch (err) {
|
|
@@ -32233,6 +32354,7 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
|
|
|
32233
32354
|
}
|
|
32234
32355
|
});
|
|
32235
32356
|
async function verify(email, code) {
|
|
32357
|
+
const priorAuth = getAuth();
|
|
32236
32358
|
const res = await publicPost("/auth/verify", { email, code });
|
|
32237
32359
|
const exp = decodeJwtExp(res.accessToken);
|
|
32238
32360
|
if (!exp) {
|
|
@@ -32247,6 +32369,7 @@ async function verify(email, code) {
|
|
|
32247
32369
|
expiresAt
|
|
32248
32370
|
});
|
|
32249
32371
|
console.log(success(`Logged in (${email}).`));
|
|
32372
|
+
warnIfUnexpectedNewAccount(res.isNewUser, email, priorAuth);
|
|
32250
32373
|
const delivered = await flushBugQueue().catch(() => 0);
|
|
32251
32374
|
if (delivered > 0) {
|
|
32252
32375
|
console.log(muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`));
|
|
@@ -32292,8 +32415,8 @@ function run(label2, action) {
|
|
|
32292
32415
|
init_api();
|
|
32293
32416
|
init_config();
|
|
32294
32417
|
init_utils();
|
|
32295
|
-
import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, statSync, lstatSync, unlinkSync as unlinkSync3, readdirSync as
|
|
32296
|
-
import { join as
|
|
32418
|
+
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";
|
|
32419
|
+
import { join as join5, relative, dirname as dirname4, extname as extname2, resolve as resolve4, sep } from "path";
|
|
32297
32420
|
import { hostname } from "os";
|
|
32298
32421
|
import { createHash as createHash2 } from "crypto";
|
|
32299
32422
|
|
|
@@ -32406,17 +32529,17 @@ var UploadConflictError = class extends Error {
|
|
|
32406
32529
|
currentServerVersion;
|
|
32407
32530
|
path;
|
|
32408
32531
|
};
|
|
32409
|
-
async function uploadOneFile(
|
|
32410
|
-
const { sha256
|
|
32532
|
+
async function uploadOneFile(projectGuid2, localPath, virtualPath, opts = {}) {
|
|
32533
|
+
const { sha256, size } = await hashFile(localPath);
|
|
32411
32534
|
const mime = opts.mime ?? guessMime(virtualPath);
|
|
32412
|
-
const initBody = { path: virtualPath, size, sha256
|
|
32535
|
+
const initBody = { path: virtualPath, size, sha256, mime };
|
|
32413
32536
|
if (opts.expectedServerVersion !== void 0) {
|
|
32414
32537
|
initBody.expected_server_version = opts.expectedServerVersion;
|
|
32415
32538
|
}
|
|
32416
32539
|
let init;
|
|
32417
32540
|
try {
|
|
32418
32541
|
init = await post(
|
|
32419
|
-
`/projects/${
|
|
32542
|
+
`/projects/${projectGuid2}/files/upload-init`,
|
|
32420
32543
|
initBody
|
|
32421
32544
|
);
|
|
32422
32545
|
} catch (err) {
|
|
@@ -32438,7 +32561,7 @@ async function uploadOneFile(projectGuid, localPath, virtualPath, opts = {}) {
|
|
|
32438
32561
|
}
|
|
32439
32562
|
let comp;
|
|
32440
32563
|
try {
|
|
32441
|
-
comp = await post(`/projects/${
|
|
32564
|
+
comp = await post(`/projects/${projectGuid2}/files/upload-complete`, completeBody);
|
|
32442
32565
|
} catch (err) {
|
|
32443
32566
|
if (err instanceof ApiError && err.statusCode === 409) {
|
|
32444
32567
|
const current = typeof err.data?.current_server_version === "number" ? err.data.current_server_version : null;
|
|
@@ -32520,16 +32643,16 @@ async function transferToS3(localPath, size, mime, data, opts = {}) {
|
|
|
32520
32643
|
completed.sort((a, b7) => a.part_number - b7.part_number);
|
|
32521
32644
|
return { parts: completed };
|
|
32522
32645
|
}
|
|
32523
|
-
async function uploadInitBatch(
|
|
32646
|
+
async function uploadInitBatch(projectGuid2, files) {
|
|
32524
32647
|
const res = await post(
|
|
32525
|
-
`/projects/${
|
|
32648
|
+
`/projects/${projectGuid2}/files/upload-init-batch`,
|
|
32526
32649
|
{ files }
|
|
32527
32650
|
);
|
|
32528
32651
|
return res.data.results;
|
|
32529
32652
|
}
|
|
32530
|
-
async function uploadCompleteBatch(
|
|
32653
|
+
async function uploadCompleteBatch(projectGuid2, items) {
|
|
32531
32654
|
const res = await post(
|
|
32532
|
-
`/projects/${
|
|
32655
|
+
`/projects/${projectGuid2}/files/upload-complete-batch`,
|
|
32533
32656
|
{ items }
|
|
32534
32657
|
);
|
|
32535
32658
|
return res.data.results;
|
|
@@ -32537,15 +32660,17 @@ async function uploadCompleteBatch(projectGuid, items) {
|
|
|
32537
32660
|
|
|
32538
32661
|
// src/setup.ts
|
|
32539
32662
|
init_platform();
|
|
32540
|
-
import { resolve as resolve3, join as
|
|
32663
|
+
import { resolve as resolve3, join as join4, dirname as dirname3 } from "path";
|
|
32541
32664
|
import { homedir as homedir3, tmpdir } from "os";
|
|
32542
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as
|
|
32665
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync6, mkdtempSync, rmSync, cpSync, readdirSync as readdirSync3 } from "fs";
|
|
32543
32666
|
init_config();
|
|
32544
32667
|
var PRIMER_FILES = {
|
|
32545
32668
|
claude: "CLAUDE.md",
|
|
32546
32669
|
codex: "AGENTS.md",
|
|
32547
32670
|
grok: "AGENTS.md",
|
|
32548
32671
|
// Grok Build reads the AGENTS.md family (and CLAUDE.md) natively
|
|
32672
|
+
agy: "AGENTS.md",
|
|
32673
|
+
// Antigravity reads the same AGENTS.md/GEMINI.md rules family natively
|
|
32549
32674
|
aider: "AGENTS.md",
|
|
32550
32675
|
// shares the Codex primer; aider is pointed at it via .aider.conf.yml
|
|
32551
32676
|
gemini: "GEMINI.md",
|
|
@@ -32561,6 +32686,7 @@ var DEFAULT_SYNC_IGNORE = [
|
|
|
32561
32686
|
".gipity/",
|
|
32562
32687
|
".claude/",
|
|
32563
32688
|
".codex/",
|
|
32689
|
+
".agents/",
|
|
32564
32690
|
".gitignore",
|
|
32565
32691
|
AIDER_CONF_FILE,
|
|
32566
32692
|
// Home-directory junk: a project created inside a real home dir (or one that
|
|
@@ -32654,14 +32780,14 @@ function stripGipityHooks(settings) {
|
|
|
32654
32780
|
function readSettingsFile(path5) {
|
|
32655
32781
|
if (!existsSync4(path5)) return {};
|
|
32656
32782
|
try {
|
|
32657
|
-
return JSON.parse(
|
|
32783
|
+
return JSON.parse(readFileSync6(path5, "utf-8"));
|
|
32658
32784
|
} catch {
|
|
32659
32785
|
return {};
|
|
32660
32786
|
}
|
|
32661
32787
|
}
|
|
32662
32788
|
function ensureGipityPlugin(force = false) {
|
|
32663
|
-
const claudeDir =
|
|
32664
|
-
const settingsPath =
|
|
32789
|
+
const claudeDir = join4(homedir3(), ".claude");
|
|
32790
|
+
const settingsPath = join4(claudeDir, "settings.json");
|
|
32665
32791
|
const settings = readSettingsFile(settingsPath);
|
|
32666
32792
|
let changed = stripGipityHooks(settings);
|
|
32667
32793
|
const marketplaces = settings.extraKnownMarketplaces ?? (settings.extraKnownMarketplaces = {});
|
|
@@ -32693,8 +32819,8 @@ function versionGte(have, want) {
|
|
|
32693
32819
|
}
|
|
32694
32820
|
function userScopeInstallState() {
|
|
32695
32821
|
try {
|
|
32696
|
-
const p =
|
|
32697
|
-
const data = JSON.parse(
|
|
32822
|
+
const p = join4(homedir3(), ".claude", "plugins", "installed_plugins.json");
|
|
32823
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
32698
32824
|
const entries = data?.plugins?.[GIPITY_PLUGIN_ID];
|
|
32699
32825
|
if (!Array.isArray(entries)) return { exists: false, current: false };
|
|
32700
32826
|
const userEntries = entries.filter((e) => e?.scope === "user");
|
|
@@ -32731,8 +32857,8 @@ function ensureGipityPluginInstalled() {
|
|
|
32731
32857
|
}
|
|
32732
32858
|
function grokInstallState() {
|
|
32733
32859
|
try {
|
|
32734
|
-
const p =
|
|
32735
|
-
const data = JSON.parse(
|
|
32860
|
+
const p = join4(homedir3(), ".grok", "installed-plugins", "registry.json");
|
|
32861
|
+
const data = JSON.parse(readFileSync6(p, "utf-8"));
|
|
32736
32862
|
const versions = [];
|
|
32737
32863
|
for (const repo of Object.values(data?.repos ?? {})) {
|
|
32738
32864
|
const v7 = repo?.plugins?.gipity?.version;
|
|
@@ -32757,12 +32883,14 @@ function ensureGrokPluginInstalled() {
|
|
|
32757
32883
|
console.log("Installed the Gipity plugin for Grok (skills + file-sync hooks).");
|
|
32758
32884
|
}
|
|
32759
32885
|
}
|
|
32760
|
-
var AGENTS_SKILLS_DIR =
|
|
32761
|
-
var AGENT_HOOKS_DIR =
|
|
32762
|
-
var AGENT_SKILLS_MANIFEST =
|
|
32763
|
-
|
|
32886
|
+
var AGENTS_SKILLS_DIR = join4(homedir3(), ".agents", "skills");
|
|
32887
|
+
var AGENT_HOOKS_DIR = join4(homedir3(), ".gipity", "agent-hooks");
|
|
32888
|
+
var AGENT_SKILLS_MANIFEST = join4(homedir3(), ".gipity", "agent-skills.json");
|
|
32889
|
+
var AGY_SKILLS_DIR = join4(homedir3(), ".gemini", "config", "skills");
|
|
32890
|
+
var AGY_SKILLS_MANIFEST = join4(homedir3(), ".gipity", "agy-skills.json");
|
|
32891
|
+
function skillsManifestState(manifestPath) {
|
|
32764
32892
|
try {
|
|
32765
|
-
const m = JSON.parse(
|
|
32893
|
+
const m = JSON.parse(readFileSync6(manifestPath, "utf-8"));
|
|
32766
32894
|
return {
|
|
32767
32895
|
current: typeof m?.version === "string" && versionGte(m.version, GIPITY_PLUGIN_VERSION),
|
|
32768
32896
|
skills: Array.isArray(m?.skills) ? m.skills : []
|
|
@@ -32771,50 +32899,63 @@ function agentSkillsState() {
|
|
|
32771
32899
|
return { current: false, skills: [] };
|
|
32772
32900
|
}
|
|
32773
32901
|
}
|
|
32774
|
-
function
|
|
32775
|
-
|
|
32902
|
+
function agentSkillsState() {
|
|
32903
|
+
return skillsManifestState(AGENT_SKILLS_MANIFEST);
|
|
32904
|
+
}
|
|
32905
|
+
function agySkillsState() {
|
|
32906
|
+
return skillsManifestState(AGY_SKILLS_MANIFEST);
|
|
32907
|
+
}
|
|
32908
|
+
function installSkillsAndHooks(skillsDir, manifestPath, harnessLabel) {
|
|
32776
32909
|
if (!binaryOnPath("git")) return;
|
|
32777
|
-
const tmp = mkdtempSync(
|
|
32910
|
+
const tmp = mkdtempSync(join4(tmpdir(), "gipity-skills-"));
|
|
32778
32911
|
try {
|
|
32779
32912
|
const clone = spawnSyncCommand(
|
|
32780
32913
|
resolveCommand("git"),
|
|
32781
|
-
["clone", "--depth", "1", `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`,
|
|
32914
|
+
["clone", "--depth", "1", `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`, join4(tmp, "repo")],
|
|
32782
32915
|
{ stdio: "ignore", timeout: 12e4 }
|
|
32783
32916
|
);
|
|
32784
32917
|
if (clone.status !== 0) return;
|
|
32785
|
-
const repo =
|
|
32918
|
+
const repo = join4(tmp, "repo");
|
|
32786
32919
|
let version = GIPITY_PLUGIN_VERSION;
|
|
32787
32920
|
try {
|
|
32788
|
-
const manifest = JSON.parse(
|
|
32921
|
+
const manifest = JSON.parse(readFileSync6(join4(repo, ".claude-plugin", "plugin.json"), "utf-8"));
|
|
32789
32922
|
if (typeof manifest?.version === "string") version = manifest.version;
|
|
32790
32923
|
} catch {
|
|
32791
32924
|
}
|
|
32792
|
-
const skillsSrc =
|
|
32925
|
+
const skillsSrc = join4(repo, "skills");
|
|
32793
32926
|
const names = [];
|
|
32794
|
-
for (const entry of
|
|
32927
|
+
for (const entry of readdirSync3(skillsSrc, { withFileTypes: true })) {
|
|
32795
32928
|
if (!entry.isDirectory()) continue;
|
|
32796
|
-
if (!existsSync4(
|
|
32797
|
-
mkdirSync3(
|
|
32798
|
-
cpSync(
|
|
32929
|
+
if (!existsSync4(join4(skillsSrc, entry.name, "SKILL.md"))) continue;
|
|
32930
|
+
mkdirSync3(skillsDir, { recursive: true });
|
|
32931
|
+
cpSync(join4(skillsSrc, entry.name), join4(skillsDir, entry.name), {
|
|
32799
32932
|
recursive: true,
|
|
32800
32933
|
force: true
|
|
32801
32934
|
});
|
|
32802
32935
|
names.push(entry.name);
|
|
32803
32936
|
}
|
|
32804
32937
|
mkdirSync3(AGENT_HOOKS_DIR, { recursive: true });
|
|
32805
|
-
for (const script of
|
|
32806
|
-
cpSync(
|
|
32938
|
+
for (const script of readdirSync3(join4(repo, "hooks", "scripts"))) {
|
|
32939
|
+
cpSync(join4(repo, "hooks", "scripts", script), join4(AGENT_HOOKS_DIR, script), { force: true });
|
|
32807
32940
|
}
|
|
32808
|
-
writeFileSync4(
|
|
32809
|
-
console.log(`Installed ${names.length} Gipity skills for
|
|
32941
|
+
writeFileSync4(manifestPath, JSON.stringify({ version, skills: names }, null, 2) + "\n");
|
|
32942
|
+
console.log(`Installed ${names.length} Gipity skills for ${harnessLabel} (${skillsDir}).`);
|
|
32810
32943
|
} catch {
|
|
32811
32944
|
} finally {
|
|
32812
32945
|
rmSync(tmp, { recursive: true, force: true });
|
|
32813
32946
|
}
|
|
32814
32947
|
}
|
|
32948
|
+
function ensureAgentSkillsInstalled() {
|
|
32949
|
+
if (agentSkillsState().current) return;
|
|
32950
|
+
installSkillsAndHooks(AGENTS_SKILLS_DIR, AGENT_SKILLS_MANIFEST, "Codex");
|
|
32951
|
+
}
|
|
32952
|
+
function ensureAgySkillsInstalled() {
|
|
32953
|
+
if (agySkillsState().current) return;
|
|
32954
|
+
installSkillsAndHooks(AGY_SKILLS_DIR, AGY_SKILLS_MANIFEST, "Antigravity");
|
|
32955
|
+
}
|
|
32815
32956
|
function applyCodexHooks(existing) {
|
|
32816
|
-
const launcher =
|
|
32817
|
-
const cmd = (script, ...args) => [`sh "${launcher}" "${
|
|
32957
|
+
const launcher = join4(AGENT_HOOKS_DIR, "launch.sh");
|
|
32958
|
+
const cmd = (script, ...args) => [`sh "${launcher}" "${join4(AGENT_HOOKS_DIR, script)}"`, ...args].join(" ");
|
|
32818
32959
|
const wanted = [
|
|
32819
32960
|
{ event: "PostToolUse", matcher: "Edit|Write", command: cmd("sync-push.cjs"), timeout: 30 },
|
|
32820
32961
|
{ event: "UserPromptSubmit", command: cmd("sync-pull.cjs"), timeout: 300 },
|
|
@@ -32852,8 +32993,8 @@ function setupCodexHooks() {
|
|
|
32852
32993
|
if (process.platform === "win32") return;
|
|
32853
32994
|
const cwd = resolve3(process.cwd());
|
|
32854
32995
|
if (cwd === resolve3(homedir3())) return;
|
|
32855
|
-
const path5 =
|
|
32856
|
-
const existing = existsSync4(path5) ?
|
|
32996
|
+
const path5 = join4(cwd, ".codex", "hooks.json");
|
|
32997
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
32857
32998
|
const next = applyCodexHooks(existing);
|
|
32858
32999
|
if (next === null) return;
|
|
32859
33000
|
mkdirSync3(dirname3(path5), { recursive: true });
|
|
@@ -32869,13 +33010,109 @@ function setupCodexIntegration() {
|
|
|
32869
33010
|
ensureAgentSkillsInstalled();
|
|
32870
33011
|
setupCodexHooks();
|
|
32871
33012
|
}
|
|
33013
|
+
var AGY_HOOKS_SCRIPT = `#!/usr/bin/env node
|
|
33014
|
+
'use strict';
|
|
33015
|
+
const { spawnSync } = require('child_process');
|
|
33016
|
+
const { join } = require('path');
|
|
33017
|
+
|
|
33018
|
+
const WRITE_TOOLS = new Set(['write_to_file', 'replace_file_content']);
|
|
33019
|
+
|
|
33020
|
+
function readStdin() {
|
|
33021
|
+
return new Promise((res) => {
|
|
33022
|
+
let data = '';
|
|
33023
|
+
process.stdin.setEncoding('utf-8');
|
|
33024
|
+
process.stdin.on('data', (c) => { data += c; });
|
|
33025
|
+
process.stdin.on('end', () => res(data));
|
|
33026
|
+
process.stdin.on('error', () => res(data));
|
|
33027
|
+
});
|
|
33028
|
+
}
|
|
33029
|
+
|
|
33030
|
+
async function main() {
|
|
33031
|
+
const event = process.argv[2];
|
|
33032
|
+
const raw = await readStdin();
|
|
33033
|
+
let payload = {};
|
|
33034
|
+
try { payload = JSON.parse(raw); } catch { /* keep {} */ }
|
|
33035
|
+
|
|
33036
|
+
try {
|
|
33037
|
+
if (event === 'post-tool-use') {
|
|
33038
|
+
const toolCall = payload.toolCall;
|
|
33039
|
+
const filePath = toolCall && toolCall.args && toolCall.args.TargetFile;
|
|
33040
|
+
if (toolCall && WRITE_TOOLS.has(toolCall.name) && typeof filePath === 'string' && filePath) {
|
|
33041
|
+
spawnSync(process.execPath, [join(__dirname, 'sync-push.cjs')], {
|
|
33042
|
+
input: JSON.stringify({ tool_input: { file_path: filePath } }),
|
|
33043
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
33044
|
+
windowsHide: true,
|
|
33045
|
+
});
|
|
33046
|
+
}
|
|
33047
|
+
spawnSync(process.execPath, [join(__dirname, 'capture.cjs'), 'agy', 'post-tool-use'], {
|
|
33048
|
+
input: raw,
|
|
33049
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
33050
|
+
windowsHide: true,
|
|
33051
|
+
});
|
|
33052
|
+
} else if (event === 'stop') {
|
|
33053
|
+
spawnSync(process.execPath, [join(__dirname, 'capture.cjs'), 'agy', 'stop'], {
|
|
33054
|
+
input: raw,
|
|
33055
|
+
stdio: ['pipe', 'ignore', 'ignore'],
|
|
33056
|
+
windowsHide: true,
|
|
33057
|
+
});
|
|
33058
|
+
}
|
|
33059
|
+
} catch { /* never let a side effect break the response below */ }
|
|
33060
|
+
|
|
33061
|
+
process.stdout.write('{}');
|
|
33062
|
+
}
|
|
33063
|
+
|
|
33064
|
+
main();
|
|
33065
|
+
`;
|
|
33066
|
+
function applyAgyHooks(existing) {
|
|
33067
|
+
const wrapper = join4(AGENT_HOOKS_DIR, "agy-hooks.cjs");
|
|
33068
|
+
const launcher = join4(AGENT_HOOKS_DIR, "launch.sh");
|
|
33069
|
+
const wrapCmd = (event) => `sh "${launcher}" "${wrapper}" ${event}`;
|
|
33070
|
+
const block = {
|
|
33071
|
+
PostToolUse: [
|
|
33072
|
+
{ matcher: ".*", hooks: [{ type: "command", command: wrapCmd("post-tool-use"), timeout: 30 }] }
|
|
33073
|
+
],
|
|
33074
|
+
Stop: [
|
|
33075
|
+
{ hooks: [{ type: "command", command: wrapCmd("stop"), timeout: 60 }] }
|
|
33076
|
+
]
|
|
33077
|
+
};
|
|
33078
|
+
let settings = {};
|
|
33079
|
+
if (existing !== null) {
|
|
33080
|
+
try {
|
|
33081
|
+
settings = JSON.parse(existing);
|
|
33082
|
+
} catch {
|
|
33083
|
+
return null;
|
|
33084
|
+
}
|
|
33085
|
+
}
|
|
33086
|
+
if (JSON.stringify(settings.gipity ?? null) === JSON.stringify(block)) return null;
|
|
33087
|
+
settings.gipity = block;
|
|
33088
|
+
return JSON.stringify(settings, null, 2) + "\n";
|
|
33089
|
+
}
|
|
33090
|
+
function setupAgyHooks() {
|
|
33091
|
+
if (process.platform === "win32") return;
|
|
33092
|
+
const cwd = resolve3(process.cwd());
|
|
33093
|
+
if (cwd === resolve3(homedir3())) return;
|
|
33094
|
+
mkdirSync3(AGENT_HOOKS_DIR, { recursive: true });
|
|
33095
|
+
writeFileSync4(join4(AGENT_HOOKS_DIR, "agy-hooks.cjs"), AGY_HOOKS_SCRIPT);
|
|
33096
|
+
const path5 = join4(cwd, ".agents", "hooks.json");
|
|
33097
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
33098
|
+
const next = applyAgyHooks(existing);
|
|
33099
|
+
if (next === null) return;
|
|
33100
|
+
mkdirSync3(dirname3(path5), { recursive: true });
|
|
33101
|
+
writeFileSync4(path5, next);
|
|
33102
|
+
console.log(existing === null ? "Wrote Antigravity sync + session-capture hooks (.agents/hooks.json)." : "Updated Antigravity hooks (.agents/hooks.json).");
|
|
33103
|
+
}
|
|
33104
|
+
function setupAgyIntegration() {
|
|
33105
|
+
if (!binaryOnPath("agy")) return;
|
|
33106
|
+
ensureAgySkillsInstalled();
|
|
33107
|
+
setupAgyHooks();
|
|
33108
|
+
}
|
|
32872
33109
|
function setupClaudeHooks() {
|
|
32873
33110
|
ensureGipityPlugin();
|
|
32874
33111
|
const cwd = resolve3(process.cwd());
|
|
32875
33112
|
if (cwd === resolve3(homedir3())) return;
|
|
32876
|
-
const claudeDir =
|
|
33113
|
+
const claudeDir = join4(cwd, ".claude");
|
|
32877
33114
|
mkdirSync3(claudeDir, { recursive: true });
|
|
32878
|
-
const settingsPath =
|
|
33115
|
+
const settingsPath = join4(claudeDir, "settings.json");
|
|
32879
33116
|
const settings = readSettingsFile(settingsPath);
|
|
32880
33117
|
stripGipityHooks(settings);
|
|
32881
33118
|
const perms = settings.permissions || {};
|
|
@@ -32922,7 +33159,7 @@ function applySkillsBlock(existing, apiBase2 = DEFAULT_API_BASE) {
|
|
|
32922
33159
|
function writeSkillsFile(relPath, wrap) {
|
|
32923
33160
|
const path5 = resolve3(process.cwd(), relPath);
|
|
32924
33161
|
mkdirSync3(dirname3(path5), { recursive: true });
|
|
32925
|
-
const existing = existsSync4(path5) ?
|
|
33162
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
32926
33163
|
const baseNext = applySkillsBlock(existing, resolveApiBase());
|
|
32927
33164
|
const next = wrap && existing === null ? wrap(baseNext) : baseNext;
|
|
32928
33165
|
if (next !== existing) writeFileSync4(path5, next);
|
|
@@ -32965,7 +33202,7 @@ function applyAiderConf(existing) {
|
|
|
32965
33202
|
function setupAiderMd() {
|
|
32966
33203
|
writeSkillsFile(PRIMER_FILES.aider);
|
|
32967
33204
|
const path5 = resolve3(process.cwd(), AIDER_CONF_FILE);
|
|
32968
|
-
const existing = existsSync4(path5) ?
|
|
33205
|
+
const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
|
|
32969
33206
|
const next = applyAiderConf(existing);
|
|
32970
33207
|
if (next !== null) writeFileSync4(path5, next);
|
|
32971
33208
|
}
|
|
@@ -32990,6 +33227,7 @@ var SUPPORTED_TOOLS = [
|
|
|
32990
33227
|
{ key: "claude", label: "Claude Code (CLAUDE.md + Gipity plugin)", setup: setupClaudeMd, integrate: setupClaudeHooks },
|
|
32991
33228
|
{ key: "codex", label: "OpenAI Codex (AGENTS.md + skills + sync hooks)", setup: setupAgentsMd, integrate: setupCodexIntegration },
|
|
32992
33229
|
{ key: "grok", label: "Grok Build (AGENTS.md + Gipity plugin)", setup: setupAgentsMd, integrate: ensureGrokPluginInstalled },
|
|
33230
|
+
{ key: "agy", label: "Antigravity (AGENTS.md + skills + sync hooks)", setup: setupAgentsMd, integrate: setupAgyIntegration },
|
|
32993
33231
|
{ key: "aider", label: "Aider (AGENTS.md + .aider.conf.yml)", setup: setupAiderMd, optIn: true },
|
|
32994
33232
|
{ key: "gemini", label: "Gemini CLI (GEMINI.md)", setup: setupGeminiMd },
|
|
32995
33233
|
{ key: "copilot", label: "GitHub Copilot (.github/copilot-instructions.md)", setup: setupCopilotMd },
|
|
@@ -33007,7 +33245,7 @@ function setupGitignore() {
|
|
|
33007
33245
|
const gitignorePath = resolve3(process.cwd(), ".gitignore");
|
|
33008
33246
|
const entries = [".gipity/", ".gipity.json", ...SCRATCH_IGNORE];
|
|
33009
33247
|
if (existsSync4(gitignorePath)) {
|
|
33010
|
-
let content =
|
|
33248
|
+
let content = readFileSync6(gitignorePath, "utf-8");
|
|
33011
33249
|
const lines = content.split(/\r?\n/);
|
|
33012
33250
|
const toAdd = entries.filter((e) => !lines.includes(e));
|
|
33013
33251
|
if (toAdd.length > 0) {
|
|
@@ -33035,11 +33273,11 @@ var BULK_DELETE_FRACTION = 0.25;
|
|
|
33035
33273
|
var DOWNLOAD_IDLE_MS = 3e4;
|
|
33036
33274
|
function syncStatePath() {
|
|
33037
33275
|
const configPath = getConfigPath();
|
|
33038
|
-
return
|
|
33276
|
+
return join5(dirname4(configPath), ".gipity", "sync-state.json");
|
|
33039
33277
|
}
|
|
33040
33278
|
function lockPath() {
|
|
33041
33279
|
const configPath = getConfigPath();
|
|
33042
|
-
return
|
|
33280
|
+
return join5(dirname4(configPath), ".gipity", "sync.lock");
|
|
33043
33281
|
}
|
|
33044
33282
|
function projectDir() {
|
|
33045
33283
|
const configPath = getConfigPath();
|
|
@@ -33053,8 +33291,8 @@ function isLockReclaimable(path5, now = Date.now()) {
|
|
|
33053
33291
|
let raw;
|
|
33054
33292
|
let mtimeMs;
|
|
33055
33293
|
try {
|
|
33056
|
-
raw =
|
|
33057
|
-
mtimeMs =
|
|
33294
|
+
raw = readFileSync7(path5, "utf-8").trim();
|
|
33295
|
+
mtimeMs = statSync2(path5).mtimeMs;
|
|
33058
33296
|
} catch {
|
|
33059
33297
|
return false;
|
|
33060
33298
|
}
|
|
@@ -33113,28 +33351,28 @@ async function acquireLock(progress) {
|
|
|
33113
33351
|
);
|
|
33114
33352
|
}
|
|
33115
33353
|
if (!waitSpinner) {
|
|
33116
|
-
waitSpinner = progress?.spinner("Waiting for another sync to finish
|
|
33354
|
+
waitSpinner = progress?.spinner("Waiting for another sync to finish...") ?? null;
|
|
33117
33355
|
}
|
|
33118
33356
|
await new Promise((r) => setTimeout(r, LOCK_POLL_MS2));
|
|
33119
33357
|
}
|
|
33120
33358
|
}
|
|
33121
33359
|
}
|
|
33122
|
-
function readBaseline(
|
|
33360
|
+
function readBaseline(projectGuid2) {
|
|
33123
33361
|
const path5 = syncStatePath();
|
|
33124
|
-
if (!existsSync5(path5)) return { projectGuid, files: {}, lastFullSync: null };
|
|
33362
|
+
if (!existsSync5(path5)) return { projectGuid: projectGuid2, files: {}, lastFullSync: null };
|
|
33125
33363
|
try {
|
|
33126
|
-
const parsed = JSON.parse(
|
|
33127
|
-
if (parsed.projectGuid !==
|
|
33128
|
-
return { projectGuid, files: {}, lastFullSync: null };
|
|
33364
|
+
const parsed = JSON.parse(readFileSync7(path5, "utf-8"));
|
|
33365
|
+
if (parsed.projectGuid !== projectGuid2) {
|
|
33366
|
+
return { projectGuid: projectGuid2, files: {}, lastFullSync: null };
|
|
33129
33367
|
}
|
|
33130
33368
|
return {
|
|
33131
|
-
projectGuid,
|
|
33369
|
+
projectGuid: projectGuid2,
|
|
33132
33370
|
files: parsed.files ?? {},
|
|
33133
33371
|
lastFullSync: parsed.lastFullSync ?? null,
|
|
33134
33372
|
...parsed.deletesSkippedStreak ? { deletesSkippedStreak: parsed.deletesSkippedStreak } : {}
|
|
33135
33373
|
};
|
|
33136
33374
|
} catch {
|
|
33137
|
-
return { projectGuid, files: {}, lastFullSync: null };
|
|
33375
|
+
return { projectGuid: projectGuid2, files: {}, lastFullSync: null };
|
|
33138
33376
|
}
|
|
33139
33377
|
}
|
|
33140
33378
|
function writeBaseline(b7) {
|
|
@@ -33147,25 +33385,25 @@ function walkLocal(root, ignorePatterns, baseline) {
|
|
|
33147
33385
|
function walk(dir) {
|
|
33148
33386
|
let entries;
|
|
33149
33387
|
try {
|
|
33150
|
-
entries =
|
|
33388
|
+
entries = readdirSync4(dir, { withFileTypes: true });
|
|
33151
33389
|
} catch {
|
|
33152
33390
|
return;
|
|
33153
33391
|
}
|
|
33154
33392
|
for (const entry of entries) {
|
|
33155
|
-
const full =
|
|
33393
|
+
const full = join5(dir, entry.name);
|
|
33156
33394
|
const rel2 = relative(root, full).replace(/\\/g, "/");
|
|
33157
33395
|
if (shouldIgnore(rel2, ignorePatterns)) continue;
|
|
33158
33396
|
if (entry.isDirectory()) {
|
|
33159
|
-
if (existsSync5(
|
|
33397
|
+
if (existsSync5(join5(full, CONFIG_FILE2))) continue;
|
|
33160
33398
|
walk(full);
|
|
33161
33399
|
} else if (entry.isFile()) {
|
|
33162
33400
|
try {
|
|
33163
|
-
const stat2 =
|
|
33401
|
+
const stat2 = statSync2(full);
|
|
33164
33402
|
const size = stat2.size;
|
|
33165
33403
|
const mtime = stat2.mtime.toISOString();
|
|
33166
33404
|
const prior = baseline[rel2];
|
|
33167
|
-
const
|
|
33168
|
-
result.set(rel2, { size, mtime, sha256
|
|
33405
|
+
const sha256 = prior && prior.size === size && prior.mtime === mtime ? prior.sha256 : void 0;
|
|
33406
|
+
result.set(rel2, { size, mtime, sha256 });
|
|
33169
33407
|
} catch {
|
|
33170
33408
|
}
|
|
33171
33409
|
}
|
|
@@ -33179,8 +33417,8 @@ async function ensureLocalHashes(root, local, paths) {
|
|
|
33179
33417
|
const info2 = local.get(path5);
|
|
33180
33418
|
if (!info2 || info2.sha256) continue;
|
|
33181
33419
|
try {
|
|
33182
|
-
const { sha256
|
|
33183
|
-
info2.sha256 =
|
|
33420
|
+
const { sha256 } = await hashFile(join5(root, path5));
|
|
33421
|
+
info2.sha256 = sha256;
|
|
33184
33422
|
} catch {
|
|
33185
33423
|
}
|
|
33186
33424
|
}
|
|
@@ -33195,10 +33433,10 @@ function resolveInRoot(root, relPath) {
|
|
|
33195
33433
|
throw new Error(`Refusing path outside project root: ${relPath}`);
|
|
33196
33434
|
}
|
|
33197
33435
|
try {
|
|
33198
|
-
const rootReal =
|
|
33436
|
+
const rootReal = realpathSync2(rootResolved);
|
|
33199
33437
|
let ancestor = full;
|
|
33200
33438
|
while (!existsSync5(ancestor) && dirname4(ancestor) !== ancestor) ancestor = dirname4(ancestor);
|
|
33201
|
-
const ancestorReal =
|
|
33439
|
+
const ancestorReal = realpathSync2(ancestor);
|
|
33202
33440
|
if (ancestorReal !== rootReal && !ancestorReal.startsWith(rootReal + sep)) {
|
|
33203
33441
|
throw new Error(`Refusing path outside project root (symlink escape): ${relPath}`);
|
|
33204
33442
|
}
|
|
@@ -33207,8 +33445,8 @@ function resolveInRoot(root, relPath) {
|
|
|
33207
33445
|
}
|
|
33208
33446
|
return full;
|
|
33209
33447
|
}
|
|
33210
|
-
async function fetchRemote(
|
|
33211
|
-
const res = await get(`/projects/${
|
|
33448
|
+
async function fetchRemote(projectGuid2) {
|
|
33449
|
+
const res = await get(`/projects/${projectGuid2}/files/tree`);
|
|
33212
33450
|
const out = /* @__PURE__ */ new Map();
|
|
33213
33451
|
for (const f of res.data) {
|
|
33214
33452
|
if (f.type !== "file") continue;
|
|
@@ -33269,14 +33507,14 @@ function extractTarToMap(stream, idleMs, onBytes, keep) {
|
|
|
33269
33507
|
stream.pipe(extract3);
|
|
33270
33508
|
});
|
|
33271
33509
|
}
|
|
33272
|
-
async function downloadAll(
|
|
33273
|
-
const stream = await downloadStream(`/projects/${
|
|
33510
|
+
async function downloadAll(projectGuid2, onBytes) {
|
|
33511
|
+
const stream = await downloadStream(`/projects/${projectGuid2}/files/tree?content=tar`);
|
|
33274
33512
|
return extractTarToMap(stream, DOWNLOAD_IDLE_MS, onBytes);
|
|
33275
33513
|
}
|
|
33276
|
-
async function fetchOneOutcome(
|
|
33514
|
+
async function fetchOneOutcome(projectGuid2, path5, expectedSha) {
|
|
33277
33515
|
try {
|
|
33278
33516
|
const stream = await downloadStream(
|
|
33279
|
-
`/projects/${
|
|
33517
|
+
`/projects/${projectGuid2}/files/tree?content=tar&path=${encodeURIComponent(path5)}`
|
|
33280
33518
|
);
|
|
33281
33519
|
const want = normalizeTreePath(path5);
|
|
33282
33520
|
const files = await extractTarToMap(stream, DOWNLOAD_IDLE_MS, void 0, (p) => p === want);
|
|
@@ -33292,8 +33530,8 @@ async function fetchOneOutcome(projectGuid, path5, expectedSha) {
|
|
|
33292
33530
|
return { kind: "transient" };
|
|
33293
33531
|
}
|
|
33294
33532
|
}
|
|
33295
|
-
async function fetchOne(
|
|
33296
|
-
const out = await fetchOneOutcome(
|
|
33533
|
+
async function fetchOne(projectGuid2, path5, expectedSha) {
|
|
33534
|
+
const out = await fetchOneOutcome(projectGuid2, path5, expectedSha);
|
|
33297
33535
|
return out.kind === "ok" ? out.buf : null;
|
|
33298
33536
|
}
|
|
33299
33537
|
function classifyLocal(info2, base) {
|
|
@@ -33486,9 +33724,9 @@ Plan deletes ${totalDeletes} files (${Math.round(fraction * 100)}% of the tree).
|
|
|
33486
33724
|
}
|
|
33487
33725
|
var GIPITY_IGNORE_FILE = ".gipityignore";
|
|
33488
33726
|
function readGipityIgnore(root) {
|
|
33489
|
-
const path5 =
|
|
33727
|
+
const path5 = join5(root, GIPITY_IGNORE_FILE);
|
|
33490
33728
|
if (!existsSync5(path5)) return [];
|
|
33491
|
-
return
|
|
33729
|
+
return readFileSync7(path5, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\.\//, "").replace(/^\//, ""));
|
|
33492
33730
|
}
|
|
33493
33731
|
function effectiveIgnore(root, configIgnore) {
|
|
33494
33732
|
const base = configIgnore && configIgnore.length ? configIgnore : DEFAULT_SYNC_IGNORE;
|
|
@@ -33525,14 +33763,14 @@ async function sync(opts = {}) {
|
|
|
33525
33763
|
releaseLock();
|
|
33526
33764
|
}
|
|
33527
33765
|
}
|
|
33528
|
-
async function syncInner(
|
|
33529
|
-
const config = { projectGuid, ignore: ignore2 };
|
|
33766
|
+
async function syncInner(projectGuid2, root, ignore2, opts, interactive) {
|
|
33767
|
+
const config = { projectGuid: projectGuid2, ignore: ignore2 };
|
|
33530
33768
|
const p = opts.progress;
|
|
33531
|
-
const baseline = readBaseline(
|
|
33532
|
-
p?.phase("Scanning local files
|
|
33769
|
+
const baseline = readBaseline(projectGuid2);
|
|
33770
|
+
p?.phase("Scanning local files...");
|
|
33533
33771
|
const local = walkLocal(root, ignore2, baseline.files);
|
|
33534
|
-
p?.phase("Checking Gipity for changes
|
|
33535
|
-
const remote = await fetchRemote(
|
|
33772
|
+
p?.phase("Checking Gipity for changes...");
|
|
33773
|
+
const remote = await fetchRemote(projectGuid2);
|
|
33536
33774
|
for (const path5 of [...remote.keys()]) {
|
|
33537
33775
|
if (shouldIgnore(path5, ignore2)) remote.delete(path5);
|
|
33538
33776
|
}
|
|
@@ -33542,7 +33780,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33542
33780
|
const r = remote.get(path5);
|
|
33543
33781
|
if (r?.sha256 || baseline.files[path5]) needHash.push(path5);
|
|
33544
33782
|
}
|
|
33545
|
-
if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}
|
|
33783
|
+
if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}...`);
|
|
33546
33784
|
await ensureLocalHashes(root, local, needHash);
|
|
33547
33785
|
const planned = plan(local, remote, baseline.files);
|
|
33548
33786
|
if (opts.plan) {
|
|
@@ -33628,13 +33866,13 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33628
33866
|
if (buf) downloadedBytes.set(a.path, buf);
|
|
33629
33867
|
}
|
|
33630
33868
|
} catch (err) {
|
|
33631
|
-
errors.push(`Bulk download incomplete (${err.message}); recovering files individually
|
|
33869
|
+
errors.push(`Bulk download incomplete (${err.message}); recovering files individually...`);
|
|
33632
33870
|
} finally {
|
|
33633
33871
|
p?.finish();
|
|
33634
33872
|
}
|
|
33635
33873
|
const missing = wantedDownloads.filter((a) => !downloadedBytes.has(a.path));
|
|
33636
33874
|
if (missing.length) {
|
|
33637
|
-
p?.phase(`Recovering ${missing.length} file${missing.length === 1 ? "" : "s"} the bulk download dropped
|
|
33875
|
+
p?.phase(`Recovering ${missing.length} file${missing.length === 1 ? "" : "s"} the bulk download dropped...`);
|
|
33638
33876
|
for (const a of missing) {
|
|
33639
33877
|
const expectedSha = remote.get(a.path)?.sha256 ?? void 0;
|
|
33640
33878
|
let buf = null;
|
|
@@ -33676,7 +33914,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33676
33914
|
if (currentBytes) {
|
|
33677
33915
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33678
33916
|
writeFileSync5(full, currentBytes);
|
|
33679
|
-
const stat2 =
|
|
33917
|
+
const stat2 = statSync2(full);
|
|
33680
33918
|
baseline.files[a.path] = {
|
|
33681
33919
|
size: stat2.size,
|
|
33682
33920
|
mtime: stat2.mtime.toISOString(),
|
|
@@ -33692,12 +33930,12 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33692
33930
|
renamedRel,
|
|
33693
33931
|
{ expectedServerVersion: null }
|
|
33694
33932
|
);
|
|
33695
|
-
const stat2 =
|
|
33696
|
-
const { sha256
|
|
33933
|
+
const stat2 = statSync2(renamedFull);
|
|
33934
|
+
const { sha256 } = await hashFile(renamedFull);
|
|
33697
33935
|
baseline.files[renamedRel] = {
|
|
33698
33936
|
size: stat2.size,
|
|
33699
33937
|
mtime: stat2.mtime.toISOString(),
|
|
33700
|
-
sha256
|
|
33938
|
+
sha256,
|
|
33701
33939
|
serverVersion: result.serverVersion
|
|
33702
33940
|
};
|
|
33703
33941
|
} catch (e) {
|
|
@@ -33733,7 +33971,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33733
33971
|
}
|
|
33734
33972
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33735
33973
|
writeFileSync5(full, buf);
|
|
33736
|
-
const stat2 =
|
|
33974
|
+
const stat2 = statSync2(full);
|
|
33737
33975
|
local.set(a.path, { size: stat2.size, mtime: stat2.mtime.toISOString(), sha256: void 0 });
|
|
33738
33976
|
baseline.files[a.path] = {
|
|
33739
33977
|
size: stat2.size,
|
|
@@ -33764,7 +34002,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33764
34002
|
if (buf) {
|
|
33765
34003
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33766
34004
|
writeFileSync5(full, buf);
|
|
33767
|
-
const stat2 =
|
|
34005
|
+
const stat2 = statSync2(full);
|
|
33768
34006
|
baseline.files[a.path] = {
|
|
33769
34007
|
size: stat2.size,
|
|
33770
34008
|
mtime: stat2.mtime.toISOString(),
|
|
@@ -33778,12 +34016,12 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33778
34016
|
const result = await uploadOneFile(config.projectGuid, renamed, a.renamedLocalTo, {
|
|
33779
34017
|
expectedServerVersion: null
|
|
33780
34018
|
});
|
|
33781
|
-
const stat2 =
|
|
33782
|
-
const { sha256
|
|
34019
|
+
const stat2 = statSync2(renamed);
|
|
34020
|
+
const { sha256 } = await hashFile(renamed);
|
|
33783
34021
|
baseline.files[a.renamedLocalTo] = {
|
|
33784
34022
|
size: stat2.size,
|
|
33785
34023
|
mtime: stat2.mtime.toISOString(),
|
|
33786
|
-
sha256
|
|
34024
|
+
sha256,
|
|
33787
34025
|
serverVersion: result.serverVersion
|
|
33788
34026
|
};
|
|
33789
34027
|
} catch (err) {
|
|
@@ -33817,14 +34055,14 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33817
34055
|
continue;
|
|
33818
34056
|
}
|
|
33819
34057
|
try {
|
|
33820
|
-
const stat2 =
|
|
34058
|
+
const stat2 = statSync2(full);
|
|
33821
34059
|
if (stat2.size > UPLOAD_MAX_BYTES) {
|
|
33822
34060
|
errors.push(`Upload failed for ${a.path}: file exceeds the 30 GB upload limit`);
|
|
33823
34061
|
onBytes?.(a.localSize ?? 0);
|
|
33824
34062
|
continue;
|
|
33825
34063
|
}
|
|
33826
|
-
const
|
|
33827
|
-
prepared.push({ a, full, size: stat2.size, mtime: stat2.mtime.toISOString(), sha256
|
|
34064
|
+
const sha256 = local.get(a.path)?.sha256 ?? (await hashFile(full)).sha256;
|
|
34065
|
+
prepared.push({ a, full, size: stat2.size, mtime: stat2.mtime.toISOString(), sha256 });
|
|
33828
34066
|
} catch (e) {
|
|
33829
34067
|
errors.push(`Upload failed for ${a.path}: ${e.message}`);
|
|
33830
34068
|
onBytes?.(a.localSize ?? 0);
|
|
@@ -33971,13 +34209,13 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
33971
34209
|
const full = resolveInRoot(root, a.path);
|
|
33972
34210
|
mkdirSync4(dirname4(full), { recursive: true });
|
|
33973
34211
|
writeFileSync5(full, buf);
|
|
33974
|
-
const stat2 =
|
|
33975
|
-
const { sha256
|
|
34212
|
+
const stat2 = statSync2(full);
|
|
34213
|
+
const { sha256 } = await hashFile(full);
|
|
33976
34214
|
const current = typeof err.data?.current_server_version === "number" ? err.data.current_server_version : null;
|
|
33977
34215
|
baseline.files[a.path] = {
|
|
33978
34216
|
size: stat2.size,
|
|
33979
34217
|
mtime: stat2.mtime.toISOString(),
|
|
33980
|
-
sha256
|
|
34218
|
+
sha256,
|
|
33981
34219
|
serverVersion: current ?? a.expectedServerVersion ?? 0
|
|
33982
34220
|
};
|
|
33983
34221
|
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)`);
|
|
@@ -34021,7 +34259,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
|
|
|
34021
34259
|
function cleanupEmptyDirs(root, emptiedDirs) {
|
|
34022
34260
|
const isEmpty = (dir) => {
|
|
34023
34261
|
try {
|
|
34024
|
-
return
|
|
34262
|
+
return readdirSync4(dir).length === 0;
|
|
34025
34263
|
} catch {
|
|
34026
34264
|
return false;
|
|
34027
34265
|
}
|
|
@@ -34056,12 +34294,12 @@ async function pushFile(filePath) {
|
|
|
34056
34294
|
const result = await uploadOneFile(config.projectGuid, filePath, rel2, {
|
|
34057
34295
|
expectedServerVersion: baseEntry ? baseEntry.serverVersion : null
|
|
34058
34296
|
});
|
|
34059
|
-
const stat2 =
|
|
34060
|
-
const { sha256
|
|
34297
|
+
const stat2 = statSync2(filePath);
|
|
34298
|
+
const { sha256 } = await hashFile(filePath);
|
|
34061
34299
|
baseline.files[rel2] = {
|
|
34062
34300
|
size: stat2.size,
|
|
34063
34301
|
mtime: stat2.mtime.toISOString(),
|
|
34064
|
-
sha256
|
|
34302
|
+
sha256,
|
|
34065
34303
|
serverVersion: result.serverVersion
|
|
34066
34304
|
};
|
|
34067
34305
|
writeBaseline(baseline);
|
|
@@ -34249,11 +34487,11 @@ async function syncBeforeAction(opts) {
|
|
|
34249
34487
|
}
|
|
34250
34488
|
|
|
34251
34489
|
// src/helpers/body.ts
|
|
34252
|
-
import { readFileSync as
|
|
34490
|
+
import { readFileSync as readFileSync8 } from "node:fs";
|
|
34253
34491
|
function readStdin() {
|
|
34254
34492
|
if (process.stdin.isTTY) return "";
|
|
34255
34493
|
try {
|
|
34256
|
-
return
|
|
34494
|
+
return readFileSync8(0, "utf-8");
|
|
34257
34495
|
} catch {
|
|
34258
34496
|
return "";
|
|
34259
34497
|
}
|
|
@@ -34269,7 +34507,7 @@ function readFileField(spec) {
|
|
|
34269
34507
|
if (!field) throw new Error(`Invalid --file '${spec}': missing field name before '='.`);
|
|
34270
34508
|
if (!path5) throw new Error(`Invalid --file '${spec}': missing file path after '='.`);
|
|
34271
34509
|
try {
|
|
34272
|
-
return [field, { data:
|
|
34510
|
+
return [field, { data: readFileSync8(path5).toString("base64"), media_type: guessMime(path5) }];
|
|
34273
34511
|
} catch (e) {
|
|
34274
34512
|
throw new Error(`Cannot read --file '${path5}': ${e.message}`);
|
|
34275
34513
|
}
|
|
@@ -34300,7 +34538,7 @@ function resolveJsonBody(raw) {
|
|
|
34300
34538
|
} else if (raw.startsWith("@")) {
|
|
34301
34539
|
const path5 = raw.slice(1);
|
|
34302
34540
|
try {
|
|
34303
|
-
source =
|
|
34541
|
+
source = readFileSync8(path5, "utf-8");
|
|
34304
34542
|
} catch (e) {
|
|
34305
34543
|
throw new Error(`Cannot read body file '${path5}': ${e.message}`);
|
|
34306
34544
|
}
|
|
@@ -34332,7 +34570,12 @@ function parseDuration(raw, unit) {
|
|
|
34332
34570
|
}
|
|
34333
34571
|
|
|
34334
34572
|
// src/commands/token.ts
|
|
34335
|
-
var tokenCommand = new Command("token").description("Manage API tokens").addHelpText("after",
|
|
34573
|
+
var tokenCommand = new Command("token").description("Manage API tokens").addHelpText("after", `
|
|
34574
|
+
Long-lived agent API tokens (gip_at_*) for headless agents and CI - they drive
|
|
34575
|
+
the gipity CLI as YOU.
|
|
34576
|
+
|
|
34577
|
+
To let a script or cron write to one app instead, mint a project API key:
|
|
34578
|
+
gipity key create "my script" --role editor (sent as X-Api-Key).`);
|
|
34336
34579
|
var fmtDate = (d) => d ? new Date(d).toLocaleDateString() : "never";
|
|
34337
34580
|
tokenCommand.command("create").description("Mint a long-lived agent API token (shown once)").option("--name <name>", 'Label for the token, e.g. "Hermes on my VPS"').option("--expires <days>", "Days until the token expires (default: never)").option("--json", "Output as JSON").action((opts) => run("Create", async () => {
|
|
34338
34581
|
const body = {};
|
|
@@ -34377,27 +34620,94 @@ tokenCommand.command("revoke <short_guid>").alias("rm").description("Revoke an a
|
|
|
34377
34620
|
console.log(success(`Revoked token ${bold(shortGuid)}.`));
|
|
34378
34621
|
}));
|
|
34379
34622
|
|
|
34623
|
+
// src/commands/key.ts
|
|
34624
|
+
init_api();
|
|
34625
|
+
init_config();
|
|
34626
|
+
init_colors();
|
|
34627
|
+
var keyCommand = new Command("key").description("Manage project API keys for scripts and agents (X-Api-Key)").addHelpText("after", `
|
|
34628
|
+
Give a script, cron, or agent write access to your app without a login:
|
|
34629
|
+
|
|
34630
|
+
gipity key create "laptop importer" --role editor
|
|
34631
|
+
# the script sends: X-Api-Key: <the key printed once above>
|
|
34632
|
+
|
|
34633
|
+
Inside a function the caller shows up as ctx.auth.via === 'api_key' with
|
|
34634
|
+
ctx.auth.apiKeyName set to the key's name - stamp that on rows to tell
|
|
34635
|
+
script-written entries from hand-entered ones. Never build your own key table.
|
|
34636
|
+
|
|
34637
|
+
Account-level tokens that run the CLI headlessly are a different thing: see
|
|
34638
|
+
gipity token --help.`);
|
|
34639
|
+
var fmtDate2 = (d) => d ? new Date(d).toLocaleDateString() : "never";
|
|
34640
|
+
var projectOpt = ["--project <guid-or-slug>", "Target a specific project instead of cwd / Home"];
|
|
34641
|
+
async function projectGuid(opts) {
|
|
34642
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
34643
|
+
return config.projectGuid;
|
|
34644
|
+
}
|
|
34645
|
+
keyCommand.command("create <name>").description("Mint a project API key (shown once). Give it a name you will recognize later").option("--role <role>", "viewer | editor | owner (default: viewer)", "viewer").option("--expires-days <n>", "Days until the key expires (default: never)").option(...projectOpt).option("--json", "Output as JSON").action((name, opts) => run("Create", async () => {
|
|
34646
|
+
const body = { name, role: opts.role };
|
|
34647
|
+
if (opts.expiresDays !== void 0) {
|
|
34648
|
+
const days = parseInt(opts.expiresDays, 10);
|
|
34649
|
+
if (!Number.isFinite(days) || days <= 0) throw new Error("--expires-days must be a positive number of days");
|
|
34650
|
+
body.expires_in_days = days;
|
|
34651
|
+
}
|
|
34652
|
+
const res = await post(
|
|
34653
|
+
`/projects/${await projectGuid(opts)}/api-keys`,
|
|
34654
|
+
body
|
|
34655
|
+
);
|
|
34656
|
+
const k7 = res.data;
|
|
34657
|
+
if (opts.json) {
|
|
34658
|
+
console.log(JSON.stringify(k7));
|
|
34659
|
+
return;
|
|
34660
|
+
}
|
|
34661
|
+
const expNote = k7.expires_at ? ` (expires ${fmtDate2(k7.expires_at)})` : " (never expires)";
|
|
34662
|
+
console.log(success(`Created API key ${bold(k7.short_guid)} "${k7.name}" as ${k7.role}${muted(expNote)}.`));
|
|
34663
|
+
console.log("");
|
|
34664
|
+
console.log(k7.key);
|
|
34665
|
+
console.log("");
|
|
34666
|
+
console.log(muted("Send it from your script on every request:"));
|
|
34667
|
+
console.log(muted(` curl -H "X-Api-Key: ${k7.key}" ...`));
|
|
34668
|
+
console.log(muted(`Revoke it any time: gipity key revoke ${k7.short_guid}`));
|
|
34669
|
+
console.log("");
|
|
34670
|
+
console.log(warning("Copy it now - it will not be shown again."));
|
|
34671
|
+
}));
|
|
34672
|
+
keyCommand.command("list").alias("ls").description("List this project's API keys (values are never shown again)").option(...projectOpt).option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
34673
|
+
const res = await get(`/projects/${await projectGuid(opts)}/api-keys`);
|
|
34674
|
+
printList(
|
|
34675
|
+
res.data,
|
|
34676
|
+
opts,
|
|
34677
|
+
'No API keys. Mint one with: gipity key create "my script" --role editor',
|
|
34678
|
+
(k7) => `${bold(k7.short_guid)} ${k7.name} ${muted(`${k7.role} ${k7.prefix}\u2026 last used ${fmtDate2(k7.last_used_at)} expires ${fmtDate2(k7.expires_at)}`)}`
|
|
34679
|
+
);
|
|
34680
|
+
}));
|
|
34681
|
+
keyCommand.command("revoke <short_guid>").alias("rm").description("Revoke a project API key (instant, irreversible)").option(...projectOpt).option("--json", "Output as JSON").action((shortGuid, opts) => run("Revoke", async () => {
|
|
34682
|
+
await del(`/projects/${await projectGuid(opts)}/api-keys/${encodeURIComponent(shortGuid)}`);
|
|
34683
|
+
if (opts.json) {
|
|
34684
|
+
console.log(JSON.stringify({ short_guid: shortGuid, revoked: true }));
|
|
34685
|
+
return;
|
|
34686
|
+
}
|
|
34687
|
+
console.log(success(`Revoked API key ${bold(shortGuid)}.`));
|
|
34688
|
+
}));
|
|
34689
|
+
|
|
34380
34690
|
// src/commands/init.ts
|
|
34381
34691
|
init_api();
|
|
34382
34692
|
init_config();
|
|
34383
34693
|
init_auth();
|
|
34384
|
-
import { basename, resolve as resolve7, dirname as dirname5 } from "path";
|
|
34385
|
-
import { existsSync as existsSync8, readFileSync as
|
|
34694
|
+
import { basename as basename2, resolve as resolve7, dirname as dirname5 } from "path";
|
|
34695
|
+
import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
|
|
34386
34696
|
init_colors();
|
|
34387
34697
|
init_utils();
|
|
34388
34698
|
|
|
34389
34699
|
// src/adopt-cwd.ts
|
|
34390
34700
|
init_api();
|
|
34391
|
-
import { readdirSync as
|
|
34392
|
-
import { join as
|
|
34701
|
+
import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
|
|
34702
|
+
import { join as join9, resolve as resolve6, sep as sep2, parse as parsePath } from "path";
|
|
34393
34703
|
import { homedir as homedir6 } from "os";
|
|
34394
34704
|
|
|
34395
34705
|
// src/project-setup.ts
|
|
34396
34706
|
init_config();
|
|
34397
34707
|
|
|
34398
34708
|
// src/template-vars.ts
|
|
34399
|
-
import { promises as fs, readdirSync as
|
|
34400
|
-
import { join as
|
|
34709
|
+
import { promises as fs, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
|
|
34710
|
+
import { join as join6, relative as relative2, extname as extname3 } from "path";
|
|
34401
34711
|
var SUBSTITUTABLE_EXTS = /* @__PURE__ */ new Set([
|
|
34402
34712
|
".html",
|
|
34403
34713
|
".htm",
|
|
@@ -34422,6 +34732,80 @@ function escapeHtml(s) {
|
|
|
34422
34732
|
function jsEscape(s) {
|
|
34423
34733
|
return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
34424
34734
|
}
|
|
34735
|
+
var ACCENT_PALETTE = [
|
|
34736
|
+
"#f59e0b",
|
|
34737
|
+
"#f97316",
|
|
34738
|
+
"#ef4444",
|
|
34739
|
+
"#ec4899",
|
|
34740
|
+
"#a855f7",
|
|
34741
|
+
"#6366f1",
|
|
34742
|
+
"#3b82f6",
|
|
34743
|
+
"#0ea5e9",
|
|
34744
|
+
"#14b8a6",
|
|
34745
|
+
"#22c55e",
|
|
34746
|
+
"#84cc16",
|
|
34747
|
+
"#eab308"
|
|
34748
|
+
];
|
|
34749
|
+
function hashString(s) {
|
|
34750
|
+
let h = 2166136261;
|
|
34751
|
+
for (let i = 0; i < s.length; i++) {
|
|
34752
|
+
h ^= s.charCodeAt(i);
|
|
34753
|
+
h = Math.imul(h, 16777619);
|
|
34754
|
+
}
|
|
34755
|
+
return h >>> 0;
|
|
34756
|
+
}
|
|
34757
|
+
function darkTint(hex) {
|
|
34758
|
+
const n = parseInt(hex.slice(1), 16);
|
|
34759
|
+
const base = 1052692;
|
|
34760
|
+
const ch = (shift) => {
|
|
34761
|
+
const a = n >> shift & 255;
|
|
34762
|
+
const b7 = base >> shift & 255;
|
|
34763
|
+
return Math.round(b7 + (a - b7) * 0.14).toString(16).padStart(2, "0");
|
|
34764
|
+
};
|
|
34765
|
+
return `#${ch(16)}${ch(8)}${ch(0)}`;
|
|
34766
|
+
}
|
|
34767
|
+
function buildHeadBlock(v7) {
|
|
34768
|
+
const t = escapeHtml(v7.projectName);
|
|
34769
|
+
const d = v7.description ? escapeHtml(v7.description) : "";
|
|
34770
|
+
const url = v7.accountSlug && v7.projectSlug ? `https://app.gipity.ai/${v7.accountSlug}/${v7.projectSlug}/` : void 0;
|
|
34771
|
+
const themeColor = darkTint(ACCENT_PALETTE[hashString(v7.projectGuid || v7.projectName) % ACCENT_PALETTE.length]);
|
|
34772
|
+
const jsonLd = JSON.stringify({
|
|
34773
|
+
"@context": "https://schema.org",
|
|
34774
|
+
"@type": "WebApplication",
|
|
34775
|
+
name: v7.projectName,
|
|
34776
|
+
...v7.description ? { description: v7.description } : {},
|
|
34777
|
+
...url ? { url } : {}
|
|
34778
|
+
}, null, 2).replace(/<\//g, "<\\/");
|
|
34779
|
+
const lines = [
|
|
34780
|
+
`<title>${t}</title>`,
|
|
34781
|
+
...d ? [`<meta name="description" content="${d}">`] : [],
|
|
34782
|
+
...url ? [`<link rel="canonical" href="${url}">`] : [],
|
|
34783
|
+
`<meta property="og:title" content="${t}">`,
|
|
34784
|
+
`<meta property="og:type" content="website">`,
|
|
34785
|
+
...d ? [`<meta property="og:description" content="${d}">`] : [],
|
|
34786
|
+
...url ? [
|
|
34787
|
+
`<meta property="og:url" content="${url}">`,
|
|
34788
|
+
`<meta property="og:image" content="${url}images/og-image.png">`,
|
|
34789
|
+
`<meta property="og:image:width" content="1200">`,
|
|
34790
|
+
`<meta property="og:image:height" content="630">`
|
|
34791
|
+
] : [],
|
|
34792
|
+
`<meta name="twitter:card" content="summary_large_image">`,
|
|
34793
|
+
`<meta name="twitter:title" content="${t}">`,
|
|
34794
|
+
...d ? [`<meta name="twitter:description" content="${d}">`] : [],
|
|
34795
|
+
...url ? [`<meta name="twitter:image" content="${url}images/og-image.png">`] : [],
|
|
34796
|
+
`<meta name="theme-color" content="${themeColor}">`,
|
|
34797
|
+
`<link rel="icon" type="image/png" sizes="192x192" href="./images/favicon-192.png">`,
|
|
34798
|
+
`<link rel="icon" type="image/png" sizes="512x512" href="./images/favicon-512.png">`,
|
|
34799
|
+
`<link rel="icon" type="image/x-icon" href="./images/favicon.ico">`,
|
|
34800
|
+
`<link rel="apple-touch-icon" href="./images/apple-touch-icon.png">`,
|
|
34801
|
+
`<link rel="manifest" href="./manifest.webmanifest">`,
|
|
34802
|
+
`<script type="application/ld+json">
|
|
34803
|
+
${jsonLd}
|
|
34804
|
+
</script>`
|
|
34805
|
+
];
|
|
34806
|
+
return lines.map((l7) => `
|
|
34807
|
+
${l7}`).join("");
|
|
34808
|
+
}
|
|
34425
34809
|
function buildTemplateVars(v7) {
|
|
34426
34810
|
const safeTitle = escapeHtml(v7.projectName);
|
|
34427
34811
|
const safeDesc = v7.description ? escapeHtml(v7.description) : "";
|
|
@@ -34437,6 +34821,9 @@ function buildTemplateVars(v7) {
|
|
|
34437
34821
|
"{{JS_TITLE}}": jsEscape(v7.projectName),
|
|
34438
34822
|
"{{PROJECT_GUID}}": v7.projectGuid,
|
|
34439
34823
|
"{{DATABASE}}": slug,
|
|
34824
|
+
"{{HEAD_BLOCK}}": buildHeadBlock(v7),
|
|
34825
|
+
// Legacy per-tag placeholders — current templates carry only {{HEAD_BLOCK}},
|
|
34826
|
+
// but older local template copies may still reference these.
|
|
34440
34827
|
"{{DESCRIPTION_META}}": v7.description ? `
|
|
34441
34828
|
<meta name="description" content="${safeDesc}">` : "",
|
|
34442
34829
|
"{{OG_DESCRIPTION}}": v7.description ? `
|
|
@@ -34463,18 +34850,18 @@ function* walkTextFiles(root) {
|
|
|
34463
34850
|
const dir = stack.pop();
|
|
34464
34851
|
let entries;
|
|
34465
34852
|
try {
|
|
34466
|
-
entries =
|
|
34853
|
+
entries = readdirSync5(dir, { withFileTypes: true });
|
|
34467
34854
|
} catch {
|
|
34468
34855
|
continue;
|
|
34469
34856
|
}
|
|
34470
34857
|
for (const entry of entries) {
|
|
34471
34858
|
if (isSyncIgnored(entry.name)) continue;
|
|
34472
|
-
const full =
|
|
34859
|
+
const full = join6(dir, entry.name);
|
|
34473
34860
|
if (entry.isDirectory()) {
|
|
34474
34861
|
stack.push(full);
|
|
34475
34862
|
} else if (entry.isFile()) {
|
|
34476
34863
|
try {
|
|
34477
|
-
if (!
|
|
34864
|
+
if (!statSync3(full).isFile()) continue;
|
|
34478
34865
|
} catch {
|
|
34479
34866
|
continue;
|
|
34480
34867
|
}
|
|
@@ -34491,7 +34878,7 @@ async function substituteDir(dir, vars) {
|
|
|
34491
34878
|
for (const rel2 of walkTextFiles(dir)) {
|
|
34492
34879
|
const ext = extname3(rel2).toLowerCase();
|
|
34493
34880
|
if (!SUBSTITUTABLE_EXTS.has(ext)) continue;
|
|
34494
|
-
const abs =
|
|
34881
|
+
const abs = join6(dir, rel2);
|
|
34495
34882
|
let content;
|
|
34496
34883
|
try {
|
|
34497
34884
|
content = await fs.readFile(abs, "utf-8");
|
|
@@ -34531,7 +34918,9 @@ async function finalizeLocalProject(opts) {
|
|
|
34531
34918
|
try {
|
|
34532
34919
|
const sub = await substituteDir(opts.dir, {
|
|
34533
34920
|
projectGuid: opts.projectGuid,
|
|
34534
|
-
projectName: opts.projectName
|
|
34921
|
+
projectName: opts.projectName,
|
|
34922
|
+
accountSlug: opts.accountSlug,
|
|
34923
|
+
projectSlug: opts.projectSlug
|
|
34535
34924
|
});
|
|
34536
34925
|
if (sub.changed.length) {
|
|
34537
34926
|
console.log(muted(`Resolved template vars in ${sub.changed.length} file${sub.changed.length > 1 ? "s" : ""}.`));
|
|
@@ -34557,18 +34946,18 @@ async function finalizeLocalProject(opts) {
|
|
|
34557
34946
|
}
|
|
34558
34947
|
|
|
34559
34948
|
// src/relay/paths.ts
|
|
34560
|
-
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as
|
|
34949
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
|
|
34561
34950
|
import { homedir as homedir4 } from "os";
|
|
34562
|
-
import { join as
|
|
34951
|
+
import { join as join7, resolve as resolve5 } from "path";
|
|
34563
34952
|
function getProjectsRoot() {
|
|
34564
|
-
const settingsPath =
|
|
34565
|
-
const defaultDir =
|
|
34953
|
+
const settingsPath = join7(homedir4(), ".gipity", "settings.json");
|
|
34954
|
+
const defaultDir = join7(homedir4(), "GipityProjects");
|
|
34566
34955
|
try {
|
|
34567
34956
|
if (existsSync6(settingsPath)) {
|
|
34568
|
-
const settings = JSON.parse(
|
|
34957
|
+
const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
|
|
34569
34958
|
if (settings.projectsDir) return resolve5(settings.projectsDir);
|
|
34570
34959
|
} else {
|
|
34571
|
-
mkdirSync5(
|
|
34960
|
+
mkdirSync5(join7(homedir4(), ".gipity"), { recursive: true });
|
|
34572
34961
|
writeFileSync6(settingsPath, JSON.stringify({ projectsDir: defaultDir }, null, 2) + "\n");
|
|
34573
34962
|
}
|
|
34574
34963
|
} catch {
|
|
@@ -34577,11 +34966,11 @@ function getProjectsRoot() {
|
|
|
34577
34966
|
}
|
|
34578
34967
|
|
|
34579
34968
|
// src/relay/state.ts
|
|
34580
|
-
import { readFileSync as
|
|
34581
|
-
import { join as
|
|
34969
|
+
import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
|
|
34970
|
+
import { join as join8 } from "path";
|
|
34582
34971
|
import { homedir as homedir5 } from "os";
|
|
34583
|
-
var RELAY_DIR = process.env.GIPITY_DIR ||
|
|
34584
|
-
var RELAY_FILE =
|
|
34972
|
+
var RELAY_DIR = process.env.GIPITY_DIR || join8(homedir5(), ".gipity");
|
|
34973
|
+
var RELAY_FILE = join8(RELAY_DIR, "relay.json");
|
|
34585
34974
|
var FILE_MODE = 384;
|
|
34586
34975
|
function emptyState() {
|
|
34587
34976
|
return { device: null, paused: false };
|
|
@@ -34589,7 +34978,7 @@ function emptyState() {
|
|
|
34589
34978
|
function loadState() {
|
|
34590
34979
|
if (!existsSync7(RELAY_FILE)) return emptyState();
|
|
34591
34980
|
try {
|
|
34592
|
-
const raw = JSON.parse(
|
|
34981
|
+
const raw = JSON.parse(readFileSync10(RELAY_FILE, "utf-8"));
|
|
34593
34982
|
return {
|
|
34594
34983
|
device: raw.device ?? null,
|
|
34595
34984
|
paused: Boolean(raw.paused),
|
|
@@ -34674,7 +35063,7 @@ function diagnosticsConsented() {
|
|
|
34674
35063
|
if (env && env !== "0" && env.toLowerCase() !== "false") return false;
|
|
34675
35064
|
return loadState().diagnostics_consent !== false;
|
|
34676
35065
|
}
|
|
34677
|
-
var RELAY_PID_FILE =
|
|
35066
|
+
var RELAY_PID_FILE = join8(RELAY_DIR, "relay.pid");
|
|
34678
35067
|
function getDaemonPidPath() {
|
|
34679
35068
|
return RELAY_PID_FILE;
|
|
34680
35069
|
}
|
|
@@ -34691,7 +35080,7 @@ function clearDaemonPid() {
|
|
|
34691
35080
|
function isDaemonRunning() {
|
|
34692
35081
|
if (!existsSync7(RELAY_PID_FILE)) return false;
|
|
34693
35082
|
try {
|
|
34694
|
-
const raw =
|
|
35083
|
+
const raw = readFileSync10(RELAY_PID_FILE, "utf-8").trim();
|
|
34695
35084
|
const pid = parseInt(raw, 10);
|
|
34696
35085
|
if (!pid || isNaN(pid)) {
|
|
34697
35086
|
try {
|
|
@@ -34731,17 +35120,17 @@ function scanForAdoption(cwd) {
|
|
|
34731
35120
|
if (truncated) return;
|
|
34732
35121
|
let entries;
|
|
34733
35122
|
try {
|
|
34734
|
-
entries =
|
|
35123
|
+
entries = readdirSync6(dir);
|
|
34735
35124
|
} catch {
|
|
34736
35125
|
return;
|
|
34737
35126
|
}
|
|
34738
35127
|
for (const name of entries) {
|
|
34739
35128
|
if (truncated) return;
|
|
34740
35129
|
if (isSyncIgnored(name)) continue;
|
|
34741
|
-
const full =
|
|
35130
|
+
const full = join9(dir, name);
|
|
34742
35131
|
let st2;
|
|
34743
35132
|
try {
|
|
34744
|
-
st2 =
|
|
35133
|
+
st2 = statSync4(full);
|
|
34745
35134
|
} catch {
|
|
34746
35135
|
continue;
|
|
34747
35136
|
}
|
|
@@ -34767,7 +35156,7 @@ function scanForAdoption(cwd) {
|
|
|
34767
35156
|
function isLikelyEmpty(cwd) {
|
|
34768
35157
|
let entries;
|
|
34769
35158
|
try {
|
|
34770
|
-
entries =
|
|
35159
|
+
entries = readdirSync6(cwd);
|
|
34771
35160
|
} catch {
|
|
34772
35161
|
return true;
|
|
34773
35162
|
}
|
|
@@ -34805,18 +35194,18 @@ function canAdoptCwd(cwd) {
|
|
|
34805
35194
|
function countGitRepoChildren(dir) {
|
|
34806
35195
|
let entries;
|
|
34807
35196
|
try {
|
|
34808
|
-
entries =
|
|
35197
|
+
entries = readdirSync6(dir);
|
|
34809
35198
|
} catch {
|
|
34810
35199
|
return 0;
|
|
34811
35200
|
}
|
|
34812
35201
|
let count = 0;
|
|
34813
35202
|
for (const name of entries.slice(0, 50)) {
|
|
34814
35203
|
if (name.startsWith(".")) continue;
|
|
34815
|
-
const child =
|
|
35204
|
+
const child = join9(dir, name);
|
|
34816
35205
|
try {
|
|
34817
|
-
if (!
|
|
34818
|
-
const gitPath =
|
|
34819
|
-
if (
|
|
35206
|
+
if (!statSync4(child).isDirectory()) continue;
|
|
35207
|
+
const gitPath = join9(child, ".git");
|
|
35208
|
+
if (statSync4(gitPath)) count++;
|
|
34820
35209
|
} catch {
|
|
34821
35210
|
}
|
|
34822
35211
|
if (count >= 3) return count;
|
|
@@ -34830,7 +35219,7 @@ function formatCwdLabel(cwd) {
|
|
|
34830
35219
|
if (norm.startsWith(home + sep2)) {
|
|
34831
35220
|
const tail = norm.slice(home.length + 1).split(sep2);
|
|
34832
35221
|
if (tail.length <= 2) return "~/" + tail.join("/");
|
|
34833
|
-
return "
|
|
35222
|
+
return "~/.../" + tail.slice(-2).join("/");
|
|
34834
35223
|
}
|
|
34835
35224
|
const parts = norm.split(sep2).filter(Boolean);
|
|
34836
35225
|
if (parts.length <= 1) return norm;
|
|
@@ -34917,7 +35306,7 @@ function resolveTools(forFlag) {
|
|
|
34917
35306
|
(t) => requested.includes(t.key) || !t.optIn && requested.includes("all")
|
|
34918
35307
|
);
|
|
34919
35308
|
}
|
|
34920
|
-
var initCommand = new Command("init").description("Link this directory to a project").addHelpText("after", "\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity, and installs the Gipity skills + file-sync hooks into the agent CLIs found on this machine (Claude Code, Codex, Grok).").argument("[name]", "Project name/slug (defaults to current directory name)").option("--agent <guid>", "Agent GUID to use").option("--no-capture", "Don't record Claude Code sessions in this directory to your Gipity project (sets captureHooks: false in .gipity.json)").option(
|
|
35309
|
+
var initCommand = new Command("init").description("Link this directory to a project").addHelpText("after", "\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity, and installs the Gipity skills + file-sync hooks into the agent CLIs found on this machine (Claude Code, Codex, Grok, Antigravity).").argument("[name]", "Project name/slug (defaults to current directory name)").option("--agent <guid>", "Agent GUID to use").option("--no-capture", "Don't record Claude Code sessions in this directory to your Gipity project (sets captureHooks: false in .gipity.json)").option(
|
|
34921
35310
|
"--for <tools>",
|
|
34922
35311
|
`Which AI tool primer files to write (comma-separated). Default: all except aider (opt-in - it also writes .aider.conf.yml). Choices: ${TOOL_KEYS.join(", ")}, all`
|
|
34923
35312
|
).addHelpText("after", `
|
|
@@ -34995,7 +35384,7 @@ Working with an existing Gipity project:
|
|
|
34995
35384
|
process.exit(1);
|
|
34996
35385
|
}
|
|
34997
35386
|
}
|
|
34998
|
-
const projectName = (name ||
|
|
35387
|
+
const projectName = (name || basename2(cwd)).slice(0, 100);
|
|
34999
35388
|
const projectSlug = slugify(projectName);
|
|
35000
35389
|
if (!projectSlug) {
|
|
35001
35390
|
console.error(error("Could not derive a valid project slug. Provide a name: gipity init my-app"));
|
|
@@ -35037,7 +35426,7 @@ Working with an existing Gipity project:
|
|
|
35037
35426
|
if (adopted.applied > 0) console.log(`Synced ${adopted.applied} change${adopted.applied > 1 ? "s" : ""} with Gipity.`);
|
|
35038
35427
|
if (opts.capture === false) {
|
|
35039
35428
|
try {
|
|
35040
|
-
const cfg = JSON.parse(
|
|
35429
|
+
const cfg = JSON.parse(readFileSync11(resolve7(cwd, ".gipity.json"), "utf-8"));
|
|
35041
35430
|
cfg.captureHooks = false;
|
|
35042
35431
|
saveConfigAt(cwd, cfg);
|
|
35043
35432
|
} catch {
|
|
@@ -35045,7 +35434,7 @@ Working with an existing Gipity project:
|
|
|
35045
35434
|
}
|
|
35046
35435
|
console.log(success(`Wrote primer files: ${primerSummary}.`));
|
|
35047
35436
|
if (wantsClaude) {
|
|
35048
|
-
console.log(success("Ready! Run your coding agent here (claude, codex, grok), or `gipity build` to launch one with a picker."));
|
|
35437
|
+
console.log(success("Ready! Run your coding agent here (claude, codex, grok, agy), or `gipity build` to launch one with a picker."));
|
|
35049
35438
|
if (opts.capture === false) {
|
|
35050
35439
|
console.log(muted("Session recording is off for this project (captureHooks: false in .gipity.json)."));
|
|
35051
35440
|
} else {
|
|
@@ -35071,15 +35460,15 @@ init_auth();
|
|
|
35071
35460
|
init_api();
|
|
35072
35461
|
init_config();
|
|
35073
35462
|
init_colors();
|
|
35074
|
-
import { existsSync as existsSync9, readFileSync as
|
|
35075
|
-
import { join as
|
|
35463
|
+
import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
|
|
35464
|
+
import { join as join10, resolve as resolve8 } from "path";
|
|
35076
35465
|
import { homedir as homedir7 } from "os";
|
|
35077
35466
|
function checkGipityPlugin() {
|
|
35078
|
-
const path5 =
|
|
35467
|
+
const path5 = join10(homedir7(), ".claude", "settings.json");
|
|
35079
35468
|
let settings = {};
|
|
35080
35469
|
if (existsSync9(path5)) {
|
|
35081
35470
|
try {
|
|
35082
|
-
settings = JSON.parse(
|
|
35471
|
+
settings = JSON.parse(readFileSync12(path5, "utf-8"));
|
|
35083
35472
|
} catch {
|
|
35084
35473
|
}
|
|
35085
35474
|
}
|
|
@@ -35092,12 +35481,14 @@ function checkGipityPlugin() {
|
|
|
35092
35481
|
return { missing, ok: missing.length === 0, stale };
|
|
35093
35482
|
}
|
|
35094
35483
|
async function probeAuth(loggedIn) {
|
|
35095
|
-
if (!loggedIn && !usingEnvToken()) return "none";
|
|
35096
|
-
if (!usingEnvToken() && sessionExpired()) return "expired";
|
|
35097
|
-
const timeout = new Promise(
|
|
35484
|
+
if (!loggedIn && !usingEnvToken()) return { state: "none", account: null };
|
|
35485
|
+
if (!usingEnvToken() && sessionExpired()) return { state: "expired", account: null };
|
|
35486
|
+
const timeout = new Promise(
|
|
35487
|
+
(res) => setTimeout(() => res({ state: "unreachable", account: null }), 5e3).unref?.()
|
|
35488
|
+
);
|
|
35098
35489
|
const call = get("/users/me").then(
|
|
35099
|
-
() => "ok",
|
|
35100
|
-
(err) => err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable"
|
|
35490
|
+
(res) => ({ state: "ok", account: res.data?.accountSlug ?? null }),
|
|
35491
|
+
(err) => ({ state: err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable", account: null })
|
|
35101
35492
|
);
|
|
35102
35493
|
return Promise.race([call, timeout]);
|
|
35103
35494
|
}
|
|
@@ -35109,6 +35500,8 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35109
35500
|
const hookCheck = config ? checkGipityPlugin() : null;
|
|
35110
35501
|
const queueDelivered = auth && !sessionExpired() ? await flushBugQueue().catch(() => 0) : 0;
|
|
35111
35502
|
const probe = await probeAuth(!!auth);
|
|
35503
|
+
const accountMismatch = probe.state === "ok" && !!config && !!probe.account && probe.account !== config.accountSlug;
|
|
35504
|
+
const apiBaseInUse = resolveApiBase();
|
|
35112
35505
|
if (opts.json) {
|
|
35113
35506
|
console.log(JSON.stringify({
|
|
35114
35507
|
project: config ? {
|
|
@@ -35116,17 +35509,21 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35116
35509
|
slug: config.projectSlug,
|
|
35117
35510
|
account: config.accountSlug,
|
|
35118
35511
|
apiBase: config.apiBase,
|
|
35512
|
+
apiBaseInUse,
|
|
35119
35513
|
url: liveUrl(config)
|
|
35120
35514
|
} : null,
|
|
35121
35515
|
// `valid` reflects the refresh token (the real session) - access
|
|
35122
35516
|
// tokens auto-renew, so their expiry must not read as "invalid".
|
|
35123
35517
|
// `probe` is what one live call just proved: 'rejected' means every
|
|
35124
35518
|
// authenticated command will fail even though `valid` reads true.
|
|
35519
|
+
// 'mismatch' overrides 'ok' when the live account isn't the one that
|
|
35520
|
+
// owns this project - `valid` still reads true (the token IS valid).
|
|
35125
35521
|
auth: auth || usingEnvToken() ? {
|
|
35126
35522
|
email: auth?.email,
|
|
35523
|
+
account: probe.account,
|
|
35127
35524
|
source: usingEnvToken() ? "agent-token" : "session",
|
|
35128
|
-
valid: usingEnvToken() ? probe !== "rejected" : !sessionExpired(),
|
|
35129
|
-
probe
|
|
35525
|
+
valid: usingEnvToken() ? probe.state !== "rejected" : !sessionExpired(),
|
|
35526
|
+
probe: accountMismatch ? "mismatch" : probe.state
|
|
35130
35527
|
} : null,
|
|
35131
35528
|
plugin: hookCheck
|
|
35132
35529
|
}, null, 2));
|
|
@@ -35139,18 +35536,25 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35139
35536
|
console.log(`${muted("Account:")} ${config.accountSlug}`);
|
|
35140
35537
|
console.log(`${muted("Live:")} ${liveUrl(config)}`);
|
|
35141
35538
|
console.log(`${muted("API:")} ${config.apiBase}`);
|
|
35539
|
+
if (apiBaseInUse !== config.apiBase) {
|
|
35540
|
+
console.log(`${muted("API (in use):")} ${warning(apiBaseInUse)} ${muted("(overrides .gipity.json \u2014 GIPITY_API_BASE / --api-base / allowlist)")}`);
|
|
35541
|
+
}
|
|
35142
35542
|
if (config.agentGuid) console.log(`${muted("Agent:")} ${config.agentGuid}`);
|
|
35143
35543
|
}
|
|
35144
35544
|
if (usingEnvToken()) {
|
|
35145
|
-
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)")}` : ""}`);
|
|
35545
|
+
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)")}` : ""}`);
|
|
35146
35546
|
} else if (!auth) {
|
|
35147
35547
|
console.log(`${muted("Auth:")} ${warning("not logged in. Run: gipity login")}`);
|
|
35148
|
-
} else if (probe === "expired") {
|
|
35548
|
+
} else if (probe.state === "expired") {
|
|
35149
35549
|
console.log(`${muted("Auth:")} ${warning(`session expired for ${auth.email}. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
|
|
35150
|
-
} else if (probe === "rejected") {
|
|
35550
|
+
} else if (probe.state === "rejected") {
|
|
35151
35551
|
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)`)}`);
|
|
35152
35552
|
} else {
|
|
35153
|
-
console.log(`${muted("Auth:")} ${success(auth.email)}${probe === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35553
|
+
console.log(`${muted("Auth:")} ${success(auth.email)}${probe.state === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
|
|
35554
|
+
}
|
|
35555
|
+
if (accountMismatch) {
|
|
35556
|
+
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`)}`);
|
|
35557
|
+
console.log(muted("(If this project was shared with you via gipity rbac, this is expected.)"));
|
|
35154
35558
|
}
|
|
35155
35559
|
if (queueDelivered > 0) {
|
|
35156
35560
|
console.log(`${muted("Bug queue:")} ${success(`delivered ${queueDelivered} queued bug report${queueDelivered === 1 ? "" : "s"}`)}`);
|
|
@@ -35181,7 +35585,10 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
|
|
|
35181
35585
|
});
|
|
35182
35586
|
|
|
35183
35587
|
// src/commands/sync.ts
|
|
35588
|
+
import { dirname as dirname6 } from "node:path";
|
|
35184
35589
|
init_colors();
|
|
35590
|
+
init_utils();
|
|
35591
|
+
init_config();
|
|
35185
35592
|
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) => {
|
|
35186
35593
|
try {
|
|
35187
35594
|
const progress = opts.json ? void 0 : createProgressReporter();
|
|
@@ -35194,6 +35601,16 @@ var syncCommand = new Command("sync").description("Sync files").addHelpText("aft
|
|
|
35194
35601
|
console.log(`
|
|
35195
35602
|
${result.applied} action${result.applied > 1 ? "s" : ""} applied.`);
|
|
35196
35603
|
}
|
|
35604
|
+
if (isWsl()) {
|
|
35605
|
+
const configPath = getConfigPath();
|
|
35606
|
+
const twin = configPath ? findWindowsTwinProject(dirname6(configPath)) : null;
|
|
35607
|
+
if (twin) {
|
|
35608
|
+
console.log(muted(
|
|
35609
|
+
`
|
|
35610
|
+
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.`
|
|
35611
|
+
));
|
|
35612
|
+
}
|
|
35613
|
+
}
|
|
35197
35614
|
if (result.deferredDeletes > 0) {
|
|
35198
35615
|
console.log(muted(
|
|
35199
35616
|
`
|
|
@@ -35241,8 +35658,8 @@ var pushCommand = new Command("push").description("Push one or more files").argu
|
|
|
35241
35658
|
// src/commands/upload.ts
|
|
35242
35659
|
init_api();
|
|
35243
35660
|
init_config();
|
|
35244
|
-
import { statSync as
|
|
35245
|
-
import { basename as
|
|
35661
|
+
import { statSync as statSync5 } from "fs";
|
|
35662
|
+
import { basename as basename3 } from "path";
|
|
35246
35663
|
init_utils();
|
|
35247
35664
|
init_colors();
|
|
35248
35665
|
var uploadCommand = new Command("upload").description(`Upload a local file and print a durable, worker-reachable URL for it.
|
|
@@ -35259,14 +35676,14 @@ token-signed serve url instead (reachable only by holders of the url).`).argumen
|
|
|
35259
35676
|
const { config } = await resolveProjectContext();
|
|
35260
35677
|
let size;
|
|
35261
35678
|
try {
|
|
35262
|
-
const st2 =
|
|
35679
|
+
const st2 = statSync5(file);
|
|
35263
35680
|
if (!st2.isFile()) throw new Error("not a regular file");
|
|
35264
35681
|
size = st2.size;
|
|
35265
35682
|
} catch (err) {
|
|
35266
35683
|
throw new Error(`can't read ${file}: ${err.message}`);
|
|
35267
35684
|
}
|
|
35268
35685
|
if (size === 0) throw new Error(`${file} is empty - nothing to upload.`);
|
|
35269
|
-
const filename =
|
|
35686
|
+
const filename = basename3(file);
|
|
35270
35687
|
const contentType = opts.contentType || guessMime(file);
|
|
35271
35688
|
const doUpload = async () => {
|
|
35272
35689
|
const init = await post(`/api/${config.projectGuid}/uploads/init`, {
|
|
@@ -35283,7 +35700,7 @@ token-signed serve url instead (reachable only by holders of the url).`).argumen
|
|
|
35283
35700
|
const comp = await post(`/api/${config.projectGuid}/uploads/complete`, completeBody);
|
|
35284
35701
|
return comp.data;
|
|
35285
35702
|
};
|
|
35286
|
-
const data = opts.json ? await doUpload() : await withSpinner(`Uploading ${filename} (${formatSize(size)})
|
|
35703
|
+
const data = opts.json ? await doUpload() : await withSpinner(`Uploading ${filename} (${formatSize(size)})...`, doUpload, { done: null });
|
|
35287
35704
|
if (opts.json) {
|
|
35288
35705
|
console.log(JSON.stringify(data));
|
|
35289
35706
|
return;
|
|
@@ -35307,7 +35724,7 @@ init_colors();
|
|
|
35307
35724
|
init_auth();
|
|
35308
35725
|
|
|
35309
35726
|
// src/commands/page-eval.ts
|
|
35310
|
-
import { readFileSync as
|
|
35727
|
+
import { readFileSync as readFileSync14 } from "node:fs";
|
|
35311
35728
|
init_api();
|
|
35312
35729
|
init_colors();
|
|
35313
35730
|
init_auth();
|
|
@@ -35316,8 +35733,8 @@ init_config();
|
|
|
35316
35733
|
// src/page-fixtures.ts
|
|
35317
35734
|
init_api();
|
|
35318
35735
|
init_config();
|
|
35319
|
-
import { readFileSync as
|
|
35320
|
-
import { basename as
|
|
35736
|
+
import { readFileSync as readFileSync13, statSync as statSync6, existsSync as existsSync10, readdirSync as readdirSync7 } from "node:fs";
|
|
35737
|
+
import { basename as basename4, resolve as resolve10, relative as relative3, join as join11 } from "node:path";
|
|
35321
35738
|
var FIND_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".gipity", "dist", "build", ".next", "coverage"]);
|
|
35322
35739
|
function findByBasename(root, name, limit = 3) {
|
|
35323
35740
|
const hits = [];
|
|
@@ -35328,15 +35745,15 @@ function findByBasename(root, name, limit = 3) {
|
|
|
35328
35745
|
visited++;
|
|
35329
35746
|
let entries;
|
|
35330
35747
|
try {
|
|
35331
|
-
entries =
|
|
35748
|
+
entries = readdirSync7(dir, { withFileTypes: true });
|
|
35332
35749
|
} catch {
|
|
35333
35750
|
continue;
|
|
35334
35751
|
}
|
|
35335
35752
|
for (const e of entries) {
|
|
35336
35753
|
if (e.isDirectory()) {
|
|
35337
|
-
if (!FIND_SKIP.has(e.name)) queue.push(
|
|
35754
|
+
if (!FIND_SKIP.has(e.name)) queue.push(join11(dir, e.name));
|
|
35338
35755
|
} else if (e.name === name && hits.length < limit) {
|
|
35339
|
-
hits.push(
|
|
35756
|
+
hits.push(join11(dir, e.name));
|
|
35340
35757
|
}
|
|
35341
35758
|
}
|
|
35342
35759
|
}
|
|
@@ -35346,22 +35763,22 @@ function assertLocalAsset(flag, localPath) {
|
|
|
35346
35763
|
const abs = resolve10(localPath);
|
|
35347
35764
|
if (existsSync10(abs)) return;
|
|
35348
35765
|
const root = getProjectRoot() ?? process.cwd();
|
|
35349
|
-
const elsewhere = findByBasename(root,
|
|
35766
|
+
const elsewhere = findByBasename(root, basename4(localPath)).filter((p) => p !== abs);
|
|
35350
35767
|
const found = elsewhere.length ? `
|
|
35351
35768
|
That file DOES exist here \u2014 pass this path instead:
|
|
35352
35769
|
${elsewhere.map((p) => ` ${flag} ${relative3(process.cwd(), p) || p}`).join("\n")}` : `
|
|
35353
|
-
Nothing named "${
|
|
35770
|
+
Nothing named "${basename4(localPath)}" under ${root} either. Generate a frame with \`gipity generate image "<description>" -o ${localPath}\`, or check the path.`;
|
|
35354
35771
|
throw new Error(
|
|
35355
35772
|
`${flag} ${localPath}: no such file \u2014 looked for ${abs} (relative paths resolve against the current directory, ${process.cwd()}).${found}`
|
|
35356
35773
|
);
|
|
35357
35774
|
}
|
|
35358
|
-
async function uploadPublicFixture(
|
|
35775
|
+
async function uploadPublicFixture(projectGuid2, localPath, flag = "--fixture") {
|
|
35359
35776
|
assertLocalAsset(flag, localPath);
|
|
35360
|
-
const name =
|
|
35361
|
-
const size =
|
|
35777
|
+
const name = basename4(localPath);
|
|
35778
|
+
const size = statSync6(localPath).size;
|
|
35362
35779
|
const contentType = guessMime(localPath);
|
|
35363
35780
|
const init = await post(
|
|
35364
|
-
`/api/${
|
|
35781
|
+
`/api/${projectGuid2}/uploads/init`,
|
|
35365
35782
|
{ filename: name, content_type: contentType, size, public: true }
|
|
35366
35783
|
);
|
|
35367
35784
|
if (init.data.method !== "PUT" || !init.data.url) {
|
|
@@ -35370,19 +35787,19 @@ async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
|
|
|
35370
35787
|
const res = await fetch(init.data.url, {
|
|
35371
35788
|
method: "PUT",
|
|
35372
35789
|
headers: { "Content-Type": contentType },
|
|
35373
|
-
body:
|
|
35790
|
+
body: readFileSync13(localPath)
|
|
35374
35791
|
});
|
|
35375
35792
|
if (!res.ok) {
|
|
35376
35793
|
throw new Error(`upload of fixture "${name}" failed: ${res.status} ${res.statusText}`);
|
|
35377
35794
|
}
|
|
35378
35795
|
const done = await post(
|
|
35379
|
-
`/api/${
|
|
35796
|
+
`/api/${projectGuid2}/uploads/complete`,
|
|
35380
35797
|
{ upload_guid: init.data.upload_guid }
|
|
35381
35798
|
);
|
|
35382
35799
|
return { guid: done.data.guid, url: done.data.url, name, localPath };
|
|
35383
35800
|
}
|
|
35384
|
-
async function deleteFixture(
|
|
35385
|
-
await del(`/api/${
|
|
35801
|
+
async function deleteFixture(projectGuid2, guid) {
|
|
35802
|
+
await del(`/api/${projectGuid2}/uploads/${guid}`);
|
|
35386
35803
|
}
|
|
35387
35804
|
var CAMERA_EXTS = [".png", ".jpg", ".jpeg", ".webp", ".mp4", ".webm", ".y4m", ".mjpeg"];
|
|
35388
35805
|
function assertCameraFile(localPath) {
|
|
@@ -35390,14 +35807,14 @@ function assertCameraFile(localPath) {
|
|
|
35390
35807
|
const ext = localPath.slice(localPath.lastIndexOf(".")).toLowerCase();
|
|
35391
35808
|
if (CAMERA_EXTS.includes(ext)) return;
|
|
35392
35809
|
throw new Error(
|
|
35393
|
-
`--camera ${
|
|
35810
|
+
`--camera ${basename4(localPath)}: unsupported file type "${ext || "(none)"}" \u2014 the camera feed must be an image or video (${CAMERA_EXTS.join(", ")}).
|
|
35394
35811
|
A still image is played as a looping single-frame feed, which is what a gesture/pose/object model needs.
|
|
35395
35812
|
No frame to hand? Generate one: gipity generate image "a hand making a closed fist, palm to camera, plain background"`
|
|
35396
35813
|
);
|
|
35397
35814
|
}
|
|
35398
|
-
async function uploadCameraFeed(
|
|
35815
|
+
async function uploadCameraFeed(projectGuid2, localPath) {
|
|
35399
35816
|
assertCameraFile(localPath);
|
|
35400
|
-
return uploadPublicFixture(
|
|
35817
|
+
return uploadPublicFixture(projectGuid2, localPath, "--camera");
|
|
35401
35818
|
}
|
|
35402
35819
|
|
|
35403
35820
|
// src/commands/page-eval.ts
|
|
@@ -35443,7 +35860,7 @@ function summarizeExpr(expr) {
|
|
|
35443
35860
|
const oneLine = meaningful.length <= 1;
|
|
35444
35861
|
if (oneLine && expr.trim().length <= EXPR_ECHO_MAX_CHARS) return expr.trim();
|
|
35445
35862
|
const first = (meaningful[0] ?? "").trim();
|
|
35446
|
-
const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS -
|
|
35863
|
+
const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS - 3)}...` : first;
|
|
35447
35864
|
const shape = oneLine ? `(${expr.trim().length} chars)` : `(+${meaningful.length - 1} more ${meaningful.length - 1 === 1 ? "line" : "lines"}, ${expr.trim().length} chars)`;
|
|
35448
35865
|
return `${head} ${shape}`;
|
|
35449
35866
|
}
|
|
@@ -35470,7 +35887,7 @@ function capWaitForTimeoutMs(raw) {
|
|
|
35470
35887
|
return WAIT_FOR_MAX_MS;
|
|
35471
35888
|
}
|
|
35472
35889
|
function readScriptFile(pathArg) {
|
|
35473
|
-
return
|
|
35890
|
+
return readFileSync14(pathArg === "-" ? 0 : pathArg, "utf8");
|
|
35474
35891
|
}
|
|
35475
35892
|
function parsesAsExpression(s) {
|
|
35476
35893
|
try {
|
|
@@ -35520,7 +35937,15 @@ function budgetOverrunHint(reason, usedBudgetMs) {
|
|
|
35520
35937
|
}
|
|
35521
35938
|
return `That budget was ${Math.round(usedBudgetMs / 1e3)}s. Raise it with --timeout <ms> (up to ${EVAL_SCRIPT_BUDGET_MAX_MS}), e.g. --timeout ${EVAL_SCRIPT_BUDGET_MAX_MS} \u2014 or gate on a ready signal instead: --wait-for '<selector>' --wait-timeout ${MAX_WAIT_MS}.`;
|
|
35522
35939
|
}
|
|
35940
|
+
function navigationAbortHint(reason) {
|
|
35941
|
+
if (!/navigated or reloaded/i.test(reason)) return null;
|
|
35942
|
+
return `If you were verifying that state SURVIVES a reload, that is what --reload is for (one command, one page): gipity page eval "<url>" '<seed/assert expr>' --reload '<assert-restored expr>'. It reloads the page in place (localStorage/sessionStorage/cookies preserved) and reports the second result separately. Splitting into two 'page eval' calls does NOT do this: each call is its own browser session, so the second one starts from empty storage and never exercises the restore path.`;
|
|
35943
|
+
}
|
|
35944
|
+
function stepsDeterministically(script) {
|
|
35945
|
+
return /\badvance\s*\(/.test(script);
|
|
35946
|
+
}
|
|
35523
35947
|
function slowRenderMessage(fps, o) {
|
|
35948
|
+
if (!o.camera && stepsDeterministically(o.script ?? "")) return null;
|
|
35524
35949
|
if (o.camera) {
|
|
35525
35950
|
const frames = Math.max(1, Math.round(fps * (o.waitMs / 1e3)));
|
|
35526
35951
|
return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps, so the app's vision pipeline ran on roughly ${bold(`${frames} frame${frames === 1 ? "" : "s"}`)} during the ${Math.round(o.waitMs / 1e3)}s before this eval (it infers once per painted frame). ${bold("That is enough:")} --camera loops your still image, so every one of those frames is the SAME pixels and the model returns the SAME answer on each \u2014 re-running with a bigger --wait/--timeout cannot change the result. ${bold("Do not escalate the wait.")} If a detection landed, it is real. If nothing was detected, the suspect is the ${bold("frame")} (a model needs the whole subject in shot \u2014 a tight crop, an odd angle or a busy background reads as nothing) or the ${bold("app")} (frames never reach the model) \u2014 never the frame rate. Settle it in ONE run: ${CAMERA_FRAME_CHECK}`;
|
|
@@ -35566,7 +35991,7 @@ function evalExecTimeoutMessage(result, budgetMs) {
|
|
|
35566
35991
|
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.`;
|
|
35567
35992
|
}
|
|
35568
35993
|
var JS_DECOY_FLAGS = ["--js", "--javascript", "--script", "--code", "--expr", "--eval", "--exec", "--action"];
|
|
35569
|
-
var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script). ONE client per call - to verify realtime/presence across concurrent clients use `page test --observe` instead").argument("<url>", "URL to load").argument("[expr]", `JavaScript to evaluate in page context (inline expression or statement body; await works, and the trailing expression is returned automatically \u2014 REPL-style \u2014 so no explicit return is needed; result is JSON-serialized). Omit when using --file. Time budget: the body has ${EVAL_SCRIPT_BUDGET_MS / 1e3}s to finish after page load (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera) - raise it with --timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s.`).option("--file <path>", `Read the script body from a file instead of the inline <expr> arg (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF'
|
|
35994
|
+
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(
|
|
35570
35995
|
"--step <expr>",
|
|
35571
35996
|
`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.`,
|
|
35572
35997
|
(val, prev) => [...prev, val],
|
|
@@ -35578,7 +36003,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35578
36003
|
[]
|
|
35579
36004
|
).option(
|
|
35580
36005
|
"--reload <expr>",
|
|
35581
|
-
|
|
36006
|
+
'After the first eval, reload the page IN PLACE (localStorage/sessionStorage/cookies preserved) and evaluate this second expression against the post-reload DOM. One command verifies persisted state survives a reload ("remember it when I come back"): seed/assert state with <expr>, then assert the restored UI here. This is the ONLY way to check that - reloading inside the body aborts the eval (the result is lost with the old page), and two separate `page eval` calls are two fresh browser profiles, so the second starts from empty storage.'
|
|
35582
36007
|
).option("--reload-file <path>", "Read the post-reload expression from a file instead of inline --reload (mutually exclusive)").option(
|
|
35583
36008
|
"--camera <path>",
|
|
35584
36009
|
`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".`
|
|
@@ -35610,7 +36035,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35610
36035
|
try {
|
|
35611
36036
|
expr = readScriptFile(opts.file);
|
|
35612
36037
|
} catch {
|
|
35613
|
-
pageEvalCommand.error(opts.file === "-" ? `error: --file - reads the script from stdin, but stdin was empty/unreadable
|
|
36038
|
+
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}`);
|
|
35614
36039
|
}
|
|
35615
36040
|
}
|
|
35616
36041
|
if (opts.reload !== void 0 && opts.reloadFile) {
|
|
@@ -35662,23 +36087,23 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35662
36087
|
const fixturePaths = opts.fixture ?? [];
|
|
35663
36088
|
const hosted = [];
|
|
35664
36089
|
let camera;
|
|
35665
|
-
let
|
|
36090
|
+
let projectGuid2;
|
|
35666
36091
|
let sentExpr = expr;
|
|
35667
36092
|
let sentSteps = steps;
|
|
35668
36093
|
if (opts.camera) assertCameraFile(opts.camera);
|
|
35669
36094
|
try {
|
|
35670
36095
|
if (fixturePaths.length || opts.camera) {
|
|
35671
36096
|
const { config } = await resolveProjectContext({});
|
|
35672
|
-
|
|
36097
|
+
projectGuid2 = config.projectGuid;
|
|
35673
36098
|
}
|
|
35674
36099
|
if (opts.camera) {
|
|
35675
|
-
console.log(muted(`Hosting camera feed ${opts.camera}
|
|
35676
|
-
camera = await uploadCameraFeed(
|
|
36100
|
+
console.log(muted(`Hosting camera feed ${opts.camera}...`));
|
|
36101
|
+
camera = await uploadCameraFeed(projectGuid2, opts.camera);
|
|
35677
36102
|
}
|
|
35678
36103
|
if (fixturePaths.length) {
|
|
35679
36104
|
for (const p of fixturePaths) {
|
|
35680
|
-
console.log(muted(`Hosting fixture ${p}
|
|
35681
|
-
hosted.push(await uploadPublicFixture(
|
|
36105
|
+
console.log(muted(`Hosting fixture ${p}...`));
|
|
36106
|
+
hosted.push(await uploadPublicFixture(projectGuid2, p));
|
|
35682
36107
|
}
|
|
35683
36108
|
const map = {};
|
|
35684
36109
|
for (const h of hosted) map[h.name] = h.url;
|
|
@@ -35716,7 +36141,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
|
|
|
35716
36141
|
}));
|
|
35717
36142
|
} catch (err) {
|
|
35718
36143
|
const msg = err?.message ?? "";
|
|
35719
|
-
const hint = budgetOverrunHint(msg, scriptBudgetMs);
|
|
36144
|
+
const hint = budgetOverrunHint(msg, scriptBudgetMs) ?? (reloadExpr === void 0 ? navigationAbortHint(msg) : null);
|
|
35720
36145
|
if (!hint) throw err;
|
|
35721
36146
|
throw new Error(`${msg}
|
|
35722
36147
|
${hint}`);
|
|
@@ -35748,7 +36173,12 @@ ${hint}`);
|
|
|
35748
36173
|
console.log(`${warning("\u26A0 Navigation incomplete:")} ${d.note || "page did not reach full load"}`);
|
|
35749
36174
|
}
|
|
35750
36175
|
if (d.slowRender) {
|
|
35751
|
-
|
|
36176
|
+
const slow = slowRenderMessage(d.slowRender.fps, {
|
|
36177
|
+
camera: !!camera,
|
|
36178
|
+
waitMs,
|
|
36179
|
+
script: [expr, ...steps, reloadExpr ?? ""].join("\n")
|
|
36180
|
+
});
|
|
36181
|
+
if (slow) console.log(slow);
|
|
35752
36182
|
}
|
|
35753
36183
|
if (d.auth?.requested) {
|
|
35754
36184
|
const who = getAuth()?.email;
|
|
@@ -35783,7 +36213,7 @@ ${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserve
|
|
|
35783
36213
|
} finally {
|
|
35784
36214
|
for (const h of [...hosted, ...camera ? [camera] : []]) {
|
|
35785
36215
|
try {
|
|
35786
|
-
await deleteFixture(
|
|
36216
|
+
await deleteFixture(projectGuid2, h.guid);
|
|
35787
36217
|
} catch (err) {
|
|
35788
36218
|
console.error(warning(`\u26A0 Could not auto-delete fixture "${h.name}" (${h.guid}) \u2014 still hosted at ${h.url}: ${err.message}`));
|
|
35789
36219
|
}
|
|
@@ -35877,7 +36307,7 @@ init_auth();
|
|
|
35877
36307
|
init_colors();
|
|
35878
36308
|
init_utils();
|
|
35879
36309
|
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
35880
|
-
import { dirname as
|
|
36310
|
+
import { dirname as dirname7, join as join12, resolve as resolvePath } from "path";
|
|
35881
36311
|
var DEVICE_PRESETS = {
|
|
35882
36312
|
default: { width: 1280, height: 720 },
|
|
35883
36313
|
desktop: { width: 1920, height: 1080 },
|
|
@@ -35941,7 +36371,7 @@ function dimSuffix(vp2) {
|
|
|
35941
36371
|
}
|
|
35942
36372
|
function defaultScreenshotDir() {
|
|
35943
36373
|
const root = getProjectRoot();
|
|
35944
|
-
return
|
|
36374
|
+
return join12(root ?? ".", "screenshots");
|
|
35945
36375
|
}
|
|
35946
36376
|
function timestampSlug(d = /* @__PURE__ */ new Date()) {
|
|
35947
36377
|
const p = (n) => String(n).padStart(2, "0");
|
|
@@ -36004,7 +36434,7 @@ var WAIT_FOR_MAX_MS2 = 15e3;
|
|
|
36004
36434
|
var WAIT_FOR_DEFAULT_MS = 15e3;
|
|
36005
36435
|
var MAX_POST_LOAD_DELAY_MS = 3e4;
|
|
36006
36436
|
var CAMERA_DEFAULT_DELAY_MS = 15e3;
|
|
36007
|
-
var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").option("--device <names>", `Device preset(s): ${Object.keys(DEVICE_PRESETS).join(", ")} (comma-separated or repeat flag). mobile/tablet emulate a real touch device \u2014 touch events, mobile user-agent, DPR \u2014 so touch-gated mobile UI actually renders.`, appendOption, []).option("--viewport <dims>", "Raw viewport(s): WxH or WxH@dpr (comma-separated or repeat flag), e.g. --viewport 390x844 for a phone-sized window", appendOption, []).option("--wait <ms>", `Sleep this many ms after DOMContentLoaded before capturing (default 1000, max ${MAX_POST_LOAD_DELAY_MS}). With --camera it defaults to ${CAMERA_DEFAULT_DELAY_MS} so the vision model is up. Same flag as page eval/inspect.`).option("--wait-for <selector>", `Wait until this CSS selector appears before capturing, then capture (deterministic - beats guessing a --wait). Same flag as page eval/inspect. Gate on what you are photographing ('[data-vision="ready"]', '#verdict:not(:empty)'). Max ${WAIT_FOR_MAX_MS2}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (default ${WAIT_FOR_DEFAULT_MS}, max ${WAIT_FOR_MAX_MS2}). Timing out is reported - the capture still happens, so you see the state it got stuck in.`).option("--action <js>", `Run JS in the page before capturing
|
|
36437
|
+
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 () => {
|
|
36008
36438
|
const aliasFlag = ACTION_ALIAS_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
|
|
36009
36439
|
if (aliasFlag) {
|
|
36010
36440
|
console.error(muted(
|
|
@@ -36020,7 +36450,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36020
36450
|
try {
|
|
36021
36451
|
actionScript = readScriptFile(opts.file);
|
|
36022
36452
|
} catch {
|
|
36023
|
-
throw new Error(opts.file === "-" ? `--file - reads the pre-capture script from stdin, but stdin was empty/unreadable
|
|
36453
|
+
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}`);
|
|
36024
36454
|
}
|
|
36025
36455
|
}
|
|
36026
36456
|
const delayRaw = opts.wait ?? opts.postLoadDelay;
|
|
@@ -36082,14 +36512,14 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36082
36512
|
const ts2 = timestampSlug();
|
|
36083
36513
|
const shotName = (vp2) => defaultFilename(slug, ts2, vp2 && userSpecifiedViewports ? dimSuffix(vp2) : void 0);
|
|
36084
36514
|
const names = userSpecifiedViewports ? customViewports.map(shotName) : [shotName()];
|
|
36085
|
-
const
|
|
36086
|
-
const save = !opts.ephemeral &&
|
|
36515
|
+
const projectGuid2 = getConfig()?.projectGuid;
|
|
36516
|
+
const save = !opts.ephemeral && projectGuid2 ? { project_guid: projectGuid2, names } : void 0;
|
|
36087
36517
|
let camera;
|
|
36088
36518
|
if (opts.camera) {
|
|
36089
36519
|
assertCameraFile(opts.camera);
|
|
36090
|
-
if (!
|
|
36091
|
-
console.log(muted(`Hosting camera feed ${opts.camera}
|
|
36092
|
-
camera = await uploadCameraFeed(
|
|
36520
|
+
if (!projectGuid2) throw new Error("--camera needs a linked project (the frame is hosted for the browser to fetch) \u2014 run `gipity link` first.");
|
|
36521
|
+
console.log(muted(`Hosting camera feed ${opts.camera}...`));
|
|
36522
|
+
camera = await uploadCameraFeed(projectGuid2, opts.camera);
|
|
36093
36523
|
}
|
|
36094
36524
|
const preCapture = [
|
|
36095
36525
|
opts.waitFor ? buildWaitForGate(opts.waitFor, waitForTimeoutMs) : "",
|
|
@@ -36113,14 +36543,14 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36113
36543
|
let entries;
|
|
36114
36544
|
try {
|
|
36115
36545
|
try {
|
|
36116
|
-
entries = opts.json ? await doShoot() : await withSpinner("Capturing
|
|
36546
|
+
entries = opts.json ? await doShoot() : await withSpinner("Capturing...", doShoot, { done: null });
|
|
36117
36547
|
} catch (err) {
|
|
36118
36548
|
throw preCapture ? new Error(augmentSandboxTimeout(err.message)) : err;
|
|
36119
36549
|
}
|
|
36120
36550
|
} finally {
|
|
36121
36551
|
if (camera) {
|
|
36122
36552
|
try {
|
|
36123
|
-
await deleteFixture(
|
|
36553
|
+
await deleteFixture(projectGuid2, camera.guid);
|
|
36124
36554
|
} catch (err) {
|
|
36125
36555
|
console.error(warning(`\u26A0 Could not auto-delete camera feed "${camera.name}" (${camera.guid}) \u2014 still hosted at ${camera.url}: ${err.message}`));
|
|
36126
36556
|
}
|
|
@@ -36136,8 +36566,8 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
|
|
|
36136
36566
|
const dir = defaultScreenshotDir();
|
|
36137
36567
|
const savedFiles = [];
|
|
36138
36568
|
for (let i = 0; i < pngs.length; i++) {
|
|
36139
|
-
const target = opts.output ? opts.output :
|
|
36140
|
-
mkdirSync7(
|
|
36569
|
+
const target = opts.output ? opts.output : join12(dir, names[i] ?? shotName(meta.screenshots[i].viewport));
|
|
36570
|
+
mkdirSync7(dirname7(target), { recursive: true });
|
|
36141
36571
|
writeFileSync8(target, pngs[i].buffer);
|
|
36142
36572
|
savedFiles.push(resolvePath(target));
|
|
36143
36573
|
}
|
|
@@ -36265,10 +36695,10 @@ function shortUrl(url, truncate = true, maxLen = 100) {
|
|
|
36265
36695
|
result = url;
|
|
36266
36696
|
}
|
|
36267
36697
|
if (!truncate || result.length <= maxLen) return result;
|
|
36268
|
-
const keep = maxLen -
|
|
36698
|
+
const keep = maxLen - 3;
|
|
36269
36699
|
const headLen = Math.ceil(keep / 2);
|
|
36270
36700
|
const tailLen = Math.floor(keep / 2);
|
|
36271
|
-
return result.slice(0, headLen) + "
|
|
36701
|
+
return result.slice(0, headLen) + "..." + result.slice(-tailLen);
|
|
36272
36702
|
}
|
|
36273
36703
|
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) => {
|
|
36274
36704
|
if (opts.screenshot !== void 0) {
|
|
@@ -36482,7 +36912,7 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
|
|
|
36482
36912
|
force: opts.force,
|
|
36483
36913
|
only: opts.only?.split(",").map((s) => s.trim())
|
|
36484
36914
|
});
|
|
36485
|
-
const res = opts.json ? await doDeploy() : await withSpinner(`Deploying to ${target}
|
|
36915
|
+
const res = opts.json ? await doDeploy() : await withSpinner(`Deploying to ${target}...`, doDeploy, { done: null });
|
|
36486
36916
|
const d = res.data;
|
|
36487
36917
|
const inspectAfter = async () => {
|
|
36488
36918
|
if (!opts.inspect) return;
|
|
@@ -36622,10 +37052,10 @@ dbCommand.command("list").description("List databases").option("--all", "List da
|
|
|
36622
37052
|
grouped.get(key).push(db2);
|
|
36623
37053
|
}
|
|
36624
37054
|
let first = true;
|
|
36625
|
-
for (const [
|
|
37055
|
+
for (const [projectGuid2, dbs] of grouped) {
|
|
36626
37056
|
if (!first) console.log("");
|
|
36627
37057
|
first = false;
|
|
36628
|
-
const label2 = dbs[0].projectSlug || dbs[0].projectName ||
|
|
37058
|
+
const label2 = dbs[0].projectSlug || dbs[0].projectName || projectGuid2;
|
|
36629
37059
|
console.log(label2);
|
|
36630
37060
|
for (const db2 of dbs) {
|
|
36631
37061
|
console.log(`${db2.friendlyName}`);
|
|
@@ -36734,8 +37164,8 @@ memoryCommand.command("delete <topic>").description("Delete a topic").option("--
|
|
|
36734
37164
|
// src/commands/sandbox.ts
|
|
36735
37165
|
init_api();
|
|
36736
37166
|
init_config();
|
|
36737
|
-
import { readFileSync as
|
|
36738
|
-
import { dirname as
|
|
37167
|
+
import { readFileSync as readFileSync15, existsSync as existsSync11, statSync as statSync7, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
|
|
37168
|
+
import { dirname as dirname8, extname as extname4, relative as relative4, resolve as resolve11, sep as sep3 } from "path";
|
|
36739
37169
|
init_colors();
|
|
36740
37170
|
var LANG_MAP = {
|
|
36741
37171
|
js: "javascript",
|
|
@@ -36873,7 +37303,7 @@ function isWriteTarget(code, p) {
|
|
|
36873
37303
|
function resolveRelativeCwd() {
|
|
36874
37304
|
const configPath = getConfigPath();
|
|
36875
37305
|
if (!configPath) return void 0;
|
|
36876
|
-
const projectRoot =
|
|
37306
|
+
const projectRoot = dirname8(configPath);
|
|
36877
37307
|
const rel2 = relative4(projectRoot, process.cwd());
|
|
36878
37308
|
if (!rel2 || rel2.startsWith("..")) return void 0;
|
|
36879
37309
|
return rel2.split(/[\\/]/).filter(Boolean).join("/");
|
|
@@ -36955,7 +37385,7 @@ GCC/Rust).
|
|
|
36955
37385
|
if (args.length >= 2 && INTERPRETERS[args[0].toLowerCase()] !== void 0) {
|
|
36956
37386
|
langFromInterp = INTERPRETERS[args[0].toLowerCase()];
|
|
36957
37387
|
const rest = args.slice(1).join(" ");
|
|
36958
|
-
if (existsSync11(rest) &&
|
|
37388
|
+
if (existsSync11(rest) && statSync7(rest).isFile()) filePath = rest;
|
|
36959
37389
|
else inlineCode = rest;
|
|
36960
37390
|
} else if (args.length === 1) {
|
|
36961
37391
|
inlineCode = args[0];
|
|
@@ -36976,7 +37406,7 @@ GCC/Rust).
|
|
|
36976
37406
|
let source = inlineCode;
|
|
36977
37407
|
if (filePath) {
|
|
36978
37408
|
try {
|
|
36979
|
-
source =
|
|
37409
|
+
source = readFileSync15(filePath, "utf8");
|
|
36980
37410
|
} catch {
|
|
36981
37411
|
console.error(error(`Cannot read file: ${filePath}`));
|
|
36982
37412
|
process.exit(1);
|
|
@@ -37028,7 +37458,7 @@ GCC/Rust).
|
|
|
37028
37458
|
// wire name for --discard-output.)
|
|
37029
37459
|
noSyncOutput: discardOutput.length ? discardOutput : void 0
|
|
37030
37460
|
});
|
|
37031
|
-
const res = opts.json ? await doRun() : await withSpinner("Running in sandbox
|
|
37461
|
+
const res = opts.json ? await doRun() : await withSpinner("Running in sandbox...", doRun, { done: null });
|
|
37032
37462
|
const pulledLocal = !!(res.data.outputFiles?.length && getConfigPath());
|
|
37033
37463
|
if (pulledLocal) {
|
|
37034
37464
|
await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
@@ -37040,7 +37470,7 @@ GCC/Rust).
|
|
|
37040
37470
|
const rel2 = f.path.replace(/\\/g, "/");
|
|
37041
37471
|
const abs = resolve11(base, rel2);
|
|
37042
37472
|
if (abs !== base && !abs.startsWith(base + sep3)) continue;
|
|
37043
|
-
mkdirSync8(
|
|
37473
|
+
mkdirSync8(dirname8(abs), { recursive: true });
|
|
37044
37474
|
writeFileSync9(abs, Buffer.from(f.contentBase64, "base64"));
|
|
37045
37475
|
scratchWritten.push(rel2);
|
|
37046
37476
|
}
|
|
@@ -37096,7 +37526,7 @@ Note: ${rootStrays.join(", ")} landed at the project root and will deploy with t
|
|
|
37096
37526
|
const root = getProjectRoot();
|
|
37097
37527
|
const emptyOutputs = pulledLocal && root ? (res.data.outputFiles ?? []).filter((f) => {
|
|
37098
37528
|
try {
|
|
37099
|
-
return
|
|
37529
|
+
return statSync7(resolve11(root, f)).size === 0;
|
|
37100
37530
|
} catch {
|
|
37101
37531
|
return false;
|
|
37102
37532
|
}
|
|
@@ -37128,7 +37558,7 @@ var chatCommand = new Command("chat").description("Send a message to your agent"
|
|
|
37128
37558
|
const endpoint = useExisting ? `/conversations/${config.conversationGuid}/messages` : "/conversations";
|
|
37129
37559
|
const body = useExisting ? { content: message, projectGuid: config.projectGuid } : { agentGuid: config.agentGuid, content: message, projectGuid: config.projectGuid };
|
|
37130
37560
|
const doChat = () => post(endpoint, body);
|
|
37131
|
-
const res = opts.json ? await doChat() : await withSpinner("Thinking
|
|
37561
|
+
const res = opts.json ? await doChat() : await withSpinner("Thinking...", doChat, { done: null });
|
|
37132
37562
|
if (!oneOff && res.data.conversationGuid !== config.conversationGuid) {
|
|
37133
37563
|
saveConfig({ ...config, conversationGuid: res.data.conversationGuid });
|
|
37134
37564
|
}
|
|
@@ -37213,7 +37643,7 @@ chatCommand.command("delete <guid>").description("Delete a chat").option("--json
|
|
|
37213
37643
|
// src/commands/project.ts
|
|
37214
37644
|
init_api();
|
|
37215
37645
|
init_config();
|
|
37216
|
-
import { join as
|
|
37646
|
+
import { join as join13 } from "path";
|
|
37217
37647
|
import { mkdirSync as mkdirSync9 } from "fs";
|
|
37218
37648
|
init_colors();
|
|
37219
37649
|
init_utils();
|
|
@@ -37271,7 +37701,7 @@ projectCommand.command("create <name>").description("Create a project").option("
|
|
|
37271
37701
|
}
|
|
37272
37702
|
const res = await post("/projects", body);
|
|
37273
37703
|
const project = res.data;
|
|
37274
|
-
const dir =
|
|
37704
|
+
const dir = join13(getProjectsRoot(), project.slug);
|
|
37275
37705
|
mkdirSync9(dir, { recursive: true });
|
|
37276
37706
|
const accountSlug = await getAccountSlug();
|
|
37277
37707
|
let agentGuid = "";
|
|
@@ -37614,7 +38044,7 @@ workflowCommand.command("run <name>").description("Trigger a workflow (add --wai
|
|
|
37614
38044
|
}
|
|
37615
38045
|
if (r.status !== "completed") process.exit(1);
|
|
37616
38046
|
}));
|
|
37617
|
-
workflowCommand.command("runs <name> [runGuid]").description("List recent runs, or pass a run guid (wr_
|
|
38047
|
+
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 () => {
|
|
37618
38048
|
const opts = mergedOpts(cmd);
|
|
37619
38049
|
const wf2 = await resolveWorkflow(name);
|
|
37620
38050
|
if (runGuid) {
|
|
@@ -38100,7 +38530,7 @@ function printUsage(d) {
|
|
|
38100
38530
|
for (const p of d.projects) {
|
|
38101
38531
|
const name = p.projectName ?? "(no project)";
|
|
38102
38532
|
row(
|
|
38103
|
-
name.length > 16 ? `${name.slice(0,
|
|
38533
|
+
name.length > 16 ? `${name.slice(0, 13)}...` : name,
|
|
38104
38534
|
formatBytes(p.liveBytes),
|
|
38105
38535
|
`${p.liveFiles.toLocaleString()} files`
|
|
38106
38536
|
);
|
|
@@ -38109,7 +38539,7 @@ function printUsage(d) {
|
|
|
38109
38539
|
const shownBytes = d.projects.reduce((n, p) => n + p.liveBytes, 0);
|
|
38110
38540
|
const shownFiles = d.projects.reduce((n, p) => n + p.liveFiles, 0);
|
|
38111
38541
|
row(
|
|
38112
|
-
|
|
38542
|
+
`...and ${hidden.toLocaleString()} more`,
|
|
38113
38543
|
formatBytes(totals.liveBytes - shownBytes),
|
|
38114
38544
|
`${(totals.liveFiles - shownFiles).toLocaleString()} files`
|
|
38115
38545
|
);
|
|
@@ -38161,50 +38591,11 @@ storageCommand.command("retention").description("View or adjust your file versio
|
|
|
38161
38591
|
init_platform();
|
|
38162
38592
|
init_auth();
|
|
38163
38593
|
init_api();
|
|
38164
|
-
import { join as
|
|
38165
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync2, readdirSync as
|
|
38594
|
+
import { join as join17, dirname as dirname10, resolve as resolve13, basename as basename5, sep as sep5 } from "path";
|
|
38595
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync2, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
|
|
38166
38596
|
import { homedir as homedir12 } from "os";
|
|
38167
38597
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
38168
38598
|
import { randomUUID } from "crypto";
|
|
38169
|
-
|
|
38170
|
-
// src/login-flow.ts
|
|
38171
|
-
init_api();
|
|
38172
|
-
init_auth();
|
|
38173
|
-
init_utils();
|
|
38174
|
-
init_colors();
|
|
38175
|
-
async function interactiveLogin() {
|
|
38176
|
-
const email = await prompt(" Email: ");
|
|
38177
|
-
if (!email) {
|
|
38178
|
-
console.error(`
|
|
38179
|
-
${error("Email required.")}`);
|
|
38180
|
-
process.exit(1);
|
|
38181
|
-
}
|
|
38182
|
-
await publicPost("/auth/login", { email });
|
|
38183
|
-
console.log(" Check your email for a 6-digit code.\n");
|
|
38184
|
-
const code = await prompt(" Code: ");
|
|
38185
|
-
if (!code) {
|
|
38186
|
-
console.error(`
|
|
38187
|
-
${error("Code required.")}`);
|
|
38188
|
-
process.exit(1);
|
|
38189
|
-
}
|
|
38190
|
-
const res = await publicPost("/auth/verify", { email, code });
|
|
38191
|
-
const exp = decodeJwtExp(res.accessToken);
|
|
38192
|
-
if (!exp) {
|
|
38193
|
-
console.error(`
|
|
38194
|
-
${error("Invalid token received.")}`);
|
|
38195
|
-
process.exit(1);
|
|
38196
|
-
}
|
|
38197
|
-
const expiresAt = new Date(exp * 1e3).toISOString();
|
|
38198
|
-
saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
|
|
38199
|
-
console.log(` ${success(`Logged in (${email}).`)}`);
|
|
38200
|
-
const delivered = await flushBugQueue().catch(() => 0);
|
|
38201
|
-
if (delivered > 0) {
|
|
38202
|
-
console.log(` ${muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`)}`);
|
|
38203
|
-
}
|
|
38204
|
-
return getAuth();
|
|
38205
|
-
}
|
|
38206
|
-
|
|
38207
|
-
// src/commands/build.ts
|
|
38208
38599
|
init_config();
|
|
38209
38600
|
|
|
38210
38601
|
// src/prompts.ts
|
|
@@ -38310,11 +38701,10 @@ function buildFreshWrap(contextBlock, userMsg) {
|
|
|
38310
38701
|
// src/relay/onboarding.ts
|
|
38311
38702
|
init_utils();
|
|
38312
38703
|
init_colors();
|
|
38313
|
-
import { readFileSync as readFileSync16 } from "node:fs";
|
|
38314
38704
|
|
|
38315
38705
|
// src/relay/installers.ts
|
|
38316
38706
|
import { homedir as homedir8, platform as osPlatform } from "os";
|
|
38317
|
-
import { join as
|
|
38707
|
+
import { join as join14 } from "path";
|
|
38318
38708
|
var UnsupportedPlatformError = class extends Error {
|
|
38319
38709
|
constructor(plat) {
|
|
38320
38710
|
super(`gipity relay install does not support ${plat}`);
|
|
@@ -38332,8 +38722,8 @@ function display(argvs) {
|
|
|
38332
38722
|
}
|
|
38333
38723
|
function launchdPlan(cliPath) {
|
|
38334
38724
|
const label2 = "ai.gipity.relay";
|
|
38335
|
-
const path5 =
|
|
38336
|
-
const logDir =
|
|
38725
|
+
const path5 = join14(homedir8(), "Library", "LaunchAgents", `${label2}.plist`);
|
|
38726
|
+
const logDir = join14(homedir8(), "Library", "Logs", "Gipity");
|
|
38337
38727
|
const uid = process.getuid?.() ?? 0;
|
|
38338
38728
|
const target = `gui/${uid}`;
|
|
38339
38729
|
const content = `<?xml version="1.0" encoding="UTF-8"?>
|
|
@@ -38357,8 +38747,8 @@ function launchdPlan(cliPath) {
|
|
|
38357
38747
|
<dict>
|
|
38358
38748
|
<key>SuccessfulExit</key><false/>
|
|
38359
38749
|
</dict>
|
|
38360
|
-
<key>StandardOutPath</key><string>${
|
|
38361
|
-
<key>StandardErrorPath</key><string>${
|
|
38750
|
+
<key>StandardOutPath</key><string>${join14(logDir, "relay.out.log")}</string>
|
|
38751
|
+
<key>StandardErrorPath</key><string>${join14(logDir, "relay.err.log")}</string>
|
|
38362
38752
|
<key>ProcessType</key><string>Background</string>
|
|
38363
38753
|
</dict>
|
|
38364
38754
|
</plist>
|
|
@@ -38381,7 +38771,7 @@ function launchdPlan(cliPath) {
|
|
|
38381
38771
|
}
|
|
38382
38772
|
function systemdUserPlan(cliPath) {
|
|
38383
38773
|
const unitName = "gipity-relay.service";
|
|
38384
|
-
const path5 =
|
|
38774
|
+
const path5 = join14(homedir8(), ".config", "systemd", "user", unitName);
|
|
38385
38775
|
const content = `[Unit]
|
|
38386
38776
|
Description=Gipity relay - local Claude Code control from the Gipity web CLI
|
|
38387
38777
|
After=network-online.target
|
|
@@ -38419,7 +38809,7 @@ WantedBy=default.target
|
|
|
38419
38809
|
}
|
|
38420
38810
|
function windowsTaskPlan(cliPath) {
|
|
38421
38811
|
const taskName = "GipityRelay";
|
|
38422
|
-
const path5 =
|
|
38812
|
+
const path5 = join14(homedir8(), "AppData", "Local", "Gipity", "relay-task.xml");
|
|
38423
38813
|
const runsViaNode = /\.[cm]?js$/i.test(cliPath);
|
|
38424
38814
|
const command = runsViaNode ? process.execPath : cliPath;
|
|
38425
38815
|
const argLine = runsViaNode ? `"${cliPath}" relay run` : "relay run";
|
|
@@ -38481,13 +38871,13 @@ function windowsTaskPlan(cliPath) {
|
|
|
38481
38871
|
init_platform();
|
|
38482
38872
|
init_api();
|
|
38483
38873
|
import { hostname as hostname3, userInfo, platform as osPlatform2 } from "os";
|
|
38484
|
-
import { resolve as resolve12, dirname as
|
|
38874
|
+
import { resolve as resolve12, dirname as dirname9 } from "path";
|
|
38485
38875
|
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
|
|
38486
38876
|
|
|
38487
38877
|
// src/relay/machine-id.ts
|
|
38488
38878
|
import { createHash as createHash3 } from "crypto";
|
|
38489
38879
|
import { execFileSync } from "child_process";
|
|
38490
|
-
import { readFileSync as
|
|
38880
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
38491
38881
|
import { hostname as hostname2, platform, arch } from "os";
|
|
38492
38882
|
function rawMachineId() {
|
|
38493
38883
|
const override = process.env.GIPITY_RELAY_MACHINE_ID?.trim();
|
|
@@ -38496,7 +38886,7 @@ function rawMachineId() {
|
|
|
38496
38886
|
if (platform() === "linux") {
|
|
38497
38887
|
for (const path5 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
|
|
38498
38888
|
try {
|
|
38499
|
-
const id2 =
|
|
38889
|
+
const id2 = readFileSync16(path5, "utf-8").trim();
|
|
38500
38890
|
if (id2) return `linux:${id2}`;
|
|
38501
38891
|
} catch {
|
|
38502
38892
|
}
|
|
@@ -38591,7 +38981,7 @@ function startDaemon() {
|
|
|
38591
38981
|
function installAutostart(opts = {}) {
|
|
38592
38982
|
const stdio = opts.stdio ?? "ignore";
|
|
38593
38983
|
const plan2 = planFor({ cliPath: resolveCliPath() });
|
|
38594
|
-
mkdirSync10(
|
|
38984
|
+
mkdirSync10(dirname9(plan2.path), { recursive: true });
|
|
38595
38985
|
writeFileSync10(plan2.path, plan2.content);
|
|
38596
38986
|
let ok = true;
|
|
38597
38987
|
for (const argv2 of plan2.enableCmds) {
|
|
@@ -38616,15 +39006,6 @@ function removeAutostart(opts = {}) {
|
|
|
38616
39006
|
|
|
38617
39007
|
// src/relay/onboarding.ts
|
|
38618
39008
|
var ensureDaemonRunning = startDaemon;
|
|
38619
|
-
function isWsl() {
|
|
38620
|
-
if (process.platform !== "linux") return false;
|
|
38621
|
-
if (process.env.WSL_DISTRO_NAME || process.env.WSL_INTEROP) return true;
|
|
38622
|
-
try {
|
|
38623
|
-
return /microsoft/i.test(readFileSync16("/proc/version", "utf-8"));
|
|
38624
|
-
} catch {
|
|
38625
|
-
return false;
|
|
38626
|
-
}
|
|
38627
|
-
}
|
|
38628
39009
|
async function runRelaySetup(opts) {
|
|
38629
39010
|
const alreadyAnswered = getRelayEnabled() !== void 0;
|
|
38630
39011
|
if (opts.mode === "offer-once" && alreadyAnswered) {
|
|
@@ -38640,12 +39021,12 @@ async function runRelaySetup(opts) {
|
|
|
38640
39021
|
console.log("");
|
|
38641
39022
|
console.log(` ${success("Registered as")} ${bold(existingDevice.name)} ${muted(`(${existingDevice.guid})`)}`);
|
|
38642
39023
|
if (isPaused()) {
|
|
38643
|
-
console.log(` ${dim("The relay is paused
|
|
39024
|
+
console.log(` ${dim("The relay is paused - resume it with")} ${brand("gipity relay resume")}${dim(".")}`);
|
|
38644
39025
|
} else {
|
|
38645
39026
|
console.log(` ${dim("The relay is running. Open")} ${brand("gipity.ai")}${dim(" and type `/claude` to drive Claude Code here.")}`);
|
|
38646
39027
|
}
|
|
38647
39028
|
console.log("");
|
|
38648
|
-
console.log(` ${dim("To register this computer again
|
|
39029
|
+
console.log(` ${dim("To register this computer again (for example under a different name),")}`);
|
|
38649
39030
|
console.log(` ${dim("unregister it first, then re-run setup:")}`);
|
|
38650
39031
|
console.log(` ${brand("gipity relay revoke")} ${dim("# unpairs this computer and removes the login service")}`);
|
|
38651
39032
|
console.log(` ${brand("gipity connect")} ${dim("# register it again (asks for a new name)")}`);
|
|
@@ -38655,28 +39036,29 @@ async function runRelaySetup(opts) {
|
|
|
38655
39036
|
}
|
|
38656
39037
|
if (opts.mode === "run-now") {
|
|
38657
39038
|
console.log(` ${bold("Set up this computer as a relay")}`);
|
|
38658
|
-
console.log(` ${dim("
|
|
38659
|
-
console.log(` ${dim("
|
|
39039
|
+
console.log(` ${dim("Gipity drives the coding agents on this computer from the web (")}${brand("gipity.ai")}${dim(")")}`);
|
|
39040
|
+
console.log(` ${dim("on any browser, desktop or phone. Uses your Claude, Codex, or Grok")}`);
|
|
39041
|
+
console.log(` ${dim("subscription - the cheapest way to pay for tokens.")}`);
|
|
38660
39042
|
} else {
|
|
38661
|
-
console.log(` ${bold("
|
|
38662
|
-
console.log(` ${dim("
|
|
38663
|
-
console.log("");
|
|
38664
|
-
console.log(` ${dim("Enable now (takes 2 seconds) or turn on later with")} ${brand("gipity connect")}`);
|
|
39043
|
+
console.log(` ${bold("Optional:")} ${dim("Gipity can drive the coding agents on this computer from the")}`);
|
|
39044
|
+
console.log(` ${dim("web (")}${brand("gipity.ai")}${dim(") on any browser, desktop or phone. Like Claude Code remote")}`);
|
|
39045
|
+
console.log(` ${dim("control, but one pane for all your coding agents.")}`);
|
|
38665
39046
|
}
|
|
38666
39047
|
console.log("");
|
|
38667
|
-
const promptText = opts.mode === "run-now" ? " Set up remote control on this computer
|
|
39048
|
+
const promptText = opts.mode === "run-now" ? " Set up remote control on this computer" : " Enable remote control";
|
|
38668
39049
|
const enable = await confirm(promptText, { default: "yes" });
|
|
38669
39050
|
if (!enable) {
|
|
38670
39051
|
setRelayEnabled(false);
|
|
38671
|
-
console.log(` ${muted("Skipped.")}`);
|
|
39052
|
+
console.log(` ${muted("Skipped. Turn on later with")} ${brand("gipity connect")}${muted(".")}`);
|
|
38672
39053
|
console.log("");
|
|
38673
39054
|
return false;
|
|
38674
39055
|
}
|
|
39056
|
+
console.log("");
|
|
38675
39057
|
const defaultName = friendlyDeviceName();
|
|
38676
39058
|
const rawName = await prompt(` Device name [${bold(defaultName)}]: `);
|
|
38677
39059
|
const name = (rawName || defaultName).trim();
|
|
38678
39060
|
if (!name || name.length > 100) {
|
|
38679
|
-
console.error(` ${error("Device name must be 1
|
|
39061
|
+
console.error(` ${error("Device name must be 1-100 non-whitespace characters. Skipping.")}`);
|
|
38680
39062
|
setRelayEnabled(false);
|
|
38681
39063
|
return false;
|
|
38682
39064
|
}
|
|
@@ -38689,11 +39071,9 @@ async function runRelaySetup(opts) {
|
|
|
38689
39071
|
console.error(` ${dim("Skipping for now - we'll offer again next time. Or turn it on with `gipity connect`.")}`);
|
|
38690
39072
|
return false;
|
|
38691
39073
|
}
|
|
38692
|
-
|
|
38693
|
-
|
|
38694
|
-
|
|
38695
|
-
}
|
|
38696
|
-
const autostartOs = await confirm(" Also start at OS login (auto-start with Windows / macOS / Linux)?", { default: "yes" });
|
|
39074
|
+
startDaemon();
|
|
39075
|
+
console.log("");
|
|
39076
|
+
const autostartOs = await confirm(" Also start at OS login", { default: "yes" });
|
|
38697
39077
|
if (autostartOs) {
|
|
38698
39078
|
try {
|
|
38699
39079
|
const res = installAutostart();
|
|
@@ -38705,8 +39085,6 @@ async function runRelaySetup(opts) {
|
|
|
38705
39085
|
} else {
|
|
38706
39086
|
console.log(` ${muted("Autostart install returned non-zero - you can run")} ${brand("gipity relay install")} ${muted("later.")}`);
|
|
38707
39087
|
}
|
|
38708
|
-
} else {
|
|
38709
|
-
console.log(` ${success("Auto-start installed.")} ${dim(res.summary)}`);
|
|
38710
39088
|
}
|
|
38711
39089
|
} catch (err) {
|
|
38712
39090
|
if (err instanceof UnsupportedPlatformError) {
|
|
@@ -38717,16 +39095,12 @@ async function runRelaySetup(opts) {
|
|
|
38717
39095
|
}
|
|
38718
39096
|
}
|
|
38719
39097
|
if (getDiagnosticsConsent() === void 0) {
|
|
38720
|
-
|
|
38721
|
-
|
|
38722
|
-
{ default: "yes" }
|
|
38723
|
-
);
|
|
39098
|
+
console.log("");
|
|
39099
|
+
const diag = await confirm(" Share anonymous diagnostics info", { default: "yes" });
|
|
38724
39100
|
setDiagnosticsConsent(diag);
|
|
38725
|
-
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`.")}`);
|
|
38726
39101
|
}
|
|
38727
39102
|
console.log("");
|
|
38728
|
-
console.log(` ${success(`
|
|
38729
|
-
console.log(` ${dim("In the Gipity web CLI, type `/claude` to dispatch messages to this PC.")}`);
|
|
39103
|
+
console.log(` ${success(`Done! ${bold(device.name)} is set up. New chats from gipity.ai can now execute here.`)}`);
|
|
38730
39104
|
console.log("");
|
|
38731
39105
|
return true;
|
|
38732
39106
|
}
|
|
@@ -38869,9 +39243,8 @@ function padR(s, width) {
|
|
|
38869
39243
|
}
|
|
38870
39244
|
function leadingEllipsify(s, maxW) {
|
|
38871
39245
|
if (s.length <= maxW) return s;
|
|
38872
|
-
if (maxW <=
|
|
38873
|
-
|
|
38874
|
-
return "\u2026" + s.slice(-(maxW - 1));
|
|
39246
|
+
if (maxW <= 4) return s.slice(-maxW);
|
|
39247
|
+
return "..." + s.slice(-(maxW - 3));
|
|
38875
39248
|
}
|
|
38876
39249
|
function center(s, width) {
|
|
38877
39250
|
const gap = width - visLen(s);
|
|
@@ -38993,7 +39366,7 @@ function printFull(opts, outerW) {
|
|
|
38993
39366
|
import { execSync as execSync2 } from "child_process";
|
|
38994
39367
|
import { existsSync as existsSync12 } from "fs";
|
|
38995
39368
|
import { homedir as homedir10 } from "os";
|
|
38996
|
-
import { join as
|
|
39369
|
+
import { join as join15 } from "path";
|
|
38997
39370
|
var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
|
|
38998
39371
|
function claudeInstallPlan(platformOverride) {
|
|
38999
39372
|
const plat = platformOverride ?? process.platform;
|
|
@@ -39012,7 +39385,7 @@ function isClaudeInstalled(platformOverride) {
|
|
|
39012
39385
|
}
|
|
39013
39386
|
function isClaudeAuthenticated() {
|
|
39014
39387
|
if (process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY) return true;
|
|
39015
|
-
return existsSync12(
|
|
39388
|
+
return existsSync12(join15(homedir10(), ".claude", ".credentials.json"));
|
|
39016
39389
|
}
|
|
39017
39390
|
function probeClaudeAuthenticated() {
|
|
39018
39391
|
if (!isClaudeInstalled()) return false;
|
|
@@ -39162,11 +39535,53 @@ var grokAdapter = {
|
|
|
39162
39535
|
installHint: "curl -fsSL https://grok.x.ai/install.sh | sh (or see docs.x.ai/grok-build)"
|
|
39163
39536
|
};
|
|
39164
39537
|
|
|
39538
|
+
// src/agents/agy.ts
|
|
39539
|
+
var MODEL_ID_TO_AGY_DISPLAY = {
|
|
39540
|
+
"gemini-3.5-flash": "Gemini 3.5 Flash (Medium)",
|
|
39541
|
+
"gemini-3.1-pro-preview": "Gemini 3.1 Pro (High)"
|
|
39542
|
+
};
|
|
39543
|
+
function translateModel(model) {
|
|
39544
|
+
return MODEL_ID_TO_AGY_DISPLAY[model] ?? model;
|
|
39545
|
+
}
|
|
39546
|
+
function projectArgs(resume) {
|
|
39547
|
+
return resume ? ["--conversation", resume] : ["--new-project"];
|
|
39548
|
+
}
|
|
39549
|
+
var agyAdapter = {
|
|
39550
|
+
key: "agy",
|
|
39551
|
+
source: "agy",
|
|
39552
|
+
displayName: "Antigravity",
|
|
39553
|
+
providerName: "Google",
|
|
39554
|
+
binary: "agy",
|
|
39555
|
+
buildInteractiveArgs({ resume, model }) {
|
|
39556
|
+
const args = [...projectArgs(resume)];
|
|
39557
|
+
if (model) args.push("--model", translateModel(model));
|
|
39558
|
+
return args;
|
|
39559
|
+
},
|
|
39560
|
+
buildHeadlessArgs({ message, resume, model, bypassApprovals }) {
|
|
39561
|
+
const args = ["-p", message, ...projectArgs(resume)];
|
|
39562
|
+
if (model) args.push("--model", translateModel(model));
|
|
39563
|
+
if (bypassApprovals) args.push("--dangerously-skip-permissions");
|
|
39564
|
+
return args;
|
|
39565
|
+
},
|
|
39566
|
+
sessionIdFromStreamEvent() {
|
|
39567
|
+
return null;
|
|
39568
|
+
},
|
|
39569
|
+
hooksSupportedOnPlatform(platform2) {
|
|
39570
|
+
return platform2 !== "win32";
|
|
39571
|
+
},
|
|
39572
|
+
// No stream-json ingest mapper for agy: hook capture carries the full
|
|
39573
|
+
// conversationId on every event (confirmed live, including headless -p),
|
|
39574
|
+
// so the daemon doesn't need to parse agy's stdout at all.
|
|
39575
|
+
daemonStreamCapture: false,
|
|
39576
|
+
installHint: "see https://antigravity.google (Google account sign-in required)"
|
|
39577
|
+
};
|
|
39578
|
+
|
|
39165
39579
|
// src/agents/index.ts
|
|
39166
39580
|
var AGENT_ADAPTERS = [
|
|
39167
39581
|
claudeCodeAdapter,
|
|
39168
39582
|
codexAdapter,
|
|
39169
|
-
grokAdapter
|
|
39583
|
+
grokAdapter,
|
|
39584
|
+
agyAdapter
|
|
39170
39585
|
];
|
|
39171
39586
|
var AGENT_KEYS = AGENT_ADAPTERS.map((a) => a.key);
|
|
39172
39587
|
function getAdapter(key) {
|
|
@@ -39183,8 +39598,8 @@ function getAdapterBySource(source) {
|
|
|
39183
39598
|
// src/prefs.ts
|
|
39184
39599
|
import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
|
|
39185
39600
|
import { homedir as homedir11 } from "os";
|
|
39186
|
-
import { join as
|
|
39187
|
-
var PREFS_PATH =
|
|
39601
|
+
import { join as join16 } from "path";
|
|
39602
|
+
var PREFS_PATH = join16(homedir11(), ".gipity", "prefs.json");
|
|
39188
39603
|
function readPrefs() {
|
|
39189
39604
|
if (!existsSync13(PREFS_PATH)) return {};
|
|
39190
39605
|
try {
|
|
@@ -39200,17 +39615,17 @@ function writePrefs(update) {
|
|
|
39200
39615
|
try {
|
|
39201
39616
|
const current = readPrefs();
|
|
39202
39617
|
const next = { ...current, ...update };
|
|
39203
|
-
mkdirSync11(
|
|
39618
|
+
mkdirSync11(join16(homedir11(), ".gipity"), { recursive: true });
|
|
39204
39619
|
writeFileSync11(PREFS_PATH, JSON.stringify(next, null, 2) + "\n");
|
|
39205
39620
|
} catch {
|
|
39206
39621
|
}
|
|
39207
39622
|
}
|
|
39208
39623
|
|
|
39209
39624
|
// src/commands/build.ts
|
|
39210
|
-
var __clDir =
|
|
39625
|
+
var __clDir = dirname10(fileURLToPath2(import.meta.url));
|
|
39211
39626
|
function readOwnPkg(fromDir) {
|
|
39212
|
-
for (let d = fromDir; ; d =
|
|
39213
|
-
const p =
|
|
39627
|
+
for (let d = fromDir; ; d = dirname10(d)) {
|
|
39628
|
+
const p = join17(d, "package.json");
|
|
39214
39629
|
if (existsSync14(p)) {
|
|
39215
39630
|
try {
|
|
39216
39631
|
const pkg2 = JSON.parse(readFileSync18(p, "utf-8"));
|
|
@@ -39218,13 +39633,13 @@ function readOwnPkg(fromDir) {
|
|
|
39218
39633
|
} catch {
|
|
39219
39634
|
}
|
|
39220
39635
|
}
|
|
39221
|
-
if (
|
|
39636
|
+
if (dirname10(d) === d) return {};
|
|
39222
39637
|
}
|
|
39223
39638
|
}
|
|
39224
39639
|
var __clPkg = readOwnPkg(__clDir);
|
|
39225
39640
|
function reportSyncResult(result) {
|
|
39226
39641
|
if (result.aborted) {
|
|
39227
|
-
console.log(` ${warning("Merge cancelled
|
|
39642
|
+
console.log(` ${warning("Merge cancelled - this folder was NOT synced with the project.")}`);
|
|
39228
39643
|
console.log(` ${muted("For a clean copy, quit and open the project in an empty folder. To merge anyway, run `gipity sync`.")}`);
|
|
39229
39644
|
return;
|
|
39230
39645
|
}
|
|
@@ -39232,16 +39647,16 @@ function reportSyncResult(result) {
|
|
|
39232
39647
|
console.log(` Synced ${result.applied} change${result.applied > 1 ? "s" : ""} with Gipity.`);
|
|
39233
39648
|
}
|
|
39234
39649
|
if (result.errors.length) {
|
|
39235
|
-
console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? "" : "s"}
|
|
39650
|
+
console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? "" : "s"} - your local copy may be incomplete:`)}`);
|
|
39236
39651
|
for (const e of result.errors.slice(0, 8)) console.log(` - ${e}`);
|
|
39237
|
-
if (result.errors.length > 8) console.log(`
|
|
39652
|
+
if (result.errors.length > 8) console.log(` ...and ${result.errors.length - 8} more.`);
|
|
39238
39653
|
console.log(` ${muted("Nothing was deleted. Re-run `gipity sync` to finish the pull.")}`);
|
|
39239
39654
|
}
|
|
39240
39655
|
}
|
|
39241
|
-
async function fetchProjectStats(
|
|
39242
|
-
if (
|
|
39656
|
+
async function fetchProjectStats(projectGuid2, cwd) {
|
|
39657
|
+
if (projectGuid2) {
|
|
39243
39658
|
try {
|
|
39244
|
-
const res = await get(`/projects/${encodeURIComponent(
|
|
39659
|
+
const res = await get(`/projects/${encodeURIComponent(projectGuid2)}/files/stats`);
|
|
39245
39660
|
const d = res.data;
|
|
39246
39661
|
const topLevelNames = d.top_level.map((e) => e.type === "directory" ? `${e.name}/` : e.name);
|
|
39247
39662
|
return {
|
|
@@ -39262,12 +39677,12 @@ function localFsFallback(dir) {
|
|
|
39262
39677
|
const topLevelEntries = [];
|
|
39263
39678
|
const walk = (d, depth) => {
|
|
39264
39679
|
try {
|
|
39265
|
-
for (const name of
|
|
39680
|
+
for (const name of readdirSync8(d).sort()) {
|
|
39266
39681
|
if (isSyncIgnored(name)) continue;
|
|
39267
39682
|
let isDir = false;
|
|
39268
39683
|
let size = 0;
|
|
39269
39684
|
try {
|
|
39270
|
-
const st2 =
|
|
39685
|
+
const st2 = statSync8(join17(d, name));
|
|
39271
39686
|
isDir = st2.isDirectory();
|
|
39272
39687
|
size = st2.isFile() ? st2.size : 0;
|
|
39273
39688
|
} catch {
|
|
@@ -39276,7 +39691,7 @@ function localFsFallback(dir) {
|
|
|
39276
39691
|
if (isDir) {
|
|
39277
39692
|
folderCount++;
|
|
39278
39693
|
if (depth === 0) topLevelEntries.push(`${name}/`);
|
|
39279
|
-
walk(
|
|
39694
|
+
walk(join17(d, name), depth + 1);
|
|
39280
39695
|
} else {
|
|
39281
39696
|
fileCount++;
|
|
39282
39697
|
totalBytes += size;
|
|
@@ -39287,7 +39702,7 @@ function localFsFallback(dir) {
|
|
|
39287
39702
|
}
|
|
39288
39703
|
};
|
|
39289
39704
|
walk(dir, 0);
|
|
39290
|
-
const topLevel = topLevelEntries.length ? topLevelEntries.slice(0, 20).join(", ") + (topLevelEntries.length > 20 ? ",
|
|
39705
|
+
const topLevel = topLevelEntries.length ? topLevelEntries.slice(0, 20).join(", ") + (topLevelEntries.length > 20 ? ", ..." : "") : "(empty directory)";
|
|
39291
39706
|
return { fileCount, folderCount, totalBytes, topLevel };
|
|
39292
39707
|
}
|
|
39293
39708
|
async function buildProjectContextBlock2(opts) {
|
|
@@ -39309,7 +39724,7 @@ function hasStreamJsonFlag(args) {
|
|
|
39309
39724
|
}
|
|
39310
39725
|
function markFolderTrusted(dir) {
|
|
39311
39726
|
try {
|
|
39312
|
-
const file =
|
|
39727
|
+
const file = join17(homedir12(), ".claude.json");
|
|
39313
39728
|
const cfg = existsSync14(file) ? JSON.parse(readFileSync18(file, "utf-8")) : {};
|
|
39314
39729
|
if (typeof cfg !== "object" || cfg === null) return;
|
|
39315
39730
|
if (typeof cfg.projects !== "object" || cfg.projects === null) cfg.projects = {};
|
|
@@ -39437,7 +39852,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39437
39852
|
if (existing && !opts.newProject && !opts.project && !nonInteractive) {
|
|
39438
39853
|
const cwdHasConfig = existsSync14(resolve13(process.cwd(), ".gipity.json"));
|
|
39439
39854
|
if (!cwdHasConfig && isLikelyEmpty(process.cwd())) {
|
|
39440
|
-
const ancestorRoot =
|
|
39855
|
+
const ancestorRoot = dirname10(getConfigPath());
|
|
39441
39856
|
console.log(` ${bold("You are inside an existing Gipity project.")}
|
|
39442
39857
|
`);
|
|
39443
39858
|
console.log(` ${bold("1.")} Continue in ${brand(existing.projectSlug)} ${muted(`(rooted at ${formatCwdLabel(ancestorRoot)})`)}`);
|
|
@@ -39484,7 +39899,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39484
39899
|
}
|
|
39485
39900
|
project = found;
|
|
39486
39901
|
}
|
|
39487
|
-
const projectDir2 = opts.here ? process.cwd() :
|
|
39902
|
+
const projectDir2 = opts.here ? process.cwd() : join17(getProjectsRoot(), project.slug);
|
|
39488
39903
|
mkdirSync12(projectDir2, { recursive: true });
|
|
39489
39904
|
process.chdir(projectDir2);
|
|
39490
39905
|
clearConfigCache();
|
|
@@ -39513,6 +39928,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39513
39928
|
console.log(` ${warning("Could not sync files (will retry on next prompt):")} ${err.message}`);
|
|
39514
39929
|
}
|
|
39515
39930
|
setupProjectTools();
|
|
39931
|
+
if (!nonInteractive) console.log(` ${muted("Installed Gipity plugin, skills, and hooks.")}`);
|
|
39516
39932
|
if (nonInteractive) {
|
|
39517
39933
|
headlessNewProject = isNewProject;
|
|
39518
39934
|
} else {
|
|
@@ -39526,7 +39942,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39526
39942
|
`);
|
|
39527
39943
|
setupProjectTools();
|
|
39528
39944
|
let doSync = true;
|
|
39529
|
-
const projectRoot =
|
|
39945
|
+
const projectRoot = dirname10(getConfigPath());
|
|
39530
39946
|
const scan = scanForAdoption(projectRoot);
|
|
39531
39947
|
if (scan.tier === "refuse" && !nonInteractive) {
|
|
39532
39948
|
const sizeStr = scan.truncated ? `>${formatBytes(ADOPT_THRESHOLDS.REFUSE_BYTES)}` : formatBytes(scan.bytes);
|
|
@@ -39585,7 +40001,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39585
40001
|
let agentGuid = "";
|
|
39586
40002
|
const result = forceAdoptCwd ? { kind: "adopt-cwd" } : projects.length > 0 ? await pickOrCreateProject(projects, existingSlugs) : { kind: "create-new", project: await createNewProject(existingSlugs) };
|
|
39587
40003
|
if (result.kind === "adopt-cwd") {
|
|
39588
|
-
const cwdBase =
|
|
40004
|
+
const cwdBase = basename5(process.cwd());
|
|
39589
40005
|
const adoptSlug = slugify(cwdBase) || "project";
|
|
39590
40006
|
const adoptName = (cwdBase || adoptSlug).slice(0, 100);
|
|
39591
40007
|
accountSlug = await getAccountSlug();
|
|
@@ -39607,7 +40023,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39607
40023
|
} else {
|
|
39608
40024
|
project = result.project;
|
|
39609
40025
|
isNewProject = result.kind === "create-new";
|
|
39610
|
-
const projectDir2 =
|
|
40026
|
+
const projectDir2 = join17(getProjectsRoot(), project.slug);
|
|
39611
40027
|
mkdirSync12(projectDir2, { recursive: true });
|
|
39612
40028
|
process.chdir(projectDir2);
|
|
39613
40029
|
clearConfigCache();
|
|
@@ -39637,6 +40053,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39637
40053
|
}
|
|
39638
40054
|
initialPrompt = "";
|
|
39639
40055
|
setupProjectTools();
|
|
40056
|
+
if (!nonInteractive) console.log(` ${muted("Installed Gipity plugin, skills, and hooks.")}`);
|
|
39640
40057
|
console.log(` ${success(`Project "${project.name}" ready.`)}
|
|
39641
40058
|
`);
|
|
39642
40059
|
}
|
|
@@ -39732,9 +40149,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
|
|
|
39732
40149
|
}
|
|
39733
40150
|
if (!nonInteractive) {
|
|
39734
40151
|
console.log(` ${bold("Launching Claude Code, powered by Gipity.")}`);
|
|
39735
|
-
console.log(` ${muted("Just tell Claude what you'd like to build or do - everything Claude can do,")}`);
|
|
39736
|
-
console.log(` ${muted("plus hosting, databases, and live deploys on Gipity.")}`);
|
|
39737
40152
|
if (convGuidForHooks) {
|
|
40153
|
+
console.log("");
|
|
39738
40154
|
console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand("prompt.gipity.ai")}. (--no-capture on init to disable.)`)}`);
|
|
39739
40155
|
}
|
|
39740
40156
|
console.log("");
|
|
@@ -39832,11 +40248,9 @@ async function pickAgent(lastUsed) {
|
|
|
39832
40248
|
console.log(` ${bold("Which coding agent?")}
|
|
39833
40249
|
`);
|
|
39834
40250
|
AGENT_ADAPTERS.forEach((a, i) => {
|
|
39835
|
-
const installed = binaryOnPath(a.binary) || a.key === "claude" && isClaudeInstalled();
|
|
39836
40251
|
const notes = [];
|
|
39837
40252
|
if (a.key === lastUsed) notes.push("last used");
|
|
39838
|
-
if (
|
|
39839
|
-
if (a.key === "codex" && !a.hooksSupportedOnPlatform(process.platform)) {
|
|
40253
|
+
if ((a.key === "codex" || a.key === "agy") && !a.hooksSupportedOnPlatform(process.platform)) {
|
|
39840
40254
|
notes.push("session recording unavailable on Windows");
|
|
39841
40255
|
}
|
|
39842
40256
|
const note = notes.length ? ` ${muted(`(${notes.join(", ")})`)}` : "";
|
|
@@ -39948,9 +40362,8 @@ Sending to ${adapter.displayName}: ${message}
|
|
|
39948
40362
|
} else {
|
|
39949
40363
|
agentArgs = [...adapter.buildInteractiveArgs({ resume, model }), ...extras];
|
|
39950
40364
|
console.log(` ${bold(`Launching ${adapter.displayName}, powered by Gipity.`)}`);
|
|
39951
|
-
console.log(` ${muted(`Just tell ${adapter.displayName} what you'd like to build or do - plus hosting,`)}`);
|
|
39952
|
-
console.log(` ${muted("databases, and live deploys on Gipity.")}`);
|
|
39953
40365
|
if (convGuid) {
|
|
40366
|
+
console.log("");
|
|
39954
40367
|
console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand("prompt.gipity.ai")}. (--no-capture on init to disable.)`)}`);
|
|
39955
40368
|
}
|
|
39956
40369
|
if (adapter.oneTimeSetupNote) {
|
|
@@ -39989,7 +40402,7 @@ ${doneLine}
|
|
|
39989
40402
|
});
|
|
39990
40403
|
}
|
|
39991
40404
|
function flushHeadlessCapture(adapter, sessionId, convGuid) {
|
|
39992
|
-
const runner =
|
|
40405
|
+
const runner = join17(__clDir, "..", "hooks", "capture-runner.js");
|
|
39993
40406
|
if (!existsSync14(runner)) return;
|
|
39994
40407
|
const payload = JSON.stringify({ session_id: sessionId, cwd: process.cwd() });
|
|
39995
40408
|
const env = { ...process.env, GIPITY_CONVERSATION_GUID: convGuid };
|
|
@@ -40380,7 +40793,7 @@ var addCommand = new Command("add").description("Add a template or kit").argumen
|
|
|
40380
40793
|
console.error(muted(isKit ? "Installing kit (server-side install pipeline)..." : "Installing (server writes files + generates favicons; first add for a title can take ~10s)..."));
|
|
40381
40794
|
}
|
|
40382
40795
|
const doAdd = () => post(`/projects/${config.projectGuid}/add`, body);
|
|
40383
|
-
const res = opts.json ? await doAdd() : await withSpinner("Installing
|
|
40796
|
+
const res = opts.json ? await doAdd() : await withSpinner("Installing...", doAdd, { done: null });
|
|
40384
40797
|
const syncResult = await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
|
|
40385
40798
|
const data = res.data;
|
|
40386
40799
|
if (opts.json) {
|
|
@@ -40414,6 +40827,87 @@ Pulled ${syncResult.applied} files to local.`);
|
|
|
40414
40827
|
}
|
|
40415
40828
|
}));
|
|
40416
40829
|
|
|
40830
|
+
// src/commands/brand.ts
|
|
40831
|
+
init_api();
|
|
40832
|
+
init_config();
|
|
40833
|
+
init_colors();
|
|
40834
|
+
function printBrand(d) {
|
|
40835
|
+
console.log(`${bold("Glyph:")} ${d.resolved.glyph}${d.resolved.isEmoji ? muted(" (emoji)") : ""}`);
|
|
40836
|
+
console.log(`${bold("Accent:")} ${d.resolved.accent}${d.branding.primary_color ? "" : muted(" (derived from project)")}`);
|
|
40837
|
+
if (d.branding.app_name) console.log(`${bold("Name:")} ${d.branding.app_name}`);
|
|
40838
|
+
if (d.branding.tagline) console.log(`${bold("Tagline:")} ${d.branding.tagline}`);
|
|
40839
|
+
}
|
|
40840
|
+
var DEPLOY_HINT = "Deploy to publish the new assets: gipity deploy dev";
|
|
40841
|
+
var brandCommand = new Command("brand").description("The app's generated icons + share card (favicons, iOS icon, og-image)").addHelpText("after", `
|
|
40842
|
+
Examples:
|
|
40843
|
+
gipity brand Show the current brand
|
|
40844
|
+
gipity brand set --emoji \u{1F98D} Emoji icon + share card, regenerate all assets
|
|
40845
|
+
gipity brand set --glyph R --color "#3b82f6" Letter glyph on a blue accent
|
|
40846
|
+
gipity brand set --tagline "Bananas at 60fps" Subtitle on the share card
|
|
40847
|
+
gipity brand apply Re-render assets from the stored brand
|
|
40848
|
+
gipity brand apply --fix-head Migrate an older app: assets + splice the
|
|
40849
|
+
share/SEO tags into src/index.html
|
|
40850
|
+
|
|
40851
|
+
Assets are generated server-side into src/images/ (+ src/manifest.webmanifest)
|
|
40852
|
+
and are versioned like any other file - rollback restores previous ones.`);
|
|
40853
|
+
brandCommand.command("show", { isDefault: true }).description("Show the stored brand and the resolved render spec").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Brand", async () => {
|
|
40854
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
40855
|
+
const res = await get(`/projects/${config.projectGuid}/brand`);
|
|
40856
|
+
if (opts.json) {
|
|
40857
|
+
console.log(JSON.stringify(res.data));
|
|
40858
|
+
return;
|
|
40859
|
+
}
|
|
40860
|
+
printBrand(res.data);
|
|
40861
|
+
console.log(muted(`
|
|
40862
|
+
Assets: ${(res.data.assets ?? []).join(", ")}`));
|
|
40863
|
+
console.log(muted(`Change it: gipity brand set --emoji \u{1F3A8} | --glyph A | --color "#RRGGBB" | --tagline "..."`));
|
|
40864
|
+
}));
|
|
40865
|
+
brandCommand.command("set").description("Change brand fields and regenerate all assets").option("--emoji <emoji>", "Emoji glyph for the icon + share card (e.g. \u{1F98D})").option("--glyph <char>", "Letter/digit glyph (alias of --emoji, any single character)").option("--color <hex>", "Accent color, #RRGGBB").option("--name <name>", "Display name override (og/share title stays the page title)").option("--tagline <text>", "One-line subtitle on the share card (max 120 chars)").option("--fix-head", "Also splice the share/SEO head block into src/index.html (for apps installed before templates shipped it)").option("--no-regenerate", "Store the fields without re-rendering assets").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Brand", async () => {
|
|
40866
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
40867
|
+
const body = {};
|
|
40868
|
+
const glyph = opts.emoji ?? opts.glyph;
|
|
40869
|
+
if (glyph) body.icon_glyph = glyph;
|
|
40870
|
+
if (opts.color) body.primary_color = opts.color;
|
|
40871
|
+
if (opts.name) body.app_name = opts.name;
|
|
40872
|
+
if (opts.tagline) body.tagline = opts.tagline;
|
|
40873
|
+
if (opts.regenerate === false) body.regenerate = false;
|
|
40874
|
+
if (opts.fixHead) body.fix_head = true;
|
|
40875
|
+
if (!glyph && !opts.color && !opts.name && !opts.tagline) {
|
|
40876
|
+
console.error("Nothing to set - pass --emoji, --glyph, --color, --name, or --tagline. (gipity brand set --help; use gipity brand apply to just re-render)");
|
|
40877
|
+
process.exitCode = 1;
|
|
40878
|
+
return;
|
|
40879
|
+
}
|
|
40880
|
+
const res = await post(`/projects/${config.projectGuid}/brand`, body);
|
|
40881
|
+
if (opts.json) {
|
|
40882
|
+
console.log(JSON.stringify(res.data));
|
|
40883
|
+
return;
|
|
40884
|
+
}
|
|
40885
|
+
printBrand(res.data);
|
|
40886
|
+
reportFiles(res.data.files ?? []);
|
|
40887
|
+
}));
|
|
40888
|
+
brandCommand.command("apply").description("Re-render all brand assets from the stored brand (no field changes)").option("--fix-head", "Also splice the share/SEO head block into src/index.html (for apps installed before templates shipped it)").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Brand", async () => {
|
|
40889
|
+
const { config } = await resolveProjectContext({ projectOverride: opts.project });
|
|
40890
|
+
const body = opts.fixHead ? { fix_head: true } : {};
|
|
40891
|
+
const res = await post(`/projects/${config.projectGuid}/brand`, body);
|
|
40892
|
+
if (opts.json) {
|
|
40893
|
+
console.log(JSON.stringify(res.data));
|
|
40894
|
+
return;
|
|
40895
|
+
}
|
|
40896
|
+
reportFiles(res.data.files ?? []);
|
|
40897
|
+
}));
|
|
40898
|
+
function reportFiles(files) {
|
|
40899
|
+
if (!files.length) {
|
|
40900
|
+
console.log(muted("\nStored (assets not regenerated - run gipity brand apply)."));
|
|
40901
|
+
return;
|
|
40902
|
+
}
|
|
40903
|
+
console.log(success(`
|
|
40904
|
+
\u2713 Regenerated ${files.length} asset${files.length === 1 ? "" : "s"}.`));
|
|
40905
|
+
if (files.includes("src/index.html")) {
|
|
40906
|
+
console.log(success("\u2713 src/index.html head updated (page title/description preserved)."));
|
|
40907
|
+
}
|
|
40908
|
+
console.log(muted(` ${DEPLOY_HINT}`));
|
|
40909
|
+
}
|
|
40910
|
+
|
|
40417
40911
|
// src/commands/remove.ts
|
|
40418
40912
|
init_api();
|
|
40419
40913
|
init_config();
|
|
@@ -40428,7 +40922,7 @@ var removeCommand = new Command("remove").description("Remove a kit").argument("
|
|
|
40428
40922
|
}
|
|
40429
40923
|
}
|
|
40430
40924
|
const doRemove = () => post(`/projects/${config.projectGuid}/remove`, { name: kit });
|
|
40431
|
-
const res = opts.json ? await doRemove() : await withSpinner("Removing
|
|
40925
|
+
const res = opts.json ? await doRemove() : await withSpinner("Removing...", doRemove, { done: null });
|
|
40432
40926
|
const data = res.data;
|
|
40433
40927
|
const syncResult = await sync({
|
|
40434
40928
|
interactive: false,
|
|
@@ -40478,7 +40972,7 @@ function dispositionFilename(headers) {
|
|
|
40478
40972
|
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 () => {
|
|
40479
40973
|
const config = requireConfig();
|
|
40480
40974
|
const doExport = () => downloadWithHeaders(`/projects/${config.projectGuid}/export`);
|
|
40481
|
-
const { buffer, headers } = opts.json ? await doExport() : await withSpinner("Exporting
|
|
40975
|
+
const { buffer, headers } = opts.json ? await doExport() : await withSpinner("Exporting...", doExport, { done: null });
|
|
40482
40976
|
const filename = dispositionFilename(headers) ?? `${config.projectSlug}.gip`;
|
|
40483
40977
|
let dest = path2.resolve(output ?? filename);
|
|
40484
40978
|
if (output && fs3.existsSync(dest) && fs3.statSync(dest).isDirectory()) {
|
|
@@ -40486,14 +40980,14 @@ var saveCommand = new Command("save").description("Save this app as a portable .
|
|
|
40486
40980
|
}
|
|
40487
40981
|
fs3.mkdirSync(path2.dirname(dest), { recursive: true });
|
|
40488
40982
|
fs3.writeFileSync(dest, buffer);
|
|
40489
|
-
const
|
|
40983
|
+
const sha256 = createHash4("sha256").update(buffer).digest("hex");
|
|
40490
40984
|
const skipped = Number(headers.get("x-gip-skipped") ?? 0);
|
|
40491
40985
|
const entries = zipEntryCount(buffer);
|
|
40492
40986
|
if (opts.json) {
|
|
40493
40987
|
console.log(JSON.stringify({
|
|
40494
40988
|
path: dest,
|
|
40495
40989
|
bytes: buffer.length,
|
|
40496
|
-
sha256
|
|
40990
|
+
sha256,
|
|
40497
40991
|
...entries !== null ? { entries } : {},
|
|
40498
40992
|
skipped
|
|
40499
40993
|
}));
|
|
@@ -40503,7 +40997,7 @@ var saveCommand = new Command("save").description("Save this app as a portable .
|
|
|
40503
40997
|
console.log(` Path: ${dest}`);
|
|
40504
40998
|
console.log(` Size: ${formatBytes(buffer.length)}`);
|
|
40505
40999
|
if (entries !== null) console.log(` Entries: ${entries} (including the bundle manifest)`);
|
|
40506
|
-
console.log(` SHA-256: ${
|
|
41000
|
+
console.log(` SHA-256: ${sha256}`);
|
|
40507
41001
|
if (skipped > 0) {
|
|
40508
41002
|
console.log("");
|
|
40509
41003
|
console.warn(warning(`${skipped} file(s) were skipped (unreadable or over 100 MB) - this bundle is partial.`));
|
|
@@ -40620,7 +41114,7 @@ var loadCommand = new Command("load").description("Create a new app from a .gip
|
|
|
40620
41114
|
};
|
|
40621
41115
|
}
|
|
40622
41116
|
if (opts.inspect) {
|
|
40623
|
-
const res = opts.json ? await doInspect() : await withSpinner("Inspecting
|
|
41117
|
+
const res = opts.json ? await doInspect() : await withSpinner("Inspecting...", doInspect, { done: null });
|
|
40624
41118
|
if (opts.json) {
|
|
40625
41119
|
console.log(JSON.stringify(res.data));
|
|
40626
41120
|
return;
|
|
@@ -40628,7 +41122,7 @@ var loadCommand = new Command("load").description("Create a new app from a .gip
|
|
|
40628
41122
|
printInspect(res.data, source);
|
|
40629
41123
|
return;
|
|
40630
41124
|
}
|
|
40631
|
-
const { data, partial } = opts.json ? await doImport() : await withSpinner("Importing
|
|
41125
|
+
const { data, partial } = opts.json ? await doImport() : await withSpinner("Importing...", doImport, { done: null });
|
|
40632
41126
|
const project = data.project;
|
|
40633
41127
|
const dir = path3.join(getProjectsRoot(), project.slug);
|
|
40634
41128
|
fs4.mkdirSync(dir, { recursive: true });
|
|
@@ -41029,7 +41523,7 @@ ${error(`\u26A0 ${problems} error/crash line(s) flagged above`)}`
|
|
|
41029
41523
|
);
|
|
41030
41524
|
if (problems > 0) process.exitCode = 1;
|
|
41031
41525
|
}
|
|
41032
|
-
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,
|
|
41526
|
+
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", `
|
|
41033
41527
|
Examples:
|
|
41034
41528
|
# Passive: load in 3 staggered clients, flag console errors
|
|
41035
41529
|
gipity page test "https://dev.gipity.ai/me/app/" --clients 3 --stagger 8
|
|
@@ -41087,9 +41581,6 @@ function looksLikeHtml(body) {
|
|
|
41087
41581
|
const head = body.slice(0, 300).toLowerCase();
|
|
41088
41582
|
return /<!doctype html|<html[\s>]/.test(head);
|
|
41089
41583
|
}
|
|
41090
|
-
function sha256(s) {
|
|
41091
|
-
return createHash5("sha256").update(s).digest("hex");
|
|
41092
|
-
}
|
|
41093
41584
|
function baseWithSlash(url) {
|
|
41094
41585
|
return url.endsWith("/") ? url : url + "/";
|
|
41095
41586
|
}
|
|
@@ -41099,8 +41590,14 @@ function resolveUrl(base, path5) {
|
|
|
41099
41590
|
async function fetchRaw(url) {
|
|
41100
41591
|
try {
|
|
41101
41592
|
const res = await fetch(url, { redirect: "follow" });
|
|
41102
|
-
const
|
|
41103
|
-
return {
|
|
41593
|
+
const buf = Buffer.from(await res.arrayBuffer());
|
|
41594
|
+
return {
|
|
41595
|
+
status: res.status,
|
|
41596
|
+
contentType: res.headers.get("content-type"),
|
|
41597
|
+
body: buf.toString("utf-8"),
|
|
41598
|
+
bytes: buf.length,
|
|
41599
|
+
sha256: createHash5("sha256").update(buf).digest("hex")
|
|
41600
|
+
};
|
|
41104
41601
|
} catch {
|
|
41105
41602
|
return null;
|
|
41106
41603
|
}
|
|
@@ -41112,17 +41609,16 @@ async function probeShell(base) {
|
|
|
41112
41609
|
return {
|
|
41113
41610
|
served: r.status >= 200 && r.status < 300,
|
|
41114
41611
|
status: r.status,
|
|
41115
|
-
sha256:
|
|
41612
|
+
sha256: r.sha256,
|
|
41116
41613
|
isHtml: looksLikeHtml(r.body)
|
|
41117
41614
|
};
|
|
41118
41615
|
}
|
|
41119
41616
|
function classify(path5, r, shell) {
|
|
41120
41617
|
if (!r) return { status: null, contentType: null, bytes: 0, verdict: "MISSING", detail: "fetch failed (host unreachable)" };
|
|
41121
|
-
const
|
|
41122
|
-
const base = { status: r.status, contentType: r.contentType, bytes };
|
|
41618
|
+
const base = { status: r.status, contentType: r.contentType, bytes: r.bytes };
|
|
41123
41619
|
if (r.status >= 400) return { ...base, verdict: "MISSING", detail: `HTTP ${r.status}` };
|
|
41124
41620
|
const expect = expectedType(path5);
|
|
41125
|
-
if (shell.served && shell.sha256 &&
|
|
41621
|
+
if (shell.served && shell.sha256 && r.sha256 === shell.sha256) {
|
|
41126
41622
|
return { ...base, verdict: "MISSING", detail: `HTTP ${r.status} but served the SPA shell (file not deployed)` };
|
|
41127
41623
|
}
|
|
41128
41624
|
if (expect && expect !== "html" && looksLikeHtml(r.body)) {
|
|
@@ -41138,7 +41634,7 @@ var TAG = {
|
|
|
41138
41634
|
"MISSING": error("MISSING "),
|
|
41139
41635
|
"WRONG-TYPE": warning("WRONG-TYPE")
|
|
41140
41636
|
};
|
|
41141
|
-
var pageFetchCommand = new Command("fetch").description("Verify deployed non-rendered files (llms.txt, AGENTS.md, robots.txt, served JSON
|
|
41637
|
+
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 () => {
|
|
41142
41638
|
const shell = await probeShell(url);
|
|
41143
41639
|
const results = [];
|
|
41144
41640
|
for (const p of paths) {
|
|
@@ -41184,10 +41680,31 @@ pageCommand.action(() => {
|
|
|
41184
41680
|
|
|
41185
41681
|
// src/commands/records.ts
|
|
41186
41682
|
init_api();
|
|
41683
|
+
init_auth();
|
|
41187
41684
|
init_config();
|
|
41188
41685
|
init_colors();
|
|
41189
41686
|
init_utils();
|
|
41190
41687
|
var recordsCommand = new Command("records").description("Manage app records (Gipity Records - validated CRUD with audit history)");
|
|
41688
|
+
var ANON_FLAG = "--anon";
|
|
41689
|
+
var ANON_HELP = "Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account";
|
|
41690
|
+
async function recordsHttp(opts) {
|
|
41691
|
+
const config = requireConfig();
|
|
41692
|
+
if (!opts.anon) {
|
|
41693
|
+
const who = getAuth()?.email;
|
|
41694
|
+
console.error(muted(`Auth: calling as ${who ?? "your signed-in account"} (the owner persona; use ${ANON_FLAG} for the public visitor path)`));
|
|
41695
|
+
return { guid: config.projectGuid, get, post, put, patch, del };
|
|
41696
|
+
}
|
|
41697
|
+
console.error(muted("Auth: anonymous visitor (the public path a signed-out user hits)"));
|
|
41698
|
+
const headers = await mintAppToken(config.projectGuid);
|
|
41699
|
+
return {
|
|
41700
|
+
guid: config.projectGuid,
|
|
41701
|
+
get: (path5) => publicRequest("GET", path5, void 0, headers),
|
|
41702
|
+
post: (path5, body) => publicRequest("POST", path5, body, headers),
|
|
41703
|
+
put: (path5, body) => publicRequest("PUT", path5, body, headers),
|
|
41704
|
+
patch: (path5, body) => publicRequest("PATCH", path5, body, headers),
|
|
41705
|
+
del: (path5, body) => publicRequest("DELETE", path5, body, headers)
|
|
41706
|
+
};
|
|
41707
|
+
}
|
|
41191
41708
|
recordsCommand.command("list").description("List record tables").option("--json", "Output as JSON").action((opts) => run("List", async () => {
|
|
41192
41709
|
const config = requireConfig();
|
|
41193
41710
|
const res = await get(`/api/${config.projectGuid}/records-config`);
|
|
@@ -41219,16 +41736,16 @@ recordsCommand.command("config <table>").description("Show or set a table's Reco
|
|
|
41219
41736
|
res.data
|
|
41220
41737
|
);
|
|
41221
41738
|
}));
|
|
41222
|
-
recordsCommand.command("query <table>").description("List records").option("--filter <filter>", 'Filter string (e.g., "status:eq:active")').option("--sort <sort>", 'Sort string (e.g., "created_at:desc")').option("--limit <n>", "Max rows", "20").option("--offset <n>", "Offset", "0").option("--fields <fields>", "Comma-separated column names").option("--json", "Output as JSON").action((table, opts) => run("Query", async () => {
|
|
41223
|
-
const
|
|
41739
|
+
recordsCommand.command("query <table>").description("List records").option("--filter <filter>", 'Filter string (e.g., "status:eq:active")').option("--sort <sort>", 'Sort string (e.g., "created_at:desc")').option("--limit <n>", "Max rows", "20").option("--offset <n>", "Offset", "0").option("--fields <fields>", "Comma-separated column names").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, opts) => run("Query", async () => {
|
|
41740
|
+
const api = await recordsHttp(opts);
|
|
41224
41741
|
const params = new URLSearchParams();
|
|
41225
41742
|
if (opts.filter) params.set("filter", opts.filter);
|
|
41226
41743
|
if (opts.sort) params.set("sort", opts.sort);
|
|
41227
41744
|
params.set("limit", opts.limit);
|
|
41228
41745
|
params.set("offset", opts.offset);
|
|
41229
41746
|
if (opts.fields) params.set("fields", opts.fields);
|
|
41230
|
-
const res = await get(
|
|
41231
|
-
`/api/${
|
|
41747
|
+
const res = await api.get(
|
|
41748
|
+
`/api/${api.guid}/records/${table}?${params}`
|
|
41232
41749
|
);
|
|
41233
41750
|
if (opts.json) {
|
|
41234
41751
|
console.log(JSON.stringify(res));
|
|
@@ -41241,41 +41758,42 @@ recordsCommand.command("query <table>").description("List records").option("--fi
|
|
|
41241
41758
|
console.log("");
|
|
41242
41759
|
}
|
|
41243
41760
|
}));
|
|
41244
|
-
recordsCommand.command("get <table> <id>").description("Get a record").option("--json", "Output as JSON").action((table, id2, opts) => run("Get", async () => {
|
|
41245
|
-
const
|
|
41246
|
-
const res = await get(`/api/${
|
|
41761
|
+
recordsCommand.command("get <table> <id>").description("Get a record").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("Get", async () => {
|
|
41762
|
+
const api = await recordsHttp(opts);
|
|
41763
|
+
const res = await api.get(`/api/${api.guid}/records/${table}/${id2}`);
|
|
41247
41764
|
console.log(opts.json ? JSON.stringify(res.data) : JSON.stringify(res.data, null, 2));
|
|
41248
41765
|
}));
|
|
41249
|
-
recordsCommand.command("history <table>
|
|
41250
|
-
const
|
|
41251
|
-
const
|
|
41252
|
-
|
|
41766
|
+
recordsCommand.command("history <table> [id]").description("Audit history (who/what changed it, with English summaries). Omit <id> for the whole table's feed.").option("--limit <n>", "Max events", "20").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("History", async () => {
|
|
41767
|
+
const api = await recordsHttp(opts);
|
|
41768
|
+
const scope = id2 ? `${table}/${encodeURIComponent(id2)}` : table;
|
|
41769
|
+
const res = await api.get(
|
|
41770
|
+
`/api/${api.guid}/records/${scope}/history?limit=${encodeURIComponent(opts.limit)}`
|
|
41253
41771
|
);
|
|
41254
|
-
printList(res.data, opts, "No history for this record."
|
|
41772
|
+
printList(res.data, opts, id2 ? "No history for this record." : `No history for "${table}".`, (e) => {
|
|
41255
41773
|
const summary = e.detail?.summary || `${e.action} ${e.entity_type} ${e.entity_id}`;
|
|
41256
41774
|
return `${muted(e.created_at)} ${bold(e.source || "-")} ${summary}`;
|
|
41257
41775
|
});
|
|
41258
41776
|
}));
|
|
41259
|
-
recordsCommand.command("create <table>").description("Create a record").requiredOption("--data <json>", "JSON object with field values").option("--json", "Output as JSON").action((table, opts) => run("Create", async () => {
|
|
41260
|
-
const
|
|
41777
|
+
recordsCommand.command("create <table>").description("Create a record").requiredOption("--data <json>", "JSON object with field values").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, opts) => run("Create", async () => {
|
|
41778
|
+
const api = await recordsHttp(opts);
|
|
41261
41779
|
const data = JSON.parse(opts.data);
|
|
41262
|
-
const res = await post(`/api/${
|
|
41780
|
+
const res = await api.post(`/api/${api.guid}/records/${table}`, data);
|
|
41263
41781
|
printResult(`Created: ${JSON.stringify(res.data)}`, opts, res.data);
|
|
41264
41782
|
}));
|
|
41265
|
-
recordsCommand.command("update <table> <id>").description("Update a record").requiredOption("--data <json>", "JSON object with fields to update").option("--json", "Output as JSON").action((table, id2, opts) => run("Update", async () => {
|
|
41266
|
-
const
|
|
41783
|
+
recordsCommand.command("update <table> <id>").description("Update a record").requiredOption("--data <json>", "JSON object with fields to update").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("Update", async () => {
|
|
41784
|
+
const api = await recordsHttp(opts);
|
|
41267
41785
|
const data = JSON.parse(opts.data);
|
|
41268
|
-
const res = await put(`/api/${
|
|
41786
|
+
const res = await api.put(`/api/${api.guid}/records/${table}/${id2}`, data);
|
|
41269
41787
|
printResult(`Updated: ${JSON.stringify(res.data)}`, opts, res.data);
|
|
41270
41788
|
}));
|
|
41271
|
-
recordsCommand.command("delete <table> <id>").description("Delete a record").action((table, id2) => run("Delete", async () => {
|
|
41789
|
+
recordsCommand.command("delete <table> <id>").description("Delete a record").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("Delete", async () => {
|
|
41272
41790
|
if (!await confirm(`Delete record ${id2} from "${table}"?`)) {
|
|
41273
|
-
|
|
41791
|
+
printResult("Cancelled.", opts, { table, id: id2, deleted: false, cancelled: true });
|
|
41274
41792
|
return;
|
|
41275
41793
|
}
|
|
41276
|
-
const
|
|
41277
|
-
await del(`/api/${
|
|
41278
|
-
printResult("Deleted.", {
|
|
41794
|
+
const api = await recordsHttp(opts);
|
|
41795
|
+
const res = await api.del(`/api/${api.guid}/records/${table}/${id2}`);
|
|
41796
|
+
printResult("Deleted.", opts, res?.data ?? { table, id: id2, deleted: true });
|
|
41279
41797
|
}));
|
|
41280
41798
|
|
|
41281
41799
|
// src/commands/fn.ts
|
|
@@ -41320,17 +41838,11 @@ ${error(`error: ${log3.error_message}`)}`;
|
|
|
41320
41838
|
return line;
|
|
41321
41839
|
});
|
|
41322
41840
|
}));
|
|
41323
|
-
async function callAnon(
|
|
41324
|
-
let appToken;
|
|
41325
|
-
try {
|
|
41326
|
-
const minted = await publicPost("/api/token", { app: projectGuid });
|
|
41327
|
-
appToken = minted.data.token;
|
|
41328
|
-
} catch {
|
|
41329
|
-
}
|
|
41841
|
+
async function callAnon(projectGuid2, name, body) {
|
|
41330
41842
|
return publicPost(
|
|
41331
|
-
`/api/${
|
|
41843
|
+
`/api/${projectGuid2}/fn/${encodeURIComponent(name)}`,
|
|
41332
41844
|
body,
|
|
41333
|
-
|
|
41845
|
+
await mintAppToken(projectGuid2)
|
|
41334
41846
|
);
|
|
41335
41847
|
}
|
|
41336
41848
|
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 () => {
|
|
@@ -41383,7 +41895,7 @@ var SERVICES = [
|
|
|
41383
41895
|
{ name: "location/ip", method: "POST", desc: "IP geolocation ({ ip? })" },
|
|
41384
41896
|
{ name: "location/geocode", method: "POST", desc: "Reverse geocode ({ lat, lon })" }
|
|
41385
41897
|
];
|
|
41386
|
-
var serviceCommand = new Command("service").description("Call an app service (llm, tts, image, transcribe, ...)").addHelpText("after", "\nCall a Gipity app service: LLM, image, music, TTS, and more
|
|
41898
|
+
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");
|
|
41387
41899
|
serviceCommand.command("list").description("List callable app services").option("--json", "Output the service list as JSON").action((opts) => run("Services", async () => {
|
|
41388
41900
|
requireConfig();
|
|
41389
41901
|
if (opts.json) {
|
|
@@ -41429,7 +41941,7 @@ secretsCommand.command("list").alias("ls").description("List secret names (never
|
|
|
41429
41941
|
}
|
|
41430
41942
|
console.log(bold(`${res.data.length} ${scope} secret${res.data.length === 1 ? "" : "s"}:`));
|
|
41431
41943
|
for (const s of res.data) {
|
|
41432
|
-
const masked = s.preview ? muted(
|
|
41944
|
+
const masked = s.preview ? muted(`...${s.preview}`) : muted("(hidden)");
|
|
41433
41945
|
console.log(` ${s.name} ${masked} ${muted(`updated ${new Date(s.updated_at).toLocaleDateString()}`)}`);
|
|
41434
41946
|
}
|
|
41435
41947
|
}));
|
|
@@ -41738,7 +42250,11 @@ jobCommand.command("status <runGuid>").description("Show status of a job run").o
|
|
|
41738
42250
|
const r = res.data;
|
|
41739
42251
|
const statusColor = r.status === "success" ? success : r.status === "failed" ? error : muted;
|
|
41740
42252
|
console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
|
|
41741
|
-
if (r.progress_pct != null)
|
|
42253
|
+
if (r.progress_pct != null) {
|
|
42254
|
+
const prog = `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}`;
|
|
42255
|
+
const failedish = r.status === "failed" || r.status === "cancelled" || r.status === "canceled" || r.status === "error";
|
|
42256
|
+
console.log(failedish ? `last progress before ${r.status}: ${prog}` : `progress: ${prog}`);
|
|
42257
|
+
}
|
|
41742
42258
|
if (r.duration_ms != null) console.log(`duration: ${r.duration_ms}ms`);
|
|
41743
42259
|
if (r.error) console.log(`${error("error:")} ${r.error}`);
|
|
41744
42260
|
if (r.output) console.log(`output: ${JSON.stringify(r.output)}`);
|
|
@@ -41758,7 +42274,7 @@ jobCommand.command("wait <runGuid>").description("Block until a job run finishes
|
|
|
41758
42274
|
if (!opts.json) {
|
|
41759
42275
|
const prog = r.progress_pct != null ? `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}` : r.status;
|
|
41760
42276
|
if (prog !== lastProgress) {
|
|
41761
|
-
console.error(muted(
|
|
42277
|
+
console.error(muted(`... ${prog}`));
|
|
41762
42278
|
lastProgress = prog;
|
|
41763
42279
|
}
|
|
41764
42280
|
}
|
|
@@ -41829,7 +42345,7 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
|
|
|
41829
42345
|
res = await fetch(url, { headers, signal: controller.signal });
|
|
41830
42346
|
} catch (e) {
|
|
41831
42347
|
if (peekedOut) {
|
|
41832
|
-
console.error(muted(
|
|
42348
|
+
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.`));
|
|
41833
42349
|
return;
|
|
41834
42350
|
}
|
|
41835
42351
|
throw e;
|
|
@@ -41849,7 +42365,7 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
|
|
|
41849
42365
|
chunk = await reader.read();
|
|
41850
42366
|
} catch (e) {
|
|
41851
42367
|
if (peekedOut) {
|
|
41852
|
-
console.error(muted(
|
|
42368
|
+
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.`));
|
|
41853
42369
|
return;
|
|
41854
42370
|
}
|
|
41855
42371
|
throw e;
|
|
@@ -41912,11 +42428,11 @@ jobCommand.command("cancel <runGuid>").description("Cancel a queued or running j
|
|
|
41912
42428
|
}
|
|
41913
42429
|
}));
|
|
41914
42430
|
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) => {
|
|
41915
|
-
const { existsSync: existsSync24, statSync:
|
|
41916
|
-
const { resolve: resolve19, join:
|
|
42431
|
+
const { existsSync: existsSync24, statSync: statSync12 } = await import("node:fs");
|
|
42432
|
+
const { resolve: resolve19, join: join27 } = await import("node:path");
|
|
41917
42433
|
const { spawnCommand: spawnCommand2, spawnSyncCommand: spawnSyncCommand2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
|
|
41918
42434
|
const jobDir = resolve19(process.cwd(), "jobs", name);
|
|
41919
|
-
if (!existsSync24(jobDir) || !
|
|
42435
|
+
if (!existsSync24(jobDir) || !statSync12(jobDir).isDirectory()) {
|
|
41920
42436
|
console.error(error(`jobs/${name}/ not found in current directory`));
|
|
41921
42437
|
process.exit(1);
|
|
41922
42438
|
}
|
|
@@ -41937,7 +42453,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
41937
42453
|
},
|
|
41938
42454
|
{ file: "main.sh", runtime: "bash", entry: "bash" }
|
|
41939
42455
|
];
|
|
41940
|
-
const picked = variants.find((v7) => existsSync24(
|
|
42456
|
+
const picked = variants.find((v7) => existsSync24(join27(jobDir, v7.file)));
|
|
41941
42457
|
if (!picked) {
|
|
41942
42458
|
console.error(error(`No main.py / main.js / main.sh found under jobs/${name}/`));
|
|
41943
42459
|
process.exit(1);
|
|
@@ -41962,7 +42478,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
|
|
|
41962
42478
|
"-v",
|
|
41963
42479
|
`${jobDir}:/work`
|
|
41964
42480
|
];
|
|
41965
|
-
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync24(
|
|
42481
|
+
const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync24(join27(jobDir, picked.depsFile));
|
|
41966
42482
|
const handlerCmd = `${picked.entry} /work/${picked.file}`;
|
|
41967
42483
|
let shellCmd;
|
|
41968
42484
|
if (needsDeps && picked.installCmd) {
|
|
@@ -42138,7 +42654,7 @@ init_api();
|
|
|
42138
42654
|
init_config();
|
|
42139
42655
|
init_colors();
|
|
42140
42656
|
import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, readFileSync as readFileSync19 } from "fs";
|
|
42141
|
-
import { resolve as resolvePath2, dirname as
|
|
42657
|
+
import { resolve as resolvePath2, dirname as dirname11, relative as relative5, isAbsolute, basename as basename6 } from "path";
|
|
42142
42658
|
|
|
42143
42659
|
// src/provider-docs.ts
|
|
42144
42660
|
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`;
|
|
@@ -42199,11 +42715,11 @@ function noteFormatMismatch(filename, buf) {
|
|
|
42199
42715
|
const asked = dot > 0 ? filename.slice(dot + 1).toLowerCase() : "";
|
|
42200
42716
|
if (!asked || canonicalExt(asked) === actual) return;
|
|
42201
42717
|
console.error(muted(
|
|
42202
|
-
`Note: ${
|
|
42718
|
+
`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.`
|
|
42203
42719
|
));
|
|
42204
42720
|
}
|
|
42205
42721
|
function ensureOutputDir(output) {
|
|
42206
|
-
const dir =
|
|
42722
|
+
const dir = dirname11(resolvePath2(output));
|
|
42207
42723
|
try {
|
|
42208
42724
|
mkdirSync13(dir, { recursive: true });
|
|
42209
42725
|
} catch (err) {
|
|
@@ -42213,7 +42729,7 @@ function ensureOutputDir(output) {
|
|
|
42213
42729
|
async function pushGenerated(savedPath) {
|
|
42214
42730
|
const configPath = getConfigPath();
|
|
42215
42731
|
if (!configPath) return;
|
|
42216
|
-
const rel2 = relative5(
|
|
42732
|
+
const rel2 = relative5(dirname11(configPath), savedPath);
|
|
42217
42733
|
if (rel2.startsWith("..") || isAbsolute(rel2)) return;
|
|
42218
42734
|
try {
|
|
42219
42735
|
await pushFile(savedPath);
|
|
@@ -42272,7 +42788,7 @@ Examples:
|
|
|
42272
42788
|
seed: Number.isFinite(opts.seed) ? opts.seed : void 0,
|
|
42273
42789
|
input_images: inputImages
|
|
42274
42790
|
});
|
|
42275
|
-
const verb = inputImages ? "Editing image
|
|
42791
|
+
const verb = inputImages ? "Editing image..." : "Generating image...";
|
|
42276
42792
|
const result = opts.json ? await doGenerate() : await withSpinner(verb, doGenerate, { done: null });
|
|
42277
42793
|
const ext = result.content_type.includes("png") ? "png" : "jpg";
|
|
42278
42794
|
const filename = opts.output || `generated.${ext}`;
|
|
@@ -42318,7 +42834,7 @@ Examples:
|
|
|
42318
42834
|
aspect_ratio: opts.aspect,
|
|
42319
42835
|
resolution: opts.resolution
|
|
42320
42836
|
});
|
|
42321
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating video
|
|
42837
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating video...", doGenerate, { done: null });
|
|
42322
42838
|
const filename = opts.output || "generated.mp4";
|
|
42323
42839
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42324
42840
|
if (opts.json) {
|
|
@@ -42366,7 +42882,7 @@ Examples:
|
|
|
42366
42882
|
language: opts.language,
|
|
42367
42883
|
speakers
|
|
42368
42884
|
});
|
|
42369
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating speech
|
|
42885
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating speech...", doGenerate, { done: null });
|
|
42370
42886
|
const filename = opts.output || "speech.mp3";
|
|
42371
42887
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42372
42888
|
if (opts.json) {
|
|
@@ -42405,7 +42921,7 @@ Examples:
|
|
|
42405
42921
|
model: opts.model,
|
|
42406
42922
|
instrumental: !opts.vocals
|
|
42407
42923
|
});
|
|
42408
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating music
|
|
42924
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating music...", doGenerate, { done: null });
|
|
42409
42925
|
const filename = opts.output || "music.mp3";
|
|
42410
42926
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42411
42927
|
if (opts.json) {
|
|
@@ -42443,7 +42959,7 @@ Examples:
|
|
|
42443
42959
|
duration_seconds: opts.duration,
|
|
42444
42960
|
prompt_influence: opts.influence
|
|
42445
42961
|
});
|
|
42446
|
-
const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect
|
|
42962
|
+
const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect...", doGenerate, { done: null });
|
|
42447
42963
|
const filename = opts.output || "sound.mp3";
|
|
42448
42964
|
const savedPath = await downloadFile(result.url, filename, !!opts.output);
|
|
42449
42965
|
if (opts.json) {
|
|
@@ -42465,12 +42981,12 @@ init_api();
|
|
|
42465
42981
|
init_config();
|
|
42466
42982
|
init_colors();
|
|
42467
42983
|
import { existsSync as existsSync15, readFileSync as readFileSync20 } from "fs";
|
|
42468
|
-
import { join as
|
|
42984
|
+
import { join as join18 } from "path";
|
|
42469
42985
|
function readInstalledKitReadme(name) {
|
|
42470
42986
|
const root = getProjectRoot() ?? process.cwd();
|
|
42471
|
-
const kitDir =
|
|
42472
|
-
if (!existsSync15(
|
|
42473
|
-
const readme =
|
|
42987
|
+
const kitDir = join18(root, "src", "packages", name);
|
|
42988
|
+
if (!existsSync15(join18(kitDir, "package.json"))) return null;
|
|
42989
|
+
const readme = join18(kitDir, "README.md");
|
|
42474
42990
|
return existsSync15(readme) ? readFileSync20(readme, "utf-8") : null;
|
|
42475
42991
|
}
|
|
42476
42992
|
var skillCommand = new Command("skill").description("Task docs - read the matching skill before building");
|
|
@@ -42645,14 +43161,14 @@ var domainCommand = new Command("domain").description("Manage custom domains").a
|
|
|
42645
43161
|
|
|
42646
43162
|
// src/commands/realtime.ts
|
|
42647
43163
|
import { existsSync as existsSync16 } from "fs";
|
|
42648
|
-
import { dirname as
|
|
43164
|
+
import { dirname as dirname12, resolve as resolve14 } from "path";
|
|
42649
43165
|
init_api();
|
|
42650
43166
|
init_config();
|
|
42651
43167
|
init_colors();
|
|
42652
43168
|
function hasDeployManifest() {
|
|
42653
43169
|
const cfgPath = getConfigPath();
|
|
42654
43170
|
if (!cfgPath) return false;
|
|
42655
|
-
return existsSync16(resolve14(
|
|
43171
|
+
return existsSync16(resolve14(dirname12(cfgPath), "gipity.yaml"));
|
|
42656
43172
|
}
|
|
42657
43173
|
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 () => {
|
|
42658
43174
|
const config = requireConfig();
|
|
@@ -42763,7 +43279,7 @@ function undeployedFunctionsFromFailures(results) {
|
|
|
42763
43279
|
return [...names];
|
|
42764
43280
|
}
|
|
42765
43281
|
var LONG_RUN_MS = 6e4;
|
|
42766
|
-
async function pollTestStatus(
|
|
43282
|
+
async function pollTestStatus(projectGuid2, runGuid, opts) {
|
|
42767
43283
|
const startTime = Date.now();
|
|
42768
43284
|
const getPollInterval = () => {
|
|
42769
43285
|
const elapsed = Date.now() - startTime;
|
|
@@ -42785,7 +43301,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
42785
43301
|
if (Date.now() - startTime > POLL_HARD_CAP_MS) {
|
|
42786
43302
|
throw new Error(`Test run ${runGuid} still not finished after ${Math.round(POLL_HARD_CAP_MS / 6e4)} minutes - giving up on the poll. Check it later with \`gipity test status ${runGuid}\` or re-run.`);
|
|
42787
43303
|
}
|
|
42788
|
-
const res = await get(`/projects/${
|
|
43304
|
+
const res = await get(`/projects/${projectGuid2}/test/status/${runGuid}`);
|
|
42789
43305
|
const data = res.data;
|
|
42790
43306
|
if (!opts.json && data.results.length > lastResultCount) {
|
|
42791
43307
|
const newResults = data.results.slice(lastResultCount);
|
|
@@ -42830,7 +43346,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
|
|
|
42830
43346
|
const elapsed = Math.round((now - startTime) / 1e3);
|
|
42831
43347
|
const progress = data.totalFiles === 0 ? "starting up" : `${data.completedFiles}/${data.totalFiles} files`;
|
|
42832
43348
|
const tally = data.passed + data.failed > 0 ? ` (${data.passed} passed${data.failed > 0 ? `, ${data.failed} failed` : ""} so far)` : "";
|
|
42833
|
-
console.log(muted(`
|
|
43349
|
+
console.log(muted(` ... still running - ${progress}${tally}, ${elapsed}s elapsed`));
|
|
42834
43350
|
if (now - startTime >= LONG_RUN_MS && !longRunHintShown) {
|
|
42835
43351
|
longRunHintShown = true;
|
|
42836
43352
|
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>`."));
|
|
@@ -43061,21 +43577,21 @@ var locationCommand = new Command("location").description("Show location").argum
|
|
|
43061
43577
|
}));
|
|
43062
43578
|
|
|
43063
43579
|
// src/commands/doctor.ts
|
|
43064
|
-
import { existsSync as existsSync18, readFileSync as readFileSync22, statSync as
|
|
43065
|
-
import { join as
|
|
43580
|
+
import { existsSync as existsSync18, readFileSync as readFileSync22, statSync as statSync9 } from "fs";
|
|
43581
|
+
import { join as join20, resolve as resolve15 } from "path";
|
|
43066
43582
|
import { homedir as homedir14 } from "os";
|
|
43067
43583
|
|
|
43068
43584
|
// src/updater/state.ts
|
|
43069
43585
|
import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync17 } from "fs";
|
|
43070
|
-
import { join as
|
|
43586
|
+
import { join as join19 } from "path";
|
|
43071
43587
|
import { homedir as homedir13 } from "os";
|
|
43072
|
-
var GIPITY_DIR =
|
|
43073
|
-
var LOCAL_DIR =
|
|
43074
|
-
var LOCAL_PKG_DIR =
|
|
43075
|
-
var LOCAL_ENTRY =
|
|
43076
|
-
var STATE_FILE =
|
|
43077
|
-
var SETTINGS_FILE =
|
|
43078
|
-
var UPDATE_LOG =
|
|
43588
|
+
var GIPITY_DIR = join19(homedir13(), ".gipity");
|
|
43589
|
+
var LOCAL_DIR = join19(GIPITY_DIR, "local");
|
|
43590
|
+
var LOCAL_PKG_DIR = join19(LOCAL_DIR, "node_modules", "gipity");
|
|
43591
|
+
var LOCAL_ENTRY = join19(LOCAL_PKG_DIR, "dist", "index.js");
|
|
43592
|
+
var STATE_FILE = join19(GIPITY_DIR, "update-state.json");
|
|
43593
|
+
var SETTINGS_FILE = join19(GIPITY_DIR, "settings.json");
|
|
43594
|
+
var UPDATE_LOG = join19(GIPITY_DIR, "update.log");
|
|
43079
43595
|
var DEFAULT_STATE = {
|
|
43080
43596
|
installedVersion: null,
|
|
43081
43597
|
lastCheckAt: 0,
|
|
@@ -43136,7 +43652,7 @@ function versionManagerNode(execPath, env = process.env, home = homedir14()) {
|
|
|
43136
43652
|
return null;
|
|
43137
43653
|
}
|
|
43138
43654
|
function localVersion() {
|
|
43139
|
-
const pkgPath =
|
|
43655
|
+
const pkgPath = join20(LOCAL_PKG_DIR, "package.json");
|
|
43140
43656
|
if (!existsSync18(pkgPath)) return null;
|
|
43141
43657
|
try {
|
|
43142
43658
|
return JSON.parse(readFileSync22(pkgPath, "utf-8")).version ?? null;
|
|
@@ -43250,7 +43766,7 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
43250
43766
|
console.log(`${muted("last check ")} ${rel(state.lastCheckAt)}`);
|
|
43251
43767
|
console.log(`${muted("last error ")} ${state.lastError ? error(state.lastError) : dim("none")}`);
|
|
43252
43768
|
console.log(`${muted("state file ")} ${existsSync18(STATE_FILE) ? STATE_FILE : dim("(none yet)")}`);
|
|
43253
|
-
console.log(`${muted("update log ")} ${existsSync18(UPDATE_LOG) ? `${UPDATE_LOG} (${
|
|
43769
|
+
console.log(`${muted("update log ")} ${existsSync18(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync9(UPDATE_LOG).size} bytes)` : dim("(none yet)")}`);
|
|
43254
43770
|
console.log("");
|
|
43255
43771
|
console.log(dim("Force an update with: gipity update"));
|
|
43256
43772
|
});
|
|
@@ -43259,10 +43775,10 @@ var doctorCommand = new Command("doctor").description("Check install + environme
|
|
|
43259
43775
|
import { appendFileSync } from "fs";
|
|
43260
43776
|
|
|
43261
43777
|
// src/updater/install.ts
|
|
43262
|
-
import { existsSync as existsSync19, mkdirSync as mkdirSync15, rmSync as rmSync2, statSync as
|
|
43263
|
-
import { dirname as
|
|
43778
|
+
import { existsSync as existsSync19, mkdirSync as mkdirSync15, rmSync as rmSync2, statSync as statSync10, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
|
|
43779
|
+
import { dirname as dirname13, join as join21 } from "path";
|
|
43264
43780
|
init_platform();
|
|
43265
|
-
var UPDATE_LOCK =
|
|
43781
|
+
var UPDATE_LOCK = join21(GIPITY_DIR, "update.lock");
|
|
43266
43782
|
var LOCK_STALE_MS2 = 10 * 60 * 1e3;
|
|
43267
43783
|
function npmInstallGipity(version) {
|
|
43268
43784
|
const res = spawnSyncCommand(resolveCommand("npm"), ["install", "--no-audit", "--no-fund", "--ignore-scripts", `gipity@${version}`], {
|
|
@@ -43283,18 +43799,18 @@ function isWedged(stderr) {
|
|
|
43283
43799
|
}
|
|
43284
43800
|
function resetLocalTree(dir = LOCAL_DIR) {
|
|
43285
43801
|
mkdirSync15(dir, { recursive: true });
|
|
43286
|
-
rmSync2(
|
|
43287
|
-
rmSync2(
|
|
43288
|
-
writeFileSync15(
|
|
43802
|
+
rmSync2(join21(dir, "node_modules"), { recursive: true, force: true });
|
|
43803
|
+
rmSync2(join21(dir, "package-lock.json"), { force: true });
|
|
43804
|
+
writeFileSync15(join21(dir, "package.json"), JSON.stringify({ name: "gipity-local", private: true, version: "0.0.0" }, null, 2));
|
|
43289
43805
|
}
|
|
43290
43806
|
function acquireUpdateLock(lockPath2 = UPDATE_LOCK) {
|
|
43291
|
-
mkdirSync15(
|
|
43807
|
+
mkdirSync15(dirname13(lockPath2), { recursive: true });
|
|
43292
43808
|
try {
|
|
43293
43809
|
writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
|
|
43294
43810
|
return true;
|
|
43295
43811
|
} catch {
|
|
43296
43812
|
try {
|
|
43297
|
-
if (Date.now() -
|
|
43813
|
+
if (Date.now() - statSync10(lockPath2).mtimeMs > LOCK_STALE_MS2) {
|
|
43298
43814
|
unlinkSync5(lockPath2);
|
|
43299
43815
|
writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
|
|
43300
43816
|
return true;
|
|
@@ -43455,7 +43971,7 @@ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync17, existsSync
|
|
|
43455
43971
|
import { stat, readFile } from "fs/promises";
|
|
43456
43972
|
import { createInterface as createInterface2 } from "readline";
|
|
43457
43973
|
import { homedir as homedir16, hostname as hostname5, platform as osPlatform3, loadavg as loadavg2, freemem as freemem2, totalmem as totalmem2, cpus as cpus2 } from "os";
|
|
43458
|
-
import { join as
|
|
43974
|
+
import { join as join24 } from "path";
|
|
43459
43975
|
init_auth();
|
|
43460
43976
|
init_api();
|
|
43461
43977
|
|
|
@@ -44211,8 +44727,8 @@ async function revokeRelayAgentToken() {
|
|
|
44211
44727
|
init_platform();
|
|
44212
44728
|
init_client_context();
|
|
44213
44729
|
import { cpus, freemem, totalmem, loadavg, release, uptime, homedir as homedir15 } from "os";
|
|
44214
|
-
import { statfsSync, readdirSync as
|
|
44215
|
-
import { join as
|
|
44730
|
+
import { statfsSync, readdirSync as readdirSync9 } from "fs";
|
|
44731
|
+
import { join as join22 } from "path";
|
|
44216
44732
|
var PROBE_TIMEOUT_MS = 4e3;
|
|
44217
44733
|
function parseVersion(out) {
|
|
44218
44734
|
const m = out.match(/\d+\.\d+(?:\.\d+)?(?:[-.\w]*)?/);
|
|
@@ -44279,7 +44795,7 @@ async function detectGpu() {
|
|
|
44279
44795
|
function diskUsage() {
|
|
44280
44796
|
try {
|
|
44281
44797
|
if (typeof statfsSync !== "function") return void 0;
|
|
44282
|
-
const st2 = statfsSync(
|
|
44798
|
+
const st2 = statfsSync(join22(homedir15(), "GipityProjects"), { bigint: false });
|
|
44283
44799
|
if (!st2 || !st2.bsize) return void 0;
|
|
44284
44800
|
return { total: st2.bsize * st2.blocks, free: st2.bsize * st2.bavail };
|
|
44285
44801
|
} catch {
|
|
@@ -44293,7 +44809,7 @@ function diskUsage() {
|
|
|
44293
44809
|
}
|
|
44294
44810
|
function localProjectCount() {
|
|
44295
44811
|
try {
|
|
44296
|
-
return
|
|
44812
|
+
return readdirSync9(join22(homedir15(), "GipityProjects"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
|
|
44297
44813
|
} catch {
|
|
44298
44814
|
return void 0;
|
|
44299
44815
|
}
|
|
@@ -44306,10 +44822,11 @@ async function collectDiagnostics() {
|
|
|
44306
44822
|
return [];
|
|
44307
44823
|
}
|
|
44308
44824
|
})();
|
|
44309
|
-
const [claude, codex, grok, cursor, gpu] = await Promise.all([
|
|
44825
|
+
const [claude, codex, grok, agy, cursor, gpu] = await Promise.all([
|
|
44310
44826
|
probeVersion("claude"),
|
|
44311
44827
|
probeVersion("codex"),
|
|
44312
44828
|
probeVersion("grok"),
|
|
44829
|
+
probeVersion("agy"),
|
|
44313
44830
|
probeVersion("cursor"),
|
|
44314
44831
|
detectGpu()
|
|
44315
44832
|
]);
|
|
@@ -44317,6 +44834,7 @@ async function collectDiagnostics() {
|
|
|
44317
44834
|
if (claude) agents.claude_code = claude;
|
|
44318
44835
|
if (codex) agents.codex = codex;
|
|
44319
44836
|
if (grok) agents.grok = grok;
|
|
44837
|
+
if (agy) agents.agy = agy;
|
|
44320
44838
|
if (cursor) agents.cursor = cursor;
|
|
44321
44839
|
return {
|
|
44322
44840
|
collected_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -44602,7 +45120,7 @@ var SessionPool = class {
|
|
|
44602
45120
|
// src/relay/daemon.ts
|
|
44603
45121
|
init_config();
|
|
44604
45122
|
init_api();
|
|
44605
|
-
var RELAY_LOG_PATH =
|
|
45123
|
+
var RELAY_LOG_PATH = join24(homedir16(), ".gipity", "relay.log");
|
|
44606
45124
|
var HEARTBEAT_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_HEARTBEAT_MS || "60000", 10);
|
|
44607
45125
|
var DIAGNOSTICS_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_DIAGNOSTICS_MS || String(24 * 60 * 60 * 1e3), 10);
|
|
44608
45126
|
var LONG_POLL_TIMEOUT_MS = parseInt(process.env.GIPITY_RELAY_POLL_TIMEOUT_MS || "35000", 10);
|
|
@@ -44719,7 +45237,7 @@ function log2(level, msg, extra) {
|
|
|
44719
45237
|
const pretty = `${C7.dim(hhmmss())} ${badge(level)} ${C7.bold(msg)}${formatExtra(extra)}`;
|
|
44720
45238
|
process.stderr.write(pretty + "\n");
|
|
44721
45239
|
try {
|
|
44722
|
-
const dir =
|
|
45240
|
+
const dir = join24(homedir16(), ".gipity");
|
|
44723
45241
|
mkdirSync17(dir, { recursive: true });
|
|
44724
45242
|
lockLogPerms(dir, RELAY_LOG_PATH);
|
|
44725
45243
|
const json = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...extra ?? {} });
|
|
@@ -45120,7 +45638,7 @@ function isSafeSessionId(s) {
|
|
|
45120
45638
|
function transcriptPathFor(cwd, sessionId) {
|
|
45121
45639
|
if (!isSafeSessionId(sessionId)) return null;
|
|
45122
45640
|
const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-");
|
|
45123
|
-
return
|
|
45641
|
+
return join24(homedir16(), ".claude", "projects", slug, `${sessionId}.jsonl`);
|
|
45124
45642
|
}
|
|
45125
45643
|
function formatDuration(ms2) {
|
|
45126
45644
|
const totalSec = ms2 / 1e3;
|
|
@@ -45574,8 +46092,8 @@ async function resolveCwdForProject(d) {
|
|
|
45574
46092
|
throw new Error(`Invalid project slug: ${JSON.stringify(d.project_slug)}`);
|
|
45575
46093
|
}
|
|
45576
46094
|
const root = getProjectsRoot();
|
|
45577
|
-
const path5 =
|
|
45578
|
-
const configPath =
|
|
46095
|
+
const path5 = join24(root, d.project_slug);
|
|
46096
|
+
const configPath = join24(path5, ".gipity.json");
|
|
45579
46097
|
if (existsSync21(configPath)) {
|
|
45580
46098
|
try {
|
|
45581
46099
|
const cfg = JSON.parse(readFileSync24(configPath, "utf-8"));
|
|
@@ -45713,7 +46231,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
|
|
|
45713
46231
|
} catch {
|
|
45714
46232
|
}
|
|
45715
46233
|
try {
|
|
45716
|
-
unlinkSync7(
|
|
46234
|
+
unlinkSync7(join24(cwd, ".gipity", "sync.lock"));
|
|
45717
46235
|
} catch {
|
|
45718
46236
|
}
|
|
45719
46237
|
finish(() => reject(new Error(`timed out after ${Math.round(timeoutMs / 1e3)}s`)));
|
|
@@ -46351,9 +46869,7 @@ var connectCommand = new Command("connect").alias("setup").description("Connect
|
|
|
46351
46869
|
console.log("");
|
|
46352
46870
|
const enabled = await runRelaySetup({ mode: "run-now" });
|
|
46353
46871
|
if (enabled) {
|
|
46354
|
-
|
|
46355
|
-
console.log(` ${success("Done")} \u2014 your relay ${running2 ? "is running in the background" : "is set up"} and will start with your computer.`);
|
|
46356
|
-
console.log(` ${muted("Open")} ${brand("gipity.ai")} ${muted("and start a chat to drive your coding agent here. Manage it with `gipity relay status`.")}`);
|
|
46872
|
+
console.log(` ${muted("Manage it anytime with `gipity relay status`.")}`);
|
|
46357
46873
|
} else {
|
|
46358
46874
|
console.log(` ${muted("No relay set up. Run `gipity connect` again anytime, or `gipity build` to start building.")}`);
|
|
46359
46875
|
}
|
|
@@ -46373,9 +46889,9 @@ init_utils();
|
|
|
46373
46889
|
init_colors();
|
|
46374
46890
|
import { existsSync as existsSync23, rmSync as rmSync4, unlinkSync as unlinkSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync17 } from "fs";
|
|
46375
46891
|
import { homedir as homedir17, platform as osPlatform4 } from "os";
|
|
46376
|
-
import { join as
|
|
46892
|
+
import { join as join25, resolve as resolve17 } from "path";
|
|
46377
46893
|
function removeGipityPluginConfig() {
|
|
46378
|
-
const settingsPath =
|
|
46894
|
+
const settingsPath = join25(homedir17(), ".claude", "settings.json");
|
|
46379
46895
|
if (!existsSync23(settingsPath)) return false;
|
|
46380
46896
|
let settings;
|
|
46381
46897
|
try {
|
|
@@ -46402,7 +46918,7 @@ function removeInstallerPathLines() {
|
|
|
46402
46918
|
const marker = "# Added by the Gipity installer";
|
|
46403
46919
|
const touched = [];
|
|
46404
46920
|
for (const name of [".bashrc", ".zshrc", ".profile"]) {
|
|
46405
|
-
const rc2 =
|
|
46921
|
+
const rc2 = join25(homedir17(), name);
|
|
46406
46922
|
if (!existsSync23(rc2)) continue;
|
|
46407
46923
|
let text;
|
|
46408
46924
|
try {
|
|
@@ -46485,8 +47001,8 @@ async function revokeDeviceBestEffort() {
|
|
|
46485
47001
|
}
|
|
46486
47002
|
var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").option("--yes", "Skip confirmation prompts").action(async (opts) => {
|
|
46487
47003
|
const autoYes = opts.yes || getAutoConfirm();
|
|
46488
|
-
const gipityDir =
|
|
46489
|
-
const launcherBin =
|
|
47004
|
+
const gipityDir = join25(homedir17(), ".gipity");
|
|
47005
|
+
const launcherBin = join25(gipityDir, "launcher", "bin", "gipity");
|
|
46490
47006
|
const installedViaLauncher = existsSync23(launcherBin);
|
|
46491
47007
|
console.log(`${bold("Gipity uninstall")} - this will:`);
|
|
46492
47008
|
console.log(`\u2022 Stop the running relay daemon (if any)`);
|
|
@@ -46533,12 +47049,22 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
|
|
|
46533
47049
|
if (agentSkills.skills.length) {
|
|
46534
47050
|
for (const name of agentSkills.skills) {
|
|
46535
47051
|
try {
|
|
46536
|
-
rmSync4(
|
|
47052
|
+
rmSync4(join25(AGENTS_SKILLS_DIR, name), { recursive: true, force: true });
|
|
46537
47053
|
} catch {
|
|
46538
47054
|
}
|
|
46539
47055
|
}
|
|
46540
47056
|
console.log(`${success(`Removed ${agentSkills.skills.length} Gipity skills from ~/.agents/skills.`)}`);
|
|
46541
47057
|
}
|
|
47058
|
+
const agySkills = agySkillsState();
|
|
47059
|
+
if (agySkills.skills.length) {
|
|
47060
|
+
for (const name of agySkills.skills) {
|
|
47061
|
+
try {
|
|
47062
|
+
rmSync4(join25(AGY_SKILLS_DIR, name), { recursive: true, force: true });
|
|
47063
|
+
} catch {
|
|
47064
|
+
}
|
|
47065
|
+
}
|
|
47066
|
+
console.log(`${success(`Removed ${agySkills.skills.length} Gipity skills from ~/.gemini/config/skills.`)}`);
|
|
47067
|
+
}
|
|
46542
47068
|
if (existsSync23(gipityDir)) {
|
|
46543
47069
|
try {
|
|
46544
47070
|
rmSync4(gipityDir, { recursive: true, force: true });
|
|
@@ -46685,7 +47211,7 @@ gmailCommand.command("send").description("Send a new email from your Gmail").req
|
|
|
46685
47211
|
const recap = opts.to.join(", ") + (opts.cc.length ? `, cc: ${opts.cc.join(", ")}` : "");
|
|
46686
47212
|
printResult(`Sent from your Gmail to ${recap}.`, opts, { sent: true, to: opts.to });
|
|
46687
47213
|
}));
|
|
46688
|
-
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:
|
|
47214
|
+
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 () => {
|
|
46689
47215
|
if (!opts.to.length) throw new Error("--to is required (pass at least one)");
|
|
46690
47216
|
const payload = {
|
|
46691
47217
|
thread_id: opts.threadId,
|
|
@@ -47067,7 +47593,7 @@ function collectRealFlags(argv2, program3) {
|
|
|
47067
47593
|
|
|
47068
47594
|
// src/trace.ts
|
|
47069
47595
|
import { openSync as openSync5, writeSync, mkdirSync as mkdirSync18 } from "fs";
|
|
47070
|
-
import { join as
|
|
47596
|
+
import { join as join26 } from "path";
|
|
47071
47597
|
import { homedir as homedir18 } from "os";
|
|
47072
47598
|
var TRACE_KEY = /* @__PURE__ */ Symbol.for("gipity.traceOutput");
|
|
47073
47599
|
function installOutputTrace(label2) {
|
|
@@ -47079,10 +47605,10 @@ function installOutputTrace(label2) {
|
|
|
47079
47605
|
existing.emit({ event: "reenter", label: label2 });
|
|
47080
47606
|
return;
|
|
47081
47607
|
}
|
|
47082
|
-
const dir =
|
|
47608
|
+
const dir = join26(process.env.GIPITY_DIR || join26(homedir18(), ".gipity"), "trace");
|
|
47083
47609
|
mkdirSync18(dir, { recursive: true });
|
|
47084
47610
|
const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
|
|
47085
|
-
const fd2 = openSync5(
|
|
47611
|
+
const fd2 = openSync5(join26(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
|
|
47086
47612
|
const emit = (rec) => {
|
|
47087
47613
|
try {
|
|
47088
47614
|
writeSync(fd2, JSON.stringify({ t: (/* @__PURE__ */ new Date()).toISOString(), ...rec }) + "\n");
|
|
@@ -47117,7 +47643,7 @@ function installOutputTrace(label2) {
|
|
|
47117
47643
|
|
|
47118
47644
|
// src/index.ts
|
|
47119
47645
|
installOutputTrace("index");
|
|
47120
|
-
var __dirname =
|
|
47646
|
+
var __dirname = dirname14(fileURLToPath3(import.meta.url));
|
|
47121
47647
|
var pkg = JSON.parse(readFileSync28(resolve18(__dirname, "../package.json"), "utf-8"));
|
|
47122
47648
|
function versionLabel() {
|
|
47123
47649
|
const base = `v${pkg.version}`;
|
|
@@ -47158,8 +47684,8 @@ function configureHelp(cmd) {
|
|
|
47158
47684
|
var program2 = new Command();
|
|
47159
47685
|
program2.enablePositionalOptions();
|
|
47160
47686
|
var startGroup = [statusCommand, initCommand, skillCommand, projectCommand];
|
|
47161
|
-
var buildGroup = [addCommand, removeCommand, saveCommand, loadCommand, deployCommand, pageCommand, testCommand];
|
|
47162
|
-
var backendGroup = [dbCommand, fnCommand, secretsCommand, logsCommand, jobCommand, workflowCommand];
|
|
47687
|
+
var buildGroup = [addCommand, removeCommand, brandCommand, saveCommand, loadCommand, deployCommand, pageCommand, testCommand];
|
|
47688
|
+
var backendGroup = [dbCommand, fnCommand, secretsCommand, keyCommand, logsCommand, jobCommand, workflowCommand];
|
|
47163
47689
|
var servicesGroup = [serviceCommand, generateCommand, notifyCommand, paymentsCommand, realtimeCommand, recordsCommand, rbacCommand, auditCommand, domainCommand, tokenCommand];
|
|
47164
47690
|
var filesGroup = [syncCommand, fileCommand, pushCommand, uploadCommand, storageCommand];
|
|
47165
47691
|
var gipGroup = [chatCommand, memoryCommand, agentCommand, approvalCommand, gmailCommand];
|
|
@@ -47176,6 +47702,7 @@ var HELP_SECTIONS = [
|
|
|
47176
47702
|
{ title: "Connect & setup", cmds: connectGroup }
|
|
47177
47703
|
];
|
|
47178
47704
|
var SKILL_DOCS = {
|
|
47705
|
+
brand: "web-app-basics",
|
|
47179
47706
|
save: "app-import",
|
|
47180
47707
|
load: "app-import",
|
|
47181
47708
|
github: "app-import",
|
|
@@ -47188,6 +47715,7 @@ var SKILL_DOCS = {
|
|
|
47188
47715
|
notify: "app-notify",
|
|
47189
47716
|
payments: "app-payments",
|
|
47190
47717
|
records: "app-records",
|
|
47718
|
+
key: "app-auth",
|
|
47191
47719
|
realtime: "app-realtime",
|
|
47192
47720
|
job: "jobs",
|
|
47193
47721
|
sandbox: "sandbox-tools",
|
|
@@ -47336,7 +47864,7 @@ Command.prototype._excessArguments = function(receivedArgs) {
|
|
|
47336
47864
|
let message = `error: unexpected extra argument${excess.length === 1 ? "" : "s"} ${list}${forSubcommand}.`;
|
|
47337
47865
|
const kv2 = excess.map((a) => /^([A-Za-z][\w-]*)=/.exec(a)).find(Boolean);
|
|
47338
47866
|
if (kv2) {
|
|
47339
|
-
message += ` Options are passed as \`--${kv2[1]} <value>\`, not \`${kv2[1]}
|
|
47867
|
+
message += ` Options are passed as \`--${kv2[1]} <value>\`, not \`${kv2[1]}=...\` - did you mean \`--${kv2[1]}\`?`;
|
|
47340
47868
|
}
|
|
47341
47869
|
this.error(message, { code: "commander.excessArguments" });
|
|
47342
47870
|
};
|