gipity 1.0.407 → 1.0.409

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 (42) hide show
  1. package/dist/banner.js +16 -1
  2. package/dist/commands/add.js +1 -1
  3. package/dist/commands/approval.js +1 -1
  4. package/dist/commands/claude.js +9 -9
  5. package/dist/commands/email.js +2 -1
  6. package/dist/commands/gmail.js +2 -1
  7. package/dist/commands/init.js +2 -1
  8. package/dist/commands/job.js +2 -1
  9. package/dist/commands/login.js +1 -0
  10. package/dist/commands/notify.js +2 -1
  11. package/dist/commands/page.js +1 -1
  12. package/dist/commands/payments.js +2 -1
  13. package/dist/commands/plan.js +1 -1
  14. package/dist/commands/remove.js +1 -1
  15. package/dist/commands/secrets.js +2 -1
  16. package/dist/commands/service.js +2 -1
  17. package/dist/commands/sync.js +2 -1
  18. package/dist/commands/test.js +6 -0
  19. package/dist/commands/text.js +2 -1
  20. package/dist/commands/token.js +2 -1
  21. package/dist/config.js +4 -4
  22. package/dist/helpers/output.js +42 -13
  23. package/dist/knowledge.js +2 -0
  24. package/dist/platform.js +53 -6
  25. package/dist/relay/daemon.js +8 -30
  26. package/dist/relay/installers.js +10 -2
  27. package/dist/setup.js +4 -4
  28. package/dist/updater/bootstrap.js +3 -4
  29. package/dist/updater/check.js +2 -3
  30. package/package.json +3 -4
  31. package/dist/coding-guidelines.js +0 -52
  32. package/dist/commands/browser.js +0 -84
  33. package/dist/commands/checkpoint.js +0 -68
  34. package/dist/commands/hook-capture.js +0 -215
  35. package/dist/commands/scaffold.js +0 -58
  36. package/dist/commands/skills.js +0 -45
  37. package/dist/commands/start-cc.js +0 -313
  38. package/dist/gip3dw-guide.js +0 -18
  39. package/dist/platform-overview.js +0 -52
  40. package/dist/relay/transcripts.js +0 -119
  41. package/hooks/post-write.sh +0 -17
  42. package/hooks/pre-turn.sh +0 -20
