@tonk/cli 0.2.5 → 0.2.7

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 (67) hide show
  1. package/dist/commands/create.d.ts.map +1 -1
  2. package/dist/commands/create.js +8 -6
  3. package/dist/commands/create.js.map +1 -1
  4. package/dist/commands/hello.d.ts.map +1 -1
  5. package/dist/commands/hello.js +12 -0
  6. package/dist/commands/hello.js.map +1 -1
  7. package/dist/commands/kill.d.ts.map +1 -1
  8. package/dist/commands/kill.js +24 -0
  9. package/dist/commands/kill.js.map +1 -1
  10. package/dist/commands/ls.d.ts.map +1 -1
  11. package/dist/commands/ls.js +18 -0
  12. package/dist/commands/ls.js.map +1 -1
  13. package/dist/commands/proxy.d.ts.map +1 -1
  14. package/dist/commands/proxy.js +18 -0
  15. package/dist/commands/proxy.js.map +1 -1
  16. package/dist/commands/ps.d.ts.map +1 -1
  17. package/dist/commands/ps.js +18 -0
  18. package/dist/commands/ps.js.map +1 -1
  19. package/dist/commands/push.d.ts.map +1 -1
  20. package/dist/commands/push.js +25 -1
  21. package/dist/commands/push.js.map +1 -1
  22. package/dist/commands/start.d.ts.map +1 -1
  23. package/dist/commands/start.js +19 -0
  24. package/dist/commands/start.js.map +1 -1
  25. package/dist/commands/worker/commands.d.ts +42 -0
  26. package/dist/commands/worker/commands.d.ts.map +1 -0
  27. package/dist/commands/worker/commands.js +906 -0
  28. package/dist/commands/worker/commands.js.map +1 -0
  29. package/dist/commands/worker/index.d.ts +7 -0
  30. package/dist/commands/worker/index.d.ts.map +1 -0
  31. package/dist/commands/worker/index.js +30 -0
  32. package/dist/commands/worker/index.js.map +1 -0
  33. package/dist/commands/worker/utils/config.d.ts +26 -0
  34. package/dist/commands/worker/utils/config.d.ts.map +1 -0
  35. package/dist/commands/worker/utils/config.js +197 -0
  36. package/dist/commands/worker/utils/config.js.map +1 -0
  37. package/dist/commands/worker/utils/display.d.ts +6 -0
  38. package/dist/commands/worker/utils/display.d.ts.map +1 -0
  39. package/dist/commands/worker/utils/display.js +32 -0
  40. package/dist/commands/worker/utils/display.js.map +1 -0
  41. package/dist/commands/worker/utils/finder.d.ts +12 -0
  42. package/dist/commands/worker/utils/finder.d.ts.map +1 -0
  43. package/dist/commands/worker/utils/finder.js +60 -0
  44. package/dist/commands/worker/utils/finder.js.map +1 -0
  45. package/dist/commands/worker.d.ts +3 -0
  46. package/dist/commands/worker.d.ts.map +1 -0
  47. package/dist/commands/worker.js +527 -0
  48. package/dist/commands/worker.js.map +1 -0
  49. package/dist/lib/workerManager.d.ts +60 -0
  50. package/dist/lib/workerManager.d.ts.map +1 -0
  51. package/dist/lib/workerManager.js +406 -0
  52. package/dist/lib/workerManager.js.map +1 -0
  53. package/dist/tonk.js +10 -0
  54. package/dist/tonk.js.map +1 -1
  55. package/dist/types/worker.d.ts +102 -0
  56. package/dist/types/worker.d.ts.map +1 -0
  57. package/dist/types/worker.js +7 -0
  58. package/dist/types/worker.js.map +1 -0
  59. package/dist/types/workerConfig.d.ts +164 -0
  60. package/dist/types/workerConfig.d.ts.map +1 -0
  61. package/dist/types/workerConfig.js +80 -0
  62. package/dist/types/workerConfig.js.map +1 -0
  63. package/dist/utils/analytics.d.ts +26 -0
  64. package/dist/utils/analytics.d.ts.map +1 -0
  65. package/dist/utils/analytics.js +155 -0
  66. package/dist/utils/analytics.js.map +1 -0
  67. package/package.json +4 -1
