gipity 1.0.264 → 1.0.306

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.
Files changed (95) hide show
  1. package/dist/__tests__/capture-transcript.test.d.ts +1 -0
  2. package/dist/__tests__/capture-transcript.test.js +92 -0
  3. package/dist/__tests__/capture-transcript.test.js.map +1 -0
  4. package/dist/__tests__/cli-smoke.test.js +5 -5
  5. package/dist/__tests__/cli-smoke.test.js.map +1 -1
  6. package/dist/__tests__/prompts.test.d.ts +1 -0
  7. package/dist/__tests__/prompts.test.js +129 -0
  8. package/dist/__tests__/prompts.test.js.map +1 -0
  9. package/dist/__tests__/push-cas.test.d.ts +1 -0
  10. package/dist/__tests__/push-cas.test.js +133 -0
  11. package/dist/__tests__/push-cas.test.js.map +1 -0
  12. package/dist/__tests__/sync-apply.test.d.ts +1 -0
  13. package/dist/__tests__/sync-apply.test.js +457 -0
  14. package/dist/__tests__/sync-apply.test.js.map +1 -0
  15. package/dist/__tests__/sync-lock.test.d.ts +1 -0
  16. package/dist/__tests__/sync-lock.test.js +105 -0
  17. package/dist/__tests__/sync-lock.test.js.map +1 -0
  18. package/dist/__tests__/sync.test.js +115 -151
  19. package/dist/__tests__/sync.test.js.map +1 -1
  20. package/dist/adopt-cwd.d.ts +66 -0
  21. package/dist/adopt-cwd.js +255 -0
  22. package/dist/adopt-cwd.js.map +1 -0
  23. package/dist/api.d.ts +11 -1
  24. package/dist/api.js +46 -3
  25. package/dist/api.js.map +1 -1
  26. package/dist/capture/sources/claude-code.d.ts +78 -0
  27. package/dist/capture/sources/claude-code.js +158 -0
  28. package/dist/capture/sources/claude-code.js.map +1 -0
  29. package/dist/commands/chat.js +8 -8
  30. package/dist/commands/chat.js.map +1 -1
  31. package/dist/commands/claude.js +265 -129
  32. package/dist/commands/claude.js.map +1 -1
  33. package/dist/commands/init.js +34 -59
  34. package/dist/commands/init.js.map +1 -1
  35. package/dist/commands/location.js +1 -1
  36. package/dist/commands/location.js.map +1 -1
  37. package/dist/commands/logout.js +1 -1
  38. package/dist/commands/logout.js.map +1 -1
  39. package/dist/commands/page-inspect.js +36 -25
  40. package/dist/commands/page-inspect.js.map +1 -1
  41. package/dist/commands/page-screenshot.d.ts +2 -0
  42. package/dist/commands/page-screenshot.js +212 -0
  43. package/dist/commands/page-screenshot.js.map +1 -0
  44. package/dist/commands/project.js +71 -12
  45. package/dist/commands/project.js.map +1 -1
  46. package/dist/commands/relay.js +1 -1
  47. package/dist/commands/relay.js.map +1 -1
  48. package/dist/commands/scaffold.js +19 -13
  49. package/dist/commands/scaffold.js.map +1 -1
  50. package/dist/commands/sync.js +15 -24
  51. package/dist/commands/sync.js.map +1 -1
  52. package/dist/commands/uninstall.js +1 -1
  53. package/dist/commands/uninstall.js.map +1 -1
  54. package/dist/commands/update.js +1 -1
  55. package/dist/commands/update.js.map +1 -1
  56. package/dist/config.d.ts +9 -0
  57. package/dist/config.js +9 -0
  58. package/dist/config.js.map +1 -1
  59. package/dist/helpers/sync.d.ts +4 -2
  60. package/dist/helpers/sync.js +13 -7
  61. package/dist/helpers/sync.js.map +1 -1
  62. package/dist/hooks/capture-runner.d.ts +24 -0
  63. package/dist/hooks/capture-runner.js +233 -0
  64. package/dist/hooks/capture-runner.js.map +1 -0
  65. package/dist/index.js +17 -16
  66. package/dist/index.js.map +1 -1
  67. package/dist/project-setup.d.ts +21 -0
  68. package/dist/project-setup.js +42 -0
  69. package/dist/project-setup.js.map +1 -0
  70. package/dist/prompts.d.ts +7 -0
  71. package/dist/prompts.js +25 -2
  72. package/dist/prompts.js.map +1 -1
  73. package/dist/relay/daemon.d.ts +8 -8
  74. package/dist/relay/daemon.js +23 -61
  75. package/dist/relay/daemon.js.map +1 -1
  76. package/dist/relay/device-http.d.ts +9 -0
  77. package/dist/relay/device-http.js +58 -0
  78. package/dist/relay/device-http.js.map +1 -0
  79. package/dist/relay/onboarding.js +3 -2
  80. package/dist/relay/onboarding.js.map +1 -1
  81. package/dist/setup.d.ts +8 -0
  82. package/dist/setup.js +55 -16
  83. package/dist/setup.js.map +1 -1
  84. package/dist/sync.d.ts +55 -37
  85. package/dist/sync.js +628 -445
  86. package/dist/sync.js.map +1 -1
  87. package/dist/updater/bootstrap.d.ts +6 -1
  88. package/dist/updater/bootstrap.js +21 -13
  89. package/dist/updater/bootstrap.js.map +1 -1
  90. package/dist/updater/shim.js +12 -1
  91. package/dist/updater/shim.js.map +1 -1
  92. package/dist/upload.d.ts +13 -2
  93. package/dist/upload.js +59 -6
  94. package/dist/upload.js.map +1 -1
  95. package/package.json +2 -2