@@ -1,313 +0,0 @@
1
- import { Command } from 'commander';
2
- import { join } from 'path';
3
- import { mkdirSync } from 'fs';
4
- import { execSync, spawn } from 'child_process';
5
- import { homedir } from 'os';
6
- /** On Windows, spawn without shell:true needs an explicit extension (.exe or .cmd) */
7
- function resolveCommand(cmd) {
8
- if (process.platform !== 'win32')
9
- return cmd;
10
- try {
11
- const lines = execSync(`where ${cmd}`, { encoding: 'utf-8' }).split(/\r?\n/).map(l => l.trim()).filter(Boolean);
12
- // Prefer .exe (native) over .cmd (npm shim)
13
- return lines.find(l => l.endsWith('.exe')) || lines.find(l => l.endsWith('.cmd')) || cmd;
14
- }
15
- catch {
16
- return `${cmd}.cmd`;
17
- }
18
- }
19
- import { getAuth, saveAuth } from '../auth.js';
20
- import { get, post, publicPost } from '../api.js';
21
- import { getConfig, saveConfig, clearConfigCache, getApiBaseOverride } from '../config.js';
22
- import { syncDown, syncUp } from '../sync.js';
23
- import { slugify, setupClaudeHooks, setupClaudeMd, setupGitignore } from '../setup.js';
24
- import { prompt, pickOne, decodeJwtExp } from '../utils.js';
25
- import { brand, bold, faint, info, success, error as clrError, muted } from '../colors.js';
26
- const PROJECTS_ROOT = join(homedir(), 'GipityProjects');
27
- function suggestProjectName(existingSlugs) {
28
- const slugSet = new Set(existingSlugs);
29
- for (let i = 1; i <= 99; i++) {
30
- const candidate = `project${String(i).padStart(2, '0')}`;
31
- if (!slugSet.has(candidate))
32
- return candidate;
33
- }
34
- return `project-${Date.now().toString(36).slice(-6)}`;
35
- }
36
- export const startCcCommand = new Command('start-cc')
37
- .description('Log in, set up a project, and launch Claude Code')
38
- .option('--no-claude', 'Set up project but skip launching Claude Code')
39
- .allowUnknownOption(true)
40
- .allowExcessArguments(true)
41
- .action(async (opts) => {
42
- try {
43
- console.log(`\n ${brand(bold('Welcome to Gipity'))}`);
44
- console.log(` ${faint('─────────────────')}`);
45
- console.log('');
46
- // ── Step 1: Auth ──────────────────────────────────────────────────
47
- let auth = getAuth();
48
- if (auth) {
49
- console.log(` Logged in as ${auth.email}`);
50
- }
51
- else {
52
- console.log(' Let\'s get you logged in.\n');
53
- const email = await prompt(' Email: ');
54
- if (!email) {
55
- console.error(`\n ${clrError('Email required.')}`);
56
- process.exit(1);
57
- }
58
- await publicPost('/auth/login', { email });
59
- console.log(' Check your email for a 6-digit code.\n');
60
- const code = await prompt(' Code: ');
61
- if (!code) {
62
- console.error(`\n ${clrError('Code required.')}`);
63
- process.exit(1);
64
- }
65
- const res = await publicPost('/auth/verify', { email, code });
66
- const exp = decodeJwtExp(res.accessToken);
67
- if (!exp) {
68
- console.error(`\n ${clrError('Invalid token received.')}`);
69
- process.exit(1);
70
- }
71
- const expiresAt = new Date(exp * 1000).toISOString();
72
- saveAuth({ accessToken: res.accessToken, refreshToken: res.refreshToken, email, expiresAt });
73
- auth = getAuth();
74
- console.log(` ${success(`Logged in as ${email}`)}`);
75
- }
76
- console.log('');
77
- // ── Step 2: Project ───────────────────────────────────────────────
78
- let initialPrompt = '';
79
- // If cwd already has .gipity.json, use it (user ran from inside a project)
80
- const existing = getConfig();
81
- if (existing) {
82
- console.log(` Project: ${brand(existing.projectSlug)} ${muted(`(${existing.projectGuid})`)}`);
83
- console.log(` ${success('Already set up.')}\n`);
84
- setupClaudeHooks();
85
- setupClaudeMd();
86
- setupGitignore();
87
- }
88
- else {
89
- // Fetch user's projects
90
- let projects = [];
91
- let fetchFailed = false;
92
- try {
93
- const res = await get('/projects?limit=100');
94
- projects = res.data;
95
- }
96
- catch (err) {
97
- fetchFailed = true;
98
- const isConnectionError = err?.code === 'ECONNREFUSED' || err?.code === 'ENOTFOUND' ||
99
- err?.code === 'ETIMEDOUT' || err?.cause?.code === 'ECONNREFUSED' ||
100
- err?.cause?.code === 'ENOTFOUND' || err?.cause?.code === 'ETIMEDOUT';
101
- if (isConnectionError) {
102
- const apiBase = getApiBaseOverride() || 'https://a.gipity.ai';
103
- console.error(` ${clrError(`Could not connect to ${apiBase}`)}`);
104
- console.error(` ${muted('Check your connection and try again.')}`);
105
- process.exit(1);
106
- }
107
- // Non-connection error (e.g. 401) — fall through to create
108
- }
109
- const existingSlugs = projects.map(p => p.slug);
110
- let project;
111
- let isNewProject = false;
112
- if (projects.length > 0) {
113
- const result = await pickOrCreateProject(projects, existingSlugs);
114
- project = result.project;
115
- isNewProject = result.isNew;
116
- }
117
- else if (!fetchFailed) {
118
- project = await createNewProject(existingSlugs);
119
- isNewProject = true;
120
- }
121
- else {
122
- console.error(` ${clrError('Could not load projects. Please try again.')}`);
123
- process.exit(1);
124
- }
125
- // Resolve project directory under ~/GipityProjects/{slug}
126
- const projectDir = join(PROJECTS_ROOT, project.slug);
127
- mkdirSync(projectDir, { recursive: true });
128
- process.chdir(projectDir);
129
- clearConfigCache();
130
- // Fetch agents
131
- let agentGuid = '';
132
- try {
133
- const agents = await get(`/projects/${project.short_guid}/agents`);
134
- if (agents.data.length > 0)
135
- agentGuid = agents.data[0].short_guid;
136
- }
137
- catch {
138
- // No agents
139
- }
140
- const accountSlug = project.user?.account_slug || '';
141
- // Always write config (refresh stale GUIDs from a previous setup)
142
- saveConfig({
143
- projectGuid: project.short_guid,
144
- projectSlug: project.slug,
145
- accountSlug,
146
- agentGuid,
147
- conversationGuid: null,
148
- apiBase: getApiBaseOverride() || 'https://a.gipity.ai',
149
- ignore: ['node_modules', '.git', '.gipity.json', '.gipity/', '.claude/', '.gitignore', 'CLAUDE.md', '*.log'],
150
- });
151
- console.log(`\n Using ${projectDir}`);
152
- // Sync: push local files up first, then pull any remote-only files down (non-fatal)
153
- try {
154
- const upResult = await syncUp();
155
- if (upResult.pushed > 0) {
156
- console.log(` Pushed ${upResult.pushed} file${upResult.pushed > 1 ? 's' : ''} to Gipity.`);
157
- }
158
- const downResult = await syncDown({ confirmDeletions: true });
159
- if (downResult.pulled > 0) {
160
- console.log(` Pulled ${downResult.pulled} file${downResult.pulled > 1 ? 's' : ''} from Gipity.`);
161
- }
162
- }
163
- catch {
164
- console.log(' Could not sync files (will retry on next prompt).');
165
- }
166
- // ── Step 2b: Project type (new projects only) ───────────────────
167
- if (isNewProject) {
168
- const projectType = await pickProjectType();
169
- initialPrompt = projectType.initialPrompt;
170
- if (projectType.scaffoldType) {
171
- console.log(` Scaffolding ${projectType.label}...`);
172
- try {
173
- await post(`/projects/${project.short_guid}/scaffold`, {
174
- title: project.name,
175
- type: projectType.scaffoldType,
176
- });
177
- const scaffoldSync = await syncDown();
178
- if (scaffoldSync.pulled > 0) {
179
- console.log(` Created ${scaffoldSync.pulled} starter file${scaffoldSync.pulled > 1 ? 's' : ''}.`);
180
- }
181
- }
182
- catch {
183
- console.log(' Scaffolding failed — starting with empty project.');
184
- }
185
- }
186
- }
187
- setupClaudeHooks();
188
- setupClaudeMd();
189
- setupGitignore();
190
- console.log(` ${success(`Project "${project.name}" ready.`)}\n`);
191
- }
192
- // ── Step 3: Launch Claude Code ────────────────────────────────────
193
- if (opts.claude === false) {
194
- console.log(` Done. cd ${process.cwd()} && claude`);
195
- return;
196
- }
197
- // Check claude is installed
198
- try {
199
- const checkCmd = process.platform === 'win32' ? 'where claude' : 'which claude';
200
- execSync(checkCmd, { stdio: 'ignore' });
201
- }
202
- catch {
203
- console.log(' Claude Code not found. Install it: npm install -g @anthropic-ai/claude-code');
204
- console.log(` Then: cd ${process.cwd()} && claude`);
205
- return;
206
- }
207
- // Pass through all unknown args to claude (everything after 'start-cc')
208
- const startCcIdx = process.argv.indexOf('start-cc');
209
- const knownFlags = ['--no-claude', '--api-base'];
210
- const claudeArgs = process.argv.slice(startCcIdx + 1).filter(arg => {
211
- for (const flag of knownFlags) {
212
- if (arg === flag)
213
- return false;
214
- if (arg.startsWith('--api-base='))
215
- return false;
216
- }
217
- return true;
218
- });
219
- // Filter out the value after --api-base if space-separated and after start-cc
220
- const apiBaseIdx = process.argv.indexOf('--api-base', startCcIdx);
221
- if (apiBaseIdx !== -1) {
222
- const valueIdx = claudeArgs.indexOf(process.argv[apiBaseIdx + 1]);
223
- if (valueIdx !== -1)
224
- claudeArgs.splice(valueIdx, 1);
225
- }
226
- console.log(` ${info('Launching Claude Code...')}\n`);
227
- const allArgs = initialPrompt
228
- ? [initialPrompt, ...claudeArgs]
229
- : claudeArgs;
230
- // Resolve full path on Windows so we can avoid shell:true, which
231
- // passes args through cmd.exe and mangles quotes/special chars.
232
- const claudeCmd = resolveCommand('claude');
233
- const child = spawn(claudeCmd, allArgs, {
234
- stdio: 'inherit',
235
- cwd: process.cwd(),
236
- });
237
- child.on('exit', (code) => process.exit(code ?? 0));
238
- }
239
- catch (err) {
240
- console.error(`\n ${clrError(`Error: ${err.message}`)}`);
241
- process.exit(1);
242
- }
243
- });
244
- async function pickOrCreateProject(projects, existingSlugs) {
245
- const recent = projects.slice(0, 7);
246
- const hasMore = projects.length > 7;
247
- console.log(` ${bold('Choose project to open:')}\n`);
248
- console.log(` ${bold('1.')} Create new project`);
249
- recent.forEach((p, i) => console.log(` ${bold(`${i + 2}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
250
- if (hasMore)
251
- console.log(` ${bold(`${recent.length + 2}.`)} Show all projects`);
252
- console.log('');
253
- const maxOption = hasMore ? recent.length + 2 : recent.length + 1;
254
- const idx = await pickOne('Choose', maxOption, 1);
255
- // Selected a recent project
256
- if (idx >= 2 && idx <= recent.length + 1)
257
- return { project: recent[idx - 2], isNew: false };
258
- // Show all projects
259
- if (hasMore && idx === recent.length + 2) {
260
- console.log('');
261
- console.log(` ${bold('All projects:')}`);
262
- projects.forEach((p, i) => console.log(` ${bold(`${i + 1}.`)} ${p.name} ${muted(`(${p.slug})`)}`));
263
- console.log('');
264
- const allChoice = await prompt(` Choose (1-${projects.length}): `);
265
- const allIdx = parseInt(allChoice, 10);
266
- if (allIdx >= 1 && allIdx <= projects.length)
267
- return { project: projects[allIdx - 1], isNew: false };
268
- }
269
- // Default (Enter) or 1 = create new project
270
- return { project: await createNewProject(existingSlugs), isNew: true };
271
- }
272
- async function createNewProject(existingSlugs) {
273
- const suggested = suggestProjectName(existingSlugs);
274
- console.log('');
275
- const name = await prompt(` ${bold('Project name')} [${bold(suggested)}]: `);
276
- const projectName = name || suggested;
277
- const projectSlug = slugify(projectName);
278
- if (!projectSlug) {
279
- console.error(` ${clrError('Invalid project name.')}`);
280
- process.exit(1);
281
- }
282
- console.log(` ${info(`Creating "${projectName}"...`)}`);
283
- const res = await post('/projects', { name: projectName, slug: projectSlug });
284
- console.log(` ${success('Created.')}`);
285
- return res.data;
286
- }
287
- const PROJECT_TYPE_OPTIONS = [
288
- {
289
- label: 'Start empty',
290
- initialPrompt: 'This is a new empty project on Gipity. Briefly introduce yourself, mention that I have access to cloud hosting, databases, server-side API endpoints, sandboxed code execution, image generation, speech, and web search, and ask me what I want to build.',
291
- },
292
- {
293
- label: 'Web app',
294
- scaffoldType: 'web',
295
- initialPrompt: 'This is a new web app project on Gipity. The project has been scaffolded with starter HTML/CSS/JS. Deploy it to dev so I can see it live. Then briefly introduce yourself, mention that I have access to databases, server-side API endpoints, image generation, web search, and cloud hosting, and ask me what I want to build.',
296
- },
297
- {
298
- label: '3D World game',
299
- scaffoldType: '3d-world',
300
- initialPrompt: 'This is a new 3D World game on Gipity. Deploy it to dev so I can play it right away. Then briefly introduce yourself, mention that I can customize the world, objects, physics, game logic, and multiplayer, and ask me what kind of game I want to make.',
301
- },
302
- ];
303
- async function pickProjectType() {
304
- console.log('');
305
- console.log(` ${bold('What kind of project?')}\n`);
306
- console.log(` ${bold('1.')} Start empty. ${muted('Build everything from scratch.')}`);
307
- console.log(` ${bold('2.')} Web app or game. ${muted('Scaffolds basic HTML/CSS/JS starter files.')}`);
308
- console.log(` ${bold('3.')} 3D World game. ${muted('Scaffolds a playable starter world.')}`);
309
- console.log('');
310
- const idx = await pickOne('Choose', 3, 1);
311
- return PROJECT_TYPE_OPTIONS[idx - 1];
312
- }
313
- //# sourceMappingURL=start-cc.js.map
@@ -1,18 +0,0 @@
1
- /**
2
- * Shared 3D World template description.
3
- * Single source of truth used by:
4
- * - server/src/services/skills/ (web agent skills)
5
- * - cli/src/setup.ts (CLAUDE.md template for Claude Code)
6
- */
7
- export const GIP_3DW_GUIDE = `## 3D World
8
-
9
- **3D World** is the 3D multiplayer game template on Gipity. All 3D World games share the same visual style, physics engine (Rapier), and multiplayer backend (Colyseus). All files are fully editable.
10
-
11
- Scaffold a 3D World project with \`app_scaffold type=3d-world\` (web agent) or \`gipity scaffold --type 3d-world\` (CLI). This creates a playable 3D game with Three.js + Rapier physics + Colyseus multiplayer. Key files: \`config.js\` (metadata), \`settings.js\` (tunable values), \`strings.js\` (display text), \`objects.js\` (entity factories), \`game.js\` (orchestrator), plus engine files (\`core.js\`, \`world.js\`, \`physics.js\`, etc.).
12
-
13
- **Genres:** obby/parkour, tycoon, simulator, PvP combat, shooter, tower defense, horror, racing, RPG, social.
14
-
15
- **Features:** Opt-in gameplay modules enabled via \`config.features\`. Available: \`rocket-launcher\` (projectile weapon with physics explosions). Example: \`features: { 'rocket-launcher': true }\` in config.js. Features auto-initialize during boot.
16
-
17
- Regular game requests ("make a wordle", "build a quiz") should use the standard web scaffold — they don't need the 3D template.`;
18
- //# sourceMappingURL=gip3dw-guide.js.map
@@ -1,52 +0,0 @@
1
- /**
2
- * Shared Gipity platform capability descriptions.
3
- * Single source of truth used by:
4
- * - server/src/services/skills/ (web agent skills)
5
- * - tools/gipity-cli/src/setup.ts (CLAUDE.md template for Claude Code)
6
- */
7
- export const PLATFORM_SERVICES = `## Platform Services
8
-
9
- Capabilities beyond local file editing — use \`gipity chat\` (CLI) or the built-in tools (web agent):
10
-
11
- - **Image generation**: OpenAI (gpt-image-1, DALL-E 3) and BFL/Flux
12
- - **Speech / TTS**: ElevenLabs and OpenAI voices (streaming and batch)
13
- - **Sound effects / Music**: ElevenLabs audio generation
14
- - **Audio processing**: Transcription, source isolation
15
- - **Web search**: Brave API
16
- - **Twitter/X search**: v2 API, last 7 days
17
- - **Browser automation**: Open URLs, screenshot, click, fill forms, read console
18
- - **Workflow automation**: Cron-scheduled or webhook-triggered multi-step AI pipelines
19
- - **Email**: SendGrid transactional email
20
- - **Google services**: Gmail, Google Calendar integration
21
- - **External services**: Slack, GitHub, Todoist, Notion via service connectors
22
- - **Cross-model queries**: Ask GPT-5, Claude, etc. for second opinions
23
- - **Serverless functions**: JavaScript functions callable via REST`;
24
- export const CLI_COMMANDS = `## CLI Commands
25
-
26
- | Command | Purpose |
27
- |---------|---------|
28
- | \`gipity scaffold [title]\` | Create app structure (\`--type web\` default, or \`--type 3d-world\` for 3D games) |
29
- | \`gipity deploy [dev\\|prod]\` | Deploy and get live URL |
30
- | \`gipity sync [up\\|down\\|check]\` | Manual file sync |
31
- | \`gipity db create <name>\` | Create a project database |
32
- | \`gipity db query "SQL"\` | Run SQL on project database |
33
- | \`gipity db list\` | List databases |
34
- | \`gipity memory list\\|read\\|write\` | Persistent key-value memory |
35
- | \`gipity api list\\|define\\|get\` | Manage backend API procedures |
36
- | \`gipity checkpoint list\` | List file snapshots |
37
- | \`gipity checkpoint restore <id>\` | Restore files to a snapshot (undo) |
38
- | \`gipity logs fn <name>\` | View function execution logs (errors, timing) |
39
- | \`gipity browser <url>\` | Inspect a URL: console errors, timing, failed resources |
40
- | \`gipity status\` | Check project and sync status |
41
-
42
- All commands support \`--json\` for structured output.`;
43
- export const DEPLOY_VERIFICATION = `## Deploy Verification
44
-
45
- Use the browser tool to verify deploys when it matters — first deploy, structural changes (new pages, new frameworks, changed imports), or when something might have broken. Skip verification for trivial changes (copy tweaks, style adjustments, config values).
46
-
47
- To verify: \`browser action=open url=<deployed-url>\` — waits for async modules, captures console errors automatically. Check output for \`[Console errors captured after page load]\`. Use \`browser action=screenshot\` to confirm visual correctness.
48
-
49
- **Debugging in production:** Add \`console.error()\` calls to app code for diagnostics, redeploy, then use \`browser action=console\` to read the output. Remove debug logging when done.
50
-
51
- **Screenshots via \`gipity chat\`**: For 3D games and pages with async rendering (WebGL, canvas, dynamic content), tell the agent to wait a few seconds after opening before capturing so the scene renders. The saved filename may differ from what you requested (collision-free naming). Always check the tool output for the actual saved path.`;
52
- //# sourceMappingURL=platform-overview.js.map
@@ -1,119 +0,0 @@
1
- /**
2
- * Shared helpers for reading Claude Code's JSONL transcript and mapping its
3
- * entries into our `/ingest` wire format. Used by:
4
- *
5
- * - `hook-capture.ts` (Stop hook) — streams deltas live while Claude runs
6
- * - `daemon.ts` (post-spawn sweep) — catches anything the hook dropped
7
- *
8
- * Both paths share one offset file at `.gipity/transcripts/<session_id>.offset`
9
- * so whichever path posts first owns the delta; the other sees an empty read
10
- * and no-ops. Ordering is guaranteed: hooks run inside the `gipity claude`
11
- * child process, the sweep runs after that child has exited.
12
- */
13
- import { readFileSync, writeFileSync, existsSync, statSync, mkdirSync } from 'fs';
14
- import { resolve, join, dirname } from 'path';
15
- import { homedir } from 'os';
16
- // ─── offset file (shared lock-step between hook and sweep) ─────────────
17
- export function offsetPath(sessionId) {
18
- return resolve(process.cwd(), '.gipity', 'transcripts', `${sessionId}.offset`);
19
- }
20
- export function readOffset(sessionId) {
21
- try {
22
- return parseInt(readFileSync(offsetPath(sessionId), 'utf-8'), 10) || 0;
23
- }
24
- catch {
25
- return 0;
26
- }
27
- }
28
- export function writeOffset(sessionId, offset) {
29
- const p = offsetPath(sessionId);
30
- mkdirSync(dirname(p), { recursive: true });
31
- writeFileSync(p, String(offset));
32
- }
33
- export function latestSessionPath(convGuid) {
34
- return resolve(process.cwd(), '.gipity', 'transcripts', `latest-${convGuid}.json`);
35
- }
36
- export function writeLatestSession(cwd, convGuid, rec) {
37
- const p = join(cwd, '.gipity', 'transcripts', `latest-${convGuid}.json`);
38
- mkdirSync(dirname(p), { recursive: true });
39
- writeFileSync(p, JSON.stringify(rec, null, 2));
40
- }
41
- export function readLatestSession(cwd, convGuid) {
42
- try {
43
- const p = join(cwd, '.gipity', 'transcripts', `latest-${convGuid}.json`);
44
- if (!existsSync(p))
45
- return null;
46
- const parsed = JSON.parse(readFileSync(p, 'utf-8'));
47
- if (typeof parsed?.session_id !== 'string')
48
- return null;
49
- return parsed;
50
- }
51
- catch {
52
- return null;
53
- }
54
- }
55
- // ─── delta reader ──────────────────────────────────────────────────────
56
- /** Parse JSONL delta from `transcriptPath` starting at the stored offset
57
- * for `sessionId`. The caller advances the offset (via `writeOffset`)
58
- * ONLY after the ingest POST succeeds — a failed POST leaves the offset
59
- * behind so the next reader picks up the same delta. */
60
- export function readTranscriptDelta(transcriptPath, sessionId) {
61
- if (!existsSync(transcriptPath))
62
- return { entries: [], newOffset: 0 };
63
- const size = statSync(transcriptPath).size;
64
- let offset = readOffset(sessionId);
65
- if (offset > size)
66
- offset = 0; // file regressed (rotated/truncated) — rescan
67
- if (offset === size)
68
- return { entries: [], newOffset: offset };
69
- const fd = readFileSync(transcriptPath);
70
- const slice = fd.slice(offset).toString('utf-8');
71
- const lines = slice.split('\n').filter((l) => l.trim());
72
- const entries = [];
73
- for (const line of lines) {
74
- try {
75
- entries.push(JSON.parse(line));
76
- }
77
- catch { /* skip partial */ }
78
- }
79
- return { entries, newOffset: size };
80
- }
81
- // ─── JSONL → ingest mapping ────────────────────────────────────────────
82
- /** Pull plain text out of an assistant entry's `message.content` array. */
83
- function joinAssistantText(entry) {
84
- const content = entry?.message?.content;
85
- if (!Array.isArray(content))
86
- return { text: '', blocks: null };
87
- const parts = [];
88
- for (const block of content) {
89
- if (block?.type === 'text' && typeof block.text === 'string')
90
- parts.push(block.text);
91
- }
92
- return { text: parts.join('\n'), blocks: content };
93
- }
94
- /** Convert Claude's raw JSONL entries into our ingest format. We only
95
- * emit `assistant` entries here — user prompts and tool calls come in via
96
- * dedicated hooks (UserPromptSubmit, PostToolUse), so pulling them from
97
- * the transcript would duplicate. If those hooks fail we accept partial
98
- * recovery (the assistant turn is the main thing the user sees). */
99
- export function mapEntriesToIngest(entries) {
100
- const out = [];
101
- for (const entry of entries) {
102
- if (entry?.type !== 'assistant')
103
- continue;
104
- const { text, blocks } = joinAssistantText(entry);
105
- if (!text && (!blocks || blocks.length === 0))
106
- continue;
107
- out.push({ kind: 'assistant', text, blocks: blocks ?? [] });
108
- }
109
- return out;
110
- }
111
- // ─── transcript path resolver (mirror of daemon.ts's helper) ───────────
112
- // Claude Code encodes the project cwd into a slug by replacing `/` with
113
- // `-`. Kept in sync with daemon's `transcriptPathFor` — both callers
114
- // import from here once daemon.ts is refactored.
115
- export function transcriptPathFor(cwd, sessionId) {
116
- const slug = cwd.replace(/\//g, '-');
117
- return join(homedir(), '.claude', 'projects', slug, `${sessionId}.jsonl`);
118
- }
119
- //# sourceMappingURL=transcripts.js.map
@@ -1,17 +0,0 @@
1
- #!/bin/bash
2
- # PostToolUse hook: push file to Gipity after Write/Edit
3
- # Runs after CC's Write or Edit tool. Fires gipity push in background.
4
-
5
- INPUT=$(cat)
6
- FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')
7
-
8
- # Skip if no file path extracted
9
- [ -z "$FILE_PATH" ] && exit 0
10
-
11
- # Skip if not a Gipity project
12
- [ ! -f ".gipity.json" ] && exit 0
13
-
14
- # Push in background, suppress output
15
- gipity push "$FILE_PATH" --quiet &
16
- disown
17
- exit 0
package/hooks/pre-turn.sh DELETED
@@ -1,20 +0,0 @@
1
- #!/bin/bash
2
- # UserPromptSubmit hook: sync check before each CC turn
3
- # Pulls remote changes and reports diff as systemMessage.
4
-
5
- # Skip if not a Gipity project
6
- [ ! -f ".gipity.json" ] && exit 0
7
-
8
- # Sync down with JSON output
9
- RESULT=$(gipity sync down --json 2>/dev/null)
10
-
11
- # Check if any files were pulled
12
- PULLED=$(echo "$RESULT" | jq -r '.pulled // 0' 2>/dev/null)
13
-
14
- if [ "$PULLED" -gt 0 ]; then
15
- SUMMARY=$(echo "$RESULT" | jq -r '.summary // "Files changed remotely."' 2>/dev/null)
16
- # Output systemMessage so CC sees the diff
17
- echo "{\"systemMessage\": \"Gipity sync: ${SUMMARY}\"}"
18
- fi
19
-
20
- exit 0