viveworker 0.6.1 → 0.6.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/README.md +9 -0
- package/package.json +1 -1
- package/scripts/share-cli.mjs +115 -6
- package/scripts/viveworker.mjs +11 -0
package/README.md
CHANGED
|
@@ -168,12 +168,21 @@ What it supports:
|
|
|
168
168
|
- optional password protection
|
|
169
169
|
- optional expiry
|
|
170
170
|
|
|
171
|
+
HTML uploads are optimized by default when possible.
|
|
172
|
+
This is especially useful for bundled standalone HTML exports that carry large embedded font payloads.
|
|
173
|
+
`viveworker share upload` will try to shrink those files locally before upload, which often makes otherwise-too-large deck or prototype exports shareable without extra prep.
|
|
174
|
+
|
|
175
|
+
If you want the original HTML bytes untouched, use `--no-optimize`.
|
|
176
|
+
When optimization strips embedded fonts, layout and typography may change slightly, but the goal is to preserve a usable standalone share URL.
|
|
177
|
+
|
|
171
178
|
It reuses the same A2A credentials as the rest of `viveworker`, so there is no separate auth or setup step.
|
|
172
179
|
|
|
173
180
|
Typical commands:
|
|
174
181
|
|
|
175
182
|
- `npx viveworker share upload report.html`
|
|
183
|
+
- `npx viveworker share upload deck_standalone.html`
|
|
176
184
|
- `npx viveworker share upload report.pdf --password "hunter2" --expires-days 7`
|
|
185
|
+
- `npx viveworker share upload deck_standalone.html --no-optimize`
|
|
177
186
|
- `npx viveworker share list`
|
|
178
187
|
- `npx viveworker share update <slug> --password "hunter2"`
|
|
179
188
|
- `npx viveworker share update <slug> --expires-days 7`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "viveworker",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "Open mobile control surface for Codex, Claude, Thread Sharing, File Share, Moltbook, and A2A tasks on your trusted LAN.",
|
|
5
5
|
"author": "Yuta Hoshino <hoshino.lireneo@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
package/scripts/share-cli.mjs
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* .html .htm .pdf .png .jpg .jpeg .gif .webp .csv
|
|
8
8
|
*
|
|
9
9
|
* Commands:
|
|
10
|
-
* viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--json]
|
|
10
|
+
* viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--no-optimize] [--json]
|
|
11
11
|
* viveworker share list [--json]
|
|
12
12
|
* viveworker share update <slug> [--password <pw>] [--no-password] [--expires-days <n>] [--json]
|
|
13
13
|
* viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]
|
|
@@ -71,7 +71,7 @@ export async function runShareCli(args) {
|
|
|
71
71
|
|
|
72
72
|
function printHelp() {
|
|
73
73
|
console.log("Commands:");
|
|
74
|
-
console.log(" viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--json]");
|
|
74
|
+
console.log(" viveworker share upload <file> [--password <pw>] [--expires-days <n>] [--no-optimize] [--json]");
|
|
75
75
|
console.log(" viveworker share list [--json]");
|
|
76
76
|
console.log(" viveworker share update <slug> [--password <pw>] [--no-password] [--expires-days <n>] [--json]");
|
|
77
77
|
console.log(" viveworker share link <slug> --password <pw> [--ttl-hours <n>] [--json]");
|
|
@@ -79,6 +79,7 @@ function printHelp() {
|
|
|
79
79
|
console.log("");
|
|
80
80
|
console.log(`Accepted file types: ${ALLOWED_EXTENSIONS.join(" / ")}`);
|
|
81
81
|
console.log("CSV files are rendered as an HTML table on view; append ?raw=1 for bytes.");
|
|
82
|
+
console.log("HTML uploads are optimized by default when possible (use --no-optimize to disable).");
|
|
82
83
|
console.log("");
|
|
83
84
|
console.log("Credentials are read from ~/.viveworker/a2a.env (same as `viveworker a2a`).");
|
|
84
85
|
}
|
|
@@ -107,9 +108,6 @@ async function handleUpload(args) {
|
|
|
107
108
|
if (stat.size === 0) {
|
|
108
109
|
throw new Error(`File is empty: ${filePath}`);
|
|
109
110
|
}
|
|
110
|
-
if (stat.size > MAX_FILE_SIZE) {
|
|
111
|
-
throw new Error(`File too large (${stat.size} bytes, max ${MAX_FILE_SIZE})`);
|
|
112
|
-
}
|
|
113
111
|
const ext = path.extname(absolute).toLowerCase();
|
|
114
112
|
if (!ALLOWED_EXTENSIONS.includes(ext)) {
|
|
115
113
|
throw new Error(
|
|
@@ -134,7 +132,18 @@ async function handleUpload(args) {
|
|
|
134
132
|
|
|
135
133
|
const { apiKey, userId, shareUrl } = await resolveCredentials();
|
|
136
134
|
|
|
137
|
-
const
|
|
135
|
+
const originalBytes = await fs.readFile(absolute);
|
|
136
|
+
const optimization =
|
|
137
|
+
flags["no-optimize"] || flags["noOptimize"]
|
|
138
|
+
? { bytes: originalBytes, info: null }
|
|
139
|
+
: maybeOptimizeUpload({ absolute, ext, bytes: originalBytes });
|
|
140
|
+
const bytes = optimization.bytes;
|
|
141
|
+
if (bytes.length > MAX_FILE_SIZE) {
|
|
142
|
+
const detail = optimization.info
|
|
143
|
+
? ` after optimization to ${formatSize(bytes.length)}`
|
|
144
|
+
: "";
|
|
145
|
+
throw new Error(`File too large (${originalBytes.length} bytes, max ${MAX_FILE_SIZE})${detail}`);
|
|
146
|
+
}
|
|
138
147
|
const form = new FormData();
|
|
139
148
|
const blob = new Blob([bytes], { type: mime });
|
|
140
149
|
const file = new File([blob], path.basename(absolute), { type: mime });
|
|
@@ -164,6 +173,15 @@ async function handleUpload(args) {
|
|
|
164
173
|
console.log("");
|
|
165
174
|
console.log(`✅ Uploaded ${body.originalName || path.basename(absolute)} (${formatSize(body.size)})`);
|
|
166
175
|
console.log("");
|
|
176
|
+
if (optimization.info) {
|
|
177
|
+
console.log(
|
|
178
|
+
` Optimized HTML: ${formatSize(optimization.info.originalBytes)} → ${formatSize(optimization.info.optimizedBytes)}`
|
|
179
|
+
);
|
|
180
|
+
console.log(
|
|
181
|
+
` Removed embedded fonts: ${optimization.info.removedFonts}`
|
|
182
|
+
);
|
|
183
|
+
console.log("");
|
|
184
|
+
}
|
|
167
185
|
console.log(` ${body.url}`);
|
|
168
186
|
console.log("");
|
|
169
187
|
if (body.hasPassword) console.log(` 🔒 Password-protected`);
|
|
@@ -179,6 +197,97 @@ async function handleUpload(args) {
|
|
|
179
197
|
console.log("");
|
|
180
198
|
}
|
|
181
199
|
|
|
200
|
+
function maybeOptimizeUpload({ absolute, ext, bytes }) {
|
|
201
|
+
if (ext !== ".html" && ext !== ".htm") {
|
|
202
|
+
return { bytes, info: null };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const source = bytes.toString("utf8");
|
|
206
|
+
const optimized = optimizeBundledStandaloneHtml(source);
|
|
207
|
+
if (!optimized) {
|
|
208
|
+
return { bytes, info: null };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const optimizedBytes = Buffer.from(optimized.html, "utf8");
|
|
212
|
+
if (optimizedBytes.length >= bytes.length) {
|
|
213
|
+
return { bytes, info: null };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
bytes: optimizedBytes,
|
|
218
|
+
info: {
|
|
219
|
+
kind: "bundled-standalone-html",
|
|
220
|
+
removedFonts: optimized.removedFonts,
|
|
221
|
+
originalBytes: bytes.length,
|
|
222
|
+
optimizedBytes: optimizedBytes.length,
|
|
223
|
+
file: absolute,
|
|
224
|
+
},
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function optimizeBundledStandaloneHtml(source) {
|
|
229
|
+
const manifestTagPattern = /(<script\b[^>]*type="__bundler\/manifest"[^>]*>)([\s\S]*?)(<\/script>)/i;
|
|
230
|
+
const templateTagPattern = /(<script\b[^>]*type="__bundler\/template"[^>]*>)([\s\S]*?)(<\/script>)/i;
|
|
231
|
+
const manifestMatch = source.match(manifestTagPattern);
|
|
232
|
+
const templateMatch = source.match(templateTagPattern);
|
|
233
|
+
if (!manifestMatch || !templateMatch) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
let manifest;
|
|
238
|
+
let template;
|
|
239
|
+
try {
|
|
240
|
+
manifest = JSON.parse(manifestMatch[2]);
|
|
241
|
+
template = JSON.parse(templateMatch[2]);
|
|
242
|
+
} catch {
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const fontUuids = Object.entries(manifest)
|
|
247
|
+
.filter(([, entry]) => entry?.mime === "font/woff2")
|
|
248
|
+
.map(([uuid]) => uuid);
|
|
249
|
+
if (fontUuids.length === 0) {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
for (const uuid of fontUuids) {
|
|
254
|
+
delete manifest[uuid];
|
|
255
|
+
const escapedUuid = escapeRegExp(uuid);
|
|
256
|
+
template = template.replace(
|
|
257
|
+
new RegExp(`@font-face\\s*\\{[^{}]*${escapedUuid}[^{}]*\\}`, "g"),
|
|
258
|
+
"",
|
|
259
|
+
);
|
|
260
|
+
template = template.split(uuid).join("");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
let html = source.replace(
|
|
264
|
+
manifestTagPattern,
|
|
265
|
+
(_, openTag, _json, closeTag) => `${openTag}${stringifyForHtmlScriptTag(manifest)}${closeTag}`,
|
|
266
|
+
);
|
|
267
|
+
html = html.replace(
|
|
268
|
+
templateTagPattern,
|
|
269
|
+
(_, openTag, _json, closeTag) => `${openTag}${stringifyForHtmlScriptTag(template)}${closeTag}`,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
html,
|
|
274
|
+
removedFonts: fontUuids.length,
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function escapeRegExp(value) {
|
|
279
|
+
return String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function stringifyForHtmlScriptTag(value) {
|
|
283
|
+
return JSON.stringify(value)
|
|
284
|
+
.replace(/</g, "\\u003C")
|
|
285
|
+
.replace(/>/g, "\\u003E")
|
|
286
|
+
.replace(/&/g, "\\u0026")
|
|
287
|
+
.replace(/\u2028/g, "\\u2028")
|
|
288
|
+
.replace(/\u2029/g, "\\u2029");
|
|
289
|
+
}
|
|
290
|
+
|
|
182
291
|
// ---------------------------------------------------------------------------
|
|
183
292
|
// list
|
|
184
293
|
// ---------------------------------------------------------------------------
|
package/scripts/viveworker.mjs
CHANGED
|
@@ -64,6 +64,17 @@ if (rawArgs[0] === "share") {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
if (rawArgs[0] === "stats") {
|
|
68
|
+
const { runStatsCli } = await import("./stats-cli.mjs");
|
|
69
|
+
try {
|
|
70
|
+
await runStatsCli(rawArgs.slice(1));
|
|
71
|
+
process.exit(0);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(error.message || String(error));
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
67
78
|
const cli = parseArgs(rawArgs);
|
|
68
79
|
|
|
69
80
|
try {
|