@abloatai/ablo 0.27.0 → 0.29.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 (126) hide show
  1. package/CHANGELOG.md +59 -3
  2. package/README.md +1 -1
  3. package/dist/BaseSyncedStore.js +8 -9
  4. package/dist/Database.d.ts +14 -1
  5. package/dist/Database.js +228 -28
  6. package/dist/Model.d.ts +17 -0
  7. package/dist/Model.js +32 -1
  8. package/dist/SyncClient.d.ts +10 -6
  9. package/dist/SyncClient.js +390 -75
  10. package/dist/adapters/inMemoryStorage.d.ts +1 -0
  11. package/dist/adapters/inMemoryStorage.js +29 -13
  12. package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
  13. package/dist/auth/schemas.d.ts +6 -0
  14. package/dist/auth/schemas.js +7 -0
  15. package/dist/cli.cjs +821 -402
  16. package/dist/client/Ablo.d.ts +53 -24
  17. package/dist/client/Ablo.js +12 -199
  18. package/dist/client/claimHeartbeatLoop.d.ts +1 -1
  19. package/dist/client/claimHeartbeatLoop.js +1 -1
  20. package/dist/client/createInternalComponents.d.ts +4 -0
  21. package/dist/client/createInternalComponents.js +3 -1
  22. package/dist/client/durableWrites.d.ts +21 -0
  23. package/dist/client/durableWrites.js +46 -0
  24. package/dist/client/httpClient.d.ts +21 -45
  25. package/dist/client/httpClient.js +104 -26
  26. package/dist/client/httpTransport.d.ts +8 -0
  27. package/dist/client/{ApiClient.js → httpTransport.js} +361 -308
  28. package/dist/client/modelRegistration.js +11 -0
  29. package/dist/client/options.d.ts +54 -7
  30. package/dist/client/options.js +3 -2
  31. package/dist/client/resourceTypes.d.ts +9 -85
  32. package/dist/client/resourceTypes.js +1 -1
  33. package/dist/client/sessionMint.d.ts +5 -6
  34. package/dist/client/sessionMint.js +4 -5
  35. package/dist/client/validateAbloOptions.js +3 -3
  36. package/dist/client/wsMutationExecutor.d.ts +3 -2
  37. package/dist/client/wsMutationExecutor.js +3 -2
  38. package/dist/coordination/schema.d.ts +30 -0
  39. package/dist/coordination/schema.js +14 -0
  40. package/dist/core/StoreManager.d.ts +2 -0
  41. package/dist/core/StoreManager.js +12 -0
  42. package/dist/core/index.d.ts +1 -1
  43. package/dist/errorCodes.js +6 -2
  44. package/dist/errors.d.ts +4 -40
  45. package/dist/errors.js +5 -5
  46. package/dist/index.d.ts +20 -8
  47. package/dist/index.js +9 -5
  48. package/dist/interfaces/index.d.ts +5 -2
  49. package/dist/mutators/UndoManager.d.ts +2 -0
  50. package/dist/mutators/UndoManager.js +32 -0
  51. package/dist/mutators/defineMutators.d.ts +18 -0
  52. package/dist/mutators/defineMutators.js +4 -8
  53. package/dist/react/index.d.ts +1 -1
  54. package/dist/react/useAblo.d.ts +6 -4
  55. package/dist/react/useAblo.js +25 -3
  56. package/dist/schema/index.d.ts +2 -2
  57. package/dist/schema/index.js +1 -1
  58. package/dist/schema/schema.d.ts +31 -1
  59. package/dist/schema/select.d.ts +15 -0
  60. package/dist/schema/select.js +27 -3
  61. package/dist/source/connector.d.ts +3 -3
  62. package/dist/source/factory.d.ts +4 -6
  63. package/dist/source/factory.js +4 -7
  64. package/dist/source/index.d.ts +4 -4
  65. package/dist/source/index.js +2 -2
  66. package/dist/source/signing.d.ts +0 -3
  67. package/dist/source/types.d.ts +0 -26
  68. package/dist/stores/ObjectStore.d.ts +14 -1
  69. package/dist/stores/ObjectStore.js +33 -10
  70. package/dist/stores/ObjectStoreContract.d.ts +2 -0
  71. package/dist/surface.d.ts +1 -1
  72. package/dist/surface.js +3 -0
  73. package/dist/sync/BootstrapFetcher.d.ts +10 -0
  74. package/dist/sync/BootstrapFetcher.js +27 -4
  75. package/dist/sync/SyncWebSocket.d.ts +2 -1
  76. package/dist/sync/createClaimStream.d.ts +11 -2
  77. package/dist/sync/createClaimStream.js +4 -3
  78. package/dist/sync/persistedPrefix.d.ts +12 -0
  79. package/dist/sync/persistedPrefix.js +22 -0
  80. package/dist/testing/index.d.ts +2 -0
  81. package/dist/testing/index.js +1 -0
  82. package/dist/testing/mocks/FakeDatabase.d.ts +18 -0
  83. package/dist/testing/mocks/FakeDatabase.js +10 -0
  84. package/dist/testing/mocks/MockWebSocket.d.ts +2 -1
  85. package/dist/transactions/TransactionQueue.d.ts +66 -8
  86. package/dist/transactions/TransactionQueue.js +607 -89
  87. package/dist/transactions/commitEnvelope.d.ts +132 -0
  88. package/dist/transactions/commitEnvelope.js +139 -0
  89. package/dist/transactions/commitOutboxStore.d.ts +32 -0
  90. package/dist/transactions/commitOutboxStore.js +26 -0
  91. package/dist/transactions/commitPayload.d.ts +15 -0
  92. package/dist/transactions/commitPayload.js +6 -0
  93. package/dist/transactions/durableWriteStore.d.ts +123 -0
  94. package/dist/transactions/durableWriteStore.js +30 -0
  95. package/dist/transactions/httpCommitEnvelope.d.ts +44 -0
  96. package/dist/transactions/httpCommitEnvelope.js +181 -0
  97. package/dist/transactions/idempotencyKey.d.ts +10 -0
  98. package/dist/transactions/idempotencyKey.js +9 -0
  99. package/dist/transactions/replayValidation.d.ts +83 -0
  100. package/dist/transactions/replayValidation.js +46 -1
  101. package/dist/types/global.d.ts +10 -29
  102. package/dist/types/global.js +4 -7
  103. package/dist/types/streams.d.ts +2 -28
  104. package/dist/wire/bootstrapReason.d.ts +9 -0
  105. package/dist/wire/bootstrapReason.js +8 -0
  106. package/dist/wire/frames.d.ts +6 -21
  107. package/dist/wire/frames.js +4 -4
  108. package/dist/wire/index.d.ts +6 -3
  109. package/dist/wire/index.js +3 -2
  110. package/dist/wire/protocolVersion.d.ts +30 -17
  111. package/dist/wire/protocolVersion.js +34 -18
  112. package/docs/agents.md +8 -3
  113. package/docs/api.md +16 -14
  114. package/docs/client-behavior.md +6 -3
  115. package/docs/coordination.md +4 -5
  116. package/docs/data-sources.md +5 -8
  117. package/docs/guarantees.md +21 -0
  118. package/docs/integration-guide.md +1 -0
  119. package/docs/mcp.md +1 -1
  120. package/docs/migration.md +21 -1
  121. package/docs/quickstart.md +11 -0
  122. package/docs/react.md +0 -46
  123. package/examples/README.md +6 -5
  124. package/llms.txt +15 -15
  125. package/package.json +3 -3
  126. package/dist/client/ApiClient.d.ts +0 -177
package/dist/cli.cjs CHANGED
@@ -212184,11 +212184,11 @@ var require_commonjs2 = __commonJS({
212184
212184
  if (pad) {
212185
212185
  const need = width - c.length;
212186
212186
  if (need > 0) {
212187
- const z6 = new Array(need + 1).join("0");
212187
+ const z7 = new Array(need + 1).join("0");
212188
212188
  if (i < 0) {
212189
- c = "-" + z6 + c.slice(1);
212189
+ c = "-" + z7 + c.slice(1);
212190
212190
  } else {
212191
- c = z6 + c;
212191
+ c = z7 + c;
212192
212192
  }
212193
212193
  }
212194
212194
  }
@@ -276724,8 +276724,8 @@ var Y2 = ({ indicator: t = "dots" } = {}) => {
276724
276724
  if (i) process.stdout.write(`${I2} ${l2}...`);
276725
276725
  else if (t === "timer") process.stdout.write(`${I2} ${l2} ${O2(g2)}`);
276726
276726
  else {
276727
- const z6 = ".".repeat(Math.floor(w2)).slice(0, 3);
276728
- process.stdout.write(`${I2} ${l2}${z6}`);
276727
+ const z7 = ".".repeat(Math.floor(w2)).slice(0, 3);
276728
+ process.stdout.write(`${I2} ${l2}${z7}`);
276729
276729
  }
276730
276730
  h2 = h2 + 1 < n.length ? h2 + 1 : 0, w2 = w2 < n.length ? w2 + 0.125 : 0;
276731
276731
  }, r2);
@@ -276796,7 +276796,7 @@ var ERROR_CODES = {
276796
276796
  jwt_invalid: wire("auth", 401, false, "The bearer JWT failed validation for a reason the server could not classify further. Check the token's issuer, signature, audience, and expiry."),
276797
276797
  jwt_malformed: wire("auth", 401, false, "The bearer token is not a well-formed JWT and could not be decoded. Check that the full, unmodified token was sent."),
276798
276798
  jwt_missing_issuer: wire("auth", 401, false, "The bearer JWT has no `iss` (issuer) claim, so it cannot be routed to a trusted issuer."),
276799
- jwt_issuer_untrusted: wire("auth", 401, false, "The bearer JWT's `iss` is not a registered trusted issuer. Register it via POST /v1/trusted-issuers, or check the token's issuer claim."),
276799
+ jwt_issuer_untrusted: wire("auth", 401, false, "The bearer JWT's `iss` is not a registered trusted issuer. Check the token's issuer claim, or register the issuer with your deployment before retrying."),
276800
276800
  jwt_signature_invalid: wire("auth", 401, false, "The bearer JWT's signature could not be verified against the issuer's JWKS (wrong key, rotated key, or forged token)."),
276801
276801
  jwt_audience_mismatch: wire("auth", 401, false, "The bearer JWT's `aud` (audience) claim does not match the audience this issuer is registered with."),
276802
276802
  jwt_missing_subject: wire("auth", 401, false, "The bearer JWT has no `sub` (subject) claim to identify the user."),
@@ -276845,7 +276845,11 @@ var ERROR_CODES = {
276845
276845
  model_claim_not_configured: client("claim", "Claiming requires the collaboration runtime, which the standard Ablo({ schema, apiKey }) client wires up for every model automatically \u2014 there is no per-model claim configuration to add. This appears only when a model proxy is constructed directly without that runtime (an internal/advanced path)."),
276846
276846
  model_watch_not_configured: client("claim", "watch() opens a presence/claim subscription and needs a live WebSocket, so it is unavailable on the HTTP transport and on model proxies built without a socket. Use the standard Ablo({ schema, apiKey }) client (default WebSocket transport)."),
276847
276847
  // ── stale context / idempotency (409) ──────────────────────────────
276848
- stale_context: wire("conflict", 409, true, "The row changed after you read it \u2014 the write's `readAt` watermark is older than the current row version. Re-read the row and retry."),
276848
+ // Not retryable at the transport: the rejected request carries its frozen
276849
+ // `readAt`, so resending the identical payload can never succeed. Recovery
276850
+ // is a caller-level re-read that produces a NEW request with a fresh
276851
+ // watermark — the same shape as `claim_conflict`.
276852
+ stale_context: wire("conflict", 409, false, "The row changed after you read it \u2014 the write's `readAt` watermark is older than the current row version. Re-read the row and retry."),
276849
276853
  // Raised by the functional `update(id, current => next)` form once its
276850
276854
  // internal reconcile budget is exhausted, because the row stayed continuously
276851
276855
  // contended. The SDK has already retried; the caller decides whether to back
@@ -277258,6 +277262,11 @@ var claimRejectionSchema = import_zod3.z.object({
277258
277262
  heldByClaim: wireClaimSummarySchema.optional(),
277259
277263
  policyReason: import_zod3.z.string().optional()
277260
277264
  });
277265
+ var claimLostSchema = import_zod3.z.object({
277266
+ claimId: import_zod3.z.string(),
277267
+ reason: import_zod3.z.enum(["expired", "preempted"]),
277268
+ target: targetRefSchema
277269
+ });
277261
277270
  var modelTargetSchema = import_zod3.z.object({
277262
277271
  model: import_zod3.z.string(),
277263
277272
  id: import_zod3.z.string(),
@@ -277472,9 +277481,61 @@ var AbloError = class extends Error {
277472
277481
  function docUrlForCode(code) {
277473
277482
  return `https://docs.abloatai.com/errors#${code}`;
277474
277483
  }
277484
+ var AbloAuthenticationError = class extends AbloError {
277485
+ type = "AbloAuthenticationError";
277486
+ };
277487
+ var AbloPermissionError = class extends AbloError {
277488
+ type = "AbloPermissionError";
277489
+ };
277490
+ var AbloRateLimitError = class extends AbloError {
277491
+ type = "AbloRateLimitError";
277492
+ retryAfterSeconds;
277493
+ constructor(message, options) {
277494
+ super(message, options);
277495
+ if (options?.retryAfterSeconds !== void 0) {
277496
+ this.retryAfterSeconds = options.retryAfterSeconds;
277497
+ }
277498
+ }
277499
+ };
277500
+ var AbloIdempotencyError = class extends AbloError {
277501
+ type = "AbloIdempotencyError";
277502
+ };
277475
277503
  var AbloValidationError = class extends AbloError {
277476
277504
  type = "AbloValidationError";
277477
277505
  };
277506
+ var AbloServerError = class extends AbloError {
277507
+ type = "AbloServerError";
277508
+ };
277509
+ var AbloStaleContextError = class extends AbloError {
277510
+ type = "AbloStaleContextError";
277511
+ /** Sync id at the caller's `readAt` when the write was attempted. */
277512
+ readAt;
277513
+ /** Entities that received deltas between `readAt` and the write. */
277514
+ conflicts;
277515
+ constructor(message, options) {
277516
+ super(message, options);
277517
+ if (options?.readAt !== void 0) this.readAt = options.readAt;
277518
+ if (options?.conflicts !== void 0) this.conflicts = options.conflicts;
277519
+ }
277520
+ };
277521
+ var AbloClaimedError = class extends AbloError {
277522
+ type = "AbloClaimedError";
277523
+ claims;
277524
+ constructor(message, options) {
277525
+ super(message, options);
277526
+ if (options?.claims !== void 0) this.claims = options.claims;
277527
+ }
277528
+ };
277529
+ var CapabilityError = class extends AbloPermissionError {
277530
+ requiredCapability;
277531
+ constructor(code, message, requiredCapability) {
277532
+ super(`${code}: ${message}`, { code });
277533
+ this.name = "CapabilityError";
277534
+ if (requiredCapability !== void 0) {
277535
+ this.requiredCapability = requiredCapability;
277536
+ }
277537
+ }
277538
+ };
277478
277539
  var OptionalWireStringSchema = import_zod4.z.preprocess(
277479
277540
  (value) => typeof value === "string" ? value : void 0,
277480
277541
  import_zod4.z.string().optional()
@@ -277502,7 +277563,7 @@ var ErrorFieldSchema = import_zod4.z.preprocess(
277502
277563
  ).catch(void 0);
277503
277564
  var ErrorBodyShapeSchema = import_zod4.z.object({
277504
277565
  /** The `error` field may be a flat code string, as some endpoints return,
277505
- * or a nested error object, as a {@link CommitReceipt} carries. */
277566
+ * or a nested error object, as commit endpoints return on rejection. */
277506
277567
  error: ErrorFieldSchema,
277507
277568
  code: OptionalWireStringSchema,
277508
277569
  reason: OptionalWireStringSchema,
@@ -277513,9 +277574,58 @@ var ErrorBodyShapeSchema = import_zod4.z.object({
277513
277574
  heldByClaim: wireClaimSummarySchema.optional().catch(void 0),
277514
277575
  claims: import_zod4.z.array(wireClaimSummarySchema).optional().catch(void 0)
277515
277576
  }).passthrough();
277577
+ function parseErrorBodyShape(body) {
277578
+ if (typeof body !== "object" || body === null) return {};
277579
+ const parsed = ErrorBodyShapeSchema.safeParse(body);
277580
+ return parsed.success ? parsed.data : {};
277581
+ }
277582
+ function errorFromWire(message, opts = {}) {
277583
+ const { code, requestId, requiredCapability, claims } = opts;
277584
+ const httpStatus = opts.httpStatus ?? (code ? errorCodeSpec(code)?.httpStatus : void 0);
277585
+ const publicCode = code === "claim_conflict" ? "claim_conflict" : code;
277586
+ const baseOpts = { code: publicCode, httpStatus, requestId };
277587
+ if (code === "capability_scope_denied" || code === "capability_invalid") {
277588
+ return new CapabilityError(code, message, requiredCapability);
277589
+ }
277590
+ if (code === "claim_conflict" || code === "entity_claimed" || code === "claim_lost") {
277591
+ return new AbloClaimedError(message, { ...baseOpts, claims });
277592
+ }
277593
+ if (code === "stale_context") {
277594
+ return new AbloStaleContextError(message, baseOpts);
277595
+ }
277596
+ if (httpStatus === 401) return new AbloAuthenticationError(message, baseOpts);
277597
+ if (httpStatus === 403) return new AbloPermissionError(message, baseOpts);
277598
+ if (httpStatus === 409) return new AbloIdempotencyError(message, baseOpts);
277599
+ if (httpStatus === 422 || httpStatus === 400) return new AbloValidationError(message, baseOpts);
277600
+ if (httpStatus === 429) return new AbloRateLimitError(message, baseOpts);
277601
+ if (httpStatus !== void 0 && httpStatus >= 500) return new AbloServerError(message, baseOpts);
277602
+ return new AbloError(message, baseOpts);
277603
+ }
277604
+ function translateHttpError(status2, body, requestId) {
277605
+ const parsed = parseErrorBodyShape(body);
277606
+ const nested = parsed.error != null && typeof parsed.error === "object" ? parsed.error : void 0;
277607
+ const flatError = typeof parsed.error === "string" ? parsed.error : void 0;
277608
+ const code = parsed.code ?? nested?.code ?? flatError;
277609
+ const message = nested?.message ?? parsed.reason ?? parsed.message ?? flatError ?? (typeof body === "string" ? body : `HTTP ${status2}`);
277610
+ const requiredCapability = nested?.requiredCapability ?? parsed.requiredCapability;
277611
+ const claims = parsed.claims ?? nested?.claims ?? (parsed.heldByClaim ? [parsed.heldByClaim] : nested?.heldByClaim ? [nested.heldByClaim] : void 0);
277612
+ return errorFromWire(message, {
277613
+ code,
277614
+ httpStatus: status2,
277615
+ requestId,
277616
+ requiredCapability,
277617
+ claims
277618
+ });
277619
+ }
277620
+ function hasWireCode(body) {
277621
+ const parsed = parseErrorBodyShape(body);
277622
+ if (typeof parsed.code === "string") return true;
277623
+ if (typeof parsed.error === "string") return true;
277624
+ return typeof parsed.error === "object" && parsed.error !== null && typeof parsed.error.code === "string";
277625
+ }
277516
277626
 
277517
277627
  // src/cli/migrate.ts
277518
- var import_picocolors4 = __toESM(require_picocolors(), 1);
277628
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
277519
277629
  var import_fs5 = require("fs");
277520
277630
 
277521
277631
  // node_modules/postgres/src/index.js
@@ -279684,7 +279794,7 @@ var import_source = require("@abloatai/ablo/source");
279684
279794
 
279685
279795
  // src/cli/push.ts
279686
279796
  init_cjs_shims();
279687
- var import_picocolors3 = __toESM(require_picocolors(), 1);
279797
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
279688
279798
 
279689
279799
  // src/auth/credentialPolicy.ts
279690
279800
  init_cjs_shims();
@@ -279958,6 +280068,174 @@ function resolvePushPlan() {
279958
280068
  return { flow: getMode(), apiKey: key, source };
279959
280069
  }
279960
280070
 
280071
+ // src/cli/target.ts
280072
+ init_cjs_shims();
280073
+
280074
+ // src/auth/index.ts
280075
+ init_cjs_shims();
280076
+
280077
+ // src/auth/schemas.ts
280078
+ init_cjs_shims();
280079
+ var import_zod5 = require("zod");
280080
+ var AuthParticipantKindSchema = import_zod5.z.enum(["user", "agent", "system"]);
280081
+ var AuthTokenSchema = import_zod5.z.string().trim().min(1);
280082
+ var CapabilityExchangeResponseSchema = import_zod5.z.object({
280083
+ capabilityId: import_zod5.z.string().min(1),
280084
+ token: AuthTokenSchema,
280085
+ expiresAt: import_zod5.z.string().min(1),
280086
+ organizationId: import_zod5.z.string().min(1),
280087
+ scope: import_zod5.z.object({
280088
+ organizationId: import_zod5.z.string().min(1),
280089
+ syncGroups: import_zod5.z.array(import_zod5.z.string()),
280090
+ operations: import_zod5.z.array(import_zod5.z.string()),
280091
+ participantKind: AuthParticipantKindSchema,
280092
+ participantId: import_zod5.z.string().min(1)
280093
+ }).passthrough(),
280094
+ userMeta: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown())
280095
+ }).passthrough();
280096
+ var IdentityResolveResponseSchema = import_zod5.z.object({
280097
+ participantKind: AuthParticipantKindSchema,
280098
+ participantId: import_zod5.z.string().min(1),
280099
+ accountScope: import_zod5.z.string().min(1),
280100
+ // The rest of the plane this credential resolves to. `nullish` (optional +
280101
+ // nullable) so a server too old to send them still parses, and a human
280102
+ // session — which carries no such scope — validates with them absent.
280103
+ // `projectId` equals the org id for the org-default project.
280104
+ projectId: import_zod5.z.string().min(1).nullish(),
280105
+ environment: import_zod5.z.enum(["sandbox", "production"]).nullish(),
280106
+ sandboxId: import_zod5.z.string().min(1).nullish(),
280107
+ syncGroups: import_zod5.z.array(import_zod5.z.string()),
280108
+ userMeta: import_zod5.z.record(import_zod5.z.string(), import_zod5.z.unknown())
280109
+ }).passthrough();
280110
+ var EphemeralKeyResponseSchema = import_zod5.z.object({
280111
+ object: import_zod5.z.literal("ephemeral_key").optional(),
280112
+ id: import_zod5.z.string().min(1),
280113
+ token: AuthTokenSchema,
280114
+ expiresAt: import_zod5.z.string().min(1),
280115
+ organizationId: import_zod5.z.string().min(1),
280116
+ participantId: import_zod5.z.string().min(1),
280117
+ syncGroups: import_zod5.z.array(import_zod5.z.string())
280118
+ }).passthrough();
280119
+ function formatIssues(error) {
280120
+ return error.issues.map((issue) => {
280121
+ const path = issue.path.length > 0 ? issue.path.join(".") : "<root>";
280122
+ return `${path}: ${issue.message}`;
280123
+ }).join("; ");
280124
+ }
280125
+ function parseIdentityResolveResponse(raw) {
280126
+ const parsed = IdentityResolveResponseSchema.safeParse(raw);
280127
+ if (!parsed.success) {
280128
+ throw new AbloAuthenticationError(
280129
+ `identity resolve response was malformed: ${formatIssues(parsed.error)}`,
280130
+ { code: "identity_resolve_failed", cause: parsed.error }
280131
+ );
280132
+ }
280133
+ return parsed.data;
280134
+ }
280135
+
280136
+ // src/auth/index.ts
280137
+ async function resolveIdentity(options) {
280138
+ if (!options.baseUrl) {
280139
+ throw new AbloAuthenticationError("baseUrl is required for identity resolve", {
280140
+ code: "base_url_missing"
280141
+ });
280142
+ }
280143
+ const fetcher = options.fetch ?? fetch;
280144
+ const url = `${options.baseUrl.replace(/\/+$/, "")}/auth/identity`;
280145
+ const timeoutMs = options.timeoutMs ?? 1e4;
280146
+ const controller = new AbortController();
280147
+ const timer2 = setTimeout(() => {
280148
+ controller.abort();
280149
+ }, timeoutMs);
280150
+ let response;
280151
+ try {
280152
+ const headers = { Accept: "application/json" };
280153
+ if (options.authToken) {
280154
+ headers.Authorization = `Bearer ${options.authToken}`;
280155
+ }
280156
+ response = await fetcher(url, {
280157
+ method: "GET",
280158
+ headers,
280159
+ signal: controller.signal
280160
+ });
280161
+ } catch (err) {
280162
+ throw new AbloAuthenticationError(
280163
+ `identity resolve failed: ${err instanceof Error ? err.message : String(err)}`,
280164
+ { code: "identity_network_error", cause: err }
280165
+ );
280166
+ } finally {
280167
+ clearTimeout(timer2);
280168
+ }
280169
+ if (!response.ok) {
280170
+ let body = null;
280171
+ try {
280172
+ body = await response.json();
280173
+ } catch {
280174
+ }
280175
+ const requestId = response.headers.get("x-request-id") ?? void 0;
280176
+ throw hasWireCode(body) ? translateHttpError(response.status, body, requestId) : new AbloAuthenticationError(
280177
+ `identity resolve rejected (${response.status})`,
280178
+ { code: "identity_resolve_failed", httpStatus: response.status }
280179
+ );
280180
+ }
280181
+ return parseIdentityResolveResponse(await response.json());
280182
+ }
280183
+
280184
+ // src/client/auth.ts
280185
+ init_cjs_shims();
280186
+
280187
+ // src/client/credentialEndpoint.ts
280188
+ init_cjs_shims();
280189
+
280190
+ // src/client/auth.ts
280191
+ var LEGACY_HOSTED_API_HOSTS = /* @__PURE__ */ new Set([
280192
+ "mesh.ablo.finance",
280193
+ "mesh-staging.ablo.finance",
280194
+ "api.ablo.finance",
280195
+ "sync-staging.ablo.finance"
280196
+ ]);
280197
+ function normalizeAbloHostedBaseUrl(rawUrl) {
280198
+ const trimmed = rawUrl.trim();
280199
+ if (!trimmed) return trimmed;
280200
+ const schemed = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
280201
+ try {
280202
+ const url = new URL(schemed);
280203
+ if (url.protocol === "ws:") url.protocol = "http:";
280204
+ if (url.protocol === "wss:") url.protocol = "https:";
280205
+ if (!LEGACY_HOSTED_API_HOSTS.has(url.hostname)) {
280206
+ return url.toString().replace(/\/+$/, "");
280207
+ }
280208
+ url.hostname = ABLO_HOSTED_API_DOMAIN;
280209
+ if (url.protocol === "http:") url.protocol = "https:";
280210
+ return url.toString().replace(/\/+$/, "");
280211
+ } catch {
280212
+ return schemed;
280213
+ }
280214
+ }
280215
+ function resolveBootstrapBaseUrl(input) {
280216
+ if (input.bootstrapBaseUrl) {
280217
+ return ensureApiSuffix(normalizeAbloHostedBaseUrl(input.bootstrapBaseUrl).replace(/^ws/, "http"));
280218
+ }
280219
+ const url = normalizeAbloHostedBaseUrl(input.url);
280220
+ return ensureApiSuffix(url.replace(/^ws/, "http"));
280221
+ }
280222
+ function ensureApiSuffix(httpBase) {
280223
+ const trimmed = httpBase.replace(/\/+$/, "");
280224
+ try {
280225
+ const u2 = new URL(trimmed);
280226
+ const segments = u2.pathname.split("/").filter(Boolean);
280227
+ if (segments[segments.length - 1] === "api") return trimmed;
280228
+ u2.pathname = `${u2.pathname.replace(/\/+$/, "")}/api`;
280229
+ return u2.toString().replace(/\/+$/, "");
280230
+ } catch {
280231
+ return trimmed.endsWith("/api") ? trimmed : `${trimmed}/api`;
280232
+ }
280233
+ }
280234
+
280235
+ // src/cli/projects.ts
280236
+ init_cjs_shims();
280237
+ var import_picocolors3 = __toESM(require_picocolors(), 1);
280238
+
279961
280239
  // src/cli/theme.ts
279962
280240
  init_cjs_shims();
279963
280241
  var RESET = "\x1B[0m";
@@ -279971,6 +280249,288 @@ function brand(label = "ablo") {
279971
280249
  return `${PAPER_BG}${BLACK_FG} ${label} ${RESET}`;
279972
280250
  }
279973
280251
 
280252
+ // src/cli/projects.ts
280253
+ function apiUrl() {
280254
+ return (process.env.ABLO_API_URL ?? ABLO_DEFAULT_BASE_URL).replace(/\/+$/, "");
280255
+ }
280256
+ function requireKey() {
280257
+ const apiKey = resolveApiKey();
280258
+ if (!apiKey) {
280259
+ console.error(
280260
+ import_picocolors3.default.red(" No API key.") + import_picocolors3.default.dim(
280261
+ ` Run ${import_picocolors3.default.bold("npx ablo login")} \u2014 or set ${import_picocolors3.default.bold("ABLO_API_KEY")} (${import_picocolors3.default.bold("sk_test_")} = sandbox; ${import_picocolors3.default.bold("sk_live_")} = production).`
280262
+ )
280263
+ );
280264
+ process.exit(1);
280265
+ }
280266
+ return apiKey;
280267
+ }
280268
+ async function request(path, apiKey, init2 = {}, baseUrl2) {
280269
+ const res = await fetch(`${baseUrl2 ?? apiUrl()}${path}`, {
280270
+ method: init2.method ?? "GET",
280271
+ headers: {
280272
+ "content-type": "application/json",
280273
+ authorization: `Bearer ${apiKey}`
280274
+ },
280275
+ ...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
280276
+ });
280277
+ let body = {};
280278
+ try {
280279
+ body = await res.json();
280280
+ } catch {
280281
+ }
280282
+ return { status: res.status, body };
280283
+ }
280284
+ async function listProjects(apiKey, baseUrl2) {
280285
+ try {
280286
+ const { status: status2, body } = await request("/api/v1/projects", apiKey, {}, baseUrl2);
280287
+ if (status2 !== 200 || !Array.isArray(body.data)) return null;
280288
+ return body.data;
280289
+ } catch {
280290
+ return null;
280291
+ }
280292
+ }
280293
+ async function fetchProjects() {
280294
+ const all = await listProjects(requireKey());
280295
+ if (!all) {
280296
+ console.error(import_picocolors3.default.red(" Could not list projects \u2014 is the API reachable and the key valid?"));
280297
+ process.exit(1);
280298
+ }
280299
+ return all;
280300
+ }
280301
+ function projectSlugFromPackageName(name) {
280302
+ if (typeof name !== "string") return void 0;
280303
+ const slug = name.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
280304
+ if (!slug || slug === "default") return void 0;
280305
+ return slug;
280306
+ }
280307
+ async function ensureProject(slug, name) {
280308
+ const apiKey = resolveApiKey();
280309
+ if (!apiKey) return null;
280310
+ try {
280311
+ const { status: status2, body } = await request("/api/v1/projects", apiKey, {
280312
+ method: "POST",
280313
+ body: { slug, ...name ? { name } : {} }
280314
+ });
280315
+ if (status2 === 201) {
280316
+ const created = body;
280317
+ setActiveProject({ id: created.id, slug: created.slug });
280318
+ return { id: created.id, slug: created.slug, created: true };
280319
+ }
280320
+ if (body.code === "project_slug_taken") {
280321
+ const all = await listProjects(apiKey);
280322
+ const existing = all?.find((p2) => p2.slug === slug);
280323
+ if (!existing) return null;
280324
+ setActiveProject({ id: existing.id, slug: existing.slug });
280325
+ return { id: existing.id, slug: existing.slug, created: false };
280326
+ }
280327
+ return null;
280328
+ } catch {
280329
+ return null;
280330
+ }
280331
+ }
280332
+ function printList(projects2) {
280333
+ const active = getActiveProject();
280334
+ for (const p2 of projects2) {
280335
+ const isActive = active ? active.id === p2.id : p2.default;
280336
+ const marker = isActive ? import_picocolors3.default.green("\u25CF") : import_picocolors3.default.dim("\u25CB");
280337
+ const tags = [p2.default ? import_picocolors3.default.dim("default") : "", isActive ? import_picocolors3.default.green("active") : ""].filter(Boolean).join(import_picocolors3.default.dim(", "));
280338
+ console.log(
280339
+ ` ${marker} ${p2.slug.padEnd(20)} ${import_picocolors3.default.dim(p2.id)}${tags ? ` ${tags}` : ""}`
280340
+ );
280341
+ }
280342
+ }
280343
+ async function projects(argv) {
280344
+ const sub = argv[0];
280345
+ if (sub === "list" || sub === void 0) {
280346
+ console.log(`
280347
+ ${brand("ablo")} ${import_picocolors3.default.dim("projects")}
280348
+ `);
280349
+ printList(await fetchProjects());
280350
+ console.log();
280351
+ return;
280352
+ }
280353
+ if (sub === "create") {
280354
+ const slug = argv[1];
280355
+ if (!slug || slug.startsWith("-")) {
280356
+ console.error(import_picocolors3.default.red(' usage: ablo projects create <slug> [--name "Display Name"]'));
280357
+ process.exit(1);
280358
+ }
280359
+ const nameIdx = argv.indexOf("--name");
280360
+ const name = nameIdx >= 0 ? argv[nameIdx + 1] : void 0;
280361
+ const { status: status2, body } = await request("/api/v1/projects", requireKey(), {
280362
+ method: "POST",
280363
+ body: { slug, ...name ? { name } : {} }
280364
+ });
280365
+ if (status2 !== 201) {
280366
+ console.error(
280367
+ import_picocolors3.default.red(` Create failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
280368
+ );
280369
+ if (body.code === "project_slug_taken") {
280370
+ console.error(import_picocolors3.default.dim(` Pick another slug, or switch to it: ${import_picocolors3.default.bold(`ablo projects use ${slug}`)}`));
280371
+ }
280372
+ process.exit(1);
280373
+ }
280374
+ const created = body;
280375
+ console.log(` ${import_picocolors3.default.green("\u2713")} Created project ${import_picocolors3.default.bold(created.slug)} ${import_picocolors3.default.dim(`(${created.id})`)}`);
280376
+ console.log(
280377
+ import_picocolors3.default.dim(` Make it active with ${import_picocolors3.default.bold(`ablo projects use ${created.slug}`)}; mint its keys in the dashboard.`)
280378
+ );
280379
+ return;
280380
+ }
280381
+ if (sub === "rename") {
280382
+ const ref = argv[1];
280383
+ const name = argv.slice(2).join(" ").trim();
280384
+ if (!ref || ref.startsWith("-") || !name) {
280385
+ console.error(import_picocolors3.default.red(" usage: ablo projects rename <slug|id> <new name>"));
280386
+ process.exit(1);
280387
+ }
280388
+ const all = await fetchProjects();
280389
+ const target = all.find((p2) => p2.slug === ref || p2.id === ref);
280390
+ if (!target) {
280391
+ console.error(import_picocolors3.default.red(` No project "${ref}".`) + import_picocolors3.default.dim(" Run ablo projects list."));
280392
+ process.exit(1);
280393
+ }
280394
+ if (target.default) {
280395
+ console.error(import_picocolors3.default.red(" The default project cannot be renamed."));
280396
+ process.exit(1);
280397
+ }
280398
+ const { status: status2, body } = await request(`/api/v1/projects/${target.id}`, requireKey(), {
280399
+ method: "PATCH",
280400
+ body: { name }
280401
+ });
280402
+ if (status2 !== 200) {
280403
+ console.error(
280404
+ import_picocolors3.default.red(` Rename failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
280405
+ );
280406
+ process.exit(1);
280407
+ }
280408
+ const updated = body;
280409
+ console.log(
280410
+ ` ${import_picocolors3.default.green("\u2713")} Renamed ${import_picocolors3.default.bold(updated.slug)} \u2192 ${import_picocolors3.default.bold(updated.name ?? updated.slug)} ${import_picocolors3.default.dim(`(${updated.id})`)}`
280411
+ );
280412
+ return;
280413
+ }
280414
+ if (sub === "use") {
280415
+ const ref = argv[1];
280416
+ if (!ref) {
280417
+ console.error(import_picocolors3.default.red(" usage: ablo projects use <slug|id|default>"));
280418
+ process.exit(1);
280419
+ }
280420
+ const all = await fetchProjects();
280421
+ const target = all.find((p2) => p2.slug === ref || p2.id === ref);
280422
+ if (!target) {
280423
+ console.error(import_picocolors3.default.red(` No project "${ref}".`) + import_picocolors3.default.dim(" Run ablo projects list."));
280424
+ process.exit(1);
280425
+ }
280426
+ if (target.default) {
280427
+ setActiveProject(void 0);
280428
+ console.log(` ${import_picocolors3.default.green("\u2713")} now targeting the ${import_picocolors3.default.bold("default")} project`);
280429
+ } else {
280430
+ setActiveProject({ id: target.id, slug: target.slug });
280431
+ console.log(` ${import_picocolors3.default.green("\u2713")} now targeting project ${import_picocolors3.default.bold(target.slug)} ${import_picocolors3.default.dim(`(${target.id})`)}`);
280432
+ }
280433
+ const guard = guardActiveProjectKey();
280434
+ if (!guard.ok) {
280435
+ const loginCmd = guard.activeProfile === DEFAULT_PROFILE ? "ablo login" : `ablo login --project ${guard.activeProfile}`;
280436
+ console.log(
280437
+ import_picocolors3.default.dim(` No key stored for this project yet \u2014 run ${import_picocolors3.default.bold(loginCmd)} to mint one.`)
280438
+ );
280439
+ }
280440
+ return;
280441
+ }
280442
+ console.error(
280443
+ import_picocolors3.default.red(` unknown subcommand: ${sub}`) + import_picocolors3.default.dim(
280444
+ ` (expected ${import_picocolors3.default.bold("list")}, ${import_picocolors3.default.bold("create")}, ${import_picocolors3.default.bold("rename")}, or ${import_picocolors3.default.bold("use")})`
280445
+ )
280446
+ );
280447
+ process.exit(1);
280448
+ }
280449
+
280450
+ // src/cli/target.ts
280451
+ var DEFAULT_PROJECT_SLUG = "default";
280452
+ async function resolveTarget(opts) {
280453
+ const keyEnv = modeFromKey(opts.apiKey) ?? null;
280454
+ const localProject = getActiveProject();
280455
+ const confirmed = await confirmFromServer(opts);
280456
+ return {
280457
+ url: opts.url,
280458
+ keyPrefix: `${opts.apiKey.slice(0, 12)}\u2026`,
280459
+ keySource: opts.keySource,
280460
+ keyEnv,
280461
+ confirmed,
280462
+ localProject,
280463
+ mismatches: reconcile({ confirmed, keyEnv, localProject })
280464
+ };
280465
+ }
280466
+ async function confirmFromServer(opts) {
280467
+ const keyEnv = modeFromKey(opts.apiKey) ?? null;
280468
+ try {
280469
+ const identity = await resolveIdentity({
280470
+ baseUrl: resolveBootstrapBaseUrl({ url: opts.url }),
280471
+ authToken: opts.apiKey,
280472
+ timeoutMs: opts.timeoutMs ?? 4e3
280473
+ });
280474
+ const projectId = identity.projectId ?? null;
280475
+ const environment = identity.environment ?? keyEnv;
280476
+ const project = projectId ? await nameProject(projectId, identity.accountScope, opts) : null;
280477
+ return {
280478
+ organizationId: identity.accountScope,
280479
+ environment,
280480
+ project,
280481
+ projectId,
280482
+ sandboxId: identity.sandboxId ?? null
280483
+ };
280484
+ } catch {
280485
+ return null;
280486
+ }
280487
+ }
280488
+ async function nameProject(projectId, organizationId, opts) {
280489
+ const list = await listProjects(opts.apiKey, resolveBootstrapBaseUrl({ url: opts.url }));
280490
+ const match = list?.find((p2) => p2.id === projectId);
280491
+ if (match) {
280492
+ return { id: match.id, slug: match.slug, name: match.name, isDefault: match.default };
280493
+ }
280494
+ return {
280495
+ id: projectId,
280496
+ slug: projectId,
280497
+ name: null,
280498
+ isDefault: projectId === organizationId
280499
+ };
280500
+ }
280501
+ function intendedProjectSlug(localProject) {
280502
+ return localProject?.slug ?? DEFAULT_PROJECT_SLUG;
280503
+ }
280504
+ function confirmedProjectSlug(project) {
280505
+ return project.isDefault ? DEFAULT_PROJECT_SLUG : project.slug;
280506
+ }
280507
+ function reconcile(input) {
280508
+ const { confirmed, keyEnv, localProject } = input;
280509
+ const mismatches = [];
280510
+ const realEnv = confirmed?.environment ?? keyEnv;
280511
+ const cliMode = getMode();
280512
+ if (realEnv && realEnv !== cliMode) {
280513
+ mismatches.push({ kind: "environment", keyEnv: realEnv, cliMode });
280514
+ }
280515
+ if (confirmed?.projectId) {
280516
+ const intendedId = localProject?.id ?? confirmed.organizationId;
280517
+ if (intendedId !== confirmed.projectId) {
280518
+ mismatches.push({
280519
+ kind: "project",
280520
+ intended: intendedProjectSlug(localProject),
280521
+ actual: confirmed.project ? confirmedProjectSlug(confirmed.project) : confirmed.projectId
280522
+ });
280523
+ }
280524
+ }
280525
+ return mismatches;
280526
+ }
280527
+ function describeMismatch(m2) {
280528
+ if (m2.kind === "project") {
280529
+ return `The key targets project ${m2.actual}, but ${m2.intended} is selected locally. This key routes the push, so it lands on ${m2.actual}. Run \`ablo projects use ${m2.actual}\` to match, or use a key minted for ${m2.intended}.`;
280530
+ }
280531
+ return `The key deploys to ${m2.keyEnv}, but the CLI mode is ${m2.cliMode}. The key wins, so this acts on ${m2.keyEnv}. Run \`ablo mode ${m2.keyEnv}\` to match, or present a ${m2.cliMode} key.`;
280532
+ }
280533
+
279974
280534
  // src/cli/push.ts
279975
280535
  function coerceBackfill(raw) {
279976
280536
  if (raw === "true") return true;
@@ -279984,14 +280544,14 @@ var DEFAULT_URL = ABLO_DEFAULT_BASE_URL;
279984
280544
  function fmtSignal(s) {
279985
280545
  const sig = s;
279986
280546
  const where = sig.field ? `${sig.model}.${sig.field}` : sig.model;
279987
- let line = ` \u2022 ${import_picocolors3.default.bold(where ?? "?")} \u2014 ${sig.detail ?? ""}`;
280547
+ let line = ` \u2022 ${import_picocolors4.default.bold(where ?? "?")} \u2014 ${sig.detail ?? ""}`;
279988
280548
  if (sig.shadowed) {
279989
280549
  const env = sig.shadowed.environment ?? "production";
279990
280550
  const ver = sig.shadowed.version != null ? `v${sig.shadowed.version}` : "active";
279991
280551
  const when = sig.shadowed.pushedAt ? new Date(sig.shadowed.pushedAt).toISOString().slice(0, 10) : "unknown date";
279992
280552
  const by = sig.shadowed.pushedBy ? ` by ${sig.shadowed.pushedBy}` : "";
279993
280553
  line += `
279994
- ${import_picocolors3.default.dim(`\u21B3 baseline: ${env} ${ver}, pushed ${when}${by}`)}`;
280554
+ ${import_picocolors4.default.dim(`\u21B3 baseline: ${env} ${ver}, pushed ${when}${by}`)}`;
279995
280555
  }
279996
280556
  return line;
279997
280557
  }
@@ -280088,7 +280648,7 @@ async function loadSchema(schemaPath, exportName) {
280088
280648
  const abs = (0, import_path3.resolve)(process.cwd(), schemaPath);
280089
280649
  if (!(0, import_fs4.existsSync)(abs)) {
280090
280650
  throw new AbloValidationError(
280091
- `schema not found at ${import_picocolors3.default.bold(schemaPath)}. Run ${import_picocolors3.default.bold("npx ablo init")} or pass ${import_picocolors3.default.bold("--schema <path>")}.`,
280651
+ `schema not found at ${import_picocolors4.default.bold(schemaPath)}. Run ${import_picocolors4.default.bold("npx ablo init")} or pass ${import_picocolors4.default.bold("--schema <path>")}.`,
280092
280652
  { code: "cli_invalid_arguments" }
280093
280653
  );
280094
280654
  }
@@ -280099,7 +280659,7 @@ async function loadSchema(schemaPath, exportName) {
280099
280659
  const schema = mod[exportName] ?? nested?.[exportName];
280100
280660
  if (!schema || typeof schema !== "object" || !("models" in schema)) {
280101
280661
  throw new AbloValidationError(
280102
- `${import_picocolors3.default.bold(schemaPath)} has no \`${exportName}\` export that looks like a Schema. Did you \`export const ${exportName} = defineSchema({ ... })\`?`,
280662
+ `${import_picocolors4.default.bold(schemaPath)} has no \`${exportName}\` export that looks like a Schema. Did you \`export const ${exportName} = defineSchema({ ... })\`?`,
280103
280663
  { code: "cli_invalid_arguments" }
280104
280664
  );
280105
280665
  }
@@ -280152,7 +280712,7 @@ function localModels(schema) {
280152
280712
  }
280153
280713
  function printPlan(local, remote) {
280154
280714
  if (!remote?.models) {
280155
- console.log(` ${import_picocolors3.default.dim("plan")} ${import_picocolors3.default.dim("(deployed schema unavailable \u2014 the server computes the diff on apply)")}
280715
+ console.log(` ${import_picocolors4.default.dim("plan")} ${import_picocolors4.default.dim("(deployed schema unavailable \u2014 the server computes the diff on apply)")}
280156
280716
  `);
280157
280717
  return;
280158
280718
  }
@@ -280163,36 +280723,37 @@ function printPlan(local, remote) {
280163
280723
  const changed = [...local.keys()].filter((k3) => remoteMap.has(k3) && remoteMap.get(k3) !== local.get(k3));
280164
280724
  const verLabel = remote.version != null ? `v${remote.version}` : "active";
280165
280725
  if (added.length === 0 && removed.length === 0 && changed.length === 0) {
280166
- console.log(` ${import_picocolors3.default.dim("plan")} ${import_picocolors3.default.dim(`no model-level changes vs deployed ${verLabel} (any field changes apply on push)`)}
280726
+ console.log(` ${import_picocolors4.default.dim("plan")} ${import_picocolors4.default.dim(`no model-level changes vs deployed ${verLabel} (any field changes apply on push)`)}
280167
280727
  `);
280168
280728
  return;
280169
280729
  }
280170
- console.log(` ${import_picocolors3.default.dim("plan")} ${import_picocolors3.default.dim(`vs deployed ${verLabel}:`)}`);
280171
- for (const k3 of added) console.log(` ${import_picocolors3.default.green(`+ ${k3}`)} ${import_picocolors3.default.dim("(new model)")}`);
280730
+ console.log(` ${import_picocolors4.default.dim("plan")} ${import_picocolors4.default.dim(`vs deployed ${verLabel}:`)}`);
280731
+ for (const k3 of added) console.log(` ${import_picocolors4.default.green(`+ ${k3}`)} ${import_picocolors4.default.dim("(new model)")}`);
280172
280732
  for (const k3 of changed)
280173
- console.log(` ${import_picocolors3.default.yellow(`~ ${k3}`)} ${import_picocolors3.default.dim(`conflict ${remoteMap.get(k3) || "(default)"} \u2192 ${local.get(k3) || "(default)"}`)}`);
280174
- for (const k3 of removed) console.log(` ${import_picocolors3.default.red(`- ${k3}`)} ${import_picocolors3.default.dim("(removed \u2014 destructive, needs --force)")}`);
280733
+ console.log(` ${import_picocolors4.default.yellow(`~ ${k3}`)} ${import_picocolors4.default.dim(`conflict ${remoteMap.get(k3) || "(default)"} \u2192 ${local.get(k3) || "(default)"}`)}`);
280734
+ for (const k3 of removed) console.log(` ${import_picocolors4.default.red(`- ${k3}`)} ${import_picocolors4.default.dim("(removed \u2014 destructive, needs --force)")}`);
280175
280735
  console.log("");
280176
280736
  }
280177
- async function confirmPush(args, env) {
280737
+ async function confirmPush(args, target) {
280738
+ const env = target.confirmed?.environment ?? target.keyEnv;
280178
280739
  const isProd = env === "production";
280179
280740
  const tty = Boolean(process.stdout.isTTY && process.stdin.isTTY);
280180
280741
  if (isProd && !args.yes) {
280181
280742
  const git = schemaGitState(args.schemaPath);
280182
280743
  if (git?.dirty && !args.allowDirty) {
280183
- console.error(` ${import_picocolors3.default.red("\u2717")} Refusing to deploy uncommitted schema to ${import_picocolors3.default.red(import_picocolors3.default.bold("production"))}.`);
280184
- console.error(import_picocolors3.default.dim(` Commit ${import_picocolors3.default.bold(args.schemaPath)} first, or pass ${import_picocolors3.default.bold("--allow-dirty")} to override.`));
280744
+ console.error(` ${import_picocolors4.default.red("\u2717")} Refusing to deploy uncommitted schema to ${import_picocolors4.default.red(import_picocolors4.default.bold("production"))}.`);
280745
+ console.error(import_picocolors4.default.dim(` Commit ${import_picocolors4.default.bold(args.schemaPath)} first, or pass ${import_picocolors4.default.bold("--allow-dirty")} to override.`));
280185
280746
  process.exit(1);
280186
280747
  }
280187
280748
  if (!tty) {
280188
- console.error(` ${import_picocolors3.default.red("\u2717")} Refusing to deploy to ${import_picocolors3.default.red(import_picocolors3.default.bold("production"))} non-interactively without confirmation.`);
280189
- console.error(import_picocolors3.default.dim(` Re-run with ${import_picocolors3.default.bold("--yes")} to confirm in CI/scripts.`));
280749
+ console.error(` ${import_picocolors4.default.red("\u2717")} Refusing to deploy to ${import_picocolors4.default.red(import_picocolors4.default.bold("production"))} non-interactively without confirmation.`);
280750
+ console.error(import_picocolors4.default.dim(` Re-run with ${import_picocolors4.default.bold("--yes")} to confirm in CI/scripts.`));
280190
280751
  process.exit(1);
280191
280752
  }
280192
- const project = getActiveProject();
280193
- const expected = project?.slug ?? "production";
280753
+ const confirmedProject = target.confirmed?.project;
280754
+ const expected = confirmedProject ? confirmedProject.isDefault ? "default" : confirmedProject.slug : target.localProject?.slug ?? "production";
280194
280755
  const typed = await he({
280195
- message: `This deploys to ${import_picocolors3.default.bold("production")}. Type ${import_picocolors3.default.bold(expected)} to confirm:`,
280756
+ message: `This deploys to ${import_picocolors4.default.bold("production")} project ${import_picocolors4.default.bold(expected)}. Type ${import_picocolors4.default.bold(expected)} to confirm:`,
280196
280757
  placeholder: expected
280197
280758
  });
280198
280759
  if (pD(typed) || String(typed).trim() !== expected) {
@@ -280202,32 +280763,53 @@ async function confirmPush(args, env) {
280202
280763
  return;
280203
280764
  }
280204
280765
  if (!isProd && !args.yes && tty) {
280205
- const ok = await ye({ message: `Apply to ${import_picocolors3.default.bold("sandbox")}?` });
280766
+ const ok = await ye({ message: `Apply to ${import_picocolors4.default.bold("sandbox")}?` });
280206
280767
  if (pD(ok) || !ok) {
280207
280768
  xe("Aborted.");
280208
280769
  process.exit(1);
280209
280770
  }
280210
280771
  }
280211
280772
  }
280212
- function printPushTarget(opts) {
280213
- const env = modeFromKey(opts.apiKey);
280214
- const envLabel = env === "production" ? import_picocolors3.default.bold("production") : env === "sandbox" ? import_picocolors3.default.bold("sandbox") : import_picocolors3.default.yellow("unknown env");
280215
- const project = getActiveProject();
280216
- const projectLabel = project ? `${import_picocolors3.default.bold(project.slug)} ${import_picocolors3.default.dim(`(${project.id})`)}` : `${import_picocolors3.default.bold("default")} ${import_picocolors3.default.dim("(org-default)")}`;
280773
+ function printPushTarget(target, schema) {
280774
+ const confirmed = target.confirmed;
280775
+ const env = confirmed?.environment ?? target.keyEnv;
280776
+ const envLabel = env === "production" ? import_picocolors4.default.bold("production") : env === "sandbox" ? import_picocolors4.default.bold("sandbox") : import_picocolors4.default.yellow("unknown env");
280217
280777
  const cliMode = getMode();
280218
- const modeNote = env && env !== cliMode ? ` ${import_picocolors3.default.yellow(`(cli mode: ${cliMode})`)}` : "";
280778
+ const modeNote = env && env !== cliMode ? ` ${import_picocolors4.default.yellow(`(cli mode: ${cliMode})`)}` : "";
280779
+ let projectLabel;
280780
+ if (confirmed?.project) {
280781
+ const p2 = confirmed.project;
280782
+ const name = p2.isDefault ? `${import_picocolors4.default.bold("default")} ${import_picocolors4.default.dim("(org-default)")}` : import_picocolors4.default.bold(p2.slug);
280783
+ projectLabel = `${name} ${import_picocolors4.default.dim(`(${p2.id})`)}`;
280784
+ } else if (confirmed) {
280785
+ projectLabel = `${import_picocolors4.default.bold("default")} ${import_picocolors4.default.dim("(org-default)")}`;
280786
+ } else {
280787
+ const local = target.localProject;
280788
+ const shown = local ? `${import_picocolors4.default.bold(local.slug)} ${import_picocolors4.default.dim(`(${local.id})`)}` : `${import_picocolors4.default.bold("default")} ${import_picocolors4.default.dim("(org-default)")}`;
280789
+ projectLabel = `${shown} ${import_picocolors4.default.yellow("(unconfirmed \u2014 server did not answer)")}`;
280790
+ }
280219
280791
  console.log(`
280220
- ${brand("ablo")} ${import_picocolors3.default.dim("push")} ${import_picocolors3.default.dim("\u2192")} ${envLabel}${modeNote}`);
280221
- console.log(` ${import_picocolors3.default.dim("project")} ${projectLabel}`);
280222
- console.log(` ${import_picocolors3.default.dim("target")} ${import_picocolors3.default.dim(opts.url)}`);
280792
+ ${brand("ablo")} ${import_picocolors4.default.dim("push")} ${import_picocolors4.default.dim("\u2192")} ${envLabel}${modeNote}`);
280793
+ if (confirmed?.organizationId) console.log(` ${import_picocolors4.default.dim("org")} ${import_picocolors4.default.dim(confirmed.organizationId)}`);
280794
+ console.log(` ${import_picocolors4.default.dim("project")} ${projectLabel}`);
280795
+ console.log(` ${import_picocolors4.default.dim("target")} ${import_picocolors4.default.dim(target.url)}`);
280223
280796
  console.log(
280224
- ` ${import_picocolors3.default.dim("key")} ${maskKey(opts.apiKey)} ${import_picocolors3.default.dim(`(${describeKeySource(opts.keySource)})`)}`
280797
+ ` ${import_picocolors4.default.dim("key")} ${target.keyPrefix} ${import_picocolors4.default.dim(`(${describeKeySource(target.keySource)})`)}`
280225
280798
  );
280226
280799
  console.log(
280227
- ` ${import_picocolors3.default.dim("schema")} ${import_picocolors3.default.bold(opts.schemaPath)} ${import_picocolors3.default.dim(`${opts.modelCount} models, hash ${opts.hash}`)}
280800
+ ` ${import_picocolors4.default.dim("schema")} ${import_picocolors4.default.bold(schema.path)} ${import_picocolors4.default.dim(`${schema.modelCount} models, hash ${schema.hash}`)}
280228
280801
  `
280229
280802
  );
280230
280803
  }
280804
+ function warnMismatches(target) {
280805
+ let projectDrift = false;
280806
+ for (const m2 of target.mismatches) {
280807
+ if (m2.kind === "project") projectDrift = true;
280808
+ console.log(` ${import_picocolors4.default.yellow("\u26A0")} ${import_picocolors4.default.yellow(describeMismatch(m2))}
280809
+ `);
280810
+ }
280811
+ return { projectDrift };
280812
+ }
280231
280813
  function describeKeySource(source) {
280232
280814
  switch (source) {
280233
280815
  case "env":
@@ -280245,7 +280827,7 @@ async function push(argv) {
280245
280827
  try {
280246
280828
  args = parsePushArgs(argv);
280247
280829
  } catch (err) {
280248
- console.error(import_picocolors3.default.red(` ${err instanceof Error ? err.message : String(err)}`));
280830
+ console.error(import_picocolors4.default.red(` ${err instanceof Error ? err.message : String(err)}`));
280249
280831
  process.exit(1);
280250
280832
  }
280251
280833
  let keySource = "env";
@@ -280256,44 +280838,43 @@ async function push(argv) {
280256
280838
  }
280257
280839
  if (!args.apiKey) {
280258
280840
  console.error(
280259
- import_picocolors3.default.red(` No API key.`) + import_picocolors3.default.dim(
280260
- ` Run ${import_picocolors3.default.bold("npx ablo login")} for the sandbox dev loop \u2014 or set ${import_picocolors3.default.bold("ABLO_API_KEY")} (${import_picocolors3.default.bold("sk_test_")} = sandbox; ${import_picocolors3.default.bold("sk_live_")} = deliberate production deploy). Mode is currently '${getMode()}'.`
280841
+ import_picocolors4.default.red(` No API key.`) + import_picocolors4.default.dim(
280842
+ ` Run ${import_picocolors4.default.bold("npx ablo login")} for the sandbox dev loop \u2014 or set ${import_picocolors4.default.bold("ABLO_API_KEY")} (${import_picocolors4.default.bold("sk_test_")} = sandbox; ${import_picocolors4.default.bold("sk_live_")} = deliberate production deploy). Mode is currently '${getMode()}'.`
280261
280843
  )
280262
280844
  );
280263
280845
  process.exit(1);
280264
280846
  }
280265
280847
  const schema = await loadSchema(args.schemaPath, args.exportName);
280266
280848
  const hash = (0, import_schema2.schemaHash)(schema);
280267
- printPushTarget({
280268
- schemaPath: args.schemaPath,
280269
- url: args.url,
280270
- apiKey: args.apiKey,
280271
- keySource,
280849
+ const target = await resolveTarget({ url: args.url, apiKey: args.apiKey, keySource });
280850
+ printPushTarget(target, {
280851
+ path: args.schemaPath,
280272
280852
  modelCount: Object.keys(schema.models).length,
280273
280853
  hash
280274
280854
  });
280275
280855
  const remote = await fetchActiveSchema(args.url, args.apiKey);
280276
280856
  printPlan(localModels(schema), remote);
280857
+ warnMismatches(target);
280277
280858
  const git = schemaGitState(args.schemaPath);
280278
280859
  if (git?.dirty) {
280279
280860
  const what = git.untracked ? "is untracked (not committed)" : "has uncommitted changes";
280280
- console.log(` ${import_picocolors3.default.yellow("\u26A0")} ${import_picocolors3.default.bold(args.schemaPath)} ${what} \u2014 this deploy won't match a git commit.
280861
+ console.log(` ${import_picocolors4.default.yellow("\u26A0")} ${import_picocolors4.default.bold(args.schemaPath)} ${what} \u2014 this deploy won't match a git commit.
280281
280862
  `);
280282
280863
  }
280283
280864
  if (args.dryRun) {
280284
- console.log(` ${import_picocolors3.default.dim("\u25CB")} dry run \u2014 nothing applied. Re-run without ${import_picocolors3.default.bold("--dry-run")} to deploy.`);
280865
+ console.log(` ${import_picocolors4.default.dim("\u25CB")} dry run \u2014 nothing applied. Re-run without ${import_picocolors4.default.bold("--dry-run")} to deploy.`);
280285
280866
  return;
280286
280867
  }
280287
- await confirmPush(args, modeFromKey(args.apiKey));
280868
+ await confirmPush(args, target);
280288
280869
  const { ok: resOk, status: status2, body, bodyText } = await pushSchema(schema, args);
280289
280870
  if (resOk) {
280290
280871
  if (body.unchanged) {
280291
- console.log(` ${import_picocolors3.default.dim("\u25CB")} No changes \u2014 schema already active (v${body.version}).`);
280872
+ console.log(` ${import_picocolors4.default.dim("\u25CB")} No changes \u2014 schema already active (v${body.version}).`);
280292
280873
  } else {
280293
- console.log(` ${import_picocolors3.default.green("\u2713")} Activated ${import_picocolors3.default.bold(`v${body.version}`)} ${import_picocolors3.default.dim(`(hash ${body.hash})`)}`);
280874
+ console.log(` ${import_picocolors4.default.green("\u2713")} Activated ${import_picocolors4.default.bold(`v${body.version}`)} ${import_picocolors4.default.dim(`(hash ${body.hash})`)}`);
280294
280875
  if (Array.isArray(body.warnings) && body.warnings.length > 0) {
280295
- console.log(import_picocolors3.default.yellow(` Applied ${body.warnings.length} destructive change(s):`));
280296
- for (const w2 of body.warnings) console.log(import_picocolors3.default.yellow(fmtSignal(w2)));
280876
+ console.log(import_picocolors4.default.yellow(` Applied ${body.warnings.length} destructive change(s):`));
280877
+ for (const w2 of body.warnings) console.log(import_picocolors4.default.yellow(fmtSignal(w2)));
280297
280878
  }
280298
280879
  }
280299
280880
  return;
@@ -280301,75 +280882,75 @@ async function push(argv) {
280301
280882
  if (status2 === 409) {
280302
280883
  const unexecutable = Array.isArray(body.unexecutable) ? body.unexecutable : [];
280303
280884
  const warnings = Array.isArray(body.warnings) ? body.warnings : [];
280304
- console.error(import_picocolors3.default.red(" Incompatible change \u2014 this push is not safe to apply as-is."));
280885
+ console.error(import_picocolors4.default.red(" Incompatible change \u2014 this push is not safe to apply as-is."));
280305
280886
  if (unexecutable.length > 0) {
280306
- console.error(import_picocolors3.default.red(` Unexecutable (would fail on existing rows):`));
280307
- for (const u2 of unexecutable) console.error(import_picocolors3.default.red(fmtSignal(u2)));
280887
+ console.error(import_picocolors4.default.red(` Unexecutable (would fail on existing rows):`));
280888
+ for (const u2 of unexecutable) console.error(import_picocolors4.default.red(fmtSignal(u2)));
280308
280889
  }
280309
280890
  if (warnings.length > 0) {
280310
- console.error(import_picocolors3.default.yellow(` Destructive (data loss):`));
280311
- for (const w2 of warnings) console.error(import_picocolors3.default.yellow(fmtSignal(w2)));
280891
+ console.error(import_picocolors4.default.yellow(` Destructive (data loss):`));
280892
+ for (const w2 of warnings) console.error(import_picocolors4.default.yellow(fmtSignal(w2)));
280312
280893
  }
280313
280894
  const hasShadowed = [...unexecutable, ...warnings].some(
280314
280895
  (s) => s.shadowed != null
280315
280896
  );
280316
280897
  if (hasShadowed) {
280317
280898
  console.error(
280318
- import_picocolors3.default.dim(
280899
+ import_picocolors4.default.dim(
280319
280900
  " These models exist in the baseline above but not in your push. Sandbox readers fall"
280320
280901
  )
280321
280902
  );
280322
280903
  console.error(
280323
- import_picocolors3.default.dim(
280904
+ import_picocolors4.default.dim(
280324
280905
  " back to the production schema until you push your own, so applying this drops them."
280325
280906
  )
280326
280907
  );
280327
280908
  }
280328
- console.error(import_picocolors3.default.dim(` Re-push with ${import_picocolors3.default.bold("--force")} to override, or use ${import_picocolors3.default.bold("--rename old:new")} if you renamed a model.`));
280909
+ console.error(import_picocolors4.default.dim(` Re-push with ${import_picocolors4.default.bold("--force")} to override, or use ${import_picocolors4.default.bold("--rename old:new")} if you renamed a model.`));
280329
280910
  } else if (status2 === 403) {
280330
280911
  const code = body.code ?? body.reason;
280331
280912
  const serverMsg = body.message ?? body.reason;
280332
- console.error(import_picocolors3.default.red(` Forbidden${code ? ` [${code}]` : ""}: ${serverMsg ?? "permission denied"}`));
280333
- console.error(import_picocolors3.default.dim(` Push used ${import_picocolors3.default.bold(maskKey(args.apiKey))} from ${describeKeySource(keySource)}.`));
280913
+ console.error(import_picocolors4.default.red(` Forbidden${code ? ` [${code}]` : ""}: ${serverMsg ?? "permission denied"}`));
280914
+ console.error(import_picocolors4.default.dim(` Push used ${import_picocolors4.default.bold(maskKey(args.apiKey))} from ${describeKeySource(keySource)}.`));
280334
280915
  if (code === "database_role_cannot_enforce_rls") {
280335
280916
  console.error(
280336
- import_picocolors3.default.dim(
280337
- ` Your database role bypasses row-level security. Run ${import_picocolors3.default.bold("npx ablo migrate")} to create a scoped (NOBYPASSRLS) role and repoint DATABASE_URL, then re-push.`
280917
+ import_picocolors4.default.dim(
280918
+ ` Your database role bypasses row-level security. Run ${import_picocolors4.default.bold("npx ablo migrate")} to create a scoped (NOBYPASSRLS) role and repoint DATABASE_URL, then re-push.`
280338
280919
  )
280339
280920
  );
280340
280921
  } else if (code === "database_tables_unforced_rls") {
280341
280922
  console.error(
280342
- import_picocolors3.default.dim(
280343
- ` One or more synced tables don't have FORCE ROW LEVEL SECURITY. Run ${import_picocolors3.default.bold("npx ablo migrate")} to (re)apply the tenant policies, then re-push.`
280923
+ import_picocolors4.default.dim(
280924
+ ` One or more synced tables don't have FORCE ROW LEVEL SECURITY. Run ${import_picocolors4.default.bold("npx ablo migrate")} to (re)apply the tenant policies, then re-push.`
280344
280925
  )
280345
280926
  );
280346
280927
  } else if (code === "capability_scope_denied" || code === "capability_invalid") {
280347
280928
  console.error(
280348
- import_picocolors3.default.dim(
280349
- ` This is a ${import_picocolors3.default.bold("database privilege")} error (Postgres 42501 / row-level security), not a key scope \u2014 a different API key won't help. The role behind this org's database can't write the target. Provision a writable, RLS-scoped role with ${import_picocolors3.default.bold("npx ablo migrate")}, or check the org's database registration. See docs/plans/read-path-logical-replication-vs-hosting.md.`
280929
+ import_picocolors4.default.dim(
280930
+ ` This is a ${import_picocolors4.default.bold("database privilege")} error (Postgres 42501 / row-level security), not a key scope \u2014 a different API key won't help. The role behind this org's database can't write the target. Provision a writable, RLS-scoped role with ${import_picocolors4.default.bold("npx ablo migrate")}, or check the org's database registration. See docs/plans/read-path-logical-replication-vs-hosting.md.`
280350
280931
  )
280351
280932
  );
280352
280933
  } else if (code === "schema_provisioning_forbidden") {
280353
280934
  console.error(
280354
- import_picocolors3.default.dim(
280355
- ` This is not a key problem \u2014 the push was authorized, but the target database refused to let the engine create tables (Postgres 42501). On the replication read path Ablo never runs DDL: register your database as a data source with ${import_picocolors3.default.bold("npx ablo connect --register")}, and pushes to that environment record the schema as metadata only \u2014 no tables are created anywhere.`
280935
+ import_picocolors4.default.dim(
280936
+ ` This is not a key problem \u2014 the push was authorized, but the target database refused to let the engine create tables (Postgres 42501). On the replication read path Ablo never runs DDL: register your database as a data source with ${import_picocolors4.default.bold("npx ablo connect --register")}, and pushes to that environment record the schema as metadata only \u2014 no tables are created anywhere.`
280356
280937
  )
280357
280938
  );
280358
280939
  } else if (args.apiKey != null && classifyCredentialKind(args.apiKey) === "restricted") {
280359
280940
  console.error(
280360
- import_picocolors3.default.dim(
280361
- ` Schema pushes need a SECRET key: ${import_picocolors3.default.bold("sk_test_")} (sandbox dev loop) or a dashboard ${import_picocolors3.default.bold("sk_live_")} (production deploy: ${import_picocolors3.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")}).`
280941
+ import_picocolors4.default.dim(
280942
+ ` Schema pushes need a SECRET key: ${import_picocolors4.default.bold("sk_test_")} (sandbox dev loop) or a dashboard ${import_picocolors4.default.bold("sk_live_")} (production deploy: ${import_picocolors4.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")}).`
280362
280943
  )
280363
280944
  );
280364
280945
  } else {
280365
280946
  console.error(
280366
- import_picocolors3.default.dim(
280367
- ` This key isn't authorized to push schema (needs ${import_picocolors3.default.bold("schema:push")}). ` + (keySource === "stored" ? `It's your stored ${import_picocolors3.default.bold("ablo login")} sandbox key \u2014 a key in ${import_picocolors3.default.bold(".env.local")} or ${import_picocolors3.default.bold("ABLO_API_KEY")} takes precedence, so put a schema:push key there (sandbox ${import_picocolors3.default.bold("sk_test_")} or production ${import_picocolors3.default.bold("sk_live_")}) and re-push. ` : `Use a schema:push key \u2014 a sandbox ${import_picocolors3.default.bold("sk_test_")} or production ${import_picocolors3.default.bold("sk_live_")}. `) + `Manage keys at https://abloatai.com`
280947
+ import_picocolors4.default.dim(
280948
+ ` This key isn't authorized to push schema (needs ${import_picocolors4.default.bold("schema:push")}). ` + (keySource === "stored" ? `It's your stored ${import_picocolors4.default.bold("ablo login")} sandbox key \u2014 a key in ${import_picocolors4.default.bold(".env.local")} or ${import_picocolors4.default.bold("ABLO_API_KEY")} takes precedence, so put a schema:push key there (sandbox ${import_picocolors4.default.bold("sk_test_")} or production ${import_picocolors4.default.bold("sk_live_")}) and re-push. ` : `Use a schema:push key \u2014 a sandbox ${import_picocolors4.default.bold("sk_test_")} or production ${import_picocolors4.default.bold("sk_live_")}. `) + `Manage keys at https://abloatai.com`
280368
280949
  )
280369
280950
  );
280370
280951
  }
280371
280952
  } else {
280372
- console.error(import_picocolors3.default.red(` Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}`));
280953
+ console.error(import_picocolors4.default.red(` Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}`));
280373
280954
  }
280374
280955
  process.exit(1);
280375
280956
  }
@@ -280432,10 +281013,10 @@ var log = {
280432
281013
  console.log(`[migrate] ${msg}`, fields);
280433
281014
  },
280434
281015
  warn: (msg, fields) => {
280435
- console.warn(import_picocolors4.default.yellow(`[migrate] ${msg}`), fields);
281016
+ console.warn(import_picocolors5.default.yellow(`[migrate] ${msg}`), fields);
280436
281017
  },
280437
281018
  error: (msg, fields) => {
280438
- console.error(import_picocolors4.default.red(`[migrate] ${msg}`), fields);
281019
+ console.error(import_picocolors5.default.red(`[migrate] ${msg}`), fields);
280439
281020
  }
280440
281021
  };
280441
281022
  async function applyStatements(dbUrl, targetSchema, statements, concurrent = []) {
@@ -280509,7 +281090,7 @@ async function migrate(argv) {
280509
281090
  try {
280510
281091
  args = parseMigrateArgs(argv);
280511
281092
  } catch (err) {
280512
- console.error(import_picocolors4.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281093
+ console.error(import_picocolors5.default.red(` ${err instanceof Error ? err.message : String(err)}`));
280513
281094
  process.exit(1);
280514
281095
  }
280515
281096
  const schema = await loadSchema(args.schemaPath, args.exportName);
@@ -280520,11 +281101,11 @@ async function migrate(argv) {
280520
281101
  ].join("\n");
280521
281102
  const totalStatements = plan.statements.length + plan.concurrent.length;
280522
281103
  console.log(
280523
- ` ${import_picocolors4.default.dim("Schema")} ${import_picocolors4.default.bold(args.schemaPath)} \u2192 ${import_picocolors4.default.dim(`${Object.keys(schema.models).length} models, ${totalStatements} statements`)}`
281104
+ ` ${import_picocolors5.default.dim("Schema")} ${import_picocolors5.default.bold(args.schemaPath)} \u2192 ${import_picocolors5.default.dim(`${Object.keys(schema.models).length} models, ${totalStatements} statements`)}`
280524
281105
  );
280525
281106
  if (args.outputFile) {
280526
281107
  (0, import_fs5.writeFileSync)(args.outputFile, sql + "\n");
280527
- console.log(` ${import_picocolors4.default.green("\u2713")} SQL written to ${import_picocolors4.default.bold(args.outputFile)}`);
281108
+ console.log(` ${import_picocolors5.default.green("\u2713")} SQL written to ${import_picocolors5.default.bold(args.outputFile)}`);
280528
281109
  return;
280529
281110
  }
280530
281111
  if (args.dryRun) {
@@ -280534,7 +281115,7 @@ async function migrate(argv) {
280534
281115
  const dbUrl = readProjectDatabaseUrl();
280535
281116
  if (!dbUrl) {
280536
281117
  console.error(
280537
- import_picocolors4.default.red(
281118
+ import_picocolors5.default.red(
280538
281119
  " No DATABASE_URL found (checked process env, .env.local, .env). Set it to apply, or use --dry-run to preview."
280539
281120
  )
280540
281121
  );
@@ -280542,7 +281123,7 @@ async function migrate(argv) {
280542
281123
  }
280543
281124
  try {
280544
281125
  await applyStatements(dbUrl, args.targetSchema, plan.statements, plan.concurrent);
280545
- console.log(` ${import_picocolors4.default.green("\u2713")} Migration complete`);
281126
+ console.log(` ${import_picocolors5.default.green("\u2713")} Migration complete`);
280546
281127
  } catch {
280547
281128
  process.exit(1);
280548
281129
  }
@@ -280550,7 +281131,7 @@ async function migrate(argv) {
280550
281131
 
280551
281132
  // src/cli/connect.ts
280552
281133
  init_cjs_shims();
280553
- var import_picocolors5 = __toESM(require_picocolors(), 1);
281134
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
280554
281135
  var ABLO_PUBLICATION = "ablo_publication";
280555
281136
  var ABLO_REPLICATION_ROLE = "ablo_replicator";
280556
281137
  function parseConnectArgs(argv) {
@@ -280607,50 +281188,50 @@ function connectSetupSql(input) {
280607
281188
  function printConnectRecipe(args) {
280608
281189
  const sql = connectSetupSql({ tables: args.tables, role: args.role });
280609
281190
  console.log(`
280610
- ${brand("ablo")} ${import_picocolors5.default.dim("connect")} ${import_picocolors5.default.dim("logical replication \u2014 the read path (your writes stay on your own backend)")}
281191
+ ${brand("ablo")} ${import_picocolors6.default.dim("connect")} ${import_picocolors6.default.dim("logical replication \u2014 the read path (your writes stay on your own backend)")}
280611
281192
  `);
280612
281193
  console.log(
280613
- ` Ablo consumes your write-ahead log (WAL) via logical replication and ${import_picocolors5.default.bold("never")} runs DDL,
281194
+ ` Ablo consumes your write-ahead log (WAL) via logical replication and ${import_picocolors6.default.bold("never")} runs DDL,
280614
281195
  owns, hosts, or migrates your schema. Your app continues to own the write path \u2014 Ablo tails
280615
- the changes and serves them as live shapes. Run this once against your Postgres ${import_picocolors5.default.dim("(as a superuser / DB owner)")}:
281196
+ the changes and serves them as live shapes. Run this once against your Postgres ${import_picocolors6.default.dim("(as a superuser / DB owner)")}:
280616
281197
  `
280617
281198
  );
280618
- console.log(` ${import_picocolors5.default.bold("1.")} Enable logical decoding ${import_picocolors5.default.dim("(then RESTART Postgres \u2014 wal_level is not reloadable)")}`);
280619
- console.log(` ${import_picocolors5.default.cyan(sql[0])}`);
281199
+ console.log(` ${import_picocolors6.default.bold("1.")} Enable logical decoding ${import_picocolors6.default.dim("(then RESTART Postgres \u2014 wal_level is not reloadable)")}`);
281200
+ console.log(` ${import_picocolors6.default.cyan(sql[0])}`);
280620
281201
  console.log(
280621
- import_picocolors5.default.dim(
280622
- ` On Amazon RDS / Aurora you can't ALTER SYSTEM: set ${import_picocolors5.default.bold("rds.logical_replication = 1")} in the
281202
+ import_picocolors6.default.dim(
281203
+ ` On Amazon RDS / Aurora you can't ALTER SYSTEM: set ${import_picocolors6.default.bold("rds.logical_replication = 1")} in the
280623
281204
  instance's parameter group instead, then reboot.`
280624
281205
  )
280625
281206
  );
280626
281207
  console.log(`
280627
- ${import_picocolors5.default.bold("2.")} Publish the tables Ablo should read`);
280628
- console.log(` ${import_picocolors5.default.cyan(sql[1])}`);
281208
+ ${import_picocolors6.default.bold("2.")} Publish the tables Ablo should read`);
281209
+ console.log(` ${import_picocolors6.default.cyan(sql[1])}`);
280629
281210
  if (args.tables.length === 0) {
280630
- console.log(import_picocolors5.default.dim(` (Scope it with ${import_picocolors5.default.bold("ablo connect --tables a,b,c")} to publish a subset.)`));
281211
+ console.log(import_picocolors6.default.dim(` (Scope it with ${import_picocolors6.default.bold("ablo connect --tables a,b,c")} to publish a subset.)`));
280631
281212
  }
280632
281213
  console.log(`
280633
- ${import_picocolors5.default.bold("3.")} Create a least-privilege replication role ${import_picocolors5.default.dim("(pick your own password)")}`);
280634
- console.log(` ${import_picocolors5.default.cyan(sql[2])}`);
280635
- console.log(` ${import_picocolors5.default.cyan(sql[3])}`);
280636
- console.log(` ${import_picocolors5.default.cyan(sql[4])}`);
281214
+ ${import_picocolors6.default.bold("3.")} Create a least-privilege replication role ${import_picocolors6.default.dim("(pick your own password)")}`);
281215
+ console.log(` ${import_picocolors6.default.cyan(sql[2])}`);
281216
+ console.log(` ${import_picocolors6.default.cyan(sql[3])}`);
281217
+ console.log(` ${import_picocolors6.default.cyan(sql[4])}`);
280637
281218
  console.log(
280638
- import_picocolors5.default.dim(
281219
+ import_picocolors6.default.dim(
280639
281220
  ` On Amazon RDS, the REPLICATION attribute is granted, not set directly:
280640
- ${import_picocolors5.default.bold(`GRANT rds_replication TO ${quoteIdent(args.role)};`)}`
281221
+ ${import_picocolors6.default.bold(`GRANT rds_replication TO ${quoteIdent(args.role)};`)}`
280641
281222
  )
280642
281223
  );
280643
281224
  console.log(
280644
281225
  `
280645
- ${import_picocolors5.default.bold("4.")} Put the role's connection string in ${import_picocolors5.default.bold("DATABASE_URL")}, then verify:
280646
- ${import_picocolors5.default.cyan("npx ablo connect --check")}
281226
+ ${import_picocolors6.default.bold("4.")} Put the role's connection string in ${import_picocolors6.default.bold("DATABASE_URL")}, then verify:
281227
+ ${import_picocolors6.default.cyan("npx ablo connect --check")}
280647
281228
  `
280648
281229
  );
280649
281230
  console.log(
280650
- import_picocolors5.default.dim(
281231
+ import_picocolors6.default.dim(
280651
281232
  ` Reminder: this is the READ path \u2014 Ablo tails your WAL and never writes to your database.
280652
281233
  Your writes keep going through your own backend. (Ablo HOSTING your rows, or dialing in via
280653
- ${import_picocolors5.default.bold("databaseUrl")}, is the deprecated posture \u2014 see the read-path decision doc.)`
281234
+ ${import_picocolors6.default.bold("databaseUrl")}, is the deprecated posture \u2014 see the read-path decision doc.)`
280654
281235
  )
280655
281236
  );
280656
281237
  console.log();
@@ -280668,11 +281249,11 @@ var SYNC_INFRA_TYPES = [
280668
281249
  ];
280669
281250
  function printCheckItem(item) {
280670
281251
  if (item.ok) {
280671
- console.log(` ${import_picocolors5.default.green("\u2713")} ${item.label}`);
281252
+ console.log(` ${import_picocolors6.default.green("\u2713")} ${item.label}`);
280672
281253
  } else {
280673
- console.log(` ${import_picocolors5.default.red("\u2717")} ${item.label}`);
281254
+ console.log(` ${import_picocolors6.default.red("\u2717")} ${item.label}`);
280674
281255
  if (item.fix) {
280675
- for (const line of item.fix.split("\n")) console.log(` ${import_picocolors5.default.red("\u2022")} ${line}`);
281256
+ for (const line of item.fix.split("\n")) console.log(` ${import_picocolors6.default.red("\u2022")} ${line}`);
280676
281257
  }
280677
281258
  }
280678
281259
  }
@@ -280684,9 +281265,9 @@ async function probeReadiness(sql, opts = {}) {
280684
281265
  );
280685
281266
  const walLevel = walRows[0]?.setting ?? "unknown";
280686
281267
  items.push(
280687
- walLevel === "logical" ? { ok: true, label: `wal_level is ${import_picocolors5.default.bold("logical")}` } : {
281268
+ walLevel === "logical" ? { ok: true, label: `wal_level is ${import_picocolors6.default.bold("logical")}` } : {
280688
281269
  ok: false,
280689
- label: `wal_level is ${import_picocolors5.default.bold(walLevel)} (need ${import_picocolors5.default.bold("logical")})`,
281270
+ label: `wal_level is ${import_picocolors6.default.bold(walLevel)} (need ${import_picocolors6.default.bold("logical")})`,
280690
281271
  fix: `ALTER SYSTEM SET wal_level = 'logical'; then RESTART Postgres.
280691
281272
  On RDS/Aurora set rds.logical_replication = 1 in the parameter group, then reboot.
280692
281273
  On Neon enable Logical Replication in the project (Console \u2192 Settings \u2192 Logical Replication, or the API) \u2014 Neon forbids ALTER SYSTEM; the toggle sets wal_level=logical.`
@@ -280700,10 +281281,10 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
280700
281281
  items.push(
280701
281282
  pubRow ? {
280702
281283
  ok: true,
280703
- label: `publication ${import_picocolors5.default.bold(publication)} exists ${import_picocolors5.default.dim(pubRow.puballtables ? "(all tables)" : "(table subset)")}`
281284
+ label: `publication ${import_picocolors6.default.bold(publication)} exists ${import_picocolors6.default.dim(pubRow.puballtables ? "(all tables)" : "(table subset)")}`
280704
281285
  } : {
280705
281286
  ok: false,
280706
- label: `publication ${import_picocolors5.default.bold(publication)} not found`,
281287
+ label: `publication ${import_picocolors6.default.bold(publication)} not found`,
280707
281288
  fix: `CREATE PUBLICATION ${quoteIdent(publication)} FOR ALL TABLES;`
280708
281289
  }
280709
281290
  );
@@ -280713,9 +281294,9 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
280713
281294
  const role = roleRows[0];
280714
281295
  const hasReplication = Boolean(role && (role.rolreplication || role.rolsuper));
280715
281296
  items.push(
280716
- hasReplication ? { ok: true, label: `DATABASE_URL role can stream replication ${import_picocolors5.default.dim("(REPLICATION)")}` } : {
281297
+ hasReplication ? { ok: true, label: `DATABASE_URL role can stream replication ${import_picocolors6.default.dim("(REPLICATION)")}` } : {
280717
281298
  ok: false,
280718
- label: `DATABASE_URL role lacks the ${import_picocolors5.default.bold("REPLICATION")} attribute`,
281299
+ label: `DATABASE_URL role lacks the ${import_picocolors6.default.bold("REPLICATION")} attribute`,
280719
281300
  fix: `ALTER ROLE current_user WITH REPLICATION;
280720
281301
  On RDS: GRANT rds_replication TO <your_role>;`
280721
281302
  }
@@ -280773,7 +281354,7 @@ function requireDatabaseUrl(verb) {
280773
281354
  const dbUrl = readProjectDatabaseUrl();
280774
281355
  if (!dbUrl) {
280775
281356
  console.error(
280776
- import_picocolors5.default.red(" No DATABASE_URL found (checked process env, .env.local, .env).") + import_picocolors5.default.dim(` Set it to the Postgres you want Ablo to read, then re-run ${import_picocolors5.default.bold(`ablo connect ${verb}`)}.`)
281357
+ import_picocolors6.default.red(" No DATABASE_URL found (checked process env, .env.local, .env).") + import_picocolors6.default.dim(` Set it to the Postgres you want Ablo to read, then re-run ${import_picocolors6.default.bold(`ablo connect ${verb}`)}.`)
280777
281358
  );
280778
281359
  process.exit(1);
280779
281360
  }
@@ -280787,7 +281368,7 @@ async function probeAndReport(dbUrl) {
280787
281368
  items = await probeReadiness(sql);
280788
281369
  } catch (err) {
280789
281370
  const pg = err ?? {};
280790
- console.error(import_picocolors5.default.red(` Couldn't read the database: ${pg.message ?? String(err)}`));
281371
+ console.error(import_picocolors6.default.red(` Couldn't read the database: ${pg.message ?? String(err)}`));
280791
281372
  await sql.end({ timeout: 2 });
280792
281373
  process.exit(1);
280793
281374
  }
@@ -280798,17 +281379,17 @@ async function probeAndReport(dbUrl) {
280798
281379
  async function runCheck() {
280799
281380
  const dbUrl = requireDatabaseUrl("--check");
280800
281381
  console.log(`
280801
- ${brand("ablo")} ${import_picocolors5.default.dim("connect --check")} ${import_picocolors5.default.dim("logical-replication readiness")}
281382
+ ${brand("ablo")} ${import_picocolors6.default.dim("connect --check")} ${import_picocolors6.default.dim("logical-replication readiness")}
280802
281383
  `);
280803
281384
  const failures = await probeAndReport(dbUrl);
280804
281385
  console.log();
280805
281386
  if (failures === 0) {
280806
- console.log(` ${import_picocolors5.default.green("\u2713")} Ready \u2014 Ablo can connect and tail this database's WAL.
281387
+ console.log(` ${import_picocolors6.default.green("\u2713")} Ready \u2014 Ablo can connect and tail this database's WAL.
280807
281388
  `);
280808
281389
  process.exit(0);
280809
281390
  }
280810
281391
  console.log(
280811
- ` ${import_picocolors5.default.red(`${failures} item${failures === 1 ? "" : "s"} to fix`)} ${import_picocolors5.default.dim(`\u2014 apply the fixes above, then re-run ${import_picocolors5.default.bold("ablo connect --check")}.`)}
281392
+ ` ${import_picocolors6.default.red(`${failures} item${failures === 1 ? "" : "s"} to fix`)} ${import_picocolors6.default.dim(`\u2014 apply the fixes above, then re-run ${import_picocolors6.default.bold("ablo connect --check")}.`)}
280812
281393
  `
280813
281394
  );
280814
281395
  process.exit(1);
@@ -280821,18 +281402,18 @@ async function runRegister() {
280821
281402
  const apiKey = resolveApiKey();
280822
281403
  if (!apiKey) {
280823
281404
  console.error(
280824
- import_picocolors5.default.red(" Not logged in.") + import_picocolors5.default.dim(` Run ${import_picocolors5.default.bold("ablo login")} (or set ${import_picocolors5.default.bold("ABLO_API_KEY")}) so Ablo knows which project to register this database for.`)
281405
+ import_picocolors6.default.red(" Not logged in.") + import_picocolors6.default.dim(` Run ${import_picocolors6.default.bold("ablo login")} (or set ${import_picocolors6.default.bold("ABLO_API_KEY")}) so Ablo knows which project to register this database for.`)
280825
281406
  );
280826
281407
  process.exit(1);
280827
281408
  }
280828
281409
  console.log(`
280829
- ${brand("ablo")} ${import_picocolors5.default.dim("connect --register")} ${import_picocolors5.default.dim("register this database for replication")}
281410
+ ${brand("ablo")} ${import_picocolors6.default.dim("connect --register")} ${import_picocolors6.default.dim("register this database for replication")}
280830
281411
  `);
280831
281412
  const failures = await probeAndReport(dbUrl);
280832
281413
  if (failures > 0) {
280833
281414
  console.log(
280834
281415
  `
280835
- ${import_picocolors5.default.red(`${failures} item${failures === 1 ? "" : "s"} to fix`)} ${import_picocolors5.default.dim("\u2014 a database that isn\u2019t replication-ready can\u2019t stream. Fix the above, then re-run.")}
281416
+ ${import_picocolors6.default.red(`${failures} item${failures === 1 ? "" : "s"} to fix`)} ${import_picocolors6.default.dim("\u2014 a database that isn\u2019t replication-ready can\u2019t stream. Fix the above, then re-run.")}
280836
281417
  `
280837
281418
  );
280838
281419
  process.exit(1);
@@ -280846,7 +281427,7 @@ async function runRegister() {
280846
281427
  body: JSON.stringify({ connectionString: dbUrl })
280847
281428
  });
280848
281429
  } catch (err) {
280849
- console.error(import_picocolors5.default.red(`
281430
+ console.error(import_picocolors6.default.red(`
280850
281431
  Couldn't reach ${apiUrl2}: ${err instanceof Error ? err.message : String(err)}
280851
281432
  `));
280852
281433
  process.exit(1);
@@ -280855,7 +281436,7 @@ async function runRegister() {
280855
281436
  const body2 = await res.json().catch(() => ({}));
280856
281437
  console.log(
280857
281438
  `
280858
- ${import_picocolors5.default.green("\u2713")} Registered${body2.host ? ` ${import_picocolors5.default.dim(body2.host)}` : ""}${body2.id ? ` ${import_picocolors5.default.dim(`(${body2.id})`)}` : ""} \u2014 Ablo will replicate this database on the next sync.
281439
+ ${import_picocolors6.default.green("\u2713")} Registered${body2.host ? ` ${import_picocolors6.default.dim(body2.host)}` : ""}${body2.id ? ` ${import_picocolors6.default.dim(`(${body2.id})`)}` : ""} \u2014 Ablo will replicate this database on the next sync.
280859
281440
  `
280860
281441
  );
280861
281442
  process.exit(0);
@@ -280863,28 +281444,28 @@ async function runRegister() {
280863
281444
  const body = await res.json().catch(() => ({}));
280864
281445
  const code = body.code ?? body.error?.code;
280865
281446
  const message = body.message ?? body.error?.message ?? `HTTP ${res.status}`;
280866
- console.error(import_picocolors5.default.red(`
281447
+ console.error(import_picocolors6.default.red(`
280867
281448
  Registration failed: ${message}`));
280868
281449
  if (code === "forbidden") {
280869
- console.error(import_picocolors5.default.dim(` Registering a database needs a ${import_picocolors5.default.bold("secret")} key (sk_\u2026). Run ${import_picocolors5.default.bold("ablo login")} for one.`));
281450
+ console.error(import_picocolors6.default.dim(` Registering a database needs a ${import_picocolors6.default.bold("secret")} key (sk_\u2026). Run ${import_picocolors6.default.bold("ablo login")} for one.`));
280870
281451
  } else if (code === "datasource_connection_unsupported") {
280871
281452
  console.error(
280872
- import_picocolors5.default.dim(` This deployment can\u2019t accept connection strings \u2014 use a self-hosted/hosted engine, or the signed endpoint fallback.`)
281453
+ import_picocolors6.default.dim(` This deployment can\u2019t accept connection strings \u2014 use a self-hosted/hosted engine, or the signed endpoint fallback.`)
280873
281454
  );
280874
281455
  } else if (code === "database_not_replication_ready") {
280875
281456
  for (const f of body.details?.failures ?? []) {
280876
- console.error(` ${import_picocolors5.default.red("\u2717")} ${import_picocolors5.default.bold(f.item ?? "item")}${f.actual ? import_picocolors5.default.dim(` (${f.actual})`) : ""}`);
280877
- if (f.fix) for (const line of f.fix.split("\n")) console.error(` ${import_picocolors5.default.red("\u2022")} ${line}`);
281457
+ console.error(` ${import_picocolors6.default.red("\u2717")} ${import_picocolors6.default.bold(f.item ?? "item")}${f.actual ? import_picocolors6.default.dim(` (${f.actual})`) : ""}`);
281458
+ if (f.fix) for (const line of f.fix.split("\n")) console.error(` ${import_picocolors6.default.red("\u2022")} ${line}`);
280878
281459
  }
280879
- console.error(import_picocolors5.default.dim(`
280880
- Apply the fixes, verify with ${import_picocolors5.default.bold("ablo connect --check")}, then re-run.`));
281460
+ console.error(import_picocolors6.default.dim(`
281461
+ Apply the fixes, verify with ${import_picocolors6.default.bold("ablo connect --check")}, then re-run.`));
280881
281462
  } else if (code === "database_unreachable") {
280882
- if (body.details?.reason) console.error(import_picocolors5.default.dim(` ${body.details.reason}`));
281463
+ if (body.details?.reason) console.error(import_picocolors6.default.dim(` ${body.details.reason}`));
280883
281464
  console.error(
280884
- import_picocolors5.default.dim(
281465
+ import_picocolors6.default.dim(
280885
281466
  ` Ablo's servers must be able to reach this database \u2014 a localhost or private-network
280886
281467
  Postgres can't be replicated by the hosted service. Use a reachable host (or a tunnel),
280887
- or the signed ${import_picocolors5.default.bold("dataSource()")} endpoint fallback.`
281468
+ or the signed ${import_picocolors6.default.bold("dataSource()")} endpoint fallback.`
280888
281469
  )
280889
281470
  );
280890
281471
  }
@@ -280900,27 +281481,27 @@ async function runAuditInfra() {
280900
281481
  artifacts = await auditTenantSyncInfra(sql);
280901
281482
  } catch (err) {
280902
281483
  const pg = err ?? {};
280903
- console.error(import_picocolors5.default.red(` Couldn't audit the database: ${pg.message ?? String(err)}`));
281484
+ console.error(import_picocolors6.default.red(` Couldn't audit the database: ${pg.message ?? String(err)}`));
280904
281485
  await sql.end({ timeout: 2 });
280905
281486
  process.exit(1);
280906
281487
  }
280907
281488
  await sql.end({ timeout: 2 });
280908
281489
  console.log(`
280909
- ${brand("ablo")} ${import_picocolors5.default.dim("connect --audit-infra")} ${import_picocolors5.default.dim("Stage 5 tenant DB sync-infra audit")}
281490
+ ${brand("ablo")} ${import_picocolors6.default.dim("connect --audit-infra")} ${import_picocolors6.default.dim("Stage 5 tenant DB sync-infra audit")}
280910
281491
  `);
280911
281492
  const present = artifacts.filter((a) => a.present);
280912
281493
  if (present.length === 0) {
280913
- console.log(` ${import_picocolors5.default.green("\u2713")} No deprecated Ablo sync infrastructure found in public.
281494
+ console.log(` ${import_picocolors6.default.green("\u2713")} No deprecated Ablo sync infrastructure found in public.
280914
281495
  `);
280915
281496
  process.exit(0);
280916
281497
  }
280917
281498
  for (const artifact of present) {
280918
281499
  const label = artifact.kind === "type" ? "type" : "relation";
280919
- console.log(` ${import_picocolors5.default.yellow("!")} ${label} ${import_picocolors5.default.bold(`public.${artifact.name}`)} exists`);
281500
+ console.log(` ${import_picocolors6.default.yellow("!")} ${label} ${import_picocolors6.default.bold(`public.${artifact.name}`)} exists`);
280920
281501
  }
280921
281502
  console.log(
280922
281503
  `
280923
- ${import_picocolors5.default.yellow(`${present.length} artifact${present.length === 1 ? "" : "s"} found`)} ` + import_picocolors5.default.dim("\u2014 do not drop automatically. Confirm the org/environment is log-authoritative, then follow ") + import_picocolors5.default.bold("docs/runbooks/wal-stage5-customer-db-infra-cleanup.md") + import_picocolors5.default.dim(".\n")
281504
+ ${import_picocolors6.default.yellow(`${present.length} artifact${present.length === 1 ? "" : "s"} found`)} ` + import_picocolors6.default.dim("\u2014 do not drop automatically. Confirm the org/environment is log-authoritative, then follow ") + import_picocolors6.default.bold("docs/runbooks/wal-stage5-customer-db-infra-cleanup.md") + import_picocolors6.default.dim(".\n")
280924
281505
  );
280925
281506
  process.exit(1);
280926
281507
  }
@@ -280929,7 +281510,7 @@ async function connect(argv) {
280929
281510
  try {
280930
281511
  args = parseConnectArgs(argv);
280931
281512
  } catch (err) {
280932
- console.error(import_picocolors5.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281513
+ console.error(import_picocolors6.default.red(` ${err instanceof Error ? err.message : String(err)}`));
280933
281514
  process.exit(1);
280934
281515
  }
280935
281516
  if (args.check) {
@@ -280963,7 +281544,7 @@ var CONNECT_USAGE = ` ablo connect \u2014 connect your database for the read pa
280963
281544
  init_cjs_shims();
280964
281545
  var import_fs6 = require("fs");
280965
281546
  var import_path4 = require("path");
280966
- var import_picocolors6 = __toESM(require_picocolors(), 1);
281547
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
280967
281548
  var import_schema4 = require("@abloatai/ablo/schema");
280968
281549
  var DEFAULT_SCHEMA_PATH3 = "ablo/schema.ts";
280969
281550
  var DEFAULT_EXPORT3 = "schema";
@@ -280995,7 +281576,7 @@ async function generate(argv) {
280995
281576
  try {
280996
281577
  args = parseGenerateArgs(argv);
280997
281578
  } catch (err) {
280998
- console.error(import_picocolors6.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281579
+ console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
280999
281580
  process.exit(1);
281000
281581
  }
281001
281582
  let source;
@@ -281004,18 +281585,18 @@ async function generate(argv) {
281004
281585
  const schemaJson = JSON.parse((0, import_schema4.serializeSchema)(schema));
281005
281586
  source = (0, import_schema4.generateTypes)(schemaJson);
281006
281587
  } catch (err) {
281007
- console.error(import_picocolors6.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281588
+ console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281008
281589
  process.exit(1);
281009
281590
  }
281010
281591
  const abs = (0, import_path4.resolve)(process.cwd(), args.out);
281011
281592
  (0, import_fs6.mkdirSync)((0, import_path4.dirname)(abs), { recursive: true });
281012
281593
  (0, import_fs6.writeFileSync)(abs, source);
281013
- console.log(` ${import_picocolors6.default.green("\u2713")} Generated types \u2192 ${import_picocolors6.default.bold(args.out)}`);
281594
+ console.log(` ${import_picocolors7.default.green("\u2713")} Generated types \u2192 ${import_picocolors7.default.bold(args.out)}`);
281014
281595
  }
281015
281596
 
281016
281597
  // src/cli/dev.ts
281017
281598
  init_cjs_shims();
281018
- var import_picocolors7 = __toESM(require_picocolors(), 1);
281599
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
281019
281600
  var import_fs7 = require("fs");
281020
281601
  var import_path5 = require("path");
281021
281602
  var import_schema5 = require("@abloatai/ablo/schema");
@@ -281053,25 +281634,25 @@ function classifyKey(apiKey, activeMode) {
281053
281634
  if (!apiKey) {
281054
281635
  return {
281055
281636
  ok: false,
281056
- reason: `No API key. Run ${import_picocolors7.default.bold("npx ablo login")} for the sandbox dev loop \u2014 or set ${import_picocolors7.default.bold("ABLO_API_KEY")} (${import_picocolors7.default.bold("sk_test_")} = sandbox; ${import_picocolors7.default.bold("sk_live_")} = deliberate production deploy). ` + import_picocolors7.default.dim(`Mode is currently '${activeMode}'.`)
281637
+ reason: `No API key. Run ${import_picocolors8.default.bold("npx ablo login")} for the sandbox dev loop \u2014 or set ${import_picocolors8.default.bold("ABLO_API_KEY")} (${import_picocolors8.default.bold("sk_test_")} = sandbox; ${import_picocolors8.default.bold("sk_live_")} = deliberate production deploy). ` + import_picocolors8.default.dim(`Mode is currently '${activeMode}'.`)
281057
281638
  };
281058
281639
  }
281059
281640
  if (apiKey.startsWith("sk_test_")) return { ok: true };
281060
281641
  if (apiKey.startsWith("sk_live_")) {
281061
281642
  return {
281062
281643
  ok: false,
281063
- reason: `Production schema deploys run one-shot: ${import_picocolors7.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")} (or ${import_picocolors7.default.bold("ablo mode production")}). ${import_picocolors7.default.bold("--watch")} is sandbox-only.`
281644
+ reason: `Production schema deploys run one-shot: ${import_picocolors8.default.bold("ABLO_API_KEY=sk_live_\u2026 npx ablo push")} (or ${import_picocolors8.default.bold("ablo mode production")}). ${import_picocolors8.default.bold("--watch")} is sandbox-only.`
281064
281645
  };
281065
281646
  }
281066
281647
  if (classifyCredentialKind(apiKey) === "restricted") {
281067
281648
  return {
281068
281649
  ok: false,
281069
- reason: `Restricted (${import_picocolors7.default.bold("rk_")}) keys can't push schema. Use a secret key: ${import_picocolors7.default.bold("sk_test_")} for the sandbox dev loop, or ${import_picocolors7.default.bold("sk_live_")} with ${import_picocolors7.default.bold("npx ablo push")} for a production deploy.`
281650
+ reason: `Restricted (${import_picocolors8.default.bold("rk_")}) keys can't push schema. Use a secret key: ${import_picocolors8.default.bold("sk_test_")} for the sandbox dev loop, or ${import_picocolors8.default.bold("sk_live_")} with ${import_picocolors8.default.bold("npx ablo push")} for a production deploy.`
281070
281651
  };
281071
281652
  }
281072
281653
  return {
281073
281654
  ok: false,
281074
- reason: `${import_picocolors7.default.bold("ABLO_API_KEY")} is not an Ablo key \u2014 expected ${import_picocolors7.default.bold("sk_test_\u2026")} (sandbox) or ${import_picocolors7.default.bold("sk_live_\u2026")} (production deploy via ${import_picocolors7.default.bold("npx ablo push")}).`
281655
+ reason: `${import_picocolors8.default.bold("ABLO_API_KEY")} is not an Ablo key \u2014 expected ${import_picocolors8.default.bold("sk_test_\u2026")} (sandbox) or ${import_picocolors8.default.bold("sk_live_\u2026")} (production deploy via ${import_picocolors8.default.bold("npx ablo push")}).`
281075
281656
  };
281076
281657
  }
281077
281658
  function wireEnvLocal(apiKey, cwd = process.cwd()) {
@@ -281081,7 +281662,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
281081
281662
  if (!(0, import_fs7.existsSync)(envPath)) {
281082
281663
  (0, import_fs7.writeFileSync)(envPath, `${line}
281083
281664
  `, { mode: 384 });
281084
- action = `Created ${import_picocolors7.default.bold(".env.local")} with ${import_picocolors7.default.bold("ABLO_API_KEY")}`;
281665
+ action = `Created ${import_picocolors8.default.bold(".env.local")} with ${import_picocolors8.default.bold("ABLO_API_KEY")}`;
281085
281666
  } else {
281086
281667
  const content = (0, import_fs7.readFileSync)(envPath, "utf8");
281087
281668
  const match = /^ABLO_API_KEY=(.*)$/m.exec(content);
@@ -281089,12 +281670,12 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
281089
281670
  if (!match) {
281090
281671
  (0, import_fs7.appendFileSync)(envPath, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}${line}
281091
281672
  `);
281092
- action = `Added ${import_picocolors7.default.bold("ABLO_API_KEY")} to ${import_picocolors7.default.bold(".env.local")}`;
281673
+ action = `Added ${import_picocolors8.default.bold("ABLO_API_KEY")} to ${import_picocolors8.default.bold(".env.local")}`;
281093
281674
  } else if (existing === apiKey) {
281094
- action = `${import_picocolors7.default.bold(".env.local")} already has this key`;
281675
+ action = `${import_picocolors8.default.bold(".env.local")} already has this key`;
281095
281676
  } else {
281096
281677
  (0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
281097
- action = `Updated ${import_picocolors7.default.bold("ABLO_API_KEY")} in ${import_picocolors7.default.bold(".env.local")} ${import_picocolors7.default.dim(`(was ${existing.slice(0, 12)}\u2026)`)}`;
281678
+ action = `Updated ${import_picocolors8.default.bold("ABLO_API_KEY")} in ${import_picocolors8.default.bold(".env.local")} ${import_picocolors8.default.dim(`(was ${existing.slice(0, 12)}\u2026)`)}`;
281098
281679
  }
281099
281680
  }
281100
281681
  const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
@@ -281108,7 +281689,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
281108
281689
  `}.env.local
281109
281690
  `
281110
281691
  );
281111
- gitignoreNote = ` Added ${import_picocolors7.default.bold(".env.local")} to ${import_picocolors7.default.bold(".gitignore")} so the key can't be committed.`;
281692
+ gitignoreNote = ` Added ${import_picocolors8.default.bold(".env.local")} to ${import_picocolors8.default.bold(".gitignore")} so the key can't be committed.`;
281112
281693
  }
281113
281694
  return `${action}.${gitignoreNote}`;
281114
281695
  }
@@ -281123,7 +281704,7 @@ async function runPush(schema, args) {
281123
281704
  if (ok) {
281124
281705
  return {
281125
281706
  ok: true,
281126
- message: body.unchanged ? `schema unchanged ${import_picocolors7.default.dim(`(v${body.version})`)}` : `schema pushed (sandbox) ${import_picocolors7.default.dim(`(v${body.version}, hash ${body.hash})`)}`
281707
+ message: body.unchanged ? `schema unchanged ${import_picocolors8.default.dim(`(v${body.version})`)}` : `schema pushed (sandbox) ${import_picocolors8.default.dim(`(v${body.version}, hash ${body.hash})`)}`
281127
281708
  };
281128
281709
  }
281129
281710
  if (status2 === 409) {
@@ -281133,33 +281714,33 @@ async function runPush(schema, args) {
281133
281714
  (s) => s.shadowed != null
281134
281715
  );
281135
281716
  const lines = [
281136
- import_picocolors7.default.bold("Incompatible schema change \u2014 not safe to apply as-is."),
281717
+ import_picocolors8.default.bold("Incompatible schema change \u2014 not safe to apply as-is."),
281137
281718
  "",
281138
- ...unexecutable.map((u2) => import_picocolors7.default.red(fmtSignal(u2))),
281139
- ...warnings.map((w2) => import_picocolors7.default.yellow(fmtSignal(w2))),
281719
+ ...unexecutable.map((u2) => import_picocolors8.default.red(fmtSignal(u2))),
281720
+ ...warnings.map((w2) => import_picocolors8.default.yellow(fmtSignal(w2))),
281140
281721
  "",
281141
281722
  ...hasShadowed ? [
281142
- import_picocolors7.default.dim(
281723
+ import_picocolors8.default.dim(
281143
281724
  " These models exist in the baseline above but not in your push. Sandbox readers"
281144
281725
  ),
281145
- import_picocolors7.default.dim(
281726
+ import_picocolors8.default.dim(
281146
281727
  " fall back to the production schema until you push your own, so applying this drops them."
281147
281728
  ),
281148
281729
  ""
281149
281730
  ] : [],
281150
- import_picocolors7.default.dim(
281151
- ` Fix: ${import_picocolors7.default.bold("ablo push --force")} to apply anyway, or ${import_picocolors7.default.bold("--rename old:new")} if you renamed a model.`
281731
+ import_picocolors8.default.dim(
281732
+ ` Fix: ${import_picocolors8.default.bold("ablo push --force")} to apply anyway, or ${import_picocolors8.default.bold("--rename old:new")} if you renamed a model.`
281152
281733
  )
281153
281734
  ];
281154
281735
  return { ok: false, message: lines.join("\n") };
281155
281736
  }
281156
281737
  if (status2 === 403) {
281157
281738
  const serverSays = body.message ?? body.reason;
281158
- const hint = body.code === "database_role_cannot_enforce_rls" ? `Run ${import_picocolors7.default.bold("npx ablo migrate")} \u2014 it creates the scoped role for you (your DB credential never leaves this machine).` : `Schema authoring needs a ${import_picocolors7.default.bold("sandbox")} key with ${import_picocolors7.default.bold("schema:push")} \u2014 manage keys at ${import_picocolors7.default.cyan("https://abloatai.com")}.`;
281739
+ const hint = body.code === "database_role_cannot_enforce_rls" ? `Run ${import_picocolors8.default.bold("npx ablo migrate")} \u2014 it creates the scoped role for you (your DB credential never leaves this machine).` : `Schema authoring needs a ${import_picocolors8.default.bold("sandbox")} key with ${import_picocolors8.default.bold("schema:push")} \u2014 manage keys at ${import_picocolors8.default.cyan("https://abloatai.com")}.`;
281159
281740
  return {
281160
281741
  ok: false,
281161
281742
  message: `${serverSays ?? "This key can't author schema (missing schema:push scope)."}
281162
- ` + import_picocolors7.default.dim(hint)
281743
+ ` + import_picocolors8.default.dim(hint)
281163
281744
  };
281164
281745
  }
281165
281746
  return { ok: false, message: `Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}` };
@@ -281169,25 +281750,25 @@ async function dev(argv) {
281169
281750
  try {
281170
281751
  args = parseDevArgs(argv);
281171
281752
  } catch (err) {
281172
- console.error(import_picocolors7.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281753
+ console.error(import_picocolors8.default.red(` ${err instanceof Error ? err.message : String(err)}`));
281173
281754
  process.exit(1);
281174
281755
  }
281175
281756
  if (!args.apiKey) args.apiKey = resolveEffectiveApiKey("sandbox").key;
281176
281757
  const key = classifyKey(args.apiKey, getMode());
281177
281758
  if (!key.ok) {
281178
- console.error(import_picocolors7.default.red(` ${key.reason}`));
281759
+ console.error(import_picocolors8.default.red(` ${key.reason}`));
281179
281760
  process.exit(1);
281180
281761
  }
281181
281762
  console.log(`
281182
- ${brand("ablo")} ${import_picocolors7.default.dim("push")} ${import_picocolors7.default.dim("(sandbox)")}
281763
+ ${brand("ablo")} ${import_picocolors8.default.dim("push")} ${import_picocolors8.default.dim("(sandbox)")}
281183
281764
  `);
281184
281765
  const schema = await loadSchema(args.schemaPath, args.exportName);
281185
281766
  const modelCount = Object.keys(schema.models).length;
281186
281767
  console.log(
281187
- ` ${import_picocolors7.default.dim("schema")} ${import_picocolors7.default.bold(args.schemaPath)} ${import_picocolors7.default.dim(`(${modelCount} models, hash ${(0, import_schema5.schemaHash)(schema)})`)}`
281768
+ ` ${import_picocolors8.default.dim("schema")} ${import_picocolors8.default.bold(args.schemaPath)} ${import_picocolors8.default.dim(`(${modelCount} models, hash ${(0, import_schema5.schemaHash)(schema)})`)}`
281188
281769
  );
281189
- console.log(` ${import_picocolors7.default.dim("key")} ${args.apiKey.slice(0, 12)}\u2026`);
281190
- console.log(` ${import_picocolors7.default.dim("api")} ${args.url}
281770
+ console.log(` ${import_picocolors8.default.dim("key")} ${args.apiKey.slice(0, 12)}\u2026`);
281771
+ console.log(` ${import_picocolors8.default.dim("api")} ${args.url}
281191
281772
  `);
281192
281773
  const s = Y2();
281193
281774
  s.start("Pushing schema definition (sandbox)");
@@ -281196,16 +281777,16 @@ async function dev(argv) {
281196
281777
  if (!first.ok) process.exit(1);
281197
281778
  if (process.env.ABLO_API_KEY) {
281198
281779
  console.log(`
281199
- ${import_picocolors7.default.green("\u2713")} ${import_picocolors7.default.bold("ABLO_API_KEY")} is set in this shell \u2014 the SDK reads it directly.`);
281780
+ ${import_picocolors8.default.green("\u2713")} ${import_picocolors8.default.bold("ABLO_API_KEY")} is set in this shell \u2014 the SDK reads it directly.`);
281200
281781
  } else {
281201
281782
  console.log(`
281202
- ${import_picocolors7.default.green("\u2713")} ${wireEnvLocal(args.apiKey)}`);
281203
- console.log(` ${import_picocolors7.default.dim("Frameworks load it automatically; plain Node: node --env-file=.env.local app.ts")}`);
281783
+ ${import_picocolors8.default.green("\u2713")} ${wireEnvLocal(args.apiKey)}`);
281784
+ console.log(` ${import_picocolors8.default.dim("Frameworks load it automatically; plain Node: node --env-file=.env.local app.ts")}`);
281204
281785
  }
281205
281786
  console.log(` Your app is wired for the sandbox.`);
281206
281787
  if (!args.watch) return;
281207
281788
  const abs = (0, import_path5.resolve)(process.cwd(), args.schemaPath);
281208
- console.log(` ${import_picocolors7.default.dim(`watching ${args.schemaPath} \u2026 (Ctrl-C to stop)`)}
281789
+ console.log(` ${import_picocolors8.default.dim(`watching ${args.schemaPath} \u2026 (Ctrl-C to stop)`)}
281209
281790
  `);
281210
281791
  let timer2 = null;
281211
281792
  let pushing = false;
@@ -281225,7 +281806,7 @@ async function dev(argv) {
281225
281806
  const r2 = await runPush(next, args);
281226
281807
  s2.stop(r2.message, r2.ok ? 0 : 1);
281227
281808
  } catch (err) {
281228
- s2.stop(import_picocolors7.default.red(`schema reload failed: ${err instanceof Error ? err.message : String(err)}`), 1);
281809
+ s2.stop(import_picocolors8.default.red(`schema reload failed: ${err instanceof Error ? err.message : String(err)}`), 1);
281229
281810
  } finally {
281230
281811
  pushing = false;
281231
281812
  }
@@ -281233,7 +281814,7 @@ async function dev(argv) {
281233
281814
  const stop = () => {
281234
281815
  watcher.close();
281235
281816
  console.log(`
281236
- ${import_picocolors7.default.dim("stopped.")}`);
281817
+ ${import_picocolors8.default.dim("stopped.")}`);
281237
281818
  process.exit(0);
281238
281819
  };
281239
281820
  process.on("SIGINT", stop);
@@ -281245,7 +281826,7 @@ async function dev(argv) {
281245
281826
  // src/cli/login.ts
281246
281827
  init_cjs_shims();
281247
281828
  var import_child_process2 = require("child_process");
281248
- var import_picocolors8 = __toESM(require_picocolors(), 1);
281829
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
281249
281830
  var CLIENT_ID = "ablo-cli";
281250
281831
  var stripSlash = (u2) => u2.replace(/\/+$/, "");
281251
281832
  var AUTH_URL = stripSlash(process.env.ABLO_AUTH_URL ?? "https://auth.abloatai.com");
@@ -281303,9 +281884,9 @@ async function deviceLogin(argv, deps = {}) {
281303
281884
  const code = await codeRes.json();
281304
281885
  const approvePath = `/cli?user_code=${code.user_code}`;
281305
281886
  const url = account === "signup" ? `${DASHBOARD_URL}/signup?next=${encodeURIComponent(approvePath)}` : `${DASHBOARD_URL}${approvePath}`;
281306
- Me(`${import_picocolors8.default.bold(code.user_code)}
281887
+ Me(`${import_picocolors9.default.bold(code.user_code)}
281307
281888
 
281308
- ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281889
+ ${import_picocolors9.default.dim(url)}`, "Approve in your browser");
281309
281890
  openUrl(url);
281310
281891
  const s = Y2();
281311
281892
  s.start("Waiting for approval\u2026");
@@ -281372,7 +281953,7 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281372
281953
  if (reason) M2.error(reason);
281373
281954
  else if (provRes) M2.error(`Key provisioning returned ${provRes.status} from ${DASHBOARD_URL}/api/cli/provision-key.`);
281374
281955
  M2.error(
281375
- `The browser approval succeeded but the key handoff failed. Try again, or grab a ${import_picocolors8.default.bold("sk_test_")} key from the dashboard and set ${import_picocolors8.default.bold("ABLO_API_KEY")}.`
281956
+ `The browser approval succeeded but the key handoff failed. Try again, or grab a ${import_picocolors9.default.bold("sk_test_")} key from the dashboard and set ${import_picocolors9.default.bold("ABLO_API_KEY")}.`
281376
281957
  );
281377
281958
  process.exit(1);
281378
281959
  }
@@ -281392,9 +281973,9 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281392
281973
  { mode: "sandbox", activeProject: prov.project ?? void 0 }
281393
281974
  );
281394
281975
  s.stop(`Saved keys to ${path}`);
281395
- const where = prov.project ? ` ${import_picocolors8.default.dim(`(project ${prov.project.slug})`)}` : "";
281976
+ const where = prov.project ? ` ${import_picocolors9.default.dim(`(project ${prov.project.slug})`)}` : "";
281396
281977
  Se(
281397
- `${import_picocolors8.default.green("\u2713")} Logged in ${import_picocolors8.default.dim("(sandbox)")}${where}. Run ${import_picocolors8.default.bold("npx ablo push")} to push your schema.`
281978
+ `${import_picocolors9.default.green("\u2713")} Logged in ${import_picocolors9.default.dim("(sandbox)")}${where}. Run ${import_picocolors9.default.bold("npx ablo push")} to push your schema.`
281398
281979
  );
281399
281980
  }
281400
281981
  async function login(argv = [], deps = {}) {
@@ -281403,20 +281984,20 @@ async function login(argv = [], deps = {}) {
281403
281984
  function logout() {
281404
281985
  const removed = clearCredential();
281405
281986
  if (removed) {
281406
- console.log(` ${import_picocolors8.default.green("\u2713")} Logged out ${import_picocolors8.default.dim(`(credentials removed from ${configDir()})`)}`);
281987
+ console.log(` ${import_picocolors9.default.green("\u2713")} Logged out ${import_picocolors9.default.dim(`(credentials removed from ${configDir()})`)}`);
281407
281988
  } else {
281408
- console.log(` ${import_picocolors8.default.dim("\u25CB")} Not logged in \u2014 nothing to remove.`);
281989
+ console.log(` ${import_picocolors9.default.dim("\u25CB")} Not logged in \u2014 nothing to remove.`);
281409
281990
  }
281410
281991
  if (process.env.ABLO_API_KEY) {
281411
281992
  console.log(
281412
- import_picocolors8.default.dim(` Note: ${import_picocolors8.default.bold("ABLO_API_KEY")} is still set in this shell and takes precedence.`)
281993
+ import_picocolors9.default.dim(` Note: ${import_picocolors9.default.bold("ABLO_API_KEY")} is still set in this shell and takes precedence.`)
281413
281994
  );
281414
281995
  }
281415
281996
  }
281416
281997
 
281417
281998
  // src/cli/mode.ts
281418
281999
  init_cjs_shims();
281419
- var import_picocolors9 = __toESM(require_picocolors(), 1);
282000
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
281420
282001
  var PREFIX = { sandbox: "sk_test_", production: "rk_live_" };
281421
282002
  function hintFor(m2, current) {
281422
282003
  const parts = [];
@@ -281426,10 +282007,10 @@ function hintFor(m2, current) {
281426
282007
  }
281427
282008
  function apply(m2) {
281428
282009
  setMode(m2);
281429
- console.log(` ${import_picocolors9.default.green("\u2713")} now in ${import_picocolors9.default.bold(m2)}`);
282010
+ console.log(` ${import_picocolors10.default.green("\u2713")} now in ${import_picocolors10.default.bold(m2)}`);
281430
282011
  if (!getKeyEntry(m2)) {
281431
282012
  console.log(
281432
- import_picocolors9.default.dim(` No ${m2} key stored \u2014 run ${import_picocolors9.default.bold("ablo login")} or ${import_picocolors9.default.bold(`ablo login --api-key ${PREFIX[m2]}\u2026`)}.`)
282013
+ import_picocolors10.default.dim(` No ${m2} key stored \u2014 run ${import_picocolors10.default.bold("ablo login")} or ${import_picocolors10.default.bold(`ablo login --api-key ${PREFIX[m2]}\u2026`)}.`)
281433
282014
  );
281434
282015
  }
281435
282016
  }
@@ -281442,14 +282023,14 @@ async function mode(argv) {
281442
282023
  }
281443
282024
  if (arg) {
281444
282025
  console.error(
281445
- import_picocolors9.default.red(` unknown mode: ${arg}`) + import_picocolors9.default.dim(` (expected ${import_picocolors9.default.bold("sandbox")} or ${import_picocolors9.default.bold("production")})`)
282026
+ import_picocolors10.default.red(` unknown mode: ${arg}`) + import_picocolors10.default.dim(` (expected ${import_picocolors10.default.bold("sandbox")} or ${import_picocolors10.default.bold("production")})`)
281446
282027
  );
281447
282028
  process.exit(1);
281448
282029
  }
281449
282030
  const current = getMode();
281450
282031
  if (!process.stdin.isTTY || process.env.CI) {
281451
282032
  console.error(
281452
- import_picocolors9.default.red(" `ablo mode` needs an argument without a TTY: ") + import_picocolors9.default.bold("ablo mode sandbox") + import_picocolors9.default.dim(" | ") + import_picocolors9.default.bold("ablo mode production") + import_picocolors9.default.dim(` (current: ${current})`)
282033
+ import_picocolors10.default.red(" `ablo mode` needs an argument without a TTY: ") + import_picocolors10.default.bold("ablo mode sandbox") + import_picocolors10.default.dim(" | ") + import_picocolors10.default.bold("ablo mode production") + import_picocolors10.default.dim(` (current: ${current})`)
281453
282034
  );
281454
282035
  process.exit(1);
281455
282036
  }
@@ -281468,206 +282049,6 @@ async function mode(argv) {
281468
282049
  apply(selected);
281469
282050
  }
281470
282051
 
281471
- // src/cli/projects.ts
281472
- init_cjs_shims();
281473
- var import_picocolors10 = __toESM(require_picocolors(), 1);
281474
- function apiUrl() {
281475
- return (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
281476
- }
281477
- function requireKey() {
281478
- const apiKey = resolveApiKey();
281479
- if (!apiKey) {
281480
- console.error(
281481
- import_picocolors10.default.red(" No API key.") + import_picocolors10.default.dim(
281482
- ` Run ${import_picocolors10.default.bold("npx ablo login")} \u2014 or set ${import_picocolors10.default.bold("ABLO_API_KEY")} (${import_picocolors10.default.bold("sk_test_")} = sandbox; ${import_picocolors10.default.bold("sk_live_")} = production).`
281483
- )
281484
- );
281485
- process.exit(1);
281486
- }
281487
- return apiKey;
281488
- }
281489
- async function request(path, apiKey, init2 = {}) {
281490
- const res = await fetch(`${apiUrl()}${path}`, {
281491
- method: init2.method ?? "GET",
281492
- headers: {
281493
- "content-type": "application/json",
281494
- authorization: `Bearer ${apiKey}`
281495
- },
281496
- ...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
281497
- });
281498
- let body = {};
281499
- try {
281500
- body = await res.json();
281501
- } catch {
281502
- }
281503
- return { status: res.status, body };
281504
- }
281505
- async function listProjects(apiKey) {
281506
- try {
281507
- const { status: status2, body } = await request("/api/v1/projects", apiKey);
281508
- if (status2 !== 200 || !Array.isArray(body.data)) return null;
281509
- return body.data;
281510
- } catch {
281511
- return null;
281512
- }
281513
- }
281514
- async function fetchProjects() {
281515
- const all = await listProjects(requireKey());
281516
- if (!all) {
281517
- console.error(import_picocolors10.default.red(" Could not list projects \u2014 is the API reachable and the key valid?"));
281518
- process.exit(1);
281519
- }
281520
- return all;
281521
- }
281522
- function projectSlugFromPackageName(name) {
281523
- if (typeof name !== "string") return void 0;
281524
- const slug = name.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
281525
- if (!slug || slug === "default") return void 0;
281526
- return slug;
281527
- }
281528
- async function ensureProject(slug, name) {
281529
- const apiKey = resolveApiKey();
281530
- if (!apiKey) return null;
281531
- try {
281532
- const { status: status2, body } = await request("/api/v1/projects", apiKey, {
281533
- method: "POST",
281534
- body: { slug, ...name ? { name } : {} }
281535
- });
281536
- if (status2 === 201) {
281537
- const created = body;
281538
- setActiveProject({ id: created.id, slug: created.slug });
281539
- return { id: created.id, slug: created.slug, created: true };
281540
- }
281541
- if (body.code === "project_slug_taken") {
281542
- const all = await listProjects(apiKey);
281543
- const existing = all?.find((p2) => p2.slug === slug);
281544
- if (!existing) return null;
281545
- setActiveProject({ id: existing.id, slug: existing.slug });
281546
- return { id: existing.id, slug: existing.slug, created: false };
281547
- }
281548
- return null;
281549
- } catch {
281550
- return null;
281551
- }
281552
- }
281553
- function printList(projects2) {
281554
- const active = getActiveProject();
281555
- for (const p2 of projects2) {
281556
- const isActive = active ? active.id === p2.id : p2.default;
281557
- const marker = isActive ? import_picocolors10.default.green("\u25CF") : import_picocolors10.default.dim("\u25CB");
281558
- const tags = [p2.default ? import_picocolors10.default.dim("default") : "", isActive ? import_picocolors10.default.green("active") : ""].filter(Boolean).join(import_picocolors10.default.dim(", "));
281559
- console.log(
281560
- ` ${marker} ${p2.slug.padEnd(20)} ${import_picocolors10.default.dim(p2.id)}${tags ? ` ${tags}` : ""}`
281561
- );
281562
- }
281563
- }
281564
- async function projects(argv) {
281565
- const sub = argv[0];
281566
- if (sub === "list" || sub === void 0) {
281567
- console.log(`
281568
- ${brand("ablo")} ${import_picocolors10.default.dim("projects")}
281569
- `);
281570
- printList(await fetchProjects());
281571
- console.log();
281572
- return;
281573
- }
281574
- if (sub === "create") {
281575
- const slug = argv[1];
281576
- if (!slug || slug.startsWith("-")) {
281577
- console.error(import_picocolors10.default.red(' usage: ablo projects create <slug> [--name "Display Name"]'));
281578
- process.exit(1);
281579
- }
281580
- const nameIdx = argv.indexOf("--name");
281581
- const name = nameIdx >= 0 ? argv[nameIdx + 1] : void 0;
281582
- const { status: status2, body } = await request("/api/v1/projects", requireKey(), {
281583
- method: "POST",
281584
- body: { slug, ...name ? { name } : {} }
281585
- });
281586
- if (status2 !== 201) {
281587
- console.error(
281588
- import_picocolors10.default.red(` Create failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
281589
- );
281590
- if (body.code === "project_slug_taken") {
281591
- console.error(import_picocolors10.default.dim(` Pick another slug, or switch to it: ${import_picocolors10.default.bold(`ablo projects use ${slug}`)}`));
281592
- }
281593
- process.exit(1);
281594
- }
281595
- const created = body;
281596
- console.log(` ${import_picocolors10.default.green("\u2713")} Created project ${import_picocolors10.default.bold(created.slug)} ${import_picocolors10.default.dim(`(${created.id})`)}`);
281597
- console.log(
281598
- import_picocolors10.default.dim(` Make it active with ${import_picocolors10.default.bold(`ablo projects use ${created.slug}`)}; mint its keys in the dashboard.`)
281599
- );
281600
- return;
281601
- }
281602
- if (sub === "rename") {
281603
- const ref = argv[1];
281604
- const name = argv.slice(2).join(" ").trim();
281605
- if (!ref || ref.startsWith("-") || !name) {
281606
- console.error(import_picocolors10.default.red(" usage: ablo projects rename <slug|id> <new name>"));
281607
- process.exit(1);
281608
- }
281609
- const all = await fetchProjects();
281610
- const target = all.find((p2) => p2.slug === ref || p2.id === ref);
281611
- if (!target) {
281612
- console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
281613
- process.exit(1);
281614
- }
281615
- if (target.default) {
281616
- console.error(import_picocolors10.default.red(" The default project cannot be renamed."));
281617
- process.exit(1);
281618
- }
281619
- const { status: status2, body } = await request(`/api/v1/projects/${target.id}`, requireKey(), {
281620
- method: "PATCH",
281621
- body: { name }
281622
- });
281623
- if (status2 !== 200) {
281624
- console.error(
281625
- import_picocolors10.default.red(` Rename failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
281626
- );
281627
- process.exit(1);
281628
- }
281629
- const updated = body;
281630
- console.log(
281631
- ` ${import_picocolors10.default.green("\u2713")} Renamed ${import_picocolors10.default.bold(updated.slug)} \u2192 ${import_picocolors10.default.bold(updated.name ?? updated.slug)} ${import_picocolors10.default.dim(`(${updated.id})`)}`
281632
- );
281633
- return;
281634
- }
281635
- if (sub === "use") {
281636
- const ref = argv[1];
281637
- if (!ref) {
281638
- console.error(import_picocolors10.default.red(" usage: ablo projects use <slug|id|default>"));
281639
- process.exit(1);
281640
- }
281641
- const all = await fetchProjects();
281642
- const target = all.find((p2) => p2.slug === ref || p2.id === ref);
281643
- if (!target) {
281644
- console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
281645
- process.exit(1);
281646
- }
281647
- if (target.default) {
281648
- setActiveProject(void 0);
281649
- console.log(` ${import_picocolors10.default.green("\u2713")} now targeting the ${import_picocolors10.default.bold("default")} project`);
281650
- } else {
281651
- setActiveProject({ id: target.id, slug: target.slug });
281652
- console.log(` ${import_picocolors10.default.green("\u2713")} now targeting project ${import_picocolors10.default.bold(target.slug)} ${import_picocolors10.default.dim(`(${target.id})`)}`);
281653
- }
281654
- const guard = guardActiveProjectKey();
281655
- if (!guard.ok) {
281656
- const loginCmd = guard.activeProfile === DEFAULT_PROFILE ? "ablo login" : `ablo login --project ${guard.activeProfile}`;
281657
- console.log(
281658
- import_picocolors10.default.dim(` No key stored for this project yet \u2014 run ${import_picocolors10.default.bold(loginCmd)} to mint one.`)
281659
- );
281660
- }
281661
- return;
281662
- }
281663
- console.error(
281664
- import_picocolors10.default.red(` unknown subcommand: ${sub}`) + import_picocolors10.default.dim(
281665
- ` (expected ${import_picocolors10.default.bold("list")}, ${import_picocolors10.default.bold("create")}, ${import_picocolors10.default.bold("rename")}, or ${import_picocolors10.default.bold("use")})`
281666
- )
281667
- );
281668
- process.exit(1);
281669
- }
281670
-
281671
282052
  // src/cli/status.ts
281672
282053
  init_cjs_shims();
281673
282054
  var import_picocolors11 = __toESM(require_picocolors(), 1);
@@ -281757,17 +282138,44 @@ function formatConflict(conflict) {
281757
282138
  const parts = ["user", "agent", "system"].flatMap((k3) => conflict[k3] ? [`${k3}:${conflict[k3]}`] : []);
281758
282139
  return parts.length ? `{${parts.join(",")}}` : "";
281759
282140
  }
282141
+ function printTargetLines(target, localProject) {
282142
+ const confirmed = target?.confirmed ?? null;
282143
+ if (confirmed?.organizationId) {
282144
+ console.log(` ${import_picocolors11.default.dim("org")} ${import_picocolors11.default.dim(confirmed.organizationId)}`);
282145
+ }
282146
+ let projectLine;
282147
+ if (confirmed?.project) {
282148
+ const p2 = confirmed.project;
282149
+ projectLine = p2.isDefault ? `${import_picocolors11.default.bold("default")} ${import_picocolors11.default.dim("(org-default)")}` : `${import_picocolors11.default.bold(p2.slug)} ${import_picocolors11.default.dim(`(${p2.id})`)}`;
282150
+ } else if (confirmed) {
282151
+ projectLine = `${import_picocolors11.default.bold("default")} ${import_picocolors11.default.dim("(org-default)")}`;
282152
+ } else if (localProject) {
282153
+ projectLine = `${import_picocolors11.default.bold(localProject.slug)} ${import_picocolors11.default.dim(`(${localProject.id})`)} ${import_picocolors11.default.yellow("(unconfirmed)")}`;
282154
+ } else {
282155
+ projectLine = `${import_picocolors11.default.bold("default")} ${target ? import_picocolors11.default.yellow("(unconfirmed)") : import_picocolors11.default.dim("(org-default)")}`;
282156
+ }
282157
+ console.log(` ${import_picocolors11.default.dim("project")} ${projectLine}`);
282158
+ const env = confirmed?.environment ?? target?.keyEnv ?? null;
282159
+ if (env) {
282160
+ const suffix = confirmed ? "" : ` ${import_picocolors11.default.yellow("(unconfirmed)")}`;
282161
+ console.log(` ${import_picocolors11.default.dim("env")} ${import_picocolors11.default.bold(env)}${suffix}`);
282162
+ }
282163
+ for (const m2 of target?.mismatches ?? []) {
282164
+ console.log(` ${import_picocolors11.default.yellow("\u26A0")} ${import_picocolors11.default.yellow(describeMismatch(m2))}`);
282165
+ }
282166
+ }
281760
282167
  async function status(args = []) {
281761
282168
  const apiUrl2 = (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
281762
282169
  const cfg = readConfig();
281763
282170
  const mode2 = getMode();
282171
+ const effective = resolveEffectiveApiKey();
282172
+ const target = effective.key ? await resolveTarget({ url: apiUrl2, apiKey: effective.key, keySource: effective.source ?? "stored" }) : null;
281764
282173
  if (args.includes("--json")) {
281765
282174
  const entry = getKeyEntry(mode2);
281766
282175
  const key2 = describeEffectiveKey(mode2, process.env.ABLO_API_KEY, entry);
281767
282176
  const plan2 = resolvePushPlan();
281768
282177
  const activeProject2 = getActiveProject();
281769
- const effective2 = resolveEffectiveApiKey();
281770
- const pushed = await fetchPushedSchema(apiUrl2, effective2.key);
282178
+ const pushed = await fetchPushedSchema(apiUrl2, effective.key);
281771
282179
  const out = {
281772
282180
  mode: mode2,
281773
282181
  // The locally-active project (`ablo projects use`); null = org-default.
@@ -281776,8 +282184,8 @@ async function status(args = []) {
281776
282184
  // 'env' | '.env.local' | '.env' | 'stored'. Key confusion is a common
281777
282185
  // source of trouble, and the source is usually the answer.
281778
282186
  effectiveKey: {
281779
- prefix: effective2.key ? effective2.key.slice(0, 12) : null,
281780
- source: effective2.source
282187
+ prefix: effective.key ? effective.key.slice(0, 12) : null,
282188
+ source: effective.source
281781
282189
  },
281782
282190
  keyPrefix: key2.keyPrefix,
281783
282191
  keySource: key2.keySource,
@@ -281787,6 +282195,19 @@ async function status(args = []) {
281787
282195
  keyMatchesStoredActiveKey: key2.keyMatchesStoredActiveKey,
281788
282196
  keyMismatch: key2.keyMismatch,
281789
282197
  organizationId: entry?.organizationId ?? null,
282198
+ // The SERVER-CONFIRMED plane this key resolves to — the authoritative
282199
+ // answer to "where does a push land", independent of the local
282200
+ // `project`/`mode` preferences above. Null when the server didn't answer.
282201
+ confirmedTarget: target?.confirmed ? {
282202
+ organizationId: target.confirmed.organizationId,
282203
+ environment: target.confirmed.environment,
282204
+ project: target.confirmed.project,
282205
+ projectId: target.confirmed.projectId,
282206
+ sandboxId: target.confirmed.sandboxId
282207
+ } : null,
282208
+ // Divergences between local intent and the confirmed plane (project not
282209
+ // selected, mode not the key's environment). Empty when aligned.
282210
+ mismatches: target?.mismatches ?? [],
281790
282211
  // What `ablo push` would do right now — the answer to "why did push
281791
282212
  // demand a different key".
281792
282213
  push: {
@@ -281800,6 +282221,7 @@ async function status(args = []) {
281800
282221
  schema: pushed ? {
281801
282222
  active: pushed.active,
281802
282223
  version: pushed.version ?? null,
282224
+ hash: pushed.hash ?? null,
281803
282225
  pushedAt: pushed.pushedAt ?? null,
281804
282226
  models: pushed.models
281805
282227
  } : null,
@@ -281812,7 +282234,6 @@ async function status(args = []) {
281812
282234
  console.log(`
281813
282235
  ${brand("ablo")} ${import_picocolors11.default.dim("status")}
281814
282236
  `);
281815
- const effective = resolveEffectiveApiKey();
281816
282237
  if (effective.key && effective.source && effective.source !== "stored") {
281817
282238
  const label = effective.source === "env" ? "ABLO_API_KEY env" : effective.source;
281818
282239
  console.log(
@@ -281828,9 +282249,7 @@ async function status(args = []) {
281828
282249
  console.log(` ${import_picocolors11.default.yellow("!")} ${import_picocolors11.default.yellow(key.keyMismatch.message)}`);
281829
282250
  }
281830
282251
  const activeProject = getActiveProject();
281831
- console.log(
281832
- ` ${import_picocolors11.default.dim("project")} ${activeProject ? `${import_picocolors11.default.bold(activeProject.slug)} ${import_picocolors11.default.dim(`(${activeProject.id})`)}` : import_picocolors11.default.bold("default")}`
281833
- );
282252
+ printTargetLines(target, activeProject);
281834
282253
  for (const m2 of ["sandbox", "production"]) {
281835
282254
  const entry = getKeyEntry(m2);
281836
282255
  const marker = m2 === mode2 ? import_picocolors11.default.green("\u25CF") : import_picocolors11.default.dim("\u25CB");
@@ -281841,8 +282260,7 @@ async function status(args = []) {
281841
282260
  console.log(` ${marker} ${m2.padEnd(10)} ${import_picocolors11.default.dim("\u2014 no key")}`);
281842
282261
  }
281843
282262
  }
281844
- const org = activeEntry?.organizationId;
281845
- if (org) console.log(` ${import_picocolors11.default.dim("org")} ${org}`);
282263
+ const org = target?.confirmed?.organizationId ?? activeEntry?.organizationId;
281846
282264
  const plan = resolvePushPlan();
281847
282265
  console.log(
281848
282266
  ` ${import_picocolors11.default.dim("push")} ${plan.apiKey ? `${import_picocolors11.default.bold(plan.flow)} ${import_picocolors11.default.dim(`with ${plan.apiKey.slice(0, 12)}\u2026 (${plan.source})`)}` : `${import_picocolors11.default.bold(plan.flow)} ${import_picocolors11.default.yellow("\u2014 no credential")} ${import_picocolors11.default.dim(`(run ${import_picocolors11.default.bold("ablo login")} or set ${import_picocolors11.default.bold("ABLO_API_KEY")})`)}`}`
@@ -281856,7 +282274,8 @@ async function status(args = []) {
281856
282274
  if (pushed?.active) {
281857
282275
  const when = pushed.pushedAt ? ` ${import_picocolors11.default.dim(`@ ${pushed.pushedAt.slice(0, 10)}`)}` : "";
281858
282276
  const ver = pushed.version != null ? ` ${import_picocolors11.default.dim(`(rev ${pushed.version})`)}` : "";
281859
- console.log(` ${import_picocolors11.default.dim("schema")} ${import_picocolors11.default.bold(`${pushed.models.length} models pushed`)}${ver}${when}`);
282277
+ const hashLabel = pushed.hash ? ` ${import_picocolors11.default.dim(`hash ${pushed.hash}`)}` : "";
282278
+ console.log(` ${import_picocolors11.default.dim("schema")} ${import_picocolors11.default.bold(`${pushed.models.length} models pushed`)}${ver}${hashLabel}${when}`);
281860
282279
  for (const m2 of pushed.models) {
281861
282280
  const tn = m2.typename === m2.key ? import_picocolors11.default.dim(`typename=${m2.typename}`) : import_picocolors11.default.yellow(`typename=${m2.typename}`);
281862
282281
  const conflict = formatConflict(m2.conflict);