omgbase 0.4.0 → 0.5.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.
Files changed (44) hide show
  1. package/dist/build-info.json +4 -0
  2. package/dist/src/build.d.ts +22 -0
  3. package/dist/src/build.js +58 -0
  4. package/dist/src/cmd/_mutate.d.ts +12 -0
  5. package/dist/src/cmd/_mutate.js +26 -13
  6. package/dist/src/cmd/admin.js +25 -22
  7. package/dist/src/cmd/bootstrap.js +14 -22
  8. package/dist/src/cmd/cat.js +10 -12
  9. package/dist/src/cmd/diff.js +39 -27
  10. package/dist/src/cmd/docs.js +44 -6
  11. package/dist/src/cmd/find.js +3 -15
  12. package/dist/src/cmd/help.js +1 -1
  13. package/dist/src/cmd/hist.js +4 -10
  14. package/dist/src/cmd/links.js +21 -16
  15. package/dist/src/cmd/log.js +8 -18
  16. package/dist/src/cmd/ls.js +3 -15
  17. package/dist/src/cmd/mcp.js +16 -32
  18. package/dist/src/cmd/mutate.js +24 -3
  19. package/dist/src/cmd/node.js +7 -3
  20. package/dist/src/cmd/oqx.d.ts +10 -0
  21. package/dist/src/cmd/oqx.js +25 -28
  22. package/dist/src/cmd/outline.js +5 -8
  23. package/dist/src/cmd/retarget.js +6 -21
  24. package/dist/src/cmd/run.js +5 -32
  25. package/dist/src/cmd/show.js +11 -12
  26. package/dist/src/cmd/source.js +18 -32
  27. package/dist/src/cmd/status.js +13 -25
  28. package/dist/src/cmd/sync.js +8 -9
  29. package/dist/src/cmd/update.js +10 -5
  30. package/dist/src/cmd/version.d.ts +2 -0
  31. package/dist/src/cmd/version.js +68 -0
  32. package/dist/src/commands.js +2 -0
  33. package/dist/src/context.d.ts +4 -0
  34. package/dist/src/context.js +16 -0
  35. package/dist/src/dispatch.d.ts +3 -0
  36. package/dist/src/dispatch.js +35 -18
  37. package/dist/src/main.d.ts +2 -0
  38. package/dist/src/main.js +19 -13
  39. package/dist/src/output.d.ts +28 -1
  40. package/dist/src/output.js +66 -20
  41. package/dist/src/seams.d.ts +13 -0
  42. package/dist/src/seams.js +37 -0
  43. package/dist/src/shell/session.js +4 -2
  44. package/package.json +4 -4
@@ -1,3 +1,4 @@
1
+ import { errorBody } from "@omgbase/core";
1
2
  import { columns, visibleWidth } from "./render.js";
2
3
  // Output contract + error rendering (11 §2.4, §4).
3
4
  //
@@ -20,6 +21,34 @@ export class CliUsageError extends Error {
20
21
  this.name = "CliUsageError";
21
22
  }
22
23
  }
24
+ /**
25
+ * Node's `parseArgs` failures are the user's mistake, not the engine's (spec/cli
26
+ * §2.2): an option the command does not declare, a value-taking option without
27
+ * its value, a boolean given one. Each becomes a usage error with a portable
28
+ * message — the wording is ours, so a port need not reproduce Node's — and the
29
+ * hint points at the command's card. Anything else is returned untouched.
30
+ */
31
+ export function usageFromParseArgs(err, prog, command) {
32
+ const e = err;
33
+ if (!e || typeof e !== "object" || typeof e.code !== "string" || !e.code.startsWith("ERR_PARSE_ARGS_") || typeof e.message !== "string")
34
+ return err;
35
+ const hint = `run '${prog} ${command} --help' for the options`;
36
+ const unknown = /^Unknown option '([^']+)'/.exec(e.message);
37
+ if (unknown)
38
+ return new CliUsageError(`unknown option '${unknown[1]}'`, hint);
39
+ const missing = /^Option '([^']+?)(?: <value>)?' argument missing/.exec(e.message);
40
+ if (missing)
41
+ return new CliUsageError(`option '${lastSpelling(missing[1])}' requires a value`, hint);
42
+ const extra = /^Option '([^']+)' does not take an argument/.exec(e.message);
43
+ if (extra)
44
+ return new CliUsageError(`option '${lastSpelling(extra[1])}' does not take a value`, hint);
45
+ return new CliUsageError(e.message, hint);
46
+ }
47
+ /** Node names an option with a short alias as `-n, --name`; quote the long form. */
48
+ function lastSpelling(spellings) {
49
+ const parts = spellings.split(",").map((p) => p.trim()).filter(Boolean);
50
+ return parts[parts.length - 1] ?? spellings;
51
+ }
23
52
  export function renderHelp(cli, spec) {
24
53
  const { io, style, prog } = cli;
25
54
  io.out(` ${style.bold(spec.name)} — ${spec.summary}`);
@@ -60,7 +89,14 @@ export class EngineErrorLike extends Error {
60
89
  };
61
90
  }
