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.
Files changed (80) hide show
  1. package/LICENSE +21 -0
  2. package/dist/src/cmd/_embed.d.ts +55 -0
  3. package/dist/src/cmd/_embed.js +102 -0
  4. package/dist/src/cmd/_mutate.d.ts +47 -0
  5. package/dist/src/cmd/_mutate.js +211 -0
  6. package/dist/src/cmd/_remote.d.ts +8 -0
  7. package/dist/src/cmd/_remote.js +61 -0
  8. package/dist/src/cmd/_source.d.ts +13 -0
  9. package/dist/src/cmd/_source.js +47 -0
  10. package/dist/src/cmd/admin.d.ts +6 -0
  11. package/dist/src/cmd/admin.js +289 -0
  12. package/dist/src/cmd/bootstrap.d.ts +3 -0
  13. package/dist/src/cmd/bootstrap.js +252 -0
  14. package/dist/src/cmd/cat.d.ts +2 -0
  15. package/dist/src/cmd/cat.js +80 -0
  16. package/dist/src/cmd/diff.d.ts +2 -0
  17. package/dist/src/cmd/diff.js +76 -0
  18. package/dist/src/cmd/docs.d.ts +6 -0
  19. package/dist/src/cmd/docs.js +154 -0
  20. package/dist/src/cmd/edit.d.ts +2 -0
  21. package/dist/src/cmd/edit.js +74 -0
  22. package/dist/src/cmd/find.d.ts +2 -0
  23. package/dist/src/cmd/find.js +98 -0
  24. package/dist/src/cmd/help.d.ts +2 -0
  25. package/dist/src/cmd/help.js +47 -0
  26. package/dist/src/cmd/hist.d.ts +2 -0
  27. package/dist/src/cmd/hist.js +58 -0
  28. package/dist/src/cmd/links.d.ts +2 -0
  29. package/dist/src/cmd/links.js +76 -0
  30. package/dist/src/cmd/log.d.ts +2 -0
  31. package/dist/src/cmd/log.js +109 -0
  32. package/dist/src/cmd/ls.d.ts +2 -0
  33. package/dist/src/cmd/ls.js +88 -0
  34. package/dist/src/cmd/mcp.d.ts +2 -0
  35. package/dist/src/cmd/mcp.js +192 -0
  36. package/dist/src/cmd/mutate.d.ts +10 -0
  37. package/dist/src/cmd/mutate.js +352 -0
  38. package/dist/src/cmd/node.d.ts +2 -0
  39. package/dist/src/cmd/node.js +66 -0
  40. package/dist/src/cmd/oqx.d.ts +2 -0
  41. package/dist/src/cmd/oqx.js +193 -0
  42. package/dist/src/cmd/outline.d.ts +2 -0
  43. package/dist/src/cmd/outline.js +87 -0
  44. package/dist/src/cmd/retarget.d.ts +2 -0
  45. package/dist/src/cmd/retarget.js +91 -0
  46. package/dist/src/cmd/run.d.ts +2 -0
  47. package/dist/src/cmd/run.js +135 -0
  48. package/dist/src/cmd/shell.d.ts +2 -0
  49. package/dist/src/cmd/shell.js +159 -0
  50. package/dist/src/cmd/show.d.ts +2 -0
  51. package/dist/src/cmd/show.js +107 -0
  52. package/dist/src/cmd/source.d.ts +2 -0
  53. package/dist/src/cmd/source.js +231 -0
  54. package/dist/src/cmd/status.d.ts +2 -0
  55. package/dist/src/cmd/status.js +95 -0
  56. package/dist/src/cmd/sync.d.ts +2 -0
  57. package/dist/src/cmd/sync.js +176 -0
  58. package/dist/src/cmd/update.d.ts +2 -0
  59. package/dist/src/cmd/update.js +100 -0
  60. package/dist/src/commands.d.ts +18 -0
  61. package/dist/src/commands.js +112 -0
  62. package/dist/src/context.d.ts +73 -0
  63. package/dist/src/context.js +80 -0
  64. package/dist/src/dispatch.d.ts +8 -0
  65. package/dist/src/dispatch.js +148 -0
  66. package/dist/src/main.d.ts +3 -0
  67. package/dist/src/main.js +67 -0
  68. package/dist/src/output.d.ts +60 -0
  69. package/dist/src/output.js +131 -0
  70. package/dist/src/render.d.ts +44 -0
  71. package/dist/src/render.js +116 -0
  72. package/dist/src/shell/refs.d.ts +37 -0
  73. package/dist/src/shell/refs.js +111 -0
  74. package/dist/src/shell/session.d.ts +71 -0
  75. package/dist/src/shell/session.js +317 -0
  76. package/dist/src/shell/tokenize.d.ts +3 -0
  77. package/dist/src/shell/tokenize.js +79 -0
  78. package/dist/src/style.d.ts +58 -0
  79. package/dist/src/style.js +159 -0
  80. package/package.json +63 -0
