@eide/foir-cli 0.4.1 → 0.4.3

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
@@ -464,6 +464,9 @@ import { SegmentsService as SegmentsService2 } from "@eide/foir-proto-ts/segment
464
464
  import { ExperimentsService as ExperimentsService2 } from "@eide/foir-proto-ts/experiments/v1/experiments_pb";
465
465
  import { SettingsService as SettingsService2 } from "@eide/foir-proto-ts/settings/v1/settings_pb";
466
466
  import { StorageService as StorageService2 } from "@eide/foir-proto-ts/storage/v1/storage_pb";
467
+ import { OperationsService as OperationsService2 } from "@eide/foir-proto-ts/operations/v1/operations_pb";
468
+ import { HooksService as HooksService2 } from "@eide/foir-proto-ts/hooks/v1/hooks_pb";
469
+ import { SchedulesService as SchedulesService2 } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
467
470
 
468
471
  // src/lib/rpc/identity.ts
469
472
  import { create } from "@bufbuild/protobuf";
@@ -937,13 +940,18 @@ function createIdentityMethods(client) {
937
940
  },
938
941
  // ── API Keys ──────────────────────────────────────────
939
942
  async createApiKey(params) {
940
- const resp = await client.createApiKey(
941
- create(CreateApiKeyRequestSchema, {
942
- name: params.name,
943
- keyType: params.keyType,
944
- rateLimitPerHour: params.rateLimitPerHour
945
- })
946
- );
943
+ const req = create(CreateApiKeyRequestSchema, {
944
+ name: params.name,
945
+ keyType: params.keyType,
946
+ rateLimitPerHour: params.rateLimitPerHour
947
+ });
948
+ if (params.allowedModels?.length) {
949
+ req.allowedModels = params.allowedModels;
950
+ }
951
+ if (params.allowedFileTypes?.length) {
952
+ req.allowedFileTypes = params.allowedFileTypes;
953
+ }
954
+ const resp = await client.createApiKey(req);
947
955
  return { apiKey: resp.apiKey ?? null };
948
956
  },
949
957
  async getApiKey(id) {
@@ -1136,7 +1144,8 @@ function createIdentityMethods(client) {
1136
1144
  }
1137
1145
 
1138
1146
  // src/lib/rpc/models.ts
1139
- import { create as create2 } from "@bufbuild/protobuf";
1147
+ import { create as create2, fromJson } from "@bufbuild/protobuf";
1148
+ import { ValueSchema } from "@bufbuild/protobuf/wkt";
1140
1149
  import {
1141
1150
  FieldSchema as ProtoFieldSchema,
1142
1151
  SharingConfigSchema as ProtoSharingConfigSchema,
@@ -1160,6 +1169,7 @@ function jsFieldToProto(f) {
1160
1169
  required: f.required,
1161
1170
  helpText: f.helpText,
1162
1171
  placeholder: f.placeholder,
1172
+ defaultValue: f.defaultValue !== void 0 ? fromJson(ValueSchema, f.defaultValue) : void 0,
1163
1173
  config: f.config && Object.keys(f.config).length > 0 ? f.config : void 0,
1164
1174
  itemType: f.itemType,
1165
1175
  storage: f.storage,
@@ -1879,7 +1889,12 @@ function createConfigsMethods(client) {
1879
1889
  }
1880
1890
 
1881
1891
  // src/lib/rpc/segments.ts
1882
- import { create as create5 } from "@bufbuild/protobuf";
1892
+ import { create as create5, fromJson as fromJson2 } from "@bufbuild/protobuf";
1893
+ import { ValueSchema as ValueSchema2 } from "@bufbuild/protobuf/wkt";
1894
+ import {
1895
+ RuleExpressionSchema,
1896
+ RuleOperandSchema
1897
+ } from "@eide/foir-proto-ts/expressions/v1/expressions_pb";
1883
1898
  import {
1884
1899
  CreateSegmentRequestSchema,
1885
1900
  GetSegmentRequestSchema,
@@ -1894,6 +1909,27 @@ import {
1894
1909
  OptOutOfSegmentRequestSchema,
1895
1910
  OptBackIntoSegmentRequestSchema
1896
1911
  } from "@eide/foir-proto-ts/segments/v1/segments_pb";
1912
+ function rawOperandToProto(raw) {
1913
+ return create5(RuleOperandSchema, {
1914
+ type: raw.type,
1915
+ path: raw.path,
1916
+ value: raw.value !== void 0 ? fromJson2(ValueSchema2, raw.value) : void 0,
1917
+ valueType: raw.valueType,
1918
+ label: raw.label
1919
+ });
1920
+ }
1921
+ function rawRulesToProto(raw) {
1922
+ return create5(RuleExpressionSchema, {
1923
+ type: raw.type,
1924
+ id: raw.id,
1925
+ left: raw.left ? rawOperandToProto(raw.left) : void 0,
1926
+ operator: raw.operator,
1927
+ right: raw.right ? rawOperandToProto(raw.right) : void 0,
1928
+ conditions: Array.isArray(raw.conditions) ? raw.conditions.map((c) => rawRulesToProto(c)) : [],
1929
+ logicalOperator: raw.logicalOperator,
1930
+ value: raw.value !== void 0 ? fromJson2(ValueSchema2, raw.value) : void 0
1931
+ });
1932
+ }
1897
1933
  function createSegmentsMethods(client) {
1898
1934
  return {
1899
1935
  // ── Queries ──────────────────────────────────────────────
@@ -1920,12 +1956,13 @@ function createSegmentsMethods(client) {
1920
1956
  },
1921
1957
  // ── Mutations ────────────────────────────────────────────
1922
1958
  async createSegment(params) {
1959
+ const rules = params.rules && !("$typeName" in params.rules) ? rawRulesToProto(params.rules) : params.rules;
1923
1960
  const resp = await client.createSegment(
1924
1961
  create5(CreateSegmentRequestSchema, {
1925
1962
  key: params.key,
1926
1963
  name: params.name,
1927
1964
  description: params.description,
1928
- rules: params.rules,
1965
+ rules,
1929
1966
  evaluationMode: params.evaluationMode,
1930
1967
  isActive: params.isActive
1931
1968
  })
@@ -1933,12 +1970,13 @@ function createSegmentsMethods(client) {
1933
1970
  return resp.segment ?? null;
1934
1971
  },
1935
1972
  async updateSegment(params) {
1973
+ const rules = params.rules && !("$typeName" in params.rules) ? rawRulesToProto(params.rules) : params.rules;
1936
1974
  const resp = await client.updateSegment(
1937
1975
  create5(UpdateSegmentRequestSchema, {
1938
1976
  id: params.id,
1939
1977
  name: params.name,
1940
1978
  description: params.description,
1941
- rules: params.rules,
1979
+ rules,
1942
1980
  evaluationMode: params.evaluationMode,
1943
1981
  isActive: params.isActive
1944
1982
  })
@@ -2693,6 +2731,279 @@ function createStorageMethods(client) {
2693
2731
  };
2694
2732
  }
2695
2733
 
2734
+ // src/lib/rpc/operations.ts
2735
+ import { create as create9 } from "@bufbuild/protobuf";
2736
+ import {
2737
+ ListOperationsRequestSchema as ListOperationsRequestSchema2,
2738
+ GetOperationRequestSchema,
2739
+ CreateOperationRequestSchema,
2740
+ UpdateOperationRequestSchema,
2741
+ DeleteOperationRequestSchema,
2742
+ GetSigningSecretRequestSchema
2743
+ } from "@eide/foir-proto-ts/operations/v1/operations_pb";
2744
+ function createOperationsMethods(client) {
2745
+ return {
2746
+ // ── CRUD ──────────────────────────────────────────────────
2747
+ async listOperations(params = {}) {
2748
+ return client.listOperations(
2749
+ create9(ListOperationsRequestSchema2, {
2750
+ configId: params.configId,
2751
+ category: params.category,
2752
+ isActive: params.isActive,
2753
+ search: params.search,
2754
+ limit: params.limit ?? 50,
2755
+ offset: params.offset ?? 0
2756
+ })
2757
+ );
2758
+ },
2759
+ async getOperation(params) {
2760
+ const resp = await client.getOperation(
2761
+ create9(GetOperationRequestSchema, {
2762
+ id: params.id ?? "",
2763
+ key: params.key
2764
+ })
2765
+ );
2766
+ return resp.operation ?? null;
2767
+ },
2768
+ async createOperation(params) {
2769
+ const resp = await client.createOperation(
2770
+ create9(CreateOperationRequestSchema, {
2771
+ key: params.key,
2772
+ name: params.name,
2773
+ endpoint: params.endpoint,
2774
+ description: params.description,
2775
+ icon: params.icon,
2776
+ category: params.category,
2777
+ timeoutMs: params.timeoutMs,
2778
+ inputSchema: params.inputSchema,
2779
+ outputSchema: params.outputSchema,
2780
+ streamConfig: params.streamConfig,
2781
+ quotas: params.quotas,
2782
+ retryPolicy: params.retryPolicy,
2783
+ allowedRoles: params.allowedRoles ?? [],
2784
+ precondition: params.precondition,
2785
+ configId: params.configId
2786
+ })
2787
+ );
2788
+ return resp.operation ?? null;
2789
+ },
2790
+ async updateOperation(params) {
2791
+ const resp = await client.updateOperation(
2792
+ create9(UpdateOperationRequestSchema, {
2793
+ id: params.id,
2794
+ name: params.name,
2795
+ description: params.description,
2796
+ endpoint: params.endpoint,
2797
+ timeoutMs: params.timeoutMs,
2798
+ inputSchema: params.inputSchema,
2799
+ outputSchema: params.outputSchema,
2800
+ streamConfig: params.streamConfig,
2801
+ quotas: params.quotas,
2802
+ retryPolicy: params.retryPolicy,
2803
+ precondition: params.precondition,
2804
+ isActive: params.isActive
2805
+ })
2806
+ );
2807
+ return resp.operation ?? null;
2808
+ },
2809
+ async deleteOperation(id) {
2810
+ const resp = await client.deleteOperation(
2811
+ create9(DeleteOperationRequestSchema, { id })
2812
+ );
2813
+ return resp.success;
2814
+ },
2815
+ async getSigningSecret() {
2816
+ const resp = await client.getSigningSecret(
2817
+ create9(GetSigningSecretRequestSchema, {})
2818
+ );
2819
+ return { secret: resp.secret, prefix: resp.prefix };
2820
+ }
2821
+ };
2822
+ }
2823
+
2824
+ // src/lib/rpc/hooks.ts
2825
+ import { create as create10 } from "@bufbuild/protobuf";
2826
+ import {
2827
+ ListHooksRequestSchema,
2828
+ GetHookRequestSchema,
2829
+ GetHookByKeyRequestSchema,
2830
+ CreateHookRequestSchema,
2831
+ UpdateHookRequestSchema,
2832
+ DeleteHookRequestSchema,
2833
+ ListHookDeliveriesRequestSchema,
2834
+ RetryHookDeliveryRequestSchema,
2835
+ TestHookRequestSchema
2836
+ } from "@eide/foir-proto-ts/hooks/v1/hooks_pb";
2837
+ function createHooksMethods(client) {
2838
+ return {
2839
+ // ── Queries ──────────────────────────────────────────────
2840
+ async listHooks(params = {}) {
2841
+ return client.listHooks(
2842
+ create10(ListHooksRequestSchema, {
2843
+ event: params.event,
2844
+ isActive: params.isActive,
2845
+ configId: params.configId,
2846
+ limit: params.limit ?? 50,
2847
+ offset: params.offset ?? 0
2848
+ })
2849
+ );
2850
+ },
2851
+ async getHook(id) {
2852
+ const resp = await client.getHook(create10(GetHookRequestSchema, { id }));
2853
+ return resp.hook ?? null;
2854
+ },
2855
+ // ── Mutations ────────────────────────────────────────────
2856
+ async createHook(params) {
2857
+ const resp = await client.createHook(
2858
+ create10(CreateHookRequestSchema, {
2859
+ key: params.key,
2860
+ name: params.name,
2861
+ event: params.event,
2862
+ targetType: params.targetType,
2863
+ description: params.description,
2864
+ operationKey: params.operationKey,
2865
+ notificationConfig: params.notificationConfig ?? void 0,
2866
+ filter: params.filter ?? void 0,
2867
+ configId: params.configId
2868
+ })
2869
+ );
2870
+ return resp.hook ?? null;
2871
+ },
2872
+ async updateHook(params) {
2873
+ const resp = await client.updateHook(
2874
+ create10(UpdateHookRequestSchema, {
2875
+ id: params.id,
2876
+ name: params.name,
2877
+ description: params.description,
2878
+ operationKey: params.operationKey,
2879
+ notificationConfig: params.notificationConfig ?? void 0,
2880
+ filter: params.filter ?? void 0,
2881
+ isActive: params.isActive
2882
+ })
2883
+ );
2884
+ return resp.hook ?? null;
2885
+ },
2886
+ async deleteHook(id) {
2887
+ const resp = await client.deleteHook(
2888
+ create10(DeleteHookRequestSchema, { id })
2889
+ );
2890
+ return resp.success;
2891
+ },
2892
+ // ── Get by Key ──────────────────────────────────────────
2893
+ async getHookByKey(key) {
2894
+ const resp = await client.getHookByKey(
2895
+ create10(GetHookByKeyRequestSchema, { key })
2896
+ );
2897
+ return resp.hook ?? null;
2898
+ },
2899
+ // ── Deliveries ──────────────────────────────────────────
2900
+ async listHookDeliveries(params) {
2901
+ return client.listHookDeliveries(
2902
+ create10(ListHookDeliveriesRequestSchema, {
2903
+ hookId: params.hookId,
2904
+ status: params.status,
2905
+ limit: params.limit ?? 50,
2906
+ offset: params.offset ?? 0
2907
+ })
2908
+ );
2909
+ },
2910
+ async retryHookDelivery(deliveryId) {
2911
+ const resp = await client.retryHookDelivery(
2912
+ create10(RetryHookDeliveryRequestSchema, { deliveryId })
2913
+ );
2914
+ return resp.success;
2915
+ },
2916
+ // ── Testing ─────────────────────────────────────────────
2917
+ async testHook(params) {
2918
+ const resp = await client.testHook(
2919
+ create10(TestHookRequestSchema, {
2920
+ hookId: params.hookId,
2921
+ testPayload: params.testPayload
2922
+ })
2923
+ );
2924
+ return {
2925
+ success: resp.success,
2926
+ error: resp.error ?? void 0
2927
+ };
2928
+ }
2929
+ };
2930
+ }
2931
+
2932
+ // src/lib/rpc/cron-schedules.ts
2933
+ import { create as create11 } from "@bufbuild/protobuf";
2934
+ import {
2935
+ ListCronSchedulesRequestSchema,
2936
+ GetCronScheduleRequestSchema,
2937
+ GetCronScheduleByKeyRequestSchema,
2938
+ CreateCronScheduleRequestSchema,
2939
+ UpdateCronScheduleRequestSchema,
2940
+ DeleteCronScheduleRequestSchema
2941
+ } from "@eide/foir-proto-ts/schedules/v1/schedules_pb";
2942
+ function createCronSchedulesMethods(client) {
2943
+ return {
2944
+ // ── Queries ──────────────────────────────────────────────
2945
+ async listCronSchedules(params = {}) {
2946
+ return client.listCronSchedules(
2947
+ create11(ListCronSchedulesRequestSchema, {
2948
+ configId: params.configId,
2949
+ isActive: params.isActive,
2950
+ limit: params.limit ?? 50,
2951
+ offset: params.offset ?? 0
2952
+ })
2953
+ );
2954
+ },
2955
+ async getCronSchedule(id) {
2956
+ const resp = await client.getCronSchedule(
2957
+ create11(GetCronScheduleRequestSchema, { id })
2958
+ );
2959
+ return resp.schedule ?? null;
2960
+ },
2961
+ async getCronScheduleByKey(key) {
2962
+ const resp = await client.getCronScheduleByKey(
2963
+ create11(GetCronScheduleByKeyRequestSchema, { key })
2964
+ );
2965
+ return resp.schedule ?? null;
2966
+ },
2967
+ // ── Mutations ────────────────────────────────────────────
2968
+ async createCronSchedule(params) {
2969
+ const resp = await client.createCronSchedule(
2970
+ create11(CreateCronScheduleRequestSchema, {
2971
+ key: params.key,
2972
+ name: params.name,
2973
+ description: params.description,
2974
+ cron: params.cron,
2975
+ timezone: params.timezone,
2976
+ operationKey: params.operationKey,
2977
+ configId: params.configId,
2978
+ targetType: params.targetType,
2979
+ targetConfig: params.targetConfig
2980
+ })
2981
+ );
2982
+ return resp.schedule ?? null;
2983
+ },
2984
+ async updateCronSchedule(params) {
2985
+ const resp = await client.updateCronSchedule(
2986
+ create11(UpdateCronScheduleRequestSchema, {
2987
+ id: params.id,
2988
+ name: params.name,
2989
+ description: params.description,
2990
+ cron: params.cron,
2991
+ timezone: params.timezone,
2992
+ operationKey: params.operationKey,
2993
+ isActive: params.isActive
2994
+ })
2995
+ );
2996
+ return resp.schedule ?? null;
2997
+ },
2998
+ async deleteCronSchedule(id) {
2999
+ const resp = await client.deleteCronSchedule(
3000
+ create11(DeleteCronScheduleRequestSchema, { id })
3001
+ );
3002
+ return resp.success;
3003
+ }
3004
+ };
3005
+ }
3006
+
2696
3007
  // src/lib/client.ts
2697
3008
  import { GraphQLClient } from "graphql-request";
2698
3009
  async function createPlatformClient(options) {
@@ -2739,7 +3050,14 @@ async function createPlatformClient(options) {
2739
3050
  createRpcClient(ExperimentsService2, transport)
2740
3051
  ),
2741
3052
  settings: createSettingsMethods(createRpcClient(SettingsService2, transport)),
2742
- storage: createStorageMethods(createRpcClient(StorageService2, transport))
3053
+ storage: createStorageMethods(createRpcClient(StorageService2, transport)),
3054
+ operations: createOperationsMethods(
3055
+ createRpcClient(OperationsService2, transport)
3056
+ ),
3057
+ hooks: createHooksMethods(createRpcClient(HooksService2, transport)),
3058
+ cronSchedules: createCronSchedulesMethods(
3059
+ createRpcClient(SchedulesService2, transport)
3060
+ )
2743
3061
  };
2744
3062
  }
2745
3063
  function createPlatformClientWithHeaders(apiUrl, headers) {
@@ -2764,7 +3082,14 @@ function createPlatformClientWithHeaders(apiUrl, headers) {
2764
3082
  createRpcClient(ExperimentsService2, transport)
2765
3083
  ),
2766
3084
  settings: createSettingsMethods(createRpcClient(SettingsService2, transport)),
2767
- storage: createStorageMethods(createRpcClient(StorageService2, transport))
3085
+ storage: createStorageMethods(createRpcClient(StorageService2, transport)),
3086
+ operations: createOperationsMethods(
3087
+ createRpcClient(OperationsService2, transport)
3088
+ ),
3089
+ hooks: createHooksMethods(createRpcClient(HooksService2, transport)),
3090
+ cronSchedules: createCronSchedulesMethods(
3091
+ createRpcClient(SchedulesService2, transport)
3092
+ )
2768
3093
  };
2769
3094
  }
2770
3095
  async function getStorageAuth(options) {
@@ -4026,7 +4351,7 @@ import chalk5 from "chalk";
4026
4351
  import inquirer4 from "inquirer";
4027
4352
  var FIELD_DEFAULTS = {
4028
4353
  text: "",
4029
- richtext: "",
4354
+ content: "",
4030
4355
  number: 0,
4031
4356
  boolean: false,
4032
4357
  date: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
@@ -4061,7 +4386,7 @@ function generateModelTemplate(key) {
4061
4386
  },
4062
4387
  {
4063
4388
  key: "description",
4064
- type: "richtext",
4389
+ type: "content",
4065
4390
  label: "Description"
4066
4391
  },
4067
4392
  {
@@ -4235,6 +4560,332 @@ Edit the files, then run:
4235
4560
  import chalk6 from "chalk";
4236
4561
  import { existsSync as existsSync4, readFileSync, writeFileSync as writeFileSync2 } from "fs";
4237
4562
  import { resolve as resolve4 } from "path";
4563
+
4564
+ // src/lib/reconciler.ts
4565
+ function zeroCounts() {
4566
+ return { created: 0, updated: 0, deleted: 0 };
4567
+ }
4568
+ async function reconcileConfig(client, configId, manifest) {
4569
+ const summary = {
4570
+ models: zeroCounts(),
4571
+ operations: zeroCounts(),
4572
+ hooks: zeroCounts(),
4573
+ segments: zeroCounts(),
4574
+ cronSchedules: zeroCounts(),
4575
+ authProviders: zeroCounts(),
4576
+ profileSchemaUpdated: false,
4577
+ apiKeys: []
4578
+ };
4579
+ const operationBaseUrl = manifest.operationBaseUrl ?? "";
4580
+ await reconcileModels(client, configId, manifest.models ?? [], summary);
4581
+ await reconcileOperations(client, configId, manifest.operations ?? [], operationBaseUrl, summary);
4582
+ await reconcileHooks(client, configId, manifest.hooks ?? [], summary);
4583
+ await reconcileSegments(client, manifest.segments ?? [], summary);
4584
+ await reconcileCronSchedules(client, configId, manifest.schedules ?? [], summary);
4585
+ await reconcileAuthProviders(client, manifest.authProviders ?? [], summary);
4586
+ await reconcileProfileSchema(client, manifest, summary);
4587
+ await reconcileApiKeys(client, manifest.apiKeys ?? [], summary);
4588
+ return summary;
4589
+ }
4590
+ async function reconcileModels(client, configId, models, summary) {
4591
+ const existing = await client.models.listModels({ limit: 200 });
4592
+ const configOwned = existing.items.filter(
4593
+ (m) => m.configId === configId
4594
+ );
4595
+ const existingByKey = new Map(
4596
+ configOwned.map((m) => [m.key, m])
4597
+ );
4598
+ const manifestKeys = /* @__PURE__ */ new Set();
4599
+ for (const m of models) {
4600
+ if (!m.key || !m.name) continue;
4601
+ manifestKeys.add(m.key);
4602
+ const config2 = { ...m.config };
4603
+ if (m.pluralName) config2.pluralName = m.pluralName;
4604
+ if (m.description) config2.description = m.description;
4605
+ const ex = existingByKey.get(m.key);
4606
+ if (ex) {
4607
+ await client.models.updateModel({
4608
+ id: ex.id,
4609
+ name: m.name,
4610
+ fields: m.fields,
4611
+ config: config2
4612
+ });
4613
+ summary.models.updated++;
4614
+ } else {
4615
+ await client.models.createModel({
4616
+ key: m.key,
4617
+ name: m.name,
4618
+ fields: m.fields,
4619
+ config: config2,
4620
+ configId
4621
+ });
4622
+ summary.models.created++;
4623
+ }
4624
+ }
4625
+ for (const [key, ex] of existingByKey) {
4626
+ if (!manifestKeys.has(key)) {
4627
+ await client.models.deleteModel(
4628
+ ex.id
4629
+ );
4630
+ summary.models.deleted++;
4631
+ }
4632
+ }
4633
+ }
4634
+ function resolveEndpoint(endpoint, baseUrl) {
4635
+ if (!endpoint) return "";
4636
+ if (endpoint.startsWith("http://") || endpoint.startsWith("https://")) return endpoint;
4637
+ return baseUrl ? `${baseUrl.replace(/\/+$/, "")}${endpoint.startsWith("/") ? "" : "/"}${endpoint}` : endpoint;
4638
+ }
4639
+ async function reconcileOperations(client, configId, operations, operationBaseUrl, summary) {
4640
+ const existing = await client.operations.listOperations({ configId, limit: 200 });
4641
+ const existingByKey = new Map(
4642
+ (existing.operations ?? []).map((o) => [o.key, o])
4643
+ );
4644
+ const manifestKeys = /* @__PURE__ */ new Set();
4645
+ for (const op of operations) {
4646
+ if (!op.key || !op.name) continue;
4647
+ manifestKeys.add(op.key);
4648
+ const ex = existingByKey.get(op.key);
4649
+ const endpoint = resolveEndpoint(op.endpoint, operationBaseUrl);
4650
+ if (ex) {
4651
+ await client.operations.updateOperation({
4652
+ id: ex.id,
4653
+ name: op.name,
4654
+ description: op.description,
4655
+ endpoint,
4656
+ timeoutMs: op.timeoutMs,
4657
+ inputSchema: op.inputSchema,
4658
+ outputSchema: op.outputSchema,
4659
+ streamConfig: op.streamConfig,
4660
+ quotas: op.quotas,
4661
+ retryPolicy: op.retryPolicy,
4662
+ precondition: op.precondition,
4663
+ isActive: op.isActive
4664
+ });
4665
+ summary.operations.updated++;
4666
+ } else {
4667
+ await client.operations.createOperation({
4668
+ key: op.key,
4669
+ name: op.name,
4670
+ endpoint,
4671
+ description: op.description,
4672
+ icon: op.icon,
4673
+ category: op.category,
4674
+ timeoutMs: op.timeoutMs,
4675
+ inputSchema: op.inputSchema,
4676
+ outputSchema: op.outputSchema,
4677
+ streamConfig: op.streamConfig,
4678
+ quotas: op.quotas,
4679
+ retryPolicy: op.retryPolicy,
4680
+ allowedRoles: op.allowedRoles,
4681
+ precondition: op.precondition,
4682
+ configId
4683
+ });
4684
+ summary.operations.created++;
4685
+ }
4686
+ }
4687
+ for (const [key, ex] of existingByKey) {
4688
+ if (!manifestKeys.has(key)) {
4689
+ await client.operations.deleteOperation(
4690
+ ex.id
4691
+ );
4692
+ summary.operations.deleted++;
4693
+ }
4694
+ }
4695
+ }
4696
+ async function reconcileHooks(client, configId, hooks, summary) {
4697
+ const existing = await client.hooks.listHooks({ configId, limit: 200 });
4698
+ const existingByKey = new Map(
4699
+ (existing.hooks ?? []).map((h) => [h.key, h])
4700
+ );
4701
+ const manifestKeys = /* @__PURE__ */ new Set();
4702
+ for (const hook of hooks) {
4703
+ const key = hook.key || `${hook.event}-${hook.operationKey ?? "default"}`;
4704
+ if (!hook.event) continue;
4705
+ manifestKeys.add(key);
4706
+ const name = hook.name || key;
4707
+ const ex = existingByKey.get(key);
4708
+ if (ex) {
4709
+ await client.hooks.updateHook({
4710
+ id: ex.id,
4711
+ name,
4712
+ description: hook.description,
4713
+ operationKey: hook.operationKey,
4714
+ filter: hook.filter,
4715
+ notificationConfig: hook.notificationConfig,
4716
+ isActive: hook.isActive
4717
+ });
4718
+ summary.hooks.updated++;
4719
+ } else {
4720
+ await client.hooks.createHook({
4721
+ key,
4722
+ name,
4723
+ description: hook.description,
4724
+ event: hook.event,
4725
+ targetType: hook.type ?? "operation",
4726
+ operationKey: hook.operationKey,
4727
+ filter: hook.filter,
4728
+ notificationConfig: hook.notificationConfig,
4729
+ configId
4730
+ });
4731
+ summary.hooks.created++;
4732
+ }
4733
+ }
4734
+ for (const [key, ex] of existingByKey) {
4735
+ if (!manifestKeys.has(key)) {
4736
+ await client.hooks.deleteHook(
4737
+ ex.id
4738
+ );
4739
+ summary.hooks.deleted++;
4740
+ }
4741
+ }
4742
+ }
4743
+ async function reconcileSegments(client, segments, summary) {
4744
+ const existing = await client.segments.listSegments({ limit: 200 });
4745
+ const existingByKey = new Map(
4746
+ (existing.segments ?? []).map((s) => [s.key, s])
4747
+ );
4748
+ for (const seg of segments) {
4749
+ if (!seg.key || !seg.name) continue;
4750
+ const ex = existingByKey.get(seg.key);
4751
+ if (ex) {
4752
+ await client.segments.updateSegment({
4753
+ id: ex.id,
4754
+ name: seg.name,
4755
+ description: seg.description,
4756
+ rules: seg.rules,
4757
+ evaluationMode: seg.evaluationMode,
4758
+ isActive: seg.isActive
4759
+ });
4760
+ summary.segments.updated++;
4761
+ } else {
4762
+ await client.segments.createSegment({
4763
+ key: seg.key,
4764
+ name: seg.name,
4765
+ description: seg.description,
4766
+ rules: seg.rules,
4767
+ evaluationMode: seg.evaluationMode,
4768
+ isActive: seg.isActive
4769
+ });
4770
+ summary.segments.created++;
4771
+ }
4772
+ }
4773
+ }
4774
+ async function reconcileCronSchedules(client, configId, schedules, summary) {
4775
+ const existing = await client.cronSchedules.listCronSchedules({ configId, limit: 200 });
4776
+ const schedulesList = existing.schedules ?? [];
4777
+ const existingByKey = new Map(
4778
+ schedulesList.map((s) => [s.key, s])
4779
+ );
4780
+ const manifestKeys = /* @__PURE__ */ new Set();
4781
+ for (const sched of schedules) {
4782
+ const key = sched.key ?? sched.operationKey;
4783
+ if (!key || !sched.cron) continue;
4784
+ manifestKeys.add(key);
4785
+ const name = sched.name ?? key;
4786
+ const ex = existingByKey.get(key);
4787
+ if (ex) {
4788
+ await client.cronSchedules.updateCronSchedule({
4789
+ id: ex.id,
4790
+ name,
4791
+ description: sched.description,
4792
+ cron: sched.cron,
4793
+ timezone: sched.timezone,
4794
+ operationKey: sched.operationKey,
4795
+ isActive: sched.enabled
4796
+ });
4797
+ summary.cronSchedules.updated++;
4798
+ } else {
4799
+ await client.cronSchedules.createCronSchedule({
4800
+ key,
4801
+ name,
4802
+ description: sched.description,
4803
+ cron: sched.cron,
4804
+ timezone: sched.timezone,
4805
+ operationKey: sched.operationKey,
4806
+ configId
4807
+ });
4808
+ summary.cronSchedules.created++;
4809
+ }
4810
+ }
4811
+ for (const [key, ex] of existingByKey) {
4812
+ if (!manifestKeys.has(key)) {
4813
+ await client.cronSchedules.deleteCronSchedule(
4814
+ ex.id
4815
+ );
4816
+ summary.cronSchedules.deleted++;
4817
+ }
4818
+ }
4819
+ }
4820
+ async function reconcileAuthProviders(client, providers, summary) {
4821
+ const existing = await client.identity.listAuthProviders({ limit: 200 });
4822
+ const existingByKey = new Map(
4823
+ existing.items.map((p) => [p.key, p])
4824
+ );
4825
+ for (const prov of providers) {
4826
+ if (!prov.key || !prov.name || !prov.type) continue;
4827
+ const ex = existingByKey.get(prov.key);
4828
+ if (ex) {
4829
+ await client.identity.updateAuthProvider({
4830
+ id: ex.id,
4831
+ name: prov.name,
4832
+ config: prov.config,
4833
+ enabled: prov.enabled,
4834
+ isDefault: prov.isDefault,
4835
+ priority: prov.priority
4836
+ });
4837
+ summary.authProviders.updated++;
4838
+ } else {
4839
+ await client.identity.createAuthProvider({
4840
+ key: prov.key,
4841
+ name: prov.name,
4842
+ type: prov.type.toUpperCase(),
4843
+ config: prov.config,
4844
+ enabled: prov.enabled ?? true,
4845
+ isDefault: prov.isDefault ?? false,
4846
+ priority: prov.priority ?? 0
4847
+ });
4848
+ summary.authProviders.created++;
4849
+ }
4850
+ }
4851
+ }
4852
+ async function reconcileProfileSchema(client, manifest, summary) {
4853
+ const profileSchema = manifest["customerProfileSchema"];
4854
+ if (!profileSchema) return;
4855
+ await client.settings.updateCustomerProfileSchema({
4856
+ fields: profileSchema.fields,
4857
+ publicFields: profileSchema.publicFields ?? []
4858
+ });
4859
+ summary.profileSchemaUpdated = true;
4860
+ }
4861
+ async function reconcileApiKeys(client, apiKeys, summary) {
4862
+ if (apiKeys.length === 0) return;
4863
+ const existing = await client.identity.listApiKeys({ limit: 200 });
4864
+ const existingByName = new Map(
4865
+ existing.items.map((k) => [k.name, k])
4866
+ );
4867
+ for (const key of apiKeys) {
4868
+ if (!key.name || !key.keyType || !key.envVar) continue;
4869
+ if (existingByName.has(key.name)) continue;
4870
+ const result = await client.identity.createApiKey({
4871
+ name: key.name,
4872
+ keyType: key.keyType === "secret" ? 2 : 1,
4873
+ allowedModels: key.allowedModels,
4874
+ allowedFileTypes: key.allowedFileTypes
4875
+ });
4876
+ const rawKey = result?.apiKey?.rawKey;
4877
+ if (rawKey) {
4878
+ summary.apiKeys.push({
4879
+ name: key.name,
4880
+ keyType: key.keyType,
4881
+ envVar: key.envVar,
4882
+ rawKey
4883
+ });
4884
+ }
4885
+ }
4886
+ }
4887
+
4888
+ // src/commands/push.ts
4238
4889
  var CONFIG_FILE_NAMES = [
4239
4890
  "foir.config.ts",
4240
4891
  "foir.config.js",
@@ -4265,6 +4916,30 @@ function writeEnvVar(envPath, key, value) {
4265
4916
  writeFileSync2(envPath, content, "utf-8");
4266
4917
  return true;
4267
4918
  }
4919
+ function printSummary(summary) {
4920
+ const lines = [];
4921
+ const fmt = (label, c) => {
4922
+ const parts = [];
4923
+ if (c.created) parts.push(`${c.created} created`);
4924
+ if (c.updated) parts.push(`${c.updated} updated`);
4925
+ if (c.deleted) parts.push(`${c.deleted} deleted`);
4926
+ if (parts.length > 0) lines.push(` ${label.padEnd(13)} ${parts.join(", ")}`);
4927
+ };
4928
+ fmt("Models:", summary.models);
4929
+ fmt("Operations:", summary.operations);
4930
+ fmt("Hooks:", summary.hooks);
4931
+ fmt("Segments:", summary.segments);
4932
+ fmt("Schedules:", summary.cronSchedules);
4933
+ fmt("Auth:", summary.authProviders);
4934
+ if (summary.profileSchemaUpdated) {
4935
+ lines.push(" Profile: schema updated");
4936
+ }
4937
+ if (lines.length > 0) {
4938
+ for (const line of lines) {
4939
+ console.log(line);
4940
+ }
4941
+ }
4942
+ }
4268
4943
  function registerPushCommand(program2, globalOpts) {
4269
4944
  program2.command("push").description("Push foir.config.ts to the platform").option("--config <path>", "Path to config file (default: auto-discover)").option("--force", "Force reinstall (delete and recreate)", false).option("--env <path>", "Path to .env file (default: .env)").action(
4270
4945
  withErrorHandler(
@@ -4286,89 +4961,42 @@ function registerPushCommand(program2, globalOpts) {
4286
4961
  'Config must have at least "key" and "name" fields.'
4287
4962
  );
4288
4963
  }
4289
- if (opts.force) {
4290
- config2.force = true;
4291
- }
4292
4964
  const client = await createPlatformClient(globalOpts());
4293
4965
  console.log(
4294
4966
  chalk6.dim(`Pushing config "${config2.key}" to platform...`)
4295
4967
  );
4296
- const result = await client.configs.applyConfig(
4968
+ const applyResult = await client.configs.applyConfig(
4297
4969
  config2.key,
4298
4970
  config2
4299
4971
  );
4300
- if (!result) {
4972
+ if (!applyResult) {
4301
4973
  throw new Error(
4302
4974
  "Failed to apply config \u2014 no response from server."
4303
4975
  );
4304
4976
  }
4977
+ const configId = applyResult.id;
4978
+ console.log(chalk6.dim("Reconciling resources..."));
4979
+ const summary = await reconcileConfig(client, configId, config2);
4305
4980
  console.log();
4306
4981
  console.log(chalk6.green("\u2713 Config applied successfully"));
4307
- console.log(` Config ID: ${chalk6.cyan(result.id)}`);
4308
- console.log(` Config Key: ${chalk6.cyan(result.key)}`);
4309
- const summary = result.summary;
4310
- if (summary) {
4311
- console.log();
4312
- const lines = [];
4313
- if (summary.modelsCreated || summary.modelsUpdated) {
4314
- lines.push(
4315
- ` Models: ${summary.modelsCreated ?? 0} created, ${summary.modelsUpdated ?? 0} updated`
4316
- );
4317
- }
4318
- if (summary.operationsCreated || summary.operationsUpdated) {
4319
- lines.push(
4320
- ` Operations: ${summary.operationsCreated ?? 0} created, ${summary.operationsUpdated ?? 0} updated`
4321
- );
4322
- }
4323
- if (summary.hooksCreated || summary.hooksUpdated) {
4324
- lines.push(
4325
- ` Hooks: ${summary.hooksCreated ?? 0} created, ${summary.hooksUpdated ?? 0} updated`
4326
- );
4327
- }
4328
- if (summary.segmentsCreated || summary.segmentsUpdated) {
4329
- lines.push(
4330
- ` Segments: ${summary.segmentsCreated ?? 0} created, ${summary.segmentsUpdated ?? 0} updated`
4331
- );
4332
- }
4333
- if (summary.schedulesCreated || summary.schedulesUpdated) {
4334
- lines.push(
4335
- ` Schedules: ${summary.schedulesCreated ?? 0} created, ${summary.schedulesUpdated ?? 0} updated`
4336
- );
4337
- }
4338
- if (summary.authProvidersCreated || summary.authProvidersUpdated) {
4339
- lines.push(
4340
- ` Auth: ${summary.authProvidersCreated ?? 0} created, ${summary.authProvidersUpdated ?? 0} updated`
4341
- );
4342
- }
4343
- if (summary.resourcesDeleted) {
4344
- lines.push(
4345
- ` Cleaned up: ${summary.resourcesDeleted} orphaned resource(s)`
4346
- );
4347
- }
4348
- if (lines.length > 0) {
4349
- for (const line of lines) {
4350
- console.log(line);
4351
- }
4352
- }
4353
- }
4982
+ console.log(` Config ID: ${chalk6.cyan(configId)}`);
4983
+ console.log(` Config Key: ${chalk6.cyan(config2.key)}`);
4984
+ console.log();
4985
+ printSummary(summary);
4354
4986
  const envPath = resolve4(opts.env ?? ".env");
4355
- const r = result;
4356
- const provisionedKeys = r.provisionedApiKeys;
4357
- const webhookSecret = r.webhookSecret;
4358
4987
  const envWrites = [];
4359
- if (provisionedKeys && provisionedKeys.length > 0) {
4360
- for (const pk of provisionedKeys) {
4361
- envWrites.push({
4362
- key: pk.envVar,
4363
- value: pk.rawKey,
4364
- label: `${pk.name} (${pk.keyType})`
4365
- });
4366
- }
4988
+ for (const pk of summary.apiKeys) {
4989
+ envWrites.push({
4990
+ key: pk.envVar,
4991
+ value: pk.rawKey,
4992
+ label: `${pk.name} (${pk.keyType})`
4993
+ });
4367
4994
  }
4368
- if (webhookSecret) {
4995
+ const { secret: signingSecret } = await client.operations.getSigningSecret();
4996
+ if (signingSecret) {
4369
4997
  envWrites.push({
4370
4998
  key: "FOIR_WEBHOOK_SECRET",
4371
- value: webhookSecret,
4999
+ value: signingSecret,
4372
5000
  label: "Webhook signing secret"
4373
5001
  });
4374
5002
  }
@@ -6471,6 +7099,98 @@ function buildDispatchTable() {
6471
7099
  },
6472
7100
  deleteCustomerAuthProvider: async (v, c) => await c.identity.deleteAuthProvider(str(v.id))
6473
7101
  },
7102
+ // ── Rollouts ────────────────────────────────────────────────
7103
+ rollouts: {
7104
+ listPublishBatches: async (v, c) => wrapList(
7105
+ await c.records.listPublishBatches({ limit: num(v.limit, 50) })
7106
+ ),
7107
+ getPublishBatch: async (v, c) => await c.records.getPublishBatch(str(v.id)),
7108
+ createPublishBatch: async (v, c) => {
7109
+ const input = v.input;
7110
+ if (!input) throw new Error("Input required (--data or --file)");
7111
+ return await c.records.createPublishBatch({
7112
+ name: str(input.name),
7113
+ versionIds: input.versionIds ?? [],
7114
+ scheduledAt: input.scheduledAt ? new Date(String(input.scheduledAt)) : void 0
7115
+ });
7116
+ },
7117
+ updatePublishBatch: async (v, c) => {
7118
+ const input = v.input;
7119
+ if (!input) throw new Error("Input required (--data or --file)");
7120
+ return await c.records.updatePublishBatch({
7121
+ batchId: str(input.id ?? v.id),
7122
+ name: str(input.name),
7123
+ scheduledAt: input.scheduledAt ? new Date(String(input.scheduledAt)) : void 0
7124
+ });
7125
+ },
7126
+ cancelPublishBatch: async (v, c) => await c.records.cancelPublishBatch(str(v.id)),
7127
+ rollbackPublishBatch: async (v, c) => await c.records.rollbackPublishBatch(str(v.id)),
7128
+ retryFailedBatchItems: async (v, c) => await c.records.retryFailedBatchItems(str(v.id)),
7129
+ addItemsToPublishBatch: async (v, c) => {
7130
+ const input = v.input;
7131
+ if (!input) throw new Error("Input required (--data or --file)");
7132
+ return await c.records.addItemsToPublishBatch(
7133
+ str(v.id),
7134
+ input.versionIds ?? []
7135
+ );
7136
+ },
7137
+ removeItemsFromPublishBatch: async (v, c) => {
7138
+ const input = v.input;
7139
+ if (!input) throw new Error("Input required (--data or --file)");
7140
+ return await c.records.removeItemsFromPublishBatch(
7141
+ str(v.id),
7142
+ input.versionIds ?? []
7143
+ );
7144
+ }
7145
+ },
7146
+ // ── Hooks ──────────────────────────────────────────────────
7147
+ hooks: {
7148
+ hooks: async (v, c) => {
7149
+ const resp = await c.hooks.listHooks({ limit: num(v.limit, 50) });
7150
+ return { items: resp.hooks ?? [], total: resp.total ?? 0 };
7151
+ },
7152
+ hookByKey: async (v, c) => await c.hooks.getHookByKey(str(v.key)),
7153
+ createHook: async (v, c) => {
7154
+ const input = v.input;
7155
+ if (!input) throw new Error("Input required (--data or --file)");
7156
+ return await c.hooks.createHook({
7157
+ key: str(input.key),
7158
+ name: str(input.name),
7159
+ event: str(input.event),
7160
+ targetType: str(input.targetType) ?? "operation",
7161
+ description: str(input.description),
7162
+ operationKey: str(input.operationKey),
7163
+ filter: input.filter
7164
+ });
7165
+ },
7166
+ updateHook: async (v, c) => {
7167
+ const input = v.input;
7168
+ if (!input) throw new Error("Input required (--data or --file)");
7169
+ return await c.hooks.updateHook({
7170
+ id: str(input.id ?? v.id),
7171
+ name: str(input.name),
7172
+ operationKey: str(input.operationKey),
7173
+ filter: input.filter,
7174
+ isActive: input.isActive
7175
+ });
7176
+ },
7177
+ deleteHook: async (v, c) => await c.hooks.deleteHook(str(v.id)),
7178
+ hookDeliveries: async (v, c) => {
7179
+ const resp = await c.hooks.listHookDeliveries({
7180
+ hookId: str(v.hookId),
7181
+ limit: num(v.limit, 50)
7182
+ });
7183
+ return { items: resp.deliveries ?? [], total: resp.total ?? 0 };
7184
+ },
7185
+ retryHookDelivery: async (v, c) => await c.hooks.retryHookDelivery(str(v.deliveryId)),
7186
+ testHook: async (v, c) => {
7187
+ const data = v.data;
7188
+ return await c.hooks.testHook({
7189
+ hookId: str(v.hookId),
7190
+ testPayload: data
7191
+ });
7192
+ }
7193
+ },
6474
7194
  // ── Configs ─────────────────────────────────────────────────
6475
7195
  configs: {
6476
7196
  configs: async (v, c) => {
@@ -23,6 +23,7 @@ interface FieldDefinitionInput {
23
23
  required?: boolean;
24
24
  helpText?: string;
25
25
  placeholder?: string;
26
+ defaultValue?: unknown;
26
27
  config?: Record<string, unknown>;
27
28
  itemType?: string;
28
29
  storage?: string;
@@ -32,6 +33,8 @@ interface FieldDefinitionInput {
32
33
  interface ApplyConfigModelInput {
33
34
  key: string;
34
35
  name: string;
36
+ pluralName?: string;
37
+ description?: string;
35
38
  fields?: FieldDefinitionInput[];
36
39
  config?: Record<string, unknown>;
37
40
  }
@@ -39,11 +42,24 @@ interface ApplyConfigOperationInput {
39
42
  key: string;
40
43
  name: string;
41
44
  description?: string;
45
+ icon?: string;
42
46
  category?: string;
43
47
  /** HTTP endpoint URL that the platform calls when this operation is triggered. */
44
48
  endpoint?: string;
45
- config?: Record<string, unknown>;
46
49
  isActive?: boolean;
50
+ timeoutMs?: number;
51
+ inputSchema?: Record<string, unknown>;
52
+ outputSchema?: Record<string, unknown>;
53
+ /** Streaming configuration for SSE/chunked responses. */
54
+ streamConfig?: Record<string, unknown>;
55
+ /** Usage quota rules (e.g., rate limits per customer). */
56
+ quotas?: Record<string, unknown>;
57
+ /** Retry policy for failed executions. */
58
+ retryPolicy?: Record<string, unknown>;
59
+ /** Roles allowed to execute this operation. */
60
+ allowedRoles?: string[];
61
+ /** Precondition that must be met before execution (e.g., segment membership). */
62
+ precondition?: Record<string, unknown>;
47
63
  }
48
64
  interface ApplyConfigSegmentInput {
49
65
  key: string;
@@ -54,11 +70,15 @@ interface ApplyConfigSegmentInput {
54
70
  isActive?: boolean;
55
71
  }
56
72
  interface ApplyConfigScheduleInput {
73
+ /** Unique key for this schedule. Defaults to operationKey if not provided. */
74
+ key?: string;
75
+ /** Display name. Defaults to key if not provided. */
76
+ name?: string;
77
+ description?: string;
57
78
  operationKey: string;
58
79
  cron: string;
59
80
  timezone?: string;
60
81
  enabled?: boolean;
61
- payload?: Record<string, unknown>;
62
82
  }
63
83
  interface ApplyConfigAuthProviderInput {
64
84
  key: string;
@@ -83,20 +103,20 @@ interface ApplyConfigHookInput {
83
103
  key?: string;
84
104
  /** Display name. */
85
105
  name?: string;
106
+ /** Description of what this hook does. */
107
+ description?: string;
86
108
  /** Lifecycle event that triggers this hook. */
87
109
  event: string;
110
+ /** Target type — defaults to 'operation'. */
111
+ type?: string;
88
112
  /** Key of the operation to execute. */
89
113
  operationKey?: string;
90
114
  /** Filter to scope the hook (e.g., `{ modelKey: 'redirect' }`). */
91
115
  filter?: Record<string, unknown>;
92
- type?: string;
93
- url?: string;
94
- method?: string;
95
- async?: boolean;
96
- headers?: Record<string, string>;
97
- additionalData?: Record<string, unknown>;
98
- expression?: Record<string, unknown>;
99
- hooks?: ApplyConfigHookInput[];
116
+ /** Notification config for notification-type hooks. */
117
+ notificationConfig?: Record<string, unknown>;
118
+ /** Whether the hook is active. Defaults to true. */
119
+ isActive?: boolean;
100
120
  }
101
121
  interface ApplyConfigApiKeyInput {
102
122
  /** Name for this API key (e.g. "Tilly iOS", "Tilly BFF"). */
@@ -105,14 +125,20 @@ interface ApplyConfigApiKeyInput {
105
125
  keyType: 'public' | 'secret';
106
126
  /** Environment variable name to write the key to in .env (e.g. "FOIR_PUBLIC_KEY"). */
107
127
  envVar: string;
108
- /** Optional scopes to restrict the key. */
109
- scopes?: Record<string, unknown>;
128
+ /** Scopes to restrict the key (e.g. ["configs:read", "records:write"]). Use ["*"] for full access. */
129
+ scopes?: string[];
130
+ /** Restrict the key to specific model keys (e.g. ["tilly_note", "tilly_block"]). */
131
+ allowedModels?: string[];
132
+ /** Restrict file uploads to specific MIME types (e.g. ["image/*", "video/*"]). */
133
+ allowedFileTypes?: string[];
110
134
  }
111
135
  interface ApplyConfigInput {
112
136
  key: string;
113
137
  name: string;
114
138
  configType?: string;
115
139
  force?: boolean;
140
+ /** Base URL prepended to relative operation endpoints. */
141
+ operationBaseUrl?: string;
116
142
  models?: ApplyConfigModelInput[];
117
143
  operations?: ApplyConfigOperationInput[];
118
144
  segments?: ApplyConfigSegmentInput[];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eide/foir-cli",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -49,7 +49,7 @@
49
49
  "@bufbuild/protobuf": "^2.0.0",
50
50
  "@connectrpc/connect": "^2.0.0",
51
51
  "@connectrpc/connect-node": "^2.0.0",
52
- "@eide/foir-proto-ts": "^0.3.1",
52
+ "@eide/foir-proto-ts": "^0.3.3",
53
53
  "chalk": "^5.3.0",
54
54
  "commander": "^12.1.0",
55
55
  "dotenv": "^16.4.5",