@bobfrankston/brother-label 1.1.6 → 1.1.8

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.js CHANGED
@@ -33,6 +33,7 @@ const BOOLEAN = [
33
33
  { names: ["-t"], key: "text" },
34
34
  { names: ["-c", "-clip"], key: "clip" },
35
35
  { names: ["-no-wait", "-nowait"], key: "nowait" },
36
+ { names: ["-mdns"], key: "mdns" },
36
37
  { names: ["-help", "-h", "-?"], key: "help" },
37
38
  { names: ["-version", "-v"], key: "version" },
38
39
  ];
@@ -45,7 +46,8 @@ Usage:
45
46
  brother-print [options] <file> Print html/txt/image file (auto-detected)
46
47
  brother-print -clip [options] Print clipboard contents (image or text)
47
48
  brother-print -text <v> -qr <v> ... Print ordered text/qr segments
48
- brother-print list List Brother printers and status
49
+ brother-print list [-mdns] List Brother printers and status
50
+ (-mdns also discovers network printers)
49
51
  brother-print status [-p <name>] Show printer status
50
52
  brother-print config Show / set defaults
51
53
 
@@ -67,9 +69,14 @@ Options:
67
69
  -no-wait Fail immediately if printer is offline (default: wait)
68
70
  -timeout <secs> Max wait time when printer is offline
69
71
  -interval <secs> Polling interval while waiting (default 2)
72
+ -mdns With "list": also discover network printers via mDNS
70
73
  -help Show this help
71
74
  -version Show version`);
72
75
  }
76
+ /** Expected user-facing error: prints one line, no log file, no stack. */
77
+ class UserError extends Error {
78
+ isUserError = true;
79
+ }
73
80
  async function main() {
74
81
  const argv = preprocessSingleQuotes(process.argv.slice(2));
75
82
  let parsed;
@@ -94,9 +101,12 @@ async function main() {
94
101
  return;
95
102
  }
96
103
  try {
104
+ if (typeof parsed.opts.printer === "string") {
105
+ parsed.opts.printer = await resolvePrinter(parsed.opts.printer);
106
+ }
97
107
  switch (parsed.command) {
98
108
  case "list":
99
- await cmdList();
109
+ await cmdList(parsed.opts);
100
110
  return;
101
111
  case "status":
102
112
  await cmdStatus(parsed.opts);
@@ -108,10 +118,58 @@ async function main() {
108
118
  await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);
109
119
  }
110
120
  catch (e) {
121
+ if (e?.isUserError) {
122
+ console.error(`Error: ${e.message}`);
123
+ process.exit(1);
124
+ }
111
125
  reportError(e);
112
126
  process.exit(1);
113
127
  }
114
128
  }
129
+ /**
130
+ * Resolve a -p value to an installed Windows queue name. Accepts:
131
+ * #N 1-based index into the list shown by `brother-label list`
132
+ * <exact> Full queue name (case-sensitive match shortcut)
133
+ * <substr> Case-insensitive substring against name or port (e.g. "ql820",
134
+ * "172.20.1.165", "BRW4CD577B59B6A"). Errors if ambiguous.
135
+ */
136
+ async function resolvePrinter(value) {
137
+ const { listPrinters: coreListPrinters } = await import("@bobfrankston/label-core");
138
+ const all = await coreListPrinters(/brother|^pt-|^ql-/i);
139
+ const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));
140
+ if (sorted.length === 0) {
141
+ throw new UserError(`No Brother printers installed. Run 'brother-label list' to verify.`);
142
+ }
143
+ const idx = value.match(/^#(\d+)$/);
144
+ if (idx) {
145
+ const n = parseInt(idx[1], 10);
146
+ if (n < 1 || n > sorted.length) {
147
+ throw new UserError(`-p ${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);
148
+ }
149
+ return sorted[n - 1].name;
150
+ }
151
+ const exact = sorted.find(p => p.name === value);
152
+ if (exact)
153
+ return exact.name;
154
+ const lower = value.toLowerCase();
155
+ const norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
156
+ const target = norm(value);
157
+ const matches = sorted.filter(p => {
158
+ const name = p.name.toLowerCase();
159
+ const port = (p.portName || "").toLowerCase();
160
+ if (name.includes(lower) || port.includes(lower))
161
+ return true;
162
+ // Normalized fallback: "ql820" matches "Brother QL-820NWB"
163
+ return norm(p.name).includes(target) || norm(p.portName || "").includes(target);
164
+ });
165
+ if (matches.length === 1)
166
+ return matches[0].name;
167
+ if (matches.length === 0) {
168
+ throw new UserError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);
169
+ }
170
+ const names = matches.map(p => p.name).join(", ");
171
+ throw new UserError(`'${value}' is ambiguous — matches: ${names}`);
172
+ }
115
173
  /**
116
174
  * Print an error with full context: message, cause chain (Node's fetch and
117
175
  * other newer APIs put the actual cause in error.cause), and stack on -v.
@@ -160,58 +218,86 @@ function reportError(e) {
160
218
  wroteFile = true;
161
219
  }
162
220
  catch { /* fallback */ }
163
- const topMsg = e?.message ?? String(e);
164
- console.error(`Error: ${topMsg}`);
165
- if (wroteFile)
166
- console.error(`Details: ${tempPath}`);
167
- else
221
+ if (wroteFile) {
222
+ console.error(`Error: ${tempPath}`);
223
+ }
224
+ else {
168
225
  console.error(dump);
226
+ }
169
227
  if (process.env.BROTHER_PRINT_DEBUG) {
170
228
  console.error("--- full report (BROTHER_PRINT_DEBUG enabled) ---");
171
229
  console.error(dump);
172
230
  }
173
231
  }
