@floomhq/floom 1.0.64 → 2.0.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/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/login.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
import { createServer } from "node:http";
|
|
2
|
-
import { createServer as createNetServer } from "node:net";
|
|
3
|
-
import open from "open";
|
|
4
|
-
import ora from "ora";
|
|
5
|
-
import { getApiUrl, writeConfig } from "./config.js";
|
|
6
|
-
import { c, header, symbols } from "./ui.js";
|
|
7
|
-
import { FloomError, friendlyHttp, friendlyNetwork } from "./errors.js";
|
|
8
|
-
const DEFAULT_PORT = 7456;
|
|
9
|
-
const TIMEOUT_MS = 5 * 60 * 1000;
|
|
10
|
-
export async function login() {
|
|
11
|
-
const apiUrl = getApiUrl();
|
|
12
|
-
const port = await pickPort();
|
|
13
|
-
process.stdout.write(header());
|
|
14
|
-
process.stdout.write(`${symbols.arrow} Opening browser to sign in with Google...\n\n`);
|
|
15
|
-
const spinner = ora({
|
|
16
|
-
text: c.dim("Waiting for sign-in to complete..."),
|
|
17
|
-
color: "yellow",
|
|
18
|
-
}).start();
|
|
19
|
-
let tokens;
|
|
20
|
-
try {
|
|
21
|
-
tokens = await waitForCallback(port);
|
|
22
|
-
}
|
|
23
|
-
catch (err) {
|
|
24
|
-
spinner.stop();
|
|
25
|
-
if (err instanceof Error && /timed out/i.test(err.message)) {
|
|
26
|
-
throw new FloomError("No worries - try `npx -y @floomhq/floom login` again when ready.");
|
|
27
|
-
}
|
|
28
|
-
throw err;
|
|
29
|
-
}
|
|
30
|
-
spinner.text = c.dim("Verifying your account...");
|
|
31
|
-
const expiresIn = Number(tokens.expires_in ?? "3600");
|
|
32
|
-
const expiresAt = Math.floor(Date.now() / 1000) + (Number.isFinite(expiresIn) ? expiresIn : 3600);
|
|
33
|
-
let me;
|
|
34
|
-
try {
|
|
35
|
-
const meRes = await fetch(`${apiUrl}/api/me`, {
|
|
36
|
-
headers: { authorization: `Bearer ${tokens.access_token}` },
|
|
37
|
-
});
|
|
38
|
-
if (!meRes.ok) {
|
|
39
|
-
spinner.stop();
|
|
40
|
-
throw friendlyHttp(meRes.status, "verify your sign-in");
|
|
41
|
-
}
|
|
42
|
-
me = (await meRes.json());
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
spinner.stop();
|
|
46
|
-
if (err instanceof FloomError)
|
|
47
|
-
throw err;
|
|
48
|
-
throw friendlyNetwork(err);
|
|
49
|
-
}
|
|
50
|
-
await writeConfig({
|
|
51
|
-
apiUrl,
|
|
52
|
-
accessToken: tokens.access_token,
|
|
53
|
-
refreshToken: tokens.refresh_token,
|
|
54
|
-
expiresAt,
|
|
55
|
-
email: me.email,
|
|
56
|
-
});
|
|
57
|
-
spinner.stop();
|
|
58
|
-
process.stdout.write(`${symbols.ok} Signed in as ${c.bold(me.email ?? me.id)}\n`);
|
|
59
|
-
process.stdout.write(` ${c.dim("Your token is saved at ~/.floom/config.json")}\n\n`);
|
|
60
|
-
}
|
|
61
|
-
/** Reserve a free port. Prefers 7456 for existing Supabase CLI auth setups. */
|
|
62
|
-
async function pickPort() {
|
|
63
|
-
if (await canListen(DEFAULT_PORT))
|
|
64
|
-
return DEFAULT_PORT;
|
|
65
|
-
return reserveEphemeralPort();
|
|
66
|
-
}
|
|
67
|
-
function canListen(port) {
|
|
68
|
-
return new Promise((resolve) => {
|
|
69
|
-
const server = createNetServer();
|
|
70
|
-
server.once("error", () => resolve(false));
|
|
71
|
-
server.listen(port, "127.0.0.1", () => {
|
|
72
|
-
server.close(() => resolve(true));
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
function reserveEphemeralPort() {
|
|
77
|
-
return new Promise((resolve, reject) => {
|
|
78
|
-
const server = createNetServer();
|
|
79
|
-
server.once("error", reject);
|
|
80
|
-
server.listen(0, "127.0.0.1", () => {
|
|
81
|
-
const address = server.address();
|
|
82
|
-
if (!address || typeof address === "string") {
|
|
83
|
-
server.close();
|
|
84
|
-
reject(new FloomError("Could not reserve a local sign-in port."));
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
|
-
const port = address.port;
|
|
88
|
-
server.close(() => resolve(port));
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
function waitForCallback(port) {
|
|
93
|
-
return new Promise((resolve, reject) => {
|
|
94
|
-
const apiUrl = getApiUrl();
|
|
95
|
-
let settled = false;
|
|
96
|
-
const server = createServer((req, res) => {
|
|
97
|
-
// CORS preflight from the browser bridge page.
|
|
98
|
-
const origin = req.headers.origin ?? "*";
|
|
99
|
-
if (req.method === "OPTIONS") {
|
|
100
|
-
res.writeHead(204, {
|
|
101
|
-
"access-control-allow-origin": origin,
|
|
102
|
-
"access-control-allow-methods": "POST, OPTIONS",
|
|
103
|
-
"access-control-allow-headers": "content-type",
|
|
104
|
-
"access-control-allow-private-network": "true",
|
|
105
|
-
"access-control-max-age": "600",
|
|
106
|
-
});
|
|
107
|
-
res.end();
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
if (req.method === "GET" && req.url?.startsWith("/cli-callback?")) {
|
|
111
|
-
try {
|
|
112
|
-
const data = parseCallbackQuery(req.url);
|
|
113
|
-
if (!data.access_token || !data.refresh_token) {
|
|
114
|
-
res.writeHead(400, {
|
|
115
|
-
"content-type": "text/html; charset=utf-8",
|
|
116
|
-
});
|
|
117
|
-
res.end(localCallbackPage("Missing tokens from OAuth response."));
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
res.writeHead(200, {
|
|
121
|
-
"content-type": "text/html; charset=utf-8",
|
|
122
|
-
"referrer-policy": "no-referrer",
|
|
123
|
-
});
|
|
124
|
-
res.end(localCallbackPage("Signed in. You can close this tab."));
|
|
125
|
-
settled = true;
|
|
126
|
-
cleanup();
|
|
127
|
-
resolve(data);
|
|
128
|
-
}
|
|
129
|
-
catch {
|
|
130
|
-
res.writeHead(400, { "content-type": "text/html; charset=utf-8" });
|
|
131
|
-
res.end(localCallbackPage("Invalid OAuth response."));
|
|
132
|
-
}
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
if (req.method === "POST" && req.url === "/cli-callback") {
|
|
136
|
-
let body = "";
|
|
137
|
-
req.on("data", (chunk) => { body += chunk.toString("utf8"); });
|
|
138
|
-
req.on("end", () => {
|
|
139
|
-
try {
|
|
140
|
-
const data = parseCallbackBody(body, req.headers["content-type"]);
|
|
141
|
-
if (!data.access_token || !data.refresh_token) {
|
|
142
|
-
res.writeHead(400, {
|
|
143
|
-
"access-control-allow-origin": origin,
|
|
144
|
-
"access-control-allow-private-network": "true",
|
|
145
|
-
"content-type": "text/html; charset=utf-8",
|
|
146
|
-
});
|
|
147
|
-
res.end(localCallbackPage("Missing tokens from OAuth response."));
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
res.writeHead(200, {
|
|
151
|
-
"access-control-allow-origin": origin,
|
|
152
|
-
"access-control-allow-private-network": "true",
|
|
153
|
-
"content-type": "text/html; charset=utf-8",
|
|
154
|
-
});
|
|
155
|
-
res.end(localCallbackPage("Signed in. You can close this tab."));
|
|
156
|
-
settled = true;
|
|
157
|
-
cleanup();
|
|
158
|
-
resolve(data);
|
|
159
|
-
}
|
|
160
|
-
catch {
|
|
161
|
-
res.writeHead(400, { "content-type": "text/html; charset=utf-8", "access-control-allow-origin": origin });
|
|
162
|
-
res.end(localCallbackPage("Invalid OAuth response."));
|
|
163
|
-
}
|
|
164
|
-
});
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
res.writeHead(404, { "content-type": "text/plain" });
|
|
168
|
-
res.end("Not found");
|
|
169
|
-
});
|
|
170
|
-
const timer = setTimeout(() => {
|
|
171
|
-
if (settled)
|
|
172
|
-
return;
|
|
173
|
-
settled = true;
|
|
174
|
-
cleanup();
|
|
175
|
-
reject(new Error("Login timed out after 5 minutes."));
|
|
176
|
-
}, TIMEOUT_MS);
|
|
177
|
-
function cleanup() {
|
|
178
|
-
clearTimeout(timer);
|
|
179
|
-
server.close();
|
|
180
|
-
}
|
|
181
|
-
server.on("error", (err) => {
|
|
182
|
-
if (settled)
|
|
183
|
-
return;
|
|
184
|
-
settled = true;
|
|
185
|
-
clearTimeout(timer);
|
|
186
|
-
reject(new FloomError(`Local auth server failed on port ${port}.`, `Is port ${port} already in use? (${err.message})`));
|
|
187
|
-
});
|
|
188
|
-
server.listen(port, "127.0.0.1", () => {
|
|
189
|
-
const target = `${apiUrl}/auth/cli?port=${port}`;
|
|
190
|
-
open(target).catch((e) => {
|
|
191
|
-
const msg = e instanceof Error ? e.message : String(e);
|
|
192
|
-
process.stdout.write(c.yellow(`Could not auto-open browser (${msg}).\n`) +
|
|
193
|
-
c.dim(`Open this URL manually: ${target}\n`));
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
function parseCallbackQuery(url) {
|
|
199
|
-
const parsed = new URL(url, "http://127.0.0.1");
|
|
200
|
-
const result = {};
|
|
201
|
-
for (const key of ["access_token", "refresh_token", "expires_in", "token_type"]) {
|
|
202
|
-
const value = parsed.searchParams.get(key);
|
|
203
|
-
if (value)
|
|
204
|
-
result[key] = value;
|
|
205
|
-
}
|
|
206
|
-
return result;
|
|
207
|
-
}
|
|
208
|
-
function parseCallbackBody(body, contentType) {
|
|
209
|
-
const type = Array.isArray(contentType) ? contentType.join(";") : contentType ?? "";
|
|
210
|
-
if (type.includes("application/x-www-form-urlencoded")) {
|
|
211
|
-
const params = new URLSearchParams(body);
|
|
212
|
-
const parsed = {};
|
|
213
|
-
for (const key of ["access_token", "refresh_token", "expires_in", "token_type"]) {
|
|
214
|
-
const value = params.get(key);
|
|
215
|
-
if (value)
|
|
216
|
-
parsed[key] = value;
|
|
217
|
-
}
|
|
218
|
-
return parsed;
|
|
219
|
-
}
|
|
220
|
-
return JSON.parse(body);
|
|
221
|
-
}
|
|
222
|
-
function localCallbackPage(message) {
|
|
223
|
-
const safeMessage = escapeHtml(message);
|
|
224
|
-
return `<!doctype html>
|
|
225
|
-
<html lang="en">
|
|
226
|
-
<head>
|
|
227
|
-
<meta charset="utf-8">
|
|
228
|
-
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
229
|
-
<title>Floom CLI sign-in</title>
|
|
230
|
-
<style>
|
|
231
|
-
:root { color-scheme: light; --ink: #111827; --muted: #4b5563; --line: #e5e7eb; --accent: #0f766e; --bg: #f8fafc; }
|
|
232
|
-
* { box-sizing: border-box; }
|
|
233
|
-
body { margin: 0; min-height: 100vh; display: grid; place-items: center; background: var(--bg); color: var(--ink); font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
|
|
234
|
-
main { width: min(560px, calc(100vw - 40px)); padding: 40px; background: #fff; border: 1px solid var(--line); border-radius: 16px; box-shadow: 0 24px 80px rgba(15, 23, 42, 0.08); }
|
|
235
|
-
.mark { display: inline-flex; align-items: center; gap: 10px; margin-bottom: 28px; font-weight: 700; letter-spacing: 0.02em; }
|
|
236
|
-
.dot { width: 10px; height: 10px; border-radius: 999px; background: var(--accent); display: inline-block; }
|
|
237
|
-
h1 { margin: 0; font-size: clamp(32px, 7vw, 56px); line-height: 0.95; letter-spacing: 0; }
|
|
238
|
-
p { margin: 20px 0 0; font-size: 18px; line-height: 1.55; color: var(--muted); }
|
|
239
|
-
code { display: inline-block; margin-top: 24px; padding: 10px 12px; border: 1px solid var(--line); border-radius: 8px; background: #f9fafb; color: var(--ink); font-size: 14px; }
|
|
240
|
-
</style>
|
|
241
|
-
</head>
|
|
242
|
-
<body>
|
|
243
|
-
<main>
|
|
244
|
-
<div class="mark"><span class="dot"></span><span>floom</span></div>
|
|
245
|
-
<h1>${safeMessage}</h1>
|
|
246
|
-
<p>Return to your terminal to continue. Your Floom CLI now has the token it needs.</p>
|
|
247
|
-
<code>floom doctor</code>
|
|
248
|
-
</main>
|
|
249
|
-
</body>
|
|
250
|
-
</html>`;
|
|
251
|
-
}
|
|
252
|
-
function escapeHtml(value) {
|
|
253
|
-
return value
|
|
254
|
-
.replace(/&/g, "&")
|
|
255
|
-
.replace(/</g, "<")
|
|
256
|
-
.replace(/>/g, ">")
|
|
257
|
-
.replace(/"/g, """)
|
|
258
|
-
.replace(/'/g, "'");
|
|
259
|
-
}
|
package/dist/mcp.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { c } from "./ui.js";
|
|
2
|
-
export function printMcpSetup() {
|
|
3
|
-
const snippet = `## Floom
|
|
4
|
-
- Use Floom skills from the local Floom skills folder when they match the task.
|
|
5
|
-
- To install a shared skill, run \`npx -y @floomhq/floom add <slug-or-url> --target <claude|codex|cursor|opencode|kimi>\`.
|
|
6
|
-
- To find reusable behavior, run \`npx -y @floomhq/floom search <query>\`.
|
|
7
|
-
- MCP sync uses the skills directory configured for that agent; set \`FLOOM_SKILLS_DIR\` or the agent-specific env var when registering the server.`;
|
|
8
|
-
process.stdout.write(`\n${c.bold("Floom MCP setup")}\n\n`);
|
|
9
|
-
process.stdout.write(`${c.dim("Pick your tool:")}\n\n`);
|
|
10
|
-
process.stdout.write(` ${c.bold("Claude Code")}\n`);
|
|
11
|
-
process.stdout.write(` ${c.cyan("claude mcp add floom -- npx -y @floomhq/floom-mcp-sync")}\n\n`);
|
|
12
|
-
process.stdout.write(` ${c.bold("Codex CLI")}\n`);
|
|
13
|
-
process.stdout.write(` ${c.cyan("codex mcp add floom -- npx -y @floomhq/floom-mcp-sync")}\n\n`);
|
|
14
|
-
process.stdout.write(` ${c.bold("Cursor / OpenCode / Kimi")}\n`);
|
|
15
|
-
process.stdout.write(` ${c.cyan("Run the agent's MCP registration flow with FLOOM_SKILLS_DIR set to its skills root.")}\n`);
|
|
16
|
-
process.stdout.write(` ${c.dim("Examples: ~/.cursor/skills-cursor, ~/.config/opencode/skills, ~/.kimi/skills")}\n\n`);
|
|
17
|
-
process.stdout.write(`${c.dim("Full guide:")} ${c.cyan("https://floom.dev/docs/mcp")}\n\n`);
|
|
18
|
-
process.stdout.write(`${c.dim("Recommended agent instruction snippet:")}\n\n`);
|
|
19
|
-
process.stdout.write(`${snippet}\n\n`);
|
|
20
|
-
}
|