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,317 @@
1
+ import { Style } from "../style.js";
2
+ import { makeCli } from "../context.js";
3
+ import { CliUsageError, EXIT_OK, EXIT_ERROR, EXIT_USAGE, renderError } from "../output.js";
4
+ import { runCommand, parseGlobals } from "../dispatch.js";
5
+ import { tokenize, TokenizeError } from "./tokenize.js";
6
+ import { parseRef, deriveRows, coerce, RefError, } from "./refs.js";
7
+ export class ShellSession {
8
+ workspace;
9
+ cwd;
10
+ io;
11
+ style;
12
+ server;
13
+ headers;
14
+ bindings = new Map();
15
+ frame = null;
16
+ last;
17
+ /** Set by `exit`/`quit`; the interactive loop watches this. */
18
+ exited = false;
19
+ constructor(opts) {
20
+ this.workspace = opts.workspace;
21
+ this.cwd = opts.cwd;
22
+ this.io = opts.io;
23
+ this.server = opts.server;
24
+ this.headers = opts.headers;
25
+ const noColor = opts.noColor ?? !opts.io.stdoutTTY;
26
+ this.style = new Style({ noColor, isTTY: opts.io.stdoutTTY });
27
+ }
28
+ /** Execute one input line. Returns an exit code (0 ok, 1 error, 2 usage). */
29
+ async exec(line) {
30
+ const trimmed = line.trim();
31
+ if (trimmed === "" || trimmed.startsWith("#"))
32
+ return EXIT_OK; // blank / comment
33
+ let tokens;
34
+ try {
35
+ tokens = tokenize(line);
36
+ }
37
+ catch (err) {
38
+ if (err instanceof TokenizeError)
39
+ return this.usage(err.message);
40
+ throw err;
41
+ }
42
+ if (tokens.length === 0)
43
+ return EXIT_OK;
44
+ try {
45
+ const head = tokens[0];
46
+ switch (head) {
47
+ case "exit":
48
+ case "quit":
49
+ this.exited = true;
50
+ return EXIT_OK;
51
+ case "unset":
52
+ return this.doUnset(tokens.slice(1));
53
+ case "bindings":
54
+ return this.doBindings();
55
+ case "?":
56
+ return this.doShellHelp();
57
+ default: {
58
+ const ref = parseRef(head); // null unless `@…`; throws on a malformed `@…`
59
+ // `@name = <command | @ref>` binds a snapshot (the command runs
60
+ // quietly). A bare reference on its own line inspects the value (and,
61
+ // if it's a collection, makes it the addressable frame). Anything else
62
+ // is a normal omg command line with `@refs` substituted into its argv.
63
+ if (ref && tokens[1] === "=")
64
+ return await this.doAssign(ref, tokens.slice(2));
65
+ if (ref && tokens.length === 1)
66
+ return this.inspect(head);
67
+ return await this.runLine(tokens);
68
+ }
69
+ }
70
+ }
71
+ catch (err) {
72
+ if (err instanceof RefError || err instanceof CliUsageError || err instanceof TokenizeError) {
73
+ return this.usage(err.message);
74
+ }
75
+ return renderError(err, this.io, this.style, false);
76
+ }
77
+ }
78
+ // ---- builtins -------------------------------------------------------------
79
+ /**
80
+ * `@name = <command | @ref>` — bind a snapshot of a result under `name`.
81
+ * A command runs quietly (stdout suppressed); a bare `@ref` copies its value.
82
+ * The target must be a plain name (no `[i]`/`.field`, not `@_`/`@N`).
83
+ */
84
+ async doAssign(target, rhs) {
85
+ const name = target.base;
86
+ if (target.index !== undefined || target.field !== undefined) {
87
+ return this.usage("cannot assign to @name[i] or @name.field — bind a whole result to @name");
88
+ }
89
+ if (!/^[A-Za-z][\w-]*$/.test(name) || name === "_") {
90
+ return this.usage(`bad binding name '@${name}' (use a letter-led identifier)`);
91
+ }
92
+ if (rhs.length === 0)
93
+ return this.usage("@name = <command | @ref>");
94
+ let captured;
95
+ if (rhs.length === 1 && parseRef(rhs[0])) {
96
+ // @x = @ref — snapshot the referenced value.
97
+ const { value } = this.resolveRef(rhs[0]);
98
+ captured = { value, rows: deriveRows(value) };
99
+ }
100
+ else {
101
+ // @x = <command> — run it quietly (stdout suppressed) and snapshot the
102
+ // typed result. Diagnostics/errors still reach the user via stderr.
103
+ const substituted = this.substitute(rhs);
104
+ let value;
105
+ const code = await this.dispatch(substituted, (v) => (value = v), true);
106
+ if (code !== EXIT_OK)
107
+ return code; // command failed → don't bind
108
+ captured = { value, rows: deriveRows(value) };
109
+ }
110
+ this.bindings.set(name, captured);
111
+ this.last = { value: captured.value };
112
+ this.io.err(this.style.dim(` @${name} = ${this.summary(captured)}`));
113
+ return EXIT_OK;
114
+ }
115
+ doUnset(rest) {
116
+ const name = rest[0];
117
+ if (!name)
118
+ return this.usage("unset <name>");
119
+ if (this.bindings.delete(name)) {
120
+ this.io.err(this.style.dim(` unset @${name}`));
121
+ }
122
+ else {
123
+ this.io.err(this.style.dim(` no binding @${name}`));
124
+ }
125
+ return EXIT_OK;
126
+ }
127
+ doBindings() {
128
+ if (this.bindings.size === 0) {
129
+ this.io.err(this.style.dim(" no bindings"));
130
+ return EXIT_OK;
131
+ }
132
+ for (const [name, cap] of this.bindings) {
133
+ this.io.out(`${this.style.accent(`@${name}`)} ${this.style.dim(this.summary(cap))}`);
134
+ }
135
+ return EXIT_OK;
136
+ }
137
+ doShellHelp() {
138
+ const { io, style } = this;
139
+ const line = (s) => io.out(" " + s);
140
+ io.out(style.bold(" omg shell — session bindings"));
141
+ line(style.dim("run any omg command; results become addressable:"));
142
+ line(`${style.accent("@1 @2 …")} ${style.dim("rows of the last displayed collection")}`);
143
+ line(`${style.accent("@_")} ${style.dim("the previous command's result")}`);
144
+ line(`${style.accent("@name")} ${style.dim("a named binding (also @name[i], @name.field)")}`);
145
+ line("");
146
+ line(`${style.accent("@x = <cmd|@ref>")} ${style.dim("bind a snapshot of a result")}`);
147
+ line(`${style.accent("unset x")} ${style.dim("drop a binding")}`);
148
+ line(`${style.accent("bindings")} ${style.dim("list bindings")}`);
149
+ line(`${style.accent("exit")} / ${style.accent("quit")} ${style.dim("leave the shell")}`);
150
+ return EXIT_OK;
151
+ }
152
+ // ---- command lines --------------------------------------------------------
153
+ /** A normal omg command line: substitute refs, dispatch, record the result. */
154
+ async runLine(tokens) {
155
+ const substituted = this.substitute(tokens);
156
+ return this.dispatch(substituted, (v) => this.record(v), false);
157
+ }
158
+ /**
159
+ * Build a per-line Cli over the shared Workspace and run the command through
160
+ * the same path as a one-shot invocation (freshness sweep + dispatch + error
161
+ * mapping). `sink` receives the command's typed result; `quiet` drops stdout
162
+ * (used by `let`, which snapshots rather than displays).
163
+ */
164
+ async dispatch(tokens, sink, quiet) {
165
+ const { flags, command, rest } = parseGlobals(tokens);
166
+ if (!command)
167
+ return EXIT_OK;
168
+ // In a remote session, thread the server address into every line (unless the
169
+ // line names its own) so each command runs against the shared remote engine.
170
+ if (this.server !== undefined && flags.server === undefined) {
171
+ flags.server = this.server;
172
+ if (this.headers && flags.headers === undefined)
173
+ flags.headers = this.headers;
174
+ }
175
+ const io = quiet ? this.quietIO() : this.io;
176
+ const cli = makeCli(flags, io, { ...(this.workspace ? { workspace: this.workspace } : {}), cwd: this.cwd, capture: sink });
177
+ return runCommand(cli, command, rest);
178
+ }
179
+ /** Update @_ and, when the result is a collection, replace the numbered frame. */
180
+ record(value) {
181
+ this.last = { value };
182
+ const rows = deriveRows(value);
183
+ if (rows) {
184
+ this.frame = rows;
185
+ if (rows.length > 0) {
186
+ this.io.err(this.style.dim(` ${rows.length} row${rows.length === 1 ? "" : "s"} — address with @1..@${rows.length}`));
187
+ }
188
+ }
189
+ }
190
+ /** Inspect a bare `@ref` line: print it and, if a collection, make it the frame. */
191
+ inspect(token) {
192
+ const { value, ref } = this.resolveRef(token);
193
+ const rows = deriveRows(value);
194
+ if (rows) {
195
+ this.frame = rows;
196
+ for (let i = 0; i < rows.length; i++) {
197
+ const r = rows[i];
198
+ this.io.out(`${this.style.dim(`[${i + 1}]`)} ${this.style.id(r.ref)} ${this.style.accent(r.label)}`.trimEnd());
199
+ }
200
+ this.io.err(this.style.dim(` ${rows.length} row${rows.length === 1 ? "" : "s"}`));
201
+ }
202
+ else if (typeof value === "string") {
203
+ this.io.out(value);
204
+ }
205
+ else if (ref !== undefined) {
206
+ this.io.out(ref);
207
+ }
208
+ else {
209
+ // A scalar or a record: show its id/locator if coercible, else JSON.
210
+ try {
211
+ this.io.out(coerce(value));
212
+ }
213
+ catch {
214
+ this.io.out(JSON.stringify(value));
215
+ }
216
+ }
217
+ this.last = { value };
218
+ return EXIT_OK;
219
+ }
220
+ // ---- reference resolution -------------------------------------------------
221
+ /** Replace whole-token `@refs` with their coerced argv strings. */
222
+ substitute(tokens) {
223
+ return tokens.map((t) => {
224
+ const parsed = parseRef(t);
225
+ if (!parsed)
226
+ return t;
227
+ const { value, ref } = this.resolveRef(t);
228
+ // A known row id is used verbatim; otherwise coerce (which refuses a bare
229
+ // collection, forcing the user to pick a row with [i]).
230
+ return ref ?? coerce(value);
231
+ });
232
+ }
233
+ /**
234
+ * Resolve a reference token to its typed value plus, when known, a direct
235
+ * argv-usable id/locator. Coercion is deferred to the caller (substitution)
236
+ * so that inspecting a bare collection reference does not error.
237
+ */
238
+ resolveRef(token) {
239
+ const parsed = parseRef(token);
240
+ if (!parsed)
241
+ throw new RefError(`not a reference: '${token}'`);
242
+ let value;
243
+ let ref; // a known id/locator for the current row, if any
244
+ if (parsed.base === "_") {
245
+ if (!this.last)
246
+ throw new RefError("no previous result (@_)");
247
+ value = this.last.value;
248
+ }
249
+ else if (/^\d+$/.test(parsed.base)) {
250
+ if (!this.frame)
251
+ throw new RefError("no displayed collection to index with @N");
252
+ const row = this.frame[Number(parsed.base) - 1];
253
+ if (!row)
254
+ throw new RefError(`@${parsed.base} out of range (${this.frame.length} row${this.frame.length === 1 ? "" : "s"})`);
255
+ value = row.value;
256
+ ref = row.ref;
257
+ }
258
+ else {
259
+ const binding = this.bindings.get(parsed.base);
260
+ if (!binding)
261
+ throw new RefError(`no binding @${parsed.base}`);
262
+ value = binding.value;
263
+ }
264
+ if (parsed.index !== undefined) {
265
+ const rows = deriveRows(value);
266
+ if (!rows)
267
+ throw new RefError(`@${parsed.base} is not a collection to index with [i]`);
268
+ const row = rows[parsed.index - 1];
269
+ if (!row)
270
+ throw new RefError(`[${parsed.index}] out of range (${rows.length} item${rows.length === 1 ? "" : "s"})`);
271
+ value = row.value;
272
+ ref = row.ref;
273
+ }
274
+ if (parsed.field !== undefined) {
275
+ if (!value || typeof value !== "object")
276
+ throw new RefError(`cannot read .${parsed.field} of a non-object`);
277
+ const o = value;
278
+ if (!(parsed.field in o))
279
+ throw new RefError(`no field .${parsed.field}`);
280
+ value = o[parsed.field];
281
+ ref = undefined; // no longer a row id — coerce from the field value
282
+ }
283
+ return ref !== undefined ? { value, ref } : { value };
284
+ }
285
+ // ---- helpers --------------------------------------------------------------
286
+ summary(cap) {
287
+ if (cap.rows)
288
+ return `${cap.rows.length} row${cap.rows.length === 1 ? "" : "s"}`;
289
+ const v = cap.value;
290
+ if (typeof v === "string")
291
+ return v.length > 48 ? `"${v.slice(0, 45)}…"` : `"${v}"`;
292
+ if (typeof v === "number" || typeof v === "boolean")
293
+ return String(v);
294
+ if (v && typeof v === "object") {
295
+ const o = v;
296
+ const id = o.id ?? o.node ?? o.path ?? o.locator;
297
+ return id ? String(id) : "record";
298
+ }
299
+ return "empty";
300
+ }
301
+ usage(message) {
302
+ this.io.err(`${this.style.err("usage")}: ${message}`);
303
+ return EXIT_USAGE;
304
+ }
305
+ /** An IO that drops stdout (data) but forwards stderr (diagnostics). */
306
+ quietIO() {
307
+ const base = this.io;
308
+ return {
309
+ out: () => { },
310
+ err: (s) => base.err(s),
311
+ stdoutTTY: base.stdoutTTY,
312
+ stderrTTY: base.stderrTTY,
313
+ };
314
+ }
315
+ }
316
+ export { EXIT_OK, EXIT_ERROR };
317
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1,3 @@
1
+ export declare class TokenizeError extends Error {
2
+ }
3
+ export declare function tokenize(line: string): string[];
@@ -0,0 +1,79 @@
1
+ // Shell line tokenizer (11 shell). Splits a line into argv tokens the way a
2
+ // POSIX-ish shell would for the pieces we care about: whitespace separates
3
+ // tokens; single quotes are literal; double quotes group with backslash escapes;
4
+ // a backslash escapes the next char outside quotes. This is deliberately small —
5
+ // no globbing, no variable expansion (session `@refs` are resolved separately),
6
+ // no pipes — the shell stores and dereferences, it is not a second language.
7
+ export class TokenizeError extends Error {
8
+ }
9
+ export function tokenize(line) {
10
+ const tokens = [];
11
+ let cur = "";
12
+ let hasCur = false; // distinguishes an empty quoted token ("") from no token
13
+ let i = 0;
14
+ const n = line.length;
15
+ while (i < n) {
16
+ const ch = line[i];
17
+ if (ch === " " || ch === "\t") {
18
+ if (hasCur) {
19
+ tokens.push(cur);
20
+ cur = "";
21
+ hasCur = false;
22
+ }
23
+ i++;
24
+ continue;
25
+ }
26
+ if (ch === "'") {
27
+ hasCur = true;
28
+ i++;
29
+ const end = line.indexOf("'", i);
30
+ if (end === -1)
31
+ throw new TokenizeError("unterminated single quote");
32
+ cur += line.slice(i, end);
33
+ i = end + 1;
34
+ continue;
35
+ }
36
+ if (ch === '"') {
37
+ hasCur = true;
38
+ i++;
39
+ while (i < n && line[i] !== '"') {
40
+ if (line[i] === "\\" && i + 1 < n) {
41
+ const next = line[i + 1];
42
+ // Inside double quotes only \" \\ \$ \` are special escapes; keep the
43
+ // rest verbatim (backslash included) as a real shell does.
44
+ if (next === '"' || next === "\\" || next === "$" || next === "`") {
45
+ cur += next;
46
+ i += 2;
47
+ continue;
48
+ }
49
+ }
50
+ cur += line[i];
51
+ i++;
52
+ }
53
+ if (i >= n)
54
+ throw new TokenizeError("unterminated double quote");
55
+ i++; // closing quote
56
+ continue;
57
+ }
58
+ if (ch === "\\") {
59
+ if (i + 1 < n) {
60
+ cur += line[i + 1];
61
+ hasCur = true;
62
+ i += 2;
63
+ continue;
64
+ }
65
+ // trailing backslash: treat literally
66
+ cur += ch;
67
+ hasCur = true;
68
+ i++;
69
+ continue;
70
+ }
71
+ cur += ch;
72
+ hasCur = true;
73
+ i++;
74
+ }
75
+ if (hasCur)
76
+ tokens.push(cur);
77
+ return tokens;
78
+ }
79
+ //# sourceMappingURL=tokenize.js.map
@@ -0,0 +1,58 @@
1
+ export type Tier = "rich" | "basic" | "plain";
2
+ export interface StyleOptions {
3
+ /** Force color off (--no-color, NO_COLOR, or non-TTY stdout). */
4
+ noColor?: boolean;
5
+ /** The stream we're styling for (defaults to process.stdout). */
6
+ isTTY?: boolean;
7
+ }
8
+ export declare class Style {
9
+ readonly tier: Tier;
10
+ private readonly color;
11
+ constructor(opts?: StyleOptions);
12
+ get isRich(): boolean;
13
+ private sgr;
14
+ /** 24-bit foreground when rich; falls back to a near 256-color code otherwise. */
15
+ private fg;
16
+ bold(s: string): string;
17
+ dim(s: string): string;
18
+ italic(s: string): string;
19
+ ok(s: string): string;
20
+ warn(s: string): string;
21
+ err(s: string): string;
22
+ accent(s: string): string;
23
+ path(s: string): string;
24
+ id(s: string): string;
25
+ /**
26
+ * Horizontal gradient across a string, cyan→magenta. Rich tier only; basic
27
+ * falls back to a flat accent, plain to the bare text. Used for the wordmark.
28
+ */
29
+ gradient(s: string, from?: [number, number, number], to?: [number, number, number]): string;
30
+ /** A solid gradient bar of `width` cells (rich only; degrades to a dim rule). */
31
+ gradientBar(width: number): string;
32
+ }
33
+ export interface Glyphs {
34
+ live: string;
35
+ dead: string;
36
+ ok: string;
37
+ warn: string;
38
+ err: string;
39
+ sync: string;
40
+ bullet: string;
41
+ diamond: string;
42
+ arrow: string;
43
+ taskOpen: string;
44
+ taskDone: string;
45
+ doc: string;
46
+ block: string;
47
+ heading: string;
48
+ queued: string;
49
+ tl: string;
50
+ tr: string;
51
+ bl: string;
52
+ br: string;
53
+ h: string;
54
+ v: string;
55
+ teeL: string;
56
+ teeR: string;
57
+ }
58
+ export declare function glyphs(tier: Tier): Glyphs;
@@ -0,0 +1,159 @@
1
+ // Visual capability ladder + ANSI primitives (11 §4, §8). Hand-rolled ANSI, no
2
+ // deps. Three tiers, detected from the environment:
3
+ //
4
+ // rich — 24-bit truecolor + Nerd Font icons (gradients, per-type glyphs)
5
+ // basic — 16/256-color + safe unicode (● ✓ ⟳ ◆, box drawing)
6
+ // plain — no color, no non-ASCII (NO_COLOR, non-TTY, --json, dumb terminals)
7
+ //
8
+ // stdout is data; all styling is for the human-facing (TTY) path only (§4). A
9
+ // piped or --json invocation degrades to plain automatically.
10
+ function detectTier(opts) {
11
+ if (opts.noColor)
12
+ return "plain";
13
+ const isTTY = opts.isTTY ?? Boolean(process.stdout.isTTY);
14
+ if (!isTTY)
15
+ return "plain";
16
+ if (process.env.NO_COLOR != null)
17
+ return "plain";
18
+ if (process.env.TERM === "dumb")
19
+ return "plain";
20
+ const truecolor = process.env.COLORTERM === "truecolor" || process.env.COLORTERM === "24bit";
21
+ // Nerd Font heuristic: users set this convention, or common terminal programs
22
+ // that ship glyph fonts. Imperfect by design (11 §glyph policy) — degrade
23
+ // gracefully when unsure.
24
+ const term = `${process.env.TERM_PROGRAM ?? ""} ${process.env.LC_TERMINAL ?? ""}`.toLowerCase();
25
+ const nerd = process.env.OMG_NERD_FONT === "1" ||
26
+ process.env.NERD_FONT === "1" ||
27
+ /wezterm|kitty|ghostty|iterm/.test(term);
28
+ return truecolor && nerd ? "rich" : "basic";
29
+ }
30
+ const RESET = "\x1b[0m";
31
+ export class Style {
32
+ tier;
33
+ color;
34
+ constructor(opts = {}) {
35
+ this.tier = detectTier(opts);
36
+ this.color = this.tier !== "plain";
37
+ }
38
+ get isRich() {
39
+ return this.tier === "rich";
40
+ }
41
+ // ---- raw SGR helpers ------------------------------------------------------
42
+ sgr(code, s) {
43
+ return this.color ? `\x1b[${code}m${s}${RESET}` : s;
44
+ }
45
+ /** 24-bit foreground when rich; falls back to a near 256-color code otherwise. */
46
+ fg(r, g, b, s) {
47
+ if (!this.color)
48
+ return s;
49
+ if (this.tier === "rich")
50
+ return `\x1b[38;2;${r};${g};${b}m${s}${RESET}`;
51
+ // basic tier: map to the nearest xterm-256 cube color.
52
+ const idx = 16 + 36 * Math.round((r / 255) * 5) + 6 * Math.round((g / 255) * 5) + Math.round((b / 255) * 5);
53
+ return `\x1b[38;5;${idx}m${s}${RESET}`;
54
+ }
55
+ bold(s) {
56
+ return this.sgr("1", s);
57
+ }
58
+ dim(s) {
59
+ return this.sgr("2", s);
60
+ }
61
+ italic(s) {
62
+ return this.sgr("3", s);
63
+ }
64
+ // ---- semantic colors ------------------------------------------------------
65
+ ok(s) {
66
+ return this.fg(64, 200, 128, s); // green
67
+ }
68
+ warn(s) {
69
+ return this.fg(230, 180, 60, s); // amber
70
+ }
71
+ err(s) {
72
+ return this.fg(228, 90, 90, s); // red
73
+ }
74
+ accent(s) {
75
+ return this.fg(90, 190, 235, s); // cyan
76
+ }
77
+ path(s) {
78
+ return this.fg(120, 180, 245, s); // soft blue
79
+ }
80
+ id(s) {
81
+ return this.dim(s); // IDs recede; locators are for eyes (§4.2)
82
+ }
83
+ /**
84
+ * Horizontal gradient across a string, cyan→magenta. Rich tier only; basic
85
+ * falls back to a flat accent, plain to the bare text. Used for the wordmark.
86
+ */
87
+ gradient(s, from = [90, 190, 235], to = [200, 110, 235]) {
88
+ if (this.tier !== "rich")
89
+ return this.color ? this.accent(s) : s;
90
+ const chars = [...s];
91
+ const n = Math.max(chars.length - 1, 1);
92
+ return (chars
93
+ .map((ch, i) => {
94
+ const t = i / n;
95
+ const r = Math.round(from[0] + (to[0] - from[0]) * t);
96
+ const g = Math.round(from[1] + (to[1] - from[1]) * t);
97
+ const b = Math.round(from[2] + (to[2] - from[2]) * t);
98
+ return `\x1b[38;2;${r};${g};${b}m${ch}`;
99
+ })
100
+ .join("") + RESET);
101
+ }
102
+ /** A solid gradient bar of `width` cells (rich only; degrades to a dim rule). */
103
+ gradientBar(width) {
104
+ if (this.tier !== "rich")
105
+ return this.color ? this.dim("─".repeat(width)) : "-".repeat(width);
106
+ const from = [90, 190, 235];
107
+ const to = [200, 110, 235];
108
+ let out = "";
109
+ const n = Math.max(width - 1, 1);
110
+ for (let i = 0; i < width; i++) {
111
+ const t = i / n;
112
+ const r = Math.round(from[0] + (to[0] - from[0]) * t);
113
+ const g = Math.round(from[1] + (to[1] - from[1]) * t);
114
+ const b = Math.round(from[2] + (to[2] - from[2]) * t);
115
+ out += `\x1b[38;2;${r};${g};${b}m━`;
116
+ }
117
+ return out + RESET;
118
+ }
119
+ }
120
+ // Nerd Font glyphs by codepoint (Private Use Area). Written as \u{} escapes so
121
+ // the source is unambiguous under any editor font; they render as icons only
122
+ // when a Nerd Font is active (the "rich" tier gate).
123
+ const RICH = {
124
+ live: "\u{f111}", // nf-fa-circle
125
+ dead: "\u{f10c}", // nf-fa-circle_o
126
+ ok: "\u{f00c}", // nf-fa-check
127
+ warn: "\u{f071}", // nf-fa-warning
128
+ err: "\u{f00d}", // nf-fa-times
129
+ sync: "\u{f021}", // nf-fa-refresh
130
+ bullet: "\u{f444}", // nf-oct-dot_fill
131
+ diamond: "\u{f219}", // nf-fa-diamond
132
+ arrow: "\u{f101}", // nf-fa-angle_double_right
133
+ taskOpen: "\u{f096}", // nf-fa-square_o
134
+ taskDone: "\u{f046}", // nf-fa-check_square_o
135
+ doc: "\u{f15c}", // nf-fa-file_text
136
+ block: "\u{f0c8}", // nf-fa-square
137
+ heading: "\u{f1dc}", // nf-fa-header
138
+ queued: "\u{f017}", // nf-fa-clock_o
139
+ tl: "╭", tr: "╮", bl: "╰", br: "╯",
140
+ h: "─", v: "│", teeL: "├", teeR: "┤",
141
+ };
142
+ const BASIC = {
143
+ live: "●", dead: "○", ok: "✓", warn: "!", err: "✗",
144
+ sync: "⟳", bullet: "•", diamond: "◆", arrow: "›",
145
+ taskOpen: "☐", taskDone: "☑", doc: "▤", block: "▪",
146
+ heading: "§", queued: "~",
147
+ tl: "╭", tr: "╮", bl: "╰", br: "╯",
148
+ h: "─", v: "│", teeL: "├", teeR: "┤",
149
+ };
150
+ const PLAIN = {
151
+ live: "*", dead: "o", ok: "ok", warn: "!", err: "x",
152
+ sync: "~", bullet: "-", diamond: "*", arrow: ">",
153
+ taskOpen: "[ ]", taskDone: "[x]", doc: "[D]", block: "[B]", heading: "#", queued: "~",
154
+ tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|", teeL: "+", teeR: "+",
155
+ };
156
+ export function glyphs(tier) {
157
+ return tier === "rich" ? RICH : tier === "basic" ? BASIC : PLAIN;
158
+ }
159
+ //# sourceMappingURL=style.js.map
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "omgbase",
3
+ "version": "0.1.0",
4
+ "description": "Open Markdown Graph Base — CLI",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/omgbase/omgbase.git",
9
+ "directory": "packages/cli"
10
+ },
11
+ "homepage": "https://github.com/omgbase/omgbase#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/omgbase/omgbase/issues"
14
+ },
15
+ "keywords": [
16
+ "markdown",
17
+ "knowledge-graph",
18
+ "sqlite",
19
+ "mcp",
20
+ "model-context-protocol",
21
+ "obsidian",
22
+ "agents",
23
+ "omgbase",
24
+ "cli",
25
+ "sync",
26
+ "search"
27
+ ],
28
+ "engines": {
29
+ "node": ">=22"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "!dist/**/*.test.*",
37
+ "!dist/corpus/**",
38
+ "!dist/**/*.map",
39
+ "!dist/**/*.tsbuildinfo",
40
+ "LICENSE"
41
+ ],
42
+ "type": "module",
43
+ "bin": {
44
+ "omgbase": "./dist/src/main.js",
45
+ "omg": "./dist/src/main.js"
46
+ },
47
+ "dependencies": {
48
+ "@omgbase/core": "^0.1.0",
49
+ "@omgbase/fs-adapter": "^0.1.0",
50
+ "@omgbase/sync": "^0.1.0",
51
+ "yaml": "^2.9.0"
52
+ },
53
+ "devDependencies": {
54
+ "@simplebrains/recital": "^0.8.0"
55
+ },
56
+ "scripts": {
57
+ "build": "tsc -p tsconfig.json",
58
+ "typecheck": "tsc -p tsconfig.json --noEmit",
59
+ "test": "vitest run",
60
+ "lint": "eslint .",
61
+ "lint:fix": "eslint . --fix"
62
+ }
63
+ }