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.
- package/dist/__tests__/capture-transcript.test.d.ts +1 -0
- package/dist/__tests__/capture-transcript.test.js +92 -0
- package/dist/__tests__/capture-transcript.test.js.map +1 -0
- package/dist/__tests__/cli-smoke.test.js +5 -5
- package/dist/__tests__/cli-smoke.test.js.map +1 -1
- package/dist/__tests__/prompts.test.d.ts +1 -0
- package/dist/__tests__/prompts.test.js +129 -0
- package/dist/__tests__/prompts.test.js.map +1 -0
- package/dist/__tests__/push-cas.test.d.ts +1 -0
- package/dist/__tests__/push-cas.test.js +133 -0
- package/dist/__tests__/push-cas.test.js.map +1 -0
- package/dist/__tests__/sync-apply.test.d.ts +1 -0
- package/dist/__tests__/sync-apply.test.js +457 -0
- package/dist/__tests__/sync-apply.test.js.map +1 -0
- package/dist/__tests__/sync-lock.test.d.ts +1 -0
- package/dist/__tests__/sync-lock.test.js +105 -0
- package/dist/__tests__/sync-lock.test.js.map +1 -0
- package/dist/__tests__/sync.test.js +115 -151
- package/dist/__tests__/sync.test.js.map +1 -1
- package/dist/adopt-cwd.d.ts +66 -0
- package/dist/adopt-cwd.js +255 -0
- package/dist/adopt-cwd.js.map +1 -0
- package/dist/api.d.ts +11 -1
- package/dist/api.js +46 -3
- package/dist/api.js.map +1 -1
- package/dist/capture/sources/claude-code.d.ts +78 -0
- package/dist/capture/sources/claude-code.js +158 -0
- package/dist/capture/sources/claude-code.js.map +1 -0
- package/dist/commands/chat.js +8 -8
- package/dist/commands/chat.js.map +1 -1
- package/dist/commands/claude.js +265 -129
- package/dist/commands/claude.js.map +1 -1
- package/dist/commands/init.js +34 -59
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/location.js +1 -1
- package/dist/commands/location.js.map +1 -1
- package/dist/commands/logout.js +1 -1
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/page-inspect.js +36 -25
- package/dist/commands/page-inspect.js.map +1 -1
- package/dist/commands/page-screenshot.d.ts +2 -0
- package/dist/commands/page-screenshot.js +212 -0
- package/dist/commands/page-screenshot.js.map +1 -0
- package/dist/commands/project.js +71 -12
- package/dist/commands/project.js.map +1 -1
- package/dist/commands/relay.js +1 -1
- package/dist/commands/relay.js.map +1 -1
- package/dist/commands/scaffold.js +19 -13
- package/dist/commands/scaffold.js.map +1 -1
- package/dist/commands/sync.js +15 -24
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/uninstall.js +1 -1
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +1 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/config.d.ts +9 -0
- package/dist/config.js +9 -0
- package/dist/config.js.map +1 -1
- package/dist/helpers/sync.d.ts +4 -2
- package/dist/helpers/sync.js +13 -7
- package/dist/helpers/sync.js.map +1 -1
- package/dist/hooks/capture-runner.d.ts +24 -0
- package/dist/hooks/capture-runner.js +233 -0
- package/dist/hooks/capture-runner.js.map +1 -0
- package/dist/index.js +17 -16
- package/dist/index.js.map +1 -1
- package/dist/project-setup.d.ts +21 -0
- package/dist/project-setup.js +42 -0
- package/dist/project-setup.js.map +1 -0
- package/dist/prompts.d.ts +7 -0
- package/dist/prompts.js +25 -2
- package/dist/prompts.js.map +1 -1
- package/dist/relay/daemon.d.ts +8 -8
- package/dist/relay/daemon.js +23 -61
- package/dist/relay/daemon.js.map +1 -1
- package/dist/relay/device-http.d.ts +9 -0
- package/dist/relay/device-http.js +58 -0
- package/dist/relay/device-http.js.map +1 -0
- package/dist/relay/onboarding.js +3 -2
- package/dist/relay/onboarding.js.map +1 -1
- package/dist/setup.d.ts +8 -0
- package/dist/setup.js +55 -16
- package/dist/setup.js.map +1 -1
- package/dist/sync.d.ts +55 -37
- package/dist/sync.js +628 -445
- package/dist/sync.js.map +1 -1
- package/dist/updater/bootstrap.d.ts +6 -1
- package/dist/updater/bootstrap.js +21 -13
- package/dist/updater/bootstrap.js.map +1 -1
- package/dist/updater/shim.js +12 -1
- package/dist/updater/shim.js.map +1 -1
- package/dist/upload.d.ts +13 -2
- package/dist/upload.js +59 -6
- package/dist/upload.js.map +1 -1
- package/package.json +2 -2
package/dist/commands/chat.js
CHANGED
|
@@ -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 {
|
|
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
|
|
31
|
-
if (syncResult.
|
|
32
|
-
syncSummary = `\
|
|
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.
|
|
35
|
-
path:
|
|
36
|
-
type:
|
|
37
|
-
...(
|
|
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,
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/claude.js
CHANGED
|
@@ -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 {
|
|
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('
|
|
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
|
-
|
|
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
|
|
223
|
-
if (
|
|
224
|
-
console.log(`
|
|
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
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
//
|
|
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
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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('
|
|
325
|
-
console.log(
|
|
326
|
-
console.log(` ${
|
|
327
|
-
console.log(` ${muted('
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
508
|
-
const
|
|
509
|
-
|
|
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
|
-
|
|
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
|
|
516
|
-
const
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
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
|
-
|
|
524
|
-
|
|
525
|
-
|
|
662
|
+
return false;
|
|
663
|
+
}
|
|
664
|
+
if (scan.tier === 'moderate') {
|
|
526
665
|
console.log('');
|
|
527
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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]);
|