62
91
  }
63
- /** Coerce any thrown value into a typed error body for rendering. */
92
+ /**
93
+ * Coerce any thrown value into a typed error body for rendering. The CLI's own
94
+ * `EngineErrorLike` carries its code and hint; everything else goes through the
95
+ * engine's one mapping (`errorBody`, the same the MCP server renders — spec/cli
96
+ * §3.5): typed errors keep their code, OQX errors and a bad cursor are
97
+ * `filter_invalid`, an unknown revision `target_missing`, and anything else the
98
+ * catch-all `repo_not_found`.
99
+ */
64
100
  function toBody(err) {
65
101
  if (err instanceof EngineErrorLike) {
66
102
  const b = err.body();
@@ -71,27 +107,22 @@ function toBody(err) {
71
107
  ...(err.extra.hint ? { hint: err.extra.hint } : {}),
72
108
  };
73
109
  }
74
- // core EngineError / MutationError / FilterInvalid all carry a code + message
75
- // (and often a body()); duck-type them.
76
- const anyErr = err;
77
- if (typeof anyErr?.body === "function") {
78
- const b = anyErr.body();
79
- return { code: b.error ?? b.code ?? "error", message: b.message ?? String(err), data: b.data };
80
- }
81
- if (anyErr?.code) {
82
- return {
83
- code: anyErr.code,
84
- message: anyErr.message ?? String(err),
85
- ...(anyErr.data !== undefined ? { data: anyErr.data } : {}),
86
- ...(anyErr.hint ? { hint: anyErr.hint } : {}),
87
- };
88
- }
89
- return { code: "error", message: anyErr?.message ?? String(err) };
110
+ const b = errorBody(err);
111
+ return { code: b.error, message: b.message, ...(b.data !== undefined ? { data: b.data } : {}) };
112
+ }
113
+ /** A payload worth printing: anything but `undefined`, `null` and an empty record. */
114
+ function hasPayload(data) {
115
+ if (data === undefined || data === null)
116
+ return false;
117
+ if (typeof data === "object" && !Array.isArray(data) && Object.keys(data).length === 0)
118
+ return false;
119
+ return true;
90
120
  }
91
121
  /**
92
122
  * Render a thrown error to stderr and return the process exit code. `json`
93
123
  * selects machine output (typed object); otherwise the human form with any
94
- * current-truth payload pretty-printed.
124
+ * current-truth payload pretty-printed. Both forms carry the hint; an empty
125
+ * payload (`{}`) prints nothing.
95
126
  */
