@eide/foir-cli 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +560 -344
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -472,6 +472,7 @@ import { HooksService as HooksService2 } from "@eide/foir-proto-ts/hooks/v1/hook
472
472
  import { NotificationsService as NotificationsService2 } from "@eide/foir-proto-ts/notifications/v1/notifications_pb";
473
473
  import { SchedulesService as SchedulesService2 } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
474
474
  import { AppsService as AppsService2 } from "@eide/foir-proto-ts/apps/v1/apps_service_pb";
475
+ import { SecretsService as SecretsService2 } from "@eide/foir-proto-ts/secrets/v1/secrets_pb";
475
476
 
476
477
  // src/lib/rpc/identity.ts
477
478
  import { create } from "@bufbuild/protobuf";
@@ -1447,21 +1448,12 @@ import {
1447
1448
  ListScheduledPublishesRequestSchema,
1448
1449
  ListDraftVersionsRequestSchema,
1449
1450
  BatchPublishVersionsRequestSchema,
1450
- CreatePublishBatchRequestSchema,
1451
- UpdatePublishBatchRequestSchema,
1452
- CancelPublishBatchRequestSchema,
1453
- RollbackPublishBatchRequestSchema,
1454
- RetryFailedBatchItemsRequestSchema,
1455
- AddItemsToPublishBatchRequestSchema,
1456
- RemoveItemsFromPublishBatchRequestSchema,
1457
- ListPublishBatchesRequestSchema,
1458
- GetPublishBatchRequestSchema,
1459
1451
  GlobalSearchRequestSchema,
1460
1452
  GetEmbeddingStatsRequestSchema,
1461
1453
  GetRecordEmbeddingsRequestSchema,
1462
1454
  FindSimilarRecordsRequestSchema
1463
1455
  } from "@eide/foir-proto-ts/records/v1/records_pb";
1464
- import { RecordType, BatchStatus } from "@eide/foir-proto-ts/records/v1/records_pb";
1456
+ import { RecordType } from "@eide/foir-proto-ts/records/v1/records_pb";
1465
1457
  function sanitizeData(obj) {
1466
1458
  const result = {};
1467
1459
  for (const [key, value] of Object.entries(obj)) {
@@ -1770,103 +1762,6 @@ function createRecordsMethods(client) {
1770
1762
  publishedCount: resp.publishedCount
1771
1763
  };
1772
1764
  },
1773
- // ── Publish Batches ───────────────────────────────────────
1774
- async createPublishBatch(params) {
1775
- const resp = await client.createPublishBatch(
1776
- create4(CreatePublishBatchRequestSchema, {
1777
- name: params.name,
1778
- versionIds: params.versionIds,
1779
- scheduledAt: params.scheduledAt ? {
1780
- seconds: BigInt(
1781
- Math.floor(params.scheduledAt.getTime() / 1e3)
1782
- ),
1783
- nanos: 0
1784
- } : void 0
1785
- })
1786
- );
1787
- return resp.batch ?? null;
1788
- },
1789
- async updatePublishBatch(params) {
1790
- const resp = await client.updatePublishBatch(
1791
- create4(UpdatePublishBatchRequestSchema, {
1792
- batchId: params.batchId,
1793
- name: params.name,
1794
- scheduledAt: params.scheduledAt ? {
1795
- seconds: BigInt(
1796
- Math.floor(params.scheduledAt.getTime() / 1e3)
1797
- ),
1798
- nanos: 0
1799
- } : void 0
1800
- })
1801
- );
1802
- return resp.batch ?? null;
1803
- },
1804
- async cancelPublishBatch(batchId) {
1805
- const resp = await client.cancelPublishBatch(
1806
- create4(CancelPublishBatchRequestSchema, { batchId })
1807
- );
1808
- return resp.batch ?? null;
1809
- },
1810
- async rollbackPublishBatch(batchId) {
1811
- const resp = await client.rollbackPublishBatch(
1812
- create4(RollbackPublishBatchRequestSchema, { batchId })
1813
- );
1814
- return resp.batch ?? null;
1815
- },
1816
- async retryFailedBatchItems(batchId) {
1817
- const resp = await client.retryFailedBatchItems(
1818
- create4(RetryFailedBatchItemsRequestSchema, { batchId })
1819
- );
1820
- return {
1821
- batch: resp.batch ?? null,
1822
- retriedCount: resp.retriedCount
1823
- };
1824
- },
1825
- async addItemsToPublishBatch(batchId, versionIds) {
1826
- const resp = await client.addItemsToPublishBatch(
1827
- create4(AddItemsToPublishBatchRequestSchema, { batchId, versionIds })
1828
- );
1829
- return resp.batch ?? null;
1830
- },
1831
- async removeItemsFromPublishBatch(batchId, versionIds) {
1832
- const resp = await client.removeItemsFromPublishBatch(
1833
- create4(RemoveItemsFromPublishBatchRequestSchema, {
1834
- batchId,
1835
- versionIds
1836
- })
1837
- );
1838
- return resp.batch ?? null;
1839
- },
1840
- async listPublishBatches(params) {
1841
- const resp = await client.listPublishBatches(
1842
- create4(ListPublishBatchesRequestSchema, {
1843
- status: params?.status,
1844
- from: params?.from ? {
1845
- seconds: BigInt(Math.floor(params.from.getTime() / 1e3)),
1846
- nanos: 0
1847
- } : void 0,
1848
- to: params?.to ? {
1849
- seconds: BigInt(Math.floor(params.to.getTime() / 1e3)),
1850
- nanos: 0
1851
- } : void 0,
1852
- limit: params?.limit ?? 50,
1853
- offset: params?.offset ?? 0
1854
- })
1855
- );
1856
- return {
1857
- items: resp.batches ?? [],
1858
- total: resp.total
1859
- };
1860
- },
1861
- async getPublishBatch(batchId) {
1862
- const resp = await client.getPublishBatch(
1863
- create4(GetPublishBatchRequestSchema, { batchId })
1864
- );
1865
- return {
1866
- batch: resp.batch ?? null,
1867
- versions: resp.versions ?? []
1868
- };
1869
- },
1870
1765
  // ── Search & Embeddings ──────────────────────────────────
1871
1766
  async globalSearch(params) {
1872
1767
  const resp = await client.globalSearch(
@@ -3241,6 +3136,74 @@ function createCronSchedulesMethods(client) {
3241
3136
  };
3242
3137
  }
3243
3138
 
3139
+ // src/lib/rpc/secrets.ts
3140
+ import { create as create14 } from "@bufbuild/protobuf";
3141
+ import {
3142
+ PutSecretRequestSchema,
3143
+ GetSecretRequestSchema,
3144
+ ListSecretsRequestSchema,
3145
+ RotateSecretRequestSchema,
3146
+ DeleteSecretRequestSchema,
3147
+ RestoreSecretRequestSchema,
3148
+ PurgeSoftDeletedRequestSchema,
3149
+ OwnerKind
3150
+ } from "@eide/foir-proto-ts/secrets/v1/secrets_pb";
3151
+ function createSecretsMethods(client) {
3152
+ return {
3153
+ async put(args) {
3154
+ const resp = await client.putSecret(
3155
+ create14(PutSecretRequestSchema, {
3156
+ tenantId: args.tenantId,
3157
+ projectId: args.projectId,
3158
+ ownerKind: args.ownerKind,
3159
+ ownerId: args.ownerId ?? "",
3160
+ label: args.label ?? "",
3161
+ plaintext: args.plaintext
3162
+ })
3163
+ );
3164
+ return resp.ref;
3165
+ },
3166
+ async get(ref, purpose) {
3167
+ return client.getSecret(
3168
+ create14(GetSecretRequestSchema, { ref, purpose: purpose ?? "" })
3169
+ );
3170
+ },
3171
+ async list(args) {
3172
+ const resp = await client.listSecrets(
3173
+ create14(ListSecretsRequestSchema, {
3174
+ tenantId: args.tenantId,
3175
+ projectId: args.projectId,
3176
+ ownerKind: args.ownerKind ?? OwnerKind.UNSPECIFIED,
3177
+ ownerId: args.ownerId ?? "",
3178
+ includeSoftDeleted: args.includeSoftDeleted ?? false
3179
+ })
3180
+ );
3181
+ return resp.secrets;
3182
+ },
3183
+ async rotate(ref, plaintext) {
3184
+ const resp = await client.rotateSecret(
3185
+ create14(RotateSecretRequestSchema, { ref, plaintext })
3186
+ );
3187
+ return resp.newRef;
3188
+ },
3189
+ async delete(ref) {
3190
+ await client.deleteSecret(create14(DeleteSecretRequestSchema, { ref }));
3191
+ },
3192
+ async restore(ref) {
3193
+ await client.restoreSecret(create14(RestoreSecretRequestSchema, { ref }));
3194
+ },
3195
+ async purge(args = {}) {
3196
+ const resp = await client.purgeSoftDeleted(
3197
+ create14(PurgeSoftDeletedRequestSchema, {
3198
+ tenantId: args.tenantId ?? "",
3199
+ projectId: args.projectId ?? ""
3200
+ })
3201
+ );
3202
+ return resp.purgedCount;
3203
+ }
3204
+ };
3205
+ }
3206
+
3244
3207
  // src/lib/client.ts
3245
3208
  async function createPlatformClient(options) {
3246
3209
  const apiUrl = getApiUrl(options);
@@ -3300,7 +3263,8 @@ async function createPlatformClient(options) {
3300
3263
  cronSchedules: createCronSchedulesMethods(
3301
3264
  createRpcClient(SchedulesService2, transport)
3302
3265
  ),
3303
- apps: createAppsMethods(createRpcClient(AppsService2, transport))
3266
+ apps: createAppsMethods(createRpcClient(AppsService2, transport)),
3267
+ secrets: createSecretsMethods(createRpcClient(SecretsService2, transport))
3304
3268
  };
3305
3269
  }
3306
3270
  function createPlatformClientWithHeaders(apiUrl, headers) {
@@ -3336,7 +3300,8 @@ function createPlatformClientWithHeaders(apiUrl, headers) {
3336
3300
  cronSchedules: createCronSchedulesMethods(
3337
3301
  createRpcClient(SchedulesService2, transport)
3338
3302
  ),
3339
- apps: createAppsMethods(createRpcClient(AppsService2, transport))
3303
+ apps: createAppsMethods(createRpcClient(AppsService2, transport)),
3304
+ secrets: createSecretsMethods(createRpcClient(SecretsService2, transport))
3340
3305
  };
3341
3306
  }
3342
3307
  async function getStorageAuth(options) {
@@ -3540,17 +3505,39 @@ async function provisionApiKey(client) {
3540
3505
 
3541
3506
  // src/lib/output.ts
3542
3507
  import chalk2 from "chalk";
3508
+ import {
3509
+ toJson
3510
+ } from "@bufbuild/protobuf";
3511
+ function bigintReplacer(_key, value) {
3512
+ return typeof value === "bigint" ? value.toString() : value;
3513
+ }
3514
+ function safeStringify(value, indent) {
3515
+ return JSON.stringify(value, bigintReplacer, indent);
3516
+ }
3543
3517
  function formatOutput(data, options) {
3544
3518
  if (options?.quiet) return;
3545
3519
  if (options?.json) {
3546
- console.log(JSON.stringify(data, null, 2));
3520
+ console.log(safeStringify(data, 2));
3547
3521
  return;
3548
3522
  }
3549
3523
  if (options?.jsonl) {
3550
- console.log(JSON.stringify(data));
3524
+ console.log(safeStringify(data));
3525
+ return;
3526
+ }
3527
+ console.log(safeStringify(data, 2));
3528
+ }
3529
+ function formatOutputProto(schema, message, options) {
3530
+ if (message == null) {
3531
+ formatOutput(null, options);
3551
3532
  return;
3552
3533
  }
3553
- console.log(JSON.stringify(data, null, 2));
3534
+ formatOutput(toJson(schema, message), options);
3535
+ }
3536
+ function formatListProto(schema, items, options, config2) {
3537
+ const converted = items.map(
3538
+ (item) => toJson(schema, item)
3539
+ );
3540
+ formatList(converted, options, config2);
3554
3541
  }
3555
3542
  function formatList(items, options, config2) {
3556
3543
  if (options?.quiet) {
@@ -3560,12 +3547,12 @@ function formatList(items, options, config2) {
3560
3547
  return;
3561
3548
  }
3562
3549
  if (options?.json) {
3563
- console.log(JSON.stringify(items, null, 2));
3550
+ console.log(safeStringify(items, 2));
3564
3551
  return;
3565
3552
  }
3566
3553
  if (options?.jsonl) {
3567
3554
  for (const item of items) {
3568
- console.log(JSON.stringify(item));
3555
+ console.log(safeStringify(item));
3569
3556
  }
3570
3557
  return;
3571
3558
  }
@@ -3703,7 +3690,11 @@ import { basename } from "path";
3703
3690
  import chalk3 from "chalk";
3704
3691
  import { createClient } from "@connectrpc/connect";
3705
3692
  import { createConnectTransport as createConnectTransport2 } from "@connectrpc/connect-node";
3706
- import { StorageService as StorageService3 } from "@eide/foir-proto-ts/storage/v1/storage_pb";
3693
+ import {
3694
+ StorageService as StorageService3,
3695
+ FileSchema,
3696
+ StorageUsageSchema
3697
+ } from "@eide/foir-proto-ts/storage/v1/storage_pb";
3707
3698
 
3708
3699
  // src/lib/input.ts
3709
3700
  import inquirer2 from "inquirer";
@@ -3876,7 +3867,7 @@ function registerMediaCommands(program2, globalOpts) {
3876
3867
  }
3877
3868
  const file = await storage.confirmFileUpload(upload.uploadId);
3878
3869
  if (opts.json || opts.jsonl) {
3879
- formatOutput(file, opts);
3870
+ formatOutputProto(FileSchema, file, opts);
3880
3871
  } else {
3881
3872
  success(`Uploaded ${filename}`);
3882
3873
  if (file?.url) {
@@ -3904,7 +3895,7 @@ function registerMediaCommands(program2, globalOpts) {
3904
3895
  limit: Number(flags.limit) || 50,
3905
3896
  offset: Number(flags.offset) || 0
3906
3897
  });
3907
- formatList(result.items, opts, {
3898
+ formatListProto(FileSchema, result.items, opts, {
3908
3899
  columns: [
3909
3900
  { key: "id", header: "ID", width: 28 },
3910
3901
  { key: "filename", header: "Filename", width: 24 },
@@ -3939,7 +3930,7 @@ function registerMediaCommands(program2, globalOpts) {
3939
3930
  const { getToken } = await getStorageAuth(opts);
3940
3931
  const storage = createStorageRpcClient(getToken);
3941
3932
  const file = await storage.getFile(id);
3942
- formatOutput(file, opts);
3933
+ formatOutputProto(FileSchema, file, opts);
3943
3934
  })
3944
3935
  );
3945
3936
  media.command("usage").description("Get storage usage").action(
@@ -3949,7 +3940,7 @@ function registerMediaCommands(program2, globalOpts) {
3949
3940
  const storage = createStorageRpcClient(getToken);
3950
3941
  const usage = await storage.getStorageUsage();
3951
3942
  if (opts.json || opts.jsonl) {
3952
- formatOutput(usage, opts);
3943
+ formatOutputProto(StorageUsageSchema, usage, opts);
3953
3944
  } else {
3954
3945
  const mb = (Number(usage?.totalBytes ?? 0) / (1024 * 1024)).toFixed(
3955
3946
  1
@@ -3973,7 +3964,7 @@ function registerMediaCommands(program2, globalOpts) {
3973
3964
  tags: flags.tags?.split(",").map((t) => t.trim())
3974
3965
  });
3975
3966
  if (opts.json || opts.jsonl) {
3976
- formatOutput(file, opts);
3967
+ formatOutputProto(FileSchema, file, opts);
3977
3968
  } else {
3978
3969
  success("Updated file");
3979
3970
  }
@@ -3994,7 +3985,7 @@ function registerMediaCommands(program2, globalOpts) {
3994
3985
  description: flags.description
3995
3986
  });
3996
3987
  if (opts.json || opts.jsonl) {
3997
- formatOutput(file, opts);
3988
+ formatOutputProto(FileSchema, file, opts);
3998
3989
  } else {
3999
3990
  success("Updated file metadata");
4000
3991
  }
@@ -4032,7 +4023,7 @@ function registerMediaCommands(program2, globalOpts) {
4032
4023
  const storage = createStorageRpcClient(getToken);
4033
4024
  const file = await storage.restoreFile(id);
4034
4025
  if (opts.json || opts.jsonl) {
4035
- formatOutput(file, opts);
4026
+ formatOutputProto(FileSchema, file, opts);
4036
4027
  } else {
4037
4028
  success("Restored file");
4038
4029
  }
@@ -4592,6 +4583,8 @@ function registerCreateConfigCommand(program2, globalOpts) {
4592
4583
  }
4593
4584
 
4594
4585
  // src/commands/search.ts
4586
+ import { toJson as toJson2 } from "@bufbuild/protobuf";
4587
+ import { SearchResultItemSchema } from "@eide/foir-proto-ts/records/v1/records_pb";
4595
4588
  function registerSearchCommands(program2, globalOpts) {
4596
4589
  program2.command("search <query>").description("Search across all records").option(
4597
4590
  "--models <keys>",
@@ -4609,7 +4602,15 @@ function registerSearchCommands(program2, globalOpts) {
4609
4602
  modelKeys
4610
4603
  });
4611
4604
  if (opts.json || opts.jsonl) {
4612
- formatOutput(result, opts);
4605
+ formatOutput(
4606
+ {
4607
+ items: result.items.map(
4608
+ (item) => toJson2(SearchResultItemSchema, item)
4609
+ ),
4610
+ total: result.total
4611
+ },
4612
+ opts
4613
+ );
4613
4614
  return;
4614
4615
  }
4615
4616
  if (result.items.length > 0) {
@@ -6267,6 +6268,7 @@ function registerProfilesCommand(program2, globalOpts) {
6267
6268
  }
6268
6269
 
6269
6270
  // src/commands/context.ts
6271
+ import { TenantSchema } from "@eide/foir-proto-ts/identity/v1/identity_pb";
6270
6272
  function registerContextCommands(program2, globalOpts) {
6271
6273
  const context = program2.command("context").description("Manage project and tenant context");
6272
6274
  context.command("projects").description("List available projects").action(
@@ -6331,7 +6333,8 @@ function registerContextCommands(program2, globalOpts) {
6331
6333
  const sessionContext = await client.identity.getSessionContext();
6332
6334
  if (!sessionContext)
6333
6335
  throw new Error("Could not retrieve session context.");
6334
- formatList(
6336
+ formatListProto(
6337
+ TenantSchema,
6335
6338
  sessionContext.availableTenants ?? [],
6336
6339
  opts,
6337
6340
  {
@@ -6346,6 +6349,10 @@ function registerContextCommands(program2, globalOpts) {
6346
6349
  }
6347
6350
 
6348
6351
  // src/commands/models.ts
6352
+ import {
6353
+ ModelSchema,
6354
+ ModelVersionSchema
6355
+ } from "@eide/foir-proto-ts/models/v1/models_pb";
6349
6356
  function registerModelsCommands(program2, globalOpts) {
6350
6357
  const models = program2.command("models").description("Manage models");
6351
6358
  models.command("list").description("List all models").option("--category <cat>", "Filter by category").option("--search <term>", "Search by name").option("--limit <n>", "Max results", "50").option("--offset <n>", "Skip results", "0").action(
@@ -6358,24 +6365,20 @@ function registerModelsCommands(program2, globalOpts) {
6358
6365
  limit: parseInt(cmdOpts.limit ?? "50", 10),
6359
6366
  offset: parseInt(cmdOpts.offset ?? "0", 10)
6360
6367
  });
6361
- formatList(
6362
- result.items,
6363
- opts,
6364
- {
6365
- columns: [
6366
- { key: "key", header: "Key", width: 24 },
6367
- { key: "name", header: "Name", width: 24 },
6368
- { key: "category", header: "Category", width: 14 },
6369
- {
6370
- key: "updatedAt",
6371
- header: "Updated",
6372
- width: 12,
6373
- format: (v) => timeAgo(v)
6374
- }
6375
- ],
6376
- total: result.total
6377
- }
6378
- );
6368
+ formatListProto(ModelSchema, result.items, opts, {
6369
+ columns: [
6370
+ { key: "key", header: "Key", width: 24 },
6371
+ { key: "name", header: "Name", width: 24 },
6372
+ { key: "category", header: "Category", width: 14 },
6373
+ {
6374
+ key: "updatedAt",
6375
+ header: "Updated",
6376
+ width: 12,
6377
+ format: (v) => timeAgo(v)
6378
+ }
6379
+ ],
6380
+ total: result.total
6381
+ });
6379
6382
  })
6380
6383
  );
6381
6384
  models.command("get <key>").description("Get a model by key").action(
@@ -6386,7 +6389,7 @@ function registerModelsCommands(program2, globalOpts) {
6386
6389
  if (!model) {
6387
6390
  throw new Error(`Model "${key}" not found.`);
6388
6391
  }
6389
- formatOutput(model, opts);
6392
+ formatOutputProto(ModelSchema, model, opts);
6390
6393
  })
6391
6394
  );
6392
6395
  models.command("create").description("Create a new model").option("-d, --data <json>", "Model data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -6395,7 +6398,7 @@ function registerModelsCommands(program2, globalOpts) {
6395
6398
  const client = await createPlatformClient(opts);
6396
6399
  const inputData = await parseInputData(cmdOpts);
6397
6400
  const model = await client.models.createModel(inputData);
6398
- formatOutput(model, opts);
6401
+ formatOutputProto(ModelSchema, model, opts);
6399
6402
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6400
6403
  success(`Created model ${model?.key}`);
6401
6404
  }
@@ -6416,7 +6419,7 @@ function registerModelsCommands(program2, globalOpts) {
6416
6419
  id: existing.id,
6417
6420
  ...inputData
6418
6421
  });
6419
- formatOutput(model, opts);
6422
+ formatOutputProto(ModelSchema, model, opts);
6420
6423
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6421
6424
  success(`Updated model ${key}`);
6422
6425
  }
@@ -6462,30 +6465,28 @@ function registerModelsCommands(program2, globalOpts) {
6462
6465
  const result = await client.models.listModelVersions(existing.id, {
6463
6466
  limit: parseInt(cmdOpts.limit ?? "20", 10)
6464
6467
  });
6465
- formatList(
6466
- result.items,
6467
- opts,
6468
- {
6469
- columns: [
6470
- { key: "id", header: "Version ID", width: 28 },
6471
- { key: "versionNumber", header: "#", width: 5 },
6472
- { key: "changeDescription", header: "Description", width: 32 },
6473
- {
6474
- key: "createdAt",
6475
- header: "Created",
6476
- width: 12,
6477
- format: (v) => timeAgo(v)
6478
- },
6479
- { key: "createdBy", header: "By", width: 18 }
6480
- ]
6481
- }
6482
- );
6468
+ formatListProto(ModelVersionSchema, result.items, opts, {
6469
+ columns: [
6470
+ { key: "id", header: "Version ID", width: 28 },
6471
+ { key: "versionNumber", header: "#", width: 5 },
6472
+ { key: "changeDescription", header: "Description", width: 32 },
6473
+ {
6474
+ key: "createdAt",
6475
+ header: "Created",
6476
+ width: 12,
6477
+ format: (v) => timeAgo(v)
6478
+ },
6479
+ { key: "createdBy", header: "By", width: 18 }
6480
+ ]
6481
+ });
6483
6482
  }
6484
6483
  )
6485
6484
  );
6486
6485
  }
6487
6486
 
6488
6487
  // src/commands/records.ts
6488
+ import { toJson as toJson3 } from "@bufbuild/protobuf";
6489
+ import { RecordSchema } from "@eide/foir-proto-ts/records/v1/records_pb";
6489
6490
  function registerRecordsCommands(program2, globalOpts) {
6490
6491
  const records = program2.command("records").description("Manage records");
6491
6492
  records.command("list <modelKey>").description("List records for a model").option("--filter <expr>", "Filter expression (e.g. status=active)").option("--limit <n>", "Max results", "20").option("--offset <n>", "Skip results", "0").action(
@@ -6501,24 +6502,20 @@ function registerRecordsCommands(program2, globalOpts) {
6501
6502
  };
6502
6503
  if (cmdOpts.filter) params.filters = parseFilters(cmdOpts.filter);
6503
6504
  const result = await client.records.listRecords(params);
6504
- formatList(
6505
- result.items,
6506
- opts,
6507
- {
6508
- columns: [
6509
- { key: "id", header: "ID", width: 28 },
6510
- { key: "naturalKey", header: "Key", width: 24 },
6511
- { key: "versionNumber", header: "Version", width: 8 },
6512
- {
6513
- key: "updatedAt",
6514
- header: "Updated",
6515
- width: 12,
6516
- format: (v) => timeAgo(v)
6517
- }
6518
- ],
6519
- total: result.total
6520
- }
6521
- );
6505
+ formatListProto(RecordSchema, result.items, opts, {
6506
+ columns: [
6507
+ { key: "id", header: "ID", width: 28 },
6508
+ { key: "naturalKey", header: "Key", width: 24 },
6509
+ { key: "versionNumber", header: "Version", width: 8 },
6510
+ {
6511
+ key: "updatedAt",
6512
+ header: "Updated",
6513
+ width: 12,
6514
+ format: (v) => timeAgo(v)
6515
+ }
6516
+ ],
6517
+ total: result.total
6518
+ });
6522
6519
  }
6523
6520
  )
6524
6521
  );
@@ -6528,23 +6525,13 @@ function registerRecordsCommands(program2, globalOpts) {
6528
6525
  async (modelKey, idOrKey, _cmdOpts) => {
6529
6526
  const opts = globalOpts();
6530
6527
  const client = await createPlatformClient(opts);
6531
- let result;
6532
- if (isUUID(idOrKey)) {
6533
- const record = await client.records.getRecord(idOrKey);
6534
- result = record;
6535
- } else {
6536
- const record = await client.records.getRecordByKey(
6537
- modelKey,
6538
- idOrKey
6539
- );
6540
- result = record;
6541
- }
6542
- if (!result) {
6528
+ const record = isUUID(idOrKey) ? await client.records.getRecord(idOrKey) : await client.records.getRecordByKey(modelKey, idOrKey);
6529
+ if (!record) {
6543
6530
  throw new Error(
6544
6531
  `Record "${idOrKey}" not found in model "${modelKey}".`
6545
6532
  );
6546
6533
  }
6547
- formatOutput(result, opts);
6534
+ formatOutputProto(RecordSchema, record, opts);
6548
6535
  }
6549
6536
  )
6550
6537
  );
@@ -6557,7 +6544,14 @@ function registerRecordsCommands(program2, globalOpts) {
6557
6544
  const inputData = await parseInputData(cmdOpts);
6558
6545
  const input = { modelKey, ...inputData };
6559
6546
  const result = await client.records.createRecord(input);
6560
- formatOutput(result, opts);
6547
+ formatOutput(
6548
+ {
6549
+ record: result.record ? toJson3(RecordSchema, result.record) : null,
6550
+ variant: result.variant ? toJson3(RecordSchema, result.variant) : null,
6551
+ version: result.version ? toJson3(RecordSchema, result.version) : null
6552
+ },
6553
+ opts
6554
+ );
6561
6555
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6562
6556
  success(`Created record ${result.record?.id}`);
6563
6557
  }
@@ -6573,7 +6567,7 @@ function registerRecordsCommands(program2, globalOpts) {
6573
6567
  const inputData = await parseInputData(cmdOpts);
6574
6568
  const input = { id, ...inputData };
6575
6569
  const record = await client.records.updateRecord(input);
6576
- formatOutput(record, opts);
6570
+ formatOutputProto(RecordSchema, record, opts);
6577
6571
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6578
6572
  success(`Updated record ${id}`);
6579
6573
  }
@@ -6634,7 +6628,7 @@ function registerRecordsCommands(program2, globalOpts) {
6634
6628
  const client = await createPlatformClient(opts);
6635
6629
  const naturalKey = cmdOpts.naturalKey ?? `${id}-copy`;
6636
6630
  const record = await client.records.duplicateRecord(id, naturalKey);
6637
- formatOutput(record, opts);
6631
+ formatOutputProto(RecordSchema, record, opts);
6638
6632
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6639
6633
  success(`Duplicated \u2192 ${record?.id}`);
6640
6634
  }
@@ -6650,39 +6644,11 @@ function registerRecordsCommands(program2, globalOpts) {
6650
6644
  const result = await client.records.listRecordVersions(parentId, {
6651
6645
  limit: parseInt(cmdOpts.limit ?? "20", 10)
6652
6646
  });
6653
- formatList(
6654
- result.items,
6655
- opts,
6656
- {
6657
- columns: [
6658
- { key: "id", header: "Version ID", width: 28 },
6659
- { key: "versionNumber", header: "#", width: 5 },
6660
- { key: "changeDescription", header: "Description", width: 30 },
6661
- {
6662
- key: "createdAt",
6663
- header: "Created",
6664
- width: 12,
6665
- format: (v) => timeAgo(v)
6666
- }
6667
- ]
6668
- }
6669
- );
6670
- }
6671
- )
6672
- );
6673
- records.command("variants <recordId>").description("List variants for a record").action(
6674
- withErrorHandler(globalOpts, async (recordId) => {
6675
- const opts = globalOpts();
6676
- const client = await createPlatformClient(opts);
6677
- const result = await client.records.listRecordVariants(recordId);
6678
- formatList(
6679
- result.items,
6680
- opts,
6681
- {
6647
+ formatListProto(RecordSchema, result.items, opts, {
6682
6648
  columns: [
6683
- { key: "id", header: "Variant ID", width: 28 },
6684
- { key: "variantKey", header: "Key", width: 20 },
6685
- { key: "isDefault", header: "Default", width: 8 },
6649
+ { key: "id", header: "Version ID", width: 28 },
6650
+ { key: "versionNumber", header: "#", width: 5 },
6651
+ { key: "changeDescription", header: "Description", width: 30 },
6686
6652
  {
6687
6653
  key: "createdAt",
6688
6654
  header: "Created",
@@ -6690,8 +6656,28 @@ function registerRecordsCommands(program2, globalOpts) {
6690
6656
  format: (v) => timeAgo(v)
6691
6657
  }
6692
6658
  ]
6693
- }
6694
- );
6659
+ });
6660
+ }
6661
+ )
6662
+ );
6663
+ records.command("variants <recordId>").description("List variants for a record").action(
6664
+ withErrorHandler(globalOpts, async (recordId) => {
6665
+ const opts = globalOpts();
6666
+ const client = await createPlatformClient(opts);
6667
+ const result = await client.records.listRecordVariants(recordId);
6668
+ formatListProto(RecordSchema, result.items, opts, {
6669
+ columns: [
6670
+ { key: "id", header: "Variant ID", width: 28 },
6671
+ { key: "variantKey", header: "Key", width: 20 },
6672
+ { key: "isDefault", header: "Default", width: 8 },
6673
+ {
6674
+ key: "createdAt",
6675
+ header: "Created",
6676
+ width: 12,
6677
+ format: (v) => timeAgo(v)
6678
+ }
6679
+ ]
6680
+ });
6695
6681
  })
6696
6682
  );
6697
6683
  records.command("create-version <parentId>").description(
@@ -6708,7 +6694,7 @@ function registerRecordsCommands(program2, globalOpts) {
6708
6694
  inputData,
6709
6695
  cmdOpts.message
6710
6696
  );
6711
- formatOutput(version2, opts);
6697
+ formatOutputProto(RecordSchema, version2, opts);
6712
6698
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6713
6699
  success(`Created version ${version2?.id}`);
6714
6700
  }
@@ -6725,7 +6711,7 @@ function registerRecordsCommands(program2, globalOpts) {
6725
6711
  recordId,
6726
6712
  cmdOpts.key
6727
6713
  );
6728
- formatOutput(variant, opts);
6714
+ formatOutputProto(RecordSchema, variant, opts);
6729
6715
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6730
6716
  success(`Created variant ${variant?.id}`);
6731
6717
  }
@@ -6735,6 +6721,7 @@ function registerRecordsCommands(program2, globalOpts) {
6735
6721
  }
6736
6722
 
6737
6723
  // src/commands/customers.ts
6724
+ import { CustomerSchema } from "@eide/foir-proto-ts/identity/v1/identity_pb";
6738
6725
  var statusMap = {
6739
6726
  ACTIVE: CustomerStatus.ACTIVE,
6740
6727
  PENDING: CustomerStatus.PENDING,
@@ -6755,52 +6742,47 @@ function registerCustomersCommands(program2, globalOpts) {
6755
6742
  limit: parseInt(cmdOpts.limit ?? "20", 10),
6756
6743
  offset: parseInt(cmdOpts.offset ?? "0", 10)
6757
6744
  });
6758
- formatList(
6759
- result.items,
6760
- opts,
6761
- {
6762
- columns: [
6763
- { key: "id", header: "ID", width: 28 },
6764
- { key: "email", header: "Email", width: 30 },
6765
- { key: "status", header: "Status", width: 12 },
6766
- {
6767
- key: "lastLoginAt",
6768
- header: "Last Login",
6769
- width: 12,
6770
- format: (v) => timeAgo(v)
6771
- },
6772
- {
6773
- key: "createdAt",
6774
- header: "Created",
6775
- width: 12,
6776
- format: (v) => timeAgo(v)
6777
- }
6778
- ],
6779
- total: result.total
6780
- }
6781
- );
6745
+ formatListProto(CustomerSchema, result.items, opts, {
6746
+ columns: [
6747
+ { key: "id", header: "ID", width: 28 },
6748
+ { key: "email", header: "Email", width: 30 },
6749
+ { key: "status", header: "Status", width: 12 },
6750
+ {
6751
+ key: "lastLoginAt",
6752
+ header: "Last Login",
6753
+ width: 12,
6754
+ format: (v) => timeAgo(v)
6755
+ },
6756
+ {
6757
+ key: "createdAt",
6758
+ header: "Created",
6759
+ width: 12,
6760
+ format: (v) => timeAgo(v)
6761
+ }
6762
+ ],
6763
+ total: result.total
6764
+ });
6782
6765
  })
6783
6766
  );
6784
6767
  customers.command("get <idOrEmail>").description("Get a customer by ID or email").action(
6785
6768
  withErrorHandler(globalOpts, async (idOrEmail) => {
6786
6769
  const opts = globalOpts();
6787
6770
  const client = await createPlatformClient(opts);
6788
- let result = null;
6771
+ let customer;
6789
6772
  if (idOrEmail.includes("@")) {
6790
6773
  const list = await client.identity.listCustomers({
6791
6774
  search: idOrEmail,
6792
6775
  limit: 1
6793
6776
  });
6794
- const match = list.items[0];
6795
- result = match ? match : null;
6777
+ customer = list.items[0] ?? null;
6796
6778
  } else {
6797
6779
  const resp = await client.identity.getCustomer(idOrEmail);
6798
- result = resp.customer;
6780
+ customer = resp.customer ?? null;
6799
6781
  }
6800
- if (!result) {
6782
+ if (!customer) {
6801
6783
  throw new Error(`Customer "${idOrEmail}" not found.`);
6802
6784
  }
6803
- formatOutput(result, opts);
6785
+ formatOutputProto(CustomerSchema, customer, opts);
6804
6786
  })
6805
6787
  );
6806
6788
  customers.command("create").description("Create a new customer").option("--email <email>", "Customer email (required)").option("-d, --data <json>", "Additional data as JSON").action(
@@ -6824,11 +6806,8 @@ function registerCustomersCommands(program2, globalOpts) {
6824
6806
  throw new Error("--email is required.");
6825
6807
  }
6826
6808
  const resp = await client.identity.createCustomer(input);
6827
- const customer = resp.customer;
6828
- formatOutput(
6829
- customer,
6830
- opts
6831
- );
6809
+ const customer = resp.customer ?? null;
6810
+ formatOutputProto(CustomerSchema, customer, opts);
6832
6811
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6833
6812
  success(`Created customer ${customer?.email}`);
6834
6813
  }
@@ -6860,6 +6839,7 @@ function registerCustomersCommands(program2, globalOpts) {
6860
6839
  }
6861
6840
 
6862
6841
  // src/commands/customer-profiles.ts
6842
+ import { CustomerProfileSchemaSchema } from "@eide/foir-proto-ts/settings/v1/settings_pb";
6863
6843
  function registerCustomerProfilesCommands(program2, globalOpts) {
6864
6844
  const cp = program2.command("customer-profiles").alias("cp").description("Manage customer profile schema and profiles");
6865
6845
  const schema = cp.command("schema").description("Manage the customer profile schema");
@@ -6878,7 +6858,7 @@ function registerCustomerProfilesCommands(program2, globalOpts) {
6878
6858
  }
6879
6859
  return;
6880
6860
  }
6881
- formatOutput(result, opts);
6861
+ formatOutputProto(CustomerProfileSchemaSchema, result, opts);
6882
6862
  })
6883
6863
  );
6884
6864
  schema.command("update").description(
@@ -6897,7 +6877,7 @@ function registerCustomerProfilesCommands(program2, globalOpts) {
6897
6877
  fields: inputData.fields,
6898
6878
  publicFields: inputData.publicFields ?? []
6899
6879
  });
6900
- formatOutput(result, opts);
6880
+ formatOutputProto(CustomerProfileSchemaSchema, result, opts);
6901
6881
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6902
6882
  const version2 = result?.version;
6903
6883
  success(`Updated customer profile schema (version ${version2})`);
@@ -6938,6 +6918,11 @@ function registerCustomerProfilesCommands(program2, globalOpts) {
6938
6918
  }
6939
6919
 
6940
6920
  // src/commands/operations.ts
6921
+ import {
6922
+ OperationSchema,
6923
+ OperationExecutionSchema,
6924
+ DeadLetterEntrySchema
6925
+ } from "@eide/foir-proto-ts/operations/v1/operations_pb";
6941
6926
  function registerOperationsCommands(program2, globalOpts) {
6942
6927
  const operations = program2.command("operations").description("Manage operations");
6943
6928
  operations.command("list").description("List operations").option("--category <cat>", "Filter by category").option("--active", "Only active operations").option("--limit <n>", "Max results", "50").action(
@@ -6949,7 +6934,7 @@ function registerOperationsCommands(program2, globalOpts) {
6949
6934
  isActive: cmdOpts.active ? true : void 0,
6950
6935
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10)
6951
6936
  });
6952
- formatList(data.operations, opts, {
6937
+ formatListProto(OperationSchema, data.operations, opts, {
6953
6938
  columns: [
6954
6939
  { key: "key", header: "Key", width: 24 },
6955
6940
  { key: "name", header: "Name", width: 24 },
@@ -6976,7 +6961,7 @@ function registerOperationsCommands(program2, globalOpts) {
6976
6961
  const client = await createPlatformClient(opts);
6977
6962
  const result = await client.operations.getOperation({ key });
6978
6963
  if (!result) throw new Error(`Operation "${key}" not found.`);
6979
- formatOutput(result, opts);
6964
+ formatOutputProto(OperationSchema, result, opts);
6980
6965
  })
6981
6966
  );
6982
6967
  operations.command("execute <key>").description("Execute an operation").option("-d, --data <json>", "Input data as JSON").option("-f, --file <path>", "Read input from file").option("--async", "Execute asynchronously").action(
@@ -6993,7 +6978,17 @@ function registerOperationsCommands(program2, globalOpts) {
6993
6978
  operationKey: key,
6994
6979
  input: inputData
6995
6980
  });
6996
- formatOutput(result.execution ?? result, opts);
6981
+ if (result.execution) {
6982
+ formatOutputProto(OperationExecutionSchema, result.execution, opts);
6983
+ } else {
6984
+ formatOutput(
6985
+ {
6986
+ completed: result.completed,
6987
+ errorMessage: result.errorMessage ?? null
6988
+ },
6989
+ opts
6990
+ );
6991
+ }
6997
6992
  if (!(opts.json || opts.jsonl || opts.quiet)) {
6998
6993
  if (result.completed) {
6999
6994
  success(`Operation completed in ${result.execution?.durationMs ?? 0}ms`);
@@ -7012,7 +7007,7 @@ function registerOperationsCommands(program2, globalOpts) {
7012
7007
  operationKey: cmdOpts.operation,
7013
7008
  limit: parseInt(cmdOpts.limit ?? "20", 10)
7014
7009
  });
7015
- formatList(data.entries, opts, {
7010
+ formatListProto(DeadLetterEntrySchema, data.entries, opts, {
7016
7011
  columns: [
7017
7012
  { key: "id", header: "ID", width: 28 },
7018
7013
  { key: "operationKey", header: "Operation", width: 20 },
@@ -7059,6 +7054,11 @@ function registerOperationsCommands(program2, globalOpts) {
7059
7054
  }
7060
7055
 
7061
7056
  // src/commands/segments.ts
7057
+ import {
7058
+ SegmentSchema,
7059
+ SegmentPreviewSchema,
7060
+ SegmentEvaluationResultSchema
7061
+ } from "@eide/foir-proto-ts/segments/v1/segments_pb";
7062
7062
  function registerSegmentsCommands(program2, globalOpts) {
7063
7063
  const segments = program2.command("segments").description("Manage segments");
7064
7064
  segments.command("list").description("List segments").option("--active", "Only active segments").option("--limit <n>", "Max results", "50").option("--offset <n>", "Skip results", "0").action(
@@ -7070,7 +7070,7 @@ function registerSegmentsCommands(program2, globalOpts) {
7070
7070
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10),
7071
7071
  offset: parseInt(String(cmdOpts.offset ?? "0"), 10)
7072
7072
  });
7073
- formatList(data.segments, opts, {
7073
+ formatListProto(SegmentSchema, data.segments, opts, {
7074
7074
  columns: [
7075
7075
  { key: "id", header: "ID", width: 28 },
7076
7076
  { key: "key", header: "Key", width: 20 },
@@ -7103,7 +7103,7 @@ function registerSegmentsCommands(program2, globalOpts) {
7103
7103
  result = await client.segments.getSegmentByKey(idOrKey);
7104
7104
  }
7105
7105
  if (!result) throw new Error(`Segment "${idOrKey}" not found.`);
7106
- formatOutput(result, opts);
7106
+ formatOutputProto(SegmentSchema, result, opts);
7107
7107
  })
7108
7108
  );
7109
7109
  segments.command("create").description("Create a new segment").option("-d, --data <json>", "Segment data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -7114,7 +7114,7 @@ function registerSegmentsCommands(program2, globalOpts) {
7114
7114
  const result = await client.segments.createSegment(
7115
7115
  input
7116
7116
  );
7117
- formatOutput(result, opts);
7117
+ formatOutputProto(SegmentSchema, result, opts);
7118
7118
  if (!(opts.json || opts.jsonl || opts.quiet))
7119
7119
  success(`Created segment ${result?.key}`);
7120
7120
  })
@@ -7130,7 +7130,7 @@ function registerSegmentsCommands(program2, globalOpts) {
7130
7130
  id,
7131
7131
  ...input
7132
7132
  });
7133
- formatOutput(result, opts);
7133
+ formatOutputProto(SegmentSchema, result, opts);
7134
7134
  if (!(opts.json || opts.jsonl || opts.quiet))
7135
7135
  success(`Updated segment ${id}`);
7136
7136
  }
@@ -7166,7 +7166,7 @@ function registerSegmentsCommands(program2, globalOpts) {
7166
7166
  rules,
7167
7167
  parseInt(cmdOpts.sampleSize ?? "100", 10)
7168
7168
  );
7169
- formatOutput(result, opts);
7169
+ formatOutputProto(SegmentPreviewSchema, result, opts);
7170
7170
  })
7171
7171
  );
7172
7172
  segments.command("test <segmentId> <customerId>").description("Test whether a customer matches a segment").action(
@@ -7179,13 +7179,17 @@ function registerSegmentsCommands(program2, globalOpts) {
7179
7179
  segmentId,
7180
7180
  customerId
7181
7181
  );
7182
- formatOutput(result, opts);
7182
+ formatOutputProto(SegmentEvaluationResultSchema, result, opts);
7183
7183
  }
7184
7184
  )
7185
7185
  );
7186
7186
  }
7187
7187
 
7188
7188
  // src/commands/experiments.ts
7189
+ import {
7190
+ ExperimentSchema,
7191
+ ExperimentStatsSchema
7192
+ } from "@eide/foir-proto-ts/experiments/v1/experiments_pb";
7189
7193
  function registerExperimentsCommands(program2, globalOpts) {
7190
7194
  const experiments = program2.command("experiments").description("Manage experiments");
7191
7195
  experiments.command("list").description("List experiments").option("--status <status>", "Filter by status").option("--active", "Only active experiments").option("--limit <n>", "Max results", "50").option("--offset <n>", "Skip results", "0").action(
@@ -7198,7 +7202,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7198
7202
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10),
7199
7203
  offset: parseInt(String(cmdOpts.offset ?? "0"), 10)
7200
7204
  });
7201
- formatList(data.experiments, opts, {
7205
+ formatListProto(ExperimentSchema, data.experiments, opts, {
7202
7206
  columns: [
7203
7207
  { key: "id", header: "ID", width: 28 },
7204
7208
  { key: "key", header: "Key", width: 20 },
@@ -7231,7 +7235,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7231
7235
  result = await client.experiments.getExperimentByKey(idOrKey);
7232
7236
  }
7233
7237
  if (!result) throw new Error(`Experiment "${idOrKey}" not found.`);
7234
- formatOutput(result, opts);
7238
+ formatOutputProto(ExperimentSchema, result, opts);
7235
7239
  })
7236
7240
  );
7237
7241
  experiments.command("create").description("Create a new experiment").option("-d, --data <json>", "Experiment data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -7242,7 +7246,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7242
7246
  const result = await client.experiments.createExperiment(
7243
7247
  input
7244
7248
  );
7245
- formatOutput(result, opts);
7249
+ formatOutputProto(ExperimentSchema, result, opts);
7246
7250
  if (!(opts.json || opts.jsonl || opts.quiet))
7247
7251
  success(`Created experiment ${result?.key}`);
7248
7252
  })
@@ -7258,7 +7262,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7258
7262
  id,
7259
7263
  ...input
7260
7264
  });
7261
- formatOutput(result, opts);
7265
+ formatOutputProto(ExperimentSchema, result, opts);
7262
7266
  if (!(opts.json || opts.jsonl || opts.quiet))
7263
7267
  success(`Updated experiment ${id}`);
7264
7268
  }
@@ -7289,7 +7293,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7289
7293
  const opts = globalOpts();
7290
7294
  const client = await createPlatformClient(opts);
7291
7295
  const result = await client.experiments.startExperiment(id);
7292
- formatOutput(result, opts);
7296
+ formatOutputProto(ExperimentSchema, result, opts);
7293
7297
  if (!(opts.json || opts.jsonl || opts.quiet))
7294
7298
  success("Experiment started");
7295
7299
  })
@@ -7299,7 +7303,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7299
7303
  const opts = globalOpts();
7300
7304
  const client = await createPlatformClient(opts);
7301
7305
  const result = await client.experiments.pauseExperiment(id);
7302
- formatOutput(result, opts);
7306
+ formatOutputProto(ExperimentSchema, result, opts);
7303
7307
  if (!(opts.json || opts.jsonl || opts.quiet))
7304
7308
  success("Experiment paused");
7305
7309
  })
@@ -7309,7 +7313,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7309
7313
  const opts = globalOpts();
7310
7314
  const client = await createPlatformClient(opts);
7311
7315
  const result = await client.experiments.resumeExperiment(id);
7312
- formatOutput(result, opts);
7316
+ formatOutputProto(ExperimentSchema, result, opts);
7313
7317
  if (!(opts.json || opts.jsonl || opts.quiet))
7314
7318
  success("Experiment resumed");
7315
7319
  })
@@ -7319,7 +7323,7 @@ function registerExperimentsCommands(program2, globalOpts) {
7319
7323
  const opts = globalOpts();
7320
7324
  const client = await createPlatformClient(opts);
7321
7325
  const result = await client.experiments.endExperiment(id);
7322
- formatOutput(result, opts);
7326
+ formatOutputProto(ExperimentSchema, result, opts);
7323
7327
  if (!(opts.json || opts.jsonl || opts.quiet))
7324
7328
  success("Experiment ended");
7325
7329
  })
@@ -7329,12 +7333,13 @@ function registerExperimentsCommands(program2, globalOpts) {
7329
7333
  const opts = globalOpts();
7330
7334
  const client = await createPlatformClient(opts);
7331
7335
  const result = await client.experiments.getExperimentStats(id);
7332
- formatOutput(result, opts);
7336
+ formatOutputProto(ExperimentStatsSchema, result, opts);
7333
7337
  })
7334
7338
  );
7335
7339
  }
7336
7340
 
7337
7341
  // src/commands/schedules.ts
7342
+ import { CronScheduleSchema } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
7338
7343
  function registerSchedulesCommands(program2, globalOpts) {
7339
7344
  const schedules = program2.command("schedules").description("Manage schedules");
7340
7345
  schedules.command("list").description("List schedules").option("--active", "Only active schedules").option("--limit <n>", "Max results", "50").action(
@@ -7345,7 +7350,7 @@ function registerSchedulesCommands(program2, globalOpts) {
7345
7350
  isActive: cmdOpts.active ? true : void 0,
7346
7351
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10)
7347
7352
  });
7348
- formatList(data.schedules, opts, {
7353
+ formatListProto(CronScheduleSchema, data.schedules, opts, {
7349
7354
  columns: [
7350
7355
  { key: "key", header: "Key", width: 20 },
7351
7356
  { key: "name", header: "Name", width: 24 },
@@ -7374,7 +7379,7 @@ function registerSchedulesCommands(program2, globalOpts) {
7374
7379
  const client = await createPlatformClient(opts);
7375
7380
  const result = await client.cronSchedules.getCronScheduleByKey(key);
7376
7381
  if (!result) throw new Error(`Schedule "${key}" not found.`);
7377
- formatOutput(result, opts);
7382
+ formatOutputProto(CronScheduleSchema, result, opts);
7378
7383
  })
7379
7384
  );
7380
7385
  schedules.command("create").description("Create a new schedule").option("-d, --data <json>", "Schedule data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -7385,7 +7390,7 @@ function registerSchedulesCommands(program2, globalOpts) {
7385
7390
  const result = await client.cronSchedules.createCronSchedule(
7386
7391
  input
7387
7392
  );
7388
- formatOutput(result, opts);
7393
+ formatOutputProto(CronScheduleSchema, result, opts);
7389
7394
  if (!(opts.json || opts.jsonl || opts.quiet))
7390
7395
  success(`Created schedule ${result?.key}`);
7391
7396
  })
@@ -7403,7 +7408,7 @@ function registerSchedulesCommands(program2, globalOpts) {
7403
7408
  id: existing.id,
7404
7409
  ...input
7405
7410
  });
7406
- formatOutput(result, opts);
7411
+ formatOutputProto(CronScheduleSchema, result, opts);
7407
7412
  if (!(opts.json || opts.jsonl || opts.quiet))
7408
7413
  success(`Updated schedule "${key}"`);
7409
7414
  }
@@ -7419,7 +7424,11 @@ function registerSchedulesCommands(program2, globalOpts) {
7419
7424
  id: existing.id
7420
7425
  });
7421
7426
  if (opts.json || opts.jsonl) {
7422
- formatOutput(result ?? { triggered: true }, opts);
7427
+ if (result) {
7428
+ formatOutputProto(CronScheduleSchema, result, opts);
7429
+ } else {
7430
+ formatOutput({ triggered: true }, opts);
7431
+ }
7423
7432
  } else {
7424
7433
  success(`Triggered schedule "${key}"`);
7425
7434
  }
@@ -7434,7 +7443,7 @@ function registerSchedulesCommands(program2, globalOpts) {
7434
7443
  const result = await client.cronSchedules.pauseCronSchedule({
7435
7444
  id: existing.id
7436
7445
  });
7437
- formatOutput(result, opts);
7446
+ formatOutputProto(CronScheduleSchema, result, opts);
7438
7447
  if (!(opts.json || opts.jsonl || opts.quiet))
7439
7448
  success(`Paused schedule "${key}"`);
7440
7449
  })
@@ -7448,7 +7457,7 @@ function registerSchedulesCommands(program2, globalOpts) {
7448
7457
  const result = await client.cronSchedules.resumeCronSchedule({
7449
7458
  id: existing.id
7450
7459
  });
7451
- formatOutput(result, opts);
7460
+ formatOutputProto(CronScheduleSchema, result, opts);
7452
7461
  if (!(opts.json || opts.jsonl || opts.quiet))
7453
7462
  success(`Resumed schedule "${key}"`);
7454
7463
  })
@@ -7481,6 +7490,8 @@ function registerSchedulesCommands(program2, globalOpts) {
7481
7490
 
7482
7491
  // src/commands/api-keys.ts
7483
7492
  import chalk10 from "chalk";
7493
+ import { toJson as toJson4 } from "@bufbuild/protobuf";
7494
+ import { ApiKeySchema } from "@eide/foir-proto-ts/identity/v1/identity_pb";
7484
7495
  function registerApiKeysCommands(program2, globalOpts) {
7485
7496
  const apiKeys = program2.command("api-keys").description("Manage API keys");
7486
7497
  apiKeys.command("list").description("List API keys").option("--include-inactive", "Include revoked/inactive keys").option("--search <term>", "Search by name").option("--limit <n>", "Max results", "50").action(
@@ -7490,7 +7501,7 @@ function registerApiKeysCommands(program2, globalOpts) {
7490
7501
  const result = await client.identity.listApiKeys({
7491
7502
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10)
7492
7503
  });
7493
- formatList(result.items, opts, {
7504
+ formatListProto(ApiKeySchema, result.items, opts, {
7494
7505
  columns: [
7495
7506
  { key: "id", header: "ID", width: 28 },
7496
7507
  { key: "name", header: "Name", width: 24 },
@@ -7529,7 +7540,12 @@ function registerApiKeysCommands(program2, globalOpts) {
7529
7540
  name: cmdOpts.name
7530
7541
  });
7531
7542
  if (opts.json || opts.jsonl) {
7532
- formatOutput(result, opts);
7543
+ formatOutput(
7544
+ {
7545
+ apiKey: result.apiKey ? toJson4(ApiKeySchema, result.apiKey) : null
7546
+ },
7547
+ opts
7548
+ );
7533
7549
  } else {
7534
7550
  success(`Created API key: ${result.apiKey?.name}`);
7535
7551
  console.log("");
@@ -7556,7 +7572,12 @@ function registerApiKeysCommands(program2, globalOpts) {
7556
7572
  const client = await createPlatformClient(opts);
7557
7573
  const result = await client.identity.rotateApiKey(id);
7558
7574
  if (opts.json || opts.jsonl) {
7559
- formatOutput(result, opts);
7575
+ formatOutput(
7576
+ {
7577
+ apiKey: result.apiKey ? toJson4(ApiKeySchema, result.apiKey) : null
7578
+ },
7579
+ opts
7580
+ );
7560
7581
  } else {
7561
7582
  success(`Rotated API key: ${result.apiKey?.name}`);
7562
7583
  console.log("");
@@ -7597,6 +7618,7 @@ function registerApiKeysCommands(program2, globalOpts) {
7597
7618
 
7598
7619
  // src/commands/auth-providers.ts
7599
7620
  import chalk11 from "chalk";
7621
+ import { AuthProviderSchema } from "@eide/foir-proto-ts/identity/v1/identity_pb";
7600
7622
  var VALID_TYPES = [
7601
7623
  "OAUTH2",
7602
7624
  "TOKEN_SSO",
@@ -7614,7 +7636,7 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7614
7636
  const result = await client.identity.listAuthProviders({
7615
7637
  enabled: cmdOpts.enabledOnly ? true : void 0
7616
7638
  });
7617
- formatList(result.items, opts, {
7639
+ formatListProto(AuthProviderSchema, result.items, opts, {
7618
7640
  columns: [
7619
7641
  { key: "id", header: "ID", width: 28 },
7620
7642
  { key: "key", header: "Key", width: 20 },
@@ -7646,7 +7668,7 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7646
7668
  throw new Error(`Auth provider not found: ${id}`);
7647
7669
  }
7648
7670
  if (opts.json || opts.jsonl) {
7649
- formatOutput(provider, opts);
7671
+ formatOutputProto(AuthProviderSchema, provider, opts);
7650
7672
  } else {
7651
7673
  const p = provider;
7652
7674
  console.log(chalk11.bold(`${p.name}`) + chalk11.gray(` (${p.key})`));
@@ -7707,7 +7729,7 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7707
7729
  input
7708
7730
  );
7709
7731
  if (opts.json || opts.jsonl) {
7710
- formatOutput(provider, opts);
7732
+ formatOutputProto(AuthProviderSchema, provider, opts);
7711
7733
  } else {
7712
7734
  success(
7713
7735
  `Created auth provider: ${provider?.name} (${provider?.key})`
@@ -7745,7 +7767,7 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7745
7767
  input
7746
7768
  );
7747
7769
  if (opts.json || opts.jsonl) {
7748
- formatOutput(provider, opts);
7770
+ formatOutputProto(AuthProviderSchema, provider, opts);
7749
7771
  } else {
7750
7772
  success(
7751
7773
  `Updated auth provider: ${provider?.name} (${provider?.key})`
@@ -7780,6 +7802,7 @@ function registerAuthProvidersCommands(program2, globalOpts) {
7780
7802
  }
7781
7803
 
7782
7804
  // src/commands/locales.ts
7805
+ import { LocaleSchema } from "@eide/foir-proto-ts/settings/v1/settings_pb";
7783
7806
  function registerLocalesCommands(program2, globalOpts) {
7784
7807
  const locales = program2.command("locales").description("Manage locales");
7785
7808
  locales.command("list").description("List locales").option("--include-inactive", "Include inactive locales").option("--limit <n>", "Max results", "50").action(
@@ -7790,7 +7813,7 @@ function registerLocalesCommands(program2, globalOpts) {
7790
7813
  includeInactive: !!cmdOpts.includeInactive,
7791
7814
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10)
7792
7815
  });
7793
- formatList(result.locales, opts, {
7816
+ formatListProto(LocaleSchema, result.locales, opts, {
7794
7817
  columns: [
7795
7818
  { key: "code", header: "Code", width: 8 },
7796
7819
  { key: "name", header: "Name", width: 20 },
@@ -7823,7 +7846,7 @@ function registerLocalesCommands(program2, globalOpts) {
7823
7846
  result = await client.settings.getLocaleByCode(idOrCode);
7824
7847
  }
7825
7848
  if (!result) throw new Error(`Locale "${idOrCode}" not found.`);
7826
- formatOutput(result, opts);
7849
+ formatOutputProto(LocaleSchema, result, opts);
7827
7850
  })
7828
7851
  );
7829
7852
  locales.command("default").description("Get the default locale").action(
@@ -7832,7 +7855,7 @@ function registerLocalesCommands(program2, globalOpts) {
7832
7855
  const client = await createPlatformClient(opts);
7833
7856
  const result = await client.settings.getDefaultLocale();
7834
7857
  if (!result) throw new Error("No default locale configured.");
7835
- formatOutput(result, opts);
7858
+ formatOutputProto(LocaleSchema, result, opts);
7836
7859
  })
7837
7860
  );
7838
7861
  locales.command("create").description("Create a new locale").option("-d, --data <json>", "Locale data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -7843,9 +7866,9 @@ function registerLocalesCommands(program2, globalOpts) {
7843
7866
  const result = await client.settings.createLocale(
7844
7867
  input
7845
7868
  );
7846
- formatOutput(result, opts);
7869
+ formatOutputProto(LocaleSchema, result, opts);
7847
7870
  if (!(opts.json || opts.jsonl || opts.quiet))
7848
- success(`Created locale ${result?.locale ?? result?.code}`);
7871
+ success(`Created locale ${result?.locale}`);
7849
7872
  })
7850
7873
  );
7851
7874
  locales.command("update <id>").description("Update a locale").option("-d, --data <json>", "Locale data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -7859,9 +7882,9 @@ function registerLocalesCommands(program2, globalOpts) {
7859
7882
  id,
7860
7883
  ...input
7861
7884
  });
7862
- formatOutput(result, opts);
7885
+ formatOutputProto(LocaleSchema, result, opts);
7863
7886
  if (!(opts.json || opts.jsonl || opts.quiet))
7864
- success(`Updated locale ${result?.locale ?? result?.code}`);
7887
+ success(`Updated locale ${result?.locale}`);
7865
7888
  }
7866
7889
  )
7867
7890
  );
@@ -7888,6 +7911,7 @@ function registerLocalesCommands(program2, globalOpts) {
7888
7911
  }
7889
7912
 
7890
7913
  // src/commands/settings.ts
7914
+ import { SettingSchema } from "@eide/foir-proto-ts/settings/v1/settings_pb";
7891
7915
  function inferDataType(value) {
7892
7916
  if (value === "true" || value === "false")
7893
7917
  return { dataType: "BOOLEAN", parsed: value === "true" };
@@ -7909,7 +7933,7 @@ function registerSettingsCommands(program2, globalOpts) {
7909
7933
  const items = await client.settings.getSettings({
7910
7934
  category: cmdOpts.category
7911
7935
  });
7912
- formatList(items, opts, {
7936
+ formatListProto(SettingSchema, items, opts, {
7913
7937
  columns: [
7914
7938
  { key: "key", header: "Key", width: 28 },
7915
7939
  {
@@ -7937,7 +7961,7 @@ function registerSettingsCommands(program2, globalOpts) {
7937
7961
  const items = await client.settings.getSettings({ key });
7938
7962
  const setting = items[0] ?? null;
7939
7963
  if (!setting) throw new Error(`Setting "${key}" not found.`);
7940
- formatOutput(setting, opts);
7964
+ formatOutputProto(SettingSchema, setting, opts);
7941
7965
  })
7942
7966
  );
7943
7967
  settings.command("set <key> <value>").description("Set a setting value").option("--category <cat>", "Category (required for new settings)").option("--data-type <type>", "Data type (STRING, NUMBER, BOOLEAN, JSON)").action(
@@ -7961,7 +7985,7 @@ function registerSettingsCommands(program2, globalOpts) {
7961
7985
  dataType
7962
7986
  }
7963
7987
  });
7964
- formatOutput(result, opts);
7988
+ formatOutputProto(SettingSchema, result, opts);
7965
7989
  if (!(opts.json || opts.jsonl || opts.quiet))
7966
7990
  success(`Set ${key} = ${value}`);
7967
7991
  }
@@ -7979,6 +8003,7 @@ function registerSettingsCommands(program2, globalOpts) {
7979
8003
  }
7980
8004
 
7981
8005
  // src/commands/variant-catalog.ts
8006
+ import { VariantCatalogEntrySchema } from "@eide/foir-proto-ts/settings/v1/settings_pb";
7982
8007
  function registerVariantCatalogCommands(program2, globalOpts) {
7983
8008
  const catalog = program2.command("variant-catalog").description("Manage variant catalog entries (markets, devices, locales)");
7984
8009
  catalog.command("list").description("List variant catalog entries").option("--active", "Only active entries").option("--limit <n>", "Max results", "50").action(
@@ -7989,7 +8014,7 @@ function registerVariantCatalogCommands(program2, globalOpts) {
7989
8014
  isActive: cmdOpts.active ? true : void 0,
7990
8015
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10)
7991
8016
  });
7992
- formatList(result.entries, opts, {
8017
+ formatListProto(VariantCatalogEntrySchema, result.entries, opts, {
7993
8018
  columns: [
7994
8019
  { key: "key", header: "Key", width: 20 },
7995
8020
  { key: "name", header: "Name", width: 24 },
@@ -8024,7 +8049,7 @@ function registerVariantCatalogCommands(program2, globalOpts) {
8024
8049
  }
8025
8050
  if (!result)
8026
8051
  throw new Error(`Variant catalog entry "${idOrKey}" not found.`);
8027
- formatOutput(result, opts);
8052
+ formatOutputProto(VariantCatalogEntrySchema, result, opts);
8028
8053
  })
8029
8054
  );
8030
8055
  catalog.command("create").description("Create a variant catalog entry").option("-d, --data <json>", "Entry data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -8035,11 +8060,9 @@ function registerVariantCatalogCommands(program2, globalOpts) {
8035
8060
  const result = await client.settings.createVariantCatalogEntry(
8036
8061
  input
8037
8062
  );
8038
- formatOutput(result, opts);
8063
+ formatOutputProto(VariantCatalogEntrySchema, result, opts);
8039
8064
  if (!(opts.json || opts.jsonl || opts.quiet))
8040
- success(
8041
- `Created variant catalog entry ${result?.key}`
8042
- );
8065
+ success(`Created variant catalog entry ${result?.key}`);
8043
8066
  })
8044
8067
  );
8045
8068
  catalog.command("update <id>").description("Update a variant catalog entry").option("-d, --data <json>", "Entry data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -8053,11 +8076,9 @@ function registerVariantCatalogCommands(program2, globalOpts) {
8053
8076
  id,
8054
8077
  ...input
8055
8078
  });
8056
- formatOutput(result, opts);
8079
+ formatOutputProto(VariantCatalogEntrySchema, result, opts);
8057
8080
  if (!(opts.json || opts.jsonl || opts.quiet))
8058
- success(
8059
- `Updated variant catalog entry ${result?.key}`
8060
- );
8081
+ success(`Updated variant catalog entry ${result?.key}`);
8061
8082
  }
8062
8083
  )
8063
8084
  );
@@ -8085,6 +8106,10 @@ function registerVariantCatalogCommands(program2, globalOpts) {
8085
8106
  }
8086
8107
 
8087
8108
  // src/commands/files.ts
8109
+ import {
8110
+ FileSchema as FileSchema2,
8111
+ StorageUsageSchema as StorageUsageSchema2
8112
+ } from "@eide/foir-proto-ts/storage/v1/storage_pb";
8088
8113
  function registerFilesCommands(program2, globalOpts) {
8089
8114
  const files = program2.command("files").description("Manage files (for upload, use `foir media upload`)");
8090
8115
  files.command("list").description("List files").option("--folder <folder>", "Filter by folder").option("--mime-type <type>", "Filter by MIME type").option("--search <term>", "Search by filename").option("--limit <n>", "Max results", "50").option("--offset <n>", "Skip results", "0").action(
@@ -8098,7 +8123,7 @@ function registerFilesCommands(program2, globalOpts) {
8098
8123
  limit: parseInt(cmdOpts.limit ?? "50", 10),
8099
8124
  offset: parseInt(cmdOpts.offset ?? "0", 10)
8100
8125
  });
8101
- formatList(data.items, opts, {
8126
+ formatListProto(FileSchema2, data.items, opts, {
8102
8127
  columns: [
8103
8128
  { key: "id", header: "ID", width: 28 },
8104
8129
  { key: "filename", header: "Filename", width: 30 },
@@ -8127,7 +8152,7 @@ function registerFilesCommands(program2, globalOpts) {
8127
8152
  const client = await createPlatformClient(opts);
8128
8153
  const file = await client.storage.getFile(id);
8129
8154
  if (!file) throw new Error(`File "${id}" not found.`);
8130
- formatOutput(file, opts);
8155
+ formatOutputProto(FileSchema2, file, opts);
8131
8156
  })
8132
8157
  );
8133
8158
  files.command("usage").description("Get storage usage statistics").action(
@@ -8135,7 +8160,7 @@ function registerFilesCommands(program2, globalOpts) {
8135
8160
  const opts = globalOpts();
8136
8161
  const client = await createPlatformClient(opts);
8137
8162
  const usage = await client.storage.getStorageUsage();
8138
- formatOutput(usage, opts);
8163
+ formatOutputProto(StorageUsageSchema2, usage, opts);
8139
8164
  })
8140
8165
  );
8141
8166
  files.command("update <id>").description("Update file properties").option("--filename <name>", "New filename").option("--folder <folder>", "Move to folder").option("--tags <tags>", "Comma-separated tags").action(
@@ -8150,7 +8175,7 @@ function registerFilesCommands(program2, globalOpts) {
8150
8175
  if (cmdOpts.tags)
8151
8176
  params.tags = cmdOpts.tags.split(",").map((t) => t.trim());
8152
8177
  const file = await client.storage.updateFile(params);
8153
- formatOutput(file, opts);
8178
+ formatOutputProto(FileSchema2, file, opts);
8154
8179
  if (!(opts.json || opts.jsonl || opts.quiet))
8155
8180
  success(`Updated file ${id}`);
8156
8181
  }
@@ -8168,7 +8193,7 @@ function registerFilesCommands(program2, globalOpts) {
8168
8193
  caption: cmdOpts.caption,
8169
8194
  description: cmdOpts.description
8170
8195
  });
8171
- formatOutput(file, opts);
8196
+ formatOutputProto(FileSchema2, file, opts);
8172
8197
  if (!(opts.json || opts.jsonl || opts.quiet))
8173
8198
  success(`Updated metadata for file ${id}`);
8174
8199
  }
@@ -8206,6 +8231,7 @@ function formatBytes(bytes) {
8206
8231
  }
8207
8232
 
8208
8233
  // src/commands/notes.ts
8234
+ import { NoteSchema } from "@eide/foir-proto-ts/settings/v1/settings_pb";
8209
8235
  function registerNotesCommands(program2, globalOpts) {
8210
8236
  const notes = program2.command("notes").description("Manage notes and comments");
8211
8237
  notes.command("list").description("List notes for an entity").requiredOption("--entity-type <type>", "Entity type (e.g. record, model)").requiredOption("--entity-id <id>", "Entity ID").option("--include-resolved", "Include resolved notes").option("--limit <n>", "Max results", "20").action(
@@ -8217,7 +8243,7 @@ function registerNotesCommands(program2, globalOpts) {
8217
8243
  entityId: cmdOpts.entityId,
8218
8244
  limit: parseInt(String(cmdOpts.limit ?? "20"), 10)
8219
8245
  });
8220
- formatList(data.notes, opts, {
8246
+ formatListProto(NoteSchema, data.notes, opts, {
8221
8247
  columns: [
8222
8248
  { key: "id", header: "ID", width: 28 },
8223
8249
  { key: "content", header: "Content", width: 40 },
@@ -8245,7 +8271,7 @@ function registerNotesCommands(program2, globalOpts) {
8245
8271
  const client = await createPlatformClient(opts);
8246
8272
  const note = await client.settings.getNote(id);
8247
8273
  if (!note) throw new Error(`Note "${id}" not found.`);
8248
- formatOutput(note, opts);
8274
+ formatOutputProto(NoteSchema, note, opts);
8249
8275
  })
8250
8276
  );
8251
8277
  notes.command("create").description("Create a note").requiredOption("--entity-type <type>", "Entity type").requiredOption("--entity-id <id>", "Entity ID").requiredOption("--body <text>", "Note body text").option("--parent-note-id <id>", "Reply to a note").action(
@@ -8261,7 +8287,7 @@ function registerNotesCommands(program2, globalOpts) {
8261
8287
  };
8262
8288
  if (cmdOpts.parentNoteId) params.parentNoteId = cmdOpts.parentNoteId;
8263
8289
  const note = await client.settings.createNote(params);
8264
- formatOutput(note, opts);
8290
+ formatOutputProto(NoteSchema, note, opts);
8265
8291
  if (!(opts.json || opts.jsonl || opts.quiet)) success("Note created");
8266
8292
  })
8267
8293
  );
@@ -8275,7 +8301,7 @@ function registerNotesCommands(program2, globalOpts) {
8275
8301
  id,
8276
8302
  isResolved: true
8277
8303
  });
8278
- formatOutput(note, opts);
8304
+ formatOutputProto(NoteSchema, note, opts);
8279
8305
  if (!(opts.json || opts.jsonl || opts.quiet))
8280
8306
  success(`Resolved note ${id}`);
8281
8307
  }
@@ -8304,6 +8330,7 @@ function registerNotesCommands(program2, globalOpts) {
8304
8330
  }
8305
8331
 
8306
8332
  // src/commands/notifications.ts
8333
+ import { NotificationSchema } from "@eide/foir-proto-ts/notifications/v1/notifications_pb";
8307
8334
  function registerNotificationsCommands(program2, globalOpts) {
8308
8335
  const notifications = program2.command("notifications").description("Manage notifications");
8309
8336
  notifications.command("list").description("List notifications").option("--unread", "Only unread notifications").option("--limit <n>", "Max results", "20").action(
@@ -8318,7 +8345,7 @@ function registerNotificationsCommands(program2, globalOpts) {
8318
8345
  console.log(`Unread: ${data.unreadCount}
8319
8346
  `);
8320
8347
  }
8321
- formatList(data.notifications, opts, {
8348
+ formatListProto(NotificationSchema, data.notifications, opts, {
8322
8349
  columns: [
8323
8350
  { key: "id", header: "ID", width: 28 },
8324
8351
  { key: "type", header: "Type", width: 16 },
@@ -8367,6 +8394,7 @@ function registerNotificationsCommands(program2, globalOpts) {
8367
8394
  }
8368
8395
 
8369
8396
  // src/commands/configs.ts
8397
+ import { ConfigSchema } from "@eide/foir-proto-ts/configs/v1/configs_pb";
8370
8398
  function registerConfigsCommands(program2, globalOpts) {
8371
8399
  const configs = program2.command("configs").description("Manage configs (apps, webhooks)");
8372
8400
  configs.command("list").description("List configs").option("--type <type>", "Filter by config type").option("--enabled", "Only enabled configs").option("--limit <n>", "Max results", "50").action(
@@ -8378,7 +8406,7 @@ function registerConfigsCommands(program2, globalOpts) {
8378
8406
  enabled: cmdOpts.enabled ? true : void 0,
8379
8407
  limit: parseInt(String(cmdOpts.limit ?? "50"), 10)
8380
8408
  });
8381
- formatList(data.configs, opts, {
8409
+ formatListProto(ConfigSchema, data.configs, opts, {
8382
8410
  columns: [
8383
8411
  { key: "id", header: "ID", width: 28 },
8384
8412
  { key: "key", header: "Key", width: 20 },
@@ -8406,7 +8434,7 @@ function registerConfigsCommands(program2, globalOpts) {
8406
8434
  result = await client.configs.getConfigByKey(idOrKey);
8407
8435
  }
8408
8436
  if (!result) throw new Error(`Config "${idOrKey}" not found.`);
8409
- formatOutput(result, opts);
8437
+ formatOutputProto(ConfigSchema, result, opts);
8410
8438
  })
8411
8439
  );
8412
8440
  configs.command("create").description("Create a new config").option("-d, --data <json>", "Config data as JSON").option("-f, --file <path>", "Read data from file").action(
@@ -8415,7 +8443,7 @@ function registerConfigsCommands(program2, globalOpts) {
8415
8443
  const client = await createPlatformClient(opts);
8416
8444
  const input = await parseInputData(cmdOpts);
8417
8445
  const config2 = await client.configs.createConfig(input);
8418
- formatOutput(config2, opts);
8446
+ formatOutputProto(ConfigSchema, config2, opts);
8419
8447
  if (!(opts.json || opts.jsonl || opts.quiet))
8420
8448
  success(`Created config ${config2?.key}`);
8421
8449
  })
@@ -8437,6 +8465,10 @@ function registerConfigsCommands(program2, globalOpts) {
8437
8465
  }
8438
8466
 
8439
8467
  // src/commands/apps.ts
8468
+ import {
8469
+ AppSchema,
8470
+ ValidateManifestResponseSchema
8471
+ } from "@eide/foir-proto-ts/apps/v1/apps_service_pb";
8440
8472
  function registerAppsCommands(program2, globalOpts) {
8441
8473
  const apps = program2.command("apps").description("Install and manage apps");
8442
8474
  apps.command("list").description("List installed apps").action(
@@ -8448,7 +8480,7 @@ function registerAppsCommands(program2, globalOpts) {
8448
8480
  resolved.project.tenantId,
8449
8481
  resolved.project.id
8450
8482
  );
8451
- formatList(apps2, opts, {
8483
+ formatListProto(AppSchema, apps2, opts, {
8452
8484
  columns: [
8453
8485
  { key: "name", header: "Name", width: 24 },
8454
8486
  {
@@ -8463,9 +8495,10 @@ function registerAppsCommands(program2, globalOpts) {
8463
8495
  header: "Installed",
8464
8496
  width: 12,
8465
8497
  format: (v) => {
8466
- const ts = v;
8467
- if (!ts?.seconds) return "";
8468
- return new Date(Number(ts.seconds) * 1e3).toLocaleDateString();
8498
+ if (!v) return "";
8499
+ const date = new Date(v);
8500
+ if (Number.isNaN(date.getTime())) return "";
8501
+ return date.toLocaleDateString();
8469
8502
  }
8470
8503
  }
8471
8504
  ]
@@ -8483,7 +8516,7 @@ function registerAppsCommands(program2, globalOpts) {
8483
8516
  name
8484
8517
  );
8485
8518
  if (!app) throw new Error(`App "${name}" not installed.`);
8486
- formatOutput(app, opts);
8519
+ formatOutputProto(AppSchema, app, opts);
8487
8520
  })
8488
8521
  );
8489
8522
  apps.command("install <manifestUrl>").description("Install an app from a manifest URL").option(
@@ -8544,7 +8577,7 @@ function registerAppsCommands(program2, globalOpts) {
8544
8577
  placementFieldChoices
8545
8578
  });
8546
8579
  if (opts.json) {
8547
- formatOutput(app, opts);
8580
+ formatOutputProto(AppSchema, app, opts);
8548
8581
  } else {
8549
8582
  success(`Installed ${app?.name ?? ""}`);
8550
8583
  }
@@ -8587,7 +8620,7 @@ function registerAppsCommands(program2, globalOpts) {
8587
8620
  updateResp.newManifestHash
8588
8621
  );
8589
8622
  if (opts.json) {
8590
- formatOutput(app, opts);
8623
+ formatOutputProto(AppSchema, app, opts);
8591
8624
  } else {
8592
8625
  success(`Updated ${name}`);
8593
8626
  }
@@ -8645,7 +8678,7 @@ function registerAppsCommands(program2, globalOpts) {
8645
8678
  const client = await createPlatformClient(opts);
8646
8679
  const resp = await client.apps.validateManifestUrl(manifestUrl);
8647
8680
  if (opts.json) {
8648
- formatOutput(resp, opts);
8681
+ formatOutputProto(ValidateManifestResponseSchema, resp, opts);
8649
8682
  return;
8650
8683
  }
8651
8684
  if (resp.ok) {
@@ -8690,6 +8723,188 @@ function classToLabel(n) {
8690
8723
  }
8691
8724
  }
8692
8725
 
8726
+ // src/commands/secrets.ts
8727
+ import { promises as fs5 } from "fs";
8728
+ function registerSecretsCommands(program2, globalOpts) {
8729
+ const secrets = program2.command("secrets").description("Manage vault secrets");
8730
+ secrets.command("put").description("Store a new secret and print its ref").option("--label <label>", "Optional human-readable label").option("--app <name>", "Owner: app name (defaults to project-owned)").option("--file <path>", "Read plaintext from file (binary-safe)").option("--value <plaintext>", "Plaintext value (string only; prefer --file for binary)").action(
8731
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8732
+ const opts = globalOpts();
8733
+ const resolved = await requireProject2(opts);
8734
+ const plaintext = await readPlaintext(cmdOpts);
8735
+ const client = await createPlatformClient(opts);
8736
+ const ownerKind = cmdOpts.app ? OwnerKind.APP : OwnerKind.PROJECT;
8737
+ const ref = await client.secrets.put({
8738
+ tenantId: resolved.project.tenantId,
8739
+ projectId: resolved.project.id,
8740
+ ownerKind,
8741
+ ownerId: cmdOpts.app,
8742
+ label: cmdOpts.label,
8743
+ plaintext
8744
+ });
8745
+ if (opts.json || opts.jsonl) {
8746
+ formatOutput({ ref }, opts);
8747
+ } else if (opts.quiet) {
8748
+ console.log(ref);
8749
+ } else {
8750
+ success(`Stored secret`);
8751
+ console.log(` ref: ${ref}`);
8752
+ if (cmdOpts.label) console.log(` label: ${cmdOpts.label}`);
8753
+ if (cmdOpts.app) console.log(` app: ${cmdOpts.app}`);
8754
+ }
8755
+ })
8756
+ );
8757
+ secrets.command("list").description("List secrets metadata (no plaintext)").option("--app <name>", "List app-owned secrets for this app").option("--include-soft-deleted", "Include soft-deleted entries").action(
8758
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8759
+ const opts = globalOpts();
8760
+ const resolved = await requireProject2(opts);
8761
+ const client = await createPlatformClient(opts);
8762
+ const ownerKind = cmdOpts.app ? OwnerKind.APP : OwnerKind.PROJECT;
8763
+ const items = await client.secrets.list({
8764
+ tenantId: resolved.project.tenantId,
8765
+ projectId: resolved.project.id,
8766
+ ownerKind,
8767
+ ownerId: typeof cmdOpts.app === "string" ? cmdOpts.app : "",
8768
+ includeSoftDeleted: !!cmdOpts.includeSoftDeleted
8769
+ });
8770
+ if (opts.json || opts.jsonl) {
8771
+ formatOutput(
8772
+ items.map((s) => ({
8773
+ ref: s.ref,
8774
+ label: s.label,
8775
+ ownerKind: ownerKindToString(s.ownerKind),
8776
+ ownerId: s.ownerId,
8777
+ createdAt: tsToString(s.createdAt),
8778
+ lastReadAt: tsToString(s.lastReadAt),
8779
+ softDeletedAt: tsToString(s.softDeletedAt)
8780
+ })),
8781
+ opts
8782
+ );
8783
+ return;
8784
+ }
8785
+ if (items.length === 0) {
8786
+ warn("No secrets found.");
8787
+ return;
8788
+ }
8789
+ for (const s of items) {
8790
+ const owner = s.ownerKind === OwnerKind.APP ? `app:${s.ownerId}` : "project";
8791
+ const deleted = s.softDeletedAt ? " [soft-deleted]" : "";
8792
+ console.log(`${s.ref} ${owner.padEnd(24)} ${s.label ?? ""}${deleted}`);
8793
+ }
8794
+ })
8795
+ );
8796
+ secrets.command("rotate <ref>").description("Replace plaintext for an existing ref; returns new ref").option("--file <path>", "Read plaintext from file (binary-safe)").option("--value <plaintext>", "Plaintext value (string only; prefer --file for binary)").option("--confirm", "Skip confirmation prompt").action(
8797
+ withErrorHandler(globalOpts, async (ref, cmdOpts) => {
8798
+ const opts = globalOpts();
8799
+ const confirmed = await confirmAction(
8800
+ `Rotate secret ${ref}? Every record pointing at it will be updated to the new ref.`,
8801
+ { confirm: !!cmdOpts.confirm }
8802
+ );
8803
+ if (!confirmed) {
8804
+ console.log("Aborted.");
8805
+ return;
8806
+ }
8807
+ const plaintext = await readPlaintext(cmdOpts);
8808
+ const client = await createPlatformClient(opts);
8809
+ const newRef = await client.secrets.rotate(ref, plaintext);
8810
+ if (opts.json || opts.jsonl) {
8811
+ formatOutput({ oldRef: ref, newRef }, opts);
8812
+ } else if (opts.quiet) {
8813
+ console.log(newRef);
8814
+ } else {
8815
+ success(`Rotated`);
8816
+ console.log(` old: ${ref}`);
8817
+ console.log(` new: ${newRef}`);
8818
+ }
8819
+ })
8820
+ );
8821
+ secrets.command("delete <ref>").description("Soft-delete a secret (recoverable until purged)").option("--confirm", "Skip confirmation prompt").action(
8822
+ withErrorHandler(globalOpts, async (ref, cmdOpts) => {
8823
+ const opts = globalOpts();
8824
+ const confirmed = await confirmAction(
8825
+ `Soft-delete secret ${ref}? Refuses if the secret is still referenced.`,
8826
+ { confirm: !!cmdOpts.confirm }
8827
+ );
8828
+ if (!confirmed) {
8829
+ console.log("Aborted.");
8830
+ return;
8831
+ }
8832
+ const client = await createPlatformClient(opts);
8833
+ await client.secrets.delete(ref);
8834
+ success(`Deleted ${ref}`);
8835
+ })
8836
+ );
8837
+ secrets.command("restore <ref>").description("Undo a soft-delete (only valid before purge_after passes)").action(
8838
+ withErrorHandler(globalOpts, async (ref) => {
8839
+ const opts = globalOpts();
8840
+ const client = await createPlatformClient(opts);
8841
+ await client.secrets.restore(ref);
8842
+ success(`Restored ${ref}`);
8843
+ })
8844
+ );
8845
+ secrets.command("purge").description("Drop every soft-deleted secret past its TTL (admin-only)").option("--confirm", "Skip confirmation prompt").action(
8846
+ withErrorHandler(globalOpts, async (cmdOpts) => {
8847
+ const opts = globalOpts();
8848
+ const confirmed = await confirmAction(
8849
+ `Purge soft-deleted secrets? Cannot be undone.`,
8850
+ { confirm: !!cmdOpts.confirm }
8851
+ );
8852
+ if (!confirmed) {
8853
+ console.log("Aborted.");
8854
+ return;
8855
+ }
8856
+ const resolved = await requireProject2(opts);
8857
+ const client = await createPlatformClient(opts);
8858
+ const count = await client.secrets.purge({
8859
+ tenantId: resolved.project.tenantId,
8860
+ projectId: resolved.project.id
8861
+ });
8862
+ success(`Purged ${count} secret(s)`);
8863
+ })
8864
+ );
8865
+ }
8866
+ async function requireProject2(opts) {
8867
+ const resolved = await resolveProjectContext(opts);
8868
+ if (!resolved) {
8869
+ throw new Error(
8870
+ "No project selected. Run `foir select-project` or set FOIR_PROJECT."
8871
+ );
8872
+ }
8873
+ return resolved;
8874
+ }
8875
+ async function readPlaintext(cmdOpts) {
8876
+ const file = typeof cmdOpts.file === "string" ? cmdOpts.file : "";
8877
+ const value = typeof cmdOpts.value === "string" ? cmdOpts.value : "";
8878
+ if (file && value) {
8879
+ throw new Error("Pass either --file or --value, not both.");
8880
+ }
8881
+ if (!file && !value) {
8882
+ throw new Error("One of --file or --value is required.");
8883
+ }
8884
+ if (file) {
8885
+ return fs5.readFile(file);
8886
+ }
8887
+ return new TextEncoder().encode(value);
8888
+ }
8889
+ function ownerKindToString(k) {
8890
+ switch (k) {
8891
+ case OwnerKind.PROJECT:
8892
+ return "project";
8893
+ case OwnerKind.APP:
8894
+ return "app";
8895
+ case OwnerKind.CUSTOMER:
8896
+ return "customer";
8897
+ default:
8898
+ return "";
8899
+ }
8900
+ }
8901
+ function tsToString(t) {
8902
+ if (!t || t.seconds === void 0 && t.nanos === void 0) return "";
8903
+ const seconds = Number(t.seconds ?? 0n);
8904
+ const nanos = t.nanos ?? 0;
8905
+ return new Date(seconds * 1e3 + Math.floor(nanos / 1e6)).toISOString();
8906
+ }
8907
+
8693
8908
  // src/cli.ts
8694
8909
  var __filename = fileURLToPath(import.meta.url);
8695
8910
  var __dirname = dirname4(__filename);
@@ -8739,4 +8954,5 @@ registerNotesCommands(program, getGlobalOpts);
8739
8954
  registerNotificationsCommands(program, getGlobalOpts);
8740
8955
  registerConfigsCommands(program, getGlobalOpts);
8741
8956
  registerAppsCommands(program, getGlobalOpts);
8957
+ registerSecretsCommands(program, getGlobalOpts);
8742
8958
  program.parse();