omgbase 0.1.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/LICENSE +21 -0
- package/dist/src/cmd/_embed.d.ts +55 -0
- package/dist/src/cmd/_embed.js +102 -0
- package/dist/src/cmd/_mutate.d.ts +47 -0
- package/dist/src/cmd/_mutate.js +211 -0
- package/dist/src/cmd/_remote.d.ts +8 -0
- package/dist/src/cmd/_remote.js +61 -0
- package/dist/src/cmd/_source.d.ts +13 -0
- package/dist/src/cmd/_source.js +47 -0
- package/dist/src/cmd/admin.d.ts +6 -0
- package/dist/src/cmd/admin.js +289 -0
- package/dist/src/cmd/bootstrap.d.ts +3 -0
- package/dist/src/cmd/bootstrap.js +252 -0
- package/dist/src/cmd/cat.d.ts +2 -0
- package/dist/src/cmd/cat.js +80 -0
- package/dist/src/cmd/diff.d.ts +2 -0
- package/dist/src/cmd/diff.js +76 -0
- package/dist/src/cmd/docs.d.ts +6 -0
- package/dist/src/cmd/docs.js +154 -0
- package/dist/src/cmd/edit.d.ts +2 -0
- package/dist/src/cmd/edit.js +74 -0
- package/dist/src/cmd/find.d.ts +2 -0
- package/dist/src/cmd/find.js +98 -0
- package/dist/src/cmd/help.d.ts +2 -0
- package/dist/src/cmd/help.js +47 -0
- package/dist/src/cmd/hist.d.ts +2 -0
- package/dist/src/cmd/hist.js +58 -0
- package/dist/src/cmd/links.d.ts +2 -0
- package/dist/src/cmd/links.js +76 -0
- package/dist/src/cmd/log.d.ts +2 -0
- package/dist/src/cmd/log.js +109 -0
- package/dist/src/cmd/ls.d.ts +2 -0
- package/dist/src/cmd/ls.js +88 -0
- package/dist/src/cmd/mcp.d.ts +2 -0
- package/dist/src/cmd/mcp.js +192 -0
- package/dist/src/cmd/mutate.d.ts +10 -0
- package/dist/src/cmd/mutate.js +352 -0
- package/dist/src/cmd/node.d.ts +2 -0
- package/dist/src/cmd/node.js +66 -0
- package/dist/src/cmd/oqx.d.ts +2 -0
- package/dist/src/cmd/oqx.js +193 -0
- package/dist/src/cmd/outline.d.ts +2 -0
- package/dist/src/cmd/outline.js +87 -0
- package/dist/src/cmd/retarget.d.ts +2 -0
- package/dist/src/cmd/retarget.js +91 -0
- package/dist/src/cmd/run.d.ts +2 -0
- package/dist/src/cmd/run.js +135 -0
- package/dist/src/cmd/shell.d.ts +2 -0
- package/dist/src/cmd/shell.js +159 -0
- package/dist/src/cmd/show.d.ts +2 -0
- package/dist/src/cmd/show.js +107 -0
- package/dist/src/cmd/source.d.ts +2 -0
- package/dist/src/cmd/source.js +231 -0
- package/dist/src/cmd/status.d.ts +2 -0
- package/dist/src/cmd/status.js +95 -0
- package/dist/src/cmd/sync.d.ts +2 -0
- package/dist/src/cmd/sync.js +176 -0
- package/dist/src/cmd/update.d.ts +2 -0
- package/dist/src/cmd/update.js +100 -0
- package/dist/src/commands.d.ts +18 -0
- package/dist/src/commands.js +112 -0
- package/dist/src/context.d.ts +73 -0
- package/dist/src/context.js +80 -0
- package/dist/src/dispatch.d.ts +8 -0
- package/dist/src/dispatch.js +148 -0
- package/dist/src/main.d.ts +3 -0
- package/dist/src/main.js +67 -0
- package/dist/src/output.d.ts +60 -0
- package/dist/src/output.js +131 -0
- package/dist/src/render.d.ts +44 -0
- package/dist/src/render.js +116 -0
- package/dist/src/shell/refs.d.ts +37 -0
- package/dist/src/shell/refs.js +111 -0
- package/dist/src/shell/session.d.ts +71 -0
- package/dist/src/shell/session.js +317 -0
- package/dist/src/shell/tokenize.d.ts +3 -0
- package/dist/src/shell/tokenize.js +79 -0
- package/dist/src/style.d.ts +58 -0
- package/dist/src/style.js +159 -0
- package/package.json +63 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { findDoc, diffUnified } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
+
import { remoteCall } from "./_remote.js";
|
|
5
|
+
// `omg diff <doc>` (11 §5.5) — unified diff. With no revisions: current vs
|
|
6
|
+
// previous ("what did the last commit do here").
|
|
7
|
+
async function runDiff(cli, args) {
|
|
8
|
+
const { values, positionals } = parseArgs({
|
|
9
|
+
args,
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
options: { from: { type: "string" }, to: { type: "string" }, help: { type: "boolean" } },
|
|
12
|
+
});
|
|
13
|
+
if (values.help) {
|
|
14
|
+
return renderHelp(cli, {
|
|
15
|
+
name: "diff",
|
|
16
|
+
summary: "Unified diff of a document between two revisions (default: previous → current)",
|
|
17
|
+
usage: "diff <doc> [--from <rev>] [--to <rev>]",
|
|
18
|
+
options: [
|
|
19
|
+
["--from <rev>", "older revision id (default: the one before --to)"],
|
|
20
|
+
["--to <rev>", "newer revision id (default: current)"],
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const ref = positionals[0];
|
|
25
|
+
if (!ref)
|
|
26
|
+
throw new CliUsageError("diff requires a <doc>");
|
|
27
|
+
let docId, path, fromRev, toRev, text;
|
|
28
|
+
if (cli.flags.server) {
|
|
29
|
+
const r = await remoteCall(cli, "diff_unified", {
|
|
30
|
+
doc: ref,
|
|
31
|
+
...(values.from ? { from_rev: values.from } : {}),
|
|
32
|
+
...(values.to ? { to_rev: values.to } : {}),
|
|
33
|
+
});
|
|
34
|
+
({ doc: docId, path, from: fromRev, to: toRev, diff: text } = r);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
const ws = cli.workspace();
|
|
38
|
+
const repo = cli.repo(ws);
|
|
39
|
+
const info = ref.startsWith("d_") ? findDoc(ws.store, { docId: ref }) : findDoc(ws.store, { repoId: repo.repoId, path: ref });
|
|
40
|
+
if (!info)
|
|
41
|
+
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
42
|
+
// Resolve from/to revisions: default is (previous, current) by seq desc.
|
|
43
|
+
const revs = ws.store.db
|
|
44
|
+
.prepare("SELECT rev_id, seq FROM revisions WHERE doc_id = ? ORDER BY seq DESC LIMIT 2")
|
|
45
|
+
.all(info.docId);
|
|
46
|
+
const to = values.to ?? revs[0]?.rev_id;
|
|
47
|
+
const from = values.from ?? revs[1]?.rev_id ?? revs[0]?.rev_id;
|
|
48
|
+
if (!to || !from)
|
|
49
|
+
throw new EngineErrorLike("doc_missing", `no revisions to diff for ${ref}`);
|
|
50
|
+
docId = info.docId;
|
|
51
|
+
path = info.path;
|
|
52
|
+
fromRev = from;
|
|
53
|
+
toRev = to;
|
|
54
|
+
text = diffUnified(ws.store, info.docId, from, to);
|
|
55
|
+
}
|
|
56
|
+
if (cli.flags.mode === "json") {
|
|
57
|
+
cli.io.out(JSON.stringify({ doc: docId, path, from: fromRev, to: toRev, diff: text }));
|
|
58
|
+
return EXIT_OK;
|
|
59
|
+
}
|
|
60
|
+
const { style, io } = cli;
|
|
61
|
+
if (!text) {
|
|
62
|
+
io.err(style.dim(" no changes"));
|
|
63
|
+
return EXIT_OK;
|
|
64
|
+
}
|
|
65
|
+
for (const line of text.split("\n")) {
|
|
66
|
+
if (line.startsWith("+"))
|
|
67
|
+
io.out(style.ok(line));
|
|
68
|
+
else if (line.startsWith("-"))
|
|
69
|
+
io.out(style.err(line));
|
|
70
|
+
else
|
|
71
|
+
io.out(line);
|
|
72
|
+
}
|
|
73
|
+
return EXIT_OK;
|
|
74
|
+
}
|
|
75
|
+
export const cmdDiff = { name: "diff", summary: "Unified diff of a document", run: (cli, a) => runDiff(cli, a) };
|
|
76
|
+
//# sourceMappingURL=diff.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Cli } from "../context.js";
|
|
2
|
+
import type { Command } from "../commands.js";
|
|
3
|
+
export declare function runRmDoc(cli: Cli, doc: string, actor?: string): Promise<number>;
|
|
4
|
+
export declare const cmdNew: Command;
|
|
5
|
+
export declare const cmdMv: Command;
|
|
6
|
+
export declare const cmdMeta: Command;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { parse as parseYamlScalar } from "yaml";
|
|
3
|
+
import { docsCreate, docsMove, docsDelete, docsSetMeta } from "@omgbase/core";
|
|
4
|
+
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
5
|
+
import { readContent, extractContentOpts } from "./_mutate.js";
|
|
6
|
+
import { remoteCall } from "./_remote.js";
|
|
7
|
+
// Document-level commands (11 §5.6): new / mv / rm --doc / meta. Thin wrappers
|
|
8
|
+
// over the core doc ops, which own the file-write + commit + flock protocol.
|
|
9
|
+
function ctxOf(cli, ws, repoId, rootPath, actor) {
|
|
10
|
+
return { repoId, ...(rootPath ? { rootPath } : {}), omgbaseDir: ws.omgbaseDir, ...(actor ? { actor } : {}) };
|
|
11
|
+
}
|
|
12
|
+
function report(cli, verb, res) {
|
|
13
|
+
if (cli.flags.mode !== "human") {
|
|
14
|
+
cli.io.out(JSON.stringify(res));
|
|
15
|
+
return EXIT_OK;
|
|
16
|
+
}
|
|
17
|
+
cli.io.err(cli.style.dim(` ${cli.style.ok(cli.render.g.ok)} ${verb} ${cli.style.accent(res.path)}`));
|
|
18
|
+
cli.io.out(res.docId);
|
|
19
|
+
return EXIT_OK;
|
|
20
|
+
}
|
|
21
|
+
// ---- new --------------------------------------------------------------------
|
|
22
|
+
async function runNew(cli, args) {
|
|
23
|
+
const content = extractContentOpts(args);
|
|
24
|
+
const { values, positionals } = parseArgs({
|
|
25
|
+
args: content.rest,
|
|
26
|
+
allowPositionals: true,
|
|
27
|
+
options: { actor: { type: "string" }, help: { type: "boolean" } },
|
|
28
|
+
});
|
|
29
|
+
if (values.help) {
|
|
30
|
+
return renderHelp(cli, {
|
|
31
|
+
name: "new",
|
|
32
|
+
summary: "Create a document at <path> from complete file bytes (frontmatter included)",
|
|
33
|
+
usage: "new <path> (-m <markdown> | -f <file> | -) [--actor <s>] [--dry-run]",
|
|
34
|
+
options: [
|
|
35
|
+
["-m <markdown>", "content inline"],
|
|
36
|
+
["-f <file>", "content from a file"],
|
|
37
|
+
["-", "content from stdin"],
|
|
38
|
+
["--actor <s>", "commit actor (default human:$USER)"],
|
|
39
|
+
],
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
const path = positionals[0];
|
|
43
|
+
if (!path)
|
|
44
|
+
throw new CliUsageError("new requires a <path>");
|
|
45
|
+
const bytes = readContent(content);
|
|
46
|
+
if (cli.flags.server) {
|
|
47
|
+
return report(cli, "created", await remoteCall(cli, "docs_create", { path, markdown: bytes }));
|
|
48
|
+
}
|
|
49
|
+
const ws = cli.workspace();
|
|
50
|
+
const repo = cli.repo(ws);
|
|
51
|
+
const res = docsCreate(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), path, bytes);
|
|
52
|
+
return report(cli, "created", res);
|
|
53
|
+
}
|
|
54
|
+
// ---- mv ---------------------------------------------------------------------
|
|
55
|
+
async function runMv(cli, args) {
|
|
56
|
+
const { values, positionals } = parseArgs({
|
|
57
|
+
args,
|
|
58
|
+
allowPositionals: true,
|
|
59
|
+
options: { actor: { type: "string" }, help: { type: "boolean" } },
|
|
60
|
+
});
|
|
61
|
+
if (values.help) {
|
|
62
|
+
return renderHelp(cli, {
|
|
63
|
+
name: "mv",
|
|
64
|
+
summary: "Rename/move a document to a new path; its identity and history are preserved",
|
|
65
|
+
usage: "mv <doc> <new-path> [--actor <s>] [--dry-run]",
|
|
66
|
+
options: [["--actor <s>", "commit actor (default human:$USER)"]],
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const doc = positionals[0];
|
|
70
|
+
const toPath = positionals[1];
|
|
71
|
+
if (!doc || !toPath)
|
|
72
|
+
throw new CliUsageError("mv requires <doc> and <new-path>");
|
|
73
|
+
if (cli.flags.server) {
|
|
74
|
+
return report(cli, "moved to", await remoteCall(cli, "docs_move", { doc, to_path: toPath }));
|
|
75
|
+
}
|
|
76
|
+
const ws = cli.workspace();
|
|
77
|
+
const repo = cli.repo(ws);
|
|
78
|
+
const res = docsMove(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, toPath);
|
|
79
|
+
return report(cli, "moved to", res);
|
|
80
|
+
}
|
|
81
|
+
// ---- rm --doc (block rm lives in mutate.ts; this handles the --doc branch) --
|
|
82
|
+
export async function runRmDoc(cli, doc, actor) {
|
|
83
|
+
if (cli.flags.server) {
|
|
84
|
+
return report(cli, "deleted", await remoteCall(cli, "docs_delete", { doc }));
|
|
85
|
+
}
|
|
86
|
+
const ws = cli.workspace();
|
|
87
|
+
const repo = cli.repo(ws);
|
|
88
|
+
const res = docsDelete(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, actor), doc);
|
|
89
|
+
return report(cli, "deleted", res);
|
|
90
|
+
}
|
|
91
|
+
// ---- meta -------------------------------------------------------------------
|
|
92
|
+
async function runMeta(cli, args) {
|
|
93
|
+
const { values, positionals } = parseArgs({
|
|
94
|
+
args,
|
|
95
|
+
allowPositionals: true,
|
|
96
|
+
options: {
|
|
97
|
+
set: { type: "string", multiple: true },
|
|
98
|
+
"set-json": { type: "string", multiple: true },
|
|
99
|
+
unset: { type: "string", multiple: true },
|
|
100
|
+
actor: { type: "string" },
|
|
101
|
+
help: { type: "boolean" },
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
if (values.help) {
|
|
105
|
+
return renderHelp(cli, {
|
|
106
|
+
name: "meta",
|
|
107
|
+
summary: "Surgical frontmatter patch: set/unset keys without touching the body",
|
|
108
|
+
usage: "meta <doc> [--set k=v]… [--set-json k=<json>]… [--unset k]… [--actor <s>] [--dry-run]",
|
|
109
|
+
options: [
|
|
110
|
+
["--set k=v", "set a string value (repeatable)"],
|
|
111
|
+
["--set-json k=<json>", "set a typed value from JSON (repeatable)"],
|
|
112
|
+
["--unset k", "remove a key (repeatable)"],
|
|
113
|
+
["--actor <s>", "commit actor (default human:$USER)"],
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const doc = positionals[0];
|
|
118
|
+
if (!doc)
|
|
119
|
+
throw new CliUsageError("meta requires a <doc>");
|
|
120
|
+
const set = {};
|
|
121
|
+
for (const kv of values.set ?? []) {
|
|
122
|
+
const eq = kv.indexOf("=");
|
|
123
|
+
if (eq < 0)
|
|
124
|
+
throw new CliUsageError(`--set expects k=v, got '${kv}'`);
|
|
125
|
+
const key = kv.slice(0, eq);
|
|
126
|
+
const val = kv.slice(eq + 1);
|
|
127
|
+
// YAML scalar parse: numbers/bools/null become typed, strings stay strings.
|
|
128
|
+
set[key] = parseYamlScalar(val);
|
|
129
|
+
}
|
|
130
|
+
for (const kv of values["set-json"] ?? []) {
|
|
131
|
+
const eq = kv.indexOf("=");
|
|
132
|
+
if (eq < 0)
|
|
133
|
+
throw new CliUsageError(`--set-json expects k=json, got '${kv}'`);
|
|
134
|
+
set[kv.slice(0, eq)] = JSON.parse(kv.slice(eq + 1));
|
|
135
|
+
}
|
|
136
|
+
const unset = values.unset ?? [];
|
|
137
|
+
if (Object.keys(set).length === 0 && unset.length === 0)
|
|
138
|
+
throw new CliUsageError("meta requires --set or --unset");
|
|
139
|
+
if (cli.flags.server) {
|
|
140
|
+
return report(cli, "patched", await remoteCall(cli, "docs_set_meta", {
|
|
141
|
+
doc,
|
|
142
|
+
...(Object.keys(set).length ? { set } : {}),
|
|
143
|
+
...(unset.length ? { unset } : {}),
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
const ws = cli.workspace();
|
|
147
|
+
const repo = cli.repo(ws);
|
|
148
|
+
const res = docsSetMeta(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, { set, unset });
|
|
149
|
+
return report(cli, "patched", res);
|
|
150
|
+
}
|
|
151
|
+
export const cmdNew = { name: "new", summary: "Create a document", run: (c, a) => runNew(c, a) };
|
|
152
|
+
export const cmdMv = { name: "mv", summary: "Rename a document", run: (c, a) => runMv(c, a) };
|
|
153
|
+
export const cmdMeta = { name: "meta", summary: "Patch a document's frontmatter", run: (c, a) => runMeta(c, a) };
|
|
154
|
+
//# sourceMappingURL=docs.js.map
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { writeFileSync, readFileSync, mkdtempSync, rmSync } from "node:fs";
|
|
4
|
+
import { tmpdir } from "node:os";
|
|
5
|
+
import { join } from "node:path";
|
|
6
|
+
import { resolveRef, nodesGet } from "@omgbase/core";
|
|
7
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
8
|
+
import { runOps } from "./_mutate.js";
|
|
9
|
+
// `omg edit <block>` (11 §5.6, §5.7): read the block's raw markdown → open it in
|
|
10
|
+
// $EDITOR → update with the PRE-READ hash pinned as CAS. A mid-edit change by
|
|
11
|
+
// someone else lands as a clean stale_expectation (with current truth), never a
|
|
12
|
+
// lost write. The human structural-edit loop.
|
|
13
|
+
function rawHashOf(ws, blockId) {
|
|
14
|
+
const row = ws.store.db
|
|
15
|
+
.prepare("SELECT lower(hex(raw_hash)) h FROM blocks WHERE block_id = ? AND deleted_commit IS NULL")
|
|
16
|
+
.get(blockId);
|
|
17
|
+
return row?.h ?? null;
|
|
18
|
+
}
|
|
19
|
+
function runEdit(cli, args) {
|
|
20
|
+
const { values, positionals } = parseArgs({
|
|
21
|
+
args,
|
|
22
|
+
allowPositionals: true,
|
|
23
|
+
options: { actor: { type: "string" }, help: { type: "boolean" } },
|
|
24
|
+
});
|
|
25
|
+
if (values.help) {
|
|
26
|
+
return renderHelp(cli, {
|
|
27
|
+
name: "edit",
|
|
28
|
+
summary: "Open a block's markdown in $EDITOR and write it back with compare-and-swap pinned to what you saw",
|
|
29
|
+
usage: "edit <block> [--actor <s>] [--dry-run]",
|
|
30
|
+
options: [["--actor <s>", "commit actor (default human:$USER)"]],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const ref = positionals[0];
|
|
34
|
+
if (!ref)
|
|
35
|
+
throw new CliUsageError("edit requires a <block>");
|
|
36
|
+
const ws = cli.workspace();
|
|
37
|
+
const repo = cli.repo(ws);
|
|
38
|
+
const resolved = resolveRef(ws.store, repo.repoId, ref);
|
|
39
|
+
if (!resolved || resolved.kind !== "block")
|
|
40
|
+
throw new EngineErrorLike("block_missing", `not a block: ${ref}`);
|
|
41
|
+
const blockId = resolved.blockId;
|
|
42
|
+
const node = nodesGet(ws.store, resolved.docId, blockId, { resolution: "raw" });
|
|
43
|
+
const before = node?.raw ?? "";
|
|
44
|
+
const preHash = rawHashOf(ws, blockId); // pin BEFORE the editor opens
|
|
45
|
+
const editor = process.env.OMG_EDITOR ?? process.env.VISUAL ?? process.env.EDITOR;
|
|
46
|
+
if (!editor)
|
|
47
|
+
throw new EngineErrorLike("target_missing", "no $EDITOR set (or $VISUAL/$OMG_EDITOR)");
|
|
48
|
+
if (!cli.io.stdoutTTY && !process.env.OMG_EDITOR) {
|
|
49
|
+
// Interactive editors need a terminal; OMG_EDITOR (e.g. a script) is the
|
|
50
|
+
// scripted/test escape hatch.
|
|
51
|
+
throw new EngineErrorLike("target_missing", "edit needs a TTY; set OMG_EDITOR to a non-interactive editor for scripts");
|
|
52
|
+
}
|
|
53
|
+
const dir = mkdtempSync(join(tmpdir(), "omg-edit-"));
|
|
54
|
+
const file = join(dir, "block.md");
|
|
55
|
+
try {
|
|
56
|
+
writeFileSync(file, before);
|
|
57
|
+
const [cmd, ...preArgs] = editor.split(/\s+/);
|
|
58
|
+
const res = spawnSync(cmd, [...preArgs, file], { stdio: "inherit" });
|
|
59
|
+
if (res.status !== 0)
|
|
60
|
+
throw new EngineErrorLike("target_missing", `editor exited ${res.status ?? "abnormally"}`);
|
|
61
|
+
const after = readFileSync(file, "utf8");
|
|
62
|
+
if (after === before) {
|
|
63
|
+
cli.io.err(cli.style.dim(" no changes"));
|
|
64
|
+
return EXIT_OK;
|
|
65
|
+
}
|
|
66
|
+
const op = { op: "update", block: blockId, markdown: after, ...(preHash ? { expect: { content_hash: preHash } } : {}) };
|
|
67
|
+
return runOps(cli, ws, repo, [op], values.actor ? { actor: values.actor } : {});
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
rmSync(dir, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
export const cmdEdit = { name: "edit", summary: "Edit a block in $EDITOR (CAS pinned)", run: (c, a) => runEdit(c, a) };
|
|
74
|
+
//# sourceMappingURL=edit.js.map
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { resolve as resolveThing } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
+
import { loadEmbedding } from "./_embed.js";
|
|
5
|
+
import { remoteCall } from "./_remote.js";
|
|
6
|
+
// `omg find <text>` (11 §5.2) — resolve: ranked {id, locator, preview,
|
|
7
|
+
// evidence}. `-1` prints the top hit's id alone (pipe fuel: omg cat $(omg find … -1)).
|
|
8
|
+
async function runFind(cli, args) {
|
|
9
|
+
const { values, positionals } = parseArgs({
|
|
10
|
+
args,
|
|
11
|
+
allowPositionals: true,
|
|
12
|
+
options: {
|
|
13
|
+
n: { type: "string", short: "n" },
|
|
14
|
+
one: { type: "boolean", short: "1" },
|
|
15
|
+
verbose: { type: "boolean", short: "v" },
|
|
16
|
+
"no-semantic": { type: "boolean" },
|
|
17
|
+
help: { type: "boolean" },
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
if (values.help) {
|
|
21
|
+
return renderHelp(cli, {
|
|
22
|
+
name: "find",
|
|
23
|
+
summary: "Resolve a name/title/concept to ranked hits — full-text, fused with the embedding index when a provider is configured",
|
|
24
|
+
usage: "find <text> [-n <N>] [-1] [-v] [--no-semantic]",
|
|
25
|
+
options: [
|
|
26
|
+
["-n <N>", "max hits (default 10)"],
|
|
27
|
+
["-1", `print only the top hit's id (\`${cli.prog} cat $(${cli.prog} find "risks" -1)\`)`],
|
|
28
|
+
["-v, --verbose", "print per-hit evidence"],
|
|
29
|
+
["--no-semantic", "full-text only; skip the embedding provider even if one is configured"],
|
|
30
|
+
],
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
const text = positionals.join(" ").trim();
|
|
34
|
+
if (!text)
|
|
35
|
+
throw new CliUsageError("find requires <text>");
|
|
36
|
+
const limit = values.one ? 1 : values.n ? Number(values.n) : 10;
|
|
37
|
+
let hits;
|
|
38
|
+
if (cli.flags.server) {
|
|
39
|
+
// Remote: the `resolve` tool does the hybrid ranking server-side (using the
|
|
40
|
+
// server's embedder if configured). Same ranked-hit shape → render unchanged.
|
|
41
|
+
hits = await remoteCall(cli, "resolve", { query: text, limit });
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
const ws = cli.workspace();
|
|
45
|
+
const repo = cli.repo(ws);
|
|
46
|
+
// Hybrid by default: if a provider is configured, fuse the query vector into
|
|
47
|
+
// the ranking (--no-semantic forces FTS-only). resolve() blends when a vector
|
|
48
|
+
// is supplied.
|
|
49
|
+
const input = { repoId: repo.repoId, query: text, limit };
|
|
50
|
+
if (!values["no-semantic"]) {
|
|
51
|
+
const loaded = await loadEmbedding(ws, repo.repoId);
|
|
52
|
+
if (loaded) {
|
|
53
|
+
try {
|
|
54
|
+
const vec = await loaded.worker.embedQuery(text);
|
|
55
|
+
input.vector = { model: loaded.provider.model, vec };
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
await loaded.close();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
hits = resolveThing(ws.store, input);
|
|
63
|
+
}
|
|
64
|
+
cli.capture?.(hits); // shell: the ranked hits become the addressable frame
|
|
65
|
+
// -1: bare top id.
|
|
66
|
+
if (values.one) {
|
|
67
|
+
if (hits[0])
|
|
68
|
+
cli.io.out(hits[0].id);
|
|
69
|
+
return EXIT_OK;
|
|
70
|
+
}
|
|
71
|
+
if (cli.flags.mode === "ids") {
|
|
72
|
+
for (const h of hits)
|
|
73
|
+
cli.io.out(h.id);
|
|
74
|
+
return EXIT_OK;
|
|
75
|
+
}
|
|
76
|
+
if (cli.flags.mode === "json") {
|
|
77
|
+
cli.io.out(JSON.stringify(hits));
|
|
78
|
+
return EXIT_OK;
|
|
79
|
+
}
|
|
80
|
+
if (cli.flags.mode === "jsonl") {
|
|
81
|
+
for (const h of hits)
|
|
82
|
+
cli.io.out(JSON.stringify(h));
|
|
83
|
+
return EXIT_OK;
|
|
84
|
+
}
|
|
85
|
+
const { style, io } = cli;
|
|
86
|
+
if (hits.length === 0) {
|
|
87
|
+
io.err(style.dim(" no matches"));
|
|
88
|
+
return EXIT_OK;
|
|
89
|
+
}
|
|
90
|
+
for (const h of hits) {
|
|
91
|
+
io.out(`${style.id(h.id)} ${style.accent(h.locator)} ${h.preview}`);
|
|
92
|
+
if (values.verbose)
|
|
93
|
+
io.err(style.dim(` ${JSON.stringify(h.evidence)}`));
|
|
94
|
+
}
|
|
95
|
+
return EXIT_OK;
|
|
96
|
+
}
|
|
97
|
+
export const cmdFind = { name: "find", summary: "Ranked hybrid search for the id of a thing", run: (cli, a) => runFind(cli, a) };
|
|
98
|
+
//# sourceMappingURL=find.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EXIT_OK } from "../output.js";
|
|
2
|
+
// `omg help` / bare invocation / --help. Renders the branded header + the
|
|
3
|
+
// command catalog grouped by area. The catalog is data (11 §8); help reads it
|
|
4
|
+
// from the registry lazily to avoid an import cycle.
|
|
5
|
+
const GROUPS = [
|
|
6
|
+
{ title: "bootstrap", names: ["init", "source", "repos"] },
|
|
7
|
+
{ title: "orient & read", names: ["status", "ls", "outline", "cat", "show", "find"] },
|
|
8
|
+
{ title: "query", names: ["query", "run"] },
|
|
9
|
+
{ title: "history & links", names: ["log", "hist", "diff", "links"] },
|
|
10
|
+
{ title: "mutate", names: ["apply", "insert", "update", "edit", "move", "rm", "done", "append", "retarget", "split", "merge", "node"] },
|
|
11
|
+
{ title: "documents", names: ["new", "mv", "meta"] },
|
|
12
|
+
{ title: "session", names: ["shell"] },
|
|
13
|
+
{ title: "sync & serve", names: ["sync", "mcp"] },
|
|
14
|
+
{ title: "admin", names: ["rebuild-index", "gc", "doctor", "config", "embed"] },
|
|
15
|
+
];
|
|
16
|
+
async function runHelp(cli) {
|
|
17
|
+
const { render, style, io, prog } = cli;
|
|
18
|
+
const { COMMANDS } = await import("../commands.js");
|
|
19
|
+
const byName = new Map(COMMANDS.map((c) => [c.name, c]));
|
|
20
|
+
io.out(render.wordmark("Open Markdown Graph Base"));
|
|
21
|
+
io.out(render.rule(40));
|
|
22
|
+
io.out("");
|
|
23
|
+
io.out(` ${style.dim("usage:")} ${prog} ${style.dim("[--json|--jsonl|--ids] [-C dir] [--repo slug]")} <command> ${style.dim("[args]")}`);
|
|
24
|
+
io.out("");
|
|
25
|
+
for (const group of GROUPS) {
|
|
26
|
+
io.out(` ${style.bold(group.title)}`);
|
|
27
|
+
const rows = group.names
|
|
28
|
+
.map((n) => byName.get(n))
|
|
29
|
+
.filter((c) => Boolean(c))
|
|
30
|
+
.map((c) => {
|
|
31
|
+
const names = [c.name, ...(c.aliases ?? [])].join(", ");
|
|
32
|
+
return ` ${style.accent(names.padEnd(22))}${style.dim(c.summary)}`;
|
|
33
|
+
});
|
|
34
|
+
for (const r of rows)
|
|
35
|
+
io.out(r);
|
|
36
|
+
io.out("");
|
|
37
|
+
}
|
|
38
|
+
io.out(` ${style.dim("run")} ${prog} <command> --help ${style.dim("for details")}`);
|
|
39
|
+
return EXIT_OK;
|
|
40
|
+
}
|
|
41
|
+
export const cmdHelp = {
|
|
42
|
+
name: "help",
|
|
43
|
+
summary: "Show this help",
|
|
44
|
+
listed: false,
|
|
45
|
+
run: (cli) => runHelp(cli),
|
|
46
|
+
};
|
|
47
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { resolveRef, historyNode } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
4
|
+
import { remoteCall } from "./_remote.js";
|
|
5
|
+
// `omg hist <node>` (11 §5.5) — history_node: a block's biography.
|
|
6
|
+
async function runHist(cli, args) {
|
|
7
|
+
const { values, positionals } = parseArgs({
|
|
8
|
+
args,
|
|
9
|
+
allowPositionals: true,
|
|
10
|
+
options: { n: { type: "string", short: "n" }, help: { type: "boolean" } },
|
|
11
|
+
});
|
|
12
|
+
if (values.help) {
|
|
13
|
+
return renderHelp(cli, {
|
|
14
|
+
name: "hist",
|
|
15
|
+
summary: "A block's change biography: every commit that touched it",
|
|
16
|
+
usage: "hist <node> [-n <N>]",
|
|
17
|
+
options: [["-n <N>", "max changes"]],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
const ref = positionals[0];
|
|
21
|
+
if (!ref)
|
|
22
|
+
throw new CliUsageError("hist requires a <node>");
|
|
23
|
+
let changes;
|
|
24
|
+
if (cli.flags.server) {
|
|
25
|
+
// Remote: history_node keys on a block id (globally unique). Pass the ref
|
|
26
|
+
// through — a locator would need a local resolve, so remote hist wants an id.
|
|
27
|
+
changes = await remoteCall(cli, "history_node", { id: ref, ...(values.n ? { limit: Number(values.n) } : {}) });
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const ws = cli.workspace();
|
|
31
|
+
const repo = cli.repo(ws);
|
|
32
|
+
const resolved = resolveRef(ws.store, repo.repoId, ref);
|
|
33
|
+
if (!resolved || resolved.kind !== "block")
|
|
34
|
+
throw new EngineErrorLike("block_missing", `hist needs a block id; got ${ref}`);
|
|
35
|
+
changes = historyNode(ws.store, resolved.blockId, values.n ? { limit: Number(values.n) } : {});
|
|
36
|
+
}
|
|
37
|
+
if (cli.flags.mode === "json") {
|
|
38
|
+
cli.io.out(JSON.stringify(changes));
|
|
39
|
+
return EXIT_OK;
|
|
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
|
+
}
|
|
46
|
+
const { style, io } = cli;
|
|
47
|
+
if (changes.length === 0) {
|
|
48
|
+
io.err(style.dim(" no history"));
|
|
49
|
+
return EXIT_OK;
|
|
50
|
+
}
|
|
51
|
+
for (const c of changes) {
|
|
52
|
+
const conf = c.confidence != null ? style.dim(` (${c.confidence.toFixed(2)})`) : "";
|
|
53
|
+
io.out(`${style.dim(`#${c.seq}`)} ${style.accent(c.kind)}${conf} ${style.dim(c.origin)} ${style.dim(c.ts)}`);
|
|
54
|
+
}
|
|
55
|
+
return EXIT_OK;
|
|
56
|
+
}
|
|
57
|
+
export const cmdHist = { name: "hist", summary: "A block's change biography", run: (cli, a) => runHist(cli, a) };
|
|
58
|
+
//# sourceMappingURL=hist.js.map
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { resolveRef, docLinks } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
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.
|
|
7
|
+
function runLinks(cli, args) {
|
|
8
|
+
const { values, positionals } = parseArgs({
|
|
9
|
+
args,
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
options: {
|
|
12
|
+
in: { type: "boolean" },
|
|
13
|
+
out: { type: "boolean" },
|
|
14
|
+
pred: { type: "string" },
|
|
15
|
+
blocks: { type: "boolean" },
|
|
16
|
+
help: { type: "boolean" },
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
if (values.help) {
|
|
20
|
+
return renderHelp(cli, {
|
|
21
|
+
name: "links",
|
|
22
|
+
summary: "Open edges touching a node, grouped by predicate (both directions by default)",
|
|
23
|
+
usage: "links <node> [--in|--out] [--pred <p,p>] [--blocks]",
|
|
24
|
+
options: [
|
|
25
|
+
["--in", "incoming edges only (backlinks)"],
|
|
26
|
+
["--out", "outgoing edges only"],
|
|
27
|
+
["--pred <p,p>", "keep only these predicates"],
|
|
28
|
+
["--blocks", "block-grain edges (default: doc-grain)"],
|
|
29
|
+
],
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const ref = positionals[0];
|
|
33
|
+
if (!ref)
|
|
34
|
+
throw new CliUsageError("links requires a <node>");
|
|
35
|
+
const ws = cli.workspace();
|
|
36
|
+
const repo = cli.repo(ws);
|
|
37
|
+
const resolved = resolveRef(ws.store, repo.repoId, ref);
|
|
38
|
+
if (!resolved)
|
|
39
|
+
throw new EngineErrorLike("doc_missing", `no node ${ref}`);
|
|
40
|
+
const direction = values.in && !values.out ? "in" : values.out && !values.in ? "out" : "both";
|
|
41
|
+
const opts = { direction };
|
|
42
|
+
if (values.pred)
|
|
43
|
+
opts.predicates = values.pred.split(",").map((s) => s.trim());
|
|
44
|
+
if (values.blocks)
|
|
45
|
+
opts.blocks = true;
|
|
46
|
+
const result = docLinks(ws.store, resolved.docId, opts);
|
|
47
|
+
cli.capture?.(result); // shell: edges (out+in) become the addressable frame
|
|
48
|
+
if (cli.flags.mode === "json") {
|
|
49
|
+
cli.io.out(JSON.stringify(result));
|
|
50
|
+
return EXIT_OK;
|
|
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
|
+
}
|
|
57
|
+
const { render, style, io } = cli;
|
|
58
|
+
const g = render.g;
|
|
59
|
+
if (result.out.length === 0 && result.in.length === 0) {
|
|
60
|
+
io.err(style.dim(" no links"));
|
|
61
|
+
return EXIT_OK;
|
|
62
|
+
}
|
|
63
|
+
if (result.out.length > 0) {
|
|
64
|
+
io.out(style.dim(" out"));
|
|
65
|
+
for (const e of result.out)
|
|
66
|
+
io.out(` ${style.accent(e.predicate)} ${style.dim(g.arrow)} ${style.id(e.node)} ${style.dim(`×${e.count}`)}`);
|
|
67
|
+
}
|
|
68
|
+
if (result.in.length > 0) {
|
|
69
|
+
io.out(style.dim(" in (backlinks)"));
|
|
70
|
+
for (const e of result.in)
|
|
71
|
+
io.out(` ${style.id(e.node)} ${style.dim(g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
|
|
72
|
+
}
|
|
73
|
+
return EXIT_OK;
|
|
74
|
+
}
|
|
75
|
+
export const cmdLinks = { name: "links", summary: "Open edges touching a node", run: (cli, a) => runLinks(cli, a) };
|
|
76
|
+
//# sourceMappingURL=links.js.map
|