@abloatai/cli 0.60.0 → 0.61.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.cjs +425 -87
  2. package/package.json +3 -3
package/dist/cli.cjs CHANGED
@@ -3976,7 +3976,7 @@ var init_observeCliError = __esm({
3976
3976
  import_errorObservation = require("@abloatai/transaction/errorObservation");
3977
3977
  import_errors5 = require("@abloatai/transaction/errors");
3978
3978
  dsn = process.env.ABLO_CLI_SENTRY_DSN ?? "https://1ac154bff10b06836e1ea9de9e0d92f0@o4510928209772544.ingest.de.sentry.io/4511660691423312" ?? "";
3979
- release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.60.0";
3979
+ release = process.env.ABLO_CLI_RELEASE ?? "@abloatai/cli@0.61.0";
3980
3980
  initialized = false;
3981
3981
  nativeProcessExit = process.exit.bind(process);
3982
3982
  exitBoundaryInstalled = false;
@@ -4311,7 +4311,7 @@ var init_src2 = __esm({
4311
4311
 
4312
4312
  // src/cliEnvironment.ts
4313
4313
  function cliVersion() {
4314
- return "0.60.0";
4314
+ return "0.61.0";
4315
4315
  }
4316
4316
  function cliOs() {
4317
4317
  const value = (0, import_node_os.platform)();
@@ -5368,6 +5368,210 @@ var init_dbProvider = __esm({
5368
5368
  }
5369
5369
  });
5370
5370
 
5371
+ // src/connect/inspect.ts
5372
+ function inspectRegisteredConnection(validation) {
5373
+ if (!validation.ok) {
5374
+ return {
5375
+ object: "database_connection_reconcile",
5376
+ code: "operator_action_required",
5377
+ steps: {
5378
+ database: "pending",
5379
+ registration: "unchanged",
5380
+ snapshot: "pending",
5381
+ readiness: "action_required"
5382
+ },
5383
+ repairItems: [],
5384
+ needsDatabaseReconcile: false,
5385
+ needsSnapshotRequest: false,
5386
+ detail: validation.code ?? validation.message
5387
+ };
5388
+ }
5389
+ if (!validation.reachable) {
5390
+ return {
5391
+ object: "database_connection_reconcile",
5392
+ code: "operator_action_required",
5393
+ steps: {
5394
+ database: "action_required",
5395
+ registration: "unchanged",
5396
+ snapshot: "pending",
5397
+ readiness: "action_required"
5398
+ },
5399
+ repairItems: [],
5400
+ needsDatabaseReconcile: false,
5401
+ needsSnapshotRequest: false,
5402
+ detail: validation.reason ?? "source_unreachable"
5403
+ };
5404
+ }
5405
+ if (validation.failures.length > 0) {
5406
+ const repairItems = [...new Set(validation.failures.map((failure) => failure.item))].sort();
5407
+ const operatorItems = repairItems.filter((item) => !DATABASE_RECONCILABLE_ITEMS.has(item));
5408
+ if (operatorItems.length > 0) {
5409
+ return {
5410
+ object: "database_connection_reconcile",
5411
+ code: "operator_action_required",
5412
+ steps: {
5413
+ database: "action_required",
5414
+ registration: "unchanged",
5415
+ snapshot: "pending",
5416
+ readiness: "action_required"
5417
+ },
5418
+ repairItems,
5419
+ needsDatabaseReconcile: false,
5420
+ needsSnapshotRequest: false,
5421
+ detail: operatorItems.join(",")
5422
+ };
5423
+ }
5424
+ return {
5425
+ object: "database_connection_reconcile",
5426
+ code: "reconciling",
5427
+ steps: {
5428
+ database: "pending",
5429
+ registration: "unchanged",
5430
+ snapshot: "pending",
5431
+ readiness: "pending"
5432
+ },
5433
+ repairItems,
5434
+ needsDatabaseReconcile: true,
5435
+ needsSnapshotRequest: repairItems.some((item) => SNAPSHOT_SENSITIVE_ITEMS.has(item)) || validation.initialSnapshot?.status === "retrying"
5436
+ };
5437
+ }
5438
+ if (validation.initialSnapshot?.status === "retrying") {
5439
+ return {
5440
+ object: "database_connection_reconcile",
5441
+ code: "reconciling",
5442
+ steps: {
5443
+ database: "unchanged",
5444
+ registration: "unchanged",
5445
+ snapshot: "pending",
5446
+ readiness: "pending"
5447
+ },
5448
+ repairItems: [],
5449
+ needsDatabaseReconcile: false,
5450
+ needsSnapshotRequest: true,
5451
+ ...validation.initialSnapshot.detail ? { detail: validation.initialSnapshot.detail } : {}
5452
+ };
5453
+ }
5454
+ if (validation.initialSnapshot?.status === "loading") {
5455
+ return {
5456
+ object: "database_connection_reconcile",
5457
+ code: "loading",
5458
+ steps: {
5459
+ database: "unchanged",
5460
+ registration: "unchanged",
5461
+ snapshot: "loading",
5462
+ readiness: "pending"
5463
+ },
5464
+ repairItems: [],
5465
+ needsDatabaseReconcile: false,
5466
+ needsSnapshotRequest: false
5467
+ };
5468
+ }
5469
+ if (validation.ready) {
5470
+ return {
5471
+ object: "database_connection_reconcile",
5472
+ code: "ready",
5473
+ steps: {
5474
+ database: "unchanged",
5475
+ registration: "unchanged",
5476
+ snapshot: "ready",
5477
+ readiness: "ready"
5478
+ },
5479
+ repairItems: [],
5480
+ needsDatabaseReconcile: false,
5481
+ needsSnapshotRequest: false
5482
+ };
5483
+ }
5484
+ return {
5485
+ object: "database_connection_reconcile",
5486
+ code: "operator_action_required",
5487
+ steps: {
5488
+ database: "unchanged",
5489
+ registration: "unchanged",
5490
+ snapshot: "pending",
5491
+ readiness: "action_required"
5492
+ },
5493
+ repairItems: [],
5494
+ needsDatabaseReconcile: false,
5495
+ needsSnapshotRequest: false,
5496
+ detail: "unclassified_not_ready"
5497
+ };
5498
+ }
5499
+ function absentConnectionStatus() {
5500
+ return {
5501
+ object: "database_connection_reconcile",
5502
+ code: "absent",
5503
+ steps: {
5504
+ database: "pending",
5505
+ registration: "pending",
5506
+ snapshot: "pending",
5507
+ readiness: "pending"
5508
+ },
5509
+ repairItems: [],
5510
+ needsDatabaseReconcile: true,
5511
+ needsSnapshotRequest: false
5512
+ };
5513
+ }
5514
+ var SNAPSHOT_SENSITIVE_ITEMS, DATABASE_RECONCILABLE_ITEMS;
5515
+ var init_inspect = __esm({
5516
+ "src/connect/inspect.ts"() {
5517
+ "use strict";
5518
+ init_cjs_shims();
5519
+ SNAPSHOT_SENSITIVE_ITEMS = /* @__PURE__ */ new Set([
5520
+ "publication",
5521
+ "publication_drift",
5522
+ "replica_identity",
5523
+ "snapshot_row_security",
5524
+ "table_select"
5525
+ ]);
5526
+ DATABASE_RECONCILABLE_ITEMS = /* @__PURE__ */ new Set([
5527
+ "publication",
5528
+ "replication_role",
5529
+ "replica_identity",
5530
+ "table_select",
5531
+ "snapshot_row_security",
5532
+ "write_role",
5533
+ "row_security",
5534
+ "database_privileges",
5535
+ "schema_privileges",
5536
+ "idempotency_ledger",
5537
+ "table_privileges",
5538
+ "logical_marker",
5539
+ "publication_drift"
5540
+ ]);
5541
+ }
5542
+ });
5543
+
5544
+ // src/connect/snapshot.ts
5545
+ function requestInitialSnapshot(input) {
5546
+ return requestControlPlane({
5547
+ path: "/v1/datasources/resnapshot",
5548
+ method: "POST",
5549
+ ...input.apiUrl ? { baseUrl: input.apiUrl } : {},
5550
+ apiKey: input.apiKey,
5551
+ body: {},
5552
+ responseSchema: import_wire2.datasourceResnapshotResponseSchema
5553
+ });
5554
+ }
5555
+ var import_wire2;
5556
+ var init_snapshot = __esm({
5557
+ "src/connect/snapshot.ts"() {
5558
+ "use strict";
5559
+ init_cjs_shims();
5560
+ import_wire2 = require("@abloatai/transaction/wire");
5561
+ init_controlPlane();
5562
+ }
5563
+ });
5564
+
5565
+ // src/connect/index.ts
5566
+ var init_connect = __esm({
5567
+ "src/connect/index.ts"() {
5568
+ "use strict";
5569
+ init_cjs_shims();
5570
+ init_inspect();
5571
+ init_snapshot();
5572
+ }
5573
+ });
5574
+
5371
5575
  // src/remoteValidation.ts
5372
5576
  function dialFailureReason(err) {
5373
5577
  if (err === null || typeof err !== "object") return null;
@@ -5385,7 +5589,7 @@ function dialFailureReason(err) {
5385
5589
  return null;
5386
5590
  }
5387
5591
  function describeRemoteFailure(failure) {
5388
- const label = (0, import_wire2.isReadinessItem)(failure.item) ? READINESS_LABELS[failure.item](failure) : failure.item;
5592
+ const label = (0, import_wire3.isReadinessItem)(failure.item) ? READINESS_LABELS[failure.item](failure) : failure.item;
5389
5593
  return { label, fix: failure.fix };
5390
5594
  }
5391
5595
  async function requestRemoteValidation(input) {
@@ -5398,7 +5602,7 @@ async function requestRemoteValidation(input) {
5398
5602
  ...input.connectionString ? { connectionString: input.connectionString } : {},
5399
5603
  ...input.writeConnectionString ? { writeConnectionString: input.writeConnectionString } : {}
5400
5604
  },
5401
- responseSchema: import_wire2.datasourceValidationResponseSchema,
5605
+ responseSchema: import_wire3.datasourceValidationResponseSchema,
5402
5606
  ...input.fetchImpl ? { fetchImpl: input.fetchImpl } : {}
5403
5607
  });
5404
5608
  if (!result.ok) {
@@ -5419,12 +5623,12 @@ async function requestRemoteValidation(input) {
5419
5623
  failures: verdict.failures
5420
5624
  };
5421
5625
  }
5422
- var import_wire2, DIAL_FAILURE_CODES, withActual, READINESS_LABELS;
5626
+ var import_wire3, DIAL_FAILURE_CODES, withActual, READINESS_LABELS;
5423
5627
  var init_remoteValidation = __esm({
5424
5628
  "src/remoteValidation.ts"() {
5425
5629
  "use strict";
5426
5630
  init_cjs_shims();
5427
- import_wire2 = require("@abloatai/transaction/wire");
5631
+ import_wire3 = require("@abloatai/transaction/wire");
5428
5632
  init_controlPlane();
5429
5633
  DIAL_FAILURE_CODES = /* @__PURE__ */ new Set([
5430
5634
  "ENOTFOUND",
@@ -5777,19 +5981,21 @@ async function registerDirectDataSource(opts) {
5777
5981
  ...opts.replicationSlot ? { replicationSlot: opts.replicationSlot } : {},
5778
5982
  ...opts.publication ? { publication: opts.publication } : {}
5779
5983
  },
5780
- responseSchema: import_wire3.datasourceSummarySchema
5984
+ responseSchema: import_wire4.datasourceSummarySchema
5781
5985
  });
5782
5986
  if (result.ok) {
5783
5987
  const body = result.value;
5784
5988
  const statusNote = body.status === "active" ? `${opts.route}, active` : opts.route;
5785
- console.log(
5786
- `
5989
+ if (!opts.quiet) {
5990
+ console.log(
5991
+ `
5787
5992
  ${import_picocolors8.default.green("\u2713")} Registered${body.host ? ` ${import_picocolors8.default.dim(body.host)}` : ""}${body.id ? ` ${import_picocolors8.default.dim(`(${body.id})`)}` : ""} as a direct DataSource (${statusNote}).
5788
5993
  Your database is connected. Reads follow its replication stream; writes go through Ablo
5789
5994
  and land in your own tables. Rows that already exist load automatically \u2014 no manual
5790
5995
  backfill or row updates. Check their progress with ${import_picocolors8.default.cyan("ablo connect check")}.
5791
5996
  `
5792
- );
5997
+ );
5998
+ }
5793
5999
  return true;
5794
6000
  }
5795
6001
  const err = result.error;
@@ -5861,14 +6067,14 @@ async function registerDirectDataSource(opts) {
5861
6067
  console.error();
5862
6068
  return false;
5863
6069
  }
5864
- var import_picocolors8, import_zod3, import_wire3, import_source2, import_footprint, DIRECT_DATA_SOURCE_ROUTES, registerFailureDetailsSchema;
6070
+ var import_picocolors8, import_zod3, import_wire4, import_source2, import_footprint, DIRECT_DATA_SOURCE_ROUTES, registerFailureDetailsSchema;
5865
6071
  var init_connectSetup = __esm({
5866
6072
  "src/connectSetup.ts"() {
5867
6073
  "use strict";
5868
6074
  init_cjs_shims();
5869
6075
  import_picocolors8 = __toESM(require_picocolors(), 1);
5870
6076
  import_zod3 = require("zod");
5871
- import_wire3 = require("@abloatai/transaction/wire");
6077
+ import_wire4 = require("@abloatai/transaction/wire");
5872
6078
  init_controlPlane();
5873
6079
  init_remoteValidation();
5874
6080
  import_source2 = require("@abloatai/transaction/source");
@@ -5881,10 +6087,10 @@ var init_connectSetup = __esm({
5881
6087
  "vpn"
5882
6088
  ];
5883
6089
  registerFailureDetailsSchema = import_zod3.z.object({
5884
- failures: import_zod3.z.array(import_wire3.readinessFailureSchema).optional(),
6090
+ failures: import_zod3.z.array(import_wire4.readinessFailureSchema).optional(),
5885
6091
  reason: import_zod3.z.string().optional(),
5886
6092
  details: import_zod3.z.object({
5887
- failures: import_zod3.z.array(import_wire3.readinessFailureSchema).optional(),
6093
+ failures: import_zod3.z.array(import_wire4.readinessFailureSchema).optional(),
5888
6094
  reason: import_zod3.z.string().optional()
5889
6095
  }).loose().optional()
5890
6096
  }).loose();
@@ -5910,7 +6116,7 @@ async function deregisterDataSource(opts) {
5910
6116
  path: "/v1/datasources",
5911
6117
  method: "DELETE",
5912
6118
  apiKey: opts.apiKey,
5913
- responseSchema: import_wire4.datasourceDisconnectedResponseSchema,
6119
+ responseSchema: import_wire5.datasourceDisconnectedResponseSchema,
5914
6120
  ...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {},
5915
6121
  ...opts.fetchImpl !== void 0 ? { fetchImpl: opts.fetchImpl } : {}
5916
6122
  });
@@ -6046,7 +6252,7 @@ async function disconnect(argv) {
6046
6252
  }
6047
6253
  renderDisconnected(outcome.response, project, branchLabel);
6048
6254
  }
6049
- var import_picocolors9, import_errors10, import_wire4, DISCONNECT_USAGE;
6255
+ var import_picocolors9, import_errors10, import_wire5, DISCONNECT_USAGE;
6050
6256
  var init_disconnect = __esm({
6051
6257
  "src/disconnect.ts"() {
6052
6258
  "use strict";
@@ -6054,7 +6260,7 @@ var init_disconnect = __esm({
6054
6260
  import_picocolors9 = __toESM(require_picocolors(), 1);
6055
6261
  init_dist2();
6056
6262
  import_errors10 = require("@abloatai/transaction/errors");
6057
- import_wire4 = require("@abloatai/transaction/wire");
6263
+ import_wire5 = require("@abloatai/transaction/wire");
6058
6264
  init_config();
6059
6265
  init_dbRole();
6060
6266
  init_controlPlane();
@@ -6207,7 +6413,7 @@ async function fetchPushedSchema(apiUrl3, apiKey) {
6207
6413
  signal: ctrl.signal
6208
6414
  });
6209
6415
  if (!res.ok) return null;
6210
- const parsed = import_wire5.schemaReadResponseSchema.safeParse(await res.json());
6416
+ const parsed = import_wire6.schemaReadResponseSchema.safeParse(await res.json());
6211
6417
  if (!parsed.success) return null;
6212
6418
  const read = parsed.data;
6213
6419
  return read.active ? {
@@ -6235,7 +6441,7 @@ async function fetchDeliveryState(apiUrl3, apiKey, timeoutMs = 4e3) {
6235
6441
  signal: ctrl.signal
6236
6442
  });
6237
6443
  if (!res.ok) return { kind: "unknown", detail: `HTTP ${res.status}` };
6238
- const parsed = import_wire5.logDeliveryResponseSchema.safeParse(await res.json());
6444
+ const parsed = import_wire6.logDeliveryResponseSchema.safeParse(await res.json());
6239
6445
  if (!parsed.success) return { kind: "unknown", detail: "unrecognized response" };
6240
6446
  const { window_seconds, recorded, unroutable, sample } = parsed.data;
6241
6447
  return { kind: "known", window_seconds, recorded, unroutable, sample: sample ?? null };
@@ -6259,7 +6465,7 @@ async function fetchDataSourceState(apiUrl3, apiKey, timeoutMs = 4e3) {
6259
6465
  if (!res.ok) {
6260
6466
  return { kind: "unknown", detail: `HTTP ${res.status}` };
6261
6467
  }
6262
- const parsed = import_wire5.datasourceListResponseSchema.safeParse(await res.json());
6468
+ const parsed = import_wire6.datasourceListResponseSchema.safeParse(await res.json());
6263
6469
  if (!parsed.success) return { kind: "unknown", detail: "unrecognized response" };
6264
6470
  const rows = parsed.data.data;
6265
6471
  if (rows.length === 0) return { kind: "none" };
@@ -6346,12 +6552,12 @@ function blockers(input) {
6346
6552
  }
6347
6553
  return found;
6348
6554
  }
6349
- var import_wire5, import_schema5, WRITE_READY_VERDICT;
6555
+ var import_wire6, import_schema5, WRITE_READY_VERDICT;
6350
6556
  var init_readiness = __esm({
6351
6557
  "src/readiness.ts"() {
6352
6558
  "use strict";
6353
6559
  init_cjs_shims();
6354
- import_wire5 = require("@abloatai/transaction/wire");
6560
+ import_wire6 = require("@abloatai/transaction/wire");
6355
6561
  init_push();
6356
6562
  init_dbProvider();
6357
6563
  init_remoteValidation();
@@ -6646,7 +6852,7 @@ async function locateExistingConnection(input) {
6646
6852
  connectionString: input.connectionString,
6647
6853
  ...input.schema ? { schema: input.schema } : {}
6648
6854
  },
6649
- responseSchema: import_wire6.datasourceLocationResponseSchema
6855
+ responseSchema: import_wire7.datasourceLocationResponseSchema
6650
6856
  });
6651
6857
  if (!result.ok) return null;
6652
6858
  if (result.value.held) return result.value.held;
@@ -6660,13 +6866,13 @@ function alreadyConnectedElsewhere(held) {
6660
6866
  const where = held.project ? `project ${held.project}, branch ${held.branch}` : `branch ${held.branch}`;
6661
6867
  return `This database schema is already connected to ${where}. A (database, schema) binding belongs to one plane at a time.`;
6662
6868
  }
6663
- var import_wire6, import_schema6;
6869
+ var import_wire7, import_schema6;
6664
6870
  var init_connectPreflight = __esm({
6665
6871
  "src/connectPreflight.ts"() {
6666
6872
  "use strict";
6667
6873
  init_cjs_shims();
6668
6874
  init_src();
6669
- import_wire6 = require("@abloatai/transaction/wire");
6875
+ import_wire7 = require("@abloatai/transaction/wire");
6670
6876
  init_controlPlane();
6671
6877
  init_push();
6672
6878
  import_schema6 = require("@abloatai/transaction/schema");
@@ -6689,6 +6895,26 @@ function postRegistrationOutcome(input) {
6689
6895
  notice: ROTATE_STRANDED_CREDENTIALS_NOTICE
6690
6896
  };
6691
6897
  }
6898
+ function emitReconcileStatus(status2, json) {
6899
+ if (json) {
6900
+ console.log(JSON.stringify(status2));
6901
+ return;
6902
+ }
6903
+ if (status2.code === "ready") {
6904
+ console.log(
6905
+ ` ${import_picocolors12.default.green("\u2713")} Already ready \u2014 database, credentials, and snapshot are unchanged.
6906
+ `
6907
+ );
6908
+ } else if (status2.code === "loading") {
6909
+ console.log(
6910
+ ` ${import_picocolors12.default.yellow("\u2014")} Existing rows are loading; re-run the same command to poll readiness.
6911
+ `
6912
+ );
6913
+ } else {
6914
+ console.log(` ${import_picocolors12.default.yellow("\u2014")} ${status2.code}
6915
+ `);
6916
+ }
6917
+ }
6692
6918
  function isPlaintextRefusal(err) {
6693
6919
  const message2 = err instanceof Error ? err.message : String(err);
6694
6920
  return /plaintext password/i.test(message2);
@@ -6720,15 +6946,10 @@ async function runConnectApply(args) {
6720
6946
  const rotating = args.rotate;
6721
6947
  const verb = rotating ? "connect rotate" : "connect apply";
6722
6948
  let adminUrl = args.url ?? readProjectAdminDatabaseUrl();
6723
- if (!adminUrl) {
6724
- throw new import_errors11.AbloValidationError(
6725
- "No admin connection string. Pass --url <admin-conn> (or set DATABASE_URL) and re-run.",
6726
- { code: "cli_database_url_missing" }
6727
- );
6728
- }
6729
6949
  const adminSource = args.url ? "--url" : "DATABASE_URL";
6730
6950
  let target = "your database";
6731
6951
  try {
6952
+ if (!adminUrl) throw new Error("not resolved yet");
6732
6953
  const parsed = new URL(adminUrl);
6733
6954
  target = `${parsed.host}${parsed.pathname === "/" ? "" : parsed.pathname}`;
6734
6955
  } catch {
@@ -6752,15 +6973,62 @@ ${ambient}` : ""}`,
6752
6973
  { code: "cli_api_key_missing" }
6753
6974
  );
6754
6975
  }
6755
- console.log(
6756
- `
6976
+ const apiUrl3 = apiBaseUrl();
6977
+ const planeState = await fetchDataSourceState(apiUrl3, apiKey);
6978
+ const existingRegistration = !rotating && planeState.kind === "connected" && planeState.connections.includes("direct");
6979
+ let existingNeedsSnapshot = false;
6980
+ if (existingRegistration) {
6981
+ const inspected = inspectRegisteredConnection(
6982
+ await requestRemoteValidation({ apiUrl: apiUrl3, apiKey })
6983
+ );
6984
+ if (inspected.code === "ready") {
6985
+ emitReconcileStatus(inspected, args.json);
6986
+ return;
6987
+ }
6988
+ if (inspected.code === "loading") {
6989
+ emitReconcileStatus(inspected, args.json);
6990
+ return;
6991
+ }
6992
+ if (!inspected.needsDatabaseReconcile && inspected.needsSnapshotRequest) {
6993
+ const snapshot = await requestInitialSnapshot({ apiUrl: apiUrl3, apiKey });
6994
+ const resumed = {
6995
+ ...inspected,
6996
+ code: snapshot.replication_slot?.released === false ? "operator_action_required" : "loading",
6997
+ steps: {
6998
+ ...inspected.steps,
6999
+ snapshot: snapshot.replication_slot?.released === false ? "action_required" : "loading",
7000
+ readiness: snapshot.replication_slot?.released === false ? "action_required" : "pending"
7001
+ },
7002
+ ...snapshot.replication_slot?.released === false ? { detail: snapshot.replication_slot.detail ?? "replication_slot_active" } : {}
7003
+ };
7004
+ emitReconcileStatus(resumed, args.json);
7005
+ return;
7006
+ }
7007
+ if (inspected.code === "operator_action_required") {
7008
+ throw new import_errors11.AbloConnectionError(
7009
+ `The registered source needs operator action before it can be reconciled (${inspected.detail ?? "not ready"}).`,
7010
+ { code: "cli_database_unreachable", details: { ...inspected } }
7011
+ );
7012
+ }
7013
+ existingNeedsSnapshot = inspected.needsSnapshotRequest;
7014
+ }
7015
+ if (!adminUrl) {
7016
+ throw new import_errors11.AbloValidationError(
7017
+ "This connection needs database reconciliation. Pass the transient owner connection with --url <admin-conn> (or set DATABASE_URL) and re-run the same `ablo connect apply` operation.",
7018
+ { code: "cli_database_url_missing" }
7019
+ );
7020
+ }
7021
+ if (!args.json) {
7022
+ console.log(
7023
+ `
6757
7024
  ${brand("ablo")} ${import_picocolors12.default.dim(verb)} ${import_picocolors12.default.dim(rotating ? "re-key the scoped roles" : "set up your database for Ablo")}
6758
7025
  `
6759
- );
6760
- console.log(
6761
- ` ${import_picocolors12.default.dim("\u2192")} ${import_picocolors12.default.bold(target)}${adminSource === "DATABASE_URL" ? import_picocolors12.default.dim(" (admin via DATABASE_URL)") : ""}
7026
+ );
7027
+ console.log(
7028
+ ` ${import_picocolors12.default.dim("\u2192")} ${import_picocolors12.default.bold(target)}${adminSource === "DATABASE_URL" ? import_picocolors12.default.dim(" (admin via DATABASE_URL)") : ""}
6762
7029
  `
6763
- );
7030
+ );
7031
+ }
6764
7032
  const pooledAdmin = detectPooler(adminUrl);
6765
7033
  if (pooledAdmin?.confidence === "host") {
6766
7034
  if (pooledAdmin.direct) {
@@ -6771,7 +7039,7 @@ ${ambient}` : ""}`,
6771
7039
  target = `${parsed.host}${parsed.pathname === "/" ? "" : parsed.pathname}`;
6772
7040
  } catch {
6773
7041
  }
6774
- console.log(
7042
+ if (!args.json) console.log(
6775
7043
  ` ${import_picocolors12.default.yellow("!")} ${import_picocolors12.default.bold(pooledLabel)} is a connection pooler, so this run uses the direct host:
6776
7044
  ${import_picocolors12.default.bold(target)}
6777
7045
  ` + import_picocolors12.default.dim(
@@ -6798,7 +7066,7 @@ ${ambient}` : ""}`,
6798
7066
  }
6799
7067
  }
6800
7068
  if (pooledAdmin?.confidence === "port") {
6801
- console.log(
7069
+ if (!args.json) console.log(
6802
7070
  ` ${import_picocolors12.default.yellow("!")} Port ${import_picocolors12.default.bold(new URL(adminUrl).port)} is the one a connection pooler usually answers on.
6803
7071
  ` + import_picocolors12.default.dim(
6804
7072
  ` Replication cannot run over a pooler, so if that is what this is, point ${import_picocolors12.default.bold("--url")}
@@ -6814,7 +7082,7 @@ ${ambient}` : ""}`,
6814
7082
  }).catch(() => null);
6815
7083
  const mismatch = connectTarget ? describeMismatches(connectTarget.mismatches) : null;
6816
7084
  if (mismatch) {
6817
- console.log(` ${import_picocolors12.default.yellow("!")} ${mismatch}
7085
+ if (!args.json) console.log(` ${import_picocolors12.default.yellow("!")} ${mismatch}
6818
7086
  `);
6819
7087
  }
6820
7088
  const confirmed = connectTarget?.confirmed;
@@ -6859,7 +7127,7 @@ ${ambient}` : ""}`,
6859
7127
  );
6860
7128
  }
6861
7129
  if (args.tables.length === 0) {
6862
- console.log(
7130
+ if (!args.json) console.log(
6863
7131
  import_picocolors12.default.dim(
6864
7132
  ` publishing the ${tables.length} table${tables.length === 1 ? "" : "s"} declared by your Ablo schema in ${import_picocolors12.default.bold(args.schema)} (${import_picocolors12.default.bold("--tables")} to override)
6865
7133
  `
@@ -6950,6 +7218,13 @@ ${ambient}` : ""}`,
6950
7218
  publication
6951
7219
  });
6952
7220
  const existingRoles = await presentRoles(admin, [role, writeRole]).catch(() => []);
7221
+ if (existingRegistration && existingRoles.length !== 2) {
7222
+ await admin.end({ timeout: 2 });
7223
+ throw new import_errors11.AbloValidationError(
7224
+ `The registered source's scoped role pair is incomplete (${existingRoles.length}/2 present). Re-run with \`ablo connect rotate\` to repair credentials explicitly; no database changes were made.`,
7225
+ { code: "cli_invalid_arguments", details: { existingRoles: [...existingRoles] } }
7226
+ );
7227
+ }
6953
7228
  if (rotatePlane) {
6954
7229
  const refusal = rotateWithoutConnection({ rotating, ...rotatePlane, existingRoles });
6955
7230
  if (refusal) {
@@ -6963,11 +7238,14 @@ ${ambient}` : ""}`,
6963
7238
  }
6964
7239
  const blocker2 = reapplyBlocker({
6965
7240
  rotating,
6966
- existingRoles
7241
+ // A complete, registered pair keeps its current passwords: apply repairs
7242
+ // only non-secret invariants and never re-registers it. A partial pair is
7243
+ // still refused because it cannot be completed without credential repair.
7244
+ existingRoles: existingRegistration && existingRoles.length === 2 ? [] : existingRoles
6967
7245
  });
6968
7246
  if (blocker2) {
6969
7247
  await admin.end({ timeout: 2 });
6970
- console.log(
7248
+ if (!args.json) console.log(
6971
7249
  ` ${import_picocolors12.default.yellow("!")} ${blocker2.roles.map((r2) => import_picocolors12.default.bold(r2)).join(" and ")} ${blocker2.plural ? "are" : "is"} already set up here.
6972
7250
  `
6973
7251
  );
@@ -7014,17 +7292,23 @@ ${ambient}` : ""}`,
7014
7292
  });
7015
7293
  const steps = buildPlan("scram-verifier");
7016
7294
  if (pubReconcile.removed.length > 0 || pubReconcile.recreated) {
7017
- console.log(
7018
- ` ${import_picocolors12.default.yellow("!")} ${import_picocolors12.default.bold(publication)} already publishes a different set; reconciling to your mapped tables:`
7019
- );
7020
- for (const t of pubReconcile.added) console.log(` ${import_picocolors12.default.green("+")} ${t}`);
7021
- for (const t of pubReconcile.removed)
7022
- console.log(` ${import_picocolors12.default.red("-")} ${t} ${import_picocolors12.default.dim("(stops replicating to Ablo)")}`);
7023
- if (pubReconcile.recreated && existingPublication.allTables)
7024
- console.log(` ${import_picocolors12.default.red("-")} ${import_picocolors12.default.dim("every other table (was FOR ALL TABLES)")}`);
7025
- console.log();
7295
+ if (!args.json) {
7296
+ console.log(
7297
+ ` ${import_picocolors12.default.yellow("!")} ${import_picocolors12.default.bold(publication)} already publishes a different set; reconciling to your mapped tables:`
7298
+ );
7299
+ for (const t of pubReconcile.added) console.log(` ${import_picocolors12.default.green("+")} ${t}`);
7300
+ for (const t of pubReconcile.removed) {
7301
+ console.log(` ${import_picocolors12.default.red("-")} ${t} ${import_picocolors12.default.dim("(stops replicating to Ablo)")}`);
7302
+ }
7303
+ if (pubReconcile.recreated && existingPublication.allTables) {
7304
+ console.log(
7305
+ ` ${import_picocolors12.default.red("-")} ${import_picocolors12.default.dim("every other table (was FOR ALL TABLES)")}`
7306
+ );
7307
+ }
7308
+ console.log();
7309
+ }
7026
7310
  }
7027
- printPlan(steps, args.showSql);
7311
+ if (!args.json) printPlan(steps, args.showSql);
7028
7312
  if (!args.yes) {
7029
7313
  if (!process.stdout.isTTY) {
7030
7314
  await admin.end({ timeout: 2 });
@@ -7075,9 +7359,39 @@ ${ambient}` : ""}`,
7075
7359
  process.exit(1);
7076
7360
  }
7077
7361
  await admin.end({ timeout: 2 });
7362
+ if (existingRegistration) {
7363
+ const after = inspectRegisteredConnection(
7364
+ await requestRemoteValidation({ apiUrl: apiUrl3, apiKey })
7365
+ );
7366
+ if (after.needsDatabaseReconcile || after.code === "operator_action_required") {
7367
+ throw new import_errors11.AbloConnectionError(
7368
+ `Database reconciliation did not reach the snapshot boundary (${after.detail ?? after.repairItems.join(", ")}).`,
7369
+ { code: "cli_database_unreachable", details: { ...after } }
7370
+ );
7371
+ }
7372
+ if (existingNeedsSnapshot || after.needsSnapshotRequest) {
7373
+ const snapshot = await requestInitialSnapshot({ apiUrl: apiUrl3, apiKey });
7374
+ const reconciled = {
7375
+ ...after,
7376
+ code: snapshot.replication_slot?.released === false ? "operator_action_required" : "loading",
7377
+ steps: {
7378
+ ...after.steps,
7379
+ database: "changed",
7380
+ registration: "unchanged",
7381
+ snapshot: snapshot.replication_slot?.released === false ? "action_required" : "loading",
7382
+ readiness: snapshot.replication_slot?.released === false ? "action_required" : "pending"
7383
+ },
7384
+ ...snapshot.replication_slot?.released === false ? { detail: snapshot.replication_slot.detail ?? "replication_slot_active" } : {}
7385
+ };
7386
+ emitReconcileStatus(reconciled, args.json);
7387
+ return;
7388
+ }
7389
+ emitReconcileStatus(after, args.json);
7390
+ return;
7391
+ }
7078
7392
  const replicationUrl = rewriteDatabaseUrl(adminUrl, role, replicationPassword);
7079
7393
  const writeUrl = rewriteDatabaseUrl(adminUrl, writeRole, writePassword);
7080
- console.log(`
7394
+ if (!args.json) console.log(`
7081
7395
  ${import_picocolors12.default.green("\u2713")} Roles ${rotating ? "re-keyed" : "created"}.
7082
7396
  `);
7083
7397
  if (!walReady) {
@@ -7129,7 +7443,7 @@ ${ambient}` : ""}`,
7129
7443
  process.exit(1);
7130
7444
  }
7131
7445
  if (replicationProbe.items === null || writeProbe.items === null) {
7132
- console.log(import_picocolors12.default.dim(` Couldn't verify from here; Ablo will validate from its own network.
7446
+ if (!args.json) console.log(import_picocolors12.default.dim(` Couldn't verify from here; Ablo will validate from its own network.
7133
7447
  `));
7134
7448
  }
7135
7449
  const items = [...replicationProbe.items ?? [], ...writeProbe.items ?? []];
@@ -7153,7 +7467,6 @@ ${ambient}` : ""}`,
7153
7467
  `
7154
7468
  );
7155
7469
  }
7156
- const apiUrl3 = apiBaseUrl();
7157
7470
  const registered = await registerDirectDataSource({
7158
7471
  apiUrl: apiUrl3,
7159
7472
  apiKey,
@@ -7162,7 +7475,8 @@ ${ambient}` : ""}`,
7162
7475
  route: args.route,
7163
7476
  schema: args.schema,
7164
7477
  replicationSlot: footprint.slot,
7165
- publication
7478
+ publication,
7479
+ quiet: args.json
7166
7480
  });
7167
7481
  process.off("SIGINT", onRotateInterrupt);
7168
7482
  process.off("SIGTERM", onRotateInterrupt);
@@ -7172,6 +7486,22 @@ ${ambient}` : ""}`,
7172
7486
  ${import_picocolors12.default.red(outcome.notice.split("\n").join("\n "))}
7173
7487
  `);
7174
7488
  }
7489
+ if (args.json && registered) {
7490
+ const initial = absentConnectionStatus();
7491
+ emitReconcileStatus(
7492
+ {
7493
+ ...initial,
7494
+ code: "loading",
7495
+ steps: {
7496
+ database: "changed",
7497
+ registration: "changed",
7498
+ snapshot: "loading",
7499
+ readiness: "pending"
7500
+ }
7501
+ },
7502
+ true
7503
+ );
7504
+ }
7175
7505
  process.exit(outcome.exitCode);
7176
7506
  }
