gekto 0.0.13 → 0.0.14

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.
@@ -6,6 +6,7 @@ import { getState, mutate, mutateBatch, addClient, removeClient, sendSnapshot, g
6
6
  import { persistEntity } from '../entityStore.js';
7
7
  import fs from 'fs';
8
8
  import nodePath from 'path';
9
+ import { getPostHog, getDistinctId } from '../posthog.js';
9
10
  let gektoInitialized = false;
10
11
  function broadcastGektoState(state) {
11
12
  const message = JSON.stringify({ type: 'gekto_state', state });
@@ -160,6 +161,11 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
160
161
  type: 'kill_all_result',
161
162
  killed: killedCount,
162
163
  }));
164
+ getPostHog().capture({
165
+ distinctId: getDistinctId(),
166
+ event: 'all agents killed',
167
+ properties: { killed_count: killedCount },
168
+ });
163
169
  // Notify about state changes
164
170
  for (const session of getActiveSessions()) {
165
171
  ws.send(JSON.stringify({ type: 'state', lizardId: session.lizardId, state: 'ready' }));
@@ -193,6 +199,11 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
193
199
  mutate('activePlanId', null);
194
200
  broadcastActivePlans();
195
201
  broadcastActivePlanId();
202
+ getPostHog().capture({
203
+ distinctId: getDistinctId(),
204
+ event: 'all agents cleared',
205
+ properties: { agent_count: agentIds.length },
206
+ });
196
207
  return;
197
208
  }
198
209
  case 'set_active_plan': {
@@ -299,6 +310,14 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
299
310
  planId: msg.planId,
300
311
  prompt: msg.prompt,
301
312
  }));
313
+ getPostHog().capture({
314
+ distinctId: getDistinctId(),
315
+ event: 'gekto message sent',
316
+ properties: {
317
+ plan_id: msg.planId,
318
+ has_images: Boolean(planImagePaths?.length),
319
+ },
320
+ });
302
321
  const planResult = await processWithTools(msg.prompt, msg.planId, getWorkingDir(), getActiveSessions(), planCallbacks, msg.existingPlan, planImagePaths);
303
322
  // Replace streamed JSON with clean message (always send to overwrite raw JSON)
304
323
  // For delegate, the system message handles display — clear the streaming text
@@ -348,6 +367,15 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
348
367
  planId: msg.planId,
349
368
  plan: planResult.plan,
350
369
  }));
370
+ getPostHog().capture({
371
+ distinctId: getDistinctId(),
372
+ event: 'plan created',
373
+ properties: {
374
+ plan_id: planResult.plan.id,
375
+ plan_title: planResult.plan.title,
376
+ action: planExistedBefore ? 'update_plan' : 'create_plan',
377
+ },
378
+ });
351
379
  }
352
380
  else if (planResult.type === 'remove' && planResult.removedAgents) {
353
381
  // Remove agents from server state
@@ -441,6 +469,7 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
441
469
  }
442
470
  catch (err) {
443
471
  console.error('[Agent] Gekto processing failed:', err);
472
+ getPostHog().captureException(err, getDistinctId(), { plan_id: msg.planId });
444
473
  ws.send(JSON.stringify({
445
474
  type: 'gekto_chat',
446
475
  planId: msg.planId,
@@ -531,6 +560,14 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
531
560
  planId: msg.planId,
532
561
  taskCount: tasks.length,
533
562
  }));
563
+ getPostHog().capture({
564
+ distinctId: getDistinctId(),
565
+ event: 'plan tasks generated',
566
+ properties: {
567
+ plan_id: msg.planId,
568
+ task_count: tasks.length,
569
+ },
570
+ });
534
571
  },
