@bobfrankston/brother-label 1.0.13 → 1.1.1

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/cli.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Brother Label Printer CLI
4
- * Thin wrapper around the API
4
+ * Thin wrapper around api.ts using shared CLI primitives from label-core.
5
5
  */
6
6
  export {};
7
7
  //# sourceMappingURL=cli.d.ts.map
package/cli.js CHANGED
@@ -1,169 +1,279 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
3
  * Brother Label Printer CLI
4
- * Thin wrapper around the API
4
+ * Thin wrapper around api.ts using shared CLI primitives from label-core.
5
5
  */
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
- import { program } from "commander";
9
- import { print, render, getConfig, setConfig, getConfigPath, listPrinters, } from "./api.js";
8
+ import { preprocessSingleQuotes, parseArgs, } from "@bobfrankston/label-core";
9
+ import { print, render, renderSegments, printSegments, getConfig, setConfig, getConfigPath, listPrinters, detectTapeSize, brotherPrinter, } from "./api.js";
10
+ const VERSION = "1.1.0";
10
11
  const VALID_TAPES = [6, 9, 12, 18, 24];
11
12
  function parseTape(value) {
12
13
  const num = parseInt(value.replace("mm", ""), 10);
13
14
  if (!VALID_TAPES.includes(num)) {
14
- throw new Error(`Invalid tape size: ${value}. Valid: 6, 9, 12, 18, 24`);
15
+ throw new Error(`Invalid tape size: ${value}. Valid: ${VALID_TAPES.join(", ")}`);
15
16
  }
16
17
  return num;
17
18
  }