7177
7507
  var import_picocolors12, import_errors11, import_footprint2, ROTATE_STRANDED_CREDENTIALS_NOTICE;
@@ -7186,11 +7516,13 @@ var init_connectApply = __esm({
7186
7516
  import_footprint2 = require("@abloatai/transaction/footprint");
7187
7517
  init_connectSetup();
7188
7518
  init_connectOwnership();
7189
- init_connect();
7519
+ init_connect2();
7190
7520
  init_dbProvider();
7191
7521
  init_dbRole();
7192
7522
  init_config();
7193
7523
  init_readiness();
7524
+ init_remoteValidation();
7525
+ init_connect();
7194
7526
  init_push();
7195
7527
  init_controlPlane();
7196
7528
  init_theme();
@@ -7212,6 +7544,7 @@ function parseConnectArgs(argv) {
7212
7544
  let envFile;
7213
7545
  let yes = false;
7214
7546
  let showSql = false;
7547
+ let json = false;
7215
7548
  let scan = false;
7216
7549
  let locate = false;
7217
7550
  let manual = false;
@@ -7269,6 +7602,9 @@ function parseConnectArgs(argv) {
7269
7602
  case "--show-sql":
7270
7603
  showSql = true;
7271
7604
  break;
7605
+ case "--json":
7606
+ json = true;
7607
+ break;
7272
7608
  case "--manual":
7273
7609
  manual = true;
7274
7610
  break;
@@ -7316,6 +7652,7 @@ function parseConnectArgs(argv) {
7316
7652
  envFile,
7317
7653
  yes,
7318
7654
  showSql,
7655
+ json,
7319
7656
  scan,
7320
7657
  locate,
7321
7658
  tables,
@@ -7899,7 +8236,7 @@ ${ambient}` : ""}`,
7899
8236
  method: "POST",
7900
8237
  apiKey,
7901
8238
  body: { connectionString: url, schema: args.schema },
7902
- responseSchema: import_wire7.datasourceLocationResponseSchema
8239
+ responseSchema: import_wire8.datasourceLocationResponseSchema
7903
8240
  });
7904
8241
  if (answer.available === false && !answer.held) {
7905
8242
  console.log(
@@ -7942,13 +8279,7 @@ async function runResnapshot() {
7942
8279
  ${brand("ablo")} ${import_picocolors13.default.dim("connect resnapshot")} ${import_picocolors13.default.dim("reload existing rows")}
7943
8280
  `
7944
8281
  );
7945
- const result = await requestControlPlane({
7946
- path: "/v1/datasources/resnapshot",
7947
- method: "POST",
7948
- apiKey,
7949
- body: {},
7950
- responseSchema: import_wire7.datasourceResnapshotResponseSchema
7951
- });
8282
+ const result = await requestInitialSnapshot({ apiKey });
7952
8283
  if (result.replication_slot?.released === false) {
7953
8284
  console.log(` ${import_picocolors13.default.yellow("\u2014")} Snapshot reset recorded, but the old slot is still active.`);
7954
8285
  if (result.replication_slot.detail) console.log(` ${import_picocolors13.default.dim(result.replication_slot.detail)}`);
@@ -8039,8 +8370,8 @@ async function connect(argv) {
8039
8370
  })
8040
8371
  );
8041
8372
  }
8042
- var import_errors12, import_picocolors13, import_footprint3, import_wire7, FOOTPRINT_LOOKUP, CONNECT_USAGE;
8043
- var init_connect = __esm({
8373
+ var import_errors12, import_picocolors13, import_footprint3, import_wire8, FOOTPRINT_LOOKUP, CONNECT_USAGE;
8374
+ var init_connect2 = __esm({
8044
8375
  "src/connect.ts"() {
8045
8376
  "use strict";
8046
8377
  init_cjs_shims();
@@ -8052,7 +8383,8 @@ var init_connect = __esm({
8052
8383
  init_dbRole();
8053
8384
  init_config();
8054
8385
  init_controlPlane();
8055
- import_wire7 = require("@abloatai/transaction/wire");
8386
+ import_wire8 = require("@abloatai/transaction/wire");
8387
+ init_connect();
8056
8388
  init_theme();
8057
8389
  init_target();
8058
8390
  init_remoteValidation();
@@ -8098,6 +8430,7 @@ var init_connect = __esm({
8098
8430
  --manual Print the setup SQL instead of running it
8099
8431
  --yes Set up without the confirmation (non-interactive)
8100
8432
  --show-sql Show the exact statements before running them
8433
+ --json Emit stable result and step codes for automation
8101
8434
 
8102
8435
  apply registers with Ablo directly; the admin credential is used only on this
8103
8436
  machine and never persisted. Your app holds only ABLO_API_KEY.`;
@@ -284318,11 +284651,11 @@ async function migrate(argv) {
284318
284651
  }
284319
284652
 
284320
284653
  // src/index.ts
284321
- init_connect();
284654
+ init_connect2();
284322
284655
 
284323
284656
  // src/commands.ts
284324
284657
  init_cjs_shims();
284325
- init_connect();
284658
+ init_connect2();
284326
284659
 
284327
284660
  // src/docs.ts
284328
284661
  init_cjs_shims();
@@ -285039,7 +285372,7 @@ var import_path6 = require("path");
285039
285372
  var import_schema7 = require("@abloatai/transaction/schema");
285040
285373
  init_push();
285041
285374
  init_controlPlane();
285042
- var import_wire8 = require("@abloatai/transaction/wire");
285375
+ var import_wire9 = require("@abloatai/transaction/wire");
285043
285376
  init_config();
285044
285377
  init_readiness();
285045
285378
  init_theme();
@@ -285214,7 +285547,7 @@ async function registerLocalSource(args) {
285214
285547
  reverseChannel: true,
285215
285548
  metadata: { managed_by: "ablo dev --local" }
285216
285549
  },
285217
- responseSchema: import_wire8.datasourceSummarySchema
285550
+ responseSchema: import_wire9.datasourceSummarySchema
285218
285551
  });
285219
285552
  }
285220
285553
  function classifyKey(apiKey) {
@@ -288322,7 +288655,7 @@ var import_child_process2 = require("child_process");
288322
288655
  var import_picocolors23 = __toESM(require_picocolors(), 1);
288323
288656
  init_dist2();
288324
288657
  var import_errors21 = require("@abloatai/transaction/errors");
288325
- var import_wire9 = require("@abloatai/transaction/wire");
288658
+ var import_wire10 = require("@abloatai/transaction/wire");
288326
288659
  init_config();
288327
288660
  init_projects();
288328
288661
  init_theme();
@@ -288517,7 +288850,7 @@ ${import_picocolors23.default.dim(url)}`, "Approve in your browser");
288517
288850
  }
288518
288851
  process.exit(1);
288519
288852
  }
288520
- const parsedProv = import_wire9.provisionKeyResponseSchema.safeParse(
288853
+ const parsedProv = import_wire10.provisionKeyResponseSchema.safeParse(
288521
288854
  await provRes.json().catch(() => null)
288522
288855
  );
288523
288856
  if (!parsedProv.success) {
@@ -288867,7 +289200,7 @@ async function status(args = []) {
288867
289200
  // src/logs.ts
288868
289201
  init_cjs_shims();
288869
289202
  var import_errors22 = require("@abloatai/transaction/errors");
288870
- var import_wire10 = require("@abloatai/transaction/wire");
289203
+ var import_wire11 = require("@abloatai/transaction/wire");
288871
289204
  var import_picocolors25 = __toESM(require_picocolors(), 1);
288872
289205
  init_config();
288873
289206
  init_theme();
@@ -288978,7 +289311,7 @@ async function logs(argv) {
288978
289311
  const json = await res.json();
288979
289312
  return {
288980
289313
  events: json.data ?? json.events ?? [],
288981
- cursor: json.next_cursor ?? (json.cursor != null ? (0, import_wire10.formatFeedCursor)({ log: json.cursor, claims: 0 }) : (0, import_wire10.formatFeedCursor)(import_wire10.FEED_CURSOR_START))
289314
+ cursor: json.next_cursor ?? (json.cursor != null ? (0, import_wire11.formatFeedCursor)({ log: json.cursor, claims: 0 }) : (0, import_wire11.formatFeedCursor)(import_wire11.FEED_CURSOR_START))
288982
289315
  };
288983
289316
  }
288984
289317
  if (!args.json) {
@@ -289010,9 +289343,9 @@ async function logs(argv) {
289010
289343
  });
289011
289344
  if (!page) continue;
289012
289345
  for (const e2 of page.events) render2(e2, args.json);
289013
- const prev = (0, import_wire10.parseFeedCursor)(cursor);
289014
- const next = (0, import_wire10.parseFeedCursor)(page.cursor);
289015
- if (prev && next && (0, import_wire10.feedCursorAdvanced)(prev, next)) cursor = page.cursor;
289346
+ const prev = (0, import_wire11.parseFeedCursor)(cursor);
289347
+ const next = (0, import_wire11.parseFeedCursor)(page.cursor);
289348
+ if (prev && next && (0, import_wire11.feedCursorAdvanced)(prev, next)) cursor = page.cursor;
289016
289349
  }
289017
289350
  }
289018
289351
 
@@ -289205,10 +289538,14 @@ var import_contract = require("@abloatai/transaction/claims/contract");
289205
289538
  var import_routes = require("@abloatai/transaction/claims/routes");
289206
289539
  init_controlPlane();
289207
289540
  init_config();
289541
+ function writeStderr(message2) {
289542
+ process.stderr.write(`${message2}
289543
+ `);
289544
+ }
289208
289545
  function requireRuntimeKey() {
289209
289546
  const { key } = resolveRuntimeApiKey();
289210
289547
  if (!key) {
289211
- console.error(
289548
+ writeStderr(
289212
289549
  import_picocolors27.default.red(" No runtime credential.") + import_picocolors27.default.dim(
289213
289550
  ` Run ${import_picocolors27.default.bold("npx ablo login")}, or set ${import_picocolors27.default.bold("ABLO_API_KEY")} to a runtime key.`
289214
289551
  )
@@ -289308,10 +289645,10 @@ function describeAcquired(model, id, granted) {
289308
289645
  async function holdWhile(model, id, flags, apiKey, command) {
289309
289646
  const granted = await acquireClaim(model, id, flags, apiKey);
289310
289647
  if (granted.status === "queued") {
289311
- if (!flags.json) console.error(describeAcquired(model, id, granted));
289648
+ if (!flags.json) writeStderr(describeAcquired(model, id, granted));
289312
289649
  await waitForGrant(granted.id, flags.ttl, apiKey);
289313
289650
  }
289314
- if (!flags.json) console.error(` ${import_picocolors27.default.green("\u2713")} Holding ${import_picocolors27.default.bold(`${model} ${id}`)}.`);
289651
+ if (!flags.json) writeStderr(` ${import_picocolors27.default.green("\u2713")} Holding ${import_picocolors27.default.bold(`${model} ${id}`)}.`);
289315
289652
  const everyMs = Math.max(1e3, Math.floor((0, import_contract.claimTtlMs)(flags.ttl) / 3));
289316
289653
  const timer2 = setInterval(() => {
289317
289654
  void beat(model, id, flags.ttl, apiKey).catch(() => {
@@ -289351,7 +289688,7 @@ async function holdWhile(model, id, flags, apiKey, command) {
289351
289688
  });
289352
289689
  });
289353
289690
  await giveBack();
289354
- if (!flags.json) console.error(` ${import_picocolors27.default.dim("\xB7")} ${import_picocolors27.default.dim(`Released ${model} ${id}.`)}`);
289691
+ if (!flags.json) writeStderr(` ${import_picocolors27.default.dim("\xB7")} ${import_picocolors27.default.dim(`Released ${model} ${id}.`)}`);
289355
289692
  return code;
289356
289693
  }
289357
289694
  function renderList(rows) {
@@ -289400,7 +289737,7 @@ async function claims(argv = []) {
289400
289737
  }
289401
289738
  const [model, id] = rest;
289402
289739
  if (model === void 0 || id === void 0) {
289403
- console.error(` ${import_picocolors27.default.red("\u2717")} Name the row: ${import_picocolors27.default.bold(`ablo claims ${verb} <model> <id>`)}.`);
289740
+ writeStderr(` ${import_picocolors27.default.red("\u2717")} Name the row: ${import_picocolors27.default.bold(`ablo claims ${verb} <model> <id>`)}.`);
289404
289741
  process.exitCode = 1;
289405
289742
  return;
289406
289743
  }
@@ -289427,8 +289764,9 @@ async function claims(argv = []) {
289427
289764
  else console.log(` ${import_picocolors27.default.green("\u2713")} Still holding ${import_picocolors27.default.bold(`${model} ${id}`)}.`);
289428
289765
  return;
289429
289766
  }
289430
- console.error(` ${import_picocolors27.default.red("\u2717")} Unknown: ${import_picocolors27.default.bold(`ablo claims ${verb}`)}.`);
289431
- console.error(usageFor("claims"));
289767
+ writeStderr(` ${import_picocolors27.default.red("\u2717")} Unknown: ${import_picocolors27.default.bold(`ablo claims ${verb}`)}.`);
289768
+ const usage = usageFor("claims");
289769
+ if (usage) writeStderr(usage);
289432
289770
  process.exitCode = 1;
289433
289771
  }
289434
289772
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abloatai/cli",
3
- "version": "0.60.0",
3
+ "version": "0.61.0",
4
4
  "description": "The ablo command line: set up Ablo, connect your database, and push your schema from the terminal.",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://docs.abloatai.com/cli",
@@ -41,10 +41,10 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@sentry/node": "^10.18.0",
44
- "@abloatai/transaction": "^0.60.0",
44
+ "@abloatai/transaction": "^0.61.0",
45
45
  "jiti": "^2.7.0",
46
46
  "zod": "^4.4.3",
47
- "@abloatai/humans": "^0.60.0"
47
+ "@abloatai/humans": "^0.61.0"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@ablo/product-analytics": "file:../product-analytics",