@floomhq/floom 1.0.23 → 1.0.25
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/login.js +46 -0
- package/dist/package.js +79 -9
- package/package.json +1 -1
package/dist/login.js
CHANGED
|
@@ -151,6 +151,11 @@ function waitForCallback(port, provider) {
|
|
|
151
151
|
});
|
|
152
152
|
return;
|
|
153
153
|
}
|
|
154
|
+
if (req.method === "GET" && req.url?.startsWith("/cli-callback")) {
|
|
155
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
156
|
+
res.end(localCallbackBridgePage());
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
154
159
|
res.writeHead(404, { "content-type": "text/plain" });
|
|
155
160
|
res.end("Not found");
|
|
156
161
|
});
|
|
@@ -197,6 +202,47 @@ function parseCallbackBody(body, contentType) {
|
|
|
197
202
|
}
|
|
198
203
|
return JSON.parse(body);
|
|
199
204
|
}
|
|
205
|
+
function localCallbackBridgePage() {
|
|
206
|
+
return `<!doctype html>
|
|
207
|
+
<html lang="en">
|
|
208
|
+
<head>
|
|
209
|
+
<meta charset="utf-8">
|
|
210
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
211
|
+
<title>Floom CLI sign-in</title>
|
|
212
|
+
</head>
|
|
213
|
+
<body>
|
|
214
|
+
<p>Completing Floom CLI sign-in...</p>
|
|
215
|
+
<script>
|
|
216
|
+
(async function () {
|
|
217
|
+
try {
|
|
218
|
+
var params = new URLSearchParams(window.location.hash.slice(1));
|
|
219
|
+
if (!params.get("access_token") || !params.get("refresh_token")) {
|
|
220
|
+
document.body.textContent = "OAuth response missing tokens.";
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
var body = new URLSearchParams();
|
|
224
|
+
["access_token", "refresh_token", "expires_in", "token_type", "state"].forEach(function (key) {
|
|
225
|
+
var value = params.get(key);
|
|
226
|
+
if (value) body.set(key, value);
|
|
227
|
+
});
|
|
228
|
+
window.history.replaceState(null, "", window.location.pathname);
|
|
229
|
+
var res = await fetch("/cli-callback", {
|
|
230
|
+
method: "POST",
|
|
231
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
232
|
+
body: body.toString()
|
|
233
|
+
});
|
|
234
|
+
var html = await res.text();
|
|
235
|
+
document.open();
|
|
236
|
+
document.write(html);
|
|
237
|
+
document.close();
|
|
238
|
+
} catch (e) {
|
|
239
|
+
document.body.textContent = "Could not complete Floom CLI sign-in. Return to your terminal and run floom login again.";
|
|
240
|
+
}
|
|
241
|
+
})();
|
|
242
|
+
</script>
|
|
243
|
+
</body>
|
|
244
|
+
</html>`;
|
|
245
|
+
}
|
|
200
246
|
function localCallbackPage(message) {
|
|
201
247
|
const safeMessage = escapeHtml(message);
|
|
202
248
|
return `<!doctype html>
|
package/dist/package.js
CHANGED
|
@@ -5,8 +5,39 @@ import { execFile as execFileCb } from "node:child_process";
|
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
6
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
7
7
|
import { FloomError } from "./errors.js";
|
|
8
|
-
const ALLOWED_PACKAGE_DIRS = new Set([
|
|
8
|
+
const ALLOWED_PACKAGE_DIRS = new Set([
|
|
9
|
+
"agents",
|
|
10
|
+
"assets",
|
|
11
|
+
"canvas-fonts",
|
|
12
|
+
"checks",
|
|
13
|
+
"core",
|
|
14
|
+
"evidence",
|
|
15
|
+
"examples",
|
|
16
|
+
"helpers",
|
|
17
|
+
"reference",
|
|
18
|
+
"references",
|
|
19
|
+
"schemas",
|
|
20
|
+
"scripts",
|
|
21
|
+
"spreadsheets",
|
|
22
|
+
"templates",
|
|
23
|
+
"tests",
|
|
24
|
+
"themes",
|
|
25
|
+
]);
|
|
9
26
|
const ALLOWED_PACKAGE_ROOT_FILES = new Set(["SKILL.md", ".gitignore"]);
|
|
27
|
+
const IGNORED_PACKAGE_ROOT_ENTRIES = new Set([".DS_Store", ".git", ".pytest_cache"]);
|
|
28
|
+
const ALLOWED_PACKAGE_ROOT_EXTENSIONS = new Set([
|
|
29
|
+
".env.example",
|
|
30
|
+
".js",
|
|
31
|
+
".json",
|
|
32
|
+
".md",
|
|
33
|
+
".py",
|
|
34
|
+
".sh",
|
|
35
|
+
".toml",
|
|
36
|
+
".ts",
|
|
37
|
+
".txt",
|
|
38
|
+
".yaml",
|
|
39
|
+
".yml",
|
|
40
|
+
]);
|
|
10
41
|
const PACKAGE_FILE_LIMIT = 100;
|
|
11
42
|
const PACKAGE_TOTAL_BYTES_LIMIT = 1_000_000;
|
|
12
43
|
const PACKAGE_FILE_BYTES_LIMIT = 500_000;
|
|
@@ -95,11 +126,38 @@ async function collectPackageFiles(root) {
|
|
|
95
126
|
const isIgnored = gitIgnoreChecker(root);
|
|
96
127
|
const rootEntries = await readdir(root, { withFileTypes: true });
|
|
97
128
|
for (const entry of rootEntries) {
|
|
98
|
-
if (ALLOWED_PACKAGE_ROOT_FILES.has(entry.name) || ALLOWED_PACKAGE_DIRS.has(entry.name))
|
|
99
|
-
continue;
|
|
100
129
|
if (await isIgnored(entry.name))
|
|
101
130
|
continue;
|
|
102
|
-
|
|
131
|
+
if (IGNORED_PACKAGE_ROOT_ENTRIES.has(entry.name))
|
|
132
|
+
continue;
|
|
133
|
+
if (ALLOWED_PACKAGE_ROOT_FILES.has(entry.name))
|
|
134
|
+
continue;
|
|
135
|
+
if (entry.isDirectory() && ALLOWED_PACKAGE_DIRS.has(entry.name))
|
|
136
|
+
continue;
|
|
137
|
+
if (entry.isFile() && isAllowedRootPackageFile(entry.name)) {
|
|
138
|
+
const rel = entry.name;
|
|
139
|
+
validatePackageRelativePath(rel);
|
|
140
|
+
const fullPath = join(root, entry.name);
|
|
141
|
+
const stat = await lstat(fullPath);
|
|
142
|
+
if (files.length >= PACKAGE_FILE_LIMIT) {
|
|
143
|
+
throw new FloomError("Skill package has too many files.", "A skill package is capped at 100 supporting files.");
|
|
144
|
+
}
|
|
145
|
+
if (stat.size > PACKAGE_FILE_BYTES_LIMIT) {
|
|
146
|
+
throw new FloomError(`File too large: ${rel}`, "Each package file is capped at 500KB.");
|
|
147
|
+
}
|
|
148
|
+
totalBytes += stat.size;
|
|
149
|
+
ensurePackageSize(totalBytes);
|
|
150
|
+
const bytes = await readFile(fullPath);
|
|
151
|
+
files.push({
|
|
152
|
+
path: rel,
|
|
153
|
+
content_base64: bytes.toString("base64"),
|
|
154
|
+
encoding: "base64",
|
|
155
|
+
size_bytes: bytes.length,
|
|
156
|
+
sha256: sha256Bytes(bytes),
|
|
157
|
+
});
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
const hint = `Move supporting files under one of: ${Array.from(ALLOWED_PACKAGE_DIRS).sort().join(", ")}.`;
|
|
103
161
|
throw new FloomError(`Unsupported root package entry: ${entry.name}`, hint);
|
|
104
162
|
}
|
|
105
163
|
for (const dirName of ALLOWED_PACKAGE_DIRS) {
|
|
@@ -121,13 +179,22 @@ async function collectPackageFiles(root) {
|
|
|
121
179
|
throw new FloomError(`Package entry must be a directory: ${dirName}`);
|
|
122
180
|
await collectDir(root, dirPath, files, isIgnored, (size) => {
|
|
123
181
|
totalBytes += size;
|
|
124
|
-
|
|
125
|
-
throw new FloomError("Skill package is too large.", "A skill package is capped at 1MB total.");
|
|
126
|
-
}
|
|
182
|
+
ensurePackageSize(totalBytes);
|
|
127
183
|
});
|
|
128
184
|
}
|
|
129
185
|
return files.sort((a, b) => a.path.localeCompare(b.path));
|
|
130
186
|
}
|
|
187
|
+
function isAllowedRootPackageFile(name) {
|
|
188
|
+
if (name.startsWith(".") && name !== ".env.example")
|
|
189
|
+
return false;
|
|
190
|
+
const lower = name.toLowerCase();
|
|
191
|
+
return Array.from(ALLOWED_PACKAGE_ROOT_EXTENSIONS).some((ext) => lower.endsWith(ext));
|
|
192
|
+
}
|
|
193
|
+
function ensurePackageSize(totalBytes) {
|
|
194
|
+
if (totalBytes > PACKAGE_TOTAL_BYTES_LIMIT) {
|
|
195
|
+
throw new FloomError("Skill package is too large.", "A skill package is capped at 1MB total.");
|
|
196
|
+
}
|
|
197
|
+
}
|
|
131
198
|
async function collectDir(root, dir, files, isIgnored, addBytes) {
|
|
132
199
|
const entries = await readdir(dir, { withFileTypes: true });
|
|
133
200
|
for (const entry of entries) {
|
|
@@ -237,8 +304,11 @@ export function validatePackageRelativePath(path) {
|
|
|
237
304
|
throw new FloomError(`Invalid package file path: ${path}`);
|
|
238
305
|
}
|
|
239
306
|
const segments = path.split("/");
|
|
240
|
-
if (segments.length
|
|
241
|
-
throw new FloomError(`Invalid package file path: ${path}`, "
|
|
307
|
+
if (segments.length === 1 && !isAllowedRootPackageFile(segments[0] ?? "")) {
|
|
308
|
+
throw new FloomError(`Invalid package file path: ${path}`, "Root package files must be safe text files such as .md, .txt, .json, .yaml, .toml, .py, .sh, .js, or .ts.");
|
|
309
|
+
}
|
|
310
|
+
if (segments.length > 1 && !ALLOWED_PACKAGE_DIRS.has(segments[0] ?? "")) {
|
|
311
|
+
throw new FloomError(`Invalid package file path: ${path}`, `Package files must be root text files or live under one of: ${Array.from(ALLOWED_PACKAGE_DIRS).sort().join(", ")}.`);
|
|
242
312
|
}
|
|
243
313
|
if (segments.some((segment) => segment === "." || segment === ".." || !PACKAGE_SEGMENT_RE.test(segment))) {
|
|
244
314
|
throw new FloomError(`Invalid package file path: ${path}`);
|