postgresai 0.16.0-dev.10 → 0.16.0-dev.11
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/CHANGELOG.md +11 -0
- package/README.md +1 -84
- package/bin/postgres-ai.ts +140 -540
- package/bun.lock +4 -4
- package/dist/bin/postgres-ai.js +790 -2282
- package/lib/checkup-summary.ts +25 -0
- package/lib/checkup.ts +88 -2
- package/lib/init.ts +29 -0
- package/lib/joe.ts +333 -391
- package/lib/mcp-server.ts +0 -625
- package/lib/util.ts +145 -29
- package/package.json +1 -1
- package/test/auth.test.ts +30 -1
- package/test/checkup.integration.test.ts +31 -21
- package/test/checkup.test.ts +48 -3
- package/test/init.test.ts +35 -0
- package/test/joe.cli.test.ts +355 -196
- package/test/joe.test.ts +565 -568
- package/test/schema-validation.test.ts +40 -0
- package/test/test-utils.ts +5 -0
- package/test/util.test.ts +116 -0
- package/lib/dblab.ts +0 -449
- package/test/dblab.cli.test.ts +0 -373
- package/test/dblab.test.ts +0 -488
- package/test/e2e/pgai-e2e-smoke.sh +0 -192
package/bin/postgres-ai.ts
CHANGED
|
@@ -16,29 +16,13 @@ import { fetchReports, fetchAllReports, fetchReportFiles, fetchReportFileData, r
|
|
|
16
16
|
import {
|
|
17
17
|
executeJoeCommand,
|
|
18
18
|
listProjects,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
formatJoeResult,
|
|
19
|
+
getCommandOutput,
|
|
20
|
+
formatJoeOutput,
|
|
22
21
|
formatProjectsTable,
|
|
23
22
|
DEFAULT_BUDGET_MS,
|
|
24
23
|
type JoeCommand,
|
|
25
24
|
type ExecuteJoeOutcome,
|
|
26
25
|
} from "../lib/joe";
|
|
27
|
-
import {
|
|
28
|
-
resolveDblabInstanceId,
|
|
29
|
-
createClone,
|
|
30
|
-
listClones,
|
|
31
|
-
getClone,
|
|
32
|
-
resetClone,
|
|
33
|
-
destroyClone,
|
|
34
|
-
listBranches,
|
|
35
|
-
createBranch,
|
|
36
|
-
deleteBranch,
|
|
37
|
-
branchLog,
|
|
38
|
-
listSnapshots,
|
|
39
|
-
createSnapshot,
|
|
40
|
-
destroySnapshot,
|
|
41
|
-
} from "../lib/dblab";
|
|
42
26
|
import { resolveBaseUrls } from "../lib/util";
|
|
43
27
|
import { registerAasCollection, parseVcpus } from "../lib/aas-onboard";
|
|
44
28
|
import { uploadFile, downloadFile, buildMarkdownLink, uploadAttachments, appendAttachmentsToContent } from "../lib/storage";
|
|
@@ -361,12 +345,18 @@ function prepareUploadConfig(
|
|
|
361
345
|
console.error("Tip: run 'postgresai auth' or pass --api-key / set PGAI_API_KEY");
|
|
362
346
|
return null; // Signal to exit
|
|
363
347
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
348
|
+
if (opts.markdown) {
|
|
349
|
+
console.error("Notice: no API key configured — regular report upload is disabled.");
|
|
350
|
+
console.error(" The full report JSON will still be sent to the PostgresAI API for markdown conversion.");
|
|
351
|
+
console.error(" To avoid sending report data, replace --markdown with --no-upload and --json or --output.");
|
|
352
|
+
} else {
|
|
353
|
+
// No credentials and upload not explicitly requested: fall back to
|
|
354
|
+
// local-only mode, but say so prominently — skipping the upload silently
|
|
355
|
+
// hides the fact that results never reach the Console.
|
|
356
|
+
console.error("Notice: no API key configured — results will NOT be uploaded to PostgresAI.");
|
|
357
|
+
console.error(" To upload: run 'postgresai auth login' or pass --api-key / set PGAI_API_KEY.");
|
|
358
|
+
console.error(" To run locally without this notice, pass --no-upload.");
|
|
359
|
+
}
|
|
370
360
|
return undefined; // Skip upload, run checks locally
|
|
371
361
|
}
|
|
372
362
|
|
|
@@ -2022,7 +2012,7 @@ program
|
|
|
2022
2012
|
"project name or ID for remote upload (used with --upload; defaults to config defaultProject; auto-generated on first run)"
|
|
2023
2013
|
)
|
|
2024
2014
|
.option("--json", "output JSON to stdout")
|
|
2025
|
-
.option("--markdown", "output markdown
|
|
2015
|
+
.option("--markdown", "output markdown via PostgresAI API (transmits the full report JSON)")
|
|
2026
2016
|
.addHelpText(
|
|
2027
2017
|
"after",
|
|
2028
2018
|
[
|
|
@@ -2036,7 +2026,7 @@ program
|
|
|
2036
2026
|
" postgresai checkup postgresql://user:pass@host:5432/db --check-id H002",
|
|
2037
2027
|
" postgresai checkup postgresql://user:pass@host:5432/db --output ./reports",
|
|
2038
2028
|
" postgresai checkup postgresql://user:pass@host:5432/db --no-upload --json",
|
|
2039
|
-
" postgresai checkup postgresql://user:pass@host:5432/db --
|
|
2029
|
+
" postgresai checkup postgresql://user:pass@host:5432/db --markdown",
|
|
2040
2030
|
].join("\n")
|
|
2041
2031
|
)
|
|
2042
2032
|
.action(async (checkIdOrConn: string | undefined, connArg: string | undefined, opts: CheckupOptions, cmd: Command) => {
|
|
@@ -2086,9 +2076,14 @@ program
|
|
|
2086
2076
|
process.exitCode = 1;
|
|
2087
2077
|
return;
|
|
2088
2078
|
}
|
|
2089
|
-
// Note: --json, --markdown and --upload/--no-upload are independent flags.
|
|
2090
|
-
// Use --no-upload to explicitly disable upload when using --json or --markdown.
|
|
2091
2079
|
const uploadExplicitlyDisabled = opts.upload === false;
|
|
2080
|
+
if (uploadExplicitlyDisabled && shouldConvertMarkdown) {
|
|
2081
|
+
console.error("Error: --no-upload and --markdown are mutually exclusive");
|
|
2082
|
+
console.error("Markdown conversion is performed by the PostgresAI API and transmits the full report JSON.");
|
|
2083
|
+
console.error("Drop --no-upload to allow transmission, or use --json or --output for local-only output.");
|
|
2084
|
+
process.exitCode = 1;
|
|
2085
|
+
return;
|
|
2086
|
+
}
|
|
2092
2087
|
let shouldUpload = !uploadExplicitlyDisabled;
|
|
2093
2088
|
|
|
2094
2089
|
// Preflight: validate/create output directory BEFORE connecting / running checks.
|
|
@@ -2334,7 +2329,9 @@ program
|
|
|
2334
2329
|
|
|
2335
2330
|
console.log('\nFor details:');
|
|
2336
2331
|
console.log(' --json Output JSON');
|
|
2337
|
-
|
|
2332
|
+
if (!uploadExplicitlyDisabled) {
|
|
2333
|
+
console.log(' --markdown Output markdown via PostgresAI API');
|
|
2334
|
+
}
|
|
2338
2335
|
console.log(' --output <dir> Save to directory');
|
|
2339
2336
|
}
|
|
2340
2337
|
} catch (error) {
|
|
@@ -3957,13 +3954,9 @@ targets
|
|
|
3957
3954
|
// Authentication and API key management
|
|
3958
3955
|
const auth = program.command("auth").description("authentication and API key management");
|
|
3959
3956
|
|
|
3960
|
-
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
.option("--set-key <key>", "store API key directly without OAuth flow")
|
|
3964
|
-
.option("--port <port>", "local callback server port (default: random)", parseInt)
|
|
3965
|
-
.option("--debug", "enable debug output")
|
|
3966
|
-
.action(async (opts: { setKey?: string; port?: number; debug?: boolean }) => {
|
|
3957
|
+
type AuthLoginOptions = { setKey?: string; port?: number; debug?: boolean };
|
|
3958
|
+
|
|
3959
|
+
async function runAuthLogin(opts: AuthLoginOptions) {
|
|
3967
3960
|
// If --set-key is provided, store it directly without OAuth
|
|
3968
3961
|
if (opts.setKey) {
|
|
3969
3962
|
const trimmedKey = opts.setKey.trim();
|
|
@@ -4217,7 +4210,21 @@ auth
|
|
|
4217
4210
|
console.error(`Authentication error: ${message}`);
|
|
4218
4211
|
process.exit(1);
|
|
4219
4212
|
}
|
|
4220
|
-
|
|
4213
|
+
}
|
|
4214
|
+
|
|
4215
|
+
function configureLoginCommand(command: Command): Command {
|
|
4216
|
+
return command
|
|
4217
|
+
.description("authenticate via browser (OAuth) or store API key directly")
|
|
4218
|
+
.option("--set-key <key>", "store API key directly without OAuth flow")
|
|
4219
|
+
.option("--port <port>", "local callback server port (default: random)", parseInt)
|
|
4220
|
+
.option("--debug", "enable debug output");
|
|
4221
|
+
}
|
|
4222
|
+
|
|
4223
|
+
configureLoginCommand(auth.command("login", { isDefault: true }))
|
|
4224
|
+
.action(runAuthLogin);
|
|
4225
|
+
|
|
4226
|
+
configureLoginCommand(program.command("login"))
|
|
4227
|
+
.action(runAuthLogin);
|
|
4221
4228
|
|
|
4222
4229
|
auth
|
|
4223
4230
|
.command("show-key")
|
|
@@ -5403,58 +5410,24 @@ function tryParseJson(s: string): unknown {
|
|
|
5403
5410
|
}
|
|
5404
5411
|
|
|
5405
5412
|
// ---------------------------------------------------------------------------
|
|
5406
|
-
// Joe API v2
|
|
5407
|
-
//
|
|
5408
|
-
//
|
|
5409
|
-
//
|
|
5410
|
-
//
|
|
5413
|
+
// Joe command surface (Joe API v2 CLI v1, issue #438). Every verb builds the
|
|
5414
|
+
// RAW command text Joe's /webui/command dispatches (`plan <sql>`, `\d users`),
|
|
5415
|
+
// runs it via the synchronous `v1.joe_command_run`, and polls
|
|
5416
|
+
// `v1.joe_command_output` until `ok`/`error` within the one-shot budget.
|
|
5417
|
+
// `--project` accepts a numeric id OR an alias/name/label. The backend rpcs
|
|
5418
|
+
// are mocked in tests.
|
|
5411
5419
|
// ---------------------------------------------------------------------------
|
|
5412
5420
|
|
|
5413
5421
|
interface JoeCliOpts {
|
|
5414
5422
|
project?: string;
|
|
5415
|
-
|
|
5416
|
-
newSession?: boolean;
|
|
5423
|
+
instanceId?: string;
|
|
5417
5424
|
budget?: number;
|
|
5418
|
-
query?: string;
|
|
5419
5425
|
variant?: string;
|
|
5420
5426
|
debug?: boolean;
|
|
5421
5427
|
json?: boolean;
|
|
5422
5428
|
}
|
|
5423
5429
|
|
|
5424
|
-
|
|
5425
|
-
// DBLab companion command surface (Joe API v2 · SPEC §8)
|
|
5426
|
-
//
|
|
5427
|
-
// `pgai dblab clone|branch|snapshot …` proxy the SAME Platform DBLab API the Console
|
|
5428
|
-
// already drives (v1.dblab_api_call). Every verb is `--project <id|alias>`
|
|
5429
|
-
// scoped: the project's single DBLab instance is resolved server-side listing,
|
|
5430
|
-
// then the verb is proxied. The DESTRUCTIVE verbs — the HTTP DELETEs: clone
|
|
5431
|
-
// destroy, branch delete, snapshot destroy — require the `joe:admin` token
|
|
5432
|
-
// scope (user-approved tightening, !612); clone reset is a POST and is NOT
|
|
5433
|
-
// gated. Read/list/create verbs stay at org-token + project-allowlist level.
|
|
5434
|
-
// All enforced backend-side — a PT403 surfaces here as an HTTP 403.
|
|
5435
|
-
//
|
|
5436
|
-
// NOTE: kept in its own region and its own lib (cli/lib/dblab.ts) so it does not
|
|
5437
|
-
// collide with the parallel Joe-commands work.
|
|
5438
|
-
// ============================================================================
|
|
5439
|
-
|
|
5440
|
-
interface DblabCmdOpts {
|
|
5441
|
-
project?: string;
|
|
5442
|
-
debug?: boolean;
|
|
5443
|
-
json?: boolean;
|
|
5444
|
-
}
|
|
5445
|
-
|
|
5446
|
-
function inferCommandFromResult(r: { command?: JoeCommand; plan_json?: unknown; plan_text?: string | null; row_count?: number | null; result_rows?: unknown[] | null; hypo_plan?: unknown; hypo_used?: boolean | null; terminated?: boolean | null; pid?: number | null; reset?: boolean | null; snapshot?: unknown }): JoeCommand | null {
|
|
5447
|
-
if (r.command) return r.command;
|
|
5448
|
-
if (r.plan_text !== undefined || r.plan_json !== undefined) return "plan";
|
|
5449
|
-
if (r.row_count !== undefined || r.result_rows !== undefined) return "exec";
|
|
5450
|
-
if (r.hypo_used !== undefined) return "hypo";
|
|
5451
|
-
if (r.terminated !== undefined) return "terminate";
|
|
5452
|
-
if (r.reset !== undefined) return "reset";
|
|
5453
|
-
if (r.snapshot !== undefined) return "activity";
|
|
5454
|
-
return null;
|
|
5455
|
-
}
|
|
5456
|
-
|
|
5457
|
-
function printJoeOutcome(command: JoeCommand, outcome: ExecuteJoeOutcome, json: boolean, budgetMs?: number): void {
|
|
5430
|
+
function printJoeOutcome(outcome: ExecuteJoeOutcome, json: boolean, budgetMs?: number): void {
|
|
5458
5431
|
// The expiry hint reports the ACTUAL effective budget (--budget when given,
|
|
5459
5432
|
// the default otherwise) — not a hardcoded DEFAULT_BUDGET_MS.
|
|
5460
5433
|
const effectiveBudgetMs =
|
|
@@ -5469,7 +5442,6 @@ function printJoeOutcome(command: JoeCommand, outcome: ExecuteJoeOutcome, json:
|
|
|
5469
5442
|
{
|
|
5470
5443
|
command_id: outcome.commandId,
|
|
5471
5444
|
status: outcome.status,
|
|
5472
|
-
session_id: outcome.sessionId,
|
|
5473
5445
|
budget_expired: true,
|
|
5474
5446
|
resume: `pgai joe result ${outcome.commandId}`,
|
|
5475
5447
|
},
|
|
@@ -5479,43 +5451,37 @@ function printJoeOutcome(command: JoeCommand, outcome: ExecuteJoeOutcome, json:
|
|
|
5479
5451
|
);
|
|
5480
5452
|
} else {
|
|
5481
5453
|
console.log(
|
|
5482
|
-
`
|
|
5454
|
+
`started ${outcome.commandId} · ${outcome.status} · budget ${budgetSeconds}s reached — resume: pgai joe result ${outcome.commandId}`
|
|
5483
5455
|
);
|
|
5484
5456
|
}
|
|
5485
5457
|
return;
|
|
5486
5458
|
}
|
|
5487
5459
|
|
|
5488
|
-
const
|
|
5489
|
-
if (outcome.status === "
|
|
5490
|
-
if (
|
|
5491
|
-
console.
|
|
5492
|
-
|
|
5493
|
-
|
|
5494
|
-
|
|
5460
|
+
const output = outcome.output;
|
|
5461
|
+
if (outcome.status === "ok") {
|
|
5462
|
+
if (!output) {
|
|
5463
|
+
console.error(`command ${outcome.commandId} ok but output is empty`);
|
|
5464
|
+
process.exitCode = 1;
|
|
5465
|
+
return;
|
|
5466
|
+
}
|
|
5467
|
+
if (json) console.log(JSON.stringify(output, null, 2));
|
|
5468
|
+
else {
|
|
5469
|
+
console.log(`command ${outcome.commandId} · ok`);
|
|
5470
|
+
const body = formatJoeOutput(output);
|
|
5495
5471
|
if (body) console.log(body);
|
|
5496
5472
|
}
|
|
5497
5473
|
return;
|
|
5498
5474
|
}
|
|
5499
5475
|
|
|
5500
|
-
|
|
5501
|
-
|
|
5502
|
-
|
|
5503
|
-
process.exitCode = 1;
|
|
5504
|
-
return;
|
|
5505
|
-
}
|
|
5506
|
-
|
|
5507
|
-
// Server-side timed_out (the dispatcher gave up). The genuine reply may still
|
|
5508
|
-
// land; suggest a resume, but signal failure with a non-zero exit.
|
|
5509
|
-
console.error(
|
|
5510
|
-
`command ${outcome.commandId} timed out on the server — resume: pgai joe result ${outcome.commandId}`
|
|
5511
|
-
);
|
|
5476
|
+
// Terminal error.
|
|
5477
|
+
if (json && output) console.log(JSON.stringify(output, null, 2));
|
|
5478
|
+
console.error(`command ${outcome.commandId} error: ${output?.error ?? "command failed"}`);
|
|
5512
5479
|
process.exitCode = 1;
|
|
5513
5480
|
}
|
|
5514
5481
|
|
|
5515
5482
|
async function runJoeCli(
|
|
5516
5483
|
command: JoeCommand,
|
|
5517
|
-
|
|
5518
|
-
args: Record<string, unknown> | null,
|
|
5484
|
+
arg: string | null,
|
|
5519
5485
|
opts: JoeCliOpts
|
|
5520
5486
|
): Promise<void> {
|
|
5521
5487
|
try {
|
|
@@ -5528,30 +5494,33 @@ async function runJoeCli(
|
|
|
5528
5494
|
return;
|
|
5529
5495
|
}
|
|
5530
5496
|
const projectRef = (opts.project ?? cfg.defaultProject ?? "").toString().trim();
|
|
5531
|
-
|
|
5532
|
-
|
|
5497
|
+
const instanceRef = (opts.instanceId ?? "").toString().trim();
|
|
5498
|
+
if (!projectRef && !instanceRef) {
|
|
5499
|
+
console.error(
|
|
5500
|
+
"Specify --instance-id <id> (or --project <id|alias> once projects_list is available)."
|
|
5501
|
+
);
|
|
5533
5502
|
process.exitCode = 1;
|
|
5534
5503
|
return;
|
|
5535
5504
|
}
|
|
5536
5505
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
5537
|
-
|
|
5538
|
-
|
|
5506
|
+
if (typeof opts.budget === "number" && (!Number.isFinite(opts.budget) || opts.budget < 0)) {
|
|
5507
|
+
throw new Error("--budget must be a non-negative number of seconds");
|
|
5508
|
+
}
|
|
5509
|
+
const budgetMs = typeof opts.budget === "number" ? opts.budget * 1000 : undefined;
|
|
5539
5510
|
|
|
5540
5511
|
const outcome = await executeJoeCommand({
|
|
5541
5512
|
apiKey,
|
|
5542
5513
|
apiBaseUrl,
|
|
5543
5514
|
command,
|
|
5544
|
-
project: projectRef,
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
session: opts.session ?? null,
|
|
5548
|
-
newSession: !!opts.newSession,
|
|
5515
|
+
project: projectRef || undefined,
|
|
5516
|
+
instanceId: instanceRef || undefined,
|
|
5517
|
+
input: { arg, variant: opts.variant ?? null },
|
|
5549
5518
|
orgId: cfg.orgId ?? undefined,
|
|
5550
5519
|
budgetMs,
|
|
5551
5520
|
debug: !!opts.debug,
|
|
5552
5521
|
});
|
|
5553
5522
|
|
|
5554
|
-
printJoeOutcome(
|
|
5523
|
+
printJoeOutcome(outcome, !!opts.json, budgetMs);
|
|
5555
5524
|
} catch (err) {
|
|
5556
5525
|
const message = err instanceof Error ? err.message : String(err);
|
|
5557
5526
|
console.error(message);
|
|
@@ -5559,40 +5528,37 @@ async function runJoeCli(
|
|
|
5559
5528
|
}
|
|
5560
5529
|
}
|
|
5561
5530
|
|
|
5562
|
-
/** Attach the shared Joe options (--project / --
|
|
5531
|
+
/** Attach the shared Joe options (--instance-id / --project / --budget / --debug / --json). */
|
|
5563
5532
|
function withJoeOptions(cmd: import("commander").Command): import("commander").Command {
|
|
5564
5533
|
return cmd
|
|
5565
|
-
.option(
|
|
5566
|
-
|
|
5567
|
-
|
|
5568
|
-
|
|
5534
|
+
.option(
|
|
5535
|
+
"--instance-id <id>",
|
|
5536
|
+
"target the Joe instance id directly (skips --project resolution; the v1 path while projects_list is unavailable)"
|
|
5537
|
+
)
|
|
5538
|
+
.option("--project <id|alias>", "target project by numeric id OR alias/name (requires projects_list)")
|
|
5539
|
+
.option("--budget <seconds>", "one-shot poll budget in seconds (default 25)", (v) => parseFloat(v))
|
|
5569
5540
|
.option("--debug", "enable debug output")
|
|
5570
5541
|
.option("--json", "output raw JSON");
|
|
5571
5542
|
}
|
|
5572
5543
|
|
|
5573
|
-
// ---------------------------------------------------------------------------
|
|
5574
|
-
// Joe command group — every Joe verb lives under `pgai joe <verb>` (grouped CLI,
|
|
5575
|
-
// USER-AUTHORIZED restructure). The MCP tool names stay flat/unchanged; only the
|
|
5576
|
-
// CLI invocation path is grouped. `projects` stays top-level (org-level, SPEC §6).
|
|
5577
|
-
// ---------------------------------------------------------------------------
|
|
5578
5544
|
const joe = program
|
|
5579
5545
|
.command("joe")
|
|
5580
|
-
.description("Joe
|
|
5546
|
+
.description("Joe — plan/EXPLAIN/exec queries on ephemeral DBLab clones");
|
|
5581
5547
|
|
|
5582
5548
|
withJoeOptions(
|
|
5583
5549
|
joe
|
|
5584
5550
|
.command("plan <sql>")
|
|
5585
5551
|
.description("plan a query (EXPLAIN, plan-only — no execution; the fast/safe default)")
|
|
5586
5552
|
).action(async (sql: string, opts: JoeCliOpts) => {
|
|
5587
|
-
await runJoeCli("plan", sql,
|
|
5553
|
+
await runJoeCli("plan", sql, opts);
|
|
5588
5554
|
});
|
|
5589
5555
|
|
|
5590
5556
|
withJoeOptions(
|
|
5591
5557
|
joe
|
|
5592
5558
|
.command("explain <sql>")
|
|
5593
|
-
.description("EXPLAIN ANALYZE a query (EXECUTES on the
|
|
5559
|
+
.description("EXPLAIN + EXPLAIN ANALYZE a query (EXECUTES on the ephemeral clone)")
|
|
5594
5560
|
).action(async (sql: string, opts: JoeCliOpts) => {
|
|
5595
|
-
await runJoeCli("explain", sql,
|
|
5561
|
+
await runJoeCli("explain", sql, opts);
|
|
5596
5562
|
});
|
|
5597
5563
|
|
|
5598
5564
|
withJoeOptions(
|
|
@@ -5600,24 +5566,23 @@ withJoeOptions(
|
|
|
5600
5566
|
.command("exec <sql>")
|
|
5601
5567
|
.description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")
|
|
5602
5568
|
).action(async (sql: string, opts: JoeCliOpts) => {
|
|
5603
|
-
await runJoeCli("exec", sql,
|
|
5569
|
+
await runJoeCli("exec", sql, opts);
|
|
5604
5570
|
});
|
|
5605
5571
|
|
|
5606
5572
|
withJoeOptions(
|
|
5607
5573
|
joe
|
|
5608
|
-
.command("hypo <
|
|
5609
|
-
.description("HypoPG hypothetical index
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
await runJoeCli("hypo", indexSql, { query: opts.query }, opts);
|
|
5574
|
+
.command("hypo <args>")
|
|
5575
|
+
.description("HypoPG hypothetical indexes (e.g. `hypo create index on users (email)`, `hypo desc`, `hypo reset`)")
|
|
5576
|
+
).action(async (args: string, opts: JoeCliOpts) => {
|
|
5577
|
+
await runJoeCli("hypo", args, opts);
|
|
5613
5578
|
});
|
|
5614
5579
|
|
|
5615
5580
|
withJoeOptions(
|
|
5616
5581
|
joe
|
|
5617
5582
|
.command("activity")
|
|
5618
|
-
.description("running-activity snapshot (pg_stat_activity
|
|
5583
|
+
.description("running-activity snapshot (pg_stat_activity) on the clone")
|
|
5619
5584
|
).action(async (opts: JoeCliOpts) => {
|
|
5620
|
-
await runJoeCli("activity", null,
|
|
5585
|
+
await runJoeCli("activity", null, opts);
|
|
5621
5586
|
});
|
|
5622
5587
|
|
|
5623
5588
|
withJoeOptions(
|
|
@@ -5625,18 +5590,9 @@ withJoeOptions(
|
|
|
5625
5590
|
.command("terminate <pid>")
|
|
5626
5591
|
.description("pg_terminate_backend(pid) on the clone")
|
|
5627
5592
|
).action(async (pid: string, opts: JoeCliOpts) => {
|
|
5628
|
-
//
|
|
5629
|
-
//
|
|
5630
|
-
|
|
5631
|
-
// backend pid. Require a fully-numeric token so a mistyped pid is a clean,
|
|
5632
|
-
// typed rejection that never reaches the submit rpc.
|
|
5633
|
-
const pidStr = (pid ?? "").trim();
|
|
5634
|
-
if (!/^[0-9]+$/.test(pidStr)) {
|
|
5635
|
-
console.error("pid must be a number");
|
|
5636
|
-
process.exitCode = 1;
|
|
5637
|
-
return;
|
|
5638
|
-
}
|
|
5639
|
-
await runJoeCli("terminate", null, { pid: parseInt(pidStr, 10) }, opts);
|
|
5593
|
+
// The bare-positive-integer pid guard lives in buildJoeCommandText, which
|
|
5594
|
+
// runs before any network call — a mistyped pid never reaches an rpc.
|
|
5595
|
+
await runJoeCli("terminate", pid, opts);
|
|
5640
5596
|
});
|
|
5641
5597
|
|
|
5642
5598
|
withJoeOptions(
|
|
@@ -5644,7 +5600,7 @@ withJoeOptions(
|
|
|
5644
5600
|
.command("reset")
|
|
5645
5601
|
.description("reset/recreate the session's thin clone")
|
|
5646
5602
|
).action(async (opts: JoeCliOpts) => {
|
|
5647
|
-
await runJoeCli("reset", null,
|
|
5603
|
+
await runJoeCli("reset", null, opts);
|
|
5648
5604
|
});
|
|
5649
5605
|
|
|
5650
5606
|
withJoeOptions(
|
|
@@ -5653,138 +5609,14 @@ withJoeOptions(
|
|
|
5653
5609
|
.description("\\d-family schema/relation/index metadata")
|
|
5654
5610
|
.option("--variant <variant>", "\\d-family variant (e.g. \\d+, \\di, \\dt)")
|
|
5655
5611
|
).action(async (object: string, opts: JoeCliOpts) => {
|
|
5656
|
-
|
|
5657
|
-
if (opts.variant) args.variant = opts.variant;
|
|
5658
|
-
await runJoeCli("describe", null, args, opts);
|
|
5612
|
+
await runJoeCli("describe", object, opts);
|
|
5659
5613
|
});
|
|
5660
5614
|
|
|
5661
|
-
//
|
|
5662
|
-
//
|
|
5663
|
-
// degrades cleanly (an informative "not yet available" line + exit 1) instead of
|
|
5664
|
-
// Commander's generic "unknown command 'history'". Wire it to the rpc when M1b lands.
|
|
5665
|
-
withJoeOptions(
|
|
5666
|
-
joe
|
|
5667
|
-
.command("history <terms>")
|
|
5668
|
-
.description("search prior Joe analyses, metadata-only (M1b — not yet available)")
|
|
5669
|
-
).action(async (_terms: string, _opts: JoeCliOpts) => {
|
|
5670
|
-
console.error(
|
|
5671
|
-
"Joe history search is not available yet — it is planned for M1b. " +
|
|
5672
|
-
"No history-search backend is deployed; this command is a placeholder until then.",
|
|
5673
|
-
);
|
|
5674
|
-
process.exitCode = 1;
|
|
5675
|
-
});
|
|
5676
|
-
|
|
5677
|
-
// Org-level discovery — a general postgresai command, NOT a Joe endpoint (SPEC §6).
|
|
5678
|
-
program
|
|
5679
|
-
.command("projects")
|
|
5680
|
-
.description("list the org's projects (shows which have Joe ready) — org-level, not a Joe endpoint")
|
|
5681
|
-
.option("--debug", "enable debug output")
|
|
5682
|
-
.option("--json", "output raw JSON")
|
|
5683
|
-
.action(async (opts: { debug?: boolean; json?: boolean }) => {
|
|
5684
|
-
try {
|
|
5685
|
-
const rootOpts = program.opts<CliOptions>();
|
|
5686
|
-
const cfg = config.readConfig();
|
|
5687
|
-
const { apiKey } = getConfig(rootOpts);
|
|
5688
|
-
if (!apiKey) {
|
|
5689
|
-
console.error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
5690
|
-
process.exitCode = 1;
|
|
5691
|
-
return;
|
|
5692
|
-
}
|
|
5693
|
-
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
5694
|
-
const projects = await listProjects({
|
|
5695
|
-
apiKey,
|
|
5696
|
-
apiBaseUrl,
|
|
5697
|
-
orgId: cfg.orgId ?? undefined,
|
|
5698
|
-
debug: !!opts.debug,
|
|
5699
|
-
});
|
|
5700
|
-
if (opts.json) {
|
|
5701
|
-
console.log(JSON.stringify(projects, null, 2));
|
|
5702
|
-
} else {
|
|
5703
|
-
console.log(formatProjectsTable(projects));
|
|
5704
|
-
}
|
|
5705
|
-
} catch (err) {
|
|
5706
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
5707
|
-
console.error(message);
|
|
5708
|
-
process.exitCode = 1;
|
|
5709
|
-
}
|
|
5710
|
-
});
|
|
5711
|
-
|
|
5712
|
-
/**
|
|
5713
|
-
* Resolve the shared inputs every DBLab verb needs: the api key, the api base
|
|
5714
|
-
* url, and the project's single DBLab `instance_id`. Throws (caught by each
|
|
5715
|
-
* action → exit 1) when the api key or `--project` is missing, or when no DBLab
|
|
5716
|
-
* instance can be resolved for the project.
|
|
5717
|
-
*/
|
|
5718
|
-
async function resolveDblabTarget(
|
|
5719
|
-
project: string | undefined,
|
|
5720
|
-
debug: boolean
|
|
5721
|
-
): Promise<{ apiKey: string; apiBaseUrl: string; orgId?: number; instanceId: string }> {
|
|
5722
|
-
const rootOpts = program.opts<CliOptions>();
|
|
5723
|
-
const cfg = config.readConfig();
|
|
5724
|
-
const { apiKey } = getConfig(rootOpts);
|
|
5725
|
-
if (!apiKey) {
|
|
5726
|
-
throw new Error("API key is required. Run 'pgai auth' first or set --api-key.");
|
|
5727
|
-
}
|
|
5728
|
-
const ref = (project ?? "").trim();
|
|
5729
|
-
if (!ref) {
|
|
5730
|
-
throw new Error("--project <id|alias> is required");
|
|
5731
|
-
}
|
|
5732
|
-
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
5733
|
-
const orgId = cfg.orgId ?? undefined;
|
|
5734
|
-
const instanceId = await resolveDblabInstanceId({ apiKey, apiBaseUrl, project: ref, orgId, debug });
|
|
5735
|
-
return { apiKey, apiBaseUrl, orgId, instanceId };
|
|
5736
|
-
}
|
|
5737
|
-
|
|
5738
|
-
// ---------------------------------------------------------------------------
|
|
5739
|
-
// DBLab command group — every DBLab verb lives under
|
|
5740
|
-
// `pgai dblab <clone|branch|snapshot> …` (grouped CLI, USER-AUTHORIZED restructure).
|
|
5741
|
-
// MCP tool names (clone_create/…) stay flat/unchanged; only the CLI path is grouped.
|
|
5742
|
-
// ---------------------------------------------------------------------------
|
|
5743
|
-
const dblab = program
|
|
5744
|
-
.command("dblab")
|
|
5745
|
-
.description("DBLab thin-clone / branch / snapshot management (proxies the Platform DBLab API)");
|
|
5746
|
-
|
|
5747
|
-
// ---- clone ----------------------------------------------------------------
|
|
5748
|
-
|
|
5749
|
-
const clone = dblab.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
|
|
5750
|
-
|
|
5751
|
-
clone
|
|
5752
|
-
.command("create")
|
|
5753
|
-
.description("create a thin clone of the project's database")
|
|
5754
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5755
|
-
.option("--branch <branch>", "branch to clone from")
|
|
5756
|
-
.option("--snapshot <id>", "snapshot id to clone from")
|
|
5757
|
-
.option("--id <id>", "clone id (DBLab generates one when omitted)")
|
|
5758
|
-
.option("--db-user <user>", "clone DB user")
|
|
5759
|
-
.option("--db-password <password>", "clone DB password")
|
|
5760
|
-
.option("--protected", "protect the clone from auto-deletion")
|
|
5761
|
-
.option("--debug", "enable debug output")
|
|
5762
|
-
.option("--json", "output raw JSON")
|
|
5763
|
-
.action(async (opts: DblabCmdOpts & { branch?: string; snapshot?: string; id?: string; dbUser?: string; dbPassword?: string; protected?: boolean }) => {
|
|
5764
|
-
try {
|
|
5765
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5766
|
-
const result = await createClone({
|
|
5767
|
-
apiKey, apiBaseUrl, instanceId,
|
|
5768
|
-
cloneId: opts.id,
|
|
5769
|
-
branch: opts.branch,
|
|
5770
|
-
snapshotId: opts.snapshot,
|
|
5771
|
-
dbUser: opts.dbUser,
|
|
5772
|
-
dbPassword: opts.dbPassword,
|
|
5773
|
-
isProtected: !!opts.protected,
|
|
5774
|
-
debug: !!opts.debug,
|
|
5775
|
-
});
|
|
5776
|
-
printResult(result, opts.json);
|
|
5777
|
-
} catch (err) {
|
|
5778
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5779
|
-
process.exitCode = 1;
|
|
5780
|
-
}
|
|
5781
|
-
});
|
|
5782
|
-
|
|
5783
|
-
// Resume / inspect a previously submitted command by id. Part of the `joe` group
|
|
5784
|
-
// (`pgai joe status <commandId>`) — distinct from `pgai dblab clone status <cloneId>`.
|
|
5615
|
+
// Resume / inspect a previously started command by id (the budget-expiry
|
|
5616
|
+
// resume handle printed by the one-shot verbs).
|
|
5785
5617
|
joe
|
|
5786
|
-
.command("
|
|
5787
|
-
.description("
|
|
5618
|
+
.command("result <commandId>")
|
|
5619
|
+
.description("fetch a Joe command's output by id (resume a budget-expired one-shot)")
|
|
5788
5620
|
.option("--debug", "enable debug output")
|
|
5789
5621
|
.option("--json", "output raw JSON")
|
|
5790
5622
|
.action(async (commandId: string, opts: { debug?: boolean; json?: boolean }) => {
|
|
@@ -5798,11 +5630,27 @@ joe
|
|
|
5798
5630
|
return;
|
|
5799
5631
|
}
|
|
5800
5632
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
5801
|
-
const
|
|
5633
|
+
const output = await getCommandOutput({ apiKey, apiBaseUrl, commandId, debug: !!opts.debug });
|
|
5802
5634
|
if (opts.json) {
|
|
5803
|
-
console.log(JSON.stringify(
|
|
5635
|
+
console.log(JSON.stringify(output, null, 2));
|
|
5636
|
+
// Same exit contract as the human output and the one-shot path
|
|
5637
|
+
// (printJoeOutcome): only `ok` is a result — scripts consuming --json
|
|
5638
|
+
// must not proceed on `pending`/`error`.
|
|
5639
|
+
if (output.status !== "ok") {
|
|
5640
|
+
process.exitCode = 1;
|
|
5641
|
+
}
|
|
5642
|
+
return;
|
|
5643
|
+
}
|
|
5644
|
+
if (output.status === "ok") {
|
|
5645
|
+
console.log(`command ${output.command_id} · ok`);
|
|
5646
|
+
const body = formatJoeOutput(output);
|
|
5647
|
+
if (body) console.log(body);
|
|
5648
|
+
} else if (output.status === "error") {
|
|
5649
|
+
console.error(`command ${output.command_id} error: ${output.error ?? "command failed"}`);
|
|
5650
|
+
process.exitCode = 1;
|
|
5804
5651
|
} else {
|
|
5805
|
-
console.
|
|
5652
|
+
console.error(`command ${output.command_id} · ${output.status} — result is not ready`);
|
|
5653
|
+
process.exitCode = 1;
|
|
5806
5654
|
}
|
|
5807
5655
|
} catch (err) {
|
|
5808
5656
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -5811,29 +5659,13 @@ joe
|
|
|
5811
5659
|
}
|
|
5812
5660
|
});
|
|
5813
5661
|
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
.
|
|
5817
|
-
.
|
|
5818
|
-
.option("--debug", "enable debug output")
|
|
5819
|
-
.option("--json", "output raw JSON")
|
|
5820
|
-
.action(async (opts: DblabCmdOpts) => {
|
|
5821
|
-
try {
|
|
5822
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5823
|
-
const result = await listClones({ apiKey, apiBaseUrl, instanceId, debug: !!opts.debug });
|
|
5824
|
-
printResult(result, opts.json);
|
|
5825
|
-
} catch (err) {
|
|
5826
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5827
|
-
process.exitCode = 1;
|
|
5828
|
-
}
|
|
5829
|
-
});
|
|
5830
|
-
|
|
5831
|
-
joe
|
|
5832
|
-
.command("result <commandId>")
|
|
5833
|
-
.description("fetch a Joe command's result by id (resume a timed-out one-shot)")
|
|
5662
|
+
// Org-level discovery — a general postgresai command, NOT a Joe endpoint.
|
|
5663
|
+
program
|
|
5664
|
+
.command("projects")
|
|
5665
|
+
.description("list the org's projects (shows which have Joe ready) — org-level, not a Joe endpoint")
|
|
5834
5666
|
.option("--debug", "enable debug output")
|
|
5835
5667
|
.option("--json", "output raw JSON")
|
|
5836
|
-
.action(async (
|
|
5668
|
+
.action(async (opts: { debug?: boolean; json?: boolean }) => {
|
|
5837
5669
|
try {
|
|
5838
5670
|
const rootOpts = program.opts<CliOptions>();
|
|
5839
5671
|
const cfg = config.readConfig();
|
|
@@ -5844,33 +5676,16 @@ joe
|
|
|
5844
5676
|
return;
|
|
5845
5677
|
}
|
|
5846
5678
|
const { apiBaseUrl } = resolveBaseUrls(rootOpts, cfg);
|
|
5847
|
-
const
|
|
5679
|
+
const projects = await listProjects({
|
|
5680
|
+
apiKey,
|
|
5681
|
+
apiBaseUrl,
|
|
5682
|
+
orgId: cfg.orgId ?? undefined,
|
|
5683
|
+
debug: !!opts.debug,
|
|
5684
|
+
});
|
|
5848
5685
|
if (opts.json) {
|
|
5849
|
-
console.log(JSON.stringify(
|
|
5850
|
-
// Same exit contract as the human output and the one-shot path
|
|
5851
|
-
// (printJoeOutcome): a terminal failure is a non-result — scripts
|
|
5852
|
-
// consuming --json must not proceed on it.
|
|
5853
|
-
if (result.status === "error" || result.status === "timed_out") {
|
|
5854
|
-
process.exitCode = 1;
|
|
5855
|
-
}
|
|
5856
|
-
return;
|
|
5857
|
-
}
|
|
5858
|
-
const inferred = inferCommandFromResult(result);
|
|
5859
|
-
if (result.status === "done" && inferred) {
|
|
5860
|
-
console.log(`command ${result.command_id} · done`);
|
|
5861
|
-
const body = formatJoeResult(inferred, result);
|
|
5862
|
-
if (body) console.log(body);
|
|
5863
|
-
} else if (result.status === "error") {
|
|
5864
|
-
console.error(`command ${result.command_id} error: ${result.error ?? "command failed"}`);
|
|
5865
|
-
process.exitCode = 1;
|
|
5866
|
-
} else if (result.status === "timed_out") {
|
|
5867
|
-
// Terminal server-side timeout — a non-result. Match the one-shot path
|
|
5868
|
-
// (printJoeOutcome): report and exit non-zero so `pgai joe result $id
|
|
5869
|
-
// && next-step` scripts do not proceed.
|
|
5870
|
-
console.error(`command ${result.command_id} timed out on the server`);
|
|
5871
|
-
process.exitCode = 1;
|
|
5686
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
5872
5687
|
} else {
|
|
5873
|
-
console.log(
|
|
5688
|
+
console.log(formatProjectsTable(projects));
|
|
5874
5689
|
}
|
|
5875
5690
|
} catch (err) {
|
|
5876
5691
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -5879,218 +5694,6 @@ joe
|
|
|
5879
5694
|
}
|
|
5880
5695
|
});
|
|
5881
5696
|
|
|
5882
|
-
clone
|
|
5883
|
-
.command("status <cloneId>")
|
|
5884
|
-
.description("show a clone's status")
|
|
5885
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5886
|
-
.option("--debug", "enable debug output")
|
|
5887
|
-
.option("--json", "output raw JSON")
|
|
5888
|
-
.action(async (cloneId: string, opts: DblabCmdOpts) => {
|
|
5889
|
-
try {
|
|
5890
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5891
|
-
const result = await getClone({ apiKey, apiBaseUrl, instanceId, cloneId, debug: !!opts.debug });
|
|
5892
|
-
printResult(result, opts.json);
|
|
5893
|
-
} catch (err) {
|
|
5894
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5895
|
-
process.exitCode = 1;
|
|
5896
|
-
}
|
|
5897
|
-
});
|
|
5898
|
-
|
|
5899
|
-
clone
|
|
5900
|
-
.command("reset <cloneId>")
|
|
5901
|
-
.description("reset a clone to a pristine snapshot")
|
|
5902
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5903
|
-
.option("--snapshot <id>", "snapshot id to reset to (default: latest)")
|
|
5904
|
-
.option("--latest", "reset to the latest snapshot")
|
|
5905
|
-
.option("--debug", "enable debug output")
|
|
5906
|
-
.option("--json", "output raw JSON")
|
|
5907
|
-
.action(async (cloneId: string, opts: DblabCmdOpts & { snapshot?: string; latest?: boolean }) => {
|
|
5908
|
-
try {
|
|
5909
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5910
|
-
const result = await resetClone({
|
|
5911
|
-
apiKey, apiBaseUrl, instanceId, cloneId,
|
|
5912
|
-
snapshotId: opts.snapshot,
|
|
5913
|
-
latest: opts.latest,
|
|
5914
|
-
debug: !!opts.debug,
|
|
5915
|
-
});
|
|
5916
|
-
printResult(result ?? { reset: true, cloneId }, opts.json);
|
|
5917
|
-
} catch (err) {
|
|
5918
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5919
|
-
process.exitCode = 1;
|
|
5920
|
-
}
|
|
5921
|
-
});
|
|
5922
|
-
|
|
5923
|
-
clone
|
|
5924
|
-
.command("destroy <cloneId>")
|
|
5925
|
-
.description("destroy a clone (requires joe:admin)")
|
|
5926
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5927
|
-
.option("--debug", "enable debug output")
|
|
5928
|
-
.option("--json", "output raw JSON")
|
|
5929
|
-
.action(async (cloneId: string, opts: DblabCmdOpts) => {
|
|
5930
|
-
try {
|
|
5931
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5932
|
-
const result = await destroyClone({ apiKey, apiBaseUrl, instanceId, cloneId, debug: !!opts.debug });
|
|
5933
|
-
printResult(result ?? { destroyed: true, cloneId }, opts.json);
|
|
5934
|
-
} catch (err) {
|
|
5935
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5936
|
-
process.exitCode = 1;
|
|
5937
|
-
}
|
|
5938
|
-
});
|
|
5939
|
-
|
|
5940
|
-
// ---- branch ---------------------------------------------------------------
|
|
5941
|
-
|
|
5942
|
-
const branch = dblab.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
|
|
5943
|
-
|
|
5944
|
-
branch
|
|
5945
|
-
.command("list")
|
|
5946
|
-
.description("list the project's branches")
|
|
5947
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5948
|
-
.option("--debug", "enable debug output")
|
|
5949
|
-
.option("--json", "output raw JSON")
|
|
5950
|
-
.action(async (opts: DblabCmdOpts) => {
|
|
5951
|
-
try {
|
|
5952
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5953
|
-
const result = await listBranches({ apiKey, apiBaseUrl, instanceId, debug: !!opts.debug });
|
|
5954
|
-
printResult(result, opts.json);
|
|
5955
|
-
} catch (err) {
|
|
5956
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5957
|
-
process.exitCode = 1;
|
|
5958
|
-
}
|
|
5959
|
-
});
|
|
5960
|
-
|
|
5961
|
-
branch
|
|
5962
|
-
.command("create <name>")
|
|
5963
|
-
.description("create a branch")
|
|
5964
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5965
|
-
.option("--snapshot <id>", "snapshot id to base the branch on")
|
|
5966
|
-
.option("--base-branch <branch>", "parent branch to fork from")
|
|
5967
|
-
.option("--debug", "enable debug output")
|
|
5968
|
-
.option("--json", "output raw JSON")
|
|
5969
|
-
.action(async (name: string, opts: DblabCmdOpts & { snapshot?: string; baseBranch?: string }) => {
|
|
5970
|
-
try {
|
|
5971
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5972
|
-
const result = await createBranch({
|
|
5973
|
-
apiKey, apiBaseUrl, instanceId,
|
|
5974
|
-
branchName: name,
|
|
5975
|
-
baseBranch: opts.baseBranch,
|
|
5976
|
-
snapshotId: opts.snapshot,
|
|
5977
|
-
debug: !!opts.debug,
|
|
5978
|
-
});
|
|
5979
|
-
printResult(result, opts.json);
|
|
5980
|
-
} catch (err) {
|
|
5981
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5982
|
-
process.exitCode = 1;
|
|
5983
|
-
}
|
|
5984
|
-
});
|
|
5985
|
-
|
|
5986
|
-
branch
|
|
5987
|
-
.command("delete <name>")
|
|
5988
|
-
.description("delete a branch (requires joe:admin)")
|
|
5989
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
5990
|
-
.option("--debug", "enable debug output")
|
|
5991
|
-
.option("--json", "output raw JSON")
|
|
5992
|
-
.action(async (name: string, opts: DblabCmdOpts) => {
|
|
5993
|
-
try {
|
|
5994
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
5995
|
-
const result = await deleteBranch({ apiKey, apiBaseUrl, instanceId, branchName: name, debug: !!opts.debug });
|
|
5996
|
-
printResult(result ?? { deleted: true, branch: name }, opts.json);
|
|
5997
|
-
} catch (err) {
|
|
5998
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
5999
|
-
process.exitCode = 1;
|
|
6000
|
-
}
|
|
6001
|
-
});
|
|
6002
|
-
|
|
6003
|
-
branch
|
|
6004
|
-
.command("log <name>")
|
|
6005
|
-
.description("show a branch's snapshot log")
|
|
6006
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
6007
|
-
.option("--debug", "enable debug output")
|
|
6008
|
-
.option("--json", "output raw JSON")
|
|
6009
|
-
.action(async (name: string, opts: DblabCmdOpts) => {
|
|
6010
|
-
try {
|
|
6011
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
6012
|
-
const result = await branchLog({ apiKey, apiBaseUrl, instanceId, branchName: name, debug: !!opts.debug });
|
|
6013
|
-
printResult(result, opts.json);
|
|
6014
|
-
} catch (err) {
|
|
6015
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
6016
|
-
process.exitCode = 1;
|
|
6017
|
-
}
|
|
6018
|
-
});
|
|
6019
|
-
|
|
6020
|
-
// ---- snapshot -------------------------------------------------------------
|
|
6021
|
-
|
|
6022
|
-
const snapshot = dblab.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
|
|
6023
|
-
|
|
6024
|
-
snapshot
|
|
6025
|
-
.command("list")
|
|
6026
|
-
.description("list the project's snapshots")
|
|
6027
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
6028
|
-
.option("--branch <branch>", "filter by branch")
|
|
6029
|
-
.option("--dataset <dataset>", "filter by dataset")
|
|
6030
|
-
.option("--debug", "enable debug output")
|
|
6031
|
-
.option("--json", "output raw JSON")
|
|
6032
|
-
.action(async (opts: DblabCmdOpts & { branch?: string; dataset?: string }) => {
|
|
6033
|
-
try {
|
|
6034
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
6035
|
-
const result = await listSnapshots({
|
|
6036
|
-
apiKey, apiBaseUrl, instanceId,
|
|
6037
|
-
branchName: opts.branch,
|
|
6038
|
-
dataset: opts.dataset,
|
|
6039
|
-
debug: !!opts.debug,
|
|
6040
|
-
});
|
|
6041
|
-
printResult(result, opts.json);
|
|
6042
|
-
} catch (err) {
|
|
6043
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
6044
|
-
process.exitCode = 1;
|
|
6045
|
-
}
|
|
6046
|
-
});
|
|
6047
|
-
|
|
6048
|
-
snapshot
|
|
6049
|
-
.command("create")
|
|
6050
|
-
.description("create a snapshot from a clone")
|
|
6051
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
6052
|
-
.requiredOption("--clone <id>", "clone id to snapshot")
|
|
6053
|
-
.option("--message <message>", "snapshot message")
|
|
6054
|
-
.option("--debug", "enable debug output")
|
|
6055
|
-
.option("--json", "output raw JSON")
|
|
6056
|
-
.action(async (opts: DblabCmdOpts & { clone?: string; message?: string }) => {
|
|
6057
|
-
try {
|
|
6058
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
6059
|
-
const result = await createSnapshot({
|
|
6060
|
-
apiKey, apiBaseUrl, instanceId,
|
|
6061
|
-
cloneId: opts.clone as string,
|
|
6062
|
-
message: opts.message,
|
|
6063
|
-
debug: !!opts.debug,
|
|
6064
|
-
});
|
|
6065
|
-
printResult(result, opts.json);
|
|
6066
|
-
} catch (err) {
|
|
6067
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
6068
|
-
process.exitCode = 1;
|
|
6069
|
-
}
|
|
6070
|
-
});
|
|
6071
|
-
|
|
6072
|
-
snapshot
|
|
6073
|
-
.command("destroy <snapshotId>")
|
|
6074
|
-
.description("destroy a snapshot (requires joe:admin)")
|
|
6075
|
-
.requiredOption("--project <id|alias>", "project id or alias")
|
|
6076
|
-
.option("--force", "force-delete even when dependent clones exist")
|
|
6077
|
-
.option("--debug", "enable debug output")
|
|
6078
|
-
.option("--json", "output raw JSON")
|
|
6079
|
-
.action(async (snapshotId: string, opts: DblabCmdOpts & { force?: boolean }) => {
|
|
6080
|
-
try {
|
|
6081
|
-
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
6082
|
-
const result = await destroySnapshot({
|
|
6083
|
-
apiKey, apiBaseUrl, instanceId, snapshotId,
|
|
6084
|
-
force: !!opts.force,
|
|
6085
|
-
debug: !!opts.debug,
|
|
6086
|
-
});
|
|
6087
|
-
printResult(result ?? { destroyed: true, snapshot: snapshotId }, opts.json);
|
|
6088
|
-
} catch (err) {
|
|
6089
|
-
console.error(err instanceof Error ? err.message : String(err));
|
|
6090
|
-
process.exitCode = 1;
|
|
6091
|
-
}
|
|
6092
|
-
});
|
|
6093
|
-
|
|
6094
5697
|
// MCP server
|
|
6095
5698
|
const mcp = program.command("mcp").description("MCP server integration");
|
|
6096
5699
|
|
|
@@ -6268,6 +5871,3 @@ if (import.meta.main) {
|
|
|
6268
5871
|
export { refreshBundledComposeIfStale, readDeployedTag, isValidComposeYaml };
|
|
6269
5872
|
export { registerMonitoringInstance, resolveAdoptedProject, type MonitoringRegistration };
|
|
6270
5873
|
export { planMonitoringRegistration, type MonRegistrationPlan };
|
|
6271
|
-
// Joe: the `pgai joe result <id>` result-body -> command inference (unit-tested
|
|
6272
|
-
// table-driven; the shapes are the per-command contract matrix in CONTRACT_DECISIONS).
|
|
6273
|
-
export { inferCommandFromResult };
|