omgbase 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/src/cmd/_embed.d.ts +55 -0
- package/dist/src/cmd/_embed.js +102 -0
- package/dist/src/cmd/_mutate.d.ts +47 -0
- package/dist/src/cmd/_mutate.js +211 -0
- package/dist/src/cmd/_remote.d.ts +8 -0
- package/dist/src/cmd/_remote.js +61 -0
- package/dist/src/cmd/_source.d.ts +13 -0
- package/dist/src/cmd/_source.js +47 -0
- package/dist/src/cmd/admin.d.ts +6 -0
- package/dist/src/cmd/admin.js +289 -0
- package/dist/src/cmd/bootstrap.d.ts +3 -0
- package/dist/src/cmd/bootstrap.js +252 -0
- package/dist/src/cmd/cat.d.ts +2 -0
- package/dist/src/cmd/cat.js +80 -0
- package/dist/src/cmd/diff.d.ts +2 -0
- package/dist/src/cmd/diff.js +76 -0
- package/dist/src/cmd/docs.d.ts +6 -0
- package/dist/src/cmd/docs.js +154 -0
- package/dist/src/cmd/edit.d.ts +2 -0
- package/dist/src/cmd/edit.js +74 -0
- package/dist/src/cmd/find.d.ts +2 -0
- package/dist/src/cmd/find.js +98 -0
- package/dist/src/cmd/help.d.ts +2 -0
- package/dist/src/cmd/help.js +47 -0
- package/dist/src/cmd/hist.d.ts +2 -0
- package/dist/src/cmd/hist.js +58 -0
- package/dist/src/cmd/links.d.ts +2 -0
- package/dist/src/cmd/links.js +76 -0
- package/dist/src/cmd/log.d.ts +2 -0
- package/dist/src/cmd/log.js +109 -0
- package/dist/src/cmd/ls.d.ts +2 -0
- package/dist/src/cmd/ls.js +88 -0
- package/dist/src/cmd/mcp.d.ts +2 -0
- package/dist/src/cmd/mcp.js +192 -0
- package/dist/src/cmd/mutate.d.ts +10 -0
- package/dist/src/cmd/mutate.js +352 -0
- package/dist/src/cmd/node.d.ts +2 -0
- package/dist/src/cmd/node.js +66 -0
- package/dist/src/cmd/oqx.d.ts +2 -0
- package/dist/src/cmd/oqx.js +193 -0
- package/dist/src/cmd/outline.d.ts +2 -0
- package/dist/src/cmd/outline.js +87 -0
- package/dist/src/cmd/retarget.d.ts +2 -0
- package/dist/src/cmd/retarget.js +91 -0
- package/dist/src/cmd/run.d.ts +2 -0
- package/dist/src/cmd/run.js +135 -0
- package/dist/src/cmd/shell.d.ts +2 -0
- package/dist/src/cmd/shell.js +159 -0
- package/dist/src/cmd/show.d.ts +2 -0
- package/dist/src/cmd/show.js +107 -0
- package/dist/src/cmd/source.d.ts +2 -0
- package/dist/src/cmd/source.js +231 -0
- package/dist/src/cmd/status.d.ts +2 -0
- package/dist/src/cmd/status.js +95 -0
- package/dist/src/cmd/sync.d.ts +2 -0
- package/dist/src/cmd/sync.js +176 -0
- package/dist/src/cmd/update.d.ts +2 -0
- package/dist/src/cmd/update.js +100 -0
- package/dist/src/commands.d.ts +18 -0
- package/dist/src/commands.js +112 -0
- package/dist/src/context.d.ts +73 -0
- package/dist/src/context.js +80 -0
- package/dist/src/dispatch.d.ts +8 -0
- package/dist/src/dispatch.js +148 -0
- package/dist/src/main.d.ts +3 -0
- package/dist/src/main.js +67 -0
- package/dist/src/output.d.ts +60 -0
- package/dist/src/output.js +131 -0
- package/dist/src/render.d.ts +44 -0
- package/dist/src/render.js +116 -0
- package/dist/src/shell/refs.d.ts +37 -0
- package/dist/src/shell/refs.js +111 -0
- package/dist/src/shell/session.d.ts +71 -0
- package/dist/src/shell/session.js +317 -0
- package/dist/src/shell/tokenize.d.ts +3 -0
- package/dist/src/shell/tokenize.js +79 -0
- package/dist/src/style.d.ts +58 -0
- package/dist/src/style.js +159 -0
- package/package.json +63 -0
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { CliUsageError } from "./output.js";
|
|
2
|
+
import { cmdInit, cmdRepos } from "./cmd/bootstrap.js";
|
|
3
|
+
import { cmdStatus } from "./cmd/status.js";
|
|
4
|
+
import { cmdLs } from "./cmd/ls.js";
|
|
5
|
+
import { cmdOutline } from "./cmd/outline.js";
|
|
6
|
+
import { cmdCat } from "./cmd/cat.js";
|
|
7
|
+
import { cmdShow } from "./cmd/show.js";
|
|
8
|
+
import { cmdFind } from "./cmd/find.js";
|
|
9
|
+
import { cmdOqx } from "./cmd/oqx.js";
|
|
10
|
+
import { cmdLog } from "./cmd/log.js";
|
|
11
|
+
import { cmdHist } from "./cmd/hist.js";
|
|
12
|
+
import { cmdDiff } from "./cmd/diff.js";
|
|
13
|
+
import { cmdLinks } from "./cmd/links.js";
|
|
14
|
+
import { cmdSync } from "./cmd/sync.js";
|
|
15
|
+
import { cmdMcp } from "./cmd/mcp.js";
|
|
16
|
+
import { cmdApply, cmdInsert, cmdMove, cmdRm, cmdDone, cmdAppend, cmdSplit, cmdMerge } from "./cmd/mutate.js";
|
|
17
|
+
import { cmdEdit } from "./cmd/edit.js";
|
|
18
|
+
import { cmdRetarget } from "./cmd/retarget.js";
|
|
19
|
+
import { cmdNode } from "./cmd/node.js";
|
|
20
|
+
import { cmdNew, cmdMv, cmdMeta } from "./cmd/docs.js";
|
|
21
|
+
import { cmdUpdateDoc } from "./cmd/update.js";
|
|
22
|
+
import { cmdRun } from "./cmd/run.js";
|
|
23
|
+
import { cmdRebuild, cmdGc, cmdDoctor, cmdConfig, cmdEmbed } from "./cmd/admin.js";
|
|
24
|
+
import { cmdShell } from "./cmd/shell.js";
|
|
25
|
+
import { cmdSource } from "./cmd/source.js";
|
|
26
|
+
import { cmdHelp } from "./cmd/help.js";
|
|
27
|
+
export const COMMANDS = [
|
|
28
|
+
cmdInit,
|
|
29
|
+
cmdRepos,
|
|
30
|
+
cmdStatus,
|
|
31
|
+
cmdLs,
|
|
32
|
+
cmdOutline,
|
|
33
|
+
cmdCat,
|
|
34
|
+
cmdShow,
|
|
35
|
+
cmdFind,
|
|
36
|
+
cmdOqx,
|
|
37
|
+
cmdLog,
|
|
38
|
+
cmdHist,
|
|
39
|
+
cmdDiff,
|
|
40
|
+
cmdLinks,
|
|
41
|
+
cmdApply,
|
|
42
|
+
cmdInsert,
|
|
43
|
+
cmdUpdateDoc,
|
|
44
|
+
cmdEdit,
|
|
45
|
+
cmdMove,
|
|
46
|
+
cmdRm,
|
|
47
|
+
cmdDone,
|
|
48
|
+
cmdAppend,
|
|
49
|
+
cmdRetarget,
|
|
50
|
+
cmdNode,
|
|
51
|
+
cmdSplit,
|
|
52
|
+
cmdMerge,
|
|
53
|
+
cmdNew,
|
|
54
|
+
cmdMv,
|
|
55
|
+
cmdMeta,
|
|
56
|
+
cmdRun,
|
|
57
|
+
cmdShell,
|
|
58
|
+
cmdSource,
|
|
59
|
+
cmdSync,
|
|
60
|
+
cmdMcp,
|
|
61
|
+
cmdRebuild,
|
|
62
|
+
cmdGc,
|
|
63
|
+
cmdDoctor,
|
|
64
|
+
cmdConfig,
|
|
65
|
+
cmdEmbed,
|
|
66
|
+
cmdHelp,
|
|
67
|
+
];
|
|
68
|
+
const BY_NAME = new Map();
|
|
69
|
+
for (const c of COMMANDS) {
|
|
70
|
+
BY_NAME.set(c.name, c);
|
|
71
|
+
for (const a of c.aliases ?? [])
|
|
72
|
+
BY_NAME.set(a, c);
|
|
73
|
+
}
|
|
74
|
+
export function resolveCommand(name) {
|
|
75
|
+
return BY_NAME.get(name) ?? null;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The usage error for an unrecognized command: names the typo, points at the
|
|
79
|
+
* command list (quoting the binary as it was invoked), and — when one listed
|
|
80
|
+
* command or alias is within two edits — offers it as "did you mean".
|
|
81
|
+
*/
|
|
82
|
+
export function unknownCommandError(prog, name) {
|
|
83
|
+
const hint = `run '${prog} --help' for the command list`;
|
|
84
|
+
const guess = didYouMean(name);
|
|
85
|
+
return new CliUsageError(`unknown command '${name}'`, guess ? `did you mean '${guess}'? ${hint}` : hint);
|
|
86
|
+
}
|
|
87
|
+
function didYouMean(name) {
|
|
88
|
+
// Two edits for a real word, one for something short — `statsu` → status,
|
|
89
|
+
// `lz` → ls, but a lone `x` suggests nothing rather than `q`.
|
|
90
|
+
const maxEdits = name.length <= 3 ? 1 : 2;
|
|
91
|
+
let best = null;
|
|
92
|
+
for (const candidate of BY_NAME.keys()) {
|
|
93
|
+
const d = editDistance(name.toLowerCase(), candidate);
|
|
94
|
+
if (d <= maxEdits && (!best || d < best.d))
|
|
95
|
+
best = { name: candidate, d };
|
|
96
|
+
}
|
|
97
|
+
return best?.name ?? null;
|
|
98
|
+
}
|
|
99
|
+
// Levenshtein distance; the inputs are short command names, so the plain
|
|
100
|
+
// two-row DP is plenty.
|
|
101
|
+
function editDistance(a, b) {
|
|
102
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
103
|
+
for (let i = 1; i <= a.length; i++) {
|
|
104
|
+
const cur = [i];
|
|
105
|
+
for (let j = 1; j <= b.length; j++) {
|
|
106
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
107
|
+
}
|
|
108
|
+
prev = cur;
|
|
109
|
+
}
|
|
110
|
+
return prev[b.length];
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=commands.js.map
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Workspace, type RepoRow } from "@omgbase/core";
|
|
2
|
+
import { Style } from "./style.js";
|
|
3
|
+
import { Renderer, type IO } from "./render.js";
|
|
4
|
+
import { CliUsageError } from "./output.js";
|
|
5
|
+
export type OutputMode = "human" | "json" | "jsonl" | "ids";
|
|
6
|
+
export interface GlobalFlags {
|
|
7
|
+
directory?: string;
|
|
8
|
+
repo?: string;
|
|
9
|
+
mode: OutputMode;
|
|
10
|
+
stale: boolean;
|
|
11
|
+
noColor: boolean;
|
|
12
|
+
dryRun: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* --server <cmd|url>: run the command against a remote engine over MCP instead
|
|
15
|
+
* of the embedded local store (ADR-014). Global by design — the surface is
|
|
16
|
+
* uniform and commands adopt remote mode one at a time (REMOTE_OK). Reads,
|
|
17
|
+
* doc-level mutators, block-level sugar (via server-side ref resolution + CAS),
|
|
18
|
+
* sync, and shell all implement it; the handful of genuinely local-only ops
|
|
19
|
+
* (e.g. `status`'s watcher state, `node props`) error rather than run locally.
|
|
20
|
+
*/
|
|
21
|
+
server?: string;
|
|
22
|
+
/**
|
|
23
|
+
* -H / --header "Name: value": extra HTTP headers, repeatable, sent on every
|
|
24
|
+
* request when `--server` is an http(s) URL (auth beyond a secret path). Kept
|
|
25
|
+
* in flag order; ignored for stdio `--server` commands.
|
|
26
|
+
*/
|
|
27
|
+
headers?: string[];
|
|
28
|
+
help: boolean;
|
|
29
|
+
version: boolean;
|
|
30
|
+
}
|
|
31
|
+
export interface Cli {
|
|
32
|
+
flags: GlobalFlags;
|
|
33
|
+
io: IO;
|
|
34
|
+
style: Style;
|
|
35
|
+
render: Renderer;
|
|
36
|
+
cwd: string;
|
|
37
|
+
/**
|
|
38
|
+
* The name the binary was invoked as (`omg` or `omgbase` — the two `bin`
|
|
39
|
+
* entries), used wherever help/hints quote a command back to the user so the
|
|
40
|
+
* text matches what they actually typed. Defaults to `omg`.
|
|
41
|
+
*/
|
|
42
|
+
prog: string;
|
|
43
|
+
/**
|
|
44
|
+
* Optional typed-result sink (11 shell). When present, a command calls it with
|
|
45
|
+
* its structured library result — the same object `--json` would emit — before
|
|
46
|
+
* terminal formatting. The interactive shell installs a sink to capture typed
|
|
47
|
+
* bindings (`@_`, `@1`, `let x = …`); one-shot CLI leaves it undefined (no-op).
|
|
48
|
+
*/
|
|
49
|
+
capture?: (value: unknown) => void;
|
|
50
|
+
/** Resolve the workspace (walk up from cwd/-C). Throws repo_not_found if none. */
|
|
51
|
+
workspace(): Workspace;
|
|
52
|
+
/** Resolve the active repo within the workspace, honoring --repo. */
|
|
53
|
+
repo(ws: Workspace): RepoRow;
|
|
54
|
+
}
|
|
55
|
+
/** Optional overrides for makeCli — the shell reuses one open Workspace across
|
|
56
|
+
* commands and installs a capture sink; one-shot CLI passes nothing. */
|
|
57
|
+
export interface CliOptions {
|
|
58
|
+
workspace?: Workspace;
|
|
59
|
+
capture?: (value: unknown) => void;
|
|
60
|
+
cwd?: string;
|
|
61
|
+
prog?: string;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The name to quote the binary as, from `process.argv[1]`: the basename when
|
|
65
|
+
* invoked through a `bin` shim (`omg`, `omgbase`), else `omg` — a direct
|
|
66
|
+
* `node dist/src/main.js` run would otherwise print "main.js".
|
|
67
|
+
*/
|
|
68
|
+
export declare function progName(argv1: string | undefined): string;
|
|
69
|
+
export declare function makeCli(flags: GlobalFlags, io: IO, opts?: CliOptions): Cli;
|
|
70
|
+
export declare const NO_WORKSPACE_OK: Set<string>;
|
|
71
|
+
export declare const SKIP_FRESHNESS: Set<string>;
|
|
72
|
+
export declare const REMOTE_OK: Set<string>;
|
|
73
|
+
export { CliUsageError };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { basename, resolve } from "node:path";
|
|
2
|
+
import { Workspace, RepoSelectionError } from "@omgbase/core";
|
|
3
|
+
import { Style } from "./style.js";
|
|
4
|
+
import { Renderer } from "./render.js";
|
|
5
|
+
import { CliUsageError, EngineErrorLike } from "./output.js";
|
|
6
|
+
/**
|
|
7
|
+
* The name to quote the binary as, from `process.argv[1]`: the basename when
|
|
8
|
+
* invoked through a `bin` shim (`omg`, `omgbase`), else `omg` — a direct
|
|
9
|
+
* `node dist/src/main.js` run would otherwise print "main.js".
|
|
10
|
+
*/
|
|
11
|
+
export function progName(argv1) {
|
|
12
|
+
if (!argv1)
|
|
13
|
+
return "omg";
|
|
14
|
+
const b = basename(argv1);
|
|
15
|
+
return /\.(c|m)?js$/.test(b) || b.length === 0 ? "omg" : b;
|
|
16
|
+
}
|
|
17
|
+
export function makeCli(flags, io, opts = {}) {
|
|
18
|
+
const cwd = opts.cwd ?? (flags.directory ? resolveDir(flags.directory) : process.cwd());
|
|
19
|
+
const noColor = flags.noColor || !io.stdoutTTY || process.env.NO_COLOR != null;
|
|
20
|
+
const style = new Style({ noColor, isTTY: io.stdoutTTY });
|
|
21
|
+
const render = new Renderer(style);
|
|
22
|
+
const prog = opts.prog ?? "omg";
|
|
23
|
+
let cachedWs = opts.workspace ?? null;
|
|
24
|
+
return {
|
|
25
|
+
flags,
|
|
26
|
+
io,
|
|
27
|
+
style,
|
|
28
|
+
render,
|
|
29
|
+
cwd,
|
|
30
|
+
prog,
|
|
31
|
+
...(opts.capture ? { capture: opts.capture } : {}),
|
|
32
|
+
workspace() {
|
|
33
|
+
if (cachedWs)
|
|
34
|
+
return cachedWs;
|
|
35
|
+
const ws = Workspace.find(cwd);
|
|
36
|
+
if (!ws) {
|
|
37
|
+
throw new EngineErrorLike("repo_not_found", `no omgbase workspace found at or above ${cwd}`, { hint: `run \`${prog} init\` to create one here, or \`${prog} -C <dir> …\` to run inside an existing workspace` });
|
|
38
|
+
}
|
|
39
|
+
cachedWs = ws;
|
|
40
|
+
return ws;
|
|
41
|
+
},
|
|
42
|
+
repo(ws) {
|
|
43
|
+
try {
|
|
44
|
+
return ws.selectRepo(cwd, flags.repo);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
if (err instanceof RepoSelectionError) {
|
|
48
|
+
throw new EngineErrorLike(err.code, err.message, { data: { candidates: err.candidates } });
|
|
49
|
+
}
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function resolveDir(dir) {
|
|
56
|
+
return resolve(dir);
|
|
57
|
+
}
|
|
58
|
+
// Which commands may run without a workspace (11 §2.1).
|
|
59
|
+
export const NO_WORKSPACE_OK = new Set(["init", "help", "version"]);
|
|
60
|
+
// Commands that manage sync themselves — skip the freshness sweep (11 §3.3).
|
|
61
|
+
// `shell` is exempt because each line it runs sweeps on its own. `source`
|
|
62
|
+
// creates/binds sources and runs its own initial sync, so it skips the pre-sweep.
|
|
63
|
+
export const SKIP_FRESHNESS = new Set(["sync", "mcp", "init", "source", "help", "version", "shell"]);
|
|
64
|
+
// Commands that implement the global `--server` remote (MCP-client) mode
|
|
65
|
+
// (ADR-014). Others error on `--server` rather than silently running locally.
|
|
66
|
+
// Covers reads, doc-level mutators, block-level sugar, sync, and shell — the
|
|
67
|
+
// block sugar resolves refs + pins CAS server-side via the blocks_* tools.
|
|
68
|
+
export const REMOTE_OK = new Set([
|
|
69
|
+
// reads
|
|
70
|
+
"sync", "query", "outline", "hist", "cat", "ls", "diff", "find", "log",
|
|
71
|
+
// doc-level mutators
|
|
72
|
+
"new", "mv", "meta", "rm", "update", "retarget",
|
|
73
|
+
// block-level sugar (server-side ref resolution + CAS via the blocks_* /
|
|
74
|
+
// tasks_complete / sections_append / node_set / apply tools)
|
|
75
|
+
"apply", "insert", "move", "split", "merge", "done", "append", "node",
|
|
76
|
+
// shell accepts --server itself, then threads it into every line it runs.
|
|
77
|
+
"shell",
|
|
78
|
+
]);
|
|
79
|
+
export { CliUsageError };
|
|
80
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Cli, type GlobalFlags } from "./context.js";
|
|
2
|
+
export interface Parsed {
|
|
3
|
+
flags: GlobalFlags;
|
|
4
|
+
command: string | null;
|
|
5
|
+
rest: string[];
|
|
6
|
+
}
|
|
7
|
+
export declare function parseGlobals(argv: string[]): Parsed;
|
|
8
|
+
export declare function runCommand(cli: Cli, command: string, rest: string[]): Promise<number>;
|
|
@@ -0,0 +1,148 @@
|
|
|
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";
|
|
4
|
+
import { resolveCommand, unknownCommandError } from "./commands.js";
|
|
5
|
+
export function parseGlobals(argv) {
|
|
6
|
+
const flags = {
|
|
7
|
+
mode: "human",
|
|
8
|
+
stale: false,
|
|
9
|
+
noColor: false,
|
|
10
|
+
dryRun: false,
|
|
11
|
+
help: false,
|
|
12
|
+
version: false,
|
|
13
|
+
};
|
|
14
|
+
let command = null;
|
|
15
|
+
const rest = [];
|
|
16
|
+
let passthrough = false;
|
|
17
|
+
for (let i = 0; i < argv.length; i++) {
|
|
18
|
+
const a = argv[i];
|
|
19
|
+
if (passthrough) {
|
|
20
|
+
rest.push(a);
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
if (a === "--") {
|
|
24
|
+
passthrough = true;
|
|
25
|
+
if (command)
|
|
26
|
+
rest.push(a);
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
switch (a) {
|
|
30
|
+
case "-C":
|
|
31
|
+
case "--directory": {
|
|
32
|
+
const dir = argv[++i];
|
|
33
|
+
if (dir === undefined)
|
|
34
|
+
throw new CliUsageError(`${a} requires a directory`);
|
|
35
|
+
flags.directory = dir;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
case "--repo": {
|
|
39
|
+
const slug = argv[++i];
|
|
40
|
+
if (slug === undefined)
|
|
41
|
+
throw new CliUsageError("--repo requires a slug");
|
|
42
|
+
flags.repo = slug;
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
case "--server": {
|
|
46
|
+
const s = argv[++i];
|
|
47
|
+
if (s === undefined)
|
|
48
|
+
throw new CliUsageError("--server requires a command or url");
|
|
49
|
+
flags.server = s;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
case "-H":
|
|
53
|
+
case "--header": {
|
|
54
|
+
const h = argv[++i];
|
|
55
|
+
if (h === undefined)
|
|
56
|
+
throw new CliUsageError(`${a} requires a "Name: value" header`);
|
|
57
|
+
(flags.headers ??= []).push(h);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
case "--json":
|
|
61
|
+
flags.mode = "json";
|
|
62
|
+
continue;
|
|
63
|
+
case "--jsonl":
|
|
64
|
+
flags.mode = "jsonl";
|
|
65
|
+
continue;
|
|
66
|
+
case "--ids":
|
|
67
|
+
flags.mode = "ids";
|
|
68
|
+
continue;
|
|
69
|
+
case "--stale":
|
|
70
|
+
flags.stale = true;
|
|
71
|
+
continue;
|
|
72
|
+
case "--no-color":
|
|
73
|
+
flags.noColor = true;
|
|
74
|
+
continue;
|
|
75
|
+
case "--dry-run":
|
|
76
|
+
flags.dryRun = true;
|
|
77
|
+
continue;
|
|
78
|
+
case "--help":
|
|
79
|
+
case "-h":
|
|
80
|
+
flags.help = true;
|
|
81
|
+
continue;
|
|
82
|
+
case "--version":
|
|
83
|
+
case "-V":
|
|
84
|
+
flags.version = true;
|
|
85
|
+
continue;
|
|
86
|
+
default:
|
|
87
|
+
if (!command && !a.startsWith("-")) {
|
|
88
|
+
command = a;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
// Once the command is known, anything unrecognized is its residual;
|
|
92
|
+
// a stray flag before any command is a usage error.
|
|
93
|
+
if (!command)
|
|
94
|
+
throw new CliUsageError(`unknown flag ${a}`);
|
|
95
|
+
rest.push(a);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { flags, command, rest };
|
|
99
|
+
}
|
|
100
|
+
// Per-command execution shared by the one-shot entry (main.ts) and the
|
|
101
|
+
// interactive shell (cmd/shell.ts). Given a resolved Cli, a command name, and
|
|
102
|
+
// its residual argv, this runs the freshness sweep (11 §3.3), dispatches the
|
|
103
|
+
// command, and maps thrown errors to exit codes via the output contract. The
|
|
104
|
+
// shell reuses this so a shell line behaves exactly like a one-shot invocation.
|
|
105
|
+
export async function runCommand(cli, command, rest) {
|
|
106
|
+
const resolved = resolveCommand(command);
|
|
107
|
+
if (!resolved) {
|
|
108
|
+
return renderError(unknownCommandError(cli.prog, command), cli.io, cli.style, cli.flags.mode !== "human");
|
|
109
|
+
}
|
|
110
|
+
// `--help` after a command → route to help for that command. Help is
|
|
111
|
+
// documentation, not work: it must never need a workspace, so it also skips
|
|
112
|
+
// the freshness sweep below (which would otherwise fail with repo_not_found
|
|
113
|
+
// from any directory without a `.omgbase/`).
|
|
114
|
+
const args = cli.flags.help ? ["--help", ...rest] : rest;
|
|
115
|
+
// Global `--server` (remote/MCP mode) is only implemented by some commands so
|
|
116
|
+
// far (REMOTE_OK); reject it elsewhere rather than silently running locally.
|
|
117
|
+
if (cli.flags.server !== undefined && !cli.flags.help && !REMOTE_OK.has(resolved.name)) {
|
|
118
|
+
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
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
// Freshness sweep (11 §3.3): current-by-default, unless --stale, a live
|
|
122
|
+
// watcher holds the lease, remote (`--server`) mode (no local tree to sweep),
|
|
123
|
+
// or the command manages sync itself.
|
|
124
|
+
if (!cli.flags.help && !cli.flags.stale && cli.flags.server === undefined && !SKIP_FRESHNESS.has(resolved.name) && !NO_WORKSPACE_OK.has(resolved.name)) {
|
|
125
|
+
const ws = cli.workspace();
|
|
126
|
+
if (!watchLeaseLive(ws.omgbaseDir)) {
|
|
127
|
+
// Best-effort: a workspace with no attached repo (or an unresolvable
|
|
128
|
+
// cwd) has nothing to sweep. That's not an error for the command
|
|
129
|
+
// itself — only the sweep is skipped; the command surfaces its own
|
|
130
|
+
// repo_not_found if it truly needs a repo.
|
|
131
|
+
try {
|
|
132
|
+
const repo = cli.repo(ws);
|
|
133
|
+
if (repo.rootPath)
|
|
134
|
+
freshnessSweep(ws.store, repo.repoId, repo.rootPath);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (!(err instanceof EngineErrorLike && err.code === "repo_not_found"))
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
return await resolved.run(cli, args);
|
|
143
|
+
}
|
|
144
|
+
catch (err) {
|
|
145
|
+
return renderError(err, cli.io, cli.style, cli.flags.mode !== "human");
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=dispatch.js.map
|
package/dist/src/main.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { VERSION } from "@omgbase/core";
|
|
3
|
+
import { processIO } from "./render.js";
|
|
4
|
+
import { makeCli, progName } from "./context.js";
|
|
5
|
+
import { renderError, EXIT_OK, EXIT_USAGE } from "./output.js";
|
|
6
|
+
import { resolveCommand, unknownCommandError } from "./commands.js";
|
|
7
|
+
import { runCommand, parseGlobals } from "./dispatch.js";
|
|
8
|
+
import { closeRemote } from "./cmd/_remote.js";
|
|
9
|
+
// Hand-rolled router (11 §8: no commander). Splits global flags from the
|
|
10
|
+
// command + its residual argv (parseGlobals, in dispatch.ts), resolves the
|
|
11
|
+
// command (with aliases), then delegates to runCommand for the freshness sweep +
|
|
12
|
+
// dispatch + error mapping — the same path the shell reuses per line.
|
|
13
|
+
export async function run(argv, io = processIO, prog = "omg") {
|
|
14
|
+
let parsed;
|
|
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
|
+
}
|
|
23
|
+
const { flags } = parsed;
|
|
24
|
+
let command = parsed.command;
|
|
25
|
+
// --version / --help without a command.
|
|
26
|
+
if (flags.version && !command) {
|
|
27
|
+
io.out(VERSION);
|
|
28
|
+
return EXIT_OK;
|
|
29
|
+
}
|
|
30
|
+
if (flags.help && !command)
|
|
31
|
+
command = "help";
|
|
32
|
+
if (!command)
|
|
33
|
+
command = "help";
|
|
34
|
+
const cli = makeCli(flags, io, { prog });
|
|
35
|
+
if (!resolveCommand(command)) {
|
|
36
|
+
return renderError(unknownCommandError(prog, command), io, cli.style, flags.mode !== "human");
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
return await runCommand(cli, command, parsed.rest);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
// Tear down the remote (`--server`) MCP connection if the command opened one.
|
|
43
|
+
await closeRemote();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// Entry point.
|
|
47
|
+
//
|
|
48
|
+
// Exit quietly when a downstream consumer closes our stdout early (`omg … | head`,
|
|
49
|
+
// or `… --ids | omg done -` where `done` stops reading): a broken-pipe write
|
|
50
|
+
// otherwise surfaces as an unhandled 'error' event and crashes with a stack
|
|
51
|
+
// trace. Standard pipe-friendly behavior for a CLI that advertises composing
|
|
52
|
+
// with grep/head/xargs (§1).
|
|
53
|
+
for (const stream of [process.stdout, process.stderr]) {
|
|
54
|
+
stream.on("error", (err) => {
|
|
55
|
+
if (err.code === "EPIPE")
|
|
56
|
+
process.exit(0);
|
|
57
|
+
throw err;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// Quote the binary back to the user as they invoked it (`omg` vs `omgbase`).
|
|
61
|
+
run(process.argv.slice(2), processIO, progName(process.argv[1])).then((code) => {
|
|
62
|
+
process.exitCode = code;
|
|
63
|
+
}, (err) => {
|
|
64
|
+
process.stderr.write(String(err?.stack ?? err) + "\n");
|
|
65
|
+
process.exitCode = 1;
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=main.js.map
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IO } from "./render.js";
|
|
2
|
+
import type { Style } from "./style.js";
|
|
3
|
+
export declare const EXIT_OK = 0;
|
|
4
|
+
export declare const EXIT_ERROR = 1;
|
|
5
|
+
export declare const EXIT_USAGE = 2;
|
|
6
|
+
/** A CLI-level usage error (bad flag, missing argument) → exit 2. An optional
|
|
7
|
+
* `hint` is a one-line pointer at the fix (e.g. "run 'omg --help' …"). */
|
|
8
|
+
export declare class CliUsageError extends Error {
|
|
9
|
+
readonly hint?: string | undefined;
|
|
10
|
+
constructor(message: string, hint?: string | undefined);
|
|
11
|
+
}
|
|
12
|
+
export interface HelpSpec {
|
|
13
|
+
name: string;
|
|
14
|
+
summary: string;
|
|
15
|
+
/** Usage form(s) WITHOUT the program name (`cat <node…|-> [--resolution r]`). */
|
|
16
|
+
usage: string | string[];
|
|
17
|
+
/** `[flag, description]` rows; `-h, --help` is appended automatically. */
|
|
18
|
+
options?: [string, string][];
|
|
19
|
+
/** Extra lines printed verbatim under the options (examples, sub-commands). */
|
|
20
|
+
notes?: string[];
|
|
21
|
+
}
|
|
22
|
+
interface HelpIO {
|
|
23
|
+
io: IO;
|
|
24
|
+
style: Style;
|
|
25
|
+
prog: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function renderHelp(cli: HelpIO, spec: HelpSpec): number;
|
|
28
|
+
/**
|
|
29
|
+
* An engine-style typed error the CLI raises directly (mirrors core's
|
|
30
|
+
* EngineError body shape, 06 §5) — used for repo_not_found, ambiguous repo,
|
|
31
|
+
* etc., that originate in the CLI layer rather than the engine.
|
|
32
|
+
*/
|
|
33
|
+
export declare class EngineErrorLike extends Error {
|
|
34
|
+
readonly code: string;
|
|
35
|
+
readonly extra: {
|
|
36
|
+
data?: unknown;
|
|
37
|
+
hint?: string;
|
|
38
|
+
retriable?: boolean;
|
|
39
|
+
};
|
|
40
|
+
constructor(code: string, message: string, extra?: {
|
|
41
|
+
data?: unknown;
|
|
42
|
+
hint?: string;
|
|
43
|
+
retriable?: boolean;
|
|
44
|
+
});
|
|
45
|
+
body(): {
|
|
46
|
+
error: string;
|
|
47
|
+
message: string;
|
|
48
|
+
data?: unknown;
|
|
49
|
+
retriable: boolean;
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Render a thrown error to stderr and return the process exit code. `json`
|
|
54
|
+
* selects machine output (typed object); otherwise the human form with any
|
|
55
|
+
* current-truth payload pretty-printed.
|
|
56
|
+
*/
|
|
57
|
+
export declare function renderError(err: unknown, io: IO, style: Style, json: boolean): number;
|
|
58
|
+
/** Loud truncation footer to stderr (§4.4). Exit code stays 0. */
|
|
59
|
+
export declare function truncationFooter(io: IO, style: Style, cursor: number | string): void;
|
|
60
|
+
export {};
|