@floomhq/floom 1.0.64 → 2.0.1
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.d.ts +1 -0
- package/dist/index.js +3663 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +1 -25
- package/package.json +37 -45
- package/LICENSE +0 -21
- package/README.md +0 -90
- package/bin/floom.js +0 -2
- package/dist/audit.js +0 -236
- package/dist/cli.js +0 -1313
- package/dist/config.js +0 -85
- package/dist/daemon.js +0 -450
- package/dist/delete.js +0 -55
- package/dist/doctor.js +0 -381
- package/dist/errors.js +0 -71
- package/dist/feedback.js +0 -34
- package/dist/info.js +0 -78
- package/dist/init.js +0 -221
- package/dist/install.js +0 -305
- package/dist/launch.js +0 -110
- package/dist/lib/api.js +0 -142
- package/dist/lib/skill-labels.js +0 -140
- package/dist/library.js +0 -102
- package/dist/list.js +0 -79
- package/dist/login.js +0 -259
- package/dist/mcp.js +0 -20
- package/dist/package.js +0 -507
- package/dist/publish.js +0 -240
- package/dist/push-watch.js +0 -372
- package/dist/scan.js +0 -24
- package/dist/search.js +0 -54
- package/dist/secrets.js +0 -119
- package/dist/setup.js +0 -301
- package/dist/share.js +0 -70
- package/dist/status.js +0 -181
- package/dist/sync-manifest.js +0 -314
- package/dist/sync.js +0 -581
- package/dist/targets.js +0 -49
- package/dist/ui.js +0 -28
- package/dist/whoami.js +0 -64
package/dist/sync-manifest.js
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
import { constants, rmSync } from "node:fs";
|
|
2
|
-
import { lstat, mkdir, open, rename, rm, stat, utimes } from "node:fs/promises";
|
|
3
|
-
import { homedir } from "node:os";
|
|
4
|
-
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
5
|
-
import { CONFIG_DIR } from "./config.js";
|
|
6
|
-
const MANIFEST_VERSION = 1;
|
|
7
|
-
const LOCK_TIMEOUT_MS = 6 * 60_000;
|
|
8
|
-
const LOCK_STALE_MS = 5 * 60_000;
|
|
9
|
-
const LOCK_HEARTBEAT_MS = 30_000;
|
|
10
|
-
const SLUG_RE = /^[A-Za-z0-9_-]{1,128}$/;
|
|
11
|
-
const FD_PATH_ROOT = "/proc/self/fd";
|
|
12
|
-
const SUPPORT_DIRS = new Set([
|
|
13
|
-
".github",
|
|
14
|
-
"agents",
|
|
15
|
-
"assets",
|
|
16
|
-
"bin",
|
|
17
|
-
"canvas-fonts",
|
|
18
|
-
"checks",
|
|
19
|
-
"claude",
|
|
20
|
-
"codex",
|
|
21
|
-
"contrib",
|
|
22
|
-
"core",
|
|
23
|
-
"cursor",
|
|
24
|
-
"design",
|
|
25
|
-
"docs",
|
|
26
|
-
"evidence",
|
|
27
|
-
"examples",
|
|
28
|
-
"extension",
|
|
29
|
-
"helpers",
|
|
30
|
-
"hosts",
|
|
31
|
-
"kimi",
|
|
32
|
-
"lib",
|
|
33
|
-
"migrations",
|
|
34
|
-
"model-overlays",
|
|
35
|
-
"openclaw",
|
|
36
|
-
"opencode",
|
|
37
|
-
"reference",
|
|
38
|
-
"references",
|
|
39
|
-
"remotion-starter",
|
|
40
|
-
"scripts",
|
|
41
|
-
"sdk",
|
|
42
|
-
"schemas",
|
|
43
|
-
"src",
|
|
44
|
-
"specialists",
|
|
45
|
-
"supabase",
|
|
46
|
-
"templates",
|
|
47
|
-
"test",
|
|
48
|
-
"tests",
|
|
49
|
-
"themes",
|
|
50
|
-
"vendor",
|
|
51
|
-
]);
|
|
52
|
-
const ROOT_SUPPORT_FILES = new Set([
|
|
53
|
-
".env.example",
|
|
54
|
-
"ACKNOWLEDGEMENTS.md",
|
|
55
|
-
"AGENTS.md",
|
|
56
|
-
"ARCHITECTURE.md",
|
|
57
|
-
"BROWSER.md",
|
|
58
|
-
"CHANGELOG.md",
|
|
59
|
-
"CLAUDE.md",
|
|
60
|
-
"CONTRIBUTING.md",
|
|
61
|
-
"DESIGN.md",
|
|
62
|
-
"ETHOS.md",
|
|
63
|
-
"LICENSE",
|
|
64
|
-
"LICENSE.md",
|
|
65
|
-
"LICENSE.txt",
|
|
66
|
-
"PLAN-snapshot-dropdown-interactive.md",
|
|
67
|
-
"README.md",
|
|
68
|
-
"REMOTION_PROTOCOL.md",
|
|
69
|
-
"SKILL.md.tmpl",
|
|
70
|
-
"TODOS.md",
|
|
71
|
-
"TODOS-format.md",
|
|
72
|
-
"USING_GBRAIN_WITH_GSTACK.md",
|
|
73
|
-
"VERSION",
|
|
74
|
-
".gitlab-ci.yml",
|
|
75
|
-
"actionlint.yaml",
|
|
76
|
-
"bun.lock",
|
|
77
|
-
"checklist.md",
|
|
78
|
-
"conductor.json",
|
|
79
|
-
"design-checklist.md",
|
|
80
|
-
"dx-hall-of-fame.md",
|
|
81
|
-
"editing.md",
|
|
82
|
-
"forms.md",
|
|
83
|
-
"greptile-triage.md",
|
|
84
|
-
"instructions.md",
|
|
85
|
-
"nanobanana.py",
|
|
86
|
-
"package.json",
|
|
87
|
-
"plan.md",
|
|
88
|
-
"pptxgenjs.md",
|
|
89
|
-
"reference.md",
|
|
90
|
-
"requirements.txt",
|
|
91
|
-
"setup",
|
|
92
|
-
"skill.md",
|
|
93
|
-
"slop-scan.config.json",
|
|
94
|
-
"style_guidelines.md",
|
|
95
|
-
"theme-showcase.pdf",
|
|
96
|
-
]);
|
|
97
|
-
function emptyManifest() {
|
|
98
|
-
return { version: MANIFEST_VERSION, files: {} };
|
|
99
|
-
}
|
|
100
|
-
function expandHome(path) {
|
|
101
|
-
if (path === "~")
|
|
102
|
-
return homedir();
|
|
103
|
-
if (path.startsWith("~/"))
|
|
104
|
-
return join(homedir(), path.slice(2));
|
|
105
|
-
return path;
|
|
106
|
-
}
|
|
107
|
-
export function syncManifestPath() {
|
|
108
|
-
if (process.env.FLOOM_SYNC_MANIFEST_PATH)
|
|
109
|
-
return expandHome(process.env.FLOOM_SYNC_MANIFEST_PATH);
|
|
110
|
-
return join(CONFIG_DIR, "sync-manifest.json");
|
|
111
|
-
}
|
|
112
|
-
function syncManifestDir() {
|
|
113
|
-
return dirname(syncManifestPath());
|
|
114
|
-
}
|
|
115
|
-
function syncLockPath() {
|
|
116
|
-
return join(syncManifestDir(), "sync.lock");
|
|
117
|
-
}
|
|
118
|
-
function isEntryForKey(key, value) {
|
|
119
|
-
if (!value || typeof value !== "object")
|
|
120
|
-
return false;
|
|
121
|
-
const entry = value;
|
|
122
|
-
if (typeof entry.hash === "string" &&
|
|
123
|
-
typeof entry.slug === "string" &&
|
|
124
|
-
typeof entry.target === "string" &&
|
|
125
|
-
typeof entry.syncedAt === "string" &&
|
|
126
|
-
entry.target === key &&
|
|
127
|
-
SLUG_RE.test(entry.slug)) {
|
|
128
|
-
const segments = key.split("/");
|
|
129
|
-
const legacyFile = segments.at(-1) === `${entry.slug}.md`;
|
|
130
|
-
const slugIndex = segments.lastIndexOf(entry.slug);
|
|
131
|
-
const packagePath = slugIndex >= 0 ? segments.slice(slugIndex + 1) : [];
|
|
132
|
-
return legacyFile || isPackageFilePath(packagePath);
|
|
133
|
-
}
|
|
134
|
-
return false;
|
|
135
|
-
}
|
|
136
|
-
function isPackageFilePath(packagePath) {
|
|
137
|
-
if (packagePath.length === 1 && packagePath[0] === "SKILL.md")
|
|
138
|
-
return true;
|
|
139
|
-
if (packagePath.length === 1 && ROOT_SUPPORT_FILES.has(packagePath[0] ?? ""))
|
|
140
|
-
return true;
|
|
141
|
-
if (packagePath.length < 2)
|
|
142
|
-
return false;
|
|
143
|
-
const first = packagePath[0];
|
|
144
|
-
if (first === undefined || !SUPPORT_DIRS.has(first))
|
|
145
|
-
return false;
|
|
146
|
-
return packagePath.every((segment) => segment !== "." && segment !== ".." && segment.length > 0);
|
|
147
|
-
}
|
|
148
|
-
export async function readSyncManifest() {
|
|
149
|
-
try {
|
|
150
|
-
await ensureSyncManifestDir();
|
|
151
|
-
const handle = await open(syncManifestPath(), constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
152
|
-
let body;
|
|
153
|
-
try {
|
|
154
|
-
body = await handle.readFile("utf8");
|
|
155
|
-
}
|
|
156
|
-
finally {
|
|
157
|
-
await handle.close();
|
|
158
|
-
}
|
|
159
|
-
const parsed = JSON.parse(body);
|
|
160
|
-
if (parsed.version !== MANIFEST_VERSION || !parsed.files || typeof parsed.files !== "object") {
|
|
161
|
-
return emptyManifest();
|
|
162
|
-
}
|
|
163
|
-
const manifest = emptyManifest();
|
|
164
|
-
for (const [key, value] of Object.entries(parsed.files)) {
|
|
165
|
-
if (isEntryForKey(key, value))
|
|
166
|
-
manifest.files[key] = value;
|
|
167
|
-
}
|
|
168
|
-
return manifest;
|
|
169
|
-
}
|
|
170
|
-
catch (err) {
|
|
171
|
-
if (err.code === "ENOENT")
|
|
172
|
-
return emptyManifest();
|
|
173
|
-
if (err instanceof SyntaxError)
|
|
174
|
-
return emptyManifest();
|
|
175
|
-
throw err;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
export async function writeSyncManifest(manifest) {
|
|
179
|
-
await ensureSyncManifestDir();
|
|
180
|
-
const path = syncManifestPath();
|
|
181
|
-
const dirPath = dirname(path);
|
|
182
|
-
const targetName = basename(path);
|
|
183
|
-
const dir = await open(dirPath, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW);
|
|
184
|
-
const tmpBase = `${targetName}.${process.pid}.${Date.now()}`;
|
|
185
|
-
const body = JSON.stringify(manifest, null, 2);
|
|
186
|
-
try {
|
|
187
|
-
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
188
|
-
const tmpName = attempt === 0 ? `${tmpBase}.tmp` : `${tmpBase}.${attempt}.tmp`;
|
|
189
|
-
const tmpPath = childPath(dir, dirPath, tmpName);
|
|
190
|
-
let handle = null;
|
|
191
|
-
try {
|
|
192
|
-
handle = await open(tmpPath, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
|
|
193
|
-
await handle.writeFile(body, "utf8");
|
|
194
|
-
await handle.close();
|
|
195
|
-
handle = null;
|
|
196
|
-
await rename(tmpPath, childPath(dir, dirPath, targetName));
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
catch (err) {
|
|
200
|
-
await handle?.close().catch(() => { });
|
|
201
|
-
if (err.code === "EEXIST")
|
|
202
|
-
continue;
|
|
203
|
-
throw err;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
finally {
|
|
208
|
-
await dir.close();
|
|
209
|
-
}
|
|
210
|
-
const err = new Error("temporary sync manifest file already exists");
|
|
211
|
-
err.code = "EEXIST";
|
|
212
|
-
throw err;
|
|
213
|
-
}
|
|
214
|
-
function childPath(parent, fallbackParent, name) {
|
|
215
|
-
if (process.platform === "linux")
|
|
216
|
-
return `${FD_PATH_ROOT}/${parent.fd}/${name}`;
|
|
217
|
-
return join(resolve(fallbackParent), name);
|
|
218
|
-
}
|
|
219
|
-
export async function ensureSyncManifestDir() {
|
|
220
|
-
const dir = syncManifestDir();
|
|
221
|
-
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
222
|
-
const stat = await lstat(dir);
|
|
223
|
-
if (stat.isSymbolicLink()) {
|
|
224
|
-
const err = new Error("sync manifest directory is a symbolic link");
|
|
225
|
-
err.code = "ELOOP";
|
|
226
|
-
throw err;
|
|
227
|
-
}
|
|
228
|
-
if (!stat.isDirectory()) {
|
|
229
|
-
const err = new Error("sync manifest path is blocked by an existing local file");
|
|
230
|
-
err.code = "ENOTDIR";
|
|
231
|
-
throw err;
|
|
232
|
-
}
|
|
233
|
-
}
|
|
234
|
-
export async function withSyncLock(fn) {
|
|
235
|
-
await ensureSyncManifestDir();
|
|
236
|
-
const lockPath = syncLockPath();
|
|
237
|
-
const startedAt = Date.now();
|
|
238
|
-
for (;;) {
|
|
239
|
-
try {
|
|
240
|
-
await mkdir(lockPath, { mode: 0o700 });
|
|
241
|
-
break;
|
|
242
|
-
}
|
|
243
|
-
catch (err) {
|
|
244
|
-
if (err.code !== "EEXIST")
|
|
245
|
-
throw err;
|
|
246
|
-
try {
|
|
247
|
-
const lockStat = await stat(lockPath);
|
|
248
|
-
if (Date.now() - lockStat.mtimeMs > LOCK_STALE_MS) {
|
|
249
|
-
await rm(lockPath, { recursive: true, force: true });
|
|
250
|
-
continue;
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
catch (statErr) {
|
|
254
|
-
if (statErr.code === "ENOENT")
|
|
255
|
-
continue;
|
|
256
|
-
throw statErr;
|
|
257
|
-
}
|
|
258
|
-
if (Date.now() - startedAt > LOCK_TIMEOUT_MS) {
|
|
259
|
-
throw new Error("Timed out waiting for Floom sync lock.");
|
|
260
|
-
}
|
|
261
|
-
await new Promise((resolveDelay) => setTimeout(resolveDelay, 50));
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
const signalHandlers = new Map();
|
|
265
|
-
const cleanupSync = () => {
|
|
266
|
-
try {
|
|
267
|
-
rmSync(lockPath, { recursive: true, force: true });
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
// Best-effort signal cleanup. The normal finally path reports real errors.
|
|
271
|
-
}
|
|
272
|
-
};
|
|
273
|
-
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
274
|
-
const handler = () => {
|
|
275
|
-
cleanupSync();
|
|
276
|
-
process.exit(signal === "SIGINT" ? 130 : 143);
|
|
277
|
-
};
|
|
278
|
-
signalHandlers.set(signal, handler);
|
|
279
|
-
process.once(signal, handler);
|
|
280
|
-
}
|
|
281
|
-
const heartbeat = setInterval(() => {
|
|
282
|
-
const now = new Date();
|
|
283
|
-
void utimes(lockPath, now, now).catch(() => { });
|
|
284
|
-
}, LOCK_HEARTBEAT_MS);
|
|
285
|
-
heartbeat.unref?.();
|
|
286
|
-
try {
|
|
287
|
-
return await fn();
|
|
288
|
-
}
|
|
289
|
-
finally {
|
|
290
|
-
clearInterval(heartbeat);
|
|
291
|
-
for (const [signal, handler] of signalHandlers) {
|
|
292
|
-
process.off(signal, handler);
|
|
293
|
-
}
|
|
294
|
-
await rm(lockPath, { recursive: true, force: true }).catch(() => { });
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
export function manifestKey(root, target) {
|
|
298
|
-
const relativeTarget = relative(resolve(root), resolve(target));
|
|
299
|
-
if (relativeTarget === ".." || relativeTarget.startsWith(`..${sep}`)) {
|
|
300
|
-
throw new Error("Invalid manifest target path.");
|
|
301
|
-
}
|
|
302
|
-
return relativeTarget.split(sep).join("/");
|
|
303
|
-
}
|
|
304
|
-
export function markSynced(manifest, key, slug, hash) {
|
|
305
|
-
manifest.files[key] = {
|
|
306
|
-
hash,
|
|
307
|
-
slug,
|
|
308
|
-
target: key,
|
|
309
|
-
syncedAt: new Date().toISOString(),
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
export function unmarkSynced(manifest, key) {
|
|
313
|
-
delete manifest.files[key];
|
|
314
|
-
}
|