96
127
  export function renderError(err, io, style, json) {
97
128
  if (err instanceof CliUsageError) {
@@ -106,7 +137,7 @@ export function renderError(err, io, style, json) {
106
137
  }
107
138
  const body = toBody(err);
108
139
  if (json) {
109
- io.err(JSON.stringify({ error: body.code, message: body.message, ...(body.data !== undefined ? { data: body.data } : {}), retriable: false }));
140
+ io.err(JSON.stringify({ error: body.code, message: body.message, ...(hasPayload(body.data) ? { data: body.data } : {}), ...(body.hint ? { hint: body.hint } : {}), retriable: false }));
110
141
  return EXIT_ERROR;
111
142
  }
112
143
  io.err(`${style.err(`error[${body.code}]`)}: ${body.message}`);
@@ -114,11 +145,26 @@ export function renderError(err, io, style, json) {
114
145
  io.err(style.dim(` hint: ${body.hint}`));
115
146
  // Conflict objects carry current truth (04 §4) — print all of it; the retry
116
147
  // is built from it.
117
- if (body.data !== undefined && body.data !== null) {
148
+ if (hasPayload(body.data)) {
118
149
  io.err(style.dim(indent(JSON.stringify(body.data, null, 2), " ")));
119
150
  }
120
151
  return EXIT_ERROR;
121
152
  }
153
+ /** Print `doc` in the current machine mode (`--json`/`--jsonl`/`--ids`); returns EXIT_OK. */
154
+ export function emitMachine(cli, doc, shape = {}) {
155
+ const { io, style, flags } = cli;
156
+ if (flags.mode === "jsonl" && shape.items)
157
+ for (const item of shape.items)
158
+ io.out(JSON.stringify(item));
159
+ else if (flags.mode === "ids" && shape.ids)
160
+ for (const id of shape.ids)
161
+ io.out(id);
162
+ else
163
+ io.out(JSON.stringify(doc));
164
+ if (flags.mode !== "json" && shape.cursor !== undefined && shape.cursor !== null)
165
+ truncationFooter(io, style, shape.cursor);
166
+ return EXIT_OK;
167
+ }
122
168
  function indent(s, pad) {
123
169
  return s
124
170
  .split("\n")
@@ -0,0 +1,13 @@
1
+ export interface SpecSeams {
2
+ /** the sequential minter is installed */
3
+ minter: boolean;
4
+ /** the pinned instant (canonical ISO form), when the clock is pinned */
5
+ clock: string | null;
6
+ }
7
+ /**
8
+ * Read + install the seams (idempotent: a second call returns what the first
9
+ * installed). Throws `CliUsageError` on an invalid value; the caller renders it.
10
+ */
11
+ export declare function installSpecSeams(env?: NodeJS.ProcessEnv): SpecSeams;
12
+ /** The seams installed by `installSpecSeams` (none when it has not run). */
13
+ export declare function activeSpecSeams(): SpecSeams;
@@ -0,0 +1,37 @@
1
+ import { pinClock, sequentialMinter, setIdMinter } from "@omgbase/core";
2
+ import { CliUsageError } from "./output.js";
3
+ let installed = null;
4
+ /**
5
+ * Read + install the seams (idempotent: a second call returns what the first
6
+ * installed). Throws `CliUsageError` on an invalid value; the caller renders it.
7
+ */
8
+ export function installSpecSeams(env = process.env) {
9
+ if (installed)
10
+ return installed;
11
+ const seams = { minter: false, clock: null };
12
+ const minter = env.OMGBASE_SPEC_MINTER;
13
+ if (minter !== undefined && minter !== "") {
14
+ if (minter !== "sequential") {
15
+ throw new CliUsageError(`OMGBASE_SPEC_MINTER=${JSON.stringify(minter)}: the only value is "sequential" (spec/surface §7.1)`, "unset it, or set OMGBASE_SPEC_MINTER=sequential for a conformance run");
16
+ }
17
+ setIdMinter(sequentialMinter());
18
+ seams.minter = true;
19
+ }
20
+ const clock = env.OMGBASE_SPEC_CLOCK;
21
+ if (clock !== undefined && clock !== "") {
22
+ try {
23
+ pinClock(clock);
24
+ }
25
+ catch {
26
+ throw new CliUsageError(`OMGBASE_SPEC_CLOCK=${JSON.stringify(clock)}: not an RFC 3339 instant (spec/surface §7.1)`, "e.g. OMGBASE_SPEC_CLOCK=2026-09-27T00:00:00.000Z");
27
+ }
28
+ seams.clock = new Date().toISOString();
29
+ }
30
+ installed = seams;
31
+ return seams;
32
+ }
33
+ /** The seams installed by `installSpecSeams` (none when it has not run). */
34
+ export function activeSpecSeams() {
35
+ return installed ?? { minter: false, clock: null };
36
+ }
37
+ //# sourceMappingURL=seams.js.map
@@ -142,7 +142,7 @@ export class ShellSession {
142
142
  line(`${style.accent("@1 @2 …")} ${style.dim("rows of the last displayed collection")}`);
143
143
  line(`${style.accent("@_")} ${style.dim("the previous command's result")}`);
144
144
  line(`${style.accent("@name")} ${style.dim("a named binding (also @name[i], @name.field)")}`);
145
- line("");
145
+ io.out("");
146
146
  line(`${style.accent("@x = <cmd|@ref>")} ${style.dim("bind a snapshot of a result")}`);
147
147
  line(`${style.accent("unset x")} ${style.dim("drop a binding")}`);
148
148
  line(`${style.accent("bindings")} ${style.dim("list bindings")}`);
@@ -162,7 +162,9 @@ export class ShellSession {
162
162
  * (used by `let`, which snapshots rather than displays).
163
163
  */
164
164
  async dispatch(tokens, sink, quiet) {
165
- const { flags, command, rest } = parseGlobals(tokens);
165
+ const { flags, command, rest, error } = parseGlobals(tokens);
166
+ if (error)
167
+ throw error;
166
168
  if (!command)
167
169
  return EXIT_OK;
168
170
  // In a remote session, thread the server address into every line (unless the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omgbase",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Open Markdown Graph Base — CLI",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,16 +45,16 @@
45
45
  "omg": "./dist/src/main.js"
46
46
  },
47
47
  "dependencies": {
48
- "@omgbase/core": "^0.4.0",
48
+ "@omgbase/core": "^0.5.0",
49
49
  "@omgbase/fs-adapter": "^0.2.0",
50
- "@omgbase/sync": "^0.4.0",
50
+ "@omgbase/sync": "^0.4.2",
51
51
  "yaml": "^2.9.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@simplebrains/recital": "^0.8.1"
55
55
  },
56
56
  "scripts": {
57
- "build": "tsc -p tsconfig.json",
57
+ "build": "tsc -p tsconfig.json && node scripts/build-info.mjs",
58
58
  "typecheck": "tsc -p tsconfig.json --noEmit",
59
59
  "test": "vitest run",
60
60
  "lint": "eslint .",