@@ -1,7 +1,7 @@
1
1
  import { Command } from 'commander';
2
2
  import { post } from '../api.js';
3
3
  import { resolveProjectContext, saveConfig } from '../config.js';
4
- import { syncDown } from '../sync.js';
4
+ import { sync } from '../sync.js';
5
5
  import { error as clrError, muted } from '../colors.js';
6
6
  export const chatCommand = new Command('chat')
7
7
  .description('Send a message to the Gipity agent')
@@ -27,14 +27,14 @@ export const chatCommand = new Command('chat')
27
27
  let syncSummary = '';
28
28
  let syncChanges = [];
29
29
  if (res.data.filesChanged) {
30
- const syncResult = await syncDown();
31
- if (syncResult.pulled > 0) {
32
- syncSummary = `\nPulled ${syncResult.pulled} file${syncResult.pulled > 1 ? 's' : ''}:\n${syncResult.summary}`;
30
+ const syncResult = await sync({ interactive: false });
31
+ if (syncResult.applied > 0) {
32
+ syncSummary = `\nSynced ${syncResult.applied} change${syncResult.applied > 1 ? 's' : ''}:\n${syncResult.summary}`;
33
33
  }
34
- syncChanges = syncResult.changes.map(c => ({
35
- path: c.path,
36
- type: c.type,
37
- ...(c.remoteSize != null ? { size: c.remoteSize } : {}),
34
+ syncChanges = syncResult.plan.actions.map(a => ({
35
+ path: a.path,
36
+ type: a.kind,
37
+ ...(a.remoteSize != null ? { size: a.remoteSize } : {}),
38
38
  }));
39
39
  }
40
40
  if (opts.json) {
@@ -1 +1 @@
1
- {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,qBAAqB,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,KAAK,IAAI,QAAQ,EAAE,KAAK,EAAE,MAAM,cAAc,CAAC;AAExD,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,oCAAoC,CAAC;KACjD,QAAQ,CAAC,WAAW,EAAE,iBAAiB,CAAC;KACxC,MAAM,CAAC,OAAO,EAAE,0BAA0B,CAAC;KAC3C,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,OAAe,EAAE,IAAI,EAAE,EAAE;IACtC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,qBAAqB,EAAE,CAAC;QAEjD,MAAM,WAAW,GAAG,MAAM,CAAC,gBAAgB,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC;QAEzD,MAAM,QAAQ,GAAG,WAAW;YAC1B,CAAC,CAAC,kBAAkB,MAAM,CAAC,gBAAgB,WAAW;YACtD,CAAC,CAAC,gBAAgB,CAAC;QAErB,MAAM,IAAI,GAAG,WAAW;YACtB,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE;YACvD,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC;QAEvF,MAAM,GAAG,GAAG,MAAM,IAAI,CAkBnB,QAAQ,EAAE,IAAI,CAAC,CAAC;QAEnB,wCAAwC;QACxC,IAAI,GAAG,CAAC,IAAI,CAAC,gBAAgB,KAAK,MAAM,CAAC,gBAAgB,EAAE,CAAC;YAC1D,UAAU,CAAC,EAAE,GAAG,MAAM,EAAE,gBAAgB,EAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB,EAAE,CAAC,CAAC;QACzE,CAAC;QAED,kDAAkD;QAClD,IAAI,WAAW,GAAG,EAAE,CAAC;QACrB,IAAI,WAAW,GAAoD,EAAE,CAAC;QAEtE,IAAI,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YAC1B,MAAM,UAAU,GAAG,MAAM,QAAQ,EAAE,CAAC;YACpC,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC1B,WAAW,GAAG,YAAY,UAAU,CAAC,MAAM,QAAQ,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,MAAM,UAAU,CAAC,OAAO,EAAE,CAAC;YAChH,CAAC;YACD,WAAW,GAAG,UAAU,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBACzC,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,GAAG,CAAC,CAAC,CAAC,UAAU,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACxD,CAAC,CAAC,CAAC;QACN,CAAC;QAED,IAAI,IAAI,CAAC,IAAI,EAAE,CAAC;YACd,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC;gBACzB,OAAO,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO;gBACzB,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;oBACvC,IAAI,EAAE,CAAC,CAAC,QAAQ;oBAChB,OAAO,EAAE,CAAC,CAAC,OAAO;oBAClB,MAAM,EAAE,CAAC,CAAC,aAAa,IAAI,EAAE;iBAC9B,CAAC,CAAC,IAAI,EAAE;gBACT,KAAK,EAAE,GAAG,CAAC,IAAI,CAAC,KAAK;gBACrB,MAAM,EAAE,GAAG,CAAC,IAAI,CAAC,WAAW,GAAG,GAAG,CAAC,IAAI,CAAC,YAAY;gBACpD,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,OAAO;gBACtB,gBAAgB,EAAE,GAAG,CAAC,IAAI,CAAC,gBAAgB;gBAC3C,WAAW,EAAE,WAAW,CAAC,MAAM,GAAG,CAAC;gBACnC,WAAW,EAAE,WAAW;aACzB,CAAC,CAAC,CAAC;QACN,CAAC;aAAM,CAAC;YACN,sBAAsB;YACtB,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAE9B,kBAAkB;YAClB,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBACxD,MAAM,SAAS,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBACxE,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,QAAQ,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAC9D,CAAC;YAED,oBAAoB;YACpB,IAAI,WAAW,EAAE,CAAC;gBAChB,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;YAC3B,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,GAAQ,EAAE,CAAC;QAClB,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,gBAAgB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
1
+ {"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,8 @@
1
1
  import { Command } from 'commander';
2
- import { join, dirname, resolve } from 'path';
2
+ import { join, dirname, resolve, basename } from 'path';
3
3
  import { mkdirSync, readFileSync, readdirSync, statSync } from 'fs';
4
4
  import { execSync, spawn } from 'child_process';
5
+ import { homedir } from 'os';
5
6
  import { fileURLToPath } from 'url';
6
7
  /** On Windows, spawn without shell:true needs an explicit extension (.exe or .cmd) */
7
8
  function resolveCommand(cmd) {
@@ -19,14 +20,15 @@ function resolveCommand(cmd) {
19
20
  import { getAuth, saveAuth, clearAuth } from '../auth.js';
20
21
  import { get, post, publicPost, ApiError, getAccountSlug } from '../api.js';
21
22
  import { getConfig, saveConfig, clearConfigCache, getApiBaseOverride } from '../config.js';
22
- import { syncDown, syncUp } from '../sync.js';
23
- import { slugify, setupClaudeHooks, setupClaudeMd, setupGitignore, DEFAULT_SYNC_IGNORE, isSyncIgnored } from '../setup.js';
23
+ import { sync } from '../sync.js';
24
+ import { slugify, setupClaudeHooks, setupClaudeMd, setupAgentsMd, setupGitignore, DEFAULT_SYNC_IGNORE, isSyncIgnored } from '../setup.js';
24
25
  import { buildProjectContextBlock as buildProjectContextBlockText, buildExistingProjectPrompt as buildExistingProjectPromptText, buildNewProjectPrompt, buildResumeWrap, buildFreshWrap, } from '../prompts.js';
25
26
  import * as relayState from '../relay/state.js';
26
27
  import { maybeOfferRelayOn, ensureDaemonRunning } from '../relay/onboarding.js';
27
- import { prompt, promptBoxed, pickOne, decodeJwtExp } from '../utils.js';
28
+ import { prompt, promptBoxed, pickOne, decodeJwtExp, confirm } from '../utils.js';
28
29
  import { brand, bold, info, success, error as clrError, muted } from '../colors.js';
29
30
  import { printBanner } from '../banner.js';
31
+ import { scanForAdoption, isLikelyEmpty, canAdoptCwd, formatCwdLabel, formatBytes, adoptCurrentDir, ADOPT_THRESHOLDS, } from '../adopt-cwd.js';
30
32
  const __clDir = dirname(fileURLToPath(import.meta.url));
31
33
  const __clPkg = JSON.parse(readFileSync(resolve(__clDir, '../../package.json'), 'utf-8'));
32
34
  import { getProjectsRoot } from '../relay/paths.js';
@@ -108,6 +110,33 @@ async function buildExistingProjectPrompt(opts) {
108
110
  const stats = await fetchProjectStats(opts.projectGuid, opts.cwd);
109
111
  return buildExistingProjectPromptText({ ...opts, ...stats });
110
112
  }
113
+ /** Interactive email+code login flow. Used on first login and when the
114
+ * server returns 401 mid-command (session expired). Writes the new auth
115
+ * to disk and returns it. */
116
+ async function interactiveLogin() {
117
+ const email = await prompt(' Email: ');
118
+ if (!email) {
119
+ console.error(`\n ${clrError('Email required.')}`);
120
+ process.exit(1);
121
+ }
122
+ await publicPost('/auth/login', { email });
123
+ console.log(' Check your email for a 6-digit code.\n');
124
+ const code = await prompt(' Code: ');
125
+ if (!code) {
126
+ console.error(`\n ${clrError('Code required.')}`);
127
+ process.exit(1);
128
+ }
129
+ const res = await publicPost('/auth/verify', { email, code });
130
+ const exp = decodeJwtExp(res.accessToken);
131
+ if (!exp) {
132
+ console.error(`\n ${clrError('Invalid token received.')}`);
133
+ process.exit(1);
134
+ }
135
+ const expiresAt = new Date(exp * 1000).toISOString();
136
+ saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
137
+ console.log(` ${success(`Logged in as ${email}`)}`);
138
+ return getAuth();
139
+ }
111
140
  // First-run relay onboarding now lives in `relay/onboarding.ts`
112
141
  // (`maybeOfferRelayOn`). `gipity claude` invokes it after project
113
142
  // selection, and also calls `ensureDaemonRunning` unconditionally before
@@ -139,7 +168,7 @@ function suggestProjectName(existingSlugs) {
139
168
  return `project-${Date.now().toString(36).slice(-6)}`;
140
169
  }
141
170
  export const claudeCommand = new Command('claude')
142
- .description('Log in, pair this machine, set up a project, and launch Claude Code (pass -p "msg" / --resume <id> for non-interactive use)')
171
+ .description('Setup and pair with Claude Code (local CLI)')
143
172
  .option('--no-claude', 'Set up project but skip launching Claude Code')
144
173
  .allowUnknownOption(true)
145
174
  .allowExcessArguments(true)
@@ -177,28 +206,7 @@ export const claudeCommand = new Command('claude')
177
206
  }
178
207
  else {
179
208
  console.log(' Let\'s get you logged in.\n');
180
- const email = await prompt(' Email: ');
181
- if (!email) {
182
- console.error(`\n ${clrError('Email required.')}`);
183
- process.exit(1);
184
- }
185
- await publicPost('/auth/login', { email });
186
- console.log(' Check your email for a 6-digit code.\n');
187
- const code = await prompt(' Code: ');
188
- if (!code) {
189
- console.error(`\n ${clrError('Code required.')}`);
190
- process.exit(1);
191
- }
192
- const res = await publicPost('/auth/verify', { email, code });
193
- const exp = decodeJwtExp(res.accessToken);
194
- if (!exp) {
195
- console.error(`\n ${clrError('Invalid token received.')}`);
196
- process.exit(1);
197
- }
198
- const expiresAt = new Date(exp * 1000).toISOString();
199
- saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
200
- auth = getAuth();
201
- console.log(` ${success(`Logged in as ${email}`)}`);
209
+ auth = await interactiveLogin();
202
210
  }
203
211
  console.log('');
204
212
  // ── Step 1b: Relay first-run onboarding (account-scoped, runs before project) ──
@@ -217,15 +225,12 @@ export const claudeCommand = new Command('claude')
217
225
  console.log(` ${success('Already set up.')}\n`);
218
226
  setupClaudeHooks();
219
227
  setupClaudeMd();
228
+ setupAgentsMd();
220
229
  setupGitignore();
221
230
  try {
222
- const upResult = await syncUp();
223
- if (upResult.pushed > 0) {
224
- console.log(` Pushed ${upResult.pushed} file${upResult.pushed > 1 ? 's' : ''} to Gipity.`);
225
- }
226
- const downResult = await syncDown({ confirmDeletions: !nonInteractive });
227
- if (downResult.pulled > 0) {
228
- console.log(` Pulled ${downResult.pulled} file${downResult.pulled > 1 ? 's' : ''} from Gipity.`);
231
+ const result = await sync({ interactive: !nonInteractive });
232
+ if (result.applied > 0) {
233
+ console.log(` Synced ${result.applied} change${result.applied > 1 ? 's' : ''} with Gipity.`);
229
234
  }
230
235
  }
231
236
  catch {
@@ -240,91 +245,129 @@ export const claudeCommand = new Command('claude')
240
245
  });
241
246
  }
242
247
  else {
243
- // Fetch user's projects
248
+ // Fetch user's projects. If the session expired (401), re-run the
249
+ // interactive login and retry once — no reason to kick the user out
250
+ // to a shell just to re-run the same command.
244
251
  let projects = [];
245
- try {
246
- const res = await get('/projects?limit=100');
247
- projects = res.data;
248
- }
249
- catch (err) {
250
- const isConnectionError = err?.code === 'ECONNREFUSED' || err?.code === 'ENOTFOUND' ||
251
- err?.code === 'ETIMEDOUT' || err?.cause?.code === 'ECONNREFUSED' ||
252
- err?.cause?.code === 'ENOTFOUND' || err?.cause?.code === 'ETIMEDOUT';
253
- if (isConnectionError) {
254
- const apiBase = getApiBaseOverride() || 'https://a.gipity.ai';
255
- console.error(` ${clrError(`Could not connect to ${apiBase}`)}`);
256
- console.error(` ${muted('Check your connection and try again.')}`);
257
- process.exit(1);
252
+ let reauthed = false;
253
+ while (true) {
254
+ try {
255
+ const res = await get('/projects?limit=100');
256
+ projects = res.data;
257
+ break;
258
258
  }
259
- if (err instanceof ApiError && err.statusCode === 401) {
260
- clearAuth();
261
- console.error(` ${clrError('Your session expired.')}`);
262
- console.error(` ${muted('Run: gipity login')}`);
259
+ catch (err) {
260
+ const isConnectionError = err?.code === 'ECONNREFUSED' || err?.code === 'ENOTFOUND' ||
261
+ err?.code === 'ETIMEDOUT' || err?.cause?.code === 'ECONNREFUSED' ||
262
+ err?.cause?.code === 'ENOTFOUND' || err?.cause?.code === 'ETIMEDOUT';
263
+ if (isConnectionError) {
264
+ const apiBase = getApiBaseOverride() || 'https://a.gipity.ai';
265
+ console.error(` ${clrError(`Could not connect to ${apiBase}`)}`);
266
+ console.error(` ${muted('Check your connection and try again.')}`);
267
+ process.exit(1);
268
+ }
269
+ if (err instanceof ApiError && err.statusCode === 401 && !reauthed && !nonInteractive) {
270
+ clearAuth();
271
+ console.log(` ${muted('Your session expired. Let\'s sign you back in.')}\n`);
272
+ auth = await interactiveLogin();
273
+ console.log('');
274
+ reauthed = true;
275
+ continue;
276
+ }
277
+ if (err instanceof ApiError && err.statusCode === 401) {
278
+ clearAuth();
279
+ console.error(` ${clrError('Your session expired.')}`);
280
+ console.error(` ${muted('Run: gipity login')}`);
281
+ process.exit(1);
282
+ }
283
+ console.error(` ${clrError(`Could not load projects: ${err?.message || err}`)}`);
263
284
  process.exit(1);
264
285
  }
265
- console.error(` ${clrError(`Could not load projects: ${err?.message || err}`)}`);
266
- process.exit(1);
267
286
  }
268
287
  const existingSlugs = projects.map(p => p.slug);
269
288
  let project;
270
289
  let isNewProject = false;
271
- if (projects.length > 0) {
272
- const result = await pickOrCreateProject(projects, existingSlugs);
273
- project = result.project;
274
- isNewProject = result.isNew;
275
- }
276
- else {
277
- project = await createNewProject(existingSlugs);
278
- isNewProject = true;
279
- }
280
- // Resolve project directory under ~/GipityProjects/{slug}
281
- const projectDir = join(getProjectsRoot(), project.slug);
282
- mkdirSync(projectDir, { recursive: true });
283
- process.chdir(projectDir);
284
- clearConfigCache();
285
- // Fetch agents
290
+ let accountSlug = '';
286
291
  let agentGuid = '';
287
- try {
288
- const agents = await get(`/projects/${project.short_guid}/agents`);
289
- if (agents.data.length > 0)
290
- agentGuid = agents.data[0].short_guid;
291
- }
292
- catch {
293
- // No agents
292
+ const result = projects.length > 0
293
+ ? await pickOrCreateProject(projects, existingSlugs)
294
+ : { kind: 'create-new', project: await createNewProject(existingSlugs) };
295
+ if (result.kind === 'adopt-cwd') {
296
+ // User chose "Use this directory" — derive a slug from the cwd
297
+ // basename, find-or-create the server project, write
298
+ // .gipity.json into cwd (no projects-root materialization).
299
+ const cwdBase = basename(process.cwd());
300
+ const adoptSlug = slugify(cwdBase) || 'project';
301
+ const adoptName = cwdBase || adoptSlug;
302
+ accountSlug = await getAccountSlug();
303
+ const adopted = await adoptCurrentDir({
304
+ cwd: process.cwd(),
305
+ projectName: adoptName,
306
+ projectSlug: adoptSlug,
307
+ accountSlug,
308
+ confirmDeletions: !nonInteractive,
309
+ });
310
+ project = adopted.project;
311
+ agentGuid = adopted.agentGuid;
312
+ // Treat as "new project" for the build prompt only when cwd is
313
+ // genuinely empty — otherwise the user has chosen to adopt
314
+ // existing content and shouldn't be asked "what to build?".
315
+ isNewProject = isLikelyEmpty(process.cwd());
316
+ console.log(`\n Using ${process.cwd()}`);
317
+ if (adopted.applied > 0) {
318
+ console.log(` Synced ${adopted.applied} change${adopted.applied > 1 ? 's' : ''} with Gipity.`);
319
+ }
294
320
  }
295
- const accountSlug = await getAccountSlug();
296
- // Always write config (refresh stale GUIDs from a previous setup)
297
- saveConfig({
298
- projectGuid: project.short_guid,
299
- projectSlug: project.slug,
300
- accountSlug,
301
- agentGuid,
302
- conversationGuid: null,
303
- apiBase: getApiBaseOverride() || 'https://a.gipity.ai',
304
- ignore: DEFAULT_SYNC_IGNORE,
305
- });
306
- console.log(`\n Using ${projectDir}`);
307
- // Sync: push local files up first, then pull any remote-only files down (non-fatal)
308
- try {
309
- const upResult = await syncUp();
310
- if (upResult.pushed > 0) {
311
- console.log(` Pushed ${upResult.pushed} file${upResult.pushed > 1 ? 's' : ''} to Gipity.`);
321
+ else {
322
+ project = result.project;
323
+ isNewProject = result.kind === 'create-new';
324
+ // Resolve project directory under ~/GipityProjects/{slug}
325
+ const projectDir = join(getProjectsRoot(), project.slug);
326
+ mkdirSync(projectDir, { recursive: true });
327
+ process.chdir(projectDir);
328
+ clearConfigCache();
329
+ // Fetch agents
330
+ try {
331
+ const agents = await get(`/projects/${project.short_guid}/agents`);
332
+ if (agents.data.length > 0)
333
+ agentGuid = agents.data[0].short_guid;
312
334
  }
313
- const downResult = await syncDown({ confirmDeletions: !nonInteractive });
314
- if (downResult.pulled > 0) {
315
- console.log(` Pulled ${downResult.pulled} file${downResult.pulled > 1 ? 's' : ''} from Gipity.`);
335
+ catch {
336
+ // No agents
337
+ }
338
+ accountSlug = await getAccountSlug();
339
+ // Always write config (refresh stale GUIDs from a previous setup)
340
+ saveConfig({
341
+ projectGuid: project.short_guid,
342
+ projectSlug: project.slug,
343
+ accountSlug,
344
+ agentGuid,
345
+ conversationGuid: null,
346
+ apiBase: getApiBaseOverride() || 'https://a.gipity.ai',
347
+ ignore: DEFAULT_SYNC_IGNORE,
348
+ });
349
+ console.log(`\n Using ${projectDir}`);
350
+ // Unified sync — push and pull resolved via three-way merge (non-fatal)
351
+ try {
352
+ const result = await sync({ interactive: !nonInteractive });
353
+ if (result.applied > 0) {
354
+ console.log(` Synced ${result.applied} change${result.applied > 1 ? 's' : ''} with Gipity.`);
355
+ }
356
+ }
357
+ catch {
358
+ console.log(' Could not sync files (will retry on next prompt).');
316
359
  }
317
- }
318
- catch {
319
- console.log(' Could not sync files (will retry on next prompt).');
320
360
  }
321
361
  // ── Step 2b: What do you want to build? (new projects only) ────
322
362
  if (isNewProject) {
323
363
  console.log('');
324
- console.log(` ${bold('What would you like to build?')}`);
325
- console.log(` ${muted('Examples: a landing page, a Pac-Man game, a helpdesk app,')}`);
326
- console.log(` ${muted('an API that returns random facts, or anything you can describe.')}`);
327
- console.log(` ${muted('Press Enter for a blank project.')}`);
364
+ console.log(` ${bold('Claude Code enabled with Gipity!')}`);
365
+ console.log('');
366
+ console.log(` ${bold("What's next? What would you like to build?")}`);
367
+ console.log(` ${muted('Examples: a landing page, a Pac-Man game, a full web app,')}`);
368
+ console.log(` ${muted('an API that returns random facts, an image, just answer questions?')}`);
369
+ console.log('');
370
+ console.log(` ${muted('Claude Code with Gipity can do everything your old Claude Code could do but so much more now!')}`);
328
371
  console.log('');
329
372
  const buildIdea = (await promptBoxed()).trim();
330
373
  const stats = await fetchProjectStats(project.short_guid, process.cwd());
@@ -349,6 +392,7 @@ export const claudeCommand = new Command('claude')
349
392
  }
350
393
  setupClaudeHooks();
351
394
  setupClaudeMd();
395
+ setupAgentsMd();
352
396
  setupGitignore();
353
397
  console.log(` ${success(`Project "${project.name}" ready.`)}\n`);
354
398
  }
@@ -384,6 +428,13 @@ export const claudeCommand = new Command('claude')
384
428
  // Skipped silently if this machine isn't paired — without a device
385
429
  // we can't satisfy the claude_code ownership rule, so hooks stay
386
430
  // offline for this run.
431
+ //
432
+ // Note: when `--output-format stream-json` is present in argv, the
433
+ // relay daemon is capturing Claude's stdout directly and the hook-
434
+ // based transcript capture would double-post every event. In that
435
+ // case we intentionally do NOT propagate GIPITY_CONVERSATION_GUID
436
+ // to the Claude child (see childEnv assignment below) — the hook's
437
+ // existing "no guid → silent no-op" guard then skips capture.
387
438
  let convGuidForHooks = process.env.GIPITY_CONVERSATION_GUID ?? null;
388
439
  if (!convGuidForHooks) {
389
440
  const device = relayState.getDevice();
@@ -403,7 +454,11 @@ export const claudeCommand = new Command('claude')
403
454
  }
404
455
  }
405
456
  if (!convGuidForHooks && cfg?.projectGuid) {
406
- const created = await post('/conversations/claude-code', { project_guid: cfg.projectGuid, device_guid: device.guid });
457
+ // Terminal `gipity claude` is always origin='local' marks the
458
+ // conversation as a local-terminal chat so the web CLI renders
459
+ // it read-only. Dispatch convs (created by the relay daemon
460
+ // spawning `gipity claude -p`) use the default 'dispatch'.
461
+ const created = await post('/conversations/claude-code', { project_guid: cfg.projectGuid, device_guid: device.guid, origin: 'local' });
407
462
  convGuidForHooks = created.data.conversation_guid;
408
463
  }
409
464
  }
@@ -488,8 +543,18 @@ export const claudeCommand = new Command('claude')
488
543
  // passes args through cmd.exe and mangles quotes/special chars.
489
544
  const claudeCmd = resolveCommand('claude');
490
545
  const childEnv = { ...process.env };
491
- if (convGuidForHooks)
546
+ // Gate hook-based capture: when the daemon is streaming via
547
+ // --output-format stream-json, it owns the capture and any hook
548
+ // post would be a dupe. Leaving GIPITY_CONVERSATION_GUID unset in
549
+ // the child's env trips the hook runner's early-return guard.
550
+ const streamJsonIdx = allArgs.indexOf('--output-format');
551
+ const daemonCapturing = streamJsonIdx !== -1 && allArgs[streamJsonIdx + 1] === 'stream-json';
552
+ if (daemonCapturing) {
553
+ delete childEnv.GIPITY_CONVERSATION_GUID;
554
+ }
555
+ else if (convGuidForHooks) {
492
556
  childEnv.GIPITY_CONVERSATION_GUID = convGuidForHooks;
557
+ }
493
558
  const child = spawn(claudeCmd, allArgs, {
494
559
  stdio: 'inherit',
495
560
  cwd: process.cwd(),
@@ -504,33 +569,103 @@ export const claudeCommand = new Command('claude')
504
569
  });
505
570
  async function pickOrCreateProject(projects, existingSlugs) {
506
571
  const filtered = projects.filter(p => !p.is_default);
507
- const recent = filtered.slice(0, 7);
508
- const hasMore = filtered.length > 7;
509
- console.log(` ${bold('Choose project to open:')}\n`);
572
+ const cwd = process.cwd();
573
+ const showAdopt = canAdoptCwd(cwd);
574
+ // Reserve slot 1 for "create new", slot 2 for "use this dir" when shown.
575
+ const reserved = showAdopt ? 2 : 1;
576
+ // Pickable single-keypress range is 1-9; leave one slot for "Show all"
577
+ // when there are more projects than fit.
578
+ const maxRecent = 9 - reserved - 1; // worst case: keep slot 9 free for "show all"
579
+ const recent = filtered.slice(0, maxRecent);
580
+ const hasMore = filtered.length > recent.length;
581
+ // Loop so that a refused/declined adopt-cwd re-shows the picker rather
582
+ // than dropping the user into a shell.
583
+ while (true) {
584
+ const newProjectLabel = formatNewProjectLabel(existingSlugs);
585
+ console.log(` ${bold('Choose project to open:')}\n`);
586
+ console.log(` ${bold('1.')} Create new project ${muted(`(${newProjectLabel})`)}`);
587
+ if (showAdopt) {
588
+ console.log(` ${bold('2.')} Use this directory ${muted(`(${formatCwdLabel(cwd)})`)}`);
589
+ }
590
+ recent.forEach((p, i) => console.log(` ${bold(`${i + reserved + 1}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
591
+ if (hasMore) {
592
+ console.log(` ${bold(`${recent.length + reserved + 1}.`)} Show all projects`);
593
+ }
594
+ console.log('');
595
+ const maxOption = recent.length + reserved + (hasMore ? 1 : 0);
596
+ const idx = await pickOne('Choose', maxOption, 1);
597
+ // Recent project (slots reserved+1 .. recent.length+reserved).
598
+ if (idx > reserved && idx <= recent.length + reserved) {
599
+ return { kind: 'pick', project: recent[idx - reserved - 1] };
600
+ }
601
+ // Show all projects (one past the last recent slot).
602
+ if (hasMore && idx === recent.length + reserved + 1) {
603
+ const picked = await pickFromAll(filtered);
604
+ if (picked)
605
+ return { kind: 'pick', project: picked };
606
+ continue; // user bailed; re-show top picker
607
+ }
608
+ // Slot 2 = "Use this directory" (only when shown).
609
+ if (showAdopt && idx === 2) {
610
+ const ok = await confirmAdoptCwd(cwd);
611
+ if (!ok) {
612
+ console.log('');
613
+ continue;
614
+ }
615
+ return { kind: 'adopt-cwd' };
616
+ }
617
+ // Default (Enter) or slot 1 = create new project.
618
+ const project = await createNewProject(existingSlugs);
619
+ return { kind: 'create-new', project };
620
+ }
621
+ }
622
+ /** Render "Show all projects" with numbered list; returns the picked
623
+ * project or null if the user picked "create new" (slot 1) or invalid. */
624
+ async function pickFromAll(filtered) {
625
+ console.log('');
626
+ console.log(` ${bold('All projects:')}\n`);
510
627
  console.log(` ${bold('1.')} Create new project`);
511
- recent.forEach((p, i) => console.log(` ${bold(`${i + 2}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
512
- if (hasMore)
513
- console.log(` ${bold(`${recent.length + 2}.`)} Show all projects`);
628
+ filtered.forEach((p, i) => console.log(` ${bold(`${i + 2}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
514
629
  console.log('');
515
- const maxOption = hasMore ? recent.length + 2 : recent.length + 1;
516
- const idx = await pickOne('Choose', maxOption, 1);
517
- // Selected a recent project
518
- if (idx >= 2 && idx <= recent.length + 1)
519
- return { project: recent[idx - 2], isNew: false };
520
- // Show all projects
521
- if (hasMore && idx === recent.length + 2) {
630
+ const allChoice = await prompt(` Choose (1-${filtered.length + 1}): `);
631
+ const allIdx = parseInt(allChoice, 10);
632
+ if (allIdx >= 2 && allIdx <= filtered.length + 1)
633
+ return filtered[allIdx - 2];
634
+ return null;
635
+ }
636
+ /** Show "(~/GipityProjects/project-NNN)" the exact dir option 1 will
637
+ * create, so the user can see where their new project will land. */
638
+ function formatNewProjectLabel(existingSlugs) {
639
+ const slug = suggestProjectName(existingSlugs);
640
+ const root = getProjectsRoot();
641
+ const home = homedir();
642
+ const display = root.startsWith(home + '/') || root === home
643
+ ? '~' + root.slice(home.length)
644
+ : root;
645
+ return `${display}/${slug}`;
646
+ }
647
+ /** Run the size-tier scan, surface the right UX:
648
+ * - easy → silent, return true.
649
+ * - moderate → confirm prompt with file count + size.
650
+ * - refuse → print explanation, return false (caller re-shows picker).
651
+ * Returns true if adoption should proceed. */
652
+ async function confirmAdoptCwd(cwd) {
653
+ process.stdout.write(` ${muted('Scanning directory...')} `);
654
+ const scan = scanForAdoption(cwd);
655
+ process.stdout.write('\r\x1b[K'); // clear the scanning line
656
+ if (scan.tier === 'refuse') {
657
+ const sizeStr = scan.truncated ? `>${formatBytes(ADOPT_THRESHOLDS.REFUSE_BYTES)}` : formatBytes(scan.bytes);
658
+ const fileStr = scan.truncated ? `>${ADOPT_THRESHOLDS.REFUSE_FILES}` : `${scan.files}`;
659
+ console.log(` ${clrError(`Directory has ${fileStr} files (${sizeStr}) — too large to adopt as a Gipity project.`)}`);
660
+ console.log(` ${muted('Move into a subdirectory, or use option 1 to create a fresh project under ~/GipityProjects/.')}`);
522
661
  console.log('');
523
- console.log(` ${bold('All projects:')}\n`);
524
- console.log(` ${bold('1.')} Create new project`);
525
- filtered.forEach((p, i) => console.log(` ${bold(`${i + 2}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
662
+ return false;
663
+ }
664
+ if (scan.tier === 'moderate') {
526
665
  console.log('');
527
- const allChoice = await prompt(` Choose (1-${filtered.length + 1}): `);
528
- const allIdx = parseInt(allChoice, 10);
529
- if (allIdx >= 2 && allIdx <= filtered.length + 1)
530
- return { project: filtered[allIdx - 2], isNew: false };
666
+ return confirm(` About to adopt ${bold(String(scan.files))} files (${bold(formatBytes(scan.bytes))}) at ${bold(formatCwdLabel(cwd))}. Continue?`, { default: 'yes' });
531
667
  }
532
- // Default (Enter) or 1 = create new project
533
- return { project: await createNewProject(existingSlugs), isNew: true };
668
+ return true;
534
669
  }
535
670
  async function createNewProject(existingSlugs) {
536
671
  const taken = new Set(existingSlugs);
@@ -553,7 +688,7 @@ async function createNewProject(existingSlugs) {
553
688
  suggested = suggestProjectName([...taken]);
554
689
  continue;
555
690
  }
556
- console.log(` ${info(`Creating "${projectName}"...`)}`);
691
+ process.stdout.write(` ${info(`Creating "${projectName}"...`)}`);
557
692
  try {
558
693
  const device = relayState.getDevice();
559
694
  const body = { name: projectName, slug: projectSlug };
@@ -562,11 +697,12 @@ async function createNewProject(existingSlugs) {
562
697
  body.deviceGuid = device.guid;
563
698
  }
564
699
  const res = await post('/projects', body);
565
- console.log(` ${success('Created.')}`);
700
+ console.log(` ${success('Created.')}`);
566
701
  return res.data;
567
702
  }
568
703
  catch (err) {
569
704
  if (err instanceof ApiError && err.statusCode === 409) {
705
+ console.log('');
570
706
  console.error(` ${clrError(`"${projectSlug}" was just taken. Pick a different name.`)}`);
571
707
  taken.add(projectSlug);
572
708
  suggested = suggestProjectName([...taken]);