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.
- package/dist/build-info.json +4 -0
- package/dist/src/build.d.ts +22 -0
- package/dist/src/build.js +58 -0
- package/dist/src/cmd/_mutate.d.ts +12 -0
- package/dist/src/cmd/_mutate.js +26 -13
- package/dist/src/cmd/admin.js +25 -22
- package/dist/src/cmd/bootstrap.js +14 -22
- package/dist/src/cmd/cat.js +10 -12
- package/dist/src/cmd/diff.js +39 -27
- package/dist/src/cmd/docs.js +44 -6
- package/dist/src/cmd/find.js +3 -15
- package/dist/src/cmd/help.js +1 -1
- package/dist/src/cmd/hist.js +4 -10
- package/dist/src/cmd/links.js +21 -16
- package/dist/src/cmd/log.js +8 -18
- package/dist/src/cmd/ls.js +3 -15
- package/dist/src/cmd/mcp.js +16 -32
- package/dist/src/cmd/mutate.js +24 -3
- package/dist/src/cmd/node.js +7 -3
- package/dist/src/cmd/oqx.d.ts +10 -0
- package/dist/src/cmd/oqx.js +25 -28
- package/dist/src/cmd/outline.js +5 -8
- package/dist/src/cmd/retarget.js +6 -21
- package/dist/src/cmd/run.js +5 -32
- package/dist/src/cmd/show.js +11 -12
- package/dist/src/cmd/source.js +18 -32
- package/dist/src/cmd/status.js +13 -25
- package/dist/src/cmd/sync.js +8 -9
- package/dist/src/cmd/update.js +10 -5
- package/dist/src/cmd/version.d.ts +2 -0
- package/dist/src/cmd/version.js +68 -0
- package/dist/src/commands.js +2 -0
- package/dist/src/context.d.ts +4 -0
- package/dist/src/context.js +16 -0
- package/dist/src/dispatch.d.ts +3 -0
- package/dist/src/dispatch.js +35 -18
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +19 -13
- package/dist/src/output.d.ts +28 -1
- package/dist/src/output.js +66 -20
- package/dist/src/seams.d.ts +13 -0
- package/dist/src/seams.js +37 -0
- package/dist/src/shell/session.js +4 -2
- package/package.json +4 -4
package/dist/src/cmd/help.js
CHANGED
|
@@ -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;
|
package/dist/src/cmd/hist.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolveRef, historyNode } from "@omgbase/core";
|
|
3
|
-
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { remoteCall } from "./_remote.js";
|
|
5
5
|
// `omg hist <node>` (11 §5.5) — history_node: a block's biography.
|
|
6
6
|
async function runHist(cli, args) {
|
|
@@ -34,15 +34,9 @@ async function runHist(cli, args) {
|
|
|
34
34
|
throw new EngineErrorLike("block_missing", `hist needs a block id; got ${ref}`);
|
|
35
35
|
changes = historyNode(ws.store, resolved.blockId, values.n ? { limit: Number(values.n) } : {});
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
return
|
|
40
|
-
}
|
|
41
|
-
if (cli.flags.mode === "jsonl") {
|
|
42
|
-
for (const c of changes)
|
|
43
|
-
cli.io.out(JSON.stringify(c));
|
|
44
|
-
return EXIT_OK;
|
|
45
|
-
}
|
|
37
|
+
// A change carries no id of its own, so `--ids` is the `--json` document.
|
|
38
|
+
if (cli.flags.mode !== "human")
|
|
39
|
+
return emitMachine(cli, changes, { items: changes });
|
|
46
40
|
const { style, io } = cli;
|
|
47
41
|
if (changes.length === 0) {
|
|
48
42
|
io.err(style.dim(" no history"));
|
package/dist/src/cmd/links.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolveRef, docLinks } from "@omgbase/core";
|
|
3
|
-
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
// `omg links <node>` (11 §5.4) — open edges touching the node; default both
|
|
5
|
-
// directions, grouped; doc-grain by default, --blocks for block-grain
|
|
6
|
-
// Backlinks = --in.
|
|
5
|
+
// directions, grouped; doc-grain by default, --blocks for block-grain (one row
|
|
6
|
+
// per edge, naming the source block). Backlinks = --in.
|
|
7
7
|
function runLinks(cli, args) {
|
|
8
8
|
const { values, positionals } = parseArgs({
|
|
9
9
|
args,
|
|
@@ -45,30 +45,35 @@ function runLinks(cli, args) {
|
|
|
45
45
|
opts.blocks = true;
|
|
46
46
|
const result = docLinks(ws.store, resolved.docId, opts);
|
|
47
47
|
cli.capture?.(result); // shell: edges (out+in) become the addressable frame
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return
|
|
51
|
-
}
|
|
52
|
-
if (cli.flags.mode === "ids") {
|
|
53
|
-
for (const e of [...result.out, ...result.in])
|
|
54
|
-
cli.io.out(e.node);
|
|
55
|
-
return EXIT_OK;
|
|
56
|
-
}
|
|
48
|
+
// `{ out, in }` is not a list: `--jsonl` prints the document; `--ids` the far nodes.
|
|
49
|
+
if (cli.flags.mode !== "human")
|
|
50
|
+
return emitMachine(cli, result, { ids: [...result.out, ...result.in].map((e) => e.node) });
|
|
57
51
|
const { render, style, io } = cli;
|
|
58
52
|
const g = render.g;
|
|
59
53
|
if (result.out.length === 0 && result.in.length === 0) {
|
|
60
54
|
io.err(style.dim(" no links"));
|
|
61
55
|
return EXIT_OK;
|
|
62
56
|
}
|
|
57
|
+
// Doc-grain: one row per (predicate, node) with its rollup count. Block-grain:
|
|
58
|
+
// one row per edge, the source block beside the far node.
|
|
59
|
+
const src = (e) => style.dim(`(${e.block ?? "frontmatter"})`);
|
|
63
60
|
if (result.out.length > 0) {
|
|
64
61
|
io.out(style.dim(" out"));
|
|
65
|
-
for (const e of result.out)
|
|
66
|
-
|
|
62
|
+
for (const e of result.out) {
|
|
63
|
+
if (values.blocks)
|
|
64
|
+
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.node)} ${src(e)}`);
|
|
65
|
+
else
|
|
66
|
+
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.node)} ${style.dim(`×${e.count}`)}`);
|
|
67
|
+
}
|
|
67
68
|
}
|
|
68
69
|
if (result.in.length > 0) {
|
|
69
70
|
io.out(style.dim(" in (backlinks)"));
|
|
70
|
-
for (const e of result.in)
|
|
71
|
-
|
|
71
|
+
for (const e of result.in) {
|
|
72
|
+
if (values.blocks)
|
|
73
|
+
io.out(` ${style.id(e.node)} ${src(e)} ${style.dim(g.arrow)} ${style.accent(e.predicate)}`);
|
|
74
|
+
else
|
|
75
|
+
io.out(` ${style.id(e.node)} ${style.dim(g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
|
|
76
|
+
}
|
|
72
77
|
}
|
|
73
78
|
return EXIT_OK;
|
|
74
79
|
}
|
package/dist/src/cmd/log.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { changesSince } from "@omgbase/core";
|
|
3
|
-
import { CliUsageError, truncationFooter, EXIT_OK, renderHelp } from "../output.js";
|
|
3
|
+
import { CliUsageError, truncationFooter, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { remoteCall } from "./_remote.js";
|
|
5
5
|
// `omg log` (11 §5.5) — changes_since, one digest summary per line. Relative
|
|
6
6
|
// --since (24h/7d) is resolved to a literal ISO timestamp client-side, then to
|
|
@@ -23,9 +23,12 @@ function relativeToIso(since) {
|
|
|
23
23
|
const ms = unit === "h" ? 3600e3 : unit === "d" ? 86400e3 : unit === "w" ? 7 * 86400e3 : 30 * 86400e3;
|
|
24
24
|
return new Date(Date.now() - n * ms).toISOString();
|
|
25
25
|
}
|
|
26
|
-
// Absolute timestamp: pass through (Date normalizes).
|
|
26
|
+
// Absolute timestamp: pass through (Date normalizes). Anything else is the
|
|
27
|
+
// user's mistake, not "the epoch" (spec/cli §6 `log`).
|
|
27
28
|
const t = Date.parse(since);
|
|
28
|
-
|
|
29
|
+
if (Number.isNaN(t))
|
|
30
|
+
throw new CliUsageError(`bad --since '${since}' (use a relative age like 24h or 7d, or an ISO timestamp)`);
|
|
31
|
+
return new Date(t).toISOString();
|
|
29
32
|
}
|
|
30
33
|
async function runLog(cli, args) {
|
|
31
34
|
const { values } = parseArgs({
|
|
@@ -76,21 +79,8 @@ async function runLog(cli, args) {
|
|
|
76
79
|
opts.origin = values.origin;
|
|
77
80
|
result = changesSince(ws.store, repo.repoId, opts);
|
|
78
81
|
}
|
|
79
|
-
if (cli.flags.mode
|
|
80
|
-
cli.
|
|
81
|
-
return EXIT_OK;
|
|
82
|
-
}
|
|
83
|
-
if (cli.flags.mode === "jsonl") {
|
|
84
|
-
for (const d of result.digests)
|
|
85
|
-
cli.io.out(JSON.stringify(d));
|
|
86
|
-
if (result.truncated)
|
|
87
|
-
truncationFooter(cli.io, cli.style, result.cursor);
|
|
88
|
-
return EXIT_OK;
|
|
89
|
-
}
|
|
90
|
-
if (cli.flags.mode === "ids") {
|
|
91
|
-
for (const d of result.digests)
|
|
92
|
-
cli.io.out(d.commit);
|
|
93
|
-
return EXIT_OK;
|
|
82
|
+
if (cli.flags.mode !== "human") {
|
|
83
|
+
return emitMachine(cli, result, { items: result.digests, ids: result.digests.map((d) => d.commit), cursor: result.truncated ? result.cursor : null });
|
|
94
84
|
}
|
|
95
85
|
const { style, io } = cli;
|
|
96
86
|
if (result.digests.length === 0) {
|
package/dist/src/cmd/ls.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { docsList } from "@omgbase/core";
|
|
3
3
|
import { columns } from "../render.js";
|
|
4
|
-
import { EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
+
import { EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
5
5
|
import { remoteCall } from "./_remote.js";
|
|
6
6
|
// `omg ls [glob]` (11 §5.2) — live documents: path, block count, last-commit
|
|
7
7
|
// time. glob is a simple prefix/suffix match on the path (SQL LIKE with * → %).
|
|
@@ -39,20 +39,8 @@ async function runLs(cli, args) {
|
|
|
39
39
|
cursor = page.truncated ? page.cursor : null;
|
|
40
40
|
} while (cursor);
|
|
41
41
|
cli.capture?.(rows); // shell: docs become the addressable frame (ref = path)
|
|
42
|
-
if (cli.flags.mode
|
|
43
|
-
|
|
44
|
-
cli.io.out(r.path);
|
|
45
|
-
return EXIT_OK;
|
|
46
|
-
}
|
|
47
|
-
if (cli.flags.mode === "json") {
|
|
48
|
-
cli.io.out(JSON.stringify(rows));
|
|
49
|
-
return EXIT_OK;
|
|
50
|
-
}
|
|
51
|
-
if (cli.flags.mode === "jsonl") {
|
|
52
|
-
for (const r of rows)
|
|
53
|
-
cli.io.out(JSON.stringify(r));
|
|
54
|
-
return EXIT_OK;
|
|
55
|
-
}
|
|
42
|
+
if (cli.flags.mode !== "human")
|
|
43
|
+
return emitMachine(cli, rows, { items: rows, ids: rows.map((r) => r.path) });
|
|
56
44
|
const { style, io } = cli;
|
|
57
45
|
if (rows.length === 0) {
|
|
58
46
|
io.err(style.dim(" no documents"));
|
package/dist/src/cmd/mcp.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { serveStdio, Watcher, WatchLease, watchLeaseLive, freshnessSweep, awaitReady, WATCH_READY_PATIENCE_MS, EmbedDrainer, EngineError
|
|
3
|
-
import {
|
|
2
|
+
import { serveStdio, Watcher, WatchLease, watchLeaseLive, freshnessSweep, awaitReady, WATCH_READY_PATIENCE_MS, EmbedDrainer, EngineError } from "@omgbase/core";
|
|
3
|
+
import { EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
+
import { installSpecSeams } from "../seams.js";
|
|
4
5
|
import { loadEmbedding } from "./_embed.js";
|
|
5
6
|
import { openRepoSource } from "./_source.js";
|
|
7
|
+
import { hostInfo } from "../build.js";
|
|
6
8
|
// `omg mcp [--no-watch]` (11 §5.8) — MCP server on stdio; the host owns the
|
|
7
9
|
// process lifetime. Runs an in-process watcher by default so a lone session is
|
|
8
10
|
// always fresh, auto-disabled when another live watch lease exists; --no-watch
|
|
@@ -11,38 +13,18 @@ import { openRepoSource } from "./_source.js";
|
|
|
11
13
|
// CRITICAL: on stdio, stdout is the MCP protocol channel. Nothing may write to
|
|
12
14
|
// stdout here — every diagnostic goes to stderr (cli.io.err).
|
|
13
15
|
/**
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* OMGBASE_SPEC_MINTER=sequential the fixture minter of spec/store §2.2 for
|
|
19
|
-
* the whole process (`d_0, d_1, …`, each
|
|
20
|
-
* prefix from 0); any other value is a
|
|
21
|
-
* startup error
|
|
22
|
-
* OMGBASE_SPEC_CLOCK=<RFC 3339> "now" is that instant for the whole
|
|
23
|
-
* process: every commit a tool stamps, every
|
|
24
|
-
* `ts` reported as current
|
|
25
|
-
*
|
|
26
|
-
* Both are announced on stderr (stdout is the protocol channel).
|
|
16
|
+
* Announce the two conformance seams of spec/surface §7.1 on stderr (stdout is
|
|
17
|
+
* the protocol channel). They are installed for every verb at the entry point
|
|
18
|
+
* (`seams.ts`, spec/cli §2.6) — `mcp` only reports them, so a conformance run
|
|
19
|
+
* is visibly not a production one.
|
|
27
20
|
*/
|
|
28
|
-
function
|
|
29
|
-
const
|
|
30
|
-
if (minter
|
|
31
|
-
if (minter !== "sequential") {
|
|
32
|
-
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");
|
|
33
|
-
}
|
|
34
|
-
setIdMinter(sequentialMinter());
|
|
21
|
+
function announceSpecSeams(cli) {
|
|
22
|
+
const seams = installSpecSeams();
|
|
23
|
+
if (seams.minter) {
|
|
35
24
|
cli.io.err(cli.style.dim("[mcp] spec seam: sequential id minter (OMGBASE_SPEC_MINTER=sequential) — conformance run, not for production"));
|
|
36
25
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
pinClock(clock);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
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");
|
|
44
|
-
}
|
|
45
|
-
cli.io.err(cli.style.dim(`[mcp] spec seam: clock pinned to ${new Date().toISOString()} (OMGBASE_SPEC_CLOCK) — conformance run, not for production`));
|
|
26
|
+
if (seams.clock !== null) {
|
|
27
|
+
cli.io.err(cli.style.dim(`[mcp] spec seam: clock pinned to ${seams.clock} (OMGBASE_SPEC_CLOCK) — conformance run, not for production`));
|
|
46
28
|
}
|
|
47
29
|
}
|
|
48
30
|
async function runMcp(cli, args) {
|
|
@@ -64,7 +46,7 @@ async function runMcp(cli, args) {
|
|
|
64
46
|
notes: [`host config: {"command": "${cli.prog}", "args": ["mcp", "-C", "/path/to/notes"]}`],
|
|
65
47
|
});
|
|
66
48
|
}
|
|
67
|
-
|
|
49
|
+
announceSpecSeams(cli);
|
|
68
50
|
// The host launches us from its own cwd, so a missing workspace here almost
|
|
69
51
|
// always means the host config lacks `-C <dir>` — say so, rather than the
|
|
70
52
|
// generic "run init" hint (which would create an empty workspace in the wrong place).
|
|
@@ -185,6 +167,8 @@ async function runMcp(cli, args) {
|
|
|
185
167
|
...(repo.rootPath ? { rootPath: repo.rootPath } : {}),
|
|
186
168
|
...(drainer ? { onMutation: () => drainer.schedule() } : {}),
|
|
187
169
|
...(embedQuery ? { embedQuery } : {}),
|
|
170
|
+
// The `version` tool's view of this binary (spec/surface §4).
|
|
171
|
+
host: hostInfo(),
|
|
188
172
|
});
|
|
189
173
|
let shuttingDown = false;
|
|
190
174
|
const shutdown = async () => {
|
package/dist/src/cmd/mutate.js
CHANGED
|
@@ -13,6 +13,20 @@ function block(cli, ws, repoId, ref) {
|
|
|
13
13
|
throw new EngineErrorLike("block_missing", `not a block: ${ref}`);
|
|
14
14
|
return r.blockId;
|
|
15
15
|
}
|
|
16
|
+
// `done` addresses task blocks only (spec/cli §6): any other live block type is
|
|
17
|
+
// a type_mismatch before an op is built — the macro itself would record a no-op
|
|
18
|
+
// `edited` commit (its `attrs.checked` only rewrites a task's marker).
|
|
19
|
+
function taskBlock(ws, ref, blockId) {
|
|
20
|
+
const row = ws.store.db
|
|
21
|
+
.prepare("SELECT type FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
|
|
22
|
+
.get(blockId);
|
|
23
|
+
if (!row)
|
|
24
|
+
throw new EngineErrorLike("block_missing", `not a block: ${ref}`);
|
|
25
|
+
if (row.type !== "task") {
|
|
26
|
+
throw new EngineErrorLike("type_mismatch", `not a task: ${ref} is a ${row.type}`, { data: { block: blockId, type: row.type } });
|
|
27
|
+
}
|
|
28
|
+
return blockId;
|
|
29
|
+
}
|
|
16
30
|
function rawHashOf(ws, blockId) {
|
|
17
31
|
const row = ws.store.db
|
|
18
32
|
.prepare("SELECT lower(hex(raw_hash)) h FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
|
|
@@ -87,7 +101,7 @@ async function runInsert(cli, args) {
|
|
|
87
101
|
}
|
|
88
102
|
const to = positionals[0];
|
|
89
103
|
if (!to)
|
|
90
|
-
throw new CliUsageError("insert requires a <to> parent (block id, or a
|
|
104
|
+
throw new CliUsageError("insert requires a <to> parent (a container block id, or a doc id/path for the top level)");
|
|
91
105
|
const markdown = readContent(content);
|
|
92
106
|
// The destination-parent CAS (spec/mutate §1.2) rides as the op-level expect.
|
|
93
107
|
const expect = values.expect ? { expect: { parent_children_hash: values.expect } } : {};
|
|
@@ -238,7 +252,7 @@ async function runDone(cli, args) {
|
|
|
238
252
|
return runOpsRemote(cli, "tasks_complete", { blocks: refs, ...(values.undo ? { checked: false } : {}) });
|
|
239
253
|
const ws = cli.workspace();
|
|
240
254
|
const repo = cli.repo(ws);
|
|
241
|
-
const blocks = refs.map((r) => block(cli, ws, repo.repoId, r));
|
|
255
|
+
const blocks = refs.map((r) => taskBlock(ws, r, block(cli, ws, repo.repoId, r)));
|
|
242
256
|
const ops = values.undo
|
|
243
257
|
? blocks.map((b) => ({ op: "update", block: b, attrs: { checked: false }, expect: pinned(ws, b) }))
|
|
244
258
|
: tasksComplete(ws.store, blocks);
|
|
@@ -301,7 +315,14 @@ async function runSplit(cli, args) {
|
|
|
301
315
|
throw new CliUsageError("split requires a <block>");
|
|
302
316
|
if (!values.at)
|
|
303
317
|
throw new CliUsageError("split requires --at n[,n…]");
|
|
304
|
-
|
|
318
|
+
// Offsets are integers (spec/mutate §2.5); anything else is a usage error
|
|
319
|
+
// here, not a NaN that reaches the kernel.
|
|
320
|
+
const at = values.at.split(",").map((s) => {
|
|
321
|
+
const n = Number(s.trim());
|
|
322
|
+
if (s.trim() === "" || !Number.isInteger(n))
|
|
323
|
+
throw new CliUsageError(`bad --at '${values.at}' (offsets must be integers: n[,n…])`);
|
|
324
|
+
return n;
|
|
325
|
+
});
|
|
305
326
|
if (cli.flags.server)
|
|
306
327
|
return runOpsRemote(cli, "blocks_split", { block: ref, at });
|
|
307
328
|
const ws = cli.workspace();
|
package/dist/src/cmd/node.js
CHANGED
|
@@ -28,8 +28,12 @@ async function runNode(cli, args) {
|
|
|
28
28
|
});
|
|
29
29
|
}
|
|
30
30
|
if (sub === "set") {
|
|
31
|
-
|
|
32
|
-
|
|
31
|
+
// The value is every positional after <prop>, joined by a space; --actor
|
|
32
|
+
// (an option WITH a value) is taken out by the parser, never by a prefix
|
|
33
|
+
// filter — that filter used to leave the actor's value inside the property
|
|
34
|
+
// value (`checked "true human:spec"`), which the task editor read as false.
|
|
35
|
+
const { values, positionals } = parseArgs({ args: rest, allowPositionals: true, options: { actor: { type: "string" } } });
|
|
36
|
+
const [nodeId, prop, ...valueParts] = positionals;
|
|
33
37
|
if (!nodeId || !prop || valueParts.length === 0)
|
|
34
38
|
throw new CliUsageError("node set <nodeId> <prop> <value>");
|
|
35
39
|
const value = valueParts.join(" ");
|
|
@@ -39,7 +43,7 @@ async function runNode(cli, args) {
|
|
|
39
43
|
const ws = cli.workspace();
|
|
40
44
|
const repo = cli.repo(ws);
|
|
41
45
|
const ops = nodeSet(ws.store, nodeId, prop, value);
|
|
42
|
-
return runOps(cli, ws, repo, ops, { reason: `node_set ${prop}
|
|
46
|
+
return runOps(cli, ws, repo, ops, { reason: `node_set ${prop}`, ...(values.actor ? { actor: values.actor } : {}) });
|
|
43
47
|
}
|
|
44
48
|
if (sub === "props") {
|
|
45
49
|
// `props` is a local editability lookup with no remote tool (a niche read).
|
package/dist/src/cmd/oqx.d.ts
CHANGED
|
@@ -1,2 +1,12 @@
|
|
|
1
|
+
import { type OqxResult } from "@omgbase/core";
|
|
1
2
|
import type { Command } from "../commands.js";
|
|
3
|
+
import type { Cli } from "../context.js";
|
|
4
|
+
/**
|
|
5
|
+
* Render an `OqxResult` in the current mode (shared with `run`). `--json` is the
|
|
6
|
+
* whole result verbatim (incl. consumer + any scalar), so count/exists round-trip
|
|
7
|
+
* without special-casing; `--jsonl` streams the hits (or the values, as JSON);
|
|
8
|
+
* `--ids` the hit ids — a scalar result has neither, so those modes print the
|
|
9
|
+
* document. The truncation footer prints in every mode but `--json`.
|
|
10
|
+
*/
|
|
11
|
+
export declare function renderOqxResult(cli: Cli, result: OqxResult): number;
|
|
2
12
|
export declare const cmdOqx: Command;
|
package/dist/src/cmd/oqx.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { oqxRun, oqxRunAsync, collectSemanticPhrases } from "@omgbase/core";
|
|
4
|
-
import { truncationFooter, EngineErrorLike, EXIT_OK, renderHelp, renderHits } from "../output.js";
|
|
4
|
+
import { truncationFooter, CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp, renderHits } from "../output.js";
|
|
5
5
|
import { loadEmbedding } from "./_embed.js";
|
|
6
6
|
import { remoteCall } from "./_remote.js";
|
|
7
7
|
import { readStdin } from "./_mutate.js";
|
|
@@ -85,10 +85,8 @@ async function runOqx(cli, args) {
|
|
|
85
85
|
const source = values.file
|
|
86
86
|
? (values.file === "-" ? readStdin() : readFileSync(values.file, "utf8"))
|
|
87
87
|
: positionals.join(" ").trim();
|
|
88
|
-
if (!source)
|
|
89
|
-
|
|
90
|
-
return EXIT_OK;
|
|
91
|
-
}
|
|
88
|
+
if (!source)
|
|
89
|
+
throw new CliUsageError("query requires a <source> (or -f file|-)");
|
|
92
90
|
const opts = {};
|
|
93
91
|
if (values.n)
|
|
94
92
|
opts.limit = Number(values.n);
|
|
@@ -135,11 +133,24 @@ async function runOqx(cli, args) {
|
|
|
135
133
|
: result.consumer === "exists" ? result.exists
|
|
136
134
|
: result.consumer === "none" ? result.none
|
|
137
135
|
: result.values ?? result);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
136
|
+
return renderOqxResult(cli, result);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Render an `OqxResult` in the current mode (shared with `run`). `--json` is the
|
|
140
|
+
* whole result verbatim (incl. consumer + any scalar), so count/exists round-trip
|
|
141
|
+
* without special-casing; `--jsonl` streams the hits (or the values, as JSON);
|
|
142
|
+
* `--ids` the hit ids — a scalar result has neither, so those modes print the
|
|
143
|
+
* document. The truncation footer prints in every mode but `--json`.
|
|
144
|
+
*/
|
|
145
|
+
export function renderOqxResult(cli, result) {
|
|
146
|
+
const cursor = result.truncated ? result.cursor ?? "" : null;
|
|
147
|
+
if (cli.flags.mode !== "human") {
|
|
148
|
+
const list = result.values ?? (result.consumer === "collect" || result.consumer === "first" || result.consumer === "single" ? result.hits : undefined);
|
|
149
|
+
return emitMachine(cli, result, {
|
|
150
|
+
...(list ? { items: list } : {}),
|
|
151
|
+
...(list && !result.values ? { ids: result.hits.map((h) => h.id) } : {}),
|
|
152
|
+
cursor,
|
|
153
|
+
});
|
|
143
154
|
}
|
|
144
155
|
// Scalar consumers (count/exists/none) have no hits — render the reduction itself.
|
|
145
156
|
if (result.consumer === "count" || result.consumer === "exists" || result.consumer === "none") {
|
|
@@ -148,26 +159,12 @@ async function runOqx(cli, args) {
|
|
|
148
159
|
return EXIT_OK;
|
|
149
160
|
}
|
|
150
161
|
// A `values` projection has no hits — one bare value per line (strings
|
|
151
|
-
// verbatim, anything else as JSON
|
|
162
|
+
// verbatim, anything else as JSON).
|
|
152
163
|
if (result.values) {
|
|
153
164
|
for (const v of result.values)
|
|
154
|
-
cli.io.out(typeof v === "string"
|
|
155
|
-
if (
|
|
156
|
-
truncationFooter(cli.io, cli.style,
|
|
157
|
-
return EXIT_OK;
|
|
158
|
-
}
|
|
159
|
-
if (cli.flags.mode === "ids") {
|
|
160
|
-
for (const h of result.hits)
|
|
161
|
-
cli.io.out(h.id);
|
|
162
|
-
if (result.truncated)
|
|
163
|
-
truncationFooter(cli.io, cli.style, result.cursor ?? "");
|
|
164
|
-
return EXIT_OK;
|
|
165
|
-
}
|
|
166
|
-
if (cli.flags.mode === "jsonl") {
|
|
167
|
-
for (const h of result.hits)
|
|
168
|
-
cli.io.out(JSON.stringify(h));
|
|
169
|
-
if (result.truncated)
|
|
170
|
-
truncationFooter(cli.io, cli.style, result.cursor ?? "");
|
|
165
|
+
cli.io.out(typeof v === "string" ? v : JSON.stringify(v));
|
|
166
|
+
if (cursor !== null)
|
|
167
|
+
truncationFooter(cli.io, cli.style, cursor);
|
|
171
168
|
return EXIT_OK;
|
|
172
169
|
}
|
|
173
170
|
const { style, io } = cli;
|
package/dist/src/cmd/outline.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { findDoc, docsOutline } from "@omgbase/core";
|
|
3
|
-
import { CliUsageError, EngineErrorLike, truncationFooter, EXIT_OK, renderHelp } from "../output.js";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, truncationFooter, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { remoteCall } 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
|
|
@@ -11,7 +11,6 @@ async function runOutline(cli, args) {
|
|
|
11
11
|
allowPositionals: true,
|
|
12
12
|
options: {
|
|
13
13
|
depth: { type: "string" },
|
|
14
|
-
section: { type: "string" },
|
|
15
14
|
skeleton: { type: "boolean" },
|
|
16
15
|
help: { type: "boolean" },
|
|
17
16
|
},
|
|
@@ -20,10 +19,9 @@ async function runOutline(cli, args) {
|
|
|
20
19
|
return renderHelp(cli, {
|
|
21
20
|
name: "outline",
|
|
22
21
|
summary: "A document's outline with block ids inline (frozen wire format) — the orientation view",
|
|
23
|
-
usage: "outline <doc|path> [--depth <n>] [--
|
|
22
|
+
usage: "outline <doc|path> [--depth <n>] [--skeleton]",
|
|
24
23
|
options: [
|
|
25
24
|
["--depth <n>", "limit heading depth"],
|
|
26
|
-
["--section <locator>", "outline only the section at a locator"],
|
|
27
25
|
["--skeleton", "structure only, no text"],
|
|
28
26
|
],
|
|
29
27
|
});
|
|
@@ -57,10 +55,9 @@ async function runOutline(cli, args) {
|
|
|
57
55
|
result = docsOutline(ws.store, info.docId, opts);
|
|
58
56
|
header = info.path;
|
|
59
57
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
}
|
|
58
|
+
// `{ text, truncated }` carries neither a list nor ids: every machine mode is the document.
|
|
59
|
+
if (cli.flags.mode !== "human")
|
|
60
|
+
return emitMachine(cli, result, { cursor: result.truncated ? "budget" : null });
|
|
64
61
|
const { render, style, io } = cli;
|
|
65
62
|
io.out(render.wordmark(header));
|
|
66
63
|
io.out(render.rule(40));
|
package/dist/src/cmd/retarget.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { linksRetarget } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
-
import { runOps } from "./_mutate.js";
|
|
4
|
+
import { runOps, unifiedDiffLines } from "./_mutate.js";
|
|
5
5
|
import { remoteCall } from "./_remote.js";
|
|
6
6
|
// `omg retarget <from> <to> [--scope glob] [--apply]` (11 §5.6) — plan-by-default.
|
|
7
|
-
// Without --apply it runs the dry-run and prints
|
|
8
|
-
//
|
|
7
|
+
// Without --apply it runs the dry-run and prints, per hit block, the unified
|
|
8
|
+
// diff of its raw (the same diff every other preview prints — the 06 §4 "always
|
|
9
|
+
// dry-run first" contract, encoded as the default). --apply commits.
|
|
9
10
|
async function runRetarget(cli, args) {
|
|
10
11
|
const { values, positionals } = parseArgs({
|
|
11
12
|
args,
|
|
@@ -65,27 +66,11 @@ function renderPlan(cli, from, to, hits) {
|
|
|
65
66
|
io.err(style.dim(` plan — ${hits.length} block(s) would change; re-run with --apply to commit`));
|
|
66
67
|
for (const h of hits) {
|
|
67
68
|
io.out(style.id(h.block));
|
|
68
|
-
for (const line of
|
|
69
|
-
io.out(line
|
|
70
|
-
}
|
|
69
|
+
for (const line of unifiedDiffLines(cli, h.oldRaw, h.newRaw))
|
|
70
|
+
io.out(line);
|
|
71
71
|
io.out("");
|
|
72
72
|
}
|
|
73
73
|
return EXIT_OK;
|
|
74
74
|
}
|
|
75
|
-
function lineDiff(before, after) {
|
|
76
|
-
const a = before.split("\n");
|
|
77
|
-
const b = after.split("\n");
|
|
78
|
-
const out = [];
|
|
79
|
-
const max = Math.max(a.length, b.length);
|
|
80
|
-
for (let i = 0; i < max; i++) {
|
|
81
|
-
if (a[i] === b[i])
|
|
82
|
-
continue;
|
|
83
|
-
if (a[i] !== undefined)
|
|
84
|
-
out.push(`- ${a[i]}`);
|
|
85
|
-
if (b[i] !== undefined)
|
|
86
|
-
out.push(`+ ${b[i]}`);
|
|
87
|
-
}
|
|
88
|
-
return out;
|
|
89
|
-
}
|
|
90
75
|
export const cmdRetarget = { name: "retarget", summary: "Rewrite a link target (plan-by-default)", run: (c, a) => runRetarget(c, a) };
|
|
91
76
|
//# sourceMappingURL=retarget.js.map
|
package/dist/src/cmd/run.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolveRef, findDoc, loadDocBlocks, blockRaw, oqxRun, oqxRunAsync, collectSemanticPhrases } from "@omgbase/core";
|
|
3
|
-
import { CliUsageError, EngineErrorLike,
|
|
3
|
+
import { CliUsageError, EngineErrorLike, renderHelp } from "../output.js";
|
|
4
4
|
import { loadEmbedding } from "./_embed.js";
|
|
5
|
+
import { renderOqxResult } from "./oqx.js";
|
|
5
6
|
// `omg run <locator|path>` (11 §5.3) — evaluate the first ```omg fence in a doc
|
|
6
7
|
// (or the fence at a block locator) and print its results. The fence body is an
|
|
7
8
|
// OQX source string (the same language `omg query` runs). Strictly read-and-print:
|
|
@@ -98,37 +99,9 @@ async function runRun(cli, args) {
|
|
|
98
99
|
else {
|
|
99
100
|
result = oqxRun(ws.store, repo.repoId, source);
|
|
100
101
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
105
|
-
if (result.consumer === "count" || result.consumer === "exists" || result.consumer === "none") {
|
|
106
|
-
cli.io.out(result.consumer === "count" ? String(result.count) : result.consumer === "exists" ? String(result.exists) : String(result.none));
|
|
107
|
-
return EXIT_OK;
|
|
108
|
-
}
|
|
109
|
-
if (result.values) {
|
|
110
|
-
for (const v of result.values)
|
|
111
|
-
cli.io.out(typeof v === "string" ? v : JSON.stringify(v));
|
|
112
|
-
if (result.truncated)
|
|
113
|
-
truncationFooter(cli.io, cli.style, result.cursor ?? "");
|
|
114
|
-
return EXIT_OK;
|
|
115
|
-
}
|
|
116
|
-
if (cli.flags.mode === "ids") {
|
|
117
|
-
for (const h of result.hits)
|
|
118
|
-
cli.io.out(h.id);
|
|
119
|
-
return EXIT_OK;
|
|
120
|
-
}
|
|
121
|
-
const { style, io } = cli;
|
|
122
|
-
if (result.hits.length === 0) {
|
|
123
|
-
io.err(style.dim(" no hits"));
|
|
124
|
-
return EXIT_OK;
|
|
125
|
-
}
|
|
126
|
-
// Same renderer as `omg query`: `<id> <path>` lines, or a column table when
|
|
127
|
-
// the fence projects (a selected `text` shows as its first line).
|
|
128
|
-
renderHits(cli, result.hits);
|
|
129
|
-
if (result.truncated)
|
|
130
|
-
truncationFooter(io, style, result.cursor ?? "");
|
|
131
|
-
return EXIT_OK;
|
|
102
|
+
// Same renderer as `omg query`, in every mode: `<id> <path>` lines, or a
|
|
103
|
+
// column table when the fence projects (a selected `text` shows as its first line).
|
|
104
|
+
return renderOqxResult(cli, result);
|
|
132
105
|
}
|
|
133
106
|
export const cmdRun = { name: "run", summary: "Evaluate an ```omg fence (OQX, inert, read-only)", run: (c, a) => runRun(c, a) };
|
|
134
107
|
//# sourceMappingURL=run.js.map
|
package/dist/src/cmd/show.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { resolveRef, nodesGet,
|
|
3
|
-
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
2
|
+
import { resolveRef, nodesGet, docsRead, docLinks, historyNode, docPropertiesMerged } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { expandBlockArgs } from "./_mutate.js";
|
|
5
5
|
function showOne(cli, ref, include) {
|
|
6
6
|
const ws = cli.workspace();
|
|
@@ -9,11 +9,15 @@ 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
|
-
|
|
12
|
+
// The card reads the merged properties + open edges; `--json` is the
|
|
13
|
+
// `read_ref` result for a document (`{ kind: "document", ...docs_read }`).
|
|
14
|
+
const res = docsRead(ws.store, resolved.docId);
|
|
15
|
+
if (!res)
|
|
16
|
+
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
13
17
|
const links = docLinks(ws.store, resolved.docId, { direction: "both" });
|
|
14
18
|
const properties = docPropertiesMerged(ws.store.db, resolved.docId);
|
|
15
|
-
const payload = { kind: "document",
|
|
16
|
-
return { payload, render: () => renderDocCard(cli,
|
|
19
|
+
const payload = { kind: "document", ...res };
|
|
20
|
+
return { payload, render: () => renderDocCard(cli, res.path, properties, links) };
|
|
17
21
|
}
|
|
18
22
|
const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution: "full" });
|
|
19
23
|
if (!node)
|
|
@@ -47,14 +51,9 @@ function runShow(cli, args) {
|
|
|
47
51
|
// One ref keeps the single-entity shape (→ @_, frame intact); several become a list.
|
|
48
52
|
const single = cards.length === 1 ? cards[0] : null;
|
|
49
53
|
cli.capture?.(single ? single.payload : cards.map((c) => c.payload));
|
|
50
|
-
if (cli.flags.mode === "json") {
|
|
51
|
-
cli.io.out(JSON.stringify(single ? single.payload : cards.map((c) => c.payload)));
|
|
52
|
-
return EXIT_OK;
|
|
53
|
-
}
|
|
54
54
|
if (cli.flags.mode !== "human") {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
return EXIT_OK;
|
|
55
|
+
const docs = cards.map((c) => c.payload);
|
|
56
|
+
return single ? emitMachine(cli, single.payload) : emitMachine(cli, docs, { items: docs });
|
|
58
57
|
}
|
|
59
58
|
for (const c of cards)
|
|
60
59
|
c.render();
|