174
- async function cmdList() {
232
+ async function cmdList(opts) {
175
233
  const printers = await listPrinters();
176
- if (printers.length === 0) {
177
- console.log("No Brother printers found.");
178
- return;
179
- }
180
234
  /* Brother's listPrinters returns just { name } (legacy shape). To get
181
235
  * portName/driverName for the diagnostic line, query label-core's
182
236
  * coreListPrinters too. */
183
237
  const { listPrinters: coreListPrinters } = await import("@bobfrankston/label-core");
184
238
  const all = await coreListPrinters(/brother|^pt-|^ql-/i);
185
239
  const byName = new Map(all.map(p => [p.name, p]));
186
- const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));
187
- console.log(`Brother printers (${sorted.length}):`);
188
- for (let i = 0; i < sorted.length; i++) {
189
- const p = sorted[i];
190
- const info = byName.get(p.name);
191
- let statusStr = "?";
192
- try {
193
- const s = await brotherPrinter.getStatus(p.name);
194
- statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
195
- }
196
- catch (e) {
197
- statusStr = `status error: ${e.message}`;
240
+ if (printers.length === 0) {
241
+ console.log("No installed Brother printers.");
242
+ }
243
+ else {
244
+ const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));
245
+ console.log(`Brother printers (${sorted.length}):`);
246
+ for (let i = 0; i < sorted.length; i++) {
247
+ const p = sorted[i];
248
+ const info = byName.get(p.name);
249
+ let statusStr = "?";
250
+ try {
251
+ const s = await brotherPrinter.getStatus(p.name);
252
+ statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
253
+ }
254
+ catch (e) {
255
+ statusStr = `status error: ${e.message}`;
256
+ }
257
+ const port = info ? shortenPort(info.portName) : "(unknown)";
258
+ const driver = info?.driverName ?? "(unknown)";
259
+ console.log(` #${i + 1} ${p.name}`);
260
+ if (info?.comment)
261
+ console.log(` description: ${info.comment}`);
262
+ if (info?.location)
263
+ console.log(` location: ${info.location}`);
264
+ console.log(` port: ${port}`);
265
+ if (info?.portName && info.portName !== port) {
266
+ console.log(` full port: ${info.portName}`);
267
+ }
268
+ console.log(` driver: ${driver}`);
269
+ if (info?.shareName && info.shareName !== p.name) {
270
+ console.log(` share name: ${info.shareName}`);
271
+ }
272
+ console.log(` status: ${statusStr}`);
198
273
  }
199
- const port = info ? shortenPort(info.portName) : "(unknown)";
200
- const driver = info?.driverName ?? "(unknown)";
201
- console.log(` #${i + 1} ${p.name}`);
202
- if (info?.comment)
203
- console.log(` description: ${info.comment}`);
204
- if (info?.location)
205
- console.log(` location: ${info.location}`);
206
- console.log(` port: ${port}`);
207
- if (info?.portName && info.portName !== port) {
208
- console.log(` full port: ${info.portName}`);
274
+ }
275
+ if (opts.mdns) {
276
+ const { discoverPdlPrinters, isBrother, modelFromTxt, matchesInstalledPort } = await import("./mdns.js");
277
+ console.log("");
278
+ console.log("Browsing mDNS for _pdl-datastream._tcp (3s)...");
279
+ const found = (await discoverPdlPrinters()).filter(isBrother);
280
+ if (found.length === 0) {
281
+ console.log("No Brother printers responded via mDNS.");
282
+ return;
209
283
  }
210
- console.log(` driver: ${driver}`);
211
- if (info?.shareName && info.shareName !== p.name) {
212
- console.log(` share name: ${info.shareName}`);
284
+ const installedPorts = all.map(p => p.portName);
285
+ const networkOnly = found.filter(m => !installedPorts.some(pn => matchesInstalledPort(m, pn)));
286
+ const matched = found.filter(m => installedPorts.some(pn => matchesInstalledPort(m, pn)));
287
+ console.log(`mDNS responders (${found.length}; ${matched.length} matched, ${networkOnly.length} network-only):`);
288
+ let n = 1;
289
+ for (const m of [...matched, ...networkOnly]) {
290
+ const isNetOnly = networkOnly.includes(m);
291
+ const tag = isNetOnly ? " [network only]" : " [installed]";
292
+ const model = modelFromTxt(m) ?? "(unknown model)";
293
+ console.log(` #${n++} ${m.instanceName}${tag}`);
294
+ console.log(` model: ${model}`);
295
+ console.log(` hostname: ${m.hostname.replace(/\.$/, "")}`);
296
+ if (m.addresses.length > 0) {
297
+ console.log(` address: ${m.addresses.join(", ")}`);
298
+ }
299
+ console.log(` port: ${m.port}`);
213
300
  }
214
- console.log(` status: ${statusStr}`);
215
301
  }
216
302
  }
217
303
  /**
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\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 os from \"os\";\nimport * as path from \"path\";\nimport {\n preprocessSingleQuotes,\n parseArgs,\n} from \"@bobfrankston/label-core\";\nimport type {\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} from \"./api.js\";\nimport type { TapeSize, PrintOptions, SegmentOptions } 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: [\"-ht\", \"-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 -ht, -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 reportError(e);\n process.exit(1);\n }\n}\n\n/**\n * Print an error with full context: message, cause chain (Node's fetch and\n * other newer APIs put the actual cause in error.cause), and stack on -v.\n */\n/**\n * Report an error: print a one-line summary to stderr and write the full\n * cause-chain (with stacks, codes, paths) to a temp file. Avoids dumping\n * walls of error text into the user's terminal. Set BROTHER_PRINT_DEBUG=1\n * to also include the full chain inline.\n */\nfunction reportError(e: any): void {\n const lines: string[] = [];\n lines.push(`# brother-print error report`);\n lines.push(`# timestamp: ${new Date().toISOString()}`);\n lines.push(`# argv: ${process.argv.join(\" \")}`);\n lines.push(`# cwd: ${process.cwd()}`);\n lines.push(`# pid: ${process.pid}`);\n lines.push(\"\");\n\n let cur = e;\n let depth = 0;\n while (cur) {\n const prefix = depth === 0 ? \"Error\" : \"Caused by\";\n const name = cur.name && cur.name !== \"Error\" ? ` ${cur.name}` : \"\";\n const code = cur.code ? ` [${cur.code}]` : \"\";\n const msg = cur.message ?? String(cur);\n lines.push(`${prefix}${name}${code}: ${msg}`);\n if (cur.path) lines.push(` path: ${cur.path}`);\n if (cur.input) lines.push(` input: ${cur.input}`);\n if (cur.stack) lines.push(String(cur.stack));\n lines.push(\"\");\n cur = cur.cause;\n depth++;\n if (depth > 8) { lines.push(\"... (cause chain truncated at 8 levels)\"); break; }\n }\n\n const dump = lines.join(\"\\n\");\n const tempPath = path.join(os.tmpdir(), `brother-print-error-${Date.now()}-${process.pid}.log`);\n let wroteFile = false;\n try { fs.writeFileSync(tempPath, dump); wroteFile = true; } catch { /* fallback */ }\n\n const topMsg = e?.message ?? String(e);\n console.error(`Error: ${topMsg}`);\n if (wroteFile) console.error(`Details: ${tempPath}`);\n else console.error(dump);\n if (process.env.BROTHER_PRINT_DEBUG) {\n console.error(\"--- full report (BROTHER_PRINT_DEBUG enabled) ---\");\n console.error(dump);\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 /* Brother's listPrinters returns just { name } (legacy shape). To get\n * portName/driverName for the diagnostic line, query label-core's\n * coreListPrinters too. */\n const { listPrinters: coreListPrinters } = await import(\"@bobfrankston/label-core\");\n const all = await coreListPrinters(/brother|^pt-|^ql-/i);\n const byName = new Map(all.map(p => [p.name, p]));\n\n const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));\n console.log(`Brother printers (${sorted.length}):`);\n for (let i = 0; i < sorted.length; i++) {\n const p = sorted[i];\n const info = byName.get(p.name);\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 const port = info ? shortenPort(info.portName) : \"(unknown)\";\n const driver = info?.driverName ?? \"(unknown)\";\n console.log(` #${i + 1} ${p.name}`);\n if (info?.comment) console.log(` description: ${info.comment}`);\n if (info?.location) console.log(` location: ${info.location}`);\n console.log(` port: ${port}`);\n if (info?.portName && info.portName !== port) {\n console.log(` full port: ${info.portName}`);\n }\n console.log(` driver: ${driver}`);\n if (info?.shareName && info.shareName !== p.name) {\n console.log(` share name: ${info.shareName}`);\n }\n console.log(` status: ${statusStr}`);\n }\n}\n\n/**\n * Pull a short identifier out of a Windows printer port: \"... on hostname\"\n * → hostname; raw IP/hostname kept as-is; USB ports kept as-is.\n */\nfunction shortenPort(portName: string): string {\n if (!portName) return \"(unknown)\";\n const onMatch = portName.match(/\\bon\\s+(\\S+)\\s*$/i);\n if (onMatch) return onMatch[1];\n const hostMatch = portName.match(/\\b(\\d{1,3}(?:\\.\\d{1,3}){3}|[\\w-]+\\.local|[\\w-]+\\.[\\w.-]+)\\b/);\n if (hostMatch) return hostMatch[1];\n return portName;\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"]}
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 os from \"os\";\nimport * as path from \"path\";\nimport {\n preprocessSingleQuotes,\n parseArgs,\n} from \"@bobfrankston/label-core\";\nimport type {\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} from \"./api.js\";\nimport type { TapeSize, PrintOptions, SegmentOptions } 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: [\"-ht\", \"-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: [\"-mdns\"], key: \"mdns\" },\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 [-mdns] List Brother printers and status\n (-mdns also discovers network printers)\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 -ht, -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 -mdns With \"list\": also discover network printers via mDNS\n -help Show this help\n -version Show version`);\n}\n\n/** Expected user-facing error: prints one line, no log file, no stack. */\nclass UserError extends Error {\n readonly isUserError = true;\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 if (typeof parsed.opts.printer === \"string\") {\n parsed.opts.printer = await resolvePrinter(parsed.opts.printer);\n }\n switch (parsed.command) {\n case \"list\": await cmdList(parsed.opts); 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 if (e?.isUserError) {\n console.error(`Error: ${e.message}`);\n process.exit(1);\n }\n reportError(e);\n process.exit(1);\n }\n}\n\n/**\n * Resolve a -p value to an installed Windows queue name. Accepts:\n * #N 1-based index into the list shown by `brother-label list`\n * <exact> Full queue name (case-sensitive match shortcut)\n * <substr> Case-insensitive substring against name or port (e.g. \"ql820\",\n * \"172.20.1.165\", \"BRW4CD577B59B6A\"). Errors if ambiguous.\n */\nasync function resolvePrinter(value: string): Promise<string> {\n const { listPrinters: coreListPrinters } = await import(\"@bobfrankston/label-core\");\n const all = await coreListPrinters(/brother|^pt-|^ql-/i);\n const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));\n\n if (sorted.length === 0) {\n throw new UserError(`No Brother printers installed. Run 'brother-label list' to verify.`);\n }\n\n const idx = value.match(/^#(\\d+)$/);\n if (idx) {\n const n = parseInt(idx[1], 10);\n if (n < 1 || n > sorted.length) {\n throw new UserError(`-p ${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);\n }\n return sorted[n - 1].name;\n }\n\n const exact = sorted.find(p => p.name === value);\n if (exact) return exact.name;\n\n const lower = value.toLowerCase();\n const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, \"\");\n const target = norm(value);\n const matches = sorted.filter(p => {\n const name = p.name.toLowerCase();\n const port = (p.portName || \"\").toLowerCase();\n if (name.includes(lower) || port.includes(lower)) return true;\n // Normalized fallback: \"ql820\" matches \"Brother QL-820NWB\"\n return norm(p.name).includes(target) || norm(p.portName || \"\").includes(target);\n });\n if (matches.length === 1) return matches[0].name;\n if (matches.length === 0) {\n throw new UserError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);\n }\n const names = matches.map(p => p.name).join(\", \");\n throw new UserError(`'${value}' is ambiguous — matches: ${names}`);\n}\n\n/**\n * Print an error with full context: message, cause chain (Node's fetch and\n * other newer APIs put the actual cause in error.cause), and stack on -v.\n */\n/**\n * Report an error: print a one-line summary to stderr and write the full\n * cause-chain (with stacks, codes, paths) to a temp file. Avoids dumping\n * walls of error text into the user's terminal. Set BROTHER_PRINT_DEBUG=1\n * to also include the full chain inline.\n */\nfunction reportError(e: any): void {\n const lines: string[] = [];\n lines.push(`# brother-print error report`);\n lines.push(`# timestamp: ${new Date().toISOString()}`);\n lines.push(`# argv: ${process.argv.join(\" \")}`);\n lines.push(`# cwd: ${process.cwd()}`);\n lines.push(`# pid: ${process.pid}`);\n lines.push(\"\");\n\n let cur = e;\n let depth = 0;\n while (cur) {\n const prefix = depth === 0 ? \"Error\" : \"Caused by\";\n const name = cur.name && cur.name !== \"Error\" ? ` ${cur.name}` : \"\";\n const code = cur.code ? ` [${cur.code}]` : \"\";\n const msg = cur.message ?? String(cur);\n lines.push(`${prefix}${name}${code}: ${msg}`);\n if (cur.path) lines.push(` path: ${cur.path}`);\n if (cur.input) lines.push(` input: ${cur.input}`);\n if (cur.stack) lines.push(String(cur.stack));\n lines.push(\"\");\n cur = cur.cause;\n depth++;\n if (depth > 8) { lines.push(\"... (cause chain truncated at 8 levels)\"); break; }\n }\n\n const dump = lines.join(\"\\n\");\n const tempPath = path.join(os.tmpdir(), `brother-print-error-${Date.now()}-${process.pid}.log`);\n let wroteFile = false;\n try { fs.writeFileSync(tempPath, dump); wroteFile = true; } catch { /* fallback */ }\n\n if (wroteFile) {\n console.error(`Error: ${tempPath}`);\n } else {\n console.error(dump);\n }\n if (process.env.BROTHER_PRINT_DEBUG) {\n console.error(\"--- full report (BROTHER_PRINT_DEBUG enabled) ---\");\n console.error(dump);\n }\n}\n\nasync function cmdList(opts: Record<string, string | boolean>): Promise<void> {\n const printers = await listPrinters();\n /* Brother's listPrinters returns just { name } (legacy shape). To get\n * portName/driverName for the diagnostic line, query label-core's\n * coreListPrinters too. */\n const { listPrinters: coreListPrinters } = await import(\"@bobfrankston/label-core\");\n const all = await coreListPrinters(/brother|^pt-|^ql-/i);\n const byName = new Map(all.map(p => [p.name, p]));\n\n if (printers.length === 0) {\n console.log(\"No installed Brother printers.\");\n } else {\n const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));\n console.log(`Brother printers (${sorted.length}):`);\n for (let i = 0; i < sorted.length; i++) {\n const p = sorted[i];\n const info = byName.get(p.name);\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 const port = info ? shortenPort(info.portName) : \"(unknown)\";\n const driver = info?.driverName ?? \"(unknown)\";\n console.log(` #${i + 1} ${p.name}`);\n if (info?.comment) console.log(` description: ${info.comment}`);\n if (info?.location) console.log(` location: ${info.location}`);\n console.log(` port: ${port}`);\n if (info?.portName && info.portName !== port) {\n console.log(` full port: ${info.portName}`);\n }\n console.log(` driver: ${driver}`);\n if (info?.shareName && info.shareName !== p.name) {\n console.log(` share name: ${info.shareName}`);\n }\n console.log(` status: ${statusStr}`);\n }\n }\n\n if (opts.mdns) {\n const { discoverPdlPrinters, isBrother, modelFromTxt, matchesInstalledPort } =\n await import(\"./mdns.js\");\n console.log(\"\");\n console.log(\"Browsing mDNS for _pdl-datastream._tcp (3s)...\");\n const found = (await discoverPdlPrinters()).filter(isBrother);\n if (found.length === 0) {\n console.log(\"No Brother printers responded via mDNS.\");\n return;\n }\n const installedPorts = all.map(p => p.portName);\n const networkOnly = found.filter(m => !installedPorts.some(pn => matchesInstalledPort(m, pn)));\n const matched = found.filter(m => installedPorts.some(pn => matchesInstalledPort(m, pn)));\n\n console.log(`mDNS responders (${found.length}; ${matched.length} matched, ${networkOnly.length} network-only):`);\n let n = 1;\n for (const m of [...matched, ...networkOnly]) {\n const isNetOnly = networkOnly.includes(m);\n const tag = isNetOnly ? \" [network only]\" : \" [installed]\";\n const model = modelFromTxt(m) ?? \"(unknown model)\";\n console.log(` #${n++} ${m.instanceName}${tag}`);\n console.log(` model: ${model}`);\n console.log(` hostname: ${m.hostname.replace(/\\.$/, \"\")}`);\n if (m.addresses.length > 0) {\n console.log(` address: ${m.addresses.join(\", \")}`);\n }\n console.log(` port: ${m.port}`);\n }\n }\n}\n\n/**\n * Pull a short identifier out of a Windows printer port: \"... on hostname\"\n * → hostname; raw IP/hostname kept as-is; USB ports kept as-is.\n */\nfunction shortenPort(portName: string): string {\n if (!portName) return \"(unknown)\";\n const onMatch = portName.match(/\\bon\\s+(\\S+)\\s*$/i);\n if (onMatch) return onMatch[1];\n const hostMatch = portName.match(/\\b(\\d{1,3}(?:\\.\\d{1,3}){3}|[\\w-]+\\.local|[\\w-]+\\.[\\w.-]+)\\b/);\n if (hostMatch) return hostMatch[1];\n return portName;\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"]}
package/cli.ts CHANGED
@@ -60,6 +60,7 @@ const BOOLEAN: BooleanFlagSpec[] = [
60
60
  { names: ["-t"], key: "text" },
61
61
  { names: ["-c", "-clip"], key: "clip" },
62
62
  { names: ["-no-wait", "-nowait"], key: "nowait" },
63
+ { names: ["-mdns"], key: "mdns" },
63
64
  { names: ["-help", "-h", "-?"], key: "help" },
64
65
  { names: ["-version", "-v"], key: "version" },
65
66
  ];
