@abloatai/ablo 0.28.0 → 0.29.1

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 (96) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/Database.js +9 -6
  3. package/dist/SyncClient.d.ts +2 -2
  4. package/dist/SyncClient.js +22 -10
  5. package/dist/adapters/inMemoryStorage.js +18 -14
  6. package/dist/ai-sdk/coordinatedTool.d.ts +15 -2
  7. package/dist/auth/schemas.d.ts +6 -0
  8. package/dist/auth/schemas.js +7 -0
  9. package/dist/cli.cjs +815 -400
  10. package/dist/client/Ablo.d.ts +29 -23
  11. package/dist/client/Ablo.js +8 -196
  12. package/dist/client/claimHeartbeatLoop.d.ts +1 -1
  13. package/dist/client/claimHeartbeatLoop.js +1 -1
  14. package/dist/client/createInternalComponents.d.ts +4 -2
  15. package/dist/client/createInternalComponents.js +3 -1
  16. package/dist/client/durableWrites.d.ts +21 -0
  17. package/dist/client/durableWrites.js +46 -0
  18. package/dist/client/httpClient.d.ts +19 -45
  19. package/dist/client/httpClient.js +104 -26
  20. package/dist/client/httpTransport.d.ts +8 -0
  21. package/dist/client/{ApiClient.js → httpTransport.js} +78 -291
  22. package/dist/client/options.d.ts +43 -19
  23. package/dist/client/options.js +3 -2
  24. package/dist/client/resourceTypes.d.ts +9 -85
  25. package/dist/client/resourceTypes.js +1 -1
  26. package/dist/client/sessionMint.d.ts +5 -6
  27. package/dist/client/sessionMint.js +4 -5
  28. package/dist/client/validateAbloOptions.js +3 -3
  29. package/dist/coordination/schema.d.ts +30 -0
  30. package/dist/coordination/schema.js +14 -0
  31. package/dist/core/index.d.ts +1 -1
  32. package/dist/errors.d.ts +4 -40
  33. package/dist/errors.js +5 -5
  34. package/dist/index.d.ts +14 -8
  35. package/dist/index.js +7 -5
  36. package/dist/interfaces/index.d.ts +3 -0
  37. package/dist/mutators/defineMutators.d.ts +18 -0
  38. package/dist/mutators/defineMutators.js +4 -8
  39. package/dist/react/index.d.ts +1 -1
  40. package/dist/schema/index.d.ts +1 -1
  41. package/dist/schema/index.js +1 -1
  42. package/dist/schema/select.d.ts +15 -0
  43. package/dist/schema/select.js +27 -3
  44. package/dist/source/connector.d.ts +3 -3
  45. package/dist/source/factory.d.ts +4 -6
  46. package/dist/source/factory.js +4 -7
  47. package/dist/source/index.d.ts +4 -4
  48. package/dist/source/index.js +2 -2
  49. package/dist/source/signing.d.ts +0 -3
  50. package/dist/source/types.d.ts +0 -26
  51. package/dist/stores/ObjectStore.js +8 -8
  52. package/dist/surface.d.ts +1 -1
  53. package/dist/surface.js +1 -0
  54. package/dist/sync/BootstrapFetcher.d.ts +10 -0
  55. package/dist/sync/BootstrapFetcher.js +27 -4
  56. package/dist/sync/createClaimStream.d.ts +11 -2
  57. package/dist/sync/createClaimStream.js +4 -3
  58. package/dist/testing/mocks/FakeDatabase.d.ts +1 -1
  59. package/dist/transactions/TransactionQueue.d.ts +2 -2
  60. package/dist/transactions/TransactionQueue.js +3 -3
  61. package/dist/transactions/commitEnvelope.d.ts +1 -1
  62. package/dist/transactions/commitEnvelope.js +2 -2
  63. package/dist/transactions/commitOutboxStore.d.ts +19 -19
  64. package/dist/transactions/durableWriteStore.d.ts +123 -0
  65. package/dist/transactions/durableWriteStore.js +30 -0
  66. package/dist/transactions/httpCommitEnvelope.d.ts +1 -0
  67. package/dist/transactions/httpCommitEnvelope.js +14 -12
  68. package/dist/transactions/idempotencyKey.d.ts +10 -0
  69. package/dist/transactions/idempotencyKey.js +9 -0
  70. package/dist/types/global.d.ts +10 -29
  71. package/dist/types/global.js +4 -7
  72. package/dist/types/streams.d.ts +2 -28
  73. package/dist/wire/frames.d.ts +6 -257
  74. package/dist/wire/frames.js +4 -25
  75. package/dist/wire/index.d.ts +4 -3
  76. package/dist/wire/index.js +2 -2
  77. package/dist/wire/protocolVersion.d.ts +30 -17
  78. package/dist/wire/protocolVersion.js +34 -18
  79. package/docs/agents.md +8 -3
  80. package/docs/api.md +6 -4
  81. package/docs/client-behavior.md +6 -3
  82. package/docs/coordination.md +2 -3
  83. package/docs/data-sources.md +5 -8
  84. package/docs/guarantees.md +21 -0
  85. package/docs/integration-guide.md +1 -0
  86. package/docs/migration.md +21 -1
  87. package/docs/quickstart.md +11 -0
  88. package/docs/react.md +0 -46
  89. package/examples/README.md +6 -5
  90. package/llms.txt +15 -15
  91. package/package.json +2 -7
  92. package/dist/client/ApiClient.d.ts +0 -177
  93. package/dist/commit/contract.d.ts +0 -493
  94. package/dist/commit/contract.js +0 -187
  95. package/dist/commit/index.d.ts +0 -6
  96. package/dist/commit/index.js +0 -5
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);
@@ -277262,6 +277262,11 @@ var claimRejectionSchema = import_zod3.z.object({
277262
277262
  heldByClaim: wireClaimSummarySchema.optional(),
277263
277263
  policyReason: import_zod3.z.string().optional()
277264
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
+ });
277265
277270
  var modelTargetSchema = import_zod3.z.object({
277266
277271
  model: import_zod3.z.string(),
277267
277272
  id: import_zod3.z.string(),
@@ -277476,9 +277481,61 @@ var AbloError = class extends Error {
277476
277481
  function docUrlForCode(code) {
277477
277482
  return `https://docs.abloatai.com/errors#${code}`;
277478
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
+ };
277479
277503
  var AbloValidationError = class extends AbloError {
277480
277504
  type = "AbloValidationError";
277481
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
+ };
277482
277539
  var OptionalWireStringSchema = import_zod4.z.preprocess(
277483
277540
  (value) => typeof value === "string" ? value : void 0,
277484
277541
  import_zod4.z.string().optional()
@@ -277506,7 +277563,7 @@ var ErrorFieldSchema = import_zod4.z.preprocess(
277506
277563
  ).catch(void 0);
277507
277564
  var ErrorBodyShapeSchema = import_zod4.z.object({
277508
277565
  /** The `error` field may be a flat code string, as some endpoints return,
277509
- * or a nested error object, as a {@link CommitReceipt} carries. */
277566
+ * or a nested error object, as commit endpoints return on rejection. */
277510
277567
  error: ErrorFieldSchema,
277511
277568
  code: OptionalWireStringSchema,
277512
277569
  reason: OptionalWireStringSchema,
@@ -277517,9 +277574,58 @@ var ErrorBodyShapeSchema = import_zod4.z.object({
277517
277574
  heldByClaim: wireClaimSummarySchema.optional().catch(void 0),
277518
277575
  claims: import_zod4.z.array(wireClaimSummarySchema).optional().catch(void 0)
277519
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
+ }
277520
277626
 
277521
277627
  // src/cli/migrate.ts
277522
- var import_picocolors4 = __toESM(require_picocolors(), 1);
277628
+ var import_picocolors5 = __toESM(require_picocolors(), 1);
277523
277629
  var import_fs5 = require("fs");
277524
277630
 
277525
277631
  // node_modules/postgres/src/index.js
@@ -279688,7 +279794,7 @@ var import_source = require("@abloatai/ablo/source");
279688
279794
 
279689
279795
  // src/cli/push.ts
279690
279796
  init_cjs_shims();
279691
- var import_picocolors3 = __toESM(require_picocolors(), 1);
279797
+ var import_picocolors4 = __toESM(require_picocolors(), 1);
279692
279798
 
279693
279799
  // src/auth/credentialPolicy.ts
279694
279800
  init_cjs_shims();
@@ -279962,6 +280068,174 @@ function resolvePushPlan() {
279962
280068
  return { flow: getMode(), apiKey: key, source };
279963
280069
  }
279964
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
+
279965
280239
  // src/cli/theme.ts
279966
280240
  init_cjs_shims();
279967
280241
  var RESET = "\x1B[0m";
@@ -279975,6 +280249,288 @@ function brand(label = "ablo") {
279975
280249
  return `${PAPER_BG}${BLACK_FG} ${label} ${RESET}`;
279976
280250
  }
279977
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
+
279978
280534
  // src/cli/push.ts
279979
280535
  function coerceBackfill(raw) {
279980
280536
  if (raw === "true") return true;
@@ -279988,14 +280544,14 @@ var DEFAULT_URL = ABLO_DEFAULT_BASE_URL;
279988
280544
  function fmtSignal(s) {
279989
280545
  const sig = s;
279990
280546
  const where = sig.field ? `${sig.model}.${sig.field}` : sig.model;
279991
- let line = ` \u2022 ${import_picocolors3.default.bold(where ?? "?")} \u2014 ${sig.detail ?? ""}`;
280547
+ let line = ` \u2022 ${import_picocolors4.default.bold(where ?? "?")} \u2014 ${sig.detail ?? ""}`;
279992
280548
  if (sig.shadowed) {
279993
280549
  const env = sig.shadowed.environment ?? "production";
279994
280550
  const ver = sig.shadowed.version != null ? `v${sig.shadowed.version}` : "active";
279995
280551
  const when = sig.shadowed.pushedAt ? new Date(sig.shadowed.pushedAt).toISOString().slice(0, 10) : "unknown date";
279996
280552
  const by = sig.shadowed.pushedBy ? ` by ${sig.shadowed.pushedBy}` : "";
279997
280553
  line += `
279998
- ${import_picocolors3.default.dim(`\u21B3 baseline: ${env} ${ver}, pushed ${when}${by}`)}`;
280554
+ ${import_picocolors4.default.dim(`\u21B3 baseline: ${env} ${ver}, pushed ${when}${by}`)}`;
279999
280555
  }
280000
280556
  return line;
280001
280557
  }
@@ -280092,7 +280648,7 @@ async function loadSchema(schemaPath, exportName) {
280092
280648
  const abs = (0, import_path3.resolve)(process.cwd(), schemaPath);
280093
280649
  if (!(0, import_fs4.existsSync)(abs)) {
280094
280650
  throw new AbloValidationError(
280095
- `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>")}.`,
280096
280652
  { code: "cli_invalid_arguments" }
280097
280653
  );
280098
280654
  }
@@ -280103,7 +280659,7 @@ async function loadSchema(schemaPath, exportName) {
280103
280659
  const schema = mod[exportName] ?? nested?.[exportName];
280104
280660
  if (!schema || typeof schema !== "object" || !("models" in schema)) {
280105
280661
  throw new AbloValidationError(
280106
- `${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({ ... })\`?`,
280107
280663
  { code: "cli_invalid_arguments" }
280108
280664
  );
280109
280665
  }
@@ -280156,7 +280712,7 @@ function localModels(schema) {
280156
280712
  }
280157
280713
  function printPlan(local, remote) {
280158
280714
  if (!remote?.models) {
280159
- 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)")}
280160
280716
  `);
280161
280717
  return;
280162
280718
  }
@@ -280167,36 +280723,37 @@ function printPlan(local, remote) {
280167
280723
  const changed = [...local.keys()].filter((k3) => remoteMap.has(k3) && remoteMap.get(k3) !== local.get(k3));
280168
280724
  const verLabel = remote.version != null ? `v${remote.version}` : "active";
280169
280725
  if (added.length === 0 && removed.length === 0 && changed.length === 0) {
280170
- 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)`)}
280171
280727
  `);
280172
280728
  return;
280173
280729
  }
280174
- console.log(` ${import_picocolors3.default.dim("plan")} ${import_picocolors3.default.dim(`vs deployed ${verLabel}:`)}`);
280175
- 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)")}`);
280176
280732
  for (const k3 of changed)
280177
- console.log(` ${import_picocolors3.default.yellow(`~ ${k3}`)} ${import_picocolors3.default.dim(`conflict ${remoteMap.get(k3) || "(default)"} \u2192 ${local.get(k3) || "(default)"}`)}`);
280178
- 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)")}`);
280179
280735
  console.log("");
280180
280736
  }
280181
- async function confirmPush(args, env) {
280737
+ async function confirmPush(args, target) {
280738
+ const env = target.confirmed?.environment ?? target.keyEnv;
280182
280739
  const isProd = env === "production";
280183
280740
  const tty = Boolean(process.stdout.isTTY && process.stdin.isTTY);
280184
280741
  if (isProd && !args.yes) {
280185
280742
  const git = schemaGitState(args.schemaPath);
280186
280743
  if (git?.dirty && !args.allowDirty) {
280187
- console.error(` ${import_picocolors3.default.red("\u2717")} Refusing to deploy uncommitted schema to ${import_picocolors3.default.red(import_picocolors3.default.bold("production"))}.`);
280188
- 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.`));
280189
280746
  process.exit(1);
280190
280747
  }
280191
280748
  if (!tty) {
280192
- console.error(` ${import_picocolors3.default.red("\u2717")} Refusing to deploy to ${import_picocolors3.default.red(import_picocolors3.default.bold("production"))} non-interactively without confirmation.`);
280193
- 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.`));
280194
280751
  process.exit(1);
280195
280752
  }
280196
- const project = getActiveProject();
280197
- const expected = project?.slug ?? "production";
280753
+ const confirmedProject = target.confirmed?.project;
280754
+ const expected = confirmedProject ? confirmedProject.isDefault ? "default" : confirmedProject.slug : target.localProject?.slug ?? "production";
280198
280755
  const typed = await he({
280199
- 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:`,
280200
280757
  placeholder: expected
280201
280758
  });
280202
280759
  if (pD(typed) || String(typed).trim() !== expected) {
@@ -280206,32 +280763,53 @@ async function confirmPush(args, env) {
280206
280763
  return;
280207
280764
  }
280208
280765
  if (!isProd && !args.yes && tty) {
280209
- 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")}?` });
280210
280767
  if (pD(ok) || !ok) {
280211
280768
  xe("Aborted.");
280212
280769
  process.exit(1);
280213
280770
  }
280214
280771
  }
280215
280772
  }
280216
- function printPushTarget(opts) {
280217
- const env = modeFromKey(opts.apiKey);
280218
- const envLabel = env === "production" ? import_picocolors3.default.bold("production") : env === "sandbox" ? import_picocolors3.default.bold("sandbox") : import_picocolors3.default.yellow("unknown env");
280219
- const project = getActiveProject();
280220
- 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");
280221
280777
  const cliMode = getMode();
280222
- 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
+ }
280223
280791
  console.log(`
280224
- ${brand("ablo")} ${import_picocolors3.default.dim("push")} ${import_picocolors3.default.dim("\u2192")} ${envLabel}${modeNote}`);
280225
- console.log(` ${import_picocolors3.default.dim("project")} ${projectLabel}`);
280226
- 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)}`);
280227
280796
  console.log(
280228
- ` ${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)})`)}`
280229
280798
  );
