@oasissys/sqlcl-cli 0.1.2

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.
@@ -0,0 +1,270 @@
1
+ /**
2
+ * SQLcl runner — talks to Oracle's SQLcl for everything.
3
+ *
4
+ * Storage model: all named connections live in SQLcl's own wallet
5
+ * (`%APPDATA%/dbtools/connections/` on Windows, `~/.dbtools/connections/`
6
+ * on Mac/Linux). Same store the official SQLcl MCP reads, so the dashboard
7
+ * and Claude's MCP tools are always in sync — no parallel state in
8
+ * ~/.oasis.json.
9
+ *
10
+ * The only thing we keep in ~/.oasis.json is `sqlcl.active` (just a name),
11
+ * because SQLcl itself is stateless between invocations and has no concept
12
+ * of a "currently selected" connection.
13
+ *
14
+ * Commands used:
15
+ * - `sql -L -S -name <N>` to run SQL against a saved connection
16
+ * - `sql -S /nolog` + `conn -savepwd -save …` (add)
17
+ * - `sql -S /nolog` + `connmgr delete -conn …` (remove)
18
+ */
19
+ import { spawn, execSync } from "node:child_process";
20
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
21
+ import os from "node:os";
22
+ import path from "node:path";
23
+ function resolveSqlclBin() {
24
+ const env = process.env.SQLCL_PATH;
25
+ if (env && existsSync(env))
26
+ return env;
27
+ const isWin = process.platform === "win32";
28
+ const binName = isWin ? "sql.exe" : "sql";
29
+ const home = os.homedir();
30
+ const bootstrap = path.join(home, ".oasis", "sqlcl", "bin", binName);
31
+ if (existsSync(bootstrap))
32
+ return bootstrap;
33
+ const candidates = isWin
34
+ ? [
35
+ "C:\\Oracle\\sqlcl\\bin\\sql.exe",
36
+ path.join(process.env.LOCALAPPDATA ?? "", "Programs", "sqlcl", "bin", "sql.exe"),
37
+ path.join(process.env.ProgramFiles ?? "", "sqlcl", "bin", "sql.exe"),
38
+ ]
39
+ : [
40
+ "/opt/sqlcl/bin/sql",
41
+ "/usr/local/sqlcl/bin/sql",
42
+ path.join(home, "sqlcl", "bin", "sql"),
43
+ ];
44
+ for (const c of candidates)
45
+ if (c && existsSync(c))
46
+ return c;
47
+ try {
48
+ const lookup = isWin ? `where ${binName}` : `which ${binName}`;
49
+ const found = execSync(lookup, { stdio: ["ignore", "pipe", "ignore"] }).toString().trim().split(/\r?\n/)[0];
50
+ if (found && existsSync(found))
51
+ return found;
52
+ }
53
+ catch { /* not on PATH */ }
54
+ return binName;
55
+ }
56
+ const SQLCL_BIN = resolveSqlclBin();
57
+ /** Location of SQLcl's wallet (one subfolder per connection). */
58
+ function dbtoolsDir() {
59
+ // Windows: %APPDATA%\dbtools ; Mac/Linux: ~/.dbtools
60
+ if (process.platform === "win32" && process.env.APPDATA) {
61
+ return path.join(process.env.APPDATA, "dbtools");
62
+ }
63
+ return path.join(os.homedir(), ".dbtools");
64
+ }
65
+ /** Parse `host[:port]/service` out of SQLcl's `connectionString` property. */
66
+ function parseConnectString(s) {
67
+ // SQLcl escapes `:` as `\:` in .properties files; optional `//` EZ-connect prefix.
68
+ const clean = s.replace(/\\:/g, ":").replace(/^\/\//, "").trim();
69
+ // host:port/service OR host/service (port defaults to 1521, Oracle's listener)
70
+ const withPort = clean.match(/^([^:/]+):(\d+)\/(.+)$/);
71
+ if (withPort)
72
+ return { host: withPort[1], port: parseInt(withPort[2], 10), service: withPort[3] };
73
+ const noPort = clean.match(/^([^:/]+)\/(.+)$/);
74
+ if (noPort)
75
+ return { host: noPort[1], port: 1521, service: noPort[2] };
76
+ return null;
77
+ }
78
+ /**
79
+ * List saved connections by reading each subdirectory's `dbtools.properties`.
80
+ * Simpler + faster than shelling out and parsing `connmgr list` (which has
81
+ * ANSI codes and no machine-friendly output).
82
+ */
83
+ export function listConnections() {
84
+ const connectionsRoot = path.join(dbtoolsDir(), "connections");
85
+ if (!existsSync(connectionsRoot))
86
+ return [];
87
+ const out = [];
88
+ for (const entry of readdirSync(connectionsRoot, { withFileTypes: true })) {
89
+ if (!entry.isDirectory())
90
+ continue;
91
+ const propFile = path.join(connectionsRoot, entry.name, "dbtools.properties");
92
+ if (!existsSync(propFile))
93
+ continue;
94
+ try {
95
+ const props = {};
96
+ for (const line of readFileSync(propFile, "utf8").split(/\r?\n/)) {
97
+ const eq = line.indexOf("=");
98
+ if (eq > 0 && !line.startsWith("#")) {
99
+ props[line.slice(0, eq).trim()] = line.slice(eq + 1).trim();
100
+ }
101
+ }
102
+ if (props.type !== "ORACLE_DATABASE")
103
+ continue; // skip non-Oracle entries if any
104
+ const parsed = parseConnectString(props.connectionString ?? "");
105
+ if (!props.name || !parsed)
106
+ continue;
107
+ out.push({ name: props.name, user: props.userName ?? "", ...parsed });
108
+ }
109
+ catch { /* skip broken dir */ }
110
+ }
111
+ return out.sort((a, b) => a.name.localeCompare(b.name));
112
+ }
113
+ /**
114
+ * Run SQL against a saved connection by name (wallet-backed, no creds on cmdline).
115
+ *
116
+ * `longBytes` controls `SET LONG` / `SET LONGCHUNKSIZE` — unlike the MCP pool
117
+ * path (which gets `SP2-0738` restricted-command errors), direct spawn lets us
118
+ * lift the LOB cap freely. Used by /api/sqlcl/lob-full to fetch full values.
119
+ */
120
+ export async function runSql(connectionName, sql, opts = {}) {
121
+ const { timeoutMs = 60_000, format = "ansiconsole", longBytes } = opts;
122
+ return new Promise((resolve) => {
123
+ const proc = spawn(SQLCL_BIN, ["-L", "-S", "-name", connectionName], {
124
+ stdio: ["pipe", "pipe", "pipe"],
125
+ env: { ...process.env, MSYS2_ARG_CONV_EXCL: "*" },
126
+ });
127
+ let stdout = "", stderr = "";
128
+ proc.stdout.on("data", d => stdout += d.toString());
129
+ proc.stderr.on("data", d => stderr += d.toString());
130
+ const timer = setTimeout(() => {
131
+ proc.kill();
132
+ resolve({ stdout, stderr: stderr + `\n[timeout after ${timeoutMs}ms]`, code: -1 });
133
+ }, timeoutMs);
134
+ proc.on("close", (code) => { clearTimeout(timer); resolve({ stdout, stderr, code: code ?? -1 }); });
135
+ proc.on("error", (err) => { clearTimeout(timer); resolve({ stdout, stderr: String(err), code: -1 }); });
136
+ const format_prelude = format === "csv"
137
+ ? "SET FEEDBACK OFF\nSET SQLFORMAT CSV\nSET HEADING ON\n"
138
+ : "SET FEEDBACK ON\nSET SQLFORMAT ANSICONSOLE\n";
139
+ const long_prelude = longBytes != null
140
+ ? `SET LONG ${longBytes}\nSET LONGCHUNKSIZE ${longBytes}\n`
141
+ : "";
142
+ proc.stdin.write(format_prelude + long_prelude);
143
+ proc.stdin.write(sql.trim().replace(/;?\s*$/, "") + ";\n");
144
+ proc.stdin.write("EXIT\n");
145
+ proc.stdin.end();
146
+ });
147
+ }
148
+ /**
149
+ * Run a raw SQLcl script against a saved connection, verbatim — no trailing-`;`
150
+ * rewriting, no format prelude. Caller owns the entire script including
151
+ * `SET …`, PL/SQL blocks ending with `/`, and `EXIT`.
152
+ *
153
+ * Used for `DBMS_SQL.describe_columns` which is a PL/SQL block terminated by
154
+ * `/` on its own line — the default runSql() strips that.
155
+ */
156
+ export async function runSqlclScript(connectionName, script, opts = {}) {
157
+ const { timeoutMs = 30_000 } = opts;
158
+ return new Promise((resolve) => {
159
+ const proc = spawn(SQLCL_BIN, ["-L", "-S", "-name", connectionName], {
160
+ stdio: ["pipe", "pipe", "pipe"],
161
+ env: { ...process.env, MSYS2_ARG_CONV_EXCL: "*" },
162
+ });
163
+ let stdout = "", stderr = "";
164
+ proc.stdout.on("data", d => stdout += d.toString());
165
+ proc.stderr.on("data", d => stderr += d.toString());
166
+ const timer = setTimeout(() => {
167
+ proc.kill();
168
+ resolve({ stdout, stderr: stderr + `\n[timeout after ${timeoutMs}ms]`, code: -1 });
169
+ }, timeoutMs);
170
+ proc.on("close", (code) => { clearTimeout(timer); resolve({ stdout, stderr, code: code ?? -1 }); });
171
+ proc.on("error", (err) => { clearTimeout(timer); resolve({ stdout, stderr: String(err), code: -1 }); });
172
+ proc.stdin.write(script.endsWith("\n") ? script : script + "\n");
173
+ proc.stdin.write("EXIT\n");
174
+ proc.stdin.end();
175
+ });
176
+ }
177
+ /** Run SQLcl commands in /nolog mode — no database connection, for wallet mgmt. */
178
+ async function runSqlclNoLog(commands, timeoutMs = 30_000) {
179
+ return new Promise((resolve) => {
180
+ const proc = spawn(SQLCL_BIN, ["-S", "/nolog"], {
181
+ stdio: ["pipe", "pipe", "pipe"],
182
+ env: { ...process.env, MSYS2_ARG_CONV_EXCL: "*" },
183
+ });
184
+ let stdout = "", stderr = "";
185
+ proc.stdout.on("data", d => stdout += d.toString());
186
+ proc.stderr.on("data", d => stderr += d.toString());
187
+ const timer = setTimeout(() => {
188
+ proc.kill();
189
+ resolve({ stdout, stderr: stderr + `\n[timeout]`, code: -1 });
190
+ }, timeoutMs);
191
+ proc.on("close", (code) => { clearTimeout(timer); resolve({ stdout, stderr, code: code ?? -1 }); });
192
+ proc.on("error", (err) => { clearTimeout(timer); resolve({ stdout, stderr: String(err), code: -1 }); });
193
+ for (const cmd of commands)
194
+ proc.stdin.write(cmd + "\n");
195
+ proc.stdin.write("EXIT\n");
196
+ proc.stdin.end();
197
+ });
198
+ }
199
+ /**
200
+ * SQLcl parses its command-line inside the CLI using whitespace as the
201
+ * separator, so names with spaces break `conn -save <name>` unless quoted.
202
+ * Double-quoting is the portable escape. Embedded double-quotes in a name
203
+ * make no sense for a connection name — reject upfront at the API layer.
204
+ */
205
+ function quoteArg(s) {
206
+ return `"${s.replace(/"/g, '""')}"`;
207
+ }
208
+ export function validateConnectionName(name) {
209
+ if (!name || !name.trim())
210
+ return "Connection name required";
211
+ if (name.includes('"'))
212
+ return "Connection name cannot contain double-quotes";
213
+ if (name.length > 128)
214
+ return "Connection name too long (max 128)";
215
+ return null;
216
+ }
217
+ /**
218
+ * Inspect SQLcl output for common auth/connection failures.
219
+ *
220
+ * `conn -save` is save-IF-connect-succeeds — the wallet entry only persists
221
+ * when auth works. SQLcl always exits 0 even on failure, so we have to look
222
+ * for error markers in stdout/stderr to distinguish success from silent fail.
223
+ */
224
+ function didConnect(r) {
225
+ const all = `${r.stdout}\n${r.stderr}`;
226
+ if (r.code !== 0)
227
+ return false;
228
+ if (/Connection failed/i.test(all))
229
+ return false;
230
+ if (/^ORA-\d+/m.test(all))
231
+ return false;
232
+ return true;
233
+ }
234
+ /**
235
+ * Save a named connection into the SQLcl wallet. The save-and-connect happens
236
+ * in a single `conn -savepwd -save` command — SQLcl only persists the wallet
237
+ * entry if auth actually succeeds, so a successful return means the creds are
238
+ * verified.
239
+ */
240
+ export async function saveConnection(c) {
241
+ const r = await runSqlclNoLog([
242
+ `conn -savepwd -save ${quoteArg(c.name)} ${c.user}/"${c.password}"@${c.host}:${c.port}/${c.service}`,
243
+ `disc`,
244
+ ]);
245
+ return didConnect(r)
246
+ ? { ok: true, message: "Connection saved" }
247
+ : { ok: false, message: summarizeSqlError(r) };
248
+ }
249
+ export async function deleteConnection(name) {
250
+ return runSqlclNoLog([`connmgr delete -conn ${quoteArg(name)}`]);
251
+ }
252
+ /**
253
+ * Verify credentials without persisting them — save under a throwaway name,
254
+ * check if the wallet gained an entry, then remove it regardless.
255
+ */
256
+ export async function probeConnection(c) {
257
+ const tmpName = `__oasis_probe_${Date.now()}`;
258
+ const res = await saveConnection({ ...c, name: tmpName });
259
+ await deleteConnection(tmpName); // best-effort cleanup whether save passed or failed
260
+ return res;
261
+ }
262
+ export function summarizeSqlError(r) {
263
+ const combined = [r.stdout, r.stderr].filter(Boolean).join("\n");
264
+ const lines = combined.split(/\r?\n/).map(l => l.trim()).filter(Boolean);
265
+ const noise = /^(WARNING:|SQLcl:|Connected\.|\s*Version|\s*Copyright|\s*Oracle)/i;
266
+ const clean = lines.filter(l => !noise.test(l));
267
+ const diag = clean.filter(l => /^(ORA-|SP2-|PLS-|IO Error|USER|ERROR)\b/i.test(l));
268
+ return (diag.length ? diag : clean).slice(-5).join("\n") || `sqlcl exited with code ${r.code}`;
269
+ }
270
+ //# sourceMappingURL=session.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/session.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Single owner of ~/.oasis.json. All reads/writes for creds and
3
+ * service-specific sections (sqlcl.connections, etc.) go through here.
4
+ *
5
+ * Guarantees:
6
+ * - Atomic writes: write to a sibling `.tmp` then rename.
7
+ * - In-process mutex: concurrent updateStore() calls serialize, so two
8
+ * callers can't lose each other's writes within the same process.
9
+ */
10
+ export declare const OASIS_FILE: string;
11
+ export interface ServiceSection {
12
+ creds?: Record<string, string>;
13
+ [key: string]: unknown;
14
+ }
15
+ export type OasisStore = Record<string, ServiceSection>;
16
+ export declare function readStore(): OasisStore;
17
+ export declare function updateStore<T>(mutator: (store: OasisStore) => T | Promise<T>): Promise<T>;
18
+ export declare function loadCreds(serviceKey: string): Record<string, string> | null;
19
+ export declare function saveCreds(serviceKey: string, creds: Record<string, string>): Promise<void>;
20
+ export declare function removeCreds(serviceKey: string): Promise<void>;
21
+ export declare function loadSection<T extends ServiceSection>(serviceKey: string): T;
22
+ export declare function saveSection<T extends ServiceSection>(serviceKey: string, mutator: (section: T) => T | void): Promise<void>;
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Single owner of ~/.oasis.json. All reads/writes for creds and
3
+ * service-specific sections (sqlcl.connections, etc.) go through here.
4
+ *
5
+ * Guarantees:
6
+ * - Atomic writes: write to a sibling `.tmp` then rename.
7
+ * - In-process mutex: concurrent updateStore() calls serialize, so two
8
+ * callers can't lose each other's writes within the same process.
9
+ */
10
+ import fs from "node:fs";
11
+ import os from "node:os";
12
+ import path from "node:path";
13
+ export const OASIS_FILE = path.join(os.homedir(), ".oasis.json");
14
+ // ─── Atomic I/O ────────────────────────────────────────────────────
15
+ function readStoreRaw() {
16
+ try {
17
+ const data = JSON.parse(fs.readFileSync(OASIS_FILE, "utf-8"));
18
+ if (data && typeof data === "object")
19
+ return data;
20
+ }
21
+ catch {
22
+ /* unreadable or missing file — start from an empty store */
23
+ }
24
+ return {};
25
+ }
26
+ function writeStoreRaw(store) {
27
+ const tmp = OASIS_FILE + ".tmp";
28
+ fs.writeFileSync(tmp, JSON.stringify(store, null, 2));
29
+ fs.renameSync(tmp, OASIS_FILE);
30
+ }
31
+ // In-process serializer. Each update() waits for the previous promise to
32
+ // settle before reading -> mutating -> writing. Fine for localhost single
33
+ // node; doesn't protect against cross-process races (acceptable — dashboard
34
+ // API and MCP handlers live in the same node process).
35
+ let chain = Promise.resolve();
36
+ export function readStore() {
37
+ return readStoreRaw();
38
+ }
39
+ export function updateStore(mutator) {
40
+ const next = chain.then(async () => {
41
+ const store = readStoreRaw();
42
+ const result = await mutator(store);
43
+ writeStoreRaw(store);
44
+ return result;
45
+ });
46
+ chain = next.catch(() => { }); // don't let a failed mutator break the chain
47
+ return next;
48
+ }
49
+ // ─── Typed section helpers ─────────────────────────────────────────
50
+ export function loadCreds(serviceKey) {
51
+ const section = readStoreRaw()[serviceKey];
52
+ return section?.creds ?? null;
53
+ }
54
+ export function saveCreds(serviceKey, creds) {
55
+ return updateStore(store => {
56
+ store[serviceKey] = { ...store[serviceKey], creds };
57
+ });
58
+ }
59
+ export function removeCreds(serviceKey) {
60
+ return updateStore(store => {
61
+ if (!store[serviceKey])
62
+ return;
63
+ delete store[serviceKey].creds;
64
+ // Legacy: older versions of the server persisted OAuth tokens here.
65
+ // Clean them out on logout so the file doesn't carry dead state forward.
66
+ delete store[serviceKey].tokens;
67
+ if (Object.keys(store[serviceKey]).length === 0)
68
+ delete store[serviceKey];
69
+ });
70
+ }
71
+ // Generic section I/O — used by sqlcl for its `connections` / `active` keys
72
+ // and by any future service with custom schema.
73
+ export function loadSection(serviceKey) {
74
+ return readStoreRaw()[serviceKey] ?? {};
75
+ }
76
+ export function saveSection(serviceKey, mutator) {
77
+ return updateStore(store => {
78
+ const current = (store[serviceKey] ?? {});
79
+ const updated = mutator(current) ?? current;
80
+ if (updated === null || (typeof updated === "object" && Object.keys(updated).length === 0)) {
81
+ delete store[serviceKey];
82
+ }
83
+ else {
84
+ store[serviceKey] = updated;
85
+ }
86
+ });
87
+ }
88
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/storage.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Minimal TOON (Token-Oriented Object Notation) encoder.
3
+ *
4
+ * TOON is the stdout format for AXI CLIs — ~40% fewer tokens than JSON while
5
+ * staying readable by agents. See https://toonformat.dev/reference/spec.html.
6
+ *
7
+ * Keep all internal logic on plain JS objects; call `encode()` only at the
8
+ * output boundary. This encoder covers the shapes an AXI CLI actually emits:
9
+ *
10
+ * - objects key: value
11
+ * - nested objects key:\n child: value
12
+ * - tabular arrays key[N]{f1,f2}:\n a,b\n c,d
13
+ * - primitive arrays key[N]: a,b,c
14
+ * - empty arrays key[0]:
15
+ *
16
+ * Two-space indentation, no tabs, no trailing newline (§12).
17
+ */
18
+ /** Encode a JS value to a TOON document (no trailing newline). */
19
+ export declare function encode(value: unknown): string;
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Minimal TOON (Token-Oriented Object Notation) encoder.
3
+ *
4
+ * TOON is the stdout format for AXI CLIs — ~40% fewer tokens than JSON while
5
+ * staying readable by agents. See https://toonformat.dev/reference/spec.html.
6
+ *
7
+ * Keep all internal logic on plain JS objects; call `encode()` only at the
8
+ * output boundary. This encoder covers the shapes an AXI CLI actually emits:
9
+ *
10
+ * - objects key: value
11
+ * - nested objects key:\n child: value
12
+ * - tabular arrays key[N]{f1,f2}:\n a,b\n c,d
13
+ * - primitive arrays key[N]: a,b,c
14
+ * - empty arrays key[0]:
15
+ *
16
+ * Two-space indentation, no tabs, no trailing newline (§12).
17
+ */
18
+ const INDENT = " ";
19
+ function isPlainObject(v) {
20
+ return typeof v === "object" && v !== null && !Array.isArray(v);
21
+ }
22
+ function isScalar(v) {
23
+ return v === null || v === undefined || ["string", "number", "boolean"].includes(typeof v);
24
+ }
25
+ // §7.2 — a string MUST be quoted when it would otherwise be ambiguous with a
26
+ // structural token, a literal (true/false/null), or a number. We also quote on
27
+ // the comma delimiter so the same formatter is safe inside tabular rows.
28
+ const NUMERIC_LIKE = /^-?\d+(?:\.\d+)?(?:e[+-]?\d+)?$/i;
29
+ const NEEDS_QUOTE = /[:"\\[\]{},]/;
30
+ function hasControlChar(s) {
31
+ for (let i = 0; i < s.length; i++) {
32
+ if (s.charCodeAt(i) <= 0x1f)
33
+ return true;
34
+ }
35
+ return false;
36
+ }
37
+ function quoteString(s) {
38
+ const needs = s.length === 0 ||
39
+ s !== s.trim() ||
40
+ s === "true" ||
41
+ s === "false" ||
42
+ s === "null" ||
43
+ NUMERIC_LIKE.test(s) ||
44
+ NEEDS_QUOTE.test(s) ||
45
+ hasControlChar(s);
46
+ if (!needs)
47
+ return s;
48
+ const escaped = s
49
+ .replace(/\\/g, "\\\\")
50
+ .replace(/"/g, '\\"')
51
+ .replace(/\n/g, "\\n")
52
+ .replace(/\r/g, "\\r")
53
+ .replace(/\t/g, "\\t");
54
+ return `"${escaped}"`;
55
+ }
56
+ function formatScalar(v) {
57
+ if (v === null || v === undefined)
58
+ return "null";
59
+ if (typeof v === "boolean")
60
+ return v ? "true" : "false";
61
+ if (typeof v === "number") {
62
+ if (!Number.isFinite(v))
63
+ return "null";
64
+ return Object.is(v, -0) ? "0" : String(v);
65
+ }
66
+ if (typeof v === "string")
67
+ return quoteString(v);
68
+ // Non-scalar leaked into a scalar slot — stringify defensively rather than throw.
69
+ return quoteString(JSON.stringify(v));
70
+ }
71
+ function tableFields(arr) {
72
+ // Tabular form is only valid when every row is a flat object (scalar values)
73
+ // sharing the first row's key set. Otherwise fall back to a list.
74
+ const fields = Object.keys(arr[0]);
75
+ if (fields.length === 0)
76
+ return null;
77
+ for (const row of arr) {
78
+ const keys = Object.keys(row);
79
+ if (keys.length !== fields.length || !fields.every(f => f in row))
80
+ return null;
81
+ if (!fields.every(f => isScalar(row[f])))
82
+ return null;
83
+ }
84
+ return fields;
85
+ }
86
+ function emitArray(key, arr, depth, lines) {
87
+ const pad = INDENT.repeat(depth);
88
+ if (arr.length === 0) {
89
+ lines.push(`${pad}${key}[0]:`);
90
+ return;
91
+ }
92
+ if (arr.every(isScalar)) {
93
+ lines.push(`${pad}${key}[${arr.length}]: ${arr.map(formatScalar).join(",")}`);
94
+ return;
95
+ }
96
+ if (arr.every(isPlainObject)) {
97
+ const rows = arr;
98
+ const fields = tableFields(rows);
99
+ if (fields) {
100
+ lines.push(`${pad}${key}[${arr.length}]{${fields.join(",")}}:`);
101
+ for (const row of rows) {
102
+ lines.push(`${INDENT.repeat(depth + 1)}${fields.map(f => formatScalar(row[f])).join(",")}`);
103
+ }
104
+ return;
105
+ }
106
+ // Non-uniform / nested objects → list of blocks.
107
+ lines.push(`${pad}${key}[${arr.length}]:`);
108
+ for (const row of rows) {
109
+ lines.push(`${INDENT.repeat(depth + 1)}-`);
110
+ emitObject(row, depth + 2, lines);
111
+ }
112
+ return;
113
+ }
114
+ // Mixed array — stringify each element as a scalar.
115
+ lines.push(`${pad}${key}[${arr.length}]: ${arr.map(formatScalar).join(",")}`);
116
+ }
117
+ function emitObject(obj, depth, lines) {
118
+ const pad = INDENT.repeat(depth);
119
+ for (const [key, value] of Object.entries(obj)) {
120
+ if (value === undefined)
121
+ continue;
122
+ if (Array.isArray(value)) {
123
+ emitArray(key, value, depth, lines);
124
+ }
125
+ else if (isPlainObject(value)) {
126
+ lines.push(`${pad}${key}:`);
127
+ emitObject(value, depth + 1, lines);
128
+ }
129
+ else {
130
+ lines.push(`${pad}${key}: ${formatScalar(value)}`);
131
+ }
132
+ }
133
+ }
134
+ /** Encode a JS value to a TOON document (no trailing newline). */
135
+ export function encode(value) {
136
+ const lines = [];
137
+ if (Array.isArray(value)) {
138
+ // Root array: emit with an empty key so the header is a bare `[N]{...}:`.
139
+ emitArray("", value, 0, lines);
140
+ return lines.join("\n");
141
+ }
142
+ if (isPlainObject(value)) {
143
+ emitObject(value, 0, lines);
144
+ return lines.join("\n");
145
+ }
146
+ return formatScalar(value);
147
+ }
148
+ //# sourceMappingURL=toon.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toon.js","sourceRoot":"","sources":["../../src/toon.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@oasissys/sqlcl-cli",
3
+ "version": "0.1.2",
4
+ "packageManager": "pnpm@11.9.0",
5
+ "description": "oasis-sqlcl — AXI CLI for Oracle SQLcl on the Oasis databases: manage connections and run SQL with token-efficient TOON output",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/oasissys/sqlcl-cli.git"
10
+ },
11
+ "keywords": [
12
+ "sqlcl",
13
+ "oracle",
14
+ "sql",
15
+ "cli",
16
+ "agent",
17
+ "axi",
18
+ "toon",
19
+ "oasis"
20
+ ],
21
+ "bin": {
22
+ "oasis-sqlcl": "./dist/bin/oasis-sqlcl.js"
23
+ },
24
+ "files": [
25
+ "dist",
26
+ "skills/sqlcl-cli",
27
+ "LICENSE",
28
+ "README.md"
29
+ ],
30
+ "scripts": {
31
+ "build": "tsc",
32
+ "build:skill": "tsx scripts/build-skill.ts",
33
+ "test": "vitest run",
34
+ "test:watch": "vitest",
35
+ "lint": "eslint .",
36
+ "dev": "tsx bin/oasis-sqlcl.ts",
37
+ "prepublishOnly": "npm run build"
38
+ },
39
+ "license": "MIT",
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "devDependencies": {
44
+ "@eslint/js": "^10.0.1",
45
+ "@types/node": "^22.0.0",
46
+ "eslint": "^10.1.0",
47
+ "globals": "^17.4.0",
48
+ "tsx": "^4.0.0",
49
+ "typescript": "^5.7.0",
50
+ "typescript-eslint": "^8.58.0",
51
+ "vitest": "^3.0.0"
52
+ },
53
+ "publishConfig": {
54
+ "access": "public"
55
+ }
56
+ }