cubelife 0.2.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 (92) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +81 -0
  3. package/SPRITE-LICENSE +14 -0
  4. package/dist/cli.d.ts +2 -0
  5. package/dist/cli.js +39 -0
  6. package/dist/commands/agents.d.ts +2 -0
  7. package/dist/commands/agents.js +303 -0
  8. package/dist/commands/auth.d.ts +2 -0
  9. package/dist/commands/auth.js +233 -0
  10. package/dist/commands/billing.d.ts +2 -0
  11. package/dist/commands/billing.js +362 -0
  12. package/dist/commands/creature.d.ts +2 -0
  13. package/dist/commands/creature.js +166 -0
  14. package/dist/commands/default.d.ts +2 -0
  15. package/dist/commands/default.js +87 -0
  16. package/dist/commands/doctor.d.ts +2 -0
  17. package/dist/commands/doctor.js +48 -0
  18. package/dist/commands/init.d.ts +2 -0
  19. package/dist/commands/init.js +200 -0
  20. package/dist/commands/mcp.d.ts +2 -0
  21. package/dist/commands/mcp.js +9 -0
  22. package/dist/commands/projects.d.ts +2 -0
  23. package/dist/commands/projects.js +122 -0
  24. package/dist/commands/setup.d.ts +2 -0
  25. package/dist/commands/setup.js +453 -0
  26. package/dist/commands/status.d.ts +2 -0
  27. package/dist/commands/status.js +89 -0
  28. package/dist/commands/tutorial.d.ts +2 -0
  29. package/dist/commands/tutorial.js +9 -0
  30. package/dist/commands/view.d.ts +2 -0
  31. package/dist/commands/view.js +262 -0
  32. package/dist/data/sprite-data.d.ts +32 -0
  33. package/dist/data/sprite-data.js +865 -0
  34. package/dist/index.d.ts +7 -0
  35. package/dist/index.js +6 -0
  36. package/dist/lib/api.d.ts +162 -0
  37. package/dist/lib/api.js +160 -0
  38. package/dist/lib/auth.d.ts +12 -0
  39. package/dist/lib/auth.js +113 -0
  40. package/dist/lib/browser.d.ts +1 -0
  41. package/dist/lib/browser.js +21 -0
  42. package/dist/lib/command-helpers.d.ts +26 -0
  43. package/dist/lib/command-helpers.js +60 -0
  44. package/dist/lib/compositor.d.ts +34 -0
  45. package/dist/lib/compositor.js +232 -0
  46. package/dist/lib/config.d.ts +39 -0
  47. package/dist/lib/config.js +89 -0
  48. package/dist/lib/constants.d.ts +12 -0
  49. package/dist/lib/constants.js +39 -0
  50. package/dist/lib/detect.d.ts +17 -0
  51. package/dist/lib/detect.js +99 -0
  52. package/dist/lib/doctor.d.ts +18 -0
  53. package/dist/lib/doctor.js +321 -0
  54. package/dist/lib/index.d.ts +11 -0
  55. package/dist/lib/index.js +6 -0
  56. package/dist/lib/integration.d.ts +66 -0
  57. package/dist/lib/integration.js +337 -0
  58. package/dist/lib/poll.d.ts +11 -0
  59. package/dist/lib/poll.js +31 -0
  60. package/dist/lib/resolve.d.ts +1 -0
  61. package/dist/lib/resolve.js +10 -0
  62. package/dist/lib/services/account-service.d.ts +17 -0
  63. package/dist/lib/services/account-service.js +30 -0
  64. package/dist/lib/services/agent-service.d.ts +17 -0
  65. package/dist/lib/services/agent-service.js +62 -0
  66. package/dist/lib/services/creature-service.d.ts +12 -0
  67. package/dist/lib/services/creature-service.js +35 -0
  68. package/dist/lib/services/project-service.d.ts +9 -0
  69. package/dist/lib/services/project-service.js +22 -0
  70. package/dist/lib/tutorial.d.ts +12 -0
  71. package/dist/lib/tutorial.js +358 -0
  72. package/dist/mcp/server.d.ts +8 -0
  73. package/dist/mcp/server.js +116 -0
  74. package/dist/ui/banner.d.ts +3 -0
  75. package/dist/ui/banner.js +27 -0
  76. package/dist/ui/half-block.d.ts +6 -0
  77. package/dist/ui/half-block.js +45 -0
  78. package/dist/ui/helpers.d.ts +3 -0
  79. package/dist/ui/helpers.js +11 -0
  80. package/dist/ui/index.d.ts +5 -0
  81. package/dist/ui/index.js +5 -0
  82. package/dist/ui/panel.d.ts +7 -0
  83. package/dist/ui/panel.js +21 -0
  84. package/dist/ui/preview.d.ts +7 -0
  85. package/dist/ui/preview.js +21 -0
  86. package/dist/ui/table.d.ts +8 -0
  87. package/dist/ui/table.js +20 -0
  88. package/dist/ui/theme.d.ts +24 -0
  89. package/dist/ui/theme.js +32 -0
  90. package/dist/version.d.ts +1 -0
  91. package/dist/version.js +1 -0
  92. package/package.json +63 -0
