securevibe 0.1.14 → 0.1.15

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/README.md CHANGED
@@ -17,8 +17,9 @@ securevibe fix <path> --apply # apply + back up originals (asks per change)
17
17
  From a clone of this repo instead: `pnpm install && pnpm build`, then
18
18
  `node packages/cli/dist/index.js scan <path>` (`pnpm demo` scans the bundled sample).
19
19
 
20
- The free beta includes a daily usage limit (50 analyses per day, reset at local
21
- midnight). The `--staged` commit guard is never limited. See [`LICENSE`](LICENSE).
20
+ The free beta includes a daily usage limit, reset at local midnight (the current count and
21
+ limit are shown after each run; the limit itself is remotely adjustable and not a fixed
22
+ number, see below). The `--staged` commit guard is never limited. See [`LICENSE`](LICENSE).
22
23
 
23
24
  ## Commands
24
25
 
@@ -147,7 +148,11 @@ against a local copy of the [OSV](https://osv.dev) advisory database, and report
147
148
  no network. `db update` and `update` are the only commands that **download** anything (OSV
148
149
  advisory dumps, or the CLI's own next version, respectively). `pr-comment` is the only command
149
150
  that **uploads** anything, and only a findings summary already computed locally — never your
150
- code or package list.
151
+ code or package list. Most other commands also make one small background check: a newer
152
+ version notice (the npm registry, cached a day, exempt under `--json`/`--sarif`/`--staged`)
153
+ and the current daily usage limit (a public read only endpoint, cached an hour, exempt under
154
+ `--staged`/`--diff`). Both are tiny GET requests for a version string or a number, and never
155
+ send your code, your findings, or anything else about your project.
151
156
  - **Honest framing.** A clean result reads "no known advisories as of `<db date>`", not "secure" —
152
157
  it means nothing matched the local database on that date, which is not a proof of safety.
153
158
 
package/dist/index.js CHANGED
@@ -38,7 +38,7 @@ async function meter(opts = {}) {
38
38
  const { recordUse, limitMessage } = await import("./usage.js");
39
39
  const r = await recordUse();
40
40
  if (!r.allowed) {
41
- process.stderr.write(limitMessage() + "\n");
41
+ process.stderr.write(limitMessage(r.limit) + "\n");
42
42
  process.exit(1);
43
43
  }
44
44
  return r;
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Remote, admin editable override for the daily usage limit. Same
3
+ * fetch/cache/TTL/fail-open shape as update-check.ts, checking a small
4
+ * public endpoint instead of the npm registry. Never throws — offline, a
5
+ * down endpoint, an invalid value, or a corrupt cache all resolve to the
6
+ * last known good cached value, or ultimately to the caller supplied
7
+ * fallback (usage.ts's DAILY_LIMIT).
8
+ */
9
+ import { promises as fs } from "node:fs";
10
+ import os from "node:os";
11
+ import path from "node:path";
12
+ const LIMITS_URL = "https://limits-config.vercel.app/api/limits";
13
+ const CACHE_TTL_MS = 60 * 60 * 1000;
14
+ const FETCH_TIMEOUT_MS = 1500;
15
+ export function cacheFilePath() {
16
+ return path.join(os.homedir(), ".securevibe", "remote-limit.json");
17
+ }
18
+ /** A remote value is only trusted if it's "unlimited" or a finite positive number. */
19
+ function isValidLimit(value) {
20
+ return value === "unlimited" || (typeof value === "number" && Number.isFinite(value) && value > 0);
21
+ }
22
+ async function readCache(file) {
23
+ try {
24
+ const raw = JSON.parse(await fs.readFile(file, "utf8"));
25
+ if (typeof raw?.checkedAt === "number" && isValidLimit(raw?.dailyLimit))
26
+ return raw;
27
+ }
28
+ catch {
29
+ /* missing or corrupt → treated as no cache */
30
+ }
31
+ return null;
32
+ }
33
+ async function writeCache(file, state) {
34
+ try {
35
+ await fs.mkdir(path.dirname(file), { recursive: true });
36
+ await fs.writeFile(file, JSON.stringify(state) + "\n", "utf8");
37
+ }
38
+ catch {
39
+ /* best effort — a failed write must never break the command */
40
+ }
41
+ }
42
+ export async function fetchRemoteLimit(fetchImpl) {
43
+ const controller = new AbortController();
44
+ const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
45
+ try {
46
+ const res = await fetchImpl(LIMITS_URL, { signal: controller.signal });
47
+ if (!res.ok)
48
+ return null;
49
+ const json = await res.json();
50
+ return isValidLimit(json?.dailyLimit) ? json.dailyLimit : null;
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ finally {
56
+ clearTimeout(timer);
57
+ }
58
+ }
59
+ /**
60
+ * Returns the effective daily limit: a fresh or cached remote override when
61
+ * one is known and valid, else the caller supplied fallback. Never throws.
62
+ */
63
+ export async function resolveDailyLimit(opts) {
64
+ const file = opts.file ?? cacheFilePath();
65
+ const now = opts.now ?? Date.now();
66
+ const fetchImpl = opts.fetchImpl ?? fetch;
67
+ const fallback = opts.fallback;
68
+ try {
69
+ const cached = await readCache(file);
70
+ if (cached && now - cached.checkedAt < CACHE_TTL_MS) {
71
+ return cached.dailyLimit;
72
+ }
73
+ const fetched = await fetchRemoteLimit(fetchImpl);
74
+ if (fetched !== null) {
75
+ await writeCache(file, { checkedAt: now, dailyLimit: fetched });
76
+ return fetched;
77
+ }
78
+ return cached ? cached.dailyLimit : fallback;
79
+ }
80
+ catch {
81
+ return fallback;
82
+ }
83
+ }
package/dist/repl.js CHANGED
@@ -35,7 +35,7 @@ async function meterOrPrint(io) {
35
35
  const { recordUse, limitMessage } = await import("./usage.js");
36
36
  const r = await recordUse();
37
37
  if (!r.allowed) {
38
- write(io, limitMessage() + "\n");
38
+ write(io, limitMessage(r.limit) + "\n");
39
39
  return undefined;
40
40
  }
41
41
  return r;
package/dist/ui/usage.js CHANGED
@@ -7,6 +7,6 @@ function bar(fraction, width = 20) {
7
7
  return color("█".repeat(filled)) + pc.dim("░".repeat(width - filled));
8
8
  }
9
9
  export function renderUsageLine(usage) {
10
- const fraction = usage.limit > 0 ? usage.used / usage.limit : 0;
10
+ const fraction = usage.limit === "unlimited" || usage.limit <= 0 ? 0 : usage.used / usage.limit;
11
11
  return pc.dim(" Free beta usage today ") + bar(fraction) + pc.dim(` ${usage.used}/${usage.limit}`);
12
12
  }
package/dist/usage.js CHANGED
@@ -4,10 +4,14 @@
4
4
  * design — there is no account system yet — so this establishes the metering
5
5
  * UX honestly without pretending to be tamper-proof. The --staged commit guard
6
6
  * is never metered: a quota must not block someone's commit.
7
+ *
8
+ * The enforced limit is resolved through remote-limit.ts on every call: an
9
+ * admin editable value if one is known and valid, else this file's DAILY_LIMIT.
7
10
  */
8
11
  import { promises as fs } from "node:fs";
9
12
  import os from "node:os";
10
13
  import path from "node:path";
14
+ import { resolveDailyLimit } from "./remote-limit.js";
11
15
  export const DAILY_LIMIT = 200;
12
16
  export function usageFilePath() {
13
17
  return path.join(os.homedir(), ".securevibe", "usage.json");
@@ -19,10 +23,10 @@ export function localDay(now = new Date()) {
19
23
  const d = String(now.getDate()).padStart(2, "0");
20
24
  return `${y}-${m}-${d}`;
21
25
  }
22
- /** Pure state transition: same day increments, a new day resets, limit blocks. */
26
+ /** Pure state transition: same day increments, a new day resets, limit blocks — "unlimited" never blocks. */
23
27
  export function nextUsage(state, today, limit = DAILY_LIMIT) {
24
28
  const base = state && state.date === today ? state : { date: today, count: 0 };
25
- if (base.count >= limit) {
29
+ if (limit !== "unlimited" && base.count >= limit) {
26
30
  return { state: base, result: { allowed: false, used: base.count, limit } };
27
31
  }
28
32
  const next = { date: today, count: base.count + 1 };
@@ -42,12 +46,13 @@ async function readState(file) {
42
46
  }
43
47
  /**
44
48
  * Record one use against today's quota. Returns whether the command may run.
45
- * Metering failures (unwritable home, etc.) never break the tool: on any IO
46
- * error the use is allowed.
49
+ * Metering failures (unwritable home, unreachable remote limit, etc.) never
50
+ * break the tool: on any IO error the use is allowed.
47
51
  */
48
- export async function recordUse(file = usageFilePath(), now = new Date()) {
52
+ export async function recordUse(file = usageFilePath(), now = new Date(), resolveLimit = () => resolveDailyLimit({ fallback: DAILY_LIMIT })) {
49
53
  try {
50
- const { state, result } = nextUsage(await readState(file), localDay(now));
54
+ const limit = await resolveLimit();
55
+ const { state, result } = nextUsage(await readState(file), localDay(now), limit);
51
56
  if (result.allowed) {
52
57
  await fs.mkdir(path.dirname(file), { recursive: true });
53
58
  await fs.writeFile(file, JSON.stringify(state) + "\n", "utf8");
package/dist/version.js CHANGED
@@ -2,4 +2,4 @@
2
2
  // Single source of truth for the CLI's own version, so it can't drift from
3
3
  // what commander reports and what the update-check compares against.
4
4
  // Kept in sync with the "version" field in package.json by hand at release time.
5
- export const VERSION = "0.1.14";
5
+ export const VERSION = "0.1.15";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "securevibe",
3
- "version": "0.1.14",
3
+ "version": "0.1.15",
4
4
  "description": "Autonomous AI security engineer for AI generated apps: scan, fix, verify, and gate every commit before you launch",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",