omgbase 0.16.0 → 0.20.0

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.
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "b5bb9c1",
3
- "built": "2026-10-13T09:12:01.162Z"
2
+ "commit": "e2ab637",
3
+ "built": "2026-10-15T09:57:20.729Z"
4
4
  }
@@ -159,7 +159,7 @@ export function runSugar(cli, ws, repo, sugar, opts = {}) {
159
159
  */
160
160
  export async function runSugarRemote(cli, tool, toolArgs, effects, refs = []) {
161
161
  if (cli.flags.dryRun)
162
- return renderPlan(cli, await remoteCall(cli, "plan", { effects: await effects() }));
162
+ return renderPlan(cli, await remoteCall(cli, "plan", { effects: await effects(), diffs: true }));
163
163
  return renderReceipt(cli, await remoteSugar(cli, tool, toolArgs, effects, refs));
164
164
  }
165
165
  /** A ref as an effect target for a remote `plan`: a `b_` id is the block, a `d_` id the document, anything else a document path. */
@@ -198,7 +198,7 @@ export function runEffects(cli, ws, repo, effects, opts = {}) {
198
198
  /** Remote peer of `runEffects`: the `apply` tool with `effects` (the Receipt), or on a dry run the `plan` tool (the Plan). */
199
199
  export async function runEffectsRemote(cli, effects, reason) {
200
200
  if (cli.flags.dryRun)
201
- return renderPlan(cli, await remoteCall(cli, "plan", { effects }));
201
+ return renderPlan(cli, await remoteCall(cli, "plan", { effects, diffs: true }));
202
202
  return renderReceipt(cli, await remoteCall(cli, "apply", { effects, ...(reason ? { reason } : {}) }));
203
203
  }
204
204
  /** `execute`: commit exactly a Plan (the plan form, spec/mutate §11.7 — admission, then the one transaction), rendered as a Receipt. */
@@ -40,7 +40,7 @@ function runGcCmd(cli, args) {
40
40
  if (values.help) {
41
41
  return renderHelp(cli, {
42
42
  name: "gc",
43
- summary: "Mark-and-sweep unreferenced blobs and tree nodes (refuses unless gc.enabled is set in repo settings)",
43
+ summary: "Mark-and-sweep unreferenced blobs and tree nodes, and the `same` disposition rows a pre-14.2 store wrote (refuses unless gc.enabled is set in repo settings)",
44
44
  usage: "gc [--dry-run] [--vacuum]",
45
45
  options: [
46
46
  ["--dry-run", "report what would be swept, sweep nothing (allowed even when gc is disabled)"],
@@ -57,7 +57,9 @@ function runGcCmd(cli, args) {
57
57
  if (!enabled && !dryRun) {
58
58
  throw new EngineErrorLike("target_missing", "gc is disabled; set gc.enabled=true (or use --dry-run)");
59
59
  }
60
- const swept = runGc(ws.store, { enabled: enabled || dryRun });
60
+ // A dry run counts every sweep — blobs, tree nodes and the `same` disposition
61
+ // rows a 14.1 store wrote (spec/store §7, 14.2) — and deletes nothing.
62
+ const swept = runGc(ws.store, { enabled: enabled || dryRun, dryRun });
61
63
  // `--vacuum` runs after the sweep (the sweep's freed pages are what it returns);
62
64
  // a dry run measures the free pages and vacuums nothing (spec/store §7).
63
65
  const vac = values.vacuum
@@ -68,7 +70,7 @@ function runGcCmd(cli, args) {
68
70
  const result = vac ? { ...swept, vacuum: vac } : swept;
69
71
  if (cli.flags.mode !== "human")
70
72
  return emitMachine(cli, result);
71
- cli.io.err(cli.style.dim(` swept ${swept.blobsSwept} blobs, ${swept.treeNodesSwept} tree nodes`));
73
+ cli.io.err(cli.style.dim(` swept ${swept.blobsSwept} blobs, ${swept.treeNodesSwept} tree nodes, ${swept.sameDispositionsSwept} same dispositions`));
72
74
  if (vac)
73
75
  cli.io.err(cli.style.dim(vac.applied ? ` vacuumed, reclaimed ${humanBytes(vac.reclaimable)}` : ` would vacuum ${humanBytes(vac.reclaimable)}`));
74
76
  return EXIT_OK;
@@ -83,7 +85,7 @@ function runDoctor(cli, args) {
83
85
  usage: ["doctor [--json]", "doctor --size [--json]"],
84
86
  options: [
85
87
  ["--size", "instead of the checks: what the database is made of — bytes and rows per category (content, history, search, sync) and table, the file's pages, what VACUUM would reclaim, advice"],
86
- ["--json", "`{ ok, checks: [{ name, ok, detail }] }`; with --size `{ file, categories, advice }`"],
88
+ ["--json", "`{ ok, checks: [{ name, ok, detail }] }`; with --size `{ file, categories, same_dispositions, archived, advice }`"],
87
89
  ],
88
90
  });
89
91
  }
@@ -156,6 +158,8 @@ function runDoctorSize(cli, report) {
156
158
  ["size", `${humanBytes(f.bytes)} on disk, wal ${humanBytes(f.wal_bytes)}`],
157
159
  ["pages", `${f.page_count} x ${f.page_size} bytes, ${f.freelist_count} free`],
158
160
  ["reclaimable", humanBytes(f.reclaimable)],
161
+ // 15.0: the history that already moved to archive segments (spec/store §7 "Retention").
162
+ ["archived", `${report.archived.revisions} revisions in ${report.archived.segments} segments, ${humanBytes(report.archived.bytes)} on disk`],
159
163
  ];
160
164
  for (const line of columns(file))
161
165
  io.out(` ${line}`);
@@ -6,7 +6,7 @@ const GROUPS = [
6
6
  { title: "bootstrap", names: ["init", "source", "repos"] },
7
7
  { title: "orient & read", names: ["status", "ls", "outline", "cat", "show", "find"] },
8
8
  { title: "query", names: ["query", "run"] },
9
- { title: "history & links", names: ["log", "hist", "diff", "links"] },
9
+ { title: "history & links", names: ["log", "hist", "diff", "links", "history"] },
10
10
  { title: "mutate", names: ["apply", "execute", "insert", "update", "edit", "move", "rm", "done", "append", "retarget", "split", "merge", "node"] },
11
11
  { title: "documents", names: ["new", "mv", "meta"] },
12
12
  { title: "session", names: ["shell"] },
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdHistory: Command;
@@ -0,0 +1,232 @@
1
+ import { parseArgs } from "node:util";
2
+ import { readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ import { evaluateRetention, retentionPolicy, exportHistory, attachArchive, detachArchive, listArchives, humanBytes, withWriterLock, RetentionPolicyError, NotASegment, StaleSegment, ARCHIVE_DIR, } from "@omgbase/core";
5
+ import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
6
+ import { columns } from "../render.js";
7
+ // `omg history <plan|export|attach|detach|list>` (spec/cli §6 `history`, cli
8
+ // 4.6): the operator surface of history retention (spec/store §7 "Retention",
9
+ // 15.0). `plan` evaluates the policy and prints what would move; `export`
10
+ // writes one archive segment (and with `--prune` squashes the store); `attach`
11
+ // / `detach` / `list` manage the segment registry. No MCP tool: the catalog's
12
+ // only trace is the `revisions.archived` attribute and `revision_archived`.
13
+ const SUBCOMMANDS = ["plan", "export", "attach", "detach", "list"];
14
+ function renderCard(cli) {
15
+ return renderHelp(cli, {
16
+ name: "history",
17
+ summary: "History retention: plan what the policy would archive, export it to a segment, attach/detach/list segments",
18
+ usage: [
19
+ "history plan [--before <ts>] [--policy <json|@file>]",
20
+ "history export [--prune] [--dest <dir>] [--before <ts>] [--policy <json|@file>] [--dry-run]",
21
+ "history attach <file|dir>",
22
+ "history detach <segment_id|path>",
23
+ "history list",
24
+ ],
25
+ options: [
26
+ ["plan", "per document: revisions kept / to archive and the bytes only they reach; the policy in effect"],
27
+ ["export", "write ONE new segment file (default dest .omgbase/archives/) holding the archived revisions' bodies; --prune then squashes the store and records the segment"],
28
+ ["attach <path>", "record a segment file (or every segment of a directory that belongs to this repo) so reads at its revisions work again"],
29
+ ["detach <ref>", "forget a segment by id or path (its revisions stay listed; reading one is revision_archived)"],
30
+ ["list", "the repo's segments: commit range, revisions, bytes, state (attached, missing, pending, detached, stale)"],
31
+ ["--before <ts>", "only revisions committed before this RFC 3339 instant are candidates"],
32
+ ["--policy <p>", "a JSON object (or @file) merged over the effective history.retention settings for this run"],
33
+ ["--prune", "(export) after the segment is durable: NULL the archived bodies, drop their change rows, sweep, record"],
34
+ ["--dest <dir>", "(export) where the segment file goes"],
35
+ ["--dry-run", "(export) the plan, nothing written"],
36
+ ],
37
+ notes: [
38
+ "policy keys: keep_days, ladder [{ after_days, keep: \"one per day|week|month|year\" }], per_doc_max, keep_origins, overrides [{ glob, … }]",
39
+ `e.g. ${cli.prog} history export --prune --policy '{"keep_days":90}'`,
40
+ ],
41
+ });
42
+ }
43
+ function parsePolicy(raw) {
44
+ if (raw === undefined)
45
+ return undefined;
46
+ const text = raw.startsWith("@") ? readFileSync(raw.slice(1), "utf8") : raw;
47
+ try {
48
+ return JSON.parse(text);
49
+ }
50
+ catch {
51
+ throw new CliUsageError(`--policy: not JSON (${raw.startsWith("@") ? raw : "pass an object, or @file"})`);
52
+ }
53
+ }
54
+ function parseBefore(raw) {
55
+ if (raw === undefined)
56
+ return undefined;
57
+ const t = Date.parse(raw);
58
+ if (Number.isNaN(t))
59
+ throw new CliUsageError(`bad --before '${raw}' (use an RFC 3339 instant)`);
60
+ return new Date(t).toISOString();
61
+ }
62
+ /** The repo's effective policy with the run's `--policy` over it; a malformed policy is a usage error. */
63
+ function policyOf(ws, repoId, raw) {
64
+ try {
65
+ return retentionPolicy(ws.store, repoId, parsePolicy(raw));
66
+ }
67
+ catch (e) {
68
+ if (e instanceof RetentionPolicyError)
69
+ throw new CliUsageError(`--policy: ${e.message}`);
70
+ throw e;
71
+ }
72
+ }
73
+ function plural(n, one, many = `${one}s`) {
74
+ return `${n} ${n === 1 ? one : many}`;
75
+ }
76
+ /** The plan rendering (`history plan`, `history export --dry-run`). */
77
+ function renderPlan(cli, plan) {
78
+ if (cli.flags.mode !== "human")
79
+ return emitMachine(cli, plan, { items: plan.docs, ids: plan.docs.map((d) => d.id) });
80
+ const { io, style } = cli;
81
+ if (plan.docs.length > 0) {
82
+ const rows = [[style.dim("path"), style.dim("revisions"), style.dim("keep"), style.dim("archive"), style.dim("bytes")]];
83
+ for (const d of plan.docs)
84
+ rows.push([style.accent(d.path), String(d.revisions), String(d.keep.length), String(d.archive.length), humanBytes(d.bytes)]);
85
+ for (const line of columns(rows, [{}, { align: "right" }, { align: "right" }, { align: "right" }, { align: "right" }]))
86
+ io.out(` ${line}`);
87
+ }
88
+ const t = plan.totals;
89
+ if (t.archive === 0)
90
+ io.out(` nothing to archive: ${plural(t.revisions, "revision")}, all kept`);
91
+ else
92
+ io.out(` ${t.archive} of ${plural(t.revisions, "revision")} in ${plural(t.docs, "document")} would archive ${humanBytes(t.bytes)}`);
93
+ io.out(` ${style.dim("policy")} ${JSON.stringify(plan.policy)}`);
94
+ if (plan.before !== null)
95
+ io.out(` ${style.dim("before")} ${plan.before}`);
96
+ return EXIT_OK;
97
+ }
98
+ function commitRange(lo, hi) {
99
+ return lo === hi ? `commit #${lo}` : `commits #${lo}–#${hi}`;
100
+ }
101
+ function runPlan(cli, args) {
102
+ const { values } = parseArgs({ args, allowPositionals: false, options: { before: { type: "string" }, policy: { type: "string" } } });
103
+ const ws = cli.workspace();
104
+ const repo = cli.repo(ws);
105
+ const before = parseBefore(values.before);
106
+ const plan = evaluateRetention(ws.store, repo.repoId, { policy: policyOf(ws, repo.repoId, values.policy), ...(before !== undefined ? { before } : {}) });
107
+ return renderPlan(cli, plan);
108
+ }
109
+ function renderExport(cli, r) {
110
+ if (cli.flags.mode !== "human")
111
+ return emitMachine(cli, { segment: r.segment, reused: r.reused, squashed: r.squashed, gc: r.gc, totals: r.plan.totals });
112
+ const { io, style } = cli;
113
+ if (!r.segment) {
114
+ io.err(style.dim(" nothing to archive"));
115
+ return EXIT_OK;
116
+ }
117
+ const s = r.segment;
118
+ io.out(s.path);
119
+ io.err(style.dim(` ${r.reused ? `finished the unrecorded segment ${s.id}` : `wrote segment ${s.id}`}: ${plural(s.revision_count, "revision")} (${commitRange(s.seq_lo, s.seq_hi)}), ${humanBytes(s.bytes)}`));
120
+ if (r.squashed && r.gc) {
121
+ const q = r.squashed;
122
+ io.err(style.dim(` squashed ${plural(q.revisions, "revision")}, ${plural(q.commits, "commit")}, ${plural(q.dispositions, "disposition")}, ${plural(q.block_changes, "block change")}; swept ${plural(r.gc.blobs, "blob")}, ${plural(r.gc.tree_nodes, "tree node")}`));
123
+ }
124
+ return EXIT_OK;
125
+ }
126
+ function defaultDest(ws) {
127
+ return join(ws.omgbaseDir, ARCHIVE_DIR);
128
+ }
129
+ function runExport(cli, args) {
130
+ const { values } = parseArgs({
131
+ args,
132
+ allowPositionals: false,
133
+ options: { prune: { type: "boolean" }, dest: { type: "string" }, before: { type: "string" }, policy: { type: "string" }, "dry-run": { type: "boolean" } },
134
+ });
135
+ const ws = cli.workspace();
136
+ const repo = cli.repo(ws);
137
+ const before = parseBefore(values.before);
138
+ const policy = policyOf(ws, repo.repoId, values.policy);
139
+ const opts = { policy, ...(before !== undefined ? { before } : {}) };
140
+ if (values["dry-run"] || cli.flags.dryRun)
141
+ return renderPlan(cli, evaluateRetention(ws.store, repo.repoId, opts));
142
+ const dest = values.dest ? resolve(cli.cwd, values.dest) : defaultDest(ws);
143
+ const run = () => exportHistory(ws.store, repo.repoId, { ...opts, dest, prune: Boolean(values.prune) });
144
+ try {
145
+ // The squash is a write: under the workspace writer lock like every other.
146
+ const result = values.prune ? withWriterLock(ws.omgbaseDir, run) : run();
147
+ return renderExport(cli, result);
148
+ }
149
+ catch (e) {
150
+ if (e instanceof StaleSegment)
151
+ throw new EngineErrorLike("target_missing", e.message, { data: { path: e.path, rev: e.rev } });
152
+ throw e;
153
+ }
154
+ }
155
+ function runAttach(cli, args) {
156
+ const { positionals } = parseArgs({ args, allowPositionals: true, options: {} });
157
+ const target = positionals[0];
158
+ if (!target)
159
+ throw new CliUsageError("history attach requires a <file|dir>");
160
+ const ws = cli.workspace();
161
+ const repo = cli.repo(ws);
162
+ let result;
163
+ try {
164
+ result = withWriterLock(ws.omgbaseDir, () => attachArchive(ws.store, repo.repoId, resolve(cli.cwd, target)));
165
+ }
166
+ catch (e) {
167
+ if (e instanceof NotASegment)
168
+ throw new EngineErrorLike("target_missing", e.message, { data: { path: e.path } });
169
+ throw e;
170
+ }
171
+ if (cli.flags.mode !== "human")
172
+ return emitMachine(cli, result, { items: result.attached, ids: result.attached.map((a) => a.segment_id) });
173
+ const { io, style } = cli;
174
+ if (result.attached.length === 0)
175
+ io.err(style.dim(" nothing to attach"));
176
+ for (const a of result.attached)
177
+ io.err(style.dim(` attached ${a.segment_id}: ${plural(a.revision_count, "revision")} (${commitRange(a.seq_lo, a.seq_hi)}) ${a.path}`));
178
+ if (result.skipped > 0)
179
+ io.err(style.dim(` skipped ${plural(result.skipped, "file")} that ${result.skipped === 1 ? "is" : "are"} not this repository's segments`));
180
+ return EXIT_OK;
181
+ }
182
+ function runDetach(cli, args) {
183
+ const { positionals } = parseArgs({ args, allowPositionals: true, options: {} });
184
+ const ref = positionals[0];
185
+ if (!ref)
186
+ throw new CliUsageError("history detach requires a <segment_id|path>");
187
+ const ws = cli.workspace();
188
+ const repo = cli.repo(ws);
189
+ const detached = withWriterLock(ws.omgbaseDir, () => detachArchive(ws.store, repo.repoId, ref.includes("/") || ref.endsWith(".db") ? resolve(cli.cwd, ref) : ref));
190
+ if (detached === null)
191
+ throw new EngineErrorLike("target_missing", `no archive segment ${JSON.stringify(ref)}`, { data: { segment: ref } });
192
+ if (cli.flags.mode !== "human")
193
+ return emitMachine(cli, { detached });
194
+ cli.io.err(cli.style.dim(` detached ${detached}`));
195
+ return EXIT_OK;
196
+ }
197
+ function runList(cli, args) {
198
+ parseArgs({ args, allowPositionals: false, options: {} });
199
+ const ws = cli.workspace();
200
+ const repo = cli.repo(ws);
201
+ const segments = listArchives(ws.store, repo.repoId, { dest: defaultDest(ws) });
202
+ if (cli.flags.mode !== "human")
203
+ return emitMachine(cli, segments, { items: segments, ids: segments.map((s) => s.segment_id) });
204
+ const { io, style } = cli;
205
+ if (segments.length === 0) {
206
+ io.err(style.dim(" no segments"));
207
+ return EXIT_OK;
208
+ }
209
+ const rows = [[style.dim("segment"), style.dim("commits"), style.dim("revisions"), style.dim("bytes"), style.dim("state"), style.dim("path")]];
210
+ for (const s of segments)
211
+ rows.push([style.id(s.segment_id), s.seq_lo === s.seq_hi ? `#${s.seq_lo}` : `#${s.seq_lo}–#${s.seq_hi}`, String(s.revision_count), humanBytes(s.bytes), s.state, style.path(s.path)]);
212
+ for (const line of columns(rows, [{}, {}, { align: "right" }, { align: "right" }]))
213
+ io.out(` ${line}`);
214
+ return EXIT_OK;
215
+ }
216
+ function runHistory(cli, args) {
217
+ const [sub, ...rest] = args;
218
+ if (sub === undefined || sub === "--help" || sub === "-h" || sub === "help" || rest.includes("--help") || rest.includes("-h"))
219
+ return renderCard(cli);
220
+ if (!SUBCOMMANDS.includes(sub)) {
221
+ throw new CliUsageError(`unknown history subcommand '${sub}' (${SUBCOMMANDS.join("|")})`, `run '${cli.prog} history --help'`);
222
+ }
223
+ switch (sub) {
224
+ case "plan": return runPlan(cli, rest);
225
+ case "export": return runExport(cli, rest);
226
+ case "attach": return runAttach(cli, rest);
227
+ case "detach": return runDetach(cli, rest);
228
+ case "list": return runList(cli, rest);
229
+ }
230
+ }
231
+ export const cmdHistory = { name: "history", summary: "Retention: plan/export archive segments, attach/detach/list", run: (cli, a) => runHistory(cli, a) };
232
+ //# sourceMappingURL=history.js.map
@@ -167,10 +167,20 @@ export function renderOqxResult(cli, result, bare = false) {
167
167
  cursor,
168
168
  });
169
169
  }
170
+ // Unknown names (spec/surface §1.5, spec/cli §6 `query`): the result's `hints`
171
+ // — names the query reads that no row of their kind carries — print LAST on
172
+ // stderr, one dim ` hint: …` line each, in every human rendering (the
173
+ // machine modes carry the member itself).
174
+ const { style, io } = cli;
175
+ const hints = () => {
176
+ for (const h of result.hints ?? [])
177
+ io.err(style.dim(` hint: ${h}`));
178
+ };
170
179
  // Scalar consumers (count/exists/none) have no hits — render the reduction itself.
171
180
  if (result.consumer === "count" || result.consumer === "exists" || result.consumer === "none") {
172
181
  const scalar = result.consumer === "count" ? String(result.count) : result.consumer === "exists" ? String(result.exists) : String(result.none);
173
182
  cli.io.out(scalar);
183
+ hints();
174
184
  return EXIT_OK;
175
185
  }
176
186
  // A `values` projection has no hits — one bare value per line (strings
@@ -180,11 +190,12 @@ export function renderOqxResult(cli, result, bare = false) {
180
190
  cli.io.out(typeof v === "string" ? v : JSON.stringify(v));
181
191
  if (cursor !== null)
182
192
  truncationFooter(cli.io, cli.style, cursor);
193
+ hints();
183
194
  return EXIT_OK;
184
195
  }
185
- const { style, io } = cli;
186
196
  if (result.hits.length === 0) {
187
197
  io.err(style.dim(" no hits"));
198
+ hints();
188
199
  return EXIT_OK;
189
200
  }
190
201
  if (bare)
@@ -193,6 +204,7 @@ export function renderOqxResult(cli, result, bare = false) {
193
204
  renderHits(cli, result.hits);
194
205
  if (result.truncated)
195
206
  truncationFooter(io, style, result.cursor ?? "");
207
+ hints();
196
208
  return EXIT_OK;
197
209
  }
198
210
  // `query` (alias `q`) is the single, default query surface — the OQX engine.
@@ -29,7 +29,7 @@ async function runRetarget(cli, args) {
29
29
  // Remote: the `plan` tool previews, `links_retarget` commits server-side.
30
30
  if (cli.flags.server) {
31
31
  if (!values.apply || cli.flags.dryRun) {
32
- const plan = await remoteCall(cli, "plan", { effects: sugar.effects });
32
+ const plan = await remoteCall(cli, "plan", { effects: sugar.effects, diffs: true });
33
33
  return values.apply ? renderPlanDiffs(cli, plan) : renderHits(cli, from, to, retargetOf(plan).hits);
34
34
  }
35
35
  // `links_retarget` where listed (its result adds `hits`/`pairs`), else `apply { retarget }` (spec/cli §2.3, 4.2).
@@ -62,7 +62,7 @@ async function runDocUpdate(cli, args) {
62
62
  // A server without `docs_update` (a minimal profile) gets `apply` over the same effect (spec/cli §2.3, 4.2).
63
63
  const setContent = { op: "set", target: { kind: "document", ...docLocator(doc) }, field: "content", value: bytes };
64
64
  if (dryRun)
65
- plan = await remoteCall(cli, "plan", { effects: [setContent] });
65
+ plan = await remoteCall(cli, "plan", { effects: [setContent], diffs: true });
66
66
  else
67
67
  receipt = await remoteSugar(cli, "docs_update", { ...docLocator(doc), content: bytes, ...(values.reason ? { reason: values.reason } : {}) }, () => [setContent]);
68
68
  }
@@ -11,6 +11,7 @@ import { cmdLog } from "./cmd/log.js";
11
11
  import { cmdHist } from "./cmd/hist.js";
12
12
  import { cmdDiff } from "./cmd/diff.js";
13
13
  import { cmdLinks } from "./cmd/links.js";
14
+ import { cmdHistory } from "./cmd/history.js";
14
15
  import { cmdSync } from "./cmd/sync.js";
15
16
  import { cmdMcp } from "./cmd/mcp.js";
16
17
  import { cmdApply, cmdExecute, cmdInsert, cmdMove, cmdRm, cmdDone, cmdAppend, cmdSplit, cmdMerge } from "./cmd/mutate.js";
@@ -39,6 +40,7 @@ export const COMMANDS = [
39
40
  cmdHist,
40
41
  cmdDiff,
41
42
  cmdLinks,
43
+ cmdHistory,
42
44
  cmdApply,
43
45
  cmdExecute,
44
46
  cmdInsert,
@@ -61,8 +61,8 @@ export const NO_WORKSPACE_OK = new Set(["init", "help", "version"]);
61
61
  // whose card is also their bare invocation (spec/cli §5): help is documentation,
62
62
  // not work, so these route to the card before any workspace is opened or swept —
63
63
  // exactly like the `--help` flag.
64
- export const HELP_WORD_OK = new Set(["config", "embed", "node", "source"]);
65
- export const BARE_IS_HELP = new Set(["node", "source"]);
64
+ export const HELP_WORD_OK = new Set(["config", "embed", "node", "source", "history"]);
65
+ export const BARE_IS_HELP = new Set(["node", "source", "history"]);
66
66
  /** Does this invocation ask for a command's help card? (the flag, the word, or a bare card-only command) */
67
67
  export function asksForHelp(flags, name, args) {
68
68
  if (flags.help)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omgbase",
3
- "version": "0.16.0",
3
+ "version": "0.20.0",
4
4
  "description": "Open Markdown Graph Base — CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,9 +45,9 @@
45
45
  "omg": "./dist/src/main.js"
46
46
  },
47
47
  "dependencies": {
48
- "@omgbase/core": "^0.19.0",
48
+ "@omgbase/core": "^0.23.0",
49
49
  "@omgbase/fs-adapter": "^0.2.0",
50
- "@omgbase/sync": "^0.5.12",
50
+ "@omgbase/sync": "^0.5.16",
51
51
  "yaml": "^2.9.0"
52
52
  },
53
53
  "devDependencies": {