gipity 1.1.5 → 1.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6597,6 +6597,9 @@ function detectHarness() {
6597
6597
  }
6598
6598
  if (hasEnvPrefix("CODEX_")) return { harness: "codex" };
6599
6599
  if (hasEnvPrefix("GROK_")) return { harness: "grok", harnessSession: process.env.GROK_SESSION_ID };
6600
+ if (process.env.ANTIGRAVITY_CONVERSATION_ID) {
6601
+ return { harness: "agy", harnessSession: process.env.ANTIGRAVITY_CONVERSATION_ID };
6602
+ }
6600
6603
  if (process.env.CURSOR_TRACE_ID || hasEnvPrefix("CURSOR_") || (process.env.TERM_PROGRAM ?? "").toLowerCase().includes("cursor")) {
6601
6604
  return { harness: "cursor" };
6602
6605
  }
@@ -6643,11 +6646,13 @@ __export(api_exports, {
6643
6646
  getAccountSlug: () => getAccountSlug,
6644
6647
  getAuthHeader: () => getAuthHeader,
6645
6648
  getBaseUrl: () => getBaseUrl,
6649
+ mintAppToken: () => mintAppToken,
6646
6650
  patch: () => patch,
6647
6651
  post: () => post,
6648
6652
  postBinary: () => postBinary,
6649
6653
  postForTarEntries: () => postForTarEntries,
6650
6654
  publicPost: () => publicPost,
6655
+ publicRequest: () => publicRequest,
6651
6656
  put: () => put,
6652
6657
  putTimeoutMs: () => putTimeoutMs,
6653
6658
  putToPresignedUrl: () => putToPresignedUrl,
@@ -6903,12 +6908,12 @@ async function getAccountSlug() {
6903
6908
  accountSlugCache = res.data.accountSlug;
6904
6909
  return accountSlugCache;
6905
6910
  }
6906
- async function publicPost(path5, body, extraHeaders) {
6911
+ async function publicRequest(method, path5, body, extraHeaders) {
6907
6912
  const url = `${baseUrl()}${path5}`;
6908
6913
  const res = await fetch(url, {
6909
- method: "POST",
6914
+ method,
6910
6915
  headers: { ...clientHeaders(), "Content-Type": "application/json", ...extraHeaders },
6911
- body: JSON.stringify(body)
6916
+ body: body === void 0 ? void 0 : JSON.stringify(body)
6912
6917
  });
6913
6918
  if (!res.ok) {
6914
6919
  const json = await res.json().catch(() => ({ error: { code: "UNKNOWN", message: res.statusText } }));
@@ -6917,6 +6922,17 @@ async function publicPost(path5, body, extraHeaders) {
6917
6922
  }
6918
6923
  return res.json();
6919
6924
  }
6925
+ function publicPost(path5, body, extraHeaders) {
6926
+ return publicRequest("POST", path5, body, extraHeaders);
6927
+ }
6928
+ async function mintAppToken(projectGuid2) {
6929
+ try {
6930
+ const minted = await publicPost("/api/token", { app: projectGuid2 });
6931
+ return { "X-App-Token": minted.data.token };
6932
+ } catch {
6933
+ return void 0;
6934
+ }
6935
+ }
6920
6936
  var tar, ApiError, REQUEST_TIMEOUT_MS, DOWNLOAD_HEADER_TIMEOUT_MS, PUT_TIMEOUT_FLOOR_MS, PUT_MIN_BYTES_PER_SEC, accountSlugCache;
6921
6937
  var init_api = __esm({
6922
6938
  "src/api.ts"() {
@@ -32075,7 +32091,7 @@ mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <s
32075
32091
 
32076
32092
  ## CLI quick reference
32077
32093
 
32078
- Key commands: \`gipity add <template|kit>\`, \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity secrets set <NAME> <value> [--account]\` (store an API key/token encrypted; read in functions via \`secrets.get('NAME')\` \u2014 never hardcode keys), \`gipity email send --to <addr> --subject <s> --body <b>\` (sends as \`gipity@gipity.ai\`; omit \`--to\` to self-send), \`gipity skill read <name>\`.
32094
+ Key commands: \`gipity add <template|kit>\`, \`gipity brand set --emoji <e>|--color <hex>\` (regenerate the app's icons + social share card), \`gipity deploy dev\`, \`gipity sandbox run\`, \`gipity page inspect <url>\`, \`gipity page screenshot <url>\`, \`gipity db query "SQL"\`, \`gipity fn call <name>\`, \`gipity logs fn <name>\`, \`gipity secrets set <NAME> <value> [--account]\` (store an API key/token encrypted; read in functions via \`secrets.get('NAME')\` \u2014 never hardcode keys), \`gipity email send --to <addr> --subject <s> --body <b>\` (sends as \`gipity@gipity.ai\`; omit \`--to\` to self-send), \`gipity skill read <name>\`.
32079
32095
  Rename for findability: \`gipity project rename <name>\` renames the current project's display name (the slug and deployed URLs never change); \`gipity chat rename <title>\` renames the current chat's tab title. Both are the display label users scan to switch between tabs \u2014 retitle a chat when the conversation clearly shifts to a new topic (sparingly, not every turn), and keep every project/chat title SHORT: 2-4 words, \u226440 characters, no trailing punctuation (e.g. "Stripe checkout", "Tetris game").
32080
32096
  Pull an existing remote project local (given its URL/slug): \`mkdir -p ~/GipityProjects/<slug> && cd ~/GipityProjects/<slug> && gipity init <slug>\` (adopts the matching project and syncs files down - this is the "clone").
32081
32097
  Move whole apps in/out: \`gipity save\` (export this project as a portable \`.gip\` bundle), \`gipity load <file.gip | github:owner/repo>\` (import as a NEW project; \`--inspect\` to preview), \`gipity github connect\` (1-2 click GitHub access for imports). Porting a Vercel/Replit/Lovable app? Load the \`app-import\` skill first.
@@ -32513,7 +32529,7 @@ var UploadConflictError = class extends Error {
32513
32529
  currentServerVersion;
32514
32530
  path;
32515
32531
  };
32516
- async function uploadOneFile(projectGuid, localPath, virtualPath, opts = {}) {
32532
+ async function uploadOneFile(projectGuid2, localPath, virtualPath, opts = {}) {
32517
32533
  const { sha256, size } = await hashFile(localPath);
32518
32534
  const mime = opts.mime ?? guessMime(virtualPath);
32519
32535
  const initBody = { path: virtualPath, size, sha256, mime };
@@ -32523,7 +32539,7 @@ async function uploadOneFile(projectGuid, localPath, virtualPath, opts = {}) {
32523
32539
  let init;
32524
32540
  try {
32525
32541
  init = await post(
32526
- `/projects/${projectGuid}/files/upload-init`,
32542
+ `/projects/${projectGuid2}/files/upload-init`,
32527
32543
  initBody
32528
32544
  );
32529
32545
  } catch (err) {
@@ -32545,7 +32561,7 @@ async function uploadOneFile(projectGuid, localPath, virtualPath, opts = {}) {
32545
32561
  }
32546
32562
  let comp;
32547
32563
  try {
32548
- comp = await post(`/projects/${projectGuid}/files/upload-complete`, completeBody);
32564
+ comp = await post(`/projects/${projectGuid2}/files/upload-complete`, completeBody);
32549
32565
  } catch (err) {
32550
32566
  if (err instanceof ApiError && err.statusCode === 409) {
32551
32567
  const current = typeof err.data?.current_server_version === "number" ? err.data.current_server_version : null;
@@ -32627,16 +32643,16 @@ async function transferToS3(localPath, size, mime, data, opts = {}) {
32627
32643
  completed.sort((a, b7) => a.part_number - b7.part_number);
32628
32644
  return { parts: completed };
32629
32645
  }
32630
- async function uploadInitBatch(projectGuid, files) {
32646
+ async function uploadInitBatch(projectGuid2, files) {
32631
32647
  const res = await post(
32632
- `/projects/${projectGuid}/files/upload-init-batch`,
32648
+ `/projects/${projectGuid2}/files/upload-init-batch`,
32633
32649
  { files }
32634
32650
  );
32635
32651
  return res.data.results;
32636
32652
  }
32637
- async function uploadCompleteBatch(projectGuid, items) {
32653
+ async function uploadCompleteBatch(projectGuid2, items) {
32638
32654
  const res = await post(
32639
- `/projects/${projectGuid}/files/upload-complete-batch`,
32655
+ `/projects/${projectGuid2}/files/upload-complete-batch`,
32640
32656
  { items }
32641
32657
  );
32642
32658
  return res.data.results;
@@ -32653,6 +32669,8 @@ var PRIMER_FILES = {
32653
32669
  codex: "AGENTS.md",
32654
32670
  grok: "AGENTS.md",
32655
32671
  // Grok Build reads the AGENTS.md family (and CLAUDE.md) natively
32672
+ agy: "AGENTS.md",
32673
+ // Antigravity reads the same AGENTS.md/GEMINI.md rules family natively
32656
32674
  aider: "AGENTS.md",
32657
32675
  // shares the Codex primer; aider is pointed at it via .aider.conf.yml
32658
32676
  gemini: "GEMINI.md",
@@ -32668,6 +32686,7 @@ var DEFAULT_SYNC_IGNORE = [
32668
32686
  ".gipity/",
32669
32687
  ".claude/",
32670
32688
  ".codex/",
32689
+ ".agents/",
32671
32690
  ".gitignore",
32672
32691
  AIDER_CONF_FILE,
32673
32692
  // Home-directory junk: a project created inside a real home dir (or one that
@@ -32867,9 +32886,11 @@ function ensureGrokPluginInstalled() {
32867
32886
  var AGENTS_SKILLS_DIR = join4(homedir3(), ".agents", "skills");
32868
32887
  var AGENT_HOOKS_DIR = join4(homedir3(), ".gipity", "agent-hooks");
32869
32888
  var AGENT_SKILLS_MANIFEST = join4(homedir3(), ".gipity", "agent-skills.json");
32870
- function agentSkillsState() {
32889
+ var AGY_SKILLS_DIR = join4(homedir3(), ".gemini", "config", "skills");
32890
+ var AGY_SKILLS_MANIFEST = join4(homedir3(), ".gipity", "agy-skills.json");
32891
+ function skillsManifestState(manifestPath) {
32871
32892
  try {
32872
- const m = JSON.parse(readFileSync6(AGENT_SKILLS_MANIFEST, "utf-8"));
32893
+ const m = JSON.parse(readFileSync6(manifestPath, "utf-8"));
32873
32894
  return {
32874
32895
  current: typeof m?.version === "string" && versionGte(m.version, GIPITY_PLUGIN_VERSION),
32875
32896
  skills: Array.isArray(m?.skills) ? m.skills : []
@@ -32878,8 +32899,13 @@ function agentSkillsState() {
32878
32899
  return { current: false, skills: [] };
32879
32900
  }
32880
32901
  }
32881
- function ensureAgentSkillsInstalled() {
32882
- if (agentSkillsState().current) return;
32902
+ function agentSkillsState() {
32903
+ return skillsManifestState(AGENT_SKILLS_MANIFEST);
32904
+ }
32905
+ function agySkillsState() {
32906
+ return skillsManifestState(AGY_SKILLS_MANIFEST);
32907
+ }
32908
+ function installSkillsAndHooks(skillsDir, manifestPath, harnessLabel) {
32883
32909
  if (!binaryOnPath("git")) return;
32884
32910
  const tmp = mkdtempSync(join4(tmpdir(), "gipity-skills-"));
32885
32911
  try {
@@ -32901,8 +32927,8 @@ function ensureAgentSkillsInstalled() {
32901
32927
  for (const entry of readdirSync3(skillsSrc, { withFileTypes: true })) {
32902
32928
  if (!entry.isDirectory()) continue;
32903
32929
  if (!existsSync4(join4(skillsSrc, entry.name, "SKILL.md"))) continue;
32904
- mkdirSync3(AGENTS_SKILLS_DIR, { recursive: true });
32905
- cpSync(join4(skillsSrc, entry.name), join4(AGENTS_SKILLS_DIR, entry.name), {
32930
+ mkdirSync3(skillsDir, { recursive: true });
32931
+ cpSync(join4(skillsSrc, entry.name), join4(skillsDir, entry.name), {
32906
32932
  recursive: true,
32907
32933
  force: true
32908
32934
  });
@@ -32912,13 +32938,21 @@ function ensureAgentSkillsInstalled() {
32912
32938
  for (const script of readdirSync3(join4(repo, "hooks", "scripts"))) {
32913
32939
  cpSync(join4(repo, "hooks", "scripts", script), join4(AGENT_HOOKS_DIR, script), { force: true });
32914
32940
  }
32915
- writeFileSync4(AGENT_SKILLS_MANIFEST, JSON.stringify({ version, skills: names }, null, 2) + "\n");
32916
- console.log(`Installed ${names.length} Gipity skills for Codex (~/.agents/skills).`);
32941
+ writeFileSync4(manifestPath, JSON.stringify({ version, skills: names }, null, 2) + "\n");
32942
+ console.log(`Installed ${names.length} Gipity skills for ${harnessLabel} (${skillsDir}).`);
32917
32943
  } catch {
32918
32944
  } finally {
32919
32945
  rmSync(tmp, { recursive: true, force: true });
32920
32946
  }
32921
32947
  }
32948
+ function ensureAgentSkillsInstalled() {
32949
+ if (agentSkillsState().current) return;
32950
+ installSkillsAndHooks(AGENTS_SKILLS_DIR, AGENT_SKILLS_MANIFEST, "Codex");
32951
+ }
32952
+ function ensureAgySkillsInstalled() {
32953
+ if (agySkillsState().current) return;
32954
+ installSkillsAndHooks(AGY_SKILLS_DIR, AGY_SKILLS_MANIFEST, "Antigravity");
32955
+ }
32922
32956
  function applyCodexHooks(existing) {
32923
32957
  const launcher = join4(AGENT_HOOKS_DIR, "launch.sh");
32924
32958
  const cmd = (script, ...args) => [`sh "${launcher}" "${join4(AGENT_HOOKS_DIR, script)}"`, ...args].join(" ");
@@ -32976,6 +33010,102 @@ function setupCodexIntegration() {
32976
33010
  ensureAgentSkillsInstalled();
32977
33011
  setupCodexHooks();
32978
33012
  }
33013
+ var AGY_HOOKS_SCRIPT = `#!/usr/bin/env node
33014
+ 'use strict';
33015
+ const { spawnSync } = require('child_process');
33016
+ const { join } = require('path');
33017
+
33018
+ const WRITE_TOOLS = new Set(['write_to_file', 'replace_file_content']);
33019
+
33020
+ function readStdin() {
33021
+ return new Promise((res) => {
33022
+ let data = '';
33023
+ process.stdin.setEncoding('utf-8');
33024
+ process.stdin.on('data', (c) => { data += c; });
33025
+ process.stdin.on('end', () => res(data));
33026
+ process.stdin.on('error', () => res(data));
33027
+ });
33028
+ }
33029
+
33030
+ async function main() {
33031
+ const event = process.argv[2];
33032
+ const raw = await readStdin();
33033
+ let payload = {};
33034
+ try { payload = JSON.parse(raw); } catch { /* keep {} */ }
33035
+
33036
+ try {
33037
+ if (event === 'post-tool-use') {
33038
+ const toolCall = payload.toolCall;
33039
+ const filePath = toolCall && toolCall.args && toolCall.args.TargetFile;
33040
+ if (toolCall && WRITE_TOOLS.has(toolCall.name) && typeof filePath === 'string' && filePath) {
33041
+ spawnSync(process.execPath, [join(__dirname, 'sync-push.cjs')], {
33042
+ input: JSON.stringify({ tool_input: { file_path: filePath } }),
33043
+ stdio: ['pipe', 'ignore', 'ignore'],
33044
+ windowsHide: true,
33045
+ });
33046
+ }
33047
+ spawnSync(process.execPath, [join(__dirname, 'capture.cjs'), 'agy', 'post-tool-use'], {
33048
+ input: raw,
33049
+ stdio: ['pipe', 'ignore', 'ignore'],
33050
+ windowsHide: true,
33051
+ });
33052
+ } else if (event === 'stop') {
33053
+ spawnSync(process.execPath, [join(__dirname, 'capture.cjs'), 'agy', 'stop'], {
33054
+ input: raw,
33055
+ stdio: ['pipe', 'ignore', 'ignore'],
33056
+ windowsHide: true,
33057
+ });
33058
+ }
33059
+ } catch { /* never let a side effect break the response below */ }
33060
+
33061
+ process.stdout.write('{}');
33062
+ }
33063
+
33064
+ main();
33065
+ `;
33066
+ function applyAgyHooks(existing) {
33067
+ const wrapper = join4(AGENT_HOOKS_DIR, "agy-hooks.cjs");
33068
+ const launcher = join4(AGENT_HOOKS_DIR, "launch.sh");
33069
+ const wrapCmd = (event) => `sh "${launcher}" "${wrapper}" ${event}`;
33070
+ const block = {
33071
+ PostToolUse: [
33072
+ { matcher: ".*", hooks: [{ type: "command", command: wrapCmd("post-tool-use"), timeout: 30 }] }
33073
+ ],
33074
+ Stop: [
33075
+ { hooks: [{ type: "command", command: wrapCmd("stop"), timeout: 60 }] }
33076
+ ]
33077
+ };
33078
+ let settings = {};
33079
+ if (existing !== null) {
33080
+ try {
33081
+ settings = JSON.parse(existing);
33082
+ } catch {
33083
+ return null;
33084
+ }
33085
+ }
33086
+ if (JSON.stringify(settings.gipity ?? null) === JSON.stringify(block)) return null;
33087
+ settings.gipity = block;
33088
+ return JSON.stringify(settings, null, 2) + "\n";
33089
+ }
33090
+ function setupAgyHooks() {
33091
+ if (process.platform === "win32") return;
33092
+ const cwd = resolve3(process.cwd());
33093
+ if (cwd === resolve3(homedir3())) return;
33094
+ mkdirSync3(AGENT_HOOKS_DIR, { recursive: true });
33095
+ writeFileSync4(join4(AGENT_HOOKS_DIR, "agy-hooks.cjs"), AGY_HOOKS_SCRIPT);
33096
+ const path5 = join4(cwd, ".agents", "hooks.json");
33097
+ const existing = existsSync4(path5) ? readFileSync6(path5, "utf-8") : null;
33098
+ const next = applyAgyHooks(existing);
33099
+ if (next === null) return;
33100
+ mkdirSync3(dirname3(path5), { recursive: true });
33101
+ writeFileSync4(path5, next);
33102
+ console.log(existing === null ? "Wrote Antigravity sync + session-capture hooks (.agents/hooks.json)." : "Updated Antigravity hooks (.agents/hooks.json).");
33103
+ }
33104
+ function setupAgyIntegration() {
33105
+ if (!binaryOnPath("agy")) return;
33106
+ ensureAgySkillsInstalled();
33107
+ setupAgyHooks();
33108
+ }
32979
33109
  function setupClaudeHooks() {
32980
33110
  ensureGipityPlugin();
32981
33111
  const cwd = resolve3(process.cwd());
@@ -33097,6 +33227,7 @@ var SUPPORTED_TOOLS = [
33097
33227
  { key: "claude", label: "Claude Code (CLAUDE.md + Gipity plugin)", setup: setupClaudeMd, integrate: setupClaudeHooks },
33098
33228
  { key: "codex", label: "OpenAI Codex (AGENTS.md + skills + sync hooks)", setup: setupAgentsMd, integrate: setupCodexIntegration },
33099
33229
  { key: "grok", label: "Grok Build (AGENTS.md + Gipity plugin)", setup: setupAgentsMd, integrate: ensureGrokPluginInstalled },
33230
+ { key: "agy", label: "Antigravity (AGENTS.md + skills + sync hooks)", setup: setupAgentsMd, integrate: setupAgyIntegration },
33100
33231
  { key: "aider", label: "Aider (AGENTS.md + .aider.conf.yml)", setup: setupAiderMd, optIn: true },
33101
33232
  { key: "gemini", label: "Gemini CLI (GEMINI.md)", setup: setupGeminiMd },
33102
33233
  { key: "copilot", label: "GitHub Copilot (.github/copilot-instructions.md)", setup: setupCopilotMd },
@@ -33226,22 +33357,22 @@ async function acquireLock(progress) {
33226
33357
  }
33227
33358
  }
33228
33359
  }
33229
- function readBaseline(projectGuid) {
33360
+ function readBaseline(projectGuid2) {
33230
33361
  const path5 = syncStatePath();
33231
- if (!existsSync5(path5)) return { projectGuid, files: {}, lastFullSync: null };
33362
+ if (!existsSync5(path5)) return { projectGuid: projectGuid2, files: {}, lastFullSync: null };
33232
33363
  try {
33233
33364
  const parsed = JSON.parse(readFileSync7(path5, "utf-8"));
33234
- if (parsed.projectGuid !== projectGuid) {
33235
- return { projectGuid, files: {}, lastFullSync: null };
33365
+ if (parsed.projectGuid !== projectGuid2) {
33366
+ return { projectGuid: projectGuid2, files: {}, lastFullSync: null };
33236
33367
  }
33237
33368
  return {
33238
- projectGuid,
33369
+ projectGuid: projectGuid2,
33239
33370
  files: parsed.files ?? {},
33240
33371
  lastFullSync: parsed.lastFullSync ?? null,
33241
33372
  ...parsed.deletesSkippedStreak ? { deletesSkippedStreak: parsed.deletesSkippedStreak } : {}
33242
33373
  };
33243
33374
  } catch {
33244
- return { projectGuid, files: {}, lastFullSync: null };
33375
+ return { projectGuid: projectGuid2, files: {}, lastFullSync: null };
33245
33376
  }
33246
33377
  }
33247
33378
  function writeBaseline(b7) {
@@ -33314,8 +33445,8 @@ function resolveInRoot(root, relPath) {
33314
33445
  }
33315
33446
  return full;
33316
33447
  }
33317
- async function fetchRemote(projectGuid) {
33318
- const res = await get(`/projects/${projectGuid}/files/tree`);
33448
+ async function fetchRemote(projectGuid2) {
33449
+ const res = await get(`/projects/${projectGuid2}/files/tree`);
33319
33450
  const out = /* @__PURE__ */ new Map();
33320
33451
  for (const f of res.data) {
33321
33452
  if (f.type !== "file") continue;
@@ -33376,14 +33507,14 @@ function extractTarToMap(stream, idleMs, onBytes, keep) {
33376
33507
  stream.pipe(extract3);
33377
33508
  });
33378
33509
  }
33379
- async function downloadAll(projectGuid, onBytes) {
33380
- const stream = await downloadStream(`/projects/${projectGuid}/files/tree?content=tar`);
33510
+ async function downloadAll(projectGuid2, onBytes) {
33511
+ const stream = await downloadStream(`/projects/${projectGuid2}/files/tree?content=tar`);
33381
33512
  return extractTarToMap(stream, DOWNLOAD_IDLE_MS, onBytes);
33382
33513
  }
33383
- async function fetchOneOutcome(projectGuid, path5, expectedSha) {
33514
+ async function fetchOneOutcome(projectGuid2, path5, expectedSha) {
33384
33515
  try {
33385
33516
  const stream = await downloadStream(
33386
- `/projects/${projectGuid}/files/tree?content=tar&path=${encodeURIComponent(path5)}`
33517
+ `/projects/${projectGuid2}/files/tree?content=tar&path=${encodeURIComponent(path5)}`
33387
33518
  );
33388
33519
  const want = normalizeTreePath(path5);
33389
33520
  const files = await extractTarToMap(stream, DOWNLOAD_IDLE_MS, void 0, (p) => p === want);
@@ -33399,8 +33530,8 @@ async function fetchOneOutcome(projectGuid, path5, expectedSha) {
33399
33530
  return { kind: "transient" };
33400
33531
  }
33401
33532
  }
33402
- async function fetchOne(projectGuid, path5, expectedSha) {
33403
- const out = await fetchOneOutcome(projectGuid, path5, expectedSha);
33533
+ async function fetchOne(projectGuid2, path5, expectedSha) {
33534
+ const out = await fetchOneOutcome(projectGuid2, path5, expectedSha);
33404
33535
  return out.kind === "ok" ? out.buf : null;
33405
33536
  }
33406
33537
  function classifyLocal(info2, base) {
@@ -33632,14 +33763,14 @@ async function sync(opts = {}) {
33632
33763
  releaseLock();
33633
33764
  }
33634
33765
  }
33635
- async function syncInner(projectGuid, root, ignore2, opts, interactive) {
33636
- const config = { projectGuid, ignore: ignore2 };
33766
+ async function syncInner(projectGuid2, root, ignore2, opts, interactive) {
33767
+ const config = { projectGuid: projectGuid2, ignore: ignore2 };
33637
33768
  const p = opts.progress;
33638
- const baseline = readBaseline(projectGuid);
33769
+ const baseline = readBaseline(projectGuid2);
33639
33770
  p?.phase("Scanning local files...");
33640
33771
  const local = walkLocal(root, ignore2, baseline.files);
33641
33772
  p?.phase("Checking Gipity for changes...");
33642
- const remote = await fetchRemote(projectGuid);
33773
+ const remote = await fetchRemote(projectGuid2);
33643
33774
  for (const path5 of [...remote.keys()]) {
33644
33775
  if (shouldIgnore(path5, ignore2)) remote.delete(path5);
33645
33776
  }
@@ -34439,7 +34570,12 @@ function parseDuration(raw, unit) {
34439
34570
  }
34440
34571
 
34441
34572
  // src/commands/token.ts
34442
- var tokenCommand = new Command("token").description("Manage API tokens").addHelpText("after", "\nLong-lived agent API tokens (gip_at_*) for headless agents and CI.");
34573
+ var tokenCommand = new Command("token").description("Manage API tokens").addHelpText("after", `
34574
+ Long-lived agent API tokens (gip_at_*) for headless agents and CI - they drive
34575
+ the gipity CLI as YOU.
34576
+
34577
+ To let a script or cron write to one app instead, mint a project API key:
34578
+ gipity key create "my script" --role editor (sent as X-Api-Key).`);
34443
34579
  var fmtDate = (d) => d ? new Date(d).toLocaleDateString() : "never";
34444
34580
  tokenCommand.command("create").description("Mint a long-lived agent API token (shown once)").option("--name <name>", 'Label for the token, e.g. "Hermes on my VPS"').option("--expires <days>", "Days until the token expires (default: never)").option("--json", "Output as JSON").action((opts) => run("Create", async () => {
34445
34581
  const body = {};
@@ -34484,6 +34620,73 @@ tokenCommand.command("revoke <short_guid>").alias("rm").description("Revoke an a
34484
34620
  console.log(success(`Revoked token ${bold(shortGuid)}.`));
34485
34621
  }));
34486
34622
 
34623
+ // src/commands/key.ts
34624
+ init_api();
34625
+ init_config();
34626
+ init_colors();
34627
+ var keyCommand = new Command("key").description("Manage project API keys for scripts and agents (X-Api-Key)").addHelpText("after", `
34628
+ Give a script, cron, or agent write access to your app without a login:
34629
+
34630
+ gipity key create "laptop importer" --role editor
34631
+ # the script sends: X-Api-Key: <the key printed once above>
34632
+
34633
+ Inside a function the caller shows up as ctx.auth.via === 'api_key' with
34634
+ ctx.auth.apiKeyName set to the key's name - stamp that on rows to tell
34635
+ script-written entries from hand-entered ones. Never build your own key table.
34636
+
34637
+ Account-level tokens that run the CLI headlessly are a different thing: see
34638
+ gipity token --help.`);
34639
+ var fmtDate2 = (d) => d ? new Date(d).toLocaleDateString() : "never";
34640
+ var projectOpt = ["--project <guid-or-slug>", "Target a specific project instead of cwd / Home"];
34641
+ async function projectGuid(opts) {
34642
+ const { config } = await resolveProjectContext({ projectOverride: opts.project });
34643
+ return config.projectGuid;
34644
+ }
34645
+ keyCommand.command("create <name>").description("Mint a project API key (shown once). Give it a name you will recognize later").option("--role <role>", "viewer | editor | owner (default: viewer)", "viewer").option("--expires-days <n>", "Days until the key expires (default: never)").option(...projectOpt).option("--json", "Output as JSON").action((name, opts) => run("Create", async () => {
34646
+ const body = { name, role: opts.role };
34647
+ if (opts.expiresDays !== void 0) {
34648
+ const days = parseInt(opts.expiresDays, 10);
34649
+ if (!Number.isFinite(days) || days <= 0) throw new Error("--expires-days must be a positive number of days");
34650
+ body.expires_in_days = days;
34651
+ }
34652
+ const res = await post(
34653
+ `/projects/${await projectGuid(opts)}/api-keys`,
34654
+ body
34655
+ );
34656
+ const k7 = res.data;
34657
+ if (opts.json) {
34658
+ console.log(JSON.stringify(k7));
34659
+ return;
34660
+ }
34661
+ const expNote = k7.expires_at ? ` (expires ${fmtDate2(k7.expires_at)})` : " (never expires)";
34662
+ console.log(success(`Created API key ${bold(k7.short_guid)} "${k7.name}" as ${k7.role}${muted(expNote)}.`));
34663
+ console.log("");
34664
+ console.log(k7.key);
34665
+ console.log("");
34666
+ console.log(muted("Send it from your script on every request:"));
34667
+ console.log(muted(` curl -H "X-Api-Key: ${k7.key}" ...`));
34668
+ console.log(muted(`Revoke it any time: gipity key revoke ${k7.short_guid}`));
34669
+ console.log("");
34670
+ console.log(warning("Copy it now - it will not be shown again."));
34671
+ }));
34672
+ keyCommand.command("list").alias("ls").description("List this project's API keys (values are never shown again)").option(...projectOpt).option("--json", "Output as JSON").action((opts) => run("List", async () => {
34673
+ const res = await get(`/projects/${await projectGuid(opts)}/api-keys`);
34674
+ printList(
34675
+ res.data,
34676
+ opts,
34677
+ 'No API keys. Mint one with: gipity key create "my script" --role editor',
34678
+ (k7) => `${bold(k7.short_guid)} ${k7.name} ${muted(`${k7.role} ${k7.prefix}\u2026 last used ${fmtDate2(k7.last_used_at)} expires ${fmtDate2(k7.expires_at)}`)}`
34679
+ );
34680
+ }));
34681
+ keyCommand.command("revoke <short_guid>").alias("rm").description("Revoke a project API key (instant, irreversible)").option(...projectOpt).option("--json", "Output as JSON").action((shortGuid, opts) => run("Revoke", async () => {
34682
+ await del(`/projects/${await projectGuid(opts)}/api-keys/${encodeURIComponent(shortGuid)}`);
34683
+ if (opts.json) {
34684
+ console.log(JSON.stringify({ short_guid: shortGuid, revoked: true }));
34685
+ return;
34686
+ }
34687
+ console.log(success(`Revoked API key ${bold(shortGuid)}.`));
34688
+ }));
34689
+
34487
34690
  // src/commands/init.ts
34488
34691
  init_api();
34489
34692
  init_config();
@@ -34529,6 +34732,80 @@ function escapeHtml(s) {
34529
34732
  function jsEscape(s) {
34530
34733
  return s.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
34531
34734
  }
34735
+ var ACCENT_PALETTE = [
34736
+ "#f59e0b",
34737
+ "#f97316",
34738
+ "#ef4444",
34739
+ "#ec4899",
34740
+ "#a855f7",
34741
+ "#6366f1",
34742
+ "#3b82f6",
34743
+ "#0ea5e9",
34744
+ "#14b8a6",
34745
+ "#22c55e",
34746
+ "#84cc16",
34747
+ "#eab308"
34748
+ ];
34749
+ function hashString(s) {
34750
+ let h = 2166136261;
34751
+ for (let i = 0; i < s.length; i++) {
34752
+ h ^= s.charCodeAt(i);
34753
+ h = Math.imul(h, 16777619);
34754
+ }
34755
+ return h >>> 0;
34756
+ }
34757
+ function darkTint(hex) {
34758
+ const n = parseInt(hex.slice(1), 16);
34759
+ const base = 1052692;
34760
+ const ch = (shift) => {
34761
+ const a = n >> shift & 255;
34762
+ const b7 = base >> shift & 255;
34763
+ return Math.round(b7 + (a - b7) * 0.14).toString(16).padStart(2, "0");
34764
+ };
34765
+ return `#${ch(16)}${ch(8)}${ch(0)}`;
34766
+ }
34767
+ function buildHeadBlock(v7) {
34768
+ const t = escapeHtml(v7.projectName);
34769
+ const d = v7.description ? escapeHtml(v7.description) : "";
34770
+ const url = v7.accountSlug && v7.projectSlug ? `https://app.gipity.ai/${v7.accountSlug}/${v7.projectSlug}/` : void 0;
34771
+ const themeColor = darkTint(ACCENT_PALETTE[hashString(v7.projectGuid || v7.projectName) % ACCENT_PALETTE.length]);
34772
+ const jsonLd = JSON.stringify({
34773
+ "@context": "https://schema.org",
34774
+ "@type": "WebApplication",
34775
+ name: v7.projectName,
34776
+ ...v7.description ? { description: v7.description } : {},
34777
+ ...url ? { url } : {}
34778
+ }, null, 2).replace(/<\//g, "<\\/");
34779
+ const lines = [
34780
+ `<title>${t}</title>`,
34781
+ ...d ? [`<meta name="description" content="${d}">`] : [],
34782
+ ...url ? [`<link rel="canonical" href="${url}">`] : [],
34783
+ `<meta property="og:title" content="${t}">`,
34784
+ `<meta property="og:type" content="website">`,
34785
+ ...d ? [`<meta property="og:description" content="${d}">`] : [],
34786
+ ...url ? [
34787
+ `<meta property="og:url" content="${url}">`,
34788
+ `<meta property="og:image" content="${url}images/og-image.png">`,
34789
+ `<meta property="og:image:width" content="1200">`,
34790
+ `<meta property="og:image:height" content="630">`
34791
+ ] : [],
34792
+ `<meta name="twitter:card" content="summary_large_image">`,
34793
+ `<meta name="twitter:title" content="${t}">`,
34794
+ ...d ? [`<meta name="twitter:description" content="${d}">`] : [],
34795
+ ...url ? [`<meta name="twitter:image" content="${url}images/og-image.png">`] : [],
34796
+ `<meta name="theme-color" content="${themeColor}">`,
34797
+ `<link rel="icon" type="image/png" sizes="192x192" href="./images/favicon-192.png">`,
34798
+ `<link rel="icon" type="image/png" sizes="512x512" href="./images/favicon-512.png">`,
34799
+ `<link rel="icon" type="image/x-icon" href="./images/favicon.ico">`,
34800
+ `<link rel="apple-touch-icon" href="./images/apple-touch-icon.png">`,
34801
+ `<link rel="manifest" href="./manifest.webmanifest">`,
34802
+ `<script type="application/ld+json">
34803
+ ${jsonLd}
34804
+ </script>`
34805
+ ];
34806
+ return lines.map((l7) => `
34807
+ ${l7}`).join("");
34808
+ }
34532
34809
  function buildTemplateVars(v7) {
34533
34810
  const safeTitle = escapeHtml(v7.projectName);
34534
34811
  const safeDesc = v7.description ? escapeHtml(v7.description) : "";
@@ -34544,6 +34821,9 @@ function buildTemplateVars(v7) {
34544
34821
  "{{JS_TITLE}}": jsEscape(v7.projectName),
34545
34822
  "{{PROJECT_GUID}}": v7.projectGuid,
34546
34823
  "{{DATABASE}}": slug,
34824
+ "{{HEAD_BLOCK}}": buildHeadBlock(v7),
34825
+ // Legacy per-tag placeholders — current templates carry only {{HEAD_BLOCK}},
34826
+ // but older local template copies may still reference these.
34547
34827
  "{{DESCRIPTION_META}}": v7.description ? `
34548
34828
  <meta name="description" content="${safeDesc}">` : "",
34549
34829
  "{{OG_DESCRIPTION}}": v7.description ? `
@@ -34638,7 +34918,9 @@ async function finalizeLocalProject(opts) {
34638
34918
  try {
34639
34919
  const sub = await substituteDir(opts.dir, {
34640
34920
  projectGuid: opts.projectGuid,
34641
- projectName: opts.projectName
34921
+ projectName: opts.projectName,
34922
+ accountSlug: opts.accountSlug,
34923
+ projectSlug: opts.projectSlug
34642
34924
  });
34643
34925
  if (sub.changed.length) {
34644
34926
  console.log(muted(`Resolved template vars in ${sub.changed.length} file${sub.changed.length > 1 ? "s" : ""}.`));
@@ -35024,7 +35306,7 @@ function resolveTools(forFlag) {
35024
35306
  (t) => requested.includes(t.key) || !t.optIn && requested.includes("all")
35025
35307
  );
35026
35308
  }
35027
- var initCommand = new Command("init").description("Link this directory to a project").addHelpText("after", "\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity, and installs the Gipity skills + file-sync hooks into the agent CLIs found on this machine (Claude Code, Codex, Grok).").argument("[name]", "Project name/slug (defaults to current directory name)").option("--agent <guid>", "Agent GUID to use").option("--no-capture", "Don't record Claude Code sessions in this directory to your Gipity project (sets captureHooks: false in .gipity.json)").option(
35309
+ var initCommand = new Command("init").description("Link this directory to a project").addHelpText("after", "\nWrites CLAUDE.md/AGENTS.md primer files so your AI coding tool understands Gipity, and installs the Gipity skills + file-sync hooks into the agent CLIs found on this machine (Claude Code, Codex, Grok, Antigravity).").argument("[name]", "Project name/slug (defaults to current directory name)").option("--agent <guid>", "Agent GUID to use").option("--no-capture", "Don't record Claude Code sessions in this directory to your Gipity project (sets captureHooks: false in .gipity.json)").option(
35028
35310
  "--for <tools>",
35029
35311
  `Which AI tool primer files to write (comma-separated). Default: all except aider (opt-in - it also writes .aider.conf.yml). Choices: ${TOOL_KEYS.join(", ")}, all`
35030
35312
  ).addHelpText("after", `
@@ -35152,7 +35434,7 @@ Working with an existing Gipity project:
35152
35434
  }
35153
35435
  console.log(success(`Wrote primer files: ${primerSummary}.`));
35154
35436
  if (wantsClaude) {
35155
- console.log(success("Ready! Run your coding agent here (claude, codex, grok), or `gipity build` to launch one with a picker."));
35437
+ console.log(success("Ready! Run your coding agent here (claude, codex, grok, agy), or `gipity build` to launch one with a picker."));
35156
35438
  if (opts.capture === false) {
35157
35439
  console.log(muted("Session recording is off for this project (captureHooks: false in .gipity.json)."));
35158
35440
  } else {
@@ -35490,13 +35772,13 @@ Nothing named "${basename4(localPath)}" under ${root} either. Generate a frame w
35490
35772
  `${flag} ${localPath}: no such file \u2014 looked for ${abs} (relative paths resolve against the current directory, ${process.cwd()}).${found}`
35491
35773
  );
35492
35774
  }
35493
- async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
35775
+ async function uploadPublicFixture(projectGuid2, localPath, flag = "--fixture") {
35494
35776
  assertLocalAsset(flag, localPath);
35495
35777
  const name = basename4(localPath);
35496
35778
  const size = statSync6(localPath).size;
35497
35779
  const contentType = guessMime(localPath);
35498
35780
  const init = await post(
35499
- `/api/${projectGuid}/uploads/init`,
35781
+ `/api/${projectGuid2}/uploads/init`,
35500
35782
  { filename: name, content_type: contentType, size, public: true }
35501
35783
  );
35502
35784
  if (init.data.method !== "PUT" || !init.data.url) {
@@ -35511,13 +35793,13 @@ async function uploadPublicFixture(projectGuid, localPath, flag = "--fixture") {
35511
35793
  throw new Error(`upload of fixture "${name}" failed: ${res.status} ${res.statusText}`);
35512
35794
  }
35513
35795
  const done = await post(
35514
- `/api/${projectGuid}/uploads/complete`,
35796
+ `/api/${projectGuid2}/uploads/complete`,
35515
35797
  { upload_guid: init.data.upload_guid }
35516
35798
  );
35517
35799
  return { guid: done.data.guid, url: done.data.url, name, localPath };
35518
35800
  }
35519
- async function deleteFixture(projectGuid, guid) {
35520
- await del(`/api/${projectGuid}/uploads/${guid}`);
35801
+ async function deleteFixture(projectGuid2, guid) {
35802
+ await del(`/api/${projectGuid2}/uploads/${guid}`);
35521
35803
  }
35522
35804
  var CAMERA_EXTS = [".png", ".jpg", ".jpeg", ".webp", ".mp4", ".webm", ".y4m", ".mjpeg"];
35523
35805
  function assertCameraFile(localPath) {
@@ -35530,9 +35812,9 @@ A still image is played as a looping single-frame feed, which is what a gesture/
35530
35812
  No frame to hand? Generate one: gipity generate image "a hand making a closed fist, palm to camera, plain background"`
35531
35813
  );
35532
35814
  }
35533
- async function uploadCameraFeed(projectGuid, localPath) {
35815
+ async function uploadCameraFeed(projectGuid2, localPath) {
35534
35816
  assertCameraFile(localPath);
35535
- return uploadPublicFixture(projectGuid, localPath, "--camera");
35817
+ return uploadPublicFixture(projectGuid2, localPath, "--camera");
35536
35818
  }
35537
35819
 
35538
35820
  // src/commands/page-eval.ts
@@ -35655,7 +35937,15 @@ function budgetOverrunHint(reason, usedBudgetMs) {
35655
35937
  }
35656
35938
  return `That budget was ${Math.round(usedBudgetMs / 1e3)}s. Raise it with --timeout <ms> (up to ${EVAL_SCRIPT_BUDGET_MAX_MS}), e.g. --timeout ${EVAL_SCRIPT_BUDGET_MAX_MS} \u2014 or gate on a ready signal instead: --wait-for '<selector>' --wait-timeout ${MAX_WAIT_MS}.`;
35657
35939
  }
35940
+ function navigationAbortHint(reason) {
35941
+ if (!/navigated or reloaded/i.test(reason)) return null;
35942
+ return `If you were verifying that state SURVIVES a reload, that is what --reload is for (one command, one page): gipity page eval "<url>" '<seed/assert expr>' --reload '<assert-restored expr>'. It reloads the page in place (localStorage/sessionStorage/cookies preserved) and reports the second result separately. Splitting into two 'page eval' calls does NOT do this: each call is its own browser session, so the second one starts from empty storage and never exercises the restore path.`;
35943
+ }
35944
+ function stepsDeterministically(script) {
35945
+ return /\badvance\s*\(/.test(script);
35946
+ }
35658
35947
  function slowRenderMessage(fps, o) {
35948
+ if (!o.camera && stepsDeterministically(o.script ?? "")) return null;
35659
35949
  if (o.camera) {
35660
35950
  const frames = Math.max(1, Math.round(fps * (o.waitMs / 1e3)));
35661
35951
  return `${warning("\u26A0 Slow render:")} page painted at ${fps} fps, so the app's vision pipeline ran on roughly ${bold(`${frames} frame${frames === 1 ? "" : "s"}`)} during the ${Math.round(o.waitMs / 1e3)}s before this eval (it infers once per painted frame). ${bold("That is enough:")} --camera loops your still image, so every one of those frames is the SAME pixels and the model returns the SAME answer on each \u2014 re-running with a bigger --wait/--timeout cannot change the result. ${bold("Do not escalate the wait.")} If a detection landed, it is real. If nothing was detected, the suspect is the ${bold("frame")} (a model needs the whole subject in shot \u2014 a tight crop, an odd angle or a busy background reads as nothing) or the ${bold("app")} (frames never reach the model) \u2014 never the frame rate. Settle it in ONE run: ${CAMERA_FRAME_CHECK}`;
@@ -35713,7 +36003,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
35713
36003
  []
35714
36004
  ).option(
35715
36005
  "--reload <expr>",
35716
- "After the first eval, reload the page IN PLACE (localStorage/sessionStorage/cookies preserved) and evaluate this second expression against the post-reload DOM. One command verifies persisted state survives a reload: seed/assert state with <expr>, then assert the restored UI here."
36006
+ 'After the first eval, reload the page IN PLACE (localStorage/sessionStorage/cookies preserved) and evaluate this second expression against the post-reload DOM. One command verifies persisted state survives a reload ("remember it when I come back"): seed/assert state with <expr>, then assert the restored UI here. This is the ONLY way to check that - reloading inside the body aborts the eval (the result is lost with the old page), and two separate `page eval` calls are two fresh browser profiles, so the second starts from empty storage.'
35717
36007
  ).option("--reload-file <path>", "Read the post-reload expression from a file instead of inline --reload (mutually exclusive)").option(
35718
36008
  "--camera <path>",
35719
36009
  `Play a local image or video (.png/.jpg/.webp/.mp4/.webm/.y4m/.mjpeg) as the browser's WEBCAM feed, so a camera app's real pipeline (getUserMedia \u2192 MediaPipe/YOLOX \u2192 your app logic) runs headlessly on a frame you choose. Implies --fake-media and waits ${CAMERA_DEFAULT_WAIT_MS / 1e3}s for the vision model to load. No frame handy? gipity generate image "a hand making a closed fist, palm to camera".`
@@ -35797,23 +36087,23 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
35797
36087
  const fixturePaths = opts.fixture ?? [];
35798
36088
  const hosted = [];
35799
36089
  let camera;
35800
- let projectGuid;
36090
+ let projectGuid2;
35801
36091
  let sentExpr = expr;
35802
36092
  let sentSteps = steps;
35803
36093
  if (opts.camera) assertCameraFile(opts.camera);
35804
36094
  try {
35805
36095
  if (fixturePaths.length || opts.camera) {
35806
36096
  const { config } = await resolveProjectContext({});
35807
- projectGuid = config.projectGuid;
36097
+ projectGuid2 = config.projectGuid;
35808
36098
  }
35809
36099
  if (opts.camera) {
35810
36100
  console.log(muted(`Hosting camera feed ${opts.camera}...`));
35811
- camera = await uploadCameraFeed(projectGuid, opts.camera);
36101
+ camera = await uploadCameraFeed(projectGuid2, opts.camera);
35812
36102
  }
35813
36103
  if (fixturePaths.length) {
35814
36104
  for (const p of fixturePaths) {
35815
36105
  console.log(muted(`Hosting fixture ${p}...`));
35816
- hosted.push(await uploadPublicFixture(projectGuid, p));
36106
+ hosted.push(await uploadPublicFixture(projectGuid2, p));
35817
36107
  }
35818
36108
  const map = {};
35819
36109
  for (const h of hosted) map[h.name] = h.url;
@@ -35851,7 +36141,7 @@ var pageEvalCommand = new Command("eval").description("Evaluate JS in a real bro
35851
36141
  }));
35852
36142
  } catch (err) {
35853
36143
  const msg = err?.message ?? "";
35854
- const hint = budgetOverrunHint(msg, scriptBudgetMs);
36144
+ const hint = budgetOverrunHint(msg, scriptBudgetMs) ?? (reloadExpr === void 0 ? navigationAbortHint(msg) : null);
35855
36145
  if (!hint) throw err;
35856
36146
  throw new Error(`${msg}
35857
36147
  ${hint}`);
@@ -35883,7 +36173,12 @@ ${hint}`);
35883
36173
  console.log(`${warning("\u26A0 Navigation incomplete:")} ${d.note || "page did not reach full load"}`);
35884
36174
  }
35885
36175
  if (d.slowRender) {
35886
- console.log(slowRenderMessage(d.slowRender.fps, { camera: !!camera, waitMs }));
36176
+ const slow = slowRenderMessage(d.slowRender.fps, {
36177
+ camera: !!camera,
36178
+ waitMs,
36179
+ script: [expr, ...steps, reloadExpr ?? ""].join("\n")
36180
+ });
36181
+ if (slow) console.log(slow);
35887
36182
  }
35888
36183
  if (d.auth?.requested) {
35889
36184
  const who = getAuth()?.email;
@@ -35918,7 +36213,7 @@ ${bold("After reload")} ${muted("(page reloaded in place \u2014 storage preserve
35918
36213
  } finally {
35919
36214
  for (const h of [...hosted, ...camera ? [camera] : []]) {
35920
36215
  try {
35921
- await deleteFixture(projectGuid, h.guid);
36216
+ await deleteFixture(projectGuid2, h.guid);
35922
36217
  } catch (err) {
35923
36218
  console.error(warning(`\u26A0 Could not auto-delete fixture "${h.name}" (${h.guid}) \u2014 still hosted at ${h.url}: ${err.message}`));
35924
36219
  }
@@ -36217,14 +36512,14 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
36217
36512
  const ts2 = timestampSlug();
36218
36513
  const shotName = (vp2) => defaultFilename(slug, ts2, vp2 && userSpecifiedViewports ? dimSuffix(vp2) : void 0);
36219
36514
  const names = userSpecifiedViewports ? customViewports.map(shotName) : [shotName()];
36220
- const projectGuid = getConfig()?.projectGuid;
36221
- const save = !opts.ephemeral && projectGuid ? { project_guid: projectGuid, names } : void 0;
36515
+ const projectGuid2 = getConfig()?.projectGuid;
36516
+ const save = !opts.ephemeral && projectGuid2 ? { project_guid: projectGuid2, names } : void 0;
36222
36517
  let camera;
36223
36518
  if (opts.camera) {
36224
36519
  assertCameraFile(opts.camera);
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.");
36520
+ if (!projectGuid2) throw new Error("--camera needs a linked project (the frame is hosted for the browser to fetch) \u2014 run `gipity link` first.");
36226
36521
  console.log(muted(`Hosting camera feed ${opts.camera}...`));
36227
- camera = await uploadCameraFeed(projectGuid, opts.camera);
36522
+ camera = await uploadCameraFeed(projectGuid2, opts.camera);
36228
36523
  }
36229
36524
  const preCapture = [
36230
36525
  opts.waitFor ? buildWaitForGate(opts.waitFor, waitForTimeoutMs) : "",
@@ -36255,7 +36550,7 @@ var pageScreenshotCommand = new Command("screenshot").description("Screenshot a
36255
36550
  } finally {
36256
36551
  if (camera) {
36257
36552
  try {
36258
- await deleteFixture(projectGuid, camera.guid);
36553
+ await deleteFixture(projectGuid2, camera.guid);
36259
36554
  } catch (err) {
36260
36555
  console.error(warning(`\u26A0 Could not auto-delete camera feed "${camera.name}" (${camera.guid}) \u2014 still hosted at ${camera.url}: ${err.message}`));
36261
36556
  }
@@ -36757,10 +37052,10 @@ dbCommand.command("list").description("List databases").option("--all", "List da
36757
37052
  grouped.get(key).push(db2);
36758
37053
  }
36759
37054
  let first = true;
36760
- for (const [projectGuid, dbs] of grouped) {
37055
+ for (const [projectGuid2, dbs] of grouped) {
36761
37056
  if (!first) console.log("");
36762
37057
  first = false;
36763
- const label2 = dbs[0].projectSlug || dbs[0].projectName || projectGuid;
37058
+ const label2 = dbs[0].projectSlug || dbs[0].projectName || projectGuid2;
36764
37059
  console.log(label2);
36765
37060
  for (const db2 of dbs) {
36766
37061
  console.log(`${db2.friendlyName}`);
@@ -39240,11 +39535,53 @@ var grokAdapter = {
39240
39535
  installHint: "curl -fsSL https://grok.x.ai/install.sh | sh (or see docs.x.ai/grok-build)"
39241
39536
  };
39242
39537
 
39538
+ // src/agents/agy.ts
39539
+ var MODEL_ID_TO_AGY_DISPLAY = {
39540
+ "gemini-3.5-flash": "Gemini 3.5 Flash (Medium)",
39541
+ "gemini-3.1-pro-preview": "Gemini 3.1 Pro (High)"
39542
+ };
39543
+ function translateModel(model) {
39544
+ return MODEL_ID_TO_AGY_DISPLAY[model] ?? model;
39545
+ }
39546
+ function projectArgs(resume) {
39547
+ return resume ? ["--conversation", resume] : ["--new-project"];
39548
+ }
39549
+ var agyAdapter = {
39550
+ key: "agy",
39551
+ source: "agy",
39552
+ displayName: "Antigravity",
39553
+ providerName: "Google",
39554
+ binary: "agy",
39555
+ buildInteractiveArgs({ resume, model }) {
39556
+ const args = [...projectArgs(resume)];
39557
+ if (model) args.push("--model", translateModel(model));
39558
+ return args;
39559
+ },
39560
+ buildHeadlessArgs({ message, resume, model, bypassApprovals }) {
39561
+ const args = ["-p", message, ...projectArgs(resume)];
39562
+ if (model) args.push("--model", translateModel(model));
39563
+ if (bypassApprovals) args.push("--dangerously-skip-permissions");
39564
+ return args;
39565
+ },
39566
+ sessionIdFromStreamEvent() {
39567
+ return null;
39568
+ },
39569
+ hooksSupportedOnPlatform(platform2) {
39570
+ return platform2 !== "win32";
39571
+ },
39572
+ // No stream-json ingest mapper for agy: hook capture carries the full
39573
+ // conversationId on every event (confirmed live, including headless -p),
39574
+ // so the daemon doesn't need to parse agy's stdout at all.
39575
+ daemonStreamCapture: false,
39576
+ installHint: "see https://antigravity.google (Google account sign-in required)"
39577
+ };
39578
+
39243
39579
  // src/agents/index.ts
39244
39580
  var AGENT_ADAPTERS = [
39245
39581
  claudeCodeAdapter,
39246
39582
  codexAdapter,
39247
- grokAdapter
39583
+ grokAdapter,
39584
+ agyAdapter
39248
39585
  ];
39249
39586
  var AGENT_KEYS = AGENT_ADAPTERS.map((a) => a.key);
39250
39587
  function getAdapter(key) {
@@ -39316,10 +39653,10 @@ function reportSyncResult(result) {
39316
39653
  console.log(` ${muted("Nothing was deleted. Re-run `gipity sync` to finish the pull.")}`);
39317
39654
  }
39318
39655
  }
39319
- async function fetchProjectStats(projectGuid, cwd) {
39320
- if (projectGuid) {
39656
+ async function fetchProjectStats(projectGuid2, cwd) {
39657
+ if (projectGuid2) {
39321
39658
  try {
39322
- const res = await get(`/projects/${encodeURIComponent(projectGuid)}/files/stats`);
39659
+ const res = await get(`/projects/${encodeURIComponent(projectGuid2)}/files/stats`);
39323
39660
  const d = res.data;
39324
39661
  const topLevelNames = d.top_level.map((e) => e.type === "directory" ? `${e.name}/` : e.name);
39325
39662
  return {
@@ -39913,7 +40250,7 @@ async function pickAgent(lastUsed) {
39913
40250
  AGENT_ADAPTERS.forEach((a, i) => {
39914
40251
  const notes = [];
39915
40252
  if (a.key === lastUsed) notes.push("last used");
39916
- if (a.key === "codex" && !a.hooksSupportedOnPlatform(process.platform)) {
40253
+ if ((a.key === "codex" || a.key === "agy") && !a.hooksSupportedOnPlatform(process.platform)) {
39917
40254
  notes.push("session recording unavailable on Windows");
39918
40255
  }
39919
40256
  const note = notes.length ? ` ${muted(`(${notes.join(", ")})`)}` : "";
@@ -40490,6 +40827,87 @@ Pulled ${syncResult.applied} files to local.`);
40490
40827
  }
40491
40828
  }));
40492
40829
 
40830
+ // src/commands/brand.ts
40831
+ init_api();
40832
+ init_config();
40833
+ init_colors();
40834
+ function printBrand(d) {
40835
+ console.log(`${bold("Glyph:")} ${d.resolved.glyph}${d.resolved.isEmoji ? muted(" (emoji)") : ""}`);
40836
+ console.log(`${bold("Accent:")} ${d.resolved.accent}${d.branding.primary_color ? "" : muted(" (derived from project)")}`);
40837
+ if (d.branding.app_name) console.log(`${bold("Name:")} ${d.branding.app_name}`);
40838
+ if (d.branding.tagline) console.log(`${bold("Tagline:")} ${d.branding.tagline}`);
40839
+ }
40840
+ var DEPLOY_HINT = "Deploy to publish the new assets: gipity deploy dev";
40841
+ var brandCommand = new Command("brand").description("The app's generated icons + share card (favicons, iOS icon, og-image)").addHelpText("after", `
40842
+ Examples:
40843
+ gipity brand Show the current brand
40844
+ gipity brand set --emoji \u{1F98D} Emoji icon + share card, regenerate all assets
40845
+ gipity brand set --glyph R --color "#3b82f6" Letter glyph on a blue accent
40846
+ gipity brand set --tagline "Bananas at 60fps" Subtitle on the share card
40847
+ gipity brand apply Re-render assets from the stored brand
40848
+ gipity brand apply --fix-head Migrate an older app: assets + splice the
40849
+ share/SEO tags into src/index.html
40850
+
40851
+ Assets are generated server-side into src/images/ (+ src/manifest.webmanifest)
40852
+ and are versioned like any other file - rollback restores previous ones.`);
40853
+ brandCommand.command("show", { isDefault: true }).description("Show the stored brand and the resolved render spec").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Brand", async () => {
40854
+ const { config } = await resolveProjectContext({ projectOverride: opts.project });
40855
+ const res = await get(`/projects/${config.projectGuid}/brand`);
40856
+ if (opts.json) {
40857
+ console.log(JSON.stringify(res.data));
40858
+ return;
40859
+ }
40860
+ printBrand(res.data);
40861
+ console.log(muted(`
40862
+ Assets: ${(res.data.assets ?? []).join(", ")}`));
40863
+ console.log(muted(`Change it: gipity brand set --emoji \u{1F3A8} | --glyph A | --color "#RRGGBB" | --tagline "..."`));
40864
+ }));
40865
+ brandCommand.command("set").description("Change brand fields and regenerate all assets").option("--emoji <emoji>", "Emoji glyph for the icon + share card (e.g. \u{1F98D})").option("--glyph <char>", "Letter/digit glyph (alias of --emoji, any single character)").option("--color <hex>", "Accent color, #RRGGBB").option("--name <name>", "Display name override (og/share title stays the page title)").option("--tagline <text>", "One-line subtitle on the share card (max 120 chars)").option("--fix-head", "Also splice the share/SEO head block into src/index.html (for apps installed before templates shipped it)").option("--no-regenerate", "Store the fields without re-rendering assets").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Brand", async () => {
40866
+ const { config } = await resolveProjectContext({ projectOverride: opts.project });
40867
+ const body = {};
40868
+ const glyph = opts.emoji ?? opts.glyph;
40869
+ if (glyph) body.icon_glyph = glyph;
40870
+ if (opts.color) body.primary_color = opts.color;
40871
+ if (opts.name) body.app_name = opts.name;
40872
+ if (opts.tagline) body.tagline = opts.tagline;
40873
+ if (opts.regenerate === false) body.regenerate = false;
40874
+ if (opts.fixHead) body.fix_head = true;
40875
+ if (!glyph && !opts.color && !opts.name && !opts.tagline) {
40876
+ console.error("Nothing to set - pass --emoji, --glyph, --color, --name, or --tagline. (gipity brand set --help; use gipity brand apply to just re-render)");
40877
+ process.exitCode = 1;
40878
+ return;
40879
+ }
40880
+ const res = await post(`/projects/${config.projectGuid}/brand`, body);
40881
+ if (opts.json) {
40882
+ console.log(JSON.stringify(res.data));
40883
+ return;
40884
+ }
40885
+ printBrand(res.data);
40886
+ reportFiles(res.data.files ?? []);
40887
+ }));
40888
+ brandCommand.command("apply").description("Re-render all brand assets from the stored brand (no field changes)").option("--fix-head", "Also splice the share/SEO head block into src/index.html (for apps installed before templates shipped it)").option("--project <guid-or-slug>", "Target a specific project instead of cwd / Home").option("--json", "Output raw JSON").action((opts) => run("Brand", async () => {
40889
+ const { config } = await resolveProjectContext({ projectOverride: opts.project });
40890
+ const body = opts.fixHead ? { fix_head: true } : {};
40891
+ const res = await post(`/projects/${config.projectGuid}/brand`, body);
40892
+ if (opts.json) {
40893
+ console.log(JSON.stringify(res.data));
40894
+ return;
40895
+ }
40896
+ reportFiles(res.data.files ?? []);
40897
+ }));
40898
+ function reportFiles(files) {
40899
+ if (!files.length) {
40900
+ console.log(muted("\nStored (assets not regenerated - run gipity brand apply)."));
40901
+ return;
40902
+ }
40903
+ console.log(success(`
40904
+ \u2713 Regenerated ${files.length} asset${files.length === 1 ? "" : "s"}.`));
40905
+ if (files.includes("src/index.html")) {
40906
+ console.log(success("\u2713 src/index.html head updated (page title/description preserved)."));
40907
+ }
40908
+ console.log(muted(` ${DEPLOY_HINT}`));
40909
+ }
40910
+
40493
40911
  // src/commands/remove.ts