@@ -74,7 +75,8 @@ Usage:
74
75
  brother-print [options] <file> Print html/txt/image file (auto-detected)
75
76
  brother-print -clip [options] Print clipboard contents (image or text)
76
77
  brother-print -text <v> -qr <v> ... Print ordered text/qr segments
77
- brother-print list List Brother printers and status
78
+ brother-print list [-mdns] List Brother printers and status
79
+ (-mdns also discovers network printers)
78
80
  brother-print status [-p <name>] Show printer status
79
81
  brother-print config Show / set defaults
80
82
 
@@ -96,10 +98,16 @@ Options:
96
98
  -no-wait Fail immediately if printer is offline (default: wait)
97
99
  -timeout <secs> Max wait time when printer is offline
98
100
  -interval <secs> Polling interval while waiting (default 2)
101
+ -mdns With "list": also discover network printers via mDNS
99
102
  -help Show this help
100
103
  -version Show version`);
101
104
  }
102
105
 
106
+ /** Expected user-facing error: prints one line, no log file, no stack. */
107
+ class UserError extends Error {
108
+ readonly isUserError = true;
109
+ }
110
+
103
111
  async function main(): Promise<void> {
104
112
  const argv = preprocessSingleQuotes(process.argv.slice(2));
105
113
  let parsed;
@@ -120,18 +128,71 @@ async function main(): Promise<void> {
120
128
  if (parsed.opts.version) { console.log(VERSION); return; }
121
129
 
122
130
  try {
131
+ if (typeof parsed.opts.printer === "string") {
132
+ parsed.opts.printer = await resolvePrinter(parsed.opts.printer);
133
+ }
123
134
  switch (parsed.command) {
124
- case "list": await cmdList(); return;
135
+ case "list": await cmdList(parsed.opts); return;
125
136
  case "status": await cmdStatus(parsed.opts); return;
126
137
  case "config": await cmdConfig(parsed.opts); return;
127
138
  }
128
139
  await cmdPrint(parsed.opts, parsed.segments, parsed.inputs);
129
140
  } catch (e: any) {
141
+ if (e?.isUserError) {
142
+ console.error(`Error: ${e.message}`);
143
+ process.exit(1);
144
+ }
130
145
  reportError(e);
131
146
  process.exit(1);
132
147
  }
133
148
  }
134
149
 
150
+ /**
151
+ * Resolve a -p value to an installed Windows queue name. Accepts:
152
+ * #N 1-based index into the list shown by `brother-label list`
153
+ * <exact> Full queue name (case-sensitive match shortcut)
154
+ * <substr> Case-insensitive substring against name or port (e.g. "ql820",
155
+ * "172.20.1.165", "BRW4CD577B59B6A"). Errors if ambiguous.
156
+ */
157
+ async function resolvePrinter(value: string): Promise<string> {
158
+ const { listPrinters: coreListPrinters } = await import("@bobfrankston/label-core");
159
+ const all = await coreListPrinters(/brother|^pt-|^ql-/i);
160
+ const sorted = [...all].sort((a, b) => a.name.localeCompare(b.name));
161
+
162
+ if (sorted.length === 0) {
163
+ throw new UserError(`No Brother printers installed. Run 'brother-label list' to verify.`);
164
+ }
165
+
166
+ const idx = value.match(/^#(\d+)$/);
167
+ if (idx) {
168
+ const n = parseInt(idx[1], 10);
169
+ if (n < 1 || n > sorted.length) {
170
+ throw new UserError(`-p ${value}: only ${sorted.length} printer(s) installed (use 'brother-label list').`);
171
+ }
172
+ return sorted[n - 1].name;
173
+ }
174
+
175
+ const exact = sorted.find(p => p.name === value);
176
+ if (exact) return exact.name;
177
+
178
+ const lower = value.toLowerCase();
179
+ const norm = (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
180
+ const target = norm(value);
181
+ const matches = sorted.filter(p => {
182
+ const name = p.name.toLowerCase();
183
+ const port = (p.portName || "").toLowerCase();
184
+ if (name.includes(lower) || port.includes(lower)) return true;
185
+ // Normalized fallback: "ql820" matches "Brother QL-820NWB"
186
+ return norm(p.name).includes(target) || norm(p.portName || "").includes(target);
187
+ });
188
+ if (matches.length === 1) return matches[0].name;
189
+ if (matches.length === 0) {
190
+ throw new UserError(`No printer matches '${value}'. Run 'brother-label list' to see available printers.`);
191
+ }
192
+ const names = matches.map(p => p.name).join(", ");
193
+ throw new UserError(`'${value}' is ambiguous — matches: ${names}`);
194
+ }
195
+
135
196
  /**
136
197
  * Print an error with full context: message, cause chain (Node's fetch and
137
198
  * other newer APIs put the actual cause in error.cause), and stack on -v.
@@ -173,22 +234,19 @@ function reportError(e: any): void {
173
234
  let wroteFile = false;
174
235
  try { fs.writeFileSync(tempPath, dump); wroteFile = true; } catch { /* fallback */ }
175
236
 
176
- const topMsg = e?.message ?? String(e);
177
- console.error(`Error: ${topMsg}`);
178
- if (wroteFile) console.error(`Details: ${tempPath}`);
179
- else console.error(dump);
237
+ if (wroteFile) {
238
+ console.error(`Error: ${tempPath}`);
239
+ } else {
240
+ console.error(dump);
241
+ }
180
242
  if (process.env.BROTHER_PRINT_DEBUG) {
181
243
  console.error("--- full report (BROTHER_PRINT_DEBUG enabled) ---");
182
244
  console.error(dump);
183
245
  }
184
246
  }
185
247
 
186
- async function cmdList(): Promise<void> {
248
+ async function cmdList(opts: Record<string, string | boolean>): Promise<void> {
187
249
  const printers = await listPrinters();
188
- if (printers.length === 0) {
189
- console.log("No Brother printers found.");
190
- return;
191
- }
192
250
  /* Brother's listPrinters returns just { name } (legacy shape). To get
193
251
  * portName/driverName for the diagnostic line, query label-core's
194
252
  * coreListPrinters too. */
@@ -196,32 +254,66 @@ async function cmdList(): Promise<void> {
196
254
  const all = await coreListPrinters(/brother|^pt-|^ql-/i);
197
255
  const byName = new Map(all.map(p => [p.name, p]));
198
256
 
199
- const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));
200
- console.log(`Brother printers (${sorted.length}):`);
201
- for (let i = 0; i < sorted.length; i++) {
202
- const p = sorted[i];
203
- const info = byName.get(p.name);
204
- let statusStr = "?";
205
- try {
206
- const s = await brotherPrinter.getStatus(p.name);
207
- statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
208
- } catch (e: any) {
209
- statusStr = `status error: ${e.message}`;
257
+ if (printers.length === 0) {
258
+ console.log("No installed Brother printers.");
259
+ } else {
260
+ const sorted = [...printers].sort((a, b) => a.name.localeCompare(b.name));
261
+ console.log(`Brother printers (${sorted.length}):`);
262
+ for (let i = 0; i < sorted.length; i++) {
263
+ const p = sorted[i];
264
+ const info = byName.get(p.name);
265
+ let statusStr = "?";
266
+ try {
267
+ const s = await brotherPrinter.getStatus(p.name);
268
+ statusStr = s.online ? "online" : `offline (${s.statusText}${s.error ? ", " + s.error : ""})`;
269
+ } catch (e: any) {
270
+ statusStr = `status error: ${e.message}`;
271
+ }
272
+ const port = info ? shortenPort(info.portName) : "(unknown)";
273
+ const driver = info?.driverName ?? "(unknown)";
274
+ console.log(` #${i + 1} ${p.name}`);
275
+ if (info?.comment) console.log(` description: ${info.comment}`);
276
+ if (info?.location) console.log(` location: ${info.location}`);
277
+ console.log(` port: ${port}`);
278
+ if (info?.portName && info.portName !== port) {
279
+ console.log(` full port: ${info.portName}`);
280
+ }
281
+ console.log(` driver: ${driver}`);
282
+ if (info?.shareName && info.shareName !== p.name) {
283
+ console.log(` share name: ${info.shareName}`);
284
+ }
285
+ console.log(` status: ${statusStr}`);
210
286
  }
