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.
@@ -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
- program
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
- program
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
- program
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
- program
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
- program
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
- program
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
- program
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
- program
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 = program.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
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
- program
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
- program
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 = program.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
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 = program.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
5983
+ const snapshot = dblab.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
5965
5984
 
5966
5985
  snapshot
5967
5986
  .command("list")
@@ -13425,7 +13425,7 @@ var {
13425
13425
  // package.json
13426
13426
  var package_default = {
13427
13427
  name: "postgresai",
13428
- version: "0.16.0-dev.5",
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.5";
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
- withJoeOptions(program2.command("plan <sql>").description("plan a query (EXPLAIN, plan-only \u2014 no execution; the fast/safe default)")).action(async (sql, opts) => {
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(program2.command("explain <sql>").description("EXPLAIN ANALYZE a query (EXECUTES on the hardened, ephemeral clone)")).action(async (sql, opts) => {
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(program2.command("exec <sql>").description("run arbitrary DDL/DML on the clone (e.g. create index, analyze)")).action(async (sql, opts) => {
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(program2.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
+ 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(program2.command("activity").description("running-activity snapshot (pg_stat_activity; query text redacted)")).action(async (opts) => {
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(program2.command("terminate <pid>").description("pg_terminate_backend(pid) on the clone")).action(async (pid, opts) => {
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(program2.command("reset").description("reset/recreate the session's thin clone")).action(async (opts) => {
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(program2.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
+ 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 clone2 = program2.command("clone").description("DBLab thin-clone management (proxies the Platform DBLab API)");
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
- program2.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) => {
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
- program2.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) => {
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 = program2.command("branch").description("DBLab branch management (proxies the Platform DBLab API)");
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 = program2.command("snapshot").description("DBLab snapshot management (proxies the Platform DBLab API)");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "postgresai",
3
- "version": "0.16.0-dev.5",
3
+ "version": "0.16.0-dev.6",
4
4
  "description": "postgres_ai CLI",
5
5
  "license": "Apache-2.0",
6
6
  "private": false,
@@ -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 clone, branch, and snapshot groups", () => {
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);
@@ -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 Joe verbs and projects/result", () => {
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
- for (const verb of ["plan", "explain", "exec", "hypo", "activity", "terminate", "reset", "describe", "projects", "result", "status"]) {
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
  });