@gorlitzer-labs/comb 0.1.0 → 0.1.2

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
@@ -1,6 +1,6 @@
1
1
  # comb
2
2
 
3
- Keep your keys out of your conversations.
3
+ **the vault** — keep your keys out of your conversations. Keys by name, never by value.
4
4
 
5
5
  You paste a Cloudflare token to an agent so it can do a job. That token is now in
6
6
  a transcript file on your disk, in plaintext, forever. Do it enough times and you
@@ -19,6 +19,19 @@ comb run --with CF_API_TOKEN -- curl -H "Authorization: Bearer $CF_API_TOKEN" ..
19
19
  comb audit
20
20
  ```
21
21
 
22
+ ## Install
23
+
24
+ ```bash
25
+ npm i -g @gorlitzer-labs/comb
26
+ ```
27
+
28
+ Also needs `sops` and `age` on your `PATH`, and Node 20+ — see [Requires](#requires)
29
+ below.
30
+
31
+ `comb` is part of the
32
+ [`gorlitzer-labs/seldon`](https://github.com/gorlitzer-labs/seldon/tree/main/modules/comb)
33
+ monorepo, at `modules/comb`.
34
+
22
35
  ## Why not a secrets server
23
36
 
24
37
  Infisical and OpenBao are good. They are also a database, a cache, a patch
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gorlitzer-labs/comb",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Keep your keys out of your conversations. A small vault front-end over SOPS + age.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -17,9 +17,9 @@
17
17
  * matters. Everything else is recoverable; that is not. It never goes in the
18
18
  * store, it never goes in the repo.
19
19
  */
20
- import { execFileSync, spawnSync } from "node:child_process";
20
+ import { spawnSync } from "node:child_process";
21
21
  import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from "node:fs";
22
- import { join, dirname } from "node:path";
22
+ import { join } from "node:path";
23
23
  import { homedir } from "node:os";
24
24
 
25
25
  /** Where the encrypted store and the key live. Override for tests. */
package/src/cli.mjs CHANGED
@@ -23,6 +23,7 @@ import { createInterface } from "node:readline";
23
23
  import * as store from "./backends/sops.mjs";
24
24
  import * as realms from "./realms.mjs";
25
25
  import { scan, defaultTargets } from "./audit.mjs";
26
+ import { VERSION, checkAndNotify } from "./update-check.mjs";
26
27
 
27
28
  const B = (s) => `\x1b[1m${s}\x1b[0m`;
28
29
  const dim = (s) => `\x1b[2m${s}\x1b[0m`;
@@ -80,6 +81,7 @@ function help() {
80
81
  }
81
82
 
82
83
  const [cmd, ...rest] = process.argv.slice(2);
84
+ if (process.stdout.isTTY) checkAndNotify();
83
85
  const flags = {}; const pos = [];
84
86
  for (let i = 0; i < rest.length; i++) {
85
87
  const a = rest[i];
@@ -234,7 +236,7 @@ try {
234
236
  }
235
237
 
236
238
  case undefined: case "help": case "--help": case "-h": help(); break;
237
- case "version": case "--version": case "-v": say("comb 0.1.0"); break;
239
+ case "version": case "--version": case "-v": say(`comb ${VERSION}`); break;
238
240
  default: say(red(`unknown command: ${cmd}`)); help(); process.exit(2);
239
241
  }
240
242
  } catch (e) {
package/src/realms.mjs CHANGED
@@ -10,7 +10,7 @@
10
10
  // Revoking a realm is re-encrypting the store WITHOUT its key — no secret has to
11
11
  // be rotated, because that realm can no longer decrypt anything, old copies
12
12
  // included (its key never changes what the current ciphertext is encrypted to).
13
- import { execFileSync, spawnSync } from "node:child_process";
13
+ import { spawnSync } from "node:child_process";
14
14
  import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
15
15
  import { join } from "node:path";
16
16
  import { combHome, storePath, keyPath } from "./backends/sops.mjs";
@@ -0,0 +1,42 @@
1
+ // Throttled npm-registry update check — shared shape across the Seldon tools.
2
+ // Reads this package's own version, checks the registry at most once a day
3
+ // (cached under ~/.seldon), and prints a one-line notice to stderr if a newer
4
+ // version is out. Never blocks meaningfully, never throws.
5
+ import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
6
+ import { execFileSync } from "node:child_process";
7
+ import { fileURLToPath } from "node:url";
8
+ import { dirname, join } from "node:path";
9
+ import { homedir } from "node:os";
10
+
11
+ const HERE = dirname(fileURLToPath(import.meta.url));
12
+ const PKG = JSON.parse(readFileSync(join(HERE, "..", "package.json"), "utf8"));
13
+ export const VERSION = PKG.version;
14
+ const NAME = PKG.name;
15
+ const SHORT = NAME.includes("/") ? NAME.split("/").pop() : NAME;
16
+ const CACHE = join(homedir(), ".seldon", `update-${SHORT}.json`);
17
+ const DAY = 24 * 60 * 60 * 1000;
18
+
19
+ const newer = (a, b) => {
20
+ const pa = String(a).split(".").map(Number), pb = String(b).split(".").map(Number);
21
+ for (let i = 0; i < 3; i++) { const d = (pa[i] || 0) - (pb[i] || 0); if (d) return d > 0; }
22
+ return false;
23
+ };
24
+ const read = () => { try { return JSON.parse(readFileSync(CACHE, "utf8")); } catch { return null; } };
25
+ const write = (o) => { try { mkdirSync(dirname(CACHE), { recursive: true }); writeFileSync(CACHE, JSON.stringify(o)); } catch { /* ignore */ } };
26
+
27
+ /** Check (throttled to once/day) and print an update notice to stderr.
28
+ * Safe to call on every run — silent when current, offline, or npm is absent. */
29
+ export function checkAndNotify() {
30
+ try {
31
+ const c = read();
32
+ let latest = c?.latest;
33
+ if (!c || Date.now() - c.at > DAY) {
34
+ try {
35
+ latest = execFileSync("npm", ["view", NAME, "version"], { timeout: 3000, stdio: ["ignore", "pipe", "ignore"] }).toString().trim();
36
+ if (/^\d+\.\d+\.\d+/.test(latest)) write({ at: Date.now(), latest });
37
+ } catch { /* offline / npm unavailable — skip */ }
38
+ }
39
+ if (latest && newer(latest, VERSION))
40
+ process.stderr.write(`\x1b[33m ${SHORT} ${latest} is available\x1b[0m \x1b[2m— you have ${VERSION}. Update: \x1b[36mseldon install ${SHORT}\x1b[0m\x1b[2m (or npm i -g ${NAME})\x1b[0m\n`);
41
+ } catch { /* never break the CLI */ }
42
+ }