labelinn 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/bin/cli.js ADDED
@@ -0,0 +1,793 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * LabelInn CLI — quick testing tool for the LabelInn API
6
+ *
7
+ * Usage:
8
+ * npx labelinn test # verify your API key
9
+ * npx labelinn printers # list printers
10
+ * npx labelinn printers <id> --status # get printer status
11
+ * npx labelinn print <printer_id> --zpl "^XA^FO50,50^ADN,36,20^FDHello^FS^XZ"
12
+ * npx labelinn print <printer_id> --image-url https://...
13
+ * npx labelinn print <printer_id> --design <design_id> --data '{"sku":"A1"}'
14
+ * npx labelinn jobs # list recent jobs
15
+ * npx labelinn jobs <id> # get job detail
16
+ * npx labelinn designs # list designs
17
+ * npx labelinn webhooks # list webhooks
18
+ */
19
+
20
+ const LabelInn = require('../lib/index.js');
21
+
22
+ const HELP = `
23
+ LabelInn CLI v1.1.0 — Developer testing tool
24
+
25
+ SETUP:
26
+ Set your API key as an environment variable:
27
+ export LABELINN_API_KEY=sk_test_xxxxx (Linux/Mac)
28
+ $env:LABELINN_API_KEY="sk_test_xxxxx" (PowerShell)
29
+
30
+ COMMANDS:
31
+ test Verify API key & connectivity
32
+
33
+ login Save API key for future commands
34
+ login --key <key> Save key non-interactively
35
+ login status Check saved credentials
36
+ login logout Remove saved credentials
37
+
38
+ printers List all printers
39
+ printers <id> Get printer details
40
+ printers <id> --status Get printer status only
41
+ printers <id> --identify Flash/beep the printer
42
+ printers <id> --update --name <n> Update printer name
43
+ printers <id> --media <json> Set media definition (JSON)
44
+
45
+ groups List printer groups
46
+ groups <id> Get group details
47
+ groups add --name <n> [--printers id1,id2] Create a group
48
+ groups rm <id> Delete a group
49
+
50
+ print <printer_id> [options] Submit a print job
51
+ --zpl <string> Raw ZPL payload
52
+ --image-url <url> HTTPS image URL
53
+ --design <design_id> Template design ID
54
+ --data <json> Template variables (JSON)
55
+ --copies <n> Number of copies
56
+ --name <name> Job name
57
+ --priority <p> low, normal, high, urgent
58
+ --schedule <iso> Schedule for later (ISO 8601)
59
+
60
+ jobs List recent print jobs
61
+ jobs <id> Get job details
62
+ jobs <id> --cancel Cancel a queued job
63
+ jobs <id> --retry Retry a failed job
64
+ jobs <id> --reprint Reprint a completed job
65
+ batch <file.json> Batch print from JSON file
66
+
67
+ designs List designs
68
+ designs <id> Get design details
69
+ designs <id> --revisions List revision history
70
+
71
+ webhooks List webhooks
72
+ webhooks add <url> [events...] Subscribe to events
73
+ webhooks rm <id> Delete a webhook
74
+ webhooks test <id> Send test ping
75
+ webhooks update <id> [--events e1,e2] [--enabled true|false]
76
+ webhooks deliveries <id> List delivery history
77
+
78
+ connect Data Connect commands
79
+ connect sources List connector sources
80
+ connect sources <id> Get source details
81
+ connect sources create --name <n> [--format csv|json|xml]
82
+ connect sources rm <id> Delete a source
83
+ connect ingest <source_id> --file <path> Ingest data from file
84
+ connect ingest <source_id> --data <json> Ingest inline JSON data
85
+ connect test-parse --file <path> Parse without storing
86
+ connect schema <source_id> Get detected schema
87
+ connect records <source_id> List ingested records
88
+ connect mappings <source_id> Get field mappings
89
+ connect print <source_id> --design <id> --printer <id> Print from connector
90
+
91
+ OPTIONS:
92
+ --key <key> API key (overrides LABELINN_API_KEY)
93
+ --base-url <url> Override base URL
94
+ --json Output raw JSON
95
+ -h, --help Show this help
96
+
97
+ EXAMPLES:
98
+ npx labelinn test
99
+ npx labelinn print prt_abc123 --zpl "^XA^FO50,50^ADN,36,20^FDHello^FS^XZ"
100
+ npx labelinn print prt_abc123 --design dsg_ship --data '{"name":"Ali","barcode":"TR12345"}' --priority high
101
+ npx labelinn printers prt_abc123 --media '{"width_mm":100,"height_mm":50}'
102
+ npx labelinn printers prt_abc123 --identify
103
+ npx labelinn groups add --name "Warehouse" --printers prt_1,prt_2
104
+ npx labelinn jobs --json
105
+ `.trim();
106
+
107
+ // ── Arg Parsing ──
108
+
109
+ function parseArgs(argv) {
110
+ const args = { _: [], flags: {} };
111
+ let i = 0;
112
+ while (i < argv.length) {
113
+ const a = argv[i];
114
+ if (a === '-h' || a === '--help') {
115
+ args.flags.help = true;
116
+ } else if (a === '--json') {
117
+ args.flags.json = true;
118
+ } else if (a === '--status') {
119
+ args.flags.status = true;
120
+ } else if (a === '--cancel') {
121
+ args.flags.cancel = true;
122
+ } else if (a === '--identify') {
123
+ args.flags.identify = true;
124
+ } else if (a === '--update') {
125
+ args.flags.update = true;
126
+ } else if (a === '--retry') {
127
+ args.flags.retry = true;
128
+ } else if (a === '--reprint') {
129
+ args.flags.reprint = true;
130
+ } else if (a === '--revisions') {
131
+ args.flags.revisions = true;
132
+ } else if (a.startsWith('--') && i + 1 < argv.length) {
133
+ const key = a.slice(2);
134
+ args.flags[key] = argv[++i];
135
+ } else {
136
+ args._.push(a);
137
+ }
138
+ i++;
139
+ }
140
+ return args;
141
+ }
142
+
143
+ function getClient(flags) {
144
+ const apiKey = flags.key || process.env.LABELINN_API_KEY || getStoredKey();
145
+ if (!apiKey) {
146
+ console.error('Error: No API key. Run "labelinn login", set LABELINN_API_KEY, or use --key <key>');
147
+ process.exit(1);
148
+ }
149
+ const opts = {};
150
+ if (flags['base-url']) opts.baseUrl = flags['base-url'];
151
+ return new LabelInn(apiKey, opts);
152
+ }
153
+
154
+ function output(data, flags) {
155
+ if (flags.json) {
156
+ console.log(JSON.stringify(data, null, 2));
157
+ } else {
158
+ // Clean internal keys for display
159
+ const clean = { ...data };
160
+ delete clean._rateLimit;
161
+ delete clean._idempotent;
162
+ console.log(JSON.stringify(clean, null, 2));
163
+ }
164
+ }
165
+
166
+ function getCredentialsPath() {
167
+ const home = process.env.HOME || process.env.USERPROFILE || '';
168
+ const path = require('path');
169
+ return path.join(home, '.labelinn', 'credentials.json');
170
+ }
171
+
172
+ function getStoredKey() {
173
+ const fs = require('fs');
174
+ const credPath = getCredentialsPath();
175
+ try {
176
+ const config = JSON.parse(fs.readFileSync(credPath, 'utf8'));
177
+ return config.api_key || '';
178
+ } catch {
179
+ return '';
180
+ }
181
+ }
182
+
183
+ async function cmdLogin(args, flags) {
184
+ const fs = require('fs');
185
+ const path = require('path');
186
+ const readline = require('readline');
187
+ const credPath = getCredentialsPath();
188
+ const credDir = path.dirname(credPath);
189
+
190
+ const subCmd = args._[1];
191
+
192
+ if (subCmd === 'status') {
193
+ try {
194
+ const config = JSON.parse(fs.readFileSync(credPath, 'utf8'));
195
+ const key = config.api_key || '';
196
+ const prefix = key.length > 15 ? key.substring(0, 15) + '...' : key;
197
+ const mode = key.startsWith('sk_test_') ? 'TEST' : 'LIVE';
198
+ console.log(`Logged in: ${prefix}`);
199
+ console.log(` Mode: ${mode}`);
200
+ console.log(` Config: ${credPath}`);
201
+ } catch {
202
+ console.log('Not logged in. Run: labelinn login');
203
+ }
204
+ return;
205
+ }
206
+
207
+ if (subCmd === 'logout') {
208
+ try {
209
+ fs.unlinkSync(credPath);
210
+ console.log('Logged out. Credentials removed.');
211
+ } catch {
212
+ console.log('Not logged in.');
213
+ }
214
+ return;
215
+ }
216
+
217
+ let apiKey = flags.key;
218
+ if (!apiKey) {
219
+ console.log('Enter your LabelInn API key.');
220
+ console.log(' Get one at: https://labelinn.com → Settings → API Keys');
221
+ console.log();
222
+
223
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
224
+ apiKey = await new Promise((resolve) => {
225
+ rl.question('API Key: ', (answer) => {
226
+ rl.close();
227
+ resolve(answer.trim());
228
+ });
229
+ });
230
+ }
231
+
232
+ if (!apiKey) {
233
+ console.error('Error: No API key provided.');
234
+ process.exit(1);
235
+ }
236
+ if (!apiKey.startsWith('sk_live_') && !apiKey.startsWith('sk_test_')) {
237
+ console.error('Error: API key must start with sk_live_ or sk_test_');
238
+ process.exit(1);
239
+ }
240
+
241
+ // Test the key
242
+ console.log('Verifying key...');
243
+ const client = new LabelInn(apiKey);
244
+ const result = await client.fleet.list();
245
+ const printers = result.printers || result;
246
+ const count = Array.isArray(printers) ? printers.length : 0;
247
+
248
+ // Save credentials
249
+ fs.mkdirSync(credDir, { recursive: true });
250
+ fs.writeFileSync(credPath, JSON.stringify({ api_key: apiKey }, null, 2), 'utf8');
251
+
252
+ const mode = apiKey.startsWith('sk_test_') ? 'TEST (sandbox)' : 'LIVE';
253
+ console.log(`✓ Logged in! Mode: ${mode}, ${count} printer(s) found.`);
254
+ console.log(` Credentials saved to ${credPath}`);
255
+ console.log(' You can now run commands without --key flag.');
256
+ }
257
+
258
+ function table(rows, columns) {
259
+ if (!rows || rows.length === 0) {
260
+ console.log('(none)');
261
+ return;
262
+ }
263
+ // Simple table output
264
+ const widths = {};
265
+ for (const col of columns) {
266
+ widths[col] = col.length;
267
+ for (const row of rows) {
268
+ const val = String(row[col] ?? '');
269
+ widths[col] = Math.max(widths[col], val.length);
270
+ }
271
+ }
272
+ const header = columns.map(c => c.toUpperCase().padEnd(widths[c])).join(' ');
273
+ console.log(header);
274
+ console.log(columns.map(c => '─'.repeat(widths[c])).join(' '));
275
+ for (const row of rows) {
276
+ console.log(columns.map(c => String(row[c] ?? '').padEnd(widths[c])).join(' '));
277
+ }
278
+ }
279
+
280
+ // ── Commands ──
281
+
282
+ async function cmdTest(client, flags) {
283
+ console.log(`Mode: ${client.isTestMode ? 'TEST (sandbox)' : 'LIVE'}`);
284
+ console.log('Checking connectivity...');
285
+
286
+ try {
287
+ const result = await client.fleet.list();
288
+ const printers = result.printers || result;
289
+ const count = Array.isArray(printers) ? printers.length : 0;
290
+ console.log(`✓ Connected! Found ${count} printer(s).`);
291
+ if (result._rateLimit) {
292
+ console.log(` Rate limit: ${result._rateLimit.remaining}/${result._rateLimit.limit} remaining`);
293
+ }
294
+ } catch (err) {
295
+ console.error(`✗ Failed: ${err.message}`);
296
+ if (err.code) console.error(` Code: ${err.code}`);
297
+ process.exit(1);
298
+ }
299
+ }
300
+
301
+ async function cmdPrinters(client, args, flags) {
302
+ const printerId = args._[1];
303
+ if (printerId) {
304
+ if (flags.status) {
305
+ const result = await client.fleet.status(printerId);
306
+ output(result, flags);
307
+ } else if (flags.identify) {
308
+ await client.fleet.identify(printerId);
309
+ console.log(`✓ Identify command sent to printer ${printerId}`);
310
+ } else if (flags.media) {
311
+ let mediaParams;
312
+ try {
313
+ mediaParams = JSON.parse(flags.media);
314
+ } catch {
315
+ console.error('Error: --media must be valid JSON, e.g. \'{"width_mm":100,"height_mm":50}\'');
316
+ process.exit(1);
317
+ }
318
+ const result = await client.fleet.setMedia(printerId, mediaParams);
319
+ if (flags.json) {
320
+ output(result, flags);
321
+ } else {
322
+ console.log(`✓ Media definition updated on ${printerId}`);
323
+ }
324
+ } else if (flags.update) {
325
+ const params = {};
326
+ if (flags.name) params.name = flags.name;
327
+ const result = await client.fleet.update(printerId, params);
328
+ if (flags.json) {
329
+ output(result, flags);
330
+ } else {
331
+ console.log(`✓ Printer ${printerId} updated`);
332
+ }
333
+ } else {
334
+ const result = await client.fleet.get(printerId);
335
+ output(result, flags);
336
+ }
337
+ } else {
338
+ const result = await client.fleet.list();
339
+ const printers = result.printers || result;
340
+ if (flags.json) {
341
+ output(result, flags);
342
+ } else {
343
+ table(Array.isArray(printers) ? printers : [], ['id', 'name', 'status', 'model', 'connection']);
344
+ }
345
+ }
346
+ }
347
+
348
+ async function cmdGroups(client, args, flags) {
349
+ const subCmd = args._[1];
350
+ if (subCmd === 'add') {
351
+ const params = {};
352
+ if (flags.name) params.name = flags.name;
353
+ if (flags.printers) params.printer_ids = flags.printers.split(',');
354
+ if (flags.description) params.description = flags.description;
355
+ const result = await client.fleet.groups.create(params);
356
+ if (flags.json) {
357
+ output(result, flags);
358
+ } else {
359
+ console.log(`✓ Group created: ${result.id}`);
360
+ }
361
+ } else if (subCmd === 'rm') {
362
+ const id = args._[2];
363
+ if (!id) { console.error('Usage: labelinn groups rm <id>'); process.exit(1); }
364
+ await client.fleet.groups.delete(id);
365
+ console.log(`✓ Group ${id} deleted`);
366
+ } else if (subCmd) {
367
+ const result = await client.fleet.groups.get(subCmd);
368
+ output(result, flags);
369
+ } else {
370
+ const result = await client.fleet.groups.list();
371
+ const groups = result.groups || result;
372
+ if (flags.json) {
373
+ output(result, flags);
374
+ } else {
375
+ table(Array.isArray(groups) ? groups : [], ['id', 'name', 'printer_count']);
376
+ }
377
+ }
378
+ }
379
+
380
+ async function cmdPrint(client, args, flags) {
381
+ const printerId = args._[1];
382
+ if (!printerId) {
383
+ console.error('Usage: labelinn print <printer_id> [--zpl|--image-url|--design] ...');
384
+ process.exit(1);
385
+ }
386
+
387
+ const params = { printer_id: printerId };
388
+
389
+ if (flags.zpl) {
390
+ params.payload_type = 'zpl';
391
+ params.payload_data = flags.zpl;
392
+ } else if (flags['image-url']) {
393
+ params.payload_type = 'image';
394
+ params.image_url = flags['image-url'];
395
+ } else if (flags.design) {
396
+ params.payload_type = 'template';
397
+ params.design_id = flags.design;
398
+ if (flags.data) {
399
+ try {
400
+ params.data = JSON.parse(flags.data);
401
+ } catch {
402
+ console.error('Error: --data must be valid JSON');
403
+ process.exit(1);
404
+ }
405
+ }
406
+ } else {
407
+ console.error('Error: Specify --zpl, --image-url, or --design');
408
+ process.exit(1);
409
+ }
410
+
411
+ if (flags.copies) params.copies = parseInt(flags.copies, 10);
412
+ if (flags.name) params.job_name = flags.name;
413
+ if (flags.priority) params.priority = flags.priority;
414
+ if (flags.schedule) params.print_at = flags.schedule;
415
+
416
+ const opts = {};
417
+ if (flags['idempotency-key']) opts.idempotencyKey = flags['idempotency-key'];
418
+
419
+ const job = await client.print.create(params, opts);
420
+ if (flags.json) {
421
+ output(job, flags);
422
+ } else {
423
+ console.log(`✓ Print job submitted`);
424
+ console.log(` Job ID: ${job.id}`);
425
+ console.log(` Status: ${job.status}`);
426
+ if (job._idempotent) console.log(' (Idempotent — duplicate request)');
427
+ }
428
+ }
429
+
430
+ async function cmdJobs(client, args, flags) {
431
+ const jobId = args._[1];
432
+ if (jobId) {
433
+ if (flags.cancel) {
434
+ await client.print.cancel(jobId);
435
+ console.log(`✓ Job ${jobId} cancelled`);
436
+ } else if (flags.retry) {
437
+ const result = await client.print.retry(jobId);
438
+ if (flags.json) {
439
+ output(result, flags);
440
+ } else {
441
+ console.log(`✓ Retry job created: ${result.id}`);
442
+ }
443
+ } else if (flags.reprint) {
444
+ const overrides = {};
445
+ if (flags.copies) overrides.copies = parseInt(flags.copies, 10);
446
+ const result = await client.print.reprint(jobId, overrides);
447
+ if (flags.json) {
448
+ output(result, flags);
449
+ } else {
450
+ console.log(`✓ Reprint job created: ${result.id}`);
451
+ }
452
+ } else {
453
+ const result = await client.print.get(jobId);
454
+ output(result, flags);
455
+ }
456
+ } else {
457
+ const query = {};
458
+ if (flags.status) query.status = flags.status;
459
+ if (flags.printer) query.printer_id = flags.printer;
460
+ if (flags.limit) query.limit = parseInt(flags.limit, 10);
461
+
462
+ const result = await client.print.list(query);
463
+ const jobs = result.jobs || result;
464
+ if (flags.json) {
465
+ output(result, flags);
466
+ } else {
467
+ table(Array.isArray(jobs) ? jobs : [], ['id', 'status', 'printer_id', 'payload_type', 'created_at']);
468
+ }
469
+ }
470
+ }
471
+
472
+ async function cmdDesigns(client, args, flags) {
473
+ const designId = args._[1];
474
+ if (designId) {
475
+ if (flags.revisions) {
476
+ const result = await client.designs.revisions(designId);
477
+ output(result, flags);
478
+ } else {
479
+ const result = await client.designs.get(designId);
480
+ output(result, flags);
481
+ }
482
+ } else {
483
+ const result = await client.designs.list();
484
+ const designs = result.designs || result;
485
+ if (flags.json) {
486
+ output(result, flags);
487
+ } else {
488
+ table(Array.isArray(designs) ? designs : [], ['id', 'name', 'width_mm', 'height_mm']);
489
+ }
490
+ }
491
+ }
492
+
493
+ async function cmdBatch(client, args, flags) {
494
+ const filePath = args._[1];
495
+ if (!filePath) {
496
+ console.error('Usage: labelinn batch <file.json>');
497
+ console.error(' File should contain a JSON array of job objects, or { "jobs": [...] }');
498
+ process.exit(1);
499
+ }
500
+
501
+ const fs = require('fs');
502
+ let raw;
503
+ try {
504
+ raw = fs.readFileSync(filePath, 'utf8');
505
+ } catch (err) {
506
+ console.error(`Error: Cannot read file "${filePath}": ${err.message}`);
507
+ process.exit(1);
508
+ }
509
+
510
+ let jobs;
511
+ try {
512
+ const parsed = JSON.parse(raw);
513
+ jobs = Array.isArray(parsed) ? parsed : (parsed.jobs || parsed);
514
+ if (!Array.isArray(jobs)) throw new Error('Not an array');
515
+ } catch (err) {
516
+ console.error(`Error: File must contain a JSON array of jobs, or { "jobs": [...] }`);
517
+ process.exit(1);
518
+ }
519
+
520
+ if (jobs.length === 0) {
521
+ console.error('Error: Jobs array is empty');
522
+ process.exit(1);
523
+ }
524
+ if (jobs.length > 100) {
525
+ console.error(`Error: Max 100 jobs per batch (got ${jobs.length})`);
526
+ process.exit(1);
527
+ }
528
+
529
+ const result = await client.print.batch(jobs);
530
+ if (flags.json) {
531
+ output(result, flags);
532
+ } else {
533
+ console.log(`✓ Batch submitted: ${result.total_jobs || jobs.length} jobs`);
534
+ console.log(` Successful: ${result.successful ?? '?'}`);
535
+ console.log(` Failed: ${result.failed ?? '?'}`);
536
+ if (result.batch_id) console.log(` Batch ID: ${result.batch_id}`);
537
+ }
538
+ }
539
+
540
+ async function cmdConnect(client, args, flags) {
541
+ const subCmd = args._[1];
542
+
543
+ if (subCmd === 'sources') {
544
+ const sourceArg = args._[2];
545
+ if (sourceArg === 'create') {
546
+ if (!flags.name) { console.error('Usage: labelinn connect sources create --name <n> [--format csv|json|xml]'); process.exit(1); }
547
+ const params = { name: flags.name };
548
+ if (flags.format) params.format = flags.format;
549
+ if (flags.description) params.description = flags.description;
550
+ const result = await client.connect.createSource(params);
551
+ if (flags.json) { output(result, flags); }
552
+ else {
553
+ console.log(`✓ Source created`);
554
+ console.log(` ID: ${result.id}`);
555
+ console.log(` Name: ${result.name}`);
556
+ console.log(` Format: ${result.format || 'auto'}`);
557
+ }
558
+ } else if (sourceArg === 'rm') {
559
+ const id = args._[3];
560
+ if (!id) { console.error('Usage: labelinn connect sources rm <id>'); process.exit(1); }
561
+ await client.connect.deleteSource(id);
562
+ console.log(`✓ Source ${id} deleted`);
563
+ } else if (sourceArg) {
564
+ // Get specific source
565
+ const result = await client.connect.getSource(sourceArg);
566
+ if (flags.json) { output(result, flags); }
567
+ else {
568
+ console.log(`Source: ${result.name || result.id}`);
569
+ console.log(` ID: ${result.id}`);
570
+ console.log(` Format: ${result.format || '?'}`);
571
+ console.log(` Records: ${result.record_count ?? '?'}`);
572
+ console.log(` Active: ${result.is_active}`);
573
+ if (result.created_at) console.log(` Created: ${result.created_at}`);
574
+ }
575
+ } else {
576
+ // List sources
577
+ const result = await client.connect.listSources();
578
+ const sources = Array.isArray(result) ? result : (result.sources || []);
579
+ if (flags.json) { output(result, flags); }
580
+ else { table(sources, ['id', 'name', 'format', 'record_count', 'is_active']); }
581
+ }
582
+ } else if (subCmd === 'ingest') {
583
+ const sourceId = args._[2];
584
+ if (!sourceId) { console.error('Usage: labelinn connect ingest <source_id> --file <path> | --data <json>'); process.exit(1); }
585
+ let payload;
586
+ if (flags.file) {
587
+ const fs = require('fs');
588
+ payload = fs.readFileSync(flags.file, 'utf-8');
589
+ } else if (flags.data) {
590
+ payload = flags.data;
591
+ } else {
592
+ console.error('Provide --file <path> or --data <json>'); process.exit(1);
593
+ }
594
+ const params = { source_id: sourceId, payload };
595
+ if (flags.format) params.format = flags.format;
596
+ const result = await client.connect.ingest(params);
597
+ if (flags.json) { output(result, flags); }
598
+ else {
599
+ console.log(`✓ Ingested ${result.records_count ?? '?'} records into ${result.source_id}`);
600
+ console.log(` Format: ${result.format || '?'}`);
601
+ }
602
+ } else if (subCmd === 'test-parse') {
603
+ let payload;
604
+ if (flags.file) {
605
+ const fs = require('fs');
606
+ payload = fs.readFileSync(flags.file, 'utf-8');
607
+ } else if (flags.data) {
608
+ payload = flags.data;
609
+ } else {
610
+ console.error('Provide --file <path> or --data <json>'); process.exit(1);
611
+ }
612
+ const params = { payload };
613
+ if (flags.format) params.format = flags.format;
614
+ const result = await client.connect.testParse(params);
615
+ if (flags.json) { output(result, flags); }
616
+ else {
617
+ console.log(`Format: ${result.format}`);
618
+ console.log(`Records: ${(result.records || []).length}`);
619
+ if (result.schema) {
620
+ console.log('Schema:');
621
+ table(result.schema, ['path', 'type', 'sample']);
622
+ }
623
+ }
624
+ } else if (subCmd === 'schema') {
625
+ const sourceId = args._[2];
626
+ if (!sourceId) { console.error('Usage: labelinn connect schema <source_id>'); process.exit(1); }
627
+ const result = await client.connect.getSchema(sourceId);
628
+ if (flags.json) { output(result, flags); }
629
+ else {
630
+ const fields = result.fields || result.schema || [];
631
+ table(fields, ['path', 'type', 'sample']);
632
+ }
633
+ } else if (subCmd === 'records') {
634
+ const sourceId = args._[2];
635
+ if (!sourceId) { console.error('Usage: labelinn connect records <source_id>'); process.exit(1); }
636
+ const result = await client.connect.listRecords(sourceId, flags.limit ? parseInt(flags.limit) : undefined);
637
+ output(result, flags);
638
+ } else if (subCmd === 'mappings') {
639
+ const sourceId = args._[2];
640
+ if (!sourceId) { console.error('Usage: labelinn connect mappings <source_id>'); process.exit(1); }
641
+ const result = await client.connect.getMappings(sourceId);
642
+ output(result, flags);
643
+ } else if (subCmd === 'print') {
644
+ const sourceId = args._[2];
645
+ if (!sourceId || !flags.design || !flags.printer) {
646
+ console.error('Usage: labelinn connect print <source_id> --design <id> --printer <id> [--copies n]');
647
+ process.exit(1);
648
+ }
649
+ const params = { source_id: sourceId, design_id: flags.design, printer_id: flags.printer };
650
+ if (flags.copies) params.copies = parseInt(flags.copies);
651
+ const result = await client.connect.print(params);
652
+ if (flags.json) { output(result, flags); }
653
+ else {
654
+ console.log(`✓ Print jobs created: ${result.count ?? (result.job_ids || []).length}`);
655
+ if (result.job_ids) console.log(` Job IDs: ${result.job_ids.join(', ')}`);
656
+ }
657
+ } else {
658
+ console.log('Data Connect commands:');
659
+ console.log(' connect sources List sources');
660
+ console.log(' connect sources <id> Get source details');
661
+ console.log(' connect sources create --name <n> Create a source');
662
+ console.log(' connect sources rm <id> Delete a source');
663
+ console.log(' connect ingest <id> --file <path> Ingest data');
664
+ console.log(' connect test-parse --file <path> Parse without storing');
665
+ console.log(' connect schema <id> Get schema');
666
+ console.log(' connect records <id> List records');
667
+ console.log(' connect mappings <id> Get mappings');
668
+ console.log(' connect print <id> --design <d> --printer <p> Print');
669
+ }
670
+ }
671
+
672
+ async function cmdWebhooks(client, args, flags) {
673
+ const subCmd = args._[1];
674
+ if (subCmd === 'add') {
675
+ const url = args._[2];
676
+ if (!url) { console.error('Usage: labelinn webhooks add <url> [events...]'); process.exit(1); }
677
+ const events = args._.slice(3);
678
+ if (events.length === 0) events.push('print_job.completed', 'print_job.failed');
679
+ const result = await client.webhooks.create({ url, events });
680
+ if (flags.json) {
681
+ output(result, flags);
682
+ } else {
683
+ console.log(`✓ Webhook created`);
684
+ console.log(` ID: ${result.id}`);
685
+ console.log(` URL: ${result.url}`);
686
+ console.log(` Events: ${(result.events || events).join(', ')}`);
687
+ if (result.signing_secret) {
688
+ console.log(` Secret: ${result.signing_secret}`);
689
+ console.log(' (Save this — it won\'t be shown again)');
690
+ }
691
+ }
692
+ } else if (subCmd === 'rm') {
693
+ const id = args._[2];
694
+ if (!id) { console.error('Usage: labelinn webhooks rm <id>'); process.exit(1); }
695
+ await client.webhooks.delete(id);
696
+ console.log(`✓ Webhook ${id} deleted`);
697
+ } else if (subCmd === 'test') {
698
+ const id = args._[2];
699
+ if (!id) { console.error('Usage: labelinn webhooks test <id>'); process.exit(1); }
700
+ const result = await client.webhooks.test(id);
701
+ if (flags.json) {
702
+ output(result, flags);
703
+ } else {
704
+ console.log(`✓ Test ping sent to webhook ${id}`);
705
+ }
706
+ } else if (subCmd === 'update') {
707
+ const id = args._[2];
708
+ if (!id) { console.error('Usage: labelinn webhooks update <id> [--events e1,e2] [--enabled true|false]'); process.exit(1); }
709
+ const params = {};
710
+ if (flags.events) params.events = flags.events.split(',');
711
+ if (flags.enabled !== undefined) params.enabled = flags.enabled === 'true';
712
+ if (flags.url) params.url = flags.url;
713
+ if (flags.description) params.description = flags.description;
714
+ const result = await client.webhooks.update(id, params);
715
+ if (flags.json) {
716
+ output(result, flags);
717
+ } else {
718
+ console.log(`✓ Webhook ${id} updated`);
719
+ }
720
+ } else if (subCmd === 'deliveries') {
721
+ const id = args._[2];
722
+ if (!id) { console.error('Usage: labelinn webhooks deliveries <id>'); process.exit(1); }
723
+ const result = await client.webhooks.deliveries(id);
724
+ if (flags.json) {
725
+ output(result, flags);
726
+ } else {
727
+ const deliveries = Array.isArray(result) ? result : (result.deliveries || result);
728
+ table(Array.isArray(deliveries) ? deliveries : [], ['id', 'event', 'status', 'http_status', 'timestamp']);
729
+ }
730
+ } else {
731
+ const result = await client.webhooks.list();
732
+ const webhooks = result.webhooks || result;
733
+ if (flags.json) {
734
+ output(result, flags);
735
+ } else {
736
+ table(Array.isArray(webhooks) ? webhooks : [], ['id', 'url', 'events']);
737
+ }
738
+ }
739
+ }
740
+
741
+ // ── Main ──
742
+
743
+ async function main() {
744
+ const args = parseArgs(process.argv.slice(2));
745
+
746
+ if (args.flags.help || args._.length === 0) {
747
+ console.log(HELP);
748
+ process.exit(0);
749
+ }
750
+
751
+ const command = args._[0];
752
+ let client;
753
+
754
+ try {
755
+ // Login command doesn't need existing auth
756
+ if (command === 'login') {
757
+ await cmdLogin(args, args.flags);
758
+ return;
759
+ }
760
+
761
+ client = getClient(args.flags);
762
+ } catch (err) {
763
+ console.error(`Error: ${err.message}`);
764
+ process.exit(1);
765
+ }
766
+
767
+ try {
768
+ switch (command) {
769
+ case 'test': await cmdTest(client, args.flags); break;
770
+ case 'printers': await cmdPrinters(client, args, args.flags); break;
771
+ case 'groups': await cmdGroups(client, args, args.flags); break;
772
+ case 'print': await cmdPrint(client, args, args.flags); break;
773
+ case 'jobs': await cmdJobs(client, args, args.flags); break;
774
+ case 'batch': await cmdBatch(client, args, args.flags); break;
775
+ case 'designs': await cmdDesigns(client, args, args.flags); break;
776
+ case 'webhooks': await cmdWebhooks(client, args, args.flags); break;
777
+ case 'connect': await cmdConnect(client, args, args.flags); break;
778
+ default:
779
+ console.error(`Unknown command: ${command}`);
780
+ console.error('Run "labelinn --help" for usage');
781
+ process.exit(1);
782
+ }
783
+ } catch (err) {
784
+ if (err instanceof LabelInn.LabelInnError) {
785
+ console.error(`API Error [${err.status}] ${err.code}: ${err.message}`);
786
+ } else {
787
+ console.error(`Error: ${err.message}`);
788
+ }
789
+ process.exit(1);
790
+ }
791
+ }
792
+
793
+ main();