gemkit-cli 0.2.3 → 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.
Files changed (160) hide show
  1. package/README.md +141 -7
  2. package/dist/commands/agent/index.d.ts +9 -0
  3. package/dist/commands/agent/index.js +1329 -0
  4. package/dist/commands/cache/index.d.ts +5 -0
  5. package/dist/commands/cache/index.js +43 -0
  6. package/dist/commands/catalog/index.d.ts +2 -0
  7. package/dist/commands/catalog/index.js +57 -0
  8. package/dist/commands/config/index.d.ts +7 -0
  9. package/dist/commands/config/index.js +122 -0
  10. package/dist/commands/convert/index.d.ts +8 -0
  11. package/dist/commands/convert/index.js +391 -0
  12. package/dist/commands/doctor/index.d.ts +2 -0
  13. package/dist/commands/doctor/index.js +243 -0
  14. package/dist/commands/extension/index.d.ts +5 -0
  15. package/dist/commands/extension/index.js +52 -0
  16. package/dist/commands/index.d.ts +5 -0
  17. package/dist/commands/index.js +37 -0
  18. package/dist/commands/init/index.d.ts +6 -0
  19. package/dist/commands/init/index.js +345 -0
  20. package/dist/commands/new/index.d.ts +5 -0
  21. package/dist/commands/new/index.js +49 -0
  22. package/dist/commands/office/index.d.ts +5 -0
  23. package/dist/commands/office/index.js +283 -0
  24. package/dist/commands/paste/index.d.ts +10 -0
  25. package/dist/commands/paste/index.js +533 -0
  26. package/dist/commands/plan/index.d.ts +8 -0
  27. package/dist/commands/plan/index.js +247 -0
  28. package/dist/commands/session/index.d.ts +8 -0
  29. package/dist/commands/session/index.js +289 -0
  30. package/dist/commands/tokens/index.d.ts +6 -0
  31. package/dist/commands/tokens/index.js +148 -0
  32. package/dist/commands/update/index.d.ts +26 -0
  33. package/dist/commands/update/index.js +199 -0
  34. package/dist/commands/versions/index.d.ts +5 -0
  35. package/dist/commands/versions/index.js +39 -0
  36. package/dist/domains/agent/index.d.ts +8 -0
  37. package/dist/domains/agent/index.js +8 -0
  38. package/dist/domains/agent/mappings.d.ts +32 -0
  39. package/dist/domains/agent/mappings.js +164 -0
  40. package/dist/domains/agent/profile.d.ts +26 -0
  41. package/dist/domains/agent/profile.js +225 -0
  42. package/dist/domains/agent/pty-context.d.ts +11 -0
  43. package/dist/domains/agent/pty-context.js +83 -0
  44. package/dist/domains/agent/pty-providers.d.ts +18 -0
  45. package/dist/domains/agent/pty-providers.js +66 -0
  46. package/dist/domains/agent/pty-session.d.ts +33 -0
  47. package/dist/domains/agent/pty-session.js +82 -0
  48. package/dist/domains/agent/pty-types.d.ts +127 -0
  49. package/dist/domains/agent/pty-types.js +4 -0
  50. package/dist/domains/agent/search.d.ts +45 -0
  51. package/dist/domains/agent/search.js +614 -0
  52. package/dist/domains/agent/types.d.ts +78 -0
  53. package/dist/domains/agent/types.js +5 -0
  54. package/dist/domains/agent-office/documents-scanner.d.ts +9 -0
  55. package/dist/domains/agent-office/documents-scanner.js +143 -0
  56. package/dist/domains/agent-office/event-emitter.d.ts +43 -0
  57. package/dist/domains/agent-office/event-emitter.js +86 -0
  58. package/dist/domains/agent-office/file-watcher.d.ts +40 -0
  59. package/dist/domains/agent-office/file-watcher.js +173 -0
  60. package/dist/domains/agent-office/icons.d.ts +11 -0
  61. package/dist/domains/agent-office/icons.js +36 -0
  62. package/dist/domains/agent-office/index.d.ts +12 -0
  63. package/dist/domains/agent-office/index.js +20 -0
  64. package/dist/domains/agent-office/renderer/web/assets.d.ts +11 -0
  65. package/dist/domains/agent-office/renderer/web/assets.js +3419 -0
  66. package/dist/domains/agent-office/renderer/web/server.d.ts +42 -0
  67. package/dist/domains/agent-office/renderer/web/server.js +228 -0
  68. package/dist/domains/agent-office/renderer/web.d.ts +30 -0
  69. package/dist/domains/agent-office/renderer/web.js +111 -0
  70. package/dist/domains/agent-office/session-bridge.d.ts +23 -0
  71. package/dist/domains/agent-office/session-bridge.js +171 -0
  72. package/dist/domains/agent-office/state-machine.d.ts +5 -0
  73. package/dist/domains/agent-office/state-machine.js +82 -0
  74. package/dist/domains/agent-office/types.d.ts +91 -0
  75. package/dist/domains/agent-office/types.js +4 -0
  76. package/dist/domains/cache/index.d.ts +1 -0
  77. package/dist/domains/cache/index.js +1 -0
  78. package/dist/domains/cache/manager.d.ts +22 -0
  79. package/dist/domains/cache/manager.js +84 -0
  80. package/dist/domains/config/index.d.ts +5 -0
  81. package/dist/domains/config/index.js +5 -0
  82. package/dist/domains/config/manager.d.ts +24 -0
  83. package/dist/domains/config/manager.js +85 -0
  84. package/dist/domains/config/schema.d.ts +17 -0
  85. package/dist/domains/config/schema.js +96 -0
  86. package/dist/domains/convert/converter.d.ts +78 -0
  87. package/dist/domains/convert/converter.js +471 -0
  88. package/dist/domains/convert/index.d.ts +5 -0
  89. package/dist/domains/convert/index.js +5 -0
  90. package/dist/domains/convert/types.d.ts +88 -0
  91. package/dist/domains/convert/types.js +18 -0
  92. package/dist/domains/github/download.d.ts +12 -0
  93. package/dist/domains/github/download.js +51 -0
  94. package/dist/domains/github/index.d.ts +2 -0
  95. package/dist/domains/github/index.js +2 -0
  96. package/dist/domains/github/releases.d.ts +16 -0
  97. package/dist/domains/github/releases.js +68 -0
  98. package/dist/domains/installation/conflict.d.ts +13 -0
  99. package/dist/domains/installation/conflict.js +38 -0
  100. package/dist/domains/installation/file-sync.d.ts +16 -0
  101. package/dist/domains/installation/file-sync.js +77 -0
  102. package/dist/domains/installation/index.d.ts +3 -0
  103. package/dist/domains/installation/index.js +3 -0
  104. package/dist/domains/installation/metadata.d.ts +20 -0
  105. package/dist/domains/installation/metadata.js +52 -0
  106. package/dist/domains/plan/index.d.ts +2 -0
  107. package/dist/domains/plan/index.js +2 -0
  108. package/dist/domains/plan/resolver.d.ts +24 -0
  109. package/dist/domains/plan/resolver.js +164 -0
  110. package/dist/domains/plan/types.d.ts +13 -0
  111. package/dist/domains/plan/types.js +4 -0
  112. package/dist/domains/session/env.d.ts +51 -0
  113. package/dist/domains/session/env.js +118 -0
  114. package/dist/domains/session/index.d.ts +8 -0
  115. package/dist/domains/session/index.js +8 -0
  116. package/dist/domains/session/manager.d.ts +56 -0
  117. package/dist/domains/session/manager.js +205 -0
  118. package/dist/domains/session/paths.d.ts +6 -0
  119. package/dist/domains/session/paths.js +6 -0
  120. package/dist/domains/session/types.d.ts +121 -0
  121. package/dist/domains/session/types.js +5 -0
  122. package/dist/domains/session/writer.d.ts +82 -0
  123. package/dist/domains/session/writer.js +431 -0
  124. package/dist/domains/tokens/index.d.ts +5 -0
  125. package/dist/domains/tokens/index.js +5 -0
  126. package/dist/domains/tokens/pricing.d.ts +38 -0
  127. package/dist/domains/tokens/pricing.js +129 -0
  128. package/dist/domains/tokens/scanner.d.ts +42 -0
  129. package/dist/domains/tokens/scanner.js +168 -0
  130. package/dist/index.d.ts +5 -0
  131. package/dist/index.js +87 -58
  132. package/dist/services/aipty.d.ts +76 -0
  133. package/dist/services/aipty.js +276 -0
  134. package/dist/services/archive.d.ts +22 -0
  135. package/dist/services/archive.js +53 -0
  136. package/dist/services/auto-update.d.ts +26 -0
  137. package/dist/services/auto-update.js +117 -0
  138. package/dist/services/hash.d.ts +36 -0
  139. package/dist/services/hash.js +63 -0
  140. package/dist/services/logger.d.ts +28 -0
  141. package/dist/services/logger.js +102 -0
  142. package/dist/services/music.d.ts +67 -0
  143. package/dist/services/music.js +290 -0
  144. package/dist/services/npm.d.ts +22 -0
  145. package/dist/services/npm.js +65 -0
  146. package/dist/services/pty-client.d.ts +66 -0
  147. package/dist/services/pty-client.js +154 -0
  148. package/dist/services/pty-server.d.ts +102 -0
  149. package/dist/services/pty-server.js +613 -0
  150. package/dist/types/index.d.ts +155 -0
  151. package/dist/types/index.js +4 -0
  152. package/dist/utils/colors.d.ts +43 -0
  153. package/dist/utils/colors.js +98 -0
  154. package/dist/utils/errors.d.ts +24 -0
  155. package/dist/utils/errors.js +56 -0
  156. package/dist/utils/paths.d.ts +46 -0
  157. package/dist/utils/paths.js +89 -0
  158. package/dist/utils/platform.d.ts +11 -0
  159. package/dist/utils/platform.js +31 -0
  160. package/package.json +55 -54
