@stll/anonymize-cli 2.0.2 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +1 -2
- package/dist/cli.mjs.map +1 -1
- package/package.json +5 -5
package/dist/cli.mjs
CHANGED
|
@@ -318,7 +318,7 @@ const loadCliDictionaries = async ({ languages, countries }) => {
|
|
|
318
318
|
};
|
|
319
319
|
//#endregion
|
|
320
320
|
//#region package.json
|
|
321
|
-
var version = "2.0
|
|
321
|
+
var version = "2.2.0";
|
|
322
322
|
//#endregion
|
|
323
323
|
//#region src/docx.ts
|
|
324
324
|
const DOCX_SESSION_KEY_BYTES = 32;
|
|
@@ -929,7 +929,6 @@ const buildPipelineConfig = async (opts, loadDictionaries) => {
|
|
|
929
929
|
...opts.countries === void 0 ? {} : { denyListCountries: [...opts.countries] },
|
|
930
930
|
enableGazetteer: false,
|
|
931
931
|
enableCountries: true,
|
|
932
|
-
enableNer: false,
|
|
933
932
|
enableConfidenceBoost: true,
|
|
934
933
|
enableCoreference: true,
|
|
935
934
|
enableZoneClassification: true,
|
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":["splitList","parseThreshold","canonicalPath","pkg.version","known"],"sources":["../src/args.ts","../src/dictionary-scope.ts","../src/dictionaries.ts","../package.json","../src/docx.ts","../src/main.ts","../src/cli.ts"],"sourcesContent":["import { availableParallelism } from \"node:os\";\nimport { parseArgs } from \"node:util\";\n\nexport const CLI_MODES = [\"replace\", \"redact\"] as const;\nexport type CliMode = (typeof CLI_MODES)[number];\n\nexport const DEFAULT_THRESHOLD = 0.3;\nexport const DEFAULT_REDACT_STRING = \"[REDACTED]\";\n\n/** Upper bound on the default worker count; batch I/O overlap\n * saturates well before this, and redaction itself is a\n * synchronous native call serialized on the JS thread. */\nexport const MAX_DEFAULT_WORKERS = 4;\n\n/** Default batch concurrency: min(4, cores). Workers overlap\n * file reads/writes; the shared native pipeline runs each\n * redaction to completion on the single JS thread. */\nexport const defaultWorkerCount = (): number =>\n Math.max(1, Math.min(MAX_DEFAULT_WORKERS, availableParallelism()));\n\n/** Invalid invocation; printed with usage hint, exit code 2. */\nexport class UsageError extends Error {}\n\nexport type CliOptions = {\n files: string[];\n output?: string | undefined;\n mode: CliMode;\n keyPath?: string | undefined;\n deanonymiseKeyPath?: string | undefined;\n revert?: string[] | undefined;\n recursive: boolean;\n workers: number;\n labels?: string[] | undefined;\n languages?: string[] | undefined;\n countries?: string[] | undefined;\n threshold: number;\n redactString: string;\n json: boolean;\n quiet: boolean;\n help: boolean;\n version: boolean;\n listLabels: boolean;\n capabilities: boolean;\n};\n\nexport const HELP = `Usage: anonymize [options] [file|dir ...]\n\nDetect and anonymize PII in text. Reads the given files, or stdin\nwhen no files are given. A directory argument processes the text\nfiles inside it (add --recursive to descend into subdirectories).\nWrites to stdout, or to --output.\nAll processing is local; the CLI makes no network calls.\n\nDOCX workflows:\n Run \"anonymize docx --help\" for structure-preserving DOCX anonymization\n and restoration with encrypted session archives.\n\nOptions:\n -o, --output <path> Output file, or directory for batch\n input (multiple files or a directory)\n -m, --mode <mode> \"replace\" (reversible [PERSON_1]\n placeholders) or \"redact\"\n (default: replace)\n -k, --key <path> Write the redaction key as JSON\n (single input, replace mode)\n -d, --deanonymise <path> Restore redacted text using the\n redaction key at <path>\n --revert <term> With --deanonymise, restore only the\n given entity. Match a placeholder token\n (\"[PERSON_1]\") or an original value\n (\"Jan Novák\"), case-sensitive exact.\n Repeatable; others stay redacted\n -r, --recursive Descend into subdirectories when a\n directory is given as input\n --workers <n> Batch files to process concurrently\n (default: min(${MAX_DEFAULT_WORKERS}, CPU cores)). Overlaps\n file I/O; redaction is serialized on\n the JS thread\n --labels <list> Comma-separated entity labels to detect\n (default: all). Accepts canonical labels\n (\"email address\"), short aliases (email,\n phone, org, dob, ssn), and hyphen/underscore\n forms (\"credit-card-number\")\n --languages <list> Name-corpus languages, e.g. \"cs,de,en\"\n (default: all bundled)\n --countries <list> ISO 3166-1 alpha-2 codes scoping deny\n lists and city data, e.g. \"CZ,DE,GB\"\n (default: all deny lists; city data\n for a 30-country default set)\n --threshold <n> Minimum confidence score, 0-1\n (default: ${DEFAULT_THRESHOLD})\n --redact-string <s> Replacement text in redact mode\n (default: \"${DEFAULT_REDACT_STRING}\")\n --json Emit JSON (entities + redacted text) to\n stdout (single input only)\n --quiet Suppress the summary on stderr\n -h, --help Show this help\n -v, --version Show the version\n --list-labels List detectable entity labels and the\n short aliases accepted by --labels\n --capabilities Emit the versioned capability manifest as JSON\n\nBatch input (directory or multiple files):\n Requires --output <directory>. The input tree is mirrored\n into the output directory. Directory walks process regular\n files only and skip likely-binary files (a NUL byte in the\n first 8 KiB); explicitly named files are always processed.\n The stderr summary reports how many files were processed,\n failed, and skipped; any failure sets exit code 1.\n --key and --json apply to single inputs only.\n\nInteractive prompt:\n When run on files from a terminal without --countries or\n --languages, the CLI asks once which country scope to load.\n Piped stdin/stderr or --quiet skips the prompt, so scripts\n and agents never block on input.\n\nExit codes:\n 0 success\n 1 runtime error (message on stderr)\n 2 usage error (message on stderr)\n\nJSON output (--json):\n { \"entityCount\": number,\n \"entities\": [{ \"start\": number, \"end\": number,\n \"label\": string, \"text\": string,\n \"score\": number, \"source\": string }],\n \"redactedText\": string }\n Offsets are UTF-16 code-unit indexes into the input.\n The stderr summary contains entity counts only, never\n the detected text.\n\nExamples:\n anonymize contract.txt > contract.anon.txt\n anonymize -k contract.key.json -o contract.anon.txt contract.txt\n anonymize -d contract.key.json contract.anon.txt\n anonymize -r --workers 8 -o out/ docs/\n anonymize -d key.json --revert \"[PERSON_1]\" contract.anon.txt\n cat notes.md | anonymize --countries CZ,SK --languages cs,sk\n anonymize --json --quiet input.txt | jq '.entities[].label'\n anonymize docx --help\n`;\n\nconst splitList = (value: string): string[] => [\n ...new Set(\n value\n .split(\",\")\n .map((part) => part.trim())\n .filter((part) => part.length > 0),\n ),\n];\n\nconst parseThreshold = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new UsageError(\n `--threshold must be a number between 0 and 1, got \"${raw}\"`,\n );\n }\n return value;\n};\n\nconst parseWorkers = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isInteger(value) || value < 1) {\n throw new UsageError(`--workers must be a positive integer, got \"${raw}\"`);\n }\n return value;\n};\n\nconst parseMode = (raw: string): CliMode => {\n const mode = CLI_MODES.find((candidate) => candidate === raw);\n if (!mode) {\n throw new UsageError(\n `--mode must be one of: ${CLI_MODES.join(\", \")}; got \"${raw}\"`,\n );\n }\n return mode;\n};\n\nconst COUNTRY_CODE_RE = /^[A-Za-z]{2}$/;\n\nexport const parseCountries = (raw: string): string[] => {\n const countries = [\n ...new Set(splitList(raw).map((code) => code.toUpperCase())),\n ];\n const invalid = countries.find((code) => !COUNTRY_CODE_RE.test(code));\n if (invalid) {\n throw new UsageError(\n `--countries expects ISO 3166-1 alpha-2 codes (e.g. \"CZ,DE\"), got \"${invalid}\"`,\n );\n }\n return countries;\n};\n\nexport const parseCliArgs = (argv: string[]): CliOptions => {\n let parsed: ReturnType<typeof parseArgs<typeof PARSE_CONFIG>>;\n try {\n parsed = parseArgs({ ...PARSE_CONFIG, args: argv });\n } catch (err) {\n throw new UsageError(err instanceof Error ? err.message : String(err));\n }\n const { values, positionals } = parsed;\n\n return {\n files: positionals,\n output: values.output,\n mode: values.mode === undefined ? \"replace\" : parseMode(values.mode),\n keyPath: values.key,\n deanonymiseKeyPath: values.deanonymise,\n revert:\n values.revert === undefined || values.revert.length === 0\n ? undefined\n : values.revert,\n recursive: values.recursive === true,\n workers:\n values.workers === undefined\n ? defaultWorkerCount()\n : parseWorkers(values.workers),\n labels: values.labels === undefined ? undefined : splitList(values.labels),\n languages:\n values.languages === undefined ? undefined : splitList(values.languages),\n countries:\n values.countries === undefined\n ? undefined\n : parseCountries(values.countries),\n threshold:\n values.threshold === undefined\n ? DEFAULT_THRESHOLD\n : parseThreshold(values.threshold),\n redactString: values[\"redact-string\"] ?? DEFAULT_REDACT_STRING,\n json: values.json === true,\n quiet: values.quiet === true,\n help: values.help === true,\n version: values.version === true,\n listLabels: values[\"list-labels\"] === true,\n capabilities: values.capabilities === true,\n };\n};\n\nconst PARSE_CONFIG = {\n allowPositionals: true,\n strict: true,\n options: {\n output: { type: \"string\", short: \"o\" },\n mode: { type: \"string\", short: \"m\" },\n key: { type: \"string\", short: \"k\" },\n deanonymise: { type: \"string\", short: \"d\" },\n revert: { type: \"string\", multiple: true },\n recursive: { type: \"boolean\", short: \"r\" },\n workers: { type: \"string\" },\n labels: { type: \"string\" },\n languages: { type: \"string\" },\n countries: { type: \"string\" },\n threshold: { type: \"string\" },\n \"redact-string\": { type: \"string\" },\n json: { type: \"boolean\" },\n quiet: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n \"list-labels\": { type: \"boolean\" },\n capabilities: { type: \"boolean\" },\n },\n} as const;\n","/* Pure helpers shared by the npm and embedded dictionary\n * loaders. Must stay free of @stll/anonymize-data imports\n * so the compiled binary's bundle excludes the raw JSON\n * dictionary modules. */\nimport type { Dictionaries, DictionaryMeta } from \"@stll/anonymize\";\n\nimport { UsageError } from \"./args\";\n\nexport const NAME_DICTIONARY_PREFIXES = [\n \"names/first/\",\n \"names/surnames/\",\n] as const;\n\n/** Language code of a name dictionary id, or null. */\nexport const nameLanguageOfDictionary = (id: string): string | null => {\n const prefix = NAME_DICTIONARY_PREFIXES.find((p) => id.startsWith(p));\n return prefix ? id.slice(prefix.length) : null;\n};\n\nexport type DictionaryScope = {\n languages?: readonly string[] | undefined;\n countries?: readonly string[] | undefined;\n};\n\nconst pickKeys = <T>(\n record: Record<string, T>,\n keep: (key: string) => boolean,\n): Record<string, T> => {\n const result: Record<string, T> = {};\n for (const [key, value] of Object.entries(record)) {\n if (keep(key)) result[key] = value;\n }\n return result;\n};\n\n/** Dictionaries with every section present (possibly empty). */\nexport type ScopedDictionaries = {\n firstNames: Record<string, readonly string[]>;\n surnames: Record<string, readonly string[]>;\n denyList: Record<string, readonly string[]>;\n denyListMeta: Record<string, DictionaryMeta>;\n citiesByCountry: Record<string, readonly string[]>;\n};\n\n/**\n * Scope a fully loaded dictionary set to the requested\n * languages and countries. Mirrors the pre-load scoping\n * the npm loader does in dictionaries.ts; used by the\n * embedded loader, which always starts from the full set.\n */\nexport const filterDictionaries = (\n all: Dictionaries,\n { languages, countries }: DictionaryScope,\n): ScopedDictionaries => {\n const firstNames = all.firstNames ?? {};\n const surnames = all.surnames ?? {};\n const allDenyList = all.denyList ?? {};\n const allDenyListMeta = all.denyListMeta ?? {};\n\n if (languages !== undefined) {\n const available = Object.keys(firstNames);\n const invalid = languages.find((lang) => !available.includes(lang));\n if (invalid) {\n throw new UsageError(\n `--languages: no name dictionary for \"${invalid}\"; available: ${available.join(\", \")}`,\n );\n }\n }\n const keepLanguage = (lang: string): boolean =>\n languages === undefined || languages.includes(lang);\n const keepCountry = (country: string | null): boolean =>\n countries === undefined || country === null || countries.includes(country);\n\n const denyListMeta: Record<string, DictionaryMeta> = {};\n const denyList: Record<string, readonly string[]> = {};\n for (const [id, meta] of Object.entries(allDenyListMeta)) {\n if (!keepCountry(meta.country)) continue;\n const nameLang = nameLanguageOfDictionary(id);\n if (nameLang !== null && !keepLanguage(nameLang)) continue;\n const entries = allDenyList[id];\n if (entries === undefined) continue;\n denyListMeta[id] = meta;\n denyList[id] = entries;\n }\n\n return {\n firstNames: pickKeys(firstNames, keepLanguage),\n surnames: pickKeys(surnames, keepLanguage),\n denyList,\n denyListMeta,\n citiesByCountry: pickKeys(all.citiesByCountry ?? {}, (country) =>\n keepCountry(country),\n ),\n };\n};\n","import type { Dictionaries, DictionaryMeta } from \"@stll/anonymize\";\nimport {\n ALL_DICTIONARY_IDS,\n DICTIONARY_META,\n loadCityDictionary,\n loadDictionary,\n loadNameDictionaries,\n type NameLanguage,\n} from \"@stll/anonymize-data\";\n\nimport { UsageError } from \"./args\";\nimport type { DictionaryScope } from \"./dictionary-scope\";\nimport {\n NAME_DICTIONARY_PREFIXES,\n nameLanguageOfDictionary,\n} from \"./dictionary-scope\";\n\n/**\n * Countries with bundled city dictionaries that are\n * loaded when no --countries scope is given.\n */\nconst DEFAULT_CITY_COUNTRIES = [\n \"AT\",\n \"AU\",\n \"BE\",\n \"BG\",\n \"BR\",\n \"CA\",\n \"CH\",\n \"CZ\",\n \"DE\",\n \"DK\",\n \"ES\",\n \"FI\",\n \"FR\",\n \"GB\",\n \"GR\",\n \"HR\",\n \"HU\",\n \"IE\",\n \"IT\",\n \"LU\",\n \"NL\",\n \"NO\",\n \"NZ\",\n \"PL\",\n \"PT\",\n \"RO\",\n \"SE\",\n \"SI\",\n \"SK\",\n \"US\",\n] as const;\n\nconst availableNameLanguages = (): readonly string[] =>\n ALL_DICTIONARY_IDS.filter((id) =>\n id.startsWith(NAME_DICTIONARY_PREFIXES[0]),\n ).map((id) => id.slice(NAME_DICTIONARY_PREFIXES[0].length));\n\nconst validateLanguages = (\n languages: readonly string[],\n): readonly NameLanguage[] => {\n const available = availableNameLanguages();\n const invalid = languages.find((lang) => !available.includes(lang));\n if (invalid) {\n throw new UsageError(\n `--languages: no name dictionary for \"${invalid}\"; available: ${available.join(\", \")}`,\n );\n }\n // SAFETY: every entry was checked against the bundled\n // name dictionary ids, which define NameLanguage.\n return languages as readonly NameLanguage[];\n};\n\nexport type LoadCliDictionariesOptions = DictionaryScope;\n\n/**\n * Load the bundled @stll/anonymize-data dictionaries,\n * scoped to the requested languages and countries.\n */\nexport const loadCliDictionaries = async ({\n languages,\n countries,\n}: LoadCliDictionariesOptions): Promise<Dictionaries> => {\n const nameLanguages =\n languages === undefined ? undefined : validateLanguages(languages);\n\n const denyIds = ALL_DICTIONARY_IDS.filter((id) => {\n const meta = DICTIONARY_META[id];\n if (\n countries &&\n meta.country !== null &&\n !countries.includes(meta.country)\n ) {\n return false;\n }\n const nameLang = nameLanguageOfDictionary(id);\n if (nameLang !== null && nameLanguages !== undefined) {\n return nameLanguages.includes(\n // SAFETY: nameLang comes from a bundled dictionary\n // id, which defines NameLanguage.\n nameLang as NameLanguage,\n );\n }\n return true;\n });\n\n const cityCountries = countries ?? DEFAULT_CITY_COUNTRIES;\n\n const [names, denyEntries, cityEntries] = await Promise.all([\n loadNameDictionaries(nameLanguages),\n Promise.all(\n denyIds.map(async (id) => ({ id, entries: await loadDictionary(id) })),\n ),\n Promise.all(\n cityCountries.map(async (country) => ({\n country,\n entries: await loadCityDictionary(country),\n })),\n ),\n ]);\n\n const denyList: Record<string, readonly string[]> = {};\n const denyListMeta: Record<string, DictionaryMeta> = {};\n for (const { id, entries } of denyEntries) {\n denyList[id] = entries;\n // SAFETY: anonymize-data categories match\n // DenyListCategory at runtime.\n denyListMeta[id] = DICTIONARY_META[id] as DictionaryMeta;\n }\n\n const citiesByCountry: Record<string, readonly string[]> = {};\n for (const { country, entries } of cityEntries) {\n if (entries.length > 0) citiesByCountry[country] = entries;\n }\n\n return {\n firstNames: names.firstNames,\n surnames: names.surnames,\n denyList,\n denyListMeta,\n citiesByCountry,\n };\n};\n","","import { randomUUID } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport {\n type FileHandle,\n link,\n lstat,\n open,\n readFile,\n rename,\n unlink,\n} from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport type { NativeOpenSessionArchiveOptions } from \"@stll/anonymize\";\nimport {\n DOCX_COVERAGE_MODES,\n anonymizeDocx,\n restoreDocxText,\n type DocxAnonymizationSession,\n type DocxAnonymizationSummary,\n type DocxRestorationResult,\n type DocxRestorationSession,\n} from \"@stll/anonymize-docx\";\n\nimport { parseCountries, UsageError } from \"./args\";\n\nconst DOCX_SESSION_KEY_BYTES = 32;\nconst DOCX_SESSION_LOCK_SUFFIX = \".lock\";\nconst MAX_EPOCH_SECONDS = 4_294_967_295;\n\nconst DOCX_SESSION_MODES = {\n continue: \"continue\",\n create: \"create\",\n} as const;\n\ntype DocxSessionMode =\n (typeof DOCX_SESSION_MODES)[keyof typeof DOCX_SESSION_MODES];\n\ntype DocxDetectionOptions = {\n labels?: string[] | undefined;\n languages?: string[] | undefined;\n countries?: string[] | undefined;\n threshold: number;\n};\n\ntype DocxCommonOptions = {\n inputPath: string;\n outputPath: string;\n sessionArchivePath: string;\n sessionKeyPath: string;\n sessionId: string;\n coverage: (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n observedAtEpochSeconds?: number | undefined;\n json: boolean;\n quiet: boolean;\n};\n\ntype DocxCommand =\n | { type: \"help\" }\n | ({\n type: \"anonymize\";\n sessionMode: DocxSessionMode;\n detection: DocxDetectionOptions;\n } & DocxCommonOptions)\n | ({ type: \"restore\" } & DocxCommonOptions);\n\nexport type DocxPipelineRequest =\n | { type: \"anonymize\"; detection: DocxDetectionOptions }\n | { type: \"restore\" };\n\ntype DocxCliSession = DocxAnonymizationSession &\n DocxRestorationSession & {\n toEncryptedArchive: (key: Uint8Array) => Uint8Array;\n toEncryptedArchiveAt: (\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ) => Uint8Array;\n };\n\nexport type DocxCliPipeline = {\n createRedactionSession: (sessionId: string) => DocxCliSession;\n restoreEncryptedRedactionSession: (\n options: NativeOpenSessionArchiveOptions,\n ) => DocxCliSession;\n};\n\ntype RunDocxCommandOptions = {\n argv: readonly string[];\n preparePipeline: (request: DocxPipelineRequest) => Promise<DocxCliPipeline>;\n};\n\nconst DOCX_HELP = `Usage:\n anonymize docx anonymize [options] <input.docx>\n anonymize docx restore [options] <input.docx>\n\nAnonymize or restore one DOCX file with an encrypted redaction session.\nDocument and session outputs are written atomically and never overwrite the\ninput, key file, or an existing document output.\n\nRequired options:\n -o, --output <path> New DOCX output path\n --session-archive <path> Encrypted session archive path\n --session-key-file <path>\n File containing exactly 32 raw key bytes\n --session-id <id> Expected opaque session identity\n\nAnonymize options:\n --session-mode <mode> \"create\" or \"continue\" (required)\n --coverage <mode> \"require-full\" (default) or \"allow-partial\"\n --labels <list> Comma-separated entity labels\n --languages <list> Name-corpus languages, e.g. \"cs,de,en\"\n --countries <list> ISO 3166-1 alpha-2 country codes\n --threshold <n> Minimum confidence score 0-1 (default: 0.3)\n\nRestore options:\n --coverage <mode> \"require-full\" (default) or \"allow-partial\"\n\nCommon options:\n --observed-at <seconds> Deterministic Unix timestamp for lifecycle checks\n --json Print the aggregate audit-safe summary as JSON\n --quiet Suppress the human-readable stderr summary\n -h, --help Show this help\n\nThe session key is read from a file, never from a command argument. In create\nmode the archive path must not exist. Continue mode atomically replaces the\nexisting archive only after the DOCX rewrite succeeds. It holds an exclusive\n\"<archive>.lock\" sidecar throughout the continuation to prevent lost updates.\nCaller-supplied detection plans and interactive review are available through the\npackage API, not this CLI.\n`;\n\nconst splitList = (value: string): string[] => [\n ...new Set(\n value\n .split(\",\")\n .map((part) => part.trim())\n .filter((part) => part.length > 0),\n ),\n];\n\nconst parseThreshold = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new UsageError(\n `--threshold must be a number between 0 and 1, got \"${raw}\"`,\n );\n }\n return value;\n};\n\nconst parseEpochSeconds = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isInteger(value) || value < 0 || value > MAX_EPOCH_SECONDS) {\n throw new UsageError(\n `--observed-at must be an integer from 0 to ${MAX_EPOCH_SECONDS}, got \"${raw}\"`,\n );\n }\n return value;\n};\n\nconst parseCoverage = (\n raw: string | undefined,\n): DocxCommonOptions[\"coverage\"] => {\n const value = raw ?? DOCX_COVERAGE_MODES.requireFull;\n if (\n value === DOCX_COVERAGE_MODES.requireFull ||\n value === DOCX_COVERAGE_MODES.allowPartial\n ) {\n return value;\n }\n throw new UsageError(\n `--coverage must be one of: ${Object.values(DOCX_COVERAGE_MODES).join(\", \")}; got \"${value}\"`,\n );\n};\n\nconst parseSessionMode = (raw: string | undefined): DocxSessionMode => {\n if (raw === undefined) {\n throw new UsageError(\"--session-mode is required for DOCX anonymization\");\n }\n if (\n raw === DOCX_SESSION_MODES.create ||\n raw === DOCX_SESSION_MODES.continue\n ) {\n return raw;\n }\n throw new UsageError(\n `--session-mode must be one of: ${Object.values(DOCX_SESSION_MODES).join(\", \")}; got \"${raw}\"`,\n );\n};\n\nconst required = (value: string | undefined, flag: string): string => {\n if (value === undefined || value.length === 0) {\n throw new UsageError(`${flag} is required for DOCX workflows`);\n }\n return value;\n};\n\ntype ParsedCommonValues = {\n output?: string | undefined;\n \"session-archive\"?: string | undefined;\n \"session-key-file\"?: string | undefined;\n \"session-id\"?: string | undefined;\n coverage?: string | undefined;\n \"observed-at\"?: string | undefined;\n json?: boolean | undefined;\n quiet?: boolean | undefined;\n};\n\nconst commonOptions = (\n values: ParsedCommonValues,\n positionals: readonly string[],\n): DocxCommonOptions => {\n if (positionals.length !== 1) {\n throw new UsageError(\"DOCX workflows require exactly one input file\");\n }\n const inputPath = positionals.at(0);\n if (inputPath === undefined) {\n throw new UsageError(\"DOCX workflows require exactly one input file\");\n }\n return {\n inputPath,\n outputPath: required(values.output, \"--output\"),\n sessionArchivePath: required(\n values[\"session-archive\"],\n \"--session-archive\",\n ),\n sessionKeyPath: required(values[\"session-key-file\"], \"--session-key-file\"),\n sessionId: required(values[\"session-id\"], \"--session-id\"),\n coverage: parseCoverage(values.coverage),\n observedAtEpochSeconds:\n values[\"observed-at\"] === undefined\n ? undefined\n : parseEpochSeconds(values[\"observed-at\"]),\n json: values.json === true,\n quiet: values.quiet === true,\n };\n};\n\nconst parseDocxCommand = (argv: readonly string[]): DocxCommand => {\n const action = argv.at(0);\n if (action === undefined || action === \"--help\" || action === \"-h\") {\n return { type: \"help\" };\n }\n const args = argv.slice(1);\n if (action === \"anonymize\") {\n let parsed: ReturnType<typeof parseArgs<typeof DOCX_ANONYMIZE_CONFIG>>;\n try {\n parsed = parseArgs({ ...DOCX_ANONYMIZE_CONFIG, args: [...args] });\n } catch (error) {\n throw new UsageError(\n error instanceof Error ? error.message : String(error),\n );\n }\n if (parsed.values.help === true) {\n return { type: \"help\" };\n }\n return {\n type: \"anonymize\",\n ...commonOptions(parsed.values, parsed.positionals),\n sessionMode: parseSessionMode(parsed.values[\"session-mode\"]),\n detection: {\n labels:\n parsed.values.labels === undefined\n ? undefined\n : splitList(parsed.values.labels),\n languages:\n parsed.values.languages === undefined\n ? undefined\n : splitList(parsed.values.languages),\n countries:\n parsed.values.countries === undefined\n ? undefined\n : parseCountries(parsed.values.countries),\n threshold:\n parsed.values.threshold === undefined\n ? 0.3\n : parseThreshold(parsed.values.threshold),\n },\n };\n }\n if (action === \"restore\") {\n let parsed: ReturnType<typeof parseArgs<typeof DOCX_RESTORE_CONFIG>>;\n try {\n parsed = parseArgs({ ...DOCX_RESTORE_CONFIG, args: [...args] });\n } catch (error) {\n throw new UsageError(\n error instanceof Error ? error.message : String(error),\n );\n }\n if (parsed.values.help === true) {\n return { type: \"help\" };\n }\n return {\n type: \"restore\",\n ...commonOptions(parsed.values, parsed.positionals),\n };\n }\n throw new UsageError(\n `unknown DOCX action \"${action}\"; expected \"anonymize\" or \"restore\"`,\n );\n};\n\nconst DOCX_COMMON_PARSE_OPTIONS = {\n output: { type: \"string\", short: \"o\" },\n \"session-archive\": { type: \"string\" },\n \"session-key-file\": { type: \"string\" },\n \"session-id\": { type: \"string\" },\n coverage: { type: \"string\" },\n \"observed-at\": { type: \"string\" },\n json: { type: \"boolean\" },\n quiet: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n} as const;\n\nconst DOCX_ANONYMIZE_CONFIG = {\n allowPositionals: true,\n strict: true,\n options: {\n ...DOCX_COMMON_PARSE_OPTIONS,\n \"session-mode\": { type: \"string\" },\n labels: { type: \"string\" },\n languages: { type: \"string\" },\n countries: { type: \"string\" },\n threshold: { type: \"string\" },\n },\n} as const;\n\nconst DOCX_RESTORE_CONFIG = {\n allowPositionals: true,\n strict: true,\n options: DOCX_COMMON_PARSE_OPTIONS,\n} as const;\n\nconst canonicalPath = (path: string): string => {\n try {\n return realpathSync(path);\n } catch {\n return resolve(path);\n }\n};\n\nconst sessionArchiveLockPath = (archivePath: string): string =>\n `${canonicalPath(archivePath)}${DOCX_SESSION_LOCK_SUFFIX}`;\n\nconst assertDistinctPaths = (\n paths: readonly { path: string; flag: string }[],\n): void => {\n const seen = new Map<string, string>();\n for (const entry of paths) {\n const canonical = canonicalPath(entry.path);\n const existing = seen.get(canonical);\n if (existing !== undefined) {\n throw new UsageError(`${entry.flag} collides with ${existing}`);\n }\n seen.set(canonical, `${entry.flag} \"${entry.path}\"`);\n }\n};\n\nconst isNodeError = (\n error: unknown,\n code: string,\n): error is NodeJS.ErrnoException =>\n error instanceof Error && \"code\" in error && error.code === code;\n\nconst assertPathDoesNotExist = async (\n path: string,\n flag: string,\n): Promise<void> => {\n try {\n await lstat(path);\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return;\n }\n throw error;\n }\n throw new UsageError(`${flag} refuses to overwrite existing path \"${path}\"`);\n};\n\nconst preflightDocxCommand = async (\n command: Exclude<DocxCommand, { type: \"help\" }>,\n): Promise<void> => {\n const paths = [\n { path: command.inputPath, flag: \"input\" },\n { path: command.outputPath, flag: \"--output\" },\n { path: command.sessionArchivePath, flag: \"--session-archive\" },\n { path: command.sessionKeyPath, flag: \"--session-key-file\" },\n ];\n if (\n command.type === \"anonymize\" &&\n command.sessionMode === DOCX_SESSION_MODES.continue\n ) {\n paths.push({\n path: sessionArchiveLockPath(command.sessionArchivePath),\n flag: \"session archive lock\",\n });\n }\n assertDistinctPaths(paths);\n await assertPathDoesNotExist(command.outputPath, \"--output\");\n if (\n command.type === \"anonymize\" &&\n command.sessionMode === DOCX_SESSION_MODES.create\n ) {\n await assertPathDoesNotExist(\n command.sessionArchivePath,\n \"--session-archive\",\n );\n }\n};\n\ntype SessionArchiveLock = {\n release: () => Promise<void>;\n};\n\ntype OperationResult =\n | { type: \"succeeded\" }\n | { type: \"failed\"; error: unknown };\n\nconst captureOperationResult = async (\n operation: Promise<void>,\n): Promise<OperationResult> => {\n try {\n await operation;\n return { type: \"succeeded\" };\n } catch (error) {\n return { type: \"failed\", error };\n }\n};\n\nconst acquireSessionArchiveLock = async (\n archivePath: string,\n): Promise<SessionArchiveLock> => {\n const lockPath = sessionArchiveLockPath(archivePath);\n let handle: FileHandle;\n try {\n handle = await open(lockPath, \"wx\", 0o600);\n } catch (error) {\n if (isNodeError(error, \"EEXIST\")) {\n throw new Error(\n `encrypted session archive is locked by another continuation; if no process is running, remove the stale lock \"${lockPath}\"`,\n );\n }\n throw error;\n }\n return {\n release: async () => {\n const closeResult = await captureOperationResult(handle.close());\n const unlinkResult = await captureOperationResult(unlink(lockPath));\n if (closeResult.type === \"failed\") {\n throw closeResult.error;\n }\n if (\n unlinkResult.type === \"failed\" &&\n !isNodeError(unlinkResult.error, \"ENOENT\")\n ) {\n throw unlinkResult.error;\n }\n },\n };\n};\n\nconst readSessionKey = async (path: string): Promise<Uint8Array> => {\n const handle = await open(path, \"r\");\n let key: Uint8Array | undefined;\n try {\n const stats = await handle.stat();\n if (!stats.isFile()) {\n throw new UsageError(\"--session-key-file must be a regular file\");\n }\n if (process.platform !== \"win32\" && (stats.mode & 0o077) !== 0) {\n throw new UsageError(\n \"--session-key-file must not grant permissions to group or other users (use chmod 600)\",\n );\n }\n key = await handle.readFile();\n if (key.byteLength !== DOCX_SESSION_KEY_BYTES) {\n throw new UsageError(\n `--session-key-file must contain exactly ${DOCX_SESSION_KEY_BYTES} raw bytes`,\n );\n }\n await handle.close();\n return key;\n } catch (error) {\n key?.fill(0);\n try {\n await handle.close();\n } catch {\n // Preserve the validation or read error.\n }\n throw error;\n }\n};\n\nconst removeStagedFile = async (path: string | undefined): Promise<void> => {\n if (path === undefined) {\n return;\n }\n try {\n await unlink(path);\n } catch {\n // Best-effort cleanup must not hide the original operation error.\n }\n};\n\nconst stageFile = async (\n target: string,\n content: Uint8Array,\n): Promise<string> => {\n const temporary = join(\n dirname(target),\n `.${basename(target)}.${randomUUID()}.tmp`,\n );\n const handle = await open(temporary, \"wx\", 0o600);\n try {\n await handle.writeFile(content);\n await handle.sync();\n await handle.close();\n } catch (error) {\n try {\n await handle.close();\n } catch {\n // Preserve the write error while cleanup remains best effort.\n }\n await removeStagedFile(temporary);\n throw error;\n }\n return temporary;\n};\n\nconst publishNewFile = async (\n temporary: string,\n target: string,\n flag: string,\n): Promise<void> => {\n try {\n await link(temporary, target);\n } catch (error) {\n if (isNodeError(error, \"EEXIST\")) {\n throw new UsageError(\n `${flag} refuses to overwrite existing path \"${target}\"`,\n );\n }\n throw error;\n }\n await removeStagedFile(temporary);\n};\n\nconst publishReplacement = async (\n temporary: string,\n target: string,\n): Promise<void> => {\n await rename(temporary, target);\n};\n\nconst sessionArchive = (\n session: DocxCliSession,\n key: Uint8Array,\n observedAtEpochSeconds: number | undefined,\n): Uint8Array =>\n observedAtEpochSeconds === undefined\n ? session.toEncryptedArchive(key)\n : session.toEncryptedArchiveAt(key, observedAtEpochSeconds);\n\nconst outputSummary = (\n command: Pick<DocxCommonOptions, \"json\" | \"quiet\">,\n action: \"anonymized\" | \"restored\",\n summary: DocxAnonymizationSummary | Omit<DocxRestorationResult, \"document\">,\n): void => {\n if (command.json) {\n process.stdout.write(`${JSON.stringify(summary, null, 2)}\\n`);\n }\n if (command.quiet) {\n return;\n }\n const coverage = summary.coverage.status;\n if (\"entityCount\" in summary) {\n process.stderr.write(\n `anonymize: DOCX ${action}: ${summary.entityCount} entities, ${summary.appliedReplacementCount} replacements, ${coverage} coverage\\n`,\n );\n return;\n }\n process.stderr.write(\n `anonymize: DOCX ${action}: ${summary.restoredPlaceholderCount} placeholders, ${coverage} coverage\\n`,\n );\n};\n\nconst openSession = (\n pipeline: DocxCliPipeline,\n command: Pick<DocxCommonOptions, \"sessionId\" | \"observedAtEpochSeconds\">,\n archive: Uint8Array,\n key: Uint8Array,\n): DocxCliSession =>\n pipeline.restoreEncryptedRedactionSession({\n archive,\n key,\n expectedSessionId: command.sessionId,\n ...(command.observedAtEpochSeconds === undefined\n ? {}\n : { observedAtEpochSeconds: command.observedAtEpochSeconds }),\n });\n\nconst runDocxAnonymize = async (\n command: Extract<DocxCommand, { type: \"anonymize\" }>,\n pipeline: DocxCliPipeline,\n): Promise<void> => {\n const archivePath =\n command.sessionMode === DOCX_SESSION_MODES.continue\n ? canonicalPath(command.sessionArchivePath)\n : command.sessionArchivePath;\n const archiveLock =\n command.sessionMode === DOCX_SESSION_MODES.continue\n ? await acquireSessionArchiveLock(archivePath)\n : undefined;\n let workflowResult: OperationResult = { type: \"succeeded\" };\n let lockReleaseResult: OperationResult = { type: \"succeeded\" };\n let key: Uint8Array | undefined;\n let documentTemporary: string | undefined;\n let archiveTemporary: string | undefined;\n try {\n key = await readSessionKey(command.sessionKeyPath);\n const [document, existingArchive] = await Promise.all([\n readFile(command.inputPath),\n command.sessionMode === DOCX_SESSION_MODES.continue\n ? readFile(archivePath)\n : Promise.resolve(undefined),\n ]);\n const session =\n existingArchive === undefined\n ? pipeline.createRedactionSession(command.sessionId)\n : openSession(pipeline, command, existingArchive, key);\n const result = anonymizeDocx({\n document,\n session,\n expectedSessionId: command.sessionId,\n policy: { coverage: { mode: command.coverage } },\n ...(command.observedAtEpochSeconds === undefined\n ? {}\n : { observedAtEpochSeconds: command.observedAtEpochSeconds }),\n });\n const encryptedArchive = sessionArchive(\n session,\n key,\n command.observedAtEpochSeconds,\n );\n documentTemporary = await stageFile(command.outputPath, result.document);\n archiveTemporary = await stageFile(archivePath, encryptedArchive);\n if (command.sessionMode === DOCX_SESSION_MODES.create) {\n await publishNewFile(\n archiveTemporary,\n command.sessionArchivePath,\n \"--session-archive\",\n );\n } else {\n await publishReplacement(archiveTemporary, archivePath);\n }\n archiveTemporary = undefined;\n try {\n await publishNewFile(documentTemporary, command.outputPath, \"--output\");\n documentTemporary = undefined;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n `encrypted session archive was updated, but DOCX output could not be published: ${message}`,\n );\n }\n outputSummary(command, \"anonymized\", result.summary);\n } catch (error) {\n workflowResult = { type: \"failed\", error };\n } finally {\n key?.fill(0);\n await Promise.all([\n removeStagedFile(documentTemporary),\n removeStagedFile(archiveTemporary),\n ]);\n if (archiveLock !== undefined) {\n lockReleaseResult = await captureOperationResult(archiveLock.release());\n }\n }\n if (workflowResult.type === \"failed\") {\n throw workflowResult.error;\n }\n if (lockReleaseResult.type === \"failed\") {\n const message =\n lockReleaseResult.error instanceof Error\n ? lockReleaseResult.error.message\n : String(lockReleaseResult.error);\n throw new Error(\n `DOCX and session outputs were published, but the session archive lock could not be released: ${message}`,\n );\n }\n};\n\nconst runDocxRestore = async (\n command: Extract<DocxCommand, { type: \"restore\" }>,\n pipeline: DocxCliPipeline,\n): Promise<void> => {\n const key = await readSessionKey(command.sessionKeyPath);\n try {\n const [document, archive] = await Promise.all([\n readFile(command.inputPath),\n readFile(command.sessionArchivePath),\n ]);\n const session = openSession(pipeline, command, archive, key);\n const result = restoreDocxText({\n document,\n session,\n expectedSessionId: command.sessionId,\n ...(command.observedAtEpochSeconds === undefined\n ? {}\n : { observedAtEpochSeconds: command.observedAtEpochSeconds }),\n });\n if (\n command.coverage === DOCX_COVERAGE_MODES.requireFull &&\n result.coverage.status === \"partial\"\n ) {\n throw new Error(\n \"DOCX contains content outside the fully supported restoration coverage\",\n );\n }\n const temporary = await stageFile(command.outputPath, result.document);\n try {\n await publishNewFile(temporary, command.outputPath, \"--output\");\n } catch (error) {\n await removeStagedFile(temporary);\n throw error;\n }\n const summary: Omit<DocxRestorationResult, \"document\"> = {\n sessionId: result.sessionId,\n restoredBlockCount: result.restoredBlockCount,\n restoredPlaceholderCount: result.restoredPlaceholderCount,\n coverage: result.coverage,\n };\n outputSummary(command, \"restored\", summary);\n } finally {\n key.fill(0);\n }\n};\n\nexport const runDocxCommand = async ({\n argv,\n preparePipeline,\n}: RunDocxCommandOptions): Promise<void> => {\n const command = parseDocxCommand(argv);\n if (command.type === \"help\") {\n process.stdout.write(DOCX_HELP);\n return;\n }\n await preflightDocxCommand(command);\n const pipeline = await preparePipeline(\n command.type === \"anonymize\"\n ? { type: \"anonymize\", detection: command.detection }\n : { type: \"restore\" },\n );\n if (command.type === \"anonymize\") {\n await runDocxAnonymize(command, pipeline);\n return;\n }\n await runDocxRestore(command, pipeline);\n};\n","import { realpathSync } from \"node:fs\";\nimport {\n mkdir,\n open,\n readdir,\n readFile,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { basename, dirname, join, relative, resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport type {\n deanonymise,\n Dictionaries,\n exportRedactionKey,\n NativeAnonymizeBinding,\n NativeOperatorConfig,\n NativePipelineBuildOptions,\n OperatorType,\n PipelineConfig,\n} from \"@stll/anonymize\";\nimport { CAPABILITY_MANIFEST } from \"@stll/anonymize/capabilities\";\nimport {\n DEFAULT_ENTITY_LABELS,\n ENTITY_LABELS,\n type EntityLabel,\n} from \"@stll/anonymize/constants\";\n\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nimport type { CliOptions } from \"./args\";\nimport {\n DEFAULT_THRESHOLD,\n HELP,\n parseCliArgs,\n parseCountries,\n UsageError,\n} from \"./args\";\nimport type { DictionaryScope } from \"./dictionary-scope\";\nimport {\n type DocxCliPipeline,\n type DocxPipelineRequest,\n runDocxCommand,\n} from \"./docx\";\n\n/**\n * The pipeline functions the CLI needs, backed by the\n * @stll/anonymize native SDK: a binding loader and the\n * config-to-pipeline builder, plus the redaction-key\n * helpers used by the deanonymise path.\n */\nexport type AnonymizeApi = {\n deanonymise: typeof deanonymise;\n exportRedactionKey: typeof exportRedactionKey;\n createNativePipelineFromConfig: (\n options: NativePipelineBuildOptions,\n ) => Promise<NativeCliPipeline>;\n loadNativeAnonymizeBinding: () => NativeAnonymizeBinding;\n};\n\n/**\n * Everything an entry point injects: the pipeline engine\n * and the dictionary source (the @stll/anonymize-data\n * package for the npm bin).\n */\nexport type CliEngine = {\n api: AnonymizeApi;\n loadDictionaries: (scope: DictionaryScope) => Promise<Dictionaries>;\n};\n\n// Statically imported so the version is baked into both\n// the npm bundle and the compiled binary; a runtime\n// package.json lookup would fail inside the binary's\n// virtual filesystem.\nconst cliVersion = (): string => pkg.version;\n\n/**\n * Filesystem identity of a path: realpath when it exists\n * (so symlinks to the same file compare equal), lexical\n * resolution otherwise (the file may not exist yet).\n */\nconst canonicalPath = (path: string): string => {\n try {\n return realpathSync(path);\n } catch {\n return resolve(path);\n }\n};\n\nconst readStdin = async (): Promise<string> => {\n process.stdin.setEncoding(\"utf8\");\n let text = \"\";\n for await (const chunk of process.stdin) text += chunk;\n return text;\n};\n\ntype NamedInput = {\n /** Source path, or null when reading stdin. */\n path: string | null;\n text: string;\n};\n\nconst readInputs = async (files: string[]): Promise<NamedInput[]> => {\n if (files.length === 0) {\n if (process.stdin.isTTY) {\n throw new UsageError(\n \"no input files and stdin is a terminal (see --help)\",\n );\n }\n return [{ path: null, text: await readStdin() }];\n }\n return Promise.all(\n files.map(async (path) => ({ path, text: await readFile(path, \"utf8\") })),\n );\n};\n\n/**\n * One file to anonymize in a batch run: the source path to\n * read and the path, relative to the output directory, to\n * write. For a plain file argument the relative path is the\n * basename; for a directory argument the input tree is\n * mirrored, so it is the path relative to that directory.\n */\ntype FileJob = {\n path: string;\n outputRelative: string;\n};\n\n/** Result of expanding the positional arguments into concrete\n * files. `batch` is true when the output must be a directory:\n * more than one file, or any directory argument. */\ntype ExpandedInputs = {\n jobs: FileJob[];\n batch: boolean;\n /** Likely-binary files skipped during directory walks. */\n skipped: number;\n};\n\n// Sniff window for the binary check. A regular text file never\n// contains a NUL byte; binaries (images, archives) reliably do.\nconst TEXT_SNIFF_BYTES = 8192;\n\n/**\n * True when the file's first {@link TEXT_SNIFF_BYTES} bytes\n * contain no NUL byte. Used to skip binaries discovered by a\n * directory walk without reading the whole file.\n */\nconst looksTextual = async (path: string): Promise<boolean> => {\n const handle = await open(path, \"r\");\n try {\n const buffer = Buffer.alloc(TEXT_SNIFF_BYTES);\n const { bytesRead } = await handle.read(buffer, 0, TEXT_SNIFF_BYTES, 0);\n return buffer.subarray(0, bytesRead).indexOf(0) === -1;\n } finally {\n await handle.close();\n }\n};\n\n/**\n * Collect regular files under `root`, sorted for deterministic\n * order. Symlinks are skipped (avoids cycles and escaping the\n * tree); subdirectories are descended only when `recursive`.\n */\nconst walkDirectory = async (\n root: string,\n recursive: boolean,\n excludeDir?: string,\n): Promise<string[]> => {\n const found: string[] = [];\n const visit = async (dir: string): Promise<void> => {\n const entries = (await readdir(dir, { withFileTypes: true })).toSorted(\n (a, b) => a.name.localeCompare(b.name),\n );\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n // Never descend into the output tree: rerunning with --output inside\n // the input directory must not ingest previously generated files.\n if (excludeDir !== undefined && resolve(full) === excludeDir) continue;\n if (recursive) await visit(full);\n } else if (entry.isFile()) {\n found.push(full);\n }\n }\n };\n await visit(root);\n return found;\n};\n\n/**\n * Expand positional arguments into concrete file jobs. A file\n * argument becomes one job (always processed); a directory is\n * walked, mirroring its tree into the output and skipping\n * likely-binary files.\n */\nconst expandInputs = async (\n files: readonly string[],\n recursive: boolean,\n outputDir?: string,\n): Promise<ExpandedInputs> => {\n const excludeDir = outputDir === undefined ? undefined : resolve(outputDir);\n const jobs: FileJob[] = [];\n let hasDirectory = false;\n let skipped = 0;\n for (const path of files) {\n // A stat failure (missing path, permission error) is not\n // fatal here: treat it as a file job so the read failure is\n // reported per file. A single such job stays single-input\n // and surfaces the error as a runtime exit; in a batch it is\n // counted as a failed file.\n let stats: Awaited<ReturnType<typeof stat>> | undefined;\n try {\n stats = await stat(path);\n } catch {\n jobs.push({ path, outputRelative: basename(path) });\n continue;\n }\n if (!stats.isDirectory()) {\n jobs.push({ path, outputRelative: basename(path) });\n continue;\n }\n hasDirectory = true;\n for (const file of await walkDirectory(path, recursive, excludeDir)) {\n // A file that disappears or turns unreadable mid-walk is queued anyway:\n // the per-file worker try/catch counts it as failed without aborting\n // the batch. Only a successful sniff that says \"binary\" skips it.\n const textual = await looksTextual(file).catch(() => true);\n if (!textual) {\n skipped += 1;\n continue;\n }\n jobs.push({ path: file, outputRelative: relative(path, file) });\n }\n }\n return { jobs, batch: hasDirectory || jobs.length > 1, skipped };\n};\n\n/**\n * Run `task` over `items` with at most `workers` in flight.\n * The shared native pipeline makes each redaction a synchronous\n * native call, so concurrency here only overlaps async file\n * I/O; the increments below are safe without locking because\n * no `await` sits between the read and the write of `next`.\n */\nconst runPool = async <T>(\n items: readonly T[],\n workers: number,\n task: (item: T) => Promise<void>,\n): Promise<void> => {\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const index = next;\n next += 1;\n // SAFETY: index < items.length checked above.\n await task(items[index] as T);\n }\n };\n const count = Math.max(1, Math.min(workers, items.length));\n await Promise.all(Array.from({ length: count }, worker));\n};\n\ntype CliEntity = {\n start: number;\n end: number;\n label: string;\n text: string;\n score: number;\n source: string;\n};\n\ntype CliRedactionResult = {\n redactedText: string;\n redactionMap: Map<string, string>;\n operatorMap: Map<string, OperatorType>;\n entityCount: number;\n};\n\ntype NativeCliPipeline = DocxCliPipeline & {\n warmLazyRegex?: () => void;\n redactText: (\n fullText: string,\n operators?: NativeOperatorConfig,\n ) => {\n resolvedEntities: CliEntity[];\n redaction: CliRedactionResult;\n };\n};\n\n// Short aliases for the canonical multi-word labels so that\n// `--labels person,email,iban` works without quoting the space\n// in \"email address\". Separator-insensitive resolution (below)\n// additionally accepts hyphen/underscore forms such as\n// \"credit-card-number\".\nconst LABEL_ALIASES: Record<string, EntityLabel> = {\n email: \"email address\",\n phone: \"phone number\",\n org: \"organization\",\n organisation: \"organization\",\n dob: \"date of birth\",\n ssn: \"social security number\",\n \"tax id\": \"tax identification number\",\n passport: \"passport number\",\n \"credit card\": \"credit card number\",\n \"national id\": \"national identification number\",\n};\n\nconst LABEL_SEPARATOR_RE = /[\\s_-]+/g;\nconst ENTITY_LABEL_SET: ReadonlySet<string> = new Set(ENTITY_LABELS);\n\nconst isEntityLabel = (label: string): label is EntityLabel =>\n ENTITY_LABEL_SET.has(label);\n\n/**\n * Resolve a user-supplied label token to a canonical label.\n * Lowercases and collapses separators, then maps known short\n * aliases. Unknown tokens are returned normalized so the\n * caller can report them verbatim.\n */\nconst canonicalizeLabel = (raw: string): string => {\n const normalized = raw.toLowerCase().replace(LABEL_SEPARATOR_RE, \" \").trim();\n const known: readonly string[] = ENTITY_LABELS;\n if (known.includes(normalized)) {\n return normalized;\n }\n return LABEL_ALIASES[normalized] ?? normalized;\n};\n\nconst validateLabels = (labels: readonly string[]): EntityLabel[] => {\n const resolved = [...new Set(labels.map(canonicalizeLabel))];\n const valid: EntityLabel[] = [];\n const availableLabels = ENTITY_LABELS.join(\", \");\n const availableAliases = Object.keys(LABEL_ALIASES).join(\", \");\n for (const label of resolved) {\n if (!isEntityLabel(label)) {\n throw new UsageError(\n [\n \"--labels: unknown label\",\n JSON.stringify(label) + \";\",\n \"available:\",\n availableLabels,\n \"(aliases:\",\n availableAliases + \")\",\n ].join(\" \"),\n );\n }\n valid.push(label);\n }\n return valid;\n};\n\ntype PipelineConfigOptions = Pick<\n CliOptions,\n \"countries\" | \"labels\" | \"languages\" | \"threshold\"\n>;\n\nconst buildPipelineConfig = async (\n opts: PipelineConfigOptions,\n loadDictionaries: CliEngine[\"loadDictionaries\"],\n): Promise<PipelineConfig> => {\n const dictionaries = await loadDictionaries({\n languages: opts.languages,\n countries: opts.countries,\n });\n return {\n threshold: opts.threshold,\n enableTriggerPhrases: true,\n enableRegex: true,\n enableLegalForms: true,\n enableNameCorpus: true,\n ...(opts.languages === undefined\n ? {}\n : { nameCorpusLanguages: [...opts.languages] }),\n enableDenyList: true,\n ...(opts.countries === undefined\n ? {}\n : { denyListCountries: [...opts.countries] }),\n enableGazetteer: false,\n enableCountries: true,\n enableNer: false,\n enableConfidenceBoost: true,\n enableCoreference: true,\n enableZoneClassification: true,\n enableHotwordRules: true,\n labels:\n opts.labels === undefined\n ? [...DEFAULT_ENTITY_LABELS]\n : validateLabels(opts.labels),\n workspaceId: \"cli\",\n dictionaries,\n };\n};\n\nconst buildOperatorConfig = (opts: CliOptions): NativeOperatorConfig => {\n const operators: NonNullable<NativeOperatorConfig[\"operators\"]> = {};\n if (opts.mode === \"redact\") {\n const labels =\n opts.labels === undefined\n ? DEFAULT_ENTITY_LABELS\n : validateLabels(opts.labels);\n for (const label of labels) operators[label] = \"redact\";\n }\n return { operators, redactString: opts.redactString };\n};\n\nconst writeOutput = async (\n path: string | undefined,\n content: string,\n): Promise<void> => {\n if (path === undefined) {\n process.stdout.write(content);\n return;\n }\n await writeFile(path, content, \"utf8\");\n};\n\ntype RedactionKeyFile = {\n entries: Record<string, { original: string; operator: string }>;\n};\n\nconst parseRedactionKey = (raw: string): Map<string, string> => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new UsageError(\"redaction key is not valid JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null || !(\"entries\" in parsed)) {\n throw new UsageError(\n 'redaction key must be an object with an \"entries\" field',\n );\n }\n const { entries } = parsed as RedactionKeyFile;\n if (\n typeof entries !== \"object\" ||\n entries === null ||\n Array.isArray(entries)\n ) {\n throw new UsageError('redaction key \"entries\" must be an object');\n }\n const map = new Map<string, string>();\n for (const [placeholder, entry] of Object.entries(entries)) {\n if (typeof entry?.original !== \"string\") {\n throw new UsageError(\n `redaction key entry \"${placeholder}\" has no original text`,\n );\n }\n map.set(placeholder, entry.original);\n }\n return map;\n};\n\n/**\n * Restrict a redaction key to the entities named by --revert.\n * Each token matches a placeholder (\"[PERSON_1]\") or an original\n * value (\"Jan Novák\"), case-sensitive and exact. A token that\n * matches nothing is a usage error listing the placeholders the\n * key does define, so the caller can correct the spelling.\n */\nconst selectRevertEntries = (\n redactionMap: ReadonlyMap<string, string>,\n tokens: readonly string[],\n): Map<string, string> => {\n const selected = new Map<string, string>();\n for (const token of tokens) {\n let matched = false;\n for (const [placeholder, original] of redactionMap) {\n if (placeholder === token || original === token) {\n selected.set(placeholder, original);\n matched = true;\n }\n }\n if (!matched) {\n const MAX_LISTED_PLACEHOLDERS = 20;\n const placeholders = [...redactionMap.keys()];\n const listed = placeholders.slice(0, MAX_LISTED_PLACEHOLDERS).join(\", \");\n const rest = placeholders.length - MAX_LISTED_PLACEHOLDERS;\n const suffix = rest > 0 ? ` and ${rest} more` : \"\";\n throw new UsageError(\n `--revert ${JSON.stringify(token)} matched no placeholder or ` +\n `original; available placeholders: ${listed}${suffix}`,\n );\n }\n }\n return selected;\n};\n\n/**\n * Ask for a country scope when running interactively on\n * files with no scope flags. Skipped for piped stdin so\n * the CLI stays scriptable.\n */\nexport const shouldPromptForScope = (\n opts: CliOptions,\n tty: { stdinIsTTY: boolean; stderrIsTTY: boolean },\n): boolean =>\n opts.countries === undefined &&\n opts.languages === undefined &&\n !opts.quiet &&\n opts.files.length > 0 &&\n tty.stdinIsTTY &&\n tty.stderrIsTTY;\n\nconst promptForCountries = async (): Promise<string[] | undefined> => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n try {\n const answer = await rl.question(\n \"Country scope (ISO codes like CZ,DE,GB; Enter loads all): \",\n );\n const trimmed = answer.trim();\n return trimmed === \"\" ? undefined : parseCountries(trimmed);\n } finally {\n rl.close();\n }\n};\n\nconst runDeanonymise = async (\n opts: CliOptions,\n api: AnonymizeApi,\n): Promise<void> => {\n if (opts.keyPath !== undefined) {\n throw new UsageError(\"--key cannot be combined with --deanonymise\");\n }\n const keyPath = opts.deanonymiseKeyPath;\n if (keyPath === undefined) throw new UsageError(\"missing redaction key path\");\n const fullMap = parseRedactionKey(await readFile(keyPath, \"utf8\"));\n\n // --revert restores a chosen subset; leaving the rest of the\n // key out means deanonymise skips those placeholders, so the\n // other entities stay redacted.\n const redactionMap =\n opts.revert === undefined\n ? fullMap\n : selectRevertEntries(fullMap, opts.revert);\n\n const inputs = await readInputs(opts.files);\n if (inputs.length > 1) {\n throw new UsageError(\"--deanonymise accepts a single input\");\n }\n const input = inputs[0];\n if (!input) throw new UsageError(\"no input to deanonymise\");\n if (opts.output !== undefined) {\n guardWriteTargets(input.path === null ? [] : [input.path], [\n { path: opts.output, flag: \"--output\" },\n ]);\n }\n await writeOutput(opts.output, api.deanonymise(input.text, redactionMap));\n};\n\n/**\n * Reject any write target (output or key file) whose\n * filesystem identity collides with an input file or with\n * another write target. Symlinks count as collisions.\n */\nconst guardWriteTargets = (\n inputPaths: readonly string[],\n writeTargets: readonly { path: string; flag: string }[],\n): void => {\n const inputs = new Set(inputPaths.map(canonicalPath));\n const seen = new Map<string, string>();\n for (const target of writeTargets) {\n const canonical = canonicalPath(target.path);\n if (inputs.has(canonical)) {\n throw new UsageError(\n `refusing to overwrite input file \"${target.path}\" (${target.flag})`,\n );\n }\n const clash = seen.get(canonical);\n if (clash !== undefined) {\n throw new UsageError(\n `${target.flag} \"${target.path}\" collides with ${clash}`,\n );\n }\n seen.set(canonical, `${target.flag} \"${target.path}\"`);\n }\n};\n\nconst summarize = (entities: readonly CliEntity[]): string => {\n const counts = new Map<string, number>();\n for (const entity of entities) {\n counts.set(entity.label, (counts.get(entity.label) ?? 0) + 1);\n }\n const parts = [...counts.entries()]\n .toSorted((a, b) => b[1] - a[1])\n .map(([label, count]) => `${label}: ${count}`);\n return parts.length > 0 ? parts.join(\", \") : \"none\";\n};\n\n/**\n * A single unit of anonymize work: the text to process, where\n * to write it (undefined means stdout), and a label for the\n * stderr summary. Used by the stdin and single-file flows,\n * which additionally support --json and --key.\n */\ntype SingleInput = {\n text: string;\n outputPath: string | undefined;\n source: string;\n};\n\nconst runAnonymiseSingle = async (\n opts: CliOptions,\n runtime: CliRuntime,\n api: AnonymizeApi,\n input: SingleInput,\n): Promise<void> => {\n const { entities, redaction } = await runtime.redact(\n input.text,\n buildOperatorConfig(opts),\n );\n\n if (opts.json) {\n // In redact mode the user chose irreversibility, so the\n // JSON must not carry any detected text. Whitelist the\n // non-sensitive metadata fields; this drops `text` and a\n // coref alias's `corefSourceText`. Offsets index the\n // caller's own input and are kept.\n const jsonEntities =\n opts.mode === \"redact\"\n ? entities.map(({ start, end, label, score, source }) => ({\n start,\n end,\n label,\n score,\n source,\n }))\n : entities;\n const payload = {\n entityCount: redaction.entityCount,\n entities: jsonEntities,\n redactedText: redaction.redactedText,\n };\n await writeOutput(\n input.outputPath,\n `${JSON.stringify(payload, null, 2)}\\n`,\n );\n } else {\n await writeOutput(input.outputPath, redaction.redactedText);\n }\n\n if (opts.keyPath !== undefined) {\n await writeFile(\n opts.keyPath,\n api.exportRedactionKey(redaction.redactionMap, redaction.operatorMap),\n \"utf8\",\n );\n }\n\n if (!opts.quiet) {\n process.stderr.write(\n `anonymize: ${input.source}: ${summarize(entities)}\\n`,\n );\n }\n};\n\n/** Tally of a batch run for the closing summary line. */\ntype BatchOutcome = { processed: number; failed: number };\n\nconst runAnonymiseBatch = async (\n opts: CliOptions,\n runtime: CliRuntime,\n output: string,\n jobs: readonly FileJob[],\n skipped: number,\n): Promise<void> => {\n await mkdir(output, { recursive: true });\n const operatorConfig = buildOperatorConfig(opts);\n const outcome: BatchOutcome = { processed: 0, failed: 0 };\n\n await runPool(jobs, opts.workers, async (job) => {\n const outputPath = join(output, job.outputRelative);\n try {\n const text = await readFile(job.path, \"utf8\");\n const { entities, redaction } = await runtime.redact(\n text,\n operatorConfig,\n );\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, redaction.redactedText, \"utf8\");\n // No await between here and the increment: safe on the\n // single JS thread despite concurrent workers.\n outcome.processed += 1;\n if (!opts.quiet) {\n process.stderr.write(\n `anonymize: ${job.path}: ${summarize(entities)}\\n`,\n );\n }\n } catch (err) {\n outcome.failed += 1;\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`anonymize: ${job.path}: error: ${message}\\n`);\n }\n });\n\n if (!opts.quiet) {\n const parts = [\n `${outcome.processed} processed`,\n `${outcome.failed} failed`,\n ];\n if (skipped > 0) parts.push(`${skipped} skipped`);\n process.stderr.write(`anonymize: ${parts.join(\", \")}\\n`);\n }\n // Any per-file failure is a nonzero exit, but the whole batch\n // still runs so one bad file does not hide the rest.\n if (outcome.failed > 0) process.exitCode = 1;\n};\n\nconst runAnonymise = async (\n opts: CliOptions,\n { api, loadDictionaries }: CliEngine,\n): Promise<void> => {\n if (opts.keyPath !== undefined && opts.mode !== \"replace\") {\n throw new UsageError('--key requires --mode \"replace\"');\n }\n\n const scoped = shouldPromptForScope(opts, {\n stdinIsTTY: process.stdin.isTTY === true,\n stderrIsTTY: process.stderr.isTTY === true,\n })\n ? { ...opts, countries: await promptForCountries() }\n : opts;\n\n // No positional arguments: read stdin as a single input.\n if (scoped.files.length === 0) {\n const [input] = await readInputs(scoped.files);\n if (!input) throw new UsageError(\"no input to anonymize\");\n guardWriteTargets([], collectSingleTargets(scoped));\n const runtime = await prepareCliRuntime(\n api,\n await buildPipelineConfig(scoped, loadDictionaries),\n );\n await runAnonymiseSingle(scoped, runtime, api, {\n text: input.text,\n outputPath: scoped.output,\n source: \"stdin\",\n });\n return;\n }\n\n const { jobs, batch, skipped } = await expandInputs(\n scoped.files,\n scoped.recursive,\n scoped.output,\n );\n\n if (!batch) {\n // Exactly one plain file: single-input flow with --json/--key.\n const [job] = jobs;\n if (!job) throw new UsageError(\"no input to anonymize\");\n guardWriteTargets([job.path], collectSingleTargets(scoped));\n const runtime = await prepareCliRuntime(\n api,\n await buildPipelineConfig(scoped, loadDictionaries),\n );\n await runAnonymiseSingle(scoped, runtime, api, {\n text: await readFile(job.path, \"utf8\"),\n outputPath: scoped.output,\n source: job.path,\n });\n return;\n }\n\n // Batch: a directory, or more than one file.\n const output = scoped.output;\n if (output === undefined) {\n throw new UsageError(\n \"batch input (a directory or multiple files) requires --output <directory>\",\n );\n }\n if (scoped.keyPath !== undefined) {\n throw new UsageError(\"--key works with a single input only\");\n }\n if (scoped.json) {\n throw new UsageError(\"--json works with a single input only\");\n }\n\n // Validate every write target before any work: colliding\n // output paths (same basename from different input dirs,\n // symlinks to an input) fail fast instead of silently\n // clobbering files mid-batch.\n guardWriteTargets(\n jobs.map((job) => job.path),\n jobs.map((job) => ({\n path: join(output, job.outputRelative),\n flag: \"--output\",\n })),\n );\n\n const runtime = await prepareCliRuntime(\n api,\n await buildPipelineConfig(scoped, loadDictionaries),\n );\n await runAnonymiseBatch(scoped, runtime, output, jobs, skipped);\n};\n\n/** Write targets for a single-input run: --output and --key. */\nconst collectSingleTargets = (\n opts: CliOptions,\n): { path: string; flag: string }[] => {\n const targets: { path: string; flag: string }[] = [];\n if (opts.output !== undefined) {\n targets.push({ path: opts.output, flag: \"--output\" });\n }\n if (opts.keyPath !== undefined) {\n targets.push({ path: opts.keyPath, flag: \"--key\" });\n }\n return targets;\n};\n\ntype CliRuntime = {\n redact: (\n fullText: string,\n operators: NativeOperatorConfig,\n ) => Promise<{ entities: CliEntity[]; redaction: CliRedactionResult }>;\n};\n\nconst prepareNativeCliPipeline = async (\n api: AnonymizeApi,\n config: PipelineConfig,\n): Promise<NativeCliPipeline> => {\n const pipeline = await api.createNativePipelineFromConfig({\n binding: api.loadNativeAnonymizeBinding(),\n config,\n gazetteerEntries: [],\n });\n pipeline.warmLazyRegex?.();\n return pipeline;\n};\n\nconst prepareCliRuntime = async (\n api: AnonymizeApi,\n config: PipelineConfig,\n): Promise<CliRuntime> => {\n const pipeline = await prepareNativeCliPipeline(api, config);\n return {\n redact: async (fullText, operators) => {\n const result = pipeline.redactText(fullText, operators);\n return {\n entities: result.resolvedEntities,\n redaction: result.redaction,\n };\n },\n };\n};\n\n/**\n * Render the canonical entity labels and the short aliases\n * accepted by --labels, for the --list-labels discovery flag.\n */\nconst formatLabelList = (): string => {\n const lines: string[] = [\"Detectable entity labels (pass to --labels):\"];\n for (const label of ENTITY_LABELS) {\n lines.push(` ${label}`);\n }\n lines.push(\"\", \"Short aliases:\");\n const aliases = Object.entries(LABEL_ALIASES);\n let width = 0;\n for (const [alias] of aliases) {\n width = Math.max(width, alias.length);\n }\n for (const [alias, canonical] of aliases) {\n lines.push(` ${alias.padEnd(width)} -> ${canonical}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n};\n\nconst dispatch = async (engine: CliEngine): Promise<void> => {\n const argv = process.argv.slice(2);\n if (argv.at(0) === \"docx\") {\n await runDocxCommand({\n argv: argv.slice(1),\n preparePipeline: async (\n request: DocxPipelineRequest,\n ): Promise<DocxCliPipeline> => {\n const options: PipelineConfigOptions =\n request.type === \"anonymize\"\n ? request.detection\n : {\n countries: [],\n languages: [],\n threshold: DEFAULT_THRESHOLD,\n };\n return prepareNativeCliPipeline(\n engine.api,\n await buildPipelineConfig(options, engine.loadDictionaries),\n );\n },\n });\n return;\n }\n const opts = parseCliArgs(argv);\n if (opts.help) {\n process.stdout.write(HELP);\n return;\n }\n if (opts.version) {\n process.stdout.write(`${cliVersion()}\\n`);\n return;\n }\n if (opts.listLabels) {\n process.stdout.write(formatLabelList());\n return;\n }\n if (opts.capabilities) {\n process.stdout.write(`${JSON.stringify(CAPABILITY_MANIFEST, null, 2)}\\n`);\n return;\n }\n if (opts.deanonymiseKeyPath !== undefined) {\n await runDeanonymise(opts, engine.api);\n return;\n }\n if (opts.revert !== undefined) {\n throw new UsageError(\"--revert requires --deanonymise <key>\");\n }\n await runAnonymise(opts, engine);\n};\n\n/**\n * Run the CLI against the given engine and set the\n * process exit code (0 ok, 1 runtime error, 2 usage).\n */\nexport const runCli = async (engine: CliEngine): Promise<void> => {\n try {\n await dispatch(engine);\n } catch (err) {\n if (err instanceof UsageError) {\n process.stderr.write(`anonymize: ${err.message}\\n`);\n process.stderr.write(`Try \"anonymize --help\" for usage.\\n`);\n process.exitCode = 2;\n } else {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`anonymize: ${message}\\n`);\n process.exitCode = 1;\n }\n }\n};\n","#!/usr/bin/env node\n/* npm-distributed entry point — backs the CLI with the\n * native engine (@stll/text-search napi bindings) and\n * the @stll/anonymize-data dictionary package. */\nimport * as anonymize from \"@stll/anonymize\";\n\nimport { loadCliDictionaries } from \"./dictionaries\";\nimport { runCli } from \"./main\";\n\nawait runCli({ api: anonymize, loadDictionaries: loadCliDictionaries });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["splitList","parseThreshold","canonicalPath","pkg.version","known"],"sources":["../src/args.ts","../src/dictionary-scope.ts","../src/dictionaries.ts","../package.json","../src/docx.ts","../src/main.ts","../src/cli.ts"],"sourcesContent":["import { availableParallelism } from \"node:os\";\nimport { parseArgs } from \"node:util\";\n\nexport const CLI_MODES = [\"replace\", \"redact\"] as const;\nexport type CliMode = (typeof CLI_MODES)[number];\n\nexport const DEFAULT_THRESHOLD = 0.3;\nexport const DEFAULT_REDACT_STRING = \"[REDACTED]\";\n\n/** Upper bound on the default worker count; batch I/O overlap\n * saturates well before this, and redaction itself is a\n * synchronous native call serialized on the JS thread. */\nexport const MAX_DEFAULT_WORKERS = 4;\n\n/** Default batch concurrency: min(4, cores). Workers overlap\n * file reads/writes; the shared native pipeline runs each\n * redaction to completion on the single JS thread. */\nexport const defaultWorkerCount = (): number =>\n Math.max(1, Math.min(MAX_DEFAULT_WORKERS, availableParallelism()));\n\n/** Invalid invocation; printed with usage hint, exit code 2. */\nexport class UsageError extends Error {}\n\nexport type CliOptions = {\n files: string[];\n output?: string | undefined;\n mode: CliMode;\n keyPath?: string | undefined;\n deanonymiseKeyPath?: string | undefined;\n revert?: string[] | undefined;\n recursive: boolean;\n workers: number;\n labels?: string[] | undefined;\n languages?: string[] | undefined;\n countries?: string[] | undefined;\n threshold: number;\n redactString: string;\n json: boolean;\n quiet: boolean;\n help: boolean;\n version: boolean;\n listLabels: boolean;\n capabilities: boolean;\n};\n\nexport const HELP = `Usage: anonymize [options] [file|dir ...]\n\nDetect and anonymize PII in text. Reads the given files, or stdin\nwhen no files are given. A directory argument processes the text\nfiles inside it (add --recursive to descend into subdirectories).\nWrites to stdout, or to --output.\nAll processing is local; the CLI makes no network calls.\n\nDOCX workflows:\n Run \"anonymize docx --help\" for structure-preserving DOCX anonymization\n and restoration with encrypted session archives.\n\nOptions:\n -o, --output <path> Output file, or directory for batch\n input (multiple files or a directory)\n -m, --mode <mode> \"replace\" (reversible [PERSON_1]\n placeholders) or \"redact\"\n (default: replace)\n -k, --key <path> Write the redaction key as JSON\n (single input, replace mode)\n -d, --deanonymise <path> Restore redacted text using the\n redaction key at <path>\n --revert <term> With --deanonymise, restore only the\n given entity. Match a placeholder token\n (\"[PERSON_1]\") or an original value\n (\"Jan Novák\"), case-sensitive exact.\n Repeatable; others stay redacted\n -r, --recursive Descend into subdirectories when a\n directory is given as input\n --workers <n> Batch files to process concurrently\n (default: min(${MAX_DEFAULT_WORKERS}, CPU cores)). Overlaps\n file I/O; redaction is serialized on\n the JS thread\n --labels <list> Comma-separated entity labels to detect\n (default: all). Accepts canonical labels\n (\"email address\"), short aliases (email,\n phone, org, dob, ssn), and hyphen/underscore\n forms (\"credit-card-number\")\n --languages <list> Name-corpus languages, e.g. \"cs,de,en\"\n (default: all bundled)\n --countries <list> ISO 3166-1 alpha-2 codes scoping deny\n lists and city data, e.g. \"CZ,DE,GB\"\n (default: all deny lists; city data\n for a 30-country default set)\n --threshold <n> Minimum confidence score, 0-1\n (default: ${DEFAULT_THRESHOLD})\n --redact-string <s> Replacement text in redact mode\n (default: \"${DEFAULT_REDACT_STRING}\")\n --json Emit JSON (entities + redacted text) to\n stdout (single input only)\n --quiet Suppress the summary on stderr\n -h, --help Show this help\n -v, --version Show the version\n --list-labels List detectable entity labels and the\n short aliases accepted by --labels\n --capabilities Emit the versioned capability manifest as JSON\n\nBatch input (directory or multiple files):\n Requires --output <directory>. The input tree is mirrored\n into the output directory. Directory walks process regular\n files only and skip likely-binary files (a NUL byte in the\n first 8 KiB); explicitly named files are always processed.\n The stderr summary reports how many files were processed,\n failed, and skipped; any failure sets exit code 1.\n --key and --json apply to single inputs only.\n\nInteractive prompt:\n When run on files from a terminal without --countries or\n --languages, the CLI asks once which country scope to load.\n Piped stdin/stderr or --quiet skips the prompt, so scripts\n and agents never block on input.\n\nExit codes:\n 0 success\n 1 runtime error (message on stderr)\n 2 usage error (message on stderr)\n\nJSON output (--json):\n { \"entityCount\": number,\n \"entities\": [{ \"start\": number, \"end\": number,\n \"label\": string, \"text\": string,\n \"score\": number, \"source\": string }],\n \"redactedText\": string }\n Offsets are UTF-16 code-unit indexes into the input.\n The stderr summary contains entity counts only, never\n the detected text.\n\nExamples:\n anonymize contract.txt > contract.anon.txt\n anonymize -k contract.key.json -o contract.anon.txt contract.txt\n anonymize -d contract.key.json contract.anon.txt\n anonymize -r --workers 8 -o out/ docs/\n anonymize -d key.json --revert \"[PERSON_1]\" contract.anon.txt\n cat notes.md | anonymize --countries CZ,SK --languages cs,sk\n anonymize --json --quiet input.txt | jq '.entities[].label'\n anonymize docx --help\n`;\n\nconst splitList = (value: string): string[] => [\n ...new Set(\n value\n .split(\",\")\n .map((part) => part.trim())\n .filter((part) => part.length > 0),\n ),\n];\n\nconst parseThreshold = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new UsageError(\n `--threshold must be a number between 0 and 1, got \"${raw}\"`,\n );\n }\n return value;\n};\n\nconst parseWorkers = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isInteger(value) || value < 1) {\n throw new UsageError(`--workers must be a positive integer, got \"${raw}\"`);\n }\n return value;\n};\n\nconst parseMode = (raw: string): CliMode => {\n const mode = CLI_MODES.find((candidate) => candidate === raw);\n if (!mode) {\n throw new UsageError(\n `--mode must be one of: ${CLI_MODES.join(\", \")}; got \"${raw}\"`,\n );\n }\n return mode;\n};\n\nconst COUNTRY_CODE_RE = /^[A-Za-z]{2}$/;\n\nexport const parseCountries = (raw: string): string[] => {\n const countries = [\n ...new Set(splitList(raw).map((code) => code.toUpperCase())),\n ];\n const invalid = countries.find((code) => !COUNTRY_CODE_RE.test(code));\n if (invalid) {\n throw new UsageError(\n `--countries expects ISO 3166-1 alpha-2 codes (e.g. \"CZ,DE\"), got \"${invalid}\"`,\n );\n }\n return countries;\n};\n\nexport const parseCliArgs = (argv: string[]): CliOptions => {\n let parsed: ReturnType<typeof parseArgs<typeof PARSE_CONFIG>>;\n try {\n parsed = parseArgs({ ...PARSE_CONFIG, args: argv });\n } catch (err) {\n throw new UsageError(err instanceof Error ? err.message : String(err));\n }\n const { values, positionals } = parsed;\n\n return {\n files: positionals,\n output: values.output,\n mode: values.mode === undefined ? \"replace\" : parseMode(values.mode),\n keyPath: values.key,\n deanonymiseKeyPath: values.deanonymise,\n revert:\n values.revert === undefined || values.revert.length === 0\n ? undefined\n : values.revert,\n recursive: values.recursive === true,\n workers:\n values.workers === undefined\n ? defaultWorkerCount()\n : parseWorkers(values.workers),\n labels: values.labels === undefined ? undefined : splitList(values.labels),\n languages:\n values.languages === undefined ? undefined : splitList(values.languages),\n countries:\n values.countries === undefined\n ? undefined\n : parseCountries(values.countries),\n threshold:\n values.threshold === undefined\n ? DEFAULT_THRESHOLD\n : parseThreshold(values.threshold),\n redactString: values[\"redact-string\"] ?? DEFAULT_REDACT_STRING,\n json: values.json === true,\n quiet: values.quiet === true,\n help: values.help === true,\n version: values.version === true,\n listLabels: values[\"list-labels\"] === true,\n capabilities: values.capabilities === true,\n };\n};\n\nconst PARSE_CONFIG = {\n allowPositionals: true,\n strict: true,\n options: {\n output: { type: \"string\", short: \"o\" },\n mode: { type: \"string\", short: \"m\" },\n key: { type: \"string\", short: \"k\" },\n deanonymise: { type: \"string\", short: \"d\" },\n revert: { type: \"string\", multiple: true },\n recursive: { type: \"boolean\", short: \"r\" },\n workers: { type: \"string\" },\n labels: { type: \"string\" },\n languages: { type: \"string\" },\n countries: { type: \"string\" },\n threshold: { type: \"string\" },\n \"redact-string\": { type: \"string\" },\n json: { type: \"boolean\" },\n quiet: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n version: { type: \"boolean\", short: \"v\" },\n \"list-labels\": { type: \"boolean\" },\n capabilities: { type: \"boolean\" },\n },\n} as const;\n","/* Pure helpers shared by the npm and embedded dictionary\n * loaders. Must stay free of @stll/anonymize-data imports\n * so the compiled binary's bundle excludes the raw JSON\n * dictionary modules. */\nimport type { Dictionaries, DictionaryMeta } from \"@stll/anonymize\";\n\nimport { UsageError } from \"./args\";\n\nexport const NAME_DICTIONARY_PREFIXES = [\n \"names/first/\",\n \"names/surnames/\",\n] as const;\n\n/** Language code of a name dictionary id, or null. */\nexport const nameLanguageOfDictionary = (id: string): string | null => {\n const prefix = NAME_DICTIONARY_PREFIXES.find((p) => id.startsWith(p));\n return prefix ? id.slice(prefix.length) : null;\n};\n\nexport type DictionaryScope = {\n languages?: readonly string[] | undefined;\n countries?: readonly string[] | undefined;\n};\n\nconst pickKeys = <T>(\n record: Record<string, T>,\n keep: (key: string) => boolean,\n): Record<string, T> => {\n const result: Record<string, T> = {};\n for (const [key, value] of Object.entries(record)) {\n if (keep(key)) result[key] = value;\n }\n return result;\n};\n\n/** Dictionaries with every section present (possibly empty). */\nexport type ScopedDictionaries = {\n firstNames: Record<string, readonly string[]>;\n surnames: Record<string, readonly string[]>;\n denyList: Record<string, readonly string[]>;\n denyListMeta: Record<string, DictionaryMeta>;\n citiesByCountry: Record<string, readonly string[]>;\n};\n\n/**\n * Scope a fully loaded dictionary set to the requested\n * languages and countries. Mirrors the pre-load scoping\n * the npm loader does in dictionaries.ts; used by the\n * embedded loader, which always starts from the full set.\n */\nexport const filterDictionaries = (\n all: Dictionaries,\n { languages, countries }: DictionaryScope,\n): ScopedDictionaries => {\n const firstNames = all.firstNames ?? {};\n const surnames = all.surnames ?? {};\n const allDenyList = all.denyList ?? {};\n const allDenyListMeta = all.denyListMeta ?? {};\n\n if (languages !== undefined) {\n const available = Object.keys(firstNames);\n const invalid = languages.find((lang) => !available.includes(lang));\n if (invalid) {\n throw new UsageError(\n `--languages: no name dictionary for \"${invalid}\"; available: ${available.join(\", \")}`,\n );\n }\n }\n const keepLanguage = (lang: string): boolean =>\n languages === undefined || languages.includes(lang);\n const keepCountry = (country: string | null): boolean =>\n countries === undefined || country === null || countries.includes(country);\n\n const denyListMeta: Record<string, DictionaryMeta> = {};\n const denyList: Record<string, readonly string[]> = {};\n for (const [id, meta] of Object.entries(allDenyListMeta)) {\n if (!keepCountry(meta.country)) continue;\n const nameLang = nameLanguageOfDictionary(id);\n if (nameLang !== null && !keepLanguage(nameLang)) continue;\n const entries = allDenyList[id];\n if (entries === undefined) continue;\n denyListMeta[id] = meta;\n denyList[id] = entries;\n }\n\n return {\n firstNames: pickKeys(firstNames, keepLanguage),\n surnames: pickKeys(surnames, keepLanguage),\n denyList,\n denyListMeta,\n citiesByCountry: pickKeys(all.citiesByCountry ?? {}, (country) =>\n keepCountry(country),\n ),\n };\n};\n","import type { Dictionaries, DictionaryMeta } from \"@stll/anonymize\";\nimport {\n ALL_DICTIONARY_IDS,\n DICTIONARY_META,\n loadCityDictionary,\n loadDictionary,\n loadNameDictionaries,\n type NameLanguage,\n} from \"@stll/anonymize-data\";\n\nimport { UsageError } from \"./args\";\nimport type { DictionaryScope } from \"./dictionary-scope\";\nimport {\n NAME_DICTIONARY_PREFIXES,\n nameLanguageOfDictionary,\n} from \"./dictionary-scope\";\n\n/**\n * Countries with bundled city dictionaries that are\n * loaded when no --countries scope is given.\n */\nconst DEFAULT_CITY_COUNTRIES = [\n \"AT\",\n \"AU\",\n \"BE\",\n \"BG\",\n \"BR\",\n \"CA\",\n \"CH\",\n \"CZ\",\n \"DE\",\n \"DK\",\n \"ES\",\n \"FI\",\n \"FR\",\n \"GB\",\n \"GR\",\n \"HR\",\n \"HU\",\n \"IE\",\n \"IT\",\n \"LU\",\n \"NL\",\n \"NO\",\n \"NZ\",\n \"PL\",\n \"PT\",\n \"RO\",\n \"SE\",\n \"SI\",\n \"SK\",\n \"US\",\n] as const;\n\nconst availableNameLanguages = (): readonly string[] =>\n ALL_DICTIONARY_IDS.filter((id) =>\n id.startsWith(NAME_DICTIONARY_PREFIXES[0]),\n ).map((id) => id.slice(NAME_DICTIONARY_PREFIXES[0].length));\n\nconst validateLanguages = (\n languages: readonly string[],\n): readonly NameLanguage[] => {\n const available = availableNameLanguages();\n const invalid = languages.find((lang) => !available.includes(lang));\n if (invalid) {\n throw new UsageError(\n `--languages: no name dictionary for \"${invalid}\"; available: ${available.join(\", \")}`,\n );\n }\n // SAFETY: every entry was checked against the bundled\n // name dictionary ids, which define NameLanguage.\n return languages as readonly NameLanguage[];\n};\n\nexport type LoadCliDictionariesOptions = DictionaryScope;\n\n/**\n * Load the bundled @stll/anonymize-data dictionaries,\n * scoped to the requested languages and countries.\n */\nexport const loadCliDictionaries = async ({\n languages,\n countries,\n}: LoadCliDictionariesOptions): Promise<Dictionaries> => {\n const nameLanguages =\n languages === undefined ? undefined : validateLanguages(languages);\n\n const denyIds = ALL_DICTIONARY_IDS.filter((id) => {\n const meta = DICTIONARY_META[id];\n if (\n countries &&\n meta.country !== null &&\n !countries.includes(meta.country)\n ) {\n return false;\n }\n const nameLang = nameLanguageOfDictionary(id);\n if (nameLang !== null && nameLanguages !== undefined) {\n return nameLanguages.includes(\n // SAFETY: nameLang comes from a bundled dictionary\n // id, which defines NameLanguage.\n nameLang as NameLanguage,\n );\n }\n return true;\n });\n\n const cityCountries = countries ?? DEFAULT_CITY_COUNTRIES;\n\n const [names, denyEntries, cityEntries] = await Promise.all([\n loadNameDictionaries(nameLanguages),\n Promise.all(\n denyIds.map(async (id) => ({ id, entries: await loadDictionary(id) })),\n ),\n Promise.all(\n cityCountries.map(async (country) => ({\n country,\n entries: await loadCityDictionary(country),\n })),\n ),\n ]);\n\n const denyList: Record<string, readonly string[]> = {};\n const denyListMeta: Record<string, DictionaryMeta> = {};\n for (const { id, entries } of denyEntries) {\n denyList[id] = entries;\n // SAFETY: anonymize-data categories match\n // DenyListCategory at runtime.\n denyListMeta[id] = DICTIONARY_META[id] as DictionaryMeta;\n }\n\n const citiesByCountry: Record<string, readonly string[]> = {};\n for (const { country, entries } of cityEntries) {\n if (entries.length > 0) citiesByCountry[country] = entries;\n }\n\n return {\n firstNames: names.firstNames,\n surnames: names.surnames,\n denyList,\n denyListMeta,\n citiesByCountry,\n };\n};\n","","import { randomUUID } from \"node:crypto\";\nimport { realpathSync } from \"node:fs\";\nimport {\n type FileHandle,\n link,\n lstat,\n open,\n readFile,\n rename,\n unlink,\n} from \"node:fs/promises\";\nimport { basename, dirname, join, resolve } from \"node:path\";\nimport { parseArgs } from \"node:util\";\n\nimport type { NativeOpenSessionArchiveOptions } from \"@stll/anonymize\";\nimport {\n DOCX_COVERAGE_MODES,\n anonymizeDocx,\n restoreDocxText,\n type DocxAnonymizationSession,\n type DocxAnonymizationSummary,\n type DocxRestorationResult,\n type DocxRestorationSession,\n} from \"@stll/anonymize-docx\";\n\nimport { parseCountries, UsageError } from \"./args\";\n\nconst DOCX_SESSION_KEY_BYTES = 32;\nconst DOCX_SESSION_LOCK_SUFFIX = \".lock\";\nconst MAX_EPOCH_SECONDS = 4_294_967_295;\n\nconst DOCX_SESSION_MODES = {\n continue: \"continue\",\n create: \"create\",\n} as const;\n\ntype DocxSessionMode =\n (typeof DOCX_SESSION_MODES)[keyof typeof DOCX_SESSION_MODES];\n\ntype DocxDetectionOptions = {\n labels?: string[] | undefined;\n languages?: string[] | undefined;\n countries?: string[] | undefined;\n threshold: number;\n};\n\ntype DocxCommonOptions = {\n inputPath: string;\n outputPath: string;\n sessionArchivePath: string;\n sessionKeyPath: string;\n sessionId: string;\n coverage: (typeof DOCX_COVERAGE_MODES)[keyof typeof DOCX_COVERAGE_MODES];\n observedAtEpochSeconds?: number | undefined;\n json: boolean;\n quiet: boolean;\n};\n\ntype DocxCommand =\n | { type: \"help\" }\n | ({\n type: \"anonymize\";\n sessionMode: DocxSessionMode;\n detection: DocxDetectionOptions;\n } & DocxCommonOptions)\n | ({ type: \"restore\" } & DocxCommonOptions);\n\nexport type DocxPipelineRequest =\n | { type: \"anonymize\"; detection: DocxDetectionOptions }\n | { type: \"restore\" };\n\ntype DocxCliSession = DocxAnonymizationSession &\n DocxRestorationSession & {\n toEncryptedArchive: (key: Uint8Array) => Uint8Array;\n toEncryptedArchiveAt: (\n key: Uint8Array,\n observedAtEpochSeconds: number,\n ) => Uint8Array;\n };\n\nexport type DocxCliPipeline = {\n createRedactionSession: (sessionId: string) => DocxCliSession;\n restoreEncryptedRedactionSession: (\n options: NativeOpenSessionArchiveOptions,\n ) => DocxCliSession;\n};\n\ntype RunDocxCommandOptions = {\n argv: readonly string[];\n preparePipeline: (request: DocxPipelineRequest) => Promise<DocxCliPipeline>;\n};\n\nconst DOCX_HELP = `Usage:\n anonymize docx anonymize [options] <input.docx>\n anonymize docx restore [options] <input.docx>\n\nAnonymize or restore one DOCX file with an encrypted redaction session.\nDocument and session outputs are written atomically and never overwrite the\ninput, key file, or an existing document output.\n\nRequired options:\n -o, --output <path> New DOCX output path\n --session-archive <path> Encrypted session archive path\n --session-key-file <path>\n File containing exactly 32 raw key bytes\n --session-id <id> Expected opaque session identity\n\nAnonymize options:\n --session-mode <mode> \"create\" or \"continue\" (required)\n --coverage <mode> \"require-full\" (default) or \"allow-partial\"\n --labels <list> Comma-separated entity labels\n --languages <list> Name-corpus languages, e.g. \"cs,de,en\"\n --countries <list> ISO 3166-1 alpha-2 country codes\n --threshold <n> Minimum confidence score 0-1 (default: 0.3)\n\nRestore options:\n --coverage <mode> \"require-full\" (default) or \"allow-partial\"\n\nCommon options:\n --observed-at <seconds> Deterministic Unix timestamp for lifecycle checks\n --json Print the aggregate audit-safe summary as JSON\n --quiet Suppress the human-readable stderr summary\n -h, --help Show this help\n\nThe session key is read from a file, never from a command argument. In create\nmode the archive path must not exist. Continue mode atomically replaces the\nexisting archive only after the DOCX rewrite succeeds. It holds an exclusive\n\"<archive>.lock\" sidecar throughout the continuation to prevent lost updates.\nCaller-supplied detection plans and interactive review are available through the\npackage API, not this CLI.\n`;\n\nconst splitList = (value: string): string[] => [\n ...new Set(\n value\n .split(\",\")\n .map((part) => part.trim())\n .filter((part) => part.length > 0),\n ),\n];\n\nconst parseThreshold = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isFinite(value) || value < 0 || value > 1) {\n throw new UsageError(\n `--threshold must be a number between 0 and 1, got \"${raw}\"`,\n );\n }\n return value;\n};\n\nconst parseEpochSeconds = (raw: string): number => {\n const value = Number(raw);\n if (!Number.isInteger(value) || value < 0 || value > MAX_EPOCH_SECONDS) {\n throw new UsageError(\n `--observed-at must be an integer from 0 to ${MAX_EPOCH_SECONDS}, got \"${raw}\"`,\n );\n }\n return value;\n};\n\nconst parseCoverage = (\n raw: string | undefined,\n): DocxCommonOptions[\"coverage\"] => {\n const value = raw ?? DOCX_COVERAGE_MODES.requireFull;\n if (\n value === DOCX_COVERAGE_MODES.requireFull ||\n value === DOCX_COVERAGE_MODES.allowPartial\n ) {\n return value;\n }\n throw new UsageError(\n `--coverage must be one of: ${Object.values(DOCX_COVERAGE_MODES).join(\", \")}; got \"${value}\"`,\n );\n};\n\nconst parseSessionMode = (raw: string | undefined): DocxSessionMode => {\n if (raw === undefined) {\n throw new UsageError(\"--session-mode is required for DOCX anonymization\");\n }\n if (\n raw === DOCX_SESSION_MODES.create ||\n raw === DOCX_SESSION_MODES.continue\n ) {\n return raw;\n }\n throw new UsageError(\n `--session-mode must be one of: ${Object.values(DOCX_SESSION_MODES).join(\", \")}; got \"${raw}\"`,\n );\n};\n\nconst required = (value: string | undefined, flag: string): string => {\n if (value === undefined || value.length === 0) {\n throw new UsageError(`${flag} is required for DOCX workflows`);\n }\n return value;\n};\n\ntype ParsedCommonValues = {\n output?: string | undefined;\n \"session-archive\"?: string | undefined;\n \"session-key-file\"?: string | undefined;\n \"session-id\"?: string | undefined;\n coverage?: string | undefined;\n \"observed-at\"?: string | undefined;\n json?: boolean | undefined;\n quiet?: boolean | undefined;\n};\n\nconst commonOptions = (\n values: ParsedCommonValues,\n positionals: readonly string[],\n): DocxCommonOptions => {\n if (positionals.length !== 1) {\n throw new UsageError(\"DOCX workflows require exactly one input file\");\n }\n const inputPath = positionals.at(0);\n if (inputPath === undefined) {\n throw new UsageError(\"DOCX workflows require exactly one input file\");\n }\n return {\n inputPath,\n outputPath: required(values.output, \"--output\"),\n sessionArchivePath: required(\n values[\"session-archive\"],\n \"--session-archive\",\n ),\n sessionKeyPath: required(values[\"session-key-file\"], \"--session-key-file\"),\n sessionId: required(values[\"session-id\"], \"--session-id\"),\n coverage: parseCoverage(values.coverage),\n observedAtEpochSeconds:\n values[\"observed-at\"] === undefined\n ? undefined\n : parseEpochSeconds(values[\"observed-at\"]),\n json: values.json === true,\n quiet: values.quiet === true,\n };\n};\n\nconst parseDocxCommand = (argv: readonly string[]): DocxCommand => {\n const action = argv.at(0);\n if (action === undefined || action === \"--help\" || action === \"-h\") {\n return { type: \"help\" };\n }\n const args = argv.slice(1);\n if (action === \"anonymize\") {\n let parsed: ReturnType<typeof parseArgs<typeof DOCX_ANONYMIZE_CONFIG>>;\n try {\n parsed = parseArgs({ ...DOCX_ANONYMIZE_CONFIG, args: [...args] });\n } catch (error) {\n throw new UsageError(\n error instanceof Error ? error.message : String(error),\n );\n }\n if (parsed.values.help === true) {\n return { type: \"help\" };\n }\n return {\n type: \"anonymize\",\n ...commonOptions(parsed.values, parsed.positionals),\n sessionMode: parseSessionMode(parsed.values[\"session-mode\"]),\n detection: {\n labels:\n parsed.values.labels === undefined\n ? undefined\n : splitList(parsed.values.labels),\n languages:\n parsed.values.languages === undefined\n ? undefined\n : splitList(parsed.values.languages),\n countries:\n parsed.values.countries === undefined\n ? undefined\n : parseCountries(parsed.values.countries),\n threshold:\n parsed.values.threshold === undefined\n ? 0.3\n : parseThreshold(parsed.values.threshold),\n },\n };\n }\n if (action === \"restore\") {\n let parsed: ReturnType<typeof parseArgs<typeof DOCX_RESTORE_CONFIG>>;\n try {\n parsed = parseArgs({ ...DOCX_RESTORE_CONFIG, args: [...args] });\n } catch (error) {\n throw new UsageError(\n error instanceof Error ? error.message : String(error),\n );\n }\n if (parsed.values.help === true) {\n return { type: \"help\" };\n }\n return {\n type: \"restore\",\n ...commonOptions(parsed.values, parsed.positionals),\n };\n }\n throw new UsageError(\n `unknown DOCX action \"${action}\"; expected \"anonymize\" or \"restore\"`,\n );\n};\n\nconst DOCX_COMMON_PARSE_OPTIONS = {\n output: { type: \"string\", short: \"o\" },\n \"session-archive\": { type: \"string\" },\n \"session-key-file\": { type: \"string\" },\n \"session-id\": { type: \"string\" },\n coverage: { type: \"string\" },\n \"observed-at\": { type: \"string\" },\n json: { type: \"boolean\" },\n quiet: { type: \"boolean\" },\n help: { type: \"boolean\", short: \"h\" },\n} as const;\n\nconst DOCX_ANONYMIZE_CONFIG = {\n allowPositionals: true,\n strict: true,\n options: {\n ...DOCX_COMMON_PARSE_OPTIONS,\n \"session-mode\": { type: \"string\" },\n labels: { type: \"string\" },\n languages: { type: \"string\" },\n countries: { type: \"string\" },\n threshold: { type: \"string\" },\n },\n} as const;\n\nconst DOCX_RESTORE_CONFIG = {\n allowPositionals: true,\n strict: true,\n options: DOCX_COMMON_PARSE_OPTIONS,\n} as const;\n\nconst canonicalPath = (path: string): string => {\n try {\n return realpathSync(path);\n } catch {\n return resolve(path);\n }\n};\n\nconst sessionArchiveLockPath = (archivePath: string): string =>\n `${canonicalPath(archivePath)}${DOCX_SESSION_LOCK_SUFFIX}`;\n\nconst assertDistinctPaths = (\n paths: readonly { path: string; flag: string }[],\n): void => {\n const seen = new Map<string, string>();\n for (const entry of paths) {\n const canonical = canonicalPath(entry.path);\n const existing = seen.get(canonical);\n if (existing !== undefined) {\n throw new UsageError(`${entry.flag} collides with ${existing}`);\n }\n seen.set(canonical, `${entry.flag} \"${entry.path}\"`);\n }\n};\n\nconst isNodeError = (\n error: unknown,\n code: string,\n): error is NodeJS.ErrnoException =>\n error instanceof Error && \"code\" in error && error.code === code;\n\nconst assertPathDoesNotExist = async (\n path: string,\n flag: string,\n): Promise<void> => {\n try {\n await lstat(path);\n } catch (error) {\n if (isNodeError(error, \"ENOENT\")) {\n return;\n }\n throw error;\n }\n throw new UsageError(`${flag} refuses to overwrite existing path \"${path}\"`);\n};\n\nconst preflightDocxCommand = async (\n command: Exclude<DocxCommand, { type: \"help\" }>,\n): Promise<void> => {\n const paths = [\n { path: command.inputPath, flag: \"input\" },\n { path: command.outputPath, flag: \"--output\" },\n { path: command.sessionArchivePath, flag: \"--session-archive\" },\n { path: command.sessionKeyPath, flag: \"--session-key-file\" },\n ];\n if (\n command.type === \"anonymize\" &&\n command.sessionMode === DOCX_SESSION_MODES.continue\n ) {\n paths.push({\n path: sessionArchiveLockPath(command.sessionArchivePath),\n flag: \"session archive lock\",\n });\n }\n assertDistinctPaths(paths);\n await assertPathDoesNotExist(command.outputPath, \"--output\");\n if (\n command.type === \"anonymize\" &&\n command.sessionMode === DOCX_SESSION_MODES.create\n ) {\n await assertPathDoesNotExist(\n command.sessionArchivePath,\n \"--session-archive\",\n );\n }\n};\n\ntype SessionArchiveLock = {\n release: () => Promise<void>;\n};\n\ntype OperationResult =\n | { type: \"succeeded\" }\n | { type: \"failed\"; error: unknown };\n\nconst captureOperationResult = async (\n operation: Promise<void>,\n): Promise<OperationResult> => {\n try {\n await operation;\n return { type: \"succeeded\" };\n } catch (error) {\n return { type: \"failed\", error };\n }\n};\n\nconst acquireSessionArchiveLock = async (\n archivePath: string,\n): Promise<SessionArchiveLock> => {\n const lockPath = sessionArchiveLockPath(archivePath);\n let handle: FileHandle;\n try {\n handle = await open(lockPath, \"wx\", 0o600);\n } catch (error) {\n if (isNodeError(error, \"EEXIST\")) {\n throw new Error(\n `encrypted session archive is locked by another continuation; if no process is running, remove the stale lock \"${lockPath}\"`,\n );\n }\n throw error;\n }\n return {\n release: async () => {\n const closeResult = await captureOperationResult(handle.close());\n const unlinkResult = await captureOperationResult(unlink(lockPath));\n if (closeResult.type === \"failed\") {\n throw closeResult.error;\n }\n if (\n unlinkResult.type === \"failed\" &&\n !isNodeError(unlinkResult.error, \"ENOENT\")\n ) {\n throw unlinkResult.error;\n }\n },\n };\n};\n\nconst readSessionKey = async (path: string): Promise<Uint8Array> => {\n const handle = await open(path, \"r\");\n let key: Uint8Array | undefined;\n try {\n const stats = await handle.stat();\n if (!stats.isFile()) {\n throw new UsageError(\"--session-key-file must be a regular file\");\n }\n if (process.platform !== \"win32\" && (stats.mode & 0o077) !== 0) {\n throw new UsageError(\n \"--session-key-file must not grant permissions to group or other users (use chmod 600)\",\n );\n }\n key = await handle.readFile();\n if (key.byteLength !== DOCX_SESSION_KEY_BYTES) {\n throw new UsageError(\n `--session-key-file must contain exactly ${DOCX_SESSION_KEY_BYTES} raw bytes`,\n );\n }\n await handle.close();\n return key;\n } catch (error) {\n key?.fill(0);\n try {\n await handle.close();\n } catch {\n // Preserve the validation or read error.\n }\n throw error;\n }\n};\n\nconst removeStagedFile = async (path: string | undefined): Promise<void> => {\n if (path === undefined) {\n return;\n }\n try {\n await unlink(path);\n } catch {\n // Best-effort cleanup must not hide the original operation error.\n }\n};\n\nconst stageFile = async (\n target: string,\n content: Uint8Array,\n): Promise<string> => {\n const temporary = join(\n dirname(target),\n `.${basename(target)}.${randomUUID()}.tmp`,\n );\n const handle = await open(temporary, \"wx\", 0o600);\n try {\n await handle.writeFile(content);\n await handle.sync();\n await handle.close();\n } catch (error) {\n try {\n await handle.close();\n } catch {\n // Preserve the write error while cleanup remains best effort.\n }\n await removeStagedFile(temporary);\n throw error;\n }\n return temporary;\n};\n\nconst publishNewFile = async (\n temporary: string,\n target: string,\n flag: string,\n): Promise<void> => {\n try {\n await link(temporary, target);\n } catch (error) {\n if (isNodeError(error, \"EEXIST\")) {\n throw new UsageError(\n `${flag} refuses to overwrite existing path \"${target}\"`,\n );\n }\n throw error;\n }\n await removeStagedFile(temporary);\n};\n\nconst publishReplacement = async (\n temporary: string,\n target: string,\n): Promise<void> => {\n await rename(temporary, target);\n};\n\nconst sessionArchive = (\n session: DocxCliSession,\n key: Uint8Array,\n observedAtEpochSeconds: number | undefined,\n): Uint8Array =>\n observedAtEpochSeconds === undefined\n ? session.toEncryptedArchive(key)\n : session.toEncryptedArchiveAt(key, observedAtEpochSeconds);\n\nconst outputSummary = (\n command: Pick<DocxCommonOptions, \"json\" | \"quiet\">,\n action: \"anonymized\" | \"restored\",\n summary: DocxAnonymizationSummary | Omit<DocxRestorationResult, \"document\">,\n): void => {\n if (command.json) {\n process.stdout.write(`${JSON.stringify(summary, null, 2)}\\n`);\n }\n if (command.quiet) {\n return;\n }\n const coverage = summary.coverage.status;\n if (\"entityCount\" in summary) {\n process.stderr.write(\n `anonymize: DOCX ${action}: ${summary.entityCount} entities, ${summary.appliedReplacementCount} replacements, ${coverage} coverage\\n`,\n );\n return;\n }\n process.stderr.write(\n `anonymize: DOCX ${action}: ${summary.restoredPlaceholderCount} placeholders, ${coverage} coverage\\n`,\n );\n};\n\nconst openSession = (\n pipeline: DocxCliPipeline,\n command: Pick<DocxCommonOptions, \"sessionId\" | \"observedAtEpochSeconds\">,\n archive: Uint8Array,\n key: Uint8Array,\n): DocxCliSession =>\n pipeline.restoreEncryptedRedactionSession({\n archive,\n key,\n expectedSessionId: command.sessionId,\n ...(command.observedAtEpochSeconds === undefined\n ? {}\n : { observedAtEpochSeconds: command.observedAtEpochSeconds }),\n });\n\nconst runDocxAnonymize = async (\n command: Extract<DocxCommand, { type: \"anonymize\" }>,\n pipeline: DocxCliPipeline,\n): Promise<void> => {\n const archivePath =\n command.sessionMode === DOCX_SESSION_MODES.continue\n ? canonicalPath(command.sessionArchivePath)\n : command.sessionArchivePath;\n const archiveLock =\n command.sessionMode === DOCX_SESSION_MODES.continue\n ? await acquireSessionArchiveLock(archivePath)\n : undefined;\n let workflowResult: OperationResult = { type: \"succeeded\" };\n let lockReleaseResult: OperationResult = { type: \"succeeded\" };\n let key: Uint8Array | undefined;\n let documentTemporary: string | undefined;\n let archiveTemporary: string | undefined;\n try {\n key = await readSessionKey(command.sessionKeyPath);\n const [document, existingArchive] = await Promise.all([\n readFile(command.inputPath),\n command.sessionMode === DOCX_SESSION_MODES.continue\n ? readFile(archivePath)\n : Promise.resolve(undefined),\n ]);\n const session =\n existingArchive === undefined\n ? pipeline.createRedactionSession(command.sessionId)\n : openSession(pipeline, command, existingArchive, key);\n const result = anonymizeDocx({\n document,\n session,\n expectedSessionId: command.sessionId,\n policy: { coverage: { mode: command.coverage } },\n ...(command.observedAtEpochSeconds === undefined\n ? {}\n : { observedAtEpochSeconds: command.observedAtEpochSeconds }),\n });\n const encryptedArchive = sessionArchive(\n session,\n key,\n command.observedAtEpochSeconds,\n );\n documentTemporary = await stageFile(command.outputPath, result.document);\n archiveTemporary = await stageFile(archivePath, encryptedArchive);\n if (command.sessionMode === DOCX_SESSION_MODES.create) {\n await publishNewFile(\n archiveTemporary,\n command.sessionArchivePath,\n \"--session-archive\",\n );\n } else {\n await publishReplacement(archiveTemporary, archivePath);\n }\n archiveTemporary = undefined;\n try {\n await publishNewFile(documentTemporary, command.outputPath, \"--output\");\n documentTemporary = undefined;\n } catch (error) {\n const message = error instanceof Error ? error.message : String(error);\n throw new Error(\n `encrypted session archive was updated, but DOCX output could not be published: ${message}`,\n );\n }\n outputSummary(command, \"anonymized\", result.summary);\n } catch (error) {\n workflowResult = { type: \"failed\", error };\n } finally {\n key?.fill(0);\n await Promise.all([\n removeStagedFile(documentTemporary),\n removeStagedFile(archiveTemporary),\n ]);\n if (archiveLock !== undefined) {\n lockReleaseResult = await captureOperationResult(archiveLock.release());\n }\n }\n if (workflowResult.type === \"failed\") {\n throw workflowResult.error;\n }\n if (lockReleaseResult.type === \"failed\") {\n const message =\n lockReleaseResult.error instanceof Error\n ? lockReleaseResult.error.message\n : String(lockReleaseResult.error);\n throw new Error(\n `DOCX and session outputs were published, but the session archive lock could not be released: ${message}`,\n );\n }\n};\n\nconst runDocxRestore = async (\n command: Extract<DocxCommand, { type: \"restore\" }>,\n pipeline: DocxCliPipeline,\n): Promise<void> => {\n const key = await readSessionKey(command.sessionKeyPath);\n try {\n const [document, archive] = await Promise.all([\n readFile(command.inputPath),\n readFile(command.sessionArchivePath),\n ]);\n const session = openSession(pipeline, command, archive, key);\n const result = restoreDocxText({\n document,\n session,\n expectedSessionId: command.sessionId,\n ...(command.observedAtEpochSeconds === undefined\n ? {}\n : { observedAtEpochSeconds: command.observedAtEpochSeconds }),\n });\n if (\n command.coverage === DOCX_COVERAGE_MODES.requireFull &&\n result.coverage.status === \"partial\"\n ) {\n throw new Error(\n \"DOCX contains content outside the fully supported restoration coverage\",\n );\n }\n const temporary = await stageFile(command.outputPath, result.document);\n try {\n await publishNewFile(temporary, command.outputPath, \"--output\");\n } catch (error) {\n await removeStagedFile(temporary);\n throw error;\n }\n const summary: Omit<DocxRestorationResult, \"document\"> = {\n sessionId: result.sessionId,\n restoredBlockCount: result.restoredBlockCount,\n restoredPlaceholderCount: result.restoredPlaceholderCount,\n coverage: result.coverage,\n };\n outputSummary(command, \"restored\", summary);\n } finally {\n key.fill(0);\n }\n};\n\nexport const runDocxCommand = async ({\n argv,\n preparePipeline,\n}: RunDocxCommandOptions): Promise<void> => {\n const command = parseDocxCommand(argv);\n if (command.type === \"help\") {\n process.stdout.write(DOCX_HELP);\n return;\n }\n await preflightDocxCommand(command);\n const pipeline = await preparePipeline(\n command.type === \"anonymize\"\n ? { type: \"anonymize\", detection: command.detection }\n : { type: \"restore\" },\n );\n if (command.type === \"anonymize\") {\n await runDocxAnonymize(command, pipeline);\n return;\n }\n await runDocxRestore(command, pipeline);\n};\n","import { realpathSync } from \"node:fs\";\nimport {\n mkdir,\n open,\n readdir,\n readFile,\n stat,\n writeFile,\n} from \"node:fs/promises\";\nimport { basename, dirname, join, relative, resolve } from \"node:path\";\nimport { createInterface } from \"node:readline/promises\";\n\nimport type {\n deanonymise,\n Dictionaries,\n exportRedactionKey,\n NativeAnonymizeBinding,\n NativeOperatorConfig,\n NativePipelineBuildOptions,\n OperatorType,\n PipelineConfig,\n} from \"@stll/anonymize\";\nimport { CAPABILITY_MANIFEST } from \"@stll/anonymize/capabilities\";\nimport {\n DEFAULT_ENTITY_LABELS,\n ENTITY_LABELS,\n type EntityLabel,\n} from \"@stll/anonymize/constants\";\n\nimport pkg from \"../package.json\" with { type: \"json\" };\n\nimport type { CliOptions } from \"./args\";\nimport {\n DEFAULT_THRESHOLD,\n HELP,\n parseCliArgs,\n parseCountries,\n UsageError,\n} from \"./args\";\nimport type { DictionaryScope } from \"./dictionary-scope\";\nimport {\n type DocxCliPipeline,\n type DocxPipelineRequest,\n runDocxCommand,\n} from \"./docx\";\n\n/**\n * The pipeline functions the CLI needs, backed by the\n * @stll/anonymize native SDK: a binding loader and the\n * config-to-pipeline builder, plus the redaction-key\n * helpers used by the deanonymise path.\n */\nexport type AnonymizeApi = {\n deanonymise: typeof deanonymise;\n exportRedactionKey: typeof exportRedactionKey;\n createNativePipelineFromConfig: (\n options: NativePipelineBuildOptions,\n ) => Promise<NativeCliPipeline>;\n loadNativeAnonymizeBinding: () => NativeAnonymizeBinding;\n};\n\n/**\n * Everything an entry point injects: the pipeline engine\n * and the dictionary source (the @stll/anonymize-data\n * package for the npm bin).\n */\nexport type CliEngine = {\n api: AnonymizeApi;\n loadDictionaries: (scope: DictionaryScope) => Promise<Dictionaries>;\n};\n\n// Statically imported so the version is baked into both\n// the npm bundle and the compiled binary; a runtime\n// package.json lookup would fail inside the binary's\n// virtual filesystem.\nconst cliVersion = (): string => pkg.version;\n\n/**\n * Filesystem identity of a path: realpath when it exists\n * (so symlinks to the same file compare equal), lexical\n * resolution otherwise (the file may not exist yet).\n */\nconst canonicalPath = (path: string): string => {\n try {\n return realpathSync(path);\n } catch {\n return resolve(path);\n }\n};\n\nconst readStdin = async (): Promise<string> => {\n process.stdin.setEncoding(\"utf8\");\n let text = \"\";\n for await (const chunk of process.stdin) text += chunk;\n return text;\n};\n\ntype NamedInput = {\n /** Source path, or null when reading stdin. */\n path: string | null;\n text: string;\n};\n\nconst readInputs = async (files: string[]): Promise<NamedInput[]> => {\n if (files.length === 0) {\n if (process.stdin.isTTY) {\n throw new UsageError(\n \"no input files and stdin is a terminal (see --help)\",\n );\n }\n return [{ path: null, text: await readStdin() }];\n }\n return Promise.all(\n files.map(async (path) => ({ path, text: await readFile(path, \"utf8\") })),\n );\n};\n\n/**\n * One file to anonymize in a batch run: the source path to\n * read and the path, relative to the output directory, to\n * write. For a plain file argument the relative path is the\n * basename; for a directory argument the input tree is\n * mirrored, so it is the path relative to that directory.\n */\ntype FileJob = {\n path: string;\n outputRelative: string;\n};\n\n/** Result of expanding the positional arguments into concrete\n * files. `batch` is true when the output must be a directory:\n * more than one file, or any directory argument. */\ntype ExpandedInputs = {\n jobs: FileJob[];\n batch: boolean;\n /** Likely-binary files skipped during directory walks. */\n skipped: number;\n};\n\n// Sniff window for the binary check. A regular text file never\n// contains a NUL byte; binaries (images, archives) reliably do.\nconst TEXT_SNIFF_BYTES = 8192;\n\n/**\n * True when the file's first {@link TEXT_SNIFF_BYTES} bytes\n * contain no NUL byte. Used to skip binaries discovered by a\n * directory walk without reading the whole file.\n */\nconst looksTextual = async (path: string): Promise<boolean> => {\n const handle = await open(path, \"r\");\n try {\n const buffer = Buffer.alloc(TEXT_SNIFF_BYTES);\n const { bytesRead } = await handle.read(buffer, 0, TEXT_SNIFF_BYTES, 0);\n return buffer.subarray(0, bytesRead).indexOf(0) === -1;\n } finally {\n await handle.close();\n }\n};\n\n/**\n * Collect regular files under `root`, sorted for deterministic\n * order. Symlinks are skipped (avoids cycles and escaping the\n * tree); subdirectories are descended only when `recursive`.\n */\nconst walkDirectory = async (\n root: string,\n recursive: boolean,\n excludeDir?: string,\n): Promise<string[]> => {\n const found: string[] = [];\n const visit = async (dir: string): Promise<void> => {\n const entries = (await readdir(dir, { withFileTypes: true })).toSorted(\n (a, b) => a.name.localeCompare(b.name),\n );\n for (const entry of entries) {\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n // Never descend into the output tree: rerunning with --output inside\n // the input directory must not ingest previously generated files.\n if (excludeDir !== undefined && resolve(full) === excludeDir) continue;\n if (recursive) await visit(full);\n } else if (entry.isFile()) {\n found.push(full);\n }\n }\n };\n await visit(root);\n return found;\n};\n\n/**\n * Expand positional arguments into concrete file jobs. A file\n * argument becomes one job (always processed); a directory is\n * walked, mirroring its tree into the output and skipping\n * likely-binary files.\n */\nconst expandInputs = async (\n files: readonly string[],\n recursive: boolean,\n outputDir?: string,\n): Promise<ExpandedInputs> => {\n const excludeDir = outputDir === undefined ? undefined : resolve(outputDir);\n const jobs: FileJob[] = [];\n let hasDirectory = false;\n let skipped = 0;\n for (const path of files) {\n // A stat failure (missing path, permission error) is not\n // fatal here: treat it as a file job so the read failure is\n // reported per file. A single such job stays single-input\n // and surfaces the error as a runtime exit; in a batch it is\n // counted as a failed file.\n let stats: Awaited<ReturnType<typeof stat>> | undefined;\n try {\n stats = await stat(path);\n } catch {\n jobs.push({ path, outputRelative: basename(path) });\n continue;\n }\n if (!stats.isDirectory()) {\n jobs.push({ path, outputRelative: basename(path) });\n continue;\n }\n hasDirectory = true;\n for (const file of await walkDirectory(path, recursive, excludeDir)) {\n // A file that disappears or turns unreadable mid-walk is queued anyway:\n // the per-file worker try/catch counts it as failed without aborting\n // the batch. Only a successful sniff that says \"binary\" skips it.\n const textual = await looksTextual(file).catch(() => true);\n if (!textual) {\n skipped += 1;\n continue;\n }\n jobs.push({ path: file, outputRelative: relative(path, file) });\n }\n }\n return { jobs, batch: hasDirectory || jobs.length > 1, skipped };\n};\n\n/**\n * Run `task` over `items` with at most `workers` in flight.\n * The shared native pipeline makes each redaction a synchronous\n * native call, so concurrency here only overlaps async file\n * I/O; the increments below are safe without locking because\n * no `await` sits between the read and the write of `next`.\n */\nconst runPool = async <T>(\n items: readonly T[],\n workers: number,\n task: (item: T) => Promise<void>,\n): Promise<void> => {\n let next = 0;\n const worker = async (): Promise<void> => {\n while (next < items.length) {\n const index = next;\n next += 1;\n // SAFETY: index < items.length checked above.\n await task(items[index] as T);\n }\n };\n const count = Math.max(1, Math.min(workers, items.length));\n await Promise.all(Array.from({ length: count }, worker));\n};\n\ntype CliEntity = {\n start: number;\n end: number;\n label: string;\n text: string;\n score: number;\n source: string;\n};\n\ntype CliRedactionResult = {\n redactedText: string;\n redactionMap: Map<string, string>;\n operatorMap: Map<string, OperatorType>;\n entityCount: number;\n};\n\ntype NativeCliPipeline = DocxCliPipeline & {\n warmLazyRegex?: () => void;\n redactText: (\n fullText: string,\n operators?: NativeOperatorConfig,\n ) => {\n resolvedEntities: CliEntity[];\n redaction: CliRedactionResult;\n };\n};\n\n// Short aliases for the canonical multi-word labels so that\n// `--labels person,email,iban` works without quoting the space\n// in \"email address\". Separator-insensitive resolution (below)\n// additionally accepts hyphen/underscore forms such as\n// \"credit-card-number\".\nconst LABEL_ALIASES: Record<string, EntityLabel> = {\n email: \"email address\",\n phone: \"phone number\",\n org: \"organization\",\n organisation: \"organization\",\n dob: \"date of birth\",\n ssn: \"social security number\",\n \"tax id\": \"tax identification number\",\n passport: \"passport number\",\n \"credit card\": \"credit card number\",\n \"national id\": \"national identification number\",\n};\n\nconst LABEL_SEPARATOR_RE = /[\\s_-]+/g;\nconst ENTITY_LABEL_SET: ReadonlySet<string> = new Set(ENTITY_LABELS);\n\nconst isEntityLabel = (label: string): label is EntityLabel =>\n ENTITY_LABEL_SET.has(label);\n\n/**\n * Resolve a user-supplied label token to a canonical label.\n * Lowercases and collapses separators, then maps known short\n * aliases. Unknown tokens are returned normalized so the\n * caller can report them verbatim.\n */\nconst canonicalizeLabel = (raw: string): string => {\n const normalized = raw.toLowerCase().replace(LABEL_SEPARATOR_RE, \" \").trim();\n const known: readonly string[] = ENTITY_LABELS;\n if (known.includes(normalized)) {\n return normalized;\n }\n return LABEL_ALIASES[normalized] ?? normalized;\n};\n\nconst validateLabels = (labels: readonly string[]): EntityLabel[] => {\n const resolved = [...new Set(labels.map(canonicalizeLabel))];\n const valid: EntityLabel[] = [];\n const availableLabels = ENTITY_LABELS.join(\", \");\n const availableAliases = Object.keys(LABEL_ALIASES).join(\", \");\n for (const label of resolved) {\n if (!isEntityLabel(label)) {\n throw new UsageError(\n [\n \"--labels: unknown label\",\n JSON.stringify(label) + \";\",\n \"available:\",\n availableLabels,\n \"(aliases:\",\n availableAliases + \")\",\n ].join(\" \"),\n );\n }\n valid.push(label);\n }\n return valid;\n};\n\ntype PipelineConfigOptions = Pick<\n CliOptions,\n \"countries\" | \"labels\" | \"languages\" | \"threshold\"\n>;\n\nconst buildPipelineConfig = async (\n opts: PipelineConfigOptions,\n loadDictionaries: CliEngine[\"loadDictionaries\"],\n): Promise<PipelineConfig> => {\n const dictionaries = await loadDictionaries({\n languages: opts.languages,\n countries: opts.countries,\n });\n return {\n threshold: opts.threshold,\n enableTriggerPhrases: true,\n enableRegex: true,\n enableLegalForms: true,\n enableNameCorpus: true,\n ...(opts.languages === undefined\n ? {}\n : { nameCorpusLanguages: [...opts.languages] }),\n enableDenyList: true,\n ...(opts.countries === undefined\n ? {}\n : { denyListCountries: [...opts.countries] }),\n enableGazetteer: false,\n enableCountries: true,\n enableConfidenceBoost: true,\n enableCoreference: true,\n enableZoneClassification: true,\n enableHotwordRules: true,\n labels:\n opts.labels === undefined\n ? [...DEFAULT_ENTITY_LABELS]\n : validateLabels(opts.labels),\n workspaceId: \"cli\",\n dictionaries,\n };\n};\n\nconst buildOperatorConfig = (opts: CliOptions): NativeOperatorConfig => {\n const operators: NonNullable<NativeOperatorConfig[\"operators\"]> = {};\n if (opts.mode === \"redact\") {\n const labels =\n opts.labels === undefined\n ? DEFAULT_ENTITY_LABELS\n : validateLabels(opts.labels);\n for (const label of labels) operators[label] = \"redact\";\n }\n return { operators, redactString: opts.redactString };\n};\n\nconst writeOutput = async (\n path: string | undefined,\n content: string,\n): Promise<void> => {\n if (path === undefined) {\n process.stdout.write(content);\n return;\n }\n await writeFile(path, content, \"utf8\");\n};\n\ntype RedactionKeyFile = {\n entries: Record<string, { original: string; operator: string }>;\n};\n\nconst parseRedactionKey = (raw: string): Map<string, string> => {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new UsageError(\"redaction key is not valid JSON\");\n }\n if (typeof parsed !== \"object\" || parsed === null || !(\"entries\" in parsed)) {\n throw new UsageError(\n 'redaction key must be an object with an \"entries\" field',\n );\n }\n const { entries } = parsed as RedactionKeyFile;\n if (\n typeof entries !== \"object\" ||\n entries === null ||\n Array.isArray(entries)\n ) {\n throw new UsageError('redaction key \"entries\" must be an object');\n }\n const map = new Map<string, string>();\n for (const [placeholder, entry] of Object.entries(entries)) {\n if (typeof entry?.original !== \"string\") {\n throw new UsageError(\n `redaction key entry \"${placeholder}\" has no original text`,\n );\n }\n map.set(placeholder, entry.original);\n }\n return map;\n};\n\n/**\n * Restrict a redaction key to the entities named by --revert.\n * Each token matches a placeholder (\"[PERSON_1]\") or an original\n * value (\"Jan Novák\"), case-sensitive and exact. A token that\n * matches nothing is a usage error listing the placeholders the\n * key does define, so the caller can correct the spelling.\n */\nconst selectRevertEntries = (\n redactionMap: ReadonlyMap<string, string>,\n tokens: readonly string[],\n): Map<string, string> => {\n const selected = new Map<string, string>();\n for (const token of tokens) {\n let matched = false;\n for (const [placeholder, original] of redactionMap) {\n if (placeholder === token || original === token) {\n selected.set(placeholder, original);\n matched = true;\n }\n }\n if (!matched) {\n const MAX_LISTED_PLACEHOLDERS = 20;\n const placeholders = [...redactionMap.keys()];\n const listed = placeholders.slice(0, MAX_LISTED_PLACEHOLDERS).join(\", \");\n const rest = placeholders.length - MAX_LISTED_PLACEHOLDERS;\n const suffix = rest > 0 ? ` and ${rest} more` : \"\";\n throw new UsageError(\n `--revert ${JSON.stringify(token)} matched no placeholder or ` +\n `original; available placeholders: ${listed}${suffix}`,\n );\n }\n }\n return selected;\n};\n\n/**\n * Ask for a country scope when running interactively on\n * files with no scope flags. Skipped for piped stdin so\n * the CLI stays scriptable.\n */\nexport const shouldPromptForScope = (\n opts: CliOptions,\n tty: { stdinIsTTY: boolean; stderrIsTTY: boolean },\n): boolean =>\n opts.countries === undefined &&\n opts.languages === undefined &&\n !opts.quiet &&\n opts.files.length > 0 &&\n tty.stdinIsTTY &&\n tty.stderrIsTTY;\n\nconst promptForCountries = async (): Promise<string[] | undefined> => {\n const rl = createInterface({\n input: process.stdin,\n output: process.stderr,\n });\n try {\n const answer = await rl.question(\n \"Country scope (ISO codes like CZ,DE,GB; Enter loads all): \",\n );\n const trimmed = answer.trim();\n return trimmed === \"\" ? undefined : parseCountries(trimmed);\n } finally {\n rl.close();\n }\n};\n\nconst runDeanonymise = async (\n opts: CliOptions,\n api: AnonymizeApi,\n): Promise<void> => {\n if (opts.keyPath !== undefined) {\n throw new UsageError(\"--key cannot be combined with --deanonymise\");\n }\n const keyPath = opts.deanonymiseKeyPath;\n if (keyPath === undefined) throw new UsageError(\"missing redaction key path\");\n const fullMap = parseRedactionKey(await readFile(keyPath, \"utf8\"));\n\n // --revert restores a chosen subset; leaving the rest of the\n // key out means deanonymise skips those placeholders, so the\n // other entities stay redacted.\n const redactionMap =\n opts.revert === undefined\n ? fullMap\n : selectRevertEntries(fullMap, opts.revert);\n\n const inputs = await readInputs(opts.files);\n if (inputs.length > 1) {\n throw new UsageError(\"--deanonymise accepts a single input\");\n }\n const input = inputs[0];\n if (!input) throw new UsageError(\"no input to deanonymise\");\n if (opts.output !== undefined) {\n guardWriteTargets(input.path === null ? [] : [input.path], [\n { path: opts.output, flag: \"--output\" },\n ]);\n }\n await writeOutput(opts.output, api.deanonymise(input.text, redactionMap));\n};\n\n/**\n * Reject any write target (output or key file) whose\n * filesystem identity collides with an input file or with\n * another write target. Symlinks count as collisions.\n */\nconst guardWriteTargets = (\n inputPaths: readonly string[],\n writeTargets: readonly { path: string; flag: string }[],\n): void => {\n const inputs = new Set(inputPaths.map(canonicalPath));\n const seen = new Map<string, string>();\n for (const target of writeTargets) {\n const canonical = canonicalPath(target.path);\n if (inputs.has(canonical)) {\n throw new UsageError(\n `refusing to overwrite input file \"${target.path}\" (${target.flag})`,\n );\n }\n const clash = seen.get(canonical);\n if (clash !== undefined) {\n throw new UsageError(\n `${target.flag} \"${target.path}\" collides with ${clash}`,\n );\n }\n seen.set(canonical, `${target.flag} \"${target.path}\"`);\n }\n};\n\nconst summarize = (entities: readonly CliEntity[]): string => {\n const counts = new Map<string, number>();\n for (const entity of entities) {\n counts.set(entity.label, (counts.get(entity.label) ?? 0) + 1);\n }\n const parts = [...counts.entries()]\n .toSorted((a, b) => b[1] - a[1])\n .map(([label, count]) => `${label}: ${count}`);\n return parts.length > 0 ? parts.join(\", \") : \"none\";\n};\n\n/**\n * A single unit of anonymize work: the text to process, where\n * to write it (undefined means stdout), and a label for the\n * stderr summary. Used by the stdin and single-file flows,\n * which additionally support --json and --key.\n */\ntype SingleInput = {\n text: string;\n outputPath: string | undefined;\n source: string;\n};\n\nconst runAnonymiseSingle = async (\n opts: CliOptions,\n runtime: CliRuntime,\n api: AnonymizeApi,\n input: SingleInput,\n): Promise<void> => {\n const { entities, redaction } = await runtime.redact(\n input.text,\n buildOperatorConfig(opts),\n );\n\n if (opts.json) {\n // In redact mode the user chose irreversibility, so the\n // JSON must not carry any detected text. Whitelist the\n // non-sensitive metadata fields; this drops `text` and a\n // coref alias's `corefSourceText`. Offsets index the\n // caller's own input and are kept.\n const jsonEntities =\n opts.mode === \"redact\"\n ? entities.map(({ start, end, label, score, source }) => ({\n start,\n end,\n label,\n score,\n source,\n }))\n : entities;\n const payload = {\n entityCount: redaction.entityCount,\n entities: jsonEntities,\n redactedText: redaction.redactedText,\n };\n await writeOutput(\n input.outputPath,\n `${JSON.stringify(payload, null, 2)}\\n`,\n );\n } else {\n await writeOutput(input.outputPath, redaction.redactedText);\n }\n\n if (opts.keyPath !== undefined) {\n await writeFile(\n opts.keyPath,\n api.exportRedactionKey(redaction.redactionMap, redaction.operatorMap),\n \"utf8\",\n );\n }\n\n if (!opts.quiet) {\n process.stderr.write(\n `anonymize: ${input.source}: ${summarize(entities)}\\n`,\n );\n }\n};\n\n/** Tally of a batch run for the closing summary line. */\ntype BatchOutcome = { processed: number; failed: number };\n\nconst runAnonymiseBatch = async (\n opts: CliOptions,\n runtime: CliRuntime,\n output: string,\n jobs: readonly FileJob[],\n skipped: number,\n): Promise<void> => {\n await mkdir(output, { recursive: true });\n const operatorConfig = buildOperatorConfig(opts);\n const outcome: BatchOutcome = { processed: 0, failed: 0 };\n\n await runPool(jobs, opts.workers, async (job) => {\n const outputPath = join(output, job.outputRelative);\n try {\n const text = await readFile(job.path, \"utf8\");\n const { entities, redaction } = await runtime.redact(\n text,\n operatorConfig,\n );\n await mkdir(dirname(outputPath), { recursive: true });\n await writeFile(outputPath, redaction.redactedText, \"utf8\");\n // No await between here and the increment: safe on the\n // single JS thread despite concurrent workers.\n outcome.processed += 1;\n if (!opts.quiet) {\n process.stderr.write(\n `anonymize: ${job.path}: ${summarize(entities)}\\n`,\n );\n }\n } catch (err) {\n outcome.failed += 1;\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`anonymize: ${job.path}: error: ${message}\\n`);\n }\n });\n\n if (!opts.quiet) {\n const parts = [\n `${outcome.processed} processed`,\n `${outcome.failed} failed`,\n ];\n if (skipped > 0) parts.push(`${skipped} skipped`);\n process.stderr.write(`anonymize: ${parts.join(\", \")}\\n`);\n }\n // Any per-file failure is a nonzero exit, but the whole batch\n // still runs so one bad file does not hide the rest.\n if (outcome.failed > 0) process.exitCode = 1;\n};\n\nconst runAnonymise = async (\n opts: CliOptions,\n { api, loadDictionaries }: CliEngine,\n): Promise<void> => {\n if (opts.keyPath !== undefined && opts.mode !== \"replace\") {\n throw new UsageError('--key requires --mode \"replace\"');\n }\n\n const scoped = shouldPromptForScope(opts, {\n stdinIsTTY: process.stdin.isTTY === true,\n stderrIsTTY: process.stderr.isTTY === true,\n })\n ? { ...opts, countries: await promptForCountries() }\n : opts;\n\n // No positional arguments: read stdin as a single input.\n if (scoped.files.length === 0) {\n const [input] = await readInputs(scoped.files);\n if (!input) throw new UsageError(\"no input to anonymize\");\n guardWriteTargets([], collectSingleTargets(scoped));\n const runtime = await prepareCliRuntime(\n api,\n await buildPipelineConfig(scoped, loadDictionaries),\n );\n await runAnonymiseSingle(scoped, runtime, api, {\n text: input.text,\n outputPath: scoped.output,\n source: \"stdin\",\n });\n return;\n }\n\n const { jobs, batch, skipped } = await expandInputs(\n scoped.files,\n scoped.recursive,\n scoped.output,\n );\n\n if (!batch) {\n // Exactly one plain file: single-input flow with --json/--key.\n const [job] = jobs;\n if (!job) throw new UsageError(\"no input to anonymize\");\n guardWriteTargets([job.path], collectSingleTargets(scoped));\n const runtime = await prepareCliRuntime(\n api,\n await buildPipelineConfig(scoped, loadDictionaries),\n );\n await runAnonymiseSingle(scoped, runtime, api, {\n text: await readFile(job.path, \"utf8\"),\n outputPath: scoped.output,\n source: job.path,\n });\n return;\n }\n\n // Batch: a directory, or more than one file.\n const output = scoped.output;\n if (output === undefined) {\n throw new UsageError(\n \"batch input (a directory or multiple files) requires --output <directory>\",\n );\n }\n if (scoped.keyPath !== undefined) {\n throw new UsageError(\"--key works with a single input only\");\n }\n if (scoped.json) {\n throw new UsageError(\"--json works with a single input only\");\n }\n\n // Validate every write target before any work: colliding\n // output paths (same basename from different input dirs,\n // symlinks to an input) fail fast instead of silently\n // clobbering files mid-batch.\n guardWriteTargets(\n jobs.map((job) => job.path),\n jobs.map((job) => ({\n path: join(output, job.outputRelative),\n flag: \"--output\",\n })),\n );\n\n const runtime = await prepareCliRuntime(\n api,\n await buildPipelineConfig(scoped, loadDictionaries),\n );\n await runAnonymiseBatch(scoped, runtime, output, jobs, skipped);\n};\n\n/** Write targets for a single-input run: --output and --key. */\nconst collectSingleTargets = (\n opts: CliOptions,\n): { path: string; flag: string }[] => {\n const targets: { path: string; flag: string }[] = [];\n if (opts.output !== undefined) {\n targets.push({ path: opts.output, flag: \"--output\" });\n }\n if (opts.keyPath !== undefined) {\n targets.push({ path: opts.keyPath, flag: \"--key\" });\n }\n return targets;\n};\n\ntype CliRuntime = {\n redact: (\n fullText: string,\n operators: NativeOperatorConfig,\n ) => Promise<{ entities: CliEntity[]; redaction: CliRedactionResult }>;\n};\n\nconst prepareNativeCliPipeline = async (\n api: AnonymizeApi,\n config: PipelineConfig,\n): Promise<NativeCliPipeline> => {\n const pipeline = await api.createNativePipelineFromConfig({\n binding: api.loadNativeAnonymizeBinding(),\n config,\n gazetteerEntries: [],\n });\n pipeline.warmLazyRegex?.();\n return pipeline;\n};\n\nconst prepareCliRuntime = async (\n api: AnonymizeApi,\n config: PipelineConfig,\n): Promise<CliRuntime> => {\n const pipeline = await prepareNativeCliPipeline(api, config);\n return {\n redact: async (fullText, operators) => {\n const result = pipeline.redactText(fullText, operators);\n return {\n entities: result.resolvedEntities,\n redaction: result.redaction,\n };\n },\n };\n};\n\n/**\n * Render the canonical entity labels and the short aliases\n * accepted by --labels, for the --list-labels discovery flag.\n */\nconst formatLabelList = (): string => {\n const lines: string[] = [\"Detectable entity labels (pass to --labels):\"];\n for (const label of ENTITY_LABELS) {\n lines.push(` ${label}`);\n }\n lines.push(\"\", \"Short aliases:\");\n const aliases = Object.entries(LABEL_ALIASES);\n let width = 0;\n for (const [alias] of aliases) {\n width = Math.max(width, alias.length);\n }\n for (const [alias, canonical] of aliases) {\n lines.push(` ${alias.padEnd(width)} -> ${canonical}`);\n }\n return `${lines.join(\"\\n\")}\\n`;\n};\n\nconst dispatch = async (engine: CliEngine): Promise<void> => {\n const argv = process.argv.slice(2);\n if (argv.at(0) === \"docx\") {\n await runDocxCommand({\n argv: argv.slice(1),\n preparePipeline: async (\n request: DocxPipelineRequest,\n ): Promise<DocxCliPipeline> => {\n const options: PipelineConfigOptions =\n request.type === \"anonymize\"\n ? request.detection\n : {\n countries: [],\n languages: [],\n threshold: DEFAULT_THRESHOLD,\n };\n return prepareNativeCliPipeline(\n engine.api,\n await buildPipelineConfig(options, engine.loadDictionaries),\n );\n },\n });\n return;\n }\n const opts = parseCliArgs(argv);\n if (opts.help) {\n process.stdout.write(HELP);\n return;\n }\n if (opts.version) {\n process.stdout.write(`${cliVersion()}\\n`);\n return;\n }\n if (opts.listLabels) {\n process.stdout.write(formatLabelList());\n return;\n }\n if (opts.capabilities) {\n process.stdout.write(`${JSON.stringify(CAPABILITY_MANIFEST, null, 2)}\\n`);\n return;\n }\n if (opts.deanonymiseKeyPath !== undefined) {\n await runDeanonymise(opts, engine.api);\n return;\n }\n if (opts.revert !== undefined) {\n throw new UsageError(\"--revert requires --deanonymise <key>\");\n }\n await runAnonymise(opts, engine);\n};\n\n/**\n * Run the CLI against the given engine and set the\n * process exit code (0 ok, 1 runtime error, 2 usage).\n */\nexport const runCli = async (engine: CliEngine): Promise<void> => {\n try {\n await dispatch(engine);\n } catch (err) {\n if (err instanceof UsageError) {\n process.stderr.write(`anonymize: ${err.message}\\n`);\n process.stderr.write(`Try \"anonymize --help\" for usage.\\n`);\n process.exitCode = 2;\n } else {\n const message = err instanceof Error ? err.message : String(err);\n process.stderr.write(`anonymize: ${message}\\n`);\n process.exitCode = 1;\n }\n }\n};\n","#!/usr/bin/env node\n/* npm-distributed entry point — backs the CLI with the\n * native engine (@stll/anonymize napi bindings) and\n * the @stll/anonymize-data dictionary package. */\nimport * as anonymize from \"@stll/anonymize\";\n\nimport { loadCliDictionaries } from \"./dictionaries\";\nimport { runCli } from \"./main\";\n\nawait runCli({ api: anonymize, loadDictionaries: loadCliDictionaries });\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/anonymize-cli",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Command-line PII detection and anonymization powered by @stll/anonymize",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"anonymization",
|
|
@@ -31,20 +31,20 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsdown",
|
|
33
33
|
"prepublishOnly": "bun run build",
|
|
34
|
-
"typecheck": "tsc --noEmit -p tsconfig.json && tsc --noEmit -p tsconfig.test.json",
|
|
34
|
+
"typecheck": "bun ../../scripts/tsc-native.ts --noEmit -p tsconfig.json && bun ../../scripts/tsc-native.ts --noEmit -p tsconfig.test.json",
|
|
35
35
|
"test": "bun test --timeout 60000",
|
|
36
36
|
"format": "oxfmt ."
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@stll/anonymize": "^2.0
|
|
39
|
+
"@stll/anonymize": "^2.2.0",
|
|
40
40
|
"@stll/anonymize-data": "^0.0.6",
|
|
41
|
-
"@stll/anonymize-docx": "^2.0
|
|
41
|
+
"@stll/anonymize-docx": "^2.2.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@types/node": "^26.1.1",
|
|
45
45
|
"bun-types": "^1.3.14",
|
|
46
46
|
"fflate": "^0.8.3",
|
|
47
|
-
"tsdown": "^0.22.
|
|
47
|
+
"tsdown": "^0.22.7",
|
|
48
48
|
"typescript": "^6.0.3"
|
|
49
49
|
}
|
|
50
50
|
}
|