535
572
  onError: (error) => {
536
573
  ws.send(JSON.stringify({
@@ -547,6 +584,7 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
547
584
  }
548
585
  catch (err) {
549
586
  console.error('[Agent] Task generation failed:', err);
587
+ getPostHog().captureException(err, getDistinctId(), { plan_id: msg.planId });
550
588
  ws.send(JSON.stringify({
551
589
  type: 'gekto_chat',
552
590
  planId: msg.planId,
@@ -570,9 +608,22 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
570
608
  mutate(`activePlans.${msg.planId}.status`, 'executing');
571
609
  broadcastSinglePlan(msg.planId);
572
610
  }
611
+ getPostHog().capture({
612
+ distinctId: getDistinctId(),
613
+ event: 'plan executed',
614
+ properties: {
615
+ plan_id: msg.planId,
616
+ task_count: currentState.activePlans[msg.planId]?.taskIds?.length ?? 0,
617
+ },
618
+ });
573
619
  return;
574
620
  }
575
621
  case 'cancel_plan': {
622
+ getPostHog().capture({
623
+ distinctId: getDistinctId(),
624
+ event: 'plan canceled',
625
+ properties: { plan_id: msg.planId },
626
+ });
576
627
  const currentState = getState();
577
628
  const cancelPlan = currentState.activePlans[msg.planId];
578
629
  if (cancelPlan) {
@@ -625,6 +676,15 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
625
676
  ]);
626
677
  broadcastTask(task.id);
627
678
  broadcastAgent(agent.id);
679
+ getPostHog().capture({
680
+ distinctId: getDistinctId(),
681
+ event: 'worker agent spawned',
682
+ properties: {
683
+ agent_id: agent.id,
684
+ task_id: task.id,
685
+ plan_id: task.planId,
686
+ },
687
+ });
628
688
  return;
629
689
  }
630
690
  // Client updates a chat message list — store on agent
@@ -695,6 +755,16 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
695
755
  broadcastTask(msg.taskId);
696
756
  if (agentId)
697
757
  broadcastAgent(agentId);
758
+ getPostHog().capture({
759
+ distinctId: getDistinctId(),
760
+ event: 'task completed',
761
+ properties: {
762
+ task_id: msg.taskId,
763
+ task_name: taskState?.name,
764
+ plan_id: taskState?.planId,
765
+ agent_id: agentId,
766
+ },
767
+ });
698
768
  }
699
769
  return;
700
770
  }
@@ -713,16 +783,38 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
713
783
  broadcastTask(msg.taskId);
714
784
  if (agentId)
715
785
  broadcastAgent(agentId);
786
+ getPostHog().capture({
787
+ distinctId: getDistinctId(),
788
+ event: 'task failed',
789
+ properties: {
790
+ task_id: msg.taskId,
791
+ task_name: taskState?.name,
792
+ plan_id: taskState?.planId,
793
+ agent_id: agentId,
794
+ error: msg.error,
795
+ },
796
+ });
716
797
  }
717
798
  return;
718
799
  }
719
800
  case 'task_started': {
720
801
  if (msg.taskId) {
802
+ const taskState = getState().tasks[msg.taskId];
721
803
  mutateBatch([
722
804
  { path: `tasks.${msg.taskId}.status`, value: 'in_progress' },
723
805
  { path: `tasks.${msg.taskId}.assignedAgentId`, value: msg.lizardId },
724
806
  ]);
725
807
  broadcastTask(msg.taskId);
808
+ getPostHog().capture({
809
+ distinctId: getDistinctId(),
810
+ event: 'task started',
811
+ properties: {
812
+ task_id: msg.taskId,
813
+ task_name: taskState?.name,
814
+ plan_id: taskState?.planId,
815
+ agent_id: msg.lizardId,
816
+ },
817
+ });
726
818
  }
727
819
  return;
728
820
  }
@@ -930,6 +1022,14 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
930
1022
  broadcastAgent(lizardId);
931
1023
  }
932
1024
  const images = msg.images;
1025
+ getPostHog().capture({
1026
+ distinctId: getDistinctId(),
1027
+ event: 'agent message sent',
1028
+ properties: {
1029
+ agent_id: lizardId,
1030
+ has_images: Boolean(images?.length),
1031
+ },
1032
+ });
933
1033
  await sendMessage(lizardId, msg.content, ws, images);
934
1034
  }