@@ -0,0 +1,345 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { spawn, spawnSync } from 'child_process';
4
+ import ora from 'ora';
5
+ import { getLatestRelease, getReleaseByVersion } from '../../domains/github/releases.js';
6
+ import { downloadRelease } from '../../domains/github/download.js';
7
+ import { extractTarGz } from '../../services/archive.js';
8
+ import { createMetadata, saveMetadata, isInstalled } from '../../domains/installation/metadata.js';
9
+ import { logger } from '../../services/logger.js';
10
+ import { brand, pc } from '../../utils/colors.js';
11
+ import { CLI_VERSION } from '../update/index.js';
12
+ import { getLatestNpmVersion, isUpdateAvailable } from '../../services/npm.js';
13
+ // Package name for CLI
14
+ const CLI_PACKAGE_NAME = 'gemkit-cli';
15
+ /**
16
+ * Update CLI via npm
17
+ */
18
+ function updateCli(version) {
19
+ const result = spawnSync('npm', ['install', '-g', `${CLI_PACKAGE_NAME}@${version}`], {
20
+ encoding: 'utf-8',
21
+ stdio: 'pipe',
22
+ shell: true,
23
+ });
24
+ return result.status === 0;
25
+ }
26
+ /**
27
+ * Get exclusion patterns based on installation mode
28
+ */
29
+ function getExcludePatterns(mode) {
30
+ switch (mode) {
31
+ case 'gemini':
32
+ // Exclude Claude and generic agent folders (Gemini uses .gemini)
33
+ return ['.claude', '.claude/**', '.agent', '.agent/**'];
34
+ case 'claude':
35
+ // Exclude generic agent folder (Claude Code uses .claude)
36
+ return ['.agent', '.agent/**'];
37
+ case 'antigravity':
38
+ // Exclude Claude folder (Antigravity uses .agent)
39
+ return ['.claude', '.claude/**'];
40
+ case 'full':
41
+ default:
42
+ // No exclusions - install everything
43
+ return [];
44
+ }
45
+ }
46
+ /**
47
+ * Get mode description for display
48
+ */
49
+ function getModeDescription(mode) {
50
+ switch (mode) {
51
+ case 'gemini':
52
+ return 'Gemini CLI (excludes .claude, .agent)';
53
+ case 'claude':
54
+ return 'Claude Code (excludes .agent)';
55
+ case 'antigravity':
56
+ return 'Antigravity (excludes .claude)';
57
+ case 'full':
58
+ default:
59
+ return 'Full (all files)';
60
+ }
61
+ }
62
+ /**
63
+ * Check if a path matches any of the exclude patterns
64
+ * Supports simple directory patterns like '.claude', '.claude/**'
65
+ */
66
+ function shouldExclude(filePath, excludePatterns) {
67
+ // Normalize path separators
68
+ const normalizedPath = filePath.replace(/\\/g, '/');
69
+ for (const pattern of excludePatterns) {
70
+ // Simple directory match: '.claude' or '.agent'
71
+ if (!pattern.includes('*')) {
72
+ if (normalizedPath === pattern || normalizedPath.startsWith(pattern + '/')) {
73
+ return true;
74
+ }
75
+ }
76
+ // Glob pattern: '.claude/**' means anything under .claude
77
+ else if (pattern.endsWith('/**')) {
78
+ const baseDir = pattern.slice(0, -3); // Remove '/**'
79
+ if (normalizedPath.startsWith(baseDir + '/')) {
80
+ return true;
81
+ }
82
+ }
83
+ }
84
+ return false;
85
+ }
86
+ /**
87
+ * Create a filter function for extraction based on exclude patterns
88
+ */
89
+ function createExcludeFilter(excludePatterns) {
90
+ if (excludePatterns.length === 0) {
91
+ return undefined; // No filtering needed
92
+ }
93
+ return (path) => {
94
+ // Return true to INCLUDE the file, false to EXCLUDE
95
+ return !shouldExclude(path, excludePatterns);
96
+ };
97
+ }
98
+ /**
99
+ * Install spawn-agent extension using gemini CLI (silent - for use with spinner)
100
+ * Runs: gemini extensions install .gemini/extensions/spawn-agent
101
+ */
102
+ async function installSpawnAgentExtensionSilent() {
103
+ const extensionPath = join('.gemini', 'extensions', 'spawn-agent');
104
+ // Check if extension directory exists
105
+ if (!existsSync(extensionPath)) {
106
+ return {
107
+ success: false,
108
+ error: `Extension directory not found: ${extensionPath}`
109
+ };
110
+ }
111
+ return new Promise((resolve) => {
112
+ const child = spawn('gemini', ['extensions', 'install', extensionPath], {
113
+ stdio: ['pipe', 'pipe', 'pipe'],
114
+ shell: process.platform === 'win32',
115
+ cwd: process.cwd()
116
+ });
117
+ let stdout = '';
118
+ let stderr = '';
119
+ let resolved = false;
120
+ // Timeout after 30 seconds
121
+ const timeout = setTimeout(() => {
122
+ if (!resolved) {
123
+ resolved = true;
124
+ child.kill();
125
+ resolve({
126
+ success: false,
127
+ error: 'Extension installation timed out'
128
+ });
129
+ }
130
+ }, 30000);
131
+ child.stdout?.on('data', (data) => {
132
+ stdout += data.toString();
133
+ });
134
+ child.stderr?.on('data', (data) => {
135
+ stderr += data.toString();
136
+ });
137
+ child.on('close', (code) => {
138
+ if (!resolved) {
139
+ resolved = true;
140
+ clearTimeout(timeout);
141
+ if (code === 0) {
142
+ resolve({ success: true });
143
+ }
144
+ else {
145
+ resolve({
146
+ success: false,
147
+ error: stderr || stdout || `Exit code: ${code}`
148
+ });
149
+ }
150
+ }
151
+ });
152
+ child.on('error', (err) => {
153
+ if (!resolved) {
154
+ resolved = true;
155
+ clearTimeout(timeout);
156
+ resolve({
157
+ success: false,
158
+ error: `Failed to run gemini CLI: ${err.message}`
159
+ });
160
+ }
161
+ });
162
+ });
163
+ }
164
+ /**
165
+ * Check if spawn-agent extension is installed in Gemini
166
+ */
167
+ export async function isSpawnAgentInstalled() {
168
+ return new Promise((resolve) => {
169
+ const child = spawn('gemini', ['extensions', 'list'], {
170
+ stdio: ['pipe', 'pipe', 'pipe'],
171
+ shell: process.platform === 'win32'
172
+ });
173
+ let stdout = '';
174
+ let resolved = false;
175
+ // Timeout after 20 seconds
176
+ const timeout = setTimeout(() => {
177
+ if (!resolved) {
178
+ resolved = true;
179
+ child.kill();
180
+ resolve(false);
181
+ }
182
+ }, 20000);
183
+ child.stdout?.on('data', (data) => {
184
+ stdout += data.toString();
185
+ });
186
+ child.on('close', (code) => {
187
+ if (!resolved) {
188
+ resolved = true;
189
+ clearTimeout(timeout);
190
+ if (code === 0) {
191
+ // Check if spawn-agent appears in the output
192
+ const isInstalled = stdout.toLowerCase().includes('spawn-agent');
193
+ resolve(isInstalled);
194
+ }
195
+ else {
196
+ resolve(false);
197
+ }
198
+ }
199
+ });
200
+ child.on('error', () => {
201
+ if (!resolved) {
202
+ resolved = true;
203
+ clearTimeout(timeout);
204
+ resolve(false);
205
+ }
206
+ });
207
+ });
208
+ }
209
+ export function registerInitCommand(cli) {
210
+ cli
211
+ .command('init', 'Initialize GemKit in project')
212
+ .option('--version <ver>', 'Specific version to install')
213
+ .option('-f, --force', 'Overwrite existing installation')
214
+ .option('--exclude <patterns...>', 'File patterns to exclude')
215
+ .option('--skip-extension', 'Skip spawn-agent extension installation')
216
+ .option('--full', 'Install all files (default)')
217
+ .option('--gemini', 'Gemini CLI mode (excludes .claude, .agent folders)')
218
+ .option('--claude', 'Claude Code mode (excludes .agent folder)')
219
+ .option('--antigravity', 'Antigravity mode (excludes .claude folder)')
220
+ .action(async (options) => {
221
+ // Determine installation mode (priority: gemini > claude > antigravity > full)
222
+ let mode = 'full';
223
+ if (options.gemini)
224
+ mode = 'gemini';
225
+ else if (options.claude)
226
+ mode = 'claude';
227
+ else if (options.antigravity)
228
+ mode = 'antigravity';
229
+ else if (options.full)
230
+ mode = 'full';
231
+ // Check if already installed
232
+ if (isInstalled() && !options.force) {
233
+ console.log();
234
+ logger.warn('GemKit is already installed. Use --force to reinstall.');
235
+ console.log();
236
+ return;
237
+ }
238
+ console.log();
239
+ console.log(pc.bold(brand.geminiPurple('Initializing GemKit')));
240
+ console.log();
241
+ console.log(` ${brand.dim('Mode')} ${brand.accent(getModeDescription(mode))}`);
242
+ console.log();
243
+ // Check CLI version and auto-update if available
244
+ const cliSpinner = ora({
245
+ text: 'Checking CLI version...',
246
+ color: 'magenta'
247
+ }).start();
248
+ try {
249
+ const latestCli = await getLatestNpmVersion(CLI_PACKAGE_NAME);
250
+ if (latestCli && isUpdateAvailable(CLI_VERSION, latestCli.version)) {
251
+ cliSpinner.text = `Updating CLI v${CLI_VERSION} → v${latestCli.version}...`;
252
+ const updated = updateCli(latestCli.version);
253
+ if (updated) {
254
+ cliSpinner.succeed(`CLI updated v${brand.primary(CLI_VERSION)} → v${brand.primary(latestCli.version)}`);
255
+ }
256
+ else {
257
+ cliSpinner.warn(`CLI v${brand.primary(CLI_VERSION)} (update to v${latestCli.version} failed)`);
258
+ }
259
+ }
260
+ else {
261
+ cliSpinner.succeed(`CLI v${brand.primary(CLI_VERSION)}`);
262
+ }
263
+ }
264
+ catch {
265
+ cliSpinner.succeed(`CLI v${brand.primary(CLI_VERSION)}`);
266
+ }
267
+ // Step 1: Fetch Kits release info
268
+ const fetchSpinner = ora({
269
+ text: 'Fetching latest Kits release...',
270
+ color: 'magenta'
271
+ }).start();
272
+ const release = options.version
273
+ ? await getReleaseByVersion(options.version)
274
+ : await getLatestRelease();
275
+ if (!release) {
276
+ fetchSpinner.fail('No Kits release found');
277
+ console.log();
278
+ process.exit(1);
279
+ }
280
+ fetchSpinner.succeed(`Found Kits v${brand.primary(release.version)}`);
281
+ // Step 2: Download Kits
282
+ const downloadSpinner = ora({
283
+ text: 'Downloading Kits...',
284
+ color: 'magenta'
285
+ }).start();
286
+ let tarPath;
287
+ try {
288
+ tarPath = await downloadRelease(release);
289
+ downloadSpinner.succeed('Kits downloaded');
290
+ }
291
+ catch (error) {
292
+ downloadSpinner.fail(`Kits download failed: ${error instanceof Error ? error.message : String(error)}`);
293
+ console.log();
294
+ process.exit(1);
295
+ }
296
+ // Step 3: Extract Kits
297
+ const excludePatterns = getExcludePatterns(mode);
298
+ const extractSpinner = ora({
299
+ text: mode === 'full' ? 'Extracting Kits...' : `Extracting Kits (${mode} mode)...`,
300
+ color: 'magenta'
301
+ }).start();
302
+ const extractDir = process.cwd();
303
+ const result = await extractTarGz({
304
+ source: tarPath,
305
+ destination: extractDir,
306
+ strip: 1,
307
+ filter: createExcludeFilter(excludePatterns),
308
+ });
309
+ if (!result.success) {
310
+ extractSpinner.fail(`Kits extraction failed: ${result.error}`);
311
+ console.log();
312
+ process.exit(1);
313
+ }
314
+ extractSpinner.succeed(`Extracted ${result.extractedFiles.length} files`);
315
+ // Save metadata
316
+ const metadata = createMetadata(release.version, 'local', result.extractedFiles);
317
+ saveMetadata(metadata);
318
+ console.log();
319
+ logger.success(`Kits v${brand.primary(release.version)} installed successfully!`);
320
+ // Step 4: Install spawn-agent extension (unless skipped or using non-Gemini mode)
321
+ const shouldInstallExtension = !options.skipExtension && (mode === 'full' || mode === 'gemini');
322
+ if (shouldInstallExtension) {
323
+ console.log();
324
+ const extensionSpinner = ora({
325
+ text: 'Installing spawn-agent extension...',
326
+ color: 'magenta'
327
+ }).start();
328
+ const extensionResult = await installSpawnAgentExtensionSilent();
329
+ if (extensionResult.success) {
330
+ extensionSpinner.succeed('spawn-agent extension installed in Gemini CLI');
331
+ }
332
+ else {
333
+ extensionSpinner.warn(`Could not install spawn-agent extension: ${extensionResult.error}`);
334
+ logger.info(`You can manually install it with: ${brand.primary('gemini extensions install .gemini/extensions/spawn-agent')}`);
335
+ }
336
+ }
337
+ else if (mode === 'claude' || mode === 'antigravity') {
338
+ console.log();
339
+ logger.info(`Skipped Gemini extension (${mode} mode).`);
340
+ }
341
+ console.log();
342
+ logger.info(`Run ${brand.primary('gk doctor')} to verify installation.`);
343
+ console.log();
344
+ });
345
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * New command - Create a new GemKit project
3
+ */
4
+ import type { CAC } from 'cac';
5
+ export declare function registerNewCommand(cli: CAC): void;
@@ -0,0 +1,49 @@
1
+ /**
2
+ * New command - Create a new GemKit project
3
+ */
4
+ import { existsSync } from 'fs';
5
+ import { join } from 'path';
6
+ import { getLatestRelease } from '../../domains/github/index.js';
7
+ import { downloadRelease } from '../../domains/github/download.js';
8
+ import { extractTarGz } from '../../services/archive.js';
9
+ import { logger } from '../../services/logger.js';
10
+ import { brand, pc } from '../../utils/colors.js';
11
+ export function registerNewCommand(cli) {
12
+ cli
13
+ .command('new <name>', 'Create a new project from starter kit')
14
+ .action(async (name) => {
15
+ const destDir = join(process.cwd(), name);
16
+ if (existsSync(destDir)) {
17
+ console.log();
18
+ logger.error(`Directory already exists: ${name}`);
19
+ console.log();
20
+ process.exit(1);
21
+ }
22
+ console.log();
23
+ console.log(pc.bold(brand.geminiPurple('Creating New Project')));
24
+ console.log();
25
+ logger.info(`Name: ${brand.primary(name)}`);
26
+ const latest = await getLatestRelease();
27
+ if (!latest) {
28
+ logger.error('Failed to fetch latest starter kit.');
29
+ console.log();
30
+ process.exit(1);
31
+ }
32
+ logger.info(`Fetching latest starter kit (v${latest.version})...`);
33
+ const tarPath = await downloadRelease(latest);
34
+ const result = await extractTarGz({
35
+ source: tarPath,
36
+ destination: destDir,
37
+ strip: 1,
38
+ });
39
+ if (result.success) {
40
+ console.log();
41
+ logger.success(`Project ${brand.primary(name)} created successfully!`);
42
+ console.log(`\nNext steps:\n cd ${brand.primary(name)}\n gk init\n`);
43
+ }
44
+ else {
45
+ logger.error(`Failed to create project: ${result.error}`);
46
+ console.log();
47
+ }
48
+ });
49
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Agent Office command - Gamified visualization for multi-agent workflows
3
+ */
4
+ import type { CAC } from 'cac';
5
+ export declare function registerOfficeCommand(cli: CAC): void;
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Agent Office command - Gamified visualization for multi-agent workflows
3
+ */
4
+ import { startWebDashboard, OfficeEventEmitter, createInitialState, SessionFileWatcher, sessionToOfficeState, } from '../../domains/agent-office/index.js';
5
+ import { logger } from '../../services/logger.js';
6
+ import { brand, ui, pc } from '../../utils/colors.js';
7
+ // ═══════════════════════════════════════════════════════════════════════════
8
+ // HELP FUNCTIONS
9
+ // ═══════════════════════════════════════════════════════════════════════════
10
+ function showMainHelp() {
11
+ console.log();
12
+ console.log(pc.bold(brand.geminiPurple('Agent Office')));
13
+ console.log();
14
+ console.log('Usage:');
15
+ console.log(` ${brand.primary('gk office')} <subcommand> [options]`);
16
+ console.log();
17
+ console.log('Subcommands:');
18
+ console.log(` ${brand.primary('start')} Start the visualization`);
19
+ console.log(` ${brand.primary('status')} Show current office state`);
20
+ console.log(` ${brand.primary('watch')} Watch office state changes`);
21
+ console.log();
22
+ console.log('Options:');
23
+ console.log(` ${brand.dim('-p, --port <n>')} [start] Web server port (default: 3847)`);
24
+ console.log(` ${brand.dim('--no-open')} [start] Don't auto-open browser`);
25
+ console.log(` ${brand.dim('--json')} [status/watch] Output as JSON`);
26
+ console.log();
27
+ console.log('Examples:');
28
+ console.log(` ${brand.dim('gk office start')}`);
29
+ console.log(` ${brand.dim('gk office start --port 4000')}`);
30
+ console.log(` ${brand.dim('gk office status --json')}`);
31
+ console.log(` ${brand.dim('gk office watch')}`);
32
+ console.log();
33
+ }
34
+ function showStartHelp() {
35
+ console.log();
36
+ console.log(pc.bold(brand.geminiPurple('gk office start')));
37
+ console.log(brand.dim('Start the Agent Office web dashboard'));
38
+ console.log();
39
+ console.log('Usage:');
40
+ console.log(` ${brand.primary('gk office start')} [options]`);
41
+ console.log();
42
+ console.log('Options:');
43
+ console.log(` ${brand.dim('-p, --port <n>')} Web server port (default: 3847)`);
44
+ console.log(` ${brand.dim('--no-open')} Don't auto-open browser`);
45
+ console.log();
46
+ console.log('Examples:');
47
+ console.log(` ${brand.dim('gk office start')}`);
48
+ console.log(` ${brand.dim('gk office start --port 4000')}`);
49
+ console.log(` ${brand.dim('gk office start --port 4000 --no-open')}`);
50
+ console.log();
51
+ }
52
+ function showStatusHelp() {
53
+ console.log();
54
+ console.log(pc.bold(brand.geminiPurple('gk office status')));
55
+ console.log(brand.dim('Show current Agent Office state'));
56
+ console.log();
57
+ console.log('Usage:');
58
+ console.log(` ${brand.primary('gk office status')} [options]`);
59
+ console.log();
60
+ console.log('Options:');
61
+ console.log(` ${brand.dim('--json')} Output as JSON`);
62
+ console.log();
63
+ }
64
+ function showWatchHelp() {
65
+ console.log();
66
+ console.log(pc.bold(brand.geminiPurple('gk office watch')));
67
+ console.log(brand.dim('Watch Agent Office state changes in real-time'));
68
+ console.log();
69
+ console.log('Usage:');
70
+ console.log(` ${brand.primary('gk office watch')} [options]`);
71
+ console.log();
72
+ console.log('Options:');
73
+ console.log(` ${brand.dim('--json')} Output as JSON`);
74
+ console.log();
75
+ }
76
+ // ═══════════════════════════════════════════════════════════════════════════
77
+ // HANDLERS
78
+ // ═══════════════════════════════════════════════════════════════════════════
79
+ /**
80
+ * Handle start subcommand
81
+ */
82
+ async function handleStart(options) {
83
+ console.log();
84
+ logger.info('Starting Agent Office...');
85
+ console.log();
86
+ try {
87
+ const dashboard = await startWebDashboard({
88
+ port: options.port || 3847,
89
+ autoOpen: options.open !== false,
90
+ onReady: (port) => {
91
+ logger.success(`Dashboard running at ${brand.primary(`http://localhost:${port}`)}`);
92
+ console.log(brand.dim(' Press Ctrl+C to stop'));
93
+ console.log();
94
+ },
95
+ onError: (error) => {
96
+ logger.error(`Error: ${error.message}`);
97
+ },
98
+ });
99
+ process.on('SIGINT', () => {
100
+ dashboard.stop();
101
+ console.log();
102
+ logger.info('Agent Office stopped.');
103
+ console.log();
104
+ process.exit(0);
105
+ });
106
+ }
107
+ catch (error) {
108
+ if (error instanceof Error) {
109
+ if (error.message === 'No active session found') {
110
+ console.log();
111
+ logger.warn('No active session found.');
112
+ console.log(brand.dim(' Start a GemKit session first with: gk agent'));
113
+ console.log();
114
+ }
115
+ else {
116
+ logger.error(`Failed to start: ${error.message}`);
117
+ }
118
+ }
119
+ process.exit(1);
120
+ }
121
+ }
122
+ /**
123
+ * Handle status subcommand
124
+ */
125
+ function handleStatus(options) {
126
+ const emitter = new OfficeEventEmitter(createInitialState());
127
+ const watcher = new SessionFileWatcher({
128
+ onSessionChange: (session) => {
129
+ const state = sessionToOfficeState(session);
130
+ if (options.json) {
131
+ console.log(JSON.stringify({
132
+ ...state,
133
+ agents: Object.fromEntries(state.agents),
134
+ }, null, 2));
135
+ }
136
+ else {
137
+ console.log();
138
+ console.log(pc.bold(brand.geminiPurple('Agent Office Status')));
139
+ console.log(ui.line());
140
+ console.log();
141
+ console.log(` ${brand.dim('Active Plan:')} ${state.activePlan ? brand.primary(state.activePlan) : brand.dim('(none)')}`);
142
+ console.log(` ${brand.dim('Agents:')} ${state.agents.size}`);
143
+ console.log(` ${brand.dim('Inbox Items:')} ${state.inbox.length}`);
144
+ console.log(` ${brand.dim('Documents:')} ${state.documents.length}`);
145
+ if (state.agents.size > 0) {
146
+ console.log();
147
+ console.log(brand.dim(' Agents:'));
148
+ for (const [id, agent] of state.agents) {
149
+ const statusColor = agent.state === 'working' ? brand.warn :
150
+ agent.state === 'idle' ? brand.success : brand.dim;
151
+ console.log(` ${agent.icon} ${brand.primary(agent.role)} ${statusColor(`[${agent.state}]`)}`);
152
+ }
153
+ }
154
+ console.log();
155
+ }
156
+ watcher.stop();
157
+ emitter.dispose();
158
+ },
159
+ onEvent: () => { }, // No-op for status
160
+ onError: (error) => {
161
+ console.log();
162
+ logger.error(`Error: ${error.message}`);
163
+ console.log();
164
+ process.exit(1);
165
+ },
166
+ });
167
+ const started = watcher.start();
168
+ if (!started) {
169
+ console.log();
170
+ logger.warn('No active session found.');
171
+ console.log(brand.dim(' Start a GemKit session first with: gk agent'));
172
+ console.log();
173
+ process.exit(1);
174
+ }
175
+ }
176
+ /**
177
+ * Handle watch subcommand
178
+ */
179
+ function handleWatch(options) {
180
+ console.log();
181
+ logger.info('Watching Agent Office state...');
182
+ console.log(brand.dim(' Press Ctrl+C to stop'));
183
+ console.log();
184
+ const emitter = new OfficeEventEmitter(createInitialState());
185
+ const watcher = new SessionFileWatcher({
186
+ onSessionChange: (session) => {
187
+ const state = sessionToOfficeState(session);
188
+ if (options.json) {
189
+ console.log(JSON.stringify({
190
+ timestamp: new Date().toISOString(),
191
+ ...state,
192
+ agents: Object.fromEntries(state.agents),
193
+ }));
194
+ }
195
+ else {
196
+ const time = new Date().toLocaleTimeString();
197
+ console.log(`${brand.dim(time)} ${brand.info('State:')} ${state.agents.size} agents, ${state.inbox.length} inbox items`);
198
+ }
199
+ },
200
+ onEvent: (event) => {
201
+ if (options.json) {
202
+ console.log(JSON.stringify({ type: 'event', event }));
203
+ }
204
+ else {
205
+ const time = new Date().toLocaleTimeString();
206
+ console.log(`${brand.dim(time)} ${brand.secondary('Event:')} ${event.type}`);
207
+ }
208
+ },
209
+ onError: (error) => {
210
+ logger.error(`Error: ${error.message}`);
211
+ },
212
+ });
213
+ const started = watcher.start();
214
+ if (!started) {
215
+ console.log();
216
+ logger.warn('No active session found.');
217
+ console.log(brand.dim(' Start a GemKit session first with: gk agent'));
218
+ console.log();
219
+ process.exit(1);
220
+ }
221
+ process.on('SIGINT', () => {
222
+ watcher.stop();
223
+ emitter.dispose();
224
+ console.log();
225
+ logger.info('Stopped watching.');
226
+ console.log();
227
+ process.exit(0);
228
+ });
229
+ }
230
+ // ═══════════════════════════════════════════════════════════════════════════
231
+ // COMMAND REGISTRATION
232
+ // ═══════════════════════════════════════════════════════════════════════════
233
+ export function registerOfficeCommand(cli) {
234
+ cli
235
+ .command('office [subcommand]', 'Agent Office visualization (start, status, watch)')
236
+ .option('-p, --port <port>', '[start] Web server port', { default: 3847 })
237
+ .option('--no-open', "[start] Don't auto-open browser")
238
+ .option('--json', '[status/watch] Output as JSON')
239
+ .action(async (subcommand, options) => {
240
+ // Parse port as number
241
+ if (options.port) {
242
+ options.port = parseInt(String(options.port), 10);
243
+ }
244
+ // Handle help for subcommands
245
+ if (options.help || options.h) {
246
+ switch (subcommand) {
247
+ case 'start':
248
+ showStartHelp();
249
+ return;
250
+ case 'status':
251
+ showStatusHelp();
252
+ return;
253
+ case 'watch':
254
+ showWatchHelp();
255
+ return;
256
+ default:
257
+ showMainHelp();
258
+ return;
259
+ }
260
+ }
261
+ // Route to handlers
262
+ switch (subcommand) {
263
+ case 'start':
264
+ await handleStart(options);
265
+ break;
266
+ case 'status':
267
+ handleStatus(options);
268
+ break;
269
+ case 'watch':
270
+ handleWatch(options);
271
+ break;
272
+ case undefined:
273
+ case 'help':
274
+ showMainHelp();
275
+ break;
276
+ default:
277
+ console.log();
278
+ logger.error(`Unknown subcommand: ${subcommand}`);
279
+ showMainHelp();
280
+ process.exit(1);
281
+ }
282
+ });
283
+ }