omgbase 0.4.1 → 0.6.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.
@@ -0,0 +1,4 @@
1
+ {
2
+ "commit": "b380706",
3
+ "built": "2026-09-28T23:04:02.290Z"
4
+ }
@@ -0,0 +1,22 @@
1
+ import { type HostInfo } from "@omgbase/core";
2
+ /**
3
+ * `--version` prints THIS package's version (spec/cli §2.5), read from the
4
+ * nearest `package.json` named `omgbase` above the running module (the source
5
+ * tree and the built `dist/src/` sit at different depths). The engine's own
6
+ * `VERSION` is the fallback only if the package file cannot be found.
7
+ */
8
+ export declare function cliVersion(): string;
9
+ export interface BuildInfo {
10
+ commit: string | null;
11
+ built: string | null;
12
+ }
13
+ /**
14
+ * `dist/build-info.json`, written by `pnpm build` (`scripts/build-info.mjs`):
15
+ * `{ commit: "<short sha>" | null, built: "<RFC 3339>" }`. It sits one level
16
+ * above this module's directory (`dist/src/` → `dist/`), so a source-tree run
17
+ * (vitest over `src/`) looks for `packages/cli/build-info.json`, finds nothing,
18
+ * and reports nulls — the same answer a build outside a git checkout gives.
19
+ */
20
+ export declare function buildInfo(): BuildInfo;
21
+ /** The `HostInfo` this binary hands the engine's `version` tool (`omg mcp`) and verb (`omg version`). */
22
+ export declare function hostInfo(): HostInfo;
@@ -0,0 +1,58 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { VERSION } from "@omgbase/core";
5
+ // What this binary knows about itself (spec/cli §2.5 `--version`, §6 `version`):
6
+ // its package version and the build's git revision + time.
7
+ /**
8
+ * `--version` prints THIS package's version (spec/cli §2.5), read from the
9
+ * nearest `package.json` named `omgbase` above the running module (the source
10
+ * tree and the built `dist/src/` sit at different depths). The engine's own
11
+ * `VERSION` is the fallback only if the package file cannot be found.
12
+ */
13
+ export function cliVersion() {
14
+ let dir = dirname(fileURLToPath(import.meta.url));
15
+ for (;;) {
16
+ const candidate = join(dir, "package.json");
17
+ if (existsSync(candidate)) {
18
+ try {
19
+ const pkg = JSON.parse(readFileSync(candidate, "utf8"));
20
+ if (pkg.name === "omgbase" && typeof pkg.version === "string")
21
+ return pkg.version;
22
+ }
23
+ catch {
24
+ /* keep walking */
25
+ }
26
+ }
27
+ const parent = dirname(dir);
28
+ if (parent === dir)
29
+ return VERSION;
30
+ dir = parent;
31
+ }
32
+ }
33
+ /**
34
+ * `dist/build-info.json`, written by `pnpm build` (`scripts/build-info.mjs`):
35
+ * `{ commit: "<short sha>" | null, built: "<RFC 3339>" }`. It sits one level
36
+ * above this module's directory (`dist/src/` → `dist/`), so a source-tree run
37
+ * (vitest over `src/`) looks for `packages/cli/build-info.json`, finds nothing,
38
+ * and reports nulls — the same answer a build outside a git checkout gives.
39
+ */
40
+ export function buildInfo() {
41
+ const path = new URL("../build-info.json", import.meta.url);
42
+ try {
43
+ const raw = JSON.parse(readFileSync(path, "utf8"));
44
+ return {
45
+ commit: typeof raw.commit === "string" ? raw.commit : null,
46
+ built: typeof raw.built === "string" ? raw.built : null,
47
+ };
48
+ }
49
+ catch {
50
+ return { commit: null, built: null };
51
+ }
52
+ }
53
+ /** The `HostInfo` this binary hands the engine's `version` tool (`omg mcp`) and verb (`omg version`). */
54
+ export function hostInfo() {
55
+ const { commit, built } = buildInfo();
56
+ return { version: cliVersion(), commit, built, resolveFrom: import.meta.url };
57
+ }
58
+ //# sourceMappingURL=build.js.map
@@ -30,18 +30,39 @@ function report(cli, verb, res) {
30
30
  cli.io.out(res.docId);
31
31
  return EXIT_OK;
32
32
  }
