gemkit-cli 0.2.2 → 0.3.0
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/README.md +152 -5
- package/dist/commands/agent/index.d.ts +9 -0
- package/dist/commands/agent/index.js +1329 -0
- package/dist/commands/cache/index.d.ts +5 -0
- package/dist/commands/cache/index.js +43 -0
- package/dist/commands/catalog/index.d.ts +2 -0
- package/dist/commands/catalog/index.js +57 -0
- package/dist/commands/config/index.d.ts +7 -0
- package/dist/commands/config/index.js +122 -0
- package/dist/commands/convert/index.d.ts +8 -0
- package/dist/commands/convert/index.js +391 -0
- package/dist/commands/doctor/index.d.ts +2 -0
- package/dist/commands/doctor/index.js +243 -0
- package/dist/commands/extension/index.d.ts +5 -0
- package/dist/commands/extension/index.js +52 -0
- package/dist/commands/index.d.ts +5 -0
- package/dist/commands/index.js +37 -0
- package/dist/commands/init/index.d.ts +6 -0
- package/dist/commands/init/index.js +345 -0
- package/dist/commands/new/index.d.ts +5 -0
- package/dist/commands/new/index.js +49 -0
- package/dist/commands/office/index.d.ts +5 -0
- package/dist/commands/office/index.js +283 -0
- package/dist/commands/paste/index.d.ts +10 -0
- package/dist/commands/paste/index.js +533 -0
- package/dist/commands/plan/index.d.ts +8 -0
- package/dist/commands/plan/index.js +247 -0
- package/dist/commands/session/index.d.ts +8 -0
- package/dist/commands/session/index.js +289 -0
- package/dist/commands/tokens/index.d.ts +6 -0
- package/dist/commands/tokens/index.js +148 -0
- package/dist/commands/update/index.d.ts +26 -0
- package/dist/commands/update/index.js +199 -0
- package/dist/commands/versions/index.d.ts +5 -0
- package/dist/commands/versions/index.js +39 -0
- package/dist/domains/agent/index.d.ts +8 -0
- package/dist/domains/agent/index.js +8 -0
- package/dist/domains/agent/mappings.d.ts +32 -0
- package/dist/domains/agent/mappings.js +164 -0
- package/dist/domains/agent/profile.d.ts +26 -0
- package/dist/domains/agent/profile.js +225 -0
- package/dist/domains/agent/pty-context.d.ts +11 -0
- package/dist/domains/agent/pty-context.js +83 -0
- package/dist/domains/agent/pty-providers.d.ts +18 -0
- package/dist/domains/agent/pty-providers.js +66 -0
- package/dist/domains/agent/pty-session.d.ts +33 -0
- package/dist/domains/agent/pty-session.js +82 -0
- package/dist/domains/agent/pty-types.d.ts +127 -0
- package/dist/domains/agent/pty-types.js +4 -0
- package/dist/domains/agent/search.d.ts +45 -0
- package/dist/domains/agent/search.js +614 -0
- package/dist/domains/agent/types.d.ts +78 -0
- package/dist/domains/agent/types.js +5 -0
- package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
- package/dist/domains/agent-office/documents-scanner.js +143 -0
- package/dist/domains/agent-office/event-emitter.d.ts +43 -0
- package/dist/domains/agent-office/event-emitter.js +86 -0
- package/dist/domains/agent-office/file-watcher.d.ts +40 -0
- package/dist/domains/agent-office/file-watcher.js +173 -0
- package/dist/domains/agent-office/icons.d.ts +11 -0
- package/dist/domains/agent-office/icons.js +36 -0
- package/dist/domains/agent-office/index.d.ts +12 -0
- package/dist/domains/agent-office/index.js +20 -0
- package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
- package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
- package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
- package/dist/domains/agent-office/renderer/web/server.js +228 -0
- package/dist/domains/agent-office/renderer/web.d.ts +30 -0
- package/dist/domains/agent-office/renderer/web.js +111 -0
- package/dist/domains/agent-office/session-bridge.d.ts +23 -0
- package/dist/domains/agent-office/session-bridge.js +171 -0
- package/dist/domains/agent-office/state-machine.d.ts +5 -0
- package/dist/domains/agent-office/state-machine.js +82 -0
- package/dist/domains/agent-office/types.d.ts +91 -0
- package/dist/domains/agent-office/types.js +4 -0
- package/dist/domains/cache/index.d.ts +1 -0
- package/dist/domains/cache/index.js +1 -0
- package/dist/domains/cache/manager.d.ts +22 -0
- package/dist/domains/cache/manager.js +84 -0
- package/dist/domains/config/index.d.ts +5 -0
- package/dist/domains/config/index.js +5 -0
- package/dist/domains/config/manager.d.ts +24 -0
- package/dist/domains/config/manager.js +85 -0
- package/dist/domains/config/schema.d.ts +17 -0
- package/dist/domains/config/schema.js +96 -0
- package/dist/domains/convert/converter.d.ts +78 -0
- package/dist/domains/convert/converter.js +471 -0
- package/dist/domains/convert/index.d.ts +5 -0
- package/dist/domains/convert/index.js +5 -0
- package/dist/domains/convert/types.d.ts +88 -0
- package/dist/domains/convert/types.js +18 -0
- package/dist/domains/github/download.d.ts +12 -0
- package/dist/domains/github/download.js +51 -0
- package/dist/domains/github/index.d.ts +2 -0
- package/dist/domains/github/index.js +2 -0
- package/dist/domains/github/releases.d.ts +16 -0
- package/dist/domains/github/releases.js +68 -0
- package/dist/domains/installation/conflict.d.ts +13 -0
- package/dist/domains/installation/conflict.js +38 -0
- package/dist/domains/installation/file-sync.d.ts +16 -0
- package/dist/domains/installation/file-sync.js +77 -0
- package/dist/domains/installation/index.d.ts +3 -0
- package/dist/domains/installation/index.js +3 -0
- package/dist/domains/installation/metadata.d.ts +20 -0
- package/dist/domains/installation/metadata.js +52 -0
- package/dist/domains/plan/index.d.ts +2 -0
- package/dist/domains/plan/index.js +2 -0
- package/dist/domains/plan/resolver.d.ts +24 -0
- package/dist/domains/plan/resolver.js +164 -0
- package/dist/domains/plan/types.d.ts +13 -0
- package/dist/domains/plan/types.js +4 -0
- package/dist/domains/session/env.d.ts +51 -0
- package/dist/domains/session/env.js +118 -0
- package/dist/domains/session/index.d.ts +8 -0
- package/dist/domains/session/index.js +8 -0
- package/dist/domains/session/manager.d.ts +56 -0
- package/dist/domains/session/manager.js +205 -0
- package/dist/domains/session/paths.d.ts +6 -0
- package/dist/domains/session/paths.js +6 -0
- package/dist/domains/session/types.d.ts +121 -0
- package/dist/domains/session/types.js +5 -0
- package/dist/domains/session/writer.d.ts +82 -0
- package/dist/domains/session/writer.js +431 -0
- package/dist/domains/tokens/index.d.ts +5 -0
- package/dist/domains/tokens/index.js +5 -0
- package/dist/domains/tokens/pricing.d.ts +38 -0
- package/dist/domains/tokens/pricing.js +129 -0
- package/dist/domains/tokens/scanner.d.ts +42 -0
- package/dist/domains/tokens/scanner.js +168 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +86 -57
- package/dist/services/aipty.d.ts +76 -0
- package/dist/services/aipty.js +276 -0
- package/dist/services/archive.d.ts +22 -0
- package/dist/services/archive.js +53 -0
- package/dist/services/auto-update.d.ts +26 -0
- package/dist/services/auto-update.js +117 -0
- package/dist/services/hash.d.ts +36 -0
- package/dist/services/hash.js +63 -0
- package/dist/services/logger.d.ts +28 -0
- package/dist/services/logger.js +102 -0
- package/dist/services/music.d.ts +67 -0
- package/dist/services/music.js +290 -0
- package/dist/services/npm.d.ts +22 -0
- package/dist/services/npm.js +65 -0
- package/dist/services/pty-client.d.ts +66 -0
- package/dist/services/pty-client.js +154 -0
- package/dist/services/pty-server.d.ts +102 -0
- package/dist/services/pty-server.js +613 -0
- package/dist/types/index.d.ts +155 -0
- package/dist/types/index.js +4 -0
- package/dist/utils/colors.d.ts +43 -0
- package/dist/utils/colors.js +98 -0
- package/dist/utils/errors.d.ts +24 -0
- package/dist/utils/errors.js +56 -0
- package/dist/utils/paths.d.ts +46 -0
- package/dist/utils/paths.js +89 -0
- package/dist/utils/platform.d.ts +11 -0
- package/dist/utils/platform.js +31 -0
- 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,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;
|