935
1035
  catch (err) {
@@ -943,6 +1043,11 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
943
1043
  else {
944
1044
  resetSession(lizardId);
945
1045
  }
1046
+ getPostHog().capture({
1047
+ distinctId: getDistinctId(),
1048
+ event: 'agent reset',
1049
+ properties: { agent_id: lizardId },
1050
+ });
946
1051
  ws.send(JSON.stringify({ type: 'state', lizardId, state: 'ready' }));
947
1052
  break;
948
1053
  case 'revert_files': {
@@ -953,6 +1058,15 @@ export function setupAgentWebSocket(server, path = '/__gekto/agent') {
953
1058
  reverted: revertResult.reverted,
954
1059
  failed: revertResult.failed,
955
1060
  }));
1061
+ getPostHog().capture({
1062
+ distinctId: getDistinctId(),
1063
+ event: 'files reverted',
1064
+ properties: {
1065
+ agent_id: lizardId,
1066
+ reverted_count: revertResult.reverted.length,
1067
+ failed_count: revertResult.failed.length,
1068
+ },
1069
+ });
956
1070
  // Remove reverted file changes from agent state
957
1071
  const agentState = getState().agents[lizardId];
958
1072
  if (agentState?.fileChanges) {
@@ -0,0 +1,53 @@
1
+ import { PostHog } from 'posthog-node';
2
+ import { randomUUID } from 'crypto';
3
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
4
+ import { join } from 'path';
5
+ // Public project API key — phc_ keys are safe to ship in client code.
6
+ const DEFAULT_API_KEY = 'phc_s8GT8r8YG23NJqUhpyq2XcNegWyLomZjnsJmmtxOKMf';
7
+ const DEFAULT_HOST = 'https://us.i.posthog.com';
8
+ // Lazy singleton — created on first use
9
+ let _client = null;
10
+ // Distinct ID used for all events — set once per process
11
+ let _distinctId = 'anonymous';
12
+ export function getPostHog() {
13
+ if (!_client) {
14
+ _client = new PostHog(process.env.POSTHOG_API_KEY || DEFAULT_API_KEY, {
15
+ host: process.env.POSTHOG_HOST || DEFAULT_HOST,
16
+ enableExceptionAutocapture: true,
17
+ });
18
+ }
19
+ return _client;
20
+ }
21
+ /** Return the current distinct ID for event capture. */
22
+ export function getDistinctId() {
23
+ return _distinctId;
24
+ }
25
+ /**
26
+ * Set the distinct ID from the user's stored email or a persisted install UUID.
27
+ * Should be called once during startup / after onboarding completes.
28
+ */
29
+ export function initDistinctId(email) {
30
+ if (email && email.trim()) {
31
+ _distinctId = email.trim();
32
+ return;
33
+ }
34
+ // Fall back to a persisted install UUID stored alongside gekto-store.json
35
+ const installIdPath = join(process.cwd(), '.gekto-install-id');
36
+ if (existsSync(installIdPath)) {
37
+ try {
38
+ const id = readFileSync(installIdPath, 'utf8').trim();
39
+ if (id) {
40
+ _distinctId = id;
41
+ return;
42
+ }
43
+ }
44
+ catch { /* ignore */ }
45
+ }
46
+ // First run — generate and persist an install UUID
47
+ const id = randomUUID();
48
+ try {
49
+ writeFileSync(installIdPath, id, 'utf8');
50
+ }
51
+ catch { /* ignore */ }
52
+ _distinctId = id;
53
+ }
package/dist/proxy.js CHANGED
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import * as p from '@clack/prompts';
3
+ import { getPostHog, getDistinctId, initDistinctId } from './posthog.js';
3
4
  // Colors for TUI
4
5
  const c = {
5
6
  reset: '\x1b[0m',
@@ -100,6 +101,7 @@ async function runOnboarding() {
100
101
  PROJECT_TYPE = existingSettings.projectType;
101
102
  TARGET_PORT = existingSettings.targetPort;
102
103
  PROXY_PORT = existingSettings.proxyPort;
104
+ initDistinctId(existingSettings.email);
103
105
  console.log(`${c.dim}Loaded settings from gekto-store.json${c.reset}`);
104
106
  return;
105
107
  }
@@ -162,6 +164,17 @@ async function runOnboarding() {
162
164
  onboardingCompleted: true,
163
165
  email,
164
166
  });
167
+ initDistinctId(email);
168
+ getPostHog().capture({
169
+ distinctId: getDistinctId(),
170
+ event: 'onboarding completed',
171
+ properties: {
172
+ project_type: PROJECT_TYPE,
173
+ has_email: Boolean(email),
174
+ $set: email ? { email } : undefined,
175
+ $set_once: { initial_project_type: PROJECT_TYPE },
176
+ },
177
+ });
165
178
  spinner.stop('Ready!');
166
179
  p.outro(`${c.green}Starting Gekto...${c.reset}`);
167
180
  }
