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,1329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent command - list, info, search, spawn (NO resume)
|
|
3
|
+
* Spawn aligned with .gemini/extensions/spawn-agent/scripts/gemini_agent.js
|
|
4
|
+
*
|
|
5
|
+
* Uses manual subcommand routing since CAC doesn't support space-separated subcommands.
|
|
6
|
+
* Help is customized to show subcommand-specific options.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { access, stat, readdir, readFile } from 'fs/promises';
|
|
10
|
+
import { join, basename, extname, relative } from 'path';
|
|
11
|
+
import { listAgentProfiles, loadAgentProfile, loadAgentProfileWithFallback } from '../../domains/agent/profile.js';
|
|
12
|
+
import { searchAgentSkillCombination, loadSkillContent } from '../../domains/agent/search.js';
|
|
13
|
+
import { mapModel, mapTools } from '../../domains/agent/mappings.js';
|
|
14
|
+
import { loadSession, saveSession, clearSession, isSessionActive, markFirstSendComplete } from '../../domains/agent/pty-session.js';
|
|
15
|
+
import { buildFirstSendPrompt } from '../../domains/agent/pty-context.js';
|
|
16
|
+
import { PtyClient } from '../../services/pty-client.js';
|
|
17
|
+
import { PtyServer } from '../../services/pty-server.js';
|
|
18
|
+
import { loadConfig } from '../../domains/config/manager.js';
|
|
19
|
+
import { readEnv } from '../../domains/session/env.js';
|
|
20
|
+
import { addAgent } from '../../domains/session/writer.js';
|
|
21
|
+
import { generateGkSessionId } from '../../services/hash.js';
|
|
22
|
+
import { logger } from '../../services/logger.js';
|
|
23
|
+
import { brand, ui, pc } from '../../utils/colors.js';
|
|
24
|
+
import { ElevatorMusic } from '../../services/music.js';
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// HEARTBEAT MESSAGES (keeps shell alive & entertains)
|
|
27
|
+
// ============================================================================
|
|
28
|
+
const HEARTBEAT_MESSAGES = [
|
|
29
|
+
{ icon: '💩', action: 'Pooping out some code' },
|
|
30
|
+
{ icon: '🍳', action: 'Cooking up solutions' },
|
|
31
|
+
{ icon: '🧠', action: 'Brain cells working overtime' },
|
|
32
|
+
{ icon: '⚡', action: 'Neurons firing rapidly' },
|
|
33
|
+
{ icon: '🔮', action: 'Consulting the crystal ball' },
|
|
34
|
+
{ icon: '🎰', action: 'Rolling the dice on this one' },
|
|
35
|
+
{ icon: '🚀', action: 'Launching into hyperthink' },
|
|
36
|
+
{ icon: '🌪️', action: 'Brainstorming intensifies' },
|
|
37
|
+
{ icon: '🎪', action: 'Juggling multiple thoughts' },
|
|
38
|
+
{ icon: '🔧', action: 'Tightening the logic bolts' },
|
|
39
|
+
{ icon: '🎨', action: 'Painting the solution' },
|
|
40
|
+
{ icon: '🏗️', action: 'Building something awesome' },
|
|
41
|
+
{ icon: '🔬', action: 'Analyzing under the microscope' },
|
|
42
|
+
{ icon: '🧪', action: 'Mixing the secret sauce' },
|
|
43
|
+
{ icon: '🎯', action: 'Aiming for perfection' },
|
|
44
|
+
{ icon: '🧙', action: 'Casting coding spells' },
|
|
45
|
+
{ icon: '🐙', action: 'Multitasking like an octopus' },
|
|
46
|
+
{ icon: '🦾', action: 'Flexing the AI muscles' },
|
|
47
|
+
{ icon: '🌈', action: 'Finding the pot of gold' },
|
|
48
|
+
{ icon: '🎸', action: 'Rocking out some logic' }
|
|
49
|
+
];
|
|
50
|
+
const SPINNER_FRAMES = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
51
|
+
/**
|
|
52
|
+
* Start heartbeat to keep shell alive
|
|
53
|
+
*/
|
|
54
|
+
function startHeartbeat(agentName) {
|
|
55
|
+
let messageIndex = 0;
|
|
56
|
+
let spinnerIndex = 0;
|
|
57
|
+
let seconds = 0;
|
|
58
|
+
const isTTY = process.stdout.isTTY;
|
|
59
|
+
const interval = setInterval(() => {
|
|
60
|
+
seconds++;
|
|
61
|
+
spinnerIndex++;
|
|
62
|
+
const messageChanged = seconds % 10 === 0;
|
|
63
|
+
if (messageChanged) {
|
|
64
|
+
messageIndex++;
|
|
65
|
+
}
|
|
66
|
+
const msg = HEARTBEAT_MESSAGES[messageIndex % HEARTBEAT_MESSAGES.length];
|
|
67
|
+
const spinner = SPINNER_FRAMES[spinnerIndex % SPINNER_FRAMES.length];
|
|
68
|
+
const line = `${spinner} ${msg.icon} ${msg.action}... ${seconds}s`;
|
|
69
|
+
if (isTTY) {
|
|
70
|
+
process.stdout.write(`\r\x1b[K${line}`);
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
if (seconds === 1 || messageChanged) {
|
|
74
|
+
console.log(line);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}, 1000);
|
|
78
|
+
return {
|
|
79
|
+
interval,
|
|
80
|
+
stop: () => {
|
|
81
|
+
clearInterval(interval);
|
|
82
|
+
if (isTTY) {
|
|
83
|
+
process.stdout.write('\r\x1b[K');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Build subagent context string for prompt injection
|
|
90
|
+
*/
|
|
91
|
+
function buildSubagentContext(options) {
|
|
92
|
+
const { agentType = 'Sub Agent', agentRole = 'unknown', parentSessionId = null, activePlan = null, projectDir = null } = options;
|
|
93
|
+
const lines = [
|
|
94
|
+
`Agent Type: ${agentType}`,
|
|
95
|
+
`Agent Role: ${agentRole}`,
|
|
96
|
+
];
|
|
97
|
+
if (parentSessionId) {
|
|
98
|
+
lines.push(`Parent Session: ${parentSessionId.slice(0, 8)}...`);
|
|
99
|
+
}
|
|
100
|
+
if (projectDir) {
|
|
101
|
+
lines.push(`Project: ${projectDir}`);
|
|
102
|
+
}
|
|
103
|
+
if (activePlan) {
|
|
104
|
+
lines.push(`Active Plan: ${activePlan}`);
|
|
105
|
+
}
|
|
106
|
+
return lines.join('\n');
|
|
107
|
+
}
|
|
108
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
109
|
+
// HELP FUNCTIONS
|
|
110
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
111
|
+
function showMainHelp() {
|
|
112
|
+
console.log();
|
|
113
|
+
console.log(pc.bold(brand.geminiPurple('Agent Management')));
|
|
114
|
+
console.log();
|
|
115
|
+
console.log('Usage:');
|
|
116
|
+
console.log(` ${brand.primary('gk agent')} <subcommand> [options]`);
|
|
117
|
+
console.log();
|
|
118
|
+
console.log('Subcommands:');
|
|
119
|
+
console.log(` ${brand.primary('list')} List all agent profiles`);
|
|
120
|
+
console.log(` ${brand.primary('info')} <name> Show agent profile details`);
|
|
121
|
+
console.log(` ${brand.primary('search')} "<task>" Find best agent+skills for a task`);
|
|
122
|
+
console.log(` ${brand.primary('spawn')} Spawn a sub-agent (non-interactive)`);
|
|
123
|
+
console.log();
|
|
124
|
+
console.log('Interactive Mode:');
|
|
125
|
+
console.log(` ${brand.primary('start')} Start interactive session`);
|
|
126
|
+
console.log(` ${brand.primary('send')} "<prompt>" Send prompt to session`);
|
|
127
|
+
console.log(` ${brand.primary('wait')} [timeout] Wait for completion`);
|
|
128
|
+
console.log(` ${brand.primary('exchange')} Get structured output`);
|
|
129
|
+
console.log(` ${brand.primary('pending')} Check tool confirmations`);
|
|
130
|
+
console.log(` ${brand.primary('read')} [lines] Read raw output`);
|
|
131
|
+
console.log(` ${brand.primary('status')} Session status`);
|
|
132
|
+
console.log(` ${brand.primary('stop')} Stop session`);
|
|
133
|
+
console.log();
|
|
134
|
+
console.log('Examples:');
|
|
135
|
+
console.log(` ${brand.dim('gk agent list')}`);
|
|
136
|
+
console.log(` ${brand.dim('gk agent spawn -a researcher -p "research best practices"')}`);
|
|
137
|
+
console.log(` ${brand.dim('gk agent start -a researcher -s research')}`);
|
|
138
|
+
console.log(` ${brand.dim('gk agent send "analyze the codebase"')}`);
|
|
139
|
+
console.log();
|
|
140
|
+
console.log('Run with subcommand for specific help:');
|
|
141
|
+
console.log(` ${brand.dim('gk agent spawn --help')}`);
|
|
142
|
+
console.log(` ${brand.dim('gk agent start --help')}`);
|
|
143
|
+
console.log();
|
|
144
|
+
}
|
|
145
|
+
function showListHelp() {
|
|
146
|
+
console.log();
|
|
147
|
+
console.log(pc.bold(brand.geminiPurple('gk agent list')));
|
|
148
|
+
console.log(brand.dim('List all available agent profiles'));
|
|
149
|
+
console.log();
|
|
150
|
+
console.log('Usage:');
|
|
151
|
+
console.log(` ${brand.primary('gk agent list')} [options]`);
|
|
152
|
+
console.log();
|
|
153
|
+
console.log('Options:');
|
|
154
|
+
console.log(` ${brand.dim('--json')} Output as JSON`);
|
|
155
|
+
console.log();
|
|
156
|
+
}
|
|
157
|
+
function showInfoHelp() {
|
|
158
|
+
console.log();
|
|
159
|
+
console.log(pc.bold(brand.geminiPurple('gk agent info')));
|
|
160
|
+
console.log(brand.dim('Show detailed information about an agent profile'));
|
|
161
|
+
console.log();
|
|
162
|
+
console.log('Usage:');
|
|
163
|
+
console.log(` ${brand.primary('gk agent info')} <name> [options]`);
|
|
164
|
+
console.log();
|
|
165
|
+
console.log('Arguments:');
|
|
166
|
+
console.log(` ${brand.dim('<name>')} Agent profile name (without .md extension)`);
|
|
167
|
+
console.log();
|
|
168
|
+
console.log('Options:');
|
|
169
|
+
console.log(` ${brand.dim('--json')} Output as JSON`);
|
|
170
|
+
console.log();
|
|
171
|
+
console.log('Example:');
|
|
172
|
+
console.log(` ${brand.dim('gk agent info researcher')}`);
|
|
173
|
+
console.log();
|
|
174
|
+
}
|
|
175
|
+
function showSearchHelp() {
|
|
176
|
+
console.log();
|
|
177
|
+
console.log(pc.bold(brand.geminiPurple('gk agent search')));
|
|
178
|
+
console.log(brand.dim('Find best agent+skills combination for a task using BM25 search'));
|
|
179
|
+
console.log();
|
|
180
|
+
console.log('Usage:');
|
|
181
|
+
console.log(` ${brand.primary('gk agent search')} "<task>" [options]`);
|
|
182
|
+
console.log();
|
|
183
|
+
console.log('Arguments:');
|
|
184
|
+
console.log(` ${brand.dim('<task>')} Task description to search for`);
|
|
185
|
+
console.log();
|
|
186
|
+
console.log('Options:');
|
|
187
|
+
console.log(` ${brand.dim('-n, --limit <n>')} Number of results (default: 5)`);
|
|
188
|
+
console.log(` ${brand.dim('-i, --intent <i>')} Force intent: research|plan|execute|debug|review|test|design|docs`);
|
|
189
|
+
console.log(` ${brand.dim('-d, --domain <d>')} Force domain: frontend|backend|auth|payment|database|mobile|ai|etc.`);
|
|
190
|
+
console.log(` ${brand.dim('--max-skills <n>')} Maximum skills to include`);
|
|
191
|
+
console.log(` ${brand.dim('--json')} Output as JSON`);
|
|
192
|
+
console.log();
|
|
193
|
+
console.log('Examples:');
|
|
194
|
+
console.log(` ${brand.dim('gk agent search "implement user authentication"')}`);
|
|
195
|
+
console.log(` ${brand.dim('gk agent search "debug the login form" -i debug')}`);
|
|
196
|
+
console.log(` ${brand.dim('gk agent search "build a payment page" -d payment')}`);
|
|
197
|
+
console.log();
|
|
198
|
+
}
|
|
199
|
+
function showSpawnHelp() {
|
|
200
|
+
console.log();
|
|
201
|
+
console.log(pc.bold(brand.geminiPurple('gk agent spawn')));
|
|
202
|
+
console.log(brand.dim('Spawn a sub-agent with Gemini or Claude CLI'));
|
|
203
|
+
console.log();
|
|
204
|
+
console.log('Usage:');
|
|
205
|
+
console.log(` ${brand.primary('gk agent spawn')} -p "<prompt>" [options]`);
|
|
206
|
+
console.log();
|
|
207
|
+
console.log('Required:');
|
|
208
|
+
console.log(` ${brand.dim('-p, --prompt <text>')} Task prompt for the agent`);
|
|
209
|
+
console.log();
|
|
210
|
+
console.log('Options:');
|
|
211
|
+
console.log(` ${brand.dim('-a, --agent <name>')} Agent profile name`);
|
|
212
|
+
console.log(` ${brand.dim('-s, --skills <list>')} Comma-separated skill names to inject`);
|
|
213
|
+
console.log(` ${brand.dim('-c, --context <files>')} Context files (@file syntax)`);
|
|
214
|
+
console.log(` ${brand.dim('-m, --model <model>')} Model override (default: from config)`);
|
|
215
|
+
console.log(` ${brand.dim('-t, --tools <list>')} Comma-separated tools to auto-approve`);
|
|
216
|
+
console.log(` ${brand.dim('--cli <provider>')} CLI provider: gemini (default) or claude`);
|
|
217
|
+
console.log(` ${brand.dim('--music')} Play elevator music while waiting`);
|
|
218
|
+
console.log(` ${brand.dim('--no-music')} Disable elevator music`);
|
|
219
|
+
console.log(` ${brand.dim('--music-file <path>')} Custom music file path`);
|
|
220
|
+
console.log();
|
|
221
|
+
console.log('CLI Providers:');
|
|
222
|
+
console.log(` ${brand.dim('gemini')} Uses Gemini CLI (default). Loads from .gemini/agents/, falls back to .claude/agents/`);
|
|
223
|
+
console.log(` ${brand.dim('claude')} Uses Claude CLI. Loads from .claude/agents/, falls back to .gemini/agents/`);
|
|
224
|
+
console.log(` ${brand.dim('Models and tools are automatically mapped between providers when using fallback.')}`);
|
|
225
|
+
console.log();
|
|
226
|
+
console.log('Examples:');
|
|
227
|
+
console.log(` ${brand.dim('gk agent spawn -p "fix the login bug"')}`);
|
|
228
|
+
console.log(` ${brand.dim('gk agent spawn -a researcher -p "research React best practices"')}`);
|
|
229
|
+
console.log(` ${brand.dim('gk agent spawn -a code-executor -s "frontend-design" -p "build a dashboard"')}`);
|
|
230
|
+
console.log(` ${brand.dim('gk agent spawn -p "implement auth" -m gemini-2.5-pro --music')}`);
|
|
231
|
+
console.log(` ${brand.dim('gk agent spawn -a researcher -t "list_directory,read_file,glob" -p "analyze code"')}`);
|
|
232
|
+
console.log(` ${brand.dim('gk agent spawn --cli claude -a researcher -p "analyze codebase"')}`);
|
|
233
|
+
console.log();
|
|
234
|
+
console.log('Allowed Tools:');
|
|
235
|
+
console.log(` ${brand.dim('Tools can be defined in agent frontmatter (tools: tool1, tool2)')}`);
|
|
236
|
+
console.log(` ${brand.dim('or passed via CLI. Both sources are merged and deduplicated.')}`);
|
|
237
|
+
console.log(` ${brand.dim('Gemini tools: list_directory, read_file, write_file, glob, run_shell_command(*)')}`);
|
|
238
|
+
console.log(` ${brand.dim('Claude tools: Read, Write, Edit, Bash, Glob, Grep, WebFetch, WebSearch')}`);
|
|
239
|
+
console.log();
|
|
240
|
+
}
|
|
241
|
+
function showStartHelp() {
|
|
242
|
+
console.log();
|
|
243
|
+
console.log(pc.bold(brand.geminiPurple('gk agent start')));
|
|
244
|
+
console.log(brand.dim('Start an interactive AI session'));
|
|
245
|
+
console.log();
|
|
246
|
+
console.log('Usage:');
|
|
247
|
+
console.log(` ${brand.primary('gk agent start')} [options]`);
|
|
248
|
+
console.log();
|
|
249
|
+
console.log('Options:');
|
|
250
|
+
console.log(` ${brand.dim('-a, --agent <name>')} Agent profile name`);
|
|
251
|
+
console.log(` ${brand.dim('-s, --skills <list>')} Comma-separated skill names`);
|
|
252
|
+
console.log(` ${brand.dim('-c, --context <files>')} Context files (@file syntax)`);
|
|
253
|
+
console.log(` ${brand.dim('-m, --model <model>')} Model override`);
|
|
254
|
+
console.log(` ${brand.dim('-t, --tools <list>')} Comma-separated tools to allow`);
|
|
255
|
+
console.log(` ${brand.dim('--cli <provider>')} CLI provider: gemini (default) or claude`);
|
|
256
|
+
console.log();
|
|
257
|
+
console.log('Examples:');
|
|
258
|
+
console.log(` ${brand.dim('gk agent start -a researcher -s research')}`);
|
|
259
|
+
console.log(` ${brand.dim('gk agent start --cli claude -a code-executor')}`);
|
|
260
|
+
console.log(` ${brand.dim('gk agent start -m gemini-2.5-pro -t "read_file,write_file"')}`);
|
|
261
|
+
console.log();
|
|
262
|
+
console.log('After starting:');
|
|
263
|
+
console.log(` ${brand.dim('gk agent send "your prompt"')}`);
|
|
264
|
+
console.log(` ${brand.dim('gk agent wait')}`);
|
|
265
|
+
console.log(` ${brand.dim('gk agent exchange')}`);
|
|
266
|
+
console.log(` ${brand.dim('gk agent stop')}`);
|
|
267
|
+
console.log();
|
|
268
|
+
}
|
|
269
|
+
function showInteractiveHelp() {
|
|
270
|
+
console.log();
|
|
271
|
+
console.log(pc.bold(brand.geminiPurple('Interactive Mode Commands')));
|
|
272
|
+
console.log();
|
|
273
|
+
console.log('Session:');
|
|
274
|
+
console.log(` ${brand.primary('start')} [options] Start interactive session`);
|
|
275
|
+
console.log(` ${brand.primary('stop')} Stop interactive session`);
|
|
276
|
+
console.log(` ${brand.primary('status')} Check session status`);
|
|
277
|
+
console.log();
|
|
278
|
+
console.log('Interaction:');
|
|
279
|
+
console.log(` ${brand.primary('send')} "<prompt>" Send prompt to session`);
|
|
280
|
+
console.log(` ${brand.primary('wait')} [timeout] Wait for response completion`);
|
|
281
|
+
console.log(` ${brand.primary('exchange')} Get structured JSON output`);
|
|
282
|
+
console.log(` ${brand.primary('pending')} Check pending tool confirmations`);
|
|
283
|
+
console.log(` ${brand.primary('read')} [lines] Read raw output (debugging)`);
|
|
284
|
+
console.log();
|
|
285
|
+
console.log('Examples:');
|
|
286
|
+
console.log(` ${brand.dim('gk agent start -a researcher -s research')}`);
|
|
287
|
+
console.log(` ${brand.dim('gk agent send "research JWT best practices"')}`);
|
|
288
|
+
console.log(` ${brand.dim('gk agent wait')}`);
|
|
289
|
+
console.log(` ${brand.dim('gk agent pending')}`);
|
|
290
|
+
console.log(` ${brand.dim('gk agent send "1"')} ${brand.dim('# approve tool')}`);
|
|
291
|
+
console.log(` ${brand.dim('gk agent exchange')}`);
|
|
292
|
+
console.log(` ${brand.dim('gk agent stop')}`);
|
|
293
|
+
console.log();
|
|
294
|
+
}
|
|
295
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
296
|
+
// COMMAND REGISTRATION
|
|
297
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
298
|
+
export function registerAgentCommand(cli) {
|
|
299
|
+
cli
|
|
300
|
+
.command('agent [subcommand] [arg]', 'Agent management (list, info, search, spawn)')
|
|
301
|
+
.alias('a')
|
|
302
|
+
// Shared
|
|
303
|
+
.option('--json', '[all] Output as JSON')
|
|
304
|
+
// Search options
|
|
305
|
+
.option('-n, --limit <n>', '[search] Number of results (default: 5)', { default: 5 })
|
|
306
|
+
.option('-i, --intent <intent>', '[search] Force intent: research|plan|execute|debug|review|test|design|docs')
|
|
307
|
+
.option('-d, --domain <domain>', '[search] Force domain: frontend|backend|auth|payment|database|mobile|ai')
|
|
308
|
+
.option('--max-skills <n>', '[search] Maximum skills to include')
|
|
309
|
+
// Spawn options
|
|
310
|
+
.option('-a, --agent <name>', '[spawn] Agent profile name')
|
|
311
|
+
.option('-p, --prompt <text>', '[spawn] Task prompt (required for spawn)')
|
|
312
|
+
.option('-s, --skills <list>', '[spawn] Comma-separated skill names')
|
|
313
|
+
.option('-c, --context <files>', '[spawn] Context files (@file syntax)')
|
|
314
|
+
.option('-m, --model <model>', '[spawn] Model override')
|
|
315
|
+
.option('-t, --tools <list>', '[spawn] Comma-separated tools to auto-approve')
|
|
316
|
+
.option('--cli <provider>', '[spawn] CLI provider: gemini (default) or claude')
|
|
317
|
+
.option('--music', '[spawn] Play elevator music while waiting')
|
|
318
|
+
.option('--no-music', '[spawn] Disable elevator music')
|
|
319
|
+
.option('--music-file <path>', '[spawn] Custom music file path')
|
|
320
|
+
.action(async (subcommand, arg, options) => {
|
|
321
|
+
// Handle help for subcommands
|
|
322
|
+
if (options.help || options.h) {
|
|
323
|
+
switch (subcommand) {
|
|
324
|
+
case 'list':
|
|
325
|
+
showListHelp();
|
|
326
|
+
return;
|
|
327
|
+
case 'info':
|
|
328
|
+
showInfoHelp();
|
|
329
|
+
return;
|
|
330
|
+
case 'search':
|
|
331
|
+
showSearchHelp();
|
|
332
|
+
return;
|
|
333
|
+
case 'spawn':
|
|
334
|
+
showSpawnHelp();
|
|
335
|
+
return;
|
|
336
|
+
case 'start':
|
|
337
|
+
showStartHelp();
|
|
338
|
+
return;
|
|
339
|
+
case 'send':
|
|
340
|
+
case 'wait':
|
|
341
|
+
case 'exchange':
|
|
342
|
+
case 'pending':
|
|
343
|
+
case 'read':
|
|
344
|
+
case 'status':
|
|
345
|
+
case 'stop':
|
|
346
|
+
showInteractiveHelp();
|
|
347
|
+
return;
|
|
348
|
+
default:
|
|
349
|
+
showMainHelp();
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// Route to subcommand handlers
|
|
354
|
+
switch (subcommand) {
|
|
355
|
+
case 'list':
|
|
356
|
+
await handleList(options);
|
|
357
|
+
break;
|
|
358
|
+
case 'info':
|
|
359
|
+
if (!arg) {
|
|
360
|
+
console.log();
|
|
361
|
+
logger.error('Agent profile name required');
|
|
362
|
+
console.log(brand.dim('Usage: gk agent info <name>'));
|
|
363
|
+
console.log();
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
await handleInfo(arg, options);
|
|
367
|
+
break;
|
|
368
|
+
case 'search':
|
|
369
|
+
if (!arg) {
|
|
370
|
+
console.log();
|
|
371
|
+
logger.error('Search query required');
|
|
372
|
+
console.log(brand.dim('Usage: gk agent search "<task>"'));
|
|
373
|
+
console.log();
|
|
374
|
+
process.exit(1);
|
|
375
|
+
}
|
|
376
|
+
await handleSearch(arg, options);
|
|
377
|
+
break;
|
|
378
|
+
case 'spawn':
|
|
379
|
+
await handleSpawn(options);
|
|
380
|
+
break;
|
|
381
|
+
// Interactive mode commands
|
|
382
|
+
case 'start':
|
|
383
|
+
await handleStart(options);
|
|
384
|
+
break;
|
|
385
|
+
case 'send':
|
|
386
|
+
if (!arg) {
|
|
387
|
+
console.log();
|
|
388
|
+
logger.error('Prompt required');
|
|
389
|
+
console.log(brand.dim('Usage: gk agent send "your prompt"'));
|
|
390
|
+
console.log();
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
await handleSend(arg);
|
|
394
|
+
break;
|
|
395
|
+
case 'wait':
|
|
396
|
+
await handleWait(arg ? parseInt(arg) : 120);
|
|
397
|
+
break;
|
|
398
|
+
case 'exchange':
|
|
399
|
+
await handleExchange();
|
|
400
|
+
break;
|
|
401
|
+
case 'pending':
|
|
402
|
+
await handlePending();
|
|
403
|
+
break;
|
|
404
|
+
case 'read':
|
|
405
|
+
await handleRead(arg ? parseInt(arg) : 200);
|
|
406
|
+
break;
|
|
407
|
+
case 'status':
|
|
408
|
+
await handleInteractiveStatus();
|
|
409
|
+
break;
|
|
410
|
+
case 'stop':
|
|
411
|
+
await handleStop();
|
|
412
|
+
break;
|
|
413
|
+
default:
|
|
414
|
+
showMainHelp();
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
419
|
+
// HANDLERS
|
|
420
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
421
|
+
async function handleList(options) {
|
|
422
|
+
const profiles = listAgentProfiles();
|
|
423
|
+
if (options.json) {
|
|
424
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
console.log();
|
|
428
|
+
console.log(pc.bold(brand.geminiPurple('Available Agents')));
|
|
429
|
+
console.log();
|
|
430
|
+
if (profiles.length === 0) {
|
|
431
|
+
logger.warn('No agent profiles found. Run "gk init" first.');
|
|
432
|
+
console.log();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
for (const profile of profiles) {
|
|
436
|
+
console.log(` ${brand.primary(profile.name)}`);
|
|
437
|
+
console.log(` ${brand.dim(profile.description)}`);
|
|
438
|
+
console.log(` ${brand.dim('Model:')} ${profile.model}`);
|
|
439
|
+
if (profile.skills && profile.skills.length > 0) {
|
|
440
|
+
console.log(` ${brand.dim('Skills:')} ${profile.skills.join(', ')}`);
|
|
441
|
+
}
|
|
442
|
+
console.log('');
|
|
443
|
+
}
|
|
444
|
+
console.log(brand.dim(` Total: ${profiles.length} agents`));
|
|
445
|
+
console.log();
|
|
446
|
+
}
|
|
447
|
+
async function handleInfo(name, options) {
|
|
448
|
+
const profile = loadAgentProfile(name);
|
|
449
|
+
if (!profile) {
|
|
450
|
+
console.log();
|
|
451
|
+
logger.error(`Agent profile not found: ${name}`);
|
|
452
|
+
console.log();
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
if (options.json) {
|
|
456
|
+
console.log(JSON.stringify(profile, null, 2));
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
console.log();
|
|
460
|
+
console.log(ui.doubleLine());
|
|
461
|
+
console.log(pc.bold(brand.geminiPurple(`Agent: ${profile.name}`)));
|
|
462
|
+
console.log(ui.doubleLine());
|
|
463
|
+
console.log();
|
|
464
|
+
console.log(` ${brand.dim('Description:')} ${profile.description}`);
|
|
465
|
+
console.log(` ${brand.dim('Model:')} ${profile.model}`);
|
|
466
|
+
if (profile.skills?.length) {
|
|
467
|
+
console.log(` ${brand.dim('Skills:')} ${profile.skills.join(', ')}`);
|
|
468
|
+
}
|
|
469
|
+
console.log(` ${brand.dim('Path:')} ${profile.filePath}`);
|
|
470
|
+
console.log();
|
|
471
|
+
console.log(brand.dim('--- Profile Content ---'));
|
|
472
|
+
console.log();
|
|
473
|
+
console.log(brand.dim(profile.content));
|
|
474
|
+
console.log();
|
|
475
|
+
}
|
|
476
|
+
async function handleSearch(task, options) {
|
|
477
|
+
const results = searchAgentSkillCombination(task, {
|
|
478
|
+
top: options.limit,
|
|
479
|
+
forceIntent: options.intent,
|
|
480
|
+
forceDomain: options.domain,
|
|
481
|
+
maxSkills: options.maxSkills,
|
|
482
|
+
});
|
|
483
|
+
if (options.json) {
|
|
484
|
+
console.log(JSON.stringify(results, null, 2));
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
console.log();
|
|
488
|
+
console.log(pc.bold(brand.geminiPurple(`Search results for: "${task}"`)));
|
|
489
|
+
console.log();
|
|
490
|
+
if (results.length === 0) {
|
|
491
|
+
logger.warn('No matching agents found for task.');
|
|
492
|
+
console.log();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
for (let i = 0; i < results.length; i++) {
|
|
496
|
+
const r = results[i];
|
|
497
|
+
const fallbackTag = r.fallback ? brand.warn(' [FALLBACK]') : '';
|
|
498
|
+
console.log(` ${brand.success(i + 1 + '.')} ${brand.primary(r.agent)}${fallbackTag} ${brand.dim(`(score: ${r.score})`)}`);
|
|
499
|
+
console.log(` ${brand.dim('Intent:')} ${r.intent} ${brand.dim('Domain:')} ${r.domain} ${brand.dim('Complexity:')} ${r.complexity}`);
|
|
500
|
+
if (r.skills.length > 0) {
|
|
501
|
+
console.log(` ${brand.dim('Skills:')} ${r.skills.join(' | ')}`);
|
|
502
|
+
}
|
|
503
|
+
if (r.description) {
|
|
504
|
+
console.log(` ${brand.dim(r.description)}`);
|
|
505
|
+
}
|
|
506
|
+
if (r.useWhen) {
|
|
507
|
+
console.log(` ${brand.dim('Use when:')} ${r.useWhen}`);
|
|
508
|
+
}
|
|
509
|
+
console.log('');
|
|
510
|
+
}
|
|
511
|
+
// Show suggested command
|
|
512
|
+
if (results.length > 0) {
|
|
513
|
+
const best = results[0];
|
|
514
|
+
const skillsArg = best.skills.length > 0 ? ` -s "${best.skills.join(',')}"` : '';
|
|
515
|
+
console.log(brand.dim(' Suggested command:'));
|
|
516
|
+
console.log(` ${brand.primary('gk')} agent spawn -a ${best.agent}${skillsArg} -p "${task}"`);
|
|
517
|
+
}
|
|
518
|
+
console.log();
|
|
519
|
+
}
|
|
520
|
+
async function handleSpawn(options) {
|
|
521
|
+
if (!options.prompt) {
|
|
522
|
+
console.log();
|
|
523
|
+
logger.error('Prompt is required. Use -p or --prompt');
|
|
524
|
+
console.log();
|
|
525
|
+
showSpawnHelp();
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
// Parse and validate CLI provider
|
|
529
|
+
const cliProvider = (options.cli === 'claude') ? 'claude' : 'gemini';
|
|
530
|
+
const config = loadConfig();
|
|
531
|
+
// Read .env for session info
|
|
532
|
+
const env = readEnv();
|
|
533
|
+
const parentGkSessionId = env.ACTIVE_GK_SESSION_ID || null;
|
|
534
|
+
const parentGeminiSessionId = env.ACTIVE_GEMINI_SESSION_ID || null;
|
|
535
|
+
const activePlan = env.ACTIVE_PLAN || null;
|
|
536
|
+
const suggestedPlan = env.SUGGESTED_PLAN || null;
|
|
537
|
+
const planDateFormat = env.PLAN_DATE_FORMAT || null;
|
|
538
|
+
const projectDir = env.PROJECT_DIR || null;
|
|
539
|
+
// Load agent profile if specified (with fallback between providers)
|
|
540
|
+
let profile = null;
|
|
541
|
+
if (options.agent) {
|
|
542
|
+
profile = loadAgentProfileWithFallback(options.agent, cliProvider);
|
|
543
|
+
if (!profile) {
|
|
544
|
+
console.log();
|
|
545
|
+
logger.error(`Agent profile not found: ${options.agent}`);
|
|
546
|
+
console.log(brand.dim(`Searched: .${cliProvider === 'claude' ? 'claude' : 'gemini'}/agents/ and fallback folder`));
|
|
547
|
+
console.log();
|
|
548
|
+
process.exit(1);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
// Determine model (map to target provider if needed)
|
|
552
|
+
let model = options.model || profile?.model || config.spawn.defaultModel;
|
|
553
|
+
// Map model to target provider format
|
|
554
|
+
model = mapModel(model, cliProvider);
|
|
555
|
+
// Resolve music setting: CLI flag > config default
|
|
556
|
+
// CAC sets options.music=true by default when --no-music is defined, so check argv explicitly
|
|
557
|
+
const hasExplicitMusicFlag = process.argv.includes('--music') || process.argv.includes('--no-music');
|
|
558
|
+
const musicEnabled = hasExplicitMusicFlag ? options.music : config.spawn.music;
|
|
559
|
+
const musicFile = options.musicFile || config.spawn.musicFile;
|
|
560
|
+
// Build skills list
|
|
561
|
+
const cliSkills = options.skills?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
|
562
|
+
const agentSkills = profile?.skills || [];
|
|
563
|
+
const allSkills = [...new Set([...agentSkills, ...cliSkills])];
|
|
564
|
+
// Build tools list (merge agent profile tools with CLI tools, deduplicated)
|
|
565
|
+
// Note: profile?.tools already mapped by loadAgentProfileWithFallback if from fallback path
|
|
566
|
+
const cliTools = options.tools?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
|
567
|
+
const agentTools = profile?.tools || [];
|
|
568
|
+
const mergedTools = [...new Set([...agentTools, ...cliTools])];
|
|
569
|
+
// Map CLI tools to target provider (agentTools already mapped if from fallback)
|
|
570
|
+
const allTools = mapTools(mergedTools, cliProvider);
|
|
571
|
+
// Build context list - aligned with gemini_agent.js
|
|
572
|
+
// Parse context refs: split by comma, then by spaces, filter empty strings
|
|
573
|
+
const contextRefs = options.context
|
|
574
|
+
? options.context.split(',').flatMap(part => part.trim().split(/\s+/).filter(s => s)).filter(s => s)
|
|
575
|
+
: [];
|
|
576
|
+
// loadContext - aligned with gemini_agent.js loadContext()
|
|
577
|
+
async function loadContext(contextRef) {
|
|
578
|
+
let fullPath = contextRef;
|
|
579
|
+
let found = false;
|
|
580
|
+
// Handle @ prefix references
|
|
581
|
+
if (contextRef.startsWith('@')) {
|
|
582
|
+
const relativePath = contextRef.substring(1);
|
|
583
|
+
const searchPaths = [
|
|
584
|
+
join(process.cwd(), '.docs', relativePath),
|
|
585
|
+
join(process.cwd(), '.plans', relativePath),
|
|
586
|
+
join(process.cwd(), 'docs', relativePath),
|
|
587
|
+
join(process.cwd(), 'plans', relativePath),
|
|
588
|
+
join(process.cwd(), relativePath)
|
|
589
|
+
];
|
|
590
|
+
for (const searchPath of searchPaths) {
|
|
591
|
+
try {
|
|
592
|
+
await access(searchPath);
|
|
593
|
+
fullPath = searchPath;
|
|
594
|
+
found = true;
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
catch {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
// If @ prefix file not found, show clear error with searched paths
|
|
602
|
+
if (!found) {
|
|
603
|
+
const pathsList = searchPaths.map(p => ` - ${p}`).join('\n');
|
|
604
|
+
throw new Error(`Context file not found: ${contextRef}\n` +
|
|
605
|
+
`Searched paths:\n${pathsList}\n` +
|
|
606
|
+
`Tip: Place file in .docs/, .plans/, docs/, plans/, or project root`);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
try {
|
|
610
|
+
// Check if path is a directory
|
|
611
|
+
const fileStat = await stat(fullPath);
|
|
612
|
+
if (fileStat.isDirectory()) {
|
|
613
|
+
return await loadContextDirectory(fullPath, contextRef);
|
|
614
|
+
}
|
|
615
|
+
// It's a file - load it directly
|
|
616
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
617
|
+
const fileName = basename(fullPath);
|
|
618
|
+
return {
|
|
619
|
+
type: 'context',
|
|
620
|
+
name: fileName,
|
|
621
|
+
path: fullPath,
|
|
622
|
+
content: content.trim(),
|
|
623
|
+
originalRef: contextRef
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
catch (error) {
|
|
627
|
+
throw new Error(`Failed to load context from ${contextRef}: ${error.message}`);
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
// loadContextDirectory - aligned with gemini_agent.js loadContextDirectory()
|
|
631
|
+
async function loadContextDirectory(dirPath, originalRef) {
|
|
632
|
+
const maxDepth = 3;
|
|
633
|
+
const extensions = ['.md', '.txt', '.json', '.yaml', '.yml'];
|
|
634
|
+
const contexts = [];
|
|
635
|
+
async function walkDir(currentPath, depth = 0) {
|
|
636
|
+
if (depth > maxDepth)
|
|
637
|
+
return;
|
|
638
|
+
const entries = await readdir(currentPath, { withFileTypes: true });
|
|
639
|
+
for (const entry of entries) {
|
|
640
|
+
const entryPath = join(currentPath, entry.name);
|
|
641
|
+
// Skip hidden files and directories
|
|
642
|
+
if (entry.name.startsWith('.'))
|
|
643
|
+
continue;
|
|
644
|
+
if (entry.isDirectory()) {
|
|
645
|
+
await walkDir(entryPath, depth + 1);
|
|
646
|
+
}
|
|
647
|
+
else if (entry.isFile()) {
|
|
648
|
+
const ext = extname(entry.name).toLowerCase();
|
|
649
|
+
if (extensions.includes(ext)) {
|
|
650
|
+
try {
|
|
651
|
+
const content = await readFile(entryPath, 'utf-8');
|
|
652
|
+
const relativePath = relative(dirPath, entryPath);
|
|
653
|
+
contexts.push({
|
|
654
|
+
type: 'context',
|
|
655
|
+
name: entry.name,
|
|
656
|
+
path: entryPath,
|
|
657
|
+
relativePath: relativePath,
|
|
658
|
+
content: content.trim(),
|
|
659
|
+
originalRef: `${originalRef}/${relativePath}`
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
catch (err) {
|
|
663
|
+
console.warn(`Warning: Could not read ${entryPath}: ${err.message}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
await walkDir(dirPath);
|
|
670
|
+
// Sort by relative path for consistent ordering
|
|
671
|
+
contexts.sort((a, b) => (a.relativePath || '').localeCompare(b.relativePath || ''));
|
|
672
|
+
return contexts;
|
|
673
|
+
}
|
|
674
|
+
// formatContext - aligned with gemini_agent.js formatContext()
|
|
675
|
+
function formatContext(contextItems) {
|
|
676
|
+
if (!contextItems || contextItems.length === 0) {
|
|
677
|
+
return '';
|
|
678
|
+
}
|
|
679
|
+
let contextSection = '<context>\n';
|
|
680
|
+
contextSection += 'The following documents provide additional context for this task:\n\n';
|
|
681
|
+
contextItems.forEach((ctx, index) => {
|
|
682
|
+
contextSection += `## Document ${index + 1}: ${ctx.name}\n`;
|
|
683
|
+
contextSection += `Source: ${ctx.originalRef}\n\n`;
|
|
684
|
+
});
|
|
685
|
+
contextSection += '</context>\n\n';
|
|
686
|
+
return contextSection;
|
|
687
|
+
}
|
|
688
|
+
// Load context files/directories - aligned with gemini_agent.js buildAgentContext()
|
|
689
|
+
const loadedContexts = [];
|
|
690
|
+
if (contextRefs && contextRefs.length > 0) {
|
|
691
|
+
for (const ctx of contextRefs) {
|
|
692
|
+
try {
|
|
693
|
+
if (typeof ctx === 'string') {
|
|
694
|
+
const loaded = await loadContext(ctx);
|
|
695
|
+
// Handle both single file (object) and directory (array) results
|
|
696
|
+
if (Array.isArray(loaded)) {
|
|
697
|
+
loadedContexts.push(...loaded);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
loadedContexts.push(loaded);
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
else {
|
|
704
|
+
loadedContexts.push(ctx);
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
catch (error) {
|
|
708
|
+
console.log();
|
|
709
|
+
logger.error(error.message);
|
|
710
|
+
console.log();
|
|
711
|
+
process.exit(1);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
// Agent display name
|
|
716
|
+
const agentDisplayName = profile?.name || options.agent || 'Sub';
|
|
717
|
+
// Build enriched prompt
|
|
718
|
+
const promptParts = [];
|
|
719
|
+
// Add subagent context
|
|
720
|
+
const subagentContext = buildSubagentContext({
|
|
721
|
+
agentType: 'Sub Agent',
|
|
722
|
+
agentRole: agentDisplayName,
|
|
723
|
+
parentSessionId: parentGkSessionId,
|
|
724
|
+
activePlan: activePlan,
|
|
725
|
+
projectDir: projectDir
|
|
726
|
+
});
|
|
727
|
+
promptParts.push('<subagent-context>');
|
|
728
|
+
promptParts.push(subagentContext);
|
|
729
|
+
promptParts.push('</subagent-context>\n');
|
|
730
|
+
// Add task/prompt
|
|
731
|
+
promptParts.push('<task>');
|
|
732
|
+
promptParts.push(options.prompt);
|
|
733
|
+
promptParts.push('</task>\n');
|
|
734
|
+
// Add agent profile content
|
|
735
|
+
if (profile) {
|
|
736
|
+
promptParts.push('<agent>');
|
|
737
|
+
promptParts.push(`# Agent: ${profile.name}\n`);
|
|
738
|
+
promptParts.push('## Role & Responsibilities');
|
|
739
|
+
promptParts.push(profile.content);
|
|
740
|
+
promptParts.push('</agent>\n');
|
|
741
|
+
}
|
|
742
|
+
// Add skills content
|
|
743
|
+
if (allSkills.length > 0) {
|
|
744
|
+
promptParts.push('<skills>');
|
|
745
|
+
promptParts.push('You have access to the following skills and capabilities:\n');
|
|
746
|
+
for (let i = 0; i < allSkills.length; i++) {
|
|
747
|
+
const skill = allSkills[i];
|
|
748
|
+
const skillContent = loadSkillContent(skill);
|
|
749
|
+
if (skillContent) {
|
|
750
|
+
promptParts.push(`## Skill ${i + 1}: ${skill}`);
|
|
751
|
+
promptParts.push(skillContent);
|
|
752
|
+
promptParts.push('---\n');
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
promptParts.push('</skills>\n');
|
|
756
|
+
}
|
|
757
|
+
// Add context section - aligned with gemini_agent.js formatContext()
|
|
758
|
+
if (loadedContexts.length > 0) {
|
|
759
|
+
promptParts.push(formatContext(loadedContexts));
|
|
760
|
+
}
|
|
761
|
+
const enrichedPrompt = promptParts.join('\n');
|
|
762
|
+
// Truncate prompt for env var (150 chars)
|
|
763
|
+
const truncatedPrompt = options.prompt.length > 150
|
|
764
|
+
? options.prompt.substring(0, 150)
|
|
765
|
+
: options.prompt;
|
|
766
|
+
console.log();
|
|
767
|
+
logger.info(`Spawning ${brand.primary(agentDisplayName)} agent with ${brand.primary(cliProvider)} CLI`);
|
|
768
|
+
logger.info(`Model: ${brand.primary(model)}`);
|
|
769
|
+
if (parentGkSessionId) {
|
|
770
|
+
logger.info(`Parent session: ${brand.dim(parentGkSessionId.slice(0, 20) + '...')}`);
|
|
771
|
+
}
|
|
772
|
+
if (activePlan) {
|
|
773
|
+
logger.info(`Active plan: ${brand.dim(activePlan)}`);
|
|
774
|
+
}
|
|
775
|
+
if (allSkills.length > 0) {
|
|
776
|
+
logger.info(`Injected skills: ${brand.dim(allSkills.join(', '))}`);
|
|
777
|
+
}
|
|
778
|
+
if (loadedContexts.length > 0) {
|
|
779
|
+
logger.info(`Injected context: ${brand.dim(loadedContexts.map(c => c.name).join(', '))}`);
|
|
780
|
+
}
|
|
781
|
+
if (allTools.length > 0) {
|
|
782
|
+
logger.info(`Allowed tools: ${brand.dim(allTools.join(', '))}`);
|
|
783
|
+
}
|
|
784
|
+
console.log(ui.line());
|
|
785
|
+
console.log();
|
|
786
|
+
// Initialize elevator music if enabled
|
|
787
|
+
let elevatorMusic = null;
|
|
788
|
+
if (musicEnabled) {
|
|
789
|
+
elevatorMusic = new ElevatorMusic({
|
|
790
|
+
audioFile: musicFile,
|
|
791
|
+
loop: true,
|
|
792
|
+
volume: 0.3
|
|
793
|
+
});
|
|
794
|
+
const started = elevatorMusic.start();
|
|
795
|
+
if (started) {
|
|
796
|
+
console.log(brand.dim('Playing elevator music while waiting...'));
|
|
797
|
+
console.log();
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
// Prepare injected info for tracking - aligned with gemini_agent.js line 911
|
|
801
|
+
const injectedContext = loadedContexts.map(c => c.path || c.name);
|
|
802
|
+
// Generate sub-agent session ID and add to session file IMMEDIATELY
|
|
803
|
+
// This allows dashboard to show agent right when music starts
|
|
804
|
+
// The ID is passed to hook via GK_SUB_SESSION_ID so hook updates same agent
|
|
805
|
+
const subAgentSessionId = generateGkSessionId('gemini-sub', process.pid);
|
|
806
|
+
if (parentGkSessionId && projectDir) {
|
|
807
|
+
addAgent(projectDir, parentGkSessionId, {
|
|
808
|
+
gkSessionId: subAgentSessionId,
|
|
809
|
+
pid: process.pid,
|
|
810
|
+
parentGkSessionId: parentGkSessionId,
|
|
811
|
+
agentRole: agentDisplayName,
|
|
812
|
+
prompt: options.prompt,
|
|
813
|
+
model: model,
|
|
814
|
+
injected: (allSkills.length > 0 || injectedContext.length > 0)
|
|
815
|
+
? { skills: allSkills, context: injectedContext }
|
|
816
|
+
: null
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
// Start heartbeat
|
|
820
|
+
const heartbeat = startHeartbeat(agentDisplayName);
|
|
821
|
+
// Build spawn args and command based on CLI provider
|
|
822
|
+
let cliCommand;
|
|
823
|
+
let spawnArgs;
|
|
824
|
+
let spawnEnv;
|
|
825
|
+
if (cliProvider === 'claude') {
|
|
826
|
+
// Claude CLI: claude -p --model <model> --allowedTools tool1,tool2
|
|
827
|
+
cliCommand = 'claude';
|
|
828
|
+
spawnArgs = ['-p', '--model', model];
|
|
829
|
+
// Claude CLI expects: --allowedTools tool1,tool2 (comma-separated)
|
|
830
|
+
if (allTools.length > 0) {
|
|
831
|
+
spawnArgs.push('--allowedTools', allTools.join(','));
|
|
832
|
+
}
|
|
833
|
+
spawnEnv = {
|
|
834
|
+
...process.env,
|
|
835
|
+
CLAUDE_TYPE: 'sub-agent',
|
|
836
|
+
GK_PARENT_SESSION_ID: parentGkSessionId || '',
|
|
837
|
+
GK_SUB_SESSION_ID: subAgentSessionId,
|
|
838
|
+
CLAUDE_AGENT_ROLE: agentDisplayName,
|
|
839
|
+
CLAUDE_AGENT_PROMPT: truncatedPrompt,
|
|
840
|
+
CLAUDE_AGENT_MODEL: model || '',
|
|
841
|
+
CLAUDE_AGENT_SKILLS: allSkills.join(','),
|
|
842
|
+
CLAUDE_AGENT_CONTEXT: injectedContext.join(','),
|
|
843
|
+
GK_ACTIVE_PLAN: activePlan || '',
|
|
844
|
+
GK_SUGGESTED_PLAN: suggestedPlan || '',
|
|
845
|
+
GK_PLAN_DATE_FORMAT: planDateFormat || ''
|
|
846
|
+
};
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
// Gemini CLI: gemini -m <model> --allowed-tools ["tool1","tool2"]
|
|
850
|
+
cliCommand = 'gemini';
|
|
851
|
+
spawnArgs = ['-m', model];
|
|
852
|
+
// Gemini CLI expects: --allowed-tools ["tool1","tool2","tool3"] (JSON array)
|
|
853
|
+
if (allTools.length > 0) {
|
|
854
|
+
spawnArgs.push('--allowed-tools', JSON.stringify(allTools));
|
|
855
|
+
}
|
|
856
|
+
spawnEnv = {
|
|
857
|
+
...process.env,
|
|
858
|
+
GEMINI_TYPE: 'sub-agent',
|
|
859
|
+
GEMINI_PARENT_SESSION_ID: parentGeminiSessionId || '',
|
|
860
|
+
GK_PARENT_SESSION_ID: parentGkSessionId || '',
|
|
861
|
+
GK_SUB_SESSION_ID: subAgentSessionId,
|
|
862
|
+
GEMINI_AGENT_ROLE: agentDisplayName,
|
|
863
|
+
GEMINI_AGENT_PROMPT: truncatedPrompt,
|
|
864
|
+
GEMINI_AGENT_MODEL: model || '',
|
|
865
|
+
GEMINI_AGENT_SKILLS: allSkills.join(','),
|
|
866
|
+
GEMINI_AGENT_CONTEXT: injectedContext.join(','),
|
|
867
|
+
GK_ACTIVE_PLAN: activePlan || '',
|
|
868
|
+
GK_SUGGESTED_PLAN: suggestedPlan || '',
|
|
869
|
+
GK_PLAN_DATE_FORMAT: planDateFormat || ''
|
|
870
|
+
};
|
|
871
|
+
}
|
|
872
|
+
// Spawn with environment variables
|
|
873
|
+
// Note: Sub-agent registration is handled by gk-session-init.cjs hook
|
|
874
|
+
// GK_SUB_SESSION_ID is passed so hook uses same ID (prevents duplicate agents)
|
|
875
|
+
const child = spawn(cliCommand, spawnArgs, {
|
|
876
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
877
|
+
shell: process.platform === 'win32',
|
|
878
|
+
env: spawnEnv
|
|
879
|
+
});
|
|
880
|
+
let stdout = '';
|
|
881
|
+
let stderr = '';
|
|
882
|
+
child.stdout.on('data', (data) => {
|
|
883
|
+
stdout += data.toString();
|
|
884
|
+
});
|
|
885
|
+
child.stderr.on('data', (data) => {
|
|
886
|
+
stderr += data.toString();
|
|
887
|
+
});
|
|
888
|
+
child.stdin.write(enrichedPrompt);
|
|
889
|
+
child.stdin.end();
|
|
890
|
+
child.on('close', (code) => {
|
|
891
|
+
heartbeat.stop();
|
|
892
|
+
if (elevatorMusic) {
|
|
893
|
+
elevatorMusic.stop();
|
|
894
|
+
console.log(brand.dim('Music stopped.'));
|
|
895
|
+
}
|
|
896
|
+
console.log();
|
|
897
|
+
if (stdout) {
|
|
898
|
+
console.log(stdout);
|
|
899
|
+
}
|
|
900
|
+
if (stderr) {
|
|
901
|
+
console.error(stderr);
|
|
902
|
+
}
|
|
903
|
+
if (code === 0) {
|
|
904
|
+
logger.success(`${agentDisplayName} agent completed successfully`);
|
|
905
|
+
}
|
|
906
|
+
else {
|
|
907
|
+
logger.error(`${agentDisplayName} agent exited with code: ${code}`);
|
|
908
|
+
}
|
|
909
|
+
console.log();
|
|
910
|
+
process.exit(code || 0);
|
|
911
|
+
});
|
|
912
|
+
child.on('error', (err) => {
|
|
913
|
+
heartbeat.stop();
|
|
914
|
+
if (elevatorMusic) {
|
|
915
|
+
elevatorMusic.stop();
|
|
916
|
+
}
|
|
917
|
+
console.log();
|
|
918
|
+
logger.error(`Failed to spawn agent: ${err.message}`);
|
|
919
|
+
console.log();
|
|
920
|
+
process.exit(1);
|
|
921
|
+
});
|
|
922
|
+
process.on('SIGINT', () => {
|
|
923
|
+
heartbeat.stop();
|
|
924
|
+
if (elevatorMusic) {
|
|
925
|
+
elevatorMusic.stop();
|
|
926
|
+
}
|
|
927
|
+
child.kill('SIGTERM');
|
|
928
|
+
process.exit(1);
|
|
929
|
+
});
|
|
930
|
+
}
|
|
931
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
932
|
+
// INTERACTIVE MODE HANDLERS
|
|
933
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
934
|
+
async function handleStart(options) {
|
|
935
|
+
// Check if session already running
|
|
936
|
+
if (isSessionActive()) {
|
|
937
|
+
console.log();
|
|
938
|
+
logger.error('Session already running. Use "gk agent stop" first.');
|
|
939
|
+
console.log();
|
|
940
|
+
process.exit(1);
|
|
941
|
+
}
|
|
942
|
+
const cliProvider = (options.cli === 'claude') ? 'claude' : 'gemini';
|
|
943
|
+
const config = loadConfig();
|
|
944
|
+
// Load agent profile with fallback
|
|
945
|
+
let profile = null;
|
|
946
|
+
if (options.agent) {
|
|
947
|
+
profile = loadAgentProfileWithFallback(options.agent, cliProvider);
|
|
948
|
+
if (!profile) {
|
|
949
|
+
console.log();
|
|
950
|
+
logger.error(`Agent profile not found: ${options.agent}`);
|
|
951
|
+
console.log(brand.dim(`Searched: .${cliProvider === 'claude' ? 'claude' : 'gemini'}/agents/ and fallback folder`));
|
|
952
|
+
console.log();
|
|
953
|
+
process.exit(1);
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
// Determine model
|
|
957
|
+
let model = options.model || profile?.model || config.spawn.defaultModel;
|
|
958
|
+
model = mapModel(model, cliProvider);
|
|
959
|
+
// Build tools list
|
|
960
|
+
const cliTools = options.tools?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
|
961
|
+
const agentTools = profile?.tools || [];
|
|
962
|
+
const mergedTools = [...new Set([...agentTools, ...cliTools])];
|
|
963
|
+
const allTools = mapTools(mergedTools, cliProvider);
|
|
964
|
+
// Build skills list and load content
|
|
965
|
+
const cliSkills = options.skills?.split(',').map(s => s.trim()).filter(Boolean) || [];
|
|
966
|
+
const agentSkills = profile?.skills || [];
|
|
967
|
+
const allSkills = [...new Set([...agentSkills, ...cliSkills])];
|
|
968
|
+
const skillContents = {};
|
|
969
|
+
for (const skill of allSkills) {
|
|
970
|
+
const content = loadSkillContent(skill);
|
|
971
|
+
if (content)
|
|
972
|
+
skillContents[skill] = content;
|
|
973
|
+
}
|
|
974
|
+
// Load context files (reuse logic from handleSpawn)
|
|
975
|
+
const contextFiles = [];
|
|
976
|
+
if (options.context) {
|
|
977
|
+
const contextRefs = options.context.split(',').flatMap(part => part.trim().split(/\s+/).filter(s => s)).filter(s => s);
|
|
978
|
+
for (const ref of contextRefs) {
|
|
979
|
+
try {
|
|
980
|
+
const loaded = await loadContextFile(ref);
|
|
981
|
+
if (Array.isArray(loaded)) {
|
|
982
|
+
contextFiles.push(...loaded);
|
|
983
|
+
}
|
|
984
|
+
else {
|
|
985
|
+
contextFiles.push(loaded);
|
|
986
|
+
}
|
|
987
|
+
}
|
|
988
|
+
catch (error) {
|
|
989
|
+
console.log();
|
|
990
|
+
logger.error(error.message);
|
|
991
|
+
console.log();
|
|
992
|
+
process.exit(1);
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
// Create session state
|
|
997
|
+
const sessionState = {
|
|
998
|
+
provider: cliProvider,
|
|
999
|
+
model,
|
|
1000
|
+
port: 3377,
|
|
1001
|
+
pid: 0,
|
|
1002
|
+
isFirstSend: true,
|
|
1003
|
+
context: {
|
|
1004
|
+
agentName: profile?.name || null,
|
|
1005
|
+
agentContent: profile?.content || null,
|
|
1006
|
+
skills: allSkills,
|
|
1007
|
+
skillContents,
|
|
1008
|
+
contextFiles,
|
|
1009
|
+
tools: allTools
|
|
1010
|
+
},
|
|
1011
|
+
startedAt: new Date().toISOString()
|
|
1012
|
+
};
|
|
1013
|
+
// Save session state first (for context)
|
|
1014
|
+
saveSession(sessionState);
|
|
1015
|
+
console.log();
|
|
1016
|
+
logger.info(`Starting ${brand.primary(cliProvider)} interactive session...`);
|
|
1017
|
+
logger.info(`Model: ${brand.primary(model)}`);
|
|
1018
|
+
if (profile) {
|
|
1019
|
+
logger.info(`Agent: ${brand.dim(profile.name)}`);
|
|
1020
|
+
}
|
|
1021
|
+
if (allSkills.length > 0) {
|
|
1022
|
+
logger.info(`Skills: ${brand.dim(allSkills.join(', '))}`);
|
|
1023
|
+
}
|
|
1024
|
+
if (allTools.length > 0) {
|
|
1025
|
+
logger.info(`Tools: ${brand.dim(allTools.join(', '))}`);
|
|
1026
|
+
}
|
|
1027
|
+
console.log();
|
|
1028
|
+
// Start PTY server
|
|
1029
|
+
try {
|
|
1030
|
+
const server = new PtyServer({
|
|
1031
|
+
provider: cliProvider,
|
|
1032
|
+
model,
|
|
1033
|
+
tools: allTools,
|
|
1034
|
+
sessionState,
|
|
1035
|
+
debug: false
|
|
1036
|
+
});
|
|
1037
|
+
await server.start();
|
|
1038
|
+
// Update session with PID (server runs in same process for now)
|
|
1039
|
+
sessionState.pid = process.pid;
|
|
1040
|
+
saveSession(sessionState);
|
|
1041
|
+
logger.success('Session ready!');
|
|
1042
|
+
console.log();
|
|
1043
|
+
console.log('Usage:');
|
|
1044
|
+
console.log(` ${brand.dim('gk agent send "your prompt"')}`);
|
|
1045
|
+
console.log(` ${brand.dim('gk agent wait')}`);
|
|
1046
|
+
console.log(` ${brand.dim('gk agent exchange')}`);
|
|
1047
|
+
console.log(` ${brand.dim('gk agent stop')}`);
|
|
1048
|
+
console.log();
|
|
1049
|
+
// Keep process running
|
|
1050
|
+
process.on('SIGINT', async () => {
|
|
1051
|
+
console.log('\nStopping session...');
|
|
1052
|
+
await server.stop();
|
|
1053
|
+
clearSession();
|
|
1054
|
+
process.exit(0);
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
catch (error) {
|
|
1058
|
+
clearSession();
|
|
1059
|
+
console.log();
|
|
1060
|
+
logger.error(`Failed to start session: ${error.message}`);
|
|
1061
|
+
console.log();
|
|
1062
|
+
process.exit(1);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
async function handleSend(prompt) {
|
|
1066
|
+
const session = loadSession();
|
|
1067
|
+
if (!session || !isSessionActive()) {
|
|
1068
|
+
console.log();
|
|
1069
|
+
logger.error('No active session. Use "gk agent start" first.');
|
|
1070
|
+
console.log();
|
|
1071
|
+
process.exit(1);
|
|
1072
|
+
}
|
|
1073
|
+
const client = new PtyClient(session.port);
|
|
1074
|
+
let finalPrompt = prompt;
|
|
1075
|
+
// First send: wrap with full context
|
|
1076
|
+
if (session.isFirstSend) {
|
|
1077
|
+
finalPrompt = buildFirstSendPrompt(prompt, session);
|
|
1078
|
+
markFirstSendComplete();
|
|
1079
|
+
}
|
|
1080
|
+
try {
|
|
1081
|
+
const result = await client.send(finalPrompt);
|
|
1082
|
+
if (result.ok) {
|
|
1083
|
+
logger.info(`Sent prompt${result.exchangeId ? ` (exchange: ${result.exchangeId.slice(0, 8)}...)` : ''}`);
|
|
1084
|
+
}
|
|
1085
|
+
else {
|
|
1086
|
+
logger.error(result.error || 'Failed to send');
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
catch (err) {
|
|
1090
|
+
logger.error(`Failed to send: ${err.message}`);
|
|
1091
|
+
process.exit(1);
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
async function handleWait(timeout) {
|
|
1095
|
+
const session = loadSession();
|
|
1096
|
+
if (!session || !isSessionActive()) {
|
|
1097
|
+
console.log();
|
|
1098
|
+
logger.error('No active session.');
|
|
1099
|
+
console.log();
|
|
1100
|
+
process.exit(1);
|
|
1101
|
+
}
|
|
1102
|
+
const client = new PtyClient(session.port);
|
|
1103
|
+
console.log(`Waiting for completion (timeout: ${timeout}s)...`);
|
|
1104
|
+
const startTime = Date.now();
|
|
1105
|
+
const timeoutMs = timeout * 1000;
|
|
1106
|
+
while (Date.now() - startTime < timeoutMs) {
|
|
1107
|
+
try {
|
|
1108
|
+
const result = await client.complete();
|
|
1109
|
+
if (result.complete) {
|
|
1110
|
+
console.log('\nComplete!');
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
if (result.reason === 'pending_tool') {
|
|
1114
|
+
console.log('\n');
|
|
1115
|
+
logger.warn('Tool confirmation required');
|
|
1116
|
+
if (result.hint) {
|
|
1117
|
+
console.log(brand.dim(result.hint));
|
|
1118
|
+
}
|
|
1119
|
+
console.log(brand.dim('Use: gk agent pending'));
|
|
1120
|
+
return;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
catch (err) {
|
|
1124
|
+
logger.error(err.message);
|
|
1125
|
+
return;
|
|
1126
|
+
}
|
|
1127
|
+
await sleep(2000);
|
|
1128
|
+
process.stdout.write('.');
|
|
1129
|
+
}
|
|
1130
|
+
console.log('');
|
|
1131
|
+
logger.warn('Timeout reached. AI may still be working.');
|
|
1132
|
+
}
|
|
1133
|
+
async function handleExchange() {
|
|
1134
|
+
const session = loadSession();
|
|
1135
|
+
if (!session || !isSessionActive()) {
|
|
1136
|
+
console.log();
|
|
1137
|
+
logger.error('No active session.');
|
|
1138
|
+
console.log();
|
|
1139
|
+
process.exit(1);
|
|
1140
|
+
}
|
|
1141
|
+
const client = new PtyClient(session.port);
|
|
1142
|
+
try {
|
|
1143
|
+
const exchange = await client.exchange();
|
|
1144
|
+
console.log(JSON.stringify(exchange, null, 2));
|
|
1145
|
+
}
|
|
1146
|
+
catch (err) {
|
|
1147
|
+
logger.error(err.message);
|
|
1148
|
+
process.exit(1);
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
async function handlePending() {
|
|
1152
|
+
const session = loadSession();
|
|
1153
|
+
if (!session || !isSessionActive()) {
|
|
1154
|
+
console.log();
|
|
1155
|
+
logger.error('No active session.');
|
|
1156
|
+
console.log();
|
|
1157
|
+
process.exit(1);
|
|
1158
|
+
}
|
|
1159
|
+
const client = new PtyClient(session.port);
|
|
1160
|
+
try {
|
|
1161
|
+
const pending = await client.pending();
|
|
1162
|
+
if (pending.hasPending) {
|
|
1163
|
+
console.log();
|
|
1164
|
+
logger.warn('Tool confirmation required');
|
|
1165
|
+
console.log(JSON.stringify(pending, null, 2));
|
|
1166
|
+
console.log();
|
|
1167
|
+
console.log('To approve: ' + brand.primary('gk agent send "1"'));
|
|
1168
|
+
console.log('To reject: ' + brand.primary('gk agent send "3"'));
|
|
1169
|
+
console.log();
|
|
1170
|
+
}
|
|
1171
|
+
else {
|
|
1172
|
+
console.log('No pending confirmations');
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
catch (err) {
|
|
1176
|
+
logger.error(err.message);
|
|
1177
|
+
process.exit(1);
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
async function handleRead(lines) {
|
|
1181
|
+
const session = loadSession();
|
|
1182
|
+
if (!session || !isSessionActive()) {
|
|
1183
|
+
console.log();
|
|
1184
|
+
logger.error('No active session.');
|
|
1185
|
+
console.log();
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
}
|
|
1188
|
+
const client = new PtyClient(session.port);
|
|
1189
|
+
try {
|
|
1190
|
+
const output = await client.read(lines);
|
|
1191
|
+
console.log(output);
|
|
1192
|
+
}
|
|
1193
|
+
catch (err) {
|
|
1194
|
+
logger.error(err.message);
|
|
1195
|
+
process.exit(1);
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
async function handleInteractiveStatus() {
|
|
1199
|
+
const session = loadSession();
|
|
1200
|
+
if (!session) {
|
|
1201
|
+
console.log('No session file found.');
|
|
1202
|
+
return;
|
|
1203
|
+
}
|
|
1204
|
+
if (!isSessionActive()) {
|
|
1205
|
+
console.log('Session file exists but process not running.');
|
|
1206
|
+
console.log('Cleaning up...');
|
|
1207
|
+
clearSession();
|
|
1208
|
+
return;
|
|
1209
|
+
}
|
|
1210
|
+
const client = new PtyClient(session.port);
|
|
1211
|
+
try {
|
|
1212
|
+
const status = await client.status();
|
|
1213
|
+
console.log();
|
|
1214
|
+
console.log(pc.bold('Session: ') + brand.success('ACTIVE'));
|
|
1215
|
+
console.log(`Provider: ${session.provider}`);
|
|
1216
|
+
console.log(`Model: ${session.model}`);
|
|
1217
|
+
console.log(`PID: ${session.pid}`);
|
|
1218
|
+
console.log(`Started: ${session.startedAt}`);
|
|
1219
|
+
console.log(`First send pending: ${session.isFirstSend}`);
|
|
1220
|
+
if (session.context.agentName) {
|
|
1221
|
+
console.log(`Agent: ${session.context.agentName}`);
|
|
1222
|
+
}
|
|
1223
|
+
if (session.context.skills.length > 0) {
|
|
1224
|
+
console.log(`Skills: ${session.context.skills.join(', ')}`);
|
|
1225
|
+
}
|
|
1226
|
+
console.log(`Output buffer: ${status.outputLength} bytes`);
|
|
1227
|
+
console.log();
|
|
1228
|
+
}
|
|
1229
|
+
catch (err) {
|
|
1230
|
+
logger.error(err.message);
|
|
1231
|
+
process.exit(1);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
async function handleStop() {
|
|
1235
|
+
const session = loadSession();
|
|
1236
|
+
if (!session) {
|
|
1237
|
+
console.log('No active session.');
|
|
1238
|
+
return;
|
|
1239
|
+
}
|
|
1240
|
+
if (isSessionActive()) {
|
|
1241
|
+
try {
|
|
1242
|
+
const client = new PtyClient(session.port);
|
|
1243
|
+
await client.stop();
|
|
1244
|
+
logger.info('Session stopping...');
|
|
1245
|
+
}
|
|
1246
|
+
catch {
|
|
1247
|
+
// Server may have already stopped
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
clearSession();
|
|
1251
|
+
logger.success('Session stopped.');
|
|
1252
|
+
}
|
|
1253
|
+
// Helper: Load context file (simplified version)
|
|
1254
|
+
async function loadContextFile(contextRef) {
|
|
1255
|
+
let fullPath = contextRef;
|
|
1256
|
+
if (contextRef.startsWith('@')) {
|
|
1257
|
+
const relativePath = contextRef.substring(1);
|
|
1258
|
+
const searchPaths = [
|
|
1259
|
+
join(process.cwd(), '.docs', relativePath),
|
|
1260
|
+
join(process.cwd(), '.plans', relativePath),
|
|
1261
|
+
join(process.cwd(), 'docs', relativePath),
|
|
1262
|
+
join(process.cwd(), 'plans', relativePath),
|
|
1263
|
+
join(process.cwd(), relativePath)
|
|
1264
|
+
];
|
|
1265
|
+
let found = false;
|
|
1266
|
+
for (const searchPath of searchPaths) {
|
|
1267
|
+
try {
|
|
1268
|
+
await access(searchPath);
|
|
1269
|
+
fullPath = searchPath;
|
|
1270
|
+
found = true;
|
|
1271
|
+
break;
|
|
1272
|
+
}
|
|
1273
|
+
catch {
|
|
1274
|
+
continue;
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
if (!found) {
|
|
1278
|
+
throw new Error(`Context file not found: ${contextRef}`);
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
const fileStat = await stat(fullPath);
|
|
1282
|
+
if (fileStat.isDirectory()) {
|
|
1283
|
+
// Load directory recursively
|
|
1284
|
+
const contexts = [];
|
|
1285
|
+
const extensions = ['.md', '.txt', '.json', '.yaml', '.yml'];
|
|
1286
|
+
async function walkDir(dirPath, depth = 0) {
|
|
1287
|
+
if (depth > 3)
|
|
1288
|
+
return;
|
|
1289
|
+
const entries = await readdir(dirPath, { withFileTypes: true });
|
|
1290
|
+
for (const entry of entries) {
|
|
1291
|
+
if (entry.name.startsWith('.'))
|
|
1292
|
+
continue;
|
|
1293
|
+
const entryPath = join(dirPath, entry.name);
|
|
1294
|
+
if (entry.isDirectory()) {
|
|
1295
|
+
await walkDir(entryPath, depth + 1);
|
|
1296
|
+
}
|
|
1297
|
+
else if (entry.isFile()) {
|
|
1298
|
+
const ext = extname(entry.name).toLowerCase();
|
|
1299
|
+
if (extensions.includes(ext)) {
|
|
1300
|
+
const content = await readFile(entryPath, 'utf-8');
|
|
1301
|
+
contexts.push({
|
|
1302
|
+
type: 'context',
|
|
1303
|
+
name: entry.name,
|
|
1304
|
+
path: entryPath,
|
|
1305
|
+
relativePath: relative(fullPath, entryPath),
|
|
1306
|
+
content: content.trim(),
|
|
1307
|
+
originalRef: `${contextRef}/${relative(fullPath, entryPath)}`
|
|
1308
|
+
});
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
await walkDir(fullPath);
|
|
1314
|
+
return contexts;
|
|
1315
|
+
}
|
|
1316
|
+
// Single file
|
|
1317
|
+
const content = await readFile(fullPath, 'utf-8');
|
|
1318
|
+
return {
|
|
1319
|
+
type: 'context',
|
|
1320
|
+
name: basename(fullPath),
|
|
1321
|
+
path: fullPath,
|
|
1322
|
+
content: content.trim(),
|
|
1323
|
+
originalRef: contextRef
|
|
1324
|
+
};
|
|
1325
|
+
}
|
|
1326
|
+
// Helper: Sleep
|
|
1327
|
+
function sleep(ms) {
|
|
1328
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
1329
|
+
}
|