postgresai 0.16.0-dev.5 → 0.16.0-dev.6
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/bin/postgres-ai.ts +37 -18
- package/dist/bin/postgres-ai.js +21 -19
- package/lib/joe.ts +1 -1
- package/lib/mcp-server.ts +3 -3
- package/package.json +1 -1
- package/test/dblab.cli.test.ts +25 -18
- package/test/joe.cli.test.ts +33 -22
package/bin/postgres-ai.ts
CHANGED
|
@@ -5424,7 +5424,7 @@ interface JoeCliOpts {
|
|
|
5424
5424
|
// ============================================================================
|
|
5425
5425
|
// DBLab companion command surface (Joe API v2 · SPEC §8)
|
|
5426
5426
|
//
|
|
5427
|
-
// `pgai clone|branch|snapshot …` proxy the SAME Platform DBLab API the Console
|
|
5427
|
+
// `pgai dblab clone|branch|snapshot …` proxy the SAME Platform DBLab API the Console
|
|
5428
5428
|
// already drives (v1.dblab_api_call). Every verb is `--project <id|alias>`
|
|
5429
5429
|
// scoped: the project's single DBLab instance is resolved server-side listing,
|
|
5430
5430
|
// then the verb is proxied. Destructive verbs (clone reset/destroy, branch
|
|
@@ -5465,7 +5465,7 @@ function printJoeOutcome(command: JoeCommand, outcome: ExecuteJoeOutcome, json:
|
|
|
5465
5465
|
status: outcome.status,
|
|
5466
5466
|
session_id: outcome.sessionId,
|
|
5467
5467
|
budget_expired: true,
|
|
5468
|
-
resume: `pgai result ${outcome.commandId}`,
|
|
5468
|
+
resume: `pgai joe result ${outcome.commandId}`,
|
|
5469
5469
|
},
|
|
5470
5470
|
null,
|
|
5471
5471
|
2
|
|
@@ -5473,7 +5473,7 @@ function printJoeOutcome(command: JoeCommand, outcome: ExecuteJoeOutcome, json:
|
|
|
5473
5473
|
);
|
|
5474
5474
|
} else {
|
|
5475
5475
|
console.log(
|
|
5476
|
-
`submitted ${outcome.commandId} · ${outcome.status} · budget ${budgetSeconds}s reached — resume: pgai result ${outcome.commandId}`
|
|
5476
|
+
`submitted ${outcome.commandId} · ${outcome.status} · budget ${budgetSeconds}s reached — resume: pgai joe result ${outcome.commandId}`
|
|
5477
5477
|
);
|
|
5478
5478
|
}
|
|
5479
5479
|
return;
|
|
@@ -5501,7 +5501,7 @@ function printJoeOutcome(command: JoeCommand, outcome: ExecuteJoeOutcome, json:
|
|
|
5501
5501
|
// Server-side timed_out (the dispatcher gave up). The genuine reply may still
|
|
5502
5502
|
// land; suggest a resume, but signal failure with a non-zero exit.
|
|
5503
5503
|
console.error(
|
|
5504
|
-
`command ${outcome.commandId} timed out on the server — resume: pgai result ${outcome.commandId}`
|
|
5504
|
+
`command ${outcome.commandId} timed out on the server — resume: pgai joe result ${outcome.commandId}`
|
|
5505
5505
|
);
|
|
5506
5506
|
process.exitCode = 1;
|
|
5507
5507
|
}
|
|
@@ -5564,8 +5564,17 @@ function withJoeOptions(cmd: import("commander").Command): import("commander").C
|
|
|
5564
5564
|
.option("--json", "output raw JSON");
|
|
5565
5565
|
}
|
|
5566
5566
|
|
|
5567
|
+
// ---------------------------------------------------------------------------
|
|
5568
|
+
// Joe command group — every Joe verb lives under `pgai joe <verb>` (grouped CLI,
|
|
5569
|
+
// USER-AUTHORIZED restructure). The MCP tool names stay flat/unchanged; only the
|
|
5570
|
+
// CLI invocation path is grouped. `projects` stays top-level (org-level, SPEC §6).
|
|
5571
|
+
// ---------------------------------------------------------------------------
|
|
5572
|
+
const joe = program
|
|
5573
|
+
.command("joe")
|
|
5574
|
+
.description("Joe API v2 — plan/EXPLAIN/exec queries on ephemeral DBLab clones");
|
|
5575
|
+
|
|
5567
5576
|
withJoeOptions(
|
|
5568
|
-
|
|
5577
|
+
joe
|
|
5569
5578
|
.command("plan <sql>")
|
|
5570
5579
|
.description("plan a query (EXPLAIN, plan-only — no execution; the fast/safe default)")
|
|
5571
5580
|
).action(async (sql: string, opts: JoeCliOpts) => {
|
|
@@ -5573,7 +5582,7 @@ withJoeOptions(
|
|
|
5573
5582
|
});
|
|
5574
5583
|
|
|
5575
5584
|
withJoeOptions(
|
|
5576
|
-
|
|
5585
|
+
joe
|
|
5577
5586
|
.command("explain <sql>")
|
|
5578
5587
|
.description("EXPLAIN ANALYZE a query (EXECUTES on the hardened, ephemeral clone)")
|
|
5579
5588
|
).action(async (sql: string, opts: JoeCliOpts) => {
|
|
@@ -5581,7 +5590,7 @@ withJoeOptions(
|
|
|
5581
5590
|
});
|
|
5582
5591
|
|
|
5583
5592
|
withJoeOptions(
|
|
5584
|
-
|
|
5593
|
+
joe
|
|
5585
5594
|
.command("exec <sql>")
|
|
5586
5595
|
.description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")
|
|
5587
5596
|
).action(async (sql: string, opts: JoeCliOpts) => {
|
|
@@ -5589,7 +5598,7 @@ withJoeOptions(
|
|
|
5589
5598
|
});
|
|
5590
5599
|
|
|
5591
5600
|
withJoeOptions(
|
|
5592
|
-
|
|
5601
|
+
joe
|
|
5593
5602
|
.command("hypo <indexSql>")
|
|
5594
5603
|
.description("HypoPG hypothetical index + re-plan (no real index, no data change)")
|
|
5595
5604
|
.requiredOption("--query <sql>", "target query the hypothetical index is evaluated against")
|
|
@@ -5598,7 +5607,7 @@ withJoeOptions(
|
|
|
5598
5607
|
});
|
|
5599
5608
|
|
|
5600
5609
|
withJoeOptions(
|
|
5601
|
-
|
|
5610
|
+
joe
|
|
5602
5611
|
.command("activity")
|
|
5603
5612
|
.description("running-activity snapshot (pg_stat_activity; query text redacted)")
|
|
5604
5613
|
).action(async (opts: JoeCliOpts) => {
|
|
@@ -5606,7 +5615,7 @@ withJoeOptions(
|
|
|
5606
5615
|
});
|
|
5607
5616
|
|
|
5608
5617
|
withJoeOptions(
|
|
5609
|
-
|
|
5618
|
+
joe
|
|
5610
5619
|
.command("terminate <pid>")
|
|
5611
5620
|
.description("pg_terminate_backend(pid) on the clone")
|
|
5612
5621
|
).action(async (pid: string, opts: JoeCliOpts) => {
|
|
@@ -5620,7 +5629,7 @@ withJoeOptions(
|
|
|
5620
5629
|
});
|
|
5621
5630
|
|
|
5622
5631
|
withJoeOptions(
|
|
5623
|
-
|
|
5632
|
+
joe
|
|
5624
5633
|
.command("reset")
|
|
5625
5634
|
.description("reset/recreate the session's thin clone")
|
|
5626
5635
|
).action(async (opts: JoeCliOpts) => {
|
|
@@ -5628,7 +5637,7 @@ withJoeOptions(
|
|
|
5628
5637
|
});
|
|
5629
5638
|
|
|
5630
5639
|
withJoeOptions(
|
|
5631
|
-
|
|
5640
|
+
joe
|
|
5632
5641
|
.command("describe <object>")
|
|
5633
5642
|
.description("\\d-family schema/relation/index metadata")
|
|
5634
5643
|
.option("--variant <variant>", "\\d-family variant (e.g. \\d+, \\di, \\dt)")
|
|
@@ -5699,9 +5708,18 @@ async function resolveDblabTarget(
|
|
|
5699
5708
|
return { apiKey, apiBaseUrl, orgId, instanceId };
|
|
5700
5709
|
}
|
|
5701
5710
|
|
|
5711
|
+
// ---------------------------------------------------------------------------
|
|
5712
|
+
// DBLab command group — every DBLab verb lives under
|
|
5713
|
+
// `pgai dblab <clone|branch|snapshot> …` (grouped CLI, USER-AUTHORIZED restructure).
|
|
5714
|
+
// MCP tool names (clone_create/…) stay flat/unchanged; only the CLI path is grouped.
|
|
5715
|
+
// ---------------------------------------------------------------------------
|
|
5716
|
+
const dblab = program
|
|
5717
|
+
.command("dblab")
|
|
5718
|
+
.description("DBLab thin-clone / branch / snapshot management (proxies the Platform DBLab API)");
|
|
5719
|
+
|
|
5702
5720
|
// ---- clone ----------------------------------------------------------------
|
|
5703
5721
|
|
|
5704
|
-
const clone =
|
|
5722
|
+
const clone = dblab.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
|
|
5705
5723
|
|
|
5706
5724
|
clone
|
|
5707
5725
|
.command("create")
|
|
@@ -5735,8 +5753,9 @@ clone
|
|
|
5735
5753
|
}
|
|
5736
5754
|
});
|
|
5737
5755
|
|
|
5738
|
-
// Resume / inspect a previously submitted command by id.
|
|
5739
|
-
|
|
5756
|
+
// Resume / inspect a previously submitted command by id. Part of the `joe` group
|
|
5757
|
+
// (`pgai joe status <commandId>`) — distinct from `pgai dblab clone status <cloneId>`.
|
|
5758
|
+
joe
|
|
5740
5759
|
.command("status <commandId>")
|
|
5741
5760
|
.description("show a Joe command's status (metadata only)")
|
|
5742
5761
|
.option("--debug", "enable debug output")
|
|
@@ -5782,7 +5801,7 @@ clone
|
|
|
5782
5801
|
}
|
|
5783
5802
|
});
|
|
5784
5803
|
|
|
5785
|
-
|
|
5804
|
+
joe
|
|
5786
5805
|
.command("result <commandId>")
|
|
5787
5806
|
.description("fetch a Joe command's result by id (resume a timed-out one-shot)")
|
|
5788
5807
|
.option("--debug", "enable debug output")
|
|
@@ -5881,7 +5900,7 @@ clone
|
|
|
5881
5900
|
|
|
5882
5901
|
// ---- branch ---------------------------------------------------------------
|
|
5883
5902
|
|
|
5884
|
-
const branch =
|
|
5903
|
+
const branch = dblab.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
|
|
5885
5904
|
|
|
5886
5905
|
branch
|
|
5887
5906
|
.command("list")
|
|
@@ -5961,7 +5980,7 @@ branch
|
|
|
5961
5980
|
|
|
5962
5981
|
// ---- snapshot -------------------------------------------------------------
|
|
5963
5982
|
|
|
5964
|
-
const snapshot =
|
|
5983
|
+
const snapshot = dblab.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
|
|
5965
5984
|
|
|
5966
5985
|
snapshot
|
|
5967
5986
|
.command("list")
|
package/dist/bin/postgres-ai.js
CHANGED
|
@@ -13425,7 +13425,7 @@ var {
|
|
|
13425
13425
|
// package.json
|
|
13426
13426
|
var package_default = {
|
|
13427
13427
|
name: "postgresai",
|
|
13428
|
-
version: "0.16.0-dev.
|
|
13428
|
+
version: "0.16.0-dev.6",
|
|
13429
13429
|
description: "postgres_ai CLI",
|
|
13430
13430
|
license: "Apache-2.0",
|
|
13431
13431
|
private: false,
|
|
@@ -16256,7 +16256,7 @@ var Result = import_lib.default.Result;
|
|
|
16256
16256
|
var TypeOverrides = import_lib.default.TypeOverrides;
|
|
16257
16257
|
var defaults = import_lib.default.defaults;
|
|
16258
16258
|
// package.json
|
|
16259
|
-
var version = "0.16.0-dev.
|
|
16259
|
+
var version = "0.16.0-dev.6";
|
|
16260
16260
|
var package_default2 = {
|
|
16261
16261
|
name: "postgresai",
|
|
16262
16262
|
version,
|
|
@@ -25145,7 +25145,7 @@ async function runJoeTool(params) {
|
|
|
25145
25145
|
session_id: outcome.sessionId,
|
|
25146
25146
|
status: outcome.status,
|
|
25147
25147
|
budget_expired: outcome.budgetExpired,
|
|
25148
|
-
resume: outcome.budgetExpired ? `pgai result ${outcome.commandId}` : undefined,
|
|
25148
|
+
resume: outcome.budgetExpired ? `pgai joe result ${outcome.commandId}` : undefined,
|
|
25149
25149
|
result: outcome.result
|
|
25150
25150
|
};
|
|
25151
25151
|
const isError = outcome.status === "error" || outcome.status === "timed_out";
|
|
@@ -39715,10 +39715,10 @@ function printJoeOutcome(command, outcome, json3) {
|
|
|
39715
39715
|
status: outcome.status,
|
|
39716
39716
|
session_id: outcome.sessionId,
|
|
39717
39717
|
budget_expired: true,
|
|
39718
|
-
resume: `pgai result ${outcome.commandId}`
|
|
39718
|
+
resume: `pgai joe result ${outcome.commandId}`
|
|
39719
39719
|
}, null, 2));
|
|
39720
39720
|
} else {
|
|
39721
|
-
console.log(`submitted ${outcome.commandId} \xB7 ${outcome.status} \xB7 budget ${budgetSeconds}s reached \u2014 resume: pgai result ${outcome.commandId}`);
|
|
39721
|
+
console.log(`submitted ${outcome.commandId} \xB7 ${outcome.status} \xB7 budget ${budgetSeconds}s reached \u2014 resume: pgai joe result ${outcome.commandId}`);
|
|
39722
39722
|
}
|
|
39723
39723
|
return;
|
|
39724
39724
|
}
|
|
@@ -39740,7 +39740,7 @@ function printJoeOutcome(command, outcome, json3) {
|
|
|
39740
39740
|
process.exitCode = 1;
|
|
39741
39741
|
return;
|
|
39742
39742
|
}
|
|
39743
|
-
console.error(`command ${outcome.commandId} timed out on the server \u2014 resume: pgai result ${outcome.commandId}`);
|
|
39743
|
+
console.error(`command ${outcome.commandId} timed out on the server \u2014 resume: pgai joe result ${outcome.commandId}`);
|
|
39744
39744
|
process.exitCode = 1;
|
|
39745
39745
|
}
|
|
39746
39746
|
async function runJoeCli(command, sql, args, opts) {
|
|
@@ -39784,22 +39784,23 @@ async function runJoeCli(command, sql, args, opts) {
|
|
|
39784
39784
|
function withJoeOptions(cmd) {
|
|
39785
39785
|
return cmd.option("--project <id|alias>", "target project by numeric id OR alias/name").option("--session <id>", "run on a specific session's clone").option("--new-session", "start a fresh session/clone (null session)").option("--budget <seconds>", "one-shot poll budget in seconds (default 25)", (v) => parseInt(v, 10)).option("--debug", "enable debug output").option("--json", "output raw JSON");
|
|
39786
39786
|
}
|
|
39787
|
-
|
|
39787
|
+
var joe = program2.command("joe").description("Joe API v2 \u2014 plan/EXPLAIN/exec queries on ephemeral DBLab clones");
|
|
39788
|
+
withJoeOptions(joe.command("plan <sql>").description("plan a query (EXPLAIN, plan-only \u2014 no execution; the fast/safe default)")).action(async (sql, opts) => {
|
|
39788
39789
|
await runJoeCli("plan", sql, null, opts);
|
|
39789
39790
|
});
|
|
39790
|
-
withJoeOptions(
|
|
39791
|
+
withJoeOptions(joe.command("explain <sql>").description("EXPLAIN ANALYZE a query (EXECUTES on the hardened, ephemeral clone)")).action(async (sql, opts) => {
|
|
39791
39792
|
await runJoeCli("explain", sql, null, opts);
|
|
39792
39793
|
});
|
|
39793
|
-
withJoeOptions(
|
|
39794
|
+
withJoeOptions(joe.command("exec <sql>").description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")).action(async (sql, opts) => {
|
|
39794
39795
|
await runJoeCli("exec", sql, null, opts);
|
|
39795
39796
|
});
|
|
39796
|
-
withJoeOptions(
|
|
39797
|
+
withJoeOptions(joe.command("hypo <indexSql>").description("HypoPG hypothetical index + re-plan (no real index, no data change)").requiredOption("--query <sql>", "target query the hypothetical index is evaluated against")).action(async (indexSql, opts) => {
|
|
39797
39798
|
await runJoeCli("hypo", indexSql, { query: opts.query }, opts);
|
|
39798
39799
|
});
|
|
39799
|
-
withJoeOptions(
|
|
39800
|
+
withJoeOptions(joe.command("activity").description("running-activity snapshot (pg_stat_activity; query text redacted)")).action(async (opts) => {
|
|
39800
39801
|
await runJoeCli("activity", null, null, opts);
|
|
39801
39802
|
});
|
|
39802
|
-
withJoeOptions(
|
|
39803
|
+
withJoeOptions(joe.command("terminate <pid>").description("pg_terminate_backend(pid) on the clone")).action(async (pid, opts) => {
|
|
39803
39804
|
const pidNum = parseInt(pid, 10);
|
|
39804
39805
|
if (Number.isNaN(pidNum)) {
|
|
39805
39806
|
console.error("pid must be a number");
|
|
@@ -39808,10 +39809,10 @@ withJoeOptions(program2.command("terminate <pid>").description("pg_terminate_bac
|
|
|
39808
39809
|
}
|
|
39809
39810
|
await runJoeCli("terminate", null, { pid: pidNum }, opts);
|
|
39810
39811
|
});
|
|
39811
|
-
withJoeOptions(
|
|
39812
|
+
withJoeOptions(joe.command("reset").description("reset/recreate the session's thin clone")).action(async (opts) => {
|
|
39812
39813
|
await runJoeCli("reset", null, null, opts);
|
|
39813
39814
|
});
|
|
39814
|
-
withJoeOptions(
|
|
39815
|
+
withJoeOptions(joe.command("describe <object>").description("\\d-family schema/relation/index metadata").option("--variant <variant>", "\\d-family variant (e.g. \\d+, \\di, \\dt)")).action(async (object4, opts) => {
|
|
39815
39816
|
const args = { object: object4 };
|
|
39816
39817
|
if (opts.variant)
|
|
39817
39818
|
args.variant = opts.variant;
|
|
@@ -39861,7 +39862,8 @@ async function resolveDblabTarget(project, debug) {
|
|
|
39861
39862
|
const instanceId = await resolveDblabInstanceId2({ apiKey, apiBaseUrl, project: ref, orgId, debug });
|
|
39862
39863
|
return { apiKey, apiBaseUrl, orgId, instanceId };
|
|
39863
39864
|
}
|
|
39864
|
-
var
|
|
39865
|
+
var dblab = program2.command("dblab").description("DBLab thin-clone / branch / snapshot management (proxies the Platform DBLab API)");
|
|
39866
|
+
var clone2 = dblab.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
|
|
39865
39867
|
clone2.command("create").description("create a thin clone of the project's database").requiredOption("--project <id|alias>", "project id or alias").option("--branch <branch>", "branch to clone from").option("--snapshot <id>", "snapshot id to clone from").option("--id <id>", "clone id (DBLab generates one when omitted)").option("--db-user <user>", "clone DB user").option("--db-password <password>", "clone DB password").option("--protected", "protect the clone from auto-deletion").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
|
|
39866
39868
|
try {
|
|
39867
39869
|
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
@@ -39883,7 +39885,7 @@ clone2.command("create").description("create a thin clone of the project's datab
|
|
|
39883
39885
|
process.exitCode = 1;
|
|
39884
39886
|
}
|
|
39885
39887
|
});
|
|
39886
|
-
|
|
39888
|
+
joe.command("status <commandId>").description("show a Joe command's status (metadata only)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commandId, opts) => {
|
|
39887
39889
|
try {
|
|
39888
39890
|
const rootOpts = program2.opts();
|
|
39889
39891
|
const cfg = readConfig();
|
|
@@ -39916,7 +39918,7 @@ clone2.command("list").description("list the project's thin clones").requiredOpt
|
|
|
39916
39918
|
process.exitCode = 1;
|
|
39917
39919
|
}
|
|
39918
39920
|
});
|
|
39919
|
-
|
|
39921
|
+
joe.command("result <commandId>").description("fetch a Joe command's result by id (resume a timed-out one-shot)").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (commandId, opts) => {
|
|
39920
39922
|
try {
|
|
39921
39923
|
const rootOpts = program2.opts();
|
|
39922
39924
|
const cfg = readConfig();
|
|
@@ -39988,7 +39990,7 @@ clone2.command("destroy <cloneId>").description("destroy a clone (requires joe:a
|
|
|
39988
39990
|
process.exitCode = 1;
|
|
39989
39991
|
}
|
|
39990
39992
|
});
|
|
39991
|
-
var branch =
|
|
39993
|
+
var branch = dblab.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
|
|
39992
39994
|
branch.command("list").description("list the project's branches").requiredOption("--project <id|alias>", "project id or alias").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
|
|
39993
39995
|
try {
|
|
39994
39996
|
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
|
@@ -40037,7 +40039,7 @@ branch.command("log <name>").description("show a branch's snapshot log").require
|
|
|
40037
40039
|
process.exitCode = 1;
|
|
40038
40040
|
}
|
|
40039
40041
|
});
|
|
40040
|
-
var snapshot =
|
|
40042
|
+
var snapshot = dblab.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
|
|
40041
40043
|
snapshot.command("list").description("list the project's snapshots").requiredOption("--project <id|alias>", "project id or alias").option("--branch <branch>", "filter by branch").option("--dataset <dataset>", "filter by dataset").option("--debug", "enable debug output").option("--json", "output raw JSON").action(async (opts) => {
|
|
40042
40044
|
try {
|
|
40043
40045
|
const { apiKey, apiBaseUrl, instanceId } = await resolveDblabTarget(opts.project, !!opts.debug);
|
package/lib/joe.ts
CHANGED
|
@@ -496,7 +496,7 @@ const defaultSleep = (ms: number): Promise<void> => new Promise((r) => setTimeou
|
|
|
496
496
|
/**
|
|
497
497
|
* Submit a command then client-side poll status/result within the one-shot budget.
|
|
498
498
|
* On a terminal state returns the result; on budget expiry returns a resume handle
|
|
499
|
-
* (`budgetExpired: true`) so the caller can `pgai result <command_id>` later.
|
|
499
|
+
* (`budgetExpired: true`) so the caller can `pgai joe result <command_id>` later.
|
|
500
500
|
*/
|
|
501
501
|
export async function runCommand(params: RunCommandParams): Promise<RunCommandOutcome> {
|
|
502
502
|
const {
|
package/lib/mcp-server.ts
CHANGED
|
@@ -284,7 +284,7 @@ async function runJoeTool(params: RunJoeToolParams): Promise<McpToolResponse> {
|
|
|
284
284
|
session_id: outcome.sessionId,
|
|
285
285
|
status: outcome.status,
|
|
286
286
|
budget_expired: outcome.budgetExpired,
|
|
287
|
-
resume: outcome.budgetExpired ? `pgai result ${outcome.commandId}` : undefined,
|
|
287
|
+
resume: outcome.budgetExpired ? `pgai joe result ${outcome.commandId}` : undefined,
|
|
288
288
|
result: outcome.result,
|
|
289
289
|
};
|
|
290
290
|
const isError = outcome.status === "error" || outcome.status === "timed_out";
|
|
@@ -648,7 +648,7 @@ export async function handleToolCall(
|
|
|
648
648
|
}
|
|
649
649
|
|
|
650
650
|
// ---- DBLab companion tools (SPEC §8) --------------------------------
|
|
651
|
-
// Each mirrors the CLI `pgai clone|branch|snapshot …` verb 1:1: resolve the
|
|
651
|
+
// Each mirrors the CLI `pgai dblab clone|branch|snapshot …` verb 1:1: resolve the
|
|
652
652
|
// project's single DBLab instance, then proxy the existing Platform DBLab
|
|
653
653
|
// API (v1.dblab_api_call). Destructive verbs are `joe:admin`-gated backend
|
|
654
654
|
// side (a PT403 surfaces as an isError response via the outer catch).
|
|
@@ -1058,7 +1058,7 @@ export async function startMcpServer(rootOpts?: RootOptsLike, extra?: { debug?:
|
|
|
1058
1058
|
},
|
|
1059
1059
|
...joeToolDefinitions(),
|
|
1060
1060
|
// DBLab companion tools (SPEC §8) — proxy the existing Platform DBLab API
|
|
1061
|
-
// (v1.dblab_api_call) the Console drives; mirror `pgai clone|branch|snapshot`.
|
|
1061
|
+
// (v1.dblab_api_call) the Console drives; mirror `pgai dblab clone|branch|snapshot`.
|
|
1062
1062
|
// `project` (id or alias) resolves the project's single DBLab instance.
|
|
1063
1063
|
{
|
|
1064
1064
|
name: "clone_create",
|
package/package.json
CHANGED
package/test/dblab.cli.test.ts
CHANGED
|
@@ -93,17 +93,24 @@ async function startFakeApi(projects?: unknown[]) {
|
|
|
93
93
|
};
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
-
describe("CLI DBLab companion command groups", () => {
|
|
97
|
-
test("top-level help lists
|
|
96
|
+
describe("CLI DBLab companion command groups (grouped under `pgai dblab …`)", () => {
|
|
97
|
+
test("top-level help lists the dblab group", () => {
|
|
98
98
|
const r = runCli(["--help"], isolatedEnv());
|
|
99
99
|
const out = `${r.stdout}\n${r.stderr}`;
|
|
100
|
+
expect(out).toContain("dblab");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test("dblab help exposes the clone, branch, and snapshot groups", () => {
|
|
104
|
+
const r = runCli(["dblab", "--help"], isolatedEnv());
|
|
105
|
+
expect(r.status).toBe(0);
|
|
106
|
+
const out = `${r.stdout}\n${r.stderr}`;
|
|
100
107
|
expect(out).toContain("clone");
|
|
101
108
|
expect(out).toContain("branch");
|
|
102
109
|
expect(out).toContain("snapshot");
|
|
103
110
|
});
|
|
104
111
|
|
|
105
112
|
test("clone help exposes create/list/status/reset/destroy", () => {
|
|
106
|
-
const r = runCli(["clone", "--help"], isolatedEnv());
|
|
113
|
+
const r = runCli(["dblab", "clone", "--help"], isolatedEnv());
|
|
107
114
|
expect(r.status).toBe(0);
|
|
108
115
|
const out = `${r.stdout}\n${r.stderr}`;
|
|
109
116
|
expect(out).toContain("create");
|
|
@@ -114,7 +121,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
114
121
|
});
|
|
115
122
|
|
|
116
123
|
test("branch help exposes list/create/delete/log", () => {
|
|
117
|
-
const r = runCli(["branch", "--help"], isolatedEnv());
|
|
124
|
+
const r = runCli(["dblab", "branch", "--help"], isolatedEnv());
|
|
118
125
|
expect(r.status).toBe(0);
|
|
119
126
|
const out = `${r.stdout}\n${r.stderr}`;
|
|
120
127
|
expect(out).toContain("list");
|
|
@@ -124,7 +131,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
124
131
|
});
|
|
125
132
|
|
|
126
133
|
test("snapshot help exposes list/create/destroy", () => {
|
|
127
|
-
const r = runCli(["snapshot", "--help"], isolatedEnv());
|
|
134
|
+
const r = runCli(["dblab", "snapshot", "--help"], isolatedEnv());
|
|
128
135
|
expect(r.status).toBe(0);
|
|
129
136
|
const out = `${r.stdout}\n${r.stderr}`;
|
|
130
137
|
expect(out).toContain("list");
|
|
@@ -133,13 +140,13 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
133
140
|
});
|
|
134
141
|
|
|
135
142
|
test("clone create fails fast without an API key", () => {
|
|
136
|
-
const r = runCli(["clone", "create", "--project", "main-db"], isolatedEnv());
|
|
143
|
+
const r = runCli(["dblab", "clone", "create", "--project", "main-db"], isolatedEnv());
|
|
137
144
|
expect(r.status).toBe(1);
|
|
138
145
|
expect(`${r.stdout}\n${r.stderr}`).toContain("API key is required");
|
|
139
146
|
});
|
|
140
147
|
|
|
141
148
|
test("clone list without --project errors", () => {
|
|
142
|
-
const r = runCli(["clone", "list"], isolatedEnv({ PGAI_API_KEY: "test-key" }));
|
|
149
|
+
const r = runCli(["dblab", "clone", "list"], isolatedEnv({ PGAI_API_KEY: "test-key" }));
|
|
143
150
|
expect(r.status).not.toBe(0);
|
|
144
151
|
expect(`${r.stdout}\n${r.stderr}`.toLowerCase()).toContain("project");
|
|
145
152
|
});
|
|
@@ -148,7 +155,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
148
155
|
const api = await startFakeApi();
|
|
149
156
|
try {
|
|
150
157
|
const r = await runCliAsync(
|
|
151
|
-
["clone", "create", "--project", "main-db", "--json"],
|
|
158
|
+
["dblab", "clone", "create", "--project", "main-db", "--json"],
|
|
152
159
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
153
160
|
);
|
|
154
161
|
expect(r.status).toBe(0);
|
|
@@ -170,7 +177,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
170
177
|
const api = await startFakeApi();
|
|
171
178
|
try {
|
|
172
179
|
const r = await runCliAsync(
|
|
173
|
-
["clone", "create", "--project", "main-db", "--branch", "feature-idx", "--snapshot", "s-9", "--protected", "--json"],
|
|
180
|
+
["dblab", "clone", "create", "--project", "main-db", "--branch", "feature-idx", "--snapshot", "s-9", "--protected", "--json"],
|
|
174
181
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
175
182
|
);
|
|
176
183
|
expect(r.status).toBe(0);
|
|
@@ -185,7 +192,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
185
192
|
const api = await startFakeApi();
|
|
186
193
|
try {
|
|
187
194
|
const r = await runCliAsync(
|
|
188
|
-
["clone", "reset", "c-8f21", "--project", "main-db", "--json"],
|
|
195
|
+
["dblab", "clone", "reset", "c-8f21", "--project", "main-db", "--json"],
|
|
189
196
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
190
197
|
);
|
|
191
198
|
expect(r.status).toBe(0);
|
|
@@ -202,7 +209,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
202
209
|
const api = await startFakeApi();
|
|
203
210
|
try {
|
|
204
211
|
const r = await runCliAsync(
|
|
205
|
-
["clone", "destroy", "c-8f21", "--project", "12", "--json"],
|
|
212
|
+
["dblab", "clone", "destroy", "c-8f21", "--project", "12", "--json"],
|
|
206
213
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
207
214
|
);
|
|
208
215
|
expect(r.status).toBe(0);
|
|
@@ -219,7 +226,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
219
226
|
const api = await startFakeApi();
|
|
220
227
|
try {
|
|
221
228
|
const r = await runCliAsync(
|
|
222
|
-
["branch", "create", "feature-idx", "--project", "main-db", "--snapshot", "latest", "--json"],
|
|
229
|
+
["dblab", "branch", "create", "feature-idx", "--project", "main-db", "--snapshot", "latest", "--json"],
|
|
223
230
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
224
231
|
);
|
|
225
232
|
expect(r.status).toBe(0);
|
|
@@ -236,7 +243,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
236
243
|
const api = await startFakeApi();
|
|
237
244
|
try {
|
|
238
245
|
const r = await runCliAsync(
|
|
239
|
-
["branch", "log", "feature-idx", "--project", "main-db", "--json"],
|
|
246
|
+
["dblab", "branch", "log", "feature-idx", "--project", "main-db", "--json"],
|
|
240
247
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
241
248
|
);
|
|
242
249
|
expect(r.status).toBe(0);
|
|
@@ -252,7 +259,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
252
259
|
const api = await startFakeApi();
|
|
253
260
|
try {
|
|
254
261
|
const r = await runCliAsync(
|
|
255
|
-
["snapshot", "list", "--project", "main-db", "--json"],
|
|
262
|
+
["dblab", "snapshot", "list", "--project", "main-db", "--json"],
|
|
256
263
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
257
264
|
);
|
|
258
265
|
expect(r.status).toBe(0);
|
|
@@ -268,7 +275,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
268
275
|
const api = await startFakeApi();
|
|
269
276
|
try {
|
|
270
277
|
const r = await runCliAsync(
|
|
271
|
-
["snapshot", "create", "--project", "main-db", "--clone", "c-8f21", "--message", "before idx", "--json"],
|
|
278
|
+
["dblab", "snapshot", "create", "--project", "main-db", "--clone", "c-8f21", "--message", "before idx", "--json"],
|
|
272
279
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
273
280
|
);
|
|
274
281
|
expect(r.status).toBe(0);
|
|
@@ -284,7 +291,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
284
291
|
const api = await startFakeApi();
|
|
285
292
|
try {
|
|
286
293
|
const r = await runCliAsync(
|
|
287
|
-
["snapshot", "destroy", "s-1", "--project", "main-db", "--force", "--json"],
|
|
294
|
+
["dblab", "snapshot", "destroy", "s-1", "--project", "main-db", "--force", "--json"],
|
|
288
295
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
289
296
|
);
|
|
290
297
|
expect(r.status).toBe(0);
|
|
@@ -300,7 +307,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
300
307
|
const api = await startFakeApi();
|
|
301
308
|
try {
|
|
302
309
|
const r = await runCliAsync(
|
|
303
|
-
["clone", "list", "--project", "does-not-exist"],
|
|
310
|
+
["dblab", "clone", "list", "--project", "does-not-exist"],
|
|
304
311
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: api.baseUrl })
|
|
305
312
|
);
|
|
306
313
|
expect(r.status).toBe(1);
|
|
@@ -327,7 +334,7 @@ describe("CLI DBLab companion command groups", () => {
|
|
|
327
334
|
try {
|
|
328
335
|
const baseUrl = `http://${server.hostname}:${server.port}/api/general`;
|
|
329
336
|
const r = await runCliAsync(
|
|
330
|
-
["clone", "destroy", "c-1", "--project", "main-db"],
|
|
337
|
+
["dblab", "clone", "destroy", "c-1", "--project", "main-db"],
|
|
331
338
|
isolatedEnv({ PGAI_API_KEY: "test-key", PGAI_API_BASE_URL: baseUrl })
|
|
332
339
|
);
|
|
333
340
|
expect(r.status).toBe(1);
|
package/test/joe.cli.test.ts
CHANGED
|
@@ -126,11 +126,22 @@ function startFakeApi() {
|
|
|
126
126
|
return { baseUrl, requests, stop: () => server.stop(true) };
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
describe("CLI Joe command surface", () => {
|
|
130
|
-
test("top-level help lists the
|
|
129
|
+
describe("CLI Joe command surface (grouped under `pgai joe …`)", () => {
|
|
130
|
+
test("top-level help lists the joe + dblab groups and top-level projects", () => {
|
|
131
131
|
const r = runCli(["--help"], isolatedEnv());
|
|
132
132
|
const out = `${r.stdout}\n${r.stderr}`;
|
|
133
|
-
|
|
133
|
+
// Joe verbs are grouped under `joe`; DBLab verbs under `dblab`. `projects`
|
|
134
|
+
// stays top-level (org-level, not a Joe endpoint).
|
|
135
|
+
for (const group of ["joe", "dblab", "projects"]) {
|
|
136
|
+
expect(out).toContain(group);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test("pgai joe --help lists all the Joe verbs", () => {
|
|
141
|
+
const r = runCli(["joe", "--help"], isolatedEnv());
|
|
142
|
+
expect(r.status).toBe(0);
|
|
143
|
+
const out = `${r.stdout}\n${r.stderr}`;
|
|
144
|
+
for (const verb of ["plan", "explain", "exec", "hypo", "activity", "terminate", "reset", "describe", "result", "status"]) {
|
|
134
145
|
expect(out).toContain(verb);
|
|
135
146
|
}
|
|
136
147
|
});
|
|
@@ -153,11 +164,11 @@ describe("CLI Joe command surface", () => {
|
|
|
153
164
|
}
|
|
154
165
|
});
|
|
155
166
|
|
|
156
|
-
test("pgai plan --project 12 submits command='plan' and prints the plan", async () => {
|
|
167
|
+
test("pgai joe plan --project 12 submits command='plan' and prints the plan", async () => {
|
|
157
168
|
const api = startFakeApi();
|
|
158
169
|
try {
|
|
159
170
|
const r = await runCliAsync(
|
|
160
|
-
["plan", "select * from users where email = 'x@acme.io'", "--project", "12"],
|
|
171
|
+
["joe", "plan", "select * from users where email = 'x@acme.io'", "--project", "12"],
|
|
161
172
|
isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl })
|
|
162
173
|
);
|
|
163
174
|
expect(r.status).toBe(0);
|
|
@@ -174,11 +185,11 @@ describe("CLI Joe command surface", () => {
|
|
|
174
185
|
}
|
|
175
186
|
});
|
|
176
187
|
|
|
177
|
-
test("pgai plan --project main-db resolves the alias via projects_list", async () => {
|
|
188
|
+
test("pgai joe plan --project main-db resolves the alias via projects_list", async () => {
|
|
178
189
|
const api = startFakeApi();
|
|
179
190
|
try {
|
|
180
191
|
const r = await runCliAsync(
|
|
181
|
-
["plan", "select 1", "--project", "main-db"],
|
|
192
|
+
["joe", "plan", "select 1", "--project", "main-db"],
|
|
182
193
|
isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl })
|
|
183
194
|
);
|
|
184
195
|
expect(r.status).toBe(0);
|
|
@@ -190,15 +201,15 @@ describe("CLI Joe command surface", () => {
|
|
|
190
201
|
}
|
|
191
202
|
});
|
|
192
203
|
|
|
193
|
-
test("optimize loop: plan then exec reuse the same session on one project", async () => {
|
|
204
|
+
test("optimize loop: joe plan then joe exec reuse the same session on one project", async () => {
|
|
194
205
|
const api = startFakeApi();
|
|
195
206
|
const env = isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl });
|
|
196
207
|
try {
|
|
197
208
|
// First command starts a fresh session; the server hands back session 88.
|
|
198
|
-
const first = await runCliAsync(["plan", "select 1", "--project", "12", "--new-session"], env);
|
|
209
|
+
const first = await runCliAsync(["joe", "plan", "select 1", "--project", "12", "--new-session"], env);
|
|
199
210
|
expect(first.status).toBe(0);
|
|
200
211
|
// Second command auto-reuses the stored session (88) on the same project.
|
|
201
|
-
const second = await runCliAsync(["exec", "create index on users (email)", "--project", "12"], env);
|
|
212
|
+
const second = await runCliAsync(["joe", "exec", "create index on users (email)", "--project", "12"], env);
|
|
202
213
|
expect(second.status).toBe(0);
|
|
203
214
|
expect(second.stdout).toContain("CREATE INDEX");
|
|
204
215
|
|
|
@@ -215,21 +226,21 @@ describe("CLI Joe command surface", () => {
|
|
|
215
226
|
}
|
|
216
227
|
});
|
|
217
228
|
|
|
218
|
-
test("cold clone exceeds the budget -> resume handle (exit 0), then pgai result", async () => {
|
|
229
|
+
test("cold clone exceeds the budget -> resume handle (exit 0), then pgai joe result", async () => {
|
|
219
230
|
const api = startFakeApi();
|
|
220
231
|
const env = isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl });
|
|
221
232
|
try {
|
|
222
233
|
// project 77 (COLD) stays `running`; --budget 0 forces immediate resume.
|
|
223
|
-
const r = await runCliAsync(["plan", "select 1", "--project", "77", "--budget", "0"], env);
|
|
234
|
+
const r = await runCliAsync(["joe", "plan", "select 1", "--project", "77", "--budget", "0"], env);
|
|
224
235
|
expect(r.status).toBe(0);
|
|
225
236
|
expect(r.stdout).toContain("resume:");
|
|
226
|
-
expect(r.stdout).toMatch(/pgai result \d+/);
|
|
237
|
+
expect(r.stdout).toMatch(/pgai joe result \d+/);
|
|
227
238
|
|
|
228
|
-
const match = r.stdout.match(/pgai result (\d+)/);
|
|
239
|
+
const match = r.stdout.match(/pgai joe result (\d+)/);
|
|
229
240
|
const commandId = match ? match[1] : "";
|
|
230
241
|
expect(commandId).not.toBe("");
|
|
231
242
|
|
|
232
|
-
const resumed = await runCliAsync(["result", commandId], env);
|
|
243
|
+
const resumed = await runCliAsync(["joe", "result", commandId], env);
|
|
233
244
|
expect(resumed.status).toBe(0);
|
|
234
245
|
expect(resumed.stdout).toContain("Seq Scan on users");
|
|
235
246
|
} finally {
|
|
@@ -241,7 +252,7 @@ describe("CLI Joe command surface", () => {
|
|
|
241
252
|
const api = startFakeApi();
|
|
242
253
|
try {
|
|
243
254
|
const r = await runCliAsync(
|
|
244
|
-
["plan", "select 1", "--project", "999"],
|
|
255
|
+
["joe", "plan", "select 1", "--project", "999"],
|
|
245
256
|
isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl })
|
|
246
257
|
);
|
|
247
258
|
expect(r.status).toBe(1);
|
|
@@ -251,11 +262,11 @@ describe("CLI Joe command surface", () => {
|
|
|
251
262
|
}
|
|
252
263
|
});
|
|
253
264
|
|
|
254
|
-
test("terminate requires a numeric pid and maps args.pid", async () => {
|
|
265
|
+
test("joe terminate requires a numeric pid and maps args.pid", async () => {
|
|
255
266
|
const api = startFakeApi();
|
|
256
267
|
try {
|
|
257
268
|
const r = await runCliAsync(
|
|
258
|
-
["terminate", "4711", "--project", "12"],
|
|
269
|
+
["joe", "terminate", "4711", "--project", "12"],
|
|
259
270
|
isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl })
|
|
260
271
|
);
|
|
261
272
|
expect(r.status).toBe(0);
|
|
@@ -268,11 +279,11 @@ describe("CLI Joe command surface", () => {
|
|
|
268
279
|
}
|
|
269
280
|
});
|
|
270
281
|
|
|
271
|
-
test("describe maps args.object (and --variant)", async () => {
|
|
282
|
+
test("joe describe maps args.object (and --variant)", async () => {
|
|
272
283
|
const api = startFakeApi();
|
|
273
284
|
try {
|
|
274
285
|
const r = await runCliAsync(
|
|
275
|
-
["describe", "users", "--project", "12", "--variant", "\\d+"],
|
|
286
|
+
["joe", "describe", "users", "--project", "12", "--variant", "\\d+"],
|
|
276
287
|
isolatedEnv({ PGAI_API_KEY: "k", PGAI_API_BASE_URL: api.baseUrl })
|
|
277
288
|
);
|
|
278
289
|
expect(r.status).toBe(0);
|
|
@@ -285,13 +296,13 @@ describe("CLI Joe command surface", () => {
|
|
|
285
296
|
});
|
|
286
297
|
|
|
287
298
|
test("missing --project fails fast", () => {
|
|
288
|
-
const r = runCli(["plan", "select 1"], isolatedEnv({ PGAI_API_KEY: "k" }));
|
|
299
|
+
const r = runCli(["joe", "plan", "select 1"], isolatedEnv({ PGAI_API_KEY: "k" }));
|
|
289
300
|
expect(r.status).toBe(1);
|
|
290
301
|
expect(`${r.stdout}\n${r.stderr}`).toContain("Project is required");
|
|
291
302
|
});
|
|
292
303
|
|
|
293
304
|
test("missing API key fails fast", () => {
|
|
294
|
-
const r = runCli(["plan", "select 1", "--project", "12"], isolatedEnv());
|
|
305
|
+
const r = runCli(["joe", "plan", "select 1", "--project", "12"], isolatedEnv());
|
|
295
306
|
expect(r.status).toBe(1);
|
|
296
307
|
expect(`${r.stdout}\n${r.stderr}`).toContain("API key is required");
|
|
297
308
|
});
|