@@ -182,9 +195,12 @@ async function main() {
182
195
  // Prevent EPIPE and other uncaught errors from crashing the server
183
196
  process.on('uncaughtException', (err) => {
184
197
  console.error('[proxy] Uncaught exception (server stays running):', err.message);
198
+ getPostHog().captureException(err, getDistinctId());
185
199
  });
186
200
  process.on('unhandledRejection', (err) => {
187
201
  console.error('[proxy] Unhandled rejection (server stays running):', err);
202
+ if (err instanceof Error)
203
+ getPostHog().captureException(err, getDistinctId());
188
204
  });
189
205
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
190
206
  // Parse CLI arguments (for overrides)
@@ -487,7 +503,24 @@ async function main() {
487
503
  // === STEP 4: Show logo and start listening ===
488
504
  console.clear();
489
505
  printLogo();
506
+ // Graceful shutdown — flush PostHog before exit
507
+ const shutdown = async () => {
508
+ await getPostHog().shutdown();
509
+ process.exit(0);
510
+ };
511
+ process.on('SIGINT', shutdown);
512
+ process.on('SIGTERM', shutdown);
490
513
  server.listen(PROXY_PORT, () => {
514
+ getPostHog().capture({
515
+ distinctId: getDistinctId(),
516
+ event: 'proxy started',
517
+ properties: {
518
+ project_type: PROJECT_TYPE,
519
+ proxy_port: PROXY_PORT,
520
+ target_port: TARGET_PORT,
521
+ dev_mode: DEV_MODE,
522
+ },
523
+ });
491
524
  if (PROJECT_TYPE === 'frontend') {
492
525
  printBox([
493
526
  `${c.bold}Gekto is ready!${c.reset}`,
package/dist/terminal.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { WebSocket, WebSocketServer } from 'ws';
2
2
  import * as pty from 'node-pty';
3
+ import { getPostHog, getDistinctId } from './posthog.js';
3
4
  const sessions = new Map();
4
5
  export function setupTerminalWebSocket(server, path = '/__gekto/terminal') {
5
6
  const wss = new WebSocketServer({ noServer: true });
@@ -14,6 +15,10 @@ export function setupTerminalWebSocket(server, path = '/__gekto/terminal') {
14
15
  // Other upgrade requests (like Vite HMR) are handled elsewhere
15
16
  });
16
17
  wss.on('connection', (ws) => {
18
+ getPostHog().capture({
19
+ distinctId: getDistinctId(),
20
+ event: 'terminal session started',
21
+ });
17
22
  // Create session but don't spawn PTY yet - wait for resize
18
23
  const session = {
19
24
  pty: null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gekto",
3
- "version": "0.0.13",
3
+ "version": "0.0.14",
4
4
  "description": "AI coding assistant widget - inject into any web app",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,6 +27,7 @@
27
27
  "dependencies": {
28
28
  "@clack/prompts": "^1.0.0",
29
29
  "node-pty": "^1.1.0-beta30",
30
+ "posthog-node": "^5.21.2",
30
31
  "ws": "^8.18.3"
31
32
  },
32
33
  "devDependencies": {