@@ -0,0 +1,453 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join, resolve } from 'node:path';
4
+ import * as p from '@clack/prompts';
5
+ import { readProjectConfig } from '../lib/config.js';
6
+ import { writeHookScript, mergeSettings, readClaudeSettings, writeClaudeSettings, detectLegacySetup, resolveProjectRoot, hookScriptPath, readJsonConfig, writeJsonConfig, buildMcpEntryWithAgent, mergeMcpServerEntry, hasStaleMcpEntry, removeMcpEntry, readYamlConfig, writeYamlConfig, buildContinueMcpEntry, mergeContinueMcpServer, hasStaleContinueEntry, removeContinueEntry, registerClaudeMcp, registerCodexMcp, } from '../lib/integration.js';
7
+ import { vscodeGlobalStoragePath, vscodeUserPath, CLINE_EXTENSION_IDS } from '../lib/detect.js';
8
+ import { API_BASE_URL } from '../lib/constants.js';
9
+ import { brand } from '../ui/theme.js';
10
+ import { isCancel } from '../ui/helpers.js';
11
+ import { rootOpts } from '../lib/command-helpers.js';
12
+ function displayPath(fullPath) {
13
+ const home = homedir();
14
+ return fullPath.startsWith(home) ? fullPath.replace(home, '~') : fullPath;
15
+ }
16
+ async function promptScopes(scopes, relativePath, json) {
17
+ if (json)
18
+ return scopes.filter((s) => s.initial !== false).map((s) => s.value);
19
+ const selected = await p.multiselect({
20
+ message: 'Where should the MCP config be installed?',
21
+ options: [
22
+ ...scopes.map((s) => ({ value: s.value, label: s.label, hint: s.hint })),
23
+ { value: '__custom__', label: 'Custom directory', hint: 'Specify a project root' },
24
+ ],
25
+ initialValues: scopes.filter((s) => s.initial).map((s) => s.value),
26
+ required: true,
27
+ });
28
+ if (isCancel(selected)) {
29
+ process.exit(0);
30
+ throw new Error('unreachable');
31
+ }
32
+ const paths = selected.filter((v) => v !== '__custom__');
33
+ if (selected.includes('__custom__')) {
34
+ const dir = await p.text({
35
+ message: 'Enter the project root directory:',
36
+ validate: (v) => { if (!v.trim())
37
+ return 'Path cannot be empty.'; },
38
+ });
39
+ if (isCancel(dir)) {
40
+ process.exit(0);
41
+ throw new Error('unreachable');
42
+ }
43
+ paths.push(join(dir, relativePath));
44
+ }
45
+ return [...new Set(paths)];
46
+ }
47
+ async function cleanStaleEntries(allScopes, selectedPaths, serverKey, json) {
48
+ const cleaned = [];
49
+ const unselected = allScopes
50
+ .map((s) => s.value)
51
+ .filter((p) => !selectedPaths.includes(p));
52
+ for (const path of unselected) {
53
+ if (await hasStaleMcpEntry(path, serverKey)) {
54
+ if (json) {
55
+ await removeMcpEntry(path, serverKey);
56
+ cleaned.push(path);
57
+ }
58
+ else {
59
+ const remove = await p.select({
60
+ message: `Found stale cubelife entry in ${displayPath(path)}. This would override your new config. Remove it?`,
61
+ options: [
62
+ { value: true, label: 'Yes, remove it' },
63
+ { value: false, label: 'No, leave it' },
64
+ ],
65
+ initialValue: true,
66
+ });
67
+ if (!isCancel(remove) && remove) {
68
+ await removeMcpEntry(path, serverKey);
69
+ cleaned.push(path);
70
+ }
71
+ }
72
+ }
73
+ }
74
+ return cleaned;
75
+ }
76
+ export function registerSetupCommands(program) {
77
+ const setup = program
78
+ .command('setup')
79
+ .description('Configure CubeLife integration for an AI tool');
80
+ setup
81
+ .command('claude-code')
82
+ .description('Set up Claude Code (MCP + hooks)')
83
+ .action(async function () {
84
+ const { json } = rootOpts(this);
85
+ const root = resolveProjectRoot();
86
+ const config = await readProjectConfig();
87
+ if (!config?.agentId) {
88
+ if (json) {
89
+ console.log(JSON.stringify({ error: 'no_agent_linked' }));
90
+ }
91
+ else {
92
+ p.log.error(`No agent linked. Run ${brand.accent('cubelife init')} first.`);
93
+ }
94
+ process.exit(1);
95
+ }
96
+ let mode;
97
+ if (json) {
98
+ mode = 'both';
99
+ }
100
+ else {
101
+ const selected = await p.select({
102
+ message: 'What should we configure?',
103
+ options: [
104
+ { value: 'both', label: 'Both MCP + Hooks (recommended)', hint: 'Full integration' },
105
+ { value: 'mcp', label: 'MCP only', hint: 'AI-driven state reports' },
106
+ { value: 'hooks', label: 'Hooks only', hint: 'Automatic tool-based state tracking' },
107
+ ],
108
+ initialValue: 'both',
109
+ });
110
+ if (isCancel(selected)) {
111
+ p.log.message('Setup cancelled.');
112
+ process.exit(0);
113
+ }
114
+ mode = selected;
115
+ }
116
+ let mcpScope = 'user';
117
+ if (mode === 'mcp' || mode === 'both') {
118
+ if (!json) {
119
+ const selected = await p.select({
120
+ message: 'Where should the MCP server be registered?',
121
+ options: [
122
+ { value: 'user', label: 'Globally (recommended)', hint: 'Works in all projects' },
123
+ { value: 'project', label: 'This project only', hint: displayPath(root) },
124
+ ],
125
+ initialValue: 'user',
126
+ });
127
+ if (isCancel(selected)) {
128
+ process.exit(0);
129
+ throw new Error('unreachable');
130
+ }
131
+ mcpScope = selected;
132
+ }
133
+ registerClaudeMcp(mcpScope);
134
+ }
135
+ if (mode === 'hooks' || mode === 'both') {
136
+ await writeHookScript();
137
+ }
138
+ const hooksSettingsPath = join(homedir(), '.claude', 'settings.json');
139
+ const allConflicts = [];
140
+ if (mode === 'hooks' || mode === 'both') {
141
+ const existing = await readClaudeSettings(resolve(hooksSettingsPath, '..', '..'));
142
+ const { settings, conflicts } = mergeSettings(existing, 'hooks');
143
+ await writeClaudeSettings(resolve(hooksSettingsPath, '..', '..'), settings);
144
+ allConflicts.push(...conflicts);
145
+ }
146
+ const legacy = detectLegacySetup(root);
147
+ if (json) {
148
+ console.log(JSON.stringify({
149
+ ok: true,
150
+ mode,
151
+ mcpScope,
152
+ conflicts: allConflicts,
153
+ legacy,
154
+ hookScript: (mode === 'hooks' || mode === 'both') ? hookScriptPath() : undefined,
155
+ }));
156
+ }
157
+ else {
158
+ if (allConflicts.length) {
159
+ for (const c of allConflicts) {
160
+ p.log.warn(c);
161
+ }
162
+ }
163
+ p.log.success('Claude Code integration configured.');
164
+ if (mode === 'mcp' || mode === 'both') {
165
+ p.log.message(` MCP: ${brand.success('cubelife')} server registered (${mcpScope} scope)`);
166
+ }
167
+ if (mode === 'hooks' || mode === 'both') {
168
+ p.log.message(' Hooks: tool events mapped to CubeLife states');
169
+ p.log.message(` Script: ${brand.muted(hookScriptPath())}`);
170
+ }
171
+ if (legacy.length) {
172
+ p.log.warn(`Legacy files detected: ${legacy.join(', ')}`);
173
+ p.log.message(brand.muted('These can be removed. The CLI manages agent keys centrally now.'));
174
+ }
175
+ p.log.message(brand.muted('Restart Claude Code to activate.'));
176
+ }
177
+ });
178
+ async function requireAgent(json) {
179
+ const config = await readProjectConfig();
180
+ if (!config?.agentId) {
181
+ if (json) {
182
+ console.log(JSON.stringify({ error: 'no_agent_linked' }));
183
+ }
184
+ else {
185
+ p.log.error(`No agent linked. Run ${brand.accent('cubelife init')} first.`);
186
+ }
187
+ process.exit(1);
188
+ throw new Error('unreachable');
189
+ }
190
+ return config.agentId;
191
+ }
192
+ async function setupMcpTool(agentId, opts, json) {
193
+ const { toolName, configPaths, allScopes, serverKey = 'mcpServers', restartHint } = opts;
194
+ const entry = buildMcpEntryWithAgent(agentId);
195
+ let anyReplaced = false;
196
+ for (const configPath of configPaths) {
197
+ const existing = await readJsonConfig(configPath);
198
+ const { config, replaced } = mergeMcpServerEntry(existing, serverKey, entry);
199
+ await writeJsonConfig(configPath, config);
200
+ if (replaced)
201
+ anyReplaced = true;
202
+ }
203
+ if (allScopes) {
204
+ await cleanStaleEntries(allScopes, configPaths, serverKey, json);
205
+ }
206
+ if (json) {
207
+ console.log(JSON.stringify({ ok: true, tool: toolName, configPaths, replaced: anyReplaced }));
208
+ }
209
+ else {
210
+ if (anyReplaced) {
211
+ p.log.warn('Existing cubelife MCP entry updated.');
212
+ }
213
+ p.log.success(`${toolName} integration configured.`);
214
+ for (const cp of configPaths) {
215
+ p.log.message(` Config: ${brand.muted(displayPath(cp))}`);
216
+ }
217
+ p.log.message(brand.muted(restartHint));
218
+ }
219
+ }
220
+ setup
221
+ .command('cursor')
222
+ .description('Set up Cursor (MCP)')
223
+ .action(async function () {
224
+ const { json } = rootOpts(this);
225
+ const agentId = await requireAgent(json);
226
+ const root = resolveProjectRoot();
227
+ const cursorScopes = [
228
+ { value: join(root, '.cursor', 'mcp.json'), label: 'This project', hint: displayPath(root), initial: true },
229
+ { value: join(homedir(), '.cursor', 'mcp.json'), label: 'Globally', hint: '~/.cursor/mcp.json', initial: false },
230
+ ];
231
+ const configPaths = await promptScopes(cursorScopes, '.cursor/mcp.json', json);
232
+ await setupMcpTool(agentId, {
233
+ toolName: 'Cursor',
234
+ configPaths,
235
+ allScopes: cursorScopes,
236
+ restartHint: 'Restart Cursor to activate.',
237
+ }, json);
238
+ });
239
+ setup
240
+ .command('windsurf')
241
+ .description('Set up Windsurf (MCP)')
242
+ .action(async function () {
243
+ const { json } = rootOpts(this);
244
+ const agentId = await requireAgent(json);
245
+ await setupMcpTool(agentId, {
246
+ toolName: 'Windsurf',
247
+ configPaths: [join(homedir(), '.codeium', 'windsurf', 'mcp_config.json')],
248
+ restartHint: 'Restart Windsurf to activate.',
249
+ }, json);
250
+ });
251
+ setup
252
+ .command('copilot')
253
+ .description('Set up GitHub Copilot (MCP)')
254
+ .action(async function () {
255
+ const { json } = rootOpts(this);
256
+ const agentId = await requireAgent(json);
257
+ const root = resolveProjectRoot();
258
+ const copilotScopes = [
259
+ { value: join(root, '.vscode', 'mcp.json'), label: 'This project', hint: displayPath(root), initial: true },
260
+ { value: join(vscodeUserPath(), 'mcp.json'), label: 'Globally (VS Code user)', hint: displayPath(join(vscodeUserPath(), 'mcp.json')), initial: false },
261
+ ];
262
+ const configPaths = await promptScopes(copilotScopes, '.vscode/mcp.json', json);
263
+ await setupMcpTool(agentId, {
264
+ toolName: 'GitHub Copilot',
265
+ configPaths,
266
+ allScopes: copilotScopes,
267
+ serverKey: 'servers',
268
+ restartHint: 'Restart VS Code to activate.',
269
+ }, json);
270
+ });
271
+ function resolveClineConfigPath() {
272
+ const gs = vscodeGlobalStoragePath();
273
+ for (const extId of CLINE_EXTENSION_IDS) {
274
+ const dir = join(gs, extId, 'settings');
275
+ if (existsSync(dir)) {
276
+ return join(dir, 'cline_mcp_settings.json');
277
+ }
278
+ }
279
+ return join(gs, CLINE_EXTENSION_IDS[0], 'settings', 'cline_mcp_settings.json');
280
+ }
281
+ setup
282
+ .command('cline')
283
+ .description('Set up Cline (MCP)')
284
+ .action(async function () {
285
+ const { json } = rootOpts(this);
286
+ const agentId = await requireAgent(json);
287
+ await setupMcpTool(agentId, {
288
+ toolName: 'Cline',
289
+ configPaths: [resolveClineConfigPath()],
290
+ restartHint: 'Restart VS Code to activate.',
291
+ }, json);
292
+ });
293
+ setup
294
+ .command('continue')
295
+ .description('Set up Continue.dev (MCP)')
296
+ .action(async function () {
297
+ const { json } = rootOpts(this);
298
+ const agentId = await requireAgent(json);
299
+ const root = resolveProjectRoot();
300
+ const continueScopes = [
301
+ { value: join(homedir(), '.continue', 'config.yaml'), label: 'Globally for Continue.dev', hint: '~/.continue/config.yaml', initial: true },
302
+ { value: join(root, '.continue', 'config.yaml'), label: 'This project', hint: displayPath(root), initial: false },
303
+ ];
304
+ const configPaths = await promptScopes(continueScopes, '.continue/config.yaml', json);
305
+ let anyReplaced = false;
306
+ for (const configPath of configPaths) {
307
+ const existing = await readYamlConfig(configPath);
308
+ const entry = buildContinueMcpEntry(agentId);
309
+ const { config, replaced } = mergeContinueMcpServer(existing, entry);
310
+ await writeYamlConfig(configPath, config);
311
+ if (replaced)
312
+ anyReplaced = true;
313
+ }
314
+ const unselectedYaml = continueScopes
315
+ .map((s) => s.value)
316
+ .filter((p) => !configPaths.includes(p));
317
+ for (const path of unselectedYaml) {
318
+ if (await hasStaleContinueEntry(path)) {
319
+ if (json) {
320
+ await removeContinueEntry(path);
321
+ }
322
+ else {
323
+ const remove = await p.select({
324
+ message: `Found stale cubelife entry in ${displayPath(path)}. This would override your new config. Remove it?`,
325
+ options: [
326
+ { value: true, label: 'Yes, remove it' },
327
+ { value: false, label: 'No, leave it' },
328
+ ],
329
+ initialValue: true,
330
+ });
331
+ if (!isCancel(remove) && remove) {
332
+ await removeContinueEntry(path);
333
+ }
334
+ }
335
+ }
336
+ }
337
+ if (json) {
338
+ console.log(JSON.stringify({ ok: true, tool: 'Continue.dev', configPaths, replaced: anyReplaced }));
339
+ }
340
+ else {
341
+ if (anyReplaced) {
342
+ p.log.warn('Existing cubelife MCP entry updated.');
343
+ }
344
+ p.log.success('Continue.dev integration configured.');
345
+ for (const cp of configPaths) {
346
+ p.log.message(` Config: ${brand.muted(displayPath(cp))}`);
347
+ }
348
+ p.log.message(brand.muted('Restart Continue.dev to activate.'));
349
+ }
350
+ });
351
+ function printMcpSnippet(agentId) {
352
+ p.log.message(brand.accent('MCP config (for any tool that supports it):'));
353
+ p.log.message('');
354
+ p.log.message(` {
355
+ "mcpServers": {
356
+ "cubelife": {
357
+ "command": "npx",
358
+ "args": ["cubelife", "mcp"],
359
+ "env": { "CUBELIFE_AGENT_ID": "${agentId}" }
360
+ }
361
+ }
362
+ }`);
363
+ }
364
+ function printSdkSnippets(agentId) {
365
+ p.log.message('');
366
+ p.log.message(brand.accent('Node SDK:'));
367
+ p.log.message('');
368
+ p.log.message(` import { CubeLifeClient } from '@cubelife/sdk';
369
+ const cube = new CubeLifeClient({ agentId: '${agentId}' });
370
+ await cube.report({ state: 'coding', detail: 'Working on feature' });`);
371
+ p.log.message('');
372
+ p.log.message(brand.accent('Python SDK:'));
373
+ p.log.message('');
374
+ p.log.message(` from cubelife import CubeLifeClient
375
+ cube = CubeLifeClient(agent_id="${agentId}")
376
+ cube.report(state="coding", detail="Working on feature")`);
377
+ p.log.message('');
378
+ p.log.message(brand.accent('REST API:'));
379
+ p.log.message('');
380
+ p.log.message(` curl -X POST ${API_BASE_URL}/api/v1/state \\
381
+ -H "Content-Type: application/json" \\
382
+ -H "X-Agent-Key: <your-api-key>" \\
383
+ -d '{"state": "coding", "detail": "Working on feature"}'`);
384
+ }
385
+ setup
386
+ .command('codex')
387
+ .description('Set up Codex CLI (MCP)')
388
+ .action(async function () {
389
+ const { json } = rootOpts(this);
390
+ const agentId = await requireAgent(json);
391
+ try {
392
+ const { replaced } = registerCodexMcp(agentId);
393
+ if (json) {
394
+ console.log(JSON.stringify({ ok: true, tool: 'Codex CLI', replaced }));
395
+ }
396
+ else {
397
+ if (replaced)
398
+ p.log.warn('Existing cubelife MCP entry updated.');
399
+ p.log.success('Codex CLI integration configured.');
400
+ p.log.message(` MCP: ${brand.success('cubelife')} server registered`);
401
+ p.log.message(brand.muted('Restart Codex CLI to activate.'));
402
+ }
403
+ }
404
+ catch {
405
+ if (json) {
406
+ console.log(JSON.stringify({ error: 'codex_mcp_failed' }));
407
+ }
408
+ else {
409
+ p.log.error('Failed to register MCP server. Is Codex CLI installed?');
410
+ p.log.message(`Install with: ${brand.accent('npm install -g @openai/codex')}`);
411
+ }
412
+ process.exit(1);
413
+ }
414
+ });
415
+ setup
416
+ .command('aider')
417
+ .description('Show Aider integration instructions')
418
+ .action(async function () {
419
+ const { json } = rootOpts(this);
420
+ const agentId = await requireAgent(json);
421
+ if (json) {
422
+ console.log(JSON.stringify({
423
+ ok: true,
424
+ tool: 'Aider',
425
+ type: 'instructions',
426
+ agentId,
427
+ }));
428
+ return;
429
+ }
430
+ p.log.message('Aider does not support MCP natively.');
431
+ p.log.message('Add CubeLife state reports to your Aider workflow using the SDK:');
432
+ printSdkSnippets(agentId);
433
+ });
434
+ setup
435
+ .command('manual')
436
+ .description('Show manual integration instructions')
437
+ .action(async function () {
438
+ const { json } = rootOpts(this);
439
+ const agentId = await requireAgent(json);
440
+ if (json) {
441
+ console.log(JSON.stringify({
442
+ ok: true,
443
+ tool: 'Manual',
444
+ type: 'instructions',
445
+ agentId,
446
+ }));
447
+ return;
448
+ }
449
+ p.log.message('Manual integration options:');
450
+ printMcpSnippet(agentId);
451
+ printSdkSnippets(agentId);
452
+ });
453
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerStatusCommand(program: Command): void;
@@ -0,0 +1,89 @@
1
+ import * as p from '@clack/prompts';
2
+ import { createAgentClient } from '../lib/api.js';
3
+ import { dot, brand } from '../ui/theme.js';
4
+ import { VALID_STATES, VALID_SENTIMENTS } from '../lib/constants.js';
5
+ import { rootOpts, getErrorMessage, } from '../lib/command-helpers.js';
6
+ export function registerStatusCommand(program) {
7
+ program
8
+ .command('status [state] [detail]')
9
+ .description('Report agent state (e.g. cubelife status coding "Working on auth")')
10
+ .option('--progress <value>', 'Task progress from 0 to 1')
11
+ .option('--sentiment <value>', 'Current sentiment (positive, negative, neutral)')
12
+ .action(async function (state, detail, opts) {
13
+ const { json, agent: agentOverride } = rootOpts(this);
14
+ if (!state) {
15
+ if (json) {
16
+ console.log(JSON.stringify({ error: 'state_required', validStates: VALID_STATES }));
17
+ }
18
+ else {
19
+ p.log.error('State is required.');
20
+ p.log.message(brand.muted(`Valid states: ${VALID_STATES.join(', ')}`));
21
+ p.log.message(brand.muted(`Example: ${brand.accent('cubelife status coding "Working on auth"')}`));
22
+ }
23
+ process.exit(1);
24
+ }
25
+ if (!VALID_STATES.includes(state)) {
26
+ if (json) {
27
+ console.log(JSON.stringify({ error: 'invalid_state', validStates: VALID_STATES }));
28
+ }
29
+ else {
30
+ p.log.error(`Invalid state: ${state}`);
31
+ p.log.message(brand.muted(`Valid states: ${VALID_STATES.join(', ')}`));
32
+ }
33
+ process.exit(1);
34
+ }
35
+ let progress;
36
+ if (opts.progress !== undefined) {
37
+ progress = parseFloat(opts.progress);
38
+ if (isNaN(progress) || progress < 0 || progress > 1) {
39
+ if (json)
40
+ console.log(JSON.stringify({ error: 'progress_must_be_0_to_1' }));
41
+ else
42
+ p.log.error('Progress must be a number between 0 and 1.');
43
+ process.exit(1);
44
+ }
45
+ }
46
+ if (opts.sentiment && !VALID_SENTIMENTS.includes(opts.sentiment)) {
47
+ if (json) {
48
+ console.log(JSON.stringify({ error: 'invalid_sentiment', validSentiments: VALID_SENTIMENTS }));
49
+ }
50
+ else {
51
+ p.log.error(`Invalid sentiment. Must be one of: ${VALID_SENTIMENTS.join(', ')}`);
52
+ }
53
+ process.exit(1);
54
+ }
55
+ const client = await createAgentClient(agentOverride);
56
+ if (!client) {
57
+ if (json) {
58
+ console.log(JSON.stringify({ error: 'no_agent_linked' }));
59
+ }
60
+ else {
61
+ p.log.error(`No agent linked. Run ${brand.accent('cubelife init')} first.`);
62
+ }
63
+ process.exit(1);
64
+ }
65
+ try {
66
+ const reportOpts = {
67
+ detail: detail ?? undefined,
68
+ progress,
69
+ sentiment: opts.sentiment,
70
+ };
71
+ await client.report(state, reportOpts);
72
+ if (json) {
73
+ console.log(JSON.stringify({ ok: true }));
74
+ }
75
+ else {
76
+ p.log.success(`${dot.working} State updated: ${brand.primary(state)}${detail ? ` ${brand.muted(`"${detail}"`)}` : ''}`);
77
+ }
78
+ }
79
+ catch (err) {
80
+ if (json) {
81
+ console.log(JSON.stringify({ ok: false, error: getErrorMessage(err) }));
82
+ }
83
+ else {
84
+ p.log.error(getErrorMessage(err));
85
+ }
86
+ process.exit(1);
87
+ }
88
+ });
89
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerTutorialCommand(program: Command): void;
@@ -0,0 +1,9 @@
1
+ import { runTutorial } from '../lib/tutorial.js';
2
+ export function registerTutorialCommand(program) {
3
+ program
4
+ .command('tutorial')
5
+ .description('Interactive setup walkthrough (6 steps)')
6
+ .action(async () => {
7
+ await runTutorial();
8
+ });
9
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerViewCommand(program: Command): void;