gipity 1.1.4 → 1.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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(readFileSync(AUTH_FILE, "utf-8"));
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(readFileSync(AUTH_FILE, "utf-8"));
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(readFileSync(AUTH_LOCK_FILE, "utf-8").trim(), 10);
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 || join(homedir(), ".gipity");
4213
- AUTH_FILE = join(AUTH_DIR, "auth.json");
4214
- AUTH_LOCK_FILE = join(AUTH_DIR, "auth.lock");
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 readFileSync2 } from "fs";
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(readFileSync2(resolve(dir, "../package.json"), "utf-8"));
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";
@@ -6928,7 +6964,7 @@ __export(config_exports, {
6928
6964
  setApiBaseOverride: () => setApiBaseOverride,
6929
6965
  shouldIgnore: () => shouldIgnore
6930
6966
  });
6931
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
6967
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync2 } from "fs";
6932
6968
  import { dirname as dirname2, resolve as resolve2 } from "path";
6933
6969
  function setApiBaseOverride(url) {
6934
6970
  apiBaseOverride = url;
@@ -6986,7 +7022,7 @@ function getConfig() {
6986
7022
  const path5 = getConfigPath();
6987
7023
  if (!path5) return null;
6988
7024
  try {
6989
- cached3 = JSON.parse(readFileSync3(path5, "utf-8"));
7025
+ cached3 = JSON.parse(readFileSync4(path5, "utf-8"));
6990
7026
  return cached3;
6991
7027
  } catch {
6992
7028
  return null;
@@ -31865,7 +31901,7 @@ init_config();
31865
31901
  init_utils();
31866
31902
  import { readFileSync as readFileSync28 } from "fs";
31867
31903
  import { fileURLToPath as fileURLToPath3 } from "url";
31868
- import { dirname as dirname13, resolve as resolve18 } from "path";
31904
+ import { dirname as dirname14, resolve as resolve18 } from "path";
31869
31905
 
31870
31906
  // src/helpers/output.ts
31871
31907
  var frameOpen = false;
@@ -32081,6 +32117,8 @@ Write files locally - Gipity's editor hooks (installed into Claude Code, Grok, a
32081
32117
 
32082
32118
  To keep local-only material (research clones, scratch data, vendored references) in the project directory without syncing or deploying it, list it in a \`.gipityignore\` at the project root - gitignore-style, one pattern per line, \`#\` comments. Ignored paths are invisible to sync in both directions; anything that already synced before being ignored stays on the server until you delete it.
32083
32119
 
32120
+ **WSL trap: user-dropped files may be in a Windows-side twin.** On WSL, a same-named folder often exists at \`C:\\Users\\<name>\\GipityProjects\\<project>\` (visible as \`/mnt/c/Users/<name>/GipityProjects/<project>\`) - from Windows Explorer it looks like "the project folder", so users drop new files (audio, images) there. Nothing syncs from it. If the user says files are "in the project" but you can't find them, check that path before searching wider; \`gipity sync\` prints a notice when such a twin exists.
32121
+
32084
32122
  ### Where files go: deploy only ships \`src/\`
32085
32123
 
32086
32124
  Deploy is opt-in, not opt-out: the \`files\` phase uploads **only** what's under \`src/\` (plus \`functions/\` and \`migrations/\` as backend, not CDN files). Anything else at the project root is kept but never deployed. Put each kind of file in the right bucket so scratch and reference material can't bloat a deploy:
@@ -32117,6 +32155,7 @@ App development skills:
32117
32155
  - \`app-debugging\` - debug a deployed app: page inspect/eval, screenshots, function logs
32118
32156
  - \`app-development\` - functions, database, and API
32119
32157
  - \`app-import\` - import apps from GitHub/.gip bundles (incl. Vercel/Replit/Lovable porting) and export any project as a portable .gip - app_import tool, gipity save/load
32158
+ - \`app-records\` - Gipity Records: declared tables \u2192 validated CRUD, provenance, workflows, auto UI
32120
32159
  - \`app-testing\` - testing deployed app functions (ctx.fn.call/callAs, the isolated test DB)
32121
32160
  - \`deploy\` - the deploy pipeline & gipity.yaml manifest
32122
32161
  - \`jobs\` - long-running CPU + GPU compute jobs (Python / Node / bash)
@@ -32153,13 +32192,13 @@ init_colors();
32153
32192
 
32154
32193
  // src/bug-queue.ts
32155
32194
  init_api();
32156
- import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync, readFileSync as readFileSync4, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
32157
- import { join as join2 } from "path";
32195
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readdirSync as readdirSync2, readFileSync as readFileSync5, writeFileSync as writeFileSync3, unlinkSync as unlinkSync2 } from "fs";
32196
+ import { join as join3 } from "path";
32158
32197
  import { homedir as homedir2 } from "os";
32159
- var QUEUE_DIR = join2(process.env.GIPITY_DIR || join2(homedir2(), ".gipity"), "bug-queue");
32198
+ var QUEUE_DIR = join3(process.env.GIPITY_DIR || join3(homedir2(), ".gipity"), "bug-queue");
32160
32199
  function queueBugReport(report) {
32161
32200
  mkdirSync2(QUEUE_DIR, { recursive: true, mode: 448 });
32162
- const file = join2(QUEUE_DIR, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
32201
+ const file = join3(QUEUE_DIR, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}.json`);
32163
32202
  writeFileSync3(file, JSON.stringify(report, null, 2), { mode: 384 });
32164
32203
  }
32165
32204
  function isRetryableFailure(err) {
@@ -32171,12 +32210,12 @@ function isRetryableFailure(err) {
32171
32210
  async function flushBugQueue() {
32172
32211
  if (!existsSync3(QUEUE_DIR)) return 0;
32173
32212
  let delivered = 0;
32174
- for (const file of readdirSync(QUEUE_DIR)) {
32213
+ for (const file of readdirSync2(QUEUE_DIR)) {
32175
32214
  if (!file.endsWith(".json")) continue;
32176
- const path5 = join2(QUEUE_DIR, file);
32215
+ const path5 = join3(QUEUE_DIR, file);
32177
32216
  let report;
32178
32217
  try {
32179
- report = JSON.parse(readFileSync4(path5, "utf-8"));
32218
+ report = JSON.parse(readFileSync5(path5, "utf-8"));
32180
32219
  } catch {
32181
32220
  try {
32182
32221
  unlinkSync2(path5);
@@ -32199,6 +32238,70 @@ async function flushBugQueue() {
32199
32238
  return delivered;
32200
32239
  }
32201
32240
 
32241
+ // src/login-flow.ts
32242
+ init_api();
32243
+ init_auth();
32244
+ init_config();
32245
+ init_utils();
32246
+ init_colors();
32247
+ function newAccountWouldBeUnexpected(email, priorAuth) {
32248
+ return !!getConfig() || priorAuth?.email.toLowerCase() === email.toLowerCase().trim();
32249
+ }
32250
+ function warnBeforeCodeIfUnexpectedNewAccount(isNewUser, email, indent = "") {
32251
+ if (isNewUser !== true) return;
32252
+ const priorAuth = getAuth();
32253
+ if (!newAccountWouldBeUnexpected(email, priorAuth)) return;
32254
+ const config = getConfig();
32255
+ console.log(`${indent}${warning(`No existing Gipity account for ${email} \u2014 entering the code will CREATE a new one.`)}`);
32256
+ if (config) {
32257
+ console.log(`${indent}${muted(`This directory is linked to project ${config.projectSlug} (account ${config.accountSlug}). If you meant to log into that account, stop and re-check the email before entering the code.`)}`);
32258
+ }
32259
+ }
32260
+ function warnIfUnexpectedNewAccount(isNewUser, email, priorAuth, indent = "") {
32261
+ if (isNewUser !== true) return;
32262
+ if (!newAccountWouldBeUnexpected(email, priorAuth)) return;
32263
+ const config = getConfig();
32264
+ console.log(`${indent}${warning(`Logged into a NEW, empty account for ${email} \u2014 no prior account existed for this email.`)}`);
32265
+ if (config) {
32266
+ console.log(`${indent}${muted(`This directory is linked to project ${config.projectSlug} (account ${config.accountSlug}), which the new account does not own \u2014 project / skill / sync commands will fail with "not found".`)}`);
32267
+ }
32268
+ console.log(`${indent}${muted("If you expected an existing account, run: gipity status \u2014 then gipity login again with the correct email.")}`);
32269
+ }
32270
+ async function interactiveLogin() {
32271
+ const email = await prompt(" Email: ");
32272
+ if (!email) {
32273
+ console.error(`
32274
+ ${error("Email required.")}`);
32275
+ process.exit(1);
32276
+ }
32277
+ const sendRes = await publicPost("/auth/login", { email });
32278
+ console.log(" Check your email for a 6-digit code.\n");
32279
+ warnBeforeCodeIfUnexpectedNewAccount(sendRes.isNewUser, email, " ");
32280
+ const code = await prompt(" Code: ");
32281
+ if (!code) {
32282
+ console.error(`
32283
+ ${error("Code required.")}`);
32284
+ process.exit(1);
32285
+ }
32286
+ const priorAuth = getAuth();
32287
+ const res = await publicPost("/auth/verify", { email, code });
32288
+ const exp = decodeJwtExp(res.accessToken);
32289
+ if (!exp) {
32290
+ console.error(`
32291
+ ${error("Invalid token received.")}`);
32292
+ process.exit(1);
32293
+ }
32294
+ const expiresAt = new Date(exp * 1e3).toISOString();
32295
+ saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
32296
+ console.log(` ${success(`Logged in (${email}).`)}`);
32297
+ warnIfUnexpectedNewAccount(res.isNewUser, email, priorAuth, " ");
32298
+ const delivered = await flushBugQueue().catch(() => 0);
32299
+ if (delivered > 0) {
32300
+ console.log(` ${muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`)}`);
32301
+ }
32302
+ return getAuth();
32303
+ }
32304
+
32202
32305
  // src/commands/login.ts
32203
32306
  var loginCommand = new Command("login").description("Log in or sign up").option("--email <email>", "Email address").option("--code <code>", "Verification code").action(async (opts) => {
32204
32307
  try {
@@ -32209,9 +32312,10 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
32209
32312
  return;
32210
32313
  }
32211
32314
  if (email && !code) {
32212
- await publicPost("/auth/login", { email });
32315
+ const sendRes2 = await publicPost("/auth/login", { email });
32213
32316
  console.log("Check your email for a 6-digit code.");
32214
32317
  console.log(muted(`Then run: gipity login --email ${email} --code <code>`));
32318
+ warnBeforeCodeIfUnexpectedNewAccount(sendRes2.isNewUser, email);
32215
32319
  return;
32216
32320
  }
32217
32321
  console.log("Enter your email to log in or create an account.");
@@ -32222,9 +32326,10 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
32222
32326
  console.error(error("Email required."));
32223
32327
  process.exit(1);
32224
32328
  }
32225
- await publicPost("/auth/login", { email });
32329
+ const sendRes = await publicPost("/auth/login", { email });
32226
32330
  console.log("");
32227
32331
  console.log("Check your email for a 6-digit code.");
32332
+ warnBeforeCodeIfUnexpectedNewAccount(sendRes.isNewUser, email);
32228
32333
  code = await prompt("Code: ");
32229
32334
  await verify(email, code);
32230
32335
  } catch (err) {
@@ -32233,6 +32338,7 @@ var loginCommand = new Command("login").description("Log in or sign up").option(
32233
32338
  }
32234
32339
  });
32235
32340
  async function verify(email, code) {
32341
+ const priorAuth = getAuth();
32236
32342
  const res = await publicPost("/auth/verify", { email, code });
32237
32343
  const exp = decodeJwtExp(res.accessToken);
32238
32344
  if (!exp) {
@@ -32247,6 +32353,7 @@ async function verify(email, code) {
32247
32353
  expiresAt
32248
32354
  });
32249
32355
  console.log(success(`Logged in (${email}).`));
32356
+ warnIfUnexpectedNewAccount(res.isNewUser, email, priorAuth);
32250
32357
  const delivered = await flushBugQueue().catch(() => 0);
32251
32358
  if (delivered > 0) {
32252
32359
  console.log(muted(`Delivered ${delivered} queued bug report${delivered === 1 ? "" : "s"}.`));
@@ -32292,8 +32399,8 @@ function run(label2, action) {
32292
32399
  init_api();
32293
32400
  init_config();
32294
32401
  init_utils();
32295
- import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, statSync, lstatSync, unlinkSync as unlinkSync3, readdirSync as readdirSync3, rmdirSync, readFileSync as readFileSync6, renameSync, openSync as openSync2, closeSync as closeSync2, utimesSync, realpathSync } from "fs";
32296
- import { join as join4, relative, dirname as dirname4, extname as extname2, resolve as resolve4, sep } from "path";
32402
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, existsSync as existsSync5, statSync as statSync2, lstatSync, unlinkSync as unlinkSync3, readdirSync as readdirSync4, rmdirSync, readFileSync as readFileSync7, renameSync, openSync as openSync2, closeSync as closeSync2, utimesSync, realpathSync as realpathSync2 } from "fs";
32403
+ import { join as join5, relative, dirname as dirname4, extname as extname2, resolve as resolve4, sep } from "path";
32297
32404
  import { hostname } from "os";
32298
32405
  import { createHash as createHash2 } from "crypto";
32299
32406
 
@@ -32407,9 +32514,9 @@ var UploadConflictError = class extends Error {
32407
32514
  path;
32408
32515
  };
32409
32516
  async function uploadOneFile(projectGuid, localPath, virtualPath, opts = {}) {
32410
- const { sha256: sha2562, size } = await hashFile(localPath);
32517
+ const { sha256, size } = await hashFile(localPath);
32411
32518
  const mime = opts.mime ?? guessMime(virtualPath);
32412
- const initBody = { path: virtualPath, size, sha256: sha2562, mime };
32519
+ const initBody = { path: virtualPath, size, sha256, mime };
32413
32520
  if (opts.expectedServerVersion !== void 0) {
32414
32521
  initBody.expected_server_version = opts.expectedServerVersion;
32415
32522
  }
@@ -32537,9 +32644,9 @@ async function uploadCompleteBatch(projectGuid, items) {
32537
32644
 
32538
32645
  // src/setup.ts
32539
32646
  init_platform();
32540
- import { resolve as resolve3, join as join3, dirname as dirname3 } from "path";
32647
+ import { resolve as resolve3, join as join4, dirname as dirname3 } from "path";
32541
32648
  import { homedir as homedir3, tmpdir } from "os";
32542
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync5, mkdtempSync, rmSync, cpSync, readdirSync as readdirSync2 } from "fs";
32649
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, writeFileSync as writeFileSync4, readFileSync as readFileSync6, mkdtempSync, rmSync, cpSync, readdirSync as readdirSync3 } from "fs";
32543
32650
  init_config();
32544
32651
  var PRIMER_FILES = {
32545
32652
  claude: "CLAUDE.md",
@@ -32654,14 +32761,14 @@ function stripGipityHooks(settings) {
32654
32761
  function readSettingsFile(path5) {
32655
32762
  if (!existsSync4(path5)) return {};
32656
32763
  try {
32657
- return JSON.parse(readFileSync5(path5, "utf-8"));
32764
+ return JSON.parse(readFileSync6(path5, "utf-8"));
32658
32765
  } catch {
32659
32766
  return {};
32660
32767
  }
32661
32768
  }
32662
32769
  function ensureGipityPlugin(force = false) {
32663
- const claudeDir = join3(homedir3(), ".claude");
32664
- const settingsPath = join3(claudeDir, "settings.json");
32770
+ const claudeDir = join4(homedir3(), ".claude");
32771
+ const settingsPath = join4(claudeDir, "settings.json");
32665
32772
  const settings = readSettingsFile(settingsPath);
32666
32773
  let changed = stripGipityHooks(settings);
32667
32774
  const marketplaces = settings.extraKnownMarketplaces ?? (settings.extraKnownMarketplaces = {});
@@ -32693,8 +32800,8 @@ function versionGte(have, want) {
32693
32800
  }
32694
32801
  function userScopeInstallState() {
32695
32802
  try {
32696
- const p = join3(homedir3(), ".claude", "plugins", "installed_plugins.json");
32697
- const data = JSON.parse(readFileSync5(p, "utf-8"));
32803
+ const p = join4(homedir3(), ".claude", "plugins", "installed_plugins.json");
32804
+ const data = JSON.parse(readFileSync6(p, "utf-8"));
32698
32805
  const entries = data?.plugins?.[GIPITY_PLUGIN_ID];
32699
32806
  if (!Array.isArray(entries)) return { exists: false, current: false };
32700
32807
  const userEntries = entries.filter((e) => e?.scope === "user");
@@ -32731,8 +32838,8 @@ function ensureGipityPluginInstalled() {
32731
32838
  }
32732
32839
  function grokInstallState() {
32733
32840
  try {
32734
- const p = join3(homedir3(), ".grok", "installed-plugins", "registry.json");
32735
- const data = JSON.parse(readFileSync5(p, "utf-8"));
32841
+ const p = join4(homedir3(), ".grok", "installed-plugins", "registry.json");
32842
+ const data = JSON.parse(readFileSync6(p, "utf-8"));
32736
32843
  const versions = [];
32737
32844
  for (const repo of Object.values(data?.repos ?? {})) {
32738
32845
  const v7 = repo?.plugins?.gipity?.version;
@@ -32757,12 +32864,12 @@ function ensureGrokPluginInstalled() {
32757
32864
  console.log("Installed the Gipity plugin for Grok (skills + file-sync hooks).");
32758
32865
  }
32759
32866
  }
32760
- var AGENTS_SKILLS_DIR = join3(homedir3(), ".agents", "skills");
32761
- var AGENT_HOOKS_DIR = join3(homedir3(), ".gipity", "agent-hooks");
32762
- var AGENT_SKILLS_MANIFEST = join3(homedir3(), ".gipity", "agent-skills.json");
32867
+ var AGENTS_SKILLS_DIR = join4(homedir3(), ".agents", "skills");
32868
+ var AGENT_HOOKS_DIR = join4(homedir3(), ".gipity", "agent-hooks");
32869
+ var AGENT_SKILLS_MANIFEST = join4(homedir3(), ".gipity", "agent-skills.json");
32763
32870
  function agentSkillsState() {
32764
32871
  try {
32765
- const m = JSON.parse(readFileSync5(AGENT_SKILLS_MANIFEST, "utf-8"));
32872
+ const m = JSON.parse(readFileSync6(AGENT_SKILLS_MANIFEST, "utf-8"));
32766
32873
  return {
32767
32874
  current: typeof m?.version === "string" && versionGte(m.version, GIPITY_PLUGIN_VERSION),
32768
32875
  skills: Array.isArray(m?.skills) ? m.skills : []
@@ -32774,36 +32881,36 @@ function agentSkillsState() {
32774
32881
  function ensureAgentSkillsInstalled() {
32775
32882
  if (agentSkillsState().current) return;
32776
32883
  if (!binaryOnPath("git")) return;
32777
- const tmp = mkdtempSync(join3(tmpdir(), "gipity-skills-"));
32884
+ const tmp = mkdtempSync(join4(tmpdir(), "gipity-skills-"));
32778
32885
  try {
32779
32886
  const clone = spawnSyncCommand(
32780
32887
  resolveCommand("git"),
32781
- ["clone", "--depth", "1", `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`, join3(tmp, "repo")],
32888
+ ["clone", "--depth", "1", `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`, join4(tmp, "repo")],
32782
32889
  { stdio: "ignore", timeout: 12e4 }
32783
32890
  );
32784
32891
  if (clone.status !== 0) return;
32785
- const repo = join3(tmp, "repo");
32892
+ const repo = join4(tmp, "repo");
32786
32893
  let version = GIPITY_PLUGIN_VERSION;
32787
32894
  try {
32788
- const manifest = JSON.parse(readFileSync5(join3(repo, ".claude-plugin", "plugin.json"), "utf-8"));
32895
+ const manifest = JSON.parse(readFileSync6(join4(repo, ".claude-plugin", "plugin.json"), "utf-8"));
32789
32896
  if (typeof manifest?.version === "string") version = manifest.version;
32790
32897
  } catch {
32791
32898
  }
32792
- const skillsSrc = join3(repo, "skills");
32899
+ const skillsSrc = join4(repo, "skills");
32793
32900
  const names = [];
32794
- for (const entry of readdirSync2(skillsSrc, { withFileTypes: true })) {
32901
+ for (const entry of readdirSync3(skillsSrc, { withFileTypes: true })) {
32795
32902
  if (!entry.isDirectory()) continue;
32796
- if (!existsSync4(join3(skillsSrc, entry.name, "SKILL.md"))) continue;
32903
+ if (!existsSync4(join4(skillsSrc, entry.name, "SKILL.md"))) continue;
32797
32904
  mkdirSync3(AGENTS_SKILLS_DIR, { recursive: true });
32798
- cpSync(join3(skillsSrc, entry.name), join3(AGENTS_SKILLS_DIR, entry.name), {
32905
+ cpSync(join4(skillsSrc, entry.name), join4(AGENTS_SKILLS_DIR, entry.name), {
32799
32906
  recursive: true,
32800
32907
  force: true
32801
32908
  });
32802
32909
  names.push(entry.name);
32803
32910
  }
32804
32911
  mkdirSync3(AGENT_HOOKS_DIR, { recursive: true });
32805
- for (const script of readdirSync2(join3(repo, "hooks", "scripts"))) {
32806
- cpSync(join3(repo, "hooks", "scripts", script), join3(AGENT_HOOKS_DIR, script), { force: true });
32912
+ for (const script of readdirSync3(join4(repo, "hooks", "scripts"))) {
32913
+ cpSync(join4(repo, "hooks", "scripts", script), join4(AGENT_HOOKS_DIR, script), { force: true });
32807
32914
  }
32808
32915
  writeFileSync4(AGENT_SKILLS_MANIFEST, JSON.stringify({ version, skills: names }, null, 2) + "\n");
32809
32916
  console.log(`Installed ${names.length} Gipity skills for Codex (~/.agents/skills).`);
@@ -32813,8 +32920,8 @@ function ensureAgentSkillsInstalled() {
32813
32920
  }
32814
32921
  }
32815
32922
  function applyCodexHooks(existing) {
32816
- const launcher = join3(AGENT_HOOKS_DIR, "launch.sh");
32817
- const cmd = (script, ...args) => [`sh "${launcher}" "${join3(AGENT_HOOKS_DIR, script)}"`, ...args].join(" ");
32923
+ const launcher = join4(AGENT_HOOKS_DIR, "launch.sh");
32924
+ const cmd = (script, ...args) => [`sh "${launcher}" "${join4(AGENT_HOOKS_DIR, script)}"`, ...args].join(" ");
32818
32925
  const wanted = [
32819
32926
  { event: "PostToolUse", matcher: "Edit|Write", command: cmd("sync-push.cjs"), timeout: 30 },
32820
32927
  { event: "UserPromptSubmit", command: cmd("sync-pull.cjs"), timeout: 300 },
@@ -32852,8 +32959,8 @@ function setupCodexHooks() {
32852
32959
  if (process.platform === "win32") return;
32853
32960
  const cwd = resolve3(process.cwd());
32854
32961
  if (cwd === resolve3(homedir3())) return;
32855
- const path5 = join3(cwd, ".codex", "hooks.json");
32856
- const existing = existsSync4(path5) ? readFileSync5(path5, "utf-8") : null;
32962
+ const path5 = join4(cwd, ".codex", "hooks.json");
32963
+ const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
32857
32964
  const next = applyCodexHooks(existing);
32858
32965
  if (next === null) return;
32859
32966
  mkdirSync3(dirname3(path5), { recursive: true });
@@ -32873,9 +32980,9 @@ function setupClaudeHooks() {
32873
32980
  ensureGipityPlugin();
32874
32981
  const cwd = resolve3(process.cwd());
32875
32982
  if (cwd === resolve3(homedir3())) return;
32876
- const claudeDir = join3(cwd, ".claude");
32983
+ const claudeDir = join4(cwd, ".claude");
32877
32984
  mkdirSync3(claudeDir, { recursive: true });
32878
- const settingsPath = join3(claudeDir, "settings.json");
32985
+ const settingsPath = join4(claudeDir, "settings.json");
32879
32986
  const settings = readSettingsFile(settingsPath);
32880
32987
  stripGipityHooks(settings);
32881
32988
  const perms = settings.permissions || {};
@@ -32922,7 +33029,7 @@ function applySkillsBlock(existing, apiBase2 = DEFAULT_API_BASE) {
32922
33029
  function writeSkillsFile(relPath, wrap) {
32923
33030
  const path5 = resolve3(process.cwd(), relPath);
32924
33031
  mkdirSync3(dirname3(path5), { recursive: true });
32925
- const existing = existsSync4(path5) ? readFileSync5(path5, "utf-8") : null;
33032
+ const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
32926
33033
  const baseNext = applySkillsBlock(existing, resolveApiBase());
32927
33034
  const next = wrap && existing === null ? wrap(baseNext) : baseNext;
32928
33035
  if (next !== existing) writeFileSync4(path5, next);
@@ -32965,7 +33072,7 @@ function applyAiderConf(existing) {
32965
33072
  function setupAiderMd() {
32966
33073
  writeSkillsFile(PRIMER_FILES.aider);
32967
33074
  const path5 = resolve3(process.cwd(), AIDER_CONF_FILE);
32968
- const existing = existsSync4(path5) ? readFileSync5(path5, "utf-8") : null;
33075
+ const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
32969
33076
  const next = applyAiderConf(existing);
32970
33077
  if (next !== null) writeFileSync4(path5, next);
32971
33078
  }
@@ -33007,7 +33114,7 @@ function setupGitignore() {
33007
33114
  const gitignorePath = resolve3(process.cwd(), ".gitignore");
33008
33115
  const entries = [".gipity/", ".gipity.json", ...SCRATCH_IGNORE];
33009
33116
  if (existsSync4(gitignorePath)) {
33010
- let content = readFileSync5(gitignorePath, "utf-8");
33117
+ let content = readFileSync6(gitignorePath, "utf-8");
33011
33118
  const lines = content.split(/\r?\n/);
33012
33119
  const toAdd = entries.filter((e) => !lines.includes(e));
33013
33120
  if (toAdd.length > 0) {
@@ -33035,11 +33142,11 @@ var BULK_DELETE_FRACTION = 0.25;
33035
33142
  var DOWNLOAD_IDLE_MS = 3e4;
33036
33143
  function syncStatePath() {
33037
33144
  const configPath = getConfigPath();
33038
- return join4(dirname4(configPath), ".gipity", "sync-state.json");
33145
+ return join5(dirname4(configPath), ".gipity", "sync-state.json");
33039
33146
  }
33040
33147
  function lockPath() {
33041
33148
  const configPath = getConfigPath();
33042
- return join4(dirname4(configPath), ".gipity", "sync.lock");
33149
+ return join5(dirname4(configPath), ".gipity", "sync.lock");
33043
33150
  }
33044
33151
  function projectDir() {
33045
33152
  const configPath = getConfigPath();
@@ -33053,8 +33160,8 @@ function isLockReclaimable(path5, now = Date.now()) {
33053
33160
  let raw;
33054
33161
  let mtimeMs;
33055
33162
  try {
33056
- raw = readFileSync6(path5, "utf-8").trim();
33057
- mtimeMs = statSync(path5).mtimeMs;
33163
+ raw = readFileSync7(path5, "utf-8").trim();
33164
+ mtimeMs = statSync2(path5).mtimeMs;
33058
33165
  } catch {
33059
33166
  return false;
33060
33167
  }
@@ -33113,7 +33220,7 @@ async function acquireLock(progress) {
33113
33220
  );
33114
33221
  }
33115
33222
  if (!waitSpinner) {
33116
- waitSpinner = progress?.spinner("Waiting for another sync to finish\u2026") ?? null;
33223
+ waitSpinner = progress?.spinner("Waiting for another sync to finish...") ?? null;
33117
33224
  }
33118
33225
  await new Promise((r) => setTimeout(r, LOCK_POLL_MS2));
33119
33226
  }
@@ -33123,7 +33230,7 @@ function readBaseline(projectGuid) {
33123
33230
  const path5 = syncStatePath();
33124
33231
  if (!existsSync5(path5)) return { projectGuid, files: {}, lastFullSync: null };
33125
33232
  try {
33126
- const parsed = JSON.parse(readFileSync6(path5, "utf-8"));
33233
+ const parsed = JSON.parse(readFileSync7(path5, "utf-8"));
33127
33234
  if (parsed.projectGuid !== projectGuid) {
33128
33235
  return { projectGuid, files: {}, lastFullSync: null };
33129
33236
  }
@@ -33147,25 +33254,25 @@ function walkLocal(root, ignorePatterns, baseline) {
33147
33254
  function walk(dir) {
33148
33255
  let entries;
33149
33256
  try {
33150
- entries = readdirSync3(dir, { withFileTypes: true });
33257
+ entries = readdirSync4(dir, { withFileTypes: true });
33151
33258
  } catch {
33152
33259
  return;
33153
33260
  }
33154
33261
  for (const entry of entries) {
33155
- const full = join4(dir, entry.name);
33262
+ const full = join5(dir, entry.name);
33156
33263
  const rel2 = relative(root, full).replace(/\\/g, "/");
33157
33264
  if (shouldIgnore(rel2, ignorePatterns)) continue;
33158
33265
  if (entry.isDirectory()) {
33159
- if (existsSync5(join4(full, CONFIG_FILE2))) continue;
33266
+ if (existsSync5(join5(full, CONFIG_FILE2))) continue;
33160
33267
  walk(full);
33161
33268
  } else if (entry.isFile()) {
33162
33269
  try {
33163
- const stat2 = statSync(full);
33270
+ const stat2 = statSync2(full);
33164
33271
  const size = stat2.size;
33165
33272
  const mtime = stat2.mtime.toISOString();
33166
33273
  const prior = baseline[rel2];
33167
- const sha2562 = prior && prior.size === size && prior.mtime === mtime ? prior.sha256 : void 0;
33168
- result.set(rel2, { size, mtime, sha256: sha2562 });
33274
+ const sha256 = prior && prior.size === size && prior.mtime === mtime ? prior.sha256 : void 0;
33275
+ result.set(rel2, { size, mtime, sha256 });
33169
33276
  } catch {
33170
33277
  }
33171
33278
  }
@@ -33179,8 +33286,8 @@ async function ensureLocalHashes(root, local, paths) {
33179
33286
  const info2 = local.get(path5);
33180
33287
  if (!info2 || info2.sha256) continue;
33181
33288
  try {
33182
- const { sha256: sha2562 } = await hashFile(join4(root, path5));
33183
- info2.sha256 = sha2562;
33289
+ const { sha256 } = await hashFile(join5(root, path5));
33290
+ info2.sha256 = sha256;
33184
33291
  } catch {
33185
33292
  }
33186
33293
  }
@@ -33195,10 +33302,10 @@ function resolveInRoot(root, relPath) {
33195
33302
  throw new Error(`Refusing path outside project root: ${relPath}`);
33196
33303
  }
33197
33304
  try {
33198
- const rootReal = realpathSync(rootResolved);
33305
+ const rootReal = realpathSync2(rootResolved);
33199
33306
  let ancestor = full;
33200
33307
  while (!existsSync5(ancestor) && dirname4(ancestor) !== ancestor) ancestor = dirname4(ancestor);
33201
- const ancestorReal = realpathSync(ancestor);
33308
+ const ancestorReal = realpathSync2(ancestor);
33202
33309
  if (ancestorReal !== rootReal && !ancestorReal.startsWith(rootReal + sep)) {
33203
33310
  throw new Error(`Refusing path outside project root (symlink escape): ${relPath}`);
33204
33311
  }
@@ -33486,9 +33593,9 @@ Plan deletes ${totalDeletes} files (${Math.round(fraction * 100)}% of the tree).
33486
33593
  }
33487
33594
  var GIPITY_IGNORE_FILE = ".gipityignore";
33488
33595
  function readGipityIgnore(root) {
33489
- const path5 = join4(root, GIPITY_IGNORE_FILE);
33596
+ const path5 = join5(root, GIPITY_IGNORE_FILE);
33490
33597
  if (!existsSync5(path5)) return [];
33491
- return readFileSync6(path5, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\.\//, "").replace(/^\//, ""));
33598
+ return readFileSync7(path5, "utf8").split("\n").map((line) => line.trim()).filter((line) => line && !line.startsWith("#")).map((line) => line.replace(/^\.\//, "").replace(/^\//, ""));
33492
33599
  }
33493
33600
  function effectiveIgnore(root, configIgnore) {
33494
33601
  const base = configIgnore && configIgnore.length ? configIgnore : DEFAULT_SYNC_IGNORE;
@@ -33529,9 +33636,9 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33529
33636
  const config = { projectGuid, ignore: ignore2 };
33530
33637
  const p = opts.progress;
33531
33638
  const baseline = readBaseline(projectGuid);
33532
- p?.phase("Scanning local files\u2026");
33639
+ p?.phase("Scanning local files...");
33533
33640
  const local = walkLocal(root, ignore2, baseline.files);
33534
- p?.phase("Checking Gipity for changes\u2026");
33641
+ p?.phase("Checking Gipity for changes...");
33535
33642
  const remote = await fetchRemote(projectGuid);
33536
33643
  for (const path5 of [...remote.keys()]) {
33537
33644
  if (shouldIgnore(path5, ignore2)) remote.delete(path5);
@@ -33542,7 +33649,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33542
33649
  const r = remote.get(path5);
33543
33650
  if (r?.sha256 || baseline.files[path5]) needHash.push(path5);
33544
33651
  }
33545
- if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}\u2026`);
33652
+ if (needHash.length) p?.phase(`Hashing ${needHash.length} file${needHash.length === 1 ? "" : "s"}...`);
33546
33653
  await ensureLocalHashes(root, local, needHash);
33547
33654
  const planned = plan(local, remote, baseline.files);
33548
33655
  if (opts.plan) {
@@ -33628,13 +33735,13 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33628
33735
  if (buf) downloadedBytes.set(a.path, buf);
33629
33736
  }
33630
33737
  } catch (err) {
33631
- errors.push(`Bulk download incomplete (${err.message}); recovering files individually\u2026`);
33738
+ errors.push(`Bulk download incomplete (${err.message}); recovering files individually...`);
33632
33739
  } finally {
33633
33740
  p?.finish();
33634
33741
  }
33635
33742
  const missing = wantedDownloads.filter((a) => !downloadedBytes.has(a.path));
33636
33743
  if (missing.length) {
33637
- p?.phase(`Recovering ${missing.length} file${missing.length === 1 ? "" : "s"} the bulk download dropped\u2026`);
33744
+ p?.phase(`Recovering ${missing.length} file${missing.length === 1 ? "" : "s"} the bulk download dropped...`);
33638
33745
  for (const a of missing) {
33639
33746
  const expectedSha = remote.get(a.path)?.sha256 ?? void 0;
33640
33747
  let buf = null;
@@ -33676,7 +33783,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33676
33783
  if (currentBytes) {
33677
33784
  mkdirSync4(dirname4(full), { recursive: true });
33678
33785
  writeFileSync5(full, currentBytes);
33679
- const stat2 = statSync(full);
33786
+ const stat2 = statSync2(full);
33680
33787
  baseline.files[a.path] = {
33681
33788
  size: stat2.size,
33682
33789
  mtime: stat2.mtime.toISOString(),
@@ -33692,12 +33799,12 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33692
33799
  renamedRel,
33693
33800
  { expectedServerVersion: null }
33694
33801
  );
33695
- const stat2 = statSync(renamedFull);
33696
- const { sha256: sha2562 } = await hashFile(renamedFull);
33802
+ const stat2 = statSync2(renamedFull);
33803
+ const { sha256 } = await hashFile(renamedFull);
33697
33804
  baseline.files[renamedRel] = {
33698
33805
  size: stat2.size,
33699
33806
  mtime: stat2.mtime.toISOString(),
33700
- sha256: sha2562,
33807
+ sha256,
33701
33808
  serverVersion: result.serverVersion
33702
33809
  };
33703
33810
  } catch (e) {
@@ -33733,7 +33840,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33733
33840
  }
33734
33841
  mkdirSync4(dirname4(full), { recursive: true });
33735
33842
  writeFileSync5(full, buf);
33736
- const stat2 = statSync(full);
33843
+ const stat2 = statSync2(full);
33737
33844
  local.set(a.path, { size: stat2.size, mtime: stat2.mtime.toISOString(), sha256: void 0 });
33738
33845
  baseline.files[a.path] = {
33739
33846
  size: stat2.size,
@@ -33764,7 +33871,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33764
33871
  if (buf) {
33765
33872
  mkdirSync4(dirname4(full), { recursive: true });
33766
33873
  writeFileSync5(full, buf);
33767
- const stat2 = statSync(full);
33874
+ const stat2 = statSync2(full);
33768
33875
  baseline.files[a.path] = {
33769
33876
  size: stat2.size,
33770
33877
  mtime: stat2.mtime.toISOString(),
@@ -33778,12 +33885,12 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33778
33885
  const result = await uploadOneFile(config.projectGuid, renamed, a.renamedLocalTo, {
33779
33886
  expectedServerVersion: null
33780
33887
  });
33781
- const stat2 = statSync(renamed);
33782
- const { sha256: sha2562 } = await hashFile(renamed);
33888
+ const stat2 = statSync2(renamed);
33889
+ const { sha256 } = await hashFile(renamed);
33783
33890
  baseline.files[a.renamedLocalTo] = {
33784
33891
  size: stat2.size,
33785
33892
  mtime: stat2.mtime.toISOString(),
33786
- sha256: sha2562,
33893
+ sha256,
33787
33894
  serverVersion: result.serverVersion
33788
33895
  };
33789
33896
  } catch (err) {
@@ -33817,14 +33924,14 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33817
33924
  continue;
33818
33925
  }
33819
33926
  try {
33820
- const stat2 = statSync(full);
33927
+ const stat2 = statSync2(full);
33821
33928
  if (stat2.size > UPLOAD_MAX_BYTES) {
33822
33929
  errors.push(`Upload failed for ${a.path}: file exceeds the 30 GB upload limit`);
33823
33930
  onBytes?.(a.localSize ?? 0);
33824
33931
  continue;
33825
33932
  }
33826
- const sha2562 = local.get(a.path)?.sha256 ?? (await hashFile(full)).sha256;
33827
- prepared.push({ a, full, size: stat2.size, mtime: stat2.mtime.toISOString(), sha256: sha2562 });
33933
+ const sha256 = local.get(a.path)?.sha256 ?? (await hashFile(full)).sha256;
33934
+ prepared.push({ a, full, size: stat2.size, mtime: stat2.mtime.toISOString(), sha256 });
33828
33935
  } catch (e) {
33829
33936
  errors.push(`Upload failed for ${a.path}: ${e.message}`);
33830
33937
  onBytes?.(a.localSize ?? 0);
@@ -33971,13 +34078,13 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33971
34078
  const full = resolveInRoot(root, a.path);
33972
34079
  mkdirSync4(dirname4(full), { recursive: true });
33973
34080
  writeFileSync5(full, buf);
33974
- const stat2 = statSync(full);
33975
- const { sha256: sha2562 } = await hashFile(full);
34081
+ const stat2 = statSync2(full);
34082
+ const { sha256 } = await hashFile(full);
33976
34083
  const current = typeof err.data?.current_server_version === "number" ? err.data.current_server_version : null;
33977
34084
  baseline.files[a.path] = {
33978
34085
  size: stat2.size,
33979
34086
  mtime: stat2.mtime.toISOString(),
33980
- sha256: sha2562,
34087
+ sha256,
33981
34088
  serverVersion: current ?? a.expectedServerVersion ?? 0
33982
34089
  };
33983
34090
  errors.push(`Could not delete ${a.path}: server has a newer version - restored the server copy locally (delete it again and re-sync to confirm)`);
@@ -34021,7 +34128,7 @@ async function syncInner(projectGuid, root, ignore2, opts, interactive) {
34021
34128
  function cleanupEmptyDirs(root, emptiedDirs) {
34022
34129
  const isEmpty = (dir) => {
34023
34130
  try {
34024
- return readdirSync3(dir).length === 0;
34131
+ return readdirSync4(dir).length === 0;
34025
34132
  } catch {
34026
34133
  return false;
34027
34134
  }
@@ -34056,12 +34163,12 @@ async function pushFile(filePath) {
34056
34163
  const result = await uploadOneFile(config.projectGuid, filePath, rel2, {
34057
34164
  expectedServerVersion: baseEntry ? baseEntry.serverVersion : null
34058
34165
  });
34059
- const stat2 = statSync(filePath);
34060
- const { sha256: sha2562 } = await hashFile(filePath);
34166
+ const stat2 = statSync2(filePath);
34167
+ const { sha256 } = await hashFile(filePath);
34061
34168
  baseline.files[rel2] = {
34062
34169
  size: stat2.size,
34063
34170
  mtime: stat2.mtime.toISOString(),
34064
- sha256: sha2562,
34171
+ sha256,
34065
34172
  serverVersion: result.serverVersion
34066
34173
  };
34067
34174
  writeBaseline(baseline);
@@ -34249,11 +34356,11 @@ async function syncBeforeAction(opts) {
34249
34356
  }
34250
34357
 
34251
34358
  // src/helpers/body.ts
34252
- import { readFileSync as readFileSync7 } from "node:fs";
34359
+ import { readFileSync as readFileSync8 } from "node:fs";
34253
34360
  function readStdin() {
34254
34361
  if (process.stdin.isTTY) return "";
34255
34362
  try {
34256
- return readFileSync7(0, "utf-8");
34363
+ return readFileSync8(0, "utf-8");
34257
34364
  } catch {
34258
34365
  return "";
34259
34366
  }
@@ -34269,7 +34376,7 @@ function readFileField(spec) {
34269
34376
  if (!field) throw new Error(`Invalid --file '${spec}': missing field name before '='.`);
34270
34377
  if (!path5) throw new Error(`Invalid --file '${spec}': missing file path after '='.`);
34271
34378
  try {
34272
- return [field, { data: readFileSync7(path5).toString("base64"), media_type: guessMime(path5) }];
34379
+ return [field, { data: readFileSync8(path5).toString("base64"), media_type: guessMime(path5) }];
34273
34380
  } catch (e) {
34274
34381
  throw new Error(`Cannot read --file '${path5}': ${e.message}`);
34275
34382
  }
@@ -34300,7 +34407,7 @@ function resolveJsonBody(raw) {
34300
34407
  } else if (raw.startsWith("@")) {
34301
34408
  const path5 = raw.slice(1);
34302
34409
  try {
34303
- source = readFileSync7(path5, "utf-8");
34410
+ source = readFileSync8(path5, "utf-8");
34304
34411
  } catch (e) {
34305
34412
  throw new Error(`Cannot read body file '${path5}': ${e.message}`);
34306
34413
  }
@@ -34381,23 +34488,23 @@ tokenCommand.command("revoke <short_guid>").alias("rm").description("Revoke an a
34381
34488
  init_api();
34382
34489
  init_config();
34383
34490
  init_auth();
34384
- import { basename, resolve as resolve7, dirname as dirname5 } from "path";
34385
- import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
34491
+ import { basename as basename2, resolve as resolve7, dirname as dirname5 } from "path";
34492
+ import { existsSync as existsSync8, readFileSync as readFileSync11 } from "fs";
34386
34493
  init_colors();
34387
34494
  init_utils();
34388
34495
 
34389
34496
  // src/adopt-cwd.ts
34390
34497
  init_api();
34391
- import { readdirSync as readdirSync5, statSync as statSync3 } from "fs";
34392
- import { join as join8, resolve as resolve6, sep as sep2, parse as parsePath } from "path";
34498
+ import { readdirSync as readdirSync6, statSync as statSync4 } from "fs";
34499
+ import { join as join9, resolve as resolve6, sep as sep2, parse as parsePath } from "path";
34393
34500
  import { homedir as homedir6 } from "os";
34394
34501
 
34395
34502
  // src/project-setup.ts
34396
34503
  init_config();
34397
34504
 
34398
34505
  // src/template-vars.ts
34399
- import { promises as fs, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
34400
- import { join as join5, relative as relative2, extname as extname3 } from "path";
34506
+ import { promises as fs, readdirSync as readdirSync5, statSync as statSync3 } from "fs";
34507
+ import { join as join6, relative as relative2, extname as extname3 } from "path";
34401
34508
  var SUBSTITUTABLE_EXTS = /* @__PURE__ */ new Set([
34402
34509
  ".html",
34403
34510
  ".htm",
@@ -34463,18 +34570,18 @@ function* walkTextFiles(root) {
34463
34570
  const dir = stack.pop();
34464
34571
  let entries;
34465
34572
  try {
34466
- entries = readdirSync4(dir, { withFileTypes: true });
34573
+ entries = readdirSync5(dir, { withFileTypes: true });
34467
34574
  } catch {
34468
34575
  continue;
34469
34576
  }
34470
34577
  for (const entry of entries) {
34471
34578
  if (isSyncIgnored(entry.name)) continue;
34472
- const full = join5(dir, entry.name);
34579
+ const full = join6(dir, entry.name);
34473
34580
  if (entry.isDirectory()) {
34474
34581
  stack.push(full);
34475
34582
  } else if (entry.isFile()) {
34476
34583
  try {
34477
- if (!statSync2(full).isFile()) continue;
34584
+ if (!statSync3(full).isFile()) continue;
34478
34585
  } catch {
34479
34586
  continue;
34480
34587
  }
@@ -34491,7 +34598,7 @@ async function substituteDir(dir, vars) {
34491
34598
  for (const rel2 of walkTextFiles(dir)) {
34492
34599
  const ext = extname3(rel2).toLowerCase();
34493
34600
  if (!SUBSTITUTABLE_EXTS.has(ext)) continue;
34494
- const abs = join5(dir, rel2);
34601
+ const abs = join6(dir, rel2);
34495
34602
  let content;
34496
34603
  try {
34497
34604
  content = await fs.readFile(abs, "utf-8");
@@ -34557,18 +34664,18 @@ async function finalizeLocalProject(opts) {
34557
34664
  }
34558
34665
 
34559
34666
  // src/relay/paths.ts
34560
- import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
34667
+ import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
34561
34668
  import { homedir as homedir4 } from "os";
34562
- import { join as join6, resolve as resolve5 } from "path";
34669
+ import { join as join7, resolve as resolve5 } from "path";
34563
34670
  function getProjectsRoot() {
34564
- const settingsPath = join6(homedir4(), ".gipity", "settings.json");
34565
- const defaultDir = join6(homedir4(), "GipityProjects");
34671
+ const settingsPath = join7(homedir4(), ".gipity", "settings.json");
34672
+ const defaultDir = join7(homedir4(), "GipityProjects");
34566
34673
  try {
34567
34674
  if (existsSync6(settingsPath)) {
34568
- const settings = JSON.parse(readFileSync8(settingsPath, "utf-8"));
34675
+ const settings = JSON.parse(readFileSync9(settingsPath, "utf-8"));
34569
34676
  if (settings.projectsDir) return resolve5(settings.projectsDir);
34570
34677
  } else {
34571
- mkdirSync5(join6(homedir4(), ".gipity"), { recursive: true });
34678
+ mkdirSync5(join7(homedir4(), ".gipity"), { recursive: true });
34572
34679
  writeFileSync6(settingsPath, JSON.stringify({ projectsDir: defaultDir }, null, 2) + "\n");
34573
34680
  }
34574
34681
  } catch {
@@ -34577,11 +34684,11 @@ function getProjectsRoot() {
34577
34684
  }
34578
34685
 
34579
34686
  // src/relay/state.ts
34580
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
34581
- import { join as join7 } from "path";
34687
+ import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync6, existsSync as existsSync7, chmodSync as chmodSync2, unlinkSync as unlinkSync4 } from "fs";
34688
+ import { join as join8 } from "path";
34582
34689
  import { homedir as homedir5 } from "os";
34583
- var RELAY_DIR = process.env.GIPITY_DIR || join7(homedir5(), ".gipity");
34584
- var RELAY_FILE = join7(RELAY_DIR, "relay.json");
34690
+ var RELAY_DIR = process.env.GIPITY_DIR || join8(homedir5(), ".gipity");
34691
+ var RELAY_FILE = join8(RELAY_DIR, "relay.json");
34585
34692
  var FILE_MODE = 384;
34586
34693
  function emptyState() {
34587
34694
  return { device: null, paused: false };
@@ -34589,7 +34696,7 @@ function emptyState() {
34589
34696
  function loadState() {
34590
34697
  if (!existsSync7(RELAY_FILE)) return emptyState();
34591
34698
  try {
34592
- const raw = JSON.parse(readFileSync9(RELAY_FILE, "utf-8"));
34699
+ const raw = JSON.parse(readFileSync10(RELAY_FILE, "utf-8"));
34593
34700
  return {
34594
34701
  device: raw.device ?? null,
34595
34702
  paused: Boolean(raw.paused),
@@ -34674,7 +34781,7 @@ function diagnosticsConsented() {
34674
34781
  if (env && env !== "0" && env.toLowerCase() !== "false") return false;
34675
34782
  return loadState().diagnostics_consent !== false;
34676
34783
  }
34677
- var RELAY_PID_FILE = join7(RELAY_DIR, "relay.pid");
34784
+ var RELAY_PID_FILE = join8(RELAY_DIR, "relay.pid");
34678
34785
  function getDaemonPidPath() {
34679
34786
  return RELAY_PID_FILE;
34680
34787
  }
@@ -34691,7 +34798,7 @@ function clearDaemonPid() {
34691
34798
  function isDaemonRunning() {
34692
34799
  if (!existsSync7(RELAY_PID_FILE)) return false;
34693
34800
  try {
34694
- const raw = readFileSync9(RELAY_PID_FILE, "utf-8").trim();
34801
+ const raw = readFileSync10(RELAY_PID_FILE, "utf-8").trim();
34695
34802
  const pid = parseInt(raw, 10);
34696
34803
  if (!pid || isNaN(pid)) {
34697
34804
  try {
@@ -34731,17 +34838,17 @@ function scanForAdoption(cwd) {
34731
34838
  if (truncated) return;
34732
34839
  let entries;
34733
34840
  try {
34734
- entries = readdirSync5(dir);
34841
+ entries = readdirSync6(dir);
34735
34842
  } catch {
34736
34843
  return;
34737
34844
  }
34738
34845
  for (const name of entries) {
34739
34846
  if (truncated) return;
34740
34847
  if (isSyncIgnored(name)) continue;
34741
- const full = join8(dir, name);
34848
+ const full = join9(dir, name);
34742
34849
  let st2;
34743
34850
  try {
34744
- st2 = statSync3(full);
34851
+ st2 = statSync4(full);
34745
34852
  } catch {
34746
34853
  continue;
34747
34854
  }
@@ -34767,7 +34874,7 @@ function scanForAdoption(cwd) {
34767
34874
  function isLikelyEmpty(cwd) {
34768
34875
  let entries;
34769
34876
  try {
34770
- entries = readdirSync5(cwd);
34877
+ entries = readdirSync6(cwd);
34771
34878
  } catch {
34772
34879
  return true;
34773
34880
  }
@@ -34805,18 +34912,18 @@ function canAdoptCwd(cwd) {
34805
34912
  function countGitRepoChildren(dir) {
34806
34913
  let entries;
34807
34914
  try {
34808
- entries = readdirSync5(dir);
34915
+ entries = readdirSync6(dir);
34809
34916
  } catch {
34810
34917
  return 0;
34811
34918
  }
34812
34919
  let count = 0;
34813
34920
  for (const name of entries.slice(0, 50)) {
34814
34921
  if (name.startsWith(".")) continue;
34815
- const child = join8(dir, name);
34922
+ const child = join9(dir, name);
34816
34923
  try {
34817
- if (!statSync3(child).isDirectory()) continue;
34818
- const gitPath = join8(child, ".git");
34819
- if (statSync3(gitPath)) count++;
34924
+ if (!statSync4(child).isDirectory()) continue;
34925
+ const gitPath = join9(child, ".git");
34926
+ if (statSync4(gitPath)) count++;
34820
34927
  } catch {
34821
34928
  }
34822
34929
  if (count >= 3) return count;
@@ -34830,7 +34937,7 @@ function formatCwdLabel(cwd) {
34830
34937
  if (norm.startsWith(home + sep2)) {
34831
34938
  const tail = norm.slice(home.length + 1).split(sep2);
34832
34939
  if (tail.length <= 2) return "~/" + tail.join("/");
34833
- return "~/\u2026/" + tail.slice(-2).join("/");
34940
+ return "~/.../" + tail.slice(-2).join("/");
34834
34941
  }
34835
34942
  const parts = norm.split(sep2).filter(Boolean);
34836
34943
  if (parts.length <= 1) return norm;
@@ -34995,7 +35102,7 @@ Working with an existing Gipity project:
34995
35102
  process.exit(1);
34996
35103
  }
34997
35104
  }
34998
- const projectName = (name || basename(cwd)).slice(0, 100);
35105
+ const projectName = (name || basename2(cwd)).slice(0, 100);
34999
35106
  const projectSlug = slugify(projectName);
35000
35107
  if (!projectSlug) {
35001
35108
  console.error(error("Could not derive a valid project slug. Provide a name: gipity init my-app"));
@@ -35037,7 +35144,7 @@ Working with an existing Gipity project:
35037
35144
  if (adopted.applied > 0) console.log(`Synced ${adopted.applied} change${adopted.applied > 1 ? "s" : ""} with Gipity.`);
35038
35145
  if (opts.capture === false) {
35039
35146
  try {
35040
- const cfg = JSON.parse(readFileSync10(resolve7(cwd, ".gipity.json"), "utf-8"));
35147
+ const cfg = JSON.parse(readFileSync11(resolve7(cwd, ".gipity.json"), "utf-8"));
35041
35148
  cfg.captureHooks = false;
35042
35149
  saveConfigAt(cwd, cfg);
35043
35150
  } catch {
@@ -35071,15 +35178,15 @@ init_auth();
35071
35178
  init_api();
35072
35179
  init_config();
35073
35180
  init_colors();
35074
- import { existsSync as existsSync9, readFileSync as readFileSync11 } from "fs";
35075
- import { join as join9, resolve as resolve8 } from "path";
35181
+ import { existsSync as existsSync9, readFileSync as readFileSync12 } from "fs";
35182
+ import { join as join10, resolve as resolve8 } from "path";
35076
35183
  import { homedir as homedir7 } from "os";
35077
35184
  function checkGipityPlugin() {
35078
- const path5 = join9(homedir7(), ".claude", "settings.json");
35185
+ const path5 = join10(homedir7(), ".claude", "settings.json");
35079
35186
  let settings = {};
35080
35187
  if (existsSync9(path5)) {
35081
35188
  try {
35082
- settings = JSON.parse(readFileSync11(path5, "utf-8"));
35189
+ settings = JSON.parse(readFileSync12(path5, "utf-8"));
35083
35190
  } catch {
35084
35191
  }
35085
35192
  }
@@ -35092,12 +35199,14 @@ function checkGipityPlugin() {
35092
35199
  return { missing, ok: missing.length === 0, stale };
35093
35200
  }
35094
35201
  async function probeAuth(loggedIn) {
35095
- if (!loggedIn && !usingEnvToken()) return "none";
35096
- if (!usingEnvToken() && sessionExpired()) return "expired";
35097
- const timeout = new Promise((res) => setTimeout(() => res("unreachable"), 5e3).unref?.());
35202
+ if (!loggedIn && !usingEnvToken()) return { state: "none", account: null };
35203
+ if (!usingEnvToken() && sessionExpired()) return { state: "expired", account: null };
35204
+ const timeout = new Promise(
35205
+ (res) => setTimeout(() => res({ state: "unreachable", account: null }), 5e3).unref?.()
35206
+ );
35098
35207
  const call = get("/users/me").then(
35099
- () => "ok",
35100
- (err) => err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable"
35208
+ (res) => ({ state: "ok", account: res.data?.accountSlug ?? null }),
35209
+ (err) => ({ state: err instanceof ApiError && err.statusCode === 401 ? "rejected" : "unreachable", account: null })
35101
35210
  );
35102
35211
  return Promise.race([call, timeout]);
35103
35212
  }
@@ -35109,6 +35218,8 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
35109
35218
  const hookCheck = config ? checkGipityPlugin() : null;
35110
35219
  const queueDelivered = auth && !sessionExpired() ? await flushBugQueue().catch(() => 0) : 0;
35111
35220
  const probe = await probeAuth(!!auth);
35221
+ const accountMismatch = probe.state === "ok" && !!config && !!probe.account && probe.account !== config.accountSlug;
35222
+ const apiBaseInUse = resolveApiBase();
35112
35223
  if (opts.json) {
35113
35224
  console.log(JSON.stringify({
35114
35225
  project: config ? {
@@ -35116,17 +35227,21 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
35116
35227
  slug: config.projectSlug,
35117
35228
  account: config.accountSlug,
35118
35229
  apiBase: config.apiBase,
35230
+ apiBaseInUse,
35119
35231
  url: liveUrl(config)
35120
35232
  } : null,
35121
35233
  // `valid` reflects the refresh token (the real session) - access
35122
35234
  // tokens auto-renew, so their expiry must not read as "invalid".
35123
35235
  // `probe` is what one live call just proved: 'rejected' means every
35124
35236
  // authenticated command will fail even though `valid` reads true.
35237
+ // 'mismatch' overrides 'ok' when the live account isn't the one that
35238
+ // owns this project - `valid` still reads true (the token IS valid).
35125
35239
  auth: auth || usingEnvToken() ? {
35126
35240
  email: auth?.email,
35241
+ account: probe.account,
35127
35242
  source: usingEnvToken() ? "agent-token" : "session",
35128
- valid: usingEnvToken() ? probe !== "rejected" : !sessionExpired(),
35129
- probe
35243
+ valid: usingEnvToken() ? probe.state !== "rejected" : !sessionExpired(),
35244
+ probe: accountMismatch ? "mismatch" : probe.state
35130
35245
  } : null,
35131
35246
  plugin: hookCheck
35132
35247
  }, null, 2));
@@ -35139,18 +35254,25 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
35139
35254
  console.log(`${muted("Account:")} ${config.accountSlug}`);
35140
35255
  console.log(`${muted("Live:")} ${liveUrl(config)}`);
35141
35256
  console.log(`${muted("API:")} ${config.apiBase}`);
35257
+ if (apiBaseInUse !== config.apiBase) {
35258
+ console.log(`${muted("API (in use):")} ${warning(apiBaseInUse)} ${muted("(overrides .gipity.json \u2014 GIPITY_API_BASE / --api-base / allowlist)")}`);
35259
+ }
35142
35260
  if (config.agentGuid) console.log(`${muted("Agent:")} ${config.agentGuid}`);
35143
35261
  }
35144
35262
  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)")}` : ""}`);
35263
+ console.log(`${muted("Auth:")} ${probe.state === "rejected" ? warning("agent API token (GIPITY_TOKEN) rejected by the server \u2014 mint a new one: gipity skill read agent-deploy") : success("agent API token (GIPITY_TOKEN)")}${probe.state === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
35146
35264
  } else if (!auth) {
35147
35265
  console.log(`${muted("Auth:")} ${warning("not logged in. Run: gipity login")}`);
35148
- } else if (probe === "expired") {
35266
+ } else if (probe.state === "expired") {
35149
35267
  console.log(`${muted("Auth:")} ${warning(`session expired for ${auth.email}. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
35150
- } else if (probe === "rejected") {
35268
+ } else if (probe.state === "rejected") {
35151
35269
  console.log(`${muted("Auth:")} ${warning(`session for ${auth.email} was rejected by the server. Run: gipity login (headless/CI: set GIPITY_TOKEN \u2014 gipity skill read agent-deploy)`)}`);
35152
35270
  } else {
35153
- console.log(`${muted("Auth:")} ${success(auth.email)}${probe === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
35271
+ console.log(`${muted("Auth:")} ${success(auth.email)}${probe.state === "unreachable" ? ` ${muted("(unverified \u2014 API unreachable)")}` : ""}`);
35272
+ }
35273
+ if (accountMismatch) {
35274
+ console.log(`${muted("Account:")} ${warning(`logged-in account (${probe.account}) differs from this project's account (${config.accountSlug}). If you didn't expect this you may be logged into the wrong account \u2014 run: gipity login`)}`);
35275
+ console.log(muted("(If this project was shared with you via gipity rbac, this is expected.)"));
35154
35276
  }
35155
35277
  if (queueDelivered > 0) {
35156
35278
  console.log(`${muted("Bug queue:")} ${success(`delivered ${queueDelivered} queued bug report${queueDelivered === 1 ? "" : "s"}`)}`);
@@ -35181,7 +35303,10 @@ var statusCommand = new Command("status").alias("whoami").description("Show proj
35181
35303
  });
35182
35304
 
35183
35305
  // src/commands/sync.ts
35306
+ import { dirname as dirname6 } from "node:path";
35184
35307
  init_colors();
35308
+ init_utils();
35309
+ init_config();
35185
35310
  var syncCommand = new Command("sync").description("Sync files").addHelpText("after", "\nAdd a .gipityignore at the project root to exclude paths (gitignore-style).").option("--plan", "Print the plan without applying any changes").option("--force", "Bypass the bulk-deletion guard").option("--prune", "Remove files that exist on Gipity but not locally (applies the bulk deletes the guard defers)").option("--json", "Output as JSON").action(async (opts) => {
35186
35311
  try {
35187
35312
  const progress = opts.json ? void 0 : createProgressReporter();
@@ -35194,6 +35319,16 @@ var syncCommand = new Command("sync").description("Sync files").addHelpText("aft
35194
35319
  console.log(`
35195
35320
  ${result.applied} action${result.applied > 1 ? "s" : ""} applied.`);
35196
35321
  }
35322
+ if (isWsl()) {
35323
+ const configPath = getConfigPath();
35324
+ const twin = configPath ? findWindowsTwinProject(dirname6(configPath)) : null;
35325
+ if (twin) {
35326
+ console.log(muted(
35327
+ `
35328
+ Note: a Windows-side copy of this project exists at ${wslPathToWindows(twin)} and is NOT synced. If you added files there, move them into this folder so they sync.`
35329
+ ));
35330
+ }
35331
+ }
35197
35332
  if (result.deferredDeletes > 0) {
35198
35333
  console.log(muted(
35199
35334
  `
@@ -35241,8 +35376,8 @@ var pushCommand = new Command("push").description("Push one or more files").argu
35241
35376
  // src/commands/upload.ts
35242
35377
  init_api();
35243
35378
  init_config();
35244
- import { statSync as statSync4 } from "fs";
35245
- import { basename as basename2 } from "path";
35379
+ import { statSync as statSync5 } from "fs";
35380
+ import { basename as basename3 } from "path";
35246
35381
  init_utils();
35247
35382
  init_colors();
35248
35383
  var uploadCommand = new Command("upload").description(`Upload a local file and print a durable, worker-reachable URL for it.
@@ -35259,14 +35394,14 @@ token-signed serve url instead (reachable only by holders of the url).`).argumen
35259
35394
  const { config } = await resolveProjectContext();
35260
35395
  let size;
35261
35396
  try {
35262
- const st2 = statSync4(file);
35397
+ const st2 = statSync5(file);
35263
35398
  if (!st2.isFile()) throw new Error("not a regular file");
35264
35399
  size = st2.size;
35265
35400
  } catch (err) {
35266
35401
  throw new Error(`can't read ${file}: ${err.message}`);
35267
35402
  }
35268
35403
  if (size === 0) throw new Error(`${file} is empty - nothing to upload.`);
35269
- const filename = basename2(file);
35404
+ const filename = basename3(file);
35270
35405
  const contentType = opts.contentType || guessMime(file);
35271
35406
  const doUpload = async () => {
35272
35407
  const init = await post(`/api/${config.projectGuid}/uploads/init`, {
@@ -35283,7 +35418,7 @@ token-signed serve url instead (reachable only by holders of the url).`).argumen
35283
35418
  const comp = await post(`/api/${config.projectGuid}/uploads/complete`, completeBody);
35284
35419
  return comp.data;
35285
35420
  };
35286
- const data = opts.json ? await doUpload() : await withSpinner(`Uploading ${filename} (${formatSize(size)})\u2026`, doUpload, { done: null });
35421
+ const data = opts.json ? await doUpload() : await withSpinner(`Uploading ${filename} (${formatSize(size)})...`, doUpload, { done: null });
35287
35422
  if (opts.json) {
35288
35423
  console.log(JSON.stringify(data));
35289
35424
  return;
@@ -35307,7 +35442,7 @@ init_colors();
35307
35442
  init_auth();
35308
35443
 
35309
35444
  // src/commands/page-eval.ts
35310
- import { readFileSync as readFileSync13 } from "node:fs";
35445
+ import { readFileSync as readFileSync14 } from "node:fs";
35311
35446
  init_api();
35312
35447
  init_colors();
35313
35448
  init_auth();
@@ -35316,8 +35451,8 @@ init_config();
35316
35451
  // src/page-fixtures.ts
35317
35452
  init_api();
35318
35453
  init_config();
35319
- import { readFileSync as readFileSync12, statSync as statSync5, existsSync as existsSync10, readdirSync as readdirSync6 } from "node:fs";
35320
- import { basename as basename3, resolve as resolve10, relative as relative3, join as join10 } from "node:path";
35454
+ import { readFileSync as readFileSync13, statSync as statSync6, existsSync as existsSync10, readdirSync as readdirSync7 } from "node:fs";
35455
+ import { basename as basename4, resolve as resolve10, relative as relative3, join as join11 } from "node:path";
35321
35456
  var FIND_SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", ".gipity", "dist", "build", ".next", "coverage"]);
35322
35457
  function findByBasename(root, name, limit = 3) {
35323
35458
  const hits = [];
@@ -35328,15 +35463,15 @@ function findByBasename(root, name, limit = 3) {
35328
35463
  visited++;
35329
35464
  let entries;
35330
35465
  try {
35331
- entries = readdirSync6(dir, { withFileTypes: true });
35466
+ entries = readdirSync7(dir, { withFileTypes: true });
35332
35467
  } catch {
35333
35468
  continue;
35334
35469
  }
35335
35470
  for (const e of entries) {
35336
35471
  if (e.isDirectory()) {
35337
- if (!FIND_SKIP.has(e.name)) queue.push(join10(dir, e.name));
35472
+ if (!FIND_SKIP.has(e.name)) queue.push(join11(dir, e.name));
35338
35473
  } else if (e.name === name && hits.length < limit) {
35339
- hits.push(join10(dir, e.name));
35474
+ hits.push(join11(dir, e.name));
35340
35475
  }
35341
35476
  }
35342
35477
  }
@@ -35346,19 +35481,19 @@ function assertLocalAsset(flag, localPath) {
35346
35481
  const abs = resolve10(localPath);
35347
35482
  if (existsSync10(abs)) return;
35348
35483
  const root = getProjectRoot() ?? process.cwd();
35349
- const elsewhere = findByBasename(root, basename3(localPath)).filter((p) => p !== abs);
35484
+ const elsewhere = findByBasename(root, basename4(localPath)).filter((p) => p !== abs);
35350
35485
  const found = elsewhere.length ? `
35351
35486
  That file DOES exist here \u2014 pass this path instead:
35352
35487
  ${elsewhere.map((p) => ` ${flag} ${relative3(process.cwd(), p) || p}`).join("\n")}` : `
35353
- Nothing named "${basename3(localPath)}" under ${root} either. Generate a frame with \`gipity generate image "<description>" -o ${localPath}\`, or check the path.`;
35488
+ Nothing named "${basename4(localPath)}" under ${root} either. Generate a frame with \`gipity generate image "<description>" -o ${localPath}\`, or check the path.`;
35354
35489
  throw new Error(
35355
35490
  `${flag} ${localPath}: no such file \u2014 looked for ${abs} (relative paths resolve against the current directory, ${process.cwd()}).${found}`
35356
35491
  );
35357
35492
  }
35358
35493
  async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
35359
35494
  assertLocalAsset(flag, localPath);
35360
- const name = basename3(localPath);
35361
- const size = statSync5(localPath).size;
35495
+ const name = basename4(localPath);
35496
+ const size = statSync6(localPath).size;
35362
35497
  const contentType = guessMime(localPath);
35363
35498
  const init = await post(
35364
35499
  `/api/${projectGuid}/uploads/init`,
@@ -35370,7 +35505,7 @@ async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
35370
35505
  const res = await fetch(init.data.url, {
35371
35506
  method: "PUT",
35372
35507
  headers: { "Content-Type": contentType },
35373
- body: readFileSync12(localPath)
35508
+ body: readFileSync13(localPath)
35374
35509
  });
35375
35510
  if (!res.ok) {
35376
35511
  throw new Error(`upload of fixture "${name}" failed: ${res.status} ${res.statusText}`);
@@ -35390,7 +35525,7 @@ function assertCameraFile(localPath) {
35390
35525
  const ext = localPath.slice(localPath.lastIndexOf(".")).toLowerCase();
35391
35526
  if (CAMERA_EXTS.includes(ext)) return;
35392
35527
  throw new Error(
35393
- `--camera ${basename3(localPath)}: unsupported file type "${ext || "(none)"}" \u2014 the camera feed must be an image or video (${CAMERA_EXTS.join(", ")}).
35528
+ `--camera ${basename4(localPath)}: unsupported file type "${ext || "(none)"}" \u2014 the camera feed must be an image or video (${CAMERA_EXTS.join(", ")}).
35394
35529
  A still image is played as a looping single-frame feed, which is what a gesture/pose/object model needs.
35395
35530
  No frame to hand? Generate one: gipity generate image "a hand making a closed fist, palm to camera, plain background"`
35396
35531
  );
@@ -35443,7 +35578,7 @@ function summarizeExpr(expr) {
35443
35578
  const oneLine = meaningful.length <= 1;
35444
35579
  if (oneLine && expr.trim().length <= EXPR_ECHO_MAX_CHARS) return expr.trim();
35445
35580
  const first = (meaningful[0] ?? "").trim();
35446
- const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS - 1)}\u2026` : first;
35581
+ const head = first.length > EXPR_ECHO_MAX_CHARS ? `${first.slice(0, EXPR_ECHO_MAX_CHARS - 3)}...` : first;
35447
35582
  const shape = oneLine ? `(${expr.trim().length} chars)` : `(+${meaningful.length - 1} more ${meaningful.length - 1 === 1 ? "line" : "lines"}, ${expr.trim().length} chars)`;
35448
35583
  return `${head} ${shape}`;
35449
35584
  }
@@ -35470,7 +35605,7 @@ function capWaitForTimeoutMs(raw) {
35470
35605
  return WAIT_FOR_MAX_MS;
35471
35606
  }
35472
35607
  function readScriptFile(pathArg) {
35473
- return readFileSync13(pathArg === "-" ? 0 : pathArg, "utf8");
35608
+ return readFileSync14(pathArg === "-" ? 0 : pathArg, "utf8");
35474
35609
  }
35475
35610
  function parsesAsExpression(s) {
35476
35611
  try {
@@ -35566,7 +35701,7 @@ function evalExecTimeoutMessage(result, budgetMs) {
35566
35701
  If the slow part is a ONE-TIME page init (a WASM/model download, a big asset, a first-frame pipeline warm-up), do NOT split the body across several 'page eval' calls \u2014 every call is a fresh page load that re-pays that init, so each one hits this same wall. Have the page kick it off on load (not behind a click) and absorb it in the pre-eval window instead.`;
35567
35702
  }
35568
35703
  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' \u2026 EOF) with no tmp file. Runs as an async function body, so top-level return/await work. Same post-load budget as <expr> (--timeout).`).option(
35704
+ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real browser on a page (DOM, computed styles, element rects; inline expr or --file script). ONE client per call - to verify realtime/presence across concurrent clients use `page test --observe` instead").argument("<url>", "URL to load").argument("[expr]", `JavaScript to evaluate in page context (inline expression or statement body; await works, and the trailing expression is returned automatically \u2014 REPL-style \u2014 so no explicit return is needed; result is JSON-serialized). Omit when using --file. Time budget: the body has ${EVAL_SCRIPT_BUDGET_MS / 1e3}s to finish after page load (${EVAL_SCRIPT_BUDGET_CAMERA_MS / 1e3}s with --camera) - raise it with --timeout, max ${EVAL_SCRIPT_BUDGET_MAX_MS / 1e3}s.`).option("--file <path>", `Read the script body from a file instead of the inline <expr> arg (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' ... EOF) with no tmp file. Runs as an async function body, so top-level return/await work. Same post-load budget as <expr> (--timeout).`).option(
35570
35705
  "--step <expr>",
35571
35706
  `Run another expression against the SAME loaded page, after <expr> (repeat, max ${MAX_EVAL_STEPS}). Whatever that page load paid for \u2014 a vision model coming up, a game booting, a socket connecting \u2014 stays up for every step, so an N-part check costs ONE page load instead of N. Each step gets its own in-page budget and its own reported result.`,
35572
35707
  (val, prev) => [...prev, val],
@@ -35610,7 +35745,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
35610
35745
  try {
35611
35746
  expr = readScriptFile(opts.file);
35612
35747
  } catch {
35613
- pageEvalCommand.error(opts.file === "-" ? `error: --file - reads the script from stdin, but stdin was empty/unreadable \u2014 pipe it in, e.g. gipity page eval "<url>" --file - <<'EOF' \u2026 EOF` : `error: Cannot read file: ${opts.file}`);
35748
+ pageEvalCommand.error(opts.file === "-" ? `error: --file - reads the script from stdin, but stdin was empty/unreadable - pipe it in, e.g. gipity page eval "<url>" --file - <<'EOF' ... EOF` : `error: Cannot read file: ${opts.file}`);
35614
35749
  }
35615
35750
  }
35616
35751
  if (opts.reload !== void 0 && opts.reloadFile) {
@@ -35672,12 +35807,12 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
35672
35807
  projectGuid = config.projectGuid;
35673
35808
  }
35674
35809
  if (opts.camera) {
35675
- console.log(muted(`Hosting camera feed ${opts.camera}\u2026`));
35810
+ console.log(muted(`Hosting camera feed ${opts.camera}...`));
35676
35811
  camera = await uploadCameraFeed(projectGuid, opts.camera);
35677
35812
  }
35678
35813
  if (fixturePaths.length) {
35679
35814
  for (const p of fixturePaths) {
35680
- console.log(muted(`Hosting fixture ${p}\u2026`));
35815
+ console.log(muted(`Hosting fixture ${p}...`));
35681
35816
  hosted.push(await uploadPublicFixture(projectGuid, p));
35682
35817
  }
35683
35818
  const map = {};
@@ -35877,7 +36012,7 @@ init_auth();
35877
36012
  init_colors();
35878
36013
  init_utils();
35879
36014
  import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
35880
- import { dirname as dirname6, join as join11, resolve as resolvePath } from "path";
36015
+ import { dirname as dirname7, join as join12, resolve as resolvePath } from "path";
35881
36016
  var DEVICE_PRESETS = {
35882
36017
  default: { width: 1280, height: 720 },
35883
36018
  desktop: { width: 1920, height: 1080 },
@@ -35941,7 +36076,7 @@ function dimSuffix(vp2) {
35941
36076
  }
35942
36077
  function defaultScreenshotDir() {
35943
36078
  const root = getProjectRoot();
35944
- return join11(root ?? ".", "screenshots");
36079
+ return join12(root ?? ".", "screenshots");
35945
36080
  }
35946
36081
  function timestampSlug(d = /* @__PURE__ */ new Date()) {
35947
36082
  const p = (n) => String(n).padStart(2, "0");
@@ -36004,7 +36139,7 @@ var WAIT_FOR_MAX_MS2 = 15e3;
36004
36139
  var WAIT_FOR_DEFAULT_MS = 15e3;
36005
36140
  var MAX_POST_LOAD_DELAY_MS = 3e4;
36006
36141
  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 \u2014 e.g. click a button to enter a state ("document.getElementById('play').click()"). Runs as an async function body, so const/await and app-relative import('./\u2026') work. Runs after the post-load delay, then settles again before the shot. If it throws, the capture still happens and the failure is reported.`).option("--file <path>", "Read the pre-capture script from a file instead of inline --action (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' \u2026 EOF) with no tmp file. For a multi-step driver \u2014 click through a flow, wait for it to render \u2014 then capture. Same async-function-body semantics as --action; same --file flag as page eval.").option("--full", "Capture the full scrollable page (default: viewport only). Scrolls the page through first so scroll-reveal/fade-in-on-scroll (IntersectionObserver) sections render into the shot instead of capturing blank.").option("-o, --output <file>", "Output path (single viewport only; default .gipity/screenshots/ss-<host>-<timestamp>.png)").option("--no-reload-between", "Skip reload between viewports (faster, lower fidelity - only safe for static pages)").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps render headlessly. The video feed is a built-in test pattern \u2014 to capture what the app does with a REAL frame (a hand, a face, an object), use --camera <path> instead.").option("--camera <path>", "Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, then capture \u2014 so the shot shows the app reacting to a frame you chose (detected gesture, boxes, labels). Implies --fake-media.").option("--auth", "Capture the page signed in as you (your Gipity account), so UI behind a Sign-in-with-Gipity login is shown. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--ephemeral", "Skip the project screenshot history: do not persist this capture to Gipity (screenshots/ in the project). Local file is still written.").option("--json", "Output JSON metadata instead of a friendly summary").addOption(new Option("--post-load-delay <ms>", "Alias for --wait").hideHelp()).addOption(new Option("--full-page", "Alias for --full").hideHelp()).addOption(new Option("--width <px>", "Alias: --viewport WxH").hideHelp()).addOption(new Option("--height <px>", "Alias: --viewport WxH").hideHelp()).action((url, opts) => run("Page screenshot", async () => {
36142
+ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a web page").argument("<url>", "URL to screenshot").option("--device <names>", `Device preset(s): ${Object.keys(DEVICE_PRESETS).join(", ")} (comma-separated or repeat flag). mobile/tablet emulate a real touch device \u2014 touch events, mobile user-agent, DPR \u2014 so touch-gated mobile UI actually renders.`, appendOption, []).option("--viewport <dims>", "Raw viewport(s): WxH or WxH@dpr (comma-separated or repeat flag), e.g. --viewport 390x844 for a phone-sized window", appendOption, []).option("--wait <ms>", `Sleep this many ms after DOMContentLoaded before capturing (default 1000, max ${MAX_POST_LOAD_DELAY_MS}). With --camera it defaults to ${CAMERA_DEFAULT_DELAY_MS} so the vision model is up. Same flag as page eval/inspect.`).option("--wait-for <selector>", `Wait until this CSS selector appears before capturing, then capture (deterministic - beats guessing a --wait). Same flag as page eval/inspect. Gate on what you are photographing ('[data-vision="ready"]', '#verdict:not(:empty)'). Max ${WAIT_FOR_MAX_MS2}ms (--wait-timeout).`).option("--wait-timeout <ms>", `Max ms to wait for --wait-for before giving up (default ${WAIT_FOR_DEFAULT_MS}, max ${WAIT_FOR_MAX_MS2}). Timing out is reported - the capture still happens, so you see the state it got stuck in.`).option("--action <js>", `Run JS in the page before capturing - e.g. click a button to enter a state ("document.getElementById('play').click()"). Runs as an async function body, so const/await and app-relative import('./...') work. Runs after the post-load delay, then settles again before the shot. If it throws, the capture still happens and the failure is reported.`).option("--file <path>", "Read the pre-capture script from a file instead of inline --action (mutually exclusive), or --file - to read it from stdin (pipe a heredoc: --file - <<'EOF' ... EOF) with no tmp file. For a multi-step driver - click through a flow, wait for it to render - then capture. Same async-function-body semantics as --action; same --file flag as page eval.").option("--full", "Capture the full scrollable page (default: viewport only). Scrolls the page through first so scroll-reveal/fade-in-on-scroll (IntersectionObserver) sections render into the shot instead of capturing blank.").option("-o, --output <file>", "Output path (single viewport only; default .gipity/screenshots/ss-<host>-<timestamp>.png)").option("--no-reload-between", "Skip reload between viewports (faster, lower fidelity - only safe for static pages)").option("--fake-media", "Grant a synthetic microphone + camera and auto-accept the getUserMedia prompt, so voice/camera apps render headlessly. The video feed is a built-in test pattern \u2014 to capture what the app does with a REAL frame (a hand, a face, an object), use --camera <path> instead.").option("--camera <path>", "Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, then capture \u2014 so the shot shows the app reacting to a frame you chose (detected gesture, boxes, labels). Implies --fake-media.").option("--auth", "Capture the page signed in as you (your Gipity account), so UI behind a Sign-in-with-Gipity login is shown. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").option("--ephemeral", "Skip the project screenshot history: do not persist this capture to Gipity (screenshots/ in the project). Local file is still written.").option("--json", "Output JSON metadata instead of a friendly summary").addOption(new Option("--post-load-delay <ms>", "Alias for --wait").hideHelp()).addOption(new Option("--full-page", "Alias for --full").hideHelp()).addOption(new Option("--width <px>", "Alias: --viewport WxH").hideHelp()).addOption(new Option("--height <px>", "Alias: --viewport WxH").hideHelp()).action((url, opts) => run("Page screenshot", async () => {
36008
36143
  const aliasFlag = ACTION_ALIAS_FLAGS.find((f) => opts[f.slice(2)] !== void 0);
36009
36144
  if (aliasFlag) {
36010
36145
  console.error(muted(
@@ -36020,7 +36155,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
36020
36155
  try {
36021
36156
  actionScript = readScriptFile(opts.file);
36022
36157
  } catch {
36023
- throw new Error(opts.file === "-" ? `--file - reads the pre-capture script from stdin, but stdin was empty/unreadable \u2014 pipe it in, e.g. gipity page screenshot "<url>" --file - <<'EOF' \u2026 EOF` : `Cannot read file: ${opts.file}`);
36158
+ throw new Error(opts.file === "-" ? `--file - reads the pre-capture script from stdin, but stdin was empty/unreadable - pipe it in, e.g. gipity page screenshot "<url>" --file - <<'EOF' ... EOF` : `Cannot read file: ${opts.file}`);
36024
36159
  }
36025
36160
  }
36026
36161
  const delayRaw = opts.wait ?? opts.postLoadDelay;
@@ -36088,7 +36223,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
36088
36223
  if (opts.camera) {
36089
36224
  assertCameraFile(opts.camera);
36090
36225
  if (!projectGuid) throw new Error("--camera needs a linked project (the frame is hosted for the browser to fetch) \u2014 run `gipity link` first.");
36091
- console.log(muted(`Hosting camera feed ${opts.camera}\u2026`));
36226
+ console.log(muted(`Hosting camera feed ${opts.camera}...`));
36092
36227
  camera = await uploadCameraFeed(projectGuid, opts.camera);
36093
36228
  }
36094
36229
  const preCapture = [
@@ -36113,7 +36248,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
36113
36248
  let entries;
36114
36249
  try {
36115
36250
  try {
36116
- entries = opts.json ? await doShoot() : await withSpinner("Capturing\u2026", doShoot, { done: null });
36251
+ entries = opts.json ? await doShoot() : await withSpinner("Capturing...", doShoot, { done: null });
36117
36252
  } catch (err) {
36118
36253
  throw preCapture ? new Error(augmentSandboxTimeout(err.message)) : err;
36119
36254
  }
@@ -36136,8 +36271,8 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
36136
36271
  const dir = defaultScreenshotDir();
36137
36272
  const savedFiles = [];
36138
36273
  for (let i = 0; i < pngs.length; i++) {
36139
- const target = opts.output ? opts.output : join11(dir, names[i] ?? shotName(meta.screenshots[i].viewport));
36140
- mkdirSync7(dirname6(target), { recursive: true });
36274
+ const target = opts.output ? opts.output : join12(dir, names[i] ?? shotName(meta.screenshots[i].viewport));
36275
+ mkdirSync7(dirname7(target), { recursive: true });
36141
36276
  writeFileSync8(target, pngs[i].buffer);
36142
36277
  savedFiles.push(resolvePath(target));
36143
36278
  }
@@ -36265,10 +36400,10 @@ function shortUrl(url, truncate = true, maxLen = 100) {
36265
36400
  result = url;
36266
36401
  }
36267
36402
  if (!truncate || result.length <= maxLen) return result;
36268
- const keep = maxLen - 1;
36403
+ const keep = maxLen - 3;
36269
36404
  const headLen = Math.ceil(keep / 2);
36270
36405
  const tailLen = Math.floor(keep / 2);
36271
- return result.slice(0, headLen) + "\u2026" + result.slice(-tailLen);
36406
+ return result.slice(0, headLen) + "..." + result.slice(-tailLen);
36272
36407
  }
36273
36408
  var pageInspectCommand = new Command("inspect").description("Inspect a web page (console, failed resources, timing, layout overflow). ONE passive load - to verify realtime/presence across concurrent clients use `page test --observe`").argument("<url>", "URL to inspect").option("--wait <ms>", "Sleep this many ms after DOMContentLoaded before capturing (lets late async/LCP work settle; max 30000)", "500").option("--wait-for <selector>", "Wait until this CSS selector appears before capturing (deterministic; replaces --wait)").option("--wait-timeout <ms>", "Max ms to wait for --wait-for before giving up", "5000").option("--json", "Output as JSON").option("--no-reprobe", "Skip the automatic second probe that filters one-time transient console errors - roughly halves inspect time on a page with any console error, at the cost of possibly reporting a cold-load transient as real").option("--no-truncate", "Show full URLs instead of truncating long ones with middle-ellipsis").option("--all", "Include render-blocking, large resources, oversized images, overflow culprits, and LCP detail").option("--no-fake-media", "Inspect with NO camera or microphone present, so a getUserMedia app takes its no-device error path (use this only when that fallback IS what you're inspecting)").option("--device <name>", `Inspect as a real touch device: ${TOUCH_DEVICES.join(", ")}. Emulates touch events, mobile user-agent and DPR, so a touch-gated mobile layout is what gets inspected.`).option("--auth", "Load the page signed in as you (your Gipity account), so pages behind a Sign-in-with-Gipity login are reachable. Only works for apps using Sign in with Gipity, hosted on *.gipity.ai. Without this flag the page loads as a genuinely anonymous, signed-out visitor \u2014 nothing carries over from earlier --auth runs.").addOption(new Option("--screenshot [path]", "Capture a screenshot").hideHelp()).action((url, opts) => {
36274
36409
  if (opts.screenshot !== void 0) {
@@ -36482,7 +36617,7 @@ var deployCommand = new Command("deploy").description("Deploy to dev or prod").a
36482
36617
  force: opts.force,
36483
36618
  only: opts.only?.split(",").map((s) => s.trim())
36484
36619
  });
36485
- const res = opts.json ? await doDeploy() : await withSpinner(`Deploying to ${target}\u2026`, doDeploy, { done: null });
36620
+ const res = opts.json ? await doDeploy() : await withSpinner(`Deploying to ${target}...`, doDeploy, { done: null });
36486
36621
  const d = res.data;
36487
36622
  const inspectAfter = async () => {
36488
36623
  if (!opts.inspect) return;
@@ -36734,8 +36869,8 @@ memoryCommand.command("delete <topic>").description("Delete a topic").option("--
36734
36869
  // src/commands/sandbox.ts
36735
36870
  init_api();
36736
36871
  init_config();
36737
- import { readFileSync as readFileSync14, existsSync as existsSync11, statSync as statSync6, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
36738
- import { dirname as dirname7, extname as extname4, relative as relative4, resolve as resolve11, sep as sep3 } from "path";
36872
+ import { readFileSync as readFileSync15, existsSync as existsSync11, statSync as statSync7, mkdirSync as mkdirSync8, writeFileSync as writeFileSync9 } from "fs";
36873
+ import { dirname as dirname8, extname as extname4, relative as relative4, resolve as resolve11, sep as sep3 } from "path";
36739
36874
  init_colors();
36740
36875
  var LANG_MAP = {
36741
36876
  js: "javascript",
@@ -36873,7 +37008,7 @@ function isWriteTarget(code, p) {
36873
37008
  function resolveRelativeCwd() {
36874
37009
  const configPath = getConfigPath();
36875
37010
  if (!configPath) return void 0;
36876
- const projectRoot = dirname7(configPath);
37011
+ const projectRoot = dirname8(configPath);
36877
37012
  const rel2 = relative4(projectRoot, process.cwd());
36878
37013
  if (!rel2 || rel2.startsWith("..")) return void 0;
36879
37014
  return rel2.split(/[\\/]/).filter(Boolean).join("/");
@@ -36955,7 +37090,7 @@ GCC/Rust).
36955
37090
  if (args.length >= 2 && INTERPRETERS[args[0].toLowerCase()] !== void 0) {
36956
37091
  langFromInterp = INTERPRETERS[args[0].toLowerCase()];
36957
37092
  const rest = args.slice(1).join(" ");
36958
- if (existsSync11(rest) && statSync6(rest).isFile()) filePath = rest;
37093
+ if (existsSync11(rest) && statSync7(rest).isFile()) filePath = rest;
36959
37094
  else inlineCode = rest;
36960
37095
  } else if (args.length === 1) {
36961
37096
  inlineCode = args[0];
@@ -36976,7 +37111,7 @@ GCC/Rust).
36976
37111
  let source = inlineCode;
36977
37112
  if (filePath) {
36978
37113
  try {
36979
- source = readFileSync14(filePath, "utf8");
37114
+ source = readFileSync15(filePath, "utf8");
36980
37115
  } catch {
36981
37116
  console.error(error(`Cannot read file: ${filePath}`));
36982
37117
  process.exit(1);
@@ -37028,7 +37163,7 @@ GCC/Rust).
37028
37163
  // wire name for --discard-output.)
37029
37164
  noSyncOutput: discardOutput.length ? discardOutput : void 0
37030
37165
  });
37031
- const res = opts.json ? await doRun() : await withSpinner("Running in sandbox\u2026", doRun, { done: null });
37166
+ const res = opts.json ? await doRun() : await withSpinner("Running in sandbox...", doRun, { done: null });
37032
37167
  const pulledLocal = !!(res.data.outputFiles?.length && getConfigPath());
37033
37168
  if (pulledLocal) {
37034
37169
  await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
@@ -37040,7 +37175,7 @@ GCC/Rust).
37040
37175
  const rel2 = f.path.replace(/\\/g, "/");
37041
37176
  const abs = resolve11(base, rel2);
37042
37177
  if (abs !== base && !abs.startsWith(base + sep3)) continue;
37043
- mkdirSync8(dirname7(abs), { recursive: true });
37178
+ mkdirSync8(dirname8(abs), { recursive: true });
37044
37179
  writeFileSync9(abs, Buffer.from(f.contentBase64, "base64"));
37045
37180
  scratchWritten.push(rel2);
37046
37181
  }
@@ -37096,7 +37231,7 @@ Note: ${rootStrays.join(", ")} landed at the project root and will deploy with t
37096
37231
  const root = getProjectRoot();
37097
37232
  const emptyOutputs = pulledLocal && root ? (res.data.outputFiles ?? []).filter((f) => {
37098
37233
  try {
37099
- return statSync6(resolve11(root, f)).size === 0;
37234
+ return statSync7(resolve11(root, f)).size === 0;
37100
37235
  } catch {
37101
37236
  return false;
37102
37237
  }
@@ -37128,7 +37263,7 @@ var chatCommand = new Command("chat").description("Send a message to your agent"
37128
37263
  const endpoint = useExisting ? `/conversations/${config.conversationGuid}/messages` : "/conversations";
37129
37264
  const body = useExisting ? { content: message, projectGuid: config.projectGuid } : { agentGuid: config.agentGuid, content: message, projectGuid: config.projectGuid };
37130
37265
  const doChat = () => post(endpoint, body);
37131
- const res = opts.json ? await doChat() : await withSpinner("Thinking\u2026", doChat, { done: null });
37266
+ const res = opts.json ? await doChat() : await withSpinner("Thinking...", doChat, { done: null });
37132
37267
  if (!oneOff && res.data.conversationGuid !== config.conversationGuid) {
37133
37268
  saveConfig({ ...config, conversationGuid: res.data.conversationGuid });
37134
37269
  }
@@ -37213,7 +37348,7 @@ chatCommand.command("delete <guid>").description("Delete a chat").option("--json
37213
37348
  // src/commands/project.ts
37214
37349
  init_api();
37215
37350
  init_config();
37216
- import { join as join12 } from "path";
37351
+ import { join as join13 } from "path";
37217
37352
  import { mkdirSync as mkdirSync9 } from "fs";
37218
37353
  init_colors();
37219
37354
  init_utils();
@@ -37271,7 +37406,7 @@ projectCommand.command("create <name>").description("Create a project").option("
37271
37406
  }
37272
37407
  const res = await post("/projects", body);
37273
37408
  const project = res.data;
37274
- const dir = join12(getProjectsRoot(), project.slug);
37409
+ const dir = join13(getProjectsRoot(), project.slug);
37275
37410
  mkdirSync9(dir, { recursive: true });
37276
37411
  const accountSlug = await getAccountSlug();
37277
37412
  let agentGuid = "";
@@ -37614,7 +37749,7 @@ workflowCommand.command("run <name>").description("Trigger a workflow (add --wai
37614
37749
  }
37615
37750
  if (r.status !== "completed") process.exit(1);
37616
37751
  }));
37617
- workflowCommand.command("runs <name> [runGuid]").description("List recent runs, or pass a run guid (wr_\u2026) to see that run's per-step outputs").option("--json", "Output as JSON").action((name, runGuid, _opts, cmd) => run("Runs", async () => {
37752
+ workflowCommand.command("runs <name> [runGuid]").description("List recent runs, or pass a run guid (wr_...) to see that run's per-step outputs").option("--json", "Output as JSON").action((name, runGuid, _opts, cmd) => run("Runs", async () => {
37618
37753
  const opts = mergedOpts(cmd);
37619
37754
  const wf2 = await resolveWorkflow(name);
37620
37755
  if (runGuid) {
@@ -38100,7 +38235,7 @@ function printUsage(d) {
38100
38235
  for (const p of d.projects) {
38101
38236
  const name = p.projectName ?? "(no project)";
38102
38237
  row(
38103
- name.length > 16 ? `${name.slice(0, 15)}\u2026` : name,
38238
+ name.length > 16 ? `${name.slice(0, 13)}...` : name,
38104
38239
  formatBytes(p.liveBytes),
38105
38240
  `${p.liveFiles.toLocaleString()} files`
38106
38241
  );
@@ -38109,7 +38244,7 @@ function printUsage(d) {
38109
38244
  const shownBytes = d.projects.reduce((n, p) => n + p.liveBytes, 0);
38110
38245
  const shownFiles = d.projects.reduce((n, p) => n + p.liveFiles, 0);
38111
38246
  row(
38112
- `\u2026and ${hidden.toLocaleString()} more`,
38247
+ `...and ${hidden.toLocaleString()} more`,
38113
38248
  formatBytes(totals.liveBytes - shownBytes),
38114
38249
  `${(totals.liveFiles - shownFiles).toLocaleString()} files`
38115
38250
  );
@@ -38161,50 +38296,11 @@ storageCommand.command("retention").description("View or adjust your file versio
38161
38296
  init_platform();
38162
38297
  init_auth();
38163
38298
  init_api();
38164
- import { join as join16, dirname as dirname9, resolve as resolve13, basename as basename4, sep as sep5 } from "path";
38165
- import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync2, readdirSync as readdirSync7, statSync as statSync7 } from "fs";
38299
+ import { join as join17, dirname as dirname10, resolve as resolve13, basename as basename5, sep as sep5 } from "path";
38300
+ import { existsSync as existsSync14, mkdirSync as mkdirSync12, readFileSync as readFileSync18, writeFileSync as writeFileSync12, renameSync as renameSync2, readdirSync as readdirSync8, statSync as statSync8 } from "fs";
38166
38301
  import { homedir as homedir12 } from "os";
38167
38302
  import { fileURLToPath as fileURLToPath2 } from "url";
38168
38303
  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
38304
  init_config();
38209
38305
 
38210
38306
  // src/prompts.ts
@@ -38310,11 +38406,10 @@ function buildFreshWrap(contextBlock, userMsg) {
38310
38406
  // src/relay/onboarding.ts
38311
38407
  init_utils();
38312
38408
  init_colors();
38313
- import { readFileSync as readFileSync16 } from "node:fs";
38314
38409
 
38315
38410
  // src/relay/installers.ts
38316
38411
  import { homedir as homedir8, platform as osPlatform } from "os";
38317
- import { join as join13 } from "path";
38412
+ import { join as join14 } from "path";
38318
38413
  var UnsupportedPlatformError = class extends Error {
38319
38414
  constructor(plat) {
38320
38415
  super(`gipity relay install does not support ${plat}`);
@@ -38332,8 +38427,8 @@ function display(argvs) {
38332
38427
  }
38333
38428
  function launchdPlan(cliPath) {
38334
38429
  const label2 = "ai.gipity.relay";
38335
- const path5 = join13(homedir8(), "Library", "LaunchAgents", `${label2}.plist`);
38336
- const logDir = join13(homedir8(), "Library", "Logs", "Gipity");
38430
+ const path5 = join14(homedir8(), "Library", "LaunchAgents", `${label2}.plist`);
38431
+ const logDir = join14(homedir8(), "Library", "Logs", "Gipity");
38337
38432
  const uid = process.getuid?.() ?? 0;
38338
38433
  const target = `gui/${uid}`;
38339
38434
  const content = `<?xml version="1.0" encoding="UTF-8"?>
@@ -38357,8 +38452,8 @@ function launchdPlan(cliPath) {
38357
38452
  <dict>
38358
38453
  <key>SuccessfulExit</key><false/>
38359
38454
  </dict>
38360
- <key>StandardOutPath</key><string>${join13(logDir, "relay.out.log")}</string>
38361
- <key>StandardErrorPath</key><string>${join13(logDir, "relay.err.log")}</string>
38455
+ <key>StandardOutPath</key><string>${join14(logDir, "relay.out.log")}</string>
38456
+ <key>StandardErrorPath</key><string>${join14(logDir, "relay.err.log")}</string>
38362
38457
  <key>ProcessType</key><string>Background</string>
38363
38458
  </dict>
38364
38459
  </plist>
@@ -38381,7 +38476,7 @@ function launchdPlan(cliPath) {
38381
38476
  }
38382
38477
  function systemdUserPlan(cliPath) {
38383
38478
  const unitName = "gipity-relay.service";
38384
- const path5 = join13(homedir8(), ".config", "systemd", "user", unitName);
38479
+ const path5 = join14(homedir8(), ".config", "systemd", "user", unitName);
38385
38480
  const content = `[Unit]
38386
38481
  Description=Gipity relay - local Claude Code control from the Gipity web CLI
38387
38482
  After=network-online.target
@@ -38419,7 +38514,7 @@ WantedBy=default.target
38419
38514
  }
38420
38515
  function windowsTaskPlan(cliPath) {
38421
38516
  const taskName = "GipityRelay";
38422
- const path5 = join13(homedir8(), "AppData", "Local", "Gipity", "relay-task.xml");
38517
+ const path5 = join14(homedir8(), "AppData", "Local", "Gipity", "relay-task.xml");
38423
38518
  const runsViaNode = /\.[cm]?js$/i.test(cliPath);
38424
38519
  const command = runsViaNode ? process.execPath : cliPath;
38425
38520
  const argLine = runsViaNode ? `"${cliPath}" relay run` : "relay run";
@@ -38481,13 +38576,13 @@ function windowsTaskPlan(cliPath) {
38481
38576
  init_platform();
38482
38577
  init_api();
38483
38578
  import { hostname as hostname3, userInfo, platform as osPlatform2 } from "os";
38484
- import { resolve as resolve12, dirname as dirname8 } from "path";
38579
+ import { resolve as resolve12, dirname as dirname9 } from "path";
38485
38580
  import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync10 } from "fs";
38486
38581
 
38487
38582
  // src/relay/machine-id.ts
38488
38583
  import { createHash as createHash3 } from "crypto";
38489
38584
  import { execFileSync } from "child_process";
38490
- import { readFileSync as readFileSync15 } from "fs";
38585
+ import { readFileSync as readFileSync16 } from "fs";
38491
38586
  import { hostname as hostname2, platform, arch } from "os";
38492
38587
  function rawMachineId() {
38493
38588
  const override = process.env.GIPITY_RELAY_MACHINE_ID?.trim();
@@ -38496,7 +38591,7 @@ function rawMachineId() {
38496
38591
  if (platform() === "linux") {
38497
38592
  for (const path5 of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
38498
38593
  try {
38499
- const id2 = readFileSync15(path5, "utf-8").trim();
38594
+ const id2 = readFileSync16(path5, "utf-8").trim();
38500
38595
  if (id2) return `linux:${id2}`;
38501
38596
  } catch {
38502
38597
  }
@@ -38591,7 +38686,7 @@ function startDaemon() {
38591
38686
  function installAutostart(opts = {}) {
38592
38687
  const stdio = opts.stdio ?? "ignore";
38593
38688
  const plan2 = planFor({ cliPath: resolveCliPath() });
38594
- mkdirSync10(dirname8(plan2.path), { recursive: true });
38689
+ mkdirSync10(dirname9(plan2.path), { recursive: true });
38595
38690
  writeFileSync10(plan2.path, plan2.content);
38596
38691
  let ok = true;
38597
38692
  for (const argv2 of plan2.enableCmds) {
@@ -38616,15 +38711,6 @@ function removeAutostart(opts = {}) {
38616
38711
 
38617
38712
  // src/relay/onboarding.ts
38618
38713
  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
38714
  async function runRelaySetup(opts) {
38629
38715
  const alreadyAnswered = getRelayEnabled() !== void 0;
38630
38716
  if (opts.mode === "offer-once" && alreadyAnswered) {
@@ -38640,12 +38726,12 @@ async function runRelaySetup(opts) {
38640
38726
  console.log("");
38641
38727
  console.log(` ${success("Registered as")} ${bold(existingDevice.name)} ${muted(`(${existingDevice.guid})`)}`);
38642
38728
  if (isPaused()) {
38643
- console.log(` ${dim("The relay is paused \u2014 resume it with")} ${brand("gipity relay resume")}${dim(".")}`);
38729
+ console.log(` ${dim("The relay is paused - resume it with")} ${brand("gipity relay resume")}${dim(".")}`);
38644
38730
  } else {
38645
38731
  console.log(` ${dim("The relay is running. Open")} ${brand("gipity.ai")}${dim(" and type `/claude` to drive Claude Code here.")}`);
38646
38732
  }
38647
38733
  console.log("");
38648
- console.log(` ${dim("To register this computer again \u2014 for example under a different name \u2014")}`);
38734
+ console.log(` ${dim("To register this computer again (for example under a different name),")}`);
38649
38735
  console.log(` ${dim("unregister it first, then re-run setup:")}`);
38650
38736
  console.log(` ${brand("gipity relay revoke")} ${dim("# unpairs this computer and removes the login service")}`);
38651
38737
  console.log(` ${brand("gipity connect")} ${dim("# register it again (asks for a new name)")}`);
@@ -38655,28 +38741,29 @@ async function runRelaySetup(opts) {
38655
38741
  }
38656
38742
  if (opts.mode === "run-now") {
38657
38743
  console.log(` ${bold("Set up this computer as a relay")}`);
38658
- console.log(` ${dim("A relay runs your coding agent (Claude Code, Codex, or Grok) here so you can drive it from the web (")}${brand("gipity.ai")}${dim(") on any browser.")}`);
38659
- console.log(` ${dim("It uses your Claude, Codex, or Grok subscription \u2014 the cheapest way to pay for tokens.")}`);
38744
+ console.log(` ${dim("Gipity drives the coding agents on this computer from the web (")}${brand("gipity.ai")}${dim(")")}`);
38745
+ console.log(` ${dim("on any browser, desktop or phone. Uses your Claude, Codex, or Grok")}`);
38746
+ console.log(` ${dim("subscription - the cheapest way to pay for tokens.")}`);
38660
38747
  } else {
38661
- console.log(` ${bold("Remote control of your coding agent")}`);
38662
- console.log(` ${dim("Drive your coding agent on this computer from the web (")}${brand("gipity.ai")}${dim(") on any browser (desktop or phone).")}`);
38663
- console.log("");
38664
- console.log(` ${dim("Enable now (takes 2 seconds) or turn on later with")} ${brand("gipity connect")}`);
38748
+ console.log(` ${bold("Optional:")} ${dim("Gipity can drive the coding agents on this computer from the")}`);
38749
+ console.log(` ${dim("web (")}${brand("gipity.ai")}${dim(") on any browser, desktop or phone. Like Claude Code remote")}`);
38750
+ console.log(` ${dim("control, but one pane for all your coding agents.")}`);
38665
38751
  }
38666
38752
  console.log("");
38667
- const promptText = opts.mode === "run-now" ? " Set up remote control on this computer?" : " Enable remote control?";
38753
+ const promptText = opts.mode === "run-now" ? " Set up remote control on this computer" : " Enable remote control";
38668
38754
  const enable = await confirm(promptText, { default: "yes" });
38669
38755
  if (!enable) {
38670
38756
  setRelayEnabled(false);
38671
- console.log(` ${muted("Skipped.")}`);
38757
+ console.log(` ${muted("Skipped. Turn on later with")} ${brand("gipity connect")}${muted(".")}`);
38672
38758
  console.log("");
38673
38759
  return false;
38674
38760
  }
38761
+ console.log("");
38675
38762
  const defaultName = friendlyDeviceName();
38676
38763
  const rawName = await prompt(` Device name [${bold(defaultName)}]: `);
38677
38764
  const name = (rawName || defaultName).trim();
38678
38765
  if (!name || name.length > 100) {
38679
- console.error(` ${error("Device name must be 1\u2013100 non-whitespace characters. Skipping.")}`);
38766
+ console.error(` ${error("Device name must be 1-100 non-whitespace characters. Skipping.")}`);
38680
38767
  setRelayEnabled(false);
38681
38768
  return false;
38682
38769
  }
@@ -38689,11 +38776,9 @@ async function runRelaySetup(opts) {
38689
38776
  console.error(` ${dim("Skipping for now - we'll offer again next time. Or turn it on with `gipity connect`.")}`);
38690
38777
  return false;
38691
38778
  }
38692
- const startNow = await confirm(" Start the relay now (and on future `gipity build` runs)?", { default: "yes" });
38693
- if (startNow) {
38694
- startDaemon();
38695
- }
38696
- const autostartOs = await confirm(" Also start at OS login (auto-start with Windows / macOS / Linux)?", { default: "yes" });
38779
+ startDaemon();
38780
+ console.log("");
38781
+ const autostartOs = await confirm(" Also start at OS login", { default: "yes" });
38697
38782
  if (autostartOs) {
38698
38783
  try {
38699
38784
  const res = installAutostart();
@@ -38705,8 +38790,6 @@ async function runRelaySetup(opts) {
38705
38790
  } else {
38706
38791
  console.log(` ${muted("Autostart install returned non-zero - you can run")} ${brand("gipity relay install")} ${muted("later.")}`);
38707
38792
  }
38708
- } else {
38709
- console.log(` ${success("Auto-start installed.")} ${dim(res.summary)}`);
38710
38793
  }
38711
38794
  } catch (err) {
38712
38795
  if (err instanceof UnsupportedPlatformError) {
@@ -38717,16 +38800,12 @@ async function runRelaySetup(opts) {
38717
38800
  }
38718
38801
  }
38719
38802
  if (getDiagnosticsConsent() === void 0) {
38720
- const diag = await confirm(
38721
- " Share anonymous diagnostics (CPU/GPU/memory/disk/versions) to improve reliability?",
38722
- { default: "yes" }
38723
- );
38803
+ console.log("");
38804
+ const diag = await confirm(" Share anonymous diagnostics info", { default: "yes" });
38724
38805
  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
38806
  }
38727
38807
  console.log("");
38728
- console.log(` ${success(`Registered as ${bold(device.name)} (${device.guid}).`)}`);
38729
- console.log(` ${dim("In the Gipity web CLI, type `/claude` to dispatch messages to this PC.")}`);
38808
+ console.log(` ${success(`Done! ${bold(device.name)} is set up. New chats from gipity.ai can now execute here.`)}`);
38730
38809
  console.log("");
38731
38810
  return true;
38732
38811
  }
@@ -38869,9 +38948,8 @@ function padR(s, width) {
38869
38948
  }
38870
38949
  function leadingEllipsify(s, maxW) {
38871
38950
  if (s.length <= maxW) return s;
38872
- if (maxW <= 1) return s.slice(-maxW);
38873
- if (maxW <= 3) return "\u2026".padStart(maxW);
38874
- return "\u2026" + s.slice(-(maxW - 1));
38951
+ if (maxW <= 4) return s.slice(-maxW);
38952
+ return "..." + s.slice(-(maxW - 3));
38875
38953
  }
38876
38954
  function center(s, width) {
38877
38955
  const gap = width - visLen(s);
@@ -38993,7 +39071,7 @@ function printFull(opts, outerW) {
38993
39071
  import { execSync as execSync2 } from "child_process";
38994
39072
  import { existsSync as existsSync12 } from "fs";
38995
39073
  import { homedir as homedir10 } from "os";
38996
- import { join as join14 } from "path";
39074
+ import { join as join15 } from "path";
38997
39075
  var CLAUDE_PACKAGE = "@anthropic-ai/claude-code";
38998
39076
  function claudeInstallPlan(platformOverride) {
38999
39077
  const plat = platformOverride ?? process.platform;
@@ -39012,7 +39090,7 @@ function isClaudeInstalled(platformOverride) {
39012
39090
  }
39013
39091
  function isClaudeAuthenticated() {
39014
39092
  if (process.env.CLAUDE_CODE_OAUTH_TOKEN || process.env.ANTHROPIC_API_KEY) return true;
39015
- return existsSync12(join14(homedir10(), ".claude", ".credentials.json"));
39093
+ return existsSync12(join15(homedir10(), ".claude", ".credentials.json"));
39016
39094
  }
39017
39095
  function probeClaudeAuthenticated() {
39018
39096
  if (!isClaudeInstalled()) return false;
@@ -39183,8 +39261,8 @@ function getAdapterBySource(source) {
39183
39261
  // src/prefs.ts
39184
39262
  import { existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
39185
39263
  import { homedir as homedir11 } from "os";
39186
- import { join as join15 } from "path";
39187
- var PREFS_PATH = join15(homedir11(), ".gipity", "prefs.json");
39264
+ import { join as join16 } from "path";
39265
+ var PREFS_PATH = join16(homedir11(), ".gipity", "prefs.json");
39188
39266
  function readPrefs() {
39189
39267
  if (!existsSync13(PREFS_PATH)) return {};
39190
39268
  try {
@@ -39200,17 +39278,17 @@ function writePrefs(update) {
39200
39278
  try {
39201
39279
  const current = readPrefs();
39202
39280
  const next = { ...current, ...update };
39203
- mkdirSync11(join15(homedir11(), ".gipity"), { recursive: true });
39281
+ mkdirSync11(join16(homedir11(), ".gipity"), { recursive: true });
39204
39282
  writeFileSync11(PREFS_PATH, JSON.stringify(next, null, 2) + "\n");
39205
39283
  } catch {
39206
39284
  }
39207
39285
  }
39208
39286
 
39209
39287
  // src/commands/build.ts
39210
- var __clDir = dirname9(fileURLToPath2(import.meta.url));
39288
+ var __clDir = dirname10(fileURLToPath2(import.meta.url));
39211
39289
  function readOwnPkg(fromDir) {
39212
- for (let d = fromDir; ; d = dirname9(d)) {
39213
- const p = join16(d, "package.json");
39290
+ for (let d = fromDir; ; d = dirname10(d)) {
39291
+ const p = join17(d, "package.json");
39214
39292
  if (existsSync14(p)) {
39215
39293
  try {
39216
39294
  const pkg2 = JSON.parse(readFileSync18(p, "utf-8"));
@@ -39218,13 +39296,13 @@ function readOwnPkg(fromDir) {
39218
39296
  } catch {
39219
39297
  }
39220
39298
  }
39221
- if (dirname9(d) === d) return {};
39299
+ if (dirname10(d) === d) return {};
39222
39300
  }
39223
39301
  }
39224
39302
  var __clPkg = readOwnPkg(__clDir);
39225
39303
  function reportSyncResult(result) {
39226
39304
  if (result.aborted) {
39227
- console.log(` ${warning("Merge cancelled \u2014 this folder was NOT synced with the project.")}`);
39305
+ console.log(` ${warning("Merge cancelled - this folder was NOT synced with the project.")}`);
39228
39306
  console.log(` ${muted("For a clean copy, quit and open the project in an empty folder. To merge anyway, run `gipity sync`.")}`);
39229
39307
  return;
39230
39308
  }
@@ -39232,9 +39310,9 @@ function reportSyncResult(result) {
39232
39310
  console.log(` Synced ${result.applied} change${result.applied > 1 ? "s" : ""} with Gipity.`);
39233
39311
  }
39234
39312
  if (result.errors.length) {
39235
- console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? "" : "s"} \u2014 your local copy may be incomplete:`)}`);
39313
+ console.log(` ${warning(`Sync finished with ${result.errors.length} problem${result.errors.length === 1 ? "" : "s"} - your local copy may be incomplete:`)}`);
39236
39314
  for (const e of result.errors.slice(0, 8)) console.log(` - ${e}`);
39237
- if (result.errors.length > 8) console.log(` \u2026and ${result.errors.length - 8} more.`);
39315
+ if (result.errors.length > 8) console.log(` ...and ${result.errors.length - 8} more.`);
39238
39316
  console.log(` ${muted("Nothing was deleted. Re-run `gipity sync` to finish the pull.")}`);
39239
39317
  }
39240
39318
  }
@@ -39262,12 +39340,12 @@ function localFsFallback(dir) {
39262
39340
  const topLevelEntries = [];
39263
39341
  const walk = (d, depth) => {
39264
39342
  try {
39265
- for (const name of readdirSync7(d).sort()) {
39343
+ for (const name of readdirSync8(d).sort()) {
39266
39344
  if (isSyncIgnored(name)) continue;
39267
39345
  let isDir = false;
39268
39346
  let size = 0;
39269
39347
  try {
39270
- const st2 = statSync7(join16(d, name));
39348
+ const st2 = statSync8(join17(d, name));
39271
39349
  isDir = st2.isDirectory();
39272
39350
  size = st2.isFile() ? st2.size : 0;
39273
39351
  } catch {
@@ -39276,7 +39354,7 @@ function localFsFallback(dir) {
39276
39354
  if (isDir) {
39277
39355
  folderCount++;
39278
39356
  if (depth === 0) topLevelEntries.push(`${name}/`);
39279
- walk(join16(d, name), depth + 1);
39357
+ walk(join17(d, name), depth + 1);
39280
39358
  } else {
39281
39359
  fileCount++;
39282
39360
  totalBytes += size;
@@ -39287,7 +39365,7 @@ function localFsFallback(dir) {
39287
39365
  }
39288
39366
  };
39289
39367
  walk(dir, 0);
39290
- const topLevel = topLevelEntries.length ? topLevelEntries.slice(0, 20).join(", ") + (topLevelEntries.length > 20 ? ", \u2026" : "") : "(empty directory)";
39368
+ const topLevel = topLevelEntries.length ? topLevelEntries.slice(0, 20).join(", ") + (topLevelEntries.length > 20 ? ", ..." : "") : "(empty directory)";
39291
39369
  return { fileCount, folderCount, totalBytes, topLevel };
39292
39370
  }
39293
39371
  async function buildProjectContextBlock2(opts) {
@@ -39309,7 +39387,7 @@ function hasStreamJsonFlag(args) {
39309
39387
  }
39310
39388
  function markFolderTrusted(dir) {
39311
39389
  try {
39312
- const file = join16(homedir12(), ".claude.json");
39390
+ const file = join17(homedir12(), ".claude.json");
39313
39391
  const cfg = existsSync14(file) ? JSON.parse(readFileSync18(file, "utf-8")) : {};
39314
39392
  if (typeof cfg !== "object" || cfg === null) return;
39315
39393
  if (typeof cfg.projects !== "object" || cfg.projects === null) cfg.projects = {};
@@ -39437,7 +39515,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39437
39515
  if (existing && !opts.newProject && !opts.project && !nonInteractive) {
39438
39516
  const cwdHasConfig = existsSync14(resolve13(process.cwd(), ".gipity.json"));
39439
39517
  if (!cwdHasConfig && isLikelyEmpty(process.cwd())) {
39440
- const ancestorRoot = dirname9(getConfigPath());
39518
+ const ancestorRoot = dirname10(getConfigPath());
39441
39519
  console.log(` ${bold("You are inside an existing Gipity project.")}
39442
39520
  `);
39443
39521
  console.log(` ${bold("1.")} Continue in ${brand(existing.projectSlug)} ${muted(`(rooted at ${formatCwdLabel(ancestorRoot)})`)}`);
@@ -39484,7 +39562,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39484
39562
  }
39485
39563
  project = found;
39486
39564
  }
39487
- const projectDir2 = opts.here ? process.cwd() : join16(getProjectsRoot(), project.slug);
39565
+ const projectDir2 = opts.here ? process.cwd() : join17(getProjectsRoot(), project.slug);
39488
39566
  mkdirSync12(projectDir2, { recursive: true });
39489
39567
  process.chdir(projectDir2);
39490
39568
  clearConfigCache();
@@ -39513,6 +39591,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39513
39591
  console.log(` ${warning("Could not sync files (will retry on next prompt):")} ${err.message}`);
39514
39592
  }
39515
39593
  setupProjectTools();
39594
+ if (!nonInteractive) console.log(` ${muted("Installed Gipity plugin, skills, and hooks.")}`);
39516
39595
  if (nonInteractive) {
39517
39596
  headlessNewProject = isNewProject;
39518
39597
  } else {
@@ -39526,7 +39605,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39526
39605
  `);
39527
39606
  setupProjectTools();
39528
39607
  let doSync = true;
39529
- const projectRoot = dirname9(getConfigPath());
39608
+ const projectRoot = dirname10(getConfigPath());
39530
39609
  const scan = scanForAdoption(projectRoot);
39531
39610
  if (scan.tier === "refuse" && !nonInteractive) {
39532
39611
  const sizeStr = scan.truncated ? `>${formatBytes(ADOPT_THRESHOLDS.REFUSE_BYTES)}` : formatBytes(scan.bytes);
@@ -39585,7 +39664,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39585
39664
  let agentGuid = "";
39586
39665
  const result = forceAdoptCwd ? { kind: "adopt-cwd" } : projects.length > 0 ? await pickOrCreateProject(projects, existingSlugs) : { kind: "create-new", project: await createNewProject(existingSlugs) };
39587
39666
  if (result.kind === "adopt-cwd") {
39588
- const cwdBase = basename4(process.cwd());
39667
+ const cwdBase = basename5(process.cwd());
39589
39668
  const adoptSlug = slugify(cwdBase) || "project";
39590
39669
  const adoptName = (cwdBase || adoptSlug).slice(0, 100);
39591
39670
  accountSlug = await getAccountSlug();
@@ -39607,7 +39686,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39607
39686
  } else {
39608
39687
  project = result.project;
39609
39688
  isNewProject = result.kind === "create-new";
39610
- const projectDir2 = join16(getProjectsRoot(), project.slug);
39689
+ const projectDir2 = join17(getProjectsRoot(), project.slug);
39611
39690
  mkdirSync12(projectDir2, { recursive: true });
39612
39691
  process.chdir(projectDir2);
39613
39692
  clearConfigCache();
@@ -39637,6 +39716,7 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39637
39716
  }
39638
39717
  initialPrompt = "";
39639
39718
  setupProjectTools();
39719
+ if (!nonInteractive) console.log(` ${muted("Installed Gipity plugin, skills, and hooks.")}`);
39640
39720
  console.log(` ${success(`Project "${project.name}" ready.`)}
39641
39721
  `);
39642
39722
  }
@@ -39732,9 +39812,8 @@ async function runLaunch(cmdName, cmdCfg, opts) {
39732
39812
  }
39733
39813
  if (!nonInteractive) {
39734
39814
  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
39815
  if (convGuidForHooks) {
39816
+ console.log("");
39738
39817
  console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand("prompt.gipity.ai")}. (--no-capture on init to disable.)`)}`);
39739
39818
  }
39740
39819
  console.log("");
@@ -39832,10 +39911,8 @@ async function pickAgent(lastUsed) {
39832
39911
  console.log(` ${bold("Which coding agent?")}
39833
39912
  `);
39834
39913
  AGENT_ADAPTERS.forEach((a, i) => {
39835
- const installed = binaryOnPath(a.binary) || a.key === "claude" && isClaudeInstalled();
39836
39914
  const notes = [];
39837
39915
  if (a.key === lastUsed) notes.push("last used");
39838
- if (!installed) notes.push(a.ensureInstalled ? "not installed - we'll install it" : "not installed");
39839
39916
  if (a.key === "codex" && !a.hooksSupportedOnPlatform(process.platform)) {
39840
39917
  notes.push("session recording unavailable on Windows");
39841
39918
  }
@@ -39948,9 +40025,8 @@ Sending to ${adapter.displayName}: ${message}
39948
40025
  } else {
39949
40026
  agentArgs = [...adapter.buildInteractiveArgs({ resume, model }), ...extras];
39950
40027
  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
40028
  if (convGuid) {
40029
+ console.log("");
39954
40030
  console.log(` ${muted(`This session is saved to Gipity - watch it live at ${brand("prompt.gipity.ai")}. (--no-capture on init to disable.)`)}`);
39955
40031
  }
39956
40032
  if (adapter.oneTimeSetupNote) {
@@ -39989,7 +40065,7 @@ ${doneLine}
39989
40065
  });
39990
40066
  }
39991
40067
  function flushHeadlessCapture(adapter, sessionId, convGuid) {
39992
- const runner = join16(__clDir, "..", "hooks", "capture-runner.js");
40068
+ const runner = join17(__clDir, "..", "hooks", "capture-runner.js");
39993
40069
  if (!existsSync14(runner)) return;
39994
40070
  const payload = JSON.stringify({ session_id: sessionId, cwd: process.cwd() });
39995
40071
  const env = { ...process.env, GIPITY_CONVERSATION_GUID: convGuid };
@@ -40380,7 +40456,7 @@ var addCommand = new Command("add").description("Add a template or kit").argumen
40380
40456
  console.error(muted(isKit ? "Installing kit (server-side install pipeline)..." : "Installing (server writes files + generates favicons; first add for a title can take ~10s)..."));
40381
40457
  }
40382
40458
  const doAdd = () => post(`/projects/${config.projectGuid}/add`, body);
40383
- const res = opts.json ? await doAdd() : await withSpinner("Installing\u2026", doAdd, { done: null });
40459
+ const res = opts.json ? await doAdd() : await withSpinner("Installing...", doAdd, { done: null });
40384
40460
  const syncResult = await sync({ interactive: false, progress: opts.json ? void 0 : createProgressReporter() });
40385
40461
  const data = res.data;
40386
40462
  if (opts.json) {
@@ -40428,7 +40504,7 @@ var removeCommand = new Command("remove").description("Remove a kit").argument("
40428
40504
  }
40429
40505
  }
40430
40506
  const doRemove = () => post(`/projects/${config.projectGuid}/remove`, { name: kit });
40431
- const res = opts.json ? await doRemove() : await withSpinner("Removing\u2026", doRemove, { done: null });
40507
+ const res = opts.json ? await doRemove() : await withSpinner("Removing...", doRemove, { done: null });
40432
40508
  const data = res.data;
40433
40509
  const syncResult = await sync({
40434
40510
  interactive: false,
@@ -40478,7 +40554,7 @@ function dispositionFilename(headers) {
40478
40554
  var saveCommand = new Command("save").description("Save this app as a portable .gip bundle").argument("[output]", "Output .gip path, or a directory to write <slug>.gip into (default: ./<slug>.gip)").option("--json", "Output as JSON").addHelpText("after", "\nRestore anywhere with `gipity load <file>.gip` - it always creates a NEW project.").action((output, opts) => run("Save", async () => {
40479
40555
  const config = requireConfig();
40480
40556
  const doExport = () => downloadWithHeaders(`/projects/${config.projectGuid}/export`);
40481
- const { buffer, headers } = opts.json ? await doExport() : await withSpinner("Exporting\u2026", doExport, { done: null });
40557
+ const { buffer, headers } = opts.json ? await doExport() : await withSpinner("Exporting...", doExport, { done: null });
40482
40558
  const filename = dispositionFilename(headers) ?? `${config.projectSlug}.gip`;
40483
40559
  let dest = path2.resolve(output ?? filename);
40484
40560
  if (output && fs3.existsSync(dest) && fs3.statSync(dest).isDirectory()) {
@@ -40486,14 +40562,14 @@ var saveCommand = new Command("save").description("Save this app as a portable .
40486
40562
  }
40487
40563
  fs3.mkdirSync(path2.dirname(dest), { recursive: true });
40488
40564
  fs3.writeFileSync(dest, buffer);
40489
- const sha2562 = createHash4("sha256").update(buffer).digest("hex");
40565
+ const sha256 = createHash4("sha256").update(buffer).digest("hex");
40490
40566
  const skipped = Number(headers.get("x-gip-skipped") ?? 0);
40491
40567
  const entries = zipEntryCount(buffer);
40492
40568
  if (opts.json) {
40493
40569
  console.log(JSON.stringify({
40494
40570
  path: dest,
40495
40571
  bytes: buffer.length,
40496
- sha256: sha2562,
40572
+ sha256,
40497
40573
  ...entries !== null ? { entries } : {},
40498
40574
  skipped
40499
40575
  }));
@@ -40503,7 +40579,7 @@ var saveCommand = new Command("save").description("Save this app as a portable .
40503
40579
  console.log(` Path: ${dest}`);
40504
40580
  console.log(` Size: ${formatBytes(buffer.length)}`);
40505
40581
  if (entries !== null) console.log(` Entries: ${entries} (including the bundle manifest)`);
40506
- console.log(` SHA-256: ${sha2562}`);
40582
+ console.log(` SHA-256: ${sha256}`);
40507
40583
  if (skipped > 0) {
40508
40584
  console.log("");
40509
40585
  console.warn(warning(`${skipped} file(s) were skipped (unreadable or over 100 MB) - this bundle is partial.`));
@@ -40620,7 +40696,7 @@ var loadCommand = new Command("load").description("Create a new app from a .gip
40620
40696
  };
40621
40697
  }
40622
40698
  if (opts.inspect) {
40623
- const res = opts.json ? await doInspect() : await withSpinner("Inspecting\u2026", doInspect, { done: null });
40699
+ const res = opts.json ? await doInspect() : await withSpinner("Inspecting...", doInspect, { done: null });
40624
40700
  if (opts.json) {
40625
40701
  console.log(JSON.stringify(res.data));
40626
40702
  return;
@@ -40628,7 +40704,7 @@ var loadCommand = new Command("load").description("Create a new app from a .gip
40628
40704
  printInspect(res.data, source);
40629
40705
  return;
40630
40706
  }
40631
- const { data, partial } = opts.json ? await doImport() : await withSpinner("Importing\u2026", doImport, { done: null });
40707
+ const { data, partial } = opts.json ? await doImport() : await withSpinner("Importing...", doImport, { done: null });
40632
40708
  const project = data.project;
40633
40709
  const dir = path3.join(getProjectsRoot(), project.slug);
40634
40710
  fs4.mkdirSync(dir, { recursive: true });
@@ -41029,7 +41105,7 @@ ${error(`\u26A0 ${problems} error/crash line(s) flagged above`)}`
41029
41105
  );
41030
41106
  if (problems > 0) process.exitCode = 1;
41031
41107
  }
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, \u2026)").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", `
41108
+ var pageTestCommand = new Command("test").description("Multi-client realtime check: load a URL in N concurrent headless clients; flag console errors, or drive an action and observe shared state (--observe)").argument("<url>", "Deployed URL to load in every client. {{label}}/{{i}} substitute per client in both modes (e.g. ?name=Bot{{i}}, or ?role={{label}} with --labels host,join), so one invocation can give each client a distinct role.").option("--clients <n>", "Number of headless clients to launch", "2").option("--stagger <s>", "Seconds between client starts (passive default 12; interactive default 0)").option("--wait <ms>", "Passive mode: ms each client stays open after load (max 30000)", "24000").option("--observe <expr>", "Interactive: JS expression sampled in each client to read shared state (e.g. presence count). Switches on interactive mode.").option("--action <expr>", "Interactive: one-time JS run in each client before observing (e.g. fill a name + submit). {{label}}/{{i}} are substituted per client.").option("--labels <csv>", "Per-client labels substituted for {{label}} in the URL/--action/--observe (default client-0, client-1, ...)").option("--hold <ms>", `Interactive: total observe window per client (${MIN_HOLD_MS}-${MAX_HOLD_MS}ms)`, "8000").option("--samples <k>", "Interactive: number of observations across the hold window (2-30)", "6").option("--wait-for <selector>", "Interactive: wait for this CSS selector before running --action (deterministic readiness gate)").option("--json", "Output as JSON").addHelpText("after", `
41033
41109
  Examples:
41034
41110
  # Passive: load in 3 staggered clients, flag console errors
41035
41111
  gipity page test "https://dev.gipity.ai/me/app/" --clients 3 --stagger 8
@@ -41087,9 +41163,6 @@ function looksLikeHtml(body) {
41087
41163
  const head = body.slice(0, 300).toLowerCase();
41088
41164
  return /<!doctype html|<html[\s>]/.test(head);
41089
41165
  }
41090
- function sha256(s) {
41091
- return createHash5("sha256").update(s).digest("hex");
41092
- }
41093
41166
  function baseWithSlash(url) {
41094
41167
  return url.endsWith("/") ? url : url + "/";
41095
41168
  }
@@ -41099,8 +41172,14 @@ function resolveUrl(base, path5) {
41099
41172
  async function fetchRaw(url) {
41100
41173
  try {
41101
41174
  const res = await fetch(url, { redirect: "follow" });
41102
- const body = await res.text();
41103
- return { status: res.status, contentType: res.headers.get("content-type"), body };
41175
+ const buf = Buffer.from(await res.arrayBuffer());
41176
+ return {
41177
+ status: res.status,
41178
+ contentType: res.headers.get("content-type"),
41179
+ body: buf.toString("utf-8"),
41180
+ bytes: buf.length,
41181
+ sha256: createHash5("sha256").update(buf).digest("hex")
41182
+ };
41104
41183
  } catch {
41105
41184
  return null;
41106
41185
  }
@@ -41112,17 +41191,16 @@ async function probeShell(base) {
41112
41191
  return {
41113
41192
  served: r.status >= 200 && r.status < 300,
41114
41193
  status: r.status,
41115
- sha256: sha256(r.body),
41194
+ sha256: r.sha256,
41116
41195
  isHtml: looksLikeHtml(r.body)
41117
41196
  };
41118
41197
  }
41119
41198
  function classify(path5, r, shell) {
41120
41199
  if (!r) return { status: null, contentType: null, bytes: 0, verdict: "MISSING", detail: "fetch failed (host unreachable)" };
41121
- const bytes = Buffer.byteLength(r.body);
41122
- const base = { status: r.status, contentType: r.contentType, bytes };
41200
+ const base = { status: r.status, contentType: r.contentType, bytes: r.bytes };
41123
41201
  if (r.status >= 400) return { ...base, verdict: "MISSING", detail: `HTTP ${r.status}` };
41124
41202
  const expect = expectedType(path5);
41125
- if (shell.served && shell.sha256 && sha256(r.body) === shell.sha256) {
41203
+ if (shell.served && shell.sha256 && r.sha256 === shell.sha256) {
41126
41204
  return { ...base, verdict: "MISSING", detail: `HTTP ${r.status} but served the SPA shell (file not deployed)` };
41127
41205
  }
41128
41206
  if (expect && expect !== "html" && looksLikeHtml(r.body)) {
@@ -41138,7 +41216,7 @@ var TAG = {
41138
41216
  "MISSING": error("MISSING "),
41139
41217
  "WRONG-TYPE": warning("WRONG-TYPE")
41140
41218
  };
41141
- var pageFetchCommand = new Command("fetch").description("Verify deployed non-rendered files (llms.txt, AGENTS.md, robots.txt, served JSON\u2026) 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 () => {
41219
+ var pageFetchCommand = new Command("fetch").description("Verify deployed non-rendered files (llms.txt, AGENTS.md, robots.txt, served JSON...) really exist - catches the static-host trap where a missing file returns 200 with the SPA shell instead of a 404").argument("<url>", "Deployed app base URL; file paths resolve relative to it").argument("<paths...>", "One or more file paths to verify, e.g. llms.txt AGENTS.md robots.txt").option("--json", "Output as JSON").action((url, paths, opts) => run("Page fetch", async () => {
41142
41220
  const shell = await probeShell(url);
41143
41221
  const results = [];
41144
41222
  for (const p of paths) {
@@ -41383,7 +41461,7 @@ var SERVICES = [
41383
41461
  { name: "location/ip", method: "POST", desc: "IP geolocation ({ ip? })" },
41384
41462
  { name: "location/geocode", method: "POST", desc: "Reverse geocode ({ lat, lon })" }
41385
41463
  ];
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.");
41464
+ var serviceCommand = new Command("service").description("Call an app service (llm, tts, image, transcribe, ...)").addHelpText("after", "\nCall a Gipity app service: LLM, image, music, TTS, and more.\nPer-service docs: gipity skill read app-llm | app-tts | app-image | app-video | app-audio (transcribe, sound, music) | app-location");
41387
41465
  serviceCommand.command("list").description("List callable app services").option("--json", "Output the service list as JSON").action((opts) => run("Services", async () => {
41388
41466
  requireConfig();
41389
41467
  if (opts.json) {
@@ -41429,7 +41507,7 @@ secretsCommand.command("list").alias("ls").description("List secret names (never
41429
41507
  }
41430
41508
  console.log(bold(`${res.data.length} ${scope} secret${res.data.length === 1 ? "" : "s"}:`));
41431
41509
  for (const s of res.data) {
41432
- const masked = s.preview ? muted(`\u2026${s.preview}`) : muted("(hidden)");
41510
+ const masked = s.preview ? muted(`...${s.preview}`) : muted("(hidden)");
41433
41511
  console.log(` ${s.name} ${masked} ${muted(`updated ${new Date(s.updated_at).toLocaleDateString()}`)}`);
41434
41512
  }
41435
41513
  }));
@@ -41738,7 +41816,11 @@ jobCommand.command("status <runGuid>").description("Show status of a job run").o
41738
41816
  const r = res.data;
41739
41817
  const statusColor = r.status === "success" ? success : r.status === "failed" ? error : muted;
41740
41818
  console.log(`${statusColor(r.status)} ${muted(r.guid)}`);
41741
- if (r.progress_pct != null) console.log(`progress: ${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}`);
41819
+ if (r.progress_pct != null) {
41820
+ const prog = `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}`;
41821
+ const failedish = r.status === "failed" || r.status === "cancelled" || r.status === "canceled" || r.status === "error";
41822
+ console.log(failedish ? `last progress before ${r.status}: ${prog}` : `progress: ${prog}`);
41823
+ }
41742
41824
  if (r.duration_ms != null) console.log(`duration: ${r.duration_ms}ms`);
41743
41825
  if (r.error) console.log(`${error("error:")} ${r.error}`);
41744
41826
  if (r.output) console.log(`output: ${JSON.stringify(r.output)}`);
@@ -41758,7 +41840,7 @@ jobCommand.command("wait <runGuid>").description("Block until a job run finishes
41758
41840
  if (!opts.json) {
41759
41841
  const prog = r.progress_pct != null ? `${Math.round(r.progress_pct * 100)}%${r.progress_message ? ` (${r.progress_message})` : ""}` : r.status;
41760
41842
  if (prog !== lastProgress) {
41761
- console.error(muted(`\u2026 ${prog}`));
41843
+ console.error(muted(`... ${prog}`));
41762
41844
  lastProgress = prog;
41763
41845
  }
41764
41846
  }
@@ -41829,7 +41911,7 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
41829
41911
  res = await fetch(url, { headers, signal: controller.signal });
41830
41912
  } catch (e) {
41831
41913
  if (peekedOut) {
41832
- console.error(muted(`\u2026 still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
41914
+ console.error(muted(`... still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
41833
41915
  return;
41834
41916
  }
41835
41917
  throw e;
@@ -41849,7 +41931,7 @@ jobCommand.command("logs <runGuid>").description("Stream live logs for a job run
41849
41931
  chunk = await reader.read();
41850
41932
  } catch (e) {
41851
41933
  if (peekedOut) {
41852
- console.error(muted(`\u2026 still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
41934
+ console.error(muted(`... still streaming after ${peekSeconds}s. Run \`gipity job logs ${runGuid} --timeout ${Math.round(peekSeconds)}\` again to keep watching, or \`gipity job wait ${runGuid}\` to block until it finishes.`));
41853
41935
  return;
41854
41936
  }
41855
41937
  throw e;
@@ -41912,11 +41994,11 @@ jobCommand.command("cancel <runGuid>").description("Cancel a queued or running j
41912
41994
  }
41913
41995
  }));
41914
41996
  jobCommand.command("run-local <name>").description("Run the job in a local Docker container against your local filesystem (no platform round-trip)").option("--image <image>", "Docker image to run inside", "easyclaw/sandbox:latest").option("--no-deps", "Skip pip/npm install even if a deps file is present").action(async (name, opts) => {
41915
- const { existsSync: existsSync24, statSync: statSync11 } = await import("node:fs");
41916
- const { resolve: resolve19, join: join26 } = await import("node:path");
41997
+ const { existsSync: existsSync24, statSync: statSync12 } = await import("node:fs");
41998
+ const { resolve: resolve19, join: join27 } = await import("node:path");
41917
41999
  const { spawnCommand: spawnCommand2, spawnSyncCommand: spawnSyncCommand2 } = await Promise.resolve().then(() => (init_platform(), platform_exports));
41918
42000
  const jobDir = resolve19(process.cwd(), "jobs", name);
41919
- if (!existsSync24(jobDir) || !statSync11(jobDir).isDirectory()) {
42001
+ if (!existsSync24(jobDir) || !statSync12(jobDir).isDirectory()) {
41920
42002
  console.error(error(`jobs/${name}/ not found in current directory`));
41921
42003
  process.exit(1);
41922
42004
  }
@@ -41937,7 +42019,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
41937
42019
  },
41938
42020
  { file: "main.sh", runtime: "bash", entry: "bash" }
41939
42021
  ];
41940
- const picked = variants.find((v7) => existsSync24(join26(jobDir, v7.file)));
42022
+ const picked = variants.find((v7) => existsSync24(join27(jobDir, v7.file)));
41941
42023
  if (!picked) {
41942
42024
  console.error(error(`No main.py / main.js / main.sh found under jobs/${name}/`));
41943
42025
  process.exit(1);
@@ -41962,7 +42044,7 @@ jobCommand.command("run-local <name>").description("Run the job in a local Docke
41962
42044
  "-v",
41963
42045
  `${jobDir}:/work`
41964
42046
  ];
41965
- const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync24(join26(jobDir, picked.depsFile));
42047
+ const needsDeps = opts.deps !== false && picked.depsFile && picked.installCmd && existsSync24(join27(jobDir, picked.depsFile));
41966
42048
  const handlerCmd = `${picked.entry} /work/${picked.file}`;
41967
42049
  let shellCmd;
41968
42050
  if (needsDeps && picked.installCmd) {
@@ -42138,7 +42220,7 @@ init_api();
42138
42220
  init_config();
42139
42221
  init_colors();
42140
42222
  import { mkdirSync as mkdirSync13, writeFileSync as writeFileSync13, readFileSync as readFileSync19 } from "fs";
42141
- import { resolve as resolvePath2, dirname as dirname10, relative as relative5, isAbsolute, basename as basename5 } from "path";
42223
+ import { resolve as resolvePath2, dirname as dirname11, relative as relative5, isAbsolute, basename as basename6 } from "path";
42142
42224
 
42143
42225
  // src/provider-docs.ts
42144
42226
  var IMAGE_GEMINI_ASPECT_RATIOS = `1:1, 16:9, 9:16, 4:3, 3:4, 3:2, 2:3, 4:5, 5:4, 21:9`;
@@ -42199,11 +42281,11 @@ function noteFormatMismatch(filename, buf) {
42199
42281
  const asked = dot > 0 ? filename.slice(dot + 1).toLowerCase() : "";
42200
42282
  if (!asked || canonicalExt(asked) === actual) return;
42201
42283
  console.error(muted(
42202
- `Note: ${basename5(filename)} holds ${formatName(actual)} bytes though its name ends .${asked} \u2014 that's fine (browsers and image/video/edit tools sniff the bytes, not the name). Saved at the exact path you requested; reference it as-is.`
42284
+ `Note: ${basename6(filename)} holds ${formatName(actual)} bytes though its name ends .${asked} \u2014 that's fine (browsers and image/video/edit tools sniff the bytes, not the name). Saved at the exact path you requested; reference it as-is.`
42203
42285
  ));
42204
42286
  }
42205
42287
  function ensureOutputDir(output) {
42206
- const dir = dirname10(resolvePath2(output));
42288
+ const dir = dirname11(resolvePath2(output));
42207
42289
  try {
42208
42290
  mkdirSync13(dir, { recursive: true });
42209
42291
  } catch (err) {
@@ -42213,7 +42295,7 @@ function ensureOutputDir(output) {
42213
42295
  async function pushGenerated(savedPath) {
42214
42296
  const configPath = getConfigPath();
42215
42297
  if (!configPath) return;
42216
- const rel2 = relative5(dirname10(configPath), savedPath);
42298
+ const rel2 = relative5(dirname11(configPath), savedPath);
42217
42299
  if (rel2.startsWith("..") || isAbsolute(rel2)) return;
42218
42300
  try {
42219
42301
  await pushFile(savedPath);
@@ -42272,7 +42354,7 @@ Examples:
42272
42354
  seed: Number.isFinite(opts.seed) ? opts.seed : void 0,
42273
42355
  input_images: inputImages
42274
42356
  });
42275
- const verb = inputImages ? "Editing image\u2026" : "Generating image\u2026";
42357
+ const verb = inputImages ? "Editing image..." : "Generating image...";
42276
42358
  const result = opts.json ? await doGenerate() : await withSpinner(verb, doGenerate, { done: null });
42277
42359
  const ext = result.content_type.includes("png") ? "png" : "jpg";
42278
42360
  const filename = opts.output || `generated.${ext}`;
@@ -42318,7 +42400,7 @@ Examples:
42318
42400
  aspect_ratio: opts.aspect,
42319
42401
  resolution: opts.resolution
42320
42402
  });
42321
- const result = opts.json ? await doGenerate() : await withSpinner("Generating video\u2026", doGenerate, { done: null });
42403
+ const result = opts.json ? await doGenerate() : await withSpinner("Generating video...", doGenerate, { done: null });
42322
42404
  const filename = opts.output || "generated.mp4";
42323
42405
  const savedPath = await downloadFile(result.url, filename, !!opts.output);
42324
42406
  if (opts.json) {
@@ -42366,7 +42448,7 @@ Examples:
42366
42448
  language: opts.language,
42367
42449
  speakers
42368
42450
  });
42369
- const result = opts.json ? await doGenerate() : await withSpinner("Generating speech\u2026", doGenerate, { done: null });
42451
+ const result = opts.json ? await doGenerate() : await withSpinner("Generating speech...", doGenerate, { done: null });
42370
42452
  const filename = opts.output || "speech.mp3";
42371
42453
  const savedPath = await downloadFile(result.url, filename, !!opts.output);
42372
42454
  if (opts.json) {
@@ -42405,7 +42487,7 @@ Examples:
42405
42487
  model: opts.model,
42406
42488
  instrumental: !opts.vocals
42407
42489
  });
42408
- const result = opts.json ? await doGenerate() : await withSpinner("Generating music\u2026", doGenerate, { done: null });
42490
+ const result = opts.json ? await doGenerate() : await withSpinner("Generating music...", doGenerate, { done: null });
42409
42491
  const filename = opts.output || "music.mp3";
42410
42492
  const savedPath = await downloadFile(result.url, filename, !!opts.output);
42411
42493
  if (opts.json) {
@@ -42443,7 +42525,7 @@ Examples:
42443
42525
  duration_seconds: opts.duration,
42444
42526
  prompt_influence: opts.influence
42445
42527
  });
42446
- const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect\u2026", doGenerate, { done: null });
42528
+ const result = opts.json ? await doGenerate() : await withSpinner("Generating sound effect...", doGenerate, { done: null });
42447
42529
  const filename = opts.output || "sound.mp3";
42448
42530
  const savedPath = await downloadFile(result.url, filename, !!opts.output);
42449
42531
  if (opts.json) {
@@ -42465,12 +42547,12 @@ init_api();
42465
42547
  init_config();
42466
42548
  init_colors();
42467
42549
  import { existsSync as existsSync15, readFileSync as readFileSync20 } from "fs";
42468
- import { join as join17 } from "path";
42550
+ import { join as join18 } from "path";
42469
42551
  function readInstalledKitReadme(name) {
42470
42552
  const root = getProjectRoot() ?? process.cwd();
42471
- const kitDir = join17(root, "src", "packages", name);
42472
- if (!existsSync15(join17(kitDir, "package.json"))) return null;
42473
- const readme = join17(kitDir, "README.md");
42553
+ const kitDir = join18(root, "src", "packages", name);
42554
+ if (!existsSync15(join18(kitDir, "package.json"))) return null;
42555
+ const readme = join18(kitDir, "README.md");
42474
42556
  return existsSync15(readme) ? readFileSync20(readme, "utf-8") : null;
42475
42557
  }
42476
42558
  var skillCommand = new Command("skill").description("Task docs - read the matching skill before building");
@@ -42645,14 +42727,14 @@ var domainCommand = new Command("domain").description("Manage custom domains").a
42645
42727
 
42646
42728
  // src/commands/realtime.ts
42647
42729
  import { existsSync as existsSync16 } from "fs";
42648
- import { dirname as dirname11, resolve as resolve14 } from "path";
42730
+ import { dirname as dirname12, resolve as resolve14 } from "path";
42649
42731
  init_api();
42650
42732
  init_config();
42651
42733
  init_colors();
42652
42734
  function hasDeployManifest() {
42653
42735
  const cfgPath = getConfigPath();
42654
42736
  if (!cfgPath) return false;
42655
- return existsSync16(resolve14(dirname11(cfgPath), "gipity.yaml"));
42737
+ return existsSync16(resolve14(dirname12(cfgPath), "gipity.yaml"));
42656
42738
  }
42657
42739
  var roomCommand = new Command("room").description("Manage realtime rooms").argument("[action]", "list | create | delete | info", "list").argument("[name]", "room name (for create | delete | info)").option("--type <type>", "room type for create: state | relay", "state").option("--auth <level>", "auth level for create: public | user", "public").option("--max-clients <n>", "max clients for create (1-200)").option("--json", "Output as JSON").action((action, name, opts) => run("Realtime room", async () => {
42658
42740
  const config = requireConfig();
@@ -42830,7 +42912,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
42830
42912
  const elapsed = Math.round((now - startTime) / 1e3);
42831
42913
  const progress = data.totalFiles === 0 ? "starting up" : `${data.completedFiles}/${data.totalFiles} files`;
42832
42914
  const tally = data.passed + data.failed > 0 ? ` (${data.passed} passed${data.failed > 0 ? `, ${data.failed} failed` : ""} so far)` : "";
42833
- console.log(muted(` \u2026 still running \u2014 ${progress}${tally}, ${elapsed}s elapsed`));
42915
+ console.log(muted(` ... still running - ${progress}${tally}, ${elapsed}s elapsed`));
42834
42916
  if (now - startTime >= LONG_RUN_MS && !longRunHintShown) {
42835
42917
  longRunHintShown = true;
42836
42918
  console.log(muted(" Note: progressing, not hung. LLM-backed tests can take minutes. To verify one function fast, use `gipity fn call <name>`; narrow this suite with `gipity test <path>`."));
@@ -43061,21 +43143,21 @@ var locationCommand = new Command("location").description("Show location").argum
43061
43143
  }));
43062
43144
 
43063
43145
  // src/commands/doctor.ts
43064
- import { existsSync as existsSync18, readFileSync as readFileSync22, statSync as statSync8 } from "fs";
43065
- import { join as join19, resolve as resolve15 } from "path";
43146
+ import { existsSync as existsSync18, readFileSync as readFileSync22, statSync as statSync9 } from "fs";
43147
+ import { join as join20, resolve as resolve15 } from "path";
43066
43148
  import { homedir as homedir14 } from "os";
43067
43149
 
43068
43150
  // src/updater/state.ts
43069
43151
  import { readFileSync as readFileSync21, writeFileSync as writeFileSync14, mkdirSync as mkdirSync14, existsSync as existsSync17 } from "fs";
43070
- import { join as join18 } from "path";
43152
+ import { join as join19 } from "path";
43071
43153
  import { homedir as homedir13 } from "os";
43072
- var GIPITY_DIR = join18(homedir13(), ".gipity");
43073
- var LOCAL_DIR = join18(GIPITY_DIR, "local");
43074
- var LOCAL_PKG_DIR = join18(LOCAL_DIR, "node_modules", "gipity");
43075
- var LOCAL_ENTRY = join18(LOCAL_PKG_DIR, "dist", "index.js");
43076
- var STATE_FILE = join18(GIPITY_DIR, "update-state.json");
43077
- var SETTINGS_FILE = join18(GIPITY_DIR, "settings.json");
43078
- var UPDATE_LOG = join18(GIPITY_DIR, "update.log");
43154
+ var GIPITY_DIR = join19(homedir13(), ".gipity");
43155
+ var LOCAL_DIR = join19(GIPITY_DIR, "local");
43156
+ var LOCAL_PKG_DIR = join19(LOCAL_DIR, "node_modules", "gipity");
43157
+ var LOCAL_ENTRY = join19(LOCAL_PKG_DIR, "dist", "index.js");
43158
+ var STATE_FILE = join19(GIPITY_DIR, "update-state.json");
43159
+ var SETTINGS_FILE = join19(GIPITY_DIR, "settings.json");
43160
+ var UPDATE_LOG = join19(GIPITY_DIR, "update.log");
43079
43161
  var DEFAULT_STATE = {
43080
43162
  installedVersion: null,
43081
43163
  lastCheckAt: 0,
@@ -43136,7 +43218,7 @@ function versionManagerNode(execPath, env = process.env, home = homedir14()) {
43136
43218
  return null;
43137
43219
  }
43138
43220
  function localVersion() {
43139
- const pkgPath = join19(LOCAL_PKG_DIR, "package.json");
43221
+ const pkgPath = join20(LOCAL_PKG_DIR, "package.json");
43140
43222
  if (!existsSync18(pkgPath)) return null;
43141
43223
  try {
43142
43224
  return JSON.parse(readFileSync22(pkgPath, "utf-8")).version ?? null;
@@ -43250,7 +43332,7 @@ var doctorCommand = new Command("doctor").description("Check install + environme
43250
43332
  console.log(`${muted("last check ")} ${rel(state.lastCheckAt)}`);
43251
43333
  console.log(`${muted("last error ")} ${state.lastError ? error(state.lastError) : dim("none")}`);
43252
43334
  console.log(`${muted("state file ")} ${existsSync18(STATE_FILE) ? STATE_FILE : dim("(none yet)")}`);
43253
- console.log(`${muted("update log ")} ${existsSync18(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync8(UPDATE_LOG).size} bytes)` : dim("(none yet)")}`);
43335
+ console.log(`${muted("update log ")} ${existsSync18(UPDATE_LOG) ? `${UPDATE_LOG} (${statSync9(UPDATE_LOG).size} bytes)` : dim("(none yet)")}`);
43254
43336
  console.log("");
43255
43337
  console.log(dim("Force an update with: gipity update"));
43256
43338
  });
@@ -43259,10 +43341,10 @@ var doctorCommand = new Command("doctor").description("Check install + environme
43259
43341
  import { appendFileSync } from "fs";
43260
43342
 
43261
43343
  // src/updater/install.ts
43262
- import { existsSync as existsSync19, mkdirSync as mkdirSync15, rmSync as rmSync2, statSync as statSync9, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
43263
- import { dirname as dirname12, join as join20 } from "path";
43344
+ import { existsSync as existsSync19, mkdirSync as mkdirSync15, rmSync as rmSync2, statSync as statSync10, unlinkSync as unlinkSync5, writeFileSync as writeFileSync15 } from "fs";
43345
+ import { dirname as dirname13, join as join21 } from "path";
43264
43346
  init_platform();
43265
- var UPDATE_LOCK = join20(GIPITY_DIR, "update.lock");
43347
+ var UPDATE_LOCK = join21(GIPITY_DIR, "update.lock");
43266
43348
  var LOCK_STALE_MS2 = 10 * 60 * 1e3;
43267
43349
  function npmInstallGipity(version) {
43268
43350
  const res = spawnSyncCommand(resolveCommand("npm"), ["install", "--no-audit", "--no-fund", "--ignore-scripts", `gipity@${version}`], {
@@ -43283,18 +43365,18 @@ function isWedged(stderr) {
43283
43365
  }
43284
43366
  function resetLocalTree(dir = LOCAL_DIR) {
43285
43367
  mkdirSync15(dir, { recursive: true });
43286
- rmSync2(join20(dir, "node_modules"), { recursive: true, force: true });
43287
- rmSync2(join20(dir, "package-lock.json"), { force: true });
43288
- writeFileSync15(join20(dir, "package.json"), JSON.stringify({ name: "gipity-local", private: true, version: "0.0.0" }, null, 2));
43368
+ rmSync2(join21(dir, "node_modules"), { recursive: true, force: true });
43369
+ rmSync2(join21(dir, "package-lock.json"), { force: true });
43370
+ writeFileSync15(join21(dir, "package.json"), JSON.stringify({ name: "gipity-local", private: true, version: "0.0.0" }, null, 2));
43289
43371
  }
43290
43372
  function acquireUpdateLock(lockPath2 = UPDATE_LOCK) {
43291
- mkdirSync15(dirname12(lockPath2), { recursive: true });
43373
+ mkdirSync15(dirname13(lockPath2), { recursive: true });
43292
43374
  try {
43293
43375
  writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
43294
43376
  return true;
43295
43377
  } catch {
43296
43378
  try {
43297
- if (Date.now() - statSync9(lockPath2).mtimeMs > LOCK_STALE_MS2) {
43379
+ if (Date.now() - statSync10(lockPath2).mtimeMs > LOCK_STALE_MS2) {
43298
43380
  unlinkSync5(lockPath2);
43299
43381
  writeFileSync15(lockPath2, String(process.pid), { flag: "wx" });
43300
43382
  return true;
@@ -43455,7 +43537,7 @@ import { appendFileSync as appendFileSync3, mkdirSync as mkdirSync17, existsSync
43455
43537
  import { stat, readFile } from "fs/promises";
43456
43538
  import { createInterface as createInterface2 } from "readline";
43457
43539
  import { homedir as homedir16, hostname as hostname5, platform as osPlatform3, loadavg as loadavg2, freemem as freemem2, totalmem as totalmem2, cpus as cpus2 } from "os";
43458
- import { join as join23 } from "path";
43540
+ import { join as join24 } from "path";
43459
43541
  init_auth();
43460
43542
  init_api();
43461
43543
 
@@ -44211,8 +44293,8 @@ async function revokeRelayAgentToken() {
44211
44293
  init_platform();
44212
44294
  init_client_context();
44213
44295
  import { cpus, freemem, totalmem, loadavg, release, uptime, homedir as homedir15 } from "os";
44214
- import { statfsSync, readdirSync as readdirSync8 } from "fs";
44215
- import { join as join21 } from "path";
44296
+ import { statfsSync, readdirSync as readdirSync9 } from "fs";
44297
+ import { join as join22 } from "path";
44216
44298
  var PROBE_TIMEOUT_MS = 4e3;
44217
44299
  function parseVersion(out) {
44218
44300
  const m = out.match(/\d+\.\d+(?:\.\d+)?(?:[-.\w]*)?/);
@@ -44279,7 +44361,7 @@ async function detectGpu() {
44279
44361
  function diskUsage() {
44280
44362
  try {
44281
44363
  if (typeof statfsSync !== "function") return void 0;
44282
- const st2 = statfsSync(join21(homedir15(), "GipityProjects"), { bigint: false });
44364
+ const st2 = statfsSync(join22(homedir15(), "GipityProjects"), { bigint: false });
44283
44365
  if (!st2 || !st2.bsize) return void 0;
44284
44366
  return { total: st2.bsize * st2.blocks, free: st2.bsize * st2.bavail };
44285
44367
  } catch {
@@ -44293,7 +44375,7 @@ function diskUsage() {
44293
44375
  }
44294
44376
  function localProjectCount() {
44295
44377
  try {
44296
- return readdirSync8(join21(homedir15(), "GipityProjects"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
44378
+ return readdirSync9(join22(homedir15(), "GipityProjects"), { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).length;
44297
44379
  } catch {
44298
44380
  return void 0;
44299
44381
  }
@@ -44602,7 +44684,7 @@ var SessionPool = class {
44602
44684
  // src/relay/daemon.ts
44603
44685
  init_config();
44604
44686
  init_api();
44605
- var RELAY_LOG_PATH = join23(homedir16(), ".gipity", "relay.log");
44687
+ var RELAY_LOG_PATH = join24(homedir16(), ".gipity", "relay.log");
44606
44688
  var HEARTBEAT_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_HEARTBEAT_MS || "60000", 10);
44607
44689
  var DIAGNOSTICS_INTERVAL_MS = parseInt(process.env.GIPITY_RELAY_DIAGNOSTICS_MS || String(24 * 60 * 60 * 1e3), 10);
44608
44690
  var LONG_POLL_TIMEOUT_MS = parseInt(process.env.GIPITY_RELAY_POLL_TIMEOUT_MS || "35000", 10);
@@ -44719,7 +44801,7 @@ function log2(level, msg, extra) {
44719
44801
  const pretty = `${C7.dim(hhmmss())} ${badge(level)} ${C7.bold(msg)}${formatExtra(extra)}`;
44720
44802
  process.stderr.write(pretty + "\n");
44721
44803
  try {
44722
- const dir = join23(homedir16(), ".gipity");
44804
+ const dir = join24(homedir16(), ".gipity");
44723
44805
  mkdirSync17(dir, { recursive: true });
44724
44806
  lockLogPerms(dir, RELAY_LOG_PATH);
44725
44807
  const json = JSON.stringify({ ts: (/* @__PURE__ */ new Date()).toISOString(), level, msg, ...extra ?? {} });
@@ -45120,7 +45202,7 @@ function isSafeSessionId(s) {
45120
45202
  function transcriptPathFor(cwd, sessionId) {
45121
45203
  if (!isSafeSessionId(sessionId)) return null;
45122
45204
  const slug = cwd.replace(/[^a-zA-Z0-9]/g, "-");
45123
- return join23(homedir16(), ".claude", "projects", slug, `${sessionId}.jsonl`);
45205
+ return join24(homedir16(), ".claude", "projects", slug, `${sessionId}.jsonl`);
45124
45206
  }
45125
45207
  function formatDuration(ms2) {
45126
45208
  const totalSec = ms2 / 1e3;
@@ -45574,8 +45656,8 @@ async function resolveCwdForProject(d) {
45574
45656
  throw new Error(`Invalid project slug: ${JSON.stringify(d.project_slug)}`);
45575
45657
  }
45576
45658
  const root = getProjectsRoot();
45577
- const path5 = join23(root, d.project_slug);
45578
- const configPath = join23(path5, ".gipity.json");
45659
+ const path5 = join24(root, d.project_slug);
45660
+ const configPath = join24(path5, ".gipity.json");
45579
45661
  if (existsSync21(configPath)) {
45580
45662
  try {
45581
45663
  const cfg = JSON.parse(readFileSync24(configPath, "utf-8"));
@@ -45713,7 +45795,7 @@ async function spawnSync2(cwd, timeoutMs, onSpawn) {
45713
45795
  } catch {
45714
45796
  }
45715
45797
  try {
45716
- unlinkSync7(join23(cwd, ".gipity", "sync.lock"));
45798
+ unlinkSync7(join24(cwd, ".gipity", "sync.lock"));
45717
45799
  } catch {
45718
45800
  }
45719
45801
  finish(() => reject(new Error(`timed out after ${Math.round(timeoutMs / 1e3)}s`)));
@@ -46351,9 +46433,7 @@ var connectCommand = new Command("connect").alias("setup").description("Connect
46351
46433
  console.log("");
46352
46434
  const enabled = await runRelaySetup({ mode: "run-now" });
46353
46435
  if (enabled) {
46354
- const running2 = isRelayEnabled() && !isPaused();
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`.")}`);
46436
+ console.log(` ${muted("Manage it anytime with `gipity relay status`.")}`);
46357
46437
  } else {
46358
46438
  console.log(` ${muted("No relay set up. Run `gipity connect` again anytime, or `gipity build` to start building.")}`);
46359
46439
  }
@@ -46373,9 +46453,9 @@ init_utils();
46373
46453
  init_colors();
46374
46454
  import { existsSync as existsSync23, rmSync as rmSync4, unlinkSync as unlinkSync9, readFileSync as readFileSync26, writeFileSync as writeFileSync17 } from "fs";
46375
46455
  import { homedir as homedir17, platform as osPlatform4 } from "os";
46376
- import { join as join24, resolve as resolve17 } from "path";
46456
+ import { join as join25, resolve as resolve17 } from "path";
46377
46457
  function removeGipityPluginConfig() {
46378
- const settingsPath = join24(homedir17(), ".claude", "settings.json");
46458
+ const settingsPath = join25(homedir17(), ".claude", "settings.json");
46379
46459
  if (!existsSync23(settingsPath)) return false;
46380
46460
  let settings;
46381
46461
  try {
@@ -46402,7 +46482,7 @@ function removeInstallerPathLines() {
46402
46482
  const marker = "# Added by the Gipity installer";
46403
46483
  const touched = [];
46404
46484
  for (const name of [".bashrc", ".zshrc", ".profile"]) {
46405
- const rc2 = join24(homedir17(), name);
46485
+ const rc2 = join25(homedir17(), name);
46406
46486
  if (!existsSync23(rc2)) continue;
46407
46487
  let text;
46408
46488
  try {
@@ -46485,8 +46565,8 @@ async function revokeDeviceBestEffort() {
46485
46565
  }
46486
46566
  var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").option("--yes", "Skip confirmation prompts").action(async (opts) => {
46487
46567
  const autoYes = opts.yes || getAutoConfirm();
46488
- const gipityDir = join24(homedir17(), ".gipity");
46489
- const launcherBin = join24(gipityDir, "launcher", "bin", "gipity");
46568
+ const gipityDir = join25(homedir17(), ".gipity");
46569
+ const launcherBin = join25(gipityDir, "launcher", "bin", "gipity");
46490
46570
  const installedViaLauncher = existsSync23(launcherBin);
46491
46571
  console.log(`${bold("Gipity uninstall")} - this will:`);
46492
46572
  console.log(`\u2022 Stop the running relay daemon (if any)`);
@@ -46533,7 +46613,7 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
46533
46613
  if (agentSkills.skills.length) {
46534
46614
  for (const name of agentSkills.skills) {
46535
46615
  try {
46536
- rmSync4(join24(AGENTS_SKILLS_DIR, name), { recursive: true, force: true });
46616
+ rmSync4(join25(AGENTS_SKILLS_DIR, name), { recursive: true, force: true });
46537
46617
  } catch {
46538
46618
  }
46539
46619
  }
@@ -46685,7 +46765,7 @@ gmailCommand.command("send").description("Send a new email from your Gmail").req
46685
46765
  const recap = opts.to.join(", ") + (opts.cc.length ? `, cc: ${opts.cc.join(", ")}` : "");
46686
46766
  printResult(`Sent from your Gmail to ${recap}.`, opts, { sent: true, to: opts.to });
46687
46767
  }));
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: \u2026" of the original)').requiredOption("--body <text>", "Reply body (you compose the quote header)").option("--json", "Output as JSON").action((opts) => run("Reply", async () => {
46768
+ gmailCommand.command("reply").description("Reply to a Gmail thread. Get thread-id + message-id from `gmail search`.").requiredOption("--thread-id <id>", "Thread ID (from `gmail search`)").requiredOption("--message-id <id>", "Message ID being replied to (In-Reply-To)").requiredOption("--to <email>", "Recipient (repeatable)", collect2, []).option("--cc <email>", "Cc recipient (repeatable)", collect2, []).option("--bcc <email>", "Bcc recipient (repeatable)", collect2, []).option("--reply-to <email>", "Reply-To header address").requiredOption("--subject <s>", 'Subject (usually "Re: ..." of the original)').requiredOption("--body <text>", "Reply body (you compose the quote header)").option("--json", "Output as JSON").action((opts) => run("Reply", async () => {
46689
46769
  if (!opts.to.length) throw new Error("--to is required (pass at least one)");
46690
46770
  const payload = {
46691
46771
  thread_id: opts.threadId,
@@ -47067,7 +47147,7 @@ function collectRealFlags(argv2, program3) {
47067
47147
 
47068
47148
  // src/trace.ts
47069
47149
  import { openSync as openSync5, writeSync, mkdirSync as mkdirSync18 } from "fs";
47070
- import { join as join25 } from "path";
47150
+ import { join as join26 } from "path";
47071
47151
  import { homedir as homedir18 } from "os";
47072
47152
  var TRACE_KEY = /* @__PURE__ */ Symbol.for("gipity.traceOutput");
47073
47153
  function installOutputTrace(label2) {
@@ -47079,10 +47159,10 @@ function installOutputTrace(label2) {
47079
47159
  existing.emit({ event: "reenter", label: label2 });
47080
47160
  return;
47081
47161
  }
47082
- const dir = join25(process.env.GIPITY_DIR || join25(homedir18(), ".gipity"), "trace");
47162
+ const dir = join26(process.env.GIPITY_DIR || join26(homedir18(), ".gipity"), "trace");
47083
47163
  mkdirSync18(dir, { recursive: true });
47084
47164
  const stamp = (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", "_").replace(/:/g, "-");
47085
- const fd2 = openSync5(join25(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
47165
+ const fd2 = openSync5(join26(dir, `${stamp}-pid${process.pid}.jsonl`), "a");
47086
47166
  const emit = (rec) => {
47087
47167
  try {
47088
47168
  writeSync(fd2, JSON.stringify({ t: (/* @__PURE__ */ new Date()).toISOString(), ...rec }) + "\n");
@@ -47117,7 +47197,7 @@ function installOutputTrace(label2) {
47117
47197
 
47118
47198
  // src/index.ts
47119
47199
  installOutputTrace("index");
47120
- var __dirname = dirname13(fileURLToPath3(import.meta.url));
47200
+ var __dirname = dirname14(fileURLToPath3(import.meta.url));
47121
47201
  var pkg = JSON.parse(readFileSync28(resolve18(__dirname, "../package.json"), "utf-8"));
47122
47202
  function versionLabel() {
47123
47203
  const base = `v${pkg.version}`;
@@ -47336,7 +47416,7 @@ Command.prototype._excessArguments = function(receivedArgs) {
47336
47416
  let message = `error: unexpected extra argument${excess.length === 1 ? "" : "s"} ${list}${forSubcommand}.`;
47337
47417
  const kv2 = excess.map((a) => /^([A-Za-z][\w-]*)=/.exec(a)).find(Boolean);
47338
47418
  if (kv2) {
47339
- message += ` Options are passed as \`--${kv2[1]} <value>\`, not \`${kv2[1]}=\u2026\` - did you mean \`--${kv2[1]}\`?`;
47419
+ message += ` Options are passed as \`--${kv2[1]} <value>\`, not \`${kv2[1]}=...\` - did you mean \`--${kv2[1]}\`?`;
47340
47420
  }
47341
47421
  this.error(message, { code: "commander.excessArguments" });
47342
47422
  };