gemkit-cli 0.2.3 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/README.md +141 -7
  2. package/dist/commands/agent/index.d.ts +9 -0
  3. package/dist/commands/agent/index.js +1329 -0
  4. package/dist/commands/cache/index.d.ts +5 -0
  5. package/dist/commands/cache/index.js +43 -0
  6. package/dist/commands/catalog/index.d.ts +2 -0
  7. package/dist/commands/catalog/index.js +57 -0
  8. package/dist/commands/config/index.d.ts +7 -0
  9. package/dist/commands/config/index.js +122 -0
  10. package/dist/commands/convert/index.d.ts +8 -0
  11. package/dist/commands/convert/index.js +391 -0
  12. package/dist/commands/doctor/index.d.ts +2 -0
  13. package/dist/commands/doctor/index.js +243 -0
  14. package/dist/commands/extension/index.d.ts +5 -0
  15. package/dist/commands/extension/index.js +52 -0
  16. package/dist/commands/index.d.ts +5 -0
  17. package/dist/commands/index.js +37 -0
  18. package/dist/commands/init/index.d.ts +6 -0
  19. package/dist/commands/init/index.js +345 -0
  20. package/dist/commands/new/index.d.ts +5 -0
  21. package/dist/commands/new/index.js +49 -0
  22. package/dist/commands/office/index.d.ts +5 -0
  23. package/dist/commands/office/index.js +283 -0
  24. package/dist/commands/paste/index.d.ts +10 -0
  25. package/dist/commands/paste/index.js +533 -0
  26. package/dist/commands/plan/index.d.ts +8 -0
  27. package/dist/commands/plan/index.js +247 -0
  28. package/dist/commands/session/index.d.ts +8 -0
  29. package/dist/commands/session/index.js +289 -0
  30. package/dist/commands/tokens/index.d.ts +6 -0
  31. package/dist/commands/tokens/index.js +148 -0
  32. package/dist/commands/update/index.d.ts +26 -0
  33. package/dist/commands/update/index.js +199 -0
  34. package/dist/commands/versions/index.d.ts +5 -0
  35. package/dist/commands/versions/index.js +39 -0
  36. package/dist/domains/agent/index.d.ts +8 -0
  37. package/dist/domains/agent/index.js +8 -0
  38. package/dist/domains/agent/mappings.d.ts +32 -0
  39. package/dist/domains/agent/mappings.js +164 -0
  40. package/dist/domains/agent/profile.d.ts +26 -0
  41. package/dist/domains/agent/profile.js +225 -0
  42. package/dist/domains/agent/pty-context.d.ts +11 -0
  43. package/dist/domains/agent/pty-context.js +83 -0
  44. package/dist/domains/agent/pty-providers.d.ts +18 -0
  45. package/dist/domains/agent/pty-providers.js +66 -0
  46. package/dist/domains/agent/pty-session.d.ts +33 -0
  47. package/dist/domains/agent/pty-session.js +82 -0
  48. package/dist/domains/agent/pty-types.d.ts +127 -0
  49. package/dist/domains/agent/pty-types.js +4 -0
  50. package/dist/domains/agent/search.d.ts +45 -0
  51. package/dist/domains/agent/search.js +614 -0
  52. package/dist/domains/agent/types.d.ts +78 -0
  53. package/dist/domains/agent/types.js +5 -0
  54. package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
  55. package/dist/domains/agent-office/documents-scanner.js +143 -0
  56. package/dist/domains/agent-office/event-emitter.d.ts +43 -0
  57. package/dist/domains/agent-office/event-emitter.js +86 -0
  58. package/dist/domains/agent-office/file-watcher.d.ts +40 -0
  59. package/dist/domains/agent-office/file-watcher.js +173 -0
  60. package/dist/domains/agent-office/icons.d.ts +11 -0
  61. package/dist/domains/agent-office/icons.js +36 -0
  62. package/dist/domains/agent-office/index.d.ts +12 -0
  63. package/dist/domains/agent-office/index.js +20 -0
  64. package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
  65. package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
  66. package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
  67. package/dist/domains/agent-office/renderer/web/server.js +228 -0
  68. package/dist/domains/agent-office/renderer/web.d.ts +30 -0
  69. package/dist/domains/agent-office/renderer/web.js +111 -0
  70. package/dist/domains/agent-office/session-bridge.d.ts +23 -0
  71. package/dist/domains/agent-office/session-bridge.js +171 -0
  72. package/dist/domains/agent-office/state-machine.d.ts +5 -0
  73. package/dist/domains/agent-office/state-machine.js +82 -0
  74. package/dist/domains/agent-office/types.d.ts +91 -0
  75. package/dist/domains/agent-office/types.js +4 -0
  76. package/dist/domains/cache/index.d.ts +1 -0
  77. package/dist/domains/cache/index.js +1 -0
  78. package/dist/domains/cache/manager.d.ts +22 -0
  79. package/dist/domains/cache/manager.js +84 -0
  80. package/dist/domains/config/index.d.ts +5 -0
  81. package/dist/domains/config/index.js +5 -0
  82. package/dist/domains/config/manager.d.ts +24 -0
  83. package/dist/domains/config/manager.js +85 -0
  84. package/dist/domains/config/schema.d.ts +17 -0
  85. package/dist/domains/config/schema.js +96 -0
  86. package/dist/domains/convert/converter.d.ts +78 -0
  87. package/dist/domains/convert/converter.js +471 -0
  88. package/dist/domains/convert/index.d.ts +5 -0
  89. package/dist/domains/convert/index.js +5 -0
  90. package/dist/domains/convert/types.d.ts +88 -0
  91. package/dist/domains/convert/types.js +18 -0
  92. package/dist/domains/github/download.d.ts +12 -0
  93. package/dist/domains/github/download.js +51 -0
  94. package/dist/domains/github/index.d.ts +2 -0
  95. package/dist/domains/github/index.js +2 -0
  96. package/dist/domains/github/releases.d.ts +16 -0
  97. package/dist/domains/github/releases.js +68 -0
  98. package/dist/domains/installation/conflict.d.ts +13 -0
  99. package/dist/domains/installation/conflict.js +38 -0
  100. package/dist/domains/installation/file-sync.d.ts +16 -0
  101. package/dist/domains/installation/file-sync.js +77 -0
  102. package/dist/domains/installation/index.d.ts +3 -0
  103. package/dist/domains/installation/index.js +3 -0
  104. package/dist/domains/installation/metadata.d.ts +20 -0
  105. package/dist/domains/installation/metadata.js +52 -0
  106. package/dist/domains/plan/index.d.ts +2 -0
  107. package/dist/domains/plan/index.js +2 -0
  108. package/dist/domains/plan/resolver.d.ts +24 -0
  109. package/dist/domains/plan/resolver.js +164 -0
  110. package/dist/domains/plan/types.d.ts +13 -0
  111. package/dist/domains/plan/types.js +4 -0
  112. package/dist/domains/session/env.d.ts +51 -0
  113. package/dist/domains/session/env.js +118 -0
  114. package/dist/domains/session/index.d.ts +8 -0
  115. package/dist/domains/session/index.js +8 -0
  116. package/dist/domains/session/manager.d.ts +56 -0
  117. package/dist/domains/session/manager.js +205 -0
  118. package/dist/domains/session/paths.d.ts +6 -0
  119. package/dist/domains/session/paths.js +6 -0
  120. package/dist/domains/session/types.d.ts +121 -0
  121. package/dist/domains/session/types.js +5 -0
  122. package/dist/domains/session/writer.d.ts +82 -0
  123. package/dist/domains/session/writer.js +431 -0
  124. package/dist/domains/tokens/index.d.ts +5 -0
  125. package/dist/domains/tokens/index.js +5 -0
  126. package/dist/domains/tokens/pricing.d.ts +38 -0
  127. package/dist/domains/tokens/pricing.js +129 -0
  128. package/dist/domains/tokens/scanner.d.ts +42 -0
  129. package/dist/domains/tokens/scanner.js +168 -0
  130. package/dist/index.d.ts +5 -0
  131. package/dist/index.js +90 -59
  132. package/dist/services/aipty.d.ts +76 -0
  133. package/dist/services/aipty.js +276 -0
  134. package/dist/services/archive.d.ts +22 -0
  135. package/dist/services/archive.js +53 -0
  136. package/dist/services/auto-update.d.ts +26 -0
  137. package/dist/services/auto-update.js +117 -0
  138. package/dist/services/hash.d.ts +36 -0
  139. package/dist/services/hash.js +63 -0
  140. package/dist/services/logger.d.ts +28 -0
  141. package/dist/services/logger.js +102 -0
  142. package/dist/services/music.d.ts +67 -0
  143. package/dist/services/music.js +290 -0
  144. package/dist/services/npm.d.ts +22 -0
  145. package/dist/services/npm.js +65 -0
  146. package/dist/services/pty-client.d.ts +66 -0
  147. package/dist/services/pty-client.js +154 -0
  148. package/dist/services/pty-server.d.ts +102 -0
  149. package/dist/services/pty-server.js +613 -0
  150. package/dist/types/index.d.ts +155 -0
  151. package/dist/types/index.js +4 -0
  152. package/dist/utils/colors.d.ts +43 -0
  153. package/dist/utils/colors.js +98 -0
  154. package/dist/utils/errors.d.ts +24 -0
  155. package/dist/utils/errors.js +56 -0
  156. package/dist/utils/paths.d.ts +46 -0
  157. package/dist/utils/paths.js +89 -0
  158. package/dist/utils/platform.d.ts +11 -0
  159. package/dist/utils/platform.js +31 -0
  160. package/package.json +55 -54
