omgbase 0.4.0 → 0.4.1
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/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/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 +13 -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/context.d.ts +4 -0
- package/dist/src/context.js +14 -0
- package/dist/src/dispatch.d.ts +3 -0
- package/dist/src/dispatch.js +35 -18
- package/dist/src/main.d.ts +7 -0
- package/dist/src/main.js +46 -12
- 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 +3 -3
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,6 +1,7 @@
|
|
|
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";
|
|
6
7
|
// `omg mcp [--no-watch]` (11 §5.8) — MCP server on stdio; the host owns the
|
|
@@ -11,38 +12,18 @@ import { openRepoSource } from "./_source.js";
|
|
|
11
12
|
// CRITICAL: on stdio, stdout is the MCP protocol channel. Nothing may write to
|
|
12
13
|
// stdout here — every diagnostic goes to stderr (cli.io.err).
|
|
13
14
|
/**
|
|
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).
|
|
15
|
+
* Announce the two conformance seams of spec/surface §7.1 on stderr (stdout is
|
|
16
|
+
* the protocol channel). They are installed for every verb at the entry point
|
|
17
|
+
* (`seams.ts`, spec/cli §2.6) — `mcp` only reports them, so a conformance run
|
|
18
|
+
* is visibly not a production one.
|
|
27
19
|
*/
|
|
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());
|
|
20
|
+
function announceSpecSeams(cli) {
|
|
21
|
+
const seams = installSpecSeams();
|
|
22
|
+
if (seams.minter) {
|
|
35
23
|
cli.io.err(cli.style.dim("[mcp] spec seam: sequential id minter (OMGBASE_SPEC_MINTER=sequential) — conformance run, not for production"));
|
|
36
24
|
}
|
|
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`));
|
|
25
|
+
if (seams.clock !== null) {
|
|
26
|
+
cli.io.err(cli.style.dim(`[mcp] spec seam: clock pinned to ${seams.clock} (OMGBASE_SPEC_CLOCK) — conformance run, not for production`));
|
|
46
27
|
}
|
|
47
28
|
}
|
|
48
29
|
async function runMcp(cli, args) {
|
|
@@ -64,7 +45,7 @@ async function runMcp(cli, args) {
|
|
|
64
45
|
notes: [`host config: {"command": "${cli.prog}", "args": ["mcp", "-C", "/path/to/notes"]}`],
|
|
65
46
|
});
|
|
66
47
|
}
|
|
67
|
-
|
|
48
|
+
announceSpecSeams(cli);
|
|
68
49
|
// The host launches us from its own cwd, so a missing workspace here almost
|
|
69
50
|
// always means the host config lacks `-C <dir>` — say so, rather than the
|
|
70
51
|
// generic "run init" hint (which would create an empty workspace in the wrong place).
|
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();
|
package/dist/src/cmd/source.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createSource, deleteSource, listSources, getSourceByName, attachSourceT
|
|
|
6
6
|
import { columns } from "../render.js";
|
|
7
7
|
import { drainEmbeddings } from "./_embed.js";
|
|
8
8
|
import { ensureFsAdapter, FS_ADAPTER } from "./_source.js";
|
|
9
|
-
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
9
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
10
10
|
// `omg source` (ADR-014) — the source registry: where a repo's bytes come from
|
|
11
11
|
// (a filesystem directory today; git/S3/etc. via adapters later). A repo owns
|
|
12
12
|
// identity + history; adding a filesystem source points it at a directory and
|
|
@@ -101,14 +101,16 @@ async function runAdd(cli, args) {
|
|
|
101
101
|
if (getSourceByName(ws.store, sourceName))
|
|
102
102
|
throw new EngineErrorLike("path_taken", `a source named '${sourceName}' already exists`);
|
|
103
103
|
// Consent gate: -y proceeds; a TTY prompts with a live file count; a non-TTY
|
|
104
|
-
// without -y
|
|
104
|
+
// without -y is a usage error (the script forgot the flag; exit 2) rather
|
|
105
|
+
// than a silent absorption of the tree — or a silent success.
|
|
105
106
|
if (!values.yes) {
|
|
107
|
+
if (!process.stdin.isTTY || !cli.io.stdoutTTY) {
|
|
108
|
+
throw new CliUsageError(`refusing without -y (would ingest files under ${shortenHome(abs)})`, "there is no TTY to confirm on; pass -y to consent to the ingest");
|
|
109
|
+
}
|
|
106
110
|
const { ok } = await confirmIngestWithCount(cli, abs, slug);
|
|
107
|
-
|
|
108
|
-
process.stderr.write("\n");
|
|
111
|
+
process.stderr.write("\n");
|
|
109
112
|
if (!ok) {
|
|
110
|
-
|
|
111
|
-
cli.io.err(cli.style.dim(` ${why}`));
|
|
113
|
+
cli.io.err(cli.style.dim(" cancelled"));
|
|
112
114
|
return EXIT_OK;
|
|
113
115
|
}
|
|
114
116
|
}
|
|
@@ -133,10 +135,8 @@ async function runAdd(cli, args) {
|
|
|
133
135
|
return confirmTTY(cli, `embed ${pending} block(s) now via ${where}?`);
|
|
134
136
|
},
|
|
135
137
|
});
|
|
136
|
-
if (cli.flags.mode !== "human")
|
|
137
|
-
cli
|
|
138
|
-
return EXIT_OK;
|
|
139
|
-
}
|
|
138
|
+
if (cli.flags.mode !== "human")
|
|
139
|
+
return emitMachine(cli, { repo: slug, repoId, source: sourceName, root: abs, ingested: swept.ingested.length, ...(drained ? { embedded: drained.embedded } : {}) });
|
|
140
140
|
const { render, style, io } = cli;
|
|
141
141
|
io.out(` ${render.g.diamond} ${style.accent(slug)} ← ${style.path(shortenHome(abs))} ${style.dim(`${swept.ingested.length} files`)}`);
|
|
142
142
|
if (drained)
|
|
@@ -146,23 +146,13 @@ async function runAdd(cli, args) {
|
|
|
146
146
|
function runList(cli) {
|
|
147
147
|
const ws = cli.workspace();
|
|
148
148
|
const sources = listSources(ws.store).map((s) => ({ ...s, repos: attachedRepoSlugs(cli, ws, s.sourceId) }));
|
|
149
|
-
if (cli.flags.mode
|
|
150
|
-
|
|
151
|
-
return
|
|
152
|
-
}
|
|
153
|
-
if (cli.flags.mode === "jsonl") {
|
|
154
|
-
for (const s of sources)
|
|
155
|
-
cli.io.out(JSON.stringify({ name: s.name, adapter: s.adapter, config: s.config, repos: s.repos }));
|
|
156
|
-
return EXIT_OK;
|
|
157
|
-
}
|
|
158
|
-
if (cli.flags.mode === "ids") {
|
|
159
|
-
for (const s of sources)
|
|
160
|
-
cli.io.out(s.name);
|
|
161
|
-
return EXIT_OK;
|
|
149
|
+
if (cli.flags.mode !== "human") {
|
|
150
|
+
const rows = sources.map((s) => ({ name: s.name, adapter: s.adapter, config: s.config, repos: s.repos }));
|
|
151
|
+
return emitMachine(cli, rows, { items: rows, ids: rows.map((r) => r.name) });
|
|
162
152
|
}
|
|
163
153
|
const { style, io } = cli;
|
|
164
154
|
if (sources.length === 0) {
|
|
165
|
-
io.
|
|
155
|
+
io.err(style.dim(" no sources registered"));
|
|
166
156
|
return EXIT_OK;
|
|
167
157
|
}
|
|
168
158
|
const rows = sources.map((s) => [
|
|
@@ -189,10 +179,8 @@ function runAttachDetach(cli, args, verb) {
|
|
|
189
179
|
attachSourceToRepo(ws.store, repo.repoId, source.sourceId);
|
|
190
180
|
else
|
|
191
181
|
detachSourceFromRepo(ws.store, repo.repoId, source.sourceId);
|
|
192
|
-
if (cli.flags.mode !== "human")
|
|
193
|
-
cli
|
|
194
|
-
return EXIT_OK;
|
|
195
|
-
}
|
|
182
|
+
if (cli.flags.mode !== "human")
|
|
183
|
+
return emitMachine(cli, { source: name, repo: repo.slug, attached: verb === "attach" });
|
|
196
184
|
cli.io.out(` ${cli.render.g.ok} ${verb === "attach" ? "attached" : "detached"} ${cli.style.accent(name)} ${verb === "attach" ? "→" : "⇸"} ${cli.style.accent(repo.slug)}`);
|
|
197
185
|
return EXIT_OK;
|
|
198
186
|
}
|
|
@@ -206,10 +194,8 @@ function runRm(cli, args) {
|
|
|
206
194
|
if (!source)
|
|
207
195
|
throw new EngineErrorLike("target_missing", `no source named '${name}'`);
|
|
208
196
|
deleteSource(ws.store, source.sourceId);
|
|
209
|
-
if (cli.flags.mode !== "human")
|
|
210
|
-
cli
|
|
211
|
-
return EXIT_OK;
|
|
212
|
-
}
|
|
197
|
+
if (cli.flags.mode !== "human")
|
|
198
|
+
return emitMachine(cli, { source: name, deleted: true });
|
|
213
199
|
cli.io.out(` ${cli.render.g.ok} deleted source ${cli.style.accent(name)}`);
|
|
214
200
|
return EXIT_OK;
|
|
215
201
|
}
|