@eide/foir-cli 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -469,7 +469,7 @@ import { StorageService as StorageService2 } from "@eide/foir-proto-ts/storage/v
469
469
  import { OperationsService as OperationsService2 } from "@eide/foir-proto-ts/operations/v1/operations_pb";
470
470
  import { HooksService as HooksService2 } from "@eide/foir-proto-ts/hooks/v1/hooks_pb";
471
471
  import { NotificationsService as NotificationsService2 } from "@eide/foir-proto-ts/notifications/v1/notifications_pb";
472
- import { SchedulesService as SchedulesService2 } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
472
+ import { SchedulesService as SchedulesService3 } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
473
473
  import { AppsService as AppsService2 } from "@eide/foir-proto-ts/apps/v1/apps_service_pb";
474
474
  import { SecretsService as SecretsService2 } from "@eide/foir-proto-ts/secrets/v1/secrets_pb";
475
475
 
@@ -1281,6 +1281,7 @@ import {
1281
1281
  FieldSchema as ProtoFieldSchema,
1282
1282
  SharingConfigSchema as ProtoSharingConfigSchema,
1283
1283
  ModelConfigSchema as ProtoModelConfigSchema,
1284
+ LookupDefinitionSchema as ProtoLookupDefinitionSchema,
1284
1285
  CreateModelRequestSchema,
1285
1286
  GetModelRequestSchema,
1286
1287
  GetModelByKeyRequestSchema,
@@ -1328,7 +1329,13 @@ function jsConfigToProto(c) {
1328
1329
  naturalKeyField: c.naturalKeyField,
1329
1330
  systemEntity: c.systemEntity ?? false,
1330
1331
  deletable: c.deletable,
1331
- editable: c.editable
1332
+ editable: c.editable,
1333
+ lookups: c.lookups ? c.lookups.map(
1334
+ (l) => create3(ProtoLookupDefinitionSchema, {
1335
+ keyBy: l.keyBy ?? [],
1336
+ name: l.name
1337
+ })
1338
+ ) : []
1332
1339
  });
1333
1340
  }
1334
1341
  function createModelsMethods(client) {
@@ -1383,7 +1390,8 @@ function createModelsMethods(client) {
1383
1390
  config: params.config ? jsConfigToProto(params.config) : void 0,
1384
1391
  changeDescription: params.changeDescription,
1385
1392
  updatePushSnapshot: params.updatePushSnapshot ?? false,
1386
- snapshotOnly: params.snapshotOnly ?? false
1393
+ snapshotOnly: params.snapshotOnly ?? false,
1394
+ allowLookupRebuild: params.allowLookupRebuild ?? false
1387
1395
  })
1388
1396
  );
1389
1397
  return resp.model ?? null;
@@ -1464,7 +1472,10 @@ import {
1464
1472
  GlobalSearchRequestSchema,
1465
1473
  GetEmbeddingStatsRequestSchema,
1466
1474
  GetRecordEmbeddingsRequestSchema,
1467
- FindSimilarRecordsRequestSchema
1475
+ FindSimilarRecordsRequestSchema,
1476
+ WriteEmbeddingsRequestSchema,
1477
+ DeleteEmbeddingsRequestSchema,
1478
+ SearchEmbeddingsRequestSchema
1468
1479
  } from "@eide/foir-proto-ts/records/v1/records_pb";
1469
1480
  import { RecordType } from "@eide/foir-proto-ts/records/v1/records_pb";
1470
1481
  function sanitizeData(obj) {
@@ -1812,6 +1823,34 @@ function createRecordsMethods(client) {
1812
1823
  })
1813
1824
  );
1814
1825
  return resp.records ?? [];
1826
+ },
1827
+ async writeEmbeddings(params) {
1828
+ const resp = await client.writeEmbeddings(
1829
+ create4(WriteEmbeddingsRequestSchema, {
1830
+ recordId: params.recordId,
1831
+ vector: params.vector,
1832
+ dimensions: params.dimensions,
1833
+ provider: params.provider,
1834
+ modelName: params.modelName
1835
+ })
1836
+ );
1837
+ return resp.success;
1838
+ },
1839
+ async deleteEmbeddings(recordId) {
1840
+ const resp = await client.deleteEmbeddings(
1841
+ create4(DeleteEmbeddingsRequestSchema, { recordId })
1842
+ );
1843
+ return resp.success;
1844
+ },
1845
+ async searchEmbeddings(params) {
1846
+ const resp = await client.searchEmbeddings(
1847
+ create4(SearchEmbeddingsRequestSchema, {
1848
+ queryVector: params.queryVector,
1849
+ modelKey: params.modelKey,
1850
+ limit: params.limit ?? 10
1851
+ })
1852
+ );
1853
+ return resp.results ?? [];
1815
1854
  }
1816
1855
  };
1817
1856
  }
@@ -3032,8 +3071,144 @@ function createCronSchedulesMethods(client) {
3032
3071
  };
3033
3072
  }
3034
3073
 
3035
- // src/lib/rpc/secrets.ts
3074
+ // src/lib/rpc/publish-batches.ts
3036
3075
  import { create as create13 } from "@bufbuild/protobuf";