33
- // `mv` does not rewrite inbound links; say so wherever the move is reported
34
- // (spec/cli §6 `mv`): the human confirmation carries the same `dangling` list
35
- // the --json result does, with the retarget that fixes them.
33
+ // `mv` rewrites the inbound links by default (spec/mutate 1.3; `--no-retarget`
34
+ // opts out); say what happened wherever the move is reported (spec/cli §6 `mv`):
35
+ // the human confirmation carries the `retargeted` count and the same `dangling`
36
+ // list the --json result does, with the fix for each kind — `retarget` for
37
+ // authored links left as written, `meta` for a frontmatter relation (never
38
+ // rewritten by a move).
36
39
  function reportMove(cli, from, res) {
37
40
  const code = report(cli, "moved to", res);
38
- if (cli.flags.mode !== "human" || res.dangling.length === 0)
41
+ if (cli.flags.mode !== "human")
39
42
  return code;
40
43
  const { style, io } = cli;
44
+ if (!cli.flags.dryRun && res.retargeted && res.retargeted.blocks.length > 0) {
45
+ const b = res.retargeted.blocks.length;
46
+ const d = res.retargeted.docs.length;
47
+ io.err(style.dim(` ${style.ok(cli.render.g.ok)} retargeted ${b} inbound link${b === 1 ? "" : "s"} in ${d} document${d === 1 ? "" : "s"}`));
48
+ }
49
+ if (res.dangling.length === 0)
50
+ return code;
41
51
  const where = res.dangling.map((l) => (l.block ? `${l.path} ${l.block}` : `${l.path} (frontmatter)`)).join(", ");
42
52
  const n = res.dangling.length;
43
53
  io.err(style.warn(` ${style.warn(cli.render.g.warn)} ${n} inbound link${n === 1 ? "" : "s"} still name${n === 1 ? "s" : ""} the old path: ${where}`));
44
- io.err(style.dim(` fix: ${cli.prog} retarget /${from} /${res.path} --apply`));
54
+ if (res.dangling.some((l) => l.block))
55
+ io.err(style.dim(` fix: ${cli.prog} retarget /${from} /${res.path} --apply`));
56
+ const seen = new Set();
57
+ for (const l of res.dangling) {
58
+ if (l.block || !l.field)
59
+ continue;
60
+ const key = `${l.path}\u0000${l.field}`;
61
+ if (seen.has(key))
62
+ continue;
63
+ seen.add(key);
64
+ io.err(style.dim(` fix: ${cli.prog} meta ${l.path} --set ${l.field}=/${res.path}`));
65
+ }
45
66
  return code;
46
67
  }
47
68
  // ---- new --------------------------------------------------------------------
@@ -82,27 +103,31 @@ async function runMv(cli, args) {
82
103
  const { values, positionals } = parseArgs({
83
104
  args,
84
105
  allowPositionals: true,
85
- options: { actor: { type: "string" }, help: { type: "boolean" } },
106
+ options: { actor: { type: "string" }, "no-retarget": { type: "boolean" }, help: { type: "boolean" } },
86
107
  });
87
108
  if (values.help) {
88
109
  return renderHelp(cli, {
89
110
  name: "mv",
90
- summary: "Rename/move a document to a new path; its identity and history are preserved",
91
- usage: "mv <doc> <new-path> [--actor <s>] [--dry-run]",
92
- options: [["--actor <s>", "commit actor (default human:$USER)"]],
111
+ summary: "Rename/move a document to a new path; its identity and history are preserved and inbound links follow it",
112
+ usage: "mv <doc> <new-path> [--no-retarget] [--actor <s>] [--dry-run]",
113
+ options: [
114
+ ["--no-retarget", "leave the inbound links as written (they dangle; fix later with retarget)"],
115
+ ["--actor <s>", "commit actor (default human:$USER)"],
116
+ ],
93
117
  });
94
118
  }
95
119
  const doc = positionals[0];
96
120
  const toPath = positionals[1];
97
121
  if (!doc || !toPath)
98
122
  throw new CliUsageError("mv requires <doc> and <new-path>");
123
+ const noRetarget = values["no-retarget"] === true;
99
124
  if (cli.flags.server) {
100
- const res = await remoteCall(cli, "docs_move", { doc, to_path: toPath, ...dryRunArg(cli) });
125
+ const res = await remoteCall(cli, "docs_move", { doc, to_path: toPath, ...(noRetarget ? { retarget_inbound: false } : {}), ...dryRunArg(cli) });
101
126
  return reportMove(cli, fromPathOf(res, doc), res);
102
127
  }
103
128
  const ws = cli.workspace();
104
129
  const repo = cli.repo(ws);
105
- const res = docsMove(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, toPath);
130
+ const res = docsMove(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, toPath, noRetarget ? { retargetInbound: false } : {});
106
131
  return reportMove(cli, fromPathOf(res, doc), res);
107
132
  }
108
133
  // ---- rm --doc (block rm lives in mutate.ts; this handles the --doc branch) --
@@ -11,7 +11,7 @@ const GROUPS = [
11
11
  { title: "documents", names: ["new", "mv", "meta"] },
12
12
  { title: "session", names: ["shell"] },
13
13
  { title: "sync & serve", names: ["sync", "mcp"] },
14
- { title: "admin", names: ["rebuild-index", "gc", "doctor", "config", "embed"] },
14
+ { title: "admin", names: ["rebuild-index", "gc", "doctor", "config", "embed", "version"] },
15
15
  ];