40494
40912
  init_api();
40495
40913
  init_config();
@@ -41262,10 +41680,31 @@ pageCommand.action(() => {
41262
41680
 
41263
41681
  // src/commands/records.ts
41264
41682
  init_api();
41683
+ init_auth();
41265
41684
  init_config();
41266
41685
  init_colors();
41267
41686
  init_utils();
41268
41687
  var recordsCommand = new Command("records").description("Manage app records (Gipity Records - validated CRUD with audit history)");
41688
+ var ANON_FLAG = "--anon";
41689
+ var ANON_HELP = "Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account";
41690
+ async function recordsHttp(opts) {
41691
+ const config = requireConfig();
41692
+ if (!opts.anon) {
41693
+ const who = getAuth()?.email;
41694
+ console.error(muted(`Auth: calling as ${who ?? "your signed-in account"} (the owner persona; use ${ANON_FLAG} for the public visitor path)`));
41695
+ return { guid: config.projectGuid, get, post, put, patch, del };
41696
+ }
41697
+ console.error(muted("Auth: anonymous visitor (the public path a signed-out user hits)"));
41698
+ const headers = await mintAppToken(config.projectGuid);
41699
+ return {
41700
+ guid: config.projectGuid,
41701
+ get: (path5) => publicRequest("GET", path5, void 0, headers),
41702
+ post: (path5, body) => publicRequest("POST", path5, body, headers),
41703
+ put: (path5, body) => publicRequest("PUT", path5, body, headers),
41704
+ patch: (path5, body) => publicRequest("PATCH", path5, body, headers),
41705
+ del: (path5, body) => publicRequest("DELETE", path5, body, headers)
41706
+ };
41707
+ }
41269
41708
  recordsCommand.command("list").description("List record tables").option("--json", "Output as JSON").action((opts) => run("List", async () => {
41270
41709
  const config = requireConfig();
41271
41710
  const res = await get(`/api/${config.projectGuid}/records-config`);
@@ -41297,16 +41736,16 @@ recordsCommand.command("config <table>").description("Show or set a table's Reco
41297
41736
  res.data
41298
41737
  );
41299
41738
  }));
41300
- recordsCommand.command("query <table>").description("List records").option("--filter <filter>", 'Filter string (e.g., "status:eq:active")').option("--sort <sort>", 'Sort string (e.g., "created_at:desc")').option("--limit <n>", "Max rows", "20").option("--offset <n>", "Offset", "0").option("--fields <fields>", "Comma-separated column names").option("--json", "Output as JSON").action((table, opts) => run("Query", async () => {
41301
- const config = requireConfig();
41739
+ recordsCommand.command("query <table>").description("List records").option("--filter <filter>", 'Filter string (e.g., "status:eq:active")').option("--sort <sort>", 'Sort string (e.g., "created_at:desc")').option("--limit <n>", "Max rows", "20").option("--offset <n>", "Offset", "0").option("--fields <fields>", "Comma-separated column names").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, opts) => run("Query", async () => {
41740
+ const api = await recordsHttp(opts);
41302
41741
  const params = new URLSearchParams();
41303
41742
  if (opts.filter) params.set("filter", opts.filter);
41304
41743
  if (opts.sort) params.set("sort", opts.sort);
41305
41744
  params.set("limit", opts.limit);
41306
41745
  params.set("offset", opts.offset);
41307
41746
  if (opts.fields) params.set("fields", opts.fields);
41308
- const res = await get(
41309
- `/api/${config.projectGuid}/records/${table}?${params}`
41747
+ const res = await api.get(
41748
+ `/api/${api.guid}/records/${table}?${params}`
41310
41749
  );
41311
41750
  if (opts.json) {
41312
41751
  console.log(JSON.stringify(res));
@@ -41319,41 +41758,42 @@ recordsCommand.command("query <table>").description("List records").option("--fi
41319
41758
  console.log("");
41320
41759
  }
41321
41760
  }));
41322
- recordsCommand.command("get <table> <id>").description("Get a record").option("--json", "Output as JSON").action((table, id2, opts) => run("Get", async () => {
41323
- const config = requireConfig();
41324
- const res = await get(`/api/${config.projectGuid}/records/${table}/${id2}`);
41761
+ recordsCommand.command("get <table> <id>").description("Get a record").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("Get", async () => {
41762
+ const api = await recordsHttp(opts);
41763
+ const res = await api.get(`/api/${api.guid}/records/${table}/${id2}`);
41325
41764
  console.log(opts.json ? JSON.stringify(res.data) : JSON.stringify(res.data, null, 2));
41326
41765
  }));
41327
- recordsCommand.command("history <table> <id>").description("Audit history for a record (who/what changed it, with English summaries)").option("--limit <n>", "Max events", "20").option("--json", "Output as JSON").action((table, id2, opts) => run("History", async () => {
41328
- const config = requireConfig();
41329
- const res = await get(
41330
- `/api/${config.projectGuid}/records/${table}/${id2}/history?limit=${encodeURIComponent(opts.limit)}`
41766
+ recordsCommand.command("history <table> [id]").description("Audit history (who/what changed it, with English summaries). Omit <id> for the whole table's feed.").option("--limit <n>", "Max events", "20").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("History", async () => {
41767
+ const api = await recordsHttp(opts);
41768
+ const scope = id2 ? `${table}/${encodeURIComponent(id2)}` : table;
41769
+ const res = await api.get(
41770
+ `/api/${api.guid}/records/${scope}/history?limit=${encodeURIComponent(opts.limit)}`
41331
41771
  );
41332
- printList(res.data, opts, "No history for this record.", (e) => {
41772
+ printList(res.data, opts, id2 ? "No history for this record." : `No history for "${table}".`, (e) => {
41333
41773
  const summary = e.detail?.summary || `${e.action} ${e.entity_type} ${e.entity_id}`;
41334
41774
  return `${muted(e.created_at)} ${bold(e.source || "-")} ${summary}`;
41335
41775
  });
41336
41776
  }));
41337
- recordsCommand.command("create <table>").description("Create a record").requiredOption("--data <json>", "JSON object with field values").option("--json", "Output as JSON").action((table, opts) => run("Create", async () => {
41338
- const config = requireConfig();
41777
+ recordsCommand.command("create <table>").description("Create a record").requiredOption("--data <json>", "JSON object with field values").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, opts) => run("Create", async () => {
41778
+ const api = await recordsHttp(opts);
41339
41779
  const data = JSON.parse(opts.data);
41340
- const res = await post(`/api/${config.projectGuid}/records/${table}`, data);
41780
+ const res = await api.post(`/api/${api.guid}/records/${table}`, data);
41341
41781
  printResult(`Created: ${JSON.stringify(res.data)}`, opts, res.data);
41342
41782
  }));
41343
- recordsCommand.command("update <table> <id>").description("Update a record").requiredOption("--data <json>", "JSON object with fields to update").option("--json", "Output as JSON").action((table, id2, opts) => run("Update", async () => {
41344
- const config = requireConfig();
41783
+ recordsCommand.command("update <table> <id>").description("Update a record").requiredOption("--data <json>", "JSON object with fields to update").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("Update", async () => {
41784
+ const api = await recordsHttp(opts);
41345
41785
  const data = JSON.parse(opts.data);
41346
- const res = await put(`/api/${config.projectGuid}/records/${table}/${id2}`, data);
41786
+ const res = await api.put(`/api/${api.guid}/records/${table}/${id2}`, data);
41347
41787
  printResult(`Updated: ${JSON.stringify(res.data)}`, opts, res.data);
41348
41788
  }));
41349
- recordsCommand.command("delete <table> <id>").description("Delete a record").action((table, id2) => run("Delete", async () => {
41789
+ recordsCommand.command("delete <table> <id>").description("Delete a record").option(ANON_FLAG, ANON_HELP).option("--json", "Output as JSON").action((table, id2, opts) => run("Delete", async () => {
41350
41790
  if (!await confirm(`Delete record ${id2} from "${table}"?`)) {
41351
- console.log("Cancelled.");
41791
+ printResult("Cancelled.", opts, { table, id: id2, deleted: false, cancelled: true });
41352
41792
  return;
41353
41793
  }
41354
- const config = requireConfig();
41355
- await del(`/api/${config.projectGuid}/records/${table}/${id2}`);
41356
- printResult("Deleted.", { json: false });
41794
+ const api = await recordsHttp(opts);
41795
+ const res = await api.del(`/api/${api.guid}/records/${table}/${id2}`);
41796
+ printResult("Deleted.", opts, res?.data ?? { table, id: id2, deleted: true });
41357
41797
  }));
41358
41798
 
41359
41799
  // src/commands/fn.ts
@@ -41398,17 +41838,11 @@ ${error(`error: ${log3.error_message}`)}`;
41398
41838
  return line;
41399
41839
  });
41400
41840
  }));
41401
- async function callAnon(projectGuid, name, body) {
41402
- let appToken;
41403
- try {
41404
- const minted = await publicPost("/api/token", { app: projectGuid });
41405
- appToken = minted.data.token;
41406
- } catch {
41407
- }
41841
+ async function callAnon(projectGuid2, name, body) {
41408
41842
  return publicPost(
41409
- `/api/${projectGuid}/fn/${encodeURIComponent(name)}`,
41843
+ `/api/${projectGuid2}/fn/${encodeURIComponent(name)}`,
41410
41844
  body,
41411
- appToken ? { "X-App-Token": appToken } : void 0
41845
+ await mintAppToken(projectGuid2)
41412
41846
  );
41413
41847
  }
41414
41848
  fnCommand.command("call <name> [body]").description("Call a function").option("-d, --data <json>", "JSON request body: inline JSON, @file to read a file, or @- / - for stdin").option("--file <field=@path>", "Attach a file as { data, media_type } under <field> (the vision/media service shape), repeatable, e.g. --file image=@receipt.png", (v7, acc) => (acc || []).concat(v7)).option("--anon", "Call as an anonymous visitor (the public path a signed-out user hits) instead of as your signed-in account").option("--field <path>", "Print only this field of the result (dot path, e.g. items.0.short_guid)").option("--json", "Output as JSON").action((name, bodyArg, opts) => run("Call", async () => {
@@ -42845,7 +43279,7 @@ function undeployedFunctionsFromFailures(results) {
42845
43279
  return [...names];
42846
43280
  }
42847
43281
  var LONG_RUN_MS = 6e4;
42848
- async function pollTestStatus(projectGuid, runGuid, opts) {
43282
+ async function pollTestStatus(projectGuid2, runGuid, opts) {
42849
43283
  const startTime = Date.now();
42850
43284
  const getPollInterval = () => {
42851
43285
  const elapsed = Date.now() - startTime;
@@ -42867,7 +43301,7 @@ async function pollTestStatus(projectGuid, runGuid, opts) {
42867
43301
  if (Date.now() - startTime > POLL_HARD_CAP_MS) {
42868
43302
  throw new Error(`Test run ${runGuid} still not finished after ${Math.round(POLL_HARD_CAP_MS / 6e4)} minutes - giving up on the poll. Check it later with \`gipity test status ${runGuid}\` or re-run.`);
42869
43303
  }
42870
- const res = await get(`/projects/${projectGuid}/test/status/${runGuid}`);
43304
+ const res = await get(`/projects/${projectGuid2}/test/status/${runGuid}`);
42871
43305
  const data = res.data;
42872
43306
  if (!opts.json && data.results.length > lastResultCount) {
42873
43307
  const newResults = data.results.slice(lastResultCount);
@@ -44388,10 +44822,11 @@ async function collectDiagnostics() {
44388
44822
  return [];
44389
44823
  }
44390
44824
  })();
44391
- const [claude, codex, grok, cursor, gpu] = await Promise.all([
44825
+ const [claude, codex, grok, agy, cursor, gpu] = await Promise.all([
44392
44826
  probeVersion("claude"),
44393
44827
  probeVersion("codex"),
44394
44828
  probeVersion("grok"),
44829
+ probeVersion("agy"),
44395
44830
  probeVersion("cursor"),
44396
44831
  detectGpu()
44397
44832
  ]);
@@ -44399,6 +44834,7 @@ async function collectDiagnostics() {
44399
44834
  if (claude) agents.claude_code = claude;
44400
44835
  if (codex) agents.codex = codex;
44401
44836
  if (grok) agents.grok = grok;
44837
+ if (agy) agents.agy = agy;
44402
44838
  if (cursor) agents.cursor = cursor;
44403
44839
  return {
44404
44840
  collected_at: (/* @__PURE__ */ new Date()).toISOString(),
@@ -46619,6 +47055,16 @@ var uninstallCommand = new Command("uninstall").description("Uninstall Gipity").
46619
47055
  }
46620
47056
  console.log(`${success(`Removed ${agentSkills.skills.length} Gipity skills from ~/.agents/skills.`)}`);
46621
47057
  }
47058
+ const agySkills = agySkillsState();
47059
+ if (agySkills.skills.length) {
47060
+ for (const name of agySkills.skills) {
47061
+ try {
47062
+ rmSync4(join25(AGY_SKILLS_DIR, name), { recursive: true, force: true });
47063
+ } catch {
47064
+ }
47065
+ }
47066
+ console.log(`${success(`Removed ${agySkills.skills.length} Gipity skills from ~/.gemini/config/skills.`)}`);
47067
+ }
46622
47068
  if (existsSync23(gipityDir)) {
46623
47069
  try {
46624
47070
  rmSync4(gipityDir, { recursive: true, force: true });
@@ -47238,8 +47684,8 @@ function configureHelp(cmd) {
47238
47684
  var program2 = new Command();
47239
47685
  program2.enablePositionalOptions();
47240
47686
  var startGroup = [statusCommand, initCommand, skillCommand, projectCommand];
47241
- var buildGroup = [addCommand, removeCommand, saveCommand, loadCommand, deployCommand, pageCommand, testCommand];
47242
- var backendGroup = [dbCommand, fnCommand, secretsCommand, logsCommand, jobCommand, workflowCommand];
47687
+ var buildGroup = [addCommand, removeCommand, brandCommand, saveCommand, loadCommand, deployCommand, pageCommand, testCommand];
47688
+ var backendGroup = [dbCommand, fnCommand, secretsCommand, keyCommand, logsCommand, jobCommand, workflowCommand];
47243
47689
  var servicesGroup = [serviceCommand, generateCommand, notifyCommand, paymentsCommand, realtimeCommand, recordsCommand, rbacCommand, auditCommand, domainCommand, tokenCommand];
47244
47690
  var filesGroup = [syncCommand, fileCommand, pushCommand, uploadCommand, storageCommand];
47245
47691
  var gipGroup = [chatCommand, memoryCommand, agentCommand, approvalCommand, gmailCommand];
@@ -47256,6 +47702,7 @@ var HELP_SECTIONS = [
47256
47702
  { title: "Connect & setup", cmds: connectGroup }
47257
47703
  ];
47258
47704
  var SKILL_DOCS = {
47705
+ brand: "web-app-basics",
47259
47706
  save: "app-import",
47260
47707
  load: "app-import",
47261
47708
  github: "app-import",
@@ -47268,6 +47715,7 @@ var SKILL_DOCS = {
47268
47715
  notify: "app-notify",
47269
47716
  payments: "app-payments",
47270
47717
  records: "app-records",
47718
+ key: "app-auth",
47271
47719
  realtime: "app-realtime",
47272
47720
  job: "jobs",
47273
47721
  sandbox: "sandbox-tools",