211
- const port = info ? shortenPort(info.portName) : "(unknown)";
212
- const driver = info?.driverName ?? "(unknown)";
213
- console.log(` #${i + 1} ${p.name}`);
214
- if (info?.comment) console.log(` description: ${info.comment}`);
215
- if (info?.location) console.log(` location: ${info.location}`);
216
- console.log(` port: ${port}`);
217
- if (info?.portName && info.portName !== port) {
218
- console.log(` full port: ${info.portName}`);
287
+ }
288
+
289
+ if (opts.mdns) {
290
+ const { discoverPdlPrinters, isBrother, modelFromTxt, matchesInstalledPort } =
291
+ await import("./mdns.js");
292
+ console.log("");
293
+ console.log("Browsing mDNS for _pdl-datastream._tcp (3s)...");
294
+ const found = (await discoverPdlPrinters()).filter(isBrother);
295
+ if (found.length === 0) {
296
+ console.log("No Brother printers responded via mDNS.");
297
+ return;
219
298
  }
220
- console.log(` driver: ${driver}`);
221
- if (info?.shareName && info.shareName !== p.name) {
222
- console.log(` share name: ${info.shareName}`);
299
+ const installedPorts = all.map(p => p.portName);
300
+ const networkOnly = found.filter(m => !installedPorts.some(pn => matchesInstalledPort(m, pn)));
301
+ const matched = found.filter(m => installedPorts.some(pn => matchesInstalledPort(m, pn)));
302
+
303
+ console.log(`mDNS responders (${found.length}; ${matched.length} matched, ${networkOnly.length} network-only):`);
304
+ let n = 1;
305
+ for (const m of [...matched, ...networkOnly]) {
306
+ const isNetOnly = networkOnly.includes(m);
307
+ const tag = isNetOnly ? " [network only]" : " [installed]";
308
+ const model = modelFromTxt(m) ?? "(unknown model)";
309
+ console.log(` #${n++} ${m.instanceName}${tag}`);
310
+ console.log(` model: ${model}`);
311
+ console.log(` hostname: ${m.hostname.replace(/\.$/, "")}`);
312
+ if (m.addresses.length > 0) {
313
+ console.log(` address: ${m.addresses.join(", ")}`);
314
+ }
315
+ console.log(` port: ${m.port}`);
223
316
  }
224
- console.log(` status: ${statusStr}`);
225
317
  }
226
318
  }
227
319
 
