gipity 1.0.429 → 1.1.1

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/setup.js CHANGED
@@ -2,10 +2,11 @@
2
2
  * Shared project setup helpers used by both `init` and `claude`.
3
3
  */
4
4
  import { resolve, join, dirname } from 'path';
5
- import { homedir } from 'os';
6
- import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
5
+ import { homedir, tmpdir } from 'os';
6
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, mkdtempSync, rmSync, cpSync, readdirSync } from 'fs';
7
7
  import { resolveCommand, spawnSyncCommand } from './platform.js';
8
8
  import { SKILLS_CONTENT, BUILD_VS_NON_BUILD_RULE, DEFINITION_OF_DONE } from './knowledge.js';
9
+ import { DEFAULT_API_BASE, resolveApiBase } from './config.js';
9
10
  export { SKILLS_CONTENT };
10
11
  /** Canonical list of workstation artifacts that are NOT part of the project.
11
12
  * Used as the single source of truth for three separate decisions:
@@ -30,6 +31,7 @@ export { SKILLS_CONTENT };
30
31
  export const PRIMER_FILES = {
31
32
  claude: 'CLAUDE.md',
32
33
  codex: 'AGENTS.md',
34
+ grok: 'AGENTS.md', // Grok Build reads the AGENTS.md family (and CLAUDE.md) natively
33
35
  aider: 'AGENTS.md', // shares the Codex primer; aider is pointed at it via .aider.conf.yml
34
36
  gemini: 'GEMINI.md',
35
37
  copilot: '.github/copilot-instructions.md',
@@ -56,7 +58,7 @@ export const AIDER_CONF_FILE = '.aider.conf.yml';
56
58
  * `ignore` package in config.ts. */
57
59
  export const SCRATCH_IGNORE = ['tmp/', '.tmp/', '*_tmp/', '.gipityscratch/'];