18
- // Detect content type from input string
19
- function buildPrintOptions(input, opts) {
20
- const options = {};
21
- // Parse tape option
22
- if (opts.tape) {
23
- options.tape = parseTape(opts.tape);
24
- }
25
- if (opts.printer) {
26
- options.printer = opts.printer;
27
- }
28
- if (opts.aspect) {
29
- options.aspect = opts.aspect;
19
+ const VALUED = [
20
+ { names: ["-tape"], key: "tape" },
21
+ { names: ["-p", "-printer"], key: "printer" },
22
+ { names: ["-o", "-output"], key: "output" },
23
+ { names: ["-a", "-aspect"], key: "aspect" },
24
+ { names: ["-H", "-height"], key: "height" },
25
+ { names: ["-s", "-space"], key: "space" },
26
+ { names: ["-timeout"], key: "timeout" },
27
+ { names: ["-interval"], key: "interval" },
28
+ ];
29
+ const BOOLEAN = [
30
+ { names: ["-w", "-html"], key: "html" },
31
+ { names: ["-i", "-image"], key: "image" },
32
+ { names: ["-t"], key: "text" },
33
+ { names: ["-c", "-clip"], key: "clip" },
34
+ { names: ["-no-wait", "-nowait"], key: "nowait" },
35
+ { names: ["-help", "-h", "-?"], key: "help" },
36
+ { names: ["-version", "-v"], key: "version" },
37
+ ];
38
+ const SUBCOMMANDS = ["list", "status", "config"];
39
+ function showHelp() {
40
+ console.log(`Brother Label Printer CLI v${VERSION}
41
+
42
+ Usage:
43
+ brother-print [options] <text> Print text
44
+ brother-print [options] <file> Print html/txt/image file (auto-detected)
45
+ brother-print -clip [options] Print clipboard contents (image or text)
46
+ brother-print -text <v> -qr <v> ... Print ordered text/qr segments
47
+ brother-print list List Brother printers and status
48
+ brother-print status [-p <name>] Show printer status
49
+ brother-print config Show / set defaults
50
+
51
+ Tape size is auto-detected from the printer. Use -tape to override.
52
+ Single quotes can wrap arguments: '7"' 'line1\\nline2'
53
+
54
+ Options:
55
+ -tape <size> Tape size: 6, 9, 12, 18, 24 (mm) [auto-detected]
56
+ -p, -printer <name> Printer queue name
57
+ -o, -output <file> Save PNG to file instead of printing
58
+ -a, -aspect <r> Aspect ratio width:height for HTML (e.g. 3.5:2)
59
+ -H, -height <size> Text height: 12mm, .5in, or 50% (of tape height)
60
+ -s, -space <size> Space between segments: 12px, 1mm, .2in
61
+ -t, -text Force input as literal text (-text <v> for segments)
62
+ -qr <data> QR code segment
63
+ -w, -html Force input as HTML file path
64
+ -i, -image Force input as image file path
65
+ -c, -clip Read content from clipboard (image preferred, then text)
66
+ -no-wait Fail immediately if printer is offline (default: wait)
67
+ -timeout <secs> Max wait time when printer is offline
68
+ -interval <secs> Polling interval while waiting (default 2)
69
+ -help Show this help
70
+ -version Show version`);
71
+ }
72
+ async function main() {
73
+ const argv = preprocessSingleQuotes(process.argv.slice(2));
74
+ let parsed;
75
+ try {
76
+ parsed = parseArgs(argv, VALUED, BOOLEAN, SUBCOMMANDS);
30
77
  }
31
- if (opts.height) {
32
- options.textHeight = opts.height;
78
+ catch (e) {
79
+ console.error(`Error: ${e.message}`);
80
+ process.exit(1);
33
81
  }
34
- // Explicit type flags override auto-detection
35
- if (opts.text) {
36
- options.text = input;
37
- return options;
82
+ if (parsed.unknown.length > 0) {
83
+ console.error(`Unknown option(s): ${parsed.unknown.join(", ")}`);
84
+ console.error(`Run "brother-print -help" for usage.`);
85
+ process.exit(1);
38
86
  }
39
- if (opts.html) {
40
- options.htmlPath = path.resolve(input);
41
- return options;
87
+ if (parsed.opts.help) {
88
+ showHelp();
89
+ return;
42
90
  }
43
- if (opts.image) {
44
- options.imagePath = path.resolve(input);
45
- return options;
91
+ if (parsed.opts.version) {
92
+ console.log(VERSION);
93
+ return;
46
94
  }
47
- // Auto-detect content type
48
- const lower = input.toLowerCase();
49
- if (lower.endsWith(".html") || lower.endsWith(".htm")) {
50
- options.htmlPath = path.resolve(input);
95
+ try {
96
+ switch (parsed.command) {
97
+ case "list":
98
+ await cmdList();
99
+ return;
100
+ case "status":
101
+ await cmdStatus(parsed.opts);
102
+ return;
103
+ case "config":
104
+ await cmdConfig(parsed.opts);
105
+ return;
106
+ }
107
+ await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);
51
108
  }
52
- else if (lower.endsWith(".txt")) {
53
- options.textFile = path.resolve(input);
109
+ catch (e) {
110
+ console.error(`Error: ${e.message}`);
111
+ process.exit(1);
54
112
  }
55
- else if (lower.endsWith(".png") || lower.endsWith(".jpg") || lower.endsWith(".jpeg") || lower.endsWith(".bmp") || lower.endsWith(".gif")) {
56
- options.imagePath = path.resolve(input);
113
+ }
114
+ async function cmdList() {
115
+ const printers = await listPrinters();
116
+ if (printers.length === 0) {
117
+ console.log("No Brother printers found.");
118
+ return;
57
119
  }
58
- else if (fs.existsSync(input)) {
59
- // Exists but unknown extension - try to detect
60
- const ext = path.extname(lower);
61
- if (ext === ".html" || ext === ".htm") {
62
- options.htmlPath = path.resolve(input);
120
+ console.log(`Brother printers (${printers.length}):`);
121
+ for (const p of printers) {
122
+ let statusStr = "?";
123
+ try {
124
+ const s = await brotherPrinter.getStatus(p.name);
125
+ statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
63
126
  }
64
- else {
65
- // Assume text file
66
- options.textFile = path.resolve(input);
127
+ catch (e) {
128
+ statusStr = `status error: ${e.message}`;
67
129
  }
130
+ console.log(` ${p.name} — ${statusStr}`);
68
131
  }
69
- else {
70
- // Treat as literal text
71
- options.text = input;
72
- }
73
- return options;
74
132
  }
75
- program
76
- .name("brother-print")
77
- .description("Print labels on Brother P-touch printers")
78
- .version("1.0.0");
79
- // Default command - print
80
- program
81
- .argument("[input]", "Text to print, or path to file (auto-detects type)")
82
- .option("-t, --tape <size>", "Tape size: 6, 9, 12, 18, 24 (mm)")
83
- .option("-p, --printer <name>", "Printer name")
84
- .option("-o, --output <file>", "Save to file instead of printing (png, jpg, bmp)")
85
- .option("-a, --aspect <ratio>", "Aspect ratio width:height for HTML (e.g., 3.5:2)")
86
- .option("-H, --height <size>", "Text height: 12mm, .5in, or 50% (of tape height)")
87
- .option("--text", "Force input as literal text")
88
- .option("--html", "Force input as HTML file path")
89
- .option("--image", "Force input as image file path")
90
- .action(async (input, opts) => {
91
- if (!input) {
92
- program.help();
133
+ async function cmdStatus(opts) {
134
+ const name = opts.printer || undefined;
135
+ const s = await brotherPrinter.getStatus(name);
136
+ console.log(`Printer: ${s.name}`);
137
+ console.log(`Online: ${s.online ? "yes" : "no"}`);
138
+ console.log(`Status: ${s.statusText}`);
139
+ if (s.error)
140
+ console.log(`Error: ${s.error}`);
141
+ console.log(`Queue: ${s.queueLength} job(s)`);
142
+ if (s.workOffline)
143
+ console.log(`Work offline flag: true`);
144
+ }
145
+ async function cmdConfig(opts) {
146
+ const hasUpdate = !!(opts.tape || opts.printer);
147
+ if (!hasUpdate) {
148
+ const cfg = getConfig();
149
+ console.log(`Configuration:`);
150
+ console.log(` File: ${getConfigPath()}`);
151
+ console.log(` Default tape: ${cfg.defaultTape ? cfg.defaultTape + "mm" : "(not set)"}`);
152
+ console.log(` Default printer: ${cfg.defaultPrinter ?? "(not set)"}`);
153
+ console.log("\nValid tape sizes: 6, 9, 12, 18, 24");
93
154
  return;
94
155
  }
95
- try {
96
- const options = buildPrintOptions(input, opts);
156
+ if (opts.tape) {
157
+ const t = parseTape(opts.tape);
158
+ setConfig({ defaultTape: t });
159
+ console.log(`Default tape set to: ${t}mm`);
160
+ }
161
+ if (opts.printer) {
162
+ setConfig({ defaultPrinter: opts.printer });
163
+ console.log(`Default printer set to: ${opts.printer}`);
164
+ }
165
+ }
166
+ async function resolveTape(opts) {
167
+ const explicit = opts.tape ? parseTape(opts.tape) : null;
168
+ const detected = await detectTapeSize(opts.printer);
169
+ if (explicit) {
170
+ if (detected && detected !== explicit) {
171
+ console.warn(`Warning: -tape ${explicit}mm specified but printer reports ${detected}mm tape loaded`);
172
+ }
173
+ return explicit;
174
+ }
175
+ if (detected)
176
+ return detected;
177
+ return getConfig().defaultTape ?? 24;
178
+ }
179
+ async function cmdPrint(opts, segments, inputs) {
180
+ const tape = await resolveTape(opts);
181
+ // If there are 2+ positional inputs, treat each as a text segment.
182
+ // (A single positional input is handled below via classifyInput so it can
183
+ // auto-detect file paths.)
184
+ if (inputs.length > 1 || (inputs.length >= 1 && segments.length > 0)) {
185
+ for (const inp of inputs) {
186
+ segments.push({ type: "text", value: inp });
187
+ }
188
+ inputs.length = 0;
189
+ }
190
+ const baseOpts = {
191
+ printer: opts.printer,
192
+ wait: !opts.nowait,
193
+ waitTimeoutMs: opts.timeout ? Math.round(parseFloat(opts.timeout) * 1000) : undefined,
194
+ waitIntervalMs: opts.interval ? Math.round(parseFloat(opts.interval) * 1000) : undefined,
195
+ onWaiting: makeWaitCallback(),
196
+ log: (msg) => console.log(msg),
197
+ };
198
+ // Multi-segment mode: explicit -text/-qr (>=1 segment), with positional joined in
199
+ if (segments.length > 1 || (segments.length === 1 && inputs.length === 0)) {
200
+ const segOpts = {
201
+ ...baseOpts,
202
+ tape,
203
+ textHeight: opts.height,
204
+ space: opts.space,
205
+ };
97
206
  if (opts.output) {
98
- // Render only, save to file
99
- const buffer = await render(options);
207
+ const buffer = await renderSegments(segments, tape, opts.height, opts.space);
100
208
  fs.writeFileSync(opts.output, buffer);
101
209
  console.log(`Saved to ${opts.output}`);
102
210
  }
103
211
  else {
104
- // Print
105
- const result = await print(options);
106
- const tape = options.tape ?? getConfig().defaultTape ?? 24;
107
- console.log(`Printed on ${tape}mm tape`);
212
+ await printSegments(segments, segOpts);
213
+ console.log(`Printed ${segments.length} segment(s) on ${tape}mm tape`);
108
214
  }
215
+ return;
109
216
  }
110
- catch (err) {
111
- console.error(`Error: ${err.message}`);
112
- process.exit(1);
217
+ // Single content op
218
+ const printOpts = {
219
+ ...baseOpts,
220
+ tape,
221
+ aspect: opts.aspect,
222
+ textHeight: opts.height,
223
+ };
224
+ if (opts.clip) {
225
+ printOpts.clip = true;
113
226
  }
114
- });
115
- // List printers
116
- program
117
- .command("list")
118
- .description("List available Brother printers")
119
- .action(async () => {
120
- try {
121
- const printers = await listPrinters();
122
- if (printers.length === 0) {
123
- console.log("No Brother printers found");
124
- }
125
- else {
126
- console.log("Brother printers:");
127
- printers.forEach(p => console.log(` ${p.name}`));
128
- }
227
+ else if (inputs.length === 1) {
228
+ Object.assign(printOpts, classifyInput(inputs[0], opts));
129
229
  }
130
- catch (err) {
131
- console.error(`Error: ${err.message}`);
132
- process.exit(1);
230
+ else if (inputs.length === 0 && segments.length === 0) {
231
+ showHelp();
232
+ return;
133
233
  }
134
- });
135
- // Config
136
- program
137
- .command("config")
138
- .description("Show or set configuration")
139
- .option("-t, --tape <size>", "Set default tape size: 6, 9, 12, 18, 24 (mm)")
140
- .option("-p, --printer <name>", "Set default printer name")
141
- .action((opts) => {
142
- try {
143
- if (!opts.tape && !opts.printer) {
144
- // Show config
145
- const config = getConfig();
146
- console.log("Configuration:");
147
- console.log(` File: ${getConfigPath()}`);
148
- console.log(` Default tape: ${config.defaultTape ? config.defaultTape + "mm" : "(not set)"}`);
149
- console.log(` Default printer: ${config.defaultPrinter ?? "(not set)"}`);
150
- console.log("\nValid tape sizes: 6, 9, 12, 18, 24");
151
- return;
152
- }
153
- if (opts.tape) {
154
- const tape = parseTape(opts.tape);
155
- setConfig({ defaultTape: tape });
156
- console.log(`Default tape set to: ${tape}mm`);
157
- }
158
- if (opts.printer) {
159
- setConfig({ defaultPrinter: opts.printer });
160
- console.log(`Default printer set to: ${opts.printer}`);
161
- }
234
+ if (opts.output) {
235
+ const buffer = await render(printOpts);
236
+ fs.writeFileSync(opts.output, buffer);
237
+ console.log(`Saved to ${opts.output}`);
238
+ return;
162
239
  }
163
- catch (err) {
164
- console.error(`Error: ${err.message}`);
165
- process.exit(1);
240
+ await print(printOpts);
241
+ console.log(`Printed on ${tape}mm tape`);
242
+ }
243
+ function classifyInput(input, opts) {
244
+ if (opts.text)
245
+ return { text: input };
246
+ if (opts.html)
247
+ return { htmlPath: path.resolve(input) };
248
+ if (opts.image)
249
+ return { imagePath: path.resolve(input) };
250
+ const lower = input.toLowerCase();
251
+ if (lower.endsWith(".html") || lower.endsWith(".htm"))
252
+ return { htmlPath: path.resolve(input) };
253
+ if (lower.endsWith(".txt"))
254
+ return { textFile: path.resolve(input) };
255
+ if (/\.(png|jpg|jpeg|bmp|gif)$/i.test(lower))
256
+ return { imagePath: path.resolve(input) };
257
+ if (fs.existsSync(input)) {
258
+ const ext = path.extname(lower);
259
+ if (ext === ".html" || ext === ".htm")
260
+ return { htmlPath: path.resolve(input) };
261
+ return { textFile: path.resolve(input) };
166
262
  }
167
- });
168
- program.parse();
263
+ return { text: input };
264
+ }
265
+ function makeWaitCallback() {
266
+ let lastReport = 0;
267
+ return (status, elapsedMs, alternatives) => {
268
+ const secs = Math.floor(elapsedMs / 1000);
269
+ if (secs - lastReport < 5)
270
+ return;
271
+ lastReport = secs;
272
+ const altStr = alternatives.length > 0
273
+ ? ` (online alternatives: ${alternatives.map(a => a.name).join(", ")})`
274
+ : "";
275
+ console.log(`[brother-print] still waiting for ${status.name} (${status.statusText}${status.error ? ", " + status.error : ""}); ${secs}s elapsed${altStr}`);
276
+ };
277
+ }
278
+ main();
169
279
  //# sourceMappingURL=cli.js.map
package/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\r\n/**\r\n * Brother Label Printer CLI\r\n * Thin wrapper around the API\r\n */\r\n\r\nimport * as fs from \"fs\";\r\nimport * as path from \"path\";\r\nimport { program } from \"commander\";\r\nimport {\r\n print,\r\n render,\r\n getConfig,\r\n setConfig,\r\n getConfigPath,\r\n listPrinters,\r\n TapeSize,\r\n PrintOptions,\r\n} from \"./api.js\";\r\n\r\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\r\n\r\nfunction parseTape(value: string): TapeSize {\r\n const num = parseInt(value.replace(\"mm\", \"\"), 10) as TapeSize;\r\n if (!VALID_TAPES.includes(num)) {\r\n throw new Error(`Invalid tape size: ${value}. Valid: 6, 9, 12, 18, 24`);\r\n }\r\n return num;\r\n}\r\n\r\n// Detect content type from input string\r\nfunction buildPrintOptions(input: string, opts: { tape?: string; printer?: string; text?: boolean; html?: boolean; image?: boolean; aspect?: string; height?: string }): PrintOptions {\r\n const options: PrintOptions = {};\r\n\r\n // Parse tape option\r\n if (opts.tape) {\r\n options.tape = parseTape(opts.tape);\r\n }\r\n if (opts.printer) {\r\n options.printer = opts.printer;\r\n }\r\n if (opts.aspect) {\r\n options.aspect = opts.aspect;\r\n }\r\n if (opts.height) {\r\n options.textHeight = opts.height;\r\n }\r\n\r\n // Explicit type flags override auto-detection\r\n if (opts.text) {\r\n options.text = input;\r\n return options;\r\n }\r\n if (opts.html) {\r\n options.htmlPath = path.resolve(input);\r\n return options;\r\n }\r\n if (opts.image) {\r\n options.imagePath = path.resolve(input);\r\n return options;\r\n }\r\n\r\n // Auto-detect content type\r\n const lower = input.toLowerCase();\r\n if (lower.endsWith(\".html\") || lower.endsWith(\".htm\")) {\r\n options.htmlPath = path.resolve(input);\r\n } else if (lower.endsWith(\".txt\")) {\r\n options.textFile = path.resolve(input);\r\n } else if (lower.endsWith(\".png\") || lower.endsWith(\".jpg\") || lower.endsWith(\".jpeg\") || lower.endsWith(\".bmp\") || lower.endsWith(\".gif\")) {\r\n options.imagePath = path.resolve(input);\r\n } else if (fs.existsSync(input)) {\r\n // Exists but unknown extension - try to detect\r\n const ext = path.extname(lower);\r\n if (ext === \".html\" || ext === \".htm\") {\r\n options.htmlPath = path.resolve(input);\r\n } else {\r\n // Assume text file\r\n options.textFile = path.resolve(input);\r\n }\r\n } else {\r\n // Treat as literal text\r\n options.text = input;\r\n }\r\n\r\n return options;\r\n}\r\n\r\nprogram\r\n .name(\"brother-print\")\r\n .description(\"Print labels on Brother P-touch printers\")\r\n .version(\"1.0.0\");\r\n\r\n// Default command - print\r\nprogram\r\n .argument(\"[input]\", \"Text to print, or path to file (auto-detects type)\")\r\n .option(\"-t, --tape <size>\", \"Tape size: 6, 9, 12, 18, 24 (mm)\")\r\n .option(\"-p, --printer <name>\", \"Printer name\")\r\n .option(\"-o, --output <file>\", \"Save to file instead of printing (png, jpg, bmp)\")\r\n .option(\"-a, --aspect <ratio>\", \"Aspect ratio width:height for HTML (e.g., 3.5:2)\")\r\n .option(\"-H, --height <size>\", \"Text height: 12mm, .5in, or 50% (of tape height)\")\r\n .option(\"--text\", \"Force input as literal text\")\r\n .option(\"--html\", \"Force input as HTML file path\")\r\n .option(\"--image\", \"Force input as image file path\")\r\n .action(async (input: string | undefined, opts) => {\r\n if (!input) {\r\n program.help();\r\n return;\r\n }\r\n\r\n try {\r\n const options = buildPrintOptions(input, opts);\r\n\r\n if (opts.output) {\r\n // Render only, save to file\r\n const buffer = await render(options);\r\n fs.writeFileSync(opts.output, buffer);\r\n console.log(`Saved to ${opts.output}`);\r\n } else {\r\n // Print\r\n const result = await print(options);\r\n const tape = options.tape ?? getConfig().defaultTape ?? 24;\r\n console.log(`Printed on ${tape}mm tape`);\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// List printers\r\nprogram\r\n .command(\"list\")\r\n .description(\"List available Brother printers\")\r\n .action(async () => {\r\n try {\r\n const printers = await listPrinters();\r\n if (printers.length === 0) {\r\n console.log(\"No Brother printers found\");\r\n } else {\r\n console.log(\"Brother printers:\");\r\n printers.forEach(p => console.log(` ${p.name}`));\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\n// Config\r\nprogram\r\n .command(\"config\")\r\n .description(\"Show or set configuration\")\r\n .option(\"-t, --tape <size>\", \"Set default tape size: 6, 9, 12, 18, 24 (mm)\")\r\n .option(\"-p, --printer <name>\", \"Set default printer name\")\r\n .action((opts) => {\r\n try {\r\n if (!opts.tape && !opts.printer) {\r\n // Show config\r\n const config = getConfig();\r\n console.log(\"Configuration:\");\r\n console.log(` File: ${getConfigPath()}`);\r\n console.log(` Default tape: ${config.defaultTape ? config.defaultTape + \"mm\" : \"(not set)\"}`);\r\n console.log(` Default printer: ${config.defaultPrinter ?? \"(not set)\"}`);\r\n console.log(\"\\nValid tape sizes: 6, 9, 12, 18, 24\");\r\n return;\r\n }\r\n\r\n if (opts.tape) {\r\n const tape = parseTape(opts.tape);\r\n setConfig({ defaultTape: tape });\r\n console.log(`Default tape set to: ${tape}mm`);\r\n }\r\n\r\n if (opts.printer) {\r\n setConfig({ defaultPrinter: opts.printer });\r\n console.log(`Default printer set to: ${opts.printer}`);\r\n }\r\n } catch (err) {\r\n console.error(`Error: ${(err as Error).message}`);\r\n process.exit(1);\r\n }\r\n });\r\n\r\nprogram.parse();\r\n"]}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["cli.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\n/**\n * Brother Label Printer CLI\n * Thin wrapper around api.ts using shared CLI primitives from label-core.\n */\n\nimport * as fs from \"fs\";\nimport * as path from \"path\";\nimport {\n preprocessSingleQuotes,\n parseArgs,\n PrintOptions as CorePrintOptions,\n PrinterStatus,\n Segment,\n ValuedOptionSpec,\n BooleanFlagSpec,\n} from \"@bobfrankston/label-core\";\nimport {\n print,\n render,\n renderSegments,\n printSegments,\n getConfig,\n setConfig,\n getConfigPath,\n listPrinters,\n detectTapeSize,\n brotherPrinter,\n TapeSize,\n PrintOptions,\n SegmentOptions,\n} from \"./api.js\";\n\nconst VERSION = \"1.1.0\";\nconst VALID_TAPES: TapeSize[] = [6, 9, 12, 18, 24];\n\nfunction parseTape(value: string): TapeSize {\n const num = parseInt(value.replace(\"mm\", \"\"), 10) as TapeSize;\n if (!VALID_TAPES.includes(num)) {\n throw new Error(`Invalid tape size: ${value}. Valid: ${VALID_TAPES.join(\", \")}`);\n }\n return num;\n}\n\nconst VALUED: ValuedOptionSpec[] = [\n { names: [\"-tape\"], key: \"tape\" },\n { names: [\"-p\", \"-printer\"], key: \"printer\" },\n { names: [\"-o\", \"-output\"], key: \"output\" },\n { names: [\"-a\", \"-aspect\"], key: \"aspect\" },\n { names: [\"-H\", \"-height\"], key: \"height\" },\n { names: [\"-s\", \"-space\"], key: \"space\" },\n { names: [\"-timeout\"], key: \"timeout\" },\n { names: [\"-interval\"], key: \"interval\" },\n];\n\nconst BOOLEAN: BooleanFlagSpec[] = [\n { names: [\"-w\", \"-html\"], key: \"html\" },\n { names: [\"-i\", \"-image\"], key: \"image\" },\n { names: [\"-t\"], key: \"text\" },\n { names: [\"-c\", \"-clip\"], key: \"clip\" },\n { names: [\"-no-wait\", \"-nowait\"], key: \"nowait\" },\n { names: [\"-help\", \"-h\", \"-?\"], key: \"help\" },\n { names: [\"-version\", \"-v\"], key: \"version\" },\n];\n\nconst SUBCOMMANDS = [\"list\", \"status\", \"config\"];\n\nfunction showHelp(): void {\n console.log(`Brother Label Printer CLI v${VERSION}\n\nUsage:\n brother-print [options] <text> Print text\n brother-print [options] <file> Print html/txt/image file (auto-detected)\n brother-print -clip [options] Print clipboard contents (image or text)\n brother-print -text <v> -qr <v> ... Print ordered text/qr segments\n brother-print list List Brother printers and status\n brother-print status [-p <name>] Show printer status\n brother-print config Show / set defaults\n\nTape size is auto-detected from the printer. Use -tape to override.\nSingle quotes can wrap arguments: '7\"' 'line1\\\\nline2'\n\nOptions:\n -tape <size> Tape size: 6, 9, 12, 18, 24 (mm) [auto-detected]\n -p, -printer <name> Printer queue name\n -o, -output <file> Save PNG to file instead of printing\n -a, -aspect <r> Aspect ratio width:height for HTML (e.g. 3.5:2)\n -H, -height <size> Text height: 12mm, .5in, or 50% (of tape height)\n -s, -space <size> Space between segments: 12px, 1mm, .2in\n -t, -text Force input as literal text (-text <v> for segments)\n -qr <data> QR code segment\n -w, -html Force input as HTML file path\n -i, -image Force input as image file path\n -c, -clip Read content from clipboard (image preferred, then text)\n -no-wait Fail immediately if printer is offline (default: wait)\n -timeout <secs> Max wait time when printer is offline\n -interval <secs> Polling interval while waiting (default 2)\n -help Show this help\n -version Show version`);\n}\n\nasync function main(): Promise<void> {\n const argv = preprocessSingleQuotes(process.argv.slice(2));\n let parsed;\n try {\n parsed = parseArgs(argv, VALUED, BOOLEAN, SUBCOMMANDS);\n } catch (e: any) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n\n if (parsed.unknown.length > 0) {\n console.error(`Unknown option(s): ${parsed.unknown.join(\", \")}`);\n console.error(`Run \"brother-print -help\" for usage.`);\n process.exit(1);\n }\n\n if (parsed.opts.help) { showHelp(); return; }\n if (parsed.opts.version) { console.log(VERSION); return; }\n\n try {\n switch (parsed.command) {\n case \"list\": await cmdList(); return;\n case \"status\": await cmdStatus(parsed.opts); return;\n case \"config\": await cmdConfig(parsed.opts); return;\n }\n await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);\n } catch (e: any) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n}\n\nasync function cmdList(): Promise<void> {\n const printers = await listPrinters();\n if (printers.length === 0) {\n console.log(\"No Brother printers found.\");\n return;\n }\n console.log(`Brother printers (${printers.length}):`);\n for (const p of printers) {\n let statusStr = \"?\";\n try {\n const s = await brotherPrinter.getStatus(p.name);\n statusStr = s.online ? \"online\" : `offline (${s.statusText}${s.error ? \", \" + s.error : \"\"})`;\n } catch (e: any) {\n statusStr = `status error: ${e.message}`;\n }\n console.log(` ${p.name} — ${statusStr}`);\n }\n}\n\nasync function cmdStatus(opts: Record<string, string | boolean>): Promise<void> {\n const name = (opts.printer as string) || undefined;\n const s = await brotherPrinter.getStatus(name);\n console.log(`Printer: ${s.name}`);\n console.log(`Online: ${s.online ? \"yes\" : \"no\"}`);\n console.log(`Status: ${s.statusText}`);\n if (s.error) console.log(`Error: ${s.error}`);\n console.log(`Queue: ${s.queueLength} job(s)`);\n if (s.workOffline) console.log(`Work offline flag: true`);\n}\n\nasync function cmdConfig(opts: Record<string, string | boolean>): Promise<void> {\n const hasUpdate = !!(opts.tape || opts.printer);\n if (!hasUpdate) {\n const cfg = getConfig();\n console.log(`Configuration:`);\n console.log(` File: ${getConfigPath()}`);\n console.log(` Default tape: ${cfg.defaultTape ? cfg.defaultTape + \"mm\" : \"(not set)\"}`);\n console.log(` Default printer: ${cfg.defaultPrinter ?? \"(not set)\"}`);\n console.log(\"\\nValid tape sizes: 6, 9, 12, 18, 24\");\n return;\n }\n if (opts.tape) {\n const t = parseTape(opts.tape as string);\n setConfig({ defaultTape: t });\n console.log(`Default tape set to: ${t}mm`);\n }\n if (opts.printer) {\n setConfig({ defaultPrinter: opts.printer as string });\n console.log(`Default printer set to: ${opts.printer}`);\n }\n}\n\nasync function resolveTape(opts: Record<string, string | boolean>): Promise<TapeSize> {\n const explicit = opts.tape ? parseTape(opts.tape as string) : null;\n const detected = await detectTapeSize(opts.printer as string | undefined);\n if (explicit) {\n if (detected && detected !== explicit) {\n console.warn(`Warning: -tape ${explicit}mm specified but printer reports ${detected}mm tape loaded`);\n }\n return explicit;\n }\n if (detected) return detected;\n return getConfig().defaultTape ?? 24;\n}\n\nasync function cmdPrint(\n opts: Record<string, string | boolean>,\n segments: Segment[],\n inputs: string[]\n): Promise<void> {\n const tape = await resolveTape(opts);\n\n // If there are 2+ positional inputs, treat each as a text segment.\n // (A single positional input is handled below via classifyInput so it can\n // auto-detect file paths.)\n if (inputs.length > 1 || (inputs.length >= 1 && segments.length > 0)) {\n for (const inp of inputs) {\n segments.push({ type: \"text\", value: inp });\n }\n inputs.length = 0;\n }\n\n const baseOpts = {\n printer: opts.printer as string | undefined,\n wait: !opts.nowait,\n waitTimeoutMs: opts.timeout ? Math.round(parseFloat(opts.timeout as string) * 1000) : undefined,\n waitIntervalMs: opts.interval ? Math.round(parseFloat(opts.interval as string) * 1000) : undefined,\n onWaiting: makeWaitCallback(),\n log: (msg: string) => console.log(msg),\n };\n\n // Multi-segment mode: explicit -text/-qr (>=1 segment), with positional joined in\n if (segments.length > 1 || (segments.length === 1 && inputs.length === 0)) {\n const segOpts: SegmentOptions = {\n ...baseOpts,\n tape,\n textHeight: opts.height as string | undefined,\n space: opts.space as string | undefined,\n };\n if (opts.output) {\n const buffer = await renderSegments(segments, tape, opts.height as string | undefined, opts.space as string | undefined);\n fs.writeFileSync(opts.output as string, buffer);\n console.log(`Saved to ${opts.output}`);\n } else {\n await printSegments(segments, segOpts);\n console.log(`Printed ${segments.length} segment(s) on ${tape}mm tape`);\n }\n return;\n }\n\n // Single content op\n const printOpts: PrintOptions = {\n ...baseOpts,\n tape,\n aspect: opts.aspect as string | undefined,\n textHeight: opts.height as string | undefined,\n };\n\n if (opts.clip) {\n printOpts.clip = true;\n } else if (inputs.length === 1) {\n Object.assign(printOpts, classifyInput(inputs[0], opts));\n } else if (inputs.length === 0 && segments.length === 0) {\n showHelp();\n return;\n }\n\n if (opts.output) {\n const buffer = await render(printOpts);\n fs.writeFileSync(opts.output as string, buffer);\n console.log(`Saved to ${opts.output}`);\n return;\n }\n await print(printOpts);\n console.log(`Printed on ${tape}mm tape`);\n}\n\nfunction classifyInput(input: string, opts: Record<string, string | boolean>): Partial<CorePrintOptions> {\n if (opts.text) return { text: input };\n if (opts.html) return { htmlPath: path.resolve(input) };\n if (opts.image) return { imagePath: path.resolve(input) };\n\n const lower = input.toLowerCase();\n if (lower.endsWith(\".html\") || lower.endsWith(\".htm\")) return { htmlPath: path.resolve(input) };\n if (lower.endsWith(\".txt\")) return { textFile: path.resolve(input) };\n if (/\\.(png|jpg|jpeg|bmp|gif)$/i.test(lower)) return { imagePath: path.resolve(input) };\n if (fs.existsSync(input)) {\n const ext = path.extname(lower);\n if (ext === \".html\" || ext === \".htm\") return { htmlPath: path.resolve(input) };\n return { textFile: path.resolve(input) };\n }\n return { text: input };\n}\n\nfunction makeWaitCallback() {\n let lastReport = 0;\n return (status: PrinterStatus, elapsedMs: number, alternatives: PrinterStatus[]) => {\n const secs = Math.floor(elapsedMs / 1000);\n if (secs - lastReport < 5) return;\n lastReport = secs;\n const altStr = alternatives.length > 0\n ? ` (online alternatives: ${alternatives.map(a => a.name).join(\", \")})`\n : \"\";\n console.log(`[brother-print] still waiting for ${status.name} (${status.statusText}${status.error ? \", \" + status.error : \"\"}); ${secs}s elapsed${altStr}`);\n };\n}\n\nmain();\n"]}