package/mdns.d.ts ADDED
@@ -0,0 +1,33 @@
1
+ /**
2
+ * mDNS discovery for network-attached Brother printers.
3
+ *
4
+ * Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles
5
+ * full records by joining PTR -> SRV -> A -> TXT replies that arrive on the
6
+ * same multicast burst.
7
+ */
8
+ export interface MdnsPrinter {
9
+ instanceName: string; /** service instance label, often the model */
10
+ hostname: string; /** e.g. BRW4CD577B59B6A.local */
11
+ addresses: string[]; /** IPv4 addresses */
12
+ port: number; /** typically 9100 */
13
+ txt: Record<string, string>; /** TXT record key=value pairs */
14
+ }
15
+ /**
16
+ * Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and
17
+ * _printer service types and joins the records into per-instance entries
18
+ * keyed by service-instance name. Returns all responders (not just Brother).
19
+ *
20
+ * Sends each query 3 times spaced ~600ms apart to defeat single-packet loss
21
+ * — Brother printers in particular can be flaky on the first burst.
22
+ */
23
+ export declare function discoverPdlPrinters(timeoutMs?: number): Promise<MdnsPrinter[]>;
24
+ export declare function isBrother(p: MdnsPrinter): boolean;
25
+ /** Pull a friendly model string out of TXT records. */
26
+ export declare function modelFromTxt(p: MdnsPrinter): string | undefined;
27
+ /**
28
+ * True if `m` looks like the same physical printer as the installed Windows
29
+ * queue identified by `portName`. Matches by IP-address substring or by
30
+ * hostname (with or without trailing `.local`) appearing in the port string.
31
+ */
32
+ export declare function matchesInstalledPort(m: MdnsPrinter, portName: string | undefined): boolean;
33
+ //# sourceMappingURL=mdns.d.ts.map
package/mdns.d.ts.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdns.d.ts","sourceRoot":"","sources":["mdns.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,MAAM,WAAW,WAAW;IACxB,YAAY,EAAE,MAAM,CAAC,CAAY,8CAA8C;IAC/E,QAAQ,EAAE,MAAM,CAAC,CAAgB,iCAAiC;IAClE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAa,qBAAqB;IACtD,IAAI,EAAE,MAAM,CAAC,CAAoB,qBAAqB;IACtD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAK,iCAAiC;CACrE;AAQD;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,SAAO,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC,CAgFlF;AAED,wBAAgB,SAAS,CAAC,CAAC,EAAE,WAAW,GAAG,OAAO,CAMjD;AAED,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,CAAC,EAAE,WAAW,GAAG,MAAM,GAAG,SAAS,CAI/D;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAU1F"}
package/mdns.js ADDED
@@ -0,0 +1,146 @@
1
+ /**
2
+ * mDNS discovery for network-attached Brother printers.
3
+ *
4
+ * Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles
5
+ * full records by joining PTR -> SRV -> A -> TXT replies that arrive on the
6
+ * same multicast burst.
7
+ */
8
+ import multicastDns from "multicast-dns";
9
+ const SERVICE_TYPES = [
10
+ "_pdl-datastream._tcp.local", /** raw TCP 9100 */
11
+ "_ipp._tcp.local", /** IPP port 631 */
12
+ "_printer._tcp.local", /** LPR port 515 */
13
+ ];
14
+ /**
15
+ * Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and
16
+ * _printer service types and joins the records into per-instance entries
17
+ * keyed by service-instance name. Returns all responders (not just Brother).
18
+ *
19
+ * Sends each query 3 times spaced ~600ms apart to defeat single-packet loss
20
+ * — Brother printers in particular can be flaky on the first burst.
21
+ */
22
+ export async function discoverPdlPrinters(timeoutMs = 4000) {
23
+ return new Promise((resolve) => {
24
+ const mdns = multicastDns();
25
+ const ptrs = new Map(); /** instance -> serviceType */
26
+ const srv = new Map();
27
+ const txt = new Map();
28
+ const aRec = new Map();
29
+ mdns.on("response", (response) => {
30
+ const records = [...(response.answers || []), ...(response.additionals || [])];
31
+ for (const r of records) {
32
+ if (r.type === "PTR" && SERVICE_TYPES.includes(r.name)) {
33
+ if (!ptrs.has(r.data))
34
+ ptrs.set(r.data, r.name);
35
+ }
36
+ else if (r.type === "SRV") {
37
+ srv.set(r.name, { target: r.data.target, port: r.data.port });
38
+ }
39
+ else if (r.type === "TXT") {
40
+ const flat = {};
41
+ const items = Array.isArray(r.data) ? r.data : [r.data];
42
+ for (const item of items) {
43
+ const s = Buffer.isBuffer(item) ? item.toString("utf8") : String(item);
44
+ const eq = s.indexOf("=");
45
+ if (eq > 0)
46
+ flat[s.slice(0, eq)] = s.slice(eq + 1);
47
+ else if (s)
48
+ flat[s] = "";
49
+ }
50
+ txt.set(r.name, flat);
51
+ }
52
+ else if (r.type === "A") {
53
+ const set = aRec.get(r.name) || new Set();
54
+ set.add(r.data);
55
+ aRec.set(r.name, set);
56
+ }
57
+ }
58
+ });
59
+ const sendQueries = () => {
60
+ for (const svc of SERVICE_TYPES) {
61
+ mdns.query([{ name: svc, type: "PTR" }]);
62
+ }
63
+ };
64
+ sendQueries();
65
+ const t1 = setTimeout(sendQueries, 600);
66
+ const t2 = setTimeout(sendQueries, 1500);
67
+ setTimeout(() => {
68
+ clearTimeout(t1);
69
+ clearTimeout(t2);
70
+ mdns.removeAllListeners("response");
71
+ mdns.destroy();
72
+ /** Collapse to one entry per physical device (keyed by hostname).
73
+ * Prefer the _pdl-datastream entry (port 9100) when available,
74
+ * since that's the port we'll actually print to. */
75
+ const byHost = new Map();
76
+ for (const [inst, svcType] of ptrs) {
77
+ const s = srv.get(inst);
78
+ if (!s)
79
+ continue;
80
+ const host = s.target.toLowerCase();
81
+ const pri = svcType.startsWith("_pdl-datastream") ? 0
82
+ : svcType.startsWith("_ipp") ? 1
83
+ : 2;
84
+ const cur = byHost.get(host);
85
+ if (!cur || pri < cur.pri)
86
+ byHost.set(host, { inst, svcType, pri });
87
+ }
88
+ const out = [];
89
+ for (const { inst, svcType } of byHost.values()) {
90
+ const s = srv.get(inst);
91
+ const ips = [...(aRec.get(s.target) || [])];
92
+ const t = txt.get(inst) || {};
93
+ const instLabel = inst.endsWith("." + svcType)
94
+ ? inst.slice(0, -1 - svcType.length)
95
+ : inst;
96
+ out.push({
97
+ instanceName: instLabel,
98
+ hostname: s.target,
99
+ addresses: ips,
100
+ port: s.port,
101
+ txt: t,
102
+ });
103
+ }
104
+ resolve(out);
105
+ }, timeoutMs);
106
+ });
107
+ }
108
+ export function isBrother(p) {
109
+ if (p.txt.usb_MFG && /brother/i.test(p.txt.usb_MFG))
110
+ return true;
111
+ if (p.txt.ty && /brother/i.test(p.txt.ty))
112
+ return true;
113
+ if (p.txt.product && /brother/i.test(p.txt.product))
114
+ return true;
115
+ if (/^BRW/i.test(p.instanceName) || /^BRW/i.test(p.hostname))
116
+ return true;
117
+ return false;
118
+ }
119
+ /** Pull a friendly model string out of TXT records. */
120
+ export function modelFromTxt(p) {
121
+ if (p.txt.ty)
122
+ return p.txt.ty;
123
+ if (p.txt.product)
124
+ return p.txt.product.replace(/^\((.*)\)$/, "$1");
125
+ return undefined;
126
+ }
127
+ /**
128
+ * True if `m` looks like the same physical printer as the installed Windows
129
+ * queue identified by `portName`. Matches by IP-address substring or by
130
+ * hostname (with or without trailing `.local`) appearing in the port string.
131
+ */
132
+ export function matchesInstalledPort(m, portName) {
133
+ if (!portName)
134
+ return false;
135
+ const p = portName.toLowerCase();
136
+ if (m.addresses.some(ip => p.includes(ip.toLowerCase())))
137
+ return true;
138
+ const host = m.hostname.toLowerCase().replace(/\.$/, "");
139
+ const bare = host.replace(/\.local$/, "");
140
+ if (host && p.includes(host))
141
+ return true;
142
+ if (bare && p.includes(bare))
143
+ return true;
144
+ return false;
145
+ }
146
+ //# sourceMappingURL=mdns.js.map
package/mdns.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdns.js","sourceRoot":"","sources":["mdns.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * mDNS discovery for network-attached Brother printers.\n *\n * Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles\n * full records by joining PTR -> SRV -> A -> TXT replies that arrive on the\n * same multicast burst.\n */\n\nimport multicastDns from \"multicast-dns\";\n\nexport interface MdnsPrinter {\n instanceName: string; /** service instance label, often the model */\n hostname: string; /** e.g. BRW4CD577B59B6A.local */\n addresses: string[]; /** IPv4 addresses */\n port: number; /** typically 9100 */\n txt: Record<string, string>; /** TXT record key=value pairs */\n}\n\nconst SERVICE_TYPES = [\n \"_pdl-datastream._tcp.local\", /** raw TCP 9100 */\n \"_ipp._tcp.local\", /** IPP port 631 */\n \"_printer._tcp.local\", /** LPR port 515 */\n];\n\n/**\n * Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and\n * _printer service types and joins the records into per-instance entries\n * keyed by service-instance name. Returns all responders (not just Brother).\n *\n * Sends each query 3 times spaced ~600ms apart to defeat single-packet loss\n * — Brother printers in particular can be flaky on the first burst.\n */\nexport async function discoverPdlPrinters(timeoutMs = 4000): Promise<MdnsPrinter[]> {\n return new Promise<MdnsPrinter[]>((resolve) => {\n const mdns = multicastDns();\n const ptrs = new Map<string, string>(); /** instance -> serviceType */\n const srv = new Map<string, { target: string; port: number }>();\n const txt = new Map<string, Record<string, string>>();\n const aRec = new Map<string, Set<string>>();\n\n mdns.on(\"response\", (response: any) => {\n const records = [...(response.answers || []), ...(response.additionals || [])];\n for (const r of records) {\n if (r.type === \"PTR\" && SERVICE_TYPES.includes(r.name)) {\n if (!ptrs.has(r.data)) ptrs.set(r.data, r.name);\n } else if (r.type === \"SRV\") {\n srv.set(r.name, { target: r.data.target, port: r.data.port });\n } else if (r.type === \"TXT\") {\n const flat: Record<string, string> = {};\n const items: any[] = Array.isArray(r.data) ? r.data : [r.data];\n for (const item of items) {\n const s = Buffer.isBuffer(item) ? item.toString(\"utf8\") : String(item);\n const eq = s.indexOf(\"=\");\n if (eq > 0) flat[s.slice(0, eq)] = s.slice(eq + 1);\n else if (s) flat[s] = \"\";\n }\n txt.set(r.name, flat);\n } else if (r.type === \"A\") {\n const set = aRec.get(r.name) || new Set<string>();\n set.add(r.data);\n aRec.set(r.name, set);\n }\n }\n });\n\n const sendQueries = () => {\n for (const svc of SERVICE_TYPES) {\n mdns.query([{ name: svc, type: \"PTR\" }]);\n }\n };\n sendQueries();\n const t1 = setTimeout(sendQueries, 600);\n const t2 = setTimeout(sendQueries, 1500);\n\n setTimeout(() => {\n clearTimeout(t1);\n clearTimeout(t2);\n mdns.removeAllListeners(\"response\");\n mdns.destroy();\n /** Collapse to one entry per physical device (keyed by hostname).\n * Prefer the _pdl-datastream entry (port 9100) when available,\n * since that's the port we'll actually print to. */\n const byHost = new Map<string, { inst: string; svcType: string; pri: number }>();\n for (const [inst, svcType] of ptrs) {\n const s = srv.get(inst);\n if (!s) continue;\n const host = s.target.toLowerCase();\n const pri = svcType.startsWith(\"_pdl-datastream\") ? 0\n : svcType.startsWith(\"_ipp\") ? 1\n : 2;\n const cur = byHost.get(host);\n if (!cur || pri < cur.pri) byHost.set(host, { inst, svcType, pri });\n }\n const out: MdnsPrinter[] = [];\n for (const { inst, svcType } of byHost.values()) {\n const s = srv.get(inst)!;\n const ips = [...(aRec.get(s.target) || [])];\n const t = txt.get(inst) || {};\n const instLabel = inst.endsWith(\".\" + svcType)\n ? inst.slice(0, -1 - svcType.length)\n : inst;\n out.push({\n instanceName: instLabel,\n hostname: s.target,\n addresses: ips,\n port: s.port,\n txt: t,\n });\n }\n resolve(out);\n }, timeoutMs);\n });\n}\n\nexport function isBrother(p: MdnsPrinter): boolean {\n if (p.txt.usb_MFG && /brother/i.test(p.txt.usb_MFG)) return true;\n if (p.txt.ty && /brother/i.test(p.txt.ty)) return true;\n if (p.txt.product && /brother/i.test(p.txt.product)) return true;\n if (/^BRW/i.test(p.instanceName) || /^BRW/i.test(p.hostname)) return true;\n return false;\n}\n\n/** Pull a friendly model string out of TXT records. */\nexport function modelFromTxt(p: MdnsPrinter): string | undefined {\n if (p.txt.ty) return p.txt.ty;\n if (p.txt.product) return p.txt.product.replace(/^\\((.*)\\)$/, \"$1\");\n return undefined;\n}\n\n/**\n * True if `m` looks like the same physical printer as the installed Windows\n * queue identified by `portName`. Matches by IP-address substring or by\n * hostname (with or without trailing `.local`) appearing in the port string.\n */\nexport function matchesInstalledPort(m: MdnsPrinter, portName: string | undefined): boolean {\n if (!portName) return false;\n const p = portName.toLowerCase();\n if (m.addresses.some(ip => p.includes(ip.toLowerCase()))) return true;\n\n const host = m.hostname.toLowerCase().replace(/\\.$/, \"\");\n const bare = host.replace(/\\.local$/, \"\");\n if (host && p.includes(host)) return true;\n if (bare && p.includes(bare)) return true;\n return false;\n}\n"]}
package/mdns.ts ADDED
@@ -0,0 +1,145 @@
1
+ /**
2
+ * mDNS discovery for network-attached Brother printers.
3
+ *
4
+ * Browses _pdl-datastream._tcp.local (port 9100, raw print) and assembles
5
+ * full records by joining PTR -> SRV -> A -> TXT replies that arrive on the
6
+ * same multicast burst.
7
+ */
8
+
9
+ import multicastDns from "multicast-dns";
10
+
11
+ export interface MdnsPrinter {
12
+ instanceName: string; /** service instance label, often the model */
13
+ hostname: string; /** e.g. BRW4CD577B59B6A.local */
14
+ addresses: string[]; /** IPv4 addresses */
15
+ port: number; /** typically 9100 */
16
+ txt: Record<string, string>; /** TXT record key=value pairs */
17
+ }
18
+
19
+ const SERVICE_TYPES = [
20
+ "_pdl-datastream._tcp.local", /** raw TCP 9100 */
21
+ "_ipp._tcp.local", /** IPP port 631 */
22
+ "_printer._tcp.local", /** LPR port 515 */
23
+ ];
24
+
25
+ /**
26
+ * Browse the LAN for printers via mDNS. Queries _pdl-datastream, _ipp, and
27
+ * _printer service types and joins the records into per-instance entries
28
+ * keyed by service-instance name. Returns all responders (not just Brother).
29
+ *
30
+ * Sends each query 3 times spaced ~600ms apart to defeat single-packet loss
31
+ * — Brother printers in particular can be flaky on the first burst.
32
+ */
33
+ export async function discoverPdlPrinters(timeoutMs = 4000): Promise<MdnsPrinter[]> {
34
+ return new Promise<MdnsPrinter[]>((resolve) => {
35
+ const mdns = multicastDns();
36
+ const ptrs = new Map<string, string>(); /** instance -> serviceType */
37
+ const srv = new Map<string, { target: string; port: number }>();
38
+ const txt = new Map<string, Record<string, string>>();
39
+ const aRec = new Map<string, Set<string>>();
40
+
41
+ mdns.on("response", (response: any) => {
42
+ const records = [...(response.answers || []), ...(response.additionals || [])];
43
+ for (const r of records) {
44
+ if (r.type === "PTR" && SERVICE_TYPES.includes(r.name)) {
45
+ if (!ptrs.has(r.data)) ptrs.set(r.data, r.name);
46
+ } else if (r.type === "SRV") {
47
+ srv.set(r.name, { target: r.data.target, port: r.data.port });
48
+ } else if (r.type === "TXT") {
49
+ const flat: Record<string, string> = {};
50
+ const items: any[] = Array.isArray(r.data) ? r.data : [r.data];
51
+ for (const item of items) {
52
+ const s = Buffer.isBuffer(item) ? item.toString("utf8") : String(item);
53
+ const eq = s.indexOf("=");
54
+ if (eq > 0) flat[s.slice(0, eq)] = s.slice(eq + 1);
55
+ else if (s) flat[s] = "";
56
+ }
57
+ txt.set(r.name, flat);
58
+ } else if (r.type === "A") {
59
+ const set = aRec.get(r.name) || new Set<string>();
60
+ set.add(r.data);
61
+ aRec.set(r.name, set);
62
+ }
63
+ }
64
+ });
65
+
66
+ const sendQueries = () => {
67
+ for (const svc of SERVICE_TYPES) {
68
+ mdns.query([{ name: svc, type: "PTR" }]);
69
+ }
70
+ };
71
+ sendQueries();
72
+ const t1 = setTimeout(sendQueries, 600);
73
+ const t2 = setTimeout(sendQueries, 1500);
74
+
75
+ setTimeout(() => {
76
+ clearTimeout(t1);
77
+ clearTimeout(t2);
78
+ mdns.removeAllListeners("response");
79
+ mdns.destroy();
80
+ /** Collapse to one entry per physical device (keyed by hostname).
81
+ * Prefer the _pdl-datastream entry (port 9100) when available,
82
+ * since that's the port we'll actually print to. */
83
+ const byHost = new Map<string, { inst: string; svcType: string; pri: number }>();
84
+ for (const [inst, svcType] of ptrs) {
85
+ const s = srv.get(inst);
86
+ if (!s) continue;
87
+ const host = s.target.toLowerCase();
88
+ const pri = svcType.startsWith("_pdl-datastream") ? 0
89
+ : svcType.startsWith("_ipp") ? 1
90
+ : 2;
91
+ const cur = byHost.get(host);
92
+ if (!cur || pri < cur.pri) byHost.set(host, { inst, svcType, pri });
93
+ }
94
+ const out: MdnsPrinter[] = [];
95
+ for (const { inst, svcType } of byHost.values()) {
96
+ const s = srv.get(inst)!;
97
+ const ips = [...(aRec.get(s.target) || [])];
98
+ const t = txt.get(inst) || {};
99
+ const instLabel = inst.endsWith("." + svcType)
100
+ ? inst.slice(0, -1 - svcType.length)
101
+ : inst;
102
+ out.push({
103
+ instanceName: instLabel,
104
+ hostname: s.target,
105
+ addresses: ips,
106
+ port: s.port,
107
+ txt: t,
108
+ });
109
+ }
110
+ resolve(out);
111
+ }, timeoutMs);
112
+ });
113
+ }
114
+
115
+ export function isBrother(p: MdnsPrinter): boolean {
116
+ if (p.txt.usb_MFG && /brother/i.test(p.txt.usb_MFG)) return true;
117
+ if (p.txt.ty && /brother/i.test(p.txt.ty)) return true;
118
+ if (p.txt.product && /brother/i.test(p.txt.product)) return true;
119
+ if (/^BRW/i.test(p.instanceName) || /^BRW/i.test(p.hostname)) return true;
120
+ return false;
121
+ }
122
+
123
+ /** Pull a friendly model string out of TXT records. */
124
+ export function modelFromTxt(p: MdnsPrinter): string | undefined {
125
+ if (p.txt.ty) return p.txt.ty;
126
+ if (p.txt.product) return p.txt.product.replace(/^\((.*)\)$/, "$1");
127
+ return undefined;
128
+ }
129
+
130
+ /**
131
+ * True if `m` looks like the same physical printer as the installed Windows
132
+ * queue identified by `portName`. Matches by IP-address substring or by
133
+ * hostname (with or without trailing `.local`) appearing in the port string.
134
+ */
135
+ export function matchesInstalledPort(m: MdnsPrinter, portName: string | undefined): boolean {
136
+ if (!portName) return false;
137
+ const p = portName.toLowerCase();
138
+ if (m.addresses.some(ip => p.includes(ip.toLowerCase()))) return true;
139
+
140
+ const host = m.hostname.toLowerCase().replace(/\.$/, "");
141
+ const bare = host.replace(/\.local$/, "");
142
+ if (host && p.includes(host)) return true;
143
+ if (bare && p.includes(bare)) return true;
144
+ return false;
145
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobfrankston/brother-label",
3
- "version": "1.1.6",
3
+ "version": "1.1.8",
4
4
  "description": "API and CLI for printing labels on Brother P-touch printers",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -45,20 +45,24 @@
45
45
  "url": "https://github.com/BobFrankston/brother-label.git"
46
46
  },
