vibecodingmachine-cli 2026.3.9-850 → 2026.3.10-1547

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 (39) hide show
  1. package/README.md +85 -85
  2. package/bin/commands/agent-commands.js +295 -28
  3. package/bin/vibecodingmachine.js +0 -0
  4. package/package.json +2 -2
  5. package/scripts/postinstall.js +161 -161
  6. package/src/commands/auth.js +100 -100
  7. package/src/commands/auto-execution.js +120 -32
  8. package/src/commands/auto-requirement-management.js +9 -9
  9. package/src/commands/auto-status-helpers.js +6 -12
  10. package/src/commands/computers.js +318 -318
  11. package/src/commands/feature.js +123 -123
  12. package/src/commands/locale.js +72 -72
  13. package/src/commands/repo.js +163 -163
  14. package/src/commands/setup.js +93 -93
  15. package/src/commands/sync.js +287 -287
  16. package/src/index.js +5 -5
  17. package/src/utils/agent-selector.js +50 -50
  18. package/src/utils/asset-cleanup.js +60 -60
  19. package/src/utils/auth.js +6 -0
  20. package/src/utils/auto-mode-ansi-ui.js +237 -237
  21. package/src/utils/auto-mode-simple-ui.js +141 -141
  22. package/src/utils/copy-with-progress.js +167 -167
  23. package/src/utils/download-with-progress.js +84 -84
  24. package/src/utils/keyboard-handler.js +153 -153
  25. package/src/utils/kiro-installer.js +178 -178
  26. package/src/utils/logger.js +4 -4
  27. package/src/utils/persistent-header.js +114 -114
  28. package/src/utils/prompt-helper.js +63 -63
  29. package/src/utils/provider-checker/agent-runner.js +110 -31
  30. package/src/utils/provider-checker/ide-manager.js +37 -8
  31. package/src/utils/provider-checker/provider-validator.js +50 -0
  32. package/src/utils/provider-checker/requirements-manager.js +21 -6
  33. package/src/utils/status-card.js +121 -121
  34. package/src/utils/stdout-interceptor.js +127 -127
  35. package/src/utils/trui-main-handlers.js +41 -8
  36. package/src/utils/trui-main-menu.js +10 -3
  37. package/src/utils/trui-nav-agents.js +23 -33
  38. package/src/utils/trui-navigation.js +2 -2
  39. package/src/utils/user-tracking.js +299 -299
