omgbase 0.10.0 → 0.11.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.
- package/README.md +1 -1
- package/dist/build-info.json +2 -2
- package/dist/src/cmd/_mutate.d.ts +1 -1
- package/dist/src/cmd/_mutate.js +1 -1
- package/dist/src/cmd/_remote.d.ts +16 -0
- package/dist/src/cmd/_remote.js +25 -1
- package/dist/src/cmd/cat.d.ts +9 -0
- package/dist/src/cmd/cat.js +44 -11
- package/dist/src/cmd/diff.js +9 -9
- package/dist/src/cmd/docs.js +15 -14
- package/dist/src/cmd/edit.js +1 -1
- package/dist/src/cmd/find.js +2 -2
- package/dist/src/cmd/hist.js +3 -3
- package/dist/src/cmd/links.js +12 -12
- package/dist/src/cmd/ls.js +5 -3
- package/dist/src/cmd/mcp.js +3 -2
- package/dist/src/cmd/mutate.js +35 -18
- package/dist/src/cmd/node.js +5 -4
- package/dist/src/cmd/oqx.js +20 -15
- package/dist/src/cmd/outline.js +4 -4
- package/dist/src/cmd/retarget.js +1 -1
- package/dist/src/cmd/run.js +1 -1
- package/dist/src/cmd/show.js +25 -11
- package/dist/src/cmd/update.js +3 -3
- package/dist/src/context.js +1 -1
- package/dist/src/output.d.ts +6 -3
- package/dist/src/output.js +7 -6
- package/dist/src/shell/refs.js +14 -4
- package/dist/src/shell/session.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -182,7 +182,7 @@ The global `--server <cmd|url>` flag runs the same command against a **remote en
|
|
|
182
182
|
Global flags are recognized anywhere on the line: `-C <dir>`, `--repo <slug>`, `--json`, `--jsonl`, `--ids`, `--stale`, `--no-color`, `--dry-run`, `--server <cmd|url>`, `-H <header>`, `--help`/`-h`, `--version`/`-V`.
|
|
183
183
|
|
|
184
184
|
- Human output (default) is colorized on a TTY; `--no-color`, `NO_COLOR`, or a pipe degrades to plain text.
|
|
185
|
-
- Query hits (`omg q`, `omg run`) print as `<id> <path>` lines. When the query projects (`select …`), they print as an aligned table instead: a dim header row, the id first, then the path (unless the projection itself selects
|
|
185
|
+
- Query hits (`omg q`, `omg run`) print as `<id> <path>` lines. When the query projects (`select …`), they print as an aligned table instead: a dim header row, the id first, then the path (unless the projection itself selects `path`), then the projected columns in `select` order. Strings and numbers are verbatim, an absent field is an empty cell, and a nested list/record is compact JSON clipped at 60 characters with `…`; `--jsonl` has the full values.
|
|
186
186
|
- `--json` prints the library's result object verbatim as one JSON document (the same shape the MCP tools return). `--jsonl` flattens list results to one object per line; `--ids` prints bare ids/paths, one per line — pipe fuel for `omg cat -`, `omg show -`, `omg done -`.
|
|
187
187
|
- `--dry-run` is global across every mutator: full validation and render, diffs printed, nothing committed.
|
|
188
188
|
- Errors always go to stderr: `error[<code>]: <message>` plus any conflict payload, or the typed error object as JSON with `--json`.
|
package/dist/build-info.json
CHANGED
|
@@ -38,7 +38,7 @@ export interface RunOpsOptions {
|
|
|
38
38
|
export declare function runOps(cli: Cli, ws: Workspace, repo: RepoRow, ops: Op[], opts?: RunOpsOptions): number;
|
|
39
39
|
/**
|
|
40
40
|
* Remote peer of runOps: call the changeset-producing tool that mirrors this
|
|
41
|
-
* command (blocks_insert,
|
|
41
|
+
* command (blocks_insert, blocks_check, …) against the `--server` engine — the
|
|
42
42
|
* server resolves refs + pins CAS + applies — then render the returned
|
|
43
43
|
* ApplyResult with the SAME renderers as local. --dry-run is threaded as the
|
|
44
44
|
* tool's dry_run so the preview comes back without committing.
|
package/dist/src/cmd/_mutate.js
CHANGED
|
@@ -142,7 +142,7 @@ export function runOps(cli, ws, repo, ops, opts = {}) {
|
|
|
142
142
|
}
|
|
143
143
|
/**
|
|
144
144
|
* Remote peer of runOps: call the changeset-producing tool that mirrors this
|
|
145
|
-
* command (blocks_insert,
|
|
145
|
+
* command (blocks_insert, blocks_check, …) against the `--server` engine — the
|
|
146
146
|
* server resolves refs + pins CAS + applies — then render the returned
|
|
147
147
|
* ApplyResult with the SAME renderers as local. --dry-run is threaded as the
|
|
148
148
|
* tool's dry_run so the preview comes back without committing.
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type EngineSpec } from "@omgbase/sync";
|
|
2
|
+
import { type Store } from "@omgbase/core";
|
|
2
3
|
import type { Cli } from "../context.js";
|
|
3
4
|
/** Classify the global `--server` (+ `-H`) flags into an `EngineSpec`: an
|
|
4
5
|
* http(s) URL → Streamable HTTP with the headers attached; anything else → a
|
|
@@ -10,6 +11,21 @@ export declare function remoteSpec(cli: Cli): EngineSpec;
|
|
|
10
11
|
* Returns the tool's parsed JSON result (same shape the local core fn returns,
|
|
11
12
|
* so the command's existing renderer works unchanged). */
|
|
12
13
|
export declare function remoteCall<T>(cli: Cli, tool: string, args: Record<string, unknown>): Promise<T>;
|
|
14
|
+
/** A document ref as the surface's locator (spec/surface §4 "Arguments", 3.0):
|
|
15
|
+
* a `d_` id is the `id` argument, anything else the `path` — the two names a
|
|
16
|
+
* `docs_*` tool takes (one of them required). */
|
|
17
|
+
export declare function docLocator(ref: string): {
|
|
18
|
+
id: string;
|
|
19
|
+
} | {
|
|
20
|
+
path: string;
|
|
21
|
+
};
|
|
22
|
+
/** The live document a ref names, for a LOCAL verb, or the surface's own
|
|
23
|
+
* `doc_missing` ("no document with id or path "<ref>"", `data` the locator) —
|
|
24
|
+
* the same wording the `docs_*` tools give a remote caller (spec/surface §4
|
|
25
|
+
* "Arguments"), so `omg mv nope.md …` reads alike in both modes. */
|
|
26
|
+
export declare function requireDoc(store: Store, repoId: string, ref: string): string;
|
|
27
|
+
/** The live node a ref names, or the surface's `block_missing` ("no node with id "<ref>""). */
|
|
28
|
+
export declare function requireNode(store: Store, repoId: string, nodeId: string): string;
|
|
13
29
|
/** Close the remote connection if one was opened (idempotent). Called after a
|
|
14
30
|
* one-shot command and on shell exit. */
|
|
15
31
|
export declare function closeRemote(): Promise<void>;
|
package/dist/src/cmd/_remote.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { connectEngine, parseEngineSpec } from "@omgbase/sync";
|
|
2
|
-
import {
|
|
2
|
+
import { isValidId, findDocByRef } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike } from "../output.js";
|
|
3
4
|
// Remote (`--server`) mode plumbing (ADR-014). When a command runs with the
|
|
4
5
|
// global `--server <cmd|url>` flag, it reaches a remote engine over MCP instead
|
|
5
6
|
// of the embedded local store: it calls the MCP tool that mirrors its local
|
|
@@ -56,6 +57,29 @@ export async function remoteCall(cli, tool, args) {
|
|
|
56
57
|
const eng = await remoteEngine(cli);
|
|
57
58
|
return eng.callTool(tool, { ...args, ...(cli.flags.repo ? { repo: cli.flags.repo } : {}) });
|
|
58
59
|
}
|
|
60
|
+
/** A document ref as the surface's locator (spec/surface §4 "Arguments", 3.0):
|
|
61
|
+
* a `d_` id is the `id` argument, anything else the `path` — the two names a
|
|
62
|
+
* `docs_*` tool takes (one of them required). */
|
|
63
|
+
export function docLocator(ref) {
|
|
64
|
+
return isValidId(ref, "d") ? { id: ref } : { path: ref };
|
|
65
|
+
}
|
|
66
|
+
/** The live document a ref names, for a LOCAL verb, or the surface's own
|
|
67
|
+
* `doc_missing` ("no document with id or path "<ref>"", `data` the locator) —
|
|
68
|
+
* the same wording the `docs_*` tools give a remote caller (spec/surface §4
|
|
69
|
+
* "Arguments"), so `omg mv nope.md …` reads alike in both modes. */
|
|
70
|
+
export function requireDoc(store, repoId, ref) {
|
|
71
|
+
const info = findDocByRef(store, repoId, ref);
|
|
72
|
+
if (!info)
|
|
73
|
+
throw new EngineErrorLike("doc_missing", `no document with id or path ${JSON.stringify(ref)}`, { data: docLocator(ref) });
|
|
74
|
+
return info.docId;
|
|
75
|
+
}
|
|
76
|
+
/** The live node a ref names, or the surface's `block_missing` ("no node with id "<ref>""). */
|
|
77
|
+
export function requireNode(store, repoId, nodeId) {
|
|
78
|
+
const row = store.db.prepare("SELECT 1 FROM nodes WHERE node_id = ? AND repo_id = ?").get(nodeId, repoId);
|
|
79
|
+
if (!row)
|
|
80
|
+
throw new EngineErrorLike("block_missing", `no node with id ${JSON.stringify(nodeId)}`, { data: { id: nodeId } });
|
|
81
|
+
return nodeId;
|
|
82
|
+
}
|
|
59
83
|
/** Close the remote connection if one was opened (idempotent). Called after a
|
|
60
84
|
* one-shot command and on shell exit. */
|
|
61
85
|
export async function closeRemote() {
|
package/dist/src/cmd/cat.d.ts
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { Command } from "../commands.js";
|
|
2
|
+
/** A ref that names a block: a `b_` block id, an `n_` node id (its block), or a
|
|
3
|
+
* `path#type[n]` locator — everything else is a document (a `d_` id or a path). */
|
|
4
|
+
export declare function isBlockRef(ref: string): boolean;
|
|
5
|
+
/** The remote tool that reads the block a ref names, by the ref's SHAPE (ids
|
|
6
|
+
* carry their kind, spec/surface §4 "Names and arguments"): an `n_` node id is
|
|
7
|
+
* `nodes_block_read` (the block the node was projected from — `blocks_read`'s
|
|
8
|
+
* shape; `blocks_read` itself takes a block id only), a `b_` id or a
|
|
9
|
+
* `path#type[n]` locator is `blocks_read`, anything else is a document (null). */
|
|
10
|
+
export declare function blockReadTool(ref: string): "nodes_block_read" | "blocks_read" | null;
|
|
2
11
|
export declare const cmdCat: Command;
|
package/dist/src/cmd/cat.js
CHANGED
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolveRef, nodesGet, docsRead } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
|
-
import { remoteCall } from "./_remote.js";
|
|
4
|
+
import { remoteCall, docLocator } from "./_remote.js";
|
|
5
5
|
import { expandBlockArgs } from "./_mutate.js";
|
|
6
|
+
// `omg cat <node…|->` (11 §5.2) — content only, default raw exact bytes, pipe-clean.
|
|
7
|
+
// For a document ref, cats the whole rendered document; for a block, the block
|
|
8
|
+
// (subtree at raw). No decoration on stdout: cat is the bytes. Several refs (or
|
|
9
|
+
// `-` = refs from stdin, one per line — the counterpart of `--ids`, same
|
|
10
|
+
// convention as the mutators) are cat'ed in order, like unix cat. `--json` is
|
|
11
|
+
// the mapped tool's result: `docs_read` for a document, `blocks_read` for a block,
|
|
12
|
+
// `nodes_block_read` for a node (the block it was projected from — the same shape).
|
|
13
|
+
//
|
|
14
|
+
// Refs are what `resolveRef` accepts: a block/doc/node id or a document path
|
|
15
|
+
// in either form (`/notes/x.md`, `notes/x.md`). `--resolution` is a block-level notion (blocks_read); a document
|
|
16
|
+
// is always its exact bytes (docs_read has no resolution ladder), so asking for
|
|
17
|
+
// another resolution on a document ref is warned about, not silently ignored.
|
|
18
|
+
/** A ref that names a block: a `b_` block id, an `n_` node id (its block), or a
|
|
19
|
+
* `path#type[n]` locator — everything else is a document (a `d_` id or a path). */
|
|
20
|
+
export function isBlockRef(ref) {
|
|
21
|
+
return blockReadTool(ref) !== null;
|
|
22
|
+
}
|
|
23
|
+
/** The remote tool that reads the block a ref names, by the ref's SHAPE (ids
|
|
24
|
+
* carry their kind, spec/surface §4 "Names and arguments"): an `n_` node id is
|
|
25
|
+
* `nodes_block_read` (the block the node was projected from — `blocks_read`'s
|
|
26
|
+
* shape; `blocks_read` itself takes a block id only), a `b_` id or a
|
|
27
|
+
* `path#type[n]` locator is `blocks_read`, anything else is a document (null). */
|
|
28
|
+
export function blockReadTool(ref) {
|
|
29
|
+
if (ref.startsWith("n_"))
|
|
30
|
+
return "nodes_block_read";
|
|
31
|
+
if (ref.startsWith("b_") || ref.includes("#"))
|
|
32
|
+
return "blocks_read";
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
6
35
|
/** `--resolution` only means something for a block; say so (stderr) rather than
|
|
7
36
|
* silently handing back raw bytes as if the flag had applied. */
|
|
8
37
|
function warnDocResolution(cli, ref, resolution) {
|
|
@@ -11,17 +40,21 @@ function warnDocResolution(cli, ref, resolution) {
|
|
|
11
40
|
cli.io.err(cli.style.warn(` --resolution ${resolution} ignored for ${ref}: a document is always its exact bytes (resolutions apply to block refs)`));
|
|
12
41
|
}
|
|
13
42
|
async function catOne(cli, ref, resolution) {
|
|
14
|
-
// Remote: the
|
|
43
|
+
// Remote: the ref's SHAPE picks the tool (ids carry their kind, a path is a
|
|
44
|
+
// document): an `n_` node id is `nodes_block_read` (the block it was projected
|
|
45
|
+
// from), a `b_` id or a `path#type[n]` locator is `blocks_read` (a block id
|
|
46
|
+
// only — an `n_` id there is `block_missing`), a `d_` id or a path is
|
|
47
|
+
// `docs_read` — the same results the local run renders (`read_ref` classified
|
|
48
|
+
// server-side before surface 3.0).
|
|
15
49
|
if (cli.flags.server) {
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const { kind: _kind, ...doc } = r;
|
|
21
|
-
void _kind;
|
|
22
|
-
return { text: String(r.content ?? ""), json: doc, capture: r.content };
|
|
50
|
+
const tool = blockReadTool(ref);
|
|
51
|
+
if (tool) {
|
|
52
|
+
const r = await remoteCall(cli, tool, { id: ref, resolution });
|
|
53
|
+
return { text: String(r.raw ?? r.text ?? r.label ?? ""), json: r, capture: r };
|
|
23
54
|
}
|
|
24
|
-
|
|
55
|
+
const r = await remoteCall(cli, "docs_read", docLocator(ref));
|
|
56
|
+
warnDocResolution(cli, ref, resolution);
|
|
57
|
+
return { text: String(r.raw ?? ""), json: r, capture: r.raw };
|
|
25
58
|
}
|
|
26
59
|
const ws = cli.workspace();
|
|
27
60
|
const repo = cli.repo(ws);
|
|
@@ -39,7 +72,7 @@ async function catOne(cli, ref, resolution) {
|
|
|
39
72
|
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
40
73
|
warnDocResolution(cli, ref, resolution);
|
|
41
74
|
// shell: the bytes (a string; @_ only). `--json` is the `docs_read` result.
|
|
42
|
-
return { text: res.
|
|
75
|
+
return { text: res.raw, json: res, capture: res.raw };
|
|
43
76
|
}
|
|
44
77
|
const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution });
|
|
45
78
|
if (!node)
|
package/dist/src/cmd/diff.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { findDoc, docDiffUnified, diffBlocks } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
|
-
import { remoteCall } from "./_remote.js";
|
|
5
|
-
// `omg diff <doc>` (11 §5.5) — `
|
|
4
|
+
import { remoteCall, docLocator } from "./_remote.js";
|
|
5
|
+
// `omg diff <doc>` (11 §5.5) — `docs_diff_unified`: the unified diff, `--json` the
|
|
6
6
|
// tool's `{ doc, path, from, to, diff }`. With no revisions: current vs previous
|
|
7
7
|
// ("what did the last commit do here"). `--blocks` is the block-grain `diff`
|
|
8
8
|
// tool: added/removed/changed blocks between the same two revisions.
|
|
@@ -20,7 +20,7 @@ async function runDiff(cli, args) {
|
|
|
20
20
|
options: [
|
|
21
21
|
["--from <rev>", "older revision id (default: the one before --to)"],
|
|
22
22
|
["--to <rev>", "newer revision id (default: current)"],
|
|
23
|
-
["--blocks", "block-grain: which blocks were added, removed or changed (the `
|
|
23
|
+
["--blocks", "block-grain: which blocks were added, removed or changed (the `docs_diff` tool)"],
|
|
24
24
|
],
|
|
25
25
|
});
|
|
26
26
|
}
|
|
@@ -29,8 +29,8 @@ async function runDiff(cli, args) {
|
|
|
29
29
|
throw new CliUsageError("diff requires a <doc>");
|
|
30
30
|
let result;
|
|
31
31
|
if (cli.flags.server) {
|
|
32
|
-
result = await remoteCall(cli, "
|
|
33
|
-
|
|
32
|
+
result = await remoteCall(cli, "docs_diff_unified", {
|
|
33
|
+
...docLocator(ref),
|
|
34
34
|
...(values.from ? { from_rev: values.from } : {}),
|
|
35
35
|
...(values.to ? { to_rev: values.to } : {}),
|
|
36
36
|
});
|
|
@@ -52,11 +52,11 @@ async function runDiff(cli, args) {
|
|
|
52
52
|
// Block-grain over the same resolved revision pair.
|
|
53
53
|
let entries;
|
|
54
54
|
if (cli.flags.server)
|
|
55
|
-
entries = await remoteCall(cli, "
|
|
55
|
+
entries = await remoteCall(cli, "docs_diff", { ...docLocator(ref), from_rev: result.from, to_rev: result.to });
|
|
56
56
|
else
|
|
57
|
-
entries = diffBlocks(cli.workspace().store, result.
|
|
57
|
+
entries = diffBlocks(cli.workspace().store, result.id, result.from, result.to);
|
|
58
58
|
if (cli.flags.mode !== "human")
|
|
59
|
-
return emitMachine(cli, entries, { items: entries, ids: entries.map((e) => e.
|
|
59
|
+
return emitMachine(cli, entries, { items: entries, ids: entries.map((e) => e.id) });
|
|
60
60
|
if (entries.length === 0) {
|
|
61
61
|
io.err(style.dim(" no changes"));
|
|
62
62
|
return EXIT_OK;
|
|
@@ -64,7 +64,7 @@ async function runDiff(cli, args) {
|
|
|
64
64
|
for (const e of entries) {
|
|
65
65
|
const mark = e.kind === "added" ? style.ok("+") : e.kind === "removed" ? style.err("-") : style.warn("~");
|
|
66
66
|
const preview = (e.after ?? e.before ?? "").split("\n")[0] ?? "";
|
|
67
|
-
io.out(`${mark} ${style.id(e.
|
|
67
|
+
io.out(`${mark} ${style.id(e.id)} ${preview}`.trimEnd());
|
|
68
68
|
}
|
|
69
69
|
return EXIT_OK;
|
|
70
70
|
}
|
package/dist/src/cmd/docs.js
CHANGED
|
@@ -3,14 +3,15 @@ import { parse as parseYamlScalar } from "yaml";
|
|
|
3
3
|
import { docsCreate, docsMove, docsDelete, docsSetMeta, surfaceDocOpResult, surfaceDocMoveResult, referencePath } from "@omgbase/core";
|
|
4
4
|
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
5
5
|
import { readContent, extractContentOpts, renderDiffs } from "./_mutate.js";
|
|
6
|
-
import { remoteCall } from "./_remote.js";
|
|
6
|
+
import { remoteCall, docLocator, requireDoc } from "./_remote.js";
|
|
7
7
|
// Document-level commands (11 §5.6): new / mv / rm --doc / meta. Thin wrappers
|
|
8
8
|
// over the core doc ops, which own the file-write + commit + flock protocol.
|
|
9
9
|
// The global --dry-run rides as the op's `dryRun` (spec/cli §3.6): the op
|
|
10
10
|
// validates and returns the per-file diffs it would make, committing nothing.
|
|
11
|
-
// Every path printed is the reference form (spec/surface §1 "Paths")
|
|
12
|
-
//
|
|
13
|
-
// function the MCP server applies —
|
|
11
|
+
// Every path printed is the reference form (spec/surface §1 "Paths") and the
|
|
12
|
+
// result's identity is `id`/`path` (3.0): the library's result is re-shaped
|
|
13
|
+
// through `surfaceDocOpResult` — the same function the MCP server applies —
|
|
14
|
+
// before it is rendered, so the CLI and the tool print one shape.
|
|
14
15
|
function ctxOf(cli, ws, repoId, rootPath, actor) {
|
|
15
16
|
return {
|
|
16
17
|
repoId,
|
|
@@ -30,7 +31,7 @@ function report(cli, verb, res) {
|
|
|
30
31
|
return EXIT_OK;
|
|
31
32
|
}
|
|
32
33
|
cli.io.err(cli.style.dim(` ${cli.style.ok(cli.render.g.ok)} ${verb} ${cli.style.accent(res.path)}`));
|
|
33
|
-
cli.io.out(res.
|
|
34
|
+
cli.io.out(res.id);
|
|
34
35
|
return EXIT_OK;
|
|
35
36
|
}
|
|
36
37
|
// `mv` rewrites the inbound links by default (spec/mutate 1.3; `--no-retarget`
|
|
@@ -51,14 +52,14 @@ function reportMove(cli, from, res) {
|
|
|
51
52
|
}
|
|
52
53
|
if (res.dangling.length === 0)
|
|
53
54
|
return code;
|
|
54
|
-
const where = res.dangling.map((l) => (l.
|
|
55
|
+
const where = res.dangling.map((l) => (l.block_id ? `${l.path} ${l.block_id}` : `${l.path} (frontmatter)`)).join(", ");
|
|
55
56
|
const n = res.dangling.length;
|
|
56
57
|
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}`));
|
|
57
|
-
if (res.dangling.some((l) => l.
|
|
58
|
+
if (res.dangling.some((l) => l.block_id))
|
|
58
59
|
io.err(style.dim(` fix: ${cli.prog} retarget ${from} ${res.path} --apply`));
|
|
59
60
|
const seen = new Set();
|
|
60
61
|
for (const l of res.dangling) {
|
|
61
|
-
if (l.
|
|
62
|
+
if (l.block_id || !l.field)
|
|
62
63
|
continue;
|
|
63
64
|
const key = `${l.path}\u0000${l.field}`;
|
|
64
65
|
if (seen.has(key))
|
|
@@ -125,22 +126,22 @@ async function runMv(cli, args) {
|
|
|
125
126
|
throw new CliUsageError("mv requires <doc> and <new-path>");
|
|
126
127
|
const noRetarget = values["no-retarget"] === true;
|
|
127
128
|
if (cli.flags.server) {
|
|
128
|
-
const res = await remoteCall(cli, "docs_move", { doc, to_path: toPath, ...(noRetarget ? { retarget_inbound: false } : {}), ...dryRunArg(cli) });
|
|
129
|
+
const res = await remoteCall(cli, "docs_move", { ...docLocator(doc), to_path: toPath, ...(noRetarget ? { retarget_inbound: false } : {}), ...dryRunArg(cli) });
|
|
129
130
|
return reportMove(cli, fromPathOf(res, doc), res);
|
|
130
131
|
}
|
|
131
132
|
const ws = cli.workspace();
|
|
132
133
|
const repo = cli.repo(ws);
|
|
133
|
-
const res = surfaceDocMoveResult(docsMove(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, toPath, noRetarget ? { retargetInbound: false } : {}));
|
|
134
|
+
const res = surfaceDocMoveResult(docsMove(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), requireDoc(ws.store, repo.repoId, doc), toPath, noRetarget ? { retargetInbound: false } : {}));
|
|
134
135
|
return reportMove(cli, fromPathOf(res, doc), res);
|
|
135
136
|
}
|
|
136
137
|
// ---- rm --doc (block rm lives in mutate.ts; this handles the --doc branch) --
|
|
137
138
|
export async function runRmDoc(cli, doc, actor) {
|
|
138
139
|
if (cli.flags.server) {
|
|
139
|
-
return report(cli, "deleted", await remoteCall(cli, "docs_delete", { doc, ...dryRunArg(cli) }));
|
|
140
|
+
return report(cli, "deleted", await remoteCall(cli, "docs_delete", { ...docLocator(doc), ...dryRunArg(cli) }));
|
|
140
141
|
}
|
|
141
142
|
const ws = cli.workspace();
|
|
142
143
|
const repo = cli.repo(ws);
|
|
143
|
-
const res = surfaceDocOpResult(docsDelete(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, actor), doc));
|
|
144
|
+
const res = surfaceDocOpResult(docsDelete(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, actor), requireDoc(ws.store, repo.repoId, doc)));
|
|
144
145
|
return report(cli, "deleted", res);
|
|
145
146
|
}
|
|
146
147
|
// ---- meta -------------------------------------------------------------------
|
|
@@ -193,7 +194,7 @@ async function runMeta(cli, args) {
|
|
|
193
194
|
throw new CliUsageError("meta requires --set or --unset");
|
|
194
195
|
if (cli.flags.server) {
|
|
195
196
|
return report(cli, "patched", await remoteCall(cli, "docs_set_meta", {
|
|
196
|
-
doc,
|
|
197
|
+
...docLocator(doc),
|
|
197
198
|
...(Object.keys(set).length ? { set } : {}),
|
|
198
199
|
...(unset.length ? { unset } : {}),
|
|
199
200
|
...dryRunArg(cli),
|
|
@@ -201,7 +202,7 @@ async function runMeta(cli, args) {
|
|
|
201
202
|
}
|
|
202
203
|
const ws = cli.workspace();
|
|
203
204
|
const repo = cli.repo(ws);
|
|
204
|
-
const res = surfaceDocOpResult(docsSetMeta(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, { set, unset }));
|
|
205
|
+
const res = surfaceDocOpResult(docsSetMeta(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), requireDoc(ws.store, repo.repoId, doc), { set, unset }));
|
|
205
206
|
return report(cli, "patched", res);
|
|
206
207
|
}
|
|
207
208
|
function dryRunArg(cli) {
|
package/dist/src/cmd/edit.js
CHANGED
|
@@ -37,7 +37,7 @@ function runEdit(cli, args) {
|
|
|
37
37
|
const repo = cli.repo(ws);
|
|
38
38
|
const resolved = resolveRef(ws.store, repo.repoId, ref);
|
|
39
39
|
if (!resolved || resolved.kind !== "block")
|
|
40
|
-
throw new EngineErrorLike("block_missing", `
|
|
40
|
+
throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
|
|
41
41
|
const blockId = resolved.blockId;
|
|
42
42
|
const node = nodesGet(ws.store, resolved.docId, blockId, { resolution: "raw" });
|
|
43
43
|
const before = node?.raw ?? "";
|
package/dist/src/cmd/find.js
CHANGED
|
@@ -36,9 +36,9 @@ async function runFind(cli, args) {
|
|
|
36
36
|
const limit = values.one ? 1 : values.n ? Number(values.n) : 10;
|
|
37
37
|
let hits;
|
|
38
38
|
if (cli.flags.server) {
|
|
39
|
-
// Remote: the `
|
|
39
|
+
// Remote: the `blocks_resolve` tool does the hybrid ranking server-side (using the
|
|
40
40
|
// server's embedder if configured). Same ranked-hit shape → render unchanged.
|
|
41
|
-
hits = await remoteCall(cli, "
|
|
41
|
+
hits = await remoteCall(cli, "blocks_resolve", { query: text, limit });
|
|
42
42
|
}
|
|
43
43
|
else {
|
|
44
44
|
const ws = cli.workspace();
|
package/dist/src/cmd/hist.js
CHANGED
|
@@ -2,7 +2,7 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
import { resolveRef, historyNode } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { remoteCall } from "./_remote.js";
|
|
5
|
-
// `omg hist <node>` (11 §5.5) —
|
|
5
|
+
// `omg hist <node>` (11 §5.5) — blocks_history: a block's biography.
|
|
6
6
|
async function runHist(cli, args) {
|
|
7
7
|
const { values, positionals } = parseArgs({
|
|
8
8
|
args,
|
|
@@ -22,9 +22,9 @@ async function runHist(cli, args) {
|
|
|
22
22
|
throw new CliUsageError("hist requires a <node>");
|
|
23
23
|
let changes;
|
|
24
24
|
if (cli.flags.server) {
|
|
25
|
-
// Remote:
|
|
25
|
+
// Remote: blocks_history keys on a block id (globally unique). Pass the ref
|
|
26
26
|
// through — a locator would need a local resolve, so remote hist wants an id.
|
|
27
|
-
changes = await remoteCall(cli, "
|
|
27
|
+
changes = await remoteCall(cli, "blocks_history", { id: ref, ...(values.n ? { limit: Number(values.n) } : {}) });
|
|
28
28
|
}
|
|
29
29
|
else {
|
|
30
30
|
const ws = cli.workspace();
|
package/dist/src/cmd/links.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolveRef, docLinks } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
|
-
// `omg links <node>` (11 §5.4) — open
|
|
4
|
+
// `omg links <node>` (11 §5.4) — open links touching the node; default both
|
|
5
5
|
// directions, grouped; doc-grain by default, --blocks for block-grain (one row
|
|
6
6
|
// per edge, naming the source block). Backlinks = --in.
|
|
7
7
|
function runLinks(cli, args) {
|
|
@@ -19,13 +19,13 @@ function runLinks(cli, args) {
|
|
|
19
19
|
if (values.help) {
|
|
20
20
|
return renderHelp(cli, {
|
|
21
21
|
name: "links",
|
|
22
|
-
summary: "Open
|
|
22
|
+
summary: "Open links touching a node, grouped by predicate (both directions by default)",
|
|
23
23
|
usage: "links <node> [--in|--out] [--pred <p,p>] [--blocks]",
|
|
24
24
|
options: [
|
|
25
|
-
["--in", "incoming
|
|
26
|
-
["--out", "outgoing
|
|
25
|
+
["--in", "incoming links only (backlinks)"],
|
|
26
|
+
["--out", "outgoing links only"],
|
|
27
27
|
["--pred <p,p>", "keep only these predicates"],
|
|
28
|
-
["--blocks", "block-grain
|
|
28
|
+
["--blocks", "block-grain links (default: doc-grain)"],
|
|
29
29
|
],
|
|
30
30
|
});
|
|
31
31
|
}
|
|
@@ -47,7 +47,7 @@ function runLinks(cli, args) {
|
|
|
47
47
|
cli.capture?.(result); // shell: edges (out+in) become the addressable frame
|
|
48
48
|
// `{ out, in }` is not a list: `--jsonl` prints the document; `--ids` the far nodes.
|
|
49
49
|
if (cli.flags.mode !== "human")
|
|
50
|
-
return emitMachine(cli, result, { ids: [...result.out, ...result.in].map((e) => e.
|
|
50
|
+
return emitMachine(cli, result, { ids: [...result.out, ...result.in].map((e) => e.doc_id) });
|
|
51
51
|
const { render, style, io } = cli;
|
|
52
52
|
const g = render.g;
|
|
53
53
|
if (result.out.length === 0 && result.in.length === 0) {
|
|
@@ -56,26 +56,26 @@ function runLinks(cli, args) {
|
|
|
56
56
|
}
|
|
57
57
|
// Doc-grain: one row per (predicate, node) with its rollup count. Block-grain:
|
|
58
58
|
// one row per edge, the source block beside the far node.
|
|
59
|
-
const src = (e) => style.dim(`(${e.
|
|
59
|
+
const src = (e) => style.dim(`(${e.block_id ?? "frontmatter"})`);
|
|
60
60
|
if (result.out.length > 0) {
|
|
61
61
|
io.out(style.dim(" out"));
|
|
62
62
|
for (const e of result.out) {
|
|
63
63
|
if (values.blocks)
|
|
64
|
-
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.
|
|
64
|
+
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.doc_id)} ${src(e)}`);
|
|
65
65
|
else
|
|
66
|
-
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.
|
|
66
|
+
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.doc_id)} ${style.dim(`×${e.count}`)}`);
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
if (result.in.length > 0) {
|
|
70
70
|
io.out(style.dim(" in (backlinks)"));
|
|
71
71
|
for (const e of result.in) {
|
|
72
72
|
if (values.blocks)
|
|
73
|
-
io.out(` ${style.id(e.
|
|
73
|
+
io.out(` ${style.id(e.doc_id)} ${src(e)} ${style.dim(g.arrow)} ${style.accent(e.predicate)}`);
|
|
74
74
|
else
|
|
75
|
-
io.out(` ${style.id(e.
|
|
75
|
+
io.out(` ${style.id(e.doc_id)} ${style.dim(g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
|
|
76
76
|
}
|
|
77
77
|
}
|
|
78
78
|
return EXIT_OK;
|
|
79
79
|
}
|
|
80
|
-
export const cmdLinks = { name: "links", summary: "Open
|
|
80
|
+
export const cmdLinks = { name: "links", summary: "Open links touching a node", run: (cli, a) => runLinks(cli, a) };
|
|
81
81
|
//# sourceMappingURL=links.js.map
|
package/dist/src/cmd/ls.js
CHANGED
|
@@ -38,9 +38,11 @@ async function runLs(cli, args) {
|
|
|
38
38
|
rows.push(...page.items);
|
|
39
39
|
cursor = page.truncated ? page.cursor : null;
|
|
40
40
|
} while (cursor);
|
|
41
|
-
cli.capture?.(rows); // shell: docs become the addressable frame (ref = path)
|
|
41
|
+
cli.capture?.(rows); // shell: docs become the addressable frame (ref = id, label path)
|
|
42
|
+
// `--ids` prints the documents' `id`s (spec/cli 3.0; the paths before, a
|
|
43
|
+
// stand-in for an identity the row did not carry).
|
|
42
44
|
if (cli.flags.mode !== "human")
|
|
43
|
-
return emitMachine(cli, rows, { items: rows, ids: rows.map((r) => r.
|
|
45
|
+
return emitMachine(cli, rows, { items: rows, ids: rows.map((r) => r.id) });
|
|
44
46
|
const { style, io } = cli;
|
|
45
47
|
if (rows.length === 0) {
|
|
46
48
|
io.err(style.dim(" no documents"));
|
|
@@ -49,7 +51,7 @@ async function runLs(cli, args) {
|
|
|
49
51
|
const table = rows.map((r) => [
|
|
50
52
|
style.accent(r.path),
|
|
51
53
|
style.dim(`${r.blocks} blocks`),
|
|
52
|
-
style.dim(relTime(r.
|
|
54
|
+
style.dim(relTime(r.updated_at)),
|
|
53
55
|
]);
|
|
54
56
|
for (const line of columns(table, [{}, { align: "right" }, { align: "right" }]))
|
|
55
57
|
io.out(line);
|
package/dist/src/cmd/mcp.js
CHANGED
|
@@ -65,7 +65,7 @@ async function runMcp(cli, args) {
|
|
|
65
65
|
const repo = cli.repo(ws);
|
|
66
66
|
// Connect the configured embedder once (if any) so the query tool's
|
|
67
67
|
// `semantic` mode has a vectorizer for the session. Unset ⇒ semantic queries
|
|
68
|
-
// return
|
|
68
|
+
// return provider_unavailable. The spawned process lives for the session and
|
|
69
69
|
// is released on shutdown.
|
|
70
70
|
//
|
|
71
71
|
// A CONFIGURED-but-broken embedder must never be mistaken for "no embedder":
|
|
@@ -151,7 +151,7 @@ async function runMcp(cli, args) {
|
|
|
151
151
|
// The query surface's `embedQuery`: a live vectorizer when the embedder loaded,
|
|
152
152
|
// or — when it's configured-but-broken — a hook that throws `embedder_failed`
|
|
153
153
|
// so semantic access fails loudly and specifically (not the misleading
|
|
154
|
-
// `
|
|
154
|
+
// `provider_unavailable`, which the server emits only when embedQuery is
|
|
155
155
|
// absent, i.e. genuinely no provider configured). `const` captures satisfy
|
|
156
156
|
// closure narrowing over the `let`-bound state above.
|
|
157
157
|
const loaded = embedding;
|
|
@@ -167,6 +167,7 @@ async function runMcp(cli, args) {
|
|
|
167
167
|
...(repo.rootPath ? { rootPath: repo.rootPath } : {}),
|
|
168
168
|
...(drainer ? { onMutation: () => drainer.schedule() } : {}),
|
|
169
169
|
...(embedQuery ? { embedQuery } : {}),
|
|
170
|
+
...(loaded ? { embedProvider: loaded.providerName } : {}),
|
|
170
171
|
// The `version` tool's view of this binary (spec/surface §4).
|
|
171
172
|
host: hostInfo(),
|
|
172
173
|
});
|
package/dist/src/cmd/mutate.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
|
-
import { resolveRef, tasksComplete, sectionsAppend, } from "@omgbase/core";
|
|
3
|
+
import { resolveRef, tasksComplete, sectionsAppend, isContainerBlock, } from "@omgbase/core";
|
|
4
4
|
import { CliUsageError, EngineErrorLike, renderHelp } from "../output.js";
|
|
5
5
|
import { runOps, runOpsRemote, readContent, readStdin, expandBlockArgs, extractContentOpts } from "./_mutate.js";
|
|
6
6
|
import { runRmDoc } from "./docs.js";
|
|
@@ -10,7 +10,7 @@ import { runRmDoc } from "./docs.js";
|
|
|
10
10
|
function block(cli, ws, repoId, ref) {
|
|
11
11
|
const r = resolveRef(ws.store, repoId, ref);
|
|
12
12
|
if (!r || r.kind !== "block")
|
|
13
|
-
throw new EngineErrorLike("block_missing", `
|
|
13
|
+
throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
|
|
14
14
|
return r.blockId;
|
|
15
15
|
}
|
|
16
16
|
// `done` addresses task blocks only (spec/cli §6): any other live block type is
|
|
@@ -21,7 +21,7 @@ function taskBlock(ws, ref, blockId) {
|
|
|
21
21
|
.prepare("SELECT type FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
|
|
22
22
|
.get(blockId);
|
|
23
23
|
if (!row)
|
|
24
|
-
throw new EngineErrorLike("block_missing", `
|
|
24
|
+
throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(ref)}`, { data: { id: ref } });
|
|
25
25
|
if (row.type !== "task") {
|
|
26
26
|
throw new EngineErrorLike("type_mismatch", `not a task: ${ref} is a ${row.type}`, { data: { block: blockId, type: row.type } });
|
|
27
27
|
}
|
|
@@ -141,7 +141,7 @@ async function runUpdate(cli, args) {
|
|
|
141
141
|
const markdown = readContent(content);
|
|
142
142
|
// Remote: the server pins CAS from current bytes when --expect is absent.
|
|
143
143
|
if (cli.flags.server)
|
|
144
|
-
return runOpsRemote(cli, "blocks_update", {
|
|
144
|
+
return runOpsRemote(cli, "blocks_update", { id: ref, markdown, ...(values.expect ? { expect: { content_hash: values.expect } } : {}) });
|
|
145
145
|
const ws = cli.workspace();
|
|
146
146
|
const repo = cli.repo(ws);
|
|
147
147
|
const blockId = block(cli, ws, repo.repoId, ref);
|
|
@@ -226,7 +226,7 @@ async function runRm(cli, args) {
|
|
|
226
226
|
const op = { op: "remove", blocks };
|
|
227
227
|
return runOps(cli, ws, repo, [op], actorOf(values));
|
|
228
228
|
}
|
|
229
|
-
// ---- done (
|
|
229
|
+
// ---- done (blocks_check) --------------------------------------------------
|
|
230
230
|
async function runDone(cli, args) {
|
|
231
231
|
const { values, positionals } = parseArgs({
|
|
232
232
|
args,
|
|
@@ -249,7 +249,7 @@ async function runDone(cli, args) {
|
|
|
249
249
|
if (refs.length === 0)
|
|
250
250
|
throw new CliUsageError("done requires one or more task blocks (or - for stdin)");
|
|
251
251
|
if (cli.flags.server)
|
|
252
|
-
return runOpsRemote(cli, "
|
|
252
|
+
return runOpsRemote(cli, "blocks_check", { ids: refs, ...(values.undo ? { checked: false } : {}) });
|
|
253
253
|
const ws = cli.workspace();
|
|
254
254
|
const repo = cli.repo(ws);
|
|
255
255
|
const blocks = refs.map((r) => taskBlock(ws, r, block(cli, ws, repo.repoId, r)));
|
|
@@ -258,7 +258,7 @@ async function runDone(cli, args) {
|
|
|
258
258
|
: tasksComplete(ws.store, blocks);
|
|
259
259
|
return runOps(cli, ws, repo, ops, actorOf(values));
|
|
260
260
|
}
|
|
261
|
-
// ---- append (
|
|
261
|
+
// ---- append (blocks_append) --------------------------------------------------
|
|
262
262
|
async function runAppend(cli, args) {
|
|
263
263
|
const content = extractContentOpts(args);
|
|
264
264
|
const { values, positionals } = parseArgs({
|
|
@@ -269,10 +269,10 @@ async function runAppend(cli, args) {
|
|
|
269
269
|
if (values.help) {
|
|
270
270
|
return renderHelp(cli, {
|
|
271
271
|
name: "append",
|
|
272
|
-
summary: "Append markdown
|
|
273
|
-
usage: "append <
|
|
272
|
+
summary: "Append markdown into a block: a heading's section or a container's children",
|
|
273
|
+
usage: "append <block> (-m <markdown> | -f <file> | -) [--actor <s>] [--dry-run]",
|
|
274
274
|
options: [
|
|
275
|
-
["<
|
|
275
|
+
["<block>", "a heading (its section receives the content) or a container — list, item, quote, table (its children do)"],
|
|
276
276
|
["-m <markdown>", "content inline"],
|
|
277
277
|
["-f <file>", "content from a file"],
|
|
278
278
|
["-", "content from stdin"],
|
|
@@ -280,17 +280,34 @@ async function runAppend(cli, args) {
|
|
|
280
280
|
],
|
|
281
281
|
});
|
|
282
282
|
}
|
|
283
|
-
const
|
|
284
|
-
if (!
|
|
285
|
-
throw new CliUsageError("append requires a <
|
|
283
|
+
const target = positionals[0];
|
|
284
|
+
if (!target)
|
|
285
|
+
throw new CliUsageError("append requires a <block>");
|
|
286
286
|
const markdown = readContent(content);
|
|
287
|
-
// Remote:
|
|
287
|
+
// Remote: blocks_append resolves the ref and decides by the block's kind
|
|
288
|
+
// server-side (spec/surface §4).
|
|
288
289
|
if (cli.flags.server)
|
|
289
|
-
return runOpsRemote(cli, "
|
|
290
|
+
return runOpsRemote(cli, "blocks_append", { id: target, text: markdown });
|
|
290
291
|
const ws = cli.workspace();
|
|
291
292
|
const repo = cli.repo(ws);
|
|
292
|
-
const
|
|
293
|
-
return runOps(cli, ws, repo,
|
|
293
|
+
const blockId = block(cli, ws, repo.repoId, target);
|
|
294
|
+
return runOps(cli, ws, repo, appendOps(ws.store, blockId, markdown), actorOf(values));
|
|
295
|
+
}
|
|
296
|
+
/** The ops `blocks_append` expands to, by what the block is: a heading → the end
|
|
297
|
+
* of its section (`sectionsAppend`); a container → the end of its children; a
|
|
298
|
+
* leaf → `block_not_container` (append means into, never a sibling insert). */
|
|
299
|
+
function appendOps(store, blockId, markdown) {
|
|
300
|
+
const row = store.db
|
|
301
|
+
.prepare("SELECT b.type AS type, d.format AS format, (SELECT count(*) FROM blocks c WHERE c.parent_block = b.block_id AND c.deleted_commit IS NULL) AS children FROM blocks b JOIN docs d ON d.doc_id = b.doc_id WHERE b.block_id = ? AND b.deleted_commit IS NULL")
|
|
302
|
+
.get(blockId);
|
|
303
|
+
if (!row)
|
|
304
|
+
throw new EngineErrorLike("block_missing", `no block with id ${JSON.stringify(blockId)}`, { data: { id: blockId } });
|
|
305
|
+
if (row.type === "heading")
|
|
306
|
+
return sectionsAppend(blockId, markdown);
|
|
307
|
+
if (isContainerBlock(row.format ?? "markdown", { type: row.type, children: new Array(row.children) })) {
|
|
308
|
+
return [{ op: "insert", to: { parent: blockId, at: "end" }, markdown }];
|
|
309
|
+
}
|
|
310
|
+
throw new EngineErrorLike("block_not_container", `block ${blockId} is a ${row.type}, not a heading or a container; append means into — insert after it with blocks_insert { to: <its parent>, at: { after: ${JSON.stringify(blockId)} } }`, { data: { id: blockId, type: row.type } });
|
|
294
311
|
}
|
|
295
312
|
// ---- split / merge ----------------------------------------------------------
|
|
296
313
|
async function runSplit(cli, args) {
|
|
@@ -324,7 +341,7 @@ async function runSplit(cli, args) {
|
|
|
324
341
|
return n;
|
|
325
342
|
});
|
|
326
343
|
if (cli.flags.server)
|
|
327
|
-
return runOpsRemote(cli, "blocks_split", {
|
|
344
|
+
return runOpsRemote(cli, "blocks_split", { id: ref, at });
|
|
328
345
|
const ws = cli.workspace();
|
|
329
346
|
const repo = cli.repo(ws);
|
|
330
347
|
const blockId = block(cli, ws, repo.repoId, ref);
|
package/dist/src/cmd/node.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { nodeSet, editablePropsFor } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
+
import { requireNode } from "./_remote.js";
|
|
4
5
|
import { runOps, runOpsRemote } from "./_mutate.js";
|
|
5
6
|
// `omg node set <nodeId> <prop> <value>` (node-editability) — surgically set an
|
|
6
7
|
// editable property of a projected node (e.g. a link's text/target, a task's
|
|
@@ -37,13 +38,13 @@ async function runNode(cli, args) {
|
|
|
37
38
|
if (!nodeId || !prop || valueParts.length === 0)
|
|
38
39
|
throw new CliUsageError("node set <nodeId> <prop> <value>");
|
|
39
40
|
const value = valueParts.join(" ");
|
|
40
|
-
// Remote:
|
|
41
|
+
// Remote: nodes_set resolves node → block + rewrites the span server-side.
|
|
41
42
|
if (cli.flags.server)
|
|
42
|
-
return runOpsRemote(cli, "
|
|
43
|
+
return runOpsRemote(cli, "nodes_set", { id: nodeId, prop, value });
|
|
43
44
|
const ws = cli.workspace();
|
|
44
45
|
const repo = cli.repo(ws);
|
|
45
|
-
const ops = nodeSet(ws.store, nodeId, prop, value);
|
|
46
|
-
return runOps(cli, ws, repo, ops, { reason: `
|
|
46
|
+
const ops = nodeSet(ws.store, requireNode(ws.store, repo.repoId, nodeId), prop, value);
|
|
47
|
+
return runOps(cli, ws, repo, ops, { reason: `nodes_set ${prop}`, ...(values.actor ? { actor: values.actor } : {}) });
|
|
47
48
|
}
|
|
48
49
|
if (sub === "props") {
|
|
49
50
|
// `props` is a local editability lookup with no remote tool (a niche read).
|
package/dist/src/cmd/oqx.js
CHANGED
|
@@ -23,7 +23,7 @@ import { readStdin } from "./_mutate.js";
|
|
|
23
23
|
// query to a parent binding, the root row's collections ^docs/^nodes/^blocks for
|
|
24
24
|
// join-equivalents, first/single lookups, full-text text(...) + embedding-score
|
|
25
25
|
// semantic(...) predicates, `order by <expr> [asc|desc]` ranking, and recursive
|
|
26
|
-
// `follow [distinct] <rel> [{ where … frontier … depth n
|
|
26
|
+
// `follow [distinct] <rel> [{ where … frontier … depth n }]` traversal (a brace after a destination is its block, oqx 0.20) over
|
|
27
27
|
// a type-preserving relation with $depth/$stop/$ordinal recursion metadata +
|
|
28
28
|
// post-walk filtering. A top-level consumer directive changes the result shape:
|
|
29
29
|
// `docs count { … }` / `docs exists { … }` reduce to a scalar,
|
|
@@ -46,7 +46,7 @@ async function runOqx(cli, args) {
|
|
|
46
46
|
if (values.help) {
|
|
47
47
|
renderHelp(cli, {
|
|
48
48
|
name: "query",
|
|
49
|
-
summary: "Composable OQX query: from <docs|blocks|nodes|
|
|
49
|
+
summary: "Composable OQX query: from <docs|blocks|nodes|links> where … select … [order by …] [follow …] (alias: q)",
|
|
50
50
|
usage: ["query <source> [-n <N>] [--cursor <c>] [--ids|--json|--jsonl]", "query -f <file|-> [-n <N>] [--cursor <c>]"],
|
|
51
51
|
options: [
|
|
52
52
|
["<source>", "the OQX query text (quote it); `from` may be omitted inside blocks"],
|
|
@@ -63,23 +63,28 @@ async function runOqx(cli, args) {
|
|
|
63
63
|
});
|
|
64
64
|
cli.io.out(" e.g. query 'from docs where nodes count { where kind == \"md:task\" } >= 2'");
|
|
65
65
|
cli.io.out(" query 'select items: section.blocks collect { where type == \"list_item\" } from nodes where kind == \"md:section\"'");
|
|
66
|
-
cli.io.out(" query 'select
|
|
67
|
-
cli.io.out(" query 'select owner_id, owner: ^nodes single { where kind == \"person\"
|
|
66
|
+
cli.io.out(" query 'select path, open from docs where nodes collect { ^open: value where kind == \"md:task\" and not attrs.checked }'");
|
|
67
|
+
cli.io.out(" query 'select owner_id, owner: ^nodes single { where kind == \"person\" and attrs.id == ^owner_id } from docs' # ^docs/^nodes/^blocks/^links: the repository (the root row) from inside a row; 0^docs from any depth; ^id / 0^id = the repository id");
|
|
68
68
|
cli.io.out(" query 'docs count { where layer == \"canon\" }' # scalar; also <target> exists/none/first/single { … } at the top level");
|
|
69
|
-
cli.io.out(" query 'from docs where nodes none { where kind == \"md:task\"
|
|
69
|
+
cli.io.out(" query 'from docs where nodes none { where kind == \"md:task\" and not checked }' # none = zero rows (≡ not … exists; \"every\" = none over the complement)");
|
|
70
|
+
cli.io.out(" query 'from docs where era is not null and (layer == \"canon\" or layer == \"working\")' # and / or / not are the connectives (&& || ! accepted); x is y / x is not y = identity, x is null = absent");
|
|
71
|
+
cli.io.out(" query 'docs { type == \"substance\" and not verified }' # sugar: a block without a consumer is collect; a leading predicate drops `where` (bare names still project — filter a bare field with `is x`); after a follow destination too: follow children { depth 2 } is its block");
|
|
72
|
+
cli.io.out(" query 'select subject, process: ^docs[slug == ^subject].path from docs where type == \"lab-note\"' # x[p] = first match or absent; x[p]! = exactly one, required; x[n] = positional; title! = the value or a loud error; a select item may use the items to its left");
|
|
73
|
+
cli.io.out(" query 'select path, $depth from docs where path == \"/timeline/review.md\" follow refs(before), refs(after)' # refs(field): the live documents a property's references name (either path form or a d_ id; dangling dropped) — a receiver, a source or a follow destination");
|
|
74
|
+
cli.io.out(" query 'select path, next: ^docs { ^path in list(before) } from docs where type == \"milestone\"' # paths are /-rooted on the surface (path == \"/x.md\"; inputs accept both forms), so a property's reference compares with ^path directly");
|
|
70
75
|
cli.io.out(" query 'from docs where type == \"practitioner\" order by era desc limit 2 offset 1' # limit/offset bound the set (after order/distinct, before the consumer)");
|
|
71
|
-
cli.io.out(" query 'select fm: entries(frontmatter) collect { k: $key, v: $it } from docs where
|
|
72
|
-
cli.io.out(" query 'select
|
|
73
|
-
cli.io.out(" query 'select
|
|
76
|
+
cli.io.out(" query 'select fm: entries(frontmatter) collect { k: $key, v: $it } from docs where path == \"/x.md\"' # a record as a collection ($key/$it); also entries(attrs), entries(inline)");
|
|
77
|
+
cli.io.out(" query 'select path values from docs where layer == \"canon\"' # `values`: bare values, no {id,path} hits (one item only)");
|
|
78
|
+
cli.io.out(" query 'select path, tags: tags collect { $it values where $it != \"draft\" } from docs' # $it = the current item (here: each tag)");
|
|
74
79
|
cli.io.out(" query 'docs collect { from nodes where kind == \"md:task\" }' # `from E` re-projects the source (→ nodes)");
|
|
75
|
-
cli.io.out(" query 'from docs where text(\"philosophers stone\")
|
|
80
|
+
cli.io.out(" query 'from docs where text(\"philosophers stone\") and layer == \"canon\"' # full-text prune");
|
|
76
81
|
cli.io.out(" query 'select s: semantic(\"the great work\") from blocks where semantic(\"the great work\") > 0.6' # embedding score (needs a provider)");
|
|
77
82
|
cli.io.out(" query 'from docs where type == \"practitioner\" order by era desc' # order by <expr> [asc|desc]");
|
|
78
|
-
cli.io.out(" query 'select t: text, d: $depth, s: $stop from blocks where
|
|
79
|
-
cli.io.out(" query 'from nodes where name == \"Overview\" follow section.subsections { depth 3 }' # follow [distinct] <
|
|
80
|
-
cli.io.out(" query 'select p:
|
|
81
|
-
cli.io.out(" query 'from blocks where type == \"list_item\"
|
|
82
|
-
cli.io.out(" query 'select src:
|
|
83
|
+
cli.io.out(" query 'select t: text, d: $depth, s: $stop from blocks where id == \"b_x\" follow block.children' # recursive walk ($depth/$stop metadata)");
|
|
84
|
+
cli.io.out(" query 'from nodes where name == \"Overview\" follow section.subsections { depth 3 }' # follow [distinct] <dest> [{ where … frontier … depth n }] — a brace after any destination is that destination's block (identity is $key: project $key: <expr> to rekey a walk)");
|
|
85
|
+
cli.io.out(" query 'select p: path, d: $depth, s: $stop from docs where path == \"/index.md\" follow out_links collect { from dst }' # citation graph (cyclic-safe: $stop=cycle)");
|
|
86
|
+
cli.io.out(" query 'from blocks where type == \"list_item\" and $leaf follow block.children' # $leaf/$depth/$stop filter the walk result post-walk");
|
|
87
|
+
cli.io.out(" query 'select src: doc_id, to: dst_path from links where predicate == \"depends_on\"' # the links target: predicate/provenance/dst_kind + doc_id/dst_path/uri");
|
|
83
88
|
return EXIT_OK;
|
|
84
89
|
}
|
|
85
90
|
const source = values.file
|
|
@@ -111,7 +116,7 @@ async function runOqx(cli, args) {
|
|
|
111
116
|
if (phrases.length > 0) {
|
|
112
117
|
const loaded = await loadEmbedding(ws, repo.repoId);
|
|
113
118
|
if (!loaded) {
|
|
114
|
-
throw new EngineErrorLike("
|
|
119
|
+
throw new EngineErrorLike("provider_unavailable", "semantic(...) needs an embedding provider", {
|
|
115
120
|
hint: "omg config set embedding.provider <command|url>",
|
|
116
121
|
});
|
|
117
122
|
}
|
package/dist/src/cmd/outline.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { findDoc, docsOutline, referencePath } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EngineErrorLike, truncationFooter, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
|
-
import { remoteCall } from "./_remote.js";
|
|
4
|
+
import { remoteCall, docLocator } from "./_remote.js";
|
|
5
5
|
// `omg outline <doc|path>` (alias ol) — the wire format (06 §6). Human output
|
|
6
6
|
// prints the outline text with full block ids inline; --json emits the
|
|
7
7
|
// OutlineResult verbatim.
|
|
@@ -32,11 +32,11 @@ async function runOutline(cli, args) {
|
|
|
32
32
|
let result;
|
|
33
33
|
let header;
|
|
34
34
|
if (cli.flags.server) {
|
|
35
|
-
// Remote: the docs_outline tool resolves the
|
|
36
|
-
// OutlineResult. The header shows the ref you passed (no local path lookup):
|
|
35
|
+
// Remote: the docs_outline tool resolves the locator (`id` for a `d_` id,
|
|
36
|
+
// else `path`) + returns the same OutlineResult. The header shows the ref you passed (no local path lookup):
|
|
37
37
|
// a path ref rooted (spec/surface §1 "Paths"), a `d_` id verbatim.
|
|
38
38
|
result = await remoteCall(cli, "docs_outline", {
|
|
39
|
-
|
|
39
|
+
...docLocator(ref),
|
|
40
40
|
...(values.depth ? { depth: Number(values.depth) } : {}),
|
|
41
41
|
...(values.skeleton ? { resolution: "skeleton" } : {}),
|
|
42
42
|
});
|
package/dist/src/cmd/retarget.js
CHANGED
|
@@ -66,7 +66,7 @@ function renderPlan(cli, from, to, hits) {
|
|
|
66
66
|
}
|
|
67
67
|
io.err(style.dim(` plan — ${hits.length} block(s) would change; re-run with --apply to commit`));
|
|
68
68
|
for (const h of hits) {
|
|
69
|
-
io.out(style.id(h.
|
|
69
|
+
io.out(style.id(h.id));
|
|
70
70
|
for (const line of unifiedDiffLines(cli, h.oldRaw, h.newRaw))
|
|
71
71
|
io.out(line);
|
|
72
72
|
io.out("");
|
package/dist/src/cmd/run.js
CHANGED
|
@@ -84,7 +84,7 @@ async function runRun(cli, args) {
|
|
|
84
84
|
if (phrases.length > 0) {
|
|
85
85
|
const loaded = await loadEmbedding(ws, repo.repoId);
|
|
86
86
|
if (!loaded) {
|
|
87
|
-
throw new EngineErrorLike("
|
|
87
|
+
throw new EngineErrorLike("provider_unavailable", "semantic(...) needs an embedding provider", {
|
|
88
88
|
hint: "omg config set embedding.provider <command|url>",
|
|
89
89
|
});
|
|
90
90
|
}
|
package/dist/src/cmd/show.js
CHANGED
|
@@ -9,15 +9,20 @@ function showOne(cli, ref, include) {
|
|
|
9
9
|
if (!resolved)
|
|
10
10
|
throw new EngineErrorLike("doc_missing", `no node ${ref}`);
|
|
11
11
|
if (resolved.kind === "document") {
|
|
12
|
-
// The card reads the merged properties + open
|
|
13
|
-
// `
|
|
12
|
+
// The card reads the merged properties + open links; `--json` is the
|
|
13
|
+
// `docs_read` result tagged with its kind (`{ kind: "document", ...docs_read }`).
|
|
14
14
|
const res = docsRead(ws.store, resolved.docId);
|
|
15
15
|
if (!res)
|
|
16
16
|
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
17
17
|
const links = docLinks(ws.store, resolved.docId, { direction: "both" });
|
|
18
|
-
|
|
18
|
+
// The authored keys, merged (frontmatter + inline — the `data` view); the
|
|
19
|
+
// computed rows are rendered under their surface names (`title`/`tags`,
|
|
20
|
+
// spec/surface §1.2 — the store's `$title`/`$tags` keys never reach the
|
|
21
|
+
// terminal) from the re-keyed `properties.computed` the read carries.
|
|
22
|
+
const properties = Object.fromEntries(Object.entries(docPropertiesMerged(ws.store.db, resolved.docId)).filter(([k]) => !k.startsWith("$")));
|
|
23
|
+
const computed = res.properties.computed ?? {};
|
|
19
24
|
const payload = { kind: "document", ...res };
|
|
20
|
-
return { payload, render: () => renderDocCard(cli, res.path, properties, links) };
|
|
25
|
+
return { payload, render: () => renderDocCard(cli, res.path, properties, computed, links) };
|
|
21
26
|
}
|
|
22
27
|
const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution: "full" });
|
|
23
28
|
if (!node)
|
|
@@ -34,7 +39,7 @@ function runShow(cli, args) {
|
|
|
34
39
|
if (values.help) {
|
|
35
40
|
return renderHelp(cli, {
|
|
36
41
|
name: "show",
|
|
37
|
-
summary: "Metadata card for a node: attrs, placement, open
|
|
42
|
+
summary: "Metadata card for a node: attrs, placement, open links, last change (`cat` is the bytes)",
|
|
38
43
|
usage: "show <node…|-> [--include history]",
|
|
39
44
|
options: [
|
|
40
45
|
["<node…>", "one or more refs: a block id (`b_…`), a doc id (`d_…`), a node id (`n_…`), or a doc path (`/notes/x.md`; the leading `/` is optional)"],
|
|
@@ -59,7 +64,7 @@ function runShow(cli, args) {
|
|
|
59
64
|
c.render();
|
|
60
65
|
return EXIT_OK;
|
|
61
66
|
}
|
|
62
|
-
function renderDocCard(cli, path, fm, links) {
|
|
67
|
+
function renderDocCard(cli, path, fm, computed, links) {
|
|
63
68
|
const { render, style, io } = cli;
|
|
64
69
|
io.out(render.wordmark(path));
|
|
65
70
|
io.out(render.rule(40));
|
|
@@ -69,23 +74,32 @@ function renderDocCard(cli, path, fm, links) {
|
|
|
69
74
|
for (const k of keys)
|
|
70
75
|
io.out(` ${style.accent(k)} ${style.dim("=")} ${fmt(fm[k])}`);
|
|
71
76
|
}
|
|
77
|
+
// The computed rows under their surface names (`title`, `tags` — spec/surface
|
|
78
|
+
// §1.2), their own section so a computed title never masquerades as authored.
|
|
79
|
+
const computedKeys = Object.keys(computed);
|
|
80
|
+
if (computedKeys.length > 0) {
|
|
81
|
+
io.out(` ${style.dim("computed")}`);
|
|
82
|
+
for (const k of computedKeys)
|
|
83
|
+
io.out(` ${style.accent(k)} ${style.dim("=")} ${fmt(computed[k])}`);
|
|
84
|
+
}
|
|
72
85
|
if (links.out.length > 0) {
|
|
73
|
-
io.out(` ${style.dim("out
|
|
86
|
+
io.out(` ${style.dim("out links")}`);
|
|
74
87
|
for (const e of links.out)
|
|
75
|
-
io.out(` ${style.accent(e.predicate)} ${style.dim(render.g.arrow)} ${style.id(e.
|
|
88
|
+
io.out(` ${style.accent(e.predicate)} ${style.dim(render.g.arrow)} ${style.id(e.doc_id)} ${style.dim(`×${e.count}`)}`);
|
|
76
89
|
}
|
|
77
90
|
if (links.in.length > 0) {
|
|
78
91
|
io.out(` ${style.dim("backlinks")}`);
|
|
79
92
|
for (const e of links.in)
|
|
80
|
-
io.out(` ${style.id(e.
|
|
93
|
+
io.out(` ${style.id(e.doc_id)} ${style.dim(render.g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
|
|
81
94
|
}
|
|
82
95
|
}
|
|
83
96
|
function renderBlockCard(cli, node, history) {
|
|
84
97
|
const { render, style, io } = cli;
|
|
85
98
|
io.out(` ${render.typeGlyph(node.type)} ${style.bold(node.type)} ${style.id(node.id)}`);
|
|
86
99
|
io.out(render.rule(40));
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
// The placement is flattened onto the row (3.0: `parent_id`/`ordinal`/`depth`); the line reads the same.
|
|
101
|
+
if (node.ordinal !== undefined)
|
|
102
|
+
io.out(` ${style.dim("placement")} parent=${style.id(node.parent_id ?? "—")} ordinal=${node.ordinal} depth=${node.depth}`);
|
|
89
103
|
if (node.attrs && Object.keys(node.attrs).length > 0) {
|
|
90
104
|
io.out(` ${style.dim("attrs")}`);
|
|
91
105
|
for (const [k, v] of Object.entries(node.attrs))
|
package/dist/src/cmd/update.js
CHANGED
|
@@ -3,7 +3,7 @@ import { docsUpdate, renderOpsetPlan, surfaceDocsUpdate } from "@omgbase/core";
|
|
|
3
3
|
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
4
4
|
import { readContent, extractContentOpts } from "./_mutate.js";
|
|
5
5
|
import { cmdUpdate as cmdUpdateBlock } from "./mutate.js";
|
|
6
|
-
import { remoteCall } from "./_remote.js";
|
|
6
|
+
import { remoteCall, docLocator, requireDoc } from "./_remote.js";
|
|
7
7
|
// `omg update <target> (-f file | -)`: a polymorphic-target command (the omgbase
|
|
8
8
|
// north-star — every command accepts any entity ref). A block id (b_…) replaces
|
|
9
9
|
// that block's markdown (CAS-pinned, the low-level op). A doc id or path is the
|
|
@@ -57,7 +57,7 @@ async function runDocUpdate(cli, args) {
|
|
|
57
57
|
// Remote: docs_update reconciles + commits server-side, returning the same
|
|
58
58
|
// { opset, result } — render unchanged.
|
|
59
59
|
const r = await remoteCall(cli, "docs_update", {
|
|
60
|
-
doc,
|
|
60
|
+
...docLocator(doc),
|
|
61
61
|
content: bytes,
|
|
62
62
|
...(values.reason ? { reason: values.reason } : {}),
|
|
63
63
|
...(dryRun ? { dry_run: true } : {}),
|
|
@@ -76,7 +76,7 @@ async function runDocUpdate(cli, args) {
|
|
|
76
76
|
...(ws.omgbaseDir ? { omgbaseDir: ws.omgbaseDir } : {}),
|
|
77
77
|
...(values.actor ? { actor: values.actor } : {}),
|
|
78
78
|
};
|
|
79
|
-
({ opset, result } = surfaceDocsUpdate(docsUpdate(ws.store, ctx, doc, bytes, {
|
|
79
|
+
({ opset, result } = surfaceDocsUpdate(docsUpdate(ws.store, ctx, requireDoc(ws.store, repo.repoId, doc), bytes, {
|
|
80
80
|
dryRun,
|
|
81
81
|
...(values.reason ? { reason: values.reason } : {}),
|
|
82
82
|
})));
|
package/dist/src/context.js
CHANGED
|
@@ -85,7 +85,7 @@ export const REMOTE_OK = new Set([
|
|
|
85
85
|
// doc-level mutators
|
|
86
86
|
"new", "mv", "meta", "rm", "update", "retarget",
|
|
87
87
|
// block-level sugar (server-side ref resolution + CAS via the blocks_* /
|
|
88
|
-
//
|
|
88
|
+
// blocks_check / blocks_append / nodes_set / apply tools)
|
|
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",
|
package/dist/src/output.d.ts
CHANGED
|
@@ -92,10 +92,13 @@ export declare const HIT_CELL_MAX = 60;
|
|
|
92
92
|
export declare function truncateCell(s: string, max?: number): string;
|
|
93
93
|
/** Render one projected value as a table cell (unstyled). */
|
|
94
94
|
export declare function hitCell(v: unknown): string;
|
|
95
|
-
/** The projected column names across `hits`: every key but the
|
|
96
|
-
*
|
|
95
|
+
/** The projected column names across `hits`: every key but the envelope's
|
|
96
|
+
* `id`/`path` (spec/surface §1.4, 3.0 — a `path` projection collapses onto
|
|
97
|
+
* the envelope key, so it is never a column), in first-seen order. Empty when
|
|
98
|
+
* nothing was projected. */
|
|
97
99
|
export declare function hitColumns(hits: HitLike[]): string[];
|
|
98
|
-
/** Human-tier hit list: `<id> <path>` lines, or an aligned table when projected
|
|
100
|
+
/** Human-tier hit list: `<id> <path>` lines, or an aligned table when projected
|
|
101
|
+
* (header `id`, `path`, then the projected keys — spec/cli §4, 3.0). */
|
|
99
102
|
export declare function renderHits(cli: {
|
|
100
103
|
io: IO;
|
|
101
104
|
style: Style;
|
package/dist/src/output.js
CHANGED
|
@@ -190,8 +190,10 @@ export function hitCell(v) {
|
|
|
190
190
|
return String(v);
|
|
191
191
|
return truncateCell(JSON.stringify(v) ?? String(v));
|
|
192
192
|
}
|
|
193
|
-
/** The projected column names across `hits`: every key but the
|
|
194
|
-
*
|
|
193
|
+
/** The projected column names across `hits`: every key but the envelope's
|
|
194
|
+
* `id`/`path` (spec/surface §1.4, 3.0 — a `path` projection collapses onto
|
|
195
|
+
* the envelope key, so it is never a column), in first-seen order. Empty when
|
|
196
|
+
* nothing was projected. */
|
|
195
197
|
export function hitColumns(hits) {
|
|
196
198
|
const cols = [];
|
|
197
199
|
const seen = new Set(["id", "path"]);
|
|
@@ -205,7 +207,8 @@ export function hitColumns(hits) {
|
|
|
205
207
|
}
|
|
206
208
|
return cols;
|
|
207
209
|
}
|
|
208
|
-
/** Human-tier hit list: `<id> <path>` lines, or an aligned table when projected
|
|
210
|
+
/** Human-tier hit list: `<id> <path>` lines, or an aligned table when projected
|
|
211
|
+
* (header `id`, `path`, then the projected keys — spec/cli §4, 3.0). */
|
|
209
212
|
export function renderHits(cli, hits) {
|
|
210
213
|
const { io, style } = cli;
|
|
211
214
|
const projected = hitColumns(hits);
|
|
@@ -214,7 +217,7 @@ export function renderHits(cli, hits) {
|
|
|
214
217
|
io.out(`${style.id(h.id)} ${style.accent(h.path)}`.trimEnd());
|
|
215
218
|
return;
|
|
216
219
|
}
|
|
217
|
-
const cols =
|
|
220
|
+
const cols = ["id", "path", ...projected];
|
|
218
221
|
const rows = [cols.map((c) => style.dim(c))];
|
|
219
222
|
for (const h of hits) {
|
|
220
223
|
rows.push(cols.map((c) => {
|
|
@@ -222,8 +225,6 @@ export function renderHits(cli, hits) {
|
|
|
222
225
|
return style.id(h.id);
|
|
223
226
|
if (c === "path")
|
|
224
227
|
return style.accent(h.path);
|
|
225
|
-
if (c === "$path" && typeof h[c] === "string")
|
|
226
|
-
return style.accent(h[c]);
|
|
227
228
|
return hitCell(h[c]);
|
|
228
229
|
}));
|
|
229
230
|
}
|
package/dist/src/shell/refs.js
CHANGED
|
@@ -10,7 +10,10 @@
|
|
|
10
10
|
// signal to use OQX instead.
|
|
11
11
|
export class RefError extends Error {
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
// `.field` reads any projected key (`@1.id`, `@t[1].path`, `@1.layer` —
|
|
14
|
+
// spec/surface §1.4, 3.0 spells the envelope as the bare attributes); a leading
|
|
15
|
+
// `$` is admitted for an OQX-named projection (`$depth`).
|
|
16
|
+
const REF_RE = /^@(\d+|_|[A-Za-z][\w-]*)(?:\[(\d+)\])?(?:\.(\$?[A-Za-z_]\w*))?$/;
|
|
14
17
|
/** Parse a whole-token reference; null if the token isn't a reference. */
|
|
15
18
|
export function parseRef(token) {
|
|
16
19
|
if (!token.startsWith("@"))
|
|
@@ -64,13 +67,20 @@ function rowOf(el) {
|
|
|
64
67
|
return { ref: el, label: el, value: el };
|
|
65
68
|
if (el && typeof el === "object") {
|
|
66
69
|
const o = el;
|
|
67
|
-
const ref = firstString(o,
|
|
68
|
-
const label = firstString(o, ["path", "locator", "name", "id", "node"]) ?? ref ?? preview(o);
|
|
70
|
+
const ref = firstString(o, REF_KEYS);
|
|
71
|
+
const label = firstString(o, ["path", "locator", "name", "id", "node", "doc_id"]) ?? ref ?? preview(o);
|
|
69
72
|
return { ref: ref ?? label ?? "", label: label ?? "", value: el };
|
|
70
73
|
}
|
|
71
74
|
const s = String(el);
|
|
72
75
|
return { ref: s, label: s, value: el };
|
|
73
76
|
}
|
|
77
|
+
// The keys that make an object an entity ref, in precedence order: a row's
|
|
78
|
+
// `id` (spec/surface §1.2, 3.0 — every tool result spells a row's identity as
|
|
79
|
+
// the attribute; a 2.x server's `id` is the same key), a `links` row's far node
|
|
80
|
+
// (`doc_id`) and an edge's `block_id`, then the pre-3.0 envelopes (`node`,
|
|
81
|
+
// `block`) — kept so a remote 2.x server's results still address — then
|
|
82
|
+
// `locator` and a path.
|
|
83
|
+
const REF_KEYS = ["id", "doc_id", "block_id", "node", "block", "locator", "path"];
|
|
74
84
|
function firstString(o, keys) {
|
|
75
85
|
for (const k of keys)
|
|
76
86
|
if (typeof o[k] === "string")
|
|
@@ -98,7 +108,7 @@ export function coerce(value) {
|
|
|
98
108
|
}
|
|
99
109
|
if (typeof value === "object") {
|
|
100
110
|
const o = value;
|
|
101
|
-
const ref = firstString(o,
|
|
111
|
+
const ref = firstString(o, REF_KEYS);
|
|
102
112
|
if (ref)
|
|
103
113
|
return ref;
|
|
104
114
|
// A collection-shaped result object (e.g. {hits:[…]}) can't be one arg.
|
|
@@ -295,7 +295,7 @@ export class ShellSession {
|
|
|
295
295
|
return String(v);
|
|
296
296
|
if (v && typeof v === "object") {
|
|
297
297
|
const o = v;
|
|
298
|
-
const id = o.id ?? o.node ?? o.path ?? o.locator;
|
|
298
|
+
const id = o.id ?? o.id ?? o.node ?? o.path ?? o.path ?? o.locator; // a query hit leads with id/path (surface 3.0)
|
|
299
299
|
return id ? String(id) : "record";
|
|
300
300
|
}
|
|
301
301
|
return "empty";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "omgbase",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.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.
|
|
48
|
+
"@omgbase/core": "^0.11.0",
|
|
49
49
|
"@omgbase/fs-adapter": "^0.2.0",
|
|
50
|
-
"@omgbase/sync": "^0.
|
|
50
|
+
"@omgbase/sync": "^0.5.0",
|
|
51
51
|
"yaml": "^2.9.0"
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|