@vee3/upload 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,23 +1,31 @@
1
- {
2
- "name": "@vee3/upload",
3
- "version": "0.1.0",
4
- "description": "Upload local files to Vee3.",
5
- "type": "module",
6
- "bin": {
7
- "vee3-upload": "./src/index.js"
8
- },
9
- "files": ["src"],
10
- "engines": {
11
- "node": ">=18"
12
- },
13
- "publishConfig": {
14
- "access": "public"
15
- },
16
- "repository": {
17
- "type": "git",
18
- "url": "https://github.com/vee3io/Vee3.git",
19
- "directory": "packages/upload"
20
- },
21
- "keywords": ["vee3", "upload", "cli", "mcp"],
22
- "license": "MIT"
23
- }
1
+ {
2
+ "name": "@vee3/upload",
3
+ "version": "0.2.0",
4
+ "description": "Upload local files to Vee3.",
5
+ "homepage": "https://vee3.io",
6
+ "type": "module",
7
+ "bin": {
8
+ "vee3-upload": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src"
12
+ ],
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "https://github.com/vee3io/Vee3.git",
22
+ "directory": "packages/upload"
23
+ },
24
+ "keywords": [
25
+ "vee3",
26
+ "upload",
27
+ "cli",
28
+ "mcp"
29
+ ],
30
+ "license": "MIT"
31
+ }
package/src/api.js CHANGED
@@ -1,34 +1,34 @@
1
- export const DEFAULT_API_BASE_URL = "https://api.vee3.io";
2
-
3
- async function formatApiError(response) {
4
- const responseText = await response.text();
5
- try {
6
- const payload = JSON.parse(responseText);
7
- if (payload && typeof payload.error === "object") {
8
- const requestId = payload.error.request_id ? ` (request_id=${payload.error.request_id})` : "";
9
- return `${payload.error.code}: ${payload.error.message}${requestId}`;
10
- }
11
- } catch {
12
- // Fall through to raw response text.
13
- }
14
- return responseText || `HTTP ${response.status}`;
15
- }
16
-
17
- export async function resolveUpload({ apiBaseUrl, uploadCode, contentType }) {
18
- const response = await fetch(`${apiBaseUrl.replace(/\/$/, "")}/v1/agent-uploads/resolve`, {
19
- method: "POST",
20
- headers: {
21
- "Content-Type": "application/json"
22
- },
23
- body: JSON.stringify({
24
- upload_code: uploadCode,
25
- content_type: contentType
26
- })
27
- });
28
-
29
- if (!response.ok) {
30
- throw new Error(await formatApiError(response));
31
- }
32
-
33
- return await response.json();
34
- }
1
+ export const DEFAULT_API_BASE_URL = "https://api.vee3.io";
2
+
3
+ async function formatApiError(response) {
4
+ const responseText = await response.text();
5
+ try {
6
+ const payload = JSON.parse(responseText);
7
+ if (payload && typeof payload.error === "object") {
8
+ const requestId = payload.error.request_id ? ` (request_id=${payload.error.request_id})` : "";
9
+ return `${payload.error.code}: ${payload.error.message}${requestId}`;
10
+ }
11
+ } catch {
12
+ // Fall through to raw response text.
13
+ }
14
+ return responseText || `HTTP ${response.status}`;
15
+ }
16
+
17
+ export async function resolveUpload({ apiBaseUrl, uploadCode, contentType }) {
18
+ const response = await fetch(`${apiBaseUrl.replace(/\/$/, "")}/v1/agent-uploads/resolve`, {
19
+ method: "POST",
20
+ headers: {
21
+ "Content-Type": "application/json"
22
+ },
23
+ body: JSON.stringify({
24
+ upload_code: uploadCode,
25
+ content_type: contentType
26
+ })
27
+ });
28
+
29
+ if (!response.ok) {
30
+ throw new Error(await formatApiError(response));
31
+ }
32
+
33
+ return await response.json();
34
+ }
package/src/index.js CHANGED
@@ -1,96 +1,99 @@
1
- #!/usr/bin/env node
2
-
3
- import { stat } from "node:fs/promises";
4
- import { DEFAULT_API_BASE_URL, resolveUpload } from "./api.js";
5
- import { detectContentType } from "./detectContentType.js";
6
- import { uploadFileToSignedUrl } from "./uploadFile.js";
7
-
8
- function printUsage() {
9
- console.error(
10
- "Usage: vee3-upload <upload_code> <file_path> [--api-base-url https://api.vee3.io] [--json]"
11
- );
12
- }
13
-
14
- function parseArguments(argumentsList) {
15
- const positionalArguments = [];
16
- let apiBaseUrl = DEFAULT_API_BASE_URL;
17
- let outputJson = false;
18
-
19
- for (let index = 0; index < argumentsList.length; index += 1) {
20
- const argument = argumentsList[index];
21
- if (argument === "--api-base-url") {
22
- const nextArgument = argumentsList[index + 1];
23
- if (!nextArgument) {
24
- throw new Error("--api-base-url requires a value");
25
- }
26
- apiBaseUrl = nextArgument;
27
- index += 1;
28
- continue;
29
- }
30
- if (argument === "--json") {
31
- outputJson = true;
32
- continue;
33
- }
34
- positionalArguments.push(argument);
35
- }
36
-
37
- if (positionalArguments.length !== 2) {
38
- throw new Error("Expected upload_code and file_path");
39
- }
40
-
41
- return {
42
- uploadCode: positionalArguments[0],
43
- filePath: positionalArguments[1],
44
- apiBaseUrl,
45
- outputJson
46
- };
47
- }
48
-
49
- async function main() {
50
- const { uploadCode, filePath, apiBaseUrl, outputJson } = parseArguments(
51
- process.argv.slice(2)
52
- );
53
- const fileStat = await stat(filePath);
54
- if (!fileStat.isFile()) {
55
- throw new Error(`File path is not a file: ${filePath}`);
56
- }
57
-
58
- const contentType = await detectContentType(filePath);
59
- const resolvedUpload = await resolveUpload({
60
- apiBaseUrl,
61
- uploadCode,
62
- contentType
63
- });
64
-
65
- if (fileStat.size > resolvedUpload.max_bytes) {
66
- throw new Error(
67
- `File is too large: ${fileStat.size} bytes exceeds ${resolvedUpload.max_bytes} bytes`
68
- );
69
- }
70
-
71
- await uploadFileToSignedUrl({
72
- uploadUrl: resolvedUpload.upload_url,
73
- filePath,
74
- fileSizeBytes: fileStat.size,
75
- requiredHeaders: resolvedUpload.required_headers
76
- });
77
-
78
- if (outputJson) {
79
- console.log(
80
- JSON.stringify({
81
- upload_id: resolvedUpload.upload_id,
82
- content_type: contentType,
83
- size_bytes: fileStat.size
84
- })
85
- );
86
- return;
87
- }
88
-
89
- console.log(resolvedUpload.upload_id);
90
- }
91
-
92
- main().catch((error) => {
93
- printUsage();
94
- console.error(error instanceof Error ? error.message : String(error));
95
- process.exit(1);
96
- });
1
+ #!/usr/bin/env node
2
+
3
+ import { stat } from "node:fs/promises";
4
+ import { DEFAULT_API_BASE_URL, resolveUpload } from "./api.js";
5
+ import { detectContentType } from "./detectContentType.js";
6
+ import { ensureSystemCertificateTrust } from "./systemCertificates.js";
7
+ import { uploadFileToSignedUrl } from "./uploadFile.js";
8
+
9
+ ensureSystemCertificateTrust();
10
+
11
+ function printUsage() {
12
+ console.error(
13
+ "Usage: vee3-upload <upload_code> <file_path> [--api-base-url https://api.vee3.io] [--json]"
14
+ );
15
+ }
16
+
17
+ function parseArguments(argumentsList) {
18
+ const positionalArguments = [];
19
+ let apiBaseUrl = DEFAULT_API_BASE_URL;
20
+ let outputJson = false;
21
+
22
+ for (let index = 0; index < argumentsList.length; index += 1) {
23
+ const argument = argumentsList[index];
24
+ if (argument === "--api-base-url") {
25
+ const nextArgument = argumentsList[index + 1];
26
+ if (!nextArgument) {
27
+ throw new Error("--api-base-url requires a value");
28
+ }
29
+ apiBaseUrl = nextArgument;
30
+ index += 1;
31
+ continue;
32
+ }
33
+ if (argument === "--json") {
34
+ outputJson = true;
35
+ continue;
36
+ }
37
+ positionalArguments.push(argument);
38
+ }
39
+
40
+ if (positionalArguments.length !== 2) {
41
+ throw new Error("Expected upload_code and file_path");
42
+ }
43
+
44
+ return {
45
+ uploadCode: positionalArguments[0],
46
+ filePath: positionalArguments[1],
47
+ apiBaseUrl,
48
+ outputJson
49
+ };
50
+ }
51
+
52
+ async function main() {
53
+ const { uploadCode, filePath, apiBaseUrl, outputJson } = parseArguments(
54
+ process.argv.slice(2)
55
+ );
56
+ const fileStat = await stat(filePath);
57
+ if (!fileStat.isFile()) {
58
+ throw new Error(`File path is not a file: ${filePath}`);
59
+ }
60
+
61
+ const contentType = await detectContentType(filePath);
62
+ const resolvedUpload = await resolveUpload({
63
+ apiBaseUrl,
64
+ uploadCode,
65
+ contentType
66
+ });
67
+
68
+ if (fileStat.size > resolvedUpload.max_bytes) {
69
+ throw new Error(
70
+ `File is too large: ${fileStat.size} bytes exceeds ${resolvedUpload.max_bytes} bytes`
71
+ );
72
+ }
73
+
74
+ await uploadFileToSignedUrl({
75
+ uploadUrl: resolvedUpload.upload_url,
76
+ filePath,
77
+ fileSizeBytes: fileStat.size,
78
+ requiredHeaders: resolvedUpload.required_headers
79
+ });
80
+
81
+ if (outputJson) {
82
+ console.log(
83
+ JSON.stringify({
84
+ upload_id: resolvedUpload.upload_id,
85
+ content_type: contentType,
86
+ size_bytes: fileStat.size
87
+ })
88
+ );
89
+ return;
90
+ }
91
+
92
+ console.log(resolvedUpload.upload_id);
93
+ }
94
+
95
+ main().catch((error) => {
96
+ printUsage();
97
+ console.error(error instanceof Error ? error.message : String(error));
98
+ process.exit(1);
99
+ });
@@ -0,0 +1,61 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ const SYSTEM_CA_FLAG = "--use-system-ca";
4
+ const REEXEC_GUARD_VARIABLE = "VEE3_UPLOAD_SYSTEM_CA_REEXEC";
5
+
6
+ function systemCaFlagAlreadyActive() {
7
+ if (process.execArgv.includes(SYSTEM_CA_FLAG)) {
8
+ return true;
9
+ }
10
+ const nodeOptions = process.env.NODE_OPTIONS ?? "";
11
+ return nodeOptions.includes(SYSTEM_CA_FLAG);
12
+ }
13
+
14
+ function systemCaFlagSupported() {
15
+ try {
16
+ return process.allowedNodeEnvironmentFlags.has(SYSTEM_CA_FLAG);
17
+ } catch {
18
+ return false;
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Networks that perform TLS inspection (corporate proxies, some antivirus
24
+ * products) present certificates signed by a private root CA that Node does not
25
+ * trust by default, so HTTPS requests fail with UNABLE_TO_VERIFY_LEAF_SIGNATURE.
26
+ * Re-executing with --use-system-ca makes Node trust the same roots the
27
+ * operating system already trusts, which is what browsers and OS HTTP clients
28
+ * use. On older Node versions without the flag we fall back to whatever
29
+ * NODE_EXTRA_CA_CERTS provides.
30
+ */
31
+ export function ensureSystemCertificateTrust() {
32
+ if (process.env[REEXEC_GUARD_VARIABLE] === "1") {
33
+ return;
34
+ }
35
+ if (systemCaFlagAlreadyActive()) {
36
+ return;
37
+ }
38
+ if (!systemCaFlagSupported()) {
39
+ return;
40
+ }
41
+
42
+ const entryScript = process.argv[1];
43
+ if (!entryScript) {
44
+ return;
45
+ }
46
+
47
+ const result = spawnSync(
48
+ process.execPath,
49
+ [SYSTEM_CA_FLAG, entryScript, ...process.argv.slice(2)],
50
+ {
51
+ stdio: "inherit",
52
+ env: { ...process.env, [REEXEC_GUARD_VARIABLE]: "1" }
53
+ }
54
+ );
55
+
56
+ if (result.error) {
57
+ return;
58
+ }
59
+
60
+ process.exit(result.status ?? 0);
61
+ }
package/src/uploadFile.js CHANGED
@@ -1,25 +1,25 @@
1
- import { createReadStream } from "node:fs";
2
-
3
- export async function uploadFileToSignedUrl({
4
- uploadUrl,
5
- filePath,
6
- fileSizeBytes,
7
- requiredHeaders
8
- }) {
9
- const headers = {
10
- ...requiredHeaders,
11
- "Content-Length": String(fileSizeBytes)
12
- };
13
-
14
- const response = await fetch(uploadUrl, {
15
- method: "PUT",
16
- headers,
17
- body: createReadStream(filePath),
18
- duplex: "half"
19
- });
20
-
21
- if (!response.ok) {
22
- const responseText = await response.text();
23
- throw new Error(responseText || `Upload failed with HTTP ${response.status}`);
24
- }
25
- }
1
+ import { createReadStream } from "node:fs";
2
+
3
+ export async function uploadFileToSignedUrl({
4
+ uploadUrl,
5
+ filePath,
6
+ fileSizeBytes,
7
+ requiredHeaders
8
+ }) {
9
+ const headers = {
10
+ ...requiredHeaders,
11
+ "Content-Length": String(fileSizeBytes)
12
+ };
13
+
14
+ const response = await fetch(uploadUrl, {
15
+ method: "PUT",
16
+ headers,
17
+ body: createReadStream(filePath),
18
+ duplex: "half"
19
+ });
20
+
21
+ if (!response.ok) {
22
+ const responseText = await response.text();
23
+ throw new Error(responseText || `Upload failed with HTTP ${response.status}`);
24
+ }
25
+ }