omgbase 0.3.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/_source.d.ts +3 -3
- package/dist/src/cmd/_source.js +14 -11
- 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 +22 -36
- package/dist/src/cmd/mutate.js +38 -11
- 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 +19 -14
- 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 +4 -4
|
@@ -44,4 +44,16 @@ export declare function runOps(cli: Cli, ws: Workspace, repo: RepoRow, ops: Op[]
|
|
|
44
44
|
* tool's dry_run so the preview comes back without committing.
|
|
45
45
|
*/
|
|
46
46
|
export declare function runOpsRemote(cli: Cli, tool: string, toolArgs: Record<string, unknown>): Promise<number>;
|
|
47
|
+
/**
|
|
48
|
+
* The dry-run rendering every write verb shares (spec/cli §3.6): the note on
|
|
49
|
+
* stderr, then per changed file the path, the unified diff of before → after
|
|
50
|
+
* (`spec/surface` §3) and an empty line. Document verbs hand their
|
|
51
|
+
* `DocOpResult.diffs` here; block verbs their `ApplyResult.diffs`.
|
|
52
|
+
*/
|
|
53
|
+
export declare function renderDiffs(cli: Cli, diffs: Record<string, {
|
|
54
|
+
before: string;
|
|
55
|
+
after: string;
|
|
56
|
+
}>): void;
|
|
57
|
+
/** The unified diff of before → after (`spec/surface` §3), one styled line per entry. */
|
|
58
|
+
export declare function unifiedDiffLines(cli: Cli, before: string, after: string): string[];
|
|
47
59
|
export { EXIT_OK, EXIT_ERROR };
|
package/dist/src/cmd/_mutate.js
CHANGED
|
@@ -162,22 +162,35 @@ function renderApply(cli, result) {
|
|
|
162
162
|
return cli.flags.dryRun ? renderDryRun(cli, result) : renderCommitted(cli, result);
|
|
163
163
|
}
|
|
164
164
|
function renderDryRun(cli, result) {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
165
|
+
renderDiffs(cli, result.diffs ?? {});
|
|
166
|
+
return EXIT_OK;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* The dry-run rendering every write verb shares (spec/cli §3.6): the note on
|
|
170
|
+
* stderr, then per changed file the path, the unified diff of before → after
|
|
171
|
+
* (`spec/surface` §3) and an empty line. Document verbs hand their
|
|
172
|
+
* `DocOpResult.diffs` here; block verbs their `ApplyResult.diffs`.
|
|
173
|
+
*/
|
|
174
|
+
export function renderDiffs(cli, diffs) {
|
|
175
|
+
const { style, io } = cli;
|
|
176
|
+
io.err(style.dim(` dry run — ${Object.keys(diffs).length} file(s) would change, nothing committed`));
|
|
177
|
+
for (const [path, { before, after }] of Object.entries(diffs)) {
|
|
168
178
|
io.out(`${style.bold(path)}`);
|
|
169
|
-
for (const line of
|
|
170
|
-
|
|
171
|
-
io.out(style.ok(line));
|
|
172
|
-
else if (line.startsWith("-"))
|
|
173
|
-
io.out(style.err(line));
|
|
174
|
-
else
|
|
175
|
-
io.out(style.dim(line));
|
|
176
|
-
}
|
|
179
|
+
for (const line of unifiedDiffLines(cli, before, after))
|
|
180
|
+
io.out(line);
|
|
177
181
|
io.out("");
|
|
178
182
|
}
|
|
179
|
-
|
|
180
|
-
|
|
183
|
+
}
|
|
184
|
+
/** The unified diff of before → after (`spec/surface` §3), one styled line per entry. */
|
|
185
|
+
export function unifiedDiffLines(cli, before, after) {
|
|
186
|
+
const { style } = cli;
|
|
187
|
+
return unifiedDiff(before, after).split("\n").map((line) => {
|
|
188
|
+
if (line.startsWith("+"))
|
|
189
|
+
return style.ok(line);
|
|
190
|
+
if (line.startsWith("-"))
|
|
191
|
+
return style.err(line);
|
|
192
|
+
return style.dim(line);
|
|
193
|
+
});
|
|
181
194
|
}
|
|
182
195
|
function renderCommitted(cli, result) {
|
|
183
196
|
const { render, style, io } = cli;
|
|
@@ -2,9 +2,9 @@ import { type SyncSource, type Store, type RepoRow } from "@omgbase/core";
|
|
|
2
2
|
/** The built-in filesystem adapter's registry name. */
|
|
3
3
|
export declare const FS_ADAPTER = "fs";
|
|
4
4
|
/** Seed the built-in `fs` adapter row (idempotent). Required before creating an
|
|
5
|
-
* fs source (the `sources.adapter` FK is enforced). The stored command is
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* fs source (the `sources.adapter` FK is enforced). The stored command is
|
|
6
|
+
* registry data only (spec/sync §2, §5): the host never runs it for `fs` — see
|
|
7
|
+
* spawnSource — so a source is never pinned to a stale path. */
|
|
8
8
|
export declare function ensureFsAdapter(store: Store): void;
|
|
9
9
|
/**
|
|
10
10
|
* Build the live filesystem SyncSource for a repo from its attached `fs` source
|
package/dist/src/cmd/_source.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { fsAdapterBinPath } from "@omgbase/fs-adapter";
|
|
1
|
+
import { createExternalSource, ensureAdapter, listAdapters, sourcesForRepo, renderConfigFlags, } from "@omgbase/core";
|
|
2
|
+
import { fsAdapterLaunch } from "@omgbase/fs-adapter";
|
|
4
3
|
// Resolve a repo's live sync source for `omg watch`/`omg mcp`/`omg sync`
|
|
5
4
|
// (sync-plugins, ADR-014 Stage 3). Resolution now prefers the source REGISTRY
|
|
6
5
|
// (adapters/sources/attachments tables): a repo's attached filesystem source is
|
|
@@ -11,19 +10,23 @@ import { fsAdapterBinPath } from "@omgbase/fs-adapter";
|
|
|
11
10
|
/** The built-in filesystem adapter's registry name. */
|
|
12
11
|
export const FS_ADAPTER = "fs";
|
|
13
12
|
/** Seed the built-in `fs` adapter row (idempotent). Required before creating an
|
|
14
|
-
* fs source (the `sources.adapter` FK is enforced). The stored command is
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* fs source (the `sources.adapter` FK is enforced). The stored command is
|
|
14
|
+
* registry data only (spec/sync §2, §5): the host never runs it for `fs` — see
|
|
15
|
+
* spawnSource — so a source is never pinned to a stale path. */
|
|
17
16
|
export function ensureFsAdapter(store) {
|
|
18
17
|
ensureAdapter(store, FS_ADAPTER, "omgbase-fs-adapter", []);
|
|
19
18
|
}
|
|
20
19
|
/** Spawn one registered source as a live SyncSource. The built-in `fs` adapter
|
|
21
|
-
* is
|
|
22
|
-
*
|
|
23
|
-
|
|
20
|
+
* is launched per spec/sync §5 (1.2): `$OMGBASE_FS_ADAPTER` whitespace-split
|
|
21
|
+
* when set and non-empty, else the bundled `@omgbase/fs-adapter` bin under the
|
|
22
|
+
* running `node`; then the row's fixed `args`, then the rendered config flags
|
|
23
|
+
* (`fsAdapterLaunch`). Other adapters would spawn their stored command. */
|
|
24
|
+
function spawnSource(store, source) {
|
|
24
25
|
const flags = renderConfigFlags(source.config);
|
|
25
26
|
if (source.adapter === FS_ADAPTER) {
|
|
26
|
-
|
|
27
|
+
const row = listAdapters(store).find((a) => a.name === FS_ADAPTER);
|
|
28
|
+
const launch = fsAdapterLaunch(row?.args ?? [], flags);
|
|
29
|
+
return createExternalSource({ ...launch, ...(hasEnv(source) ? { env: source.env } : {}) });
|
|
27
30
|
}
|
|
28
31
|
// Custom adapter: spawn its stored command (looked up by the caller).
|
|
29
32
|
throw new Error(`no launcher for adapter '${source.adapter}' (only the built-in '${FS_ADAPTER}' is wired)`);
|
|
@@ -42,6 +45,6 @@ export async function openRepoSource(store, repo) {
|
|
|
42
45
|
const root = fsSource.config.root;
|
|
43
46
|
if (typeof root !== "string" || root === "")
|
|
44
47
|
return null;
|
|
45
|
-
return spawnSource(fsSource);
|
|
48
|
+
return spawnSource(store, fsSource);
|
|
46
49
|
}
|
|
47
50
|
//# sourceMappingURL=_source.js.map
|
package/dist/src/cmd/admin.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { rebuildIndex, runGc, reposStatus, buildEmbedTasks, buildDocEmbedTasks, resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings,
|
|
3
|
-
import { CliUsageError, EngineErrorLike, EXIT_OK, EXIT_ERROR, renderHelp } from "../output.js";
|
|
2
|
+
import { rebuildIndex, runGc, reposStatus, buildEmbedTasks, buildDocEmbedTasks, resolveSettings, workspaceSettings, repoOwnSettings, writeWorkspaceSettings, writeRepoSettings, ftsIndexedRowCount, liveLeafCount, } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, EXIT_ERROR, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { loadEmbedding, drainEmbeddings } from "./_embed.js";
|
|
5
5
|
// admin/maintenance (11 §5.8–5.9). The live watcher moved to `omg sync --watch`.
|
|
6
6
|
// ---- rebuild-index ----------------------------------------------------------
|
|
@@ -27,6 +27,9 @@ function runRebuild(cli, args) {
|
|
|
27
27
|
const target = values.sections ? "sections" : values.edges ? "edges" : values.fts ? "fts" : values["block-changes"] ? "block_changes" : "all";
|
|
28
28
|
const ws = cli.workspace();
|
|
29
29
|
rebuildIndex(ws.store, target);
|
|
30
|
+
// The library call returns nothing; the machine result names what was rebuilt.
|
|
31
|
+
if (cli.flags.mode !== "human")
|
|
32
|
+
return emitMachine(cli, { rebuilt: target });
|
|
30
33
|
cli.io.err(cli.style.dim(` ${cli.style.ok(cli.render.g.ok)} rebuilt ${target}`));
|
|
31
34
|
return EXIT_OK;
|
|
32
35
|
}
|
|
@@ -52,9 +55,8 @@ function runGcCmd(cli, args) {
|
|
|
52
55
|
}
|
|
53
56
|
const result = runGc(ws.store, { enabled: enabled || dryRun });
|
|
54
57
|
if (cli.flags.mode !== "human")
|
|
55
|
-
cli
|
|
56
|
-
|
|
57
|
-
cli.io.err(cli.style.dim(` swept ${result.blobsSwept} blobs, ${result.treeNodesSwept} tree nodes`));
|
|
58
|
+
return emitMachine(cli, result);
|
|
59
|
+
cli.io.err(cli.style.dim(` swept ${result.blobsSwept} blobs, ${result.treeNodesSwept} tree nodes`));
|
|
58
60
|
return EXIT_OK;
|
|
59
61
|
}
|
|
60
62
|
// ---- doctor -----------------------------------------------------------------
|
|
@@ -63,7 +65,7 @@ function runDoctor(cli, args) {
|
|
|
63
65
|
if (values.help) {
|
|
64
66
|
return renderHelp(cli, {
|
|
65
67
|
name: "doctor",
|
|
66
|
-
summary: "Invariant sweep: convergence, FTS rows vs live blocks, dangling revisions, SQLite integrity — exit 1 on any failure (CI-able)",
|
|
68
|
+
summary: "Invariant sweep: convergence, FTS rows vs live leaf blocks, dangling revisions, SQLite integrity — exit 1 on any failure (CI-able)",
|
|
67
69
|
usage: "doctor [--json]",
|
|
68
70
|
options: [["--json", "`{ ok, checks: [{ name, ok, detail }] }`"]],
|
|
69
71
|
});
|
|
@@ -74,16 +76,19 @@ function runDoctor(cli, args) {
|
|
|
74
76
|
const checks = [];
|
|
75
77
|
const status = reposStatus(ws.store, repo.repoId);
|
|
76
78
|
checks.push({ name: "convergence", ok: status.unconverged === 0, detail: `${status.unconverged} unconverged` });
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
// The index holds exactly the live LEAF blocks (spec/search §1.1): a container
|
|
80
|
+
// has no row, a tombstone none. Counted from the index's shadow table — a plain
|
|
81
|
+
// count(*) on an external-content FTS table answers from `blocks` instead.
|
|
82
|
+
const ftsCount = ftsIndexedRowCount(db);
|
|
83
|
+
const leaves = liveLeafCount(db);
|
|
84
|
+
checks.push({ name: "fts rows == live leaf blocks", ok: ftsCount === leaves, detail: `fts=${ftsCount} leaves=${leaves}` });
|
|
80
85
|
const dangling = db.prepare("SELECT count(*) c FROM docs d WHERE d.deleted_commit IS NULL AND d.current_rev IS NOT NULL AND NOT EXISTS (SELECT 1 FROM revisions r WHERE r.rev_id = d.current_rev)").get().c;
|
|
81
86
|
checks.push({ name: "no dangling current_rev", ok: dangling === 0, detail: `${dangling} dangling` });
|
|
82
87
|
const integrity = db.pragma("integrity_check", { simple: true });
|
|
83
88
|
checks.push({ name: "sqlite integrity", ok: integrity === "ok", detail: integrity });
|
|
84
89
|
const allOk = checks.every((c) => c.ok);
|
|
85
90
|
if (cli.flags.mode !== "human") {
|
|
86
|
-
cli
|
|
91
|
+
emitMachine(cli, { ok: allOk, checks });
|
|
87
92
|
return allOk ? EXIT_OK : EXIT_ERROR;
|
|
88
93
|
}
|
|
89
94
|
const { style, io, render } = cli;
|
|
@@ -104,9 +109,10 @@ function resolveConfigScope(cli, ws) {
|
|
|
104
109
|
// An explicit --repo <slug> that doesn't resolve is a real error. A bare
|
|
105
110
|
// (cwd-based) selection that's ambiguous or empty just means "workspace
|
|
106
111
|
// layer" — the workspace is what you mean when not clearly inside a repo.
|
|
112
|
+
// (`Cli.repo` has already wrapped the RepoSelectionError as repo_not_found.)
|
|
107
113
|
if (cli.flags.repo)
|
|
108
114
|
throw err;
|
|
109
|
-
if (err instanceof
|
|
115
|
+
if (err instanceof EngineErrorLike && err.code === "repo_not_found")
|
|
110
116
|
return { kind: "workspace" };
|
|
111
117
|
throw err;
|
|
112
118
|
}
|
|
@@ -139,7 +145,7 @@ function runConfig(cli, args) {
|
|
|
139
145
|
if (scope.kind === "workspace") {
|
|
140
146
|
const s = workspaceSettings(ws.store);
|
|
141
147
|
if (cli.flags.mode !== "human")
|
|
142
|
-
cli
|
|
148
|
+
return emitMachine(cli, s);
|
|
143
149
|
else {
|
|
144
150
|
cli.io.err(cli.style.dim(` workspace defaults`));
|
|
145
151
|
for (const [k, v] of Object.entries(s))
|
|
@@ -149,10 +155,8 @@ function runConfig(cli, args) {
|
|
|
149
155
|
}
|
|
150
156
|
const effective = resolveSettings(ws.store, scope.repoId);
|
|
151
157
|
const own = repoOwnSettings(ws.store, scope.repoId);
|
|
152
|
-
if (cli.flags.mode !== "human")
|
|
153
|
-
cli
|
|
154
|
-
return EXIT_OK;
|
|
155
|
-
}
|
|
158
|
+
if (cli.flags.mode !== "human")
|
|
159
|
+
return emitMachine(cli, effective);
|
|
156
160
|
cli.io.err(cli.style.dim(` ${label} (effective; ${cli.render.g.diamond} = overrides workspace default)`));
|
|
157
161
|
for (const [k, v] of Object.entries(effective)) {
|
|
158
162
|
const overridden = Object.prototype.hasOwnProperty.call(own, k);
|
|
@@ -210,17 +214,16 @@ async function runEmbed(cli, args) {
|
|
|
210
214
|
// No provider configured (05 §6). Honest report; not an error.
|
|
211
215
|
const hint = 'set one with `omg config set embedding.provider <command|url>` (e.g. omgbase-embedder)';
|
|
212
216
|
if (cli.flags.mode !== "human")
|
|
213
|
-
cli
|
|
214
|
-
|
|
215
|
-
cli.io.err(cli.style.dim(` no embedding provider configured — ${hint}`));
|
|
217
|
+
return emitMachine(cli, { provider: null, queued: 0 });
|
|
218
|
+
cli.io.err(cli.style.dim(` no embedding provider configured — ${hint}`));
|
|
216
219
|
return EXIT_OK;
|
|
217
220
|
}
|
|
218
221
|
if (sub === "drain") {
|
|
219
222
|
await loaded.close(); // drainEmbeddings connects its own provider
|
|
220
223
|
const result = await drainEmbeddings(cli, ws, repo.repoId, { verbose, prune });
|
|
221
224
|
if (cli.flags.mode !== "human")
|
|
222
|
-
cli
|
|
223
|
-
|
|
225
|
+
return emitMachine(cli, { provider: loaded.providerName, ...(result ?? { embedded: 0, cached: 0 }) });
|
|
226
|
+
if (result)
|
|
224
227
|
cli.io.err(` ${cli.style.ok(cli.render.g.ok)} embedded ${result.embedded}, cached ${result.cached}`);
|
|
225
228
|
return EXIT_OK;
|
|
226
229
|
}
|
|
@@ -236,7 +239,7 @@ async function runEmbed(cli, args) {
|
|
|
236
239
|
// status
|
|
237
240
|
const payload = { provider: loaded.providerName, model: loaded.provider.model, dim: loaded.provider.dim, embeddable: tasks.length, queued: pending.length, docs: docTasks.length, docsQueued: docPending.length, foreignBlocks: foreign.blocks, foreignDocs: foreign.docs };
|
|
238
241
|
if (cli.flags.mode !== "human")
|
|
239
|
-
cli
|
|
242
|
+
return emitMachine(cli, payload);
|
|
240
243
|
else {
|
|
241
244
|
cli.io.out(` provider ${cli.style.accent(loaded.providerName)} ${cli.style.dim(`(${loaded.provider.model}, ${loaded.provider.dim}d)`)}`);
|
|
242
245
|
cli.io.out(` embeddable ${tasks.length} ${cli.style.dim("queued")} ${payload.queued}`);
|
|
@@ -3,9 +3,9 @@ import { join, resolve, dirname, relative, sep } from "node:path";
|
|
|
3
3
|
import { parseArgs } from "node:util";
|
|
4
4
|
import { createInterface } from "node:readline";
|
|
5
5
|
import { spawnSync } from "node:child_process";
|
|
6
|
-
import { Workspace, reposStatus, workspaceSettings, writeWorkspaceSettings } from "@omgbase/core";
|
|
6
|
+
import { Workspace, reposStatus, reposList, workspaceSettings, writeWorkspaceSettings } from "@omgbase/core";
|
|
7
7
|
import { columns as columnsLocal } from "../render.js";
|
|
8
|
-
import { EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
8
|
+
import { EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
9
9
|
// Bootstrap: init / repos (11 §5.1). Pointing a repo at a directory is
|
|
10
10
|
// `omg source add <dir>` (ADR-014) — a repo owns identity; sources bring bytes.
|
|
11
11
|
async function confirmTTY(cli, question) {
|
|
@@ -183,10 +183,8 @@ async function runInit(cli, args) {
|
|
|
183
183
|
finally {
|
|
184
184
|
ws.close();
|
|
185
185
|
}
|
|
186
|
-
if (cli.flags.mode !== "human")
|
|
187
|
-
cli
|
|
188
|
-
return EXIT_OK;
|
|
189
|
-
}
|
|
186
|
+
if (cli.flags.mode !== "human")
|
|
187
|
+
return emitMachine(cli, { workspace: dir });
|
|
190
188
|
const { render, style, io } = cli;
|
|
191
189
|
io.out(render.wordmark("initialized"));
|
|
192
190
|
io.out(render.rule(40));
|
|
@@ -209,25 +207,19 @@ function runRepos(cli, args) {
|
|
|
209
207
|
name: "repos",
|
|
210
208
|
summary: "List the repos in this workspace: slug, root path (from its fs source), doc/block counts",
|
|
211
209
|
usage: "repos [--ids|--json|--jsonl]",
|
|
212
|
-
options: [
|
|
210
|
+
options: [
|
|
211
|
+
["--ids", "slugs only, one per line"],
|
|
212
|
+
["--json", "the `repos` tool's result: `{ repos: [{ slug, hasSource }] }` (counts are `status --json`)"],
|
|
213
|
+
],
|
|
213
214
|
});
|
|
214
215
|
}
|
|
215
216
|
const ws = cli.workspace();
|
|
217
|
+
// `--json` is the `repos` tool's result; the human table adds each repo's root
|
|
218
|
+
// and its `repos_status` counts, which the reader wants at a glance.
|
|
219
|
+
const list = reposList(ws.store);
|
|
220
|
+
if (cli.flags.mode !== "human")
|
|
221
|
+
return emitMachine(cli, list, { items: list.repos, ids: list.repos.map((r) => r.slug) });
|
|
216
222
|
const repos = ws.repos().map((r) => ({ ...r, status: reposStatus(ws.store, r.repoId) }));
|
|
217
|
-
if (cli.flags.mode === "json") {
|
|
218
|
-
cli.io.out(JSON.stringify(repos.map((r) => ({ slug: r.slug, root: r.rootPath, docs: r.status.docs, blocks: r.status.blocks }))));
|
|
219
|
-
return EXIT_OK;
|
|
220
|
-
}
|
|
221
|
-
if (cli.flags.mode === "jsonl") {
|
|
222
|
-
for (const r of repos)
|
|
223
|
-
cli.io.out(JSON.stringify({ slug: r.slug, root: r.rootPath, docs: r.status.docs, blocks: r.status.blocks }));
|
|
224
|
-
return EXIT_OK;
|
|
225
|
-
}
|
|
226
|
-
if (cli.flags.mode === "ids") {
|
|
227
|
-
for (const r of repos)
|
|
228
|
-
cli.io.out(r.slug);
|
|
229
|
-
return EXIT_OK;
|
|
230
|
-
}
|
|
231
223
|
const { render, style, io } = cli;
|
|
232
224
|
const rows = repos.map((r) => [
|
|
233
225
|
` ${render.g.diamond} ${style.accent(r.slug)}`,
|
|
@@ -236,7 +228,7 @@ function runRepos(cli, args) {
|
|
|
236
228
|
style.dim(`${r.status.blocks} blocks`),
|
|
237
229
|
]);
|
|
238
230
|
if (rows.length === 0)
|
|
239
|
-
io.
|
|
231
|
+
io.err(style.dim(" no repos attached"));
|
|
240
232
|
else
|
|
241
233
|
for (const line of columnsLocal(rows))
|
|
242
234
|
io.out(line);
|
package/dist/src/cmd/cat.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolveRef, nodesGet, docsRead } 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
|
import { expandBlockArgs } from "./_mutate.js";
|
|
6
6
|
/** `--resolution` only means something for a block; say so (stderr) rather than
|
|
@@ -16,7 +16,10 @@ async function catOne(cli, ref, resolution) {
|
|
|
16
16
|
const r = await remoteCall(cli, "read_ref", { ref, ...(resolution !== "raw" ? { resolution } : {}) });
|
|
17
17
|
if (r.kind === "document") {
|
|
18
18
|
warnDocResolution(cli, ref, resolution);
|
|
19
|
-
|
|
19
|
+
// The `docs_read` result is the `read_ref` document result without `kind`.
|
|
20
|
+
const { kind: _kind, ...doc } = r;
|
|
21
|
+
void _kind;
|
|
22
|
+
return { text: String(r.content ?? ""), json: doc, capture: r.content };
|
|
20
23
|
}
|
|
21
24
|
return { text: String(r.raw ?? r.text ?? r.label ?? ""), json: r, capture: r };
|
|
22
25
|
}
|
|
@@ -35,8 +38,8 @@ async function catOne(cli, ref, resolution) {
|
|
|
35
38
|
if (!res)
|
|
36
39
|
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
37
40
|
warnDocResolution(cli, ref, resolution);
|
|
38
|
-
// shell: the bytes (a string; @_ only)
|
|
39
|
-
return { text: res.content, json:
|
|
41
|
+
// shell: the bytes (a string; @_ only). `--json` is the `docs_read` result.
|
|
42
|
+
return { text: res.content, json: res, capture: res.content };
|
|
40
43
|
}
|
|
41
44
|
const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution });
|
|
42
45
|
if (!node)
|
|
@@ -72,14 +75,9 @@ async function runCat(cli, args) {
|
|
|
72
75
|
// One ref keeps the single-entity shape (a string/object); several become a list.
|
|
73
76
|
const single = results.length === 1 ? results[0] : null;
|
|
74
77
|
cli.capture?.(single ? single.capture : results.map((r) => r.capture));
|
|
75
|
-
if (cli.flags.mode
|
|
76
|
-
|
|
77
|
-
return
|
|
78
|
-
}
|
|
79
|
-
if (cli.flags.mode === "jsonl") {
|
|
80
|
-
for (const r of results)
|
|
81
|
-
cli.io.out(JSON.stringify(r.json));
|
|
82
|
-
return EXIT_OK;
|
|
78
|
+
if (cli.flags.mode !== "human") {
|
|
79
|
+
const docs = results.map((r) => r.json);
|
|
80
|
+
return single ? emitMachine(cli, single.json) : emitMachine(cli, docs, { items: docs });
|
|
83
81
|
}
|
|
84
82
|
for (const r of results)
|
|
85
83
|
cli.io.out(r.text);
|
package/dist/src/cmd/diff.js
CHANGED
|
@@ -1,37 +1,39 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { findDoc,
|
|
3
|
-
import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
2
|
+
import { findDoc, docDiffUnified, diffBlocks } from "@omgbase/core";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { remoteCall } from "./_remote.js";
|
|
5
|
-
// `omg diff <doc>` (11 §5.5) — unified diff
|
|
6
|
-
//
|
|
5
|
+
// `omg diff <doc>` (11 §5.5) — `diff_unified`: the unified diff, `--json` the
|
|
6
|
+
// tool's `{ doc, path, from, to, diff }`. With no revisions: current vs previous
|
|
7
|
+
// ("what did the last commit do here"). `--blocks` is the block-grain `diff`
|
|
8
|
+
// tool: added/removed/changed blocks between the same two revisions.
|
|
7
9
|
async function runDiff(cli, args) {
|
|
8
10
|
const { values, positionals } = parseArgs({
|
|
9
11
|
args,
|
|
10
12
|
allowPositionals: true,
|
|
11
|
-
options: { from: { type: "string" }, to: { type: "string" }, help: { type: "boolean" } },
|
|
13
|
+
options: { from: { type: "string" }, to: { type: "string" }, blocks: { type: "boolean" }, help: { type: "boolean" } },
|
|
12
14
|
});
|
|
13
15
|
if (values.help) {
|
|
14
16
|
return renderHelp(cli, {
|
|
15
17
|
name: "diff",
|
|
16
18
|
summary: "Unified diff of a document between two revisions (default: previous → current)",
|
|
17
|
-
usage: "diff <doc> [--from <rev>] [--to <rev>]",
|
|
19
|
+
usage: "diff <doc> [--from <rev>] [--to <rev>] [--blocks]",
|
|
18
20
|
options: [
|
|
19
21
|
["--from <rev>", "older revision id (default: the one before --to)"],
|
|
20
22
|
["--to <rev>", "newer revision id (default: current)"],
|
|
23
|
+
["--blocks", "block-grain: which blocks were added, removed or changed (the `diff` tool)"],
|
|
21
24
|
],
|
|
22
25
|
});
|
|
23
26
|
}
|
|
24
27
|
const ref = positionals[0];
|
|
25
28
|
if (!ref)
|
|
26
29
|
throw new CliUsageError("diff requires a <doc>");
|
|
27
|
-
let
|
|
30
|
+
let result;
|
|
28
31
|
if (cli.flags.server) {
|
|
29
|
-
|
|
32
|
+
result = await remoteCall(cli, "diff_unified", {
|
|
30
33
|
doc: ref,
|
|
31
34
|
...(values.from ? { from_rev: values.from } : {}),
|
|
32
35
|
...(values.to ? { to_rev: values.to } : {}),
|
|
33
36
|
});
|
|
34
|
-
({ doc: docId, path, from: fromRev, to: toRev, diff: text } = r);
|
|
35
37
|
}
|
|
36
38
|
else {
|
|
37
39
|
const ws = cli.workspace();
|
|
@@ -39,30 +41,40 @@ async function runDiff(cli, args) {
|
|
|
39
41
|
const info = ref.startsWith("d_") ? findDoc(ws.store, { docId: ref }) : findDoc(ws.store, { repoId: repo.repoId, path: ref });
|
|
40
42
|
if (!info)
|
|
41
43
|
throw new EngineErrorLike("doc_missing", `no document ${ref}`);
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
|
|
46
|
-
|
|
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);
|
|
44
|
+
result = docDiffUnified(ws.store, info.docId, {
|
|
45
|
+
...(values.from ? { fromRev: values.from } : {}),
|
|
46
|
+
...(values.to ? { toRev: values.to } : {}),
|
|
47
|
+
label: ref,
|
|
48
|
+
});
|
|
55
49
|
}
|
|
56
|
-
|
|
57
|
-
|
|
50
|
+
const { style, io } = cli;
|
|
51
|
+
if (values.blocks) {
|
|
52
|
+
// Block-grain over the same resolved revision pair.
|
|
53
|
+
let entries;
|
|
54
|
+
if (cli.flags.server)
|
|
55
|
+
entries = await remoteCall(cli, "diff", { doc: ref, from_rev: result.from, to_rev: result.to });
|
|
56
|
+
else
|
|
57
|
+
entries = diffBlocks(cli.workspace().store, result.doc, result.from, result.to);
|
|
58
|
+
if (cli.flags.mode !== "human")
|
|
59
|
+
return emitMachine(cli, entries, { items: entries, ids: entries.map((e) => e.blockId) });
|
|
60
|
+
if (entries.length === 0) {
|
|
61
|
+
io.err(style.dim(" no changes"));
|
|
62
|
+
return EXIT_OK;
|
|
63
|
+
}
|
|
64
|
+
for (const e of entries) {
|
|
65
|
+
const mark = e.kind === "added" ? style.ok("+") : e.kind === "removed" ? style.err("-") : style.warn("~");
|
|
66
|
+
const preview = (e.after ?? e.before ?? "").split("\n")[0] ?? "";
|
|
67
|
+
io.out(`${mark} ${style.id(e.blockId)} ${preview}`.trimEnd());
|
|
68
|
+
}
|
|
58
69
|
return EXIT_OK;
|
|
59
70
|
}
|
|
60
|
-
|
|
61
|
-
|
|
71
|
+
if (cli.flags.mode !== "human")
|
|
72
|
+
return emitMachine(cli, result);
|
|
73
|
+
if (!result.diff) {
|
|
62
74
|
io.err(style.dim(" no changes"));
|
|
63
75
|
return EXIT_OK;
|
|
64
76
|
}
|
|
65
|
-
for (const line of
|
|
77
|
+
for (const line of result.diff.split("\n")) {
|
|
66
78
|
if (line.startsWith("+"))
|
|
67
79
|
io.out(style.ok(line));
|
|
68
80
|
else if (line.startsWith("-"))
|
package/dist/src/cmd/docs.js
CHANGED
|
@@ -2,22 +2,48 @@ import { parseArgs } from "node:util";
|
|
|
2
2
|
import { parse as parseYamlScalar } from "yaml";
|
|
3
3
|
import { docsCreate, docsMove, docsDelete, docsSetMeta } from "@omgbase/core";
|
|
4
4
|
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
5
|
-
import { readContent, extractContentOpts } from "./_mutate.js";
|
|
5
|
+
import { readContent, extractContentOpts, renderDiffs } from "./_mutate.js";
|
|
6
6
|
import { remoteCall } from "./_remote.js";
|
|
7
7
|
// Document-level commands (11 §5.6): new / mv / rm --doc / meta. Thin wrappers
|
|
8
8
|
// over the core doc ops, which own the file-write + commit + flock protocol.
|
|
9
|
+
// The global --dry-run rides as the op's `dryRun` (spec/cli §3.6): the op
|
|
10
|
+
// validates and returns the per-file diffs it would make, committing nothing.
|
|
9
11
|
function ctxOf(cli, ws, repoId, rootPath, actor) {
|
|
10
|
-
return {
|
|
12
|
+
return {
|
|
13
|
+
repoId,
|
|
14
|
+
...(rootPath ? { rootPath } : {}),
|
|
15
|
+
omgbaseDir: ws.omgbaseDir,
|
|
16
|
+
...(actor ? { actor } : {}),
|
|
17
|
+
...(cli.flags.dryRun ? { dryRun: true } : {}),
|
|
18
|
+
};
|
|
11
19
|
}
|
|
12
20
|
function report(cli, verb, res) {
|
|
13
21
|
if (cli.flags.mode !== "human") {
|
|
14
22
|
cli.io.out(JSON.stringify(res));
|
|
15
23
|
return EXIT_OK;
|
|
16
24
|
}
|
|
25
|
+
if (cli.flags.dryRun) {
|
|
26
|
+
renderDiffs(cli, res.diffs ?? {});
|
|
27
|
+
return EXIT_OK;
|
|
28
|
+
}
|
|
17
29
|
cli.io.err(cli.style.dim(` ${cli.style.ok(cli.render.g.ok)} ${verb} ${cli.style.accent(res.path)}`));
|
|
18
30
|
cli.io.out(res.docId);
|
|
19
31
|
return EXIT_OK;
|
|
20
32
|
}
|
|
33
|
+
// `mv` does not rewrite inbound links; say so wherever the move is reported
|
|
34
|
+
// (spec/cli §6 `mv`): the human confirmation carries the same `dangling` list
|
|
35
|
+
// the --json result does, with the retarget that fixes them.
|
|
36
|
+
function reportMove(cli, from, res) {
|
|
37
|
+
const code = report(cli, "moved to", res);
|
|
38
|
+
if (cli.flags.mode !== "human" || res.dangling.length === 0)
|
|
39
|
+
return code;
|
|
40
|
+
const { style, io } = cli;
|
|
41
|
+
const where = res.dangling.map((l) => (l.block ? `${l.path} ${l.block}` : `${l.path} (frontmatter)`)).join(", ");
|
|
42
|
+
const n = res.dangling.length;
|
|
43
|
+
io.err(style.warn(` ${style.warn(cli.render.g.warn)} ${n} inbound link${n === 1 ? "" : "s"} still name${n === 1 ? "s" : ""} the old path: ${where}`));
|
|
44
|
+
io.err(style.dim(` fix: ${cli.prog} retarget /${from} /${res.path} --apply`));
|
|
45
|
+
return code;
|
|
46
|
+
}
|
|
21
47
|
// ---- new --------------------------------------------------------------------
|
|
22
48
|
async function runNew(cli, args) {
|
|
23
49
|
const content = extractContentOpts(args);
|
|
@@ -44,7 +70,7 @@ async function runNew(cli, args) {
|
|
|
44
70
|
throw new CliUsageError("new requires a <path>");
|
|
45
71
|
const bytes = readContent(content);
|
|
46
72
|
if (cli.flags.server) {
|
|
47
|
-
return report(cli, "created", await remoteCall(cli, "docs_create", { path, markdown: bytes }));
|
|
73
|
+
return report(cli, "created", await remoteCall(cli, "docs_create", { path, markdown: bytes, ...dryRunArg(cli) }));
|
|
48
74
|
}
|
|
49
75
|
const ws = cli.workspace();
|
|
50
76
|
const repo = cli.repo(ws);
|
|
@@ -71,17 +97,18 @@ async function runMv(cli, args) {
|
|
|
71
97
|
if (!doc || !toPath)
|
|
72
98
|
throw new CliUsageError("mv requires <doc> and <new-path>");
|
|
73
99
|
if (cli.flags.server) {
|
|
74
|
-
|
|
100
|
+
const res = await remoteCall(cli, "docs_move", { doc, to_path: toPath, ...dryRunArg(cli) });
|
|
101
|
+
return reportMove(cli, fromPathOf(res, doc), res);
|
|
75
102
|
}
|
|
76
103
|
const ws = cli.workspace();
|
|
77
104
|
const repo = cli.repo(ws);
|
|
78
105
|
const res = docsMove(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, toPath);
|
|
79
|
-
return
|
|
106
|
+
return reportMove(cli, fromPathOf(res, doc), res);
|
|
80
107
|
}
|
|
81
108
|
// ---- rm --doc (block rm lives in mutate.ts; this handles the --doc branch) --
|
|
82
109
|
export async function runRmDoc(cli, doc, actor) {
|
|
83
110
|
if (cli.flags.server) {
|
|
84
|
-
return report(cli, "deleted", await remoteCall(cli, "docs_delete", { doc }));
|
|
111
|
+
return report(cli, "deleted", await remoteCall(cli, "docs_delete", { doc, ...dryRunArg(cli) }));
|
|
85
112
|
}
|
|
86
113
|
const ws = cli.workspace();
|
|
87
114
|
const repo = cli.repo(ws);
|
|
@@ -141,6 +168,7 @@ async function runMeta(cli, args) {
|
|
|
141
168
|
doc,
|
|
142
169
|
...(Object.keys(set).length ? { set } : {}),
|
|
143
170
|
...(unset.length ? { unset } : {}),
|
|
171
|
+
...dryRunArg(cli),
|
|
144
172
|
}));
|
|
145
173
|
}
|
|
146
174
|
const ws = cli.workspace();
|
|
@@ -148,6 +176,16 @@ async function runMeta(cli, args) {
|
|
|
148
176
|
const res = docsSetMeta(ws.store, ctxOf(cli, ws, repo.repoId, repo.rootPath, values.actor), doc, { set, unset });
|
|
149
177
|
return report(cli, "patched", res);
|
|
150
178
|
}
|
|
179
|
+
function dryRunArg(cli) {
|
|
180
|
+
return cli.flags.dryRun ? { dry_run: true } : {};
|
|
181
|
+
}
|
|
182
|
+
// The path a move left behind, for the retarget hint: the dangling links name
|
|
183
|
+
// it as written (`target`), which is the canonical spelling with a leading `/`;
|
|
184
|
+
// fall back to the ref the user typed when nothing dangles.
|
|
185
|
+
function fromPathOf(res, ref) {
|
|
186
|
+
const named = res.dangling.find((l) => l.target)?.target;
|
|
187
|
+
return (named ?? ref).replace(/^\/+/, "");
|
|
188
|
+
}
|
|
151
189
|
export const cmdNew = { name: "new", summary: "Create a document", run: (c, a) => runNew(c, a) };
|
|
152
190
|
export const cmdMv = { name: "mv", summary: "Rename a document", run: (c, a) => runMv(c, a) };
|
|
153
191
|
export const cmdMeta = { name: "meta", summary: "Patch a document's frontmatter", run: (c, a) => runMeta(c, a) };
|
package/dist/src/cmd/find.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
2
|
import { resolve as resolveThing } from "@omgbase/core";
|
|
3
|
-
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
3
|
+
import { CliUsageError, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
import { loadEmbedding } from "./_embed.js";
|
|
5
5
|
import { remoteCall } from "./_remote.js";
|
|
6
6
|
// `omg find <text>` (11 §5.2) — resolve: ranked {id, locator, preview,
|
|
@@ -68,20 +68,8 @@ async function runFind(cli, args) {
|
|
|
68
68
|
cli.io.out(hits[0].id);
|
|
69
69
|
return EXIT_OK;
|
|
70
70
|
}
|
|
71
|
-
if (cli.flags.mode
|
|
72
|
-
|
|
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
|
-
}
|
|
71
|
+
if (cli.flags.mode !== "human")
|
|
72
|
+
return emitMachine(cli, hits, { items: hits, ids: hits.map((h) => h.id) });
|
|
85
73
|
const { style, io } = cli;
|
|
86
74
|
if (hits.length === 0) {
|
|
87
75
|
io.err(style.dim(" no matches"));
|