58
60
  export const DEFAULT_SYNC_IGNORE = [
59
- 'node_modules', '.git', '.gipity.json', '.gipity/', '.claude/', '.gitignore', AIDER_CONF_FILE,
61
+ 'node_modules', '.git', '.gipity.json', '.gipity/', '.claude/', '.codex/', '.gitignore', AIDER_CONF_FILE,
60
62
  // Home-directory junk: a project created inside a real home dir (or one that
61
63
  // shells out) sweeps in a cache dir + shell dotfiles that are never app
62
64
  // files. `.cache/` alone can be gigabytes (it was 2.4 GB on one project),
@@ -148,7 +150,7 @@ export const LEGACY_MARKETPLACE_REPO = 'GipityAI/claude-plugin';
148
150
  // an installed plugin when the marketplace advances - only an explicit
149
151
  // `plugin install`/`update` does - so this constant is how a CLI upgrade tells
150
152
  // ensureGipityPluginInstalled() to refresh a stale user-scope install.
151
- export const GIPITY_PLUGIN_VERSION = '0.5.0';
153
+ export const GIPITY_PLUGIN_VERSION = '0.7.0';
152
154
  /** True for hook commands the CLI itself wrote into settings.json in past
153
155
  * versions. Matched by signature so migration strips exactly our own
154
156
  * entries and never touches user-authored hooks. */
@@ -280,8 +282,8 @@ export function userScopeInstallState() {
280
282
  export function userScopePluginCurrent() {
281
283
  return userScopeInstallState().current;
282
284
  }
283
- function claudeOnPath() {
284
- const probe = spawnSyncCommand(process.platform === 'win32' ? 'where' : 'which', ['claude'], {
285
+ export function binaryOnPath(bin) {
286
+ const probe = spawnSyncCommand(process.platform === 'win32' ? 'where' : 'which', [bin], {
285
287
  encoding: 'utf-8',
286
288
  });
287
289
  return probe.status === 0 && !!probe.stdout?.toString().trim();
@@ -304,7 +306,7 @@ export function ensureGipityPluginInstalled() {
304
306
  const state = userScopeInstallState();
305
307
  if (state.current)
306
308
  return;
307
- if (!claudeOnPath())
309
+ if (!binaryOnPath('claude'))
308
310
  return;
309
311
  // Refresh the marketplace clone so install/update resolves the current version.
310
312
  // resolveCommand: on Windows `claude` is a .cmd shim that spawn can't launch
@@ -330,6 +332,209 @@ export function ensureGipityPluginInstalled() {
330
332
  timeout: 120_000,
331
333
  });
332
334
  }
335
+ // --- Grok Build (xAI) ------------------------------------------------------
336
+ // Grok Build reads Claude-format plugins natively - skills/, commands/,
337
+ // hooks/hooks.json, and the .claude-plugin/plugin.json manifest - and sets
338
+ // CLAUDE_PLUGIN_ROOT/CLAUDE_PLUGIN_DATA aliases for plugin hooks, so the one
339
+ // GipityAI/skills repo serves both agents. A user-scope install
340
+ // (`grok plugin install <repo> --trust`) is trusted and enabled automatically,
341
+ // which gives Grok sessions the same skills and file-sync hooks as Claude Code.
342
+ /** Parsed install state for the Gipity plugin in Grok Build, read straight
343
+ * from ~/.grok/installed-plugins/registry.json (no subprocess). Mirrors
344
+ * {@link userScopeInstallState} for Claude Code. */
345
+ export function grokInstallState() {
346
+ try {
347
+ const p = join(homedir(), '.grok', 'installed-plugins', 'registry.json');
348
+ const data = JSON.parse(readFileSync(p, 'utf-8'));
349
+ const versions = [];
350
+ for (const repo of Object.values(data?.repos ?? {})) {
351
+ const v = repo?.plugins?.gipity?.version;
352
+ if (typeof v === 'string')
353
+ versions.push(v);
354
+ }
355
+ return {
356
+ exists: versions.length > 0,
357
+ current: versions.some((v) => versionGte(v, GIPITY_PLUGIN_VERSION)),
358
+ };
359
+ }
360
+ catch {
361
+ return { exists: false, current: false };
362
+ }
363
+ }
364
+ /** Install (or upgrade) the Gipity plugin in Grok Build. Best-effort and
365
+ * non-fatal, like the Claude Code counterpart: skips instantly when Grok
366
+ * isn't installed or the plugin is already current, so the steady-state cost
367
+ * is one registry.json read. */
368
+ export function ensureGrokPluginInstalled() {
369
+ const state = grokInstallState();
370
+ if (state.current)
371
+ return;
372
+ if (!binaryOnPath('grok'))
373
+ return;
374
+ const grokCmd = resolveCommand('grok');
375
+ // `install` clones the repo; on an existing install `update <name>` is the
376
+ // verb that refetches the source and advances the recorded version.
377
+ const verb = state.exists
378
+ ? ['plugin', 'update', 'gipity']
379
+ : ['plugin', 'install', GIPITY_MARKETPLACE_REPO, '--trust'];
380
+ const res = spawnSyncCommand(grokCmd, verb, { stdio: 'ignore', timeout: 120_000 });
381
+ if (!state.exists && res.status === 0) {
382
+ console.log('Installed the Gipity plugin for Grok (skills + file-sync hooks).');
383
+ }
384
+ }
385
+ // --- OpenAI Codex ------------------------------------------------------------
386
+ // Codex has no Claude-plugin compatibility, but it reads the same SKILL.md
387
+ // skill format from the cross-agent `~/.agents/skills` directory (also read by
388
+ // OpenClaw and other agentskills.io adopters), and supports project hooks in
389
+ // `.codex/hooks.json` with Claude-style matcher groups (`Edit|Write` aliases
390
+ // its apply_patch tool). So Codex setup = copy the plugin's skills into
391
+ // ~/.agents/skills, stage its hook scripts under ~/.gipity/agent-hooks, and
392
+ // write a project .codex/hooks.json pointing at them. Codex requires the user
393
+ // to approve non-managed hooks once via /hooks - there is no supported way to
394
+ // pre-trust, so we print a nudge on first write.
395
+ export const AGENTS_SKILLS_DIR = join(homedir(), '.agents', 'skills');
396
+ export const AGENT_HOOKS_DIR = join(homedir(), '.gipity', 'agent-hooks');
397
+ /** Records what ensureAgentSkillsInstalled() put on this machine: the plugin
398
+ * version and the exact skill names copied, so upgrades replace and uninstall
399
+ * removes precisely those. */
400
+ export const AGENT_SKILLS_MANIFEST = join(homedir(), '.gipity', 'agent-skills.json');
401
+ export function agentSkillsState() {
402
+ try {
403
+ const m = JSON.parse(readFileSync(AGENT_SKILLS_MANIFEST, 'utf-8'));
404
+ return {
405
+ current: typeof m?.version === 'string' && versionGte(m.version, GIPITY_PLUGIN_VERSION),
406
+ skills: Array.isArray(m?.skills) ? m.skills : [],
407
+ };
408
+ }
409
+ catch {
410
+ return { current: false, skills: [] };
411
+ }
412
+ }
413
+ /** Materialize the Gipity skills into ~/.agents/skills (the cross-agent skills
414
+ * dir Codex reads) and the plugin's hook scripts into ~/.gipity/agent-hooks.
415
+ * Source of truth is the same GipityAI/skills repo the Claude/Grok plugin
416
+ * installs clone - fetched with a shallow git clone into a temp dir.
417
+ * Best-effort: no git, no network, or a failed clone all leave things as they
418
+ * were; the next init retries. */
419
+ export function ensureAgentSkillsInstalled() {
420
+ if (agentSkillsState().current)
421
+ return;
422
+ if (!binaryOnPath('git'))
423
+ return;
424
+ const tmp = mkdtempSync(join(tmpdir(), 'gipity-skills-'));
425
+ try {
426
+ const clone = spawnSyncCommand(resolveCommand('git'), ['clone', '--depth', '1', `https://github.com/${GIPITY_MARKETPLACE_REPO}.git`, join(tmp, 'repo')], { stdio: 'ignore', timeout: 120_000 });
427
+ if (clone.status !== 0)
428
+ return;
429
+ const repo = join(tmp, 'repo');
430
+ let version = GIPITY_PLUGIN_VERSION;
431
+ try {
432
+ const manifest = JSON.parse(readFileSync(join(repo, '.claude-plugin', 'plugin.json'), 'utf-8'));
433
+ if (typeof manifest?.version === 'string')
434
+ version = manifest.version;
435
+ }
436
+ catch { /* keep the CLI's pinned version */ }
437
+ const skillsSrc = join(repo, 'skills');
438
+ const names = [];
439
+ for (const entry of readdirSync(skillsSrc, { withFileTypes: true })) {
440
+ if (!entry.isDirectory())
441
+ continue;
442
+ if (!existsSync(join(skillsSrc, entry.name, 'SKILL.md')))
443
+ continue;
444
+ mkdirSync(AGENTS_SKILLS_DIR, { recursive: true });
445
+ cpSync(join(skillsSrc, entry.name), join(AGENTS_SKILLS_DIR, entry.name), {
446
+ recursive: true,
447
+ force: true,
448
+ });
449
+ names.push(entry.name);
450
+ }
451
+ mkdirSync(AGENT_HOOKS_DIR, { recursive: true });
452
+ for (const script of readdirSync(join(repo, 'hooks', 'scripts'))) {
453
+ cpSync(join(repo, 'hooks', 'scripts', script), join(AGENT_HOOKS_DIR, script), { force: true });
454
+ }
455
+ writeFileSync(AGENT_SKILLS_MANIFEST, JSON.stringify({ version, skills: names }, null, 2) + '\n');
456
+ console.log(`Installed ${names.length} Gipity skills for Codex (~/.agents/skills).`);
457
+ }
458
+ catch { /* best-effort - never break setup */ }
459
+ finally {
460
+ rmSync(tmp, { recursive: true, force: true });
461
+ }
462
+ }
463
+ /** Pure core of the .codex/hooks.json merge: given the file's current content
464
+ * (`null` when absent), return the new content, or `null` when no change is
465
+ * needed. Adds the Gipity sync hook groups (push on file edits, pull before
466
+ * each prompt) AND the session-capture groups (SessionStart / throttled
467
+ * PostToolUse / Stop → capture.cjs, which mirrors the session into the web
468
+ * CLI; Codex has no SessionEnd or SubagentStop, so those are absent), while
469
+ * preserving any user-authored hooks. Our groups are recognized by their
470
+ * exact command string, so a re-run upgrades older sync-only files by adding
471
+ * just the missing capture entries. Exported for unit testing. */
472
+ export function applyCodexHooks(existing) {
473
+ const launcher = join(AGENT_HOOKS_DIR, 'launch.sh');
474
+ const cmd = (script, ...args) => [`sh "${launcher}" "${join(AGENT_HOOKS_DIR, script)}"`, ...args].join(' ');
475
+ const wanted = [
476
+ { event: 'PostToolUse', matcher: 'Edit|Write', command: cmd('sync-push.cjs'), timeout: 30 },
477
+ { event: 'UserPromptSubmit', command: cmd('sync-pull.cjs'), timeout: 300 },
478
+ // Session capture: mirror the Codex session into the Gipity web CLI.
479
+ { event: 'SessionStart', command: cmd('capture.cjs', 'codex', 'session-start'), timeout: 30 },
480
+ { event: 'PostToolUse', command: cmd('capture.cjs', 'codex', 'post-tool-use'), timeout: 30 },
481
+ { event: 'Stop', command: cmd('capture.cjs', 'codex', 'stop'), timeout: 60 },
482
+ ];
483
+ let settings = {};
484
+ if (existing !== null) {
485
+ try {
486
+ settings = JSON.parse(existing);
487
+ }
488
+ catch {
489
+ return null; // user file we can't parse - leave it alone
490
+ }
491
+ }
492
+ const hooks = settings.hooks ?? (settings.hooks = {});
493
+ let changed = false;
494
+ for (const w of wanted) {
495
+ const groups = Array.isArray(hooks[w.event]) ? hooks[w.event] : (hooks[w.event] = []);
496
+ const present = groups.some((g) => Array.isArray(g?.hooks) && g.hooks.some((h) => typeof h?.command === 'string' && h.command === w.command));
497
+ if (present)
498
+ continue;
499
+ const group = { hooks: [{ type: 'command', command: w.command, timeout: w.timeout }] };
500
+ if (w.matcher)
501
+ group.matcher = w.matcher;
502
+ groups.push(group);
503
+ changed = true;
504
+ }
505
+ return changed ? JSON.stringify(settings, null, 2) + '\n' : null;
506
+ }
507
+ /** Write the project-level Codex hooks (.codex/hooks.json). POSIX only - the
508
+ * commands run through the same sh launcher the plugin uses; Codex on Windows
509
+ * would need commandWindows variants (not wired yet). */
510
+ export function setupCodexHooks() {
511
+ if (process.platform === 'win32')
512
+ return;
513
+ const cwd = resolve(process.cwd());
514
+ if (cwd === resolve(homedir()))
515
+ return; // never treat $HOME as a project
516
+ const path = join(cwd, '.codex', 'hooks.json');
517
+ const existing = existsSync(path) ? readFileSync(path, 'utf-8') : null;
518
+ const next = applyCodexHooks(existing);
519
+ if (next === null)
520
+ return;
521
+ mkdirSync(dirname(path), { recursive: true });
522
+ writeFileSync(path, next);
523
+ if (existing === null) {
524
+ console.log('Wrote Codex sync + session-capture hooks (.codex/hooks.json) - approve them once with /hooks inside Codex.');
525
+ }
526
+ else {
527
+ console.log('Updated Codex hooks (.codex/hooks.json) - if Codex asks, re-approve them via /hooks.');
528
+ }
529
+ }
530
+ /** Full Codex integration: user-scope skills + project sync hooks. Gated on
531
+ * the codex binary so machines without Codex get only the AGENTS.md primer. */
532
+ export function setupCodexIntegration() {
533
+ if (!binaryOnPath('codex'))
534
+ return;
535
+ ensureAgentSkillsInstalled();
536
+ setupCodexHooks();
537
+ }
333
538
  export function setupClaudeHooks() {
334
539
  // All hooks ship in the plugin - enable it at user scope (and clean up any
335
540
  // legacy hook blocks in the user-global settings while we're there).
@@ -374,9 +579,25 @@ export const GIPITY_BLOCK_END = '<!-- END GIPITY INTEGRATION -->';
374
579
  * them into a generated doc is the wrong layer. The CLI surfaces them where the
375
580
  * agent actually looks: `gipity deploy` prints the live URL, `gipity status` and
376
581
  * `gipity project info` show the URL + GUID. That keeps them authoritative and
377
- * avoids stale values frozen into a file. */
378
- function renderManagedBlock() {
379
- const body = [SKILLS_CONTENT, BUILD_VS_NON_BUILD_RULE, DEFINITION_OF_DONE].join('\n\n');
582
+ * avoids stale values frozen into a file.
583
+ *
584
+ * The one environment value that DOES live here is the API base. The baked
585
+ * knowledge text names `https://a.gipity.ai` as the app-services endpoint;
586
+ * when this session runs against a different platform instance (GIPITY_API_BASE
587
+ * / --api-base, e.g. a local dev server), the project only exists there - an
588
+ * agent that copies the public host into app code gets 404s it can't explain.
589
+ * So the block is rendered against the resolved base, with a note naming the
590
+ * instance. The block is fully regenerated each session, so it tracks the
591
+ * environment rather than going stale. */
592
+ function renderManagedBlock(apiBase) {
593
+ let body = [SKILLS_CONTENT, BUILD_VS_NON_BUILD_RULE, DEFINITION_OF_DONE].join('\n\n');
594
+ const base = apiBase.replace(/\/+$/, '');
595
+ if (base !== DEFAULT_API_BASE) {
596
+ body = body.replaceAll(DEFAULT_API_BASE, base);
597
+ const note = `> **Platform instance:** this project runs against the Gipity platform at \`${base}\`, not the public \`${DEFAULT_API_BASE}\`. The project and its data exist only on that instance; every API/service URL in this document already points there - never substitute \`a.gipity.ai\`.`;
598
+ const headingEnd = body.indexOf('\n');
599
+ body = body.slice(0, headingEnd + 1) + '\n' + note + '\n' + body.slice(headingEnd + 1);
600
+ }
380
601
  return `${GIPITY_BLOCK_BEGIN}\n${body}\n${GIPITY_BLOCK_END}`;
381
602
  }
382
603
  /**
@@ -396,8 +617,8 @@ function renderManagedBlock() {
396
617
  *
397
618
  * Exported for unit testing.
398
619
  */
399
- export function applySkillsBlock(existing) {
400
- const block = renderManagedBlock();
620
+ export function applySkillsBlock(existing, apiBase = DEFAULT_API_BASE) {
621
+ const block = renderManagedBlock(apiBase);
401
622
  if (existing === null)
402
623
  return block + '\n';
403
624
  let next;
@@ -425,7 +646,7 @@ function writeSkillsFile(relPath, wrap) {
425
646
  const path = resolve(process.cwd(), relPath);
426
647
  mkdirSync(dirname(path), { recursive: true });
427
648
  const existing = existsSync(path) ? readFileSync(path, 'utf-8') : null;
428
- const baseNext = applySkillsBlock(existing);
649
+ const baseNext = applySkillsBlock(existing, resolveApiBase());
429
650
  // Frontmatter wrap only applies on first write (no existing content).
430
651
  // Re-runs preserve user content outside the managed block, including
431
652
  // any frontmatter they may have added themselves.
@@ -509,15 +730,22 @@ export function setupCopilotMd() {
509
730
  export function setupCursorMd() {
510
731
  writeSkillsFile(PRIMER_FILES.cursor, (block) => `---\ndescription: Gipity platform integration - CLI, sandbox, app services\nalwaysApply: true\n---\n\n${block}`);
511
732
  }
512
- /** All supported coding-tool primers and the function that writes each.
513
- * Order matters for help-text rendering and the `all` expansion.
733
+ /** All supported coding tools: the primer each gets (`setup`) plus, for tools
734
+ * with a deeper Gipity integration, an `integrate` step that installs the
735
+ * Gipity skills and file-sync hooks into that tool's own ecosystem (Claude
736
+ * Code plugin, Grok Build plugin, Codex ~/.agents/skills + .codex hooks).
737
+ * Integrations are best-effort and self-gating - each no-ops fast when its
738
+ * binary is missing or the install is already current - so running the whole
739
+ * default set on every init/launch is cheap and keeps all detected agents in
740
+ * lockstep. Order matters for help-text rendering and the `all` expansion.
514
741
  * `optIn` tools are excluded from the default / `all` set and must be named
515
742
  * explicitly (`--for aider`): aider's setup writes `.aider.conf.yml`, which
516
743
  * changes how aider behaves in this directory - a heavier footprint than
517
744
  * dropping an inert markdown primer. */
518
745
  export const SUPPORTED_TOOLS = [
519
- { key: 'claude', label: 'Claude Code (CLAUDE.md)', setup: setupClaudeMd },
520
- { key: 'codex', label: 'OpenAI Codex (AGENTS.md)', setup: setupAgentsMd },
746
+ { key: 'claude', label: 'Claude Code (CLAUDE.md + Gipity plugin)', setup: setupClaudeMd, integrate: setupClaudeHooks },
747
+ { key: 'codex', label: 'OpenAI Codex (AGENTS.md + skills + sync hooks)', setup: setupAgentsMd, integrate: setupCodexIntegration },
748
+ { key: 'grok', label: 'Grok Build (AGENTS.md + Gipity plugin)', setup: setupAgentsMd, integrate: ensureGrokPluginInstalled },
521
749
  { key: 'aider', label: 'Aider (AGENTS.md + .aider.conf.yml)', setup: setupAiderMd, optIn: true },
522
750
  { key: 'gemini', label: 'Gemini CLI (GEMINI.md)', setup: setupGeminiMd },
523
751
  { key: 'copilot', label: 'GitHub Copilot (.github/copilot-instructions.md)', setup: setupCopilotMd },
@@ -526,6 +754,19 @@ export const SUPPORTED_TOOLS = [
526
754
  /** The primer set written when the user makes no explicit `--for` choice:
527
755
  * every tool except opt-in ones. */
528
756
  export const DEFAULT_TOOLS = SUPPORTED_TOOLS.filter(t => !t.optIn);
757
+ /** Registry-driven project setup - the single entry point every "link this
758
+ * directory" path shares (`init`, `project create`, `gipity claude`, the
759
+ * relay daemon). Writes each requested tool's primer, runs its integration
760
+ * (hooks/skills install) when it has one, and refreshes .gitignore. Replaces
761
+ * the setupClaudeHooks/setupClaudeMd/setupAgentsMd/setupGitignore quartet
762
+ * that used to be copy-pasted per call site and silently skipped newer tools. */
763
+ export function setupProjectTools(tools = DEFAULT_TOOLS) {
764
+ for (const t of tools) {
765
+ t.setup();
766
+ t.integrate?.();
767
+ }
768
+ setupGitignore();
769
+ }
529
770
  export function setupGitignore() {
530
771
  const gitignorePath = resolve(process.cwd(), '.gitignore');
531
772
  // Sync already skips the scratch namespaces (DEFAULT_SYNC_IGNORE); ignore them
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gipity",
3
- "version": "1.0.429",
3
+ "version": "1.1.1",
4
4
  "description": "The full-stack platform tuned for AI agents. Database, storage, auth, functions, deploy, and drop-in kits - all agent-tuned. Pair with Claude Code or use standalone.",
5
5
  "bin": {
6
6
  "gipity": "dist/updater/shim.js",
@@ -14,13 +14,14 @@
14
14
  "prepack": "npm run build",
15
15
  "dev": "tsc --watch",
16
16
  "test": "npm run test:smoke",
17
- "test:smoke": "npm run build && node --test dist/__tests__/utils.test.js dist/__tests__/platform.test.js dist/__tests__/colors.test.js dist/__tests__/config.test.js dist/__tests__/sync.test.js dist/__tests__/sync-apply.test.js dist/__tests__/sync-unretrievable.test.js dist/__tests__/sync-clean-check.test.js dist/__tests__/sync-lock.test.js dist/__tests__/auth-lock.test.js dist/__tests__/api-401-retry.test.js dist/__tests__/push-cas.test.js dist/__tests__/upload.test.js dist/__tests__/progress.test.js dist/__tests__/updater.test.js dist/__tests__/cli-smoke.test.js dist/__tests__/claude-noninteractive.test.js dist/__tests__/claude-trust.test.js dist/__tests__/relay-state.test.js dist/__tests__/relay-daemon.test.js dist/__tests__/session-pool.test.js dist/__tests__/relay-diagnostics.test.js dist/__tests__/relay-installers.test.js dist/__tests__/relay-bridge-abort.test.js dist/__tests__/relay-redact.test.js dist/__tests__/relay-machine-id.test.js dist/__tests__/stream-json.test.js dist/__tests__/ingest-queue.test.js dist/__tests__/media-upload.test.js dist/__tests__/stream-delta.test.js dist/__tests__/phase-tracker.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/capture-lock.test.js dist/__tests__/capture-resolve.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/client-context.test.js dist/__tests__/adopt-cwd.test.js dist/__tests__/cli-cmd-agent.test.js dist/__tests__/cli-cmd-approval.test.js dist/__tests__/cli-cmd-audit.test.js dist/__tests__/cli-cmd-bug.test.js dist/__tests__/cli-cmd-bug-queue.test.js dist/__tests__/cli-cmd-chat.test.js dist/__tests__/cli-cmd-credits.test.js dist/__tests__/cli-cmd-db.test.js dist/__tests__/cli-cmd-deploy.test.js dist/__tests__/cli-cmd-domain.test.js dist/__tests__/cli-cmd-email.test.js dist/__tests__/cli-cmd-file.test.js dist/__tests__/cli-cmd-storage.test.js dist/__tests__/cli-cmd-fn.test.js dist/__tests__/cli-cmd-service.test.js dist/__tests__/cli-cmd-job.test.js dist/__tests__/cli-cmd-generate.test.js dist/__tests__/cli-cmd-gmail.test.js dist/__tests__/cli-cmd-info.test.js dist/__tests__/cli-cmd-init.test.js dist/__tests__/cli-cmd-location.test.js dist/__tests__/cli-cmd-text.test.js dist/__tests__/cli-cmd-login.test.js dist/__tests__/cli-cmd-logout.test.js dist/__tests__/cli-cmd-token.test.js dist/__tests__/cli-cmd-logs.test.js dist/__tests__/cli-cmd-memory.test.js dist/__tests__/cli-cmd-page.test.js dist/__tests__/cli-cmd-plan.test.js dist/__tests__/cli-cmd-project.test.js dist/__tests__/cli-cmd-rbac.test.js dist/__tests__/cli-cmd-realtime.test.js dist/__tests__/cli-cmd-records.test.js dist/__tests__/cli-cmd-relay.test.js dist/__tests__/cli-cmd-setup.test.js dist/__tests__/cli-cmd-doctor.test.js dist/__tests__/claude-setup.test.js dist/__tests__/cli-cmd-sandbox.test.js dist/__tests__/cli-cmd-add.test.js dist/__tests__/cli-cmd-remove.test.js dist/__tests__/cli-cmd-skill.test.js dist/__tests__/cli-cmd-test.test.js dist/__tests__/cli-cmd-workflow.test.js dist/__tests__/setup-skills-block.test.js dist/__tests__/setup-hooks.test.js dist/__tests__/trace.test.js",
17
+ "test:smoke": "npm run build && node --test dist/__tests__/utils.test.js dist/__tests__/platform.test.js dist/__tests__/colors.test.js dist/__tests__/config.test.js dist/__tests__/sync.test.js dist/__tests__/sync-apply.test.js dist/__tests__/sync-unretrievable.test.js dist/__tests__/sync-clean-check.test.js dist/__tests__/sync-lock.test.js dist/__tests__/auth-lock.test.js dist/__tests__/api-401-retry.test.js dist/__tests__/push-cas.test.js dist/__tests__/upload.test.js dist/__tests__/progress.test.js dist/__tests__/updater.test.js dist/__tests__/cli-smoke.test.js dist/__tests__/claude-noninteractive.test.js dist/__tests__/claude-trust.test.js dist/__tests__/relay-state.test.js dist/__tests__/relay-daemon.test.js dist/__tests__/session-pool.test.js dist/__tests__/relay-diagnostics.test.js dist/__tests__/relay-installers.test.js dist/__tests__/relay-bridge-abort.test.js dist/__tests__/relay-redact.test.js dist/__tests__/relay-machine-id.test.js dist/__tests__/stream-json.test.js dist/__tests__/ingest-queue.test.js dist/__tests__/media-upload.test.js dist/__tests__/stream-delta.test.js dist/__tests__/phase-tracker.test.js dist/__tests__/relay-ingest-contract.test.js dist/__tests__/prompts.test.js dist/__tests__/capture-transcript.test.js dist/__tests__/capture-parsers.test.js dist/__tests__/agents.test.js dist/__tests__/capture-lock.test.js dist/__tests__/capture-resolve.test.js dist/__tests__/flag-aliases.test.js dist/__tests__/client-context.test.js dist/__tests__/adopt-cwd.test.js dist/__tests__/cli-cmd-agent.test.js dist/__tests__/cli-cmd-approval.test.js dist/__tests__/cli-cmd-audit.test.js dist/__tests__/cli-cmd-bug.test.js dist/__tests__/cli-cmd-bug-queue.test.js dist/__tests__/cli-cmd-chat.test.js dist/__tests__/cli-cmd-credits.test.js dist/__tests__/cli-cmd-db.test.js dist/__tests__/cli-cmd-deploy.test.js dist/__tests__/cli-cmd-domain.test.js dist/__tests__/cli-cmd-email.test.js dist/__tests__/cli-cmd-file.test.js dist/__tests__/cli-cmd-storage.test.js dist/__tests__/cli-cmd-fn.test.js dist/__tests__/cli-cmd-service.test.js dist/__tests__/cli-cmd-job.test.js dist/__tests__/cli-cmd-generate.test.js dist/__tests__/cli-cmd-gmail.test.js dist/__tests__/cli-cmd-info.test.js dist/__tests__/cli-cmd-init.test.js dist/__tests__/cli-cmd-location.test.js dist/__tests__/cli-cmd-text.test.js dist/__tests__/cli-cmd-login.test.js dist/__tests__/cli-cmd-logout.test.js dist/__tests__/cli-cmd-token.test.js dist/__tests__/cli-cmd-logs.test.js dist/__tests__/cli-cmd-memory.test.js dist/__tests__/cli-cmd-page.test.js dist/__tests__/cli-cmd-plan.test.js dist/__tests__/cli-cmd-project.test.js dist/__tests__/cli-cmd-rbac.test.js dist/__tests__/cli-cmd-realtime.test.js dist/__tests__/cli-cmd-records.test.js dist/__tests__/cli-cmd-relay.test.js dist/__tests__/cli-cmd-setup.test.js dist/__tests__/cli-cmd-doctor.test.js dist/__tests__/claude-setup.test.js dist/__tests__/cli-cmd-sandbox.test.js dist/__tests__/cli-cmd-add.test.js dist/__tests__/cli-cmd-remove.test.js dist/__tests__/cli-cmd-skill.test.js dist/__tests__/cli-cmd-test.test.js dist/__tests__/cli-cmd-workflow.test.js dist/__tests__/setup-skills-block.test.js dist/__tests__/setup-hooks.test.js dist/__tests__/setup-codex-hooks.test.js dist/__tests__/trace.test.js",
18
18
  "test:smoke:quick": "node scripts/smoke-quick.mjs",
19
19
  "test:e2e": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-live.test.js dist/__tests__/cli-e2e-sync-live.test.js dist/__tests__/cli-e2e-sync-stress-live.test.js dist/__tests__/cli-e2e-rollback-live.test.js dist/__tests__/cli-e2e-services-media-live.test.js dist/__tests__/cli-e2e-workflow-live.test.js dist/__tests__/cli-e2e-sandbox-live.test.js dist/__tests__/cli-e2e-page-fetch-live.test.js dist/__tests__/cli-e2e-page-test-live.test.js",
20
20
  "test:e2e:sync": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sync-live.test.js",
21
21
  "test:e2e:sync-stress": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sync-stress-live.test.js",
22
22
  "test:e2e:rollback": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-rollback-live.test.js",
23
- "test:e2e:sandbox": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sandbox-live.test.js"
23
+ "test:e2e:sandbox": "tsc && GIPITY_E2E=1 node --test --test-timeout=180000 dist/__tests__/cli-e2e-sandbox-live.test.js",
24
+ "test:e2e:agents": "tsc && GIPITY_E2E=1 node --test --test-timeout=900000 dist/__tests__/cli-e2e-agents-live.test.js"
24
25
  },
25
26
  "dependencies": {
26
27
  "@anthropic-ai/claude-agent-sdk": "^0.3.207",