@@ -1,318 +1,318 @@
1
- const chalk = require('chalk');
2
- const Table = require('cli-table3');
3
- const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
4
- const { t } = require('vibecodingmachine-core');
5
-
6
- /**
7
- * Display all registered computers with their status
8
- */
9
- async function listComputers(options = {}) {
10
- const syncEngine = new SyncEngine();
11
-
12
- try {
13
- await syncEngine.initialize();
14
-
15
- // Fetch all computers from DynamoDB
16
- const computers = await fetchAllComputers(syncEngine);
17
-
18
- if (computers.length === 0) {
19
- console.log(chalk.yellow(`\n${t('computers.no.computers')}\n`));
20
- console.log(chalk.gray(`${t('computers.will.appear')}\n`));
21
- return;
22
- }
23
-
24
- // Filter by focus area if specified
25
- let filteredComputers = computers;
26
- if (options.focus) {
27
- filteredComputers = computers.filter(c =>
28
- c.focusArea && c.focusArea.toLowerCase().includes(options.focus.toLowerCase())
29
- );
30
- }
31
-
32
- // Filter by status if specified
33
- if (options.status) {
34
- filteredComputers = filteredComputers.filter(c =>
35
- c.status && c.status.toLowerCase() === options.status.toLowerCase()
36
- );
37
- }
38
-
39
- // Create table
40
- const table = new Table({
41
- head: [
42
- chalk.cyan(t('computers.table.computer')),
43
- chalk.cyan(t('computers.table.focus')),
44
- chalk.cyan(t('computers.table.status')),
45
- chalk.cyan(t('computers.table.requirement')),
46
- chalk.cyan(t('computers.table.progress')),
47
- chalk.cyan(t('computers.table.last.sync'))
48
- ],
49
- colWidths: [25, 30, 12, 35, 10, 15]
50
- });
51
-
52
- // Add rows
53
- for (const computer of filteredComputers) {
54
- const statusIcon = getStatusIcon(computer.status);
55
- const statusKey = `computers.status.${computer.status || 'unknown'}`;
56
- const statusText = `${statusIcon} ${t(statusKey)}`;
57
- const progress = computer.progress ? `${computer.progress}%` : '-';
58
- const lastSync = computer.lastSyncTime
59
- ? formatTimeSince(computer.lastSyncTime)
60
- : t('computers.never');
61
-
62
- table.push([
63
- computer.hostname || computer.computerId,
64
- computer.focusArea || '-',
65
- statusText,
66
- truncate(computer.currentRequirement || '-', 33),
67
- progress,
68
- lastSync
69
- ]);
70
- }
71
-
72
- console.log('\n' + table.toString() + '\n');
73
-
74
- // Show summary
75
- const activeCount = computers.filter(c => c.status === 'active').length;
76
- const idleCount = computers.filter(c => c.status === 'idle').length;
77
- const errorCount = computers.filter(c => c.status === 'error').length;
78
-
79
- console.log(chalk.gray(t('computers.total', { count: computers.length })));
80
- console.log(chalk.green(` ${t('computers.active', { count: activeCount })}`));
81
- console.log(chalk.yellow(` ${t('computers.idle', { count: idleCount })}`));
82
- if (errorCount > 0) {
83
- console.log(chalk.red(` ${t('computers.error', { count: errorCount })}`));
84
- }
85
- console.log('');
86
-
87
- } catch (error) {
88
- console.error(chalk.red(`\n✗ ${t('computers.fetch.failed')}`), error.message);
89
- await errorReporter.reportError(error, {
90
- command: 'listComputers',
91
- focusArea,
92
- status
93
- });
94
- throw error;
95
- } finally {
96
- syncEngine.stop();
97
- }
98
- }
99
-
100
- /**
101
- * Show detailed status of a specific computer
102
- */
103
- async function showComputerStatus(computerId) {
104
- const syncEngine = new SyncEngine();
105
-
106
- try {
107
- await syncEngine.initialize();
108
-
109
- const computer = await fetchComputer(syncEngine, computerId);
110
-
111
- if (!computer) {
112
- console.log(chalk.red(`\n✗ ${t('computers.not.found', { id: computerId })}\n`));
113
- return;
114
- }
115
-
116
- // Display computer details
117
- console.log('\n' + chalk.bold.cyan(t('computers.details.title')));
118
- console.log(chalk.gray('─'.repeat(60)));
119
- console.log(chalk.white(t('computers.details.hostname').padEnd(17)) + (computer.hostname || computer.computerId));
120
- console.log(chalk.white(t('computers.details.id').padEnd(17)) + computer.computerId);
121
- console.log(chalk.white(t('computers.details.focus').padEnd(17)) + (computer.focusArea || '-'));
122
- console.log(chalk.white(t('computers.details.os').padEnd(17)) + (computer.os || '-'));
123
- const statusKey = `computers.status.${computer.status || 'unknown'}`;
124
- console.log(chalk.white(t('computers.details.status').padEnd(17)) + getStatusIcon(computer.status) + ' ' + t(statusKey));
125
- console.log(chalk.white(t('computers.details.last.sync').padEnd(17)) + (computer.lastSyncTime ? formatTimeSince(computer.lastSyncTime) : t('computers.never')));
126
- console.log('');
127
-
128
- // Current work
129
- console.log(chalk.bold.cyan(t('computers.work.title')));
130
- console.log(chalk.gray('─'.repeat(60)));
131
- console.log(chalk.white(t('computers.work.requirement').padEnd(17)) + (computer.currentRequirement || t('computers.work.none')));
132
- console.log(chalk.white(t('computers.work.progress').padEnd(17)) + (computer.progress ? `${computer.progress}%` : '-'));
133
- console.log(chalk.white(t('computers.work.time.active').padEnd(17)) + (computer.timeActive || '-'));
134
- console.log(chalk.white(t('computers.work.ide').padEnd(17)) + (computer.ide || '-'));
135
- console.log('');
136
-
137
- // Statistics
138
- if (computer.stats) {
139
- console.log(chalk.bold.cyan(t('computers.stats.title')));
140
- console.log(chalk.gray('─'.repeat(60)));
141
- console.log(chalk.white(t('computers.stats.total').padEnd(21)) + (computer.stats.total || 0));
142
- console.log(chalk.white(t('computers.stats.completed').padEnd(21)) + chalk.green(computer.stats.completed || 0));
143
- console.log(chalk.white(t('computers.stats.in.progress').padEnd(21)) + chalk.yellow(computer.stats.inProgress || 0));
144
- console.log(chalk.white(t('computers.stats.to.verify').padEnd(21)) + chalk.blue(computer.stats.toVerify || 0));
145
- console.log(chalk.white(t('computers.stats.completion.rate').padEnd(21)) + (computer.stats.completionRate || '0%'));
146
- console.log('');
147
- }
148
-
149
- } catch (error) {
150
- console.error(chalk.red(`\n✗ ${t('computers.status.fetch.failed')}`), error.message);
151
- throw error;
152
- } finally {
153
- syncEngine.stop();
154
- }
155
- }
156
-
157
- /**
158
- * Register current computer with focus area
159
- */
160
- async function registerComputer(focusArea) {
161
- const syncEngine = new SyncEngine();
162
-
163
- try {
164
- await syncEngine.initialize();
165
-
166
- const os = require('os');
167
- const computerData = {
168
- computerId: syncEngine.computerId,
169
- hostname: os.hostname(),
170
- focusArea: focusArea,
171
- os: `${os.platform()} ${os.release()}`,
172
- registeredAt: Date.now(),
173
- lastSyncTime: Date.now(),
174
- status: 'idle'
175
- };
176
-
177
- await saveComputer(syncEngine, computerData);
178
-
179
- console.log(chalk.green(`\n✓ ${t('computers.register.success')}\n`));
180
- console.log(chalk.white(t('computers.register.id').padEnd(14)) + computerData.computerId);
181
- console.log(chalk.white(t('computers.register.hostname').padEnd(14)) + computerData.hostname);
182
- console.log(chalk.white(t('computers.register.focus').padEnd(14)) + computerData.focusArea);
183
- console.log('');
184
-
185
- } catch (error) {
186
- console.error(chalk.red(`\n✗ ${t('computers.register.failed')}`), error.message);
187
- await errorReporter.reportError(error, {
188
- command: 'registerComputer',
189
- focusArea
190
- });
191
- throw error;
192
- } finally {
193
- syncEngine.stop();
194
- }
195
- }
196
-
197
- /**
198
- * Update focus area for current computer
199
- */
200
- async function updateFocus(newFocusArea) {
201
- const syncEngine = new SyncEngine();
202
-
203
- try {
204
- await syncEngine.initialize();
205
-
206
- const computer = await fetchComputer(syncEngine, syncEngine.computerId);
207
-
208
- if (!computer) {
209
- console.log(chalk.yellow(`\n⚠ ${t('computers.not.registered')}\n`));
210
- await registerComputer(newFocusArea);
211
- return;
212
- }
213
-
214
- computer.focusArea = newFocusArea;
215
- computer.lastSyncTime = Date.now();
216
-
217
- await saveComputer(syncEngine, computer);
218
-
219
- console.log(chalk.green(`\n✓ ${t('computers.focus.updated')}\n`));
220
- console.log(chalk.white(t('computers.focus.computer').padEnd(14)) + computer.hostname);
221
- console.log(chalk.white(t('computers.focus.new').padEnd(14)) + newFocusArea);
222
- console.log('');
223
-
224
- } catch (error) {
225
- console.error(chalk.red(`\n✗ ${t('computers.focus.update.failed')}`), error.message);
226
- throw error;
227
- } finally {
228
- syncEngine.stop();
229
- }
230
- }
231
-
232
- // Helper functions
233
-
234
- function getStatusIcon(status) {
235
- switch (status) {
236
- case 'active': return chalk.green('●');
237
- case 'idle': return chalk.yellow('●');
238
- case 'error': return chalk.red('●');
239
- case 'offline': return chalk.gray('●');
240
- default: return chalk.gray('○');
241
- }
242
- }
243
-
244
- function formatTimeSince(timestamp) {
245
- const now = Date.now();
246
- const diff = now - timestamp;
247
-
248
- const seconds = Math.floor(diff / 1000);
249
- const minutes = Math.floor(seconds / 60);
250
- const hours = Math.floor(minutes / 60);
251
- const days = Math.floor(hours / 24);
252
-
253
- if (days > 0) return t('computers.time.days.ago', { count: days });
254
- if (hours > 0) return t('computers.time.hours.ago', { count: hours });
255
- if (minutes > 0) return t('computers.time.minutes.ago', { count: minutes });
256
- return t('computers.time.seconds.ago', { count: seconds });
257
- }
258
-
259
- function truncate(str, maxLength) {
260
- if (str.length <= maxLength) return str;
261
- return str.substring(0, maxLength - 3) + '...';
262
- }
263
-
264
- async function fetchAllComputers(syncEngine) {
265
- const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
266
-
267
- const tableName = 'vibecodingmachine-computers';
268
-
269
- try {
270
- const command = new ScanCommand({
271
- TableName: tableName
272
- });
273
-
274
- const response = await syncEngine.dynamoClient.send(command);
275
- return response.Items || [];
276
- } catch (error) {
277
- // Table might not exist yet
278
- return [];
279
- }
280
- }
281
-
282
- async function fetchComputer(syncEngine, computerId) {
283
- const { GetCommand } = require('@aws-sdk/lib-dynamodb');
284
-
285
- const tableName = 'vibecodingmachine-computers';
286
-
287
- try {
288
- const command = new GetCommand({
289
- TableName: tableName,
290
- Key: { computerId }
291
- });
292
-
293
- const response = await syncEngine.dynamoClient.send(command);
294
- return response.Item;
295
- } catch (error) {
296
- return null;
297
- }
298
- }
299
-
300
- async function saveComputer(syncEngine, computerData) {
301
- const { PutCommand } = require('@aws-sdk/lib-dynamodb');
302
-
303
- const tableName = 'vibecodingmachine-computers';
304
-
305
- const command = new PutCommand({
306
- TableName: tableName,
307
- Item: computerData
308
- });
309
-
310
- await syncEngine.dynamoClient.send(command);
311
- }
312
-
313
- module.exports = {
314
- listComputers,
315
- showComputerStatus,
316
- registerComputer,
317
- updateFocus
318
- };
1
+ const chalk = require('chalk');
2
+ const Table = require('cli-table3');
3
+ const SyncEngine = require('vibecodingmachine-core/src/sync/sync-engine');
4
+ const { t } = require('vibecodingmachine-core');
5
+
6
+ /**
7
+ * Display all registered computers with their status
8
+ */
9
+ async function listComputers(options = {}) {
10
+ const syncEngine = new SyncEngine();
11
+
12
+ try {
13
+ await syncEngine.initialize();
14
+
15
+ // Fetch all computers from DynamoDB
16
+ const computers = await fetchAllComputers(syncEngine);
17
+
18
+ if (computers.length === 0) {
19
+ console.log(chalk.yellow(`\n${t('computers.no.computers')}\n`));
20
+ console.log(chalk.gray(`${t('computers.will.appear')}\n`));
21
+ return;
22
+ }
23
+
24
+ // Filter by focus area if specified
25
+ let filteredComputers = computers;
26
+ if (options.focus) {
27
+ filteredComputers = computers.filter(c =>
28
+ c.focusArea && c.focusArea.toLowerCase().includes(options.focus.toLowerCase())
29
+ );
30
+ }
31
+
32
+ // Filter by status if specified
33
+ if (options.status) {
34
+ filteredComputers = filteredComputers.filter(c =>
35
+ c.status && c.status.toLowerCase() === options.status.toLowerCase()
36
+ );
37
+ }
38
+
39
+ // Create table
40
+ const table = new Table({
41
+ head: [
42
+ chalk.cyan(t('computers.table.computer')),
43
+ chalk.cyan(t('computers.table.focus')),
44
+ chalk.cyan(t('computers.table.status')),
45
+ chalk.cyan(t('computers.table.requirement')),
46
+ chalk.cyan(t('computers.table.progress')),
47
+ chalk.cyan(t('computers.table.last.sync'))
48
+ ],
49
+ colWidths: [25, 30, 12, 35, 10, 15]
50
+ });
51
+
52
+ // Add rows
53
+ for (const computer of filteredComputers) {
54
+ const statusIcon = getStatusIcon(computer.status);
55
+ const statusKey = `computers.status.${computer.status || 'unknown'}`;
56
+ const statusText = `${statusIcon} ${t(statusKey)}`;
57
+ const progress = computer.progress ? `${computer.progress}%` : '-';
58
+ const lastSync = computer.lastSyncTime
59
+ ? formatTimeSince(computer.lastSyncTime)
60
+ : t('computers.never');
61
+
62
+ table.push([
63
+ computer.hostname || computer.computerId,
64
+ computer.focusArea || '-',
65
+ statusText,
66
+ truncate(computer.currentRequirement || '-', 33),
67
+ progress,
68
+ lastSync
69
+ ]);
70
+ }
71
+
72
+ console.log('\n' + table.toString() + '\n');
73
+
74
+ // Show summary
75
+ const activeCount = computers.filter(c => c.status === 'active').length;
76
+ const idleCount = computers.filter(c => c.status === 'idle').length;
77
+ const errorCount = computers.filter(c => c.status === 'error').length;
78
+
79
+ console.log(chalk.gray(t('computers.total', { count: computers.length })));
80
+ console.log(chalk.green(` ${t('computers.active', { count: activeCount })}`));
81
+ console.log(chalk.yellow(` ${t('computers.idle', { count: idleCount })}`));
82
+ if (errorCount > 0) {
83
+ console.log(chalk.red(` ${t('computers.error', { count: errorCount })}`));
84
+ }
85
+ console.log('');
86
+
87
+ } catch (error) {
88
+ console.error(chalk.red(`\n✗ ${t('computers.fetch.failed')}`), error.message);
89
+ await errorReporter.reportError(error, {
90
+ command: 'listComputers',
91
+ focusArea,
92
+ status
93
+ });
94
+ throw error;
95
+ } finally {
96
+ syncEngine.stop();
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Show detailed status of a specific computer
102
+ */
103
+ async function showComputerStatus(computerId) {
104
+ const syncEngine = new SyncEngine();
105
+
106
+ try {
107
+ await syncEngine.initialize();
108
+
109
+ const computer = await fetchComputer(syncEngine, computerId);
110
+
111
+ if (!computer) {
112
+ console.log(chalk.red(`\n✗ ${t('computers.not.found', { id: computerId })}\n`));
113
+ return;
114
+ }
115
+
116
+ // Display computer details
117
+ console.log('\n' + chalk.bold.cyan(t('computers.details.title')));
118
+ console.log(chalk.gray('─'.repeat(60)));
119
+ console.log(chalk.white(t('computers.details.hostname').padEnd(17)) + (computer.hostname || computer.computerId));
120
+ console.log(chalk.white(t('computers.details.id').padEnd(17)) + computer.computerId);
121
+ console.log(chalk.white(t('computers.details.focus').padEnd(17)) + (computer.focusArea || '-'));
122
+ console.log(chalk.white(t('computers.details.os').padEnd(17)) + (computer.os || '-'));
123
+ const statusKey = `computers.status.${computer.status || 'unknown'}`;
124
+ console.log(chalk.white(t('computers.details.status').padEnd(17)) + getStatusIcon(computer.status) + ' ' + t(statusKey));
125
+ console.log(chalk.white(t('computers.details.last.sync').padEnd(17)) + (computer.lastSyncTime ? formatTimeSince(computer.lastSyncTime) : t('computers.never')));
126
+ console.log('');
127
+
128
+ // Current work
129
+ console.log(chalk.bold.cyan(t('computers.work.title')));
130
+ console.log(chalk.gray('─'.repeat(60)));
131
+ console.log(chalk.white(t('computers.work.requirement').padEnd(17)) + (computer.currentRequirement || t('computers.work.none')));
132
+ console.log(chalk.white(t('computers.work.progress').padEnd(17)) + (computer.progress ? `${computer.progress}%` : '-'));
133
+ console.log(chalk.white(t('computers.work.time.active').padEnd(17)) + (computer.timeActive || '-'));
134
+ console.log(chalk.white(t('computers.work.ide').padEnd(17)) + (computer.ide || '-'));
135
+ console.log('');
136
+
137
+ // Statistics
138
+ if (computer.stats) {
139
+ console.log(chalk.bold.cyan(t('computers.stats.title')));
140
+ console.log(chalk.gray('─'.repeat(60)));
141
+ console.log(chalk.white(t('computers.stats.total').padEnd(21)) + (computer.stats.total || 0));
142
+ console.log(chalk.white(t('computers.stats.completed').padEnd(21)) + chalk.green(computer.stats.completed || 0));
143
+ console.log(chalk.white(t('computers.stats.in.progress').padEnd(21)) + chalk.yellow(computer.stats.inProgress || 0));
144
+ console.log(chalk.white(t('computers.stats.to.verify').padEnd(21)) + chalk.blue(computer.stats.toVerify || 0));
145
+ console.log(chalk.white(t('computers.stats.completion.rate').padEnd(21)) + (computer.stats.completionRate || '0%'));
146
+ console.log('');
147
+ }
148
+
149
+ } catch (error) {
150
+ console.error(chalk.red(`\n✗ ${t('computers.status.fetch.failed')}`), error.message);
151
+ throw error;
152
+ } finally {
153
+ syncEngine.stop();
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Register current computer with focus area
159
+ */
160
+ async function registerComputer(focusArea) {
161
+ const syncEngine = new SyncEngine();
162
+
163
+ try {
164
+ await syncEngine.initialize();
165
+
166
+ const os = require('os');
167
+ const computerData = {
168
+ computerId: syncEngine.computerId,
169
+ hostname: os.hostname(),
170
+ focusArea: focusArea,
171
+ os: `${os.platform()} ${os.release()}`,
172
+ registeredAt: Date.now(),
173
+ lastSyncTime: Date.now(),
174
+ status: 'idle'
175
+ };
176
+
177
+ await saveComputer(syncEngine, computerData);
178
+
179
+ console.log(chalk.green(`\n✓ ${t('computers.register.success')}\n`));
180
+ console.log(chalk.white(t('computers.register.id').padEnd(14)) + computerData.computerId);
181
+ console.log(chalk.white(t('computers.register.hostname').padEnd(14)) + computerData.hostname);
182
+ console.log(chalk.white(t('computers.register.focus').padEnd(14)) + computerData.focusArea);
183
+ console.log('');
184
+
185
+ } catch (error) {
186
+ console.error(chalk.red(`\n✗ ${t('computers.register.failed')}`), error.message);
187
+ await errorReporter.reportError(error, {
188
+ command: 'registerComputer',
189
+ focusArea
190
+ });
191
+ throw error;
192
+ } finally {
193
+ syncEngine.stop();
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Update focus area for current computer
199
+ */
200
+ async function updateFocus(newFocusArea) {
201
+ const syncEngine = new SyncEngine();
202
+
203
+ try {
204
+ await syncEngine.initialize();
205
+
206
+ const computer = await fetchComputer(syncEngine, syncEngine.computerId);
207
+
208
+ if (!computer) {
209
+ console.log(chalk.yellow(`\n⚠ ${t('computers.not.registered')}\n`));
210
+ await registerComputer(newFocusArea);
211
+ return;
212
+ }
213
+
214
+ computer.focusArea = newFocusArea;
215
+ computer.lastSyncTime = Date.now();
216
+
217
+ await saveComputer(syncEngine, computer);
218
+
219
+ console.log(chalk.green(`\n✓ ${t('computers.focus.updated')}\n`));
220
+ console.log(chalk.white(t('computers.focus.computer').padEnd(14)) + computer.hostname);
221
+ console.log(chalk.white(t('computers.focus.new').padEnd(14)) + newFocusArea);
222
+ console.log('');
223
+
224
+ } catch (error) {
225
+ console.error(chalk.red(`\n✗ ${t('computers.focus.update.failed')}`), error.message);
226
+ throw error;
227
+ } finally {
228
+ syncEngine.stop();
229
+ }
230
+ }
231
+
232
+ // Helper functions
233
+
234
+ function getStatusIcon(status) {
235
+ switch (status) {
236
+ case 'active': return chalk.green('●');
237
+ case 'idle': return chalk.yellow('●');
238
+ case 'error': return chalk.red('●');
239
+ case 'offline': return chalk.gray('●');
240
+ default: return chalk.gray('○');
241
+ }
242
+ }
243
+
244
+ function formatTimeSince(timestamp) {
245
+ const now = Date.now();
246
+ const diff = now - timestamp;
247
+
248
+ const seconds = Math.floor(diff / 1000);
249
+ const minutes = Math.floor(seconds / 60);
250
+ const hours = Math.floor(minutes / 60);
251
+ const days = Math.floor(hours / 24);
252
+
253
+ if (days > 0) return t('computers.time.days.ago', { count: days });
254
+ if (hours > 0) return t('computers.time.hours.ago', { count: hours });
255
+ if (minutes > 0) return t('computers.time.minutes.ago', { count: minutes });
256
+ return t('computers.time.seconds.ago', { count: seconds });
257
+ }
258
+
259
+ function truncate(str, maxLength) {
260
+ if (str.length <= maxLength) return str;
261
+ return str.substring(0, maxLength - 3) + '...';
262
+ }
263
+
264
+ async function fetchAllComputers(syncEngine) {
265
+ const { ScanCommand } = require('@aws-sdk/lib-dynamodb');
266
+
267
+ const tableName = 'vibecodingmachine-computers';
268
+
269
+ try {
270
+ const command = new ScanCommand({
271
+ TableName: tableName
272
+ });
273
+
274
+ const response = await syncEngine.dynamoClient.send(command);
275
+ return response.Items || [];
276
+ } catch (error) {
277
+ // Table might not exist yet
278
+ return [];
279
+ }
280
+ }
281
+
282
+ async function fetchComputer(syncEngine, computerId) {
283
+ const { GetCommand } = require('@aws-sdk/lib-dynamodb');
284
+
285
+ const tableName = 'vibecodingmachine-computers';
286
+
287
+ try {
288
+ const command = new GetCommand({
289
+ TableName: tableName,
290
+ Key: { computerId }
291
+ });
292
+
293
+ const response = await syncEngine.dynamoClient.send(command);
294
+ return response.Item;
295
+ } catch (error) {
296
+ return null;
297
+ }
298
+ }
299
+
300
+ async function saveComputer(syncEngine, computerData) {
301
+ const { PutCommand } = require('@aws-sdk/lib-dynamodb');
302
+
303
+ const tableName = 'vibecodingmachine-computers';
304
+
305
+ const command = new PutCommand({
306
+ TableName: tableName,
307
+ Item: computerData
308
+ });
309
+
310
+ await syncEngine.dynamoClient.send(command);
311
+ }
312
+
313
+ module.exports = {
314
+ listComputers,
315
+ showComputerStatus,
316
+ registerComputer,
317
+ updateFocus
318
+ };