47
47
  "dependencies": {
48
- "@bobfrankston/label-core": "^0.1.11"
48
+ "@bobfrankston/label-core": "^0.1.11",
49
+ "multicast-dns": "^7.2.5"
49
50
  },
50
51
  "devDependencies": {
52
+ "@types/multicast-dns": "^7.2.4",
51
53
  "@types/node": "^25.2.1"
52
54
  },
53
55
  "publishConfig": {
54
56
  "access": "public"
55
57
  },
56
58
  ".dependencies": {
57
- "@bobfrankston/label-core": "file:../../../utils/label-core"
59
+ "@bobfrankston/label-core": "file:../../../utils/label-core",
60
+ "multicast-dns": "^7.2.5"
58
61
  },
59
62
  ".transformedSnapshot": {
60
63
  "dependencies": {
61
- "@bobfrankston/label-core": "^0.1.11"
64
+ "@bobfrankston/label-core": "^0.1.11",
65
+ "multicast-dns": "^7.2.5"
62
66
  }
63
67
  }
64
68
  }
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=probe-ql820.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe-ql820.d.ts","sourceRoot":"","sources":["probe-ql820.ts"],"names":[],"mappings":""}
package/probe-ql820.js ADDED
@@ -0,0 +1,94 @@
1
+ // Probe a QL-820 over TCP 9100. Sends:
2
+ // 200x NUL flush command buffer
3
+ // ESC @ initialize
4
+ // ESC i a 01 switch to raster mode
5
+ // ESC i S status request -> returns 32-byte status block
6
+ // Documents what's loaded (media width, error state, model).
7
+ import * as net from "net";
8
+ const HOST = process.argv[2];
9
+ const PORT = 9100;
10
+ if (!HOST) {
11
+ console.error("usage: probe-ql820 <host-or-ip>");
12
+ process.exit(1);
13
+ }
14
+ const FLUSH = Buffer.alloc(200, 0);
15
+ const INIT = Buffer.from([0x1b, 0x40]); // ESC @
16
+ const MODE_RASTER = Buffer.from([0x1b, 0x69, 0x61, 0x01]); // ESC i a 01
17
+ const STATUS_REQUEST = Buffer.from([0x1b, 0x69, 0x53]); // ESC i S
18
+ const sock = net.createConnection({ host: HOST, port: PORT, timeout: 15000 });
19
+ const chunks = [];
20
+ sock.on("connect", () => {
21
+ console.log(`connected to ${HOST}:${PORT}`);
22
+ sock.write(FLUSH);
23
+ sock.write(INIT);
24
+ sock.write(MODE_RASTER);
25
+ sock.write(STATUS_REQUEST);
26
+ console.log("sent: 200xNUL, ESC @, ESC i a 01, ESC i S — waiting for 32-byte reply...");
27
+ });
28
+ sock.on("data", (buf) => {
29
+ chunks.push(buf);
30
+ const total = Buffer.concat(chunks);
31
+ console.log(`received ${buf.length} bytes (total ${total.length})`);
32
+ if (total.length >= 32) {
33
+ decode(total.subarray(0, 32));
34
+ sock.end();
35
+ }
36
+ });
37
+ sock.on("timeout", () => {
38
+ console.error("timeout — no 32-byte status reply");
39
+ if (chunks.length > 0) {
40
+ const t = Buffer.concat(chunks);
41
+ console.error(`partial: ${t.length} bytes: ${t.toString("hex")}`);
42
+ }
43
+ sock.destroy();
44
+ process.exit(2);
45
+ });
46
+ sock.on("error", (e) => {
47
+ console.error("socket error:", e.message);
48
+ process.exit(1);
49
+ });
50
+ sock.on("close", () => {
51
+ if (chunks.length === 0)
52
+ console.error("closed with no data");
53
+ });
54
+ function decode(b) {
55
+ const modelMap = {
56
+ 0x4f: "QL-500/550", 0x31: "QL-560", 0x32: "QL-570", 0x33: "QL-580N",
57
+ 0x51: "QL-650TD", 0x35: "QL-700", 0x34: "QL-1050", 0x36: "QL-1060N",
58
+ 0x37: "QL-720NW", 0x38: "QL-710W", 0x39: "QL-810W", 0x41: "QL-820NWB",
59
+ 0x43: "QL-1100", 0x44: "QL-1110NWB", 0x45: "QL-1115NWB",
60
+ };
61
+ const mediaTypeMap = {
62
+ 0x00: "no media", 0x0a: "continuous tape", 0x0b: "die-cut labels",
63
+ };
64
+ console.log("\n--- 32-byte status block ---");
65
+ console.log("hex:", (b.toString("hex").match(/../g) || []).join(" "));
66
+ console.log(` print head mark: 0x${b[0].toString(16)} (expect 0x80)`);
67
+ console.log(` size (32): ${b[1]}`);
68
+ console.log(` brother code 'B': 0x${b[2].toString(16)}`);
69
+ console.log(` series code: 0x${b[3].toString(16)}`);
70
+ console.log(` model code: 0x${b[4].toString(16)} -> ${modelMap[b[4]] ?? "unknown"}`);
71
+ console.log(` country code: 0x${b[5].toString(16)}`);
72
+ console.log(` battery (820): 0x${b[6].toString(16)}`);
73
+ console.log(` extended err: 0x${b[7].toString(16)}`);
74
+ console.log(` error info 1: 0x${b[8].toString(16)}`);
75
+ console.log(` error info 2: 0x${b[9].toString(16)}`);
76
+ console.log(` media width: ${b[10]} mm`);
77
+ console.log(` media type: 0x${b[11].toString(16)} -> ${mediaTypeMap[b[11]] ?? "?"}`);
78
+ console.log(` number of colors: 0x${b[12].toString(16)}`);
79
+ console.log(` fonts: 0x${b[13].toString(16)}`);
80
+ console.log(` jp font: 0x${b[14].toString(16)}`);
81
+ console.log(` mode: 0x${b[15].toString(16)}`);
82
+ console.log(` density: 0x${b[16].toString(16)}`);
83
+ console.log(` media length: ${b[17]} mm`);
84
+ console.log(` status type: 0x${b[18].toString(16)}`);
85
+ console.log(` phase type: 0x${b[19].toString(16)}`);
86
+ console.log(` phase number: ${(b[20] << 8) | b[21]}`);
87
+ console.log(` notification: 0x${b[22].toString(16)}`);
88
+ console.log(` expansion area: 0x${b[23].toString(16)}`);
89
+ console.log(` tape color info: 0x${b[24].toString(16)}`);
90
+ console.log(` text color info: 0x${b[25].toString(16)}`);
91
+ console.log(` hw setting: ${b.subarray(26, 30).toString("hex")}`);
92
+ console.log(` reserved: ${b.subarray(30, 32).toString("hex")}`);
93
+ }
94
+ //# sourceMappingURL=probe-ql820.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"probe-ql820.js","sourceRoot":"","sources":["probe-ql820.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,2CAA2C;AAC3C,iCAAiC;AACjC,4CAA4C;AAC5C,qEAAqE;AACrE,6DAA6D;AAE7D,OAAO,KAAK,GAAG,MAAM,KAAK,CAAC;AAE3B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAC7B,MAAM,IAAI,GAAG,IAAI,CAAC;AAClB,IAAI,CAAC,IAAI,EAAE,CAAC;IACR,OAAO,CAAC,KAAK,CAAC,iCAAiC,CAAC,CAAC;IACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC;AAED,MAAM,KAAK,GAAY,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;AAC5C,MAAM,IAAI,GAAa,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAc,QAAQ;AACvE,MAAM,WAAW,GAAM,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAE,aAAa;AAC5E,MAAM,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,CAAQ,UAAU;AAEzE,MAAM,IAAI,GAAG,GAAG,CAAC,gBAAgB,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;AAC9E,MAAM,MAAM,GAAa,EAAE,CAAC;AAE5B,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC;IAC5C,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAClB,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACjB,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;IACxB,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;IAC3B,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;AAC5F,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,GAAW,EAAE,EAAE;IAC5B,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,CAAC,MAAM,iBAAiB,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACpE,IAAI,KAAK,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QACrB,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;IACf,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;IACpB,OAAO,CAAC,KAAK,CAAC,mCAAmC,CAAC,CAAC;IACnD,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpB,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,MAAM,WAAW,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,IAAI,CAAC,OAAO,EAAE,CAAC;IACf,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE;IACnB,OAAO,CAAC,KAAK,CAAC,eAAe,EAAG,CAAW,CAAC,OAAO,CAAC,CAAC;IACrD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;IAClB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,KAAK,CAAC,qBAAqB,CAAC,CAAC;AAClE,CAAC,CAAC,CAAC;AAEH,SAAS,MAAM,CAAC,CAAS;IACrB,MAAM,QAAQ,GAA2B;QACrC,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS;QACnE,IAAI,EAAE,UAAU,EAAI,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU;QACrE,IAAI,EAAE,UAAU,EAAI,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,WAAW;QACvE,IAAI,EAAE,SAAS,EAAK,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,YAAY;KAC7D,CAAC;IACF,MAAM,YAAY,GAA2B;QACzC,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,gBAAgB;KACpE,CAAC;IACF,OAAO,CAAC,GAAG,CAAC,gCAAgC,CAAC,CAAC;IAC9C,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,gBAAgB,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,SAAS,EAAE,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC3D,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;IAC7F,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;IAC5D,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAC1E,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;AAC9E,CAAC","sourcesContent":["// Probe a QL-820 over TCP 9100. Sends:\n// 200x NUL flush command buffer\n// ESC @ initialize\n// ESC i a 01 switch to raster mode\n// ESC i S status request -> returns 32-byte status block\n// Documents what's loaded (media width, error state, model).\n\nimport * as net from \"net\";\n\nconst HOST = process.argv[2];\nconst PORT = 9100;\nif (!HOST) {\n console.error(\"usage: probe-ql820 <host-or-ip>\");\n process.exit(1);\n}\n\nconst FLUSH = Buffer.alloc(200, 0);\nconst INIT = Buffer.from([0x1b, 0x40]); // ESC @\nconst MODE_RASTER = Buffer.from([0x1b, 0x69, 0x61, 0x01]); // ESC i a 01\nconst STATUS_REQUEST = Buffer.from([0x1b, 0x69, 0x53]); // ESC i S\n\nconst sock = net.createConnection({ host: HOST, port: PORT, timeout: 15000 });\nconst chunks: Buffer[] = [];\n\nsock.on(\"connect\", () => {\n console.log(`connected to ${HOST}:${PORT}`);\n sock.write(FLUSH);\n sock.write(INIT);\n sock.write(MODE_RASTER);\n sock.write(STATUS_REQUEST);\n console.log(\"sent: 200xNUL, ESC @, ESC i a 01, ESC i S — waiting for 32-byte reply...\");\n});\n\nsock.on(\"data\", (buf: Buffer) => {\n chunks.push(buf);\n const total = Buffer.concat(chunks);\n console.log(`received ${buf.length} bytes (total ${total.length})`);\n if (total.length >= 32) {\n decode(total.subarray(0, 32));\n sock.end();\n }\n});\n\nsock.on(\"timeout\", () => {\n console.error(\"timeout — no 32-byte status reply\");\n if (chunks.length > 0) {\n const t = Buffer.concat(chunks);\n console.error(`partial: ${t.length} bytes: ${t.toString(\"hex\")}`);\n }\n sock.destroy();\n process.exit(2);\n});\n\nsock.on(\"error\", (e) => {\n console.error(\"socket error:\", (e as Error).message);\n process.exit(1);\n});\n\nsock.on(\"close\", () => {\n if (chunks.length === 0) console.error(\"closed with no data\");\n});\n\nfunction decode(b: Buffer): void {\n const modelMap: Record<number, string> = {\n 0x4f: \"QL-500/550\", 0x31: \"QL-560\", 0x32: \"QL-570\", 0x33: \"QL-580N\",\n 0x51: \"QL-650TD\", 0x35: \"QL-700\", 0x34: \"QL-1050\", 0x36: \"QL-1060N\",\n 0x37: \"QL-720NW\", 0x38: \"QL-710W\", 0x39: \"QL-810W\", 0x41: \"QL-820NWB\",\n 0x43: \"QL-1100\", 0x44: \"QL-1110NWB\", 0x45: \"QL-1115NWB\",\n };\n const mediaTypeMap: Record<number, string> = {\n 0x00: \"no media\", 0x0a: \"continuous tape\", 0x0b: \"die-cut labels\",\n };\n console.log(\"\\n--- 32-byte status block ---\");\n console.log(\"hex:\", (b.toString(\"hex\").match(/../g) || []).join(\" \"));\n console.log(` print head mark: 0x${b[0].toString(16)} (expect 0x80)`);\n console.log(` size (32): ${b[1]}`);\n console.log(` brother code 'B': 0x${b[2].toString(16)}`);\n console.log(` series code: 0x${b[3].toString(16)}`);\n console.log(` model code: 0x${b[4].toString(16)} -> ${modelMap[b[4]] ?? \"unknown\"}`);\n console.log(` country code: 0x${b[5].toString(16)}`);\n console.log(` battery (820): 0x${b[6].toString(16)}`);\n console.log(` extended err: 0x${b[7].toString(16)}`);\n console.log(` error info 1: 0x${b[8].toString(16)}`);\n console.log(` error info 2: 0x${b[9].toString(16)}`);\n console.log(` media width: ${b[10]} mm`);\n console.log(` media type: 0x${b[11].toString(16)} -> ${mediaTypeMap[b[11]] ?? \"?\"}`);\n console.log(` number of colors: 0x${b[12].toString(16)}`);\n console.log(` fonts: 0x${b[13].toString(16)}`);\n console.log(` jp font: 0x${b[14].toString(16)}`);\n console.log(` mode: 0x${b[15].toString(16)}`);\n console.log(` density: 0x${b[16].toString(16)}`);\n console.log(` media length: ${b[17]} mm`);\n console.log(` status type: 0x${b[18].toString(16)}`);\n console.log(` phase type: 0x${b[19].toString(16)}`);\n console.log(` phase number: ${(b[20] << 8) | b[21]}`);\n console.log(` notification: 0x${b[22].toString(16)}`);\n console.log(` expansion area: 0x${b[23].toString(16)}`);\n console.log(` tape color info: 0x${b[24].toString(16)}`);\n console.log(` text color info: 0x${b[25].toString(16)}`);\n console.log(` hw setting: ${b.subarray(26, 30).toString(\"hex\")}`);\n console.log(` reserved: ${b.subarray(30, 32).toString(\"hex\")}`);\n}\n"]}
package/probe-ql820.ts ADDED
@@ -0,0 +1,102 @@
1
+ // Probe a QL-820 over TCP 9100. Sends:
2
+ // 200x NUL flush command buffer
3
+ // ESC @ initialize
4
+ // ESC i a 01 switch to raster mode
5
+ // ESC i S status request -> returns 32-byte status block
6
+ // Documents what's loaded (media width, error state, model).
7
+
8
+ import * as net from "net";
9
+
10
+ const HOST = process.argv[2];
11
+ const PORT = 9100;
12
+ if (!HOST) {
13
+ console.error("usage: probe-ql820 <host-or-ip>");
14
+ process.exit(1);
15
+ }
16
+
17
+ const FLUSH = Buffer.alloc(200, 0);
18
+ const INIT = Buffer.from([0x1b, 0x40]); // ESC @
19
+ const MODE_RASTER = Buffer.from([0x1b, 0x69, 0x61, 0x01]); // ESC i a 01
20
+ const STATUS_REQUEST = Buffer.from([0x1b, 0x69, 0x53]); // ESC i S
21
+
22
+ const sock = net.createConnection({ host: HOST, port: PORT, timeout: 15000 });
23
+ const chunks: Buffer[] = [];
24
+
25
+ sock.on("connect", () => {
26
+ console.log(`connected to ${HOST}:${PORT}`);
27
+ sock.write(FLUSH);
28
+ sock.write(INIT);
29
+ sock.write(MODE_RASTER);
30
+ sock.write(STATUS_REQUEST);
31
+ console.log("sent: 200xNUL, ESC @, ESC i a 01, ESC i S — waiting for 32-byte reply...");
32
+ });
33
+
34
+ sock.on("data", (buf: Buffer) => {
35
+ chunks.push(buf);
36
+ const total = Buffer.concat(chunks);
37
+ console.log(`received ${buf.length} bytes (total ${total.length})`);
38
+ if (total.length >= 32) {
39
+ decode(total.subarray(0, 32));
40
+ sock.end();
41
+ }
42
+ });
43
+
44
+ sock.on("timeout", () => {
45
+ console.error("timeout — no 32-byte status reply");
46
+ if (chunks.length > 0) {
47
+ const t = Buffer.concat(chunks);
48
+ console.error(`partial: ${t.length} bytes: ${t.toString("hex")}`);
49
+ }
50
+ sock.destroy();
51
+ process.exit(2);
52
+ });
53
+
54
+ sock.on("error", (e) => {
55
+ console.error("socket error:", (e as Error).message);
56
+ process.exit(1);
57
+ });
58
+
59
+ sock.on("close", () => {
60
+ if (chunks.length === 0) console.error("closed with no data");
61
+ });
62
+
63
+ function decode(b: Buffer): void {
64
+ const modelMap: Record<number, string> = {
65
+ 0x4f: "QL-500/550", 0x31: "QL-560", 0x32: "QL-570", 0x33: "QL-580N",
66
+ 0x51: "QL-650TD", 0x35: "QL-700", 0x34: "QL-1050", 0x36: "QL-1060N",
67
+ 0x37: "QL-720NW", 0x38: "QL-710W", 0x39: "QL-810W", 0x41: "QL-820NWB",
68
+ 0x43: "QL-1100", 0x44: "QL-1110NWB", 0x45: "QL-1115NWB",
69
+ };
70
+ const mediaTypeMap: Record<number, string> = {
71
+ 0x00: "no media", 0x0a: "continuous tape", 0x0b: "die-cut labels",
72
+ };
73
+ console.log("\n--- 32-byte status block ---");
74
+ console.log("hex:", (b.toString("hex").match(/../g) || []).join(" "));
75
+ console.log(` print head mark: 0x${b[0].toString(16)} (expect 0x80)`);
76
+ console.log(` size (32): ${b[1]}`);
77
+ console.log(` brother code 'B': 0x${b[2].toString(16)}`);
78
+ console.log(` series code: 0x${b[3].toString(16)}`);
79
+ console.log(` model code: 0x${b[4].toString(16)} -> ${modelMap[b[4]] ?? "unknown"}`);
80
+ console.log(` country code: 0x${b[5].toString(16)}`);
81
+ console.log(` battery (820): 0x${b[6].toString(16)}`);
82
+ console.log(` extended err: 0x${b[7].toString(16)}`);
83
+ console.log(` error info 1: 0x${b[8].toString(16)}`);
84
+ console.log(` error info 2: 0x${b[9].toString(16)}`);
85
+ console.log(` media width: ${b[10]} mm`);
86
+ console.log(` media type: 0x${b[11].toString(16)} -> ${mediaTypeMap[b[11]] ?? "?"}`);
87
+ console.log(` number of colors: 0x${b[12].toString(16)}`);
88
+ console.log(` fonts: 0x${b[13].toString(16)}`);
89
+ console.log(` jp font: 0x${b[14].toString(16)}`);
90
+ console.log(` mode: 0x${b[15].toString(16)}`);
91
+ console.log(` density: 0x${b[16].toString(16)}`);
92
+ console.log(` media length: ${b[17]} mm`);
93
+ console.log(` status type: 0x${b[18].toString(16)}`);
94
+ console.log(` phase type: 0x${b[19].toString(16)}`);
95
+ console.log(` phase number: ${(b[20] << 8) | b[21]}`);
96
+ console.log(` notification: 0x${b[22].toString(16)}`);
97
+ console.log(` expansion area: 0x${b[23].toString(16)}`);
98
+ console.log(` tape color info: 0x${b[24].toString(16)}`);
99
+ console.log(` text color info: 0x${b[25].toString(16)}`);
100
+ console.log(` hw setting: ${b.subarray(26, 30).toString("hex")}`);
101
+ console.log(` reserved: ${b.subarray(30, 32).toString("hex")}`);
102
+ }