@@ -0,0 +1,431 @@
1
+ /**
2
+ * Session writer - WRITE operations
3
+ * Aligned with gk-session-manager.cjs write functions
4
+ *
5
+ * Used by:
6
+ * - gk session init (replaces gk-init.cjs)
7
+ * - gk plan set (replaces gk-set-active-plan.cjs)
8
+ */
9
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, renameSync, unlinkSync } from 'fs';
10
+ import { execSync } from 'child_process';
11
+ import { join } from 'path';
12
+ import { getProjectDataDir, getSessionPath, getProjectPath, getLocalEnvPath, sanitizeProjectPath } from '../../utils/paths.js';
13
+ import { generateProjectHash, generateGkSessionId } from '../../services/hash.js';
14
+ import { readEnv } from './env.js';
15
+ import { getSession } from './manager.js';
16
+ /**
17
+ * Get parent PID and process name on Windows using CIM
18
+ */
19
+ function getProcessInfoWin32(pid) {
20
+ try {
21
+ const cmd = `powershell -Command "$p = Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}'; Write-Output $p.ParentProcessId; Write-Output $p.Name"`;
22
+ const output = execSync(cmd, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
23
+ const lines = output.split(/\r?\n/);
24
+ const parentPid = parseInt(lines[0], 10);
25
+ const processName = lines[1] || null;
26
+ return {
27
+ parentPid: (!isNaN(parentPid) && parentPid > 0) ? parentPid : null,
28
+ processName: processName
29
+ };
30
+ }
31
+ catch {
32
+ return { parentPid: null, processName: null };
33
+ }
34
+ }
35
+ /**
36
+ * Check if a process name is a shell
37
+ */
38
+ function isShellProcess(name) {
39
+ if (!name)
40
+ return false;
41
+ const shellNames = ['powershell.exe', 'pwsh.exe', 'cmd.exe', 'bash.exe', 'zsh.exe', 'sh.exe', 'fish.exe'];
42
+ return shellNames.includes(name.toLowerCase());
43
+ }
44
+ /**
45
+ * Check if a process name is an IDE/terminal host
46
+ */
47
+ function isIDEProcess(name) {
48
+ if (!name)
49
+ return false;
50
+ const lowerName = name.toLowerCase();
51
+ const ideNames = [
52
+ 'code.exe', 'code - insiders.exe',
53
+ 'cursor.exe', 'windsurf.exe', 'positron.exe',
54
+ 'idea64.exe', 'idea.exe', 'webstorm64.exe', 'webstorm.exe',
55
+ 'pycharm64.exe', 'pycharm.exe', 'phpstorm64.exe', 'phpstorm.exe',
56
+ 'goland64.exe', 'goland.exe', 'rustrover64.exe', 'rustrover.exe',
57
+ 'rider64.exe', 'rider.exe', 'clion64.exe', 'clion.exe',
58
+ 'datagrip64.exe', 'datagrip.exe', 'fleet.exe',
59
+ 'windowsterminal.exe', 'sublime_text.exe', 'zed.exe', 'terminal.app'
60
+ ];
61
+ return ideNames.includes(lowerName);
62
+ }
63
+ /**
64
+ * Get the terminal PID by walking up the process tree
65
+ * Matches gk-session-manager.cjs getTerminalPid()
66
+ */
67
+ export function getTerminalPid() {
68
+ const parentPid = process.ppid;
69
+ try {
70
+ if (process.platform === 'win32') {
71
+ let currentPid = parentPid;
72
+ let lastShellPid = null;
73
+ for (let i = 0; i < 10; i++) {
74
+ const { parentPid: nextPid, processName } = getProcessInfoWin32(currentPid);
75
+ if (isShellProcess(processName)) {
76
+ lastShellPid = currentPid;
77
+ }
78
+ if (isIDEProcess(processName) && lastShellPid) {
79
+ return lastShellPid;
80
+ }
81
+ if (!nextPid || nextPid <= 4)
82
+ break;
83
+ currentPid = nextPid;
84
+ }
85
+ return lastShellPid || parentPid;
86
+ }
87
+ else {
88
+ // Unix/Linux/macOS - simplified
89
+ return parentPid;
90
+ }
91
+ }
92
+ catch {
93
+ // Fall back to parent PID on error
94
+ }
95
+ return parentPid;
96
+ }
97
+ /**
98
+ * Ensure directory exists
99
+ */
100
+ function ensureDir(dirPath) {
101
+ if (!existsSync(dirPath)) {
102
+ mkdirSync(dirPath, { recursive: true });
103
+ }
104
+ }
105
+ /**
106
+ * Reorder session object to put agents array at the end
107
+ */
108
+ function reorderSessionFields(session) {
109
+ const { agents, ...rest } = session;
110
+ return {
111
+ ...rest,
112
+ agents: agents || []
113
+ };
114
+ }
115
+ /**
116
+ * Save session to file (atomic write)
117
+ * Matches gk-session-manager.cjs saveSession()
118
+ */
119
+ export function saveSession(projectDir, gkSessionId, data) {
120
+ if (!projectDir || !gkSessionId)
121
+ return false;
122
+ const projectDataDir = getProjectDataDir(projectDir);
123
+ ensureDir(projectDataDir);
124
+ const sessionPath = getSessionPath(projectDir, gkSessionId);
125
+ const tempPath = `${sessionPath}.tmp`;
126
+ try {
127
+ const orderedData = reorderSessionFields(data);
128
+ writeFileSync(tempPath, JSON.stringify(orderedData, null, 2), 'utf8');
129
+ // Atomic rename
130
+ renameSync(tempPath, sessionPath);
131
+ return true;
132
+ }
133
+ catch {
134
+ try {
135
+ unlinkSync(tempPath);
136
+ }
137
+ catch { /* ignore */ }
138
+ return false;
139
+ }
140
+ }
141
+ /**
142
+ * Update .gemini/.env with session and project info
143
+ * Matches gk-session-manager.cjs updateEnv()
144
+ */
145
+ export function updateEnv(envData) {
146
+ try {
147
+ const envPath = getLocalEnvPath();
148
+ const content = [
149
+ '# Auto-generated by gemkit-cli',
150
+ `# Updated at: ${new Date().toISOString()}`,
151
+ '',
152
+ '# GEMKIT IDs',
153
+ `ACTIVE_GK_SESSION_ID=${envData.gkSessionId || ''}`,
154
+ `GK_PROJECT_HASH=${envData.gkProjectHash || ''}`,
155
+ `PROJECT_DIR=${envData.projectDir || ''}`,
156
+ '',
157
+ '# GEMINI IDs (mapped)',
158
+ `ACTIVE_GEMINI_SESSION_ID=${envData.geminiSessionId || ''}`,
159
+ `GEMINI_PROJECT_HASH=${envData.geminiProjectHash || ''}`,
160
+ '',
161
+ '# PLAN INFO',
162
+ `ACTIVE_PLAN=${envData.activePlan || ''}`,
163
+ `SUGGESTED_PLAN=${envData.suggestedPlan || ''}`,
164
+ `PLAN_DATE_FORMAT=${envData.planDateFormat || ''}`,
165
+ ''
166
+ ].join('\n');
167
+ // Ensure .gemini directory exists
168
+ const geminiDir = join(process.cwd(), '.gemini');
169
+ ensureDir(geminiDir);
170
+ writeFileSync(envPath, content, 'utf8');
171
+ return true;
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ }
177
+ /**
178
+ * Get project data (internal helper)
179
+ */
180
+ function getProjectInternal(projectDir, gkProjectHash) {
181
+ if (!projectDir || !gkProjectHash)
182
+ return null;
183
+ const projectPath = getProjectPath(projectDir, gkProjectHash);
184
+ if (!existsSync(projectPath)) {
185
+ return null;
186
+ }
187
+ try {
188
+ const content = readFileSync(projectPath, 'utf-8');
189
+ return JSON.parse(content);
190
+ }
191
+ catch {
192
+ return null;
193
+ }
194
+ }
195
+ /**
196
+ * Save project data
197
+ */
198
+ export function saveProject(projectDir, gkProjectHash, data) {
199
+ if (!projectDir || !gkProjectHash)
200
+ return false;
201
+ const projectDataDir = getProjectDataDir(projectDir);
202
+ ensureDir(projectDataDir);
203
+ const projectPath = getProjectPath(projectDir, gkProjectHash);
204
+ try {
205
+ writeFileSync(projectPath, JSON.stringify(data, null, 2), 'utf8');
206
+ return true;
207
+ }
208
+ catch {
209
+ return false;
210
+ }
211
+ }
212
+ /**
213
+ * Create or get project
214
+ */
215
+ export function ensureProject(projectDir, gkProjectHash, projectPath) {
216
+ let project = getProjectInternal(projectDir, gkProjectHash);
217
+ if (!project) {
218
+ project = {
219
+ gkProjectHash: gkProjectHash,
220
+ projectDir: projectDir,
221
+ projectPath: projectPath || process.cwd(),
222
+ geminiProjectHash: null,
223
+ initialized: true,
224
+ initTimestamp: new Date().toISOString(),
225
+ lastActiveTimestamp: new Date().toISOString(),
226
+ activeGkSessionId: null,
227
+ sessions: []
228
+ };
229
+ saveProject(projectDir, gkProjectHash, project);
230
+ }
231
+ return project;
232
+ }
233
+ /**
234
+ * Add session summary to project
235
+ */
236
+ export function addSessionToProject(projectDir, gkProjectHash, sessionSummary) {
237
+ const project = ensureProject(projectDir, gkProjectHash);
238
+ project.activeGkSessionId = sessionSummary.gkSessionId;
239
+ project.lastActiveTimestamp = new Date().toISOString();
240
+ const existingIndex = project.sessions.findIndex(s => s.gkSessionId === sessionSummary.gkSessionId);
241
+ if (existingIndex >= 0) {
242
+ const existing = project.sessions[existingIndex];
243
+ project.sessions[existingIndex] = {
244
+ gkSessionId: sessionSummary.gkSessionId,
245
+ pid: sessionSummary.pid ?? existing.pid,
246
+ sessionType: sessionSummary.sessionType || existing.sessionType,
247
+ appName: sessionSummary.appName || existing.appName,
248
+ prompt: sessionSummary.prompt ?? existing.prompt ?? null,
249
+ activePlan: sessionSummary.activePlan ?? existing.activePlan ?? null,
250
+ startTime: sessionSummary.startTime || existing.startTime,
251
+ endTime: sessionSummary.endTime ?? existing.endTime ?? null
252
+ };
253
+ }
254
+ else {
255
+ project.sessions.push({
256
+ gkSessionId: sessionSummary.gkSessionId,
257
+ pid: sessionSummary.pid ?? null,
258
+ sessionType: sessionSummary.sessionType || 'gemini',
259
+ appName: sessionSummary.appName || 'gemini-main',
260
+ prompt: sessionSummary.prompt ?? null,
261
+ activePlan: sessionSummary.activePlan ?? null,
262
+ startTime: sessionSummary.startTime || new Date().toISOString(),
263
+ endTime: sessionSummary.endTime ?? null
264
+ });
265
+ }
266
+ // Sort sessions by startTime descending
267
+ project.sessions.sort((a, b) => {
268
+ const timeA = new Date(a.startTime || 0).getTime();
269
+ const timeB = new Date(b.startTime || 0).getTime();
270
+ return timeB - timeA;
271
+ });
272
+ return saveProject(projectDir, gkProjectHash, project);
273
+ }
274
+ /**
275
+ * Add agent to session
276
+ * Matches gk-session-manager.cjs addAgent()
277
+ */
278
+ export function addAgent(projectDir, gkSessionId, agentData) {
279
+ const session = getSession(projectDir, gkSessionId);
280
+ if (!session)
281
+ return false;
282
+ const agentGkSessionId = agentData.gkSessionId || gkSessionId;
283
+ session.agents = session.agents || [];
284
+ const existingIndex = session.agents.findIndex(a => a.gkSessionId === agentGkSessionId);
285
+ if (existingIndex >= 0) {
286
+ const existing = session.agents[existingIndex];
287
+ session.agents[existingIndex] = {
288
+ ...existing,
289
+ model: existing.model || agentData.model || null,
290
+ prompt: existing.prompt || agentData.prompt || null
291
+ };
292
+ }
293
+ else {
294
+ const agent = {
295
+ gkSessionId: agentGkSessionId,
296
+ pid: agentData.pid || null,
297
+ geminiSessionId: agentData.geminiSessionId || null,
298
+ geminiProjectHash: agentData.geminiProjectHash || null,
299
+ parentGkSessionId: agentData.parentGkSessionId || null,
300
+ agentType: agentData.parentGkSessionId ? 'Sub Agent' : 'Main Agent',
301
+ agentRole: agentData.agentRole || 'main',
302
+ prompt: agentData.prompt || null,
303
+ model: agentData.model || null,
304
+ tokenUsage: agentData.tokenUsage || null,
305
+ retryCount: agentData.retryCount || 0,
306
+ resumeCount: agentData.resumeCount || 0,
307
+ generation: agentData.generation || 0,
308
+ injected: agentData.injected || null,
309
+ startTime: new Date().toISOString(),
310
+ endTime: null,
311
+ status: 'active',
312
+ exitCode: null,
313
+ error: null
314
+ };
315
+ session.agents.push(agent);
316
+ }
317
+ return saveSession(projectDir, gkSessionId, session);
318
+ }
319
+ /**
320
+ * Initialize a non-Gemini session
321
+ * Matches gk-session-manager.cjs initializeNonGeminiSession()
322
+ */
323
+ export function initializeNonGeminiSession(appName, options = {}) {
324
+ const cwd = options.cwd || process.cwd();
325
+ const projectDir = sanitizeProjectPath(cwd);
326
+ const gkProjectHash = generateProjectHash(cwd);
327
+ // Use terminal PID for non-Gemini sessions
328
+ const pid = getTerminalPid();
329
+ const gkSessionId = generateGkSessionId(appName, pid);
330
+ const session = {
331
+ // GemKit identification
332
+ gkSessionId: gkSessionId,
333
+ gkProjectHash: gkProjectHash,
334
+ projectDir: projectDir,
335
+ // No Gemini mapping for non-Gemini sessions
336
+ geminiSessionId: null,
337
+ geminiParentId: null,
338
+ geminiProjectHash: null,
339
+ // Session metadata
340
+ initialized: true,
341
+ initTimestamp: new Date().toISOString(),
342
+ sessionType: 'non-gemini',
343
+ appName: appName,
344
+ pid: pid,
345
+ // Plan management
346
+ activePlan: options.activePlan || null,
347
+ suggestedPlan: options.suggestedPlan || null,
348
+ // Agents array
349
+ agents: []
350
+ };
351
+ // Save session
352
+ ensureDir(getProjectDataDir(projectDir));
353
+ saveSession(projectDir, gkSessionId, session);
354
+ // Update project
355
+ ensureProject(projectDir, gkProjectHash, cwd);
356
+ addSessionToProject(projectDir, gkProjectHash, {
357
+ gkSessionId: gkSessionId,
358
+ pid: pid,
359
+ sessionType: 'non-gemini',
360
+ appName: appName,
361
+ startTime: session.initTimestamp,
362
+ activePlan: session.activePlan
363
+ });
364
+ // Update env - CLEAR Gemini session ID for non-Gemini apps
365
+ const existingEnv = readEnv();
366
+ // Only preserve geminiProjectHash if we're in the SAME project
367
+ const isSameProject = existingEnv.PROJECT_DIR === projectDir;
368
+ const preservedGeminiProjectHash = isSameProject ? (existingEnv.GEMINI_PROJECT_HASH || '') : '';
369
+ updateEnv({
370
+ gkSessionId: gkSessionId,
371
+ gkProjectHash: gkProjectHash,
372
+ projectDir: projectDir,
373
+ geminiSessionId: '', // Clear - no Gemini session for non-Gemini apps
374
+ geminiProjectHash: preservedGeminiProjectHash,
375
+ activePlan: session.activePlan || '',
376
+ suggestedPlan: session.suggestedPlan || '',
377
+ planDateFormat: ''
378
+ });
379
+ return {
380
+ session,
381
+ gkSessionId,
382
+ pid,
383
+ projectDir,
384
+ gkProjectHash
385
+ };
386
+ }
387
+ /**
388
+ * Set active plan in session and .env
389
+ * Matches gk-session-manager.cjs setActivePlan()
390
+ */
391
+ export function setActivePlan(projectDir, gkSessionId, planPath) {
392
+ const session = getSession(projectDir, gkSessionId);
393
+ if (!session)
394
+ return false;
395
+ // Update session
396
+ session.activePlan = planPath;
397
+ session.suggestedPlan = null;
398
+ if (!saveSession(projectDir, gkSessionId, session)) {
399
+ return false;
400
+ }
401
+ // Update env - preserve existing values
402
+ const env = readEnv();
403
+ return updateEnv({
404
+ gkSessionId: gkSessionId,
405
+ gkProjectHash: session.gkProjectHash || env.GK_PROJECT_HASH,
406
+ projectDir: projectDir,
407
+ geminiSessionId: session.geminiSessionId || env.ACTIVE_GEMINI_SESSION_ID,
408
+ geminiProjectHash: session.geminiProjectHash || env.GEMINI_PROJECT_HASH,
409
+ activePlan: planPath,
410
+ suggestedPlan: '',
411
+ planDateFormat: session.planDateFormat || env.PLAN_DATE_FORMAT
412
+ });
413
+ }
414
+ /**
415
+ * Parse gkSessionId to extract components
416
+ * Matches gk-session-manager.cjs parseGkSessionId()
417
+ */
418
+ export function parseGkSessionId(gkSessionId) {
419
+ if (!gkSessionId)
420
+ return null;
421
+ // Format: {appName}-{PID}-{ts36}-{rand4}
422
+ const match = gkSessionId.match(/^(.+)-(\d+)-([a-z0-9]+)-([a-z0-9]{4})$/);
423
+ if (!match)
424
+ return null;
425
+ return {
426
+ appName: match[1],
427
+ pid: parseInt(match[2], 10),
428
+ timestamp: match[3],
429
+ random: match[4]
430
+ };
431
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tokens domain exports
3
+ */
4
+ export * from './pricing.js';
5
+ export * from './scanner.js';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Tokens domain exports
3
+ */
4
+ export * from './pricing.js';
5
+ export * from './scanner.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Token pricing data - Updated Jan 2026
3
+ * Source: https://ai.google.dev/gemini-api/docs/pricing
4
+ */
5
+ import type { TokenUsage } from '../../types/index.js';
6
+ export type { TokenUsage };
7
+ export interface ModelPricing {
8
+ input: number;
9
+ output: number;
10
+ cached: number;
11
+ thoughts: number;
12
+ }
13
+ export declare const MODEL_PRICING: Record<string, ModelPricing>;
14
+ /**
15
+ * Get pricing for a model, with fallback to default
16
+ */
17
+ export declare function getModelPricing(modelName: string): ModelPricing;
18
+ export interface CostBreakdown {
19
+ input: number;
20
+ output: number;
21
+ cached: number;
22
+ thoughts: number;
23
+ total: number;
24
+ model: string;
25
+ pricing: ModelPricing;
26
+ }
27
+ /**
28
+ * Calculate cost for token usage
29
+ */
30
+ export declare function calculateCost(usage: TokenUsage, model?: string): CostBreakdown;
31
+ /**
32
+ * Format cost as currency string
33
+ */
34
+ export declare function formatCost(amount: number): string;
35
+ /**
36
+ * Format token count with K/M suffix
37
+ */
38
+ export declare function formatTokens(n: number): string;
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Token pricing data - Updated Jan 2026
3
+ * Source: https://ai.google.dev/gemini-api/docs/pricing
4
+ */
5
+ export const MODEL_PRICING = {
6
+ 'gemini-3-pro-preview': {
7
+ input: 2.00,
8
+ output: 12.00,
9
+ cached: 0.20,
10
+ thoughts: 12.00,
11
+ },
12
+ 'gemini-3-flash-preview': {
13
+ input: 0.50,
14
+ output: 3.00,
15
+ cached: 0.05,
16
+ thoughts: 3.00,
17
+ },
18
+ 'gemini-2.5-pro': {
19
+ input: 1.25,
20
+ output: 10.00,
21
+ cached: 0.125,
22
+ thoughts: 10.00,
23
+ },
24
+ 'gemini-2.5-pro-preview': {
25
+ input: 1.25,
26
+ output: 10.00,
27
+ cached: 0.125,
28
+ thoughts: 10.00,
29
+ },
30
+ 'gemini-2.5-flash': {
31
+ input: 0.30,
32
+ output: 2.50,
33
+ cached: 0.03,
34
+ thoughts: 2.50,
35
+ },
36
+ 'gemini-2.5-flash-lite': {
37
+ input: 0.10,
38
+ output: 0.40,
39
+ cached: 0.01,
40
+ thoughts: 0.40,
41
+ },
42
+ 'gemini-2.0-flash': {
43
+ input: 0.10,
44
+ output: 0.40,
45
+ cached: 0.01,
46
+ thoughts: 0.40,
47
+ },
48
+ 'gemini-2.0-flash-exp': {
49
+ input: 0.10,
50
+ output: 0.40,
51
+ cached: 0.01,
52
+ thoughts: 0.40,
53
+ },
54
+ // Default fallback
55
+ 'default': {
56
+ input: 0.50,
57
+ output: 3.00,
58
+ cached: 0.05,
59
+ thoughts: 3.00,
60
+ }
61
+ };
62
+ /**
63
+ * Get pricing for a model, with fallback to default
64
+ */
65
+ export function getModelPricing(modelName) {
66
+ // Try exact match first
67
+ if (MODEL_PRICING[modelName]) {
68
+ return MODEL_PRICING[modelName];
69
+ }
70
+ // Try partial match
71
+ for (const key of Object.keys(MODEL_PRICING)) {
72
+ if (key.includes(modelName) || modelName.includes(key)) {
73
+ return MODEL_PRICING[key];
74
+ }
75
+ }
76
+ return MODEL_PRICING['default'];
77
+ }
78
+ /**
79
+ * Calculate cost for token usage
80
+ */
81
+ export function calculateCost(usage, model = 'default') {
82
+ const pricing = getModelPricing(model);
83
+ // Input tokens: subtract cached from input (cached are billed at lower rate)
84
+ let actualInput = usage.input - usage.cached;
85
+ if (actualInput < 0)
86
+ actualInput = 0;
87
+ const inputCost = (actualInput / 1_000_000) * pricing.input;
88
+ const outputCost = (usage.output / 1_000_000) * pricing.output;
89
+ const cachedCost = (usage.cached / 1_000_000) * pricing.cached;
90
+ const thoughtsCost = (usage.thoughts / 1_000_000) * pricing.thoughts;
91
+ return {
92
+ input: inputCost,
93
+ output: outputCost,
94
+ cached: cachedCost,
95
+ thoughts: thoughtsCost,
96
+ total: inputCost + outputCost + cachedCost + thoughtsCost,
97
+ model,
98
+ pricing
99
+ };
100
+ }
101
+ /**
102
+ * Format cost as currency string
103
+ */
104
+ export function formatCost(amount) {
105
+ if (amount >= 1.0) {
106
+ return `$${amount.toFixed(2)}`;
107
+ }
108
+ else if (amount >= 0.01) {
109
+ return `$${amount.toFixed(3)}`;
110
+ }
111
+ else if (amount >= 0.001) {
112
+ return `$${amount.toFixed(4)}`;
113
+ }
114
+ else {
115
+ return `$${amount.toFixed(6)}`;
116
+ }
117
+ }
118
+ /**
119
+ * Format token count with K/M suffix
120
+ */
121
+ export function formatTokens(n) {
122
+ if (n >= 1_000_000) {
123
+ return `${(n / 1_000_000).toFixed(1)}M`;
124
+ }
125
+ else if (n >= 1_000) {
126
+ return `${(n / 1_000).toFixed(1)}K`;
127
+ }
128
+ return String(n);
129
+ }
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Token scanner - Reads Gemini session files for token usage
3
+ */
4
+ import type { TokenUsage } from './pricing.js';
5
+ export interface SessionAnalysis {
6
+ sessionId: string;
7
+ startTime: string | null;
8
+ lastUpdated: string | null;
9
+ duration: {
10
+ seconds: number;
11
+ formatted: string;
12
+ } | null;
13
+ model: string;
14
+ modelsUsed: string[];
15
+ messageCount: number;
16
+ tokens: TokenUsage;
17
+ averages: {
18
+ outputPerMessage: number;
19
+ thoughtsPerMessage: number;
20
+ toolPerMessage: number;
21
+ };
22
+ }
23
+ /**
24
+ * Get token usage for current session only (MVP limitation)
25
+ */
26
+ export declare function getCurrentSessionTokens(): Promise<SessionAnalysis | null>;
27
+ /**
28
+ * Get full session analysis from latest Gemini session file
29
+ */
30
+ export declare function getLatestSessionAnalysis(projectHash: string): SessionAnalysis | null;
31
+ /**
32
+ * Analyze a session file and extract all stats
33
+ */
34
+ export declare function analyzeSessionFile(filePath: string): SessionAnalysis | null;
35
+ /**
36
+ * Get token usage by session ID
37
+ */
38
+ export declare function getAgentTokenUsage(sessionId: string, projectHash: string): TokenUsage | null;
39
+ /**
40
+ * Legacy format function for backward compatibility
41
+ */
42
+ export declare function formatTokenUsage(usage: TokenUsage): string;