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,337 @@
1
+ import { readFile, writeFile, mkdir, chmod } from 'node:fs/promises';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { execSync } from 'node:child_process';
4
+ import { homedir } from 'node:os';
5
+ import { join, resolve } from 'node:path';
6
+ import { parse as parseYaml, stringify as stringifyYaml } from 'yaml';
7
+ import { API_BASE_URL } from './constants.js';
8
+ const HOOKS_DIR = join(homedir(), '.cubelife', 'hooks');
9
+ const API_URL = `${API_BASE_URL}/api/v1/state`;
10
+ export async function readJsonConfig(path) {
11
+ if (!existsSync(path))
12
+ return {};
13
+ try {
14
+ const raw = await readFile(path, 'utf-8');
15
+ const trimmed = raw.trim();
16
+ if (!trimmed)
17
+ return {};
18
+ return JSON.parse(trimmed);
19
+ }
20
+ catch {
21
+ return {};
22
+ }
23
+ }
24
+ export async function writeJsonConfig(path, config) {
25
+ const dir = resolve(path, '..');
26
+ if (!existsSync(dir)) {
27
+ await mkdir(dir, { recursive: true });
28
+ }
29
+ await writeFile(path, JSON.stringify(config, null, 2) + '\n');
30
+ }
31
+ function resolveCliCommand() {
32
+ const whichCmd = process.platform === 'win32' ? 'where' : 'which';
33
+ try {
34
+ const binPath = execSync(`${whichCmd} cubelife`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim().split('\n')[0];
35
+ if (binPath)
36
+ return { command: binPath, args: ['mcp'] };
37
+ }
38
+ catch { /* not on PATH */ }
39
+ const scriptPath = resolve(process.argv[1]);
40
+ const isNpxCache = scriptPath.includes('.npm/_npx') || scriptPath.includes('npx');
41
+ if (isNpxCache) {
42
+ try {
43
+ const npxPath = execSync(`${whichCmd} npx`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim().split('\n')[0];
44
+ if (npxPath)
45
+ return { command: npxPath, args: ['cubelife', 'mcp'] };
46
+ }
47
+ catch { /* npx not found either */ }
48
+ }
49
+ return { command: process.execPath, args: [scriptPath, 'mcp'] };
50
+ }
51
+ export function buildMcpEntryWithAgent(agentId) {
52
+ const { command, args } = resolveCliCommand();
53
+ return { command, args, env: { CUBELIFE_AGENT_ID: agentId } };
54
+ }
55
+ export function mergeMcpServerEntry(existing, serverKey, entry) {
56
+ const servers = (existing[serverKey] ?? {});
57
+ const replaced = !!servers['cubelife'];
58
+ return {
59
+ config: {
60
+ ...existing,
61
+ [serverKey]: { ...servers, cubelife: entry },
62
+ },
63
+ replaced,
64
+ };
65
+ }
66
+ export async function readYamlConfig(path) {
67
+ if (!existsSync(path))
68
+ return {};
69
+ try {
70
+ const raw = await readFile(path, 'utf-8');
71
+ const trimmed = raw.trim();
72
+ if (!trimmed)
73
+ return {};
74
+ return (parseYaml(trimmed) ?? {});
75
+ }
76
+ catch {
77
+ return {};
78
+ }
79
+ }
80
+ export async function writeYamlConfig(path, config) {
81
+ const dir = resolve(path, '..');
82
+ if (!existsSync(dir)) {
83
+ await mkdir(dir, { recursive: true });
84
+ }
85
+ await writeFile(path, stringifyYaml(config, { lineWidth: 0 }));
86
+ }
87
+ export function mergeContinueMcpServer(existing, entry) {
88
+ const servers = (existing['mcpServers'] ?? []);
89
+ const idx = servers.findIndex((s) => s.name === 'cubelife');
90
+ const replaced = idx >= 0;
91
+ const filtered = replaced ? servers.filter((_, i) => i !== idx) : [...servers];
92
+ filtered.push(entry);
93
+ return {
94
+ config: { ...existing, mcpServers: filtered },
95
+ replaced,
96
+ };
97
+ }
98
+ export function buildContinueMcpEntry(agentId) {
99
+ const { command, args } = resolveCliCommand();
100
+ return { name: 'cubelife', command, args, env: { CUBELIFE_AGENT_ID: agentId } };
101
+ }
102
+ export async function hasStaleContinueEntry(configPath) {
103
+ const config = await readYamlConfig(configPath);
104
+ const servers = (config['mcpServers'] ?? []);
105
+ return servers.some((s) => s.name === 'cubelife');
106
+ }
107
+ export async function removeContinueEntry(configPath) {
108
+ const config = await readYamlConfig(configPath);
109
+ const servers = (config['mcpServers'] ?? []);
110
+ const filtered = servers.filter((s) => s.name !== 'cubelife');
111
+ await writeYamlConfig(configPath, { ...config, mcpServers: filtered });
112
+ }
113
+ export async function hasStaleMcpEntry(configPath, serverKey = 'mcpServers') {
114
+ const config = await readJsonConfig(configPath);
115
+ const servers = config[serverKey];
116
+ return !!servers?.['cubelife'];
117
+ }
118
+ export async function removeMcpEntry(configPath, serverKey = 'mcpServers') {
119
+ const config = await readJsonConfig(configPath);
120
+ const servers = config[serverKey];
121
+ if (servers?.['cubelife']) {
122
+ delete servers['cubelife'];
123
+ await writeJsonConfig(configPath, config);
124
+ }
125
+ }
126
+ export function hookScriptPath() {
127
+ if (process.platform === 'win32') {
128
+ return join(HOOKS_DIR, 'cubelife-hook.ps1');
129
+ }
130
+ return join(HOOKS_DIR, 'cubelife-hook.sh');
131
+ }
132
+ export function generateHookScript() {
133
+ if (process.platform === 'win32') {
134
+ return generatePowerShellScript();
135
+ }
136
+ return generateBashScript();
137
+ }
138
+ function generateBashScript() {
139
+ const lines = [
140
+ '#!/usr/bin/env bash',
141
+ '# Auto-generated by cubelife setup claude-code. Do not edit manually.',
142
+ "# Re-run 'cubelife setup claude-code' to regenerate.",
143
+ 'STATE="${1:-idle}"',
144
+ 'DETAIL="${2:-}"',
145
+ 'CONFIG=".cubelife/config.json"',
146
+ 'AGENTS="$HOME/.cubelife/agents.json"',
147
+ '[[ ! -f "${CONFIG}" || ! -f "${AGENTS}" ]] && exit 0',
148
+ "AGENT_ID=$(jq -r '.agentId' \"${CONFIG}\" 2>/dev/null)",
149
+ 'API_KEY=$(jq -r ".agents[\\"${AGENT_ID}\\"].key" "${AGENTS}" 2>/dev/null)',
150
+ '[[ -z "${API_KEY}" || "${API_KEY}" == "null" ]] && exit 0',
151
+ 'BODY="{\\"state\\":\\"${STATE}\\"}"',
152
+ '[[ -n "${DETAIL}" ]] && BODY="{\\"state\\":\\"${STATE}\\",\\"detail\\":\\"${DETAIL}\\"}"',
153
+ `curl -s --max-time 3 -X POST "${API_URL}" \\`,
154
+ ' -H "Content-Type: application/json" -H "X-Agent-Key: ${API_KEY}" -d "${BODY}" >/dev/null 2>&1',
155
+ ];
156
+ return lines.join('\n') + '\n';
157
+ }
158
+ function generatePowerShellScript() {
159
+ const lines = [
160
+ '# Auto-generated by cubelife setup claude-code. Do not edit manually.',
161
+ "# Re-run 'cubelife setup claude-code' to regenerate.",
162
+ 'param([string]$State = "idle", [string]$Detail = "")',
163
+ '$config = Get-Content "$env:USERPROFILE\\.cubelife\\agents.json" | ConvertFrom-Json',
164
+ '$projConfig = Get-Content ".cubelife\\config.json" | ConvertFrom-Json',
165
+ '$key = $config.agents.($projConfig.agentId).key',
166
+ 'if (-not $key) { exit 0 }',
167
+ '$body = @{ state = $State }',
168
+ 'if ($Detail) { $body.detail = $Detail }',
169
+ `try { Invoke-RestMethod -Uri "${API_URL}" -Method POST -Headers @{ "Content-Type" = "application/json"; "X-Agent-Key" = $key } -Body ($body | ConvertTo-Json) -TimeoutSec 3 | Out-Null } catch {}`,
170
+ ];
171
+ return lines.join('\n') + '\n';
172
+ }
173
+ export async function writeHookScript() {
174
+ const path = hookScriptPath();
175
+ if (!existsSync(HOOKS_DIR)) {
176
+ await mkdir(HOOKS_DIR, { recursive: true });
177
+ }
178
+ await writeFile(path, generateHookScript(), 'utf-8');
179
+ if (process.platform !== 'win32') {
180
+ await chmod(path, 0o755);
181
+ }
182
+ return path;
183
+ }
184
+ export function buildMcpEntry() {
185
+ const { command, args } = resolveCliCommand();
186
+ return { command, args };
187
+ }
188
+ function hookCmd(state, detail) {
189
+ const isWin = process.platform === 'win32';
190
+ const prefix = isWin
191
+ ? 'powershell -File ~/.cubelife/hooks/cubelife-hook.ps1'
192
+ : 'bash ~/.cubelife/hooks/cubelife-hook.sh';
193
+ return detail ? `${prefix} ${state} '${detail}'` : `${prefix} ${state}`;
194
+ }
195
+ export function buildHookEntries() {
196
+ return {
197
+ PreToolUse: [
198
+ { matcher: 'Read', hooks: [{ type: 'command', command: hookCmd('reading', 'Reading file'), async: true }] },
199
+ { matcher: 'Glob|Grep', hooks: [{ type: 'command', command: hookCmd('researching', 'Searching codebase'), async: true }] },
200
+ { matcher: 'Edit|Write|NotebookEdit', hooks: [{ type: 'command', command: hookCmd('coding', 'Writing code'), async: true }] },
201
+ { matcher: 'Bash', hooks: [{ type: 'command', command: hookCmd('coding', 'Running command'), async: true }] },
202
+ { matcher: 'Task', hooks: [{ type: 'command', command: hookCmd('thinking', 'Planning next step'), async: true }] },
203
+ { matcher: 'WebFetch|WebSearch', hooks: [{ type: 'command', command: hookCmd('researching', 'Searching the web'), async: true }] },
204
+ ],
205
+ PermissionRequest: [
206
+ { hooks: [{ type: 'command', command: hookCmd('awaiting_input', 'Waiting for approval'), async: true }] },
207
+ ],
208
+ Stop: [
209
+ { hooks: [{ type: 'command', command: hookCmd('idle'), async: true }] },
210
+ ],
211
+ };
212
+ }
213
+ function isCubelifeHook(entry) {
214
+ return entry.hooks?.some((h) => h.command?.includes('cubelife-hook')) ?? false;
215
+ }
216
+ export function hasMcpServer(settings) {
217
+ return !!settings.mcpServers?.['cubelife'];
218
+ }
219
+ export function isClaudeMcpRegistered() {
220
+ try {
221
+ const output = execSync('claude mcp list 2>/dev/null', { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] });
222
+ return output.includes('cubelife');
223
+ }
224
+ catch {
225
+ return false;
226
+ }
227
+ }
228
+ export function registerClaudeMcp(scope = 'user') {
229
+ const { command, args } = resolveCliCommand();
230
+ const replaced = isClaudeMcpRegistered();
231
+ if (replaced) {
232
+ try {
233
+ execSync('claude mcp remove cubelife', { stdio: 'ignore' });
234
+ }
235
+ catch { /* ignore */ }
236
+ }
237
+ const cmdArgs = ['claude', 'mcp', 'add', '--scope', scope, 'cubelife', '--', command, ...args];
238
+ execSync(cmdArgs.join(' '), { stdio: 'ignore' });
239
+ return { replaced };
240
+ }
241
+ export function isCodexMcpRegistered() {
242
+ const configPath = join(homedir(), '.codex', 'config.toml');
243
+ if (!existsSync(configPath))
244
+ return false;
245
+ try {
246
+ const content = readFileSync(configPath, 'utf-8');
247
+ return content.includes('[mcp_servers.cubelife]');
248
+ }
249
+ catch {
250
+ return false;
251
+ }
252
+ }
253
+ export function registerCodexMcp(agentId) {
254
+ const { command, args } = resolveCliCommand();
255
+ const replaced = isCodexMcpRegistered();
256
+ if (replaced) {
257
+ try {
258
+ execSync('codex mcp remove cubelife', { stdio: 'ignore' });
259
+ }
260
+ catch { /* ignore */ }
261
+ }
262
+ const cmdArgs = ['codex', 'mcp', 'add'];
263
+ if (agentId)
264
+ cmdArgs.push('--env', `CUBELIFE_AGENT_ID=${agentId}`);
265
+ cmdArgs.push('cubelife', '--', command, ...args);
266
+ execSync(cmdArgs.join(' '), { stdio: 'ignore' });
267
+ return { replaced };
268
+ }
269
+ export function hasHookIntegration(settings) {
270
+ return Object.values(settings.hooks ?? {}).some((entries) => entries.some(isCubelifeHook));
271
+ }
272
+ export function mergeSettings(existing, mode) {
273
+ const conflicts = [];
274
+ const result = { ...existing };
275
+ if (mode === 'mcp' || mode === 'both') {
276
+ if (!result.mcpServers)
277
+ result.mcpServers = {};
278
+ if (result.mcpServers['cubelife']) {
279
+ conflicts.push('Existing cubelife MCP entry updated');
280
+ }
281
+ result.mcpServers = { ...result.mcpServers, cubelife: buildMcpEntry() };
282
+ }
283
+ if (mode === 'hooks' || mode === 'both') {
284
+ if (!result.hooks)
285
+ result.hooks = {};
286
+ const hookDefs = buildHookEntries();
287
+ const merged = { ...result.hooks };
288
+ for (const [event, entries] of Object.entries(hookDefs)) {
289
+ const existingEntries = merged[event] ?? [];
290
+ const hadCubelife = existingEntries.some(isCubelifeHook);
291
+ if (hadCubelife) {
292
+ conflicts.push(`Existing cubelife hooks in ${event} updated`);
293
+ }
294
+ const filtered = existingEntries.filter((e) => !isCubelifeHook(e));
295
+ merged[event] = [...filtered, ...entries];
296
+ }
297
+ result.hooks = merged;
298
+ }
299
+ return { settings: result, conflicts };
300
+ }
301
+ export function resolveProjectRoot() {
302
+ try {
303
+ return execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
304
+ }
305
+ catch {
306
+ return process.cwd();
307
+ }
308
+ }
309
+ export async function readClaudeSettings(cwd) {
310
+ const path = resolve(cwd, '.claude', 'settings.json');
311
+ if (!existsSync(path))
312
+ return {};
313
+ try {
314
+ const raw = await readFile(path, 'utf-8');
315
+ return JSON.parse(raw);
316
+ }
317
+ catch {
318
+ return {};
319
+ }
320
+ }
321
+ export async function writeClaudeSettings(cwd, settings) {
322
+ const dir = resolve(cwd, '.claude');
323
+ if (!existsSync(dir)) {
324
+ await mkdir(dir, { recursive: true });
325
+ }
326
+ await writeFile(resolve(dir, 'settings.json'), JSON.stringify(settings, null, 2) + '\n');
327
+ }
328
+ export function detectLegacySetup(cwd) {
329
+ const found = [];
330
+ const candidates = ['cubelife-agents.json', '.cubelife-agents.json', 'cubelife-hook.sh'];
331
+ for (const name of candidates) {
332
+ if (existsSync(join(cwd, name))) {
333
+ found.push(name);
334
+ }
335
+ }
336
+ return found;
337
+ }
@@ -0,0 +1,11 @@
1
+ import { type AdminClient, type VerifyResponse } from './api.js';
2
+ export type PollStatus = 'success' | 'failed' | 'abandoned' | 'timeout';
3
+ export interface PollResult {
4
+ status: PollStatus;
5
+ data?: VerifyResponse;
6
+ }
7
+ export interface PollOptions {
8
+ interval?: number;
9
+ timeout?: number;
10
+ }
11
+ export declare function pollVerification(client: AdminClient, reference: string, opts?: PollOptions): Promise<PollResult>;
@@ -0,0 +1,31 @@
1
+ import { ApiError } from './api.js';
2
+ const TERMINAL_STATUSES = new Set(['failed', 'abandoned']);
3
+ const RETRYABLE_HTTP_STATUSES = new Set([408, 429, 500, 502, 503, 504]);
4
+ export async function pollVerification(client, reference, opts = {}) {
5
+ const interval = opts.interval ?? 5_000;
6
+ const timeout = opts.timeout ?? 300_000;
7
+ const deadline = Date.now() + timeout;
8
+ while (Date.now() < deadline) {
9
+ try {
10
+ const data = await client.verifyPayment(reference);
11
+ return { status: 'success', data };
12
+ }
13
+ catch (err) {
14
+ if (err instanceof ApiError) {
15
+ // 400: backend returns "Transaction not successful status: <paystack_status>"
16
+ if (err.status === 400) {
17
+ const statusMatch = err.message.match(/status[:\s]*(\w+)/i);
18
+ const paystackStatus = statusMatch?.[1]?.toLowerCase();
19
+ if (paystackStatus && TERMINAL_STATUSES.has(paystackStatus)) {
20
+ return { status: paystackStatus };
21
+ }
22
+ }
23
+ else if (!RETRYABLE_HTTP_STATUSES.has(err.status)) {
24
+ throw err;
25
+ }
26
+ }
27
+ }
28
+ await new Promise((r) => setTimeout(r, interval));
29
+ }
30
+ return { status: 'timeout' };
31
+ }
@@ -0,0 +1 @@
1
+ export declare function resolveProjectId(flagValue?: string): Promise<string | null>;
@@ -0,0 +1,10 @@
1
+ import { readProjectConfig, readGlobalConfig } from './config.js';
2
+ export async function resolveProjectId(flagValue) {
3
+ if (flagValue)
4
+ return flagValue;
5
+ const project = await readProjectConfig();
6
+ if (project?.projectId)
7
+ return project.projectId;
8
+ const global = await readGlobalConfig();
9
+ return global.defaults?.projectId ?? null;
10
+ }
@@ -0,0 +1,17 @@
1
+ import type { AdminClient, Tier } from '../api.js';
2
+ import { type DetectedTool } from '../detect.js';
3
+ export interface AccountSummary {
4
+ tier: Tier;
5
+ tierLabel: string;
6
+ agentLimit: number;
7
+ usageToday: number;
8
+ usageLimit: number;
9
+ sparks: number;
10
+ }
11
+ export declare function fetchAccountSummary(client: AdminClient): Promise<AccountSummary>;
12
+ export interface WhoamiInfo {
13
+ projectId: string | null;
14
+ agentId: string | null;
15
+ tools: DetectedTool[];
16
+ }
17
+ export declare function fetchLinkedInfo(): Promise<WhoamiInfo>;
@@ -0,0 +1,30 @@
1
+ import { TIER_LIMITS, resolveTier } from '../api.js';
2
+ import { readProjectConfig } from '../config.js';
3
+ import { detectInstalledTools } from '../detect.js';
4
+ export async function fetchAccountSummary(client) {
5
+ const billing = await client.getBillingUser();
6
+ const tier = resolveTier(billing.tier);
7
+ const limits = TIER_LIMITS[tier];
8
+ let usageToday = 0;
9
+ const usage = await client.getBillingUsage(1);
10
+ if (usage.length > 0) {
11
+ usageToday = usage[0].apiCalls;
12
+ }
13
+ return {
14
+ tier,
15
+ tierLabel: limits.label,
16
+ agentLimit: limits.agents,
17
+ usageToday,
18
+ usageLimit: limits.calls,
19
+ sparks: billing.sparks,
20
+ };
21
+ }
22
+ export async function fetchLinkedInfo() {
23
+ const config = await readProjectConfig();
24
+ const tools = detectInstalledTools();
25
+ return {
26
+ projectId: config?.projectId ?? null,
27
+ agentId: config?.agentId ?? null,
28
+ tools,
29
+ };
30
+ }
@@ -0,0 +1,17 @@
1
+ import type { AdminClient, AgentSummary, AgentResponse, CreateAgentBody, CreateAgentResponse, UpdateAgentBody } from '../api.js';
2
+ import { type AgentsStore } from '../config.js';
3
+ export interface AgentListResult {
4
+ agents: AgentSummary[];
5
+ linkedAgentId?: string;
6
+ }
7
+ export declare function listAgents(client: AdminClient, projectId: string): Promise<AgentListResult>;
8
+ export declare function createAgent(client: AdminClient, projectId: string, body: CreateAgentBody): Promise<CreateAgentResponse>;
9
+ export declare function linkAgent(projectId: string, agentId: string): Promise<void>;
10
+ export declare function getAgent(client: AdminClient, projectId: string, agentId: string): Promise<AgentResponse>;
11
+ export declare function updateAgent(client: AdminClient, projectId: string, agentId: string, updates: UpdateAgentBody): Promise<AgentResponse>;
12
+ export interface DeleteAgentResult {
13
+ wasLinked: boolean;
14
+ }
15
+ export declare function deleteAgent(client: AdminClient, projectId: string, agentId: string): Promise<DeleteAgentResult>;
16
+ export declare function regenerateKey(client: AdminClient, projectId: string, agentId: string): Promise<string>;
17
+ export declare function getStoredKey(agentId: string, store: AgentsStore): string | null;
@@ -0,0 +1,62 @@
1
+ import { readAgents, writeAgents, readProjectConfig, writeProjectConfig, } from '../config.js';
2
+ export async function listAgents(client, projectId) {
3
+ const { agents } = await client.listAgents(projectId);
4
+ const config = await readProjectConfig();
5
+ return { agents, linkedAgentId: config?.agentId };
6
+ }
7
+ export async function createAgent(client, projectId, body) {
8
+ const result = await client.createAgent(projectId, body);
9
+ const store = await readAgents();
10
+ store.agents[result.id] = {
11
+ key: result.apiKey,
12
+ name: result.name,
13
+ project: projectId,
14
+ };
15
+ await writeAgents(store);
16
+ return result;
17
+ }
18
+ export async function linkAgent(projectId, agentId) {
19
+ await writeProjectConfig({ projectId, agentId });
20
+ }
21
+ export async function getAgent(client, projectId, agentId) {
22
+ return client.getAgent(projectId, agentId);
23
+ }
24
+ export async function updateAgent(client, projectId, agentId, updates) {
25
+ const result = await client.updateAgent(projectId, agentId, updates);
26
+ if (updates.name) {
27
+ const store = await readAgents();
28
+ if (store.agents[agentId]) {
29
+ store.agents[agentId].name = updates.name;
30
+ await writeAgents(store);
31
+ }
32
+ }
33
+ return result;
34
+ }
35
+ export async function deleteAgent(client, projectId, agentId) {
36
+ await client.deleteAgent(projectId, agentId);
37
+ const store = await readAgents();
38
+ if (store.agents[agentId]) {
39
+ delete store.agents[agentId];
40
+ await writeAgents(store);
41
+ }
42
+ const config = await readProjectConfig();
43
+ const wasLinked = config?.agentId === agentId;
44
+ if (wasLinked && config) {
45
+ await writeProjectConfig({ ...config, agentId: '' });
46
+ }
47
+ return { wasLinked };
48
+ }
49
+ export async function regenerateKey(client, projectId, agentId) {
50
+ const result = await client.regenerateKey(projectId, agentId);
51
+ const store = await readAgents();
52
+ store.agents[agentId] = {
53
+ ...store.agents[agentId],
54
+ key: result.apiKey,
55
+ project: projectId,
56
+ };
57
+ await writeAgents(store);
58
+ return result.apiKey;
59
+ }
60
+ export function getStoredKey(agentId, store) {
61
+ return store.agents[agentId]?.key ?? null;
62
+ }
@@ -0,0 +1,12 @@
1
+ import type { AdminClient } from '../api.js';
2
+ export declare class AgentNotCreatureError extends Error {
3
+ constructor();
4
+ }
5
+ export interface CreatureUpdateResult {
6
+ type: string;
7
+ color: string;
8
+ name?: string;
9
+ }
10
+ export declare function setCreatureType(client: AdminClient, projectId: string, agentId: string, type: string): Promise<CreatureUpdateResult>;
11
+ export declare function setCreatureColour(client: AdminClient, projectId: string, agentId: string, colour: string): Promise<CreatureUpdateResult>;
12
+ export declare function setCreatureName(client: AdminClient, projectId: string, agentId: string, name: string): Promise<CreatureUpdateResult>;
@@ -0,0 +1,35 @@
1
+ import { CREATURE_DEFAULT_COLORS } from '../constants.js';
2
+ export class AgentNotCreatureError extends Error {
3
+ constructor() {
4
+ super('Agent is not in creature form');
5
+ this.name = 'AgentNotCreatureError';
6
+ }
7
+ }
8
+ export async function setCreatureType(client, projectId, agentId, type) {
9
+ const colour = CREATURE_DEFAULT_COLORS[type];
10
+ await client.updateAgent(projectId, agentId, {
11
+ form: 'creature',
12
+ creature: { type, color: colour },
13
+ });
14
+ return { type, color: colour };
15
+ }
16
+ export async function setCreatureColour(client, projectId, agentId, colour) {
17
+ const agent = await client.getAgent(projectId, agentId);
18
+ if (agent.form !== 'creature') {
19
+ throw new AgentNotCreatureError();
20
+ }
21
+ await client.updateAgent(projectId, agentId, {
22
+ creature: { type: agent.creature.type, color: colour },
23
+ });
24
+ return { type: agent.creature.type, color: colour };
25
+ }
26
+ export async function setCreatureName(client, projectId, agentId, name) {
27
+ const agent = await client.getAgent(projectId, agentId);
28
+ if (agent.form !== 'creature') {
29
+ throw new AgentNotCreatureError();
30
+ }
31
+ await client.updateAgent(projectId, agentId, {
32
+ creature: { type: agent.creature.type, color: agent.creature.color, name },
33
+ });
34
+ return { type: agent.creature.type, color: agent.creature.color, name };
35
+ }
@@ -0,0 +1,9 @@
1
+ import type { AdminClient, ProjectSummary, ProjectResponse } from '../api.js';
2
+ export interface ProjectListResult {
3
+ projects: ProjectSummary[];
4
+ linkedProjectId?: string;
5
+ }
6
+ export declare function listProjects(client: AdminClient): Promise<ProjectListResult>;
7
+ export declare function createProject(client: AdminClient, name: string): Promise<ProjectResponse>;
8
+ export declare function setDefaultProject(projectId: string): Promise<void>;
9
+ export declare function deleteProject(client: AdminClient, id: string): Promise<void>;
@@ -0,0 +1,22 @@
1
+ import { readProjectConfig, readGlobalConfig, writeGlobalConfig, } from '../config.js';
2
+ export async function listProjects(client) {
3
+ const { projects } = await client.listProjects();
4
+ const config = await readProjectConfig();
5
+ return { projects, linkedProjectId: config?.projectId };
6
+ }
7
+ export async function createProject(client, name) {
8
+ return client.createProject(name);
9
+ }
10
+ export async function setDefaultProject(projectId) {
11
+ const config = await readGlobalConfig();
12
+ config.defaults = { ...config.defaults, projectId };
13
+ await writeGlobalConfig(config);
14
+ }
15
+ export async function deleteProject(client, id) {
16
+ await client.deleteProject(id);
17
+ const config = await readGlobalConfig();
18
+ if (config.defaults?.projectId === id) {
19
+ config.defaults.projectId = undefined;
20
+ await writeGlobalConfig(config);
21
+ }
22
+ }
@@ -0,0 +1,12 @@
1
+ export declare function detectProgress(): Promise<number>;
2
+ export declare function stepAccount(): Promise<void>;
3
+ export declare function stepProject(): Promise<string>;
4
+ export declare function stepAgent(projectId: string): Promise<{
5
+ agentId: string;
6
+ form: 'human' | 'creature';
7
+ apiKey: string;
8
+ }>;
9
+ export declare function stepAppearance(projectId: string, agentId: string, form: 'human' | 'creature'): Promise<void>;
10
+ export declare function stepIntegration(): Promise<void>;
11
+ export declare function stepVerify(apiKey: string): Promise<void>;
12
+ export declare function runTutorial(): Promise<void>;