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.
- package/LICENSE +21 -0
- package/README.md +81 -0
- package/SPRITE-LICENSE +14 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +39 -0
- package/dist/commands/agents.d.ts +2 -0
- package/dist/commands/agents.js +303 -0
- package/dist/commands/auth.d.ts +2 -0
- package/dist/commands/auth.js +233 -0
- package/dist/commands/billing.d.ts +2 -0
- package/dist/commands/billing.js +362 -0
- package/dist/commands/creature.d.ts +2 -0
- package/dist/commands/creature.js +166 -0
- package/dist/commands/default.d.ts +2 -0
- package/dist/commands/default.js +87 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +48 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +200 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.js +9 -0
- package/dist/commands/projects.d.ts +2 -0
- package/dist/commands/projects.js +122 -0
- package/dist/commands/setup.d.ts +2 -0
- package/dist/commands/setup.js +453 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +89 -0
- package/dist/commands/tutorial.d.ts +2 -0
- package/dist/commands/tutorial.js +9 -0
- package/dist/commands/view.d.ts +2 -0
- package/dist/commands/view.js +262 -0
- package/dist/data/sprite-data.d.ts +32 -0
- package/dist/data/sprite-data.js +865 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/lib/api.d.ts +162 -0
- package/dist/lib/api.js +160 -0
- package/dist/lib/auth.d.ts +12 -0
- package/dist/lib/auth.js +113 -0
- package/dist/lib/browser.d.ts +1 -0
- package/dist/lib/browser.js +21 -0
- package/dist/lib/command-helpers.d.ts +26 -0
- package/dist/lib/command-helpers.js +60 -0
- package/dist/lib/compositor.d.ts +34 -0
- package/dist/lib/compositor.js +232 -0
- package/dist/lib/config.d.ts +39 -0
- package/dist/lib/config.js +89 -0
- package/dist/lib/constants.d.ts +12 -0
- package/dist/lib/constants.js +39 -0
- package/dist/lib/detect.d.ts +17 -0
- package/dist/lib/detect.js +99 -0
- package/dist/lib/doctor.d.ts +18 -0
- package/dist/lib/doctor.js +321 -0
- package/dist/lib/index.d.ts +11 -0
- package/dist/lib/index.js +6 -0
- package/dist/lib/integration.d.ts +66 -0
- package/dist/lib/integration.js +337 -0
- package/dist/lib/poll.d.ts +11 -0
- package/dist/lib/poll.js +31 -0
- package/dist/lib/resolve.d.ts +1 -0
- package/dist/lib/resolve.js +10 -0
- package/dist/lib/services/account-service.d.ts +17 -0
- package/dist/lib/services/account-service.js +30 -0
- package/dist/lib/services/agent-service.d.ts +17 -0
- package/dist/lib/services/agent-service.js +62 -0
- package/dist/lib/services/creature-service.d.ts +12 -0
- package/dist/lib/services/creature-service.js +35 -0
- package/dist/lib/services/project-service.d.ts +9 -0
- package/dist/lib/services/project-service.js +22 -0
- package/dist/lib/tutorial.d.ts +12 -0
- package/dist/lib/tutorial.js +358 -0
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +116 -0
- package/dist/ui/banner.d.ts +3 -0
- package/dist/ui/banner.js +27 -0
- package/dist/ui/half-block.d.ts +6 -0
- package/dist/ui/half-block.js +45 -0
- package/dist/ui/helpers.d.ts +3 -0
- package/dist/ui/helpers.js +11 -0
- package/dist/ui/index.d.ts +5 -0
- package/dist/ui/index.js +5 -0
- package/dist/ui/panel.d.ts +7 -0
- package/dist/ui/panel.js +21 -0
- package/dist/ui/preview.d.ts +7 -0
- package/dist/ui/preview.js +21 -0
- package/dist/ui/table.d.ts +8 -0
- package/dist/ui/table.js +20 -0
- package/dist/ui/theme.d.ts +24 -0
- package/dist/ui/theme.js +32 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -0
- 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,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,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,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,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
|
+
}
|