feat: append-only version history + bliss CLI ergonomics

Snapshot the code-bearing fields of routes (verb/path/handler), templates
(name/content/test_object), and db libraries (name/library) on every
content-changing save, so nothing edited is ever lost. Recorded in a new
`versions` table via recordVersion() hooked into the create/update functions
in db.js (the single write choke point), deduped against the latest snapshot
so no-op saves, error-flag-only route writes, and server-restart WS
re-bootstraps don't pile up. The live row stays current; old snapshots are the
undo trail. Read-only, no revert UI yet.

- migrations/004_add_versions.sql: versions table + indexes
- db.js: recordVersion/getVersions/getVersion + create/update hooks
- plumbing.js: /versions/:id and per-entity .../versions list endpoints
- bliss: `versions <type> <sid> <id>` and `version <id>` reads;
  `update-settings` (edit route_prefix/head_injection, preserving the
  untouched field); `use <url>` sticky target persisted to ~/.bliss/target
  so BLISS_URL needn't be re-exported each call
- client.js: target-file precedence (BLISS_URL > ~/.bliss/target > localhost)
- SKILL.md: document the above

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Your Name 2026-08-03 00:27:24 -04:00
parent 03a9d2143d
commit 42f4aacf7c
6 changed files with 211 additions and 7 deletions

View file

@ -10,16 +10,37 @@ const fs = require("fs");
const os = require("os");
const path = require("path");
const BASE = (process.env.BLISS_URL || "http://localhost:3000").replace(
/\/$/,
"",
);
const JAR_DIR = path.join(os.homedir(), ".bliss");
// Sticky target so callers don't have to re-set BLISS_URL on every invocation
// (shell env doesn't persist between separate CLI runs). Precedence:
// BLISS_URL env > ~/.bliss/target > localhost default
const TARGET_FILE = path.join(JAR_DIR, "target");
function readTarget() {
try {
return fs.readFileSync(TARGET_FILE, "utf8").trim() || null;
} catch {
return null;
}
}
function setTarget(url) {
const clean = url.replace(/\/$/, "");
fs.mkdirSync(JAR_DIR, { recursive: true });
fs.writeFileSync(TARGET_FILE, clean + "\n");
return clean;
}
const BASE = (
process.env.BLISS_URL ||
readTarget() ||
"http://localhost:3000"
).replace(/\/$/, "");
const USER = process.env.BLISS_USER;
const PASS = process.env.BLISS_PASS;
// Persist the session cookie so we don't log in on every CLI invocation. Keyed
// by base URL so pointing at a different instance uses a different jar.
const JAR_DIR = path.join(os.homedir(), ".bliss");
const JAR_FILE = path.join(
JAR_DIR,
"session-" + Buffer.from(BASE).toString("hex").slice(0, 24) + ".txt",
@ -146,6 +167,7 @@ async function getText(urlPath) {
module.exports = {
BASE,
setTarget,
request,
getJSON,
getText,