@@ -0,0 +1,906 @@
1
+ import chalk from 'chalk';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import Table from 'cli-table3';
5
+ import { promisify } from 'node:util';
6
+ import { exec } from 'node:child_process';
7
+ import { TonkWorkerManager } from '../../lib/workerManager.js';
8
+ import { displayWorkerDetails } from './utils/display.js';
9
+ import { findWorkerRoot, findAvailablePort } from './utils/finder.js';
10
+ import { trackCommand, trackCommandError, trackCommandSuccess, } from '../../utils/analytics.js';
11
+ import { readPackageJson, readWorkerConfigJs, updateWorkerConfig, promptForMissingOptions, generatePackageJsonContent, generateWorkerConfigJsContent, } from './utils/config.js';
12
+ /**
13
+ * Register the inspect command
14
+ */
15
+ export function registerInspectCommand(workerCommand) {
16
+ workerCommand
17
+ .command('inspect <nameOrId>')
18
+ .description('Inspect a specific worker')
19
+ .option('-s, --start', 'Start the worker')
20
+ .option('-S, --stop', 'Stop the worker')
21
+ .option('-c, --config <path>', 'Path to worker configuration file')
22
+ .option('-p, --ping', 'Ping the worker to check its status')
23
+ .action(async (nameOrId, options) => {
24
+ const startTime = Date.now();
25
+ try {
26
+ trackCommand('worker-inspect', {
27
+ nameOrId,
28
+ start: options.start,
29
+ stop: options.stop,
30
+ config: !!options.config,
31
+ ping: options.ping,
32
+ });
33
+ // Create worker manager
34
+ const workerManager = new TonkWorkerManager();
35
+ // Get worker
36
+ const worker = await workerManager.findByNameOrId(nameOrId);
37
+ if (!worker) {
38
+ console.error(chalk.red(`Worker with name or ID '${nameOrId}' not found.`));
39
+ const duration = Date.now() - startTime;
40
+ trackCommandError('worker-inspect', new Error('Worker not found'), duration, {
41
+ nameOrId,
42
+ });
43
+ return;
44
+ }
45
+ // If no options provided, show worker details
46
+ if (!options.start &&
47
+ !options.stop &&
48
+ !options.config &&
49
+ !options.ping) {
50
+ displayWorkerDetails(worker);
51
+ const duration = Date.now() - startTime;
52
+ trackCommandSuccess('worker-inspect', duration, {
53
+ workerId: worker.id,
54
+ workerName: worker.name,
55
+ action: 'display',
56
+ });
57
+ return;
58
+ }
59
+ // Handle ping option
60
+ if (options.ping) {
61
+ console.log(chalk.blue(`Pinging worker '${worker.name}' at ${worker.endpoint}...`));
62
+ // Check worker health
63
+ const isHealthy = await workerManager.checkHealth(worker.id);
64
+ if (isHealthy) {
65
+ console.log(chalk.green(`Worker '${worker.name}' is active!`));
66
+ }
67
+ else {
68
+ console.log(chalk.red(`Worker '${worker.name}' is not responding.`));
69
+ }
70
+ const duration = Date.now() - startTime;
71
+ trackCommandSuccess('worker-inspect', duration, {
72
+ workerId: worker.id,
73
+ workerName: worker.name,
74
+ action: 'ping',
75
+ isHealthy,
76
+ });
77
+ return;
78
+ }
79
+ // Handle start option
80
+ if (options.start) {
81
+ console.log(chalk.blue(`Starting worker '${worker.name}'...`));
82
+ await workerManager.start(worker.id);
83
+ console.log(chalk.green(`Worker '${worker.name}' started successfully!`));
84
+ const duration = Date.now() - startTime;
85
+ trackCommandSuccess('worker-inspect', duration, {
86
+ workerId: worker.id,
87
+ workerName: worker.name,
88
+ action: 'start',
89
+ });
90
+ return;
91
+ }
92
+ // Handle stop option
93
+ if (options.stop) {
94
+ console.log(chalk.blue(`Stopping worker '${worker.name}'...`));
95
+ await workerManager.stop(worker.id);
96
+ console.log(chalk.green(`Worker '${worker.name}' stopped successfully!`));
97
+ const duration = Date.now() - startTime;
98
+ trackCommandSuccess('worker-inspect', duration, {
99
+ workerId: worker.id,
100
+ workerName: worker.name,
101
+ action: 'stop',
102
+ });
103
+ return;
104
+ }
105
+ // Handle config option
106
+ if (options.config) {
107
+ await updateWorkerConfig(workerManager, worker.id, options.config);
108
+ const duration = Date.now() - startTime;
109
+ trackCommandSuccess('worker-inspect', duration, {
110
+ workerId: worker.id,
111
+ workerName: worker.name,
112
+ action: 'config',
113
+ configPath: options.config,
114
+ });
115
+ }
116
+ }
117
+ catch (error) {
118
+ const duration = Date.now() - startTime;
119
+ trackCommandError('worker-inspect', error, duration, {
120
+ nameOrId,
121
+ options,
122
+ });
123
+ console.error(chalk.red('Failed to manage worker:'), error);
124
+ }
125
+ });
126
+ }
127
+ /**
128
+ * Register the list command
129
+ */
130
+ export function registerListCommand(workerCommand) {
131
+ workerCommand
132
+ .command('ls')
133
+ .description('List all registered workers')
134
+ .action(async () => {
135
+ const startTime = Date.now();
136
+ try {
137
+ trackCommand('worker-ls', {});
138
+ // Create worker manager
139
+ const workerManager = new TonkWorkerManager();
140
+ // Get all workers
141
+ const workers = await workerManager.list();
142
+ if (workers.length === 0) {
143
+ console.log(chalk.yellow('No workers registered yet.'));
144
+ console.log(chalk.blue(`Use '${chalk.bold('tonk worker register')}' to register a worker.`));
145
+ const duration = Date.now() - startTime;
146
+ trackCommandSuccess('worker-ls', duration, {
147
+ workerCount: 0,
148
+ });
149
+ return;
150
+ }
151
+ // Create a table for display
152
+ const table = new Table({
153
+ head: [
154
+ chalk.cyan('ID'),
155
+ chalk.cyan('Name'),
156
+ chalk.cyan('Endpoint'),
157
+ chalk.cyan('Protocol'),
158
+ chalk.cyan('Status'),
159
+ chalk.cyan('Last Seen'),
160
+ ],
161
+ colWidths: [24, 20, 30, 10, 10, 20],
162
+ });
163
+ // Add workers to table
164
+ workers.forEach(worker => {
165
+ const lastSeen = worker.status.lastSeen
166
+ ? new Date(worker.status.lastSeen).toLocaleString()
167
+ : 'Never';
168
+ table.push([
169
+ worker.id,
170
+ worker.name,
171
+ worker.endpoint,
172
+ worker.protocol,
173
+ worker.status.active
174
+ ? chalk.green('Active')
175
+ : chalk.yellow('Inactive'),
176
+ lastSeen,
177
+ ]);
178
+ });
179
+ console.log(chalk.bold(`Registered Workers (${workers.length}):`));
180
+ console.log(table.toString());
181
+ console.log(chalk.blue(`\nUse '${chalk.bold('tonk worker inspect <name or id>')}' to view details of a specific worker.`));
182
+ const duration = Date.now() - startTime;
183
+ trackCommandSuccess('worker-ls', duration, {
184
+ workerCount: workers.length,
185
+ activeWorkers: workers.filter(w => w.status.active).length,
186
+ });
187
+ }
188
+ catch (error) {
189
+ const duration = Date.now() - startTime;
190
+ trackCommandError('worker-ls', error, duration);
191
+ console.error(chalk.red('Failed to list workers:'), error);
192
+ }
193
+ });
194
+ }
195
+ /**
196
+ * Register the remove command
197
+ */
198
+ export function registerRemoveCommand(workerCommand) {
199
+ workerCommand
200
+ .command('rm <nameOrId>')
201
+ .description('Remove a registered worker')
202
+ .action(async (nameOrId) => {
203
+ const startTime = Date.now();
204
+ try {
205
+ trackCommand('worker-rm', {
206
+ nameOrId,
207
+ });
208
+ // Create worker manager
209
+ const workerManager = new TonkWorkerManager();
210
+ // Get worker
211
+ const worker = await workerManager.findByNameOrId(nameOrId);
212
+ if (!worker) {
213
+ console.error(chalk.red(`Worker with name or ID '${nameOrId}' not found.`));
214
+ const duration = Date.now() - startTime;
215
+ trackCommandError('worker-rm', new Error('Worker not found'), duration, {
216
+ nameOrId,
217
+ });
218
+ return;
219
+ }
220
+ // Remove worker
221
+ await workerManager.remove(worker.id);
222
+ console.log(chalk.green(`Worker '${worker.name}' (${worker.id}) removed successfully.`));
223
+ const duration = Date.now() - startTime;
224
+ trackCommandSuccess('worker-rm', duration, {
225
+ workerId: worker.id,
226
+ workerName: worker.name,
227
+ });
228
+ }
229
+ catch (error) {
230
+ const duration = Date.now() - startTime;
231
+ trackCommandError('worker-rm', error, duration, {
232
+ nameOrId,
233
+ });
234
+ console.error(chalk.red('Failed to remove worker:'), error);
235
+ }
236
+ });
237
+ }
238
+ /**
239
+ * Register the ping command
240
+ */
241
+ export function registerPingCommand(workerCommand) {
242
+ workerCommand
243
+ .command('ping <nameOrId>')
244
+ .description('Ping a worker to check its status')
245
+ .action(async (nameOrId) => {
246
+ const startTime = Date.now();
247
+ try {
248
+ trackCommand('worker-ping', {
249
+ nameOrId,
250
+ });
251
+ // Create worker manager
252
+ const workerManager = new TonkWorkerManager();
253
+ // Get worker
254
+ const worker = await workerManager.findByNameOrId(nameOrId);
255
+ if (!worker) {
256
+ console.error(chalk.red(`Worker with name or ID '${nameOrId}' not found.`));
257
+ const duration = Date.now() - startTime;
258
+ trackCommandError('worker-ping', new Error('Worker not found'), duration, {
259
+ nameOrId,
260
+ });
261
+ return;
262
+ }
263
+ console.log(chalk.blue(`Pinging worker '${worker.name}' at ${worker.endpoint}...`));
264
+ // Check worker health
265
+ const isHealthy = await workerManager.checkHealth(worker.id);
266
+ if (isHealthy) {
267
+ console.log(chalk.green(`Worker '${worker.name}' is active!`));
268
+ }
269
+ else {
270
+ console.log(chalk.red(`Worker '${worker.name}' is not responding.`));
271
+ }
272
+ const duration = Date.now() - startTime;
273
+ trackCommandSuccess('worker-ping', duration, {
274
+ workerId: worker.id,
275
+ workerName: worker.name,
276
+ isHealthy,
277
+ });
278
+ }
279
+ catch (error) {
280
+ const duration = Date.now() - startTime;
281
+ trackCommandError('worker-ping', error, duration, {
282
+ nameOrId,
283
+ });
284
+ console.error(chalk.red('Failed to ping worker:'), error);
285
+ }
286
+ });
287
+ }
288
+ /**
289
+ * Register the start command
290
+ */
291
+ export function registerStartCommand(workerCommand) {
292
+ workerCommand
293
+ .command('start <nameOrId>')
294
+ .description('Start a worker')
295
+ .action(async (nameOrId) => {
296
+ const startTime = Date.now();
297
+ try {
298
+ trackCommand('worker-start', {
299
+ nameOrId,
300
+ });
301
+ // Create worker manager
302
+ const workerManager = new TonkWorkerManager();
303
+ // Get worker
304
+ const worker = await workerManager.findByNameOrId(nameOrId);
305
+ if (!worker) {
306
+ console.error(chalk.red(`Worker with name or ID '${nameOrId}' not found.`));
307
+ const duration = Date.now() - startTime;
308
+ trackCommandError('worker-start', new Error('Worker not found'), duration, {
309
+ nameOrId,
310
+ });
311
+ return;
312
+ }
313
+ console.log(chalk.blue(`Starting worker '${worker.name}'...`));
314
+ await workerManager.start(worker.id);
315
+ console.log(chalk.green(`Worker '${worker.name}' started successfully!`));
316
+ const duration = Date.now() - startTime;
317
+ trackCommandSuccess('worker-start', duration, {
318
+ workerId: worker.id,
319
+ workerName: worker.name,
320
+ });
321
+ }
322
+ catch (error) {
323
+ const duration = Date.now() - startTime;
324
+ trackCommandError('worker-start', error, duration, {
325
+ nameOrId,
326
+ });
327
+ console.error(chalk.red('Failed to start worker:'), error);
328
+ }
329
+ });
330
+ }
331
+ /**
332
+ * Register the stop command
333
+ */
334
+ export function registerStopCommand(workerCommand) {
335
+ workerCommand
336
+ .command('stop <nameOrId>')
337
+ .description('Stop a worker')
338
+ .action(async (nameOrId) => {
339
+ const startTime = Date.now();
340
+ try {
341
+ trackCommand('worker-stop', {
342
+ nameOrId,
343
+ });
344
+ // Create worker manager
345
+ const workerManager = new TonkWorkerManager();
346
+ // Get worker
347
+ const worker = await workerManager.findByNameOrId(nameOrId);
348
+ if (!worker) {
349
+ console.error(chalk.red(`Worker with name or ID '${nameOrId}' not found.`));
350
+ const duration = Date.now() - startTime;
351
+ trackCommandError('worker-stop', new Error('Worker not found'), duration, {
352
+ nameOrId,
353
+ });
354
+ return;
355
+ }
356
+ console.log(chalk.blue(`Stopping worker '${worker.name}'...`));
357
+ await workerManager.stop(worker.id);
358
+ console.log(chalk.green(`Worker '${worker.name}' stopped successfully!`));
359
+ const duration = Date.now() - startTime;
360
+ trackCommandSuccess('worker-stop', duration, {
361
+ workerId: worker.id,
362
+ workerName: worker.name,
363
+ });
364
+ }
365
+ catch (error) {
366
+ const duration = Date.now() - startTime;
367
+ trackCommandError('worker-stop', error, duration, {
368
+ nameOrId,
369
+ });
370
+ console.error(chalk.red('Failed to stop worker:'), error);
371
+ }
372
+ });
373
+ }
374
+ /**
375
+ * Register the logs command
376
+ */
377
+ export function registerLogsCommand(workerCommand) {
378
+ workerCommand
379
+ .command('logs <nameOrId>')
380
+ .description('View logs for a worker')
381
+ .option('-f, --follow', 'Follow log output')
382
+ .option('-l, --lines <n>', 'Number of lines to show', '100')
383
+ .option('-e, --error', 'Show only error logs')
384
+ .option('-o, --out', 'Show only standard output logs')
385
+ .action(async (nameOrId, options) => {
386
+ const startTime = Date.now();
387
+ try {
388
+ trackCommand('worker-logs', {
389
+ nameOrId,
390
+ follow: options.follow,
391
+ lines: options.lines,
392
+ error: options.error,
393
+ out: options.out,
394
+ });
395
+ // Create worker manager
396
+ const workerManager = new TonkWorkerManager();
397
+ // Get worker
398
+ const worker = await workerManager.findByNameOrId(nameOrId);
399
+ if (!worker) {
400
+ console.error(chalk.red(`Worker with name or ID '${nameOrId}' not found.`));
401
+ const duration = Date.now() - startTime;
402
+ trackCommandError('worker-logs', new Error('Worker not found'), duration, {
403
+ nameOrId,
404
+ });
405
+ return;
406
+ }
407
+ console.log(chalk.blue(`Fetching logs for worker '${worker.name}'...`));
408
+ const { spawn } = await import('node:child_process');
409
+ try {
410
+ // Build the PM2 logs command arguments
411
+ const args = ['logs', worker.id];
412
+ // Add options
413
+ if (options.lines) {
414
+ args.push('--lines', options.lines);
415
+ }
416
+ if (options.error) {
417
+ args.push('--err');
418
+ }
419
+ else if (options.out) {
420
+ args.push('--out');
421
+ }
422
+ // Always use --raw to get cleaner output
423
+ args.push('--raw');
424
+ // For non-follow mode, we'll use --lines and then kill the process after a short delay
425
+ if (!options.follow) {
426
+ console.log(chalk.blue(`Showing last ${options.lines || '100'} lines of logs...`));
427
+ const child = spawn('pm2', args, {
428
+ stdio: 'inherit',
429
+ });
430
+ // Kill the process after a short delay to get just the initial output
431
+ setTimeout(() => {
432
+ child.kill('SIGINT');
433
+ }, 1000);
434
+ // Wait for the child process to exit
435
+ await new Promise(resolve => {
436
+ child.on('exit', resolve);
437
+ });
438
+ console.log(chalk.blue('\nUse -f or --follow to stream logs in real-time'));
439
+ }
440
+ else {
441
+ // For follow mode
442
+ console.log(chalk.blue('Streaming logs in real-time. Press Ctrl+C to exit.'));
443
+ const child = spawn('pm2', args, {
444
+ stdio: 'inherit',
445
+ });
446
+ // Handle process exit
447
+ process.on('SIGINT', () => {
448
+ child.kill();
449
+ process.exit(0);
450
+ });
451
+ // Wait for the child process to exit
452
+ await new Promise(resolve => {
453
+ child.on('exit', resolve);
454
+ });
455
+ }
456
+ const duration = Date.now() - startTime;
457
+ trackCommandSuccess('worker-logs', duration, {
458
+ workerId: worker.id,
459
+ workerName: worker.name,
460
+ follow: options.follow,
461
+ lines: options.lines,
462
+ logType: options.error ? 'error' : options.out ? 'out' : 'all',
463
+ });
464
+ }
465
+ catch (error) {
466
+ const duration = Date.now() - startTime;
467
+ trackCommandError('worker-logs', error, duration, {
468
+ workerId: worker.id,
469
+ workerName: worker.name,
470
+ options,
471
+ });
472
+ console.error(chalk.red('Failed to fetch logs:'), error);
473
+ }
474
+ }
475
+ catch (error) {
476
+ const duration = Date.now() - startTime;
477
+ trackCommandError('worker-logs', error, duration, {
478
+ nameOrId,
479
+ options,
480
+ });
481
+ console.error(chalk.red('Failed to fetch worker logs:'), error);
482
+ }
483
+ });
484
+ }
485
+ /**
486
+ * Register the register command
487
+ */
488
+ export function registerRegisterCommand(workerCommand) {
489
+ workerCommand
490
+ .command('register')
491
+ .description('Register a worker with Tonk')
492
+ .argument('[dir]', 'Path to worker directory (defaults to current directory)')
493
+ .option('-n, --name <n>', 'Name of the worker')
494
+ .option('-e, --endpoint <endpoint>', 'Endpoint URL of the worker')
495
+ .option('-p, --port <port>', 'Port number for the worker')
496
+ .option('-d, --description <description>', 'Description of the worker')
497
+ .action(async (dir = '.', options) => {
498
+ const startTime = Date.now();
499
+ try {
500
+ trackCommand('worker-register', {
501
+ hasDir: !!dir && dir !== '.',
502
+ hasName: !!options.name,
503
+ hasEndpoint: !!options.endpoint,
504
+ hasPort: !!options.port,
505
+ hasDescription: !!options.description,
506
+ });
507
+ // Create worker manager
508
+ const workerManager = new TonkWorkerManager();
509
+ // If options are provided, use them directly
510
+ if (options.name && options.endpoint) {
511
+ // Register worker using provided options
512
+ const worker = await workerManager.register({
513
+ name: options.name,
514
+ description: options.description || `Worker at ${options.endpoint}`,
515
+ endpoint: options.endpoint,
516
+ protocol: 'http',
517
+ env: options.port ? [`WORKER_PORT=${options.port}`] : [],
518
+ });
519
+ console.log(chalk.green(`Worker '${worker.name}' registered successfully!`));
520
+ console.log(chalk.cyan('Worker ID:'), chalk.bold(worker.id));
521
+ console.log(chalk.cyan('Endpoint:'), chalk.bold(worker.endpoint));
522
+ console.log(chalk.cyan('Protocol:'), chalk.bold(worker.protocol));
523
+ console.log(chalk.cyan('Status:'), worker.status.active
524
+ ? chalk.green('Active')
525
+ : chalk.yellow('Inactive'));
526
+ if (Object.keys(worker.env).length > 0) {
527
+ console.log(chalk.cyan('Environment Variables:'));
528
+ Object.entries(worker.env).forEach(([key, value]) => {
529
+ console.log(` ${key}=${value}`);
530
+ });
531
+ }
532
+ const duration = Date.now() - startTime;
533
+ trackCommandSuccess('worker-register', duration, {
534
+ workerId: worker.id,
535
+ workerName: worker.name,
536
+ registrationType: 'direct',
537
+ hasCustomPort: !!options.port,
538
+ });
539
+ return;
540
+ }
541
+ // Look for package.json and worker.config.js files
542
+ const workerDir = await findWorkerRoot(dir);
543
+ if (!workerDir) {
544
+ console.error(chalk.red(`No package.json or worker.config.js found in ${path.resolve(dir)} or its parent directories.`));
545
+ console.log(chalk.yellow('Make sure you are in a valid worker directory or specify the path to one, or provide --name and --endpoint options.'));
546
+ const duration = Date.now() - startTime;
547
+ trackCommandError('worker-register', new Error('Worker directory not found'), duration, {
548
+ dir: path.resolve(dir),
549
+ });
550
+ return;
551
+ }
552
+ console.log(chalk.blue(`Found worker directory at: ${workerDir}`));
553
+ // Read package.json for worker metadata
554
+ const packageJsonPath = path.join(workerDir, 'package.json');
555
+ const packageJson = await readPackageJson(packageJsonPath);
556
+ if (!packageJson) {
557
+ console.error(chalk.red(`Failed to parse package.json file at ${packageJsonPath}`));
558
+ const duration = Date.now() - startTime;
559
+ trackCommandError('worker-register', new Error('Failed to parse package.json'), duration, {
560
+ packageJsonPath,
561
+ });
562
+ return;
563
+ }
564
+ // Read worker.config.js for configuration
565
+ const workerConfigPath = path.join(workerDir, 'worker.config.js');
566
+ const workerConfig = await readWorkerConfigJs(workerConfigPath);
567
+ if (!workerConfig) {
568
+ console.error(chalk.red(`Failed to parse worker.config.js file at ${workerConfigPath}`));
569
+ const duration = Date.now() - startTime;
570
+ trackCommandError('worker-register', new Error('Failed to parse worker.config.js'), duration, {
571
+ workerConfigPath,
572
+ });
573
+ return;
574
+ }
575
+ // Determine port from config or use default
576
+ const port = options.port || workerConfig.runtime?.port || 5555;
577
+ // Create worker registration options
578
+ const registrationOptions = {
579
+ name: packageJson.name,
580
+ description: packageJson.description || 'Tonk worker',
581
+ endpoint: options.endpoint || `http://localhost:${port}/tonk`,
582
+ protocol: 'http',
583
+ env: [`WORKER_PORT=${port}`],
584
+ config: {
585
+ ...workerConfig,
586
+ version: packageJson.version,
587
+ },
588
+ };
589
+ // Register worker
590
+ const worker = await workerManager.register(registrationOptions);
591
+ console.log(chalk.green(`Worker '${worker.name}' registered successfully!`));
592
+ console.log(chalk.cyan('Worker ID:'), chalk.bold(worker.id));
593
+ console.log(chalk.cyan('Endpoint:'), chalk.bold(worker.endpoint));
594
+ console.log(chalk.cyan('Protocol:'), chalk.bold(worker.protocol));
595
+ console.log(chalk.cyan('Status:'), worker.status.active
596
+ ? chalk.green('Active')
597
+ : chalk.yellow('Inactive'));
598
+ if (Object.keys(worker.env).length > 0) {
599
+ console.log(chalk.cyan('Environment Variables:'));
600
+ Object.entries(worker.env).forEach(([key, value]) => {
601
+ console.log(` ${key}=${value}`);
602
+ });
603
+ }
604
+ const duration = Date.now() - startTime;
605
+ trackCommandSuccess('worker-register', duration, {
606
+ workerId: worker.id,
607
+ workerName: worker.name,
608
+ registrationType: 'config-based',
609
+ workerDir,
610
+ port,
611
+ hasCustomEndpoint: !!options.endpoint,
612
+ });
613
+ }
614
+ catch (error) {
615
+ const duration = Date.now() - startTime;
616
+ trackCommandError('worker-register', error, duration, {
617
+ dir,
618
+ options,
619
+ });
620
+ console.error(chalk.red('Failed to register worker:'), error);
621
+ }
622
+ });
623
+ }
624
+ /**
625
+ * Register the install command
626
+ */
627
+ export function registerInstallCommand(workerCommand) {
628
+ workerCommand
629
+ .command('install <package>')
630
+ .description('Install and start a worker from npm')
631
+ .option('-p, --port <port>', 'Specify a port for the worker (default: auto-detect)')
632
+ .option('-n, --name <n>', 'Custom name for the worker (default: npm package name)')
633
+ .action(async (packageName, options) => {
634
+ const startTime = Date.now();
635
+ try {
636
+ trackCommand('worker-install', {
637
+ packageName,
638
+ hasCustomPort: !!options.port,
639
+ hasCustomName: !!options.name,
640
+ });
641
+ console.log(chalk.blue(`Installing worker from npm package: ${packageName}...`));
642
+ // Create worker manager
643
+ const workerManager = new TonkWorkerManager();
644
+ // Execute npm install
645
+ const execAsync = promisify(exec);
646
+ try {
647
+ console.log(chalk.blue('Installing package...'));
648
+ await execAsync(`npm install -g ${packageName}`);
649
+ console.log(chalk.green('Package installed successfully!'));
650
+ }
651
+ catch (error) {
652
+ console.error(chalk.red('Failed to install package:'), error);
653
+ const duration = Date.now() - startTime;
654
+ trackCommandError('worker-install', error, duration, {
655
+ packageName,
656
+ stage: 'npm-install',
657
+ });
658
+ return;
659
+ }
660
+ // Find an available port starting from 5555
661
+ let port = options.port;
662
+ if (!port) {
663
+ port = await findAvailablePort(5555);
664
+ console.log(chalk.blue(`Found available port: ${port}`));
665
+ }
666
+ // Get package info to determine the worker name and entry point
667
+ let packageInfo;
668
+ try {
669
+ const { stdout } = await execAsync(`npm view ${packageName} --json`);
670
+ packageInfo = JSON.parse(stdout);
671
+ }
672
+ catch (error) {
673
+ console.error(chalk.red('Failed to get package info:'), error);
674
+ const duration = Date.now() - startTime;
675
+ trackCommandError('worker-install', error, duration, {
676
+ packageName,
677
+ stage: 'package-info',
678
+ });
679
+ return;
680
+ }
681
+ // Strip scope from package name for display purposes (e.g., @tonk/worker -> worker)
682
+ const commandName = options.name || packageName.replace(/^@[^/]+\//, '');
683
+ // Verify the package can be executed directly
684
+ let setupCompleted = false;
685
+ try {
686
+ // Check if the command exists by running a simple help command
687
+ await execAsync(`${commandName} --help`);
688
+ console.log(chalk.green(`Verified that '${packageName}' is executable.`));
689
+ // Try running the setup command if it exists
690
+ try {
691
+ console.log(chalk.blue(`Running setup for '${packageName}'...`));
692
+ // Use spawn to allow user interaction during setup
693
+ const { spawn } = await import('node:child_process');
694
+ const setupProcess = spawn(commandName, ['setup'], {
695
+ stdio: 'inherit',
696
+ shell: true,
697
+ });
698
+ // Wait for the setup process to complete
699
+ setupCompleted = await new Promise((resolve, reject) => {
700
+ setupProcess.on('close', code => {
701
+ if (code === 0) {
702
+ console.log(chalk.green(`Setup completed successfully for '${packageName}'`));
703
+ resolve(true);
704
+ }
705
+ else {
706
+ console.warn(chalk.yellow(`Setup command exited with code ${code}`));
707
+ resolve(false);
708
+ }
709
+ });
710
+ setupProcess.on('error', err => {
711
+ reject(err);
712
+ });
713
+ });
714
+ }
715
+ catch (setupError) {
716
+ console.log(chalk.yellow(`Setup command not available for '${packageName}', continuing with installation...`));
717
+ }
718
+ }
719
+ catch (error) {
720
+ const errorMessage = error instanceof Error ? error.message : String(error);
721
+ console.warn(chalk.yellow(`Warning: Could not verify that '${packageName}' is executable. It may not be properly installed or may not provide a CLI.`));
722
+ console.warn(chalk.yellow(`Error: ${errorMessage}`));
723
+ }
724
+ // Register the worker with the npm package name
725
+ const worker = await workerManager.register({
726
+ name: packageName, // Use the actual package name for npm resolution
727
+ description: packageInfo.description || `Worker from npm package ${packageName}`,
728
+ endpoint: `http://localhost:${port}/tonk`,
729
+ protocol: 'http',
730
+ env: [`WORKER_PORT=${port}`],
731
+ type: 'npm',
732
+ });
733
+ console.log(chalk.green(`Worker '${packageName}' registered successfully!`));
734
+ // Start the worker
735
+ console.log(chalk.blue(`Starting worker '${packageName}'...`));
736
+ const success = await workerManager.start(worker.id);
737
+ if (success) {
738
+ console.log(chalk.green(`Worker '${packageName}' started successfully!`));
739
+ console.log(chalk.cyan('Worker ID:'), chalk.bold(worker.id));
740
+ console.log(chalk.cyan('Endpoint:'), chalk.bold(worker.endpoint));
741
+ const duration = Date.now() - startTime;
742
+ trackCommandSuccess('worker-install', duration, {
743
+ workerId: worker.id,
744
+ workerName: worker.name,
745
+ packageName,
746
+ port,
747
+ setupCompleted,
748
+ startedSuccessfully: true,
749
+ });
750
+ }
751
+ else {
752
+ console.error(chalk.red(`Failed to start worker '${packageName}'.`));
753
+ const duration = Date.now() - startTime;
754
+ trackCommandError('worker-install', new Error('Failed to start worker'), duration, {
755
+ workerId: worker.id,
756
+ packageName,
757
+ stage: 'worker-start',
758
+ });
759
+ }
760
+ }
761
+ catch (error) {
762
+ const duration = Date.now() - startTime;
763
+ trackCommandError('worker-install', error, duration, {
764
+ packageName,
765
+ options,
766
+ });
767
+ console.error(chalk.red('Failed to install worker:'), error);
768
+ }
769
+ });
770
+ }
771
+ /**
772
+ * Register the init command
773
+ */
774
+ export function registerInitCommand(workerCommand) {
775
+ workerCommand
776
+ .command('init')
777
+ .description('Initialise a new worker configuration file')
778
+ .option('-d, --dir <directory>', 'Directory to create the configuration file in', '.')
779
+ .option('-n, --name <n>', 'Name of the worker')
780
+ .option('-p, --port <port>', 'Port number for the worker', '5555')
781
+ .option('-D, --description <description>', 'Description of the worker')
782
+ .action(async (options) => {
783
+ const startTime = Date.now();
784
+ try {
785
+ trackCommand('worker-init', {
786
+ hasCustomDir: options.dir !== '.',
787
+ hasName: !!options.name,
788
+ hasCustomPort: options.port !== '5555',
789
+ hasDescription: !!options.description,
790
+ });
791
+ // Prompt for missing options
792
+ const answers = await promptForMissingOptions(options);
793
+ const mergedOptions = { ...options, ...answers };
794
+ // Create the directory if it doesn't exist
795
+ const targetDir = path.resolve(mergedOptions.dir);
796
+ let directoryCreated = false;
797
+ if (!fs.existsSync(targetDir)) {
798
+ fs.mkdirSync(targetDir, { recursive: true });
799
+ console.log(chalk.blue(`Created directory: ${targetDir}`));
800
+ directoryCreated = true;
801
+ }
802
+ // Generate the package.json content if it doesn't exist
803
+ const packageJsonPath = path.join(targetDir, 'package.json');
804
+ let packageJsonCreated = false;
805
+ if (!fs.existsSync(packageJsonPath)) {
806
+ const packageJsonContent = generatePackageJsonContent(mergedOptions);
807
+ fs.writeFileSync(packageJsonPath, packageJsonContent);
808
+ console.log(chalk.green(`Created package.json at: ${packageJsonPath}`));
809
+ packageJsonCreated = true;
810
+ }
811
+ // Generate the worker.config.js content
812
+ const workerConfigContent = generateWorkerConfigJsContent();
813
+ const workerConfigPath = path.join(targetDir, 'worker.config.js');
814
+ fs.writeFileSync(workerConfigPath, workerConfigContent);
815
+ // Create src directory if it doesn't exist
816
+ const srcDir = path.join(targetDir, 'src');
817
+ let srcDirCreated = false;
818
+ if (!fs.existsSync(srcDir)) {
819
+ fs.mkdirSync(srcDir, { recursive: true });
820
+ console.log(chalk.blue(`Created src directory: ${srcDir}`));
821
+ srcDirCreated = true;
822
+ }
823
+ // Copy CLI template file
824
+ const cliTemplatePath = path.join(__dirname, 'templates', 'cli.ts.template');
825
+ const cliDestPath = path.join(srcDir, 'cli.ts');
826
+ let cliFileCreated = false;
827
+ if (fs.existsSync(cliTemplatePath)) {
828
+ let cliContent = fs.readFileSync(cliTemplatePath, 'utf-8');
829
+ // Replace template variables
830
+ cliContent = cliContent
831
+ .replace(/{{name}}/g, mergedOptions.name)
832
+ .replace(/{{description}}/g, mergedOptions.description || 'A Tonk worker')
833
+ .replace(/{{version}}/g, '1.0.0');
834
+ fs.writeFileSync(cliDestPath, cliContent);
835
+ console.log(chalk.green(`Created CLI file at: ${cliDestPath}`));
836
+ cliFileCreated = true;
837
+ }
838
+ else {
839
+ console.warn(chalk.yellow(`CLI template not found at: ${cliTemplatePath}`));
840
+ }
841
+ // Copy index.ts template file
842
+ const indexTemplatePath = path.join(__dirname, 'templates', 'index.ts.template');
843
+ const indexDestPath = path.join(srcDir, 'index.ts');
844
+ let indexFileCreated = false;
845
+ if (!fs.existsSync(indexDestPath) && fs.existsSync(indexTemplatePath)) {
846
+ let indexContent = fs.readFileSync(indexTemplatePath, 'utf-8');
847
+ // Replace template variables
848
+ indexContent = indexContent
849
+ .replace(/{{name}}/g, mergedOptions.name)
850
+ .replace(/{{description}}/g, mergedOptions.description || 'A Tonk worker')
851
+ .replace(/{{port}}/g, mergedOptions.port);
852
+ fs.writeFileSync(indexDestPath, indexContent);
853
+ console.log(chalk.green(`Created index.ts file at: ${indexDestPath}`));
854
+ indexFileCreated = true;
855
+ }
856
+ else if (!fs.existsSync(indexTemplatePath)) {
857
+ console.warn(chalk.yellow(`Index template not found at: ${indexTemplatePath}`));
858
+ }
859
+ // Copy tsconfig.json template file
860
+ const tsconfigTemplatePath = path.join(__dirname, 'templates', 'tsconfig.json.template');
861
+ const tsconfigDestPath = path.join(targetDir, 'tsconfig.json');
862
+ let tsconfigFileCreated = false;
863
+ if (!fs.existsSync(tsconfigDestPath) &&
864
+ fs.existsSync(tsconfigTemplatePath)) {
865
+ const tsconfigContent = fs.readFileSync(tsconfigTemplatePath, 'utf-8');
866
+ fs.writeFileSync(tsconfigDestPath, tsconfigContent);
867
+ console.log(chalk.green(`Created tsconfig.json file at: ${tsconfigDestPath}`));
868
+ tsconfigFileCreated = true;
869
+ }
870
+ else if (!fs.existsSync(tsconfigTemplatePath)) {
871
+ console.warn(chalk.yellow(`tsconfig.json template not found at: ${tsconfigTemplatePath}`));
872
+ }
873
+ console.log(chalk.green(`Worker configuration file created at: ${workerConfigPath}`));
874
+ console.log(chalk.blue(`\nYou can now register this worker with:`));
875
+ console.log(chalk.cyan(` tonk worker register ${targetDir}`));
876
+ const duration = Date.now() - startTime;
877
+ trackCommandSuccess('worker-init', duration, {
878
+ workerName: mergedOptions.name,
879
+ targetDir,
880
+ port: mergedOptions.port,
881
+ directoryCreated,
882
+ packageJsonCreated,
883
+ srcDirCreated,
884
+ cliFileCreated,
885
+ indexFileCreated,
886
+ tsconfigFileCreated,
887
+ filesCreated: [
888
+ packageJsonCreated && 'package.json',
889
+ 'worker.config.js',
890
+ srcDirCreated && 'src/',
891
+ cliFileCreated && 'cli.ts',
892
+ indexFileCreated && 'index.ts',
893
+ tsconfigFileCreated && 'tsconfig.json',
894
+ ].filter(Boolean).length,
895
+ });
896
+ }
897
+ catch (error) {
898
+ const duration = Date.now() - startTime;
899
+ trackCommandError('worker-init', error, duration, {
900
+ options,
901
+ });
902
+ console.error(chalk.red('Failed to initialise worker configuration:'), error);
903
+ }
904
+ });
905
+ }
906
+ //# sourceMappingURL=commands.js.map