omgbase 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build-info.json +4 -0
- package/dist/src/build.d.ts +22 -0
- package/dist/src/build.js +58 -0
- package/dist/src/cmd/_mutate.d.ts +12 -0
- package/dist/src/cmd/_mutate.js +26 -13
- package/dist/src/cmd/admin.js +25 -22
- package/dist/src/cmd/bootstrap.js +14 -22
- package/dist/src/cmd/cat.js +10 -12
- package/dist/src/cmd/diff.js +39 -27
- package/dist/src/cmd/docs.js +44 -6
- package/dist/src/cmd/find.js +3 -15
- package/dist/src/cmd/help.js +1 -1
- package/dist/src/cmd/hist.js +4 -10
- package/dist/src/cmd/links.js +21 -16
- package/dist/src/cmd/log.js +8 -18
- package/dist/src/cmd/ls.js +3 -15
- package/dist/src/cmd/mcp.js +16 -32
- package/dist/src/cmd/mutate.js +24 -3
- package/dist/src/cmd/node.js +7 -3
- package/dist/src/cmd/oqx.d.ts +10 -0
- package/dist/src/cmd/oqx.js +25 -28
- package/dist/src/cmd/outline.js +5 -8
- package/dist/src/cmd/retarget.js +6 -21
- package/dist/src/cmd/run.js +5 -32
- package/dist/src/cmd/show.js +11 -12
- package/dist/src/cmd/source.js +18 -32
- package/dist/src/cmd/status.js +13 -25
- package/dist/src/cmd/sync.js +8 -9
- package/dist/src/cmd/update.js +10 -5
- package/dist/src/cmd/version.d.ts +2 -0
- package/dist/src/cmd/version.js +68 -0
- package/dist/src/commands.js +2 -0
- package/dist/src/context.d.ts +4 -0
- package/dist/src/context.js +16 -0
- package/dist/src/dispatch.d.ts +3 -0
- package/dist/src/dispatch.js +35 -18
- package/dist/src/main.d.ts +2 -0
- package/dist/src/main.js +19 -13
- package/dist/src/output.d.ts +28 -1
- package/dist/src/output.js +66 -20
- package/dist/src/seams.d.ts +13 -0
- package/dist/src/seams.js +37 -0
- package/dist/src/shell/session.js +4 -2
- package/package.json +4 -4
package/dist/src/cmd/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
|
}
|
package/dist/src/cmd/status.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { reposStatus, syncStatus, watchLeaseLive } from "@omgbase/core";
|
|
3
|
-
import { EXIT_OK, renderHelp } from "../output.js";
|
|
2
|
+
import { reposStatus, syncStatus, watchLeaseLive, staleEmbedCount, resolveSettings, embeddingSettings } from "@omgbase/core";
|
|
3
|
+
import { EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
4
|
// `omg status` (11 §5.2): repos_status + sync_status + watch-lease probe +
|
|
5
|
-
// embedding queue depth. The "where am I" command — the visual showcase.
|
|
5
|
+
// embedding queue depth. The "where am I" command — the visual showcase. The
|
|
6
|
+
// machine shape is the two tools' results, nested as they are on the wire
|
|
7
|
+
// (spec/cli §6): `{ ...repos_status, sync: sync_status, watcher, embedQueue }`.
|
|
6
8
|
function runStatus(cli, args) {
|
|
7
9
|
const { values } = parseArgs({ args, allowPositionals: true, options: { help: { type: "boolean" } } });
|
|
8
10
|
if (values.help) {
|
|
@@ -10,7 +12,7 @@ function runStatus(cli, args) {
|
|
|
10
12
|
name: "status",
|
|
11
13
|
summary: "Where am I: the active repo, doc/block/commit counts, sync convergence, watcher, embed queue",
|
|
12
14
|
usage: "status [--repo <slug>] [--json]",
|
|
13
|
-
options: [["--json", "
|
|
15
|
+
options: [["--json", "the repos_status result, plus `sync` (the sync_status result), `watcher` (live|none) and `embedQueue` (stale embeddable blocks)"]],
|
|
14
16
|
});
|
|
15
17
|
}
|
|
16
18
|
const ws = cli.workspace();
|
|
@@ -18,27 +20,13 @@ function runStatus(cli, args) {
|
|
|
18
20
|
const rs = reposStatus(ws.store, repo.repoId, repo.rootPath ?? undefined);
|
|
19
21
|
const ss = syncStatus(ws.store, repo.repoId, repo.rootPath ?? undefined);
|
|
20
22
|
const watcher = watchLeaseLive(ws.omgbaseDir);
|
|
21
|
-
// Embedding queue depth:
|
|
22
|
-
//
|
|
23
|
-
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
blocks: rs.blocks,
|
|
29
|
-
commits: rs.commits,
|
|
30
|
-
openEdges: rs.openEdges,
|
|
31
|
-
unconverged: rs.unconverged,
|
|
32
|
-
convergent: ss.convergent,
|
|
33
|
-
disk: ss.disk,
|
|
34
|
-
lastCommitSeq: ss.lastCommitSeq,
|
|
35
|
-
watcher: watcher ? "live" : "none",
|
|
36
|
-
embedQueue: queued,
|
|
37
|
-
};
|
|
38
|
-
if (cli.flags.mode !== "human") {
|
|
39
|
-
cli.io.out(JSON.stringify(payload));
|
|
40
|
-
return EXIT_OK;
|
|
41
|
-
}
|
|
23
|
+
// Embedding queue depth (spec/search §2.3): the embeddable blocks whose
|
|
24
|
+
// current context has no cached vector — for the configured model when one is
|
|
25
|
+
// named, else for any. Counted from the cache alone: no provider is spawned.
|
|
26
|
+
const queued = staleEmbedCount(ws.store, repo.repoId, embeddingSettings(resolveSettings(ws.store, repo.repoId)).model);
|
|
27
|
+
const payload = { ...rs, sync: ss, watcher: watcher ? "live" : "none", embedQueue: queued };
|
|
28
|
+
if (cli.flags.mode !== "human")
|
|
29
|
+
return emitMachine(cli, payload);
|
|
42
30
|
const { render, style, io } = cli;
|
|
43
31
|
const g = render.g;
|
|
44
32
|
io.out(render.wordmark(repo.slug));
|
package/dist/src/cmd/sync.js
CHANGED
|
@@ -2,7 +2,7 @@ import { resolve } from "node:path";
|
|
|
2
2
|
import { parseArgs } from "node:util";
|
|
3
3
|
import { Watcher, WatchLease, EmbedDrainer, freshnessSweep, awaitReady, WATCH_READY_PATIENCE_MS, EngineError } from "@omgbase/core";
|
|
4
4
|
import { runFsMirror } from "@omgbase/sync";
|
|
5
|
-
import { EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
|
|
5
|
+
import { EngineErrorLike, EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
6
6
|
import { loadEmbedding } from "./_embed.js";
|
|
7
7
|
import { remoteSpec } from "./_remote.js";
|
|
8
8
|
import { openRepoSource } from "./_source.js";
|
|
@@ -62,17 +62,16 @@ async function runSync(cli, args) {
|
|
|
62
62
|
}
|
|
63
63
|
function runOneShot(cli, ws, repo) {
|
|
64
64
|
if (!repo.rootPath) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
65
|
+
// Nothing to sweep: an empty-result note (stderr, §3.1), or the empty sweep shape.
|
|
66
|
+
if (cli.flags.mode !== "human")
|
|
67
|
+
return emitMachine(cli, { scanned: 0, ingested: [], deleted: [], conflicted: [], changed: false });
|
|
68
|
+
cli.io.err(cli.style.dim(` ${repo.slug} has no filesystem source — nothing to sync`));
|
|
69
69
|
return EXIT_OK;
|
|
70
70
|
}
|
|
71
71
|
const result = freshnessSweep(ws.store, repo.repoId, repo.rootPath);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return
|
|
75
|
-
}
|
|
72
|
+
// A `SweepResult` is a record (its lists are per-kind): every machine mode is the document.
|
|
73
|
+
if (cli.flags.mode !== "human")
|
|
74
|
+
return emitMachine(cli, result);
|
|
76
75
|
const { render, style, io } = cli;
|
|
77
76
|
const g = render.g;
|
|
78
77
|
io.out(render.wordmark("sync"));
|
package/dist/src/cmd/update.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import { docsUpdate, renderOpsetPlan
|
|
2
|
+
import { docsUpdate, renderOpsetPlan } from "@omgbase/core";
|
|
3
3
|
import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
|
|
4
4
|
import { readContent, extractContentOpts } from "./_mutate.js";
|
|
5
5
|
import { cmdUpdate as cmdUpdateBlock } from "./mutate.js";
|
|
@@ -13,11 +13,13 @@ import { remoteCall } from "./_remote.js";
|
|
|
13
13
|
// --dry-run (or --plan) prints the plan (identity effects + summary), commits
|
|
14
14
|
// nothing.
|
|
15
15
|
function runUpdate(cli, args) {
|
|
16
|
-
// Peek at the target to dispatch: a b_
|
|
17
|
-
//
|
|
16
|
+
// Peek at the target to dispatch: a `b_` token is a block replace — a token
|
|
17
|
+
// that names no live block fails there as block_missing (spec/cli §6), never
|
|
18
|
+
// as a document path; anything else (d_ id or path) is a whole-document
|
|
19
|
+
// update. The block path re-parses args.
|
|
18
20
|
const peek = extractContentOpts(args);
|
|
19
21
|
const target = peek.rest.find((a) => !a.startsWith("-"));
|
|
20
|
-
if (target &&
|
|
22
|
+
if (target && target.startsWith("b_"))
|
|
21
23
|
return cmdUpdateBlock.run(cli, args);
|
|
22
24
|
return runDocUpdate(cli, args);
|
|
23
25
|
}
|
|
@@ -92,7 +94,10 @@ async function runDocUpdate(cli, args) {
|
|
|
92
94
|
}
|
|
93
95
|
const g = cli.render.g;
|
|
94
96
|
cli.io.err(cli.style.dim(` ${cli.style.ok(g.ok)} updated ${cli.style.accent(opset.target.path)} · ${opset.summary.preserved} preserved, ${opset.summary.updated} updated, ${opset.summary.moved} moved, ${opset.summary.created} created, ${opset.summary.removed} removed`));
|
|
95
|
-
|
|
97
|
+
// Every id the opset touched, once each: a block appears in several ops'
|
|
98
|
+
// results (an update then a retile of the same block), the confirmation
|
|
99
|
+
// lists it a single time.
|
|
100
|
+
for (const id of new Set(result.results.flatMap((r) => r.ids)))
|
|
96
101
|
cli.io.out(id);
|
|
97
102
|
return EXIT_OK;
|
|
98
103
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
import { versionInfo } from "@omgbase/core";
|
|
3
|
+
import { EXIT_OK, emitMachine, renderHelp } from "../output.js";
|
|
4
|
+
import { hostInfo } from "../build.js";
|
|
5
|
+
import { remoteCall } from "./_remote.js";
|
|
6
|
+
// `omg version` (spec/cli §6, 1.1): the surface's `version` tool, rendered.
|
|
7
|
+
// Which engine and which component versions a host or a shell is talking to —
|
|
8
|
+
// with `--server` it is the REMOTE engine's answer, which is exactly how you
|
|
9
|
+
// tell a TypeScript `omg mcp` from a Rust `omgbase mcp`. Needs no repo; the
|
|
10
|
+
// workspace is opened only for `schema` (`—` without one). `--version` (§2.5)
|
|
11
|
+
// stays the one-line form.
|
|
12
|
+
async function runVersion(cli, args) {
|
|
13
|
+
const { values } = parseArgs({ args, allowPositionals: true, options: { help: { type: "boolean" } } });
|
|
14
|
+
if (values.help) {
|
|
15
|
+
return renderHelp(cli, {
|
|
16
|
+
name: "version",
|
|
17
|
+
summary: "Which engine and which versions: the binary, its components, the specs it implements, the schema, MCP protocol, runtime, build",
|
|
18
|
+
usage: "version [--json]",
|
|
19
|
+
options: [["--json", "the `version` tool result verbatim"]],
|
|
20
|
+
notes: ["with --server, the remote engine's answer (typescript or rust)"],
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
let info;
|
|
24
|
+
if (cli.flags.server !== undefined) {
|
|
25
|
+
info = await remoteCall(cli, "version", {});
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
// `schema` is the open database's user_version; without a workspace the
|
|
29
|
+
// command still answers (spec/cli §3.7: version ∈ NO_WORKSPACE_OK).
|
|
30
|
+
let store = null;
|
|
31
|
+
try {
|
|
32
|
+
store = cli.workspace().store;
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
store = null;
|
|
36
|
+
}
|
|
37
|
+
info = versionInfo(store, hostInfo());
|
|
38
|
+
}
|
|
39
|
+
cli.capture?.(info);
|
|
40
|
+
if (cli.flags.mode !== "human")
|
|
41
|
+
return emitMachine(cli, info);
|
|
42
|
+
const { io, style } = cli;
|
|
43
|
+
const dash = "—";
|
|
44
|
+
const keys = ["engine", "version", "specs", "schema", "mcp", "runtime", "commit", "built"];
|
|
45
|
+
const width = Math.max(...keys.map((k) => k.length));
|
|
46
|
+
const line = (key, value) => {
|
|
47
|
+
io.out(`${style.dim(key.padEnd(width))} ${value === null ? style.dim(dash) : String(value)}`);
|
|
48
|
+
};
|
|
49
|
+
line("engine", info.engine);
|
|
50
|
+
line("version", info.version);
|
|
51
|
+
const names = Object.keys(info.components);
|
|
52
|
+
const nameWidth = Math.max(0, ...names.map((n) => n.length));
|
|
53
|
+
for (const name of names)
|
|
54
|
+
io.out(` ${style.accent(name.padEnd(nameWidth))} ${info.components[name]}`);
|
|
55
|
+
line("specs", Object.entries(info.specs).map(([k, v]) => `${k} ${v}`).join(" · "));
|
|
56
|
+
line("schema", info.schema);
|
|
57
|
+
line("mcp", info.mcp.sdk !== undefined ? `${info.mcp.protocol} (sdk ${info.mcp.sdk})` : info.mcp.protocol);
|
|
58
|
+
line("runtime", info.runtime);
|
|
59
|
+
line("commit", info.commit);
|
|
60
|
+
line("built", info.built);
|
|
61
|
+
return EXIT_OK;
|
|
62
|
+
}
|
|
63
|
+
export const cmdVersion = {
|
|
64
|
+
name: "version",
|
|
65
|
+
summary: "Which engine and which versions (binary, components, specs, schema, MCP, runtime, build)",
|
|
66
|
+
run: (cli, a) => runVersion(cli, a),
|
|
67
|
+
};
|
|
68
|
+
//# sourceMappingURL=version.js.map
|
package/dist/src/commands.js
CHANGED
|
@@ -24,6 +24,7 @@ import { cmdRebuild, cmdGc, cmdDoctor, cmdConfig, cmdEmbed } from "./cmd/admin.j
|
|
|
24
24
|
import { cmdShell } from "./cmd/shell.js";
|
|
25
25
|
import { cmdSource } from "./cmd/source.js";
|
|
26
26
|
import { cmdHelp } from "./cmd/help.js";
|
|
27
|
+
import { cmdVersion } from "./cmd/version.js";
|
|
27
28
|
export const COMMANDS = [
|
|
28
29
|
cmdInit,
|
|
29
30
|
cmdRepos,
|
|
@@ -63,6 +64,7 @@ export const COMMANDS = [
|
|
|
63
64
|
cmdDoctor,
|
|
64
65
|
cmdConfig,
|
|
65
66
|
cmdEmbed,
|
|
67
|
+
cmdVersion,
|
|
66
68
|
cmdHelp,
|
|
67
69
|
];
|
|
68
70
|
const BY_NAME = new Map();
|
package/dist/src/context.d.ts
CHANGED
|
@@ -68,6 +68,10 @@ export interface CliOptions {
|
|
|
68
68
|
export declare function progName(argv1: string | undefined): string;
|
|
69
69
|
export declare function makeCli(flags: GlobalFlags, io: IO, opts?: CliOptions): Cli;
|
|
70
70
|
export declare const NO_WORKSPACE_OK: Set<string>;
|
|
71
|
+
export declare const HELP_WORD_OK: Set<string>;
|
|
72
|
+
export declare const BARE_IS_HELP: Set<string>;
|
|
73
|
+
/** Does this invocation ask for a command's help card? (the flag, the word, or a bare card-only command) */
|
|
74
|
+
export declare function asksForHelp(flags: GlobalFlags, name: string, args: string[]): boolean;
|
|
71
75
|
export declare const SKIP_FRESHNESS: Set<string>;
|
|
72
76
|
export declare const REMOTE_OK: Set<string>;
|
|
73
77
|
export { CliUsageError };
|
package/dist/src/context.js
CHANGED
|
@@ -57,6 +57,20 @@ function resolveDir(dir) {
|
|
|
57
57
|
}
|
|
58
58
|
// Which commands may run without a workspace (11 §2.1).
|
|
59
59
|
export const NO_WORKSPACE_OK = new Set(["init", "help", "version"]);
|
|
60
|
+
// Commands whose first argument may be the word `help` (their card), and those
|
|
61
|
+
// whose card is also their bare invocation (spec/cli §5): help is documentation,
|
|
62
|
+
// not work, so these route to the card before any workspace is opened or swept —
|
|
63
|
+
// exactly like the `--help` flag.
|
|
64
|
+
export const HELP_WORD_OK = new Set(["config", "embed", "node", "source"]);
|
|
65
|
+
export const BARE_IS_HELP = new Set(["node", "source"]);
|
|
66
|
+
/** Does this invocation ask for a command's help card? (the flag, the word, or a bare card-only command) */
|
|
67
|
+
export function asksForHelp(flags, name, args) {
|
|
68
|
+
if (flags.help)
|
|
69
|
+
return true;
|
|
70
|
+
if (HELP_WORD_OK.has(name) && args[0] === "help")
|
|
71
|
+
return true;
|
|
72
|
+
return BARE_IS_HELP.has(name) && args.length === 0;
|
|
73
|
+
}
|
|
60
74
|
// Commands that manage sync themselves — skip the freshness sweep (11 §3.3).
|
|
61
75
|
// `shell` is exempt because each line it runs sweeps on its own. `source`
|
|
62
76
|
// creates/binds sources and runs its own initial sync, so it skips the pre-sweep.
|
|
@@ -75,6 +89,8 @@ export const REMOTE_OK = new Set([
|
|
|
75
89
|
"apply", "insert", "move", "split", "merge", "done", "append", "node",
|
|
76
90
|
// shell accepts --server itself, then threads it into every line it runs.
|
|
77
91
|
"shell",
|
|
92
|
+
// version asks the REMOTE engine which engine/versions it is (spec/cli §6).
|
|
93
|
+
"version",
|
|
78
94
|
]);
|
|
79
95
|
export { CliUsageError };
|
|
80
96
|
//# sourceMappingURL=context.js.map
|
package/dist/src/dispatch.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { type Cli, type GlobalFlags } from "./context.js";
|
|
2
|
+
import { CliUsageError } from "./output.js";
|
|
2
3
|
export interface Parsed {
|
|
3
4
|
flags: GlobalFlags;
|
|
4
5
|
command: string | null;
|
|
5
6
|
rest: string[];
|
|
7
|
+
/** the first malformed global flag, when any (exit 2) */
|
|
8
|
+
error?: CliUsageError;
|
|
6
9
|
}
|
|
7
10
|
export declare function parseGlobals(argv: string[]): Parsed;
|
|
8
11
|
export declare function runCommand(cli: Cli, command: string, rest: string[]): Promise<number>;
|
package/dist/src/dispatch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { freshnessSweep, watchLeaseLive } from "@omgbase/core";
|
|
2
|
-
import { NO_WORKSPACE_OK, SKIP_FRESHNESS, REMOTE_OK } from "./context.js";
|
|
3
|
-
import { CliUsageError, EngineErrorLike, renderError } from "./output.js";
|
|
2
|
+
import { NO_WORKSPACE_OK, SKIP_FRESHNESS, REMOTE_OK, asksForHelp } from "./context.js";
|
|
3
|
+
import { CliUsageError, EngineErrorLike, renderError, usageFromParseArgs } from "./output.js";
|
|
4
4
|
import { resolveCommand, unknownCommandError } from "./commands.js";
|
|
5
5
|
export function parseGlobals(argv) {
|
|
6
6
|
const flags = {
|
|
@@ -14,6 +14,12 @@ export function parseGlobals(argv) {
|
|
|
14
14
|
let command = null;
|
|
15
15
|
const rest = [];
|
|
16
16
|
let passthrough = false;
|
|
17
|
+
let error;
|
|
18
|
+
// Record the first malformed flag and keep parsing (a later `--json` still
|
|
19
|
+
// selects the mode the error is rendered in).
|
|
20
|
+
const fail = (message) => {
|
|
21
|
+
error ??= new CliUsageError(message);
|
|
22
|
+
};
|
|
17
23
|
for (let i = 0; i < argv.length; i++) {
|
|
18
24
|
const a = argv[i];
|
|
19
25
|
if (passthrough) {
|
|
@@ -31,30 +37,35 @@ export function parseGlobals(argv) {
|
|
|
31
37
|
case "--directory": {
|
|
32
38
|
const dir = argv[++i];
|
|
33
39
|
if (dir === undefined)
|
|
34
|
-
|
|
35
|
-
|
|
40
|
+
fail(`${a} requires a directory`);
|
|
41
|
+
else
|
|
42
|
+
flags.directory = dir;
|
|
36
43
|
continue;
|
|
37
44
|
}
|
|
38
45
|
case "--repo": {
|
|
39
46
|
const slug = argv[++i];
|
|
40
47
|
if (slug === undefined)
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
fail("--repo requires a slug");
|
|
49
|
+
else
|
|
50
|
+
flags.repo = slug;
|
|
43
51
|
continue;
|
|
44
52
|
}
|
|
45
53
|
case "--server": {
|
|
54
|
+
// An empty value is a mistake, not "local": the flag was given.
|
|
46
55
|
const s = argv[++i];
|
|
47
|
-
if (s === undefined)
|
|
48
|
-
|
|
49
|
-
|
|
56
|
+
if (s === undefined || s === "")
|
|
57
|
+
fail("--server requires a command or url");
|
|
58
|
+
else
|
|
59
|
+
flags.server = s;
|
|
50
60
|
continue;
|
|
51
61
|
}
|
|
52
62
|
case "-H":
|
|
53
63
|
case "--header": {
|
|
54
64
|
const h = argv[++i];
|
|
55
65
|
if (h === undefined)
|
|
56
|
-
|
|
57
|
-
|
|
66
|
+
fail(`${a} requires a "Name: value" header`);
|
|
67
|
+
else
|
|
68
|
+
(flags.headers ??= []).push(h);
|
|
58
69
|
continue;
|
|
59
70
|
}
|
|
60
71
|
case "--json":
|
|
@@ -90,12 +101,14 @@ export function parseGlobals(argv) {
|
|
|
90
101
|
}
|
|
91
102
|
// Once the command is known, anything unrecognized is its residual;
|
|
92
103
|
// a stray flag before any command is a usage error.
|
|
93
|
-
if (!command)
|
|
94
|
-
|
|
104
|
+
if (!command) {
|
|
105
|
+
fail(`unknown flag ${a}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
95
108
|
rest.push(a);
|
|
96
109
|
}
|
|
97
110
|
}
|
|
98
|
-
return { flags, command, rest };
|
|
111
|
+
return { flags, command, rest, ...(error ? { error } : {}) };
|
|
99
112
|
}
|
|
100
113
|
// Per-command execution shared by the one-shot entry (main.ts) and the
|
|
101
114
|
// interactive shell (cmd/shell.ts). Given a resolved Cli, a command name, and
|
|
@@ -110,18 +123,20 @@ export async function runCommand(cli, command, rest) {
|
|
|
110
123
|
// `--help` after a command → route to help for that command. Help is
|
|
111
124
|
// documentation, not work: it must never need a workspace, so it also skips
|
|
112
125
|
// the freshness sweep below (which would otherwise fail with repo_not_found
|
|
113
|
-
// from any directory without a `.omgbase/`).
|
|
126
|
+
// from any directory without a `.omgbase/`). The help *words* (`config help`,
|
|
127
|
+
// a bare `node`, …) are the same request spelled differently (spec/cli §5).
|
|
114
128
|
const args = cli.flags.help ? ["--help", ...rest] : rest;
|
|
129
|
+
const help = asksForHelp(cli.flags, resolved.name, rest);
|
|
115
130
|
// Global `--server` (remote/MCP mode) is only implemented by some commands so
|
|
116
131
|
// far (REMOTE_OK); reject it elsewhere rather than silently running locally.
|
|
117
|
-
if (cli.flags.server !== undefined && !
|
|
132
|
+
if (cli.flags.server !== undefined && !help && !REMOTE_OK.has(resolved.name)) {
|
|
118
133
|
return renderError(new CliUsageError(`--server is not supported for '${resolved.name}' — it needs local ref resolution or a working tree; run it against a local workspace`), cli.io, cli.style, cli.flags.mode !== "human");
|
|
119
134
|
}
|
|
120
135
|
try {
|
|
121
136
|
// Freshness sweep (11 §3.3): current-by-default, unless --stale, a live
|
|
122
137
|
// watcher holds the lease, remote (`--server`) mode (no local tree to sweep),
|
|
123
138
|
// or the command manages sync itself.
|
|
124
|
-
if (!
|
|
139
|
+
if (!help && !cli.flags.stale && cli.flags.server === undefined && !SKIP_FRESHNESS.has(resolved.name) && !NO_WORKSPACE_OK.has(resolved.name)) {
|
|
125
140
|
const ws = cli.workspace();
|
|
126
141
|
if (!watchLeaseLive(ws.omgbaseDir)) {
|
|
127
142
|
// Best-effort: a workspace with no attached repo (or an unresolvable
|
|
@@ -142,7 +157,9 @@ export async function runCommand(cli, command, rest) {
|
|
|
142
157
|
return await resolved.run(cli, args);
|
|
143
158
|
}
|
|
144
159
|
catch (err) {
|
|
145
|
-
|
|
160
|
+
// A command's own `parseArgs` rejecting an option is a usage error (exit 2),
|
|
161
|
+
// rendered here once for every verb rather than in each parser.
|
|
162
|
+
return renderError(usageFromParseArgs(err, cli.prog, resolved.name), cli.io, cli.style, cli.flags.mode !== "human");
|
|
146
163
|
}
|
|
147
164
|
}
|
|
148
165
|
//# sourceMappingURL=dispatch.js.map
|
package/dist/src/main.d.ts
CHANGED
package/dist/src/main.js
CHANGED
|
@@ -1,30 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { VERSION } from "@omgbase/core";
|
|
3
2
|
import { processIO } from "./render.js";
|
|
4
3
|
import { makeCli, progName } from "./context.js";
|
|
5
|
-
import { renderError, EXIT_OK
|
|
4
|
+
import { renderError, EXIT_OK } from "./output.js";
|
|
6
5
|
import { resolveCommand, unknownCommandError } from "./commands.js";
|
|
7
6
|
import { runCommand, parseGlobals } from "./dispatch.js";
|
|
8
7
|
import { closeRemote } from "./cmd/_remote.js";
|
|
8
|
+
import { installSpecSeams } from "./seams.js";
|
|
9
|
+
import { cliVersion } from "./build.js";
|
|
9
10
|
// Hand-rolled router (11 §8: no commander). Splits global flags from the
|
|
10
11
|
// command + its residual argv (parseGlobals, in dispatch.ts), resolves the
|
|
11
12
|
// command (with aliases), then delegates to runCommand for the freshness sweep +
|
|
12
13
|
// dispatch + error mapping — the same path the shell reuses per line.
|
|
14
|
+
export { cliVersion };
|
|
13
15
|
export async function run(argv, io = processIO, prog = "omg") {
|
|
14
|
-
|
|
15
|
-
try {
|
|
16
|
-
parsed = parseGlobals(argv);
|
|
17
|
-
}
|
|
18
|
-
catch (err) {
|
|
19
|
-
// No Cli/style yet — render minimally.
|
|
20
|
-
io.err(String(err.message ?? err));
|
|
21
|
-
return EXIT_USAGE;
|
|
22
|
-
}
|
|
16
|
+
const parsed = parseGlobals(argv);
|
|
23
17
|
const { flags } = parsed;
|
|
24
18
|
let command = parsed.command;
|
|
25
19
|
// --version / --help without a command.
|
|
26
|
-
if (flags.version && !command) {
|
|
27
|
-
io.out(
|
|
20
|
+
if (flags.version && !command && !parsed.error) {
|
|
21
|
+
io.out(cliVersion());
|
|
28
22
|
return EXIT_OK;
|
|
29
23
|
}
|
|
30
24
|
if (flags.help && !command)
|
|
@@ -32,6 +26,18 @@ export async function run(argv, io = processIO, prog = "omg") {
|
|
|
32
26
|
if (!command)
|
|
33
27
|
command = "help";
|
|
34
28
|
const cli = makeCli(flags, io, { prog });
|
|
29
|
+
// A malformed global flag is a usage error like any other (spec/cli §2.2):
|
|
30
|
+
// rendered with the `usage:` prefix, in the mode the argv selected.
|
|
31
|
+
if (parsed.error)
|
|
32
|
+
return renderError(parsed.error, io, cli.style, flags.mode !== "human");
|
|
33
|
+
// The conformance seams (spec/cli §2.6) apply to every verb: install them
|
|
34
|
+
// before any command can open the workspace, mint an id or read the clock.
|
|
35
|
+
try {
|
|
36
|
+
installSpecSeams();
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
return renderError(err, io, cli.style, flags.mode !== "human");
|
|
40
|
+
}
|
|
35
41
|
if (!resolveCommand(command)) {
|
|
36
42
|
return renderError(unknownCommandError(prog, command), io, cli.style, flags.mode !== "human");
|
|
37
43
|
}
|
package/dist/src/output.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type IO } from "./render.js";
|
|
2
2
|
import type { Style } from "./style.js";
|
|
3
|
+
import type { OutputMode } from "./context.js";
|
|
3
4
|
export declare const EXIT_OK = 0;
|
|
4
5
|
export declare const EXIT_ERROR = 1;
|
|
5
6
|
export declare const EXIT_USAGE = 2;
|
|
@@ -9,6 +10,14 @@ export declare class CliUsageError extends Error {
|
|
|
9
10
|
readonly hint?: string | undefined;
|
|
10
11
|
constructor(message: string, hint?: string | undefined);
|
|
11
12
|
}
|
|
13
|
+
/**
|
|
14
|
+
* Node's `parseArgs` failures are the user's mistake, not the engine's (spec/cli
|
|
15
|
+
* §2.2): an option the command does not declare, a value-taking option without
|
|
16
|
+
* its value, a boolean given one. Each becomes a usage error with a portable
|
|
17
|
+
* message — the wording is ours, so a port need not reproduce Node's — and the
|
|
18
|
+
* hint points at the command's card. Anything else is returned untouched.
|
|
19
|
+
*/
|
|
20
|
+
export declare function usageFromParseArgs(err: unknown, prog: string, command: string): unknown;
|
|
12
21
|
export interface HelpSpec {
|
|
13
22
|
name: string;
|
|
14
23
|
summary: string;
|
|
@@ -52,9 +61,27 @@ export declare class EngineErrorLike extends Error {
|
|
|
52
61
|
/**
|
|
53
62
|
* Render a thrown error to stderr and return the process exit code. `json`
|
|
54
63
|
* selects machine output (typed object); otherwise the human form with any
|
|
55
|
-
* current-truth payload pretty-printed.
|
|
64
|
+
* current-truth payload pretty-printed. Both forms carry the hint; an empty
|
|
65
|
+
* payload (`{}`) prints nothing.
|
|
56
66
|
*/
|
|
57
67
|
export declare function renderError(err: unknown, io: IO, style: Style, json: boolean): number;
|
|
68
|
+
export interface MachineShape {
|
|
69
|
+
/** the list `--jsonl` streams (absent: the result is not list-shaped) */
|
|
70
|
+
items?: unknown[];
|
|
71
|
+
/** the ids `--ids` prints (absent: the result carries no id list) */
|
|
72
|
+
ids?: string[];
|
|
73
|
+
/** the cursor of a truncated result, for the footer */
|
|
74
|
+
cursor?: string | number | null;
|
|
75
|
+
}
|
|
76
|
+
interface MachineIO {
|
|
77
|
+
io: IO;
|
|
78
|
+
style: Style;
|
|
79
|
+
flags: {
|
|
80
|
+
mode: OutputMode;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Print `doc` in the current machine mode (`--json`/`--jsonl`/`--ids`); returns EXIT_OK. */
|
|
84
|
+
export declare function emitMachine(cli: MachineIO, doc: unknown, shape?: MachineShape): number;
|
|
58
85
|
export interface HitLike {
|
|
59
86
|
id: string;
|
|
60
87
|
path: string;
|