@pgsage/cli 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/README.md ADDED
@@ -0,0 +1,85 @@
1
+ # @pgsage/cli
2
+
3
+ Command-line interface for pgsage. Wraps `@pgsage/core` and formats results for terminal output.
4
+
5
+ ---
6
+
7
+ ## Usage
8
+
9
+ ```sh
10
+ pgsage query <question> [options]
11
+ ```
12
+
13
+ ### Options
14
+
15
+ | Flag | Description | Default |
16
+ |------|-------------|---------|
17
+ | `--json` | Output the full response as JSON | — |
18
+ | `--sql` | Output only the generated SQL | — |
19
+ | `--raw` | Output result rows as a JSON array | — |
20
+ | `--model <m>` | Planner/explainer model | `anthropic/claude-sonnet-4-6` |
21
+ | `--top-k <n>` | Schema chunks to retrieve | `10` |
22
+ | `--row-limit <n>` | Maximum rows to return | `500` |
23
+ | `-v, --version` | Show version | — |
24
+ | `-h, --help` | Show help | — |
25
+
26
+ ---
27
+
28
+ ## Examples
29
+
30
+ ```sh
31
+ # Simple lookup — default table output
32
+ pgsage query "What is the median household income in Providence County, RI?"
33
+
34
+ # Ranking
35
+ pgsage query "Top 10 counties by total population"
36
+
37
+ # Cross-category filter
38
+ pgsage query "Counties with median income above \$100k and homeownership below 60%"
39
+
40
+ # Ambiguous question — surfaces planner assumptions
41
+ pgsage query "Where do rich people live?"
42
+
43
+ # Pipe SQL to psql
44
+ pgsage query "Top 5 states by Gini index" --sql | psql $DATABASE_URL
45
+
46
+ # Parse rows with jq
47
+ pgsage query "Median rent in California counties" --raw | jq '.[0]'
48
+
49
+ # Full structured response
50
+ pgsage query "Which state has the lowest poverty rate?" --json | jq '.answer'
51
+ ```
52
+
53
+ ---
54
+
55
+ ## Output Formats
56
+
57
+ | Flag | What you get |
58
+ |------|-------------|
59
+ | _(default)_ | Coloured table with answer, assumptions, SQL, and row data |
60
+ | `--json` | Complete `QueryResponse` as pretty-printed JSON |
61
+ | `--sql` | Just the generated SQL — pipe-friendly |
62
+ | `--raw` | Result rows as a JSON array — jq-friendly |
63
+
64
+ ---
65
+
66
+ ## Environment
67
+
68
+ Reads from `.env` at the repo root via `--env-file=../../.env` (set in the binary wrapper). Required variables:
69
+
70
+ ```sh
71
+ DATABASE_URL=postgres://...
72
+ DATABASE_READONLY_URL=postgres://...
73
+ ANTHROPIC_API_KEY=sk-ant-...
74
+ VOYAGE_API_KEY=pa-...
75
+ ```
76
+
77
+ ---
78
+
79
+ ## Testing
80
+
81
+ ```sh
82
+ pnpm --filter @pgsage/cli test
83
+ ```
84
+
85
+ 16 unit tests covering all four formatters (`formatTable`, `formatJson`, `formatSql`, `formatRaw`).
@@ -0,0 +1,31 @@
1
+ /**
2
+ * CLI configuration loader.
3
+ *
4
+ * Reads connection strings and API keys from environment variables,
5
+ * constructs pg pools and a VoyageEmbeddingProvider, and returns a
6
+ * ready-to-use OrchestratorConfig.
7
+ *
8
+ * Environment variables:
9
+ * DATABASE_URL — required. read-write connection string.
10
+ * DATABASE_READONLY_URL — optional. read-only connection string.
11
+ * Falls back to DATABASE_URL if not set.
12
+ * VOYAGE_API_KEY — required. Voyage AI API key.
13
+ * ANTHROPIC_API_KEY — required. Anthropic API key (read by Mastra).
14
+ */
15
+ import { type OrchestratorConfig } from "@pgsage/core";
16
+ export interface CliConfig {
17
+ agentConfig: OrchestratorConfig;
18
+ /** Close both pools when done. */
19
+ cleanup: () => Promise<void>;
20
+ }
21
+ export interface CliOptions {
22
+ model?: string;
23
+ topK?: number;
24
+ rowLimit?: number;
25
+ }
26
+ /**
27
+ * Load CLI configuration from environment variables.
28
+ * Throws with a human-readable message if a required variable is missing.
29
+ */
30
+ export declare function loadConfig(options?: CliOptions): CliConfig;
31
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,kBAAkB,CAAC;IAChC,kCAAkC;IAClC,OAAO,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,OAAO,GAAE,UAAe,GAAG,SAAS,CAyB9D"}
package/dist/config.js ADDED
@@ -0,0 +1,49 @@
1
+ /**
2
+ * CLI configuration loader.
3
+ *
4
+ * Reads connection strings and API keys from environment variables,
5
+ * constructs pg pools and a VoyageEmbeddingProvider, and returns a
6
+ * ready-to-use OrchestratorConfig.
7
+ *
8
+ * Environment variables:
9
+ * DATABASE_URL — required. read-write connection string.
10
+ * DATABASE_READONLY_URL — optional. read-only connection string.
11
+ * Falls back to DATABASE_URL if not set.
12
+ * VOYAGE_API_KEY — required. Voyage AI API key.
13
+ * ANTHROPIC_API_KEY — required. Anthropic API key (read by Mastra).
14
+ */
15
+ import { createPool, createReadonlyPool, VoyageEmbeddingProvider, } from "@pgsage/core";
16
+ /**
17
+ * Load CLI configuration from environment variables.
18
+ * Throws with a human-readable message if a required variable is missing.
19
+ */
20
+ export function loadConfig(options = {}) {
21
+ const databaseUrl = requireEnv("DATABASE_URL");
22
+ const readonlyUrl = process.env["DATABASE_READONLY_URL"] ?? databaseUrl;
23
+ const voyageApiKey = requireEnv("VOYAGE_API_KEY");
24
+ requireEnv("ANTHROPIC_API_KEY"); // validated here; Mastra reads it from env
25
+ const pool = createPool(databaseUrl, { max: 3 });
26
+ const readonlyPool = createReadonlyPool(readonlyUrl, { max: 3 });
27
+ const embedder = new VoyageEmbeddingProvider({ apiKey: voyageApiKey });
28
+ const agentConfig = {
29
+ pool,
30
+ readonlyPool,
31
+ embedder,
32
+ model: options.model,
33
+ topK: options.topK,
34
+ rowLimit: options.rowLimit,
35
+ };
36
+ const cleanup = async () => {
37
+ await Promise.allSettled([pool.end(), readonlyPool.end()]);
38
+ };
39
+ return { agentConfig, cleanup };
40
+ }
41
+ function requireEnv(name) {
42
+ const val = process.env[name];
43
+ if (!val) {
44
+ throw new Error(`Missing required environment variable: ${name}\n` +
45
+ `Set it in your shell or in a .env file and use --env-file.`);
46
+ }
47
+ return val;
48
+ }
49
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,UAAU,EACV,kBAAkB,EAClB,uBAAuB,GAExB,MAAM,cAAc,CAAC;AActB;;;GAGG;AACH,MAAM,UAAU,UAAU,CAAC,UAAsB,EAAE;IACjD,MAAM,WAAW,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,IAAI,WAAW,CAAC;IACxE,MAAM,YAAY,GAAG,UAAU,CAAC,gBAAgB,CAAC,CAAC;IAClD,UAAU,CAAC,mBAAmB,CAAC,CAAC,CAAC,2CAA2C;IAE5E,MAAM,IAAI,GAAG,UAAU,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IACjD,MAAM,YAAY,GAAG,kBAAkB,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;IAEjE,MAAM,QAAQ,GAAG,IAAI,uBAAuB,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,CAAC,CAAC;IAEvE,MAAM,WAAW,GAAuB;QACtC,IAAI;QACJ,YAAY;QACZ,QAAQ;QACR,KAAK,EAAE,OAAO,CAAC,KAAK;QACpB,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,QAAQ,EAAE,OAAO,CAAC,QAAQ;KAC3B,CAAC;IAEF,MAAM,OAAO,GAAG,KAAK,IAAmB,EAAE;QACxC,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;IAC7D,CAAC,CAAC;IAEF,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,CAAC;AAClC,CAAC;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,KAAK,CACb,0CAA0C,IAAI,IAAI;YAChD,4DAA4D,CAC/D,CAAC;IACJ,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Output formatters for the pgsage CLI.
3
+ *
4
+ * Four output modes:
5
+ * table — default. coloured table with assumptions + SQL footer.
6
+ * json — full QueryResponse serialised as pretty-printed JSON.
7
+ * sql — only the generated SQL, suitable for piping to psql.
8
+ * raw — rows as a JSON array, suitable for jq.
9
+ */
10
+ import type { QueryResponse } from "@pgsage/core";
11
+ export declare function formatTable(response: QueryResponse): string;
12
+ export declare function formatJson(response: QueryResponse): string;
13
+ export declare function formatSql(response: QueryResponse): string;
14
+ export declare function formatRaw(response: QueryResponse): string;
15
+ export declare function formatError(message: string): string;
16
+ /**
17
+ * Writes a status message to stderr (so it doesn't pollute stdout piped output).
18
+ * Returns a stop function that clears the line.
19
+ */
20
+ export declare function startSpinner(message: string): () => void;
21
+ //# sourceMappingURL=format.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.d.ts","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAGH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAoBlD,wBAAgB,WAAW,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAiE3D;AAoCD,wBAAgB,UAAU,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAE1D;AAMD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEzD;AAMD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,aAAa,GAAG,MAAM,CAEzD;AAMD,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD;AAMD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,IAAI,CAmBxD"}
package/dist/format.js ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Output formatters for the pgsage CLI.
3
+ *
4
+ * Four output modes:
5
+ * table — default. coloured table with assumptions + SQL footer.
6
+ * json — full QueryResponse serialised as pretty-printed JSON.
7
+ * sql — only the generated SQL, suitable for piping to psql.
8
+ * raw — rows as a JSON array, suitable for jq.
9
+ */
10
+ import chalk from "chalk";
11
+ // ---------------------------------------------------------------------------
12
+ // Helpers
13
+ // ---------------------------------------------------------------------------
14
+ /** Safely stringify any primitive DB value without triggering no-base-to-string. */
15
+ function primitiveString(val) {
16
+ if (val === null || val === undefined)
17
+ return "";
18
+ if (typeof val === "string")
19
+ return val;
20
+ if (typeof val === "number" || typeof val === "boolean" || typeof val === "bigint") {
21
+ return String(val);
22
+ }
23
+ return JSON.stringify(val);
24
+ }
25
+ // ---------------------------------------------------------------------------
26
+ // Table output (default)
27
+ // ---------------------------------------------------------------------------
28
+ export function formatTable(response) {
29
+ const lines = [];
30
+ if (response.rowCount === 0) {
31
+ lines.push(chalk.yellow(" No results returned."));
32
+ appendFooter(lines, response);
33
+ return lines.join("\n");
34
+ }
35
+ // Build column widths
36
+ const fields = response.fields.length > 0
37
+ ? response.fields
38
+ : Object.keys(response.rows[0] ?? {});
39
+ const widths = fields.map((f) => f.length);
40
+ for (const row of response.rows) {
41
+ fields.forEach((f, i) => {
42
+ const val = primitiveString(row[f]);
43
+ if (val.length > widths[i])
44
+ widths[i] = val.length;
45
+ });
46
+ }
47
+ // Cap column widths at 40 chars for readability
48
+ const cappedWidths = widths.map((w) => Math.min(w, 40));
49
+ const sep = "─".repeat(cappedWidths.reduce((s, w) => s + w + 3, 1));
50
+ // Header
51
+ lines.push(chalk.dim(" " + sep));
52
+ const headerCells = fields
53
+ .map((f, i) => chalk.bold(f.padEnd(cappedWidths[i])))
54
+ .join(chalk.dim(" │ "));
55
+ lines.push(chalk.dim(" │ ") + headerCells + chalk.dim(" │"));
56
+ lines.push(chalk.dim(" " + sep));
57
+ // Rows
58
+ for (const row of response.rows) {
59
+ const cells = fields
60
+ .map((f, i) => {
61
+ const cellVal = row[f];
62
+ const val = cellVal === null || cellVal === undefined
63
+ ? chalk.dim("null")
64
+ : primitiveString(cellVal);
65
+ const truncated = val.length > 40 ? val.slice(0, 37) + "..." : val;
66
+ return truncated.padEnd(cappedWidths[i]);
67
+ })
68
+ .join(chalk.dim(" │ "));
69
+ lines.push(chalk.dim(" │ ") + cells + chalk.dim(" │"));
70
+ }
71
+ lines.push(chalk.dim(" " + sep));
72
+ // Row count / truncation notice
73
+ if (response.truncated) {
74
+ lines.push(chalk.yellow(`\n Showing first ${response.rowCount} rows (result truncated — increase --row-limit to see more).`));
75
+ }
76
+ else {
77
+ lines.push(chalk.dim(`\n ${response.rowCount} row${response.rowCount === 1 ? "" : "s"}`));
78
+ }
79
+ appendFooter(lines, response);
80
+ return lines.join("\n");
81
+ }
82
+ function appendFooter(lines, response) {
83
+ // Assumptions
84
+ if (response.assumptions.length > 0) {
85
+ lines.push("");
86
+ lines.push(chalk.cyan(" Assumptions:"));
87
+ for (const a of response.assumptions) {
88
+ lines.push(chalk.dim(" · ") + a);
89
+ }
90
+ }
91
+ // Self-correction notice
92
+ if (response.attempts.length > 0) {
93
+ lines.push("");
94
+ lines.push(chalk.dim(` (Self-corrected after ${response.attempts.length} failed attempt${response.attempts.length === 1 ? "" : "s"}.)`));
95
+ }
96
+ // SQL
97
+ lines.push("");
98
+ lines.push(chalk.cyan(" SQL:"));
99
+ const indented = response.sql
100
+ .split("\n")
101
+ .map((l) => chalk.dim(" ") + chalk.green(l))
102
+ .join("\n");
103
+ lines.push(indented);
104
+ }
105
+ // ---------------------------------------------------------------------------
106
+ // JSON output
107
+ // ---------------------------------------------------------------------------
108
+ export function formatJson(response) {
109
+ return JSON.stringify(response, null, 2);
110
+ }
111
+ // ---------------------------------------------------------------------------
112
+ // SQL-only output
113
+ // ---------------------------------------------------------------------------
114
+ export function formatSql(response) {
115
+ return response.sql;
116
+ }
117
+ // ---------------------------------------------------------------------------
118
+ // Raw rows output (JSON array)
119
+ // ---------------------------------------------------------------------------
120
+ export function formatRaw(response) {
121
+ return JSON.stringify(response.rows, null, 2);
122
+ }
123
+ // ---------------------------------------------------------------------------
124
+ // Error output
125
+ // ---------------------------------------------------------------------------
126
+ export function formatError(message) {
127
+ return chalk.red(" Error: ") + message;
128
+ }
129
+ // ---------------------------------------------------------------------------
130
+ // Spinner helpers (simple dots — no ora dependency)
131
+ // ---------------------------------------------------------------------------
132
+ /**
133
+ * Writes a status message to stderr (so it doesn't pollute stdout piped output).
134
+ * Returns a stop function that clears the line.
135
+ */
136
+ export function startSpinner(message) {
137
+ const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];
138
+ let i = 0;
139
+ const isTTY = process.stderr.isTTY;
140
+ if (!isTTY) {
141
+ process.stderr.write(message + "\n");
142
+ return () => { };
143
+ }
144
+ const timer = setInterval(() => {
145
+ process.stderr.write(`\r${chalk.cyan(frames[i % frames.length])} ${message}`);
146
+ i++;
147
+ }, 80);
148
+ return () => {
149
+ clearInterval(timer);
150
+ process.stderr.write("\r\x1b[K"); // clear line
151
+ };
152
+ }
153
+ //# sourceMappingURL=format.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"format.js","sourceRoot":"","sources":["../src/format.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pgsage CLI — thin wrapper over the @pgsage/core SDK.
4
+ *
5
+ * Usage:
6
+ * pgsage query "What is the median household income in Rhode Island?"
7
+ * pgsage query "Top 10 counties by population" --json
8
+ * pgsage query "Show unemployment in Wayne County, MI" --sql
9
+ * pgsage query "Counties with high income and low homeownership" --raw
10
+ *
11
+ * Environment variables (set in shell or .env file):
12
+ * DATABASE_URL — required
13
+ * DATABASE_READONLY_URL — optional (falls back to DATABASE_URL)
14
+ * VOYAGE_API_KEY — required
15
+ * ANTHROPIC_API_KEY — required
16
+ */
17
+ export {};
18
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;GAcG"}
package/dist/index.js ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * pgsage CLI — thin wrapper over the @pgsage/core SDK.
4
+ *
5
+ * Usage:
6
+ * pgsage query "What is the median household income in Rhode Island?"
7
+ * pgsage query "Top 10 counties by population" --json
8
+ * pgsage query "Show unemployment in Wayne County, MI" --sql
9
+ * pgsage query "Counties with high income and low homeownership" --raw
10
+ *
11
+ * Environment variables (set in shell or .env file):
12
+ * DATABASE_URL — required
13
+ * DATABASE_READONLY_URL — optional (falls back to DATABASE_URL)
14
+ * VOYAGE_API_KEY — required
15
+ * ANTHROPIC_API_KEY — required
16
+ */
17
+ import { Command } from "commander";
18
+ import chalk from "chalk";
19
+ import { createAgent } from "@pgsage/core";
20
+ import { loadConfig } from "./config.js";
21
+ import { formatTable, formatJson, formatSql, formatRaw, formatError, startSpinner, } from "./format.js";
22
+ // ---------------------------------------------------------------------------
23
+ // Version (read from package.json at startup)
24
+ // ---------------------------------------------------------------------------
25
+ // Using a hardcoded version avoids needing JSON import assertions,
26
+ // which have inconsistent support across Node/TS versions.
27
+ const VERSION = "0.1.0";
28
+ // ---------------------------------------------------------------------------
29
+ // Program definition
30
+ // ---------------------------------------------------------------------------
31
+ const program = new Command();
32
+ program
33
+ .name("pgsage")
34
+ .description("Schema-aware SQL agent — ask natural-language questions against your Postgres database.")
35
+ .version(VERSION, "-v, --version");
36
+ program
37
+ .command("query <question>")
38
+ .description("Ask a natural-language question and get SQL results.")
39
+ .option("--json", "Output the full response as JSON (overrides other format flags)")
40
+ .option("--sql", "Output only the generated SQL — no execution, no results")
41
+ .option("--raw", "Output result rows as a JSON array")
42
+ .option("--model <model>", "Planner model to use (default: anthropic/claude-sonnet-4-6)")
43
+ .option("--top-k <n>", "Number of schema chunks to retrieve (default: 10)", parseInt)
44
+ .option("--row-limit <n>", "Maximum rows to return (default: 500)", parseInt)
45
+ .action(async (question, opts) => {
46
+ let cleanup;
47
+ try {
48
+ // Load config (validates env vars)
49
+ const config = loadConfig({
50
+ model: opts.model,
51
+ topK: opts.topK,
52
+ rowLimit: opts.rowLimit,
53
+ });
54
+ cleanup = config.cleanup;
55
+ // If --sql flag: plan only, don't execute
56
+ if (opts.sql) {
57
+ const stop = startSpinner("Planning query…");
58
+ try {
59
+ const agent = createAgent(config.agentConfig);
60
+ // We can't get SQL-only from the current agent interface (it always
61
+ // executes), so we run the full query but only print the SQL.
62
+ const response = await agent.query(question);
63
+ stop();
64
+ process.stdout.write(formatSql(response) + "\n");
65
+ }
66
+ catch (err) {
67
+ stop();
68
+ throw err;
69
+ }
70
+ return;
71
+ }
72
+ // Full query
73
+ const stop = startSpinner("Thinking…");
74
+ try {
75
+ const agent = createAgent(config.agentConfig);
76
+ const response = await agent.query(question);
77
+ stop();
78
+ if (opts.json) {
79
+ process.stdout.write(formatJson(response) + "\n");
80
+ }
81
+ else if (opts.raw) {
82
+ process.stdout.write(formatRaw(response) + "\n");
83
+ }
84
+ else {
85
+ process.stdout.write("\n" + formatTable(response) + "\n\n");
86
+ }
87
+ }
88
+ catch (err) {
89
+ stop();
90
+ throw err;
91
+ }
92
+ }
93
+ catch (err) {
94
+ const message = err instanceof Error ? err.message : String(err);
95
+ process.stderr.write(formatError(message) + "\n");
96
+ process.exitCode = 1;
97
+ }
98
+ finally {
99
+ await cleanup?.();
100
+ }
101
+ });
102
+ // ---------------------------------------------------------------------------
103
+ // Help output styling
104
+ // ---------------------------------------------------------------------------
105
+ program.addHelpText("after", `
106
+ ${chalk.bold("Examples:")}
107
+ ${chalk.dim("$")} pgsage query "What is the median household income in Providence County, RI?"
108
+ ${chalk.dim("$")} pgsage query "Top 10 counties by population" --json
109
+ ${chalk.dim("$")} pgsage query "Show unemployment in Wayne County, Michigan" --sql
110
+ ${chalk.dim("$")} pgsage query "Counties with income above $100k and homeownership below 60%" --raw
111
+
112
+ ${chalk.bold("Environment variables:")}
113
+ DATABASE_URL Read-write Postgres connection string (required)
114
+ DATABASE_READONLY_URL Read-only connection string (optional, falls back to DATABASE_URL)
115
+ VOYAGE_API_KEY Voyage AI API key (required)
116
+ ANTHROPIC_API_KEY Anthropic API key (required)
117
+
118
+ ${chalk.bold("Tip:")}
119
+ Put your env vars in a .env file and run with:
120
+ ${chalk.dim("$")} node --env-file=.env $(which pgsage) query "..."
121
+ `);
122
+ // ---------------------------------------------------------------------------
123
+ // Parse and run
124
+ // ---------------------------------------------------------------------------
125
+ program.parse();
126
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@pgsage/cli",
3
+ "version": "0.1.0",
4
+ "description": "CLI wrapper for the pgsage SDK: npx pgsage query \"...\"",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "bin": {
8
+ "pgsage": "./dist/index.js"
9
+ },
10
+ "main": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "default": "./dist/index.js"
16
+ }
17
+ },
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/hownowbrowncow/pgsage.git",
24
+ "directory": "packages/cli"
25
+ },
26
+ "homepage": "https://github.com/hownowbrowncow/pgsage/tree/main/packages/cli",
27
+ "bugs": "https://github.com/hownowbrowncow/pgsage/issues",
28
+ "keywords": [
29
+ "postgres",
30
+ "sql",
31
+ "ai",
32
+ "cli",
33
+ "pgsage",
34
+ "natural-language",
35
+ "text-to-sql"
36
+ ],
37
+ "publishConfig": {
38
+ "access": "public"
39
+ },
40
+ "dependencies": {
41
+ "chalk": "^6.0.0",
42
+ "commander": "^15.0.0",
43
+ "pg": "8.23.0",
44
+ "@pgsage/core": "0.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "26.2.0",
48
+ "@types/pg": "8.23.1",
49
+ "typescript": "6.0.3",
50
+ "vitest": "4.1.11"
51
+ },
52
+ "scripts": {
53
+ "build": "tsc -p tsconfig.json",
54
+ "typecheck": "tsc -p tsconfig.typecheck.json",
55
+ "lint": "eslint .",
56
+ "test": "vitest run --passWithNoTests"
57
+ }
58
+ }