16
16
  async function runHelp(cli) {
17
17
  const { render, style, io, prog } = cli;
@@ -4,6 +4,7 @@ import { EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
4
4
  import { installSpecSeams } from "../seams.js";
5
5
  import { loadEmbedding } from "./_embed.js";
6
6
  import { openRepoSource } from "./_source.js";
7
+ import { hostInfo } from "../build.js";
7
8
  // `omg mcp [--no-watch]` (11 §5.8) — MCP server on stdio; the host owns the
8
9
  // process lifetime. Runs an in-process watcher by default so a lone session is
9
10
  // always fresh, auto-disabled when another live watch lease exists; --no-watch
@@ -166,6 +167,8 @@ async function runMcp(cli, args) {
166
167
  ...(repo.rootPath ? { rootPath: repo.rootPath } : {}),
167
168
  ...(drainer ? { onMutation: () => drainer.schedule() } : {}),
168
169
  ...(embedQuery ? { embedQuery } : {}),
170
+ // The `version` tool's view of this binary (spec/surface §4).
171
+ host: hostInfo(),
169
172
  });
170
173
  let shuttingDown = false;
171
174
  const shutdown = async () => {
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdVersion: Command;
@@ -0,0 +1,68 @@
1
+ import { parseArgs } from "node:util";
2
+ import { versionInfo } from "@omgbase/core";
3
+ import { EXIT_OK, emitMachine, renderHelp } from "../output.js";
4
+ import { hostInfo } from "../build.js";
5
+ import { remoteCall } from "./_remote.js";
6
+ // `omg version` (spec/cli §6, 1.1): the surface's `version` tool, rendered.
7
+ // Which engine and which component versions a host or a shell is talking to —
8
+ // with `--server` it is the REMOTE engine's answer, which is exactly how you
9
+ // tell a TypeScript `omg mcp` from a Rust `omgbase mcp`. Needs no repo; the
10
+ // workspace is opened only for `schema` (`—` without one). `--version` (§2.5)
11
+ // stays the one-line form.
12
+ async function runVersion(cli, args) {
13
+ const { values } = parseArgs({ args, allowPositionals: true, options: { help: { type: "boolean" } } });
14
+ if (values.help) {
15
+ return renderHelp(cli, {
16
+ name: "version",
17
+ summary: "Which engine and which versions: the binary, its components, the specs it implements, the schema, MCP protocol, runtime, build",
18
+ usage: "version [--json]",
19
+ options: [["--json", "the `version` tool result verbatim"]],
20
+ notes: ["with --server, the remote engine's answer (typescript or rust)"],
21
+ });
22
+ }
23
+ let info;
24
+ if (cli.flags.server !== undefined) {
25
+ info = await remoteCall(cli, "version", {});
26
+ }
27
+ else {
28
+ // `schema` is the open database's user_version; without a workspace the
29
+ // command still answers (spec/cli §3.7: version ∈ NO_WORKSPACE_OK).
30
+ let store = null;
31
+ try {
32
+ store = cli.workspace().store;
33
+ }
34
+ catch {
35
+ store = null;
36
+ }
37
+ info = versionInfo(store, hostInfo());
38
+ }
39
+ cli.capture?.(info);
40
+ if (cli.flags.mode !== "human")
41
+ return emitMachine(cli, info);
42
+ const { io, style } = cli;
43
+ const dash = "—";
44
+ const keys = ["engine", "version", "specs", "schema", "mcp", "runtime", "commit", "built"];
45
+ const width = Math.max(...keys.map((k) => k.length));
46
+ const line = (key, value) => {
47
+ io.out(`${style.dim(key.padEnd(width))} ${value === null ? style.dim(dash) : String(value)}`);
48
+ };
49
+ line("engine", info.engine);
50
+ line("version", info.version);
51
+ const names = Object.keys(info.components);
52
+ const nameWidth = Math.max(0, ...names.map((n) => n.length));
53
+ for (const name of names)
54
+ io.out(` ${style.accent(name.padEnd(nameWidth))} ${info.components[name]}`);
55
+ line("specs", Object.entries(info.specs).map(([k, v]) => `${k} ${v}`).join(" · "));
56
+ line("schema", info.schema);
57
+ line("mcp", info.mcp.sdk !== undefined ? `${info.mcp.protocol} (sdk ${info.mcp.sdk})` : info.mcp.protocol);
58
+ line("runtime", info.runtime);
59
+ line("commit", info.commit);
60
+ line("built", info.built);
61
+ return EXIT_OK;
62
+ }
63
+ export const cmdVersion = {
64
+ name: "version",
65
+ summary: "Which engine and which versions (binary, components, specs, schema, MCP, runtime, build)",
66
+ run: (cli, a) => runVersion(cli, a),
67
+ };
68
+ //# sourceMappingURL=version.js.map
@@ -24,6 +24,7 @@ import { cmdRebuild, cmdGc, cmdDoctor, cmdConfig, cmdEmbed } from "./cmd/admin.j
24
24
  import { cmdShell } from "./cmd/shell.js";
25
25
  import { cmdSource } from "./cmd/source.js";
26
26
  import { cmdHelp } from "./cmd/help.js";
27
+ import { cmdVersion } from "./cmd/version.js";
27
28
  export const COMMANDS = [
28
29
  cmdInit,
29
30
  cmdRepos,
@@ -63,6 +64,7 @@ export const COMMANDS = [
63
64
  cmdDoctor,
64
65
  cmdConfig,
65
66
  cmdEmbed,
67
+ cmdVersion,
66
68
  cmdHelp,
67
69
  ];
68
70
  const BY_NAME = new Map();
@@ -89,6 +89,8 @@ export const REMOTE_OK = new Set([
89
89
  "apply", "insert", "move", "split", "merge", "done", "append", "node",
90
90
  // shell accepts --server itself, then threads it into every line it runs.
91
91
  "shell",
92
+ // version asks the REMOTE engine which engine/versions it is (spec/cli §6).
93
+ "version",
92
94
  ]);
93
95
  export { CliUsageError };
94
96
  //# sourceMappingURL=context.js.map
@@ -1,10 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  import { type IO } from "./render.js";
3
- /**
4
- * `--version` prints THIS package's version (spec/cli §2.5), read from the
5
- * nearest `package.json` named `omgbase` above the running module (the source
6
- * tree and the built `dist/src/` sit at different depths). The engine's own
7
- * `VERSION` is the fallback only if the package file cannot be found.
8
- */
9
- export declare function cliVersion(): string;
3
+ import { cliVersion } from "./build.js";
4
+ export { cliVersion };
10
5
  export declare function run(argv: string[], io?: IO, prog?: string): Promise<number>;
package/dist/src/main.js CHANGED
@@ -1,8 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { existsSync, readFileSync } from "node:fs";
3
- import { dirname, join } from "node:path";
4
- import { fileURLToPath } from "node:url";
5
- import { VERSION } from "@omgbase/core";
6
2
  import { processIO } from "./render.js";
7
3
  import { makeCli, progName } from "./context.js";
8
4
  import { renderError, EXIT_OK } from "./output.js";
@@ -10,36 +6,12 @@ import { resolveCommand, unknownCommandError } from "./commands.js";
10
6
  import { runCommand, parseGlobals } from "./dispatch.js";
11
7
  import { closeRemote } from "./cmd/_remote.js";
12
8
  import { installSpecSeams } from "./seams.js";
9
+ import { cliVersion } from "./build.js";
13
10
  // Hand-rolled router (11 §8: no commander). Splits global flags from the
14
11
  // command + its residual argv (parseGlobals, in dispatch.ts), resolves the
15
12
  // command (with aliases), then delegates to runCommand for the freshness sweep +
16
13
  // dispatch + error mapping — the same path the shell reuses per line.
17
- /**
18
- * `--version` prints THIS package's version (spec/cli §2.5), read from the
19
- * nearest `package.json` named `omgbase` above the running module (the source
20
- * tree and the built `dist/src/` sit at different depths). The engine's own
21
- * `VERSION` is the fallback only if the package file cannot be found.
22
- */
23
- export function cliVersion() {
24
- let dir = dirname(fileURLToPath(import.meta.url));
25
- for (;;) {
26
- const candidate = join(dir, "package.json");
27
- if (existsSync(candidate)) {
28
- try {
29
- const pkg = JSON.parse(readFileSync(candidate, "utf8"));
30
- if (pkg.name === "omgbase" && typeof pkg.version === "string")
31
- return pkg.version;
32
- }
33
- catch {
34
- /* keep walking */
35
- }
36
- }
37
- const parent = dirname(dir);
38
- if (parent === dir)
39
- return VERSION;
40
- dir = parent;
41
- }
42
- }
14
+ export { cliVersion };
43
15
  export async function run(argv, io = processIO, prog = "omg") {
44
16
  const parsed = parseGlobals(argv);
45
17
  const { flags } = parsed;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omgbase",
3
- "version": "0.4.1",
3
+ "version": "0.6.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.1",
48
+ "@omgbase/core": "^0.6.0",
49
49
  "@omgbase/fs-adapter": "^0.2.0",
50
- "@omgbase/sync": "^0.4.1",
50
+ "@omgbase/sync": "^0.4.3",
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 .",