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,166 @@
1
+ import chalk from 'chalk';
2
+ import * as p from '@clack/prompts';
3
+ import { createAdminClient } from '../lib/api.js';
4
+ import { CREATURE_TYPES, CREATURE_DEFAULT_COLORS, CREATURE_DESCRIPTIONS, CREATURE_NAME_MAX, isValidHexColor, } from '../lib/constants.js';
5
+ import { brand } from '../ui/theme.js';
6
+ import { table } from '../ui/table.js';
7
+ import { rootOpts, requireLinkedAgent, handleCommandError, } from '../lib/command-helpers.js';
8
+ import { setCreatureType, setCreatureColour, setCreatureName, AgentNotCreatureError, } from '../lib/services/creature-service.js';
9
+ function colourSwatch(hex) {
10
+ return chalk.hex(hex)('██') + ' ' + brand.muted(hex);
11
+ }
12
+ export function registerCreatureCommands(program) {
13
+ const creature = program
14
+ .command('creature')
15
+ .description('Manage creature companions');
16
+ creature
17
+ .command('types')
18
+ .description('List available creature types')
19
+ .action(async function () {
20
+ const { json } = rootOpts(this);
21
+ if (json) {
22
+ const types = CREATURE_TYPES.map((t) => ({
23
+ type: t,
24
+ description: CREATURE_DESCRIPTIONS[t],
25
+ defaultColor: CREATURE_DEFAULT_COLORS[t],
26
+ }));
27
+ console.log(JSON.stringify({ types }));
28
+ return;
29
+ }
30
+ const rows = CREATURE_TYPES.map((t) => ({
31
+ type: t,
32
+ colour: colourSwatch(CREATURE_DEFAULT_COLORS[t]),
33
+ description: CREATURE_DESCRIPTIONS[t],
34
+ }));
35
+ console.log(table([
36
+ { label: 'Type', key: 'type', width: 10 },
37
+ { label: 'Colour', key: 'colour', width: 16 },
38
+ { label: 'Description', key: 'description' },
39
+ ], rows));
40
+ });
41
+ creature
42
+ .command('set <type>')
43
+ .description('Set the creature type for the linked agent')
44
+ .action(async function (type) {
45
+ const { json } = rootOpts(this);
46
+ if (!CREATURE_TYPES.includes(type)) {
47
+ if (json) {
48
+ console.log(JSON.stringify({ error: 'invalid_type', validTypes: CREATURE_TYPES }));
49
+ }
50
+ else {
51
+ p.log.error(`Invalid creature type: ${type}`);
52
+ p.log.message(brand.muted(`Valid types: ${CREATURE_TYPES.join(', ')}`));
53
+ }
54
+ process.exit(1);
55
+ }
56
+ const linked = await requireLinkedAgent(json);
57
+ const spin = p.spinner();
58
+ if (!json)
59
+ spin.start('Updating agent');
60
+ try {
61
+ const client = await createAdminClient();
62
+ const result = await setCreatureType(client, linked.projectId, linked.agentId, type);
63
+ if (json) {
64
+ console.log(JSON.stringify({ ok: true, type: result.type, color: result.color }));
65
+ }
66
+ else {
67
+ spin.stop('Agent updated');
68
+ p.log.success(`Set to ${brand.primary(result.type)} ${colourSwatch(result.color)}`);
69
+ }
70
+ }
71
+ catch (err) {
72
+ handleCommandError({ error: err, json, spinner: spin });
73
+ }
74
+ });
75
+ creature
76
+ .command('colour <hex>')
77
+ .alias('color')
78
+ .description('Set the creature colour')
79
+ .action(async function (hex) {
80
+ const { json } = rootOpts(this);
81
+ const normalised = hex.startsWith('#') ? hex : `#${hex}`;
82
+ if (!isValidHexColor(normalised)) {
83
+ if (json) {
84
+ console.log(JSON.stringify({ error: 'invalid_hex', expected: '#RRGGBB' }));
85
+ }
86
+ else {
87
+ p.log.error(`Invalid hex colour: ${hex}`);
88
+ p.log.message(brand.muted('Expected format: #RRGGBB (e.g. #7B9F35)'));
89
+ }
90
+ process.exit(1);
91
+ }
92
+ const linked = await requireLinkedAgent(json);
93
+ const spin = p.spinner();
94
+ if (!json)
95
+ spin.start('Updating creature');
96
+ try {
97
+ const client = await createAdminClient();
98
+ const result = await setCreatureColour(client, linked.projectId, linked.agentId, normalised);
99
+ if (json) {
100
+ console.log(JSON.stringify({ ok: true, color: result.color }));
101
+ }
102
+ else {
103
+ spin.stop('Colour updated');
104
+ p.log.success(`Colour set to ${colourSwatch(result.color)}`);
105
+ }
106
+ }
107
+ catch (err) {
108
+ if (err instanceof AgentNotCreatureError) {
109
+ if (!json)
110
+ spin.stop('Failed');
111
+ if (json) {
112
+ console.log(JSON.stringify({ error: 'agent_not_creature' }));
113
+ }
114
+ else {
115
+ p.log.error(`Agent is in human form. Switch to creature first with ${brand.accent('cubelife creature set <type>')}.`);
116
+ }
117
+ process.exit(1);
118
+ }
119
+ handleCommandError({ error: err, json, spinner: spin });
120
+ }
121
+ });
122
+ creature
123
+ .command('name <name>')
124
+ .description('Name the creature')
125
+ .action(async function (name) {
126
+ const { json } = rootOpts(this);
127
+ if (name.length > CREATURE_NAME_MAX) {
128
+ if (json) {
129
+ console.log(JSON.stringify({ error: 'name_too_long', max: CREATURE_NAME_MAX }));
130
+ }
131
+ else {
132
+ p.log.error(`Name must be ${CREATURE_NAME_MAX} characters or fewer (got ${name.length}).`);
133
+ }
134
+ process.exit(1);
135
+ }
136
+ const linked = await requireLinkedAgent(json);
137
+ const spin = p.spinner();
138
+ if (!json)
139
+ spin.start('Updating creature');
140
+ try {
141
+ const client = await createAdminClient();
142
+ const result = await setCreatureName(client, linked.projectId, linked.agentId, name);
143
+ if (json) {
144
+ console.log(JSON.stringify({ ok: true, name: result.name }));
145
+ }
146
+ else {
147
+ spin.stop('Name updated');
148
+ p.log.success(`Creature named ${brand.primary(result.name)}`);
149
+ }
150
+ }
151
+ catch (err) {
152
+ if (err instanceof AgentNotCreatureError) {
153
+ if (!json)
154
+ spin.stop('Failed');
155
+ if (json) {
156
+ console.log(JSON.stringify({ error: 'agent_not_creature' }));
157
+ }
158
+ else {
159
+ p.log.error(`Agent is in human form. Switch to creature first with ${brand.accent('cubelife creature set <type>')}.`);
160
+ }
161
+ process.exit(1);
162
+ }
163
+ handleCommandError({ error: err, json, spinner: spin });
164
+ }
165
+ });
166
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function defaultAction(program: Command): Promise<void>;
@@ -0,0 +1,87 @@
1
+ import * as p from '@clack/prompts';
2
+ import { logo, version as versionTag } from '../ui/index.js';
3
+ import { brand } from '../ui/theme.js';
4
+ import { isCancel } from '../ui/helpers.js';
5
+ import { CLI_VERSION } from '../version.js';
6
+ import { detectProgress, runTutorial } from '../lib/tutorial.js';
7
+ import { runAllChecks } from '../lib/doctor.js';
8
+ import { detectInstalledTools } from '../lib/detect.js';
9
+ function formatCheck(r) {
10
+ const icon = r.status === 'pass' ? '✓' : r.status === 'warn' ? '!' : '✗';
11
+ let line = ` ${icon} ${r.name}: ${r.message}`;
12
+ if (r.fix)
13
+ line += `\n ${r.fix}`;
14
+ return line;
15
+ }
16
+ export async function defaultAction(program) {
17
+ const progress = await detectProgress();
18
+ if (progress < 6) {
19
+ await runTutorial();
20
+ return;
21
+ }
22
+ console.log(`\n${logo()}\n${versionTag(CLI_VERSION)}\n`);
23
+ const action = await p.select({
24
+ message: 'What would you like to do?',
25
+ options: [
26
+ { value: 'view', label: 'View your agent', hint: 'cubelife view' },
27
+ { value: 'billing', label: 'Billing overview', hint: 'cubelife billing' },
28
+ { value: 'doctor', label: 'Run diagnostics', hint: 'cubelife doctor' },
29
+ { value: 'setup', label: 'Configure a tool', hint: 'cubelife setup <tool>' },
30
+ { value: 'tutorial', label: 'Re-run setup wizard', hint: 'cubelife tutorial' },
31
+ { value: 'help', label: 'View all commands', hint: 'cubelife --help' },
32
+ ],
33
+ });
34
+ if (isCancel(action)) {
35
+ process.exit(0);
36
+ }
37
+ switch (action) {
38
+ case 'view':
39
+ await program.parseAsync(['view'], { from: 'user' });
40
+ break;
41
+ case 'billing':
42
+ await program.parseAsync(['billing'], { from: 'user' });
43
+ break;
44
+ case 'doctor': {
45
+ const spin = p.spinner();
46
+ spin.start('Running diagnostics');
47
+ const results = await runAllChecks(false);
48
+ spin.stop('Diagnostics complete');
49
+ console.log();
50
+ for (const r of results) {
51
+ console.log(formatCheck(r));
52
+ }
53
+ console.log();
54
+ const fails = results.filter((r) => r.status === 'fail');
55
+ if (fails.length > 0) {
56
+ p.log.error(`${fails.length} check${fails.length === 1 ? '' : 's'} failed`);
57
+ }
58
+ else {
59
+ p.log.success('All checks passed');
60
+ }
61
+ break;
62
+ }
63
+ case 'setup': {
64
+ const tools = detectInstalledTools();
65
+ if (tools.length === 0) {
66
+ p.log.info('No AI tools detected on this system.');
67
+ p.log.message(`Run ${brand.accent('cubelife setup manual')} for generic integration instructions.`);
68
+ break;
69
+ }
70
+ const toolId = await p.select({
71
+ message: 'Which tool?',
72
+ options: tools.map((t) => ({ value: t.id, label: t.name })),
73
+ });
74
+ if (isCancel(toolId)) {
75
+ process.exit(0);
76
+ }
77
+ await program.parseAsync(['setup', toolId], { from: 'user' });
78
+ break;
79
+ }
80
+ case 'tutorial':
81
+ await runTutorial();
82
+ break;
83
+ case 'help':
84
+ program.outputHelp();
85
+ break;
86
+ }
87
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerDoctorCommand(program: Command): void;
@@ -0,0 +1,48 @@
1
+ import * as p from '@clack/prompts';
2
+ import { runAllChecks } from '../lib/doctor.js';
3
+ import { getRootOpts } from '../ui/helpers.js';
4
+ import { status as statusIcons, dim } from '../ui/theme.js';
5
+ function formatResult(r) {
6
+ const icon = statusIcons[r.status] ?? `[${r.status}]`;
7
+ let line = ` ${icon} ${r.name}: ${r.message}`;
8
+ if (r.fix)
9
+ line += `\n ${dim(r.fix)}`;
10
+ return line;
11
+ }
12
+ export function registerDoctorCommand(program) {
13
+ program
14
+ .command('doctor')
15
+ .description('Run diagnostic checks')
16
+ .option('--fix', 'Auto-fix common issues')
17
+ .action(async function () {
18
+ const opts = this.opts();
19
+ const rootOpts = getRootOpts(this);
20
+ const autoFix = !!opts['fix'];
21
+ const json = !!rootOpts['json'];
22
+ const spin = p.spinner();
23
+ if (!json)
24
+ spin.start('Running diagnostics');
25
+ const results = await runAllChecks(autoFix);
26
+ if (json) {
27
+ console.log(JSON.stringify({ checks: results }, null, 2));
28
+ return;
29
+ }
30
+ spin.stop('Diagnostics complete');
31
+ console.log();
32
+ for (const r of results) {
33
+ console.log(formatResult(r));
34
+ }
35
+ console.log();
36
+ const fails = results.filter((r) => r.status === 'fail');
37
+ const warns = results.filter((r) => r.status === 'warn');
38
+ if (fails.length > 0) {
39
+ p.log.error(`${fails.length} check${fails.length === 1 ? '' : 's'} failed`);
40
+ }
41
+ else if (warns.length > 0) {
42
+ p.log.warn(`All checks passed with ${warns.length} warning${warns.length === 1 ? '' : 's'}`);
43
+ }
44
+ else {
45
+ p.log.success('All checks passed');
46
+ }
47
+ });
48
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerInitCommand(program: Command): void;
@@ -0,0 +1,200 @@
1
+ import * as p from '@clack/prompts';
2
+ import { requireAuth } from '../lib/auth.js';
3
+ import { createAdminClient, } from '../lib/api.js';
4
+ import { readProjectConfig, readAgents } from '../lib/config.js';
5
+ import { detectInstalledTools } from '../lib/detect.js';
6
+ import { CREATURE_TYPES, ID_DISPLAY_LENGTH } from '../lib/constants.js';
7
+ import { brand } from '../ui/theme.js';
8
+ import { panel } from '../ui/panel.js';
9
+ import { isCancel } from '../ui/helpers.js';
10
+ import * as agentService from '../lib/services/agent-service.js';
11
+ async function selectOrCreateProject(client, projects) {
12
+ const CREATE_VALUE = '__create__';
13
+ const options = [
14
+ ...projects.map((proj) => ({
15
+ value: proj.id,
16
+ label: proj.name,
17
+ hint: proj.id.slice(0, ID_DISPLAY_LENGTH),
18
+ })),
19
+ { value: CREATE_VALUE, label: '+ Create new project' },
20
+ ];
21
+ const selected = await p.select({ message: 'Select a project', options });
22
+ if (isCancel(selected)) {
23
+ p.cancel('Cancelled.');
24
+ process.exit(0);
25
+ }
26
+ if (selected === CREATE_VALUE) {
27
+ const name = await p.text({
28
+ message: 'Project name',
29
+ validate: (v) => { if (!v.trim())
30
+ return 'Name cannot be empty.'; },
31
+ });
32
+ if (isCancel(name)) {
33
+ p.cancel('Cancelled.');
34
+ process.exit(0);
35
+ }
36
+ const spin = p.spinner();
37
+ spin.start('Creating project');
38
+ const result = await client.createProject(name);
39
+ spin.stop('Project created');
40
+ return { id: result.id, name: result.name };
41
+ }
42
+ const project = projects.find((proj) => proj.id === selected);
43
+ return { id: project.id, name: project.name };
44
+ }
45
+ async function selectOrCreateAgent(client, projectId, agents) {
46
+ const CREATE_VALUE = '__create__';
47
+ const options = [
48
+ ...agents.map((a) => ({
49
+ value: a.id,
50
+ label: a.name,
51
+ hint: `${a.form}${a.creature ? `:${a.creature.type}` : ''}`,
52
+ })),
53
+ { value: CREATE_VALUE, label: '+ Create new agent' },
54
+ ];
55
+ const selected = await p.select({ message: 'Select an agent', options });
56
+ if (isCancel(selected)) {
57
+ p.cancel('Cancelled.');
58
+ process.exit(0);
59
+ }
60
+ if (selected === CREATE_VALUE) {
61
+ const name = await p.text({
62
+ message: 'Agent name',
63
+ validate: (v) => { if (!v.trim())
64
+ return 'Name cannot be empty.'; },
65
+ });
66
+ if (isCancel(name)) {
67
+ p.cancel('Cancelled.');
68
+ process.exit(0);
69
+ }
70
+ const form = await p.select({
71
+ message: 'Character form',
72
+ options: [
73
+ { value: 'human', label: 'Human character' },
74
+ { value: 'creature', label: 'Creature companion' },
75
+ ],
76
+ });
77
+ if (isCancel(form)) {
78
+ p.cancel('Cancelled.');
79
+ process.exit(0);
80
+ }
81
+ let creatureType;
82
+ if (form === 'creature') {
83
+ const typeResult = await p.select({
84
+ message: 'Creature type',
85
+ options: CREATURE_TYPES.map((t) => ({ value: t, label: t })),
86
+ });
87
+ if (isCancel(typeResult)) {
88
+ p.cancel('Cancelled.');
89
+ process.exit(0);
90
+ }
91
+ creatureType = typeResult;
92
+ }
93
+ const spin = p.spinner();
94
+ spin.start('Creating agent');
95
+ const result = await agentService.createAgent(client, projectId, {
96
+ name: name,
97
+ form: form,
98
+ ...(creatureType ? { creature: { type: creatureType } } : {}),
99
+ });
100
+ spin.stop('Agent created');
101
+ return { id: result.id, name: result.name, isNew: true };
102
+ }
103
+ const agent = agents.find((a) => a.id === selected);
104
+ return { id: agent.id, name: agent.name, isNew: false };
105
+ }
106
+ export function registerInitCommand(program) {
107
+ program
108
+ .command('init')
109
+ .description('Link a project and agent to the current directory')
110
+ .option('--project <id>', 'Project ID (non-interactive)')
111
+ .option('--agent <id>', 'Agent ID (non-interactive)')
112
+ .option('--regenerate-key', 'Regenerate key when linking an existing agent non-interactively')
113
+ .action(async function (opts) {
114
+ const nonInteractive = !!(opts.project && opts.agent);
115
+ try {
116
+ await requireAuth();
117
+ }
118
+ catch {
119
+ p.log.error(`Not logged in. Run ${brand.accent('cubelife login')} first.`);
120
+ process.exit(1);
121
+ }
122
+ if (nonInteractive) {
123
+ await runNonInteractive(opts.project, opts.agent, opts.regenerateKey);
124
+ return;
125
+ }
126
+ p.intro(brand.primary('Initialise CubeLife'));
127
+ const existing = await readProjectConfig();
128
+ if (existing?.projectId && existing?.agentId) {
129
+ console.log(panel([
130
+ `${brand.label('Project'.padEnd(10))}${existing.projectId.slice(0, ID_DISPLAY_LENGTH)}`,
131
+ `${brand.label('Agent'.padEnd(10))}${existing.agentId.slice(0, ID_DISPLAY_LENGTH)}`,
132
+ ], { title: 'Current Config', width: 40 }));
133
+ const reconfigure = await p.confirm({ message: 'Reconfigure?' });
134
+ if (isCancel(reconfigure) || !reconfigure) {
135
+ p.outro('Keeping current configuration.');
136
+ return;
137
+ }
138
+ }
139
+ const client = await createAdminClient();
140
+ const spin = p.spinner();
141
+ spin.start('Fetching projects');
142
+ const { projects } = await client.listProjects();
143
+ spin.stop(`${projects.length} project${projects.length === 1 ? '' : 's'} found`);
144
+ const project = await selectOrCreateProject(client, projects);
145
+ spin.start('Fetching agents');
146
+ const { agents } = await client.listAgents(project.id);
147
+ spin.stop(`${agents.length} agent${agents.length === 1 ? '' : 's'} found`);
148
+ const agent = await selectOrCreateAgent(client, project.id, agents);
149
+ if (!agent.isNew) {
150
+ const store = await readAgents();
151
+ if (!store.agents[agent.id]) {
152
+ const regen = await p.confirm({
153
+ message: 'API key not stored locally. Regenerate key? (existing integrations using the old key will stop working)',
154
+ });
155
+ if (!isCancel(regen) && regen) {
156
+ const regenSpin = p.spinner();
157
+ regenSpin.start('Regenerating key');
158
+ await agentService.regenerateKey(client, project.id, agent.id);
159
+ regenSpin.stop('Key regenerated');
160
+ }
161
+ else {
162
+ p.log.warn(`Status reporting won't work without a stored key. Regenerate later with ${brand.accent(`cubelife agents key ${agent.id.slice(0, ID_DISPLAY_LENGTH)} --regenerate`)}.`);
163
+ }
164
+ }
165
+ }
166
+ await agentService.linkAgent(project.id, agent.id);
167
+ const tools = detectInstalledTools();
168
+ if (tools.length > 0) {
169
+ p.log.info(`Detected: ${tools.map((t) => brand.primary(t.name)).join(', ')}. Run ${brand.accent('cubelife setup')} to configure integrations.`);
170
+ }
171
+ p.outro(`Linked to ${brand.primary(agent.name)} in ${brand.primary(project.name)}. Run ${brand.accent('cubelife status coding "test"')} to try it.`);
172
+ });
173
+ }
174
+ async function runNonInteractive(projectId, agentId, regenerateKey) {
175
+ const client = await createAdminClient();
176
+ const spin = p.spinner();
177
+ spin.start('Validating');
178
+ let agentName = agentId;
179
+ try {
180
+ const agent = await agentService.getAgent(client, projectId, agentId);
181
+ agentName = agent.name;
182
+ }
183
+ catch {
184
+ spin.stop('Failed');
185
+ p.log.error(`Agent ${agentId.slice(0, ID_DISPLAY_LENGTH)} not found in project ${projectId.slice(0, ID_DISPLAY_LENGTH)}.`);
186
+ process.exit(1);
187
+ }
188
+ const store = await readAgents();
189
+ if (!store.agents[agentId]) {
190
+ if (!regenerateKey) {
191
+ spin.stop('Failed');
192
+ p.log.error(`No API key stored for this agent. Use ${brand.accent('--regenerate-key')} to create one.`);
193
+ process.exit(1);
194
+ }
195
+ await agentService.regenerateKey(client, projectId, agentId);
196
+ }
197
+ await agentService.linkAgent(projectId, agentId);
198
+ spin.stop('Linked');
199
+ p.log.success(`Linked to agent ${agentId.slice(0, ID_DISPLAY_LENGTH)} in project ${projectId.slice(0, ID_DISPLAY_LENGTH)}.`);
200
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerMcpCommand(program: Command): void;
@@ -0,0 +1,9 @@
1
+ import { startMcpServer } from '../mcp/server.js';
2
+ export function registerMcpCommand(program) {
3
+ program
4
+ .command('mcp')
5
+ .description('Start the MCP server (stdio transport)')
6
+ .action(async () => {
7
+ await startMcpServer();
8
+ });
9
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerProjectCommands(program: Command): void;
@@ -0,0 +1,122 @@
1
+ import * as p from '@clack/prompts';
2
+ import { createAdminClient, ApiError } from '../lib/api.js';
3
+ import { ID_DISPLAY_LENGTH } from '../lib/constants.js';
4
+ import { brand, dot } from '../ui/theme.js';
5
+ import { table } from '../ui/table.js';
6
+ import { isCancel } from '../ui/helpers.js';
7
+ import { rootOpts, handleCommandError, } from '../lib/command-helpers.js';
8
+ import * as projectService from '../lib/services/project-service.js';
9
+ export function registerProjectCommands(program) {
10
+ const projects = program
11
+ .command('projects')
12
+ .description('Manage CubeLife projects');
13
+ projects
14
+ .command('list')
15
+ .description('List your projects')
16
+ .action(async function () {
17
+ const { json } = rootOpts(this);
18
+ const spin = p.spinner();
19
+ if (!json)
20
+ spin.start('Fetching projects');
21
+ try {
22
+ const client = await createAdminClient();
23
+ const { projects: items, linkedProjectId } = await projectService.listProjects(client);
24
+ if (json) {
25
+ console.log(JSON.stringify({ projects: items }));
26
+ return;
27
+ }
28
+ spin.stop('Projects loaded');
29
+ if (items.length === 0) {
30
+ p.log.info(`No projects yet. Create one with ${brand.accent('cubelife projects create <name>')}`);
31
+ return;
32
+ }
33
+ const rows = items.map((proj) => ({
34
+ linked: proj.id === linkedProjectId ? dot.working : ' ',
35
+ id: proj.id.slice(0, ID_DISPLAY_LENGTH),
36
+ name: proj.name,
37
+ product: proj.product,
38
+ created: new Date(proj.createdAt).toLocaleDateString(),
39
+ }));
40
+ console.log(table([
41
+ { label: '', key: 'linked', width: 2 },
42
+ { label: 'ID', key: 'id', width: 10 },
43
+ { label: 'Name', key: 'name' },
44
+ { label: 'Product', key: 'product', width: 8 },
45
+ { label: 'Created', key: 'created', width: 12 },
46
+ ], rows));
47
+ }
48
+ catch (err) {
49
+ handleCommandError({ error: err, json, spinner: spin });
50
+ }
51
+ });
52
+ projects
53
+ .command('create <name>')
54
+ .description('Create a new project')
55
+ .action(async function (name) {
56
+ const { json, yes } = rootOpts(this);
57
+ const spin = p.spinner();
58
+ if (!json)
59
+ spin.start('Creating project');
60
+ try {
61
+ const client = await createAdminClient();
62
+ const result = await projectService.createProject(client, name);
63
+ if (json) {
64
+ console.log(JSON.stringify(result));
65
+ return;
66
+ }
67
+ spin.stop('Project created');
68
+ p.log.success(`${brand.primary(result.name)} ${brand.muted(`(${result.id})`)}`);
69
+ if (!yes) {
70
+ const setDefault = await p.confirm({
71
+ message: 'Set as default project?',
72
+ });
73
+ if (!isCancel(setDefault) && setDefault) {
74
+ await projectService.setDefaultProject(result.id);
75
+ p.log.info('Default project updated.');
76
+ }
77
+ }
78
+ }
79
+ catch (err) {
80
+ handleCommandError({ error: err, json, spinner: spin });
81
+ }
82
+ });
83
+ projects
84
+ .command('delete <id>')
85
+ .description('Delete a project')
86
+ .action(async function (id) {
87
+ const { json, yes } = rootOpts(this);
88
+ try {
89
+ if (!yes && !json) {
90
+ const confirmed = await p.confirm({
91
+ message: `Delete project ${brand.primary(id.slice(0, ID_DISPLAY_LENGTH))}? This will delete all agents in this project.`,
92
+ });
93
+ if (isCancel(confirmed) || !confirmed) {
94
+ p.cancel('Cancelled.');
95
+ return;
96
+ }
97
+ }
98
+ const client = await createAdminClient();
99
+ const spin = p.spinner();
100
+ if (!json)
101
+ spin.start('Deleting project');
102
+ await projectService.deleteProject(client, id);
103
+ if (json) {
104
+ console.log(JSON.stringify({ deleted: true, id }));
105
+ return;
106
+ }
107
+ spin.stop('Project deleted');
108
+ p.log.success('Project deleted.');
109
+ }
110
+ catch (err) {
111
+ if (err instanceof ApiError && err.status === 403) {
112
+ const friendly = 'Project deletion requires admin access. Use the dashboard or contact support.';
113
+ if (json)
114
+ console.log(JSON.stringify({ error: friendly }));
115
+ else
116
+ p.log.error(friendly);
117
+ process.exit(1);
118
+ }
119
+ handleCommandError({ error: err, json });
120
+ }
121
+ });
122
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerSetupCommands(program: Command): void;