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,262 @@
1
+ import * as p from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import { createAdminClient, createAgentClient } from '../lib/api.js';
4
+ import { renderHalfBlock } from '../ui/half-block.js';
5
+ import { compositeCreature, compositeHuman, createDissolve, applyDissolve, getFrameCount, getFps, } from '../lib/compositor.js';
6
+ import { openBrowser } from '../lib/browser.js';
7
+ import { brand } from '../ui/theme.js';
8
+ import { rootOpts, requireLinkedAgent, handleCommandError, } from '../lib/command-helpers.js';
9
+ const EMBED_BASE = 'https://life.cubeworld.co.za';
10
+ const STATE_POLL_MS = 5_000;
11
+ const METADATA_ROWS = 6;
12
+ const DISSOLVE_MS = 500;
13
+ const TRANSITION_FPS = 15;
14
+ const TRANSITION_INTERVAL = Math.round(1000 / TRANSITION_FPS);
15
+ function relativeTime(ms) {
16
+ const seconds = Math.floor((Date.now() - ms) / 1000);
17
+ if (seconds < 60)
18
+ return `${seconds}s ago`;
19
+ const minutes = Math.floor(seconds / 60);
20
+ if (minutes < 60)
21
+ return `${minutes}m ago`;
22
+ const hours = Math.floor(minutes / 60);
23
+ return `${hours}h ago`;
24
+ }
25
+ function renderProgressBar(progress, width = 12) {
26
+ const filled = Math.round(progress * width);
27
+ const empty = width - filled;
28
+ return chalk.green('█'.repeat(filled)) + chalk.grey('░'.repeat(empty));
29
+ }
30
+ function sentimentColour(sentiment) {
31
+ if (sentiment === 'positive')
32
+ return chalk.green(sentiment);
33
+ if (sentiment === 'negative')
34
+ return chalk.red(sentiment);
35
+ return chalk.grey(sentiment ?? 'neutral');
36
+ }
37
+ function renderMetadata(agent, state) {
38
+ const lines = [''];
39
+ if (agent.form === 'creature' && agent.creature) {
40
+ const colourHex = agent.creature.color ?? '#888888';
41
+ const swatch = chalk.hex(colourHex)('██');
42
+ const label = agent.creature.type.charAt(0).toUpperCase() + agent.creature.type.slice(1);
43
+ const name = agent.creature.name ? ` "${agent.creature.name}"` : '';
44
+ lines.push(` ${label}${name} ${swatch} ${brand.muted(colourHex)}`);
45
+ }
46
+ else {
47
+ lines.push(` ${agent.name}`);
48
+ }
49
+ if (state) {
50
+ const detail = state.detail ? ` ${brand.muted(`"${state.detail}"`)}` : '';
51
+ lines.push(` State: ${brand.primary(state.state)}${detail}`);
52
+ if (state.progress !== undefined && state.progress !== null) {
53
+ const pct = Math.round(state.progress * 100);
54
+ lines.push(` Progress: ${renderProgressBar(state.progress)} ${pct}%`);
55
+ }
56
+ else {
57
+ lines.push('');
58
+ }
59
+ lines.push(` Sentiment: ${sentimentColour(state.sentiment)}`);
60
+ if (state.updatedAt) {
61
+ lines.push(` Updated: ${brand.muted(relativeTime(state.updatedAt))}`);
62
+ }
63
+ else {
64
+ lines.push('');
65
+ }
66
+ }
67
+ else {
68
+ lines.push(` State: ${brand.muted('idle')}`);
69
+ lines.push('');
70
+ lines.push('');
71
+ lines.push('');
72
+ }
73
+ while (lines.length < METADATA_ROWS)
74
+ lines.push('');
75
+ return lines.map(l => l + '\x1b[K').join('\n');
76
+ }
77
+ function compositeFrame(agent, state, frameIndex) {
78
+ if (agent.form === 'creature' && agent.creature) {
79
+ const traits = {
80
+ type: agent.creature.type,
81
+ color: agent.creature.color ?? '#888888',
82
+ name: agent.creature.name,
83
+ };
84
+ return compositeCreature(traits, state, frameIndex);
85
+ }
86
+ const char = (agent.character ?? {});
87
+ const traits = {
88
+ skinTone: char.skinTone,
89
+ outfit: char.outfit,
90
+ outfitColor: char.outfitColor,
91
+ hairStyle: char.hairStyle,
92
+ hairColor: char.hairColor,
93
+ accessories: char.accessories,
94
+ };
95
+ return compositeHuman(traits, state, frameIndex);
96
+ }
97
+ async function pollState(agentClient) {
98
+ if (!agentClient)
99
+ return null;
100
+ try {
101
+ return await agentClient.getState();
102
+ }
103
+ catch {
104
+ return null;
105
+ }
106
+ }
107
+ export function registerViewCommand(program) {
108
+ program
109
+ .command('view')
110
+ .description('Render the linked agent in the terminal (live, animated)')
111
+ .option('--once', 'Render a single frame and exit')
112
+ .option('--browser', 'Open the web embed instead')
113
+ .option('--agent <id>', 'Agent ID (overrides linked agent)')
114
+ .option('--project <id>', 'Project ID (overrides linked project)')
115
+ .action(async function (opts) {
116
+ const { json } = rootOpts(this);
117
+ let projectId;
118
+ let agentId;
119
+ if (opts.agent && opts.project) {
120
+ projectId = opts.project;
121
+ agentId = opts.agent;
122
+ }
123
+ else if (opts.agent || opts.project) {
124
+ if (json) {
125
+ console.log(JSON.stringify({ error: 'both_ids_required' }));
126
+ }
127
+ else {
128
+ p.log.error('Both --agent and --project are required when overriding.');
129
+ }
130
+ process.exit(1);
131
+ }
132
+ else {
133
+ const linked = await requireLinkedAgent(json);
134
+ projectId = linked.projectId;
135
+ agentId = linked.agentId;
136
+ }
137
+ const embedUrl = `${EMBED_BASE}/embed/${projectId}/${agentId}`;
138
+ if (opts.browser) {
139
+ if (json) {
140
+ console.log(JSON.stringify({ embedUrl }));
141
+ }
142
+ else {
143
+ p.log.info(`Opening ${brand.accent(embedUrl)}`);
144
+ await openBrowser(embedUrl);
145
+ }
146
+ return;
147
+ }
148
+ try {
149
+ const adminClient = await createAdminClient();
150
+ const agentClient = await createAgentClient(agentId);
151
+ const agent = await adminClient.getAgent(projectId, agentId);
152
+ let agentState = await pollState(agentClient);
153
+ let currentState = agentState?.state ?? 'idle';
154
+ if (json) {
155
+ const output = {
156
+ agent: { id: agent.id, name: agent.name, form: agent.form },
157
+ state: agentState ?? { state: 'idle' },
158
+ embedUrl,
159
+ };
160
+ if (agent.form === 'creature' && agent.creature) {
161
+ output.creature = agent.creature;
162
+ }
163
+ console.log(JSON.stringify(output, null, 2));
164
+ return;
165
+ }
166
+ if (opts.once) {
167
+ const result = compositeFrame(agent, currentState, 0);
168
+ const rendered = renderHalfBlock(result.rgba, result.width, result.height);
169
+ console.log(rendered);
170
+ console.log(renderMetadata(agent, agentState));
171
+ return;
172
+ }
173
+ const form = agent.form === 'creature' ? 'creature' : 'human';
174
+ let totalFrames = getFrameCount(form, agent.creature?.type, currentState);
175
+ let fps = getFps(form, agent.creature?.type, currentState);
176
+ let frameInterval = Math.round(1000 / fps);
177
+ let frame = 0;
178
+ let transitionTimer = null;
179
+ let transitionStart = 0;
180
+ let transitionFromState = '';
181
+ let dissolveOrder = null;
182
+ let pollTimer = null;
183
+ const cleanup = () => {
184
+ clearInterval(animTimer);
185
+ if (transitionTimer)
186
+ clearInterval(transitionTimer);
187
+ if (pollTimer)
188
+ clearInterval(pollTimer);
189
+ process.stdout.write('\x1b[?25h');
190
+ process.stdout.write('\x1b[?1049l');
191
+ process.exit(0);
192
+ };
193
+ process.on('SIGINT', cleanup);
194
+ process.on('SIGTERM', cleanup);
195
+ process.stdout.write('\x1b[?1049h');
196
+ process.stdout.write('\x1b[?25l');
197
+ process.stdout.write('\x1b[H');
198
+ const paintFrame = (rgba, w, h) => {
199
+ const rendered = renderHalfBlock(rgba, w, h);
200
+ const metadata = renderMetadata(agent, agentState);
201
+ process.stdout.write('\x1b[H');
202
+ process.stdout.write(rendered + '\n' + metadata + '\n');
203
+ };
204
+ const render = () => {
205
+ const result = compositeFrame(agent, currentState, frame);
206
+ paintFrame(result.rgba, result.width, result.height);
207
+ frame = (frame + 1) % totalFrames;
208
+ };
209
+ const renderTransitionFrame = () => {
210
+ const elapsed = Date.now() - transitionStart;
211
+ const t = Math.min(elapsed / DISSOLVE_MS, 1);
212
+ const fromResult = compositeFrame(agent, transitionFromState, 0);
213
+ const toResult = compositeFrame(agent, currentState, 0);
214
+ const blended = applyDissolve(fromResult.rgba, toResult.rgba, dissolveOrder, t);
215
+ paintFrame(blended, toResult.width, toResult.height);
216
+ if (t >= 1) {
217
+ if (transitionTimer)
218
+ clearInterval(transitionTimer);
219
+ transitionTimer = null;
220
+ dissolveOrder = null;
221
+ frame = 0;
222
+ }
223
+ };
224
+ const startTransition = (fromState) => {
225
+ if (transitionTimer)
226
+ clearInterval(transitionTimer);
227
+ transitionFromState = fromState;
228
+ transitionStart = Date.now();
229
+ dissolveOrder = createDissolve();
230
+ transitionTimer = setInterval(renderTransitionFrame, TRANSITION_INTERVAL);
231
+ };
232
+ render();
233
+ let animTimer = setInterval(render, frameInterval);
234
+ pollTimer = setInterval(async () => {
235
+ const newState = await pollState(agentClient);
236
+ if (!newState)
237
+ return;
238
+ const stateChanged = newState.state !== currentState;
239
+ const previousState = currentState;
240
+ agentState = newState;
241
+ currentState = newState.state ?? 'idle';
242
+ if (stateChanged) {
243
+ clearInterval(animTimer);
244
+ startTransition(previousState);
245
+ totalFrames = getFrameCount(form, agent.creature?.type, currentState);
246
+ fps = getFps(form, agent.creature?.type, currentState);
247
+ frameInterval = Math.round(1000 / fps);
248
+ frame = 0;
249
+ const waitForTransition = setInterval(() => {
250
+ if (!transitionTimer) {
251
+ clearInterval(waitForTransition);
252
+ animTimer = setInterval(render, frameInterval);
253
+ }
254
+ }, TRANSITION_INTERVAL);
255
+ }
256
+ }, STATE_POLL_MS);
257
+ }
258
+ catch (err) {
259
+ handleCommandError({ error: err, json });
260
+ }
261
+ });
262
+ }
@@ -0,0 +1,32 @@
1
+ export interface TintableAsset {
2
+ type: 'tintable';
3
+ width: number;
4
+ height: number;
5
+ frames: string[];
6
+ }
7
+ export interface ColourAsset {
8
+ type: 'colour';
9
+ width: number;
10
+ height: number;
11
+ frames: string[];
12
+ }
13
+ export type SpriteAsset = TintableAsset | ColourAsset;
14
+ export interface StateConfig {
15
+ bodySheet: string;
16
+ frameCount: number;
17
+ fps: number;
18
+ facing?: 'north' | 'south';
19
+ background?: string;
20
+ prop?: string;
21
+ propLayer?: 'front' | 'back';
22
+ propScale?: number;
23
+ propOffsetX?: number;
24
+ propOffsetY?: number;
25
+ }
26
+ export interface CreatureConfig {
27
+ idleFrameCount: number;
28
+ fps: number;
29
+ }
30
+ export declare const SPRITE_DATA: Record<string, SpriteAsset>;
31
+ export declare const STATE_MANIFEST: Record<string, StateConfig>;
32
+ export declare const CREATURE_MANIFEST: Record<string, CreatureConfig>;