@floomhq/floom 1.0.9 → 1.0.11
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/bin/floom.js +0 -0
- package/dist/cli.js +32 -7
- package/dist/login.js +13 -2
- package/dist/secrets.js +3 -0
- package/package.json +1 -1
package/bin/floom.js
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
|
@@ -102,6 +102,7 @@ function commandUsage() {
|
|
|
102
102
|
${c.dim(" From a repo, setup writes that repo's CLAUDE.md/AGENTS.md")}
|
|
103
103
|
${c.cyan("agent-prompt")} Print the sentence to paste into your agent
|
|
104
104
|
${c.dim("Alias: paste")}
|
|
105
|
+
${c.dim("Flags: --target claude|codex")}
|
|
105
106
|
${c.cyan("doctor")} Troubleshoot auth, API, and local folders
|
|
106
107
|
${c.dim("Flags: --json")}
|
|
107
108
|
|
|
@@ -130,6 +131,7 @@ function commandUsage() {
|
|
|
130
131
|
}
|
|
131
132
|
const ASSET_TYPES = new Set(["knowledge", "instruction", "workflow", "skill"]);
|
|
132
133
|
const INIT_TEMPLATES = new Set(["generic", "brand-voice", "pr-review", "sales", "support", "onboarding"]);
|
|
134
|
+
const MAX_SHARE_RECIPIENTS = 25;
|
|
133
135
|
const INSTALL_TARGETS = new Set([
|
|
134
136
|
"claude_skill",
|
|
135
137
|
"memory",
|
|
@@ -206,8 +208,8 @@ function parseFlags(argv) {
|
|
|
206
208
|
}
|
|
207
209
|
if (out.shareEmails.length > 0) {
|
|
208
210
|
out.shareEmails = dedupeEmails(out.shareEmails);
|
|
209
|
-
if (out.shareEmails.length >
|
|
210
|
-
throw new FloomError("Too many --share recipients.",
|
|
211
|
+
if (out.shareEmails.length > MAX_SHARE_RECIPIENTS) {
|
|
212
|
+
throw new FloomError("Too many --share recipients.", `Use ${MAX_SHARE_RECIPIENTS} email addresses or fewer.`);
|
|
211
213
|
}
|
|
212
214
|
if (out.visibility === "private") {
|
|
213
215
|
throw new FloomError("`--private --share` would email a link recipients cannot open.", "Use `--unlisted --share` for invite emails, or `npx -y @floomhq/floom share <slug> --add <email>` for email-gated access after publishing.");
|
|
@@ -372,6 +374,27 @@ function parseDoctorArgs(argv) {
|
|
|
372
374
|
}
|
|
373
375
|
return out;
|
|
374
376
|
}
|
|
377
|
+
function parseAgentPromptArgs(argv) {
|
|
378
|
+
const out = {};
|
|
379
|
+
for (let i = 0; i < argv.length; i++) {
|
|
380
|
+
const a = argv[i] ?? "";
|
|
381
|
+
if (a === "--target" || a.startsWith("--target=")) {
|
|
382
|
+
const { value, nextIndex } = readFlagValue(argv, i, "--target");
|
|
383
|
+
if (value !== "claude" && value !== "codex") {
|
|
384
|
+
throw new FloomError("Invalid --target.", "Use `claude` or `codex`.");
|
|
385
|
+
}
|
|
386
|
+
out.target = value;
|
|
387
|
+
i = nextIndex;
|
|
388
|
+
}
|
|
389
|
+
else if (a.startsWith("--")) {
|
|
390
|
+
throw new FloomError(`Unknown flag: ${a}`, "Try `npx -y @floomhq/floom agent-prompt --target codex`.");
|
|
391
|
+
}
|
|
392
|
+
else {
|
|
393
|
+
throw new FloomError(`Unexpected argument: ${a}`, "Try `npx -y @floomhq/floom agent-prompt --target codex`.");
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
return out;
|
|
397
|
+
}
|
|
375
398
|
function parseSearchFlags(argv) {
|
|
376
399
|
const out = { json: false };
|
|
377
400
|
const terms = [];
|
|
@@ -636,8 +659,9 @@ function parseSingleFileArg(argv, usageHint) {
|
|
|
636
659
|
throw new FloomError("Missing file argument.", usageHint);
|
|
637
660
|
return file;
|
|
638
661
|
}
|
|
639
|
-
function agentPrompt() {
|
|
640
|
-
|
|
662
|
+
function agentPrompt(target = "claude") {
|
|
663
|
+
const folder = target === "codex" ? "~/.codex/skills" : "~/.claude/skills";
|
|
664
|
+
process.stdout.write(`Use my installed Floom skills when they fit the task. Search ${folder} first.\n`);
|
|
641
665
|
}
|
|
642
666
|
function sleep(ms, signal) {
|
|
643
667
|
if (signal.aborted)
|
|
@@ -829,10 +853,11 @@ async function main() {
|
|
|
829
853
|
return;
|
|
830
854
|
}
|
|
831
855
|
case "agent-prompt":
|
|
832
|
-
case "paste":
|
|
833
|
-
|
|
834
|
-
agentPrompt();
|
|
856
|
+
case "paste": {
|
|
857
|
+
const flags = parseAgentPromptArgs(rest);
|
|
858
|
+
agentPrompt(flags.target ?? "claude");
|
|
835
859
|
return;
|
|
860
|
+
}
|
|
836
861
|
case "watch": {
|
|
837
862
|
const flags = parseWatchFlags(rest);
|
|
838
863
|
await watch(flags.intervalSeconds);
|
package/dist/login.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createServer } from "node:http";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
2
3
|
import open from "open";
|
|
3
4
|
import ora from "ora";
|
|
4
5
|
import { getApiUrl, writeConfig } from "./config.js";
|
|
@@ -59,6 +60,7 @@ export async function login() {
|
|
|
59
60
|
function waitForCallback() {
|
|
60
61
|
return new Promise((resolve, reject) => {
|
|
61
62
|
const apiUrl = getApiUrl();
|
|
63
|
+
const state = randomBytes(24).toString("base64url");
|
|
62
64
|
let settled = false;
|
|
63
65
|
let retriedEphemeralPort = false;
|
|
64
66
|
const server = createServer((req, res) => {
|
|
@@ -90,6 +92,15 @@ function waitForCallback() {
|
|
|
90
92
|
res.end(localCallbackPage("Missing tokens from OAuth response."));
|
|
91
93
|
return;
|
|
92
94
|
}
|
|
95
|
+
if (data.state !== state) {
|
|
96
|
+
res.writeHead(400, {
|
|
97
|
+
"access-control-allow-origin": origin,
|
|
98
|
+
"access-control-allow-private-network": "true",
|
|
99
|
+
"content-type": "text/html; charset=utf-8",
|
|
100
|
+
});
|
|
101
|
+
res.end(localCallbackPage("Invalid OAuth state."));
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
93
104
|
res.writeHead(200, {
|
|
94
105
|
"access-control-allow-origin": origin,
|
|
95
106
|
"access-control-allow-private-network": "true",
|
|
@@ -143,7 +154,7 @@ function waitForCallback() {
|
|
|
143
154
|
return;
|
|
144
155
|
}
|
|
145
156
|
const port = address.port;
|
|
146
|
-
const target = `${apiUrl}/auth/cli?port=${port}`;
|
|
157
|
+
const target = `${apiUrl}/auth/cli?port=${port}&state=${encodeURIComponent(state)}`;
|
|
147
158
|
open(target).catch((e) => {
|
|
148
159
|
const msg = e instanceof Error ? e.message : String(e);
|
|
149
160
|
process.stdout.write(c.yellow(`Could not auto-open browser (${msg}).\n`) +
|
|
@@ -157,7 +168,7 @@ function parseCallbackBody(body, contentType) {
|
|
|
157
168
|
if (type.includes("application/x-www-form-urlencoded")) {
|
|
158
169
|
const params = new URLSearchParams(body);
|
|
159
170
|
const parsed = {};
|
|
160
|
-
for (const key of ["access_token", "refresh_token", "expires_in", "token_type"]) {
|
|
171
|
+
for (const key of ["access_token", "refresh_token", "expires_in", "token_type", "state"]) {
|
|
161
172
|
const value = params.get(key);
|
|
162
173
|
if (value)
|
|
163
174
|
parsed[key] = value;
|
package/dist/secrets.js
CHANGED
|
@@ -37,8 +37,11 @@ const PROMPT_INJECTION_PATTERNS = [
|
|
|
37
37
|
];
|
|
38
38
|
const DATA_EXFILTRATION_PATTERNS = [
|
|
39
39
|
{ label: "Data exfiltration instruction", regex: /\b(?:send|post|upload|exfiltrate|copy) (?:[^.\n]{0,80})\b(?:api keys?|tokens?|secrets?|environment variables|\.env|credentials)\b(?:[^.\n]{0,120})\b(?:to|into) https?:\/\//gi },
|
|
40
|
+
{ label: "Data exfiltration instruction", regex: /\b(?:send|post|upload|exfiltrate|copy)\b[^.\n]{0,120}(?:~\/\.ssh\/[A-Za-z0-9_.-]+|id_rsa|id_ed25519|ssh keys?|private keys?|secret files?)\b[^.\n]{0,120}\b(?:to|into) https?:\/\//gi },
|
|
40
41
|
{ label: "Data exfiltration instruction", regex: /\b(?:curl|wget|fetch)\b[^\n]{0,160}\b(?:api keys?|tokens?|secrets?|environment variables|\.env|credentials)\b/gi },
|
|
42
|
+
{ label: "Data exfiltration instruction", regex: /\b(?:curl|wget|fetch)\b[^\n]{0,160}(?:~\/\.ssh\/[A-Za-z0-9_.-]+|id_rsa|id_ed25519|ssh keys?|private keys?|secret files?)\b/gi },
|
|
41
43
|
{ label: "Credential harvesting instruction", regex: /\b(?:collect|harvest|steal|extract) (?:[^.\n]{0,80})\b(?:api keys?|tokens?|secrets?|environment variables|\.env|credentials)\b/gi },
|
|
44
|
+
{ label: "Credential harvesting instruction", regex: /\b(?:collect|harvest|steal|extract)\b[^.\n]{0,120}(?:~\/\.ssh\/[A-Za-z0-9_.-]+|id_rsa|id_ed25519|ssh keys?|private keys?|secret files?)\b/gi },
|
|
42
45
|
];
|
|
43
46
|
function redact(value) {
|
|
44
47
|
if (value.length <= 12)
|