@krauq/ufo-cli 0.0.0-stage → 1.5.2

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.
@@ -0,0 +1,378 @@
1
+ UFO business use license
2
+ ========================
3
+
4
+ Universal File Opener command line and engine binaries
5
+ Copyright 2026 Krauq LLC. All rights reserved.
6
+
7
+ This license covers the UFO binaries: the Linux app image, the Debian package,
8
+ the restricted OCI container image, and any other executable distribution of
9
+ Universal File Opener that embeds the office-core and viewer-core engines.
10
+
11
+ It does NOT cover the source files that the repository root LICENSE places
12
+ under the Apache License, Version 2.0: cli/, docs/cli/schemas/, tools/cli/,
13
+ tools/corpus/, tools/audit/, packaging/linux-cli/, packaging/npm/ and
14
+ packaging/homebrew/. Those keep their Apache terms whether or not you hold a
15
+ license under this document. The engines themselves, office-core,
16
+ viewer-core, office-compose, office-compose-android, desktop-app, app,
17
+ ios-app and baselineprofile, carry no open source grant. A binary distributed
18
+ by Krauq LLC combines those Apache licensed sources with the proprietary
19
+ engines, and the binary as a whole is licensed under this document, with the
20
+ Apache License text and the NOTICE file included as that license requires.
21
+
22
+ 1. Definitions
23
+ --------------
24
+
25
+ "Software" means the UFO binaries described above, in the exact form supplied,
26
+ identified by the release hashes and the container digest published with them.
27
+
28
+ "You" means the individual or the organization exercising the rights in this
29
+ document. For an organization, You includes its affiliates under common
30
+ control.
31
+
32
+ "Organization" means any company, partnership, sole proprietorship acting in
33
+ trade, government body, school district, charity or other legal entity, and
34
+ any group of people acting for one of them.
35
+
36
+ "People" means the total number of full time employees, part time employees
37
+ and long term contractors of the Organization, counted worldwide, whether or
38
+ not they use the Software. Every person in the Organization counts, not only
39
+ the people whose work the Software supports.
40
+
41
+ "Annual revenue" means the Organization's total gross revenue, worldwide, over
42
+ its most recently completed fiscal year, before expenses and taxes, plus any
43
+ funding the Organization raised in that same period. Raised funding counts.
44
+
45
+ "Commercial use" means use by or for an Organization, or use that directly
46
+ supports paid work, including client deliverables, internal business
47
+ documents and a product or service You sell.
48
+
49
+ 2. Free use
50
+ -----------
51
+
52
+ No fee and no separate agreement are required for any of the following. These
53
+ grants are perpetual for the release You received and do not expire when a
54
+ paid license elsewhere lapses.
55
+
56
+ The free tier is full fidelity: every verb and every format the Software
57
+ supports, at full quality, with no feature withheld and no output
58
+ watermarked. It carries three bounds: a batch plan may run up to twenty five
59
+ (25) steps; a folder report or a folder search may cover up to two hundred
60
+ (200) files; and the Software runs one worker at a time, meaning no parallel
61
+ jobs, and an MCP server that answers one request at a time. No other document,
62
+ page or command meter applies.
63
+
64
+ (a) Individuals. Use by a natural person for personal projects, learning,
65
+ hobby work, personal finances and unpaid volunteer work.
66
+
67
+ (b) Evaluation. Use by anyone, including any Organization of any size, to
68
+ evaluate whether the Software fits a job, for as long as it likes. The
69
+ free tier bounds above are the evaluation: there is no separate time
70
+ limit and nothing to renew. Evaluation includes running the Software on
71
+ Your own files, benchmarking it and building a prototype with it. Work
72
+ past those bounds needs a paid organization license under section 3,
73
+ and the thirty (30) day refund on an Organization's first purchase,
74
+ described there, is the trial at that scale.
75
+
76
+ (c) Education. Use by students, teachers, academic researchers and
77
+ accredited educational institutions for teaching, coursework and
78
+ non commercial research.
79
+
80
+ (d) Non commercial use. Use by a registered charity, a non profit or an open
81
+ source project, where the output is not sold and does not support paid
82
+ work.
83
+
84
+ (e) Small organizations. Commercial use by an Organization with BOTH fewer
85
+ than ten (10) People AND less than one million United States dollars
86
+ (USD 1,000,000) in annual revenue. Both tests apply together. An
87
+ Organization that crosses either threshold may keep using the free tier
88
+ for ninety (90) days after it crosses, and needs a paid organization
89
+ license for commercial use after that.
90
+
91
+ Free use under this section is the same Software paid Organizations run: the
92
+ same operations, the same formats and the same receipts, bounded only by the
93
+ batch, folder and worker limits above.
94
+
95
+ 3. Paid organization license
96
+ ----------------------------
97
+
98
+ An Organization that needs to work past the free tier bounds in section 2, or
99
+ that does not otherwise qualify under section 2, needs a paid organization
100
+ license for commercial use. There are three tiers, each priced by the
101
+ Organization's People count and each self declared by the Organization at
102
+ checkout, no seats:
103
+
104
+ Price Set by the Organization's People count, self declared at
105
+ checkout:
106
+
107
+ Up to fifty (50) People: ninety nine United States
108
+ dollars (USD 99) per organization per month, or nine
109
+ hundred and ninety United States dollars (USD 990) per
110
+ organization per year, paid in advance, which is ten
111
+ monthly payments for twelve months.
112
+
113
+ Fifty one (51) to five hundred (500) People: four hundred
114
+ ninety nine United States dollars (USD 499) per
115
+ organization per month, or four thousand nine hundred and
116
+ ninety United States dollars (USD 4,990) per organization
117
+ per year, paid in advance.
118
+
119
+ Over five hundred (500) People: one thousand nine hundred
120
+ ninety nine United States dollars (USD 1,999) per
121
+ organization per month, or nineteen thousand nine hundred
122
+ and ninety United States dollars (USD 19,990) per
123
+ organization per year, paid in advance.
124
+
125
+ Band The tier above that matches the Organization's own People
126
+ count, self declared by the Organization at checkout and not
127
+ verified at purchase. A band that understates the
128
+ Organization's People count is a breach of this license,
129
+ cured by buying the license for the correct band; it is not
130
+ cured by a refund or cancellation alone.
131
+ Unit One legal entity. Not per seat, not per developer, not per
132
+ application, not per unattended worker, not per document and
133
+ not per page.
134
+ Term A monthly license renews each month and a yearly license
135
+ renews each year, unless it is cancelled before the renewal
136
+ date. Cancellation takes effect at the end of the paid
137
+ period.
138
+ Renewal notice Krauq LLC sends a renewal notice at least thirty (30) days
139
+ before a yearly renewal.
140
+ Licence file Issued automatically once checkout completes. It names the
141
+ Organization, the tier and band, the order reference, the
142
+ dates it was issued and expires and the date the
143
+ Organization's continuous paid subscription began, and
144
+ carries Krauq LLC's signature. Install it with
145
+ `ufo license install FILE`; it is Your entitlement, and the
146
+ receipt of every operation records the tier it read. It
147
+ expires on the paid through date it names, with a fourteen
148
+ (14) day grace period after that date before the Software
149
+ returns to the free tier bounds in section 2, except as
150
+ releases kept below provide.
151
+ Releases kept Once an Organization's paid organization license has run
152
+ for twelve (12) consecutive months, the Organization keeps,
153
+ after that license ends, commercial use at its band's scale
154
+ of every release of the Software published on or before
155
+ the last paid through date, for as long as it likes. This
156
+ is not a renewal: it covers no later release and no defect
157
+ response under section 4, and the Software applies it
158
+ itself, offline, from the licence file. A cancelled license
159
+ and an unpaid renewal both end the subscription; buying
160
+ again starts a new one.
161
+ Refund A full refund on request within thirty (30) days of an
162
+ Organization's first purchase. This window is the trial at
163
+ scale: the free tier in section 2 is the evaluation, and this
164
+ refund is how You try a paid band's full scale without risk.
165
+ Currency Prices are in United States dollars, before tax. The payment
166
+ processor collects any applicable tax and handles currency
167
+ conversion. Payment terms are otherwise those of the payment
168
+ processor's receipt.
169
+
170
+ The first one hundred (100) paying Organizations keep the price they first
171
+ paid for twenty four (24) months from their first purchase, even if the list
172
+ price rises.
173
+
174
+ What a paid organization license buys, in addition to the rights in section
175
+ 2, is commercial use of the Software by the named Organization at its band's
176
+ scale, without the batch, folder and worker bounds in section 2, the defect
177
+ response commitment in section 4, and, after twelve consecutive paid months,
178
+ the releases kept above. It does not buy configuration
179
+ hours, integration work, a resolution time guarantee or a service level
180
+ agreement.
181
+
182
+ 4. Defect response
183
+ ------------------
184
+
185
+ For a paid Organization, Krauq LLC responds within one business day to a
186
+ report of a defect that breaks a receipted edit, meaning an operation that
187
+ reports a completed receipt while the published output does not match what the
188
+ receipt says it applied, or that changes a source file, or that overwrites an
189
+ existing output. A qualifying defect is fixed in the product and the fix ships
190
+ in a public release.
191
+
192
+ "Respond" means a human acknowledgement with a triage result. It is not a
193
+ repair deadline. Business days are Monday to Friday, United States Pacific
194
+ time, excluding United States federal holidays. Send a report to
195
+ support@krauq.com and attach the receipt of the failing operation.
196
+
197
+ This commitment does not cover configuration help, integration assistance,
198
+ performance tuning, unsupported formats, refusals that the documented limits
199
+ describe, or a result You disagree with that the receipt reported honestly. A
200
+ refusal is a correct outcome, not a defect.
201
+
202
+ 5. What this license does not allow
203
+ -----------------------------------
204
+
205
+ The following require a separate written agreement with Krauq LLC and are
206
+ outside both free and paid use:
207
+
208
+ (a) Redistribution. Supplying the Software, in whole or in part, to a third
209
+ party, whether or not for a fee, including bundling it with Your own
210
+ product, mirroring the release artifacts, or publishing the container
211
+ image to a registry You control. Running the Software for Your own
212
+ Organization's work, including work You are paid to do for a client, is
213
+ not redistribution. Handing a client the OUTPUT files is not
214
+ redistribution.
215
+
216
+ (b) SDK embedding. Presenting the Software, or any part of it, as a library,
217
+ SDK, API or component of Your own product, or exposing its operations as
218
+ a paid service to third parties.
219
+
220
+ (c) Resale. Selling, reselling, sublicensing, renting or leasing the
221
+ Software, and offering it as a hosted or managed service to anyone
222
+ outside Your Organization.
223
+
224
+ (d) Removing or altering the notices, license files, receipt fields, version
225
+ identity or third party attributions in the Software.
226
+
227
+ (e) Using the Software to build a competing document processing engine, or
228
+ developing a RAR compatible archiver from the extraction only UnRAR
229
+ component, which its own license forbids.
230
+
231
+ Agency handoff is ordinary use, not redistribution. An agency may install and
232
+ run the Software on a client controlled machine under the agency's own license
233
+ while the agency performs the work. Once the client operates the Software in
234
+ its own workflows after the handoff, the client needs its own license, which
235
+ is free if the client qualifies under section 2. Handing a client the output
236
+ files is never redistribution.
237
+
238
+ 6. Offline licence file, no activation service, no telemetry
239
+ ------------------------------------------------------------
240
+
241
+ A paid organization license is a signed licence file, not a key and not a
242
+ network check. The file names the Organization, the tier and band, the order
243
+ reference, the dates it was issued and expires and the date the continuous
244
+ paid subscription began, and carries Krauq LLC's signature. Install it once with `ufo license install FILE`; the Software then
245
+ reads it from local storage. There is nothing to enter, activate or renew
246
+ online.
247
+
248
+ The Software makes no network request, ever: not to check a license, not to
249
+ activate one, not to fetch an update, and not while processing a document.
250
+ The restricted container runs with networking disabled. It sends no
251
+ telemetry: not about the license, not about Your files, not about Your use.
252
+ Krauq LLC does not receive a copy of anything the Software reads or writes.
253
+
254
+ The licence file is Your entitlement, read locally and enforced locally. The
255
+ receipt of every operation records the tier the Software read when it ran.
256
+ Absent an installed file, or once one expires past its fourteen (14) day
257
+ grace period, the Software runs at the free tier bounds in section 2, not
258
+ disabled, except a release that the kept releases in section 3 cover, which
259
+ keeps the tier the file names. Krauq LLC cannot observe Your use beyond what a support request or a
260
+ license purchase tells it, and does not try to.
261
+
262
+ 7. Reverse engineering
263
+ ----------------------
264
+
265
+ Except to the extent applicable law expressly forbids the restriction, You may
266
+ not reverse engineer, decompile or disassemble the Software.
267
+
268
+ That restriction does not apply where reverse engineering is necessary to
269
+ debug modifications You make to an LGPL covered library that the Software
270
+ links dynamically, including the GNU C Library. Krauq LLC expressly permits
271
+ that reverse engineering, and permits You to replace those shared libraries
272
+ and relink, as the LGPL requires. Corresponding source and notices for the
273
+ LGPL and GPL covered components, including the OpenJDK runtime under its GPLv2
274
+ Classpath and assembly exceptions and the GraalVM runtime compiled into the
275
+ Linux app image's parser worker under the GPLv2 with the Classpath Exception,
276
+ are made available from the release location for the retention period stated
277
+ with the release.
278
+
279
+ 8. Third party components
280
+ -------------------------
281
+
282
+ The Software includes third party software under its own licenses. See NOTICE
283
+ at the repository root, the shipped THIRD-PARTY-NOTICES.txt, and the component
284
+ inventories, license review rows and source locks under packaging/linux-cli/.
285
+ Where a third party license grants You rights broader than this document, that
286
+ license controls for that component.
287
+
288
+ 9. Feedback and shared reproduction files
289
+ -----------------------------------------
290
+
291
+ If You send Krauq LLC a bug report, a reproduction or a suggestion, Krauq LLC
292
+ may use it to improve the product without obligation to You. Do not send files
293
+ You are not authorized to share.
294
+
295
+ A file You send to reproduce a defect is used only to reproduce and fix that
296
+ defect. It is stored only on devices and accounts Krauq LLC controls. It is
297
+ never published and is never used to train anything. Krauq LLC deletes it on
298
+ request, and in any case no later than ninety (90) days after the fix ships.
299
+
300
+ 10. Term and termination
301
+ ------------------------
302
+
303
+ Free use under section 2 continues for as long as You qualify. A paid license
304
+ runs for its paid term, and the releases it keeps under section 3 are kept
305
+ after it ends. Krauq LLC may terminate this license if You breach
306
+ section 5 and do not cure the breach within thirty (30) days of written
307
+ notice, and at once if You breach section 13. On termination You stop using
308
+ the Software. Krauq LLC will never
309
+ remove, disable or alter Your files, and the Software has no mechanism to do
310
+ so.
311
+
312
+ Output You already produced with the Software is Yours. Nothing in this
313
+ document claims any right in Your documents or their contents.
314
+
315
+ 11. Warranty disclaimer
316
+ -----------------------
317
+
318
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
319
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
320
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
321
+
322
+ UFO is local file intake, reading and editing software. It is not antivirus,
323
+ malware analysis, content disarm and reconstruction, a legal or compliance
324
+ determination, a fidelity guarantee, or proof that any content is benign or
325
+ authentic. A receipt is evidence to review. Review every published output in
326
+ the application that owns the format before relying on it.
327
+
328
+ 12. Limitation of liability and governing law
329
+ ---------------------------------------------
330
+
331
+ To the maximum extent permitted by law, Krauq LLC is not liable for indirect,
332
+ incidental, special, consequential or punitive damages, or for loss of data,
333
+ profits, revenue or business opportunity, arising out of or connected with the
334
+ Software or Your use of it, however caused.
335
+
336
+ Krauq LLC's aggregate liability for all claims connected with the Software
337
+ will not exceed the total fees the Organization paid Krauq LLC in the twelve
338
+ (12) months before the claim. For free use under section 2 that cap is one
339
+ hundred United States dollars (USD 100).
340
+
341
+ Krauq LLC gives no indemnity. You are responsible for holding the rights You
342
+ need in the files You process with the Software.
343
+
344
+ These terms, and any dispute arising out of them or out of the Software, are
345
+ governed by the laws of the State of California, where Krauq LLC is based,
346
+ without regard to its conflict of law rules. The state and federal courts
347
+ located in California are the venue for a dispute that cannot be settled by
348
+ email first. Write to support@krauq.com before anything formal.
349
+
350
+ Nothing in this section limits any right You have under the mandatory consumer
351
+ protection laws of the place where You live. A consumer in the EU, the UK or
352
+ another jurisdiction whose law says so keeps the protection of the local
353
+ mandatory rules and may be able to bring proceedings in the local courts.
354
+ Nothing in this document excludes or limits liability for death or personal
355
+ injury caused by negligence, for fraud or fraudulent misrepresentation, or for
356
+ anything else that cannot lawfully be excluded or limited.
357
+
358
+ 13. Export and sanctions
359
+ ------------------------
360
+
361
+ You may use, export and re-export the Software only as the laws of the United
362
+ States and of the place where You obtained or use it allow. In particular, the
363
+ Software may not be exported, re-exported or made available (a) into any
364
+ country or region subject to a comprehensive United States embargo, or (b) to
365
+ anyone on the U.S. Treasury Department's Specially Designated Nationals and
366
+ Blocked Persons List or the U.S. Department of Commerce's Denied Persons List
367
+ or Entity List, and it may not be used for an end use those laws prohibit. By
368
+ downloading or using the Software, You represent that You are not located in
369
+ such a country or region, are not on such a list, and are not owned or
370
+ controlled by, or acting for, anyone who is.
371
+
372
+ 14. Precedence
373
+ --------------
374
+
375
+ If a signed agreement between You and Krauq LLC covers the Software, that
376
+ agreement controls. Otherwise these terms control, from the first public
377
+ release published under them. If a court finds any part of these terms
378
+ unenforceable, the rest still applies.
package/LICENSE ADDED
@@ -0,0 +1,273 @@
1
+ Scope. The Apache License, Version 2.0, covers the source paths listed below
2
+ and nothing else. A binary distributed by Krauq LLC combines those Apache
3
+ licensed sources with the proprietary office-core and viewer-core engines, so
4
+ the binary as a whole is licensed under the business use license in
5
+ packaging/linux-cli/ENGINE-LICENSE.txt, with this Apache License text and the
6
+ NOTICE file included as the Apache License requires.
7
+
8
+ UFO CLI open source license
9
+ ===========================
10
+
11
+ Copyright 2026 Krauq LLC.
12
+
13
+ The Apache License, Version 2.0, reproduced in full below, applies to the
14
+ source files in the following repository paths, and to those paths only:
15
+
16
+ cli/ the CLI command layer: argument parsing, the
17
+ command surface, parser worker supervision,
18
+ inspection and edit orchestration, receipt
19
+ emission, the local stdio MCP server
20
+ (cli/src/main/kotlin/com/krauq/universalfileopener/
21
+ desktop/cli/McpServer.kt) and the agent skill
22
+ generator (.../cli/SkillCommand.kt), together with
23
+ the module's tests and test fixtures
24
+ docs/cli/schemas/ the receipt, report and capability JSON schemas
25
+ tools/cli/ build, packaging, installation and evaluation tools
26
+ tools/corpus/ corpus and comparison evaluation tools
27
+ tools/audit/ the customer held compatibility audit tools
28
+ packaging/linux-cli/ Linux CLI packaging, container recipe, registry
29
+ metadata and license or security evidence tooling
30
+ packaging/npm/ the npm wrapper package
31
+ packaging/homebrew/ the Homebrew formula
32
+
33
+ NOT LICENSED UNDER APACHE 2.0
34
+ -----------------------------
35
+
36
+ The Apache grant above does NOT extend to any other part of this repository or
37
+ to any distributed binary. In particular, and without limiting that statement,
38
+ the following are NOT under the Apache License and carry no open source grant
39
+ of any kind:
40
+
41
+ office-core/ the OOXML, ODF and XPS parsing, model and editor
42
+ engine
43
+ viewer-core/ the PDF byte engine, image codecs, structured text,
44
+ archive and detection engine
45
+ office-compose/ the shared document rendering and editing
46
+ office-compose-android/ controllers
47
+ desktop-app/ the Windows and Linux desktop application
48
+ app/ the Android application
49
+ ios-app/ the parked iOS foundation
50
+ baselineprofile/ the Android baseline profile module
51
+
52
+ Every other file in this repository that is not listed in the Apache section
53
+ above is likewise outside the Apache grant, including the prose under docs/,
54
+ the material under store-assets/, and the tools/ directories not named above.
55
+
56
+ Binary distributions of UFO, including the Linux app image, the Debian package
57
+ and the restricted OCI container image, embed office-core, viewer-core and
58
+ their third party dependencies. Those binaries are NOT distributed under the
59
+ Apache License. They are distributed under the separate business use license
60
+ in packaging/linux-cli/ENGINE-LICENSE.txt. Building the Apache licensed
61
+ sources in this repository against the engines does not place the engines, or
62
+ the resulting binary, under the Apache License.
63
+
64
+ Third party components retain their own licenses and notices. See NOTICE at
65
+ the repository root, desktop-app/src/main/resources/packaging/
66
+ THIRD-PARTY-NOTICES.txt, viewer-core/src/main/resources/NOTICE.md and the
67
+ component inventories under packaging/linux-cli/.
68
+
69
+ Apache License 2.0 text
70
+ =======================
71
+
72
+
73
+ Apache License
74
+ Version 2.0, January 2004
75
+ http://www.apache.org/licenses/
76
+
77
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
78
+
79
+ 1. Definitions.
80
+
81
+ "License" shall mean the terms and conditions for use, reproduction,
82
+ and distribution as defined by Sections 1 through 9 of this document.
83
+
84
+ "Licensor" shall mean the copyright owner or entity authorized by
85
+ the copyright owner that is granting the License.
86
+
87
+ "Legal Entity" shall mean the union of the acting entity and all
88
+ other entities that control, are controlled by, or are under common
89
+ control with that entity. For the purposes of this definition,
90
+ "control" means (i) the power, direct or indirect, to cause the
91
+ direction or management of such entity, whether by contract or
92
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
93
+ outstanding shares, or (iii) beneficial ownership of such entity.
94
+
95
+ "You" (or "Your") shall mean an individual or Legal Entity
96
+ exercising permissions granted by this License.
97
+
98
+ "Source" form shall mean the preferred form for making modifications,
99
+ including but not limited to software source code, documentation
100
+ source, and configuration files.
101
+
102
+ "Object" form shall mean any form resulting from mechanical
103
+ transformation or translation of a Source form, including but
104
+ not limited to compiled object code, generated documentation,
105
+ and conversions to other media types.
106
+
107
+ "Work" shall mean the work of authorship, whether in Source or
108
+ Object form, made available under the License, as indicated by a
109
+ copyright notice that is included in or attached to the work
110
+ (an example is provided in the Appendix below).
111
+
112
+ "Derivative Works" shall mean any work, whether in Source or Object
113
+ form, that is based on (or derived from) the Work and for which the
114
+ editorial revisions, annotations, elaborations, or other modifications
115
+ represent, as a whole, an original work of authorship. For the purposes
116
+ of this License, Derivative Works shall not include works that remain
117
+ separable from, or merely link (or bind by name) to the interfaces of,
118
+ the Work and Derivative Works thereof.
119
+
120
+ "Contribution" shall mean any work of authorship, including
121
+ the original version of the Work and any modifications or additions
122
+ to that Work or Derivative Works thereof, that is intentionally
123
+ submitted to Licensor for inclusion in the Work by the copyright owner
124
+ or by an individual or Legal Entity authorized to submit on behalf of
125
+ the copyright owner. For the purposes of this definition, "submitted"
126
+ means any form of electronic, verbal, or written communication sent
127
+ to the Licensor or its representatives, including but not limited to
128
+ communication on electronic mailing lists, source code control systems,
129
+ and issue tracking systems that are managed by, or on behalf of, the
130
+ Licensor for the purpose of discussing and improving the Work, but
131
+ excluding communication that is conspicuously marked or otherwise
132
+ designated in writing by the copyright owner as "Not a Contribution."
133
+
134
+ "Contributor" shall mean Licensor and any individual or Legal Entity
135
+ on behalf of whom a Contribution has been received by Licensor and
136
+ subsequently incorporated within the Work.
137
+
138
+ 2. Grant of Copyright License. Subject to the terms and conditions of
139
+ this License, each Contributor hereby grants to You a perpetual,
140
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
141
+ copyright license to reproduce, prepare Derivative Works of,
142
+ publicly display, publicly perform, sublicense, and distribute the
143
+ Work and such Derivative Works in Source or Object form.
144
+
145
+ 3. Grant of Patent License. Subject to the terms and conditions of
146
+ this License, each Contributor hereby grants to You a perpetual,
147
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
148
+ (except as stated in this section) patent license to make, have made,
149
+ use, offer to sell, sell, import, and otherwise transfer the Work,
150
+ where such license applies only to those patent claims licensable
151
+ by such Contributor that are necessarily infringed by their
152
+ Contribution(s) alone or by combination of their Contribution(s)
153
+ with the Work to which such Contribution(s) was submitted. If You
154
+ institute patent litigation against any entity (including a
155
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
156
+ or a Contribution incorporated within the Work constitutes direct
157
+ or contributory patent infringement, then any patent licenses
158
+ granted to You under this License for that Work shall terminate
159
+ as of the date such litigation is filed.
160
+
161
+ 4. Redistribution. You may reproduce and distribute copies of the
162
+ Work or Derivative Works thereof in any medium, with or without
163
+ modifications, and in Source or Object form, provided that You
164
+ meet the following conditions:
165
+
166
+ (a) You must give any other recipients of the Work or
167
+ Derivative Works a copy of this License; and
168
+
169
+ (b) You must cause any modified files to carry prominent notices
170
+ stating that You changed the files; and
171
+
172
+ (c) You must retain, in the Source form of any Derivative Works
173
+ that You distribute, all copyright, patent, trademark, and
174
+ attribution notices from the Source form of the Work,
175
+ excluding those notices that do not pertain to any part of
176
+ the Derivative Works; and
177
+
178
+ (d) If the Work includes a "NOTICE" text file as part of its
179
+ distribution, then any Derivative Works that You distribute must
180
+ include a readable copy of the attribution notices contained
181
+ within such NOTICE file, excluding those notices that do not
182
+ pertain to any part of the Derivative Works, in at least one
183
+ of the following places: within a NOTICE text file distributed
184
+ as part of the Derivative Works; within the Source form or
185
+ documentation, if provided along with the Derivative Works; or,
186
+ within a display generated by the Derivative Works, if and
187
+ wherever such third-party notices normally appear. The contents
188
+ of the NOTICE file are for informational purposes only and
189
+ do not modify the License. You may add Your own attribution
190
+ notices within Derivative Works that You distribute, alongside
191
+ or as an addendum to the NOTICE text from the Work, provided
192
+ that such additional attribution notices cannot be construed
193
+ as modifying the License.
194
+
195
+ You may add Your own copyright statement to Your modifications and
196
+ may provide additional or different license terms and conditions
197
+ for use, reproduction, or distribution of Your modifications, or
198
+ for any such Derivative Works as a whole, provided Your use,
199
+ reproduction, and distribution of the Work otherwise complies with
200
+ the conditions stated in this License.
201
+
202
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
203
+ any Contribution intentionally submitted for inclusion in the Work
204
+ by You to the Licensor shall be under the terms and conditions of
205
+ this License, without any additional terms or conditions.
206
+ Notwithstanding the above, nothing herein shall supersede or modify
207
+ the terms of any separate license agreement you may have executed
208
+ with Licensor regarding such Contributions.
209
+
210
+ 6. Trademarks. This License does not grant permission to use the trade
211
+ names, trademarks, service marks, or product names of the Licensor,
212
+ except as required for reasonable and customary use in describing the
213
+ origin of the Work and reproducing the content of the NOTICE file.
214
+
215
+ 7. Disclaimer of Warranty. Unless required by applicable law or
216
+ agreed to in writing, Licensor provides the Work (and each
217
+ Contributor provides its Contributions) on an "AS IS" BASIS,
218
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
219
+ implied, including, without limitation, any warranties or conditions
220
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
221
+ PARTICULAR PURPOSE. You are solely responsible for determining the
222
+ appropriateness of using or redistributing the Work and assume any
223
+ risks associated with Your exercise of permissions under this License.
224
+
225
+ 8. Limitation of Liability. In no event and under no legal theory,
226
+ whether in tort (including negligence), contract, or otherwise,
227
+ unless required by applicable law (such as deliberate and grossly
228
+ negligent acts) or agreed to in writing, shall any Contributor be
229
+ liable to You for damages, including any direct, indirect, special,
230
+ incidental, or consequential damages of any character arising as a
231
+ result of this License or out of the use or inability to use the
232
+ Work (including but not limited to damages for loss of goodwill,
233
+ work stoppage, computer failure or malfunction, or any and all
234
+ other commercial damages or losses), even if such Contributor
235
+ has been advised of the possibility of such damages.
236
+
237
+ 9. Accepting Warranty or Additional Liability. While redistributing
238
+ the Work or Derivative Works thereof, You may choose to offer,
239
+ and charge a fee for, acceptance of support, warranty, indemnity,
240
+ or other liability obligations and/or rights consistent with this
241
+ License. However, in accepting such obligations, You may act only
242
+ on Your own behalf and on Your sole responsibility, not on behalf
243
+ of any other Contributor, and only if You agree to indemnify,
244
+ defend, and hold each Contributor harmless for any liability
245
+ incurred by, or claims asserted against, such Contributor by reason
246
+ of your accepting any such warranty or additional liability.
247
+
248
+ END OF TERMS AND CONDITIONS
249
+
250
+ APPENDIX: How to apply the Apache License to your work.
251
+
252
+ To apply the Apache License to your work, attach the following
253
+ boilerplate notice, with the fields enclosed by brackets "[]"
254
+ replaced with your own identifying information. (Don't include
255
+ the brackets!) The text should be enclosed in the appropriate
256
+ comment syntax for the file format. We also recommend that a
257
+ file or class name and description of purpose be included on the
258
+ same "printed page" as the copyright notice for easier
259
+ identification within third-party archives.
260
+
261
+ Copyright [yyyy] [name of copyright owner]
262
+
263
+ Licensed under the Apache License, Version 2.0 (the "License");
264
+ you may not use this file except in compliance with the License.
265
+ You may obtain a copy of the License at
266
+
267
+ http://www.apache.org/licenses/LICENSE-2.0
268
+
269
+ Unless required by applicable law or agreed to in writing, software
270
+ distributed under the License is distributed on an "AS IS" BASIS,
271
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
272
+ See the License for the specific language governing permissions and
273
+ limitations under the License.
package/NOTICE ADDED
@@ -0,0 +1,90 @@
1
+ Universal File Opener (UFO)
2
+ Copyright 2026 Krauq LLC.
3
+
4
+ This product includes software developed by Krauq LLC.
5
+
6
+ What this NOTICE covers
7
+ -----------------------
8
+
9
+ This NOTICE is the Apache License 2.0 section 4(d) notice for the parts of
10
+ this repository that LICENSE places under the Apache License, Version 2.0:
11
+
12
+ cli/ the CLI command layer, the local stdio MCP server and
13
+ the agent skill generator
14
+ docs/cli/schemas/ the receipt, report and capability JSON schemas
15
+ tools/cli/ build, packaging, installation and evaluation tools
16
+ tools/corpus/ corpus and comparison evaluation tools
17
+ tools/audit/ the customer held compatibility audit tools
18
+ packaging/linux-cli/ Linux CLI packaging, container recipe, registry
19
+ metadata and evidence tooling
20
+ packaging/npm/ the npm wrapper package
21
+ packaging/homebrew/ the Homebrew formula
22
+
23
+ What this NOTICE does not cover
24
+ -------------------------------
25
+
26
+ office-core, viewer-core, office-compose, office-compose-android,
27
+ desktop-app, app, ios-app and baselineprofile are NOT under the Apache
28
+ License. No open source grant of any kind applies to them. Neither does the
29
+ Apache License apply to any distributed UFO binary: the Linux app image, the
30
+ Debian package and the restricted OCI container image embed office-core and
31
+ viewer-core and are distributed under the separate business use license in
32
+ packaging/linux-cli/ENGINE-LICENSE.txt.
33
+
34
+ A redistribution of the Apache licensed sources must keep this NOTICE and must
35
+ not describe the engines, the applications or any UFO binary as Apache
36
+ licensed.
37
+
38
+ Third party components
39
+ ----------------------
40
+
41
+ UFO binaries include third party software under its own licenses. The
42
+ human readable notice shipped beside the application is
43
+ desktop-app/src/main/resources/packaging/THIRD-PARTY-NOTICES.txt. Component
44
+ inventories, license review rows, source locks and the special restriction
45
+ decisions are under packaging/linux-cli/, including
46
+ app-special-license-review.tsv, debian-license-review.tsv,
47
+ jar-legal-evidence-review.tsv, debian-source.lock.tsv,
48
+ temurin-source.lock.tsv and vendored-asset-source.lock.tsv.
49
+
50
+ Vendored asset notices live with the assets: viewer-core/src/main/resources/
51
+ NOTICE.md records the modified Roboto derivatives and their exact byte
52
+ identities. The Android notice resource is app/src/main/res/raw/
53
+ third_party_notices.txt.
54
+
55
+ Named obligations that survive redistribution, from the review rows above:
56
+
57
+ UnRAR (com.github.junrar) extraction only; the UnRAR restriction notice
58
+ must ship, and no RAR compatible archiver may
59
+ be developed from it
60
+ SQLite JDBC (org.xerial) the exact Apache and Zentus notices are
61
+ preserved byte for byte
62
+ OpenJDK runtime GPLv2 with the Classpath and assembly
63
+ exceptions; the packaged runtime keeps its
64
+ own legal directory and corresponding source
65
+ availability
66
+ glibc dynamically linked LGPL boundary with
67
+ relinkable shared libraries and corresponding
68
+ source availability
69
+ Roboto and the Adobe Glyph modified derivatives, documented with their
70
+ List provenance and exact hashes
71
+
72
+ Trademarks
73
+ ----------
74
+
75
+ UFO, Universal File Opener and Krauq are marks of Krauq LLC. The Apache
76
+ License does not grant trademark rights. Microsoft Office, Word, Excel,
77
+ PowerPoint, Adobe, PDF and every other third party mark named in this
78
+ repository belong to their respective owners and are used only to identify
79
+ file formats and applications.
80
+
81
+ Scope
82
+ -----
83
+
84
+ The Apache License covers the source paths listed above and nothing else. A
85
+ binary distributed by Krauq LLC combines those Apache licensed sources with
86
+ the proprietary engines, so the binary as a whole is licensed under the
87
+ business use license in packaging/linux-cli/ENGINE-LICENSE.txt, and it ships
88
+ the Apache License text and this NOTICE as the Apache License requires. The
89
+ obligations named above stand as written for a source publication of the
90
+ listed paths, which ships no third party binary.
package/README.md CHANGED
@@ -1,3 +1,108 @@
1
- # Temporary Holding Version
1
+ # UFO CLI
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Useful information for any accessible regular file. Deeper readers screen
4
+ supported formats for hidden content and privacy risks, extract content, and
5
+ make targeted changes in new copies. UFO runs locally as a CLI or stdio MCP
6
+ server. Reports state what was inspected and what remains unknown.
7
+
8
+ This package installs the exact UFO 1.5.2 runtime. Direct
9
+ [Linux downloads](https://github.com/krauqllc/ufo-cli/releases/tag/v1.5.2)
10
+ are also available. Install and check your first file:
11
+
12
+ ```sh
13
+ npm install --global @krauq/ufo-cli@1.5.2 --allow-scripts=@krauq/ufo-cli
14
+ ufo --version
15
+ ufo doctor
16
+ ufo inspect --json ./your-file
17
+ ```
18
+
19
+ On npm 12, the explicit script approval lets UFO's installer run. With npm 11
20
+ or earlier, use `npm install --global @krauq/ufo-cli@1.5.2` with install scripts
21
+ enabled; omit `--allow-scripts`.
22
+
23
+ Linux x86_64, glibc 2.35+, Node.js 18.17+ and GNU tar are required. Ubuntu
24
+ 24.04 and Debian 12 are tested. Native macOS, Windows, ARM64 and Alpine/musl
25
+ are unsupported. Java is included. `ufo doctor` also checks optional rendering
26
+ dependencies on your host.
27
+
28
+ Your npm version may require a newer Node.js release than the wrapper does.
29
+ For example, npm 12.2.0 requires Node.js 22.22.2 or later in 22.x, 24.15.0 or
30
+ later in 24.x, or 26+. Use a Node.js version supported by your npm release.
31
+
32
+ Installation downloads the pinned 202 MB release archive over HTTPS and checks
33
+ its hash and complete runtime identity. npm install scripts must be enabled.
34
+ Document processing sends no network requests or telemetry. A connected agent
35
+ can send tool results to its model provider.
36
+
37
+ For a supported document, try `ufo inspect --deep ./report.docx`. Basic file
38
+ information does not promise a reader, editor or security inspector for every
39
+ format. Review findings and coverage gaps; `ufo capabilities` and command help
40
+ describe the operations available in this release.
41
+
42
+ ## Connect an agent
43
+
44
+ Install before registering MCP. An uncached `npx` startup can exceed a client's
45
+ handshake timeout while it downloads the runtime. An installed server starts
46
+ without a download. Print a client template:
47
+
48
+ ```sh
49
+ ufo mcp print-config --client claude-code
50
+ ```
51
+
52
+ Use the installed `ufo` executable and replace allowed-root placeholders with
53
+ existing absolute task directories. Printing does not register a server. UFO
54
+ grants no directories by default; use separate input and output folders when
55
+ possible. The same local server can be started directly:
56
+
57
+ ```sh
58
+ ufo mcp --allow-root /absolute/input --allow-root /absolute/output
59
+ ```
60
+
61
+ For a project guide instead, run
62
+ `ufo skill install --agent claude-code --dir "$PWD"` and start a new session. See the
63
+ [agent setup guide](https://universalfileopener.com/cli/#agents) for other clients.
64
+
65
+ ## License and maintenance
66
+
67
+ Every command is included. Individuals and eligible small organizations can
68
+ use UFO free; larger commercial organizations need a
69
+ [business license](https://universalfileopener.com/cli/pricing/).
70
+ Wrapper source is Apache 2.0; the full binary terms are in
71
+ `ENGINE-LICENSE.txt`. Install a delivered license
72
+ with `ufo license install ./license.json`, then check `ufo license status`.
73
+ License checks are offline.
74
+
75
+ The wrapper is pinned to its exact runtime version and never upgrades it
76
+ silently. Update by selecting a reviewed exact published version. On npm 12:
77
+
78
+ ```sh
79
+ npm install --global @krauq/ufo-cli@VERSION --allow-scripts=@krauq/ufo-cli
80
+ ```
81
+
82
+ With npm 11 or earlier, omit `--allow-scripts` and keep install scripts enabled.
83
+ If installation was blocked by script policy, approve and rerun the installed
84
+ package's setup. This also verifies and reuses an intact runtime:
85
+
86
+ ```sh
87
+ npm rebuild --global @krauq/ufo-cli --allow-scripts=@krauq/ufo-cli
88
+ ```
89
+
90
+ The same npm-version rule applies to rebuilding. To check all files in a
91
+ downloaded runtime, run:
92
+
93
+ ```sh
94
+ npm run verify --prefix "$(npm root --global)/@krauq/ufo-cli"
95
+ ```
96
+
97
+ Normal launches of that runtime check the launcher hash.
98
+
99
+ Use `npm uninstall --global @krauq/ufo-cli` to remove the wrapper and downloaded
100
+ runtime. It leaves your documents and per-user license in place. The optional
101
+ `ufo license remove` command removes that license separately.
102
+
103
+ To reuse an existing matching 1.5.2 installation, set
104
+ `UFO_CLI_SKIP_DOWNLOAD=1` and `UFO_CLI_BINARY` to its absolute `bin/ufo` path
105
+ during installation. The resolved path is saved and reused on later launches
106
+ and rebuilds. The wrapper rejects another version and its own launcher.
107
+ External installations are checked for the matching version, rather than the
108
+ downloaded runtime's complete file identity.
package/bin/ufo.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const path = require('node:path')
5
+ const { spawnSync } = require('node:child_process')
6
+ const { readRelease, launcher, wrapperEnvironment } = require('../lib/runtime')
7
+
8
+ try {
9
+ const root = path.resolve(__dirname, '..')
10
+ const release = readRelease(root)
11
+ const env = wrapperEnvironment(__filename)
12
+ const result = spawnSync(launcher(root, release), process.argv.slice(2), { stdio: 'inherit', env })
13
+ if (result.error) throw result.error
14
+ if (result.signal) process.kill(process.pid, result.signal)
15
+ else process.exitCode = result.status === null ? 1 : result.status
16
+ } catch (error) {
17
+ process.stderr.write(`@krauq/ufo-cli: ${error.message}\n`)
18
+ process.exitCode = 2
19
+ }
package/lib/runtime.js ADDED
@@ -0,0 +1,159 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+ const { spawnSync } = require('node:child_process')
7
+
8
+ function platformKey (platform = process.platform, arch = process.arch, glibc = process.report.getReport().header.glibcVersionRuntime) {
9
+ if (platform !== 'linux' || arch !== 'x64') {
10
+ throw new Error(`no release for ${platform}/${arch}. UFO CLI currently supports Linux x86_64. Use a supported Linux host. Native macOS and Windows CLI downloads are not yet available.`)
11
+ }
12
+ const version = String(glibc || '').match(/^(\d+)\.(\d+)/)
13
+ if (!version || Number(version[1]) < 2 || (Number(version[1]) === 2 && Number(version[2]) < 35)) {
14
+ throw new Error('UFO CLI requires glibc 2.35 or later. Alpine/musl is unsupported. Ubuntu 24.04 and Debian 12 are tested installation targets.')
15
+ }
16
+ return 'linux-x64'
17
+ }
18
+
19
+ function readRelease (root, platform = process.platform, arch = process.arch) {
20
+ const key = platformKey(platform, arch)
21
+ const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8'))
22
+ const pins = JSON.parse(fs.readFileSync(path.join(root, 'release-pins.json'), 'utf8'))
23
+ const asset = pins.assets && pins.assets[key]
24
+ if (!/^\d+\.\d+\.\d+$/.test(pins.version) || pins.version !== pkg.version || !asset) {
25
+ throw new Error('package version and release pins must name the same exact supported release.')
26
+ }
27
+ for (const field of ['sha256', 'treeSha256', 'launcherSha256']) {
28
+ if (!/^[a-f0-9]{64}$/.test(asset[field]) || /^0+$/.test(asset[field])) {
29
+ throw new Error(`release pin ${field} is missing or is a placeholder. Nothing was installed.`)
30
+ }
31
+ }
32
+ const url = new URL(asset.url)
33
+ const image = `universal-file-opener-${pins.version}-linux-x64`
34
+ if (url.protocol !== 'https:' || url.username || url.password ||
35
+ asset.launcher !== `${image}/bin/ufo` ||
36
+ !Number.isSafeInteger(asset.sizeBytes) || asset.sizeBytes <= 0 ||
37
+ !Number.isSafeInteger(asset.fileCount) || asset.fileCount <= 0) {
38
+ throw new Error('release pins contain an invalid URL, size, file count or launcher path.')
39
+ }
40
+ return { version: pins.version, image, asset }
41
+ }
42
+
43
+ function sha256 (file) {
44
+ const hash = crypto.createHash('sha256')
45
+ const buffer = Buffer.allocUnsafe(1024 * 1024)
46
+ const fd = fs.openSync(file, 'r')
47
+ try {
48
+ let count
49
+ while ((count = fs.readSync(fd, buffer, 0, buffer.length, null)) > 0) hash.update(buffer.subarray(0, count))
50
+ } finally {
51
+ fs.closeSync(fd)
52
+ }
53
+ return hash.digest('hex')
54
+ }
55
+
56
+ // Complete content identity, including executable bits. Update this pin from
57
+ // the approved archive, never from a mutable installation receipt.
58
+ function treeIdentity (image) {
59
+ const files = []
60
+ function visit (directory) {
61
+ for (const name of fs.readdirSync(directory).sort()) {
62
+ const file = path.join(directory, name)
63
+ const stat = fs.lstatSync(file)
64
+ if (stat.isDirectory()) visit(file)
65
+ else if (stat.isFile()) {
66
+ files.push({ path: path.relative(image, file).split(path.sep).join('/'), sizeBytes: stat.size, sha256: sha256(file), executable: Boolean(stat.mode & 0o111) })
67
+ } else throw new Error('runtime contains a symbolic link or a non-regular file.')
68
+ }
69
+ }
70
+ visit(image)
71
+ files.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0)
72
+ return { sha256: crypto.createHash('sha256').update(JSON.stringify(files)).digest('hex'), fileCount: files.length }
73
+ }
74
+
75
+ function executable (file) {
76
+ const resolved = fs.realpathSync(file)
77
+ if (!fs.statSync(resolved).isFile()) throw new Error('launcher is not a regular file.')
78
+ fs.accessSync(resolved, fs.constants.X_OK)
79
+ return resolved
80
+ }
81
+
82
+ function verifyImage (image, release) {
83
+ if (!fs.lstatSync(image).isDirectory()) throw new Error('runtime image is not a real directory.')
84
+ const identity = treeIdentity(image)
85
+ if (identity.fileCount !== release.asset.fileCount || identity.sha256 !== release.asset.treeSha256) {
86
+ throw new Error(`installed runtime does not match the complete ${release.version} release. Remove this package deliberately and reinstall; no existing runtime was changed.`)
87
+ }
88
+ return executable(path.join(image, 'bin/ufo'))
89
+ }
90
+
91
+ function verifyVendor (root, release) {
92
+ const vendor = path.join(root, 'vendor')
93
+ if (!fs.lstatSync(vendor).isDirectory() || JSON.stringify(fs.readdirSync(vendor)) !== JSON.stringify([release.image])) {
94
+ throw new Error('vendor must contain only the pinned runtime image. No existing runtime was changed.')
95
+ }
96
+ return verifyImage(path.join(vendor, release.image), release)
97
+ }
98
+
99
+ function wrapperEnvironment (shim, env = process.env) {
100
+ const self = fs.realpathSync(shim)
101
+ let active
102
+ try { active = JSON.parse(env.UFO_CLI_ACTIVE_WRAPPERS || '[]') } catch { active = [] }
103
+ if (!Array.isArray(active) || active.some(item => typeof item !== 'string')) active = []
104
+ if (active.includes(self)) throw new Error('recursive ufo wrapper on PATH. Set UFO_CLI_BINARY to the actual absolute UFO launcher.')
105
+ return { ...env, UFO_CLI_ACTIVE_WRAPPERS: JSON.stringify([...active, self]) }
106
+ }
107
+
108
+ function externalLauncher (root, release, env = process.env) {
109
+ const shim = path.join(root, 'bin/ufo.js')
110
+ const self = fs.realpathSync(shim)
111
+ const childEnv = wrapperEnvironment(shim, env)
112
+ let selected = env.UFO_CLI_BINARY
113
+ const marker = path.join(root, '.use-external-ufo.json')
114
+ if (!selected && fs.existsSync(marker)) {
115
+ let saved
116
+ try { saved = JSON.parse(fs.readFileSync(marker, 'utf8')) } catch {
117
+ throw new Error('saved external launcher configuration is invalid. Reinstall deliberately with UFO_CLI_BINARY set to the matching absolute launcher.')
118
+ }
119
+ if (!saved || saved.version !== release.version ||
120
+ (saved.binary !== undefined && (typeof saved.binary !== 'string' || !path.isAbsolute(saved.binary)))) {
121
+ throw new Error('saved external launcher configuration does not name this release and an absolute launcher. Reinstall deliberately with UFO_CLI_BINARY set.')
122
+ }
123
+ // Older local preparation saved only the version and used PATH. New
124
+ // installations retain the resolved launcher and verify it on every use.
125
+ selected = saved.binary
126
+ }
127
+ let candidates
128
+ if (selected) {
129
+ if (!path.isAbsolute(selected)) throw new Error('UFO_CLI_BINARY must be an absolute launcher path.')
130
+ candidates = [selected]
131
+ } else {
132
+ candidates = (env.PATH || '').split(path.delimiter).map(directory => path.resolve(directory || '.', 'ufo'))
133
+ }
134
+ for (const candidate of candidates) {
135
+ let resolved
136
+ try { resolved = executable(candidate) } catch { continue }
137
+ if (resolved === self) continue
138
+ const result = spawnSync(resolved, ['--version'], { encoding: 'utf8', env: childEnv, timeout: 10000, maxBuffer: 1024 * 1024 })
139
+ if (!result.error && result.status === 0 && result.stdout.trim() === `ufo ${release.version}`) return resolved
140
+ // Respect PATH precedence. Do not silently select a different installed
141
+ // release further down PATH after encountering a real incompatible ufo.
142
+ throw new Error(`external ufo must report "ufo ${release.version}". Set UFO_CLI_BINARY to that release's absolute launcher.`)
143
+ }
144
+ throw new Error(`no external UFO ${release.version} launcher. The npm shim cannot use itself. Install the matching Debian/app-image release or allow the pinned download.`)
145
+ }
146
+
147
+ function launcher (root, release, env = process.env) {
148
+ if (env.UFO_CLI_BINARY) return externalLauncher(root, release, env)
149
+ const installed = path.join(root, 'vendor', release.asset.launcher)
150
+ if (fs.existsSync(installed)) {
151
+ const resolved = executable(installed)
152
+ if (sha256(resolved) !== release.asset.launcherSha256) throw new Error('installed launcher does not match the release pin. Run npm run verify in the package directory.')
153
+ return resolved
154
+ }
155
+ if (fs.existsSync(path.join(root, '.use-external-ufo.json')) || env.UFO_CLI_SKIP_DOWNLOAD === '1') return externalLauncher(root, release, env)
156
+ throw new Error('no installed runtime. Enable npm install scripts and reinstall, or explicitly set UFO_CLI_SKIP_DOWNLOAD=1 with the matching UFO release already installed.')
157
+ }
158
+
159
+ module.exports = { platformKey, readRelease, sha256, treeIdentity, executable, verifyImage, verifyVendor, wrapperEnvironment, externalLauncher, launcher }
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@krauq/ufo-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.5.2",
4
+ "private": false,
5
+ "description": "Pinned local file inspection, extraction and guarded edits for CLI and MCP. Linux x86_64.",
6
+ "mcpName": "io.github.krauqllc/ufo-cli",
7
+ "publishConfig": {
8
+ "access": "public",
9
+ "registry": "https://registry.npmjs.org/"
10
+ },
11
+ "keywords": [
12
+ "docx",
13
+ "xlsx",
14
+ "pptx",
15
+ "pdf",
16
+ "mcp",
17
+ "agent",
18
+ "document-editing",
19
+ "cli"
20
+ ],
21
+ "license": "SEE LICENSE IN ENGINE-LICENSE.txt",
22
+ "homepage": "https://universalfileopener.com/cli/",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/krauqllc/ufo-cli.git"
26
+ },
27
+ "bugs": {
28
+ "url": "mailto:support@krauq.com"
29
+ },
30
+ "engines": {
31
+ "node": ">=18.17.0"
32
+ },
33
+ "os": [
34
+ "linux"
35
+ ],
36
+ "cpu": [
37
+ "x64"
38
+ ],
39
+ "libc": [
40
+ "glibc"
41
+ ],
42
+ "bin": {
43
+ "ufo": "bin/ufo.js"
44
+ },
45
+ "files": [
46
+ "bin/ufo.js",
47
+ "lib/runtime.js",
48
+ "scripts/postinstall.js",
49
+ "release-pins.json",
50
+ "README.md",
51
+ "ENGINE-LICENSE.txt",
52
+ "NOTICE",
53
+ "LICENSE"
54
+ ],
55
+ "scripts": {
56
+ "postinstall": "node scripts/postinstall.js",
57
+ "verify": "node scripts/postinstall.js --verify-only"
58
+ },
59
+ "dependencies": {}
60
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "version": "1.5.2",
3
+ "provenance": {
4
+ "releaseUrl": "https://github.com/krauqllc/ufo-cli/releases/tag/v1.5.2",
5
+ "releaseId": 403255027,
6
+ "sourceCommit": "af3021084836d6b42c8a92efab758e645f494186",
7
+ "checksumSignatureNamespace": "ufo-cli-release-checksums-v1",
8
+ "signerFingerprint": "SHA256:PaoFqFSErvcvmeZ+GAzWUSX/f2A3afw9RIvrs4wxt60",
9
+ "verifiedAt": "2026-10-04"
10
+ },
11
+ "assets": {
12
+ "linux-x64": {
13
+ "url": "https://github.com/krauqllc/ufo-cli/releases/download/v1.5.2/universal-file-opener-1.5.2-linux-x64.tar.gz",
14
+ "sha256": "60f56e71fd00b21a48b160eaee85c082811f8e256d16474d916d3af05ec8fd1b",
15
+ "sizeBytes": 205252019,
16
+ "launcher": "universal-file-opener-1.5.2-linux-x64/bin/ufo",
17
+ "launcherSha256": "0c6913ad8ca795cf68fb2843509d6db565b80a30119ef40083710dc1ea28d14b",
18
+ "treeSha256": "8fad7137aeba60ce072221179be63a686b2342326d84fdef60dfba854aa8008c",
19
+ "fileCount": 467
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,155 @@
1
+ #!/usr/bin/env node
2
+ 'use strict'
3
+
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+ const https = require('node:https')
7
+ const { spawnSync } = require('node:child_process')
8
+ const { readRelease, sha256, verifyImage, verifyVendor, externalLauncher } = require('../lib/runtime')
9
+
10
+ const DOWNLOAD_TIMEOUT_MS = 120000
11
+ const EXTRACTION_TIMEOUT_MS = 120000
12
+
13
+ function download (url, destination, sizeBytes, redirects = 0, deadline = Date.now() + DOWNLOAD_TIMEOUT_MS) {
14
+ if (new URL(url).protocol !== 'https:' || redirects > 5) throw new Error('download requires HTTPS and at most five redirects.')
15
+ const remaining = deadline - Date.now()
16
+ if (remaining <= 0) throw new Error('release download exceeded its total deadline. Nothing was installed.')
17
+ return new Promise((resolve, reject) => {
18
+ let request
19
+ let response
20
+ let out
21
+ let timer
22
+ let settled = false
23
+ function finish (error, result) {
24
+ if (settled) return
25
+ settled = true
26
+ clearTimeout(timer)
27
+ if (error) {
28
+ if (request) request.destroy()
29
+ if (response) response.destroy()
30
+ if (out) out.destroy()
31
+ reject(error)
32
+ } else resolve(result)
33
+ }
34
+ request = https.get(url, { headers: { 'user-agent': 'ufo-cli-npm-wrapper' } }, incoming => {
35
+ response = incoming
36
+ if ([301, 302, 303, 307, 308].includes(response.statusCode)) {
37
+ response.resume()
38
+ if (!response.headers.location) return finish(new Error('download redirect has no location.'))
39
+ try {
40
+ download(new URL(response.headers.location, url).toString(), destination, sizeBytes, redirects + 1, deadline)
41
+ .then(result => finish(null, result), finish)
42
+ } catch (error) { finish(error) }
43
+ return
44
+ }
45
+ if (response.statusCode !== 200) {
46
+ response.resume()
47
+ finish(new Error(`download returned HTTP ${response.statusCode}. Nothing was installed.`))
48
+ return
49
+ }
50
+ out = fs.createWriteStream(destination, { flags: 'wx', mode: 0o600 })
51
+ let received = 0
52
+ response.on('data', data => {
53
+ received += data.length
54
+ if (received > sizeBytes) response.destroy(new Error('download exceeds the release pin size.'))
55
+ })
56
+ response.on('error', finish)
57
+ out.on('error', finish)
58
+ response.pipe(out)
59
+ out.on('finish', () => out.close(error => finish(error, destination)))
60
+ })
61
+ request.on('error', finish)
62
+ request.setTimeout(Math.min(remaining, DOWNLOAD_TIMEOUT_MS), () => finish(new Error('release download timed out. Nothing was installed.')))
63
+ timer = setTimeout(() => finish(new Error('release download exceeded its total deadline. Nothing was installed.')), remaining)
64
+ })
65
+ }
66
+
67
+ function tar (args, timeoutMs = EXTRACTION_TIMEOUT_MS) {
68
+ const result = spawnSync('tar', args, { encoding: 'utf8', maxBuffer: 4 * 1024 * 1024, timeout: timeoutMs, killSignal: 'SIGKILL' })
69
+ if (result.error && result.error.code === 'ETIMEDOUT') throw new Error('archive listing or extraction exceeded its two-minute deadline. Nothing was installed.')
70
+ if (result.error || result.status !== 0) throw new Error('GNU tar could not read the release asset. Install GNU tar or use the Debian package.')
71
+ return result.stdout
72
+ }
73
+
74
+ function extract (archive, into, release) {
75
+ const members = tar(['--list', '--gzip', '--file', archive, '--quoting-style=literal']).trimEnd().split('\n')
76
+ if (!members.length || members.some(name =>
77
+ !/^[A-Za-z0-9 ._+/-]+$/.test(name) ||
78
+ !name.startsWith(`${release.image}/`) ||
79
+ name.split('/').some(part => part === '..' || part === '.'))) {
80
+ throw new Error('release archive contains an unexpected path. Nothing was installed.')
81
+ }
82
+ const listing = tar(['--list', '--verbose', '--gzip', '--file', archive, '--quoting-style=literal'])
83
+ if (listing.trimEnd().split('\n').some(line => !['-', 'd'].includes(line[0]))) {
84
+ throw new Error('release archive contains links or special files. Nothing was installed.')
85
+ }
86
+ tar(['--extract', '--gzip', '--file', archive, '--directory', into, '--no-same-owner', '--no-same-permissions'])
87
+ }
88
+
89
+ async function install (root, env = process.env, verifyOnly = false) {
90
+ const release = readRelease(root)
91
+ const vendor = path.join(root, 'vendor')
92
+ const marker = path.join(root, '.use-external-ufo.json')
93
+ if (env.UFO_CLI_SKIP_DOWNLOAD === '1' || (!fs.existsSync(vendor) && fs.existsSync(marker))) {
94
+ const binary = externalLauncher(root, release, env)
95
+ if (!verifyOnly) fs.writeFileSync(marker, JSON.stringify({ version: release.version, binary }) + '\n', { mode: 0o600 })
96
+ process.stderr.write(`@krauq/ufo-cli: using external UFO ${release.version} at ${binary}.\n`)
97
+ return binary
98
+ }
99
+ if (fs.existsSync(vendor)) {
100
+ const binary = verifyVendor(root, release)
101
+ process.stderr.write(`@krauq/ufo-cli: verified existing UFO ${release.version}; no download needed.\n`)
102
+ return binary
103
+ }
104
+ if (verifyOnly) {
105
+ throw new Error('no installed runtime to verify. Enable npm install scripts and reinstall.')
106
+ }
107
+
108
+ // Lock and stage beside vendor. Only a complete verified image is renamed
109
+ // into place; a failed download/extraction leaves no installed runtime.
110
+ const lock = path.join(root, '.ufo-install.lock')
111
+ let lockFd
112
+ let staging
113
+ let createdVendor = false
114
+ try {
115
+ try { lockFd = fs.openSync(lock, 'wx', 0o600) } catch (error) {
116
+ if (error.code === 'EEXIST') throw new Error('another install is active, or an interrupted install left .ufo-install.lock. Remove that lock only after confirming no install is running.')
117
+ throw error
118
+ }
119
+ if (fs.existsSync(vendor)) return verifyVendor(root, release)
120
+ staging = fs.mkdtempSync(path.join(root, '.ufo-stage-'))
121
+ const archive = path.join(staging, 'release.tar.gz')
122
+ if (env.UFO_CLI_LOCAL_ASSET) {
123
+ const source = fs.statSync(env.UFO_CLI_LOCAL_ASSET)
124
+ if (!source.isFile() || source.size !== release.asset.sizeBytes) throw new Error('local asset size does not match the release pin. Nothing was installed.')
125
+ fs.copyFileSync(env.UFO_CLI_LOCAL_ASSET, archive, fs.constants.COPYFILE_EXCL)
126
+ } else await download(release.asset.url, archive, release.asset.sizeBytes)
127
+ if (fs.statSync(archive).size !== release.asset.sizeBytes || sha256(archive) !== release.asset.sha256) throw new Error('release size or SHA-256 mismatch. Nothing was installed.')
128
+ extract(archive, staging, release)
129
+ const image = path.join(staging, release.image)
130
+ verifyImage(image, release)
131
+ fs.mkdirSync(vendor)
132
+ createdVendor = true
133
+ fs.renameSync(image, path.join(vendor, release.image))
134
+ createdVendor = false
135
+ fs.rmSync(path.join(root, '.use-external-ufo.json'), { force: true })
136
+ process.stderr.write(`@krauq/ufo-cli: installed verified UFO ${release.version}. Installation needs no system Java; document processing is local.\n`)
137
+ return path.join(vendor, release.asset.launcher)
138
+ } finally {
139
+ if (createdVendor && fs.readdirSync(vendor).length === 0) fs.rmdirSync(vendor)
140
+ if (staging) fs.rmSync(staging, { recursive: true, force: true })
141
+ if (lockFd !== undefined) {
142
+ fs.closeSync(lockFd)
143
+ fs.rmSync(lock, { force: true })
144
+ }
145
+ }
146
+ }
147
+
148
+ if (require.main === module) {
149
+ install(path.resolve(__dirname, '..'), process.env, process.argv.includes('--verify-only')).catch(error => {
150
+ process.stderr.write(`@krauq/ufo-cli: ${error.message}\n`)
151
+ process.exitCode = 1
152
+ })
153
+ }
154
+
155
+ module.exports = { install, download, extract, tar }