3076
+ import { timestampFromDate } from "@bufbuild/protobuf/wkt";
3077
+ import {
3078
+ ListPublishBatchesRequestSchema,
3079
+ GetPublishBatchRequestSchema,
3080
+ CreatePublishBatchRequestSchema,
3081
+ UpdatePublishBatchRequestSchema,
3082
+ DeletePublishBatchRequestSchema,
3083
+ TriggerPublishBatchRequestSchema,
3084
+ PausePublishBatchRequestSchema,
3085
+ ResumePublishBatchRequestSchema,
3086
+ PreviewRollbackBatchRequestSchema,
3087
+ RollbackPublishBatchRequestSchema,
3088
+ RetryFailedBatchItemsRequestSchema,
3089
+ AddItemsToPublishBatchRequestSchema,
3090
+ RemoveItemsFromPublishBatchRequestSchema
3091
+ } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
3092
+ function createPublishBatchesMethods(client) {
3093
+ return {
3094
+ async listPublishBatches(params = {}) {
3095
+ const resp = await client.listPublishBatches(
3096
+ create13(ListPublishBatchesRequestSchema, {
3097
+ status: params.status,
3098
+ limit: params.limit ?? 50,
3099
+ offset: params.offset ?? 0
3100
+ })
3101
+ );
3102
+ return { batches: resp.batches ?? [], total: resp.total };
3103
+ },
3104
+ async getPublishBatch(id) {
3105
+ const resp = await client.getPublishBatch(
3106
+ create13(GetPublishBatchRequestSchema, { id })
3107
+ );
3108
+ return { batch: resp.batch ?? null, items: resp.items ?? [] };
3109
+ },
3110
+ async createPublishBatch(input) {
3111
+ const resp = await client.createPublishBatch(
3112
+ create13(CreatePublishBatchRequestSchema, {
3113
+ name: input.name,
3114
+ description: input.description,
3115
+ scheduledAt: timestampFromDate(input.scheduledAt),
3116
+ versionIds: input.versionIds ?? [],
3117
+ modelIds: input.modelIds ?? [],
3118
+ operationIds: input.operationIds ?? [],
3119
+ contextDimensionIds: input.contextDimensionIds ?? [],
3120
+ authProviderIds: input.authProviderIds ?? [],
3121
+ includeProfileSchema: input.includeProfileSchema ?? false
3122
+ })
3123
+ );
3124
+ return resp.batch ?? null;
3125
+ },
3126
+ async updatePublishBatch(input) {
3127
+ const resp = await client.updatePublishBatch(
3128
+ create13(UpdatePublishBatchRequestSchema, {
3129
+ id: input.id,
3130
+ name: input.name,
3131
+ description: input.description,
3132
+ scheduledAt: input.scheduledAt ? timestampFromDate(input.scheduledAt) : void 0
3133
+ })
3134
+ );
3135
+ return resp.batch ?? null;
3136
+ },
3137
+ async deletePublishBatch(id) {
3138
+ const resp = await client.deletePublishBatch(
3139
+ create13(DeletePublishBatchRequestSchema, { id })
3140
+ );
3141
+ return resp.success;
3142
+ },
3143
+ async triggerPublishBatch(id) {
3144
+ const resp = await client.triggerPublishBatch(
3145
+ create13(TriggerPublishBatchRequestSchema, { id })
3146
+ );
3147
+ return resp.batch ?? null;
3148
+ },
3149
+ async pausePublishBatch(id) {
3150
+ const resp = await client.pausePublishBatch(
3151
+ create13(PausePublishBatchRequestSchema, { id })
3152
+ );
3153
+ return resp.batch ?? null;
3154
+ },
3155
+ async resumePublishBatch(id) {
3156
+ const resp = await client.resumePublishBatch(
3157
+ create13(ResumePublishBatchRequestSchema, { id })
3158
+ );
3159
+ return resp.batch ?? null;
3160
+ },
3161
+ async previewRollback(batchId) {
3162
+ const resp = await client.previewRollbackBatch(
3163
+ create13(PreviewRollbackBatchRequestSchema, { batchId })
3164
+ );
3165
+ return resp;
3166
+ },
3167
+ async rollbackPublishBatch(batchId) {
3168
+ const resp = await client.rollbackPublishBatch(
3169
+ create13(RollbackPublishBatchRequestSchema, { batchId })
3170
+ );
3171
+ return resp.batch ?? null;
3172
+ },
3173
+ async retryFailedItems(batchId) {
3174
+ const resp = await client.retryFailedBatchItems(
3175
+ create13(RetryFailedBatchItemsRequestSchema, { batchId })
3176
+ );
3177
+ return resp.batch ?? null;
3178
+ },
3179
+ async addItems(input) {
3180
+ const resp = await client.addItemsToPublishBatch(
3181
+ create13(AddItemsToPublishBatchRequestSchema, {
3182
+ batchId: input.batchId,
3183
+ versionIds: input.versionIds ?? [],
3184
+ modelIds: input.modelIds ?? [],
3185
+ operationIds: input.operationIds ?? [],
3186
+ contextDimensionIds: input.contextDimensionIds ?? [],
3187
+ authProviderIds: input.authProviderIds ?? [],
3188
+ includeProfileSchema: input.includeProfileSchema ?? false
3189
+ })
3190
+ );
3191
+ return resp.batch ?? null;
3192
+ },
3193
+ async removeItems(input) {
3194
+ const resp = await client.removeItemsFromPublishBatch(
3195
+ create13(RemoveItemsFromPublishBatchRequestSchema, {
3196
+ batchId: input.batchId,
3197
+ versionIds: input.versionIds ?? [],
3198
+ modelIds: input.modelIds ?? [],
3199
+ operationIds: input.operationIds ?? [],
3200
+ contextDimensionIds: input.contextDimensionIds ?? [],
3201
+ authProviderIds: input.authProviderIds ?? [],
3202
+ excludeProfileSchema: input.excludeProfileSchema ?? false
3203
+ })
3204
+ );
3205
+ return resp.batch ?? null;
3206
+ }
3207
+ };
3208
+ }
3209
+
3210
+ // src/lib/rpc/secrets.ts
3211
+ import { create as create14 } from "@bufbuild/protobuf";
3037
3212
  import {
3038
3213
  PutSecretRequestSchema,
3039
3214
  GetSecretRequestSchema,
@@ -3048,7 +3223,7 @@ function createSecretsMethods(client) {
3048
3223
  return {
3049
3224
  async put(args) {
3050
3225
  const resp = await client.putSecret(
3051
- create13(PutSecretRequestSchema, {
3226
+ create14(PutSecretRequestSchema, {
3052
3227
  tenantId: args.tenantId,
3053
3228
  projectId: args.projectId,
3054
3229
  ownerKind: args.ownerKind,
@@ -3061,12 +3236,12 @@ function createSecretsMethods(client) {
3061
3236
  },
3062
3237
  async get(ref, purpose) {
3063
3238
  return client.getSecret(
3064
- create13(GetSecretRequestSchema, { ref, purpose: purpose ?? "" })
3239
+ create14(GetSecretRequestSchema, { ref, purpose: purpose ?? "" })
3065
3240
  );
3066
3241
  },
3067
3242
  async list(args) {
3068
3243
  const resp = await client.listSecrets(
3069
- create13(ListSecretsRequestSchema, {
3244
+ create14(ListSecretsRequestSchema, {
3070
3245
  tenantId: args.tenantId,
3071
3246
  projectId: args.projectId,
3072
3247
  ownerKind: args.ownerKind ?? OwnerKind.UNSPECIFIED,
@@ -3078,19 +3253,19 @@ function createSecretsMethods(client) {
3078
3253
  },
3079
3254
  async rotate(ref, plaintext) {
3080
3255
  const resp = await client.rotateSecret(
3081
- create13(RotateSecretRequestSchema, { ref, plaintext })
3256
+ create14(RotateSecretRequestSchema, { ref, plaintext })
3082
3257
  );
3083
3258
  return resp.newRef;
3084
3259
  },
3085
3260
  async delete(ref) {
3086
- await client.deleteSecret(create13(DeleteSecretRequestSchema, { ref }));
3261
+ await client.deleteSecret(create14(DeleteSecretRequestSchema, { ref }));
3087
3262
  },
3088
3263
  async restore(ref) {
3089
- await client.restoreSecret(create13(RestoreSecretRequestSchema, { ref }));
3264
+ await client.restoreSecret(create14(RestoreSecretRequestSchema, { ref }));
3090
3265
  },
3091
3266
  async purge(args = {}) {
3092
3267
  const resp = await client.purgeSoftDeleted(
3093
- create13(PurgeSoftDeletedRequestSchema, {
3268
+ create14(PurgeSoftDeletedRequestSchema, {
3094
3269
  tenantId: args.tenantId ?? "",
3095
3270
  projectId: args.projectId ?? ""
3096
3271
  })
@@ -3154,7 +3329,10 @@ async function createPlatformClient(options) {
3154
3329
  createRpcClient(NotificationsService2, transport)
3155
3330
  ),
3156
3331
  cronSchedules: createCronSchedulesMethods(
3157
- createRpcClient(SchedulesService2, transport)
3332
+ createRpcClient(SchedulesService3, transport)
3333
+ ),
3334
+ publishBatches: createPublishBatchesMethods(
3335
+ createRpcClient(SchedulesService3, transport)
3158
3336
  ),
3159
3337
  apps: createAppsMethods(createRpcClient(AppsService2, transport)),
3160
3338
  secrets: createSecretsMethods(createRpcClient(SecretsService2, transport))
@@ -3188,7 +3366,10 @@ function createPlatformClientWithHeaders(apiUrl, headers) {
3188
3366
  createRpcClient(NotificationsService2, transport)
3189
3367
  ),
3190
3368
  cronSchedules: createCronSchedulesMethods(
3191
- createRpcClient(SchedulesService2, transport)
3369
+ createRpcClient(SchedulesService3, transport)
3370
+ ),
3371
+ publishBatches: createPublishBatchesMethods(
3372
+ createRpcClient(SchedulesService3, transport)
3192
3373
  ),
3193
3374
  apps: createAppsMethods(createRpcClient(AppsService2, transport)),
3194
3375
  secrets: createSecretsMethods(createRpcClient(SecretsService2, transport))
@@ -4890,7 +5071,15 @@ async function reconcileConfig(client, configId, manifest, options = {}) {
4890
5071
  const operationBaseUrl = manifest.operationBaseUrl ?? "";
4891
5072
  const modelConflicts = [];
4892
5073
  const mappingConflicts = [];
4893
- await reconcileModels(client, configId, manifest.models ?? [], summary, options.force ?? false, modelConflicts);
5074
+ await reconcileModels(
5075
+ client,
5076
+ configId,
5077
+ manifest.models ?? [],
5078
+ summary,
5079
+ options.force ?? false,
5080
+ options.allowLookupRebuild ?? false,
5081
+ modelConflicts
5082
+ );
4894
5083
  await reconcileOperations(client, configId, manifest.operations ?? [], operationBaseUrl, summary);
4895
5084
  await reconcileHooks(client, configId, manifest.hooks ?? [], summary);
4896
5085
  await reconcileSegments(client, configId, manifest.segments ?? [], summary);
@@ -4921,7 +5110,7 @@ async function reconcileConfig(client, configId, manifest, options = {}) {
4921
5110
  }
4922
5111
  return summary;
4923
5112
  }
4924
- async function reconcileModels(client, configId, models, summary, force, conflictOut) {
5113
+ async function reconcileModels(client, configId, models, summary, force, allowLookupRebuild, conflictOut) {
4925
5114
  const existing = await client.models.listModels({ limit: 200 });
4926
5115
  const allByKey = new Map(
4927
5116
  existing.items.map((m) => [m.key, m])
@@ -4938,6 +5127,7 @@ async function reconcileModels(client, configId, models, summary, force, conflic
4938
5127
  if (m.pluralName) config2.pluralName = m.pluralName;
4939
5128
  if (m.pluralKey) config2.pluralKey = m.pluralKey;
4940
5129
  if (m.description) config2.description = m.description;
5130
+ if (m.lookups !== void 0) config2.lookups = m.lookups;
4941
5131
  const ex = allByKey.get(m.key);
4942
5132
  if (!ex) {
4943
5133
  plans.push({ kind: "create", model: m, config: config2 });
@@ -5006,7 +5196,8 @@ async function reconcileModels(client, configId, models, summary, force, conflic
5006
5196
  name: p.name,
5007
5197
  fields: fieldsToWrite,
5008
5198
  config: p.config,
5009
- updatePushSnapshot: true
5199
+ updatePushSnapshot: true,
5200
+ allowLookupRebuild
5010
5201
  });
5011
5202
  summary.models.updated++;
5012
5203
  summary.updatedModelIds.push(p.id);
@@ -5629,6 +5820,10 @@ function registerPushCommand(program2, globalOpts) {
5629
5820
  "--publish",
5630
5821
  "Promote updated models, operations, auth providers, profile schema, and design tokens to the published channel after the push. New resources auto-publish; this flag covers updates, which are otherwise left as drafts.",
5631
5822
  false
5823
+ ).option(
5824
+ "--rebuild",
5825
+ "Accept lookup renames. A lookup with the same keyBy and a changed `name` override rebuilds its projection rows (old rows are dropped, new rows are re-emitted from records). Without this flag, the server rejects renames with a pointer here. Pure add / remove on lookups does not require this flag.",
5826
+ false
5632
5827
  ).option("--env <path>", "Path to .env file (default: .env)").action(
5633
5828
  withErrorHandler(
5634
5829
  globalOpts,
@@ -5689,7 +5884,8 @@ function registerPushCommand(program2, globalOpts) {
5689
5884
  tenantId: resolved?.project.tenantId,
5690
5885
  projectId: resolved?.project.id,
5691
5886
  force: opts.force ?? false,
5692
- publishDesignTokens: opts.publish ?? false
5887
+ publishDesignTokens: opts.publish ?? false,
5888
+ allowLookupRebuild: opts.rebuild ?? false
5693
5889
  });
5694
5890
  } catch (e) {
5695
5891
  if (e instanceof PushConflictError) {
@@ -7501,8 +7697,61 @@ function registerApiKeysCommands(program2, globalOpts) {
7501
7697
  );
7502
7698
  }
7503
7699
 
7504
- // src/commands/auth-providers.ts
7700
+ // src/commands/auth.ts
7505
7701
  import chalk11 from "chalk";
7702
+ import { CustomerAuthConfigSchema } from "@eide/foir-proto-ts/identity/v1/identity_pb";
7703
+ import { timestampDate } from "@bufbuild/protobuf/wkt";
7704
+ function registerAuthCommands(program2, globalOpts) {
7705
+ const auth = program2.command("auth").description("Inspect customer auth configuration and verify customer tokens");
7706
+ auth.command("config").description("Print the project's customer-auth configuration").action(
7707
+ withErrorHandler(globalOpts, async () => {
7708
+ const opts = globalOpts();
7709
+ const client = await createPlatformClient(opts);
7710
+ const config2 = await client.identity.getCustomerAuthConfig();
7711
+ if (!config2) {
7712
+ throw new Error("No customer auth configuration found for this project.");
7713
+ }
7714
+ formatOutputProto(CustomerAuthConfigSchema, config2, opts);
7715
+ })
7716
+ );
7717
+ auth.command("verify-token <token>").description("Verify a customer JWT and print its claims").action(
7718
+ withErrorHandler(globalOpts, async (token) => {
7719
+ const opts = globalOpts();
7720
+ const client = await createPlatformClient(opts);
7721
+ const resp = await client.identity.verifyCustomerToken(token);
7722
+ const expiresAtIso = resp.expiresAt ? timestampDate(resp.expiresAt).toISOString() : null;
7723
+ if (opts.json || opts.jsonl) {
7724
+ formatOutput(
7725
+ {
7726
+ valid: resp.valid,
7727
+ customerId: resp.customerId ?? null,
7728
+ email: resp.email ?? null,
7729
+ roles: resp.roles ?? [],
7730
+ expiresAt: expiresAtIso,
7731
+ error: resp.error ?? null
7732
+ },
7733
+ opts
7734
+ );
7735
+ return;
7736
+ }
7737
+ if (!resp.valid) {
7738
+ throw new Error(
7739
+ `Token invalid: ${resp.error ?? "no further detail"}`
7740
+ );
7741
+ }
7742
+ console.log(chalk11.green("\u25CF Valid"));
7743
+ if (resp.customerId)
7744
+ console.log(` Customer: ${chalk11.cyan(resp.customerId)}`);
7745
+ if (resp.email) console.log(` Email: ${resp.email}`);
7746
+ if (resp.roles && resp.roles.length > 0)
7747
+ console.log(` Roles: ${resp.roles.join(", ")}`);
7748
+ if (expiresAtIso) console.log(` Expires: ${expiresAtIso}`);
7749
+ })
7750
+ );
7751
+ }
7752
+
7753
+ // src/commands/auth-providers.ts
7754
+ import chalk12 from "chalk";
7506
7755
  import { AuthProviderSchema } from "@eide/foir-proto-ts/identity/v1/identity_pb";
7507
7756
  var VALID_TYPES = [
7508
7757
  "OAUTH2",
@@ -7556,7 +7805,7 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7556
7805
  formatOutputProto(AuthProviderSchema, provider, opts);
7557
7806
  } else {
7558
7807
  const p = provider;
7559
- console.log(chalk11.bold(`${p.name}`) + chalk11.gray(` (${p.key})`));
7808
+ console.log(chalk12.bold(`${p.name}`) + chalk12.gray(` (${p.key})`));
7560
7809
  console.log(` Type: ${p.type}`);
7561
7810
  console.log(` Enabled: ${p.enabled ? "yes" : "no"}`);
7562
7811
  console.log(` Default: ${p.isDefault ? "yes" : "no"}`);
@@ -7570,13 +7819,13 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7570
7819
  if (p.configDisplay) {
7571
7820
  console.log(` Config:`);
7572
7821
  console.log(
7573
- chalk11.gray(
7822
+ chalk12.gray(
7574
7823
  JSON.stringify(p.configDisplay, null, 2).replace(/^/gm, " ")
7575
7824
  )
7576
7825
  );
7577
7826
  }
7578
- console.log(chalk11.gray(` Created: ${p.createdAt}`));
7579
- console.log(chalk11.gray(` Updated: ${p.updatedAt}`));
7827
+ console.log(chalk12.gray(` Created: ${p.createdAt}`));
7828
+ console.log(chalk12.gray(` Updated: ${p.updatedAt}`));
7580
7829
  }
7581
7830
  })
7582
7831
  );
@@ -7686,6 +7935,637 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7686
7935
  );
7687
7936
  }
7688
7937
 
7938
+ // src/commands/embeddings.ts
7939
+ import chalk13 from "chalk";
7940
+ function registerEmbeddingsCommands(program2, globalOpts) {
7941
+ const embeddings = program2.command("embeddings").description("Manage vector embeddings for semantic search");
7942
+ embeddings.command("write").description("Write an embedding vector for a record").option("-d, --data <json>", "Embedding input as JSON string").option("--file <path>", "Embedding input from JSON file").action(
7943
+ withErrorHandler(globalOpts, async (cmdOpts) => {
7944
+ const opts = globalOpts();
7945
+ const input = await parseInputData({
7946
+ data: cmdOpts.data,
7947
+ file: cmdOpts.file
7948
+ });
7949
+ if (!input?.recordId) {
7950
+ throw new Error("Embedding input missing required field: recordId");
7951
+ }
7952
+ if (!Array.isArray(input.vector) || input.vector.length === 0) {
7953
+ throw new Error(
7954
+ "Embedding input missing required field: vector (non-empty number[])"
7955
+ );
7956
+ }
7957
+ const dimensions = input.dimensions ?? input.vector.length;
7958
+ const client = await createPlatformClient(opts);
7959
+ const ok = await client.records.writeEmbeddings({
7960
+ recordId: input.recordId,
7961
+ vector: input.vector,
7962
+ dimensions,
7963
+ provider: input.provider,
7964
+ modelName: input.modelName
7965
+ });
7966
+ if (opts.json || opts.jsonl) {
7967
+ formatOutput({ written: ok, recordId: input.recordId, dimensions }, opts);
7968
+ } else if (ok) {
7969
+ success(
7970
+ `Wrote ${dimensions}-dim embedding for ${input.recordId}`
7971
+ );
7972
+ } else {
7973
+ throw new Error(`Write failed for ${input.recordId}`);
7974
+ }
7975
+ })
7976
+ );
7977
+ embeddings.command("delete <recordId>").description("Delete the embedding for a record").option("--confirm", "Skip confirmation prompt").action(
7978
+ withErrorHandler(
7979
+ globalOpts,
7980
+ async (recordId, cmdOpts) => {
7981
+ const opts = globalOpts();
7982
+ const confirmed = await confirmAction(
7983
+ `Delete embedding for ${recordId}?`,
7984
+ { confirm: !!cmdOpts.confirm }
7985
+ );
7986
+ if (!confirmed) {
7987
+ console.log("Aborted.");
7988
+ return;
7989
+ }
7990
+ const client = await createPlatformClient(opts);
7991
+ const ok = await client.records.deleteEmbeddings(recordId);
7992
+ if (opts.json || opts.jsonl) {
7993
+ formatOutput({ deleted: ok, recordId }, opts);
7994
+ } else {
7995
+ success(`Deleted embedding for ${recordId}`);
7996
+ }
7997
+ }
7998
+ )
7999
+ );
8000
+ embeddings.command("search").description("Search by vector similarity").requiredOption(
8001
+ "-d, --data <json>",
8002
+ 'Search input as JSON: {"queryVector":[\u2026],"modelKey":"\u2026","limit":10}'
8003
+ ).action(
8004
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8005
+ const opts = globalOpts();
8006
+ const input = await parseInputData({
8007
+ data: cmdOpts.data
8008
+ });
8009
+ if (!Array.isArray(input.queryVector) || input.queryVector.length === 0) {
8010
+ throw new Error(
8011
+ "Search input missing required field: queryVector (non-empty number[])"
8012
+ );
8013
+ }
8014
+ const client = await createPlatformClient(opts);
8015
+ const results = await client.records.searchEmbeddings({
8016
+ queryVector: input.queryVector,
8017
+ modelKey: input.modelKey,
8018
+ limit: input.limit
8019
+ });
8020
+ if (opts.json || opts.jsonl) {
8021
+ formatOutput(results, opts);
8022
+ return;
8023
+ }
8024
+ if (results.length === 0) {
8025
+ console.log(chalk13.dim("No matches."));
8026
+ return;
8027
+ }
8028
+ for (const r of results) {
8029
+ const score = typeof r.similarity === "number" ? r.similarity.toFixed(4) : "\u2014";
8030
+ console.log(
8031
+ ` ${chalk13.cyan(r.id)} ${chalk13.dim(r.modelKey ?? "")} ${chalk13.gray(`sim=${score}`)}`
8032
+ );
8033
+ }
8034
+ })
8035
+ );
8036
+ embeddings.command("list <recordId>").description("List embeddings stored for a record").action(
8037
+ withErrorHandler(globalOpts, async (recordId) => {
8038
+ const opts = globalOpts();
8039
+ const client = await createPlatformClient(opts);
8040
+ const items = await client.records.getRecordEmbeddings(recordId);
8041
+ formatOutput(items, opts);
8042
+ })
8043
+ );
8044
+ embeddings.command("stats [modelKey]").description("Embedding statistics, optionally scoped to a model").action(
8045
+ withErrorHandler(globalOpts, async (modelKey) => {
8046
+ const opts = globalOpts();
8047
+ const client = await createPlatformClient(opts);
8048
+ const stats = await client.records.getEmbeddingStats(modelKey);
8049
+ if (opts.json || opts.jsonl) {
8050
+ formatOutput(stats, opts);
8051
+ return;
8052
+ }
8053
+ if (stats.length === 0) {
8054
+ console.log(chalk13.dim("No embeddings yet."));
8055
+ return;
8056
+ }
8057
+ for (const s of stats) {
8058
+ console.log(
8059
+ ` ${chalk13.cyan(s.modelKey)} ${chalk13.dim(`embedded=${s.embeddedRecords}/${s.totalRecords} pending=${s.pendingRecords}`)}`
8060
+ );
8061
+ }
8062
+ })
8063
+ );
8064
+ embeddings.command("similar <recordId>").description("Find records similar to the given record").option("--model-key <key>", "Limit matches to a specific model").option("--limit <n>", "Maximum results (default: 10)").action(
8065
+ withErrorHandler(
8066
+ globalOpts,
8067
+ async (recordId, cmdOpts) => {
8068
+ const opts = globalOpts();
8069
+ const client = await createPlatformClient(opts);
8070
+ const results = await client.records.findSimilarRecords({
8071
+ recordId,
8072
+ modelKey: cmdOpts.modelKey,
8073
+ limit: cmdOpts.limit ? parseInt(String(cmdOpts.limit), 10) : void 0
8074
+ });
8075
+ if (opts.json || opts.jsonl) {
8076
+ formatOutput(results, opts);
8077
+ return;
8078
+ }
8079
+ if (results.length === 0) {
8080
+ console.log(chalk13.dim("No similar records."));
8081
+ return;
8082
+ }
8083
+ for (const r of results) {
8084
+ const score = typeof r.similarity === "number" ? r.similarity.toFixed(4) : "\u2014";
8085
+ console.log(
8086
+ ` ${chalk13.cyan(r.id)} ${chalk13.dim(r.modelKey ?? "")} ${chalk13.gray(`sim=${score}`)}`
8087
+ );
8088
+ }
8089
+ }
8090
+ )
8091
+ );
8092
+ }
8093
+
8094
+ // src/commands/hooks.ts
8095
+ import {
8096
+ HookSchema,
8097
+ HookDeliverySchema
8098
+ } from "@eide/foir-proto-ts/hooks/v1/hooks_pb";
8099
+ function registerHooksCommands(program2, globalOpts) {
8100
+ const hooks = program2.command("hooks").description("Manage lifecycle hooks (event-driven webhooks)");
8101
+ hooks.command("list").description("List hooks").option("--event <event>", "Filter by event name").option("--active", "Show only active hooks").option("--inactive", "Show only inactive hooks").option("--limit <n>", "Maximum results (default: 50)").option("--offset <n>", "Pagination offset").action(
8102
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8103
+ const opts = globalOpts();
8104
+ const client = await createPlatformClient(opts);
8105
+ let isActive;
8106
+ if (cmdOpts.active) isActive = true;
8107
+ else if (cmdOpts.inactive) isActive = false;
8108
+ const resp = await client.hooks.listHooks({
8109
+ event: cmdOpts.event,
8110
+ isActive,
8111
+ limit: cmdOpts.limit ? parseInt(String(cmdOpts.limit), 10) : void 0,
8112
+ offset: cmdOpts.offset ? parseInt(String(cmdOpts.offset), 10) : void 0
8113
+ });
8114
+ formatListProto(HookSchema, resp.hooks ?? [], opts, {
8115
+ columns: [
8116
+ { key: "id", header: "ID", width: 28 },
8117
+ { key: "key", header: "Key", width: 24 },
8118
+ { key: "name", header: "Name", width: 24 },
8119
+ { key: "event", header: "Event", width: 22 },
8120
+ { key: "targetType", header: "Target", width: 14 },
8121
+ {
8122
+ key: "isActive",
8123
+ header: "Active",
8124
+ width: 7,
8125
+ format: (v) => v ? "yes" : "no"
8126
+ }
8127
+ ]
8128
+ });
8129
+ })
8130
+ );
8131
+ hooks.command("get <keyOrId>").description("Get a hook by key or ID").action(
8132
+ withErrorHandler(globalOpts, async (keyOrId) => {
8133
+ const opts = globalOpts();
8134
+ const client = await createPlatformClient(opts);
8135
+ const hook = keyOrId.includes("-") || keyOrId.length > 20 ? await client.hooks.getHook(keyOrId) : await client.hooks.getHookByKey(keyOrId);
8136
+ if (!hook) {
8137
+ throw new Error(`Hook not found: ${keyOrId}`);
8138
+ }
8139
+ formatOutputProto(HookSchema, hook, opts);
8140
+ })
8141
+ );
8142
+ hooks.command("create").description("Create a hook").option("-d, --data <json>", "Hook definition as JSON string").option("--file <path>", "Hook definition from JSON file").action(
8143
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8144
+ const opts = globalOpts();
8145
+ const input = await parseInputData({
8146
+ data: cmdOpts.data,
8147
+ file: cmdOpts.file
8148
+ });
8149
+ for (const required of ["key", "name", "event", "targetType"]) {
8150
+ if (!input[required]) {
8151
+ throw new Error(`Hook input missing required field: ${required}`);
8152
+ }
8153
+ }
8154
+ const client = await createPlatformClient(opts);
8155
+ const hook = await client.hooks.createHook({
8156
+ key: input.key,
8157
+ name: input.name,
8158
+ event: input.event,
8159
+ targetType: input.targetType,
8160
+ description: input.description,
8161
+ operationKey: input.operationKey,
8162
+ notificationConfig: input.notificationConfig,
8163
+ filter: input.filter,
8164
+ configId: input.configId
8165
+ });
8166
+ if (opts.json || opts.jsonl) {
8167
+ formatOutputProto(HookSchema, hook, opts);
8168
+ } else {
8169
+ success(`Created hook ${hook?.key} (${hook?.id})`);
8170
+ }
8171
+ })
8172
+ );
8173
+ hooks.command("update <id>").description("Update a hook").option("-d, --data <json>", "Patch fields as JSON string").option("--file <path>", "Patch fields from JSON file").option("--active", "Activate the hook").option("--no-active", "Deactivate the hook").action(
8174
+ withErrorHandler(
8175
+ globalOpts,
8176
+ async (id, cmdOpts) => {
8177
+ const opts = globalOpts();
8178
+ const patch = cmdOpts.data || cmdOpts.file ? await parseInputData({
8179
+ data: cmdOpts.data,
8180
+ file: cmdOpts.file
8181
+ }) : {};
8182
+ const update = {
8183
+ id,
8184
+ name: patch.name,
8185
+ description: patch.description,
8186
+ operationKey: patch.operationKey,
8187
+ notificationConfig: patch.notificationConfig,
8188
+ filter: patch.filter
8189
+ };
8190
+ if (cmdOpts.active !== void 0) {
8191
+ update.isActive = !!cmdOpts.active;
8192
+ }
8193
+ const client = await createPlatformClient(opts);
8194
+ const hook = await client.hooks.updateHook(update);
8195
+ if (opts.json || opts.jsonl) {
8196
+ formatOutputProto(HookSchema, hook, opts);
8197
+ } else {
8198
+ success(`Updated hook ${hook?.key} (${hook?.id})`);
8199
+ }
8200
+ }
8201
+ )
8202
+ );
8203
+ hooks.command("delete <id>").description("Delete a hook").option("--confirm", "Skip confirmation prompt").action(
8204
+ withErrorHandler(
8205
+ globalOpts,
8206
+ async (id, cmdOpts) => {
8207
+ const opts = globalOpts();
8208
+ const confirmed = await confirmAction(
8209
+ `Delete hook ${id}? This also removes its delivery history.`,
8210
+ { confirm: !!cmdOpts.confirm }
8211
+ );
8212
+ if (!confirmed) {
8213
+ console.log("Aborted.");
8214
+ return;
8215
+ }
8216
+ const client = await createPlatformClient(opts);
8217
+ const deleted = await client.hooks.deleteHook(id);
8218
+ if (opts.json || opts.jsonl) {
8219
+ formatOutput({ deleted, id }, opts);
8220
+ } else {
8221
+ success(`Deleted hook ${id}`);
8222
+ }
8223
+ }
8224
+ )
8225
+ );
8226
+ hooks.command("deliveries <hookId>").description("List recent deliveries for a hook").option("--status <status>", "Filter by status (success / failed / pending)").option("--limit <n>", "Maximum results (default: 50)").option("--offset <n>", "Pagination offset").action(
8227
+ withErrorHandler(
8228
+ globalOpts,
8229
+ async (hookId, cmdOpts) => {
8230
+ const opts = globalOpts();
8231
+ const client = await createPlatformClient(opts);
8232
+ const resp = await client.hooks.listHookDeliveries({
8233
+ hookId,
8234
+ status: cmdOpts.status,
8235
+ limit: cmdOpts.limit ? parseInt(String(cmdOpts.limit), 10) : void 0,
8236
+ offset: cmdOpts.offset ? parseInt(String(cmdOpts.offset), 10) : void 0
8237
+ });
8238
+ formatListProto(HookDeliverySchema, resp.deliveries ?? [], opts, {
8239
+ columns: [
8240
+ { key: "id", header: "ID", width: 28 },
8241
+ { key: "event", header: "Event", width: 22 },
8242
+ { key: "status", header: "Status", width: 10 },
8243
+ { key: "attempts", header: "Tries", width: 6 },
8244
+ {
8245
+ key: "durationMs",
8246
+ header: "Duration",
8247
+ width: 10,
8248
+ format: (v) => typeof v === "number" ? `${v}ms` : "\u2014"
8249
+ },
8250
+ { key: "deliveredAt", header: "Delivered", width: 24 }
8251
+ ]
8252
+ });
8253
+ }
8254
+ )
8255
+ );
8256
+ hooks.command("retry-delivery <deliveryId>").description("Retry a failed delivery").action(
8257
+ withErrorHandler(globalOpts, async (deliveryId) => {
8258
+ const opts = globalOpts();
8259
+ const client = await createPlatformClient(opts);
8260
+ const ok = await client.hooks.retryHookDelivery(deliveryId);
8261
+ if (opts.json || opts.jsonl) {
8262
+ formatOutput({ retried: ok, deliveryId }, opts);
8263
+ } else if (ok) {
8264
+ success(`Re-queued delivery ${deliveryId}`);
8265
+ } else {
8266
+ throw new Error(`Retry failed for delivery ${deliveryId}`);
8267
+ }
8268
+ })
8269
+ );
8270
+ hooks.command("test <hookId>").description("Send a test delivery against the hook").option("-d, --data <json>", "Test payload as JSON string").option("--file <path>", "Test payload from JSON file").action(
8271
+ withErrorHandler(
8272
+ globalOpts,
8273
+ async (hookId, cmdOpts) => {
8274
+ const opts = globalOpts();
8275
+ const testPayload = cmdOpts.data || cmdOpts.file ? await parseInputData({
8276
+ data: cmdOpts.data,
8277
+ file: cmdOpts.file
8278
+ }) : void 0;
8279
+ const client = await createPlatformClient(opts);
8280
+ const result = await client.hooks.testHook({ hookId, testPayload });
8281
+ if (opts.json || opts.jsonl) {
8282
+ formatOutput(result, opts);
8283
+ } else if (result.success) {
8284
+ success(`Test delivery succeeded for hook ${hookId}`);
8285
+ } else {
8286
+ throw new Error(
8287
+ `Test delivery failed: ${result.error ?? "unknown error"}`
8288
+ );
8289
+ }
8290
+ }
8291
+ )
8292
+ );
8293
+ }
8294
+
8295
+ // src/commands/rollouts.ts
8296
+ import chalk14 from "chalk";
8297
+ import { PublishBatchSchema } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
8298
+ function parseScheduledAt(value) {
8299
+ const date = new Date(value);
8300
+ if (Number.isNaN(date.getTime())) {
8301
+ throw new Error(
8302
+ `Invalid scheduledAt "${value}". Expected an ISO-8601 timestamp.`
8303
+ );
8304
+ }
8305
+ return date;
8306
+ }
8307
+ function registerRolloutsCommands(program2, globalOpts) {
8308
+ const rollouts = program2.command("rollouts").description("Manage bulk scheduled publishing rollouts");
8309
+ rollouts.command("list").description("List rollouts").option("--status <status>", "Filter by status").option("--limit <n>", "Maximum results (default: 50)").option("--offset <n>", "Pagination offset").action(
8310
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8311
+ const opts = globalOpts();
8312
+ const client = await createPlatformClient(opts);
8313
+ const { batches } = await client.publishBatches.listPublishBatches({
8314
+ status: cmdOpts.status,
8315
+ limit: cmdOpts.limit ? parseInt(String(cmdOpts.limit), 10) : void 0,
8316
+ offset: cmdOpts.offset ? parseInt(String(cmdOpts.offset), 10) : void 0
8317
+ });
8318
+ formatListProto(PublishBatchSchema, batches, opts, {
8319
+ columns: [
8320
+ { key: "id", header: "ID", width: 28 },
8321
+ { key: "name", header: "Name", width: 28 },
8322
+ { key: "status", header: "Status", width: 12 },
8323
+ { key: "itemCount", header: "Items", width: 6 },
8324
+ { key: "completedCount", header: "Done", width: 6 },
8325
+ { key: "failedCount", header: "Failed", width: 7 },
8326
+ { key: "scheduledAt", header: "Scheduled", width: 24 }
8327
+ ]
8328
+ });
8329
+ })
8330
+ );
8331
+ rollouts.command("get <id>").description("Get a rollout (with its items)").action(
8332
+ withErrorHandler(globalOpts, async (id) => {
8333
+ const opts = globalOpts();
8334
+ const client = await createPlatformClient(opts);
8335
+ const { batch, items } = await client.publishBatches.getPublishBatch(id);
8336
+ if (!batch) throw new Error(`Rollout not found: ${id}`);
8337
+ if (opts.json || opts.jsonl) {
8338
+ formatOutput({ rollout: batch, items }, opts);
8339
+ return;
8340
+ }
8341
+ formatOutputProto(PublishBatchSchema, batch, opts);
8342
+ if (items.length > 0) {
8343
+ console.log();
8344
+ console.log(chalk14.bold(`Items (${items.length}):`));
8345
+ for (const it of items) {
8346
+ console.log(
8347
+ ` ${chalk14.cyan(it.id)} ${chalk14.dim(it.targetKind)} ${chalk14.gray(it.status ?? "")}`
8348
+ );
8349
+ }
8350
+ }
8351
+ })
8352
+ );
8353
+ rollouts.command("create").description("Create a rollout").option("-d, --data <json>", "Rollout definition as JSON string").option("--file <path>", "Rollout definition from JSON file").action(
8354
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8355
+ const opts = globalOpts();
8356
+ const input = await parseInputData({
8357
+ data: cmdOpts.data,
8358
+ file: cmdOpts.file
8359
+ });
8360
+ if (!input?.name) {
8361
+ throw new Error("Rollout input missing required field: name");
8362
+ }
8363
+ if (!input.scheduledAt) {
8364
+ throw new Error("Rollout input missing required field: scheduledAt");
8365
+ }
8366
+ const scheduledAt = parseScheduledAt(input.scheduledAt);
8367
+ const client = await createPlatformClient(opts);
8368
+ const batch = await client.publishBatches.createPublishBatch({
8369
+ name: input.name,
8370
+ description: input.description,
8371
+ scheduledAt,
8372
+ versionIds: input.versionIds,
8373
+ modelIds: input.modelIds,
8374
+ operationIds: input.operationIds,
8375
+ contextDimensionIds: input.contextDimensionIds,
8376
+ authProviderIds: input.authProviderIds,
8377
+ includeProfileSchema: input.includeProfileSchema
8378
+ });
8379
+ if (opts.json || opts.jsonl) {
8380
+ formatOutputProto(PublishBatchSchema, batch, opts);
8381
+ } else {
8382
+ success(`Created rollout ${batch?.name} (${batch?.id})`);
8383
+ }
8384
+ })
8385
+ );
8386
+ rollouts.command("update <id>").description("Update a rollout (name, description, scheduledAt)").option("-d, --data <json>", "Patch fields as JSON string").option("--file <path>", "Patch fields from JSON file").action(
8387
+ withErrorHandler(
8388
+ globalOpts,
8389
+ async (id, cmdOpts) => {
8390
+ const opts = globalOpts();
8391
+ const patch = await parseInputData({
8392
+ data: cmdOpts.data,
8393
+ file: cmdOpts.file
8394
+ });
8395
+ const client = await createPlatformClient(opts);
8396
+ const batch = await client.publishBatches.updatePublishBatch({
8397
+ id,
8398
+ name: patch.name,
8399
+ description: patch.description,
8400
+ scheduledAt: patch.scheduledAt ? parseScheduledAt(patch.scheduledAt) : void 0
8401
+ });
8402
+ if (opts.json || opts.jsonl) {
8403
+ formatOutputProto(PublishBatchSchema, batch, opts);
8404
+ } else {
8405
+ success(`Updated rollout ${batch?.name} (${batch?.id})`);
8406
+ }
8407
+ }
8408
+ )
8409
+ );
8410
+ rollouts.command("delete <id>").description("Delete a rollout before it runs").option("--confirm", "Skip confirmation prompt").action(
8411
+ withErrorHandler(
8412
+ globalOpts,
8413
+ async (id, cmdOpts) => {
8414
+ const opts = globalOpts();
8415
+ const confirmed = await confirmAction(
8416
+ `Delete rollout ${id}? This cannot be undone.`,
8417
+ { confirm: !!cmdOpts.confirm }
8418
+ );
8419
+ if (!confirmed) {
8420
+ console.log("Aborted.");
8421
+ return;
8422
+ }
8423
+ const client = await createPlatformClient(opts);
8424
+ const ok = await client.publishBatches.deletePublishBatch(id);
8425
+ if (opts.json || opts.jsonl) {
8426
+ formatOutput({ deleted: ok, id }, opts);
8427
+ } else {
8428
+ success(`Deleted rollout ${id}`);
8429
+ }
8430
+ }
8431
+ )
8432
+ );
8433
+ rollouts.command("trigger <id>").description("Trigger a rollout immediately (ignore its scheduled time)").action(
8434
+ withErrorHandler(globalOpts, async (id) => {
8435
+ const opts = globalOpts();
8436
+ const client = await createPlatformClient(opts);
8437
+ const batch = await client.publishBatches.triggerPublishBatch(id);
8438
+ if (opts.json || opts.jsonl) {
8439
+ formatOutputProto(PublishBatchSchema, batch, opts);
8440
+ } else {
8441
+ success(`Triggered rollout ${batch?.name} (${batch?.id})`);
8442
+ }
8443
+ })
8444
+ );
8445
+ rollouts.command("pause <id>").description("Pause an in-flight rollout").action(
8446
+ withErrorHandler(globalOpts, async (id) => {
8447
+ const opts = globalOpts();
8448
+ const client = await createPlatformClient(opts);
8449
+ const batch = await client.publishBatches.pausePublishBatch(id);
8450
+ if (opts.json || opts.jsonl) {
8451
+ formatOutputProto(PublishBatchSchema, batch, opts);
8452
+ } else {
8453
+ success(`Paused rollout ${batch?.id}`);
8454
+ }
8455
+ })
8456
+ );
8457
+ rollouts.command("resume <id>").description("Resume a paused rollout").action(
8458
+ withErrorHandler(globalOpts, async (id) => {
8459
+ const opts = globalOpts();
8460
+ const client = await createPlatformClient(opts);
8461
+ const batch = await client.publishBatches.resumePublishBatch(id);
8462
+ if (opts.json || opts.jsonl) {
8463
+ formatOutputProto(PublishBatchSchema, batch, opts);
8464
+ } else {
8465
+ success(`Resumed rollout ${batch?.id}`);
8466
+ }
8467
+ })
8468
+ );
8469
+ rollouts.command("retry <id>").description("Retry failed items in a rollout").action(
8470
+ withErrorHandler(globalOpts, async (id) => {
8471
+ const opts = globalOpts();
8472
+ const client = await createPlatformClient(opts);
8473
+ const batch = await client.publishBatches.retryFailedItems(id);
8474
+ if (opts.json || opts.jsonl) {
8475
+ formatOutputProto(PublishBatchSchema, batch, opts);
8476
+ } else {
8477
+ success(`Re-queued failed items for rollout ${batch?.id}`);
8478
+ }
8479
+ })
8480
+ );
8481
+ rollouts.command("rollback <id>").description(
8482
+ "Roll back a completed rollout (use --preview to dry-run first)"
8483
+ ).option("--preview", "Preview the rollback without executing").option("--confirm", "Skip confirmation prompt").action(
8484
+ withErrorHandler(
8485
+ globalOpts,
8486
+ async (id, cmdOpts) => {
8487
+ const opts = globalOpts();
8488
+ const client = await createPlatformClient(opts);
8489
+ if (cmdOpts.preview) {
8490
+ const preview = await client.publishBatches.previewRollback(id);
8491
+ formatOutput(preview, opts);
8492
+ return;
8493
+ }
8494
+ const confirmed = await confirmAction(
8495
+ `Roll back rollout ${id}? This creates a new inverse rollout that reverts published changes.`,
8496
+ { confirm: !!cmdOpts.confirm }
8497
+ );
8498
+ if (!confirmed) {
8499
+ console.log("Aborted.");
8500
+ return;
8501
+ }
8502
+ const rollback = await client.publishBatches.rollbackPublishBatch(id);
8503
+ if (opts.json || opts.jsonl) {
8504
+ formatOutputProto(PublishBatchSchema, rollback, opts);
8505
+ } else {
8506
+ success(
8507
+ `Rollback rollout ${rollback?.id} created for ${id}`
8508
+ );
8509
+ }
8510
+ }
8511
+ )
8512
+ );
8513
+ rollouts.command("add-items <id>").description("Add items (records / models / operations / etc.) to a rollout").option("-d, --data <json>", "Items as JSON string").option("--file <path>", "Items from JSON file").action(
8514
+ withErrorHandler(
8515
+ globalOpts,
8516
+ async (id, cmdOpts) => {
8517
+ const opts = globalOpts();
8518
+ const items = await parseInputData({
8519
+ data: cmdOpts.data,
8520
+ file: cmdOpts.file
8521
+ });
8522
+ const client = await createPlatformClient(opts);
8523
+ const batch = await client.publishBatches.addItems({
8524
+ batchId: id,
8525
+ versionIds: items.versionIds,
8526
+ modelIds: items.modelIds,
8527
+ operationIds: items.operationIds,
8528
+ contextDimensionIds: items.contextDimensionIds,
8529
+ authProviderIds: items.authProviderIds,
8530
+ includeProfileSchema: items.includeProfileSchema
8531
+ });
8532
+ if (opts.json || opts.jsonl) {
8533
+ formatOutputProto(PublishBatchSchema, batch, opts);
8534
+ } else {
8535
+ success(`Added items to rollout ${batch?.id}`);
8536
+ }
8537
+ }
8538
+ )
8539
+ );
8540
+ rollouts.command("remove-items <id>").description("Remove items from a rollout").option("-d, --data <json>", "Items to remove as JSON string").option("--file <path>", "Items to remove from JSON file").action(
8541
+ withErrorHandler(
8542
+ globalOpts,
8543
+ async (id, cmdOpts) => {
8544
+ const opts = globalOpts();
8545
+ const items = await parseInputData({
8546
+ data: cmdOpts.data,
8547
+ file: cmdOpts.file
8548
+ });
8549
+ const client = await createPlatformClient(opts);
8550
+ const batch = await client.publishBatches.removeItems({
8551
+ batchId: id,
8552
+ versionIds: items.versionIds,
8553
+ modelIds: items.modelIds,
8554
+ operationIds: items.operationIds,
8555
+ contextDimensionIds: items.contextDimensionIds,
8556
+ authProviderIds: items.authProviderIds,
8557
+ excludeProfileSchema: items.excludeProfileSchema
8558
+ });
8559
+ if (opts.json || opts.jsonl) {
8560
+ formatOutputProto(PublishBatchSchema, batch, opts);
8561
+ } else {
8562
+ success(`Removed items from rollout ${batch?.id}`);
8563
+ }
8564
+ }
8565
+ )
8566
+ );
8567
+ }
8568
+
7689
8569
  // src/commands/locales.ts
7690
8570
  import { LocaleSchema } from "@eide/foir-proto-ts/settings/v1/settings_pb";
7691
8571
  function registerLocalesCommands(program2, globalOpts) {
@@ -7890,7 +8770,7 @@ function registerSettingsCommands(program2, globalOpts) {
7890
8770
  // src/commands/design-tokens.ts
7891
8771
  import { existsSync as existsSync6, readFileSync as readFileSync2 } from "fs";
7892
8772
  import { resolve as resolve6 } from "path";
7893
- import chalk12 from "chalk";
8773
+ import chalk15 from "chalk";
7894
8774
  function registerDesignTokensCommands(program2, globalOpts) {
7895
8775
  const tokens = program2.command("design-tokens").description("Manage project design tokens (W3C-formatted)");
7896
8776
  tokens.command("get").description("Print the current design tokens document").option("--channel <channel>", "draft | published", "draft").option(
@@ -7938,7 +8818,7 @@ function registerDesignTokensCommands(program2, globalOpts) {
7938
8818
  const resp = await client.settings.publishDesignTokens();
7939
8819
  success("Design tokens published");
7940
8820
  if (resp.breakingChanges && resp.breakingChanges.length > 0) {
7941
- console.log(chalk12.yellow("\nBreaking changes:"));
8821
+ console.log(chalk15.yellow("\nBreaking changes:"));
7942
8822
  for (const c of resp.breakingChanges) {
7943
8823
  console.log(` ${c.severity} ${c.path}: ${c.description}`);
7944
8824
  }
@@ -7954,7 +8834,7 @@ function registerDesignTokensCommands(program2, globalOpts) {
7954
8834
  const resp = await client.settings.publishDesignTokens();
7955
8835
  success("Design tokens published");
7956
8836
  if (resp.breakingChanges && resp.breakingChanges.length > 0) {
7957
- console.log(chalk12.yellow("\nBreaking changes:"));
8837
+ console.log(chalk15.yellow("\nBreaking changes:"));
7958
8838
  for (const c of resp.breakingChanges) {
7959
8839
  console.log(` ${c.severity} ${c.path}: ${c.description}`);
7960
8840
  }
@@ -7979,12 +8859,12 @@ function registerDesignTokensCommands(program2, globalOpts) {
7979
8859
  return;
7980
8860
  }
7981
8861
  if (status.hasPendingChanges) {
7982
- console.log(chalk12.yellow("\u25CF Pending changes"));
8862
+ console.log(chalk15.yellow("\u25CF Pending changes"));
7983
8863
  } else {
7984
- console.log(chalk12.green("\u25CF Up to date"));
8864
+ console.log(chalk15.green("\u25CF Up to date"));
7985
8865
  }
7986
8866
  if (status.breakingChanges && status.breakingChanges.length > 0) {
7987
- console.log(chalk12.yellow("\nPending changes:"));
8867
+ console.log(chalk15.yellow("\nPending changes:"));
7988
8868
  for (const c of status.breakingChanges) {
7989
8869
  console.log(` ${c.severity} ${c.path}: ${c.description}`);
7990
8870
  }
@@ -9156,7 +10036,11 @@ registerOperationsCommands(program, getGlobalOpts);
9156
10036
  registerSegmentsCommands(program, getGlobalOpts);
9157
10037
  registerSchedulesCommands(program, getGlobalOpts);
9158
10038
  registerApiKeysCommands(program, getGlobalOpts);
10039
+ registerAuthCommands(program, getGlobalOpts);
9159
10040
  registerAuthProvidersCommands(program, getGlobalOpts);
10041
+ registerEmbeddingsCommands(program, getGlobalOpts);
10042
+ registerHooksCommands(program, getGlobalOpts);
10043
+ registerRolloutsCommands(program, getGlobalOpts);
9160
10044
  registerLocalesCommands(program, getGlobalOpts);
9161
10045
  registerSettingsCommands(program, getGlobalOpts);
9162
10046
  registerDesignTokensCommands(program, getGlobalOpts);
@@ -67,6 +67,23 @@ type GenericFieldDefinitionInput = BaseFieldDefinitionInput & {
67
67
  };
68
68
  };
69
69
  type FieldDefinitionInput = SelectFieldDefinitionInput | EnumFieldDefinitionInput | GenericFieldDefinitionInput;
70
+ /**
71
+ * Lookup definition — declares one indexed access path on a model.
72
+ *
73
+ * `keyBy` is the ordered list of top-level scalar field keys that make
74
+ * up the lookup key. Single-field lookups are common; composites are
75
+ * first-class (e.g. `['fromHost', 'fromPath']` for a redirect model).
76
+ * `name` overrides the generated GraphQL query field name; omit it to
77
+ * use `<typeLowerCamel>By<PascalCaseKeyFields>`.
78
+ *
79
+ * Validation runs server-side via the platform's config-write RPC —
80
+ * see RFC §Data model → Constraints. The CLI does no local validation
81
+ * here; the server's structured error surfaces verbatim on failure.
82
+ */
83
+ interface LookupDefinitionInput {
84
+ keyBy: string[];
85
+ name?: string;
86
+ }
70
87
  interface ApplyConfigModelInput {
71
88
  key: string;
72
89
  name: string;
@@ -75,6 +92,12 @@ interface ApplyConfigModelInput {
75
92
  description?: string;
76
93
  fields?: FieldDefinitionInput[];
77
94
  config?: Record<string, unknown>;
95
+ /**
96
+ * Lookup definitions for this model. Server validates: existence /
97
+ * scalar-type / top-level-only / caps (4 lookups, 4 fields) / GraphQL
98
+ * field-name regex for `name`. See RFC §Data model (config surface).
99
+ */
100
+ lookups?: LookupDefinitionInput[];
78
101
  }
79
102
  interface QuotaRule {
80
103
  /** Segment key to target (empty string or omitted = default/fallback). */
@@ -453,4 +476,4 @@ interface FoirSecretsConfig {
453
476
  */
454
477
  declare function defineSecrets(config: FoirSecretsConfig): FoirSecretsConfig;
455
478
 
456
- export { type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigDesignTokensInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigScheduleInput, type ApplyConfigSegmentInput, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type ExpressionPrecondition, type FieldDefinitionInput, type FoirSecretsConfig, type Precondition, type QuotaRule, type SecretDeclaration, type SecretOwnerKind, type SegmentPrecondition, type SelectFieldConfig, type SelectFieldDefinitionInput, defineAuthProvider, defineConfig, defineDesignTokens, defineEnumField, defineField, defineHook, defineModel, defineOperation, definePlacement, defineSchedule, defineSecrets, defineSegment, defineSelectField };
479
+ export { type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigDesignTokensInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigScheduleInput, type ApplyConfigSegmentInput, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type ExpressionPrecondition, type FieldDefinitionInput, type FoirSecretsConfig, type LookupDefinitionInput, type Precondition, type QuotaRule, type SecretDeclaration, type SecretOwnerKind, type SegmentPrecondition, type SelectFieldConfig, type SelectFieldDefinitionInput, defineAuthProvider, defineConfig, defineDesignTokens, defineEnumField, defineField, defineHook, defineModel, defineOperation, definePlacement, defineSchedule, defineSecrets, defineSegment, defineSelectField };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eide/foir-cli",
3
- "version": "0.23.0",
3
+ "version": "0.25.0",
4
4
  "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -50,7 +50,7 @@
50
50
  "@bufbuild/protovalidate": "^1.1.1",
51
51
  "@connectrpc/connect": "^2.0.0",
52
52
  "@connectrpc/connect-node": "^2.0.0",
53
- "@eide/foir-proto-ts": "^0.59.0",
53
+ "@eide/foir-proto-ts": "^0.66.0",
54
54
  "chalk": "^5.3.0",
55
55
  "commander": "^12.1.0",
56
56
  "dotenv": "^16.4.5",