@helloleo/plugins 0.2.25 → 0.2.27
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/dist/index.js
CHANGED
|
@@ -21,7 +21,7 @@ var WRITE_TOOLS = new Set(["write", "edit", "apply_patch"]);
|
|
|
21
21
|
var CHECKED = /\.(?:[cm]?tsx?|[cm]?jsx?)$/;
|
|
22
22
|
var SKIPPED = /(?:^|[/\\])(?:node_modules|dist|build|\.wrangler|\.output)[/\\]/;
|
|
23
23
|
var MAX_LINES = 6;
|
|
24
|
-
var MAX_BYTES =
|
|
24
|
+
var MAX_BYTES = 262144;
|
|
25
25
|
function loaderFor(file) {
|
|
26
26
|
const tsx = file.endsWith("x");
|
|
27
27
|
return file.includes(".ts") ? tsx ? "tsx" : "ts" : tsx ? "jsx" : "js";
|
|
@@ -55,7 +55,7 @@ function parse(file) {
|
|
|
55
55
|
line,
|
|
56
56
|
col: item?.position?.column ?? 0,
|
|
57
57
|
message: item?.message ?? "Syntax error.",
|
|
58
|
-
source:
|
|
58
|
+
source: lines[line - 1] ?? ""
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
61
|
return findings;
|
|
@@ -25009,9 +25009,8 @@ function resolveTemplateId() {
|
|
|
25009
25009
|
function projectUuidFromDir(dir) {
|
|
25010
25010
|
return path3.basename(dir);
|
|
25011
25011
|
}
|
|
25012
|
-
function
|
|
25013
|
-
|
|
25014
|
-
return `${base}/api/integration-proxy/${projectUuid}`;
|
|
25012
|
+
function integrationProxyBase() {
|
|
25013
|
+
return (process.env.PLUGINS_INTEGRATION_PROXY_URL || "https://api.app.helloleo.dev").replace(/\/+$/, "");
|
|
25015
25014
|
}
|
|
25016
25015
|
var scaffoldOptionsSchema = exports_external2.object({
|
|
25017
25016
|
name: exports_external2.string().describe("Human-readable name for SEO, e.g. 'Shipping Platform'"),
|
|
@@ -25028,7 +25027,7 @@ async function writeBaseScaffold({ name, description }, dir) {
|
|
|
25028
25027
|
LEO_SLUG: slugify(name),
|
|
25029
25028
|
LEO_DESCRIPTION: description,
|
|
25030
25029
|
LEO_PROJECT_UUID: projectUuid,
|
|
25031
|
-
LEO_INTEGRATION_PROXY_URL:
|
|
25030
|
+
LEO_INTEGRATION_PROXY_URL: `${integrationProxyBase()}/api/integration-proxy/${projectUuid}`
|
|
25032
25031
|
}, dir);
|
|
25033
25032
|
await mkdir(path3.join(dir, ".leo"), { recursive: true });
|
|
25034
25033
|
const leoPath = path3.join(".leo", "project.json");
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* HelloLeo Integration Proxy
|
|
2
|
+
* HelloLeo Integration Proxy: runtime helper for connected integrations.
|
|
3
3
|
*
|
|
4
4
|
* All third-party APIs (Odoo, Airtable, HubSpot, Stripe, Notion, Gladia,
|
|
5
5
|
* Microsoft 365, Klaviyo, Supabase, DimoMaint, etc.) are reachable through
|
|
@@ -19,7 +19,12 @@
|
|
|
19
19
|
* Native integrations (odoo, airtable) keep their plain name.
|
|
20
20
|
*
|
|
21
21
|
* Response shape (always wrapped):
|
|
22
|
-
* { success: boolean, status: number, data: <provider payload
|
|
22
|
+
* { success: boolean, status: number, data: <provider payload>, docs: string }
|
|
23
|
+
*
|
|
24
|
+
* - `status` is the UPSTREAM status, not the proxy's.
|
|
25
|
+
* - `success` is false whenever that status is >= 400.
|
|
26
|
+
* - `data` is null when the upstream returned no body.
|
|
27
|
+
* - `docs` links the proxy's own reference.
|
|
23
28
|
*
|
|
24
29
|
* Unwrap "data" by provider:
|
|
25
30
|
* odoo -> data.result
|
|
@@ -34,6 +39,17 @@
|
|
|
34
39
|
* Read pagination metadata from the response to build the next call.
|
|
35
40
|
* Never .filter() large lists in the frontend -- paginate server-side.
|
|
36
41
|
*
|
|
42
|
+
* Sending files:
|
|
43
|
+
* Most APIs take a file as base64 inside the normal JSON body. Odoo is the
|
|
44
|
+
* common case: ir.attachment.create with the bytes in `datas` and BOTH
|
|
45
|
+
* `res_model` and `res_id` set, or the file is stored but appears on no
|
|
46
|
+
* record. Nothing special is needed for those.
|
|
47
|
+
*
|
|
48
|
+
* Some upload endpoints accept only multipart/form-data and answer JSON with
|
|
49
|
+
* HTTP 415. For those set bodyEncoding: 'multipart' (see uploadFile below).
|
|
50
|
+
* Never set a Content-Type header for multipart, the proxy lets fetch
|
|
51
|
+
* generate the boundary. Not accepted for odoo, which has no multipart API.
|
|
52
|
+
*
|
|
37
53
|
* Secrets for non-integration APIs (e.g. raw OpenAI): store them in
|
|
38
54
|
* HelloLeo Cloud -> Settings -> Cloud -> Secrets, and read via env.SECRET_NAME
|
|
39
55
|
* from a Worker. Never embed secrets in frontend code.
|
|
@@ -64,18 +80,38 @@ function proxyUrl(): string {
|
|
|
64
80
|
return env === 'production' ? `${INTEGRATION_PROXY_URL}?env=production` : INTEGRATION_PROXY_URL;
|
|
65
81
|
}
|
|
66
82
|
|
|
83
|
+
export interface MultipartFile {
|
|
84
|
+
/** Form field name the upstream expects, e.g. 'content' or 'file'. */
|
|
85
|
+
name: string;
|
|
86
|
+
filename: string;
|
|
87
|
+
contentType?: string;
|
|
88
|
+
/** File bytes, base64-encoded (no data: prefix). */
|
|
89
|
+
dataBase64: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface MultipartBody {
|
|
93
|
+
fields?: Record<string, string>;
|
|
94
|
+
files?: MultipartFile[];
|
|
95
|
+
}
|
|
96
|
+
|
|
67
97
|
export interface ProxyRequest {
|
|
68
98
|
integration: string;
|
|
69
99
|
endpoint: string;
|
|
70
100
|
method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
|
|
71
101
|
body?: unknown;
|
|
72
102
|
headers?: Record<string, string>;
|
|
103
|
+
/** Omit for JSON. 'multipart' when the upstream upload endpoint rejects JSON with 415. */
|
|
104
|
+
bodyEncoding?: 'json' | 'multipart';
|
|
73
105
|
}
|
|
74
106
|
|
|
75
107
|
export interface ProxyResponse<T = unknown> {
|
|
76
108
|
success: boolean;
|
|
109
|
+
/** The UPSTREAM status, not the proxy's. */
|
|
77
110
|
status: number;
|
|
78
|
-
|
|
111
|
+
/** Null when the upstream returned no body. */
|
|
112
|
+
data: T | null;
|
|
113
|
+
/** The proxy's own reference, on every response. */
|
|
114
|
+
docs: string;
|
|
79
115
|
}
|
|
80
116
|
|
|
81
117
|
export async function callIntegration<T = unknown>(
|
|
@@ -95,6 +131,24 @@ export async function callIntegration<T = unknown>(
|
|
|
95
131
|
return res.json();
|
|
96
132
|
}
|
|
97
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Upload a file to an integration whose endpoint requires multipart/form-data
|
|
136
|
+
* (Zoho WorkDrive and similar). For Odoo, do NOT use this -- create an
|
|
137
|
+
* ir.attachment with the bytes base64-encoded in `datas` instead.
|
|
138
|
+
*/
|
|
139
|
+
export async function uploadFile<T = unknown>(
|
|
140
|
+
req: Omit<ProxyRequest, 'body' | 'bodyEncoding'> & { body: MultipartBody },
|
|
141
|
+
): Promise<ProxyResponse<T>> {
|
|
142
|
+
return callIntegration<T>({ method: 'POST', ...req, bodyEncoding: 'multipart' });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export async function fileToBase64(file: Blob): Promise<string> {
|
|
146
|
+
const bytes = new Uint8Array(await file.arrayBuffer());
|
|
147
|
+
let binary = '';
|
|
148
|
+
for (let i = 0; i < bytes.length; i += 1) binary += String.fromCharCode(bytes[i]);
|
|
149
|
+
return btoa(binary);
|
|
150
|
+
}
|
|
151
|
+
|
|
98
152
|
/* Example -- Odoo search_read:
|
|
99
153
|
*
|
|
100
154
|
* const r = await callIntegration({
|
|
@@ -108,4 +162,21 @@ export async function callIntegration<T = unknown>(
|
|
|
108
162
|
* },
|
|
109
163
|
* });
|
|
110
164
|
* const products = r.data?.result ?? [];
|
|
165
|
+
*
|
|
166
|
+
* Example -- multipart upload (Zoho WorkDrive):
|
|
167
|
+
*
|
|
168
|
+
* const r = await uploadFile({
|
|
169
|
+
* integration: 'zoho_workdrive',
|
|
170
|
+
* endpoint: '/api/v1/upload',
|
|
171
|
+
* body: {
|
|
172
|
+
* fields: { parent_id: folderId, 'override-name-exist': 'true' },
|
|
173
|
+
* files: [{
|
|
174
|
+
* name: 'content',
|
|
175
|
+
* filename: file.name,
|
|
176
|
+
* contentType: file.type,
|
|
177
|
+
* dataBase64: await fileToBase64(file),
|
|
178
|
+
* }],
|
|
179
|
+
* },
|
|
180
|
+
* });
|
|
181
|
+
* if (!r.success) throw new Error(String(r.data));
|
|
111
182
|
*/
|