@@ -0,0 +1,87 @@
1
+ import { parseArgs } from "node:util";
2
+ import { findDoc, docsOutline } from "@omgbase/core";
3
+ import { CliUsageError, EngineErrorLike, truncationFooter, EXIT_OK, renderHelp } from "../output.js";
4
+ import { remoteCall } from "./_remote.js";
5
+ // `omg outline <doc|path>` (alias ol) — the wire format (06 §6). Human output
6
+ // prints the outline text with full block ids inline; --json emits the
7
+ // OutlineResult verbatim.
8
+ async function runOutline(cli, args) {
9
+ const { values, positionals } = parseArgs({
10
+ args,
11
+ allowPositionals: true,
12
+ options: {
13
+ depth: { type: "string" },
14
+ section: { type: "string" },
15
+ skeleton: { type: "boolean" },
16
+ help: { type: "boolean" },
17
+ },
18
+ });
19
+ if (values.help) {
20
+ return renderHelp(cli, {
21
+ name: "outline",
22
+ summary: "A document's outline with block ids inline (frozen wire format) — the orientation view",
23
+ usage: "outline <doc|path> [--depth <n>] [--section <locator>] [--skeleton]",
24
+ options: [
25
+ ["--depth <n>", "limit heading depth"],
26
+ ["--section <locator>", "outline only the section at a locator"],
27
+ ["--skeleton", "structure only, no text"],
28
+ ],
29
+ });
30
+ }
31
+ const ref = positionals[0];
32
+ if (!ref)
33
+ throw new CliUsageError("outline requires a <doc|path>");
34
+ let result;
35
+ let header;
36
+ if (cli.flags.server) {
37
+ // Remote: the docs_outline tool resolves the ref + returns the same
38
+ // OutlineResult. The header shows the ref you passed (no local path lookup).
39
+ result = await remoteCall(cli, "docs_outline", {
40
+ doc: ref,
41
+ ...(values.depth ? { depth: Number(values.depth) } : {}),
42
+ ...(values.skeleton ? { resolution: "skeleton" } : {}),
43
+ });
44
+ header = ref;
45
+ }
46
+ else {
47
+ const ws = cli.workspace();
48
+ const repo = cli.repo(ws);
49
+ const info = ref.startsWith("d_") ? findDoc(ws.store, { docId: ref }) : findDoc(ws.store, { repoId: repo.repoId, path: ref });
50
+ if (!info)
51
+ throw new EngineErrorLike("doc_missing", `no document ${ref}`);
52
+ const opts = {};
53
+ if (values.depth)
54
+ opts.depth = Number(values.depth);
55
+ if (values.skeleton)
56
+ opts.resolution = "skeleton";
57
+ result = docsOutline(ws.store, info.docId, opts);
58
+ header = info.path;
59
+ }
60
+ if (cli.flags.mode !== "human") {
61
+ cli.io.out(JSON.stringify(result));
62
+ return EXIT_OK;
63
+ }
64
+ const { render, style, io } = cli;
65
+ io.out(render.wordmark(header));
66
+ io.out(render.rule(40));
67
+ // Colorize the inline block ids + § marks.
68
+ for (const line of result.text.split("\n")) {
69
+ io.out(colorizeOutline(line, style));
70
+ }
71
+ if (result.truncated)
72
+ truncationFooter(io, style, "budget");
73
+ return EXIT_OK;
74
+ }
75
+ function colorizeOutline(line, style) {
76
+ // leading block id (b_…) → id style; trailing ` §` → accent.
77
+ return line
78
+ .replace(/\bb_[0-9a-z]+\b/, (m) => style.id(m))
79
+ .replace(/§/g, style.accent("§"));
80
+ }
81
+ export const cmdOutline = {
82
+ name: "outline",
83
+ aliases: ["ol"],
84
+ summary: "Document outline (frozen wire format)",
85
+ run: (cli, a) => runOutline(cli, a),
86
+ };
87
+ //# sourceMappingURL=outline.js.map
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdRetarget: Command;
@@ -0,0 +1,91 @@
1
+ import { parseArgs } from "node:util";
2
+ import { linksRetarget } from "@omgbase/core";
3
+ import { CliUsageError, EXIT_OK, renderHelp } from "../output.js";
4
+ import { runOps } from "./_mutate.js";
5
+ import { remoteCall } from "./_remote.js";
6
+ // `omg retarget <from> <to> [--scope glob] [--apply]` (11 §5.6) — plan-by-default.
7
+ // Without --apply it runs the dry-run and prints the per-block diffs (the 06 §4
8
+ // "always dry-run first" contract, encoded as the default). --apply commits.
9
+ async function runRetarget(cli, args) {
10
+ const { values, positionals } = parseArgs({
11
+ args,
12
+ allowPositionals: true,
13
+ options: { scope: { type: "string" }, apply: { type: "boolean" }, actor: { type: "string" }, help: { type: "boolean" } },
14
+ });
15
+ if (values.help) {
16
+ return renderHelp(cli, {
17
+ name: "retarget",
18
+ summary: "Rewrite every link that points at <from> to point at <to> — plan-by-default, --apply commits",
19
+ usage: "retarget <from> <to> [--scope <glob>] [--apply] [--actor <s>] [--dry-run]",
20
+ options: [
21
+ ["--scope <glob>", "only rewrite links in documents matching the path glob"],
22
+ ["--apply", "commit the rewrite (default: print the plan only)"],
23
+ ["--actor <s>", "commit actor (default human:$USER)"],
24
+ ],
25
+ });
26
+ }
27
+ const from = positionals[0];
28
+ const to = positionals[1];
29
+ if (!from || !to)
30
+ throw new CliUsageError("retarget requires <from> and <to> targets");
31
+ // Remote: links_retarget resolves + (optionally) applies server-side.
32
+ if (cli.flags.server) {
33
+ const r = await remoteCall(cli, "links_retarget", {
34
+ from_target: from, to_target: to, dry_run: !values.apply, ...(values.scope ? { path_glob: values.scope } : {}),
35
+ });
36
+ if (values.apply) {
37
+ if (cli.flags.mode !== "human")
38
+ cli.io.out(JSON.stringify(r));
39
+ else
40
+ cli.io.err(cli.style.dim(` ${cli.style.ok(cli.render.g.ok)} retargeted ${r.hits.length} block(s)`));
41
+ return EXIT_OK;
42
+ }
43
+ return renderPlan(cli, from, to, r.hits);
44
+ }
45
+ const ws = cli.workspace();
46
+ const repo = cli.repo(ws);
47
+ const { ops, hits } = linksRetarget(ws.store, repo.repoId, from, to, values.scope ? { pathGlob: values.scope } : {});
48
+ // --apply → commit via the shared helper (honors --dry-run too, if combined).
49
+ if (values.apply) {
50
+ return runOps(cli, ws, repo, ops, values.actor ? { actor: values.actor } : {});
51
+ }
52
+ return renderPlan(cli, from, to, hits);
53
+ }
54
+ // Plan (default): show what WOULD change, commit nothing.
55
+ function renderPlan(cli, from, to, hits) {
56
+ if (cli.flags.mode === "json") {
57
+ cli.io.out(JSON.stringify({ from, to, hits }));
58
+ return EXIT_OK;
59
+ }
60
+ const { style, io } = cli;
61
+ if (hits.length === 0) {
62
+ io.err(style.dim(` no blocks reference ${from}`));
63
+ return EXIT_OK;
64
+ }
65
+ io.err(style.dim(` plan — ${hits.length} block(s) would change; re-run with --apply to commit`));
66
+ for (const h of hits) {
67
+ io.out(style.id(h.block));
68
+ for (const line of lineDiff(h.oldRaw, h.newRaw)) {
69
+ io.out(line.startsWith("+") ? style.ok(line) : line.startsWith("-") ? style.err(line) : style.dim(line));
70
+ }
71
+ io.out("");
72
+ }
73
+ return EXIT_OK;
74
+ }
75
+ function lineDiff(before, after) {
76
+ const a = before.split("\n");
77
+ const b = after.split("\n");
78
+ const out = [];
79
+ const max = Math.max(a.length, b.length);
80
+ for (let i = 0; i < max; i++) {
81
+ if (a[i] === b[i])
82
+ continue;
83
+ if (a[i] !== undefined)
84
+ out.push(`- ${a[i]}`);
85
+ if (b[i] !== undefined)
86
+ out.push(`+ ${b[i]}`);
87
+ }
88
+ return out;
89
+ }
90
+ export const cmdRetarget = { name: "retarget", summary: "Rewrite a link target (plan-by-default)", run: (c, a) => runRetarget(c, a) };
91
+ //# sourceMappingURL=retarget.js.map
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdRun: Command;
@@ -0,0 +1,135 @@
1
+ import { parseArgs } from "node:util";
2
+ import { resolveRef, findDoc, loadDocBlocks, blockRaw, oqxRun, oqxRunAsync, collectSemanticPhrases } from "@omgbase/core";
3
+ import { CliUsageError, EngineErrorLike, truncationFooter, EXIT_OK, renderHelp } from "../output.js";
4
+ import { loadEmbedding } from "./_embed.js";
5
+ // `omg run <locator|path>` (11 §5.3) — evaluate the first ```omg fence in a doc
6
+ // (or the fence at a block locator) and print its results. The fence body is an
7
+ // OQX source string (the same language `omg query` runs). Strictly read-and-print:
8
+ // fences stay INERT in the corpus (ADR-011 §8) — nothing is projected or written.
9
+ function firstOmgFence(roots) {
10
+ for (const n of roots) {
11
+ if (n.type === "code_fence" && n.attrs.lang === "omg")
12
+ return n;
13
+ const inner = firstOmgFence(n.children);
14
+ if (inner)
15
+ return inner;
16
+ }
17
+ return null;
18
+ }
19
+ // Strip the ``` fences from a code-fence block's raw, returning the body.
20
+ function fenceBody(raw) {
21
+ const lines = raw.split("\n");
22
+ if (lines[0]?.trimStart().startsWith("```"))
23
+ lines.shift();
24
+ while (lines.length && lines[lines.length - 1].trim() === "")
25
+ lines.pop();
26
+ if (lines[lines.length - 1]?.trimStart().startsWith("```"))
27
+ lines.pop();
28
+ return lines.join("\n");
29
+ }
30
+ async function runRun(cli, args) {
31
+ const { values, positionals } = parseArgs({ args, allowPositionals: true, options: { help: { type: "boolean" } } });
32
+ if (values.help) {
33
+ return renderHelp(cli, {
34
+ name: "run",
35
+ summary: "Evaluate the ```omg fence (an OQX query) at a locator — or the first fence in a document — and print its results",
36
+ usage: "run <locator|path> [--ids|--json|--jsonl]",
37
+ options: [["<locator|path>", "a fence block's locator, or a document whose first ```omg fence is run"]],
38
+ notes: ["Strictly read-and-print: fences stay inert in the corpus; nothing is projected or written."],
39
+ });
40
+ }
41
+ const ref = positionals[0];
42
+ if (!ref)
43
+ throw new CliUsageError("run requires a <locator|path>");
44
+ const ws = cli.workspace();
45
+ const repo = cli.repo(ws);
46
+ // A block ref runs that fence; a doc/path ref runs the doc's first omg fence.
47
+ const resolved = resolveRef(ws.store, repo.repoId, ref);
48
+ if (!resolved)
49
+ throw new EngineErrorLike("doc_missing", `no node ${ref}`);
50
+ let fenceRaw = null;
51
+ if (resolved.kind === "block") {
52
+ const roots = loadDocBlocks(ws.store, resolved.docId);
53
+ const find = (nodes) => {
54
+ for (const n of nodes) {
55
+ if (n.blockId === resolved.blockId)
56
+ return n;
57
+ const inner = find(n.children);
58
+ if (inner)
59
+ return inner;
60
+ }
61
+ return null;
62
+ };
63
+ const node = find(roots);
64
+ if (!node || node.type !== "code_fence")
65
+ throw new EngineErrorLike("opaque_block", `block ${ref} is not an omg fence`);
66
+ fenceRaw = blockRaw(ws.store, node.rawHashHex);
67
+ }
68
+ else {
69
+ const info = findDoc(ws.store, { docId: resolved.docId });
70
+ void info;
71
+ const fence = firstOmgFence(loadDocBlocks(ws.store, resolved.docId));
72
+ if (!fence)
73
+ throw new EngineErrorLike("target_missing", `no \`\`\`omg fence in ${ref}`);
74
+ fenceRaw = blockRaw(ws.store, fence.rawHashHex);
75
+ }
76
+ const source = fenceBody(fenceRaw).trim();
77
+ if (!source)
78
+ throw new EngineErrorLike("target_missing", `the omg fence in ${ref} is empty`);
79
+ // Same execution path as `omg query`: load the embedder only when the fence uses
80
+ // semantic(...); everything else runs on the sync core.
81
+ const phrases = collectSemanticPhrases(source);
82
+ let result;
83
+ if (phrases.length > 0) {
84
+ const loaded = await loadEmbedding(ws, repo.repoId);
85
+ if (!loaded) {
86
+ throw new EngineErrorLike("semantic_unavailable", "semantic(...) needs an embedding provider", {
87
+ hint: "omg config set embedding.provider <command|url>",
88
+ });
89
+ }
90
+ try {
91
+ const embed = async (t) => ({ model: loaded.provider.model, vec: await loaded.worker.embedQuery(t) });
92
+ result = await oqxRunAsync(ws.store, repo.repoId, source, {}, embed);
93
+ }
94
+ finally {
95
+ await loaded.close();
96
+ }
97
+ }
98
+ else {
99
+ result = oqxRun(ws.store, repo.repoId, source);
100
+ }
101
+ if (cli.flags.mode === "json") {
102
+ cli.io.out(JSON.stringify(result));
103
+ return EXIT_OK;
104
+ }
105
+ if (result.consumer === "count" || result.consumer === "exists" || result.consumer === "none") {
106
+ cli.io.out(result.consumer === "count" ? String(result.count) : result.consumer === "exists" ? String(result.exists) : String(result.none));
107
+ return EXIT_OK;
108
+ }
109
+ if (result.values) {
110
+ for (const v of result.values)
111
+ cli.io.out(typeof v === "string" ? v : JSON.stringify(v));
112
+ if (result.truncated)
113
+ truncationFooter(cli.io, cli.style, result.cursor ?? "");
114
+ return EXIT_OK;
115
+ }
116
+ if (cli.flags.mode === "ids") {
117
+ for (const h of result.hits)
118
+ cli.io.out(h.id);
119
+ return EXIT_OK;
120
+ }
121
+ const { style, io } = cli;
122
+ if (result.hits.length === 0) {
123
+ io.err(style.dim(" no hits"));
124
+ return EXIT_OK;
125
+ }
126
+ for (const h of result.hits) {
127
+ const preview = typeof h.text === "string" ? String(h.text).split("\n")[0] : "";
128
+ io.out(`${style.id(h.id)} ${style.accent(h.path)} ${preview}`.trimEnd());
129
+ }
130
+ if (result.truncated)
131
+ truncationFooter(io, style, result.cursor ?? "");
132
+ return EXIT_OK;
133
+ }
134
+ export const cmdRun = { name: "run", summary: "Evaluate an ```omg fence (OQX, inert, read-only)", run: (c, a) => runRun(c, a) };
135
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdShell: Command;
@@ -0,0 +1,159 @@
1
+ import { parseArgs } from "node:util";
2
+ import { createInterface } from "node:readline";
3
+ import { EXIT_OK, renderHelp } from "../output.js";
4
+ import { ShellSession } from "../shell/session.js";
5
+ // `omg shell` (11 shell) — a persistent in-process session. One open
6
+ // Workspace/store is reused across every command (no per-invocation startup
7
+ // cost), and typed command results become ephemeral bindings (@1/@_/@name). Two
8
+ // drive modes share one ShellSession runtime: an interactive readline REPL on a
9
+ // TTY, and a script runner when stdin is piped (one line per command) — the
10
+ // latter is also what a Markdown CLI-session test would feed.
11
+ async function runShell(cli, args) {
12
+ const { values } = parseArgs({
13
+ args,
14
+ allowPositionals: true,
15
+ options: { help: { type: "boolean" }, prompt: { type: "string" } },
16
+ });
17
+ if (values.help) {
18
+ return renderHelp(cli, {
19
+ name: "shell",
20
+ summary: "A persistent session: one open workspace across commands, and results become typed bindings (@1/@_/@name)",
21
+ usage: "shell [--prompt <str>] [--server <cmd|url>]",
22
+ options: [
23
+ ["--prompt <str>", "emit <str> before reading each piped line so a driver (e.g. recital) can sync on it; also $OMG_SHELL_PROMPT (--prompt wins)"],
24
+ ["--server <cmd|url>", "run every line against a remote engine over MCP"],
25
+ ],
26
+ notes: ["Interactive on a TTY (type `?` for binding help, `exit` to leave); a script runner when stdin is piped (one command per line)."],
27
+ });
28
+ }
29
+ // An explicit prompt string, if any: --prompt wins, else $OMG_SHELL_PROMPT.
30
+ // In piped mode this switches on the prompt-emitting line runner; on a TTY it
31
+ // overrides the interactive prompt. (An empty env value is treated as unset.)
32
+ const promptOpt = values.prompt ?? (process.env.OMG_SHELL_PROMPT || undefined);
33
+ // Local mode: resolve the workspace once and hold it open for the session's
34
+ // lifetime. Remote mode (`--server`): no local store — every line routes over
35
+ // MCP through the shared connection, so we hold no workspace.
36
+ const ws = cli.flags.server ? undefined : cli.workspace();
37
+ const session = new ShellSession({
38
+ ...(ws ? { workspace: ws } : {}),
39
+ cwd: cli.cwd,
40
+ io: cli.io,
41
+ noColor: cli.style.tier === "plain",
42
+ ...(cli.flags.server ? { server: cli.flags.server } : {}),
43
+ ...(cli.flags.headers ? { headers: cli.flags.headers } : {}),
44
+ });
45
+ const interactive = cli.io.stdoutTTY && Boolean(process.stdin.isTTY);
46
+ if (!interactive) {
47
+ // Piped mode. With a prompt configured, act like the interactive REPL for a
48
+ // machine driver: emit the prompt (no trailing newline) before each line so
49
+ // a tool like recital can treat its reappearance as the end-of-command
50
+ // signal. Without one, keep the batch script runner (read all, run all).
51
+ if (promptOpt !== undefined)
52
+ return await runPromptedScript(cli, session, promptOpt);
53
+ const script = await readAll();
54
+ let code = EXIT_OK;
55
+ for (const line of script.split(/\r?\n/)) {
56
+ const c = await session.exec(line);
57
+ if (c !== EXIT_OK)
58
+ code = c;
59
+ if (session.exited)
60
+ break;
61
+ }
62
+ return code;
63
+ }
64
+ // Interactive REPL. A configured prompt overrides the default prompt string.
65
+ const prompt = promptOpt ?? (cli.style.tier === "plain" ? "omg> " : cli.style.accent("omg") + cli.style.dim("> "));
66
+ const rl = createInterface({ input: process.stdin, output: process.stdout, prompt });
67
+ cli.io.err(cli.style.dim(" omg shell — type `?` for session-binding help, `exit` to leave"));
68
+ rl.prompt();
69
+ return await new Promise((resolve) => {
70
+ rl.on("line", (line) => {
71
+ // Pause while a command runs so the next prompt appears after its output.
72
+ rl.pause();
73
+ session
74
+ .exec(line)
75
+ .catch((err) => cli.io.err(cli.style.err(String(err?.message ?? err))))
76
+ .finally(() => {
77
+ if (session.exited) {
78
+ rl.close();
79
+ }
80
+ else {
81
+ rl.resume();
82
+ rl.prompt();
83
+ }
84
+ });
85
+ });
86
+ rl.on("close", () => {
87
+ cli.io.err("");
88
+ resolve(EXIT_OK);
89
+ });
90
+ });
91
+ }
92
+ /**
93
+ * Piped session that emits `promptStr` before each line, so a machine driver
94
+ * (e.g. recital's prompt mode) can sync on the prompt reappearing. The prompt is
95
+ * written straight to stdout with no trailing newline; command output flows
96
+ * through the normal IO in between. Ends on stdin EOF or an `exit` command.
97
+ */
98
+ function runPromptedScript(cli, session, promptStr) {
99
+ const rl = createInterface({ input: process.stdin });
100
+ const queue = [];
101
+ let processing = false;
102
+ let inputClosed = false;
103
+ let code = EXIT_OK;
104
+ return new Promise((resolve) => {
105
+ // Serialize command execution: `readline` can emit several buffered `line`
106
+ // events before any pause takes hold, so run one command fully — output and
107
+ // the next prompt — before starting the next, whatever pace lines arrive at.
108
+ const pump = async () => {
109
+ if (processing)
110
+ return;
111
+ processing = true;
112
+ while (queue.length > 0) {
113
+ const line = queue.shift();
114
+ try {
115
+ const c = await session.exec(line);
116
+ if (c !== EXIT_OK)
117
+ code = c;
118
+ }
119
+ catch (err) {
120
+ cli.io.err(cli.style.err(String(err?.message ?? err)));
121
+ }
122
+ if (session.exited) {
123
+ rl.close();
124
+ resolve(code);
125
+ return;
126
+ }
127
+ process.stdout.write(promptStr);
128
+ }
129
+ processing = false;
130
+ if (inputClosed)
131
+ resolve(code);
132
+ };
133
+ process.stdout.write(promptStr);
134
+ rl.on("line", (line) => {
135
+ queue.push(line);
136
+ void pump();
137
+ });
138
+ rl.on("close", () => {
139
+ inputClosed = true;
140
+ if (!processing && queue.length === 0)
141
+ resolve(code);
142
+ });
143
+ });
144
+ }
145
+ function readAll() {
146
+ return new Promise((resolve) => {
147
+ let buf = "";
148
+ process.stdin.setEncoding("utf8");
149
+ process.stdin.on("data", (c) => (buf += c));
150
+ process.stdin.on("end", () => resolve(buf));
151
+ process.stdin.on("error", () => resolve(buf));
152
+ });
153
+ }
154
+ export const cmdShell = {
155
+ name: "shell",
156
+ summary: "Persistent session with typed bindings (@1/@_/@name)",
157
+ run: (cli, a) => runShell(cli, a),
158
+ };
159
+ //# sourceMappingURL=shell.js.map
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdShow: Command;
@@ -0,0 +1,107 @@
1
+ import { parseArgs } from "node:util";
2
+ import { resolveRef, nodesGet, findDoc, docLinks, historyNode, docPropertiesMerged } from "@omgbase/core";
3
+ import { CliUsageError, EngineErrorLike, EXIT_OK, renderHelp } from "../output.js";
4
+ import { expandBlockArgs } from "./_mutate.js";
5
+ function showOne(cli, ref, include) {
6
+ const ws = cli.workspace();
7
+ const repo = cli.repo(ws);
8
+ const resolved = resolveRef(ws.store, repo.repoId, ref);
9
+ if (!resolved)
10
+ throw new EngineErrorLike("doc_missing", `no node ${ref}`);
11
+ if (resolved.kind === "document") {
12
+ const info = findDoc(ws.store, { docId: resolved.docId });
13
+ const links = docLinks(ws.store, resolved.docId, { direction: "both" });
14
+ const properties = docPropertiesMerged(ws.store.db, resolved.docId);
15
+ const payload = { kind: "document", id: info.docId, path: info.path, properties, edges: links };
16
+ return { payload, render: () => renderDocCard(cli, info.path, properties, links) };
17
+ }
18
+ const node = nodesGet(ws.store, resolved.docId, resolved.blockId, { resolution: "full" });
19
+ if (!node)
20
+ throw new EngineErrorLike("block_missing", `no block ${ref}`);
21
+ const history = include.includes("history") ? historyNode(ws.store, resolved.blockId, { limit: 5 }) : undefined;
22
+ return { payload: { ...node, ...(history ? { history } : {}) }, render: () => renderBlockCard(cli, node, history) };
23
+ }
24
+ function runShow(cli, args) {
25
+ const { values, positionals } = parseArgs({
26
+ args,
27
+ allowPositionals: true,
28
+ options: { include: { type: "string" }, help: { type: "boolean" } },
29
+ });
30
+ if (values.help) {
31
+ return renderHelp(cli, {
32
+ name: "show",
33
+ summary: "Metadata card for a node: attrs, placement, open edges, last change (`cat` is the bytes)",
34
+ usage: "show <node…|-> [--include history]",
35
+ options: [
36
+ ["<node…>", "one or more refs: a doc/block id, a path, or a locator"],
37
+ ["-", `read refs from stdin, one per line (\`${cli.prog} q … --ids | ${cli.prog} show -\`)`],
38
+ ["--include <list>", "comma-separated extras; `history` adds the block's last 5 changes"],
39
+ ],
40
+ });
41
+ }
42
+ const refs = expandBlockArgs(positionals);
43
+ if (refs.length === 0)
44
+ throw new CliUsageError("show requires a <node> (or - to read refs from stdin)");
45
+ const include = (values.include ?? "").split(",").map((s) => s.trim()).filter(Boolean);
46
+ const cards = refs.map((ref) => showOne(cli, ref, include));
47
+ // One ref keeps the single-entity shape (→ @_, frame intact); several become a list.
48
+ const single = cards.length === 1 ? cards[0] : null;
49
+ cli.capture?.(single ? single.payload : cards.map((c) => c.payload));
50
+ if (cli.flags.mode === "json") {
51
+ cli.io.out(JSON.stringify(single ? single.payload : cards.map((c) => c.payload)));
52
+ return EXIT_OK;
53
+ }
54
+ if (cli.flags.mode !== "human") {
55
+ for (const c of cards)
56
+ cli.io.out(JSON.stringify(c.payload));
57
+ return EXIT_OK;
58
+ }
59
+ for (const c of cards)
60
+ c.render();
61
+ return EXIT_OK;
62
+ }
63
+ function renderDocCard(cli, path, fm, links) {
64
+ const { render, style, io } = cli;
65
+ io.out(render.wordmark(path));
66
+ io.out(render.rule(40));
67
+ const keys = Object.keys(fm);
68
+ if (keys.length > 0) {
69
+ io.out(` ${style.dim("properties")}`);
70
+ for (const k of keys)
71
+ io.out(` ${style.accent(k)} ${style.dim("=")} ${fmt(fm[k])}`);
72
+ }
73
+ if (links.out.length > 0) {
74
+ io.out(` ${style.dim("out edges")}`);
75
+ for (const e of links.out)
76
+ io.out(` ${style.accent(e.predicate)} ${style.dim(render.g.arrow)} ${style.id(e.node)} ${style.dim(`×${e.count}`)}`);
77
+ }
78
+ if (links.in.length > 0) {
79
+ io.out(` ${style.dim("backlinks")}`);
80
+ for (const e of links.in)
81
+ io.out(` ${style.id(e.node)} ${style.dim(render.g.arrow)} ${style.accent(e.predicate)} ${style.dim(`×${e.count}`)}`);
82
+ }
83
+ }
84
+ function renderBlockCard(cli, node, history) {
85
+ const { render, style, io } = cli;
86
+ io.out(` ${render.typeGlyph(node.type)} ${style.bold(node.type)} ${style.id(node.id)}`);
87
+ io.out(render.rule(40));
88
+ if (node.placement)
89
+ io.out(` ${style.dim("placement")} parent=${style.id(node.placement.parent ?? "—")} ordinal=${node.placement.ordinal} depth=${node.placement.depth}`);
90
+ if (node.attrs && Object.keys(node.attrs).length > 0) {
91
+ io.out(` ${style.dim("attrs")}`);
92
+ for (const [k, v] of Object.entries(node.attrs))
93
+ io.out(` ${style.accent(k)} ${style.dim("=")} ${fmt(v)}`);
94
+ }
95
+ if (node.text)
96
+ io.out(` ${style.dim("text")} ${node.text}`);
97
+ if (history && history.length > 0) {
98
+ io.out(` ${style.dim("history")}`);
99
+ for (const h of history)
100
+ io.out(` ${style.dim(`#${h.seq}`)} ${style.accent(h.kind)} ${style.dim(h.origin)} ${style.dim(h.ts)}`);
101
+ }
102
+ }
103
+ function fmt(v) {
104
+ return typeof v === "string" ? v : JSON.stringify(v);
105
+ }
106
+ export const cmdShow = { name: "show", summary: "Metadata card for a node", run: (cli, a) => runShow(cli, a) };
107
+ //# sourceMappingURL=show.js.map
@@ -0,0 +1,2 @@
1
+ import type { Command } from "../commands.js";
2
+ export declare const cmdSource: Command;