@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.
- package/ENGINE-LICENSE.txt +378 -0
- package/LICENSE +273 -0
- package/NOTICE +90 -0
- package/README.md +107 -2
- package/bin/ufo.js +19 -0
- package/lib/runtime.js +159 -0
- package/package.json +58 -4
- package/release-pins.json +22 -0
- package/scripts/postinstall.js +155 -0
|
@@ -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
|
-
#
|
|
1
|
+
# UFO CLI
|
|
2
2
|
|
|
3
|
-
|
|
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": "
|
|
4
|
-
"
|
|
5
|
-
"description": "
|
|
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 }
|