280230
280799
  console.log(
280231
- ` ${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}`)}
280232
280801
  `
280233
280802
  );
280234
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
+ }
280235
280813
  function describeKeySource(source) {
280236
280814
  switch (source) {
280237
280815
  case "env":
@@ -280249,7 +280827,7 @@ async function push(argv) {
280249
280827
  try {
280250
280828
  args = parsePushArgs(argv);
280251
280829
  } catch (err) {
280252
- 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)}`));
280253
280831
  process.exit(1);
280254
280832
  }
280255
280833
  let keySource = "env";
@@ -280260,44 +280838,43 @@ async function push(argv) {
280260
280838
  }
280261
280839
  if (!args.apiKey) {
280262
280840
  console.error(
280263
- import_picocolors3.default.red(` No API key.`) + import_picocolors3.default.dim(
280264
- ` 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()}'.`
280265
280843
  )
280266
280844
  );
280267
280845
  process.exit(1);
280268
280846
  }
280269
280847
  const schema = await loadSchema(args.schemaPath, args.exportName);
280270
280848
  const hash = (0, import_schema2.schemaHash)(schema);
280271
- printPushTarget({
280272
- schemaPath: args.schemaPath,
280273
- url: args.url,
280274
- apiKey: args.apiKey,
280275
- keySource,
280849
+ const target = await resolveTarget({ url: args.url, apiKey: args.apiKey, keySource });
280850
+ printPushTarget(target, {
280851
+ path: args.schemaPath,
280276
280852
  modelCount: Object.keys(schema.models).length,
280277
280853
  hash
280278
280854
  });
280279
280855
  const remote = await fetchActiveSchema(args.url, args.apiKey);
280280
280856
  printPlan(localModels(schema), remote);
280857
+ warnMismatches(target);
280281
280858
  const git = schemaGitState(args.schemaPath);
280282
280859
  if (git?.dirty) {
280283
280860
  const what = git.untracked ? "is untracked (not committed)" : "has uncommitted changes";
280284
- 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.
280285
280862
  `);
280286
280863
  }
280287
280864
  if (args.dryRun) {
280288
- 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.`);
280289
280866
  return;
280290
280867
  }
280291
- await confirmPush(args, modeFromKey(args.apiKey));
280868
+ await confirmPush(args, target);
280292
280869
  const { ok: resOk, status: status2, body, bodyText } = await pushSchema(schema, args);
280293
280870
  if (resOk) {
280294
280871
  if (body.unchanged) {
280295
- 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}).`);
280296
280873
  } else {
280297
- 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})`)}`);
280298
280875
  if (Array.isArray(body.warnings) && body.warnings.length > 0) {
280299
- console.log(import_picocolors3.default.yellow(` Applied ${body.warnings.length} destructive change(s):`));
280300
- 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)));
280301
280878
  }
280302
280879
  }
280303
280880
  return;
@@ -280305,75 +280882,75 @@ async function push(argv) {
280305
280882
  if (status2 === 409) {
280306
280883
  const unexecutable = Array.isArray(body.unexecutable) ? body.unexecutable : [];
280307
280884
  const warnings = Array.isArray(body.warnings) ? body.warnings : [];
280308
- 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."));
280309
280886
  if (unexecutable.length > 0) {
280310
- console.error(import_picocolors3.default.red(` Unexecutable (would fail on existing rows):`));
280311
- 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)));
280312
280889
  }
280313
280890
  if (warnings.length > 0) {
280314
- console.error(import_picocolors3.default.yellow(` Destructive (data loss):`));
280315
- 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)));
280316
280893
  }
280317
280894
  const hasShadowed = [...unexecutable, ...warnings].some(
280318
280895
  (s) => s.shadowed != null
280319
280896
  );
280320
280897
  if (hasShadowed) {
280321
280898
  console.error(
280322
- import_picocolors3.default.dim(
280899
+ import_picocolors4.default.dim(
280323
280900
  " These models exist in the baseline above but not in your push. Sandbox readers fall"
280324
280901
  )
280325
280902
  );
280326
280903
  console.error(
280327
- import_picocolors3.default.dim(
280904
+ import_picocolors4.default.dim(
280328
280905
  " back to the production schema until you push your own, so applying this drops them."
280329
280906
  )
280330
280907
  );
280331
280908
  }
280332
- 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.`));
280333
280910
  } else if (status2 === 403) {
280334
280911
  const code = body.code ?? body.reason;
280335
280912
  const serverMsg = body.message ?? body.reason;
280336
- console.error(import_picocolors3.default.red(` Forbidden${code ? ` [${code}]` : ""}: ${serverMsg ?? "permission denied"}`));
280337
- 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)}.`));
280338
280915
  if (code === "database_role_cannot_enforce_rls") {
280339
280916
  console.error(
280340
- import_picocolors3.default.dim(
280341
- ` 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.`
280342
280919
  )
280343
280920
  );
280344
280921
  } else if (code === "database_tables_unforced_rls") {
280345
280922
  console.error(
280346
- import_picocolors3.default.dim(
280347
- ` 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.`
280348
280925
  )
280349
280926
  );
280350
280927
  } else if (code === "capability_scope_denied" || code === "capability_invalid") {
280351
280928
  console.error(
280352
- import_picocolors3.default.dim(
280353
- ` 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.`
280354
280931
  )
280355
280932
  );
280356
280933
  } else if (code === "schema_provisioning_forbidden") {
280357
280934
  console.error(
280358
- import_picocolors3.default.dim(
280359
- ` 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.`
280360
280937
  )
280361
280938
  );
280362
280939
  } else if (args.apiKey != null && classifyCredentialKind(args.apiKey) === "restricted") {
280363
280940
  console.error(
280364
- import_picocolors3.default.dim(
280365
- ` 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")}).`
280366
280943
  )
280367
280944
  );
280368
280945
  } else {
280369
280946
  console.error(
280370
- import_picocolors3.default.dim(
280371
- ` 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`
280372
280949
  )
280373
280950
  );
280374
280951
  }
280375
280952
  } else {
280376
- 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}`));
280377
280954
  }
280378
280955
  process.exit(1);
280379
280956
  }
@@ -280436,10 +281013,10 @@ var log = {
280436
281013
  console.log(`[migrate] ${msg}`, fields);
280437
281014
  },
280438
281015
  warn: (msg, fields) => {
280439
- console.warn(import_picocolors4.default.yellow(`[migrate] ${msg}`), fields);
281016
+ console.warn(import_picocolors5.default.yellow(`[migrate] ${msg}`), fields);
280440
281017
  },
280441
281018
  error: (msg, fields) => {
280442
- console.error(import_picocolors4.default.red(`[migrate] ${msg}`), fields);
281019
+ console.error(import_picocolors5.default.red(`[migrate] ${msg}`), fields);
280443
281020
  }
280444
281021
  };
280445
281022
  async function applyStatements(dbUrl, targetSchema, statements, concurrent = []) {
@@ -280513,7 +281090,7 @@ async function migrate(argv) {
280513
281090
  try {
280514
281091
  args = parseMigrateArgs(argv);
280515
281092
  } catch (err) {
280516
- 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)}`));
280517
281094
  process.exit(1);
280518
281095
  }
280519
281096
  const schema = await loadSchema(args.schemaPath, args.exportName);
@@ -280524,11 +281101,11 @@ async function migrate(argv) {
280524
281101
  ].join("\n");
280525
281102
  const totalStatements = plan.statements.length + plan.concurrent.length;
280526
281103
  console.log(
280527
- ` ${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`)}`
280528
281105
  );
280529
281106
  if (args.outputFile) {
280530
281107
  (0, import_fs5.writeFileSync)(args.outputFile, sql + "\n");
280531
- 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)}`);
280532
281109
  return;
280533
281110
  }
280534
281111
  if (args.dryRun) {
@@ -280538,7 +281115,7 @@ async function migrate(argv) {
280538
281115
  const dbUrl = readProjectDatabaseUrl();
280539
281116
  if (!dbUrl) {
280540
281117
  console.error(
280541
- import_picocolors4.default.red(
281118
+ import_picocolors5.default.red(
280542
281119
  " No DATABASE_URL found (checked process env, .env.local, .env). Set it to apply, or use --dry-run to preview."
280543
281120
  )
280544
281121
  );
@@ -280546,7 +281123,7 @@ async function migrate(argv) {
280546
281123
  }
280547
281124
  try {
280548
281125
  await applyStatements(dbUrl, args.targetSchema, plan.statements, plan.concurrent);
280549
- console.log(` ${import_picocolors4.default.green("\u2713")} Migration complete`);
281126
+ console.log(` ${import_picocolors5.default.green("\u2713")} Migration complete`);
280550
281127
  } catch {
280551
281128
  process.exit(1);
280552
281129
  }
@@ -280554,7 +281131,7 @@ async function migrate(argv) {
280554
281131
 
280555
281132
  // src/cli/connect.ts
280556
281133
  init_cjs_shims();
280557
- var import_picocolors5 = __toESM(require_picocolors(), 1);
281134
+ var import_picocolors6 = __toESM(require_picocolors(), 1);
280558
281135
  var ABLO_PUBLICATION = "ablo_publication";
280559
281136
  var ABLO_REPLICATION_ROLE = "ablo_replicator";
280560
281137
  function parseConnectArgs(argv) {
@@ -280611,50 +281188,50 @@ function connectSetupSql(input) {
280611
281188
  function printConnectRecipe(args) {
280612
281189
  const sql = connectSetupSql({ tables: args.tables, role: args.role });
280613
281190
  console.log(`
280614
- ${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)")}
280615
281192
  `);
280616
281193
  console.log(
280617
- ` 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,
280618
281195
  owns, hosts, or migrates your schema. Your app continues to own the write path \u2014 Ablo tails
280619
- 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)")}:
280620
281197
  `
280621
281198
  );
280622
- console.log(` ${import_picocolors5.default.bold("1.")} Enable logical decoding ${import_picocolors5.default.dim("(then RESTART Postgres \u2014 wal_level is not reloadable)")}`);
280623
- 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])}`);
280624
281201
  console.log(
280625
- import_picocolors5.default.dim(
280626
- ` 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
280627
281204
  instance's parameter group instead, then reboot.`
280628
281205
  )
280629
281206
  );
280630
281207
  console.log(`
280631
- ${import_picocolors5.default.bold("2.")} Publish the tables Ablo should read`);
280632
- 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])}`);
280633
281210
  if (args.tables.length === 0) {
280634
- 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.)`));
280635
281212
  }
280636
281213
  console.log(`
280637
- ${import_picocolors5.default.bold("3.")} Create a least-privilege replication role ${import_picocolors5.default.dim("(pick your own password)")}`);
280638
- console.log(` ${import_picocolors5.default.cyan(sql[2])}`);
280639
- console.log(` ${import_picocolors5.default.cyan(sql[3])}`);
280640
- 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])}`);
280641
281218
  console.log(
280642
- import_picocolors5.default.dim(
281219
+ import_picocolors6.default.dim(
280643
281220
  ` On Amazon RDS, the REPLICATION attribute is granted, not set directly:
280644
- ${import_picocolors5.default.bold(`GRANT rds_replication TO ${quoteIdent(args.role)};`)}`
281221
+ ${import_picocolors6.default.bold(`GRANT rds_replication TO ${quoteIdent(args.role)};`)}`
280645
281222
  )
280646
281223
  );
280647
281224
  console.log(
280648
281225
  `
280649
- ${import_picocolors5.default.bold("4.")} Put the role's connection string in ${import_picocolors5.default.bold("DATABASE_URL")}, then verify:
280650
- ${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")}
280651
281228
  `
280652
281229
  );
280653
281230
  console.log(
280654
- import_picocolors5.default.dim(
281231
+ import_picocolors6.default.dim(
280655
281232
  ` Reminder: this is the READ path \u2014 Ablo tails your WAL and never writes to your database.
280656
281233
  Your writes keep going through your own backend. (Ablo HOSTING your rows, or dialing in via
280657
- ${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.)`
280658
281235
  )
280659
281236
  );
280660
281237
  console.log();
@@ -280672,11 +281249,11 @@ var SYNC_INFRA_TYPES = [
280672
281249
  ];
280673
281250
  function printCheckItem(item) {
280674
281251
  if (item.ok) {
280675
- console.log(` ${import_picocolors5.default.green("\u2713")} ${item.label}`);
281252
+ console.log(` ${import_picocolors6.default.green("\u2713")} ${item.label}`);
280676
281253
  } else {
280677
- console.log(` ${import_picocolors5.default.red("\u2717")} ${item.label}`);
281254
+ console.log(` ${import_picocolors6.default.red("\u2717")} ${item.label}`);
280678
281255
  if (item.fix) {
280679
- 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}`);
280680
281257
  }
280681
281258
  }
280682
281259
  }
@@ -280688,9 +281265,9 @@ async function probeReadiness(sql, opts = {}) {
280688
281265
  );
280689
281266
  const walLevel = walRows[0]?.setting ?? "unknown";
280690
281267
  items.push(
280691
- 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")}` } : {
280692
281269
  ok: false,
280693
- 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")})`,
280694
281271
  fix: `ALTER SYSTEM SET wal_level = 'logical'; then RESTART Postgres.
280695
281272
  On RDS/Aurora set rds.logical_replication = 1 in the parameter group, then reboot.
280696
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.`
@@ -280704,10 +281281,10 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
280704
281281
  items.push(
280705
281282
  pubRow ? {
280706
281283
  ok: true,
280707
- 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)")}`
280708
281285
  } : {
280709
281286
  ok: false,
280710
- label: `publication ${import_picocolors5.default.bold(publication)} not found`,
281287
+ label: `publication ${import_picocolors6.default.bold(publication)} not found`,
280711
281288
  fix: `CREATE PUBLICATION ${quoteIdent(publication)} FOR ALL TABLES;`
280712
281289
  }
280713
281290
  );
@@ -280717,9 +281294,9 @@ On Neon enable Logical Replication in the project (Console \u2192 Settings \u219
280717
281294
  const role = roleRows[0];
280718
281295
  const hasReplication = Boolean(role && (role.rolreplication || role.rolsuper));
280719
281296
  items.push(
280720
- 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)")}` } : {
280721
281298
  ok: false,
280722
- 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`,
280723
281300
  fix: `ALTER ROLE current_user WITH REPLICATION;
280724
281301
  On RDS: GRANT rds_replication TO <your_role>;`
280725
281302
  }
@@ -280777,7 +281354,7 @@ function requireDatabaseUrl(verb) {
280777
281354
  const dbUrl = readProjectDatabaseUrl();
280778
281355
  if (!dbUrl) {
280779
281356
  console.error(
280780
- 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}`)}.`)
280781
281358
  );
280782
281359
  process.exit(1);
280783
281360
  }
@@ -280791,7 +281368,7 @@ async function probeAndReport(dbUrl) {
280791
281368
  items = await probeReadiness(sql);
280792
281369
  } catch (err) {
280793
281370
  const pg = err ?? {};
280794
- 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)}`));
280795
281372
  await sql.end({ timeout: 2 });
280796
281373
  process.exit(1);
280797
281374
  }
@@ -280802,17 +281379,17 @@ async function probeAndReport(dbUrl) {
280802
281379
  async function runCheck() {
280803
281380
  const dbUrl = requireDatabaseUrl("--check");
280804
281381
  console.log(`
280805
- ${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")}
280806
281383
  `);
280807
281384
  const failures = await probeAndReport(dbUrl);
280808
281385
  console.log();
280809
281386
  if (failures === 0) {
280810
- 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.
280811
281388
  `);
280812
281389
  process.exit(0);
280813
281390
  }
280814
281391
  console.log(
280815
- ` ${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")}.`)}
280816
281393
  `
280817
281394
  );
280818
281395
  process.exit(1);
@@ -280825,18 +281402,18 @@ async function runRegister() {
280825
281402
  const apiKey = resolveApiKey();
280826
281403
  if (!apiKey) {
280827
281404
  console.error(
280828
- 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.`)
280829
281406
  );
280830
281407
  process.exit(1);
280831
281408
  }
280832
281409
  console.log(`
280833
- ${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")}
280834
281411
  `);
280835
281412
  const failures = await probeAndReport(dbUrl);
280836
281413
  if (failures > 0) {
280837
281414
  console.log(
280838
281415
  `
280839
- ${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.")}
280840
281417
  `
280841
281418
  );
280842
281419
  process.exit(1);
@@ -280850,7 +281427,7 @@ async function runRegister() {
280850
281427
  body: JSON.stringify({ connectionString: dbUrl })
280851
281428
  });
280852
281429
  } catch (err) {
280853
- console.error(import_picocolors5.default.red(`
281430
+ console.error(import_picocolors6.default.red(`
280854
281431
  Couldn't reach ${apiUrl2}: ${err instanceof Error ? err.message : String(err)}
280855
281432
  `));
280856
281433
  process.exit(1);
@@ -280859,7 +281436,7 @@ async function runRegister() {
280859
281436
  const body2 = await res.json().catch(() => ({}));
280860
281437
  console.log(
280861
281438
  `
280862
- ${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.
280863
281440
  `
280864
281441
  );
280865
281442
  process.exit(0);
@@ -280867,28 +281444,28 @@ async function runRegister() {
280867
281444
  const body = await res.json().catch(() => ({}));
280868
281445
  const code = body.code ?? body.error?.code;
280869
281446
  const message = body.message ?? body.error?.message ?? `HTTP ${res.status}`;
280870
- console.error(import_picocolors5.default.red(`
281447
+ console.error(import_picocolors6.default.red(`
280871
281448
  Registration failed: ${message}`));
280872
281449
  if (code === "forbidden") {
280873
- 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.`));
280874
281451
  } else if (code === "datasource_connection_unsupported") {
280875
281452
  console.error(
280876
- 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.`)
280877
281454
  );
280878
281455
  } else if (code === "database_not_replication_ready") {
280879
281456
  for (const f of body.details?.failures ?? []) {
280880
- console.error(` ${import_picocolors5.default.red("\u2717")} ${import_picocolors5.default.bold(f.item ?? "item")}${f.actual ? import_picocolors5.default.dim(` (${f.actual})`) : ""}`);
280881
- 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}`);
280882
281459
  }
280883
- console.error(import_picocolors5.default.dim(`
280884
- 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.`));
280885
281462
  } else if (code === "database_unreachable") {
280886
- 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}`));
280887
281464
  console.error(
280888
- import_picocolors5.default.dim(
281465
+ import_picocolors6.default.dim(
280889
281466
  ` Ablo's servers must be able to reach this database \u2014 a localhost or private-network
280890
281467
  Postgres can't be replicated by the hosted service. Use a reachable host (or a tunnel),
280891
- or the signed ${import_picocolors5.default.bold("dataSource()")} endpoint fallback.`
281468
+ or the signed ${import_picocolors6.default.bold("dataSource()")} endpoint fallback.`
280892
281469
  )
280893
281470
  );
280894
281471
  }
@@ -280904,27 +281481,27 @@ async function runAuditInfra() {
280904
281481
  artifacts = await auditTenantSyncInfra(sql);
280905
281482
  } catch (err) {
280906
281483
  const pg = err ?? {};
280907
- 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)}`));
280908
281485
  await sql.end({ timeout: 2 });
280909
281486
  process.exit(1);
280910
281487
  }
280911
281488
  await sql.end({ timeout: 2 });
280912
281489
  console.log(`
280913
- ${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")}
280914
281491
  `);
280915
281492
  const present = artifacts.filter((a) => a.present);
280916
281493
  if (present.length === 0) {
280917
- 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.
280918
281495
  `);
280919
281496
  process.exit(0);
280920
281497
  }
280921
281498
  for (const artifact of present) {
280922
281499
  const label = artifact.kind === "type" ? "type" : "relation";
280923
- 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`);
280924
281501
  }
280925
281502
  console.log(
280926
281503
  `
280927
- ${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")
280928
281505
  );
280929
281506
  process.exit(1);
280930
281507
  }
@@ -280933,7 +281510,7 @@ async function connect(argv) {
280933
281510
  try {
280934
281511
  args = parseConnectArgs(argv);
280935
281512
  } catch (err) {
280936
- 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)}`));
280937
281514
  process.exit(1);
280938
281515
  }
280939
281516
  if (args.check) {
@@ -280967,7 +281544,7 @@ var CONNECT_USAGE = ` ablo connect \u2014 connect your database for the read pa
280967
281544
  init_cjs_shims();
280968
281545
  var import_fs6 = require("fs");
280969
281546
  var import_path4 = require("path");
280970
- var import_picocolors6 = __toESM(require_picocolors(), 1);
281547
+ var import_picocolors7 = __toESM(require_picocolors(), 1);
280971
281548
  var import_schema4 = require("@abloatai/ablo/schema");
280972
281549
  var DEFAULT_SCHEMA_PATH3 = "ablo/schema.ts";
280973
281550
  var DEFAULT_EXPORT3 = "schema";
@@ -280999,7 +281576,7 @@ async function generate(argv) {
280999
281576
  try {
281000
281577
  args = parseGenerateArgs(argv);
281001
281578
  } catch (err) {
281002
- 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)}`));
281003
281580
  process.exit(1);
281004
281581
  }
281005
281582
  let source;
@@ -281008,18 +281585,18 @@ async function generate(argv) {
281008
281585
  const schemaJson = JSON.parse((0, import_schema4.serializeSchema)(schema));
281009
281586
  source = (0, import_schema4.generateTypes)(schemaJson);
281010
281587
  } catch (err) {
281011
- 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)}`));
281012
281589
  process.exit(1);
281013
281590
  }
281014
281591
  const abs = (0, import_path4.resolve)(process.cwd(), args.out);
281015
281592
  (0, import_fs6.mkdirSync)((0, import_path4.dirname)(abs), { recursive: true });
281016
281593
  (0, import_fs6.writeFileSync)(abs, source);
281017
- 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)}`);
281018
281595
  }
281019
281596
 
281020
281597
  // src/cli/dev.ts
281021
281598
  init_cjs_shims();
281022
- var import_picocolors7 = __toESM(require_picocolors(), 1);
281599
+ var import_picocolors8 = __toESM(require_picocolors(), 1);
281023
281600
  var import_fs7 = require("fs");
281024
281601
  var import_path5 = require("path");
281025
281602
  var import_schema5 = require("@abloatai/ablo/schema");
@@ -281057,25 +281634,25 @@ function classifyKey(apiKey, activeMode) {
281057
281634
  if (!apiKey) {
281058
281635
  return {
281059
281636
  ok: false,
281060
- 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}'.`)
281061
281638
  };
281062
281639
  }
281063
281640
  if (apiKey.startsWith("sk_test_")) return { ok: true };
281064
281641
  if (apiKey.startsWith("sk_live_")) {
281065
281642
  return {
281066
281643
  ok: false,
281067
- 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.`
281068
281645
  };
281069
281646
  }
281070
281647
  if (classifyCredentialKind(apiKey) === "restricted") {
281071
281648
  return {
281072
281649
  ok: false,
281073
- 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.`
281074
281651
  };
281075
281652
  }
281076
281653
  return {
281077
281654
  ok: false,
281078
- 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")}).`
281079
281656
  };
281080
281657
  }
281081
281658
  function wireEnvLocal(apiKey, cwd = process.cwd()) {
@@ -281085,7 +281662,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
281085
281662
  if (!(0, import_fs7.existsSync)(envPath)) {
281086
281663
  (0, import_fs7.writeFileSync)(envPath, `${line}
281087
281664
  `, { mode: 384 });
281088
- 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")}`;
281089
281666
  } else {
281090
281667
  const content = (0, import_fs7.readFileSync)(envPath, "utf8");
281091
281668
  const match = /^ABLO_API_KEY=(.*)$/m.exec(content);
@@ -281093,12 +281670,12 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
281093
281670
  if (!match) {
281094
281671
  (0, import_fs7.appendFileSync)(envPath, `${content.endsWith("\n") || content.length === 0 ? "" : "\n"}${line}
281095
281672
  `);
281096
- 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")}`;
281097
281674
  } else if (existing === apiKey) {
281098
- action = `${import_picocolors7.default.bold(".env.local")} already has this key`;
281675
+ action = `${import_picocolors8.default.bold(".env.local")} already has this key`;
281099
281676
  } else {
281100
281677
  (0, import_fs7.writeFileSync)(envPath, content.replace(/^ABLO_API_KEY=.*$/m, line));
281101
- 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)`)}`;
281102
281679
  }
281103
281680
  }
281104
281681
  const gitignorePath = (0, import_path5.resolve)(cwd, ".gitignore");
@@ -281112,7 +281689,7 @@ function wireEnvLocal(apiKey, cwd = process.cwd()) {
281112
281689
  `}.env.local
281113
281690
  `
281114
281691
  );
281115
- 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.`;
281116
281693
  }
281117
281694
  return `${action}.${gitignoreNote}`;
281118
281695
  }
@@ -281127,7 +281704,7 @@ async function runPush(schema, args) {
281127
281704
  if (ok) {
281128
281705
  return {
281129
281706
  ok: true,
281130
- 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})`)}`
281131
281708
  };
281132
281709
  }
281133
281710
  if (status2 === 409) {
@@ -281137,33 +281714,33 @@ async function runPush(schema, args) {
281137
281714
  (s) => s.shadowed != null
281138
281715
  );
281139
281716
  const lines = [
281140
- 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."),
281141
281718
  "",
281142
- ...unexecutable.map((u2) => import_picocolors7.default.red(fmtSignal(u2))),
281143
- ...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))),
281144
281721
  "",
281145
281722
  ...hasShadowed ? [
281146
- import_picocolors7.default.dim(
281723
+ import_picocolors8.default.dim(
281147
281724
  " These models exist in the baseline above but not in your push. Sandbox readers"
281148
281725
  ),
281149
- import_picocolors7.default.dim(
281726
+ import_picocolors8.default.dim(
281150
281727
  " fall back to the production schema until you push your own, so applying this drops them."
281151
281728
  ),
281152
281729
  ""
281153
281730
  ] : [],
281154
- import_picocolors7.default.dim(
281155
- ` 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.`
281156
281733
  )
281157
281734
  ];
281158
281735
  return { ok: false, message: lines.join("\n") };
281159
281736
  }
281160
281737
  if (status2 === 403) {
281161
281738
  const serverSays = body.message ?? body.reason;
281162
- 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")}.`;
281163
281740
  return {
281164
281741
  ok: false,
281165
281742
  message: `${serverSays ?? "This key can't author schema (missing schema:push scope)."}
281166
- ` + import_picocolors7.default.dim(hint)
281743
+ ` + import_picocolors8.default.dim(hint)
281167
281744
  };
281168
281745
  }
281169
281746
  return { ok: false, message: `Push failed (${status2}): ${body.message ?? body.reason ?? bodyText}` };
@@ -281173,25 +281750,25 @@ async function dev(argv) {
281173
281750
  try {
281174
281751
  args = parseDevArgs(argv);
281175
281752
  } catch (err) {
281176
- 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)}`));
281177
281754
  process.exit(1);
281178
281755
  }
281179
281756
  if (!args.apiKey) args.apiKey = resolveEffectiveApiKey("sandbox").key;
281180
281757
  const key = classifyKey(args.apiKey, getMode());
281181
281758
  if (!key.ok) {
281182
- console.error(import_picocolors7.default.red(` ${key.reason}`));
281759
+ console.error(import_picocolors8.default.red(` ${key.reason}`));
281183
281760
  process.exit(1);
281184
281761
  }
281185
281762
  console.log(`
281186
- ${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)")}
281187
281764
  `);
281188
281765
  const schema = await loadSchema(args.schemaPath, args.exportName);
281189
281766
  const modelCount = Object.keys(schema.models).length;
281190
281767
  console.log(
281191
- ` ${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)})`)}`
281192
281769
  );
281193
- console.log(` ${import_picocolors7.default.dim("key")} ${args.apiKey.slice(0, 12)}\u2026`);
281194
- 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}
281195
281772
  `);
281196
281773
  const s = Y2();
281197
281774
  s.start("Pushing schema definition (sandbox)");
@@ -281200,16 +281777,16 @@ async function dev(argv) {
281200
281777
  if (!first.ok) process.exit(1);
281201
281778
  if (process.env.ABLO_API_KEY) {
281202
281779
  console.log(`
281203
- ${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.`);
281204
281781
  } else {
281205
281782
  console.log(`
281206
- ${import_picocolors7.default.green("\u2713")} ${wireEnvLocal(args.apiKey)}`);
281207
- 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")}`);
281208
281785
  }
281209
281786
  console.log(` Your app is wired for the sandbox.`);
281210
281787
  if (!args.watch) return;
281211
281788
  const abs = (0, import_path5.resolve)(process.cwd(), args.schemaPath);
281212
- 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)`)}
281213
281790
  `);
281214
281791
  let timer2 = null;
281215
281792
  let pushing = false;
@@ -281229,7 +281806,7 @@ async function dev(argv) {
281229
281806
  const r2 = await runPush(next, args);
281230
281807
  s2.stop(r2.message, r2.ok ? 0 : 1);
281231
281808
  } catch (err) {
281232
- 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);
281233
281810
  } finally {
281234
281811
  pushing = false;
281235
281812
  }
@@ -281237,7 +281814,7 @@ async function dev(argv) {
281237
281814
  const stop = () => {
281238
281815
  watcher.close();
281239
281816
  console.log(`
281240
- ${import_picocolors7.default.dim("stopped.")}`);
281817
+ ${import_picocolors8.default.dim("stopped.")}`);
281241
281818
  process.exit(0);
281242
281819
  };
281243
281820
  process.on("SIGINT", stop);
@@ -281249,7 +281826,7 @@ async function dev(argv) {
281249
281826
  // src/cli/login.ts
281250
281827
  init_cjs_shims();
281251
281828
  var import_child_process2 = require("child_process");
281252
- var import_picocolors8 = __toESM(require_picocolors(), 1);
281829
+ var import_picocolors9 = __toESM(require_picocolors(), 1);
281253
281830
  var CLIENT_ID = "ablo-cli";
281254
281831
  var stripSlash = (u2) => u2.replace(/\/+$/, "");
281255
281832
  var AUTH_URL = stripSlash(process.env.ABLO_AUTH_URL ?? "https://auth.abloatai.com");
@@ -281307,9 +281884,9 @@ async function deviceLogin(argv, deps = {}) {
281307
281884
  const code = await codeRes.json();
281308
281885
  const approvePath = `/cli?user_code=${code.user_code}`;
281309
281886
  const url = account === "signup" ? `${DASHBOARD_URL}/signup?next=${encodeURIComponent(approvePath)}` : `${DASHBOARD_URL}${approvePath}`;
281310
- Me(`${import_picocolors8.default.bold(code.user_code)}
281887
+ Me(`${import_picocolors9.default.bold(code.user_code)}
281311
281888
 
281312
- ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281889
+ ${import_picocolors9.default.dim(url)}`, "Approve in your browser");
281313
281890
  openUrl(url);
281314
281891
  const s = Y2();
281315
281892
  s.start("Waiting for approval\u2026");
@@ -281376,7 +281953,7 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281376
281953
  if (reason) M2.error(reason);
281377
281954
  else if (provRes) M2.error(`Key provisioning returned ${provRes.status} from ${DASHBOARD_URL}/api/cli/provision-key.`);
281378
281955
  M2.error(
281379
- `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")}.`
281380
281957
  );
281381
281958
  process.exit(1);
281382
281959
  }
@@ -281396,9 +281973,9 @@ ${import_picocolors8.default.dim(url)}`, "Approve in your browser");
281396
281973
  { mode: "sandbox", activeProject: prov.project ?? void 0 }
281397
281974
  );
281398
281975
  s.stop(`Saved keys to ${path}`);
281399
- 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})`)}` : "";
281400
281977
  Se(
281401
- `${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.`
281402
281979
  );
281403
281980
  }
281404
281981
  async function login(argv = [], deps = {}) {
@@ -281407,20 +281984,20 @@ async function login(argv = [], deps = {}) {
281407
281984
  function logout() {
281408
281985
  const removed = clearCredential();
281409
281986
  if (removed) {
281410
- 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()})`)}`);
281411
281988
  } else {
281412
- 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.`);
281413
281990
  }
281414
281991
  if (process.env.ABLO_API_KEY) {
281415
281992
  console.log(
281416
- 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.`)
281417
281994
  );
281418
281995
  }
281419
281996
  }
281420
281997
 
281421
281998
  // src/cli/mode.ts
281422
281999
  init_cjs_shims();
281423
- var import_picocolors9 = __toESM(require_picocolors(), 1);
282000
+ var import_picocolors10 = __toESM(require_picocolors(), 1);
281424
282001
  var PREFIX = { sandbox: "sk_test_", production: "rk_live_" };
281425
282002
  function hintFor(m2, current) {
281426
282003
  const parts = [];
@@ -281430,10 +282007,10 @@ function hintFor(m2, current) {
281430
282007
  }
281431
282008
  function apply(m2) {
281432
282009
  setMode(m2);
281433
- 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)}`);
281434
282011
  if (!getKeyEntry(m2)) {
281435
282012
  console.log(
281436
- 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`)}.`)
281437
282014
  );
281438
282015
  }
281439
282016
  }
@@ -281446,14 +282023,14 @@ async function mode(argv) {
281446
282023
  }
281447
282024
  if (arg) {
281448
282025
  console.error(
281449
- 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")})`)
281450
282027
  );
281451
282028
  process.exit(1);
281452
282029
  }
281453
282030
  const current = getMode();
281454
282031
  if (!process.stdin.isTTY || process.env.CI) {
281455
282032
  console.error(
281456
- 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})`)
281457
282034
  );
281458
282035
  process.exit(1);
281459
282036
  }
@@ -281472,206 +282049,6 @@ async function mode(argv) {
281472
282049
  apply(selected);
281473
282050
  }
281474
282051
 
281475
- // src/cli/projects.ts
281476
- init_cjs_shims();
281477
- var import_picocolors10 = __toESM(require_picocolors(), 1);
281478
- function apiUrl() {
281479
- return (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
281480
- }
281481
- function requireKey() {
281482
- const apiKey = resolveApiKey();
281483
- if (!apiKey) {
281484
- console.error(
281485
- import_picocolors10.default.red(" No API key.") + import_picocolors10.default.dim(
281486
- ` 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).`
281487
- )
281488
- );
281489
- process.exit(1);
281490
- }
281491
- return apiKey;
281492
- }
281493
- async function request(path, apiKey, init2 = {}) {
281494
- const res = await fetch(`${apiUrl()}${path}`, {
281495
- method: init2.method ?? "GET",
281496
- headers: {
281497
- "content-type": "application/json",
281498
- authorization: `Bearer ${apiKey}`
281499
- },
281500
- ...init2.body !== void 0 ? { body: JSON.stringify(init2.body) } : {}
281501
- });
281502
- let body = {};
281503
- try {
281504
- body = await res.json();
281505
- } catch {
281506
- }
281507
- return { status: res.status, body };
281508
- }
281509
- async function listProjects(apiKey) {
281510
- try {
281511
- const { status: status2, body } = await request("/api/v1/projects", apiKey);
281512
- if (status2 !== 200 || !Array.isArray(body.data)) return null;
281513
- return body.data;
281514
- } catch {
281515
- return null;
281516
- }
281517
- }
281518
- async function fetchProjects() {
281519
- const all = await listProjects(requireKey());
281520
- if (!all) {
281521
- console.error(import_picocolors10.default.red(" Could not list projects \u2014 is the API reachable and the key valid?"));
281522
- process.exit(1);
281523
- }
281524
- return all;
281525
- }
281526
- function projectSlugFromPackageName(name) {
281527
- if (typeof name !== "string") return void 0;
281528
- const slug = name.toLowerCase().replace(/[^a-z0-9-_]+/g, "-").replace(/^[-_]+|[-_]+$/g, "").slice(0, 64);
281529
- if (!slug || slug === "default") return void 0;
281530
- return slug;
281531
- }
281532
- async function ensureProject(slug, name) {
281533
- const apiKey = resolveApiKey();
281534
- if (!apiKey) return null;
281535
- try {
281536
- const { status: status2, body } = await request("/api/v1/projects", apiKey, {
281537
- method: "POST",
281538
- body: { slug, ...name ? { name } : {} }
281539
- });
281540
- if (status2 === 201) {
281541
- const created = body;
281542
- setActiveProject({ id: created.id, slug: created.slug });
281543
- return { id: created.id, slug: created.slug, created: true };
281544
- }
281545
- if (body.code === "project_slug_taken") {
281546
- const all = await listProjects(apiKey);
281547
- const existing = all?.find((p2) => p2.slug === slug);
281548
- if (!existing) return null;
281549
- setActiveProject({ id: existing.id, slug: existing.slug });
281550
- return { id: existing.id, slug: existing.slug, created: false };
281551
- }
281552
- return null;
281553
- } catch {
281554
- return null;
281555
- }
281556
- }
281557
- function printList(projects2) {
281558
- const active = getActiveProject();
281559
- for (const p2 of projects2) {
281560
- const isActive = active ? active.id === p2.id : p2.default;
281561
- const marker = isActive ? import_picocolors10.default.green("\u25CF") : import_picocolors10.default.dim("\u25CB");
281562
- const tags = [p2.default ? import_picocolors10.default.dim("default") : "", isActive ? import_picocolors10.default.green("active") : ""].filter(Boolean).join(import_picocolors10.default.dim(", "));
281563
- console.log(
281564
- ` ${marker} ${p2.slug.padEnd(20)} ${import_picocolors10.default.dim(p2.id)}${tags ? ` ${tags}` : ""}`
281565
- );
281566
- }
281567
- }
281568
- async function projects(argv) {
281569
- const sub = argv[0];
281570
- if (sub === "list" || sub === void 0) {
281571
- console.log(`
281572
- ${brand("ablo")} ${import_picocolors10.default.dim("projects")}
281573
- `);
281574
- printList(await fetchProjects());
281575
- console.log();
281576
- return;
281577
- }
281578
- if (sub === "create") {
281579
- const slug = argv[1];
281580
- if (!slug || slug.startsWith("-")) {
281581
- console.error(import_picocolors10.default.red(' usage: ablo projects create <slug> [--name "Display Name"]'));
281582
- process.exit(1);
281583
- }
281584
- const nameIdx = argv.indexOf("--name");
281585
- const name = nameIdx >= 0 ? argv[nameIdx + 1] : void 0;
281586
- const { status: status2, body } = await request("/api/v1/projects", requireKey(), {
281587
- method: "POST",
281588
- body: { slug, ...name ? { name } : {} }
281589
- });
281590
- if (status2 !== 201) {
281591
- console.error(
281592
- import_picocolors10.default.red(` Create failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
281593
- );
281594
- if (body.code === "project_slug_taken") {
281595
- console.error(import_picocolors10.default.dim(` Pick another slug, or switch to it: ${import_picocolors10.default.bold(`ablo projects use ${slug}`)}`));
281596
- }
281597
- process.exit(1);
281598
- }
281599
- const created = body;
281600
- console.log(` ${import_picocolors10.default.green("\u2713")} Created project ${import_picocolors10.default.bold(created.slug)} ${import_picocolors10.default.dim(`(${created.id})`)}`);
281601
- console.log(
281602
- import_picocolors10.default.dim(` Make it active with ${import_picocolors10.default.bold(`ablo projects use ${created.slug}`)}; mint its keys in the dashboard.`)
281603
- );
281604
- return;
281605
- }
281606
- if (sub === "rename") {
281607
- const ref = argv[1];
281608
- const name = argv.slice(2).join(" ").trim();
281609
- if (!ref || ref.startsWith("-") || !name) {
281610
- console.error(import_picocolors10.default.red(" usage: ablo projects rename <slug|id> <new name>"));
281611
- process.exit(1);
281612
- }
281613
- const all = await fetchProjects();
281614
- const target = all.find((p2) => p2.slug === ref || p2.id === ref);
281615
- if (!target) {
281616
- console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
281617
- process.exit(1);
281618
- }
281619
- if (target.default) {
281620
- console.error(import_picocolors10.default.red(" The default project cannot be renamed."));
281621
- process.exit(1);
281622
- }
281623
- const { status: status2, body } = await request(`/api/v1/projects/${target.id}`, requireKey(), {
281624
- method: "PATCH",
281625
- body: { name }
281626
- });
281627
- if (status2 !== 200) {
281628
- console.error(
281629
- import_picocolors10.default.red(` Rename failed (${status2}): ${String(body.message ?? body.code ?? "")}`)
281630
- );
281631
- process.exit(1);
281632
- }
281633
- const updated = body;
281634
- console.log(
281635
- ` ${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})`)}`
281636
- );
281637
- return;
281638
- }
281639
- if (sub === "use") {
281640
- const ref = argv[1];
281641
- if (!ref) {
281642
- console.error(import_picocolors10.default.red(" usage: ablo projects use <slug|id|default>"));
281643
- process.exit(1);
281644
- }
281645
- const all = await fetchProjects();
281646
- const target = all.find((p2) => p2.slug === ref || p2.id === ref);
281647
- if (!target) {
281648
- console.error(import_picocolors10.default.red(` No project "${ref}".`) + import_picocolors10.default.dim(" Run ablo projects list."));
281649
- process.exit(1);
281650
- }
281651
- if (target.default) {
281652
- setActiveProject(void 0);
281653
- console.log(` ${import_picocolors10.default.green("\u2713")} now targeting the ${import_picocolors10.default.bold("default")} project`);
281654
- } else {
281655
- setActiveProject({ id: target.id, slug: target.slug });
281656
- console.log(` ${import_picocolors10.default.green("\u2713")} now targeting project ${import_picocolors10.default.bold(target.slug)} ${import_picocolors10.default.dim(`(${target.id})`)}`);
281657
- }
281658
- const guard = guardActiveProjectKey();
281659
- if (!guard.ok) {
281660
- const loginCmd = guard.activeProfile === DEFAULT_PROFILE ? "ablo login" : `ablo login --project ${guard.activeProfile}`;
281661
- console.log(
281662
- import_picocolors10.default.dim(` No key stored for this project yet \u2014 run ${import_picocolors10.default.bold(loginCmd)} to mint one.`)
281663
- );
281664
- }
281665
- return;
281666
- }
281667
- console.error(
281668
- import_picocolors10.default.red(` unknown subcommand: ${sub}`) + import_picocolors10.default.dim(
281669
- ` (expected ${import_picocolors10.default.bold("list")}, ${import_picocolors10.default.bold("create")}, ${import_picocolors10.default.bold("rename")}, or ${import_picocolors10.default.bold("use")})`
281670
- )
281671
- );
281672
- process.exit(1);
281673
- }
281674
-
281675
282052
  // src/cli/status.ts
281676
282053
  init_cjs_shims();
281677
282054
  var import_picocolors11 = __toESM(require_picocolors(), 1);
@@ -281761,17 +282138,44 @@ function formatConflict(conflict) {
281761
282138
  const parts = ["user", "agent", "system"].flatMap((k3) => conflict[k3] ? [`${k3}:${conflict[k3]}`] : []);
281762
282139
  return parts.length ? `{${parts.join(",")}}` : "";
281763
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
+ }
281764
282167
  async function status(args = []) {
281765
282168
  const apiUrl2 = (process.env.ABLO_API_URL ?? DEFAULT_URL).replace(/\/+$/, "");
281766
282169
  const cfg = readConfig();
281767
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;
281768
282173
  if (args.includes("--json")) {
281769
282174
  const entry = getKeyEntry(mode2);
281770
282175
  const key2 = describeEffectiveKey(mode2, process.env.ABLO_API_KEY, entry);
281771
282176
  const plan2 = resolvePushPlan();
281772
282177
  const activeProject2 = getActiveProject();
281773
- const effective2 = resolveEffectiveApiKey();
281774
- const pushed = await fetchPushedSchema(apiUrl2, effective2.key);
282178
+ const pushed = await fetchPushedSchema(apiUrl2, effective.key);
281775
282179
  const out = {
281776
282180
  mode: mode2,
281777
282181
  // The locally-active project (`ablo projects use`); null = org-default.
@@ -281780,8 +282184,8 @@ async function status(args = []) {
281780
282184
  // 'env' | '.env.local' | '.env' | 'stored'. Key confusion is a common
281781
282185
  // source of trouble, and the source is usually the answer.
281782
282186
  effectiveKey: {
281783
- prefix: effective2.key ? effective2.key.slice(0, 12) : null,
281784
- source: effective2.source
282187
+ prefix: effective.key ? effective.key.slice(0, 12) : null,
282188
+ source: effective.source
281785
282189
  },
281786
282190
  keyPrefix: key2.keyPrefix,
281787
282191
  keySource: key2.keySource,
@@ -281791,6 +282195,19 @@ async function status(args = []) {
281791
282195
  keyMatchesStoredActiveKey: key2.keyMatchesStoredActiveKey,
281792
282196
  keyMismatch: key2.keyMismatch,
281793
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 ?? [],
281794
282211
  // What `ablo push` would do right now — the answer to "why did push
281795
282212
  // demand a different key".
281796
282213
  push: {
@@ -281804,6 +282221,7 @@ async function status(args = []) {
281804
282221
  schema: pushed ? {
281805
282222
  active: pushed.active,
281806
282223
  version: pushed.version ?? null,
282224
+ hash: pushed.hash ?? null,
281807
282225
  pushedAt: pushed.pushedAt ?? null,
281808
282226
  models: pushed.models
281809
282227
  } : null,
@@ -281816,7 +282234,6 @@ async function status(args = []) {
281816
282234
  console.log(`
281817
282235
  ${brand("ablo")} ${import_picocolors11.default.dim("status")}
281818
282236
  `);
281819
- const effective = resolveEffectiveApiKey();
281820
282237
  if (effective.key && effective.source && effective.source !== "stored") {
281821
282238
  const label = effective.source === "env" ? "ABLO_API_KEY env" : effective.source;
281822
282239
  console.log(
@@ -281832,9 +282249,7 @@ async function status(args = []) {
281832
282249
  console.log(` ${import_picocolors11.default.yellow("!")} ${import_picocolors11.default.yellow(key.keyMismatch.message)}`);
281833
282250
  }
281834
282251
  const activeProject = getActiveProject();
281835
- console.log(
281836
- ` ${import_picocolors11.default.dim("project")} ${activeProject ? `${import_picocolors11.default.bold(activeProject.slug)} ${import_picocolors11.default.dim(`(${activeProject.id})`)}` : import_picocolors11.default.bold("default")}`
281837
- );
282252
+ printTargetLines(target, activeProject);
281838
282253
  for (const m2 of ["sandbox", "production"]) {
281839
282254
  const entry = getKeyEntry(m2);
281840
282255
  const marker = m2 === mode2 ? import_picocolors11.default.green("\u25CF") : import_picocolors11.default.dim("\u25CB");
@@ -281845,8 +282260,7 @@ async function status(args = []) {
281845
282260
  console.log(` ${marker} ${m2.padEnd(10)} ${import_picocolors11.default.dim("\u2014 no key")}`);
281846
282261
  }
281847
282262
  }
281848
- const org = activeEntry?.organizationId;
281849
- if (org) console.log(` ${import_picocolors11.default.dim("org")} ${org}`);
282263
+ const org = target?.confirmed?.organizationId ?? activeEntry?.organizationId;
281850
282264
  const plan = resolvePushPlan();
281851
282265
  console.log(
281852
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")})`)}`}`
@@ -281860,7 +282274,8 @@ async function status(args = []) {
281860
282274
  if (pushed?.active) {
281861
282275
  const when = pushed.pushedAt ? ` ${import_picocolors11.default.dim(`@ ${pushed.pushedAt.slice(0, 10)}`)}` : "";
281862
282276
  const ver = pushed.version != null ? ` ${import_picocolors11.default.dim(`(rev ${pushed.version})`)}` : "";
281863
- 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}`);
281864
282279
  for (const m2 of pushed.models) {
281865
282280
  const tn = m2.typename === m2.key ? import_picocolors11.default.dim(`typename=${m2.typename}`) : import_picocolors11.default.yellow(`typename=${m2.typename}`);
281866
282281
  const conflict = formatConflict(m2.conflict);