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,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIPTY - Cross-platform PTY wrapper for Claude Code and Gemini CLI
|
|
3
|
+
* Ported from claude-pty-wrapper/index.js
|
|
4
|
+
*/
|
|
5
|
+
import { EventEmitter } from 'events';
|
|
6
|
+
import type { CliProvider } from '../domains/agent/types.js';
|
|
7
|
+
export interface AIPTYOptions {
|
|
8
|
+
provider: CliProvider;
|
|
9
|
+
model?: string;
|
|
10
|
+
tools?: string[];
|
|
11
|
+
cwd?: string;
|
|
12
|
+
debug?: boolean;
|
|
13
|
+
}
|
|
14
|
+
export declare class AIPTY extends EventEmitter {
|
|
15
|
+
private process;
|
|
16
|
+
private output;
|
|
17
|
+
private outputBuffer;
|
|
18
|
+
private isReady;
|
|
19
|
+
private provider;
|
|
20
|
+
private config;
|
|
21
|
+
private model;
|
|
22
|
+
private tools;
|
|
23
|
+
private cwd;
|
|
24
|
+
private debug;
|
|
25
|
+
constructor(options: AIPTYOptions);
|
|
26
|
+
/**
|
|
27
|
+
* Start AI CLI session
|
|
28
|
+
*/
|
|
29
|
+
start(): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Send text (raw write)
|
|
32
|
+
*/
|
|
33
|
+
write(text: string): void;
|
|
34
|
+
/**
|
|
35
|
+
* Send a prompt and wait for response
|
|
36
|
+
*/
|
|
37
|
+
send(prompt: string, options?: {
|
|
38
|
+
timeout?: number;
|
|
39
|
+
}): Promise<string>;
|
|
40
|
+
/**
|
|
41
|
+
* Extract the last answer from output
|
|
42
|
+
*/
|
|
43
|
+
extractLastAnswer(): string;
|
|
44
|
+
/**
|
|
45
|
+
* Extract answer from Claude output
|
|
46
|
+
*/
|
|
47
|
+
private extractAnswerClaude;
|
|
48
|
+
/**
|
|
49
|
+
* Extract answer from Gemini output
|
|
50
|
+
*/
|
|
51
|
+
private extractAnswerGemini;
|
|
52
|
+
/**
|
|
53
|
+
* Get raw output
|
|
54
|
+
*/
|
|
55
|
+
getOutput(lines?: number): string;
|
|
56
|
+
/**
|
|
57
|
+
* Get full output
|
|
58
|
+
*/
|
|
59
|
+
getFullOutput(): string;
|
|
60
|
+
/**
|
|
61
|
+
* Check if session is running
|
|
62
|
+
*/
|
|
63
|
+
isRunning(): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Check if session is ready
|
|
66
|
+
*/
|
|
67
|
+
getIsReady(): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Get provider
|
|
70
|
+
*/
|
|
71
|
+
getProvider(): CliProvider;
|
|
72
|
+
/**
|
|
73
|
+
* Stop the session
|
|
74
|
+
*/
|
|
75
|
+
stop(): Promise<void>;
|
|
76
|
+
}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AIPTY - Cross-platform PTY wrapper for Claude Code and Gemini CLI
|
|
3
|
+
* Ported from claude-pty-wrapper/index.js
|
|
4
|
+
*/
|
|
5
|
+
import * as pty from 'node-pty';
|
|
6
|
+
import { EventEmitter } from 'events';
|
|
7
|
+
import { PTY_PROVIDERS, buildPtyArgs } from '../domains/agent/pty-providers.js';
|
|
8
|
+
export class AIPTY extends EventEmitter {
|
|
9
|
+
process = null;
|
|
10
|
+
output = '';
|
|
11
|
+
outputBuffer = [];
|
|
12
|
+
isReady = false;
|
|
13
|
+
provider;
|
|
14
|
+
config;
|
|
15
|
+
model;
|
|
16
|
+
tools;
|
|
17
|
+
cwd;
|
|
18
|
+
debug;
|
|
19
|
+
constructor(options) {
|
|
20
|
+
super();
|
|
21
|
+
this.provider = options.provider;
|
|
22
|
+
this.config = PTY_PROVIDERS[options.provider];
|
|
23
|
+
if (!this.config) {
|
|
24
|
+
throw new Error(`Unknown provider: ${options.provider}. Use 'claude' or 'gemini'`);
|
|
25
|
+
}
|
|
26
|
+
this.model = options.model || '';
|
|
27
|
+
this.tools = options.tools || [];
|
|
28
|
+
this.cwd = options.cwd || process.cwd();
|
|
29
|
+
this.debug = options.debug || false;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Start AI CLI session
|
|
33
|
+
*/
|
|
34
|
+
start() {
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
if (this.process) {
|
|
37
|
+
reject(new Error('Session already running'));
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const command = this.config.command;
|
|
41
|
+
const args = buildPtyArgs(this.provider, this.model, this.tools);
|
|
42
|
+
if (this.debug) {
|
|
43
|
+
console.log(`[DEBUG] Spawning: ${command} ${args.join(' ')}`);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
this.process = pty.spawn(command, args, {
|
|
47
|
+
name: 'xterm-256color',
|
|
48
|
+
cols: 120,
|
|
49
|
+
rows: 40,
|
|
50
|
+
cwd: this.cwd,
|
|
51
|
+
env: { ...process.env, TERM: 'xterm-256color' }
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
reject(new Error(`Failed to spawn ${command}: ${err.message}`));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
this.output = '';
|
|
59
|
+
this.outputBuffer = [];
|
|
60
|
+
this.process.onData((data) => {
|
|
61
|
+
this.output += data;
|
|
62
|
+
this.outputBuffer.push({ time: Date.now(), data });
|
|
63
|
+
if (this.debug) {
|
|
64
|
+
process.stdout.write(data);
|
|
65
|
+
}
|
|
66
|
+
this.emit('data', data);
|
|
67
|
+
// Check if ready (prompt appeared)
|
|
68
|
+
if (!this.isReady && this.output.includes(this.config.readyIndicator)) {
|
|
69
|
+
this.isReady = true;
|
|
70
|
+
this.emit('ready');
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
this.process.onExit(({ exitCode }) => {
|
|
74
|
+
this.isReady = false;
|
|
75
|
+
this.process = null;
|
|
76
|
+
this.emit('exit', exitCode);
|
|
77
|
+
});
|
|
78
|
+
// Wait for ready or timeout
|
|
79
|
+
const timeout = setTimeout(() => {
|
|
80
|
+
if (!this.isReady) {
|
|
81
|
+
// Check if trust prompt appeared (Claude specific)
|
|
82
|
+
if (this.output.includes('Yes, I trust')) {
|
|
83
|
+
this.write('1'); // Auto-trust
|
|
84
|
+
setTimeout(() => {
|
|
85
|
+
if (this.isReady)
|
|
86
|
+
resolve();
|
|
87
|
+
else
|
|
88
|
+
reject(new Error(`Timeout waiting for ${this.config.name} to be ready`));
|
|
89
|
+
}, 5000);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
reject(new Error(`Timeout waiting for ${this.config.name} to start`));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}, 60000); // 60s timeout for npx downloads
|
|
96
|
+
this.once('ready', () => {
|
|
97
|
+
clearTimeout(timeout);
|
|
98
|
+
resolve();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Send text (raw write)
|
|
104
|
+
*/
|
|
105
|
+
write(text) {
|
|
106
|
+
if (!this.process) {
|
|
107
|
+
throw new Error('No active session');
|
|
108
|
+
}
|
|
109
|
+
this.process.write(text);
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Send a prompt and wait for response
|
|
113
|
+
*/
|
|
114
|
+
send(prompt, options = {}) {
|
|
115
|
+
return new Promise((resolve, reject) => {
|
|
116
|
+
if (!this.process || !this.isReady) {
|
|
117
|
+
reject(new Error('No active session or not ready'));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const timeout = options.timeout || 120000;
|
|
121
|
+
const responseStart = this.output.length;
|
|
122
|
+
this.write(prompt);
|
|
123
|
+
setTimeout(() => {
|
|
124
|
+
this.write('\r');
|
|
125
|
+
if (this.debug) {
|
|
126
|
+
console.log('\n[DEBUG] Prompt sent, waiting for response...');
|
|
127
|
+
}
|
|
128
|
+
}, 500);
|
|
129
|
+
const checkInterval = setInterval(() => {
|
|
130
|
+
const newOutput = this.output.slice(responseStart);
|
|
131
|
+
// Check completion based on provider
|
|
132
|
+
let isComplete = false;
|
|
133
|
+
if (this.provider === 'claude') {
|
|
134
|
+
const hasResponse = newOutput.includes('●');
|
|
135
|
+
const promptAfterResponse = newOutput.lastIndexOf('❯') > newOutput.lastIndexOf('●');
|
|
136
|
+
isComplete = hasResponse && promptAfterResponse;
|
|
137
|
+
}
|
|
138
|
+
else if (this.provider === 'gemini') {
|
|
139
|
+
// Gemini: look for response block then new prompt
|
|
140
|
+
const hasResponse = newOutput.includes('✦') && newOutput.length > 100;
|
|
141
|
+
isComplete = hasResponse;
|
|
142
|
+
}
|
|
143
|
+
if (isComplete) {
|
|
144
|
+
clearInterval(checkInterval);
|
|
145
|
+
clearTimeout(timeoutId);
|
|
146
|
+
const answer = this.extractLastAnswer();
|
|
147
|
+
resolve(answer);
|
|
148
|
+
}
|
|
149
|
+
}, 500);
|
|
150
|
+
const timeoutId = setTimeout(() => {
|
|
151
|
+
clearInterval(checkInterval);
|
|
152
|
+
reject(new Error('Timeout waiting for response'));
|
|
153
|
+
}, timeout);
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Extract the last answer from output
|
|
158
|
+
*/
|
|
159
|
+
extractLastAnswer() {
|
|
160
|
+
const cleanOutput = this.output
|
|
161
|
+
.replace(/\x1b\[1C/g, ' ')
|
|
162
|
+
.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '')
|
|
163
|
+
.replace(/\x1b\][^\x07]*\x07/g, '')
|
|
164
|
+
.replace(/\r/g, '');
|
|
165
|
+
if (this.provider === 'claude') {
|
|
166
|
+
return this.extractAnswerClaude(cleanOutput);
|
|
167
|
+
}
|
|
168
|
+
else if (this.provider === 'gemini') {
|
|
169
|
+
return this.extractAnswerGemini(cleanOutput);
|
|
170
|
+
}
|
|
171
|
+
return '';
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Extract answer from Claude output
|
|
175
|
+
*/
|
|
176
|
+
extractAnswerClaude(cleanOutput) {
|
|
177
|
+
const matches = cleanOutput.match(/●\s*([\s\S]*?)(?=❯|$)/g);
|
|
178
|
+
if (matches && matches.length > 0) {
|
|
179
|
+
let lastMatch = matches[matches.length - 1];
|
|
180
|
+
lastMatch = lastMatch
|
|
181
|
+
.replace(/^●\s*/, '')
|
|
182
|
+
.replace(/[─╭╰│┌┐└┘├┤┬┴┼]+/g, '')
|
|
183
|
+
.replace(/\? for shortcuts/g, '')
|
|
184
|
+
.replace(/esc to interrupt/g, '')
|
|
185
|
+
.replace(/Churning…/g, '')
|
|
186
|
+
.replace(/\(thinking\)/g, '')
|
|
187
|
+
.trim();
|
|
188
|
+
const lines = lastMatch.split('\n')
|
|
189
|
+
.map(l => l.trim())
|
|
190
|
+
.filter(l => {
|
|
191
|
+
if (l.length === 0)
|
|
192
|
+
return false;
|
|
193
|
+
if (l.match(/^[\s─╭╰│┌┐└┘├┤┬┴┼✢✶✻✽·*]+$/))
|
|
194
|
+
return false;
|
|
195
|
+
if (l.match(/Cascading|Churning|thinking|thought for/i))
|
|
196
|
+
return false;
|
|
197
|
+
if (l.match(/^\d+s\)$/))
|
|
198
|
+
return false;
|
|
199
|
+
return true;
|
|
200
|
+
});
|
|
201
|
+
return lines.join('\n').trim();
|
|
202
|
+
}
|
|
203
|
+
return '';
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Extract answer from Gemini output
|
|
207
|
+
*/
|
|
208
|
+
extractAnswerGemini(cleanOutput) {
|
|
209
|
+
const lines = cleanOutput.split('\n');
|
|
210
|
+
let inResponse = false;
|
|
211
|
+
const response = [];
|
|
212
|
+
for (const line of lines) {
|
|
213
|
+
const trimmed = line.trim();
|
|
214
|
+
// Skip UI elements
|
|
215
|
+
if (trimmed.match(/^[┃┏┓┗┛━─╭╮╯╰│]+$/) ||
|
|
216
|
+
trimmed.match(/^Gemini/i) ||
|
|
217
|
+
trimmed.match(/^Type .* to/i)) {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (trimmed.length > 0 && !trimmed.match(/^>/)) {
|
|
221
|
+
response.push(trimmed);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return response.join('\n').trim();
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Get raw output
|
|
228
|
+
*/
|
|
229
|
+
getOutput(lines = 100) {
|
|
230
|
+
const allLines = this.output.split('\n');
|
|
231
|
+
return allLines.slice(-lines).join('\n');
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Get full output
|
|
235
|
+
*/
|
|
236
|
+
getFullOutput() {
|
|
237
|
+
return this.output;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Check if session is running
|
|
241
|
+
*/
|
|
242
|
+
isRunning() {
|
|
243
|
+
return this.process !== null && this.isReady;
|
|
244
|
+
}
|
|
245
|
+
/**
|
|
246
|
+
* Check if session is ready
|
|
247
|
+
*/
|
|
248
|
+
getIsReady() {
|
|
249
|
+
return this.isReady;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Get provider
|
|
253
|
+
*/
|
|
254
|
+
getProvider() {
|
|
255
|
+
return this.provider;
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Stop the session
|
|
259
|
+
*/
|
|
260
|
+
stop() {
|
|
261
|
+
return new Promise((resolve) => {
|
|
262
|
+
if (!this.process) {
|
|
263
|
+
resolve();
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
this.once('exit', () => resolve());
|
|
267
|
+
this.write(this.config.exitCommand);
|
|
268
|
+
setTimeout(() => {
|
|
269
|
+
if (this.process) {
|
|
270
|
+
this.process.kill();
|
|
271
|
+
}
|
|
272
|
+
resolve();
|
|
273
|
+
}, 3000);
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive extraction service for .tar.gz files
|
|
3
|
+
*/
|
|
4
|
+
export interface ExtractOptions {
|
|
5
|
+
source: string;
|
|
6
|
+
destination: string;
|
|
7
|
+
strip?: number;
|
|
8
|
+
filter?: (path: string) => boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface ExtractResult {
|
|
11
|
+
success: boolean;
|
|
12
|
+
extractedFiles: string[];
|
|
13
|
+
error?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extract a .tar.gz archive
|
|
17
|
+
*/
|
|
18
|
+
export declare function extractTarGz(options: ExtractOptions): Promise<ExtractResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Simple file copy for non-archive files
|
|
21
|
+
*/
|
|
22
|
+
export declare function copyFile(source: string, destination: string): Promise<void>;
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Archive extraction service for .tar.gz files
|
|
3
|
+
*/
|
|
4
|
+
import { createReadStream, createWriteStream, existsSync, mkdirSync } from 'fs';
|
|
5
|
+
import { pipeline } from 'stream/promises';
|
|
6
|
+
import { createGunzip } from 'zlib';
|
|
7
|
+
import { dirname } from 'path';
|
|
8
|
+
import * as tar from 'tar';
|
|
9
|
+
/**
|
|
10
|
+
* Extract a .tar.gz archive
|
|
11
|
+
*/
|
|
12
|
+
export async function extractTarGz(options) {
|
|
13
|
+
const { source, destination, strip = 1, filter } = options;
|
|
14
|
+
const extractedFiles = [];
|
|
15
|
+
if (!existsSync(source)) {
|
|
16
|
+
return { success: false, extractedFiles: [], error: `Source file not found: ${source}` };
|
|
17
|
+
}
|
|
18
|
+
// Ensure destination exists
|
|
19
|
+
if (!existsSync(destination)) {
|
|
20
|
+
mkdirSync(destination, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await pipeline(createReadStream(source), createGunzip(), tar.x({
|
|
24
|
+
cwd: destination,
|
|
25
|
+
strip,
|
|
26
|
+
filter: (path) => {
|
|
27
|
+
if (filter && !filter(path)) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
extractedFiles.push(path);
|
|
31
|
+
return true;
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
return { success: true, extractedFiles };
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
extractedFiles,
|
|
40
|
+
error: error instanceof Error ? error.message : String(error),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Simple file copy for non-archive files
|
|
46
|
+
*/
|
|
47
|
+
export async function copyFile(source, destination) {
|
|
48
|
+
const destDir = dirname(destination);
|
|
49
|
+
if (!existsSync(destDir)) {
|
|
50
|
+
mkdirSync(destDir, { recursive: true });
|
|
51
|
+
}
|
|
52
|
+
await pipeline(createReadStream(source), createWriteStream(destination));
|
|
53
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-update checker service
|
|
3
|
+
*
|
|
4
|
+
* Checks for updates on CLI startup based on config settings.
|
|
5
|
+
* Uses a timestamp file to track last check time.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Run auto-update check on startup
|
|
9
|
+
* This runs in the background and only shows notifications
|
|
10
|
+
*/
|
|
11
|
+
export declare function runAutoUpdateCheck(): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Force check for updates (ignores interval)
|
|
14
|
+
*/
|
|
15
|
+
export declare function forceUpdateCheck(): Promise<{
|
|
16
|
+
cli: {
|
|
17
|
+
current: string;
|
|
18
|
+
latest: string;
|
|
19
|
+
available: boolean;
|
|
20
|
+
} | null;
|
|
21
|
+
kits: {
|
|
22
|
+
current: string;
|
|
23
|
+
latest: string;
|
|
24
|
+
available: boolean;
|
|
25
|
+
} | null;
|
|
26
|
+
}>;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auto-update checker service
|
|
3
|
+
*
|
|
4
|
+
* Checks for updates on CLI startup based on config settings.
|
|
5
|
+
* Uses a timestamp file to track last check time.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { homedir } from 'os';
|
|
10
|
+
import { loadConfig } from '../domains/config/manager.js';
|
|
11
|
+
import { checkForUpdates } from '../commands/update/index.js';
|
|
12
|
+
import { brand } from '../utils/colors.js';
|
|
13
|
+
// File to track last update check
|
|
14
|
+
const UPDATE_CHECK_FILE = join(homedir(), '.gemkit', 'last-update-check.json');
|
|
15
|
+
/**
|
|
16
|
+
* Get last update check state
|
|
17
|
+
*/
|
|
18
|
+
function getLastCheckState() {
|
|
19
|
+
try {
|
|
20
|
+
if (!existsSync(UPDATE_CHECK_FILE)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return JSON.parse(readFileSync(UPDATE_CHECK_FILE, 'utf-8'));
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Save update check state
|
|
31
|
+
*/
|
|
32
|
+
function saveCheckState(state) {
|
|
33
|
+
try {
|
|
34
|
+
const dir = dirname(UPDATE_CHECK_FILE);
|
|
35
|
+
if (!existsSync(dir)) {
|
|
36
|
+
mkdirSync(dir, { recursive: true });
|
|
37
|
+
}
|
|
38
|
+
writeFileSync(UPDATE_CHECK_FILE, JSON.stringify(state, null, 2));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
// Ignore errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if enough time has passed since last check
|
|
46
|
+
*/
|
|
47
|
+
function shouldCheck(intervalHours) {
|
|
48
|
+
const state = getLastCheckState();
|
|
49
|
+
if (!state) {
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
const lastCheck = new Date(state.lastCheck);
|
|
53
|
+
const now = new Date();
|
|
54
|
+
const hoursSinceCheck = (now.getTime() - lastCheck.getTime()) / (1000 * 60 * 60);
|
|
55
|
+
return hoursSinceCheck >= intervalHours;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run auto-update check on startup
|
|
59
|
+
* This runs in the background and only shows notifications
|
|
60
|
+
*/
|
|
61
|
+
export async function runAutoUpdateCheck() {
|
|
62
|
+
const config = loadConfig();
|
|
63
|
+
const updateConfig = config.update;
|
|
64
|
+
// Skip if auto-check is disabled
|
|
65
|
+
if (!updateConfig?.autoCheck) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Skip if not enough time has passed
|
|
69
|
+
if (!shouldCheck(updateConfig.checkInterval)) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const updates = await checkForUpdates();
|
|
74
|
+
const state = getLastCheckState() || { lastCheck: '' };
|
|
75
|
+
const notifications = [];
|
|
76
|
+
// Check CLI updates
|
|
77
|
+
if (updates.cli?.available && state.notifiedCli !== updates.cli.latest) {
|
|
78
|
+
notifications.push(`${brand.primary('CLI')} update available: v${updates.cli.current} → v${updates.cli.latest}`);
|
|
79
|
+
state.notifiedCli = updates.cli.latest;
|
|
80
|
+
}
|
|
81
|
+
// Check Kits updates
|
|
82
|
+
if (updates.kits?.available && state.notifiedKits !== updates.kits.latest) {
|
|
83
|
+
notifications.push(`${brand.primary('Kits')} update available: v${updates.kits.current} → v${updates.kits.latest}`);
|
|
84
|
+
state.notifiedKits = updates.kits.latest;
|
|
85
|
+
}
|
|
86
|
+
// Show notifications
|
|
87
|
+
if (notifications.length > 0) {
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(brand.dim('─'.repeat(50)));
|
|
90
|
+
console.log(`${brand.warn('⬆')} Updates available:`);
|
|
91
|
+
notifications.forEach((n) => console.log(` ${n}`));
|
|
92
|
+
console.log(` Run ${brand.primary('gk update')} to update.`);
|
|
93
|
+
console.log(brand.dim('─'.repeat(50)));
|
|
94
|
+
console.log();
|
|
95
|
+
}
|
|
96
|
+
// Save state
|
|
97
|
+
state.lastCheck = new Date().toISOString();
|
|
98
|
+
saveCheckState(state);
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// Silently fail - don't interrupt user workflow
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Force check for updates (ignores interval)
|
|
106
|
+
*/
|
|
107
|
+
export async function forceUpdateCheck() {
|
|
108
|
+
const updates = await checkForUpdates();
|
|
109
|
+
// Update state
|
|
110
|
+
const state = {
|
|
111
|
+
lastCheck: new Date().toISOString(),
|
|
112
|
+
notifiedCli: updates.cli?.latest,
|
|
113
|
+
notifiedKits: updates.kits?.latest,
|
|
114
|
+
};
|
|
115
|
+
saveCheckState(state);
|
|
116
|
+
return updates;
|
|
117
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashing utilities for project identification
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Generate SHA256 hash of a string
|
|
6
|
+
*/
|
|
7
|
+
export declare function sha256(input: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Generate short hash (first 8 characters)
|
|
10
|
+
*/
|
|
11
|
+
export declare function shortHash(input: string): string;
|
|
12
|
+
/**
|
|
13
|
+
* Generate project hash from directory path
|
|
14
|
+
*/
|
|
15
|
+
export declare function generateProjectHash(projectDir: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Generate file hash for change detection
|
|
18
|
+
*/
|
|
19
|
+
export declare function hashFile(filePath: string): string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Generate hash from multiple inputs
|
|
22
|
+
*/
|
|
23
|
+
export declare function combineHash(...inputs: string[]): string;
|
|
24
|
+
/**
|
|
25
|
+
* Generate unique ID with timestamp
|
|
26
|
+
*/
|
|
27
|
+
export declare function generateUniqueId(prefix?: string): string;
|
|
28
|
+
/**
|
|
29
|
+
* Generate gkSessionId - consistent format for ALL clients
|
|
30
|
+
* Matches gk-session-manager.cjs generateGkSessionId()
|
|
31
|
+
*
|
|
32
|
+
* @param appName - Application identifier (e.g., 'gemini-main', 'vscode')
|
|
33
|
+
* @param pid - Process ID to embed in the session ID
|
|
34
|
+
* @returns Format: "{appName}-{PID}-{timestamp36}-{random4}"
|
|
35
|
+
*/
|
|
36
|
+
export declare function generateGkSessionId(appName: string, pid: number): string;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashing utilities for project identification
|
|
3
|
+
*/
|
|
4
|
+
import { createHash } from 'crypto';
|
|
5
|
+
import { existsSync, readFileSync } from 'fs';
|
|
6
|
+
/**
|
|
7
|
+
* Generate SHA256 hash of a string
|
|
8
|
+
*/
|
|
9
|
+
export function sha256(input) {
|
|
10
|
+
return createHash('sha256').update(input).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Generate short hash (first 8 characters)
|
|
14
|
+
*/
|
|
15
|
+
export function shortHash(input) {
|
|
16
|
+
return sha256(input).substring(0, 8);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate project hash from directory path
|
|
20
|
+
*/
|
|
21
|
+
export function generateProjectHash(projectDir) {
|
|
22
|
+
// Normalize path for consistent hashing
|
|
23
|
+
const normalized = projectDir.replace(/\\/g, '/').toLowerCase();
|
|
24
|
+
return shortHash(normalized);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Generate file hash for change detection
|
|
28
|
+
*/
|
|
29
|
+
export function hashFile(filePath) {
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
const content = readFileSync(filePath);
|
|
34
|
+
return createHash('sha256').update(content).digest('hex');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Generate hash from multiple inputs
|
|
38
|
+
*/
|
|
39
|
+
export function combineHash(...inputs) {
|
|
40
|
+
const combined = inputs.join(':');
|
|
41
|
+
return sha256(combined);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Generate unique ID with timestamp
|
|
45
|
+
*/
|
|
46
|
+
export function generateUniqueId(prefix = '') {
|
|
47
|
+
const timestamp = Date.now().toString(36);
|
|
48
|
+
const random = Math.random().toString(36).substring(2, 8);
|
|
49
|
+
return prefix ? `${prefix}-${timestamp}-${random}` : `${timestamp}-${random}`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Generate gkSessionId - consistent format for ALL clients
|
|
53
|
+
* Matches gk-session-manager.cjs generateGkSessionId()
|
|
54
|
+
*
|
|
55
|
+
* @param appName - Application identifier (e.g., 'gemini-main', 'vscode')
|
|
56
|
+
* @param pid - Process ID to embed in the session ID
|
|
57
|
+
* @returns Format: "{appName}-{PID}-{timestamp36}-{random4}"
|
|
58
|
+
*/
|
|
59
|
+
export function generateGkSessionId(appName, pid) {
|
|
60
|
+
const timestamp = Date.now().toString(36);
|
|
61
|
+
const random = Math.random().toString(36).substring(2, 6);
|
|
62
|
+
return `${appName}-${pid}-${timestamp}-${random}`;
|
|
63
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging service for GemKit CLI
|
|
3
|
+
*/
|
|
4
|
+
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'silent';
|
|
5
|
+
interface LoggerConfig {
|
|
6
|
+
level: LogLevel;
|
|
7
|
+
verbose: boolean;
|
|
8
|
+
json: boolean;
|
|
9
|
+
}
|
|
10
|
+
export declare function configureLogger(options: Partial<LoggerConfig>): void;
|
|
11
|
+
export declare function debug(message: string, data?: unknown): void;
|
|
12
|
+
export declare function info(message: string, data?: unknown): void;
|
|
13
|
+
export declare function success(message: string, data?: unknown): void;
|
|
14
|
+
export declare function warn(message: string, data?: unknown): void;
|
|
15
|
+
export declare function error(message: string, data?: unknown): void;
|
|
16
|
+
export declare function table(data: Record<string, unknown>[]): void;
|
|
17
|
+
export declare function json(data: unknown): void;
|
|
18
|
+
export declare const logger: {
|
|
19
|
+
debug: typeof debug;
|
|
20
|
+
info: typeof info;
|
|
21
|
+
success: typeof success;
|
|
22
|
+
warn: typeof warn;
|
|
23
|
+
error: typeof error;
|
|
24
|
+
table: typeof table;
|
|
25
|
+
json: typeof json;
|
|
26
|
+
configure: typeof configureLogger;
|
|
27
|
+
};
|
|
28
|
+
export {};
|