@gafj/gafj 0.1.1 → 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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +27 -1
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -28,7 +28,7 @@ Status: phases 1 through 6 built and tested (82 public tests on the synthetic ca
28
28
  - Bullet register (screen 6): `house_rules.bullet_register` is `result_first` (default, RAS), `action_first`, or `posting`; it travels in the resume packet's HOUSE RULES and `core/rules/resume.md` says what each means. Same facts, same claims, same gate; only word order.
29
29
  - Backup folder (screen 6): `config.backup_dir`, meant for a synced folder (Drive, OneDrive, Dropbox); `store/backup_dir.js` writes dated consistent copies there (`gafj backup`, the Back up now button, and one a day at `serve` start), keeps the newest 14, never prunes a pre-restore copy, and refuses the data folder itself. The live store never goes to a synced folder.
30
30
  - Hosted tier (plan W, gates 2 and 4 as code): `http/hosted.js` runs the same routes, screens, and doors behind a Google sign-in with one store per user under `<root>/tenants/<uid>/`; the session names the uid, the uid names the folder, and no query spans tenants (`test/hosted.test.js` drives every parameterized route as user B with user A's ids and expects 404 and no leaked text). Provider keys, MCP config, and the restore test are 404 for hosted users; the model is reached through the relay with a service key and the uid (`x-gafj-service`, `x-gafj-uid`; the relay's second auth path), never a stored user token. `/account/export` streams a restorable backup, `/account/delete` removes the folder and every session. `http/identity.js` verifies Firebase ID tokens with node:crypto against Google's JWK set, no SDK. `ui/hosted/signin.html` is the one page with a loosened CSP (Firebase Auth from gstatic). `gafj serve-hosted` is configured by environment only; `deploy/hosted/` holds the Dockerfile and fly.toml; `functions/relay/deploy.js` builds the relay with Firebase Admin, Stripe, and the Anthropic SDK and `functions/index.js` exports it as `relay`. Nothing is deployed; `docs/operator-setup.md` is the list of what only the operator can do.
31
- - `bin/cli.js`: `migrate`, `import`, `snapshot`, `review`, `lint`, `list`, `score-freeze`, `packet`, `ingest`, `render`, `export`, `backup`, `restore`, `mcp`, `print-mcp-config`, `serve`, `url`, `rotate-token`, `install-startup`, `allow-host`, `init`, `onboard`; every other command names its phase and exits
31
+ - `bin/cli.js`: `start` (the one command: candidate if none, then url or serve), `migrate`, `import`, `snapshot`, `review`, `lint`, `list`, `score-freeze`, `packet`, `ingest`, `render`, `export`, `backup`, `restore`, `mcp`, `print-mcp-config`, `serve`, `url`, `rotate-token`, `install-startup`, `allow-host`, `init`, `onboard`; every other command names its phase and exits
32
32
 
33
33
  Onboarding another candidate, phase 5b:
34
34
 
package/bin/cli.js CHANGED
@@ -39,6 +39,7 @@ const COMMANDS = {
39
39
  render: "write a passed document as Markdown: render --document <id> [--out <dir>]",
40
40
  list: "list ids: list postings|applications|interviews|documents|runs",
41
41
  mcp: "run the stdio MCP server for Claude Desktop or Claude Code (stdout is the wire)",
42
+ start: "the one command: creates your candidate the first time (asks your name), then opens the app: start [--name <name>]",
42
43
  serve: "run the local UI server on 127.0.0.1: serve [--port <n>] [--force] [--no-open]",
43
44
  url: "print a fresh launch URL for the running server (and open the browser): url [--no-open] [--host <allowed host>] [--qr]",
44
45
  "allow-host": "let a local HTTPS proxy front the server for your phone: allow-host <name.tail1234.ts.net> | allow-host --remove <name>",
@@ -487,10 +488,35 @@ async function main(argv) {
487
488
  } finally { db.close(); }
488
489
  }
489
490
 
491
+ if (cmd === "start") {
492
+ // One command for a new user and for every day after: make the candidate if there is none (asking the
493
+ // name once), then open the app: a fresh launch link if the server is up, otherwise serve.
494
+ const { readLock, pidAlive } = require("../http/server");
495
+ if (!cfg.candidate_id) {
496
+ let name = opts.name ? String(opts.name) : "";
497
+ if (!name && process.stdin.isTTY) {
498
+ const rl = require("readline").createInterface({ input: process.stdin, output: process.stdout });
499
+ name = await new Promise((resolve) => rl.question("Your name, as it should appear on a resume: ", (a) => { rl.close(); resolve(String(a || "").trim()); }));
500
+ }
501
+ name = name || "Candidate";
502
+ const db = openStore(home);
503
+ try {
504
+ const id = ulid(Date.now());
505
+ db.prepare("INSERT INTO candidate (id, name, created_at) VALUES (?, ?, ?)").run(id, name, nowIso());
506
+ cfg.candidate_id = id;
507
+ writeConfig(home.dir, cfg);
508
+ require("../store/onboarding").openBatch(db, { candidate_id: id, now: nowIso(), actor: ACTOR("start") });
509
+ } finally { db.close(); }
510
+ out(`welcome, ${name}. Your data lives in ${home.dir}`);
511
+ }
512
+ const lock = readLock(home.dir);
513
+ return main(lock && pidAlive(lock.pid) ? ["url"] : ["serve"]);
514
+ }
515
+
490
516
  if (cmd === "serve") {
491
517
  const { createServer, ensureSessionToken, readLock, writeLock, removeLock, pidAlive } = require("../http/server");
492
518
  const pkg = require("../package.json");
493
- if (!cfg.candidate_id) { process.stderr.write("no candidate yet; run gafj import first\n"); process.exit(2); }
519
+ if (!cfg.candidate_id) { process.stderr.write("no candidate yet; run gafj start\n"); process.exit(2); }
494
520
  const lock = readLock(home.dir);
495
521
  if (lock && pidAlive(lock.pid) && !opts.force) {
496
522
  out(`gafj serve is already running (pid ${lock.pid}) at http://127.0.0.1:${lock.port}; run "gafj url" for a launch link, or serve --force`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gafj/gafj",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "GAF-J: a local campaign engine for job search. One SQLite file, one ingest door, your own AI subscription.",
5
5
  "homepage": "https://gaf-j.com",
6
6
  "repository": {