@dev-pi2pie/word-counter 0.1.5-canary.4 → 0.1.5

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/dist/esm/bin.mjs CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  import { createRequire } from "node:module";
4
4
  import { Command, Option } from "commander";
5
+ import { readFile, readdir, stat } from "node:fs/promises";
6
+ import { basename, dirname, extname, join, relative, resolve, sep, win32 } from "node:path";
5
7
  import { closeSync, createWriteStream, existsSync, mkdirSync, openSync, readFileSync, statSync } from "node:fs";
6
- import { basename, dirname, extname, join, relative, resolve, sep } from "node:path";
7
8
  import os from "node:os";
8
9
  import { parseDocument } from "yaml";
9
10
  import { fileURLToPath } from "node:url";
10
- import { readFile, readdir, stat } from "node:fs/promises";
11
11
  //#region \0rolldown/runtime.js
12
12
  var __create = Object.create;
13
13
  var __defProp = Object.defineProperty;
@@ -31,6 +31,884 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
31
31
  enumerable: true
32
32
  }) : target, mod));
33
33
  //#endregion
34
+ //#region src/cli/config/schema.ts
35
+ const CONFIG_FILE_BASENAME = "wc-intl-seg.config";
36
+ const CONFIG_FORMAT_PRIORITY = [
37
+ "toml",
38
+ "jsonc",
39
+ "json"
40
+ ];
41
+ CONFIG_FORMAT_PRIORITY.map((format) => `${CONFIG_FILE_BASENAME}.${format}`);
42
+ const CONFIG_DETECTOR_VALUES = ["regex", "wasm"];
43
+ const CONFIG_CONTENT_GATE_MODE_VALUES = [
44
+ "default",
45
+ "strict",
46
+ "loose",
47
+ "off"
48
+ ];
49
+ const CONFIG_PATH_MODE_VALUES = ["auto", "manual"];
50
+ const CONFIG_PROGRESS_MODE_VALUES = [
51
+ "auto",
52
+ "on",
53
+ "off"
54
+ ];
55
+ const CONFIG_LOG_LEVEL_VALUES = ["info", "debug"];
56
+ const CONFIG_LOG_VERBOSITY_VALUES = ["compact", "verbose"];
57
+ const CONFIG_TOTAL_OF_VALUES = [
58
+ "words",
59
+ "emoji",
60
+ "symbols",
61
+ "punctuation",
62
+ "whitespace"
63
+ ];
64
+ //#endregion
65
+ //#region src/cli/config/discover.ts
66
+ async function fileExists(path) {
67
+ try {
68
+ return (await stat(path)).isFile();
69
+ } catch {
70
+ return false;
71
+ }
72
+ }
73
+ function createIgnoredSiblingNote(scope, selectedPath, ignoredSiblingPaths) {
74
+ if (ignoredSiblingPaths.length === 0) return;
75
+ return [`Using ${scope} config file "${selectedPath}".`, `Ignoring lower-priority sibling config files: ${ignoredSiblingPaths.join(", ")}.`].join(" ");
76
+ }
77
+ function resolveUserConfigDirectory(options = {}) {
78
+ return resolveUserConfigDirectories(options)[0]?.directory;
79
+ }
80
+ function resolveUserConfigDirectories(options = {}) {
81
+ const platform = options.platform ?? process.platform;
82
+ const env = options.env ?? process.env;
83
+ const homeDir = env.HOME ?? env.USERPROFILE;
84
+ if (platform === "win32") {
85
+ const candidates = [];
86
+ if (homeDir) candidates.push({
87
+ directory: win32.join(homeDir, ".config"),
88
+ kind: "primary"
89
+ });
90
+ if (env.APPDATA) candidates.push({
91
+ directory: env.APPDATA,
92
+ kind: "legacy-windows"
93
+ });
94
+ return candidates;
95
+ }
96
+ if (platform === "darwin") {
97
+ const candidates = [];
98
+ if (env.XDG_CONFIG_HOME) candidates.push({
99
+ directory: env.XDG_CONFIG_HOME,
100
+ kind: "primary"
101
+ });
102
+ if (homeDir) {
103
+ candidates.push({
104
+ directory: join(homeDir, ".config"),
105
+ kind: "primary"
106
+ });
107
+ candidates.push({
108
+ directory: join(homeDir, "Library", "Application Support"),
109
+ kind: "legacy-macos"
110
+ });
111
+ }
112
+ return candidates;
113
+ }
114
+ const directory = env.XDG_CONFIG_HOME ?? (homeDir ? join(homeDir, ".config") : void 0);
115
+ return directory ? [{
116
+ directory,
117
+ kind: "primary"
118
+ }] : [];
119
+ }
120
+ async function discoverConfigFileInDirectory(directory, scope) {
121
+ const existing = [];
122
+ for (const format of CONFIG_FORMAT_PRIORITY) {
123
+ const path = join(directory, `${CONFIG_FILE_BASENAME}.${format}`);
124
+ if (await fileExists(path)) existing.push({
125
+ format,
126
+ path
127
+ });
128
+ }
129
+ if (existing.length === 0) return;
130
+ const selected = existing[0];
131
+ const ignoredSiblingPaths = existing.slice(1).map((item) => item.path);
132
+ const note = createIgnoredSiblingNote(scope, selected.path, ignoredSiblingPaths);
133
+ return {
134
+ scope,
135
+ directory,
136
+ path: selected.path,
137
+ format: selected.format,
138
+ ignoredSiblingPaths,
139
+ notes: note ? [note] : []
140
+ };
141
+ }
142
+ async function discoverConfigFiles(options = {}) {
143
+ const cwd = options.cwd ?? process.cwd();
144
+ const userDirectories = resolveUserConfigDirectories(options);
145
+ const currentWorkingDirectory = await discoverConfigFileInDirectory(cwd, "cwd");
146
+ let user;
147
+ const discoveredFallbackUsers = [];
148
+ for (const candidate of userDirectories) {
149
+ const discovered = await discoverConfigFileInDirectory(candidate.directory, "user");
150
+ if (!discovered) continue;
151
+ if (!user) {
152
+ user = discovered;
153
+ if (candidate.kind === "legacy-macos") user.notes.push(`Using legacy macOS user config location "${discovered.directory}". Migrate this config to "${resolveUserConfigDirectory(options)}" to follow the current default path.`);
154
+ if (candidate.kind === "legacy-windows") user.notes.push(`Using legacy Windows user config location "${discovered.directory}". Migrate this config to "${resolveUserConfigDirectory(options)}" to follow the current default path.`);
155
+ continue;
156
+ }
157
+ discoveredFallbackUsers.push(discovered);
158
+ }
159
+ if (user) for (const fallback of discoveredFallbackUsers) user.notes.push(`Ignoring fallback user config file "${fallback.path}" because a higher-priority user config file was found at "${user.path}".`);
160
+ return {
161
+ ...user ? { user } : {},
162
+ ...currentWorkingDirectory ? { cwd: currentWorkingDirectory } : {}
163
+ };
164
+ }
165
+ //#endregion
166
+ //#region src/cli/config/apply.ts
167
+ function withConfigQuietSkips(currentQuietSkips, skippedFiles) {
168
+ if (skippedFiles === void 0) return currentQuietSkips;
169
+ return skippedFiles ? false : true;
170
+ }
171
+ function applyConfigToCountOptions(options, config, sources) {
172
+ const next = { ...options };
173
+ if (!sources.detector && config.detector !== void 0) next.detector = config.detector;
174
+ if (!sources.contentGate && config.contentGate?.mode !== void 0) next.contentGate = config.contentGate.mode;
175
+ if (!sources.pathMode && config.path?.mode !== void 0) next.pathMode = config.path.mode;
176
+ if (!sources.recursive && config.path?.recursive !== void 0) next.recursive = config.path.recursive;
177
+ next.pathDetectBinary = config.path?.detectBinary ?? next.pathDetectBinary ?? true;
178
+ if (!sources.includeExt && config.path?.includeExtensions !== void 0) next.includeExt = [...config.path.includeExtensions];
179
+ if (!sources.excludeExt && config.path?.excludeExtensions !== void 0) next.excludeExt = [...config.path.excludeExtensions];
180
+ if (!sources.totalOf && config.output?.totalOf !== void 0) next.totalOf = [...config.output.totalOf];
181
+ if (!sources.debug && config.logging?.level !== void 0) next.debug = config.logging.level === "debug";
182
+ const debugEnabled = next.debug === true;
183
+ if (debugEnabled && !sources.verbose && config.logging?.verbosity !== void 0) next.verbose = config.logging.verbosity === "verbose";
184
+ if (debugEnabled && !sources.debugReport && config.reporting?.debugReport?.path !== void 0) next.debugReport = config.reporting.debugReport.path;
185
+ const debugReportEnabled = next.debugReport !== void 0 && next.debugReport !== false;
186
+ if (debugEnabled && debugReportEnabled && !sources.debugReportTee && config.reporting?.debugReport?.tee !== void 0) next.debugReportTee = config.reporting.debugReport.tee;
187
+ if (!sources.progress && config.progress?.mode !== void 0) next.progressMode = config.progress.mode;
188
+ if (!sources.quietSkips) next.quietSkips = withConfigQuietSkips(next.quietSkips, config.reporting?.skippedFiles);
189
+ return next;
190
+ }
191
+ function applyConfigToInspectInvocation(validated, config) {
192
+ const next = {
193
+ ...validated,
194
+ includeExt: [...validated.includeExt],
195
+ excludeExt: [...validated.excludeExt],
196
+ sources: { ...validated.sources }
197
+ };
198
+ const detectorFromConfig = config.inspect?.detector ?? config.detector;
199
+ if (!next.sources.detector && detectorFromConfig !== void 0) next.detector = detectorFromConfig;
200
+ const contentGateFromConfig = config.inspect?.contentGate?.mode ?? config.contentGate?.mode;
201
+ if (!next.sources.contentGate && contentGateFromConfig !== void 0) next.contentGateMode = contentGateFromConfig;
202
+ if (!next.sources.pathMode && config.path?.mode !== void 0) next.pathMode = config.path.mode;
203
+ if (!next.sources.recursive && config.path?.recursive !== void 0) next.recursive = config.path.recursive;
204
+ next.pathDetectBinary = config.path?.detectBinary ?? next.pathDetectBinary ?? true;
205
+ if (!next.sources.includeExt && config.path?.includeExtensions !== void 0) next.includeExt = [...config.path.includeExtensions];
206
+ if (!next.sources.excludeExt && config.path?.excludeExtensions !== void 0) next.excludeExt = [...config.path.excludeExtensions];
207
+ return next;
208
+ }
209
+ //#endregion
210
+ //#region src/cli/total-of.ts
211
+ const TOTAL_OF_PARTS = Object.freeze([
212
+ "words",
213
+ "emoji",
214
+ "symbols",
215
+ "punctuation",
216
+ "whitespace"
217
+ ]);
218
+ const TOTAL_OF_PART_ALIASES = {
219
+ word: "words",
220
+ words: "words",
221
+ emoji: "emoji",
222
+ emojis: "emoji",
223
+ symbol: "symbols",
224
+ symbols: "symbols",
225
+ punction: "punctuation",
226
+ punctuation: "punctuation",
227
+ whitespace: "whitespace"
228
+ };
229
+ function createTotalOfCounts() {
230
+ return {
231
+ words: 0,
232
+ emoji: 0,
233
+ symbols: 0,
234
+ punctuation: 0,
235
+ whitespace: 0
236
+ };
237
+ }
238
+ function collectNonWordCounts(target, nonWords) {
239
+ if (!nonWords) return;
240
+ target.emoji += nonWords.counts.emoji;
241
+ target.symbols += nonWords.counts.symbols;
242
+ target.punctuation += nonWords.counts.punctuation;
243
+ target.whitespace += nonWords.counts.whitespace ?? 0;
244
+ }
245
+ function collectFromWordCounterResult(result) {
246
+ const counts = createTotalOfCounts();
247
+ counts.words += result.counts?.words ?? result.total;
248
+ if (result.breakdown.mode === "collector") {
249
+ collectNonWordCounts(counts, result.breakdown.nonWords);
250
+ return counts;
251
+ }
252
+ for (const item of result.breakdown.items) collectNonWordCounts(counts, item.nonWords);
253
+ return counts;
254
+ }
255
+ function collectTotalOfCounts(result) {
256
+ if (!("section" in result)) return collectFromWordCounterResult(result);
257
+ const counts = createTotalOfCounts();
258
+ for (const item of result.items) {
259
+ const itemCounts = collectFromWordCounterResult(item.result);
260
+ for (const part of TOTAL_OF_PARTS) counts[part] += itemCounts[part];
261
+ }
262
+ return counts;
263
+ }
264
+ function parseTotalOfToken(token) {
265
+ const canonical = TOTAL_OF_PART_ALIASES[token.trim().toLowerCase()];
266
+ if (canonical) return canonical;
267
+ throw new Error(`Invalid --total-of part: ${token}. Allowed: ${TOTAL_OF_PARTS.join(", ")}.`);
268
+ }
269
+ function parseTotalOfOption(value) {
270
+ const rawTokens = value.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
271
+ if (rawTokens.length === 0) throw new Error(`Invalid --total-of value: "${value}". Use comma-separated parts from: ${TOTAL_OF_PARTS.join(", ")}.`);
272
+ const parts = [];
273
+ const seen = /* @__PURE__ */ new Set();
274
+ for (const token of rawTokens) {
275
+ const parsed = parseTotalOfToken(token);
276
+ if (seen.has(parsed)) continue;
277
+ seen.add(parsed);
278
+ parts.push(parsed);
279
+ }
280
+ return parts;
281
+ }
282
+ function requiresNonWordCollection(parts) {
283
+ if (!parts || parts.length === 0) return false;
284
+ return parts.some((part) => part !== "words");
285
+ }
286
+ function requiresWhitespaceCollection(parts) {
287
+ if (!parts || parts.length === 0) return false;
288
+ return parts.includes("whitespace");
289
+ }
290
+ function resolveTotalOfOverride(result, parts) {
291
+ if (!parts || parts.length === 0) return;
292
+ const counts = collectTotalOfCounts(result);
293
+ let total = 0;
294
+ for (const part of parts) total += counts[part];
295
+ return {
296
+ parts: [...parts],
297
+ total
298
+ };
299
+ }
300
+ function formatTotalOfParts(parts) {
301
+ return parts.join(", ");
302
+ }
303
+ //#endregion
304
+ //#region src/cli/config/errors.ts
305
+ var ConfigValidationError = class extends Error {
306
+ sourceLabel;
307
+ path;
308
+ constructor(sourceLabel, path, message) {
309
+ const suffix = path.length > 0 ? ` at "${path.join(".")}"` : "";
310
+ super(`Invalid config in ${sourceLabel}${suffix}: ${message}`);
311
+ this.name = "ConfigValidationError";
312
+ this.sourceLabel = sourceLabel;
313
+ this.path = [...path];
314
+ }
315
+ };
316
+ var ConfigParseError = class extends Error {
317
+ sourceLabel;
318
+ format;
319
+ constructor(sourceLabel, format, message) {
320
+ super(`Invalid ${format.toUpperCase()} config in ${sourceLabel}: ${message}`);
321
+ this.name = "ConfigParseError";
322
+ this.sourceLabel = sourceLabel;
323
+ this.format = format;
324
+ }
325
+ };
326
+ //#endregion
327
+ //#region src/cli/config/normalize.ts
328
+ function isRecord$1(value) {
329
+ return typeof value === "object" && value !== null && !Array.isArray(value);
330
+ }
331
+ function createConfigError(sourceLabel, path, message) {
332
+ return new ConfigValidationError(sourceLabel, path, message);
333
+ }
334
+ function ensureObject$1(value, sourceLabel, path) {
335
+ if (value === void 0) return;
336
+ if (!isRecord$1(value)) throw createConfigError(sourceLabel, path, "expected an object.");
337
+ return value;
338
+ }
339
+ function rejectUnknownKeys(value, allowedKeys, sourceLabel, path) {
340
+ for (const key of Object.keys(value)) if (!allowedKeys.includes(key)) throw createConfigError(sourceLabel, [...path, key], "unknown key.");
341
+ }
342
+ function parseBoolean(value, sourceLabel, path) {
343
+ if (value === void 0) return;
344
+ if (typeof value !== "boolean") throw createConfigError(sourceLabel, path, "expected a boolean.");
345
+ return value;
346
+ }
347
+ function parseString(value, sourceLabel, path) {
348
+ if (value === void 0) return;
349
+ if (typeof value !== "string") throw createConfigError(sourceLabel, path, "expected a string.");
350
+ return value;
351
+ }
352
+ function parseStringArray(value, sourceLabel, path) {
353
+ if (value === void 0) return;
354
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw createConfigError(sourceLabel, path, "expected an array of strings.");
355
+ return [...value];
356
+ }
357
+ function parseEnum(value, allowedValues, sourceLabel, path) {
358
+ if (value === void 0) return;
359
+ if (typeof value !== "string" || !allowedValues.includes(value)) throw createConfigError(sourceLabel, path, `expected one of: ${allowedValues.map((item) => `"${item}"`).join(", ")}.`);
360
+ return value;
361
+ }
362
+ function parseTotalOf(value, sourceLabel, path) {
363
+ if (value === void 0) return;
364
+ if (!Array.isArray(value) || value.some((item) => typeof item !== "string")) throw createConfigError(sourceLabel, path, "expected an array of strings.");
365
+ const parts = [];
366
+ for (const item of value) {
367
+ const parsed = parseEnum(item, CONFIG_TOTAL_OF_VALUES, sourceLabel, path);
368
+ if (parsed && !parts.includes(parsed)) parts.push(parsed);
369
+ }
370
+ return parts;
371
+ }
372
+ function normalizeInspectConfig(value, sourceLabel) {
373
+ const section = ensureObject$1(value, sourceLabel, ["inspect"]);
374
+ if (!section) return;
375
+ rejectUnknownKeys(section, ["detector", "contentGate"], sourceLabel, ["inspect"]);
376
+ const detector = parseEnum(section.detector, CONFIG_DETECTOR_VALUES, sourceLabel, ["inspect", "detector"]);
377
+ const contentGateSection = ensureObject$1(section.contentGate, sourceLabel, ["inspect", "contentGate"]);
378
+ if (contentGateSection) rejectUnknownKeys(contentGateSection, ["mode"], sourceLabel, ["inspect", "contentGate"]);
379
+ const mode = parseEnum(contentGateSection?.mode, CONFIG_CONTENT_GATE_MODE_VALUES, sourceLabel, [
380
+ "inspect",
381
+ "contentGate",
382
+ "mode"
383
+ ]);
384
+ const normalized = {};
385
+ if (detector !== void 0) normalized.detector = detector;
386
+ if (mode !== void 0) normalized.contentGate = { mode };
387
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
388
+ }
389
+ function normalizeContentGateConfig(value, sourceLabel) {
390
+ const section = ensureObject$1(value, sourceLabel, ["contentGate"]);
391
+ if (!section) return;
392
+ rejectUnknownKeys(section, ["mode"], sourceLabel, ["contentGate"]);
393
+ const mode = parseEnum(section.mode, CONFIG_CONTENT_GATE_MODE_VALUES, sourceLabel, ["contentGate", "mode"]);
394
+ return mode === void 0 ? void 0 : { mode };
395
+ }
396
+ function normalizePathConfig(value, sourceLabel) {
397
+ const section = ensureObject$1(value, sourceLabel, ["path"]);
398
+ if (!section) return;
399
+ rejectUnknownKeys(section, [
400
+ "mode",
401
+ "recursive",
402
+ "includeExtensions",
403
+ "excludeExtensions",
404
+ "detectBinary"
405
+ ], sourceLabel, ["path"]);
406
+ const mode = parseEnum(section.mode, CONFIG_PATH_MODE_VALUES, sourceLabel, ["path", "mode"]);
407
+ const recursive = parseBoolean(section.recursive, sourceLabel, ["path", "recursive"]);
408
+ const includeExtensions = parseStringArray(section.includeExtensions, sourceLabel, ["path", "includeExtensions"]);
409
+ const excludeExtensions = parseStringArray(section.excludeExtensions, sourceLabel, ["path", "excludeExtensions"]);
410
+ const detectBinary = parseBoolean(section.detectBinary, sourceLabel, ["path", "detectBinary"]);
411
+ const normalized = {};
412
+ if (mode !== void 0) normalized.mode = mode;
413
+ if (recursive !== void 0) normalized.recursive = recursive;
414
+ if (includeExtensions !== void 0) normalized.includeExtensions = includeExtensions;
415
+ if (excludeExtensions !== void 0) normalized.excludeExtensions = excludeExtensions;
416
+ if (detectBinary !== void 0) normalized.detectBinary = detectBinary;
417
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
418
+ }
419
+ function normalizeProgressConfig(value, sourceLabel) {
420
+ const section = ensureObject$1(value, sourceLabel, ["progress"]);
421
+ if (!section) return;
422
+ rejectUnknownKeys(section, ["mode"], sourceLabel, ["progress"]);
423
+ const mode = parseEnum(section.mode, CONFIG_PROGRESS_MODE_VALUES, sourceLabel, ["progress", "mode"]);
424
+ return mode === void 0 ? void 0 : { mode };
425
+ }
426
+ function normalizeOutputConfig(value, sourceLabel) {
427
+ const section = ensureObject$1(value, sourceLabel, ["output"]);
428
+ if (!section) return;
429
+ rejectUnknownKeys(section, ["totalOf"], sourceLabel, ["output"]);
430
+ const totalOf = parseTotalOf(section.totalOf, sourceLabel, ["output", "totalOf"]);
431
+ return totalOf === void 0 ? void 0 : { totalOf };
432
+ }
433
+ function normalizeReportingConfig(value, sourceLabel) {
434
+ const section = ensureObject$1(value, sourceLabel, ["reporting"]);
435
+ if (!section) return;
436
+ rejectUnknownKeys(section, ["skippedFiles", "debugReport"], sourceLabel, ["reporting"]);
437
+ const skippedFiles = parseBoolean(section.skippedFiles, sourceLabel, ["reporting", "skippedFiles"]);
438
+ const debugReportSection = ensureObject$1(section.debugReport, sourceLabel, ["reporting", "debugReport"]);
439
+ if (debugReportSection) rejectUnknownKeys(debugReportSection, ["path", "tee"], sourceLabel, ["reporting", "debugReport"]);
440
+ const path = parseString(debugReportSection?.path, sourceLabel, [
441
+ "reporting",
442
+ "debugReport",
443
+ "path"
444
+ ]);
445
+ const tee = parseBoolean(debugReportSection?.tee, sourceLabel, [
446
+ "reporting",
447
+ "debugReport",
448
+ "tee"
449
+ ]);
450
+ const normalized = {};
451
+ if (skippedFiles !== void 0) normalized.skippedFiles = skippedFiles;
452
+ if (path !== void 0 || tee !== void 0) {
453
+ normalized.debugReport = {};
454
+ if (path !== void 0) normalized.debugReport.path = path;
455
+ if (tee !== void 0) normalized.debugReport.tee = tee;
456
+ }
457
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
458
+ }
459
+ function normalizeLoggingConfig(value, sourceLabel) {
460
+ const section = ensureObject$1(value, sourceLabel, ["logging"]);
461
+ if (!section) return;
462
+ rejectUnknownKeys(section, ["level", "verbosity"], sourceLabel, ["logging"]);
463
+ const level = parseEnum(section.level, CONFIG_LOG_LEVEL_VALUES, sourceLabel, ["logging", "level"]);
464
+ const verbosity = parseEnum(section.verbosity, CONFIG_LOG_VERBOSITY_VALUES, sourceLabel, ["logging", "verbosity"]);
465
+ const normalized = {};
466
+ if (level !== void 0) normalized.level = level;
467
+ if (verbosity !== void 0) normalized.verbosity = verbosity;
468
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
469
+ }
470
+ function normalizeWordCounterConfig(value, sourceLabel) {
471
+ const root = ensureObject$1(value, sourceLabel, []);
472
+ if (!root) throw createConfigError(sourceLabel, [], "expected a root object.");
473
+ rejectUnknownKeys(root, [
474
+ "detector",
475
+ "contentGate",
476
+ "inspect",
477
+ "path",
478
+ "progress",
479
+ "output",
480
+ "reporting",
481
+ "logging"
482
+ ], sourceLabel, []);
483
+ const detector = parseEnum(root.detector, CONFIG_DETECTOR_VALUES, sourceLabel, ["detector"]);
484
+ const contentGate = normalizeContentGateConfig(root.contentGate, sourceLabel);
485
+ const inspect = normalizeInspectConfig(root.inspect, sourceLabel);
486
+ const path = normalizePathConfig(root.path, sourceLabel);
487
+ const progress = normalizeProgressConfig(root.progress, sourceLabel);
488
+ const output = normalizeOutputConfig(root.output, sourceLabel);
489
+ const reporting = normalizeReportingConfig(root.reporting, sourceLabel);
490
+ const logging = normalizeLoggingConfig(root.logging, sourceLabel);
491
+ const normalized = {};
492
+ if (detector !== void 0) normalized.detector = detector;
493
+ if (contentGate !== void 0) normalized.contentGate = contentGate;
494
+ if (inspect !== void 0) normalized.inspect = inspect;
495
+ if (path !== void 0) normalized.path = path;
496
+ if (progress !== void 0) normalized.progress = progress;
497
+ if (output !== void 0) normalized.output = output;
498
+ if (reporting !== void 0) normalized.reporting = reporting;
499
+ if (logging !== void 0) normalized.logging = logging;
500
+ return normalized;
501
+ }
502
+ //#endregion
503
+ //#region src/cli/config/env.ts
504
+ function parseBooleanEnv(name, value) {
505
+ if (value === void 0) return;
506
+ const normalized = value.trim().toLowerCase();
507
+ if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") return true;
508
+ if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") return false;
509
+ throw new Error(`Invalid value for ${name}: ${value}`);
510
+ }
511
+ function parseCommaSeparatedEnv(value) {
512
+ if (value === void 0) return;
513
+ const tokens = value.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
514
+ return tokens.length > 0 ? tokens : [];
515
+ }
516
+ function resolveEnvConfig(env = process.env) {
517
+ const pathMode = env.WORD_COUNTER_PATH_MODE;
518
+ const recursive = parseBooleanEnv("WORD_COUNTER_RECURSIVE", env.WORD_COUNTER_RECURSIVE);
519
+ const includeExtensions = parseCommaSeparatedEnv(env.WORD_COUNTER_INCLUDE_EXT);
520
+ const excludeExtensions = parseCommaSeparatedEnv(env.WORD_COUNTER_EXCLUDE_EXT);
521
+ const skippedFiles = parseBooleanEnv("WORD_COUNTER_REPORT_SKIPS", env.WORD_COUNTER_REPORT_SKIPS);
522
+ const totalOfRaw = env.WORD_COUNTER_TOTAL_OF;
523
+ const contentGateMode = env.WORD_COUNTER_CONTENT_GATE;
524
+ const progressMode = env.WORD_COUNTER_PROGRESS;
525
+ const logLevel = env.WORD_COUNTER_LOG_LEVEL;
526
+ const logVerbosity = env.WORD_COUNTER_LOG_VERBOSITY;
527
+ const debugReportPath = env.WORD_COUNTER_DEBUG_REPORT;
528
+ const debugReportTee = parseBooleanEnv("WORD_COUNTER_DEBUG_REPORT_TEE", env.WORD_COUNTER_DEBUG_REPORT_TEE);
529
+ const config = {};
530
+ if (pathMode !== void 0 || recursive !== void 0 || includeExtensions !== void 0 || excludeExtensions !== void 0) {
531
+ config.path = {};
532
+ if (pathMode !== void 0) config.path.mode = pathMode;
533
+ if (recursive !== void 0) config.path.recursive = recursive;
534
+ if (includeExtensions !== void 0) config.path.includeExtensions = includeExtensions;
535
+ if (excludeExtensions !== void 0) config.path.excludeExtensions = excludeExtensions;
536
+ }
537
+ if (progressMode !== void 0) config.progress = { mode: progressMode };
538
+ if (totalOfRaw !== void 0) config.output = { totalOf: parseTotalOfOption(totalOfRaw) };
539
+ if (contentGateMode !== void 0) {
540
+ config.contentGate = { mode: contentGateMode };
541
+ config.inspect = {
542
+ ...config.inspect,
543
+ contentGate: { mode: contentGateMode }
544
+ };
545
+ }
546
+ if (skippedFiles !== void 0 || debugReportPath !== void 0 || debugReportTee !== void 0) {
547
+ config.reporting = {};
548
+ if (skippedFiles !== void 0) config.reporting.skippedFiles = skippedFiles;
549
+ if (debugReportPath !== void 0 || debugReportTee !== void 0) {
550
+ config.reporting.debugReport = {};
551
+ if (debugReportPath !== void 0) config.reporting.debugReport.path = debugReportPath;
552
+ if (debugReportTee !== void 0) config.reporting.debugReport.tee = debugReportTee;
553
+ }
554
+ }
555
+ if (logLevel !== void 0 || logVerbosity !== void 0) {
556
+ config.logging = {};
557
+ if (logLevel !== void 0) config.logging.level = logLevel;
558
+ if (logVerbosity !== void 0) config.logging.verbosity = logVerbosity;
559
+ }
560
+ return normalizeWordCounterConfig(config, "environment variables");
561
+ }
562
+ //#endregion
563
+ //#region src/cli/config/merge.ts
564
+ function mergeSection(base, override) {
565
+ if (!base) return override ? { ...override } : void 0;
566
+ if (!override) return { ...base };
567
+ return {
568
+ ...base,
569
+ ...override
570
+ };
571
+ }
572
+ function mergeNestedSection(base, override, nestedKey) {
573
+ const merged = mergeSection(base, override);
574
+ if (!merged) return;
575
+ const baseRecord = base;
576
+ const overrideRecord = override;
577
+ const baseNested = baseRecord?.[nestedKey];
578
+ const overrideNested = overrideRecord?.[nestedKey];
579
+ if (typeof baseNested === "object" && baseNested !== null && typeof overrideNested === "object" && overrideNested !== null) return {
580
+ ...merged,
581
+ [nestedKey]: {
582
+ ...baseNested,
583
+ ...overrideNested
584
+ }
585
+ };
586
+ return merged;
587
+ }
588
+ function mergeWordCounterConfig(base, override) {
589
+ return {
590
+ ...base,
591
+ ...override,
592
+ ...base.inspect || override.inspect ? { inspect: mergeSection(base.inspect, override.inspect) } : {},
593
+ ...base.path || override.path ? { path: mergeSection(base.path, override.path) } : {},
594
+ ...base.progress || override.progress ? { progress: mergeSection(base.progress, override.progress) } : {},
595
+ ...base.output || override.output ? { output: mergeSection(base.output, override.output) } : {},
596
+ ...base.reporting || override.reporting ? { reporting: mergeNestedSection(base.reporting, override.reporting, "debugReport") } : {},
597
+ ...base.logging || override.logging ? { logging: mergeSection(base.logging, override.logging) } : {}
598
+ };
599
+ }
600
+ //#endregion
601
+ //#region src/cli/config/jsonc.ts
602
+ function isEscaped(text, index) {
603
+ let backslashCount = 0;
604
+ for (let cursor = index - 1; cursor >= 0 && text[cursor] === "\\"; cursor -= 1) backslashCount += 1;
605
+ return backslashCount % 2 === 1;
606
+ }
607
+ function stripJsonComments(text) {
608
+ let result = "";
609
+ let inString = false;
610
+ let inLineComment = false;
611
+ let inBlockComment = false;
612
+ for (let index = 0; index < text.length; index += 1) {
613
+ const current = text[index] ?? "";
614
+ const next = text[index + 1] ?? "";
615
+ if (inLineComment) {
616
+ if (current === "\n") {
617
+ inLineComment = false;
618
+ result += current;
619
+ }
620
+ continue;
621
+ }
622
+ if (inBlockComment) {
623
+ if (current === "*" && next === "/") {
624
+ inBlockComment = false;
625
+ index += 1;
626
+ }
627
+ continue;
628
+ }
629
+ if (inString) {
630
+ result += current;
631
+ if (current === "\"" && !isEscaped(text, index)) inString = false;
632
+ continue;
633
+ }
634
+ if (current === "\"") {
635
+ inString = true;
636
+ result += current;
637
+ continue;
638
+ }
639
+ if (current === "/" && next === "/") {
640
+ inLineComment = true;
641
+ index += 1;
642
+ continue;
643
+ }
644
+ if (current === "/" && next === "*") {
645
+ inBlockComment = true;
646
+ index += 1;
647
+ continue;
648
+ }
649
+ result += current;
650
+ }
651
+ if (inBlockComment) throw new Error("Unterminated block comment in JSONC config.");
652
+ return result;
653
+ }
654
+ //#endregion
655
+ //#region src/cli/config/toml-tokens.ts
656
+ function stripInlineComment$1(line) {
657
+ let inSingle = false;
658
+ let inDouble = false;
659
+ for (let index = 0; index < line.length; index += 1) {
660
+ const current = line[index] ?? "";
661
+ const previous = line[index - 1] ?? "";
662
+ if (current === "'" && !inDouble) {
663
+ inSingle = !inSingle;
664
+ continue;
665
+ }
666
+ if (current === "\"" && !inSingle && previous !== "\\") {
667
+ inDouble = !inDouble;
668
+ continue;
669
+ }
670
+ if (current === "#" && !inSingle && !inDouble) return line.slice(0, index);
671
+ }
672
+ return line;
673
+ }
674
+ function splitPath(path, rawLine) {
675
+ const parts = path.split(".").map((part) => part.trim());
676
+ if (parts.some((part) => part.length === 0)) throw new Error(`Invalid TOML key: ${rawLine}`);
677
+ return parts;
678
+ }
679
+ function tokenizeTomlLine(rawLine, currentTable) {
680
+ const line = stripInlineComment$1(rawLine).trim();
681
+ if (!line) return;
682
+ const tableMatch = line.match(/^\[([A-Za-z0-9_.-]+)]$/);
683
+ if (tableMatch) return {
684
+ kind: "table",
685
+ path: splitPath(tableMatch[1] ?? "", rawLine),
686
+ rawLine
687
+ };
688
+ const separatorIndex = line.indexOf("=");
689
+ if (separatorIndex <= 0) throw new Error(`Invalid TOML assignment: ${rawLine}`);
690
+ const rawKey = line.slice(0, separatorIndex).trim();
691
+ const rawValue = line.slice(separatorIndex + 1).trim();
692
+ if (!rawKey || !rawValue) throw new Error(`Invalid TOML assignment: ${rawLine}`);
693
+ return {
694
+ kind: "assignment",
695
+ keyPath: [...currentTable, ...splitPath(rawKey, rawLine)],
696
+ rawValue,
697
+ rawLine
698
+ };
699
+ }
700
+ function tokenizeTomlConfig(text) {
701
+ const tokens = [];
702
+ let currentTable = [];
703
+ for (const rawLine of text.split(/\r?\n/)) {
704
+ const token = tokenizeTomlLine(rawLine, currentTable);
705
+ if (!token) continue;
706
+ if (token.kind === "table") {
707
+ currentTable = token.path;
708
+ tokens.push(token);
709
+ continue;
710
+ }
711
+ tokens.push(token);
712
+ }
713
+ return tokens;
714
+ }
715
+ //#endregion
716
+ //#region src/cli/config/toml.ts
717
+ function isRecord(value) {
718
+ return typeof value === "object" && value !== null && !Array.isArray(value);
719
+ }
720
+ function parseTomlString(rawValue) {
721
+ if (rawValue.length < 2) throw new Error(`Invalid TOML string value: ${rawValue}`);
722
+ const quote = rawValue[0];
723
+ const inner = rawValue.slice(1, -1);
724
+ if (quote === "'") return inner;
725
+ return inner.replace(/\\(["\\bfnrt])/g, (_match, escaped) => {
726
+ switch (escaped) {
727
+ case "\"": return "\"";
728
+ case "\\": return "\\";
729
+ case "b": return "\b";
730
+ case "f": return "\f";
731
+ case "n": return "\n";
732
+ case "r": return "\r";
733
+ case "t": return " ";
734
+ default: return escaped;
735
+ }
736
+ });
737
+ }
738
+ function splitTomlArrayItems(rawValue) {
739
+ const items = [];
740
+ let buffer = "";
741
+ let inSingle = false;
742
+ let inDouble = false;
743
+ for (let index = 0; index < rawValue.length; index += 1) {
744
+ const current = rawValue[index] ?? "";
745
+ const previous = rawValue[index - 1] ?? "";
746
+ if (current === "'" && !inDouble) {
747
+ inSingle = !inSingle;
748
+ buffer += current;
749
+ continue;
750
+ }
751
+ if (current === "\"" && !inSingle && previous !== "\\") {
752
+ inDouble = !inDouble;
753
+ buffer += current;
754
+ continue;
755
+ }
756
+ if (current === "," && !inSingle && !inDouble) {
757
+ items.push(buffer.trim());
758
+ buffer = "";
759
+ continue;
760
+ }
761
+ buffer += current;
762
+ }
763
+ if (buffer.trim().length > 0) items.push(buffer.trim());
764
+ return items;
765
+ }
766
+ function parseTomlValue(rawValue) {
767
+ const trimmed = rawValue.trim();
768
+ if (trimmed === "true") return true;
769
+ if (trimmed === "false") return false;
770
+ if (trimmed.startsWith("\"") && trimmed.endsWith("\"") || trimmed.startsWith("'") && trimmed.endsWith("'")) return parseTomlString(trimmed);
771
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
772
+ const inner = trimmed.slice(1, -1).trim();
773
+ if (inner.length === 0) return [];
774
+ return splitTomlArrayItems(inner).map((item) => {
775
+ const parsed = parseTomlValue(item);
776
+ if (Array.isArray(parsed)) throw new Error(`Nested TOML arrays are not supported: ${rawValue}`);
777
+ return parsed;
778
+ });
779
+ }
780
+ throw new Error(`Unsupported TOML value: ${rawValue}`);
781
+ }
782
+ function ensureObject(parent, key) {
783
+ const existing = parent[key];
784
+ if (existing === void 0) {
785
+ const next = {};
786
+ parent[key] = next;
787
+ return next;
788
+ }
789
+ if (!isRecord(existing)) throw new Error(`Cannot redefine non-table key as table: ${key}`);
790
+ return existing;
791
+ }
792
+ function setNestedValue(root, keyPath, value) {
793
+ let target = root;
794
+ for (let index = 0; index < keyPath.length - 1; index += 1) target = ensureObject(target, keyPath[index] ?? "");
795
+ const leafKey = keyPath[keyPath.length - 1] ?? "";
796
+ if (leafKey in target) throw new Error(`Duplicate TOML key: ${keyPath.join(".")}`);
797
+ target[leafKey] = value;
798
+ }
799
+ function parseTomlConfig(text) {
800
+ const result = {};
801
+ for (const token of tokenizeTomlConfig(text)) {
802
+ if (token.kind === "table") {
803
+ let tableTarget = result;
804
+ for (const part of token.path) tableTarget = ensureObject(tableTarget, part);
805
+ continue;
806
+ }
807
+ setNestedValue(result, token.keyPath, parseTomlValue(token.rawValue));
808
+ }
809
+ return result;
810
+ }
811
+ //#endregion
812
+ //#region src/cli/config/parse.ts
813
+ function parseJsonConfig(text, sourceLabel, format) {
814
+ try {
815
+ return JSON.parse(text);
816
+ } catch (error) {
817
+ throw new ConfigParseError(sourceLabel, format, error instanceof Error ? error.message : String(error));
818
+ }
819
+ }
820
+ function parseConfigText(text, format, sourceLabel) {
821
+ let parsed;
822
+ if (format === "json") parsed = parseJsonConfig(text, sourceLabel, "json");
823
+ else if (format === "jsonc") try {
824
+ parsed = parseJsonConfig(stripJsonComments(text), sourceLabel, "jsonc");
825
+ } catch (error) {
826
+ if (error instanceof ConfigParseError) throw error;
827
+ throw new ConfigParseError(sourceLabel, "jsonc", error instanceof Error ? error.message : String(error));
828
+ }
829
+ else try {
830
+ parsed = parseTomlConfig(text);
831
+ } catch (error) {
832
+ throw new ConfigParseError(sourceLabel, "toml", error instanceof Error ? error.message : String(error));
833
+ }
834
+ try {
835
+ return normalizeWordCounterConfig(parsed, sourceLabel);
836
+ } catch (error) {
837
+ if (error instanceof ConfigValidationError) throw error;
838
+ throw new ConfigParseError(sourceLabel, format, error instanceof Error ? error.message : String(error));
839
+ }
840
+ }
841
+ async function loadConfigFile(path, format) {
842
+ let text;
843
+ try {
844
+ text = await readFile(path, "utf8");
845
+ } catch (error) {
846
+ const message = error instanceof Error ? error.message : String(error);
847
+ throw new Error(`Failed to read config file (${path}): ${message}`);
848
+ }
849
+ return {
850
+ format,
851
+ path,
852
+ config: parseConfigText(text, format, path)
853
+ };
854
+ }
855
+ //#endregion
856
+ //#region src/cli/config/resolve.ts
857
+ async function resolveWordCounterConfig(options = {}) {
858
+ const discovered = await discoverConfigFiles(options);
859
+ const filesToLoad = [discovered.user, discovered.cwd].filter((item) => item !== void 0);
860
+ const loadedFiles = await Promise.all(filesToLoad.map(async (item) => {
861
+ return {
862
+ ...await loadConfigFile(item.path, item.format),
863
+ notes: item.notes
864
+ };
865
+ }));
866
+ let config = {};
867
+ const files = [];
868
+ const notes = [];
869
+ for (const loaded of loadedFiles) {
870
+ config = mergeWordCounterConfig(config, loaded.config);
871
+ files.push({
872
+ path: loaded.path,
873
+ format: loaded.format,
874
+ config: loaded.config
875
+ });
876
+ notes.push(...loaded.notes);
877
+ }
878
+ config = mergeWordCounterConfig(config, resolveEnvConfig(options.env));
879
+ return {
880
+ config,
881
+ files,
882
+ notes
883
+ };
884
+ }
885
+ //#endregion
886
+ //#region src/cli/config/sources.ts
887
+ function isCliOptionSource(program, optionName) {
888
+ return program.getOptionValueSource(optionName) === "cli";
889
+ }
890
+ function deriveCountCliSources(program) {
891
+ return {
892
+ detector: isCliOptionSource(program, "detector"),
893
+ contentGate: isCliOptionSource(program, "contentGate"),
894
+ pathMode: isCliOptionSource(program, "pathMode"),
895
+ recursive: isCliOptionSource(program, "recursive"),
896
+ includeExt: isCliOptionSource(program, "includeExt"),
897
+ excludeExt: isCliOptionSource(program, "excludeExt"),
898
+ totalOf: isCliOptionSource(program, "totalOf"),
899
+ debug: isCliOptionSource(program, "debug"),
900
+ verbose: isCliOptionSource(program, "verbose"),
901
+ debugReport: isCliOptionSource(program, "debugReport"),
902
+ debugReportTee: isCliOptionSource(program, "debugReportTee") || isCliOptionSource(program, "debugTee"),
903
+ progress: isCliOptionSource(program, "progress"),
904
+ quietSkips: isCliOptionSource(program, "quietSkips")
905
+ };
906
+ }
907
+ function deriveInitialCountProgressMode(program, rawProgressValue) {
908
+ if (!isCliOptionSource(program, "progress")) return "auto";
909
+ return rawProgressValue ? "on" : "off";
910
+ }
911
+ //#endregion
34
912
  //#region src/cli/debug/channel.ts
35
913
  const DEBUG_EVENT_SCHEMA_VERSION = 1;
36
914
  const NOOP_CLOSE = async () => {};
@@ -333,6 +1211,7 @@ async function countBatchInputsWithWorkerJobs(filePaths, options) {
333
1211
  section: options.section,
334
1212
  detectorMode: options.detectorMode ?? "regex",
335
1213
  wcOptions: options.wcOptions,
1214
+ detectBinary: options.detectBinary ?? true,
336
1215
  preserveCollectorSegments: options.preserveCollectorSegments,
337
1216
  detectorEvidence: options.detectorEvidence,
338
1217
  debugVerbosity: options.debugVerbosity,
@@ -354,8 +1233,12 @@ async function countBatchInputsWithWorkerJobs(filePaths, options) {
354
1233
  }
355
1234
  //#endregion
356
1235
  //#region src/cli/doctor/checks.ts
357
- const REQUIRED_NODE_RANGE = ">=20";
358
- const REQUIRED_NODE_MAJOR = 20;
1236
+ const REQUIRED_NODE_RANGE = ">=22.18.0";
1237
+ const REQUIRED_NODE_VERSION = {
1238
+ major: 22,
1239
+ minor: 18,
1240
+ patch: 0
1241
+ };
359
1242
  const SAMPLE_TEXT = "Hello 世界";
360
1243
  function normalizePackageVersion(value) {
361
1244
  const trimmed = value?.trim();
@@ -368,22 +1251,35 @@ function deriveBuildChannel(packageVersion) {
368
1251
  if (channel === "alpha" || channel === "beta" || channel === "rc" || channel === "canary") return channel;
369
1252
  return "stable";
370
1253
  }
371
- function parseNodeMajor(version) {
372
- const match = /^v?(\d+)(?:\.\d+){0,2}(?:[-+].*)?$/.exec(version.trim());
1254
+ function parseNodeVersion(version) {
1255
+ const match = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:[-+].*)?$/.exec(version.trim());
373
1256
  if (!match) return null;
374
1257
  const major = Number.parseInt(match[1] ?? "", 10);
375
- return Number.isFinite(major) ? major : null;
1258
+ const minor = Number.parseInt(match[2] ?? "0", 10);
1259
+ const patch = Number.parseInt(match[3] ?? "0", 10);
1260
+ if (!Number.isFinite(major) || !Number.isFinite(minor) || !Number.isFinite(patch)) return null;
1261
+ return {
1262
+ major,
1263
+ minor,
1264
+ patch
1265
+ };
1266
+ }
1267
+ function meetsRequiredNodeVersion(version) {
1268
+ if (!version) return false;
1269
+ if (version.major !== REQUIRED_NODE_VERSION.major) return version.major > REQUIRED_NODE_VERSION.major;
1270
+ if (version.minor !== REQUIRED_NODE_VERSION.minor) return version.minor > REQUIRED_NODE_VERSION.minor;
1271
+ return version.patch >= REQUIRED_NODE_VERSION.patch;
376
1272
  }
377
1273
  function resolveRuntimeSummary(overrides = {}) {
378
- const packageVersion = normalizePackageVersion(overrides.packageVersion ?? "0.1.5-canary.4");
1274
+ const packageVersion = normalizePackageVersion(overrides.packageVersion ?? "0.1.5");
379
1275
  const nodeVersion = overrides.nodeVersion ?? process.version;
380
- const major = parseNodeMajor(nodeVersion);
1276
+ const parsedNodeVersion = parseNodeVersion(nodeVersion);
381
1277
  return {
382
1278
  packageVersion,
383
1279
  buildChannel: deriveBuildChannel(packageVersion),
384
1280
  requiredNodeRange: REQUIRED_NODE_RANGE,
385
1281
  nodeVersion,
386
- meetsProjectRequirement: major !== null && major >= REQUIRED_NODE_MAJOR,
1282
+ meetsProjectRequirement: meetsRequiredNodeVersion(parsedNodeVersion),
387
1283
  platform: overrides.platform ?? process.platform,
388
1284
  arch: overrides.arch ?? process.arch
389
1285
  };
@@ -3698,100 +4594,6 @@ async function resolveBatchFilePaths(pathInputs, options) {
3698
4594
  };
3699
4595
  }
3700
4596
  //#endregion
3701
- //#region src/cli/total-of.ts
3702
- const TOTAL_OF_PARTS = Object.freeze([
3703
- "words",
3704
- "emoji",
3705
- "symbols",
3706
- "punctuation",
3707
- "whitespace"
3708
- ]);
3709
- const TOTAL_OF_PART_ALIASES = {
3710
- word: "words",
3711
- words: "words",
3712
- emoji: "emoji",
3713
- emojis: "emoji",
3714
- symbol: "symbols",
3715
- symbols: "symbols",
3716
- punction: "punctuation",
3717
- punctuation: "punctuation",
3718
- whitespace: "whitespace"
3719
- };
3720
- function createTotalOfCounts() {
3721
- return {
3722
- words: 0,
3723
- emoji: 0,
3724
- symbols: 0,
3725
- punctuation: 0,
3726
- whitespace: 0
3727
- };
3728
- }
3729
- function collectNonWordCounts(target, nonWords) {
3730
- if (!nonWords) return;
3731
- target.emoji += nonWords.counts.emoji;
3732
- target.symbols += nonWords.counts.symbols;
3733
- target.punctuation += nonWords.counts.punctuation;
3734
- target.whitespace += nonWords.counts.whitespace ?? 0;
3735
- }
3736
- function collectFromWordCounterResult(result) {
3737
- const counts = createTotalOfCounts();
3738
- counts.words += result.counts?.words ?? result.total;
3739
- if (result.breakdown.mode === "collector") {
3740
- collectNonWordCounts(counts, result.breakdown.nonWords);
3741
- return counts;
3742
- }
3743
- for (const item of result.breakdown.items) collectNonWordCounts(counts, item.nonWords);
3744
- return counts;
3745
- }
3746
- function collectTotalOfCounts(result) {
3747
- if (!("section" in result)) return collectFromWordCounterResult(result);
3748
- const counts = createTotalOfCounts();
3749
- for (const item of result.items) {
3750
- const itemCounts = collectFromWordCounterResult(item.result);
3751
- for (const part of TOTAL_OF_PARTS) counts[part] += itemCounts[part];
3752
- }
3753
- return counts;
3754
- }
3755
- function parseTotalOfToken(token) {
3756
- const canonical = TOTAL_OF_PART_ALIASES[token.trim().toLowerCase()];
3757
- if (canonical) return canonical;
3758
- throw new Error(`Invalid --total-of part: ${token}. Allowed: ${TOTAL_OF_PARTS.join(", ")}.`);
3759
- }
3760
- function parseTotalOfOption(value) {
3761
- const rawTokens = value.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
3762
- if (rawTokens.length === 0) throw new Error(`Invalid --total-of value: "${value}". Use comma-separated parts from: ${TOTAL_OF_PARTS.join(", ")}.`);
3763
- const parts = [];
3764
- const seen = /* @__PURE__ */ new Set();
3765
- for (const token of rawTokens) {
3766
- const parsed = parseTotalOfToken(token);
3767
- if (seen.has(parsed)) continue;
3768
- seen.add(parsed);
3769
- parts.push(parsed);
3770
- }
3771
- return parts;
3772
- }
3773
- function requiresNonWordCollection(parts) {
3774
- if (!parts || parts.length === 0) return false;
3775
- return parts.some((part) => part !== "words");
3776
- }
3777
- function requiresWhitespaceCollection(parts) {
3778
- if (!parts || parts.length === 0) return false;
3779
- return parts.includes("whitespace");
3780
- }
3781
- function resolveTotalOfOverride(result, parts) {
3782
- if (!parts || parts.length === 0) return;
3783
- const counts = collectTotalOfCounts(result);
3784
- let total = 0;
3785
- for (const part of parts) total += counts[part];
3786
- return {
3787
- parts: [...parts],
3788
- total
3789
- };
3790
- }
3791
- function formatTotalOfParts(parts) {
3792
- return parts.join(", ");
3793
- }
3794
- //#endregion
3795
4597
  //#region src/cli/runtime/options.ts
3796
4598
  function hasPathInput(pathValues) {
3797
4599
  return Array.isArray(pathValues) && pathValues.length > 0;
@@ -3929,10 +4731,10 @@ function selectInspectText(input, section) {
3929
4731
  if (section === "frontmatter") return parsed.frontmatter ?? "";
3930
4732
  return parsed.content;
3931
4733
  }
3932
- async function loadSingleInspectInput(path, textTokens, section) {
4734
+ async function loadSingleInspectInput(path, textTokens, section, detectBinary = true) {
3933
4735
  if (path) try {
3934
4736
  const buffer = await readFile(path);
3935
- if (isProbablyBinary(buffer)) throw new Error("binary file");
4737
+ if (detectBinary && isProbablyBinary(buffer)) throw new Error("binary file");
3936
4738
  return {
3937
4739
  text: selectInspectText(buffer.toString("utf8"), section),
3938
4740
  sourceType: "path",
@@ -3986,7 +4788,7 @@ async function loadInspectBatchInputs(pathInputs, options) {
3986
4788
  });
3987
4789
  continue;
3988
4790
  }
3989
- if (isProbablyBinary(buffer)) {
4791
+ if (options.pathDetectBinary && isProbablyBinary(buffer)) {
3990
4792
  if (entry.source === "direct") failures.push({
3991
4793
  path: entry.path,
3992
4794
  reason: "binary file"
@@ -4165,13 +4967,14 @@ const INSPECT_HELP_LINES = [
4165
4967
  "inspect detector behavior without count output",
4166
4968
  "",
4167
4969
  "Options:",
4168
- " --detector <mode> inspect detector mode (wasm, regex) (default: wasm)",
4970
+ " -d, --detector <mode> inspect detector mode (wasm, regex) (default: regex)",
4169
4971
  " --content-gate <mode> content gate mode (default, strict, loose, off) (default: default)",
4170
4972
  " --view <view> inspect view (pipeline, engine) (default: pipeline)",
4171
4973
  " -f, --format <format> inspect output format (standard, json) (default: standard)",
4172
4974
  " --pretty pretty print inspect JSON output",
4173
4975
  " --section <section> inspect section selector (all, frontmatter, content) (default: all)",
4174
4976
  " --path-mode <mode> path resolution mode for --path inputs (auto, manual) (default: auto)",
4977
+ " --recursive enable recursive directory traversal for --path directories",
4175
4978
  " --no-recursive disable recursive directory traversal for --path directories",
4176
4979
  " --include-ext <exts> comma-separated extensions to include during directory scanning",
4177
4980
  " --exclude-ext <exts> comma-separated extensions to exclude during directory scanning",
@@ -4185,7 +4988,7 @@ function printInspectHelp() {
4185
4988
  //#endregion
4186
4989
  //#region src/cli/inspect/parse.ts
4187
4990
  function parseDetector(rawValue) {
4188
- if (rawValue === void 0) return "wasm";
4991
+ if (rawValue === void 0) return "regex";
4189
4992
  if (rawValue === "wasm" || rawValue === "regex") return rawValue;
4190
4993
  return null;
4191
4994
  }
@@ -4212,13 +5015,10 @@ function parsePathMode(rawValue) {
4212
5015
  function isSupportedInspectSectionMode(value) {
4213
5016
  return value === "all" || value === "frontmatter" || value === "content";
4214
5017
  }
4215
- function isInvalidInspectDetectorViewCombination(detector, view) {
4216
- return view === "engine" && detector === "regex";
4217
- }
4218
5018
  function validateInspectInvocation(argv) {
4219
5019
  const inspectIndex = argv.findIndex((token, index) => index >= 2 && token === "inspect");
4220
5020
  const tokens = inspectIndex >= 0 ? argv.slice(inspectIndex + 1) : [];
4221
- let detector = "wasm";
5021
+ let detector = "regex";
4222
5022
  let contentGateMode = "default";
4223
5023
  let view = "pipeline";
4224
5024
  let format = "standard";
@@ -4231,6 +5031,14 @@ function validateInspectInvocation(argv) {
4231
5031
  const excludeExt = [];
4232
5032
  let regex;
4233
5033
  const textTokens = [];
5034
+ const sources = {
5035
+ detector: false,
5036
+ contentGate: false,
5037
+ pathMode: false,
5038
+ recursive: false,
5039
+ includeExt: false,
5040
+ excludeExt: false
5041
+ };
4234
5042
  let expects = null;
4235
5043
  let positionalMode = false;
4236
5044
  const consumeValue = (kind, value) => {
@@ -4238,6 +5046,7 @@ function validateInspectInvocation(argv) {
4238
5046
  const parsed = parseDetector(value);
4239
5047
  if (parsed === null) return "`--detector` must be `wasm` or `regex`.";
4240
5048
  detector = parsed;
5049
+ sources.detector = true;
4241
5050
  return null;
4242
5051
  }
4243
5052
  if (kind === "view") {
@@ -4250,6 +5059,7 @@ function validateInspectInvocation(argv) {
4250
5059
  const parsed = parseContentGateMode(value);
4251
5060
  if (parsed === null) return "`--content-gate` must be `default`, `strict`, `loose`, or `off`.";
4252
5061
  contentGateMode = parsed;
5062
+ sources.contentGate = true;
4253
5063
  return null;
4254
5064
  }
4255
5065
  if (kind === "format") {
@@ -4268,6 +5078,7 @@ function validateInspectInvocation(argv) {
4268
5078
  const parsed = parsePathMode(value);
4269
5079
  if (parsed === null) return "`--path-mode` must be `auto` or `manual`.";
4270
5080
  pathMode = parsed;
5081
+ sources.pathMode = true;
4271
5082
  return null;
4272
5083
  }
4273
5084
  if (kind === "path") {
@@ -4276,10 +5087,12 @@ function validateInspectInvocation(argv) {
4276
5087
  }
4277
5088
  if (kind === "includeExt") {
4278
5089
  includeExt.push(value);
5090
+ sources.includeExt = true;
4279
5091
  return null;
4280
5092
  }
4281
5093
  if (kind === "excludeExt") {
4282
5094
  excludeExt.push(value);
5095
+ sources.excludeExt = true;
4283
5096
  return null;
4284
5097
  }
4285
5098
  if (regex !== void 0) return "`--regex` can only be provided once.";
@@ -4308,16 +5121,22 @@ function validateInspectInvocation(argv) {
4308
5121
  positionalMode = true;
4309
5122
  continue;
4310
5123
  }
5124
+ if (token === "--recursive") {
5125
+ recursive = true;
5126
+ sources.recursive = true;
5127
+ continue;
5128
+ }
4311
5129
  if (token === "--no-recursive") {
4312
5130
  recursive = false;
5131
+ sources.recursive = true;
4313
5132
  continue;
4314
5133
  }
4315
5134
  if (token === "--pretty") {
4316
5135
  pretty = true;
4317
5136
  continue;
4318
5137
  }
4319
- if (token === "--detector" || token === "--content-gate" || token === "--view" || token === "--format" || token === "-f" || token === "--section" || token === "--path-mode" || token === "--path" || token === "-p" || token === "--include-ext" || token === "--exclude-ext" || token === "--regex") {
4320
- expects = token === "-p" ? "path" : token === "--content-gate" ? "contentGate" : token === "-f" ? "format" : token === "--path-mode" ? "pathMode" : token === "--include-ext" ? "includeExt" : token === "--exclude-ext" ? "excludeExt" : token.slice(2);
5138
+ if (token === "--detector" || token === "-d" || token === "--content-gate" || token === "--view" || token === "--format" || token === "-f" || token === "--section" || token === "--path-mode" || token === "--path" || token === "-p" || token === "--include-ext" || token === "--exclude-ext" || token === "--regex") {
5139
+ expects = token === "-p" ? "path" : token === "-d" ? "detector" : token === "--content-gate" ? "contentGate" : token === "-f" ? "format" : token === "--path-mode" ? "pathMode" : token === "--include-ext" ? "includeExt" : token === "--exclude-ext" ? "excludeExt" : token.slice(2);
4321
5140
  continue;
4322
5141
  }
4323
5142
  if (token.startsWith("--detector=") || token.startsWith("--content-gate=") || token.startsWith("--view=") || token.startsWith("--format=") || token.startsWith("--section=") || token.startsWith("--path-mode=") || token.startsWith("--path=") || token.startsWith("--include-ext=") || token.startsWith("--exclude-ext=") || token.startsWith("--regex=")) {
@@ -4354,10 +5173,6 @@ function validateInspectInvocation(argv) {
4354
5173
  ok: false,
4355
5174
  message: "No inspect input provided. Pass text or use --path."
4356
5175
  };
4357
- if (isInvalidInspectDetectorViewCombination(detector, view)) return {
4358
- ok: false,
4359
- message: "`--view engine` requires `--detector wasm`."
4360
- };
4361
5176
  return {
4362
5177
  ok: true,
4363
5178
  detector,
@@ -4367,12 +5182,14 @@ function validateInspectInvocation(argv) {
4367
5182
  pretty,
4368
5183
  section,
4369
5184
  pathMode,
5185
+ pathDetectBinary: true,
4370
5186
  recursive,
4371
5187
  includeExt,
4372
5188
  excludeExt,
4373
5189
  ...regex !== void 0 ? { regex } : {},
4374
5190
  paths,
4375
- textTokens
5191
+ textTokens,
5192
+ sources
4376
5193
  };
4377
5194
  }
4378
5195
  //#endregion
@@ -4400,25 +5217,52 @@ async function runSingleInspect(validated, input) {
4400
5217
  function isExplicitInspectInvocation(argv) {
4401
5218
  return argv[2] === "inspect";
4402
5219
  }
4403
- async function executeInspectCommand({ argv }) {
4404
- const validated = validateInspectInvocation(argv);
4405
- if (!validated.ok) {
4406
- console.error(import_picocolors.default.red(`error: ${validated.message}`));
5220
+ function emitConfigNotes$1(notes) {
5221
+ for (const note of notes) {
5222
+ const warningLine = note.startsWith("Warning:") ? note : `Warning: ${note}`;
5223
+ console.error(import_picocolors.default.yellow(warningLine));
5224
+ }
5225
+ }
5226
+ async function executeInspectCommand({ argv, runtime }) {
5227
+ const parsed = validateInspectInvocation(argv);
5228
+ if (!parsed.ok) {
5229
+ console.error(import_picocolors.default.red(`error: ${parsed.message}`));
4407
5230
  process.exitCode = 1;
4408
5231
  return;
4409
5232
  }
4410
- if (validated.help) {
5233
+ if (parsed.help) {
4411
5234
  printInspectHelp();
4412
5235
  process.exitCode = 0;
4413
5236
  return;
4414
5237
  }
5238
+ let validated = parsed;
5239
+ try {
5240
+ const resolvedConfig = await resolveWordCounterConfig({
5241
+ env: runtime?.env,
5242
+ cwd: runtime?.cwd
5243
+ });
5244
+ validated = applyConfigToInspectInvocation(validated, resolvedConfig.config);
5245
+ emitConfigNotes$1(resolvedConfig.notes);
5246
+ } catch (error) {
5247
+ const message = error instanceof Error ? error.message : String(error);
5248
+ console.error(import_picocolors.default.red(`error: ${message}`));
5249
+ process.exitCode = 1;
5250
+ return;
5251
+ }
5252
+ if (validated.view === "engine" && validated.detector === "regex") {
5253
+ console.error(import_picocolors.default.red("error: `--view engine` requires `--detector wasm`."));
5254
+ process.exitCode = 1;
5255
+ return;
5256
+ }
4415
5257
  try {
4416
5258
  if (validated.paths.length === 0) {
4417
- await runSingleInspect(validated, await loadSingleInspectInput(void 0, validated.textTokens, validated.section));
5259
+ const input = await loadSingleInspectInput(void 0, validated.textTokens, validated.section);
5260
+ await runSingleInspect(validated, input);
4418
5261
  return;
4419
5262
  }
4420
5263
  const loaded = await loadInspectBatchInputs(validated.paths, {
4421
5264
  pathMode: validated.pathMode,
5265
+ pathDetectBinary: validated.pathDetectBinary,
4422
5266
  recursive: validated.recursive,
4423
5267
  includeExt: validated.includeExt,
4424
5268
  excludeExt: validated.excludeExt,
@@ -4484,7 +5328,7 @@ function parseJobsOption(value) {
4484
5328
  return parsed;
4485
5329
  }
4486
5330
  function configureProgramOptions(program, parseMode) {
4487
- program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).addOption(new Option("--detector <mode>", "locale detector mode").choices(DETECTOR_CHOICES).default("regex")).addOption(new Option("--content-gate <mode>", "detector content gate policy mode (default, strict, loose, off)").choices(CONTENT_GATE_CHOICES)).addOption(new Option("--path-mode <mode>", "path resolution mode: auto (default) expands directories; manual treats --path values as literal files").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--latin-hint <tag>=<pattern>", "add a custom Latin hint rule (repeatable)", collectLatinHintValue, []).option("--latin-hints-file <path>", "load custom Latin hint rules from a JSON file").option("--no-default-latin-hints", "disable built-in Latin hint rules").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--detector-evidence", "emit per-window detector evidence on the debug stream (requires --debug and --detector wasm)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--jobs <n>", "batch jobs in --path mode (1=async main-thread, >1=worker load+count)", parseJobsOption, 1).option("--print-jobs-limit", "print host jobs-limit JSON and exit (must be used alone)").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--no-recursive", "disable recursive directory traversal").option("--quiet-warnings", "suppress non-fatal warning diagnostics").option("--quiet-skips", "suppress debug skip output and per-file json skipped field").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("--regex <pattern>", "regex filter for directory-scanned paths (applies to --path directories only)").option("-p, --path <path>", "read input from file or directory (directories expand in auto mode by default)", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
5331
+ program.addOption(new Option("-m, --mode <mode>", "breakdown mode").choices(MODE_CHOICES).argParser(parseMode).default("chunk")).addOption(new Option("-f, --format <format>", "output format").choices(FORMAT_CHOICES).default("standard")).addOption(new Option("--section <section>", "document section mode").choices(SECTION_CHOICES).default("all")).addOption(new Option("-d, --detector <mode>", "locale detector mode").choices(DETECTOR_CHOICES).default("regex")).addOption(new Option("--content-gate <mode>", "detector content gate policy mode (default, strict, loose, off)").choices(CONTENT_GATE_CHOICES)).addOption(new Option("--path-mode <mode>", "path resolution mode: auto (default) expands directories; manual treats --path values as literal files").choices(PATH_MODE_CHOICES).default("auto")).option("--latin-language <language>", "hint a language tag for Latin script text").option("--latin-tag <tag>", "hint a BCP 47 tag for Latin script text").option("--latin-locale <locale>", "legacy alias of --latin-language").option("--latin-hint <tag>=<pattern>", "add a custom Latin hint rule (repeatable)", collectLatinHintValue, []).option("--latin-hints-file <path>", "load custom Latin hint rules from a JSON file").option("--no-default-latin-hints", "disable built-in Latin hint rules").option("--han-language <language>", "hint a language tag for Han script text").option("--han-tag <tag>", "hint a BCP 47 tag for Han script text").option("--non-words", "collect emoji, symbols, and punctuation (excludes whitespace)").option("--include-whitespace", "include whitespace counts (implies --non-words; same as --misc)").option("--misc", "collect non-words plus whitespace (alias for --include-whitespace)").option("--total-of <parts>", "override total composition (comma-separated): words,emoji,symbols,punctuation,whitespace", parseTotalOfOption).option("--pretty", "pretty print JSON output", false).option("--debug", "enable debug diagnostics on stderr").option("--verbose", "emit verbose per-file debug diagnostics (requires --debug)").option("--detector-evidence", "emit per-window detector evidence on the debug stream (requires --debug and --detector wasm)").option("--debug-report [path]", "write debug diagnostics to a report file").option("--debug-report-tee", "mirror debug diagnostics to both report file and stderr").option("--debug-tee", "alias of --debug-report-tee").option("--merged", "show merged aggregate output (default)").option("--per-file", "show per-file output plus merged summary").option("--jobs <n>", "batch jobs in --path mode (1=async main-thread, >1=worker load+count)", parseJobsOption, 1).option("--print-jobs-limit", "print host jobs-limit JSON and exit (must be used alone)").option("--progress", "enable batch progress indicator").option("--no-progress", "disable batch progress indicator").option("--keep-progress", "keep final batch progress line visible in standard mode").option("--recursive", "enable recursive directory traversal").option("--no-recursive", "disable recursive directory traversal").option("--quiet-warnings", "suppress non-fatal warning diagnostics").option("--quiet-skips", "suppress debug skip output and per-file json skipped field").option("--include-ext <exts>", "comma-separated extensions to include during directory scanning", collectExtensionOption, []).option("--exclude-ext <exts>", "comma-separated extensions to exclude during directory scanning", collectExtensionOption, []).option("--regex <pattern>", "regex filter for directory-scanned paths (applies to --path directories only)").option("-p, --path <path>", "read input from file or directory (directories expand in auto mode by default)", collectPathValue, []).argument("[text...]", "text to count").showHelpAfterError();
4488
5332
  }
4489
5333
  //#endregion
4490
5334
  //#region src/cli/program/version.ts
@@ -4518,7 +5362,7 @@ function normalizeVersion(value) {
4518
5362
  return trimmed;
4519
5363
  }
4520
5364
  function resolvePackageVersion(options = {}) {
4521
- const embeddedVersion = normalizeVersion(options.embeddedVersion ?? "0.1.5-canary.4");
5365
+ const embeddedVersion = normalizeVersion(options.embeddedVersion ?? "0.1.5");
4522
5366
  if (embeddedVersion) return embeddedVersion;
4523
5367
  const maxLevels = options.maxLevels ?? 8;
4524
5368
  const resolveFromPath = options.resolveFromPath ?? resolveVersionFromPath;
@@ -4759,8 +5603,7 @@ function finalizeBatchSummaryFromFileResults(files, section, wcOptions, options
4759
5603
  //#region src/cli/batch/jobs/queue.ts
4760
5604
  async function runBoundedQueue(total, requestedJobs, worker) {
4761
5605
  if (total === 0) return [];
4762
- const safeRequestedJobs = Number.isFinite(requestedJobs) ? Math.floor(requestedJobs) : 1;
4763
- const concurrency = Math.max(1, Math.min(total, safeRequestedJobs));
5606
+ const concurrency = Math.max(1, Math.min(total, Number.isFinite(requestedJobs) ? Math.floor(requestedJobs) : 1));
4764
5607
  const results = Array.from({ length: total });
4765
5608
  let nextIndex = 0;
4766
5609
  const runWorker = async () => {
@@ -4794,7 +5637,7 @@ async function readBatchInput(path, options) {
4794
5637
  reason: `not readable: ${error instanceof Error ? error.message : String(error)}`
4795
5638
  };
4796
5639
  }
4797
- if (isProbablyBinary(buffer)) return {
5640
+ if (options.detectBinary && isProbablyBinary(buffer)) return {
4798
5641
  type: "skip",
4799
5642
  path,
4800
5643
  reason: "binary file"
@@ -4815,7 +5658,8 @@ async function countBatchInputsWithJobs(filePaths, options) {
4815
5658
  const entries = await runBoundedQueue(filePaths.length, options.jobs, async (index) => {
4816
5659
  const loaded = await readBatchInput(filePaths[index], {
4817
5660
  requestedJobs: options.jobs,
4818
- limits
5661
+ limits,
5662
+ detectBinary: options.detectBinary ?? true
4819
5663
  });
4820
5664
  if (loaded.type === "skip") {
4821
5665
  completed += 1;
@@ -5068,6 +5912,7 @@ async function runBatchCount(options) {
5068
5912
  section: options.section,
5069
5913
  detectorMode: options.wcOptions.detector ?? "regex",
5070
5914
  wcOptions: options.wcOptions,
5915
+ detectBinary: options.detectBinary,
5071
5916
  preserveCollectorSegments: options.preserveCollectorSegments,
5072
5917
  detectorEvidence: options.detectorEvidence,
5073
5918
  debugVerbosity: options.debug.verbosity,
@@ -5095,6 +5940,7 @@ async function runBatchCount(options) {
5095
5940
  section: options.section,
5096
5941
  detectorMode: options.wcOptions.detector ?? "regex",
5097
5942
  wcOptions: options.wcOptions,
5943
+ detectBinary: options.detectBinary,
5098
5944
  preserveCollectorSegments: options.preserveCollectorSegments,
5099
5945
  detectorEvidence: options.detectorEvidence,
5100
5946
  debugVerbosity: options.debug.verbosity,
@@ -5110,6 +5956,7 @@ async function runBatchCount(options) {
5110
5956
  section: options.section,
5111
5957
  detectorMode: options.wcOptions.detector ?? "regex",
5112
5958
  wcOptions: options.wcOptions,
5959
+ detectBinary: options.detectBinary,
5113
5960
  preserveCollectorSegments: options.preserveCollectorSegments,
5114
5961
  detectorEvidence: options.detectorEvidence,
5115
5962
  debugVerbosity: options.debug.verbosity,
@@ -5394,13 +6241,14 @@ async function executeBatchCount({ argv, options, runtime, resolved, debug, teeE
5394
6241
  pathInputs: options.path ?? [],
5395
6242
  batchOptions,
5396
6243
  extensionFilter,
6244
+ detectBinary: options.pathDetectBinary,
5397
6245
  section: options.section,
5398
6246
  wcOptions: resolved.wcOptions,
5399
6247
  preserveCollectorSegments: options.format === "json",
5400
6248
  detectorEvidence: Boolean(options.detectorEvidence),
5401
6249
  debug,
5402
6250
  progressReporter: createBatchProgressReporter({
5403
- enabled: options.format === "standard" && options.progress,
6251
+ enabled: options.format === "standard" && options.progressMode !== "off",
5404
6252
  stream: runtime.stderr ?? process.stderr,
5405
6253
  clearOnFinish: !(mirrorDebugToTerminal || options.keepProgress)
5406
6254
  }),
@@ -5603,6 +6451,12 @@ async function executeSingleCount({ textTokens, options, resolved, debug }) {
5603
6451
  }
5604
6452
  //#endregion
5605
6453
  //#region src/command.ts
6454
+ function emitConfigNotes(notes) {
6455
+ for (const note of notes) {
6456
+ const warningLine = note.startsWith("Warning:") ? note : `Warning: ${note}`;
6457
+ console.error(import_picocolors.default.yellow(warningLine));
6458
+ }
6459
+ }
5606
6460
  async function runCli(argv = process.argv, runtime = {}) {
5607
6461
  if (isExplicitDoctorInvocation(argv)) {
5608
6462
  await executeDoctorCommand({
@@ -5612,7 +6466,10 @@ async function runCli(argv = process.argv, runtime = {}) {
5612
6466
  return;
5613
6467
  }
5614
6468
  if (isExplicitInspectInvocation(argv)) {
5615
- await executeInspectCommand({ argv });
6469
+ await executeInspectCommand({
6470
+ argv,
6471
+ runtime
6472
+ });
5616
6473
  return;
5617
6474
  }
5618
6475
  const program = new Command();
@@ -5623,8 +6480,8 @@ async function runCli(argv = process.argv, runtime = {}) {
5623
6480
  };
5624
6481
  program.name("word-counter").description("Locale-aware word counting powered by Intl.Segmenter.").version(getFormattedVersionLabel(), "-v, --version", "output the version number").addHelpText("after", "\nCommands:\n inspect [options] [text...] inspect detector behavior without count output\n doctor [options] report runtime diagnostics for this host");
5625
6482
  configureProgramOptions(program, parseMode);
5626
- program.action(async (textTokens, options) => {
5627
- if (options.printJobsLimit) {
6483
+ program.action(async (textTokens, rawOptions) => {
6484
+ if (rawOptions.printJobsLimit) {
5628
6485
  try {
5629
6486
  validateStandalonePrintJobsLimitUsage(argv);
5630
6487
  } catch (error) {
@@ -5635,6 +6492,23 @@ async function runCli(argv = process.argv, runtime = {}) {
5635
6492
  console.log(JSON.stringify(resolveBatchJobsLimit()));
5636
6493
  return;
5637
6494
  }
6495
+ let options = {
6496
+ ...rawOptions,
6497
+ pathDetectBinary: rawOptions.pathDetectBinary ?? true,
6498
+ progressMode: deriveInitialCountProgressMode(program, rawOptions.progress)
6499
+ };
6500
+ try {
6501
+ const resolvedConfig = await resolveWordCounterConfig({
6502
+ env: runtime.env,
6503
+ cwd: runtime.cwd
6504
+ });
6505
+ options = applyConfigToCountOptions(options, resolvedConfig.config, deriveCountCliSources(program));
6506
+ if (!options.quietWarnings) emitConfigNotes(resolvedConfig.notes);
6507
+ } catch (error) {
6508
+ const message = error instanceof Error ? error.message : String(error);
6509
+ program.error(import_picocolors.default.red(message));
6510
+ return;
6511
+ }
5638
6512
  const debugEnabled = Boolean(options.debug);
5639
6513
  const debugReportPath = resolveDebugReportPathOption(options.debugReport);
5640
6514
  const debugReportEnabled = options.debugReport !== void 0 && options.debugReport !== false;