@hraness/wordcell 0.20.0-bootstrap.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +749 -0
- package/dist/agent-context.js +35 -0
- package/dist/agent-guide-audit.js +18 -0
- package/dist/attachments.js +19 -0
- package/dist/authoring.js +33 -0
- package/dist/benchmark.js +19 -0
- package/dist/capture.js +49 -0
- package/dist/cli.js +57 -0
- package/dist/clip/acquire.js +57 -0
- package/dist/clip/args.js +21 -0
- package/dist/clip/bounded-byte-buffer.js +9 -0
- package/dist/clip/browser-profiles.js +17 -0
- package/dist/clip/bundle-reader.js +11 -0
- package/dist/clip/cli.js +27 -0
- package/dist/clip/cookies.js +21 -0
- package/dist/clip/defuddle-worker.js +84 -0
- package/dist/clip/doctor.js +32 -0
- package/dist/clip/jobs.js +784 -0
- package/dist/clip/network-proxy.js +9 -0
- package/dist/clip/network.js +28 -0
- package/dist/clip/persist.js +30 -0
- package/dist/clip/refresh.js +11 -0
- package/dist/clip/terminal.js +11 -0
- package/dist/evaluation-builder.js +12238 -0
- package/dist/evaluation-kb.js +26 -0
- package/dist/evaluation.js +35 -0
- package/dist/git.js +39 -0
- package/dist/graph.js +41 -0
- package/dist/index-054mb7d3.js +1974 -0
- package/dist/index-06c9ctr6.js +738 -0
- package/dist/index-180zbkkt.js +2480 -0
- package/dist/index-1gwbassd.js +743 -0
- package/dist/index-1xxnjn0d.js +171 -0
- package/dist/index-23z4zxgg.js +119 -0
- package/dist/index-2gv8y733.js +432 -0
- package/dist/index-3ehtt59c.js +199 -0
- package/dist/index-3rm7cz6h.js +373 -0
- package/dist/index-48pz4jpc.js +392 -0
- package/dist/index-4j3tt0c3.js +234 -0
- package/dist/index-5n05se68.js +307 -0
- package/dist/index-5vwpzb5a.js +764 -0
- package/dist/index-6jcz0m1c.js +712 -0
- package/dist/index-6sw24nvv.js +1189 -0
- package/dist/index-8a08v6x8.js +56 -0
- package/dist/index-adx6khj5.js +488 -0
- package/dist/index-b88v3vtm.js +805 -0
- package/dist/index-bcknqxrq.js +1943 -0
- package/dist/index-bs0yf4pp.js +711 -0
- package/dist/index-by0pbant.js +827 -0
- package/dist/index-d13v9ckt.js +150 -0
- package/dist/index-dfag79p7.js +370 -0
- package/dist/index-e4fagfjz.js +44 -0
- package/dist/index-e5fbsywq.js +658 -0
- package/dist/index-ekpwvbra.js +1070 -0
- package/dist/index-f984hw45.js +927 -0
- package/dist/index-gh719d91.js +63 -0
- package/dist/index-h170byqw.js +371 -0
- package/dist/index-hgve9rh2.js +2046 -0
- package/dist/index-hya40gb2.js +474 -0
- package/dist/index-j4zgmzjr.js +152 -0
- package/dist/index-m7et4rz4.js +966 -0
- package/dist/index-mxxxytys.js +1254 -0
- package/dist/index-n05s3wsb.js +2728 -0
- package/dist/index-nd6nynv2.js +1162 -0
- package/dist/index-npg9z1a4.js +431 -0
- package/dist/index-pj501bh1.js +140 -0
- package/dist/index-rdd5f7jj.js +70 -0
- package/dist/index-sy0zety7.js +3630 -0
- package/dist/index-tkpxp48t.js +1166 -0
- package/dist/index-w2zc0vwa.js +571 -0
- package/dist/index-why54q5p.js +462 -0
- package/dist/index-x3fthpsc.js +585 -0
- package/dist/index-x6m57nfc.js +1041 -0
- package/dist/index.js +784 -0
- package/dist/kb-alias.js +55 -0
- package/dist/navigation.js +13 -0
- package/dist/pdf.js +85 -0
- package/dist/percolate.js +44 -0
- package/dist/portfolio.js +77 -0
- package/dist/query.js +29 -0
- package/dist/repository-memory.js +60 -0
- package/dist/sdk.js +36 -0
- package/dist/search-rules.js +34 -0
- package/dist/search.js +22 -0
- package/dist/semantic.js +39 -0
- package/dist/source-inbox.js +17 -0
- package/dist/untrusted-content.js +32 -0
- package/dist/url-intelligence.js +74 -0
- package/dist/workflow.js +23 -0
- package/dist/workflows/decision-context.js +20 -0
- package/dist/workflows/explain-change.js +8 -0
- package/dist/workflows/index.js +28 -0
- package/dist/workflows/plan-radar.js +10 -0
- package/package.json +421 -0
- package/skills/wordcell/AGENTS.md +22 -0
- package/skills/wordcell/SKILL.md +91 -0
- package/skills/wordcell/agents/openai.yaml +4 -0
- package/skills/wordcell/references/companion-skills.md +96 -0
- package/skills/wordcell/references/customize.md +123 -0
- package/skills/wordcell/references/pdf-review.md +51 -0
- package/skills/wordcell/references/percolate.md +176 -0
- package/skills/wordcell/references/plan-structure.md +105 -0
- package/skills/wordcell/references/plan.md +115 -0
- package/skills/wordcell/references/query.md +252 -0
- package/skills/wordcell/references/refresh.md +133 -0
- package/skills/wordcell/references/save-pdf.md +125 -0
- package/skills/wordcell/references/save-url.md +178 -0
- package/skills/wordcell/references/url-authentication.md +75 -0
- package/skills/wordcell/references/url-platforms.md +25 -0
- package/skills/wordcell/templates/companion-skill.template.md +57 -0
- package/src/agent-context.ts +1241 -0
- package/src/agent-guide-audit.ts +750 -0
- package/src/attachments.ts +723 -0
- package/src/authoring-model.ts +798 -0
- package/src/authoring-platform.ts +549 -0
- package/src/authoring-program.ts +278 -0
- package/src/authoring-runtime.ts +54 -0
- package/src/authoring.ts +75 -0
- package/src/benchmark.ts +362 -0
- package/src/capture.ts +68 -0
- package/src/cli-program.ts +3662 -0
- package/src/cli.ts +6 -0
- package/src/clip/acquire.ts +1273 -0
- package/src/clip/archive-today.ts +402 -0
- package/src/clip/args.ts +423 -0
- package/src/clip/assets.ts +262 -0
- package/src/clip/bounded-byte-buffer.ts +75 -0
- package/src/clip/browser-profiles.ts +372 -0
- package/src/clip/bundle-reader.ts +608 -0
- package/src/clip/capture.ts +1054 -0
- package/src/clip/cli.ts +181 -0
- package/src/clip/cookies.ts +512 -0
- package/src/clip/doctor.ts +1110 -0
- package/src/clip/extract.ts +868 -0
- package/src/clip/jobs.ts +959 -0
- package/src/clip/lib.ts +861 -0
- package/src/clip/media.ts +1474 -0
- package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
- package/src/clip/metadata-search-tool/Cargo.toml +18 -0
- package/src/clip/metadata-search-tool/runner.ts +685 -0
- package/src/clip/metadata-search-tool/src/main.rs +389 -0
- package/src/clip/metadata-search.ts +687 -0
- package/src/clip/network-proxy.ts +627 -0
- package/src/clip/network.ts +950 -0
- package/src/clip/package-root.ts +54 -0
- package/src/clip/persist.ts +1472 -0
- package/src/clip/platforms.ts +1824 -0
- package/src/clip/refresh.ts +197 -0
- package/src/clip/structured.ts +432 -0
- package/src/clip/terminal.ts +197 -0
- package/src/clip/url-intelligence.ts +581 -0
- package/src/clip/url-metadata-backfill.ts +616 -0
- package/src/clip/url-metadata-cli.ts +251 -0
- package/src/clip/url-metadata.ts +1397 -0
- package/src/evaluation-analysis-v2.ts +1440 -0
- package/src/evaluation-builder.ts +2539 -0
- package/src/evaluation-corpus-authoring.ts +2052 -0
- package/src/evaluation-evidence.ts +1147 -0
- package/src/evaluation-execution-v2.ts +426 -0
- package/src/evaluation-implementation.ts +131 -0
- package/src/evaluation-kb-closure.ts +1430 -0
- package/src/evaluation-kb-runner-v2.ts +735 -0
- package/src/evaluation-kb-v2.ts +2567 -0
- package/src/evaluation-kb.ts +1018 -0
- package/src/evaluation-measurement-v2.ts +1462 -0
- package/src/evaluation-packing-v2.ts +452 -0
- package/src/evaluation-redaction.ts +28 -0
- package/src/evaluation-v2.ts +3857 -0
- package/src/evaluation.ts +1220 -0
- package/src/git.ts +1276 -0
- package/src/graph.ts +1494 -0
- package/src/index.ts +21 -0
- package/src/init.ts +123 -0
- package/src/kb-alias.ts +14 -0
- package/src/navigation.ts +277 -0
- package/src/note-lock.ts +529 -0
- package/src/oh-adoption.ts +444 -0
- package/src/pdf/args.ts +202 -0
- package/src/pdf/capture.ts +298 -0
- package/src/pdf/cli.ts +159 -0
- package/src/pdf/extract.ts +303 -0
- package/src/pdf/layout.ts +618 -0
- package/src/pdf/markdown.ts +154 -0
- package/src/pdf/model.ts +234 -0
- package/src/pdf/ocr.ts +190 -0
- package/src/pdf/persist.ts +314 -0
- package/src/pdf/source.ts +135 -0
- package/src/pdf/tools.ts +146 -0
- package/src/pdf.ts +92 -0
- package/src/percolate.ts +1948 -0
- package/src/portfolio-audit.ts +574 -0
- package/src/portfolio-identity.ts +162 -0
- package/src/portfolio-registry.ts +473 -0
- package/src/portfolio.ts +765 -0
- package/src/query.ts +646 -0
- package/src/repository-memory.ts +1150 -0
- package/src/sdk.ts +1452 -0
- package/src/search-rules.ts +712 -0
- package/src/search.ts +710 -0
- package/src/semantic-runtime.ts +1139 -0
- package/src/semantic.ts +2480 -0
- package/src/source-inbox.ts +232 -0
- package/src/untrusted-content.ts +401 -0
- package/src/url-intelligence.ts +104 -0
- package/src/vault.ts +636 -0
- package/src/workflow-model.ts +444 -0
- package/src/workflow-platform.ts +74 -0
- package/src/workflow-program.ts +183 -0
- package/src/workflow-runtime.ts +51 -0
- package/src/workflow.ts +23 -0
- package/src/workflows/decision-context.ts +81 -0
- package/src/workflows/explain-change.ts +73 -0
- package/src/workflows/index.ts +3 -0
- package/src/workflows/plan-radar.ts +101 -0
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { posix } from "node:path";
|
|
3
|
+
|
|
4
|
+
import { canonicalJson, canonicalSha256 } from "@hraness/oh";
|
|
5
|
+
import {
|
|
6
|
+
parseOhHeadV1,
|
|
7
|
+
parseOhStoreBindingV1,
|
|
8
|
+
verifyOhDependencyClosureAgainstV1,
|
|
9
|
+
type OhDependencyClosureV1,
|
|
10
|
+
type OhHeadV1,
|
|
11
|
+
type OhStoreBindingV1,
|
|
12
|
+
} from "@hraness/oh/store";
|
|
13
|
+
|
|
14
|
+
const CODE_PATTERN = /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u;
|
|
15
|
+
const RECORD_KEY_PATTERN = /^[a-z][a-z0-9]*(?:[._:/-][a-z0-9]+)*$/u;
|
|
16
|
+
const MAX_CAPSULE_BYTES = 16 * 1024 * 1024;
|
|
17
|
+
const MAX_RECORDS = 1_024;
|
|
18
|
+
const MAX_ROOTS = 256;
|
|
19
|
+
const MAX_TEXT_BYTES = 4_096;
|
|
20
|
+
const MAX_STRUCTURAL_NODES = 262_144;
|
|
21
|
+
const MAX_STRUCTURAL_DEPTH = 128;
|
|
22
|
+
|
|
23
|
+
export interface OhAdoptionExpectedSourceV1 {
|
|
24
|
+
readonly authorityId: string;
|
|
25
|
+
readonly binding: OhStoreBindingV1;
|
|
26
|
+
readonly head: OhHeadV1;
|
|
27
|
+
readonly v: 1;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface OhAdoptionDestinationV1 {
|
|
31
|
+
readonly purpose: string;
|
|
32
|
+
readonly targetPath: string;
|
|
33
|
+
readonly v: 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface OhAdoptionRightsClearanceV1 {
|
|
37
|
+
readonly decisionId: string;
|
|
38
|
+
readonly disposition: "cleared-for-purpose";
|
|
39
|
+
readonly purpose: string;
|
|
40
|
+
readonly v: 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface OhAdoptionReviewRequirementV1 {
|
|
44
|
+
readonly route: string;
|
|
45
|
+
readonly status: "required";
|
|
46
|
+
readonly v: 1;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface OhAdoptionConflictReviewV1 {
|
|
50
|
+
readonly notes: readonly string[];
|
|
51
|
+
readonly status: "none-observed" | "requires-resolution";
|
|
52
|
+
readonly v: 1;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface OhAdoptionChangeDisclosureV1 {
|
|
56
|
+
readonly id: string;
|
|
57
|
+
readonly recordKey: string;
|
|
58
|
+
readonly summary: string;
|
|
59
|
+
readonly v: 1;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface OhAdoptionHostPolicyV1 {
|
|
63
|
+
readonly conflicts: OhAdoptionConflictReviewV1;
|
|
64
|
+
readonly destination: OhAdoptionDestinationV1;
|
|
65
|
+
readonly expectedSource: OhAdoptionExpectedSourceV1;
|
|
66
|
+
readonly review: OhAdoptionReviewRequirementV1;
|
|
67
|
+
readonly rights: OhAdoptionRightsClearanceV1;
|
|
68
|
+
readonly v: 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface OhAdoptionPrepareInputV1 {
|
|
72
|
+
readonly capsule: OhDependencyClosureV1;
|
|
73
|
+
readonly redactions: readonly OhAdoptionChangeDisclosureV1[];
|
|
74
|
+
readonly transformations: readonly OhAdoptionChangeDisclosureV1[];
|
|
75
|
+
readonly v: 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export interface OhAdoptionCandidateV1 {
|
|
79
|
+
readonly artifactSha256: string;
|
|
80
|
+
readonly candidateSha256: string;
|
|
81
|
+
readonly manifest: Readonly<{
|
|
82
|
+
readonly conflicts: OhAdoptionConflictReviewV1;
|
|
83
|
+
readonly destination: OhAdoptionDestinationV1;
|
|
84
|
+
readonly format: "hraness.kb.oh-adoption-candidate.v1";
|
|
85
|
+
readonly redactions: readonly OhAdoptionChangeDisclosureV1[];
|
|
86
|
+
readonly review: OhAdoptionReviewRequirementV1;
|
|
87
|
+
readonly rights: OhAdoptionRightsClearanceV1;
|
|
88
|
+
readonly source: Readonly<{
|
|
89
|
+
readonly authorityId: string;
|
|
90
|
+
readonly binding: Readonly<{
|
|
91
|
+
readonly bindingSha256: string;
|
|
92
|
+
readonly v: 1;
|
|
93
|
+
}>;
|
|
94
|
+
readonly closureSha256: string;
|
|
95
|
+
readonly head: OhHeadV1;
|
|
96
|
+
readonly records: readonly Readonly<{
|
|
97
|
+
readonly dependencies: readonly string[];
|
|
98
|
+
readonly key: string;
|
|
99
|
+
readonly kind: string;
|
|
100
|
+
readonly recordSha256: string;
|
|
101
|
+
readonly v: 1;
|
|
102
|
+
}>[];
|
|
103
|
+
readonly roots: readonly string[];
|
|
104
|
+
readonly v: 1;
|
|
105
|
+
}>;
|
|
106
|
+
readonly status: "prepared";
|
|
107
|
+
readonly transformations: readonly OhAdoptionChangeDisclosureV1[];
|
|
108
|
+
readonly v: 1;
|
|
109
|
+
}>;
|
|
110
|
+
readonly markdown: string;
|
|
111
|
+
readonly v: 1;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export interface OhAdoptionPreparerV1 {
|
|
115
|
+
/** Accepts capsule bytes and disclosures only; all authority and policy are host-bound. */
|
|
116
|
+
prepare(value: unknown): OhAdoptionCandidateV1;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
120
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
121
|
+
const prototype = Object.getPrototypeOf(value);
|
|
122
|
+
return prototype === Object.prototype || prototype === null;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boolean {
|
|
126
|
+
const actual = Reflect.ownKeys(value);
|
|
127
|
+
return actual.length === keys.length && actual.every((key) => {
|
|
128
|
+
if (typeof key !== "string" || !keys.includes(key)) return false;
|
|
129
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
130
|
+
return descriptor !== undefined && descriptor.enumerable && "value" in descriptor;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function validUnicode(value: string): boolean {
|
|
135
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
136
|
+
const code = value.charCodeAt(index);
|
|
137
|
+
if (code >= 0xd800 && code <= 0xdbff) {
|
|
138
|
+
const next = value.charCodeAt(index + 1);
|
|
139
|
+
if (next < 0xdc00 || next > 0xdfff) return false;
|
|
140
|
+
index += 1;
|
|
141
|
+
} else if (code >= 0xdc00 && code <= 0xdfff) return false;
|
|
142
|
+
}
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function structurallyBounded(value: unknown): boolean {
|
|
147
|
+
const pending: Array<readonly [unknown, number]> = [[value, 0]];
|
|
148
|
+
const seen = new Set<object>();
|
|
149
|
+
let nodes = 0;
|
|
150
|
+
let scalarBytes = 0;
|
|
151
|
+
while (pending.length > 0) {
|
|
152
|
+
const [candidate, depth] = pending.pop() as readonly [unknown, number];
|
|
153
|
+
nodes += 1;
|
|
154
|
+
scalarBytes += 4;
|
|
155
|
+
if (nodes > MAX_STRUCTURAL_NODES || depth > MAX_STRUCTURAL_DEPTH
|
|
156
|
+
|| scalarBytes > MAX_CAPSULE_BYTES) return false;
|
|
157
|
+
if (typeof candidate === "string") {
|
|
158
|
+
if (!validUnicode(candidate)) return false;
|
|
159
|
+
scalarBytes += Buffer.byteLength(candidate, "utf8");
|
|
160
|
+
if (scalarBytes > MAX_CAPSULE_BYTES) return false;
|
|
161
|
+
}
|
|
162
|
+
if (typeof candidate !== "object" || candidate === null) continue;
|
|
163
|
+
if (seen.has(candidate)) return false;
|
|
164
|
+
seen.add(candidate);
|
|
165
|
+
if (Array.isArray(candidate)) {
|
|
166
|
+
if (candidate.length > MAX_STRUCTURAL_NODES) return false;
|
|
167
|
+
const keys = Reflect.ownKeys(candidate);
|
|
168
|
+
if (keys.some((key) => key !== "length" && (typeof key !== "string"
|
|
169
|
+
|| !/^(?:0|[1-9][0-9]*)$/u.test(key) || Number(key) >= candidate.length))) return false;
|
|
170
|
+
for (let index = 0; index < candidate.length; index += 1) {
|
|
171
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, String(index));
|
|
172
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)) return false;
|
|
173
|
+
pending.push([descriptor.value, depth + 1]);
|
|
174
|
+
}
|
|
175
|
+
} else if (isRecord(candidate)) {
|
|
176
|
+
const keys = Reflect.ownKeys(candidate);
|
|
177
|
+
if (keys.length > MAX_STRUCTURAL_NODES || keys.some((key) => typeof key !== "string")) return false;
|
|
178
|
+
for (const key of keys as string[]) {
|
|
179
|
+
const descriptor = Object.getOwnPropertyDescriptor(candidate, key);
|
|
180
|
+
if (descriptor === undefined || !descriptor.enumerable || !("value" in descriptor)
|
|
181
|
+
|| !validUnicode(key)) return false;
|
|
182
|
+
scalarBytes += Buffer.byteLength(key, "utf8");
|
|
183
|
+
if (scalarBytes > MAX_CAPSULE_BYTES) return false;
|
|
184
|
+
pending.push([descriptor.value, depth + 1]);
|
|
185
|
+
}
|
|
186
|
+
} else return false;
|
|
187
|
+
}
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function immutableClone<T>(value: T): T {
|
|
192
|
+
if (Array.isArray(value)) {
|
|
193
|
+
return Object.freeze(value.map((item) => immutableClone(item))) as T;
|
|
194
|
+
}
|
|
195
|
+
if (isRecord(value)) {
|
|
196
|
+
const clone: Record<string, unknown> = {};
|
|
197
|
+
for (const key of Object.keys(value)) clone[key] = immutableClone(value[key]);
|
|
198
|
+
return Object.freeze(clone) as T;
|
|
199
|
+
}
|
|
200
|
+
return value;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function code(value: unknown, maximum = 256): string | null {
|
|
204
|
+
return typeof value === "string" && value.length <= maximum && CODE_PATTERN.test(value) ? value : null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function recordKey(value: unknown): string | null {
|
|
208
|
+
return typeof value === "string" && value.length <= 512 && RECORD_KEY_PATTERN.test(value) ? value : null;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function orderedUnique(values: readonly string[]): boolean {
|
|
212
|
+
return values.every((value, index) => index === 0 || (values[index - 1] as string) < value);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function unsafeReviewCodePoint(codePoint: number): boolean {
|
|
216
|
+
return codePoint <= 0x1f
|
|
217
|
+
|| (codePoint >= 0x7f && codePoint <= 0x9f)
|
|
218
|
+
|| codePoint === 0x061c
|
|
219
|
+
|| codePoint === 0x200e
|
|
220
|
+
|| codePoint === 0x200f
|
|
221
|
+
|| (codePoint >= 0x2028 && codePoint <= 0x202e)
|
|
222
|
+
|| (codePoint >= 0x2066 && codePoint <= 0x2069)
|
|
223
|
+
|| codePoint === 0xfeff;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function singleLine(value: unknown): string | null {
|
|
227
|
+
if (typeof value !== "string" || value.length < 1 || value.normalize("NFC") !== value
|
|
228
|
+
|| !validUnicode(value) || [...value].some((character) =>
|
|
229
|
+
unsafeReviewCodePoint(character.codePointAt(0) ?? 0))
|
|
230
|
+
|| Buffer.byteLength(value, "utf8") > MAX_TEXT_BYTES) return null;
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseExpectedSource(value: unknown): OhAdoptionExpectedSourceV1 | null {
|
|
235
|
+
if (!isRecord(value) || !exactKeys(value, ["authorityId", "binding", "head", "v"])
|
|
236
|
+
|| value.v !== 1) return null;
|
|
237
|
+
const authorityId = code(value.authorityId);
|
|
238
|
+
const binding = parseOhStoreBindingV1(value.binding);
|
|
239
|
+
const head = parseOhHeadV1(value.head);
|
|
240
|
+
return authorityId !== null && binding !== null && binding.profile.profileKind === "working" && head !== null
|
|
241
|
+
? immutableClone({ authorityId, binding, head, v: 1 }) : null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function parseDestination(value: unknown): OhAdoptionDestinationV1 | null {
|
|
245
|
+
if (!isRecord(value) || !exactKeys(value, ["purpose", "targetPath", "v"]) || value.v !== 1) return null;
|
|
246
|
+
const purpose = code(value.purpose);
|
|
247
|
+
if (purpose === null || typeof value.targetPath !== "string" || value.targetPath.length > 512
|
|
248
|
+
|| value.targetPath.includes("\\") || value.targetPath.startsWith("/")
|
|
249
|
+
|| posix.normalize(value.targetPath) !== value.targetPath
|
|
250
|
+
|| !/^notes\/[a-z0-9][a-z0-9._/-]*\.md$/u.test(value.targetPath)
|
|
251
|
+
|| value.targetPath.split("/").some((segment) => segment === "." || segment === ".." || segment.startsWith("."))) {
|
|
252
|
+
return null;
|
|
253
|
+
}
|
|
254
|
+
return { purpose, targetPath: value.targetPath, v: 1 };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function parseRights(value: unknown, purpose: string): OhAdoptionRightsClearanceV1 | null {
|
|
258
|
+
if (!isRecord(value) || !exactKeys(value, ["decisionId", "disposition", "purpose", "v"])
|
|
259
|
+
|| value.v !== 1 || value.disposition !== "cleared-for-purpose" || value.purpose !== purpose) return null;
|
|
260
|
+
const decisionId = code(value.decisionId);
|
|
261
|
+
return decisionId === null ? null
|
|
262
|
+
: { decisionId, disposition: "cleared-for-purpose", purpose, v: 1 };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function parseReview(value: unknown): OhAdoptionReviewRequirementV1 | null {
|
|
266
|
+
if (!isRecord(value) || !exactKeys(value, ["route", "status", "v"])
|
|
267
|
+
|| value.v !== 1 || value.status !== "required") return null;
|
|
268
|
+
const route = code(value.route);
|
|
269
|
+
return route === null ? null : { route, status: "required", v: 1 };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function parseConflicts(value: unknown): OhAdoptionConflictReviewV1 | null {
|
|
273
|
+
if (!isRecord(value) || !exactKeys(value, ["notes", "status", "v"]) || value.v !== 1
|
|
274
|
+
|| (value.status !== "none-observed" && value.status !== "requires-resolution")
|
|
275
|
+
|| !Array.isArray(value.notes) || value.notes.length < 1 || value.notes.length > 64) return null;
|
|
276
|
+
const notes = value.notes.map(singleLine);
|
|
277
|
+
if (notes.some((note) => note === null)) return null;
|
|
278
|
+
const sorted = [...notes as string[]].sort();
|
|
279
|
+
return orderedUnique(sorted) ? { notes: sorted, status: value.status, v: 1 } : null;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function parseHostPolicy(value: unknown): OhAdoptionHostPolicyV1 | null {
|
|
283
|
+
if (!structurallyBounded(value) || !isRecord(value)
|
|
284
|
+
|| !exactKeys(value, ["conflicts", "destination", "expectedSource", "review", "rights", "v"])
|
|
285
|
+
|| value.v !== 1) return null;
|
|
286
|
+
const destination = parseDestination(value.destination);
|
|
287
|
+
const expectedSource = parseExpectedSource(value.expectedSource);
|
|
288
|
+
const conflicts = parseConflicts(value.conflicts);
|
|
289
|
+
const review = parseReview(value.review);
|
|
290
|
+
const rights = destination === null ? null : parseRights(value.rights, destination.purpose);
|
|
291
|
+
return destination !== null && expectedSource !== null && conflicts !== null && review !== null && rights !== null
|
|
292
|
+
? immutableClone({ conflicts, destination, expectedSource, review, rights, v: 1 }) : null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function verifyCapsule(
|
|
296
|
+
value: unknown,
|
|
297
|
+
expectedSource: OhAdoptionExpectedSourceV1,
|
|
298
|
+
): OhDependencyClosureV1 | null {
|
|
299
|
+
try {
|
|
300
|
+
if (!structurallyBounded(value) || !isRecord(value)
|
|
301
|
+
|| !exactKeys(value, ["binding", "closureSha256", "head", "records", "roots", "v"])
|
|
302
|
+
|| value.v !== 1 || !Array.isArray(value.records) || !Array.isArray(value.roots)
|
|
303
|
+
|| value.records.length < 1 || value.records.length > MAX_RECORDS
|
|
304
|
+
|| value.roots.length < 1 || value.roots.length > MAX_ROOTS
|
|
305
|
+
|| Buffer.byteLength(canonicalJson(value), "utf8") > MAX_CAPSULE_BYTES) return null;
|
|
306
|
+
const verified = verifyOhDependencyClosureAgainstV1(value, {
|
|
307
|
+
binding: expectedSource.binding,
|
|
308
|
+
head: expectedSource.head,
|
|
309
|
+
});
|
|
310
|
+
return verified.ok && verified.closure.binding.profile.profileKind === "working"
|
|
311
|
+
? verified.closure : null;
|
|
312
|
+
} catch {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function parseDisclosures(value: unknown, keys: ReadonlySet<string>): readonly OhAdoptionChangeDisclosureV1[] | null {
|
|
318
|
+
if (!Array.isArray(value) || value.length > 256) return null;
|
|
319
|
+
const parsed: OhAdoptionChangeDisclosureV1[] = [];
|
|
320
|
+
for (const item of value) {
|
|
321
|
+
if (!isRecord(item) || !exactKeys(item, ["id", "recordKey", "summary", "v"]) || item.v !== 1) return null;
|
|
322
|
+
const id = code(item.id);
|
|
323
|
+
const key = recordKey(item.recordKey);
|
|
324
|
+
const summary = singleLine(item.summary);
|
|
325
|
+
if (id === null || key === null || summary === null || !keys.has(key)) return null;
|
|
326
|
+
parsed.push({ id, recordKey: key, summary, v: 1 });
|
|
327
|
+
}
|
|
328
|
+
parsed.sort((left, right) => left.id < right.id ? -1 : left.id > right.id ? 1 : 0);
|
|
329
|
+
return orderedUnique(parsed.map((item) => item.id)) ? parsed : null;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function markdownEscape(value: string): string {
|
|
333
|
+
return value.replace(/[\\`*_{}\[\]<>()#+.!|>-]/gu, "\\$&");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function renderMarkdown(manifest: OhAdoptionCandidateV1["manifest"], candidateSha256: string): string {
|
|
337
|
+
const lines = [
|
|
338
|
+
"# Oh adoption candidate",
|
|
339
|
+
"",
|
|
340
|
+
`- Status: \`${manifest.status}\``,
|
|
341
|
+
`- Candidate: \`sha256:${candidateSha256}\``,
|
|
342
|
+
`- Destination: \`${manifest.destination.targetPath}\``,
|
|
343
|
+
`- Purpose: \`${manifest.destination.purpose}\``,
|
|
344
|
+
`- Source authority: \`${manifest.source.authorityId}\``,
|
|
345
|
+
`- Source binding: \`${manifest.source.binding.bindingSha256}\``,
|
|
346
|
+
`- Source head sequence: \`${manifest.source.head.sequence}\``,
|
|
347
|
+
`- Source head operation: \`${manifest.source.head.operationSha256 ?? "empty"}\``,
|
|
348
|
+
`- Source graph revision: \`${manifest.source.head.graphRevisionSha256 ?? "empty"}\``,
|
|
349
|
+
`- Source records digest: \`${manifest.source.head.recordsSha256}\``,
|
|
350
|
+
`- Closure: \`${manifest.source.closureSha256}\``,
|
|
351
|
+
"",
|
|
352
|
+
"This is a review candidate, not reviewed knowledge. It does not mutate a vault or adopt the source operation chain, database, projection, or derived tuples.",
|
|
353
|
+
"",
|
|
354
|
+
"## Required decisions",
|
|
355
|
+
"",
|
|
356
|
+
`- Rights: \`${manifest.rights.disposition}\` via \`${manifest.rights.decisionId}\` for \`${manifest.rights.purpose}\``,
|
|
357
|
+
`- Review: \`${manifest.review.status}\` via \`${manifest.review.route}\``,
|
|
358
|
+
`- Conflicts: \`${manifest.conflicts.status}\``,
|
|
359
|
+
...manifest.conflicts.notes.map((note) => ` - ${markdownEscape(note)}`),
|
|
360
|
+
"",
|
|
361
|
+
"## Selected roots",
|
|
362
|
+
"",
|
|
363
|
+
...manifest.source.roots.map((root) => `- \`${root}\``),
|
|
364
|
+
"",
|
|
365
|
+
"## Exact source records",
|
|
366
|
+
"",
|
|
367
|
+
];
|
|
368
|
+
for (const record of manifest.source.records) {
|
|
369
|
+
lines.push(`### \`${record.key}\``, "", `- Kind: \`${record.kind}\``,
|
|
370
|
+
`- Digest: \`${record.recordSha256}\``,
|
|
371
|
+
`- Dependencies: ${record.dependencies.length === 0 ? "none" : record.dependencies.map((key) => `\`${key}\``).join(", ")}`, "");
|
|
372
|
+
}
|
|
373
|
+
lines.push("## Transformations", "",
|
|
374
|
+
...(manifest.transformations.length === 0 ? ["- None declared."] : manifest.transformations.map((item) =>
|
|
375
|
+
`- \`${item.id}\` on \`${item.recordKey}\`: ${markdownEscape(item.summary)}`)), "",
|
|
376
|
+
"## Redactions", "",
|
|
377
|
+
...(manifest.redactions.length === 0 ? ["- None declared."] : manifest.redactions.map((item) =>
|
|
378
|
+
`- \`${item.id}\` on \`${item.recordKey}\`: ${markdownEscape(item.summary)}`)), "");
|
|
379
|
+
return `${lines.join("\n")}\n`;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function prepareWithPolicy(
|
|
383
|
+
value: unknown,
|
|
384
|
+
policy: OhAdoptionHostPolicyV1,
|
|
385
|
+
): OhAdoptionCandidateV1 {
|
|
386
|
+
if (!structurallyBounded(value) || !isRecord(value)
|
|
387
|
+
|| !exactKeys(value, ["capsule", "redactions", "transformations", "v"]) || value.v !== 1) {
|
|
388
|
+
throw new TypeError("Invalid Oh adoption preparation input.");
|
|
389
|
+
}
|
|
390
|
+
const capsule = verifyCapsule(value.capsule, policy.expectedSource);
|
|
391
|
+
if (capsule === null) throw new TypeError("The source capsule is invalid for the bound authority and head.");
|
|
392
|
+
const recordKeys = new Set(capsule.records.map((record) => record.key));
|
|
393
|
+
const transformations = parseDisclosures(value.transformations, recordKeys);
|
|
394
|
+
const redactions = parseDisclosures(value.redactions, recordKeys);
|
|
395
|
+
const roots = new Set(capsule.roots);
|
|
396
|
+
if (transformations === null || redactions === null
|
|
397
|
+
|| capsule.records.filter((record) => roots.has(record.key)).every((record) => record.kind === "view")) {
|
|
398
|
+
throw new TypeError("Adoption requires valid disclosures and an authoritative root.");
|
|
399
|
+
}
|
|
400
|
+
const source = {
|
|
401
|
+
authorityId: policy.expectedSource.authorityId,
|
|
402
|
+
binding: { bindingSha256: capsule.binding.bindingSha256, v: 1 as const },
|
|
403
|
+
closureSha256: capsule.closureSha256,
|
|
404
|
+
head: capsule.head,
|
|
405
|
+
records: capsule.records.map((record) => ({ dependencies: record.dependencies, key: record.key,
|
|
406
|
+
kind: record.kind, recordSha256: record.recordSha256, v: 1 as const })),
|
|
407
|
+
roots: capsule.roots,
|
|
408
|
+
v: 1 as const,
|
|
409
|
+
};
|
|
410
|
+
const manifest = {
|
|
411
|
+
conflicts: policy.conflicts,
|
|
412
|
+
destination: policy.destination,
|
|
413
|
+
format: "hraness.kb.oh-adoption-candidate.v1" as const,
|
|
414
|
+
redactions,
|
|
415
|
+
review: policy.review,
|
|
416
|
+
rights: policy.rights,
|
|
417
|
+
source,
|
|
418
|
+
status: "prepared" as const,
|
|
419
|
+
transformations,
|
|
420
|
+
v: 1 as const,
|
|
421
|
+
};
|
|
422
|
+
const candidateSha256 = canonicalSha256(manifest);
|
|
423
|
+
const markdown = renderMarkdown(manifest, candidateSha256);
|
|
424
|
+
if (Buffer.byteLength(markdown, "utf8") > MAX_CAPSULE_BYTES) {
|
|
425
|
+
throw new RangeError("The adoption candidate exceeds its Markdown byte limit.");
|
|
426
|
+
}
|
|
427
|
+
return immutableClone({
|
|
428
|
+
artifactSha256: createHash("sha256").update(markdown).digest("hex"),
|
|
429
|
+
candidateSha256,
|
|
430
|
+
manifest,
|
|
431
|
+
markdown,
|
|
432
|
+
v: 1,
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Captures all authority, destination, rights, review, and conflict policy in
|
|
438
|
+
* trusted host code. The returned facade can only prepare inert review bytes.
|
|
439
|
+
*/
|
|
440
|
+
export function createOhAdoptionPreparerV1(value: unknown): OhAdoptionPreparerV1 {
|
|
441
|
+
const policy = parseHostPolicy(value);
|
|
442
|
+
if (policy === null) throw new TypeError("Invalid Oh adoption host policy.");
|
|
443
|
+
return Object.freeze({ prepare: (input: unknown) => prepareWithPolicy(input, policy) });
|
|
444
|
+
}
|
package/src/pdf/args.ts
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
export const pdfUsage = `wordcell pdf — save a local or public remote PDF as an auditable Markdown bundle
|
|
2
|
+
|
|
3
|
+
Usage:
|
|
4
|
+
wordcell pdf <file-or-url> [--output <directory>] [--slug <slug>] [--annotations <json>] [--force] [--json]
|
|
5
|
+
wordcell pdf save <file-or-url> [capture options]
|
|
6
|
+
|
|
7
|
+
Capture options:
|
|
8
|
+
--output <directory> Bundle parent (default: KB_PDF_OUTPUT or kb/articles)
|
|
9
|
+
--slug <slug> Override the title-derived bundle name
|
|
10
|
+
--annotations <json> Optional hash-bound image interpretations from an agent
|
|
11
|
+
--timeout-ms <milliseconds> Per-tool timeout
|
|
12
|
+
--max-pdf-bytes <size> Input limit, for example 512mb
|
|
13
|
+
--max-pages <count> Maximum pages to process
|
|
14
|
+
--max-images <count> Maximum embedded images
|
|
15
|
+
--max-asset-bytes <size> Per-image limit
|
|
16
|
+
--max-total-asset-bytes <size>
|
|
17
|
+
--force Replace only a compatible PDF capture bundle
|
|
18
|
+
--json Emit a machine-readable result
|
|
19
|
+
--quiet Suppress progress and warning lines
|
|
20
|
+
`;
|
|
21
|
+
|
|
22
|
+
export type PdfCliArguments =
|
|
23
|
+
| { readonly command: "help" }
|
|
24
|
+
| {
|
|
25
|
+
readonly command: "capture";
|
|
26
|
+
readonly input: string;
|
|
27
|
+
readonly outputBase: string;
|
|
28
|
+
readonly slug?: string;
|
|
29
|
+
readonly interpretationsPath?: string;
|
|
30
|
+
readonly force: boolean;
|
|
31
|
+
readonly json: boolean;
|
|
32
|
+
readonly quiet: boolean;
|
|
33
|
+
readonly timeoutMs?: number;
|
|
34
|
+
readonly maxPdfBytes?: number;
|
|
35
|
+
readonly maxPages?: number;
|
|
36
|
+
readonly maxImages?: number;
|
|
37
|
+
readonly maxAssetBytes?: number;
|
|
38
|
+
readonly maxTotalAssetBytes?: number;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type ParsePdfArgumentsResult =
|
|
42
|
+
| { readonly ok: true; readonly value: PdfCliArguments }
|
|
43
|
+
| { readonly ok: false; readonly message: string };
|
|
44
|
+
|
|
45
|
+
const valueOptions = new Set([
|
|
46
|
+
"--output",
|
|
47
|
+
"--slug",
|
|
48
|
+
"--annotations",
|
|
49
|
+
"--timeout-ms",
|
|
50
|
+
"--max-pdf-bytes",
|
|
51
|
+
"--max-pages",
|
|
52
|
+
"--max-images",
|
|
53
|
+
"--max-asset-bytes",
|
|
54
|
+
"--max-total-asset-bytes",
|
|
55
|
+
]);
|
|
56
|
+
|
|
57
|
+
function optionValue(
|
|
58
|
+
arguments_: readonly string[],
|
|
59
|
+
index: number,
|
|
60
|
+
name: string,
|
|
61
|
+
): string | ParsePdfArgumentsResult {
|
|
62
|
+
const value = arguments_[index + 1];
|
|
63
|
+
return value === undefined || value.startsWith("--")
|
|
64
|
+
? { ok: false, message: `${name} requires a value` }
|
|
65
|
+
: value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function positiveInteger(value: string, name: string, maximum: number): number | string {
|
|
69
|
+
if (!/^\d+$/u.test(value)) return `${name} must be a positive integer`;
|
|
70
|
+
const parsed = Number(value);
|
|
71
|
+
return Number.isSafeInteger(parsed) && parsed >= 1 && parsed <= maximum
|
|
72
|
+
? parsed
|
|
73
|
+
: `${name} must be between 1 and ${maximum}`;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function byteSize(value: string, name: string, maximum: number): number | string {
|
|
77
|
+
const match = /^(\d+)(b|kb|mb|gb)?$/iu.exec(value);
|
|
78
|
+
if (match === null || match[1] === undefined) {
|
|
79
|
+
return `${name} must be an integer byte size such as 500000, 25mb, or 1gb`;
|
|
80
|
+
}
|
|
81
|
+
const amount = Number(match[1]);
|
|
82
|
+
const unit = match[2]?.toLowerCase() ?? "b";
|
|
83
|
+
const multiplier = unit === "gb"
|
|
84
|
+
? 1024 ** 3
|
|
85
|
+
: unit === "mb"
|
|
86
|
+
? 1024 ** 2
|
|
87
|
+
: unit === "kb"
|
|
88
|
+
? 1024
|
|
89
|
+
: 1;
|
|
90
|
+
const parsed = amount * multiplier;
|
|
91
|
+
return Number.isSafeInteger(parsed) && parsed >= 1 && parsed <= maximum
|
|
92
|
+
? parsed
|
|
93
|
+
: `${name} must be between 1 byte and ${maximum} bytes`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Parse the delegated `wordcell pdf` surface without touching the filesystem. */
|
|
97
|
+
export function parsePdfArguments(
|
|
98
|
+
rawArguments: readonly string[],
|
|
99
|
+
environment: Readonly<Record<string, string | undefined>> = {},
|
|
100
|
+
): ParsePdfArgumentsResult {
|
|
101
|
+
if (
|
|
102
|
+
rawArguments.length === 0
|
|
103
|
+
|| rawArguments[0] === "help"
|
|
104
|
+
|| rawArguments[0] === "--help"
|
|
105
|
+
|| rawArguments[0] === "-h"
|
|
106
|
+
) return { ok: true, value: { command: "help" } };
|
|
107
|
+
|
|
108
|
+
let cursor = rawArguments[0] === "save" || rawArguments[0] === "capture" ? 1 : 0;
|
|
109
|
+
const positional: string[] = [];
|
|
110
|
+
let outputBase = environment.KB_PDF_OUTPUT ?? "kb/articles";
|
|
111
|
+
let slug: string | undefined;
|
|
112
|
+
let interpretationsPath: string | undefined;
|
|
113
|
+
let force = false;
|
|
114
|
+
let json = false;
|
|
115
|
+
let quiet = false;
|
|
116
|
+
let timeoutMs: number | undefined;
|
|
117
|
+
let maxPdfBytes: number | undefined;
|
|
118
|
+
let maxPages: number | undefined;
|
|
119
|
+
let maxImages: number | undefined;
|
|
120
|
+
let maxAssetBytes: number | undefined;
|
|
121
|
+
let maxTotalAssetBytes: number | undefined;
|
|
122
|
+
|
|
123
|
+
for (; cursor < rawArguments.length; cursor += 1) {
|
|
124
|
+
const argument = rawArguments[cursor];
|
|
125
|
+
if (argument === undefined) continue;
|
|
126
|
+
if (!argument.startsWith("--")) {
|
|
127
|
+
positional.push(argument);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
if (argument === "--force") {
|
|
131
|
+
force = true;
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
if (argument === "--json") {
|
|
135
|
+
json = true;
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
if (argument === "--quiet") {
|
|
139
|
+
quiet = true;
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (!valueOptions.has(argument)) {
|
|
143
|
+
return { ok: false, message: `unknown PDF option: ${argument}` };
|
|
144
|
+
}
|
|
145
|
+
const rawValue = optionValue(rawArguments, cursor, argument);
|
|
146
|
+
if (typeof rawValue !== "string") return rawValue;
|
|
147
|
+
cursor += 1;
|
|
148
|
+
if (argument === "--output") outputBase = rawValue;
|
|
149
|
+
else if (argument === "--slug") slug = rawValue;
|
|
150
|
+
else if (argument === "--annotations") interpretationsPath = rawValue;
|
|
151
|
+
else if (argument === "--timeout-ms") {
|
|
152
|
+
const parsed = positiveInteger(rawValue, argument, 10 * 60_000);
|
|
153
|
+
if (typeof parsed === "string") return { ok: false, message: parsed };
|
|
154
|
+
timeoutMs = parsed;
|
|
155
|
+
} else if (argument === "--max-pages") {
|
|
156
|
+
const parsed = positiveInteger(rawValue, argument, 10_000);
|
|
157
|
+
if (typeof parsed === "string") return { ok: false, message: parsed };
|
|
158
|
+
maxPages = parsed;
|
|
159
|
+
} else if (argument === "--max-images") {
|
|
160
|
+
const parsed = positiveInteger(rawValue, argument, 10_000);
|
|
161
|
+
if (typeof parsed === "string") return { ok: false, message: parsed };
|
|
162
|
+
maxImages = parsed;
|
|
163
|
+
} else {
|
|
164
|
+
const maximum = argument === "--max-asset-bytes" ? 2 * 1024 ** 3 : 8 * 1024 ** 3;
|
|
165
|
+
const parsed = byteSize(rawValue, argument, maximum);
|
|
166
|
+
if (typeof parsed === "string") return { ok: false, message: parsed };
|
|
167
|
+
if (argument === "--max-pdf-bytes") maxPdfBytes = parsed;
|
|
168
|
+
else if (argument === "--max-asset-bytes") maxAssetBytes = parsed;
|
|
169
|
+
else maxTotalAssetBytes = parsed;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const input = positional[0];
|
|
174
|
+
if (input === undefined || positional.length !== 1) {
|
|
175
|
+
return { ok: false, message: "wordcell pdf requires exactly one PDF path or public URL" };
|
|
176
|
+
}
|
|
177
|
+
if (input.length > 64 * 1024) {
|
|
178
|
+
return { ok: false, message: "PDF input exceeds the 65536 code-unit limit" };
|
|
179
|
+
}
|
|
180
|
+
if (outputBase.trim() === "") return { ok: false, message: "--output must not be empty" };
|
|
181
|
+
if (slug !== undefined && slug.trim() === "") return { ok: false, message: "--slug must not be empty" };
|
|
182
|
+
|
|
183
|
+
return {
|
|
184
|
+
ok: true,
|
|
185
|
+
value: {
|
|
186
|
+
command: "capture",
|
|
187
|
+
input,
|
|
188
|
+
outputBase,
|
|
189
|
+
...(slug === undefined ? {} : { slug }),
|
|
190
|
+
...(interpretationsPath === undefined ? {} : { interpretationsPath }),
|
|
191
|
+
force,
|
|
192
|
+
json,
|
|
193
|
+
quiet,
|
|
194
|
+
...(timeoutMs === undefined ? {} : { timeoutMs }),
|
|
195
|
+
...(maxPdfBytes === undefined ? {} : { maxPdfBytes }),
|
|
196
|
+
...(maxPages === undefined ? {} : { maxPages }),
|
|
197
|
+
...(maxImages === undefined ? {} : { maxImages }),
|
|
198
|
+
...(maxAssetBytes === undefined ? {} : { maxAssetBytes }),
|
|
199
|
+
...(maxTotalAssetBytes === undefined ? {} : { maxTotalAssetBytes }),
|
|
200
|
+
},
|
|
201
|
+
};
|
|
202
|
+
}
|