@foir/cli 0.64.1 → 0.66.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.
@@ -24,10 +24,21 @@ export { ExpressionPrecondition, Precondition, SegmentPrecondition } from '@foir
24
24
  * For inline `{label, value}` choices, declare `type: 'enum'` instead — the
25
25
  * `options?: never` here forbids the legacy shape at the type level.
26
26
  */
27
+ /**
28
+ * Where a field is edited, as distinct from what it holds.
29
+ *
30
+ * Most fields are content — what the record SAYS — and belong on the editor's
31
+ * main form. A field that says where the record sits or how it is handled
32
+ * reads as noise beside the prose, so `settings` moves it to the record's
33
+ * Settings tab instead. Presentation only: nothing about validation, storage
34
+ * or the published schema changes with it.
35
+ */
36
+ type FieldPlacement = 'content' | 'settings';
27
37
  interface SelectFieldConfig {
28
38
  optionModelKey: string;
29
39
  multiple?: boolean;
30
40
  options?: never;
41
+ placement?: FieldPlacement;
31
42
  }
32
43
  interface EnumFieldOption {
33
44
  label: string;
@@ -42,6 +53,7 @@ interface EnumFieldConfig {
42
53
  options: EnumFieldOption[];
43
54
  multiple?: boolean;
44
55
  default?: string | string[];
56
+ placement?: FieldPlacement;
45
57
  }
46
58
  /**
47
59
  * Per-field {read, write} principal allow-lists. A non-empty `write` names
@@ -63,6 +75,17 @@ interface BaseFieldDefinitionInput {
63
75
  * where/orderBy on records. Without it the platform rejects sort by it.
64
76
  */
65
77
  queryable?: boolean;
78
+ /**
79
+ * Whether this field's text is embedded for AI search.
80
+ *
81
+ * Tri-state, and the three states are all different: omit to follow the
82
+ * model's own default for the field's type, `true` to always embed it, and
83
+ * `false` to never embed it. `false` is the one worth writing down — it is how
84
+ * a slug, an SKU or any other machine-shaped string is kept out of the
85
+ * semantic index, and leaving it to the admin means the next push cannot hold
86
+ * it.
87
+ */
88
+ embed?: boolean;
66
89
  helpText?: string;
67
90
  placeholder?: string;
68
91
  defaultValue?: unknown;
@@ -87,14 +110,20 @@ type EnumFieldDefinitionInput = BaseFieldDefinitionInput & {
87
110
  * single-valued foreign key with ON DELETE CASCADE: sharing and deletes (and
88
111
  * later ownership) cascade parent → child. Only valid on a single
89
112
  * (non-`multiple`) reference. `association` (the default) is a plain reference;
90
- * the target keeps its own access + lifecycle. `inverse` names the reverse
91
- * traversal (parent → children, e.g. a note's `blocks`).
113
+ * the target keeps its own access + lifecycle. `association_read` is an
114
+ * association that ALSO lets whoever can read the referenced record read the
115
+ * referring one — read-only, one hop. `hierarchy` makes the field the model's
116
+ * tree edge: foir answers `path` and `ancestors` by walking it, at most one per
117
+ * model and single-valued; its `referenceTypes` are unconstrained, since a
118
+ * parent need not share the child's model.
119
+ * `inverse` names the reverse traversal (parent → children, e.g. a note's
120
+ * `blocks`).
92
121
  *
93
122
  * The platform validates these at push (e.g. composition on a `multiple`
94
123
  * reference is rejected).
95
124
  */
96
125
  interface FieldRelationshipInput {
97
- kind: 'composition' | 'association';
126
+ kind: 'composition' | 'association' | 'association_read' | 'hierarchy';
98
127
  inverse?: string;
99
128
  }
100
129
  interface ReferenceFieldConfig {
@@ -103,6 +132,7 @@ interface ReferenceFieldConfig {
103
132
  /** Many-valued reference. Composition is invalid when true. */
104
133
  multiple?: boolean;
105
134
  relationship?: FieldRelationshipInput;
135
+ placement?: FieldPlacement;
106
136
  }
107
137
  type ReferenceFieldDefinitionInput = BaseFieldDefinitionInput & {
108
138
  type: 'reference';
@@ -154,13 +184,20 @@ interface AccessInput {
154
184
  /**
155
185
  * Visibility new records start at (the record descriptor modes):
156
186
  * - `'private'` — only the owner and explicit grants.
157
- * - `'public'` — readable by anyone in the project (CMS-style content;
158
- * including authenticated customers on the public API).
159
- * On a PROJECT-plane model, omitting this defaults to `'public'` — project
160
- * content is meant to be read, so it is customer-readable by default; set
161
- * `'private'` explicitly to opt a project model out. Owned planes
162
- * (customer/admin) are always private (the platform rejects a public default
163
- * there), regardless of this value.
187
+ * - `'public'` — readable by anyone with API access, including signed-in
188
+ * customers on the public API.
189
+ *
190
+ * On a PROJECT-plane model, omitting this defaults to `'public'` at
191
+ * CREATION — project content is meant to be read, and `'private'` there does
192
+ * not mean what it looks like: a project record has no owner, so it stays
193
+ * readable by anonymous and server-side callers while being hidden from
194
+ * signed-in customers. Set `'private'` explicitly to opt a project model out.
195
+ *
196
+ * The default is applied by the platform, at the create path only. Omitting it
197
+ * on a model that already exists means "leave it alone" — it will not reset a
198
+ * visibility you changed in the console. Owned planes (customer/admin) are
199
+ * always private (the platform rejects a public default there) regardless of
200
+ * this value.
164
201
  */
165
202
  defaultVisibility?: 'private' | 'public';
166
203
  }
@@ -227,7 +264,7 @@ interface ApplyConfigOperationInput {
227
264
  * narrows who may execute. Omit or pass `[]` for no gate. Entries are verbs
228
265
  * from the admin permission vocabulary, e.g. `['content:write']`.
229
266
  *
230
- * Replaced `allowedRoles` (TTS-1049), which named role KEYS. Role keys are
267
+ * Replaced `allowedRoles`, which named role KEYS. Role keys are
231
268
  * yours to choose, so renaming a role silently changed who could execute.
232
269
  */
233
270
  allowedPermissions?: string[];
@@ -352,7 +389,7 @@ interface ApplyConfigOperationInput {
352
389
  responseTransform?: string;
353
390
  }
354
391
  /**
355
- * A context dimension — the EID-409 home for segments. `sourceType: 'segment'`
392
+ * A context dimension — the home for segments. `sourceType: 'segment'`
356
393
  * carries an `evaluationRules` RuleExpression (evaluated fresh at request time
357
394
  * into segment.<key>); `sourceType: 'model'` designates a customer-context
358
395
  * source model via `sourceModelKey`. Reconciled config-scoped by `foir push`
@@ -402,6 +439,34 @@ interface ApplyConfigVariantCatalogEntryInput {
402
439
  isDefault?: boolean;
403
440
  isActive?: boolean;
404
441
  }
442
+ /**
443
+ * One transactional email's wording, keyed by the action that sends it.
444
+ *
445
+ * `actionKey` names a send path — `email.customer.welcome`, or a notification
446
+ * hook's own key. The platform refuses one it does not recognise, because a row
447
+ * written under a typo is read by nothing and looks applied.
448
+ *
449
+ * Reconciled by `foir push`, create/update only: a `workspace` override is
450
+ * tenant-level and inherited by every project, so two projects in one workspace
451
+ * can name the same row and an omission must not delete what the other declares.
452
+ * Remove an override in the console.
453
+ */
454
+ interface ApplyConfigEmailCopyInput {
455
+ /** The send path this wording belongs to. */
456
+ actionKey: string;
457
+ /** BCP-47 locale. Defaults to `en`. */
458
+ locale?: string;
459
+ /** `project` (default) or `workspace`, which every project inherits. */
460
+ scope?: 'project' | 'workspace';
461
+ subject?: string;
462
+ heading?: string;
463
+ body?: string;
464
+ footer?: string;
465
+ ctas?: Array<{
466
+ label: string;
467
+ url: string;
468
+ }>;
469
+ }
405
470
  interface ApplyConfigScheduleInput {
406
471
  /** Unique key for this schedule. Defaults to operationKey if not provided. */
407
472
  key?: string;
@@ -480,7 +545,7 @@ interface ApplyConfigRelyingPartyInput {
480
545
  */
481
546
  loginMethods?: ApplyConfigRelyingPartyLoginMethods;
482
547
  /**
483
- * White-label custom login domains for this RP (EID-46), e.g.
548
+ * White-label custom login domains for this RP, e.g.
484
549
  * `['login.eide.clothing']`. `foir push` registers each (creating a pending
485
550
  * row + a DNS-TXT challenge) and prunes any not listed. Verification is a
486
551
  * separate step — add the CNAME + `_foir-challenge` TXT records the push
@@ -489,7 +554,7 @@ interface ApplyConfigRelyingPartyInput {
489
554
  customDomains?: string[];
490
555
  /**
491
556
  * Where a bare custom-domain (front-door) login lands after Foir sets the
492
- * first-party session cookie (EID-108) — the app's landing, not an OAuth
557
+ * first-party session cookie — the app's landing, not an OAuth
493
558
  * callback. Empty falls back to the redirect URI. Customer-lane only.
494
559
  */
495
560
  postLoginUrl?: string;
@@ -553,13 +618,13 @@ interface ApplyConfigApiKeyInput {
553
618
  /**
554
619
  * A customer RBAC role declared in foir.config.ts and reconciled by
555
620
  * `foir push`. The role's `permissions` become a customer access token's
556
- * scope set at mint time (EID-125). Roles are matched by `key` within the
621
+ * scope set at mint time. Roles are matched by `key` within the
557
622
  * project — a declared key that already exists is updated in place; a new
558
623
  * key is created. (Roles present on the platform but absent from the config
559
624
  * are left untouched — never auto-disabled.)
560
625
  *
561
626
  * Without a role, a consumer app's customers get read-only tokens after the
562
- * Model B auth change (EID-17): the public `pk_` key can no longer grant
627
+ * Model B auth change: the public `pk_` key can no longer grant
563
628
  * write/execute, so the only source of those scopes is the customer's role.
564
629
  */
565
630
  interface ApplyConfigCustomerRoleInput {
@@ -758,8 +823,8 @@ interface ApplyConfigInput {
758
823
  /**
759
824
  * Customer RBAC roles, reconciled by `foir push`. Declare an
760
825
  * `isDefault: true` role to grant every customer the write/execute scopes
761
- * their tokens need (EID-125) — without one, customers get read-only
762
- * tokens after the Model B auth change (EID-17).
826
+ * their tokens need — without one, customers get read-only
827
+ * tokens after the Model B auth change.
763
828
  */
764
829
  customerRoles?: ApplyConfigCustomerRoleInput[];
765
830
  /** Per-project app declarations, keyed by app name. */
@@ -770,6 +835,11 @@ interface ApplyConfigInput {
770
835
  * Add `--publish` to promote draft → published in the same push.
771
836
  */
772
837
  designTokens?: ApplyConfigDesignTokensInput;
838
+ /**
839
+ * Transactional email wording, reconciled by `foir push` (create/update
840
+ * only — see ApplyConfigEmailCopyInput for why it never prunes).
841
+ */
842
+ emailCopy?: ApplyConfigEmailCopyInput[];
773
843
  [key: string]: unknown;
774
844
  }
775
845
  /** Define a complete config manifest. */
@@ -795,6 +865,8 @@ declare function defineOperation(operation: ApplyConfigOperationInput): ApplyCon
795
865
  declare function defineContextDimension(dimension: ApplyConfigContextDimensionInput): ApplyConfigContextDimensionInput;
796
866
  /** Define a project locale. */
797
867
  declare function defineLocale(locale: ApplyConfigLocaleInput): ApplyConfigLocaleInput;
868
+ /** Define one transactional email's wording. */
869
+ declare function defineEmailCopy(copy: ApplyConfigEmailCopyInput): ApplyConfigEmailCopyInput;
798
870
  /** Define a variant-catalog entry (targeting definition). */
799
871
  declare function defineVariantCatalogEntry(entry: ApplyConfigVariantCatalogEntryInput): ApplyConfigVariantCatalogEntryInput;
800
872
  /** Define a cron schedule for an operation. */
@@ -841,4 +913,4 @@ interface FoirSecretsConfig {
841
913
  */
842
914
  declare function defineSecrets(config: FoirSecretsConfig): FoirSecretsConfig;
843
915
 
844
- export { type AccessInput, type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigContextDimensionInput, type ApplyConfigCustomerRoleInput, type ApplyConfigDesignTokensInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigLocaleInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigRelyingPartyInput, type ApplyConfigRelyingPartyLoginMethods, type ApplyConfigScheduleInput, type ApplyConfigVariantCatalogEntryInput, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type FieldAccessInput, type FieldDefinitionInput, type FieldRelationshipInput, type FoirSecretsConfig, type LookupDefinitionInput, type QuotaRule, type ReferenceFieldConfig, type ReferenceFieldDefinitionInput, type SecretDeclaration, type SecretOwnerKind, type SelectFieldConfig, type SelectFieldDefinitionInput, type SignupModeName, type SignupRestrictionsInput, defineAuthProvider, defineConfig, defineContextDimension, defineDesignTokens, defineEnumField, defineField, defineHook, defineLocale, defineModel, defineOperation, definePlacement, defineRelyingParty, defineSchedule, defineSecrets, defineSelectField, defineVariantCatalogEntry };
916
+ export { type AccessInput, type AppInput, type AppPlacementFieldChoiceInput, type AppSinkMappingInput, type AppSourceMappingInput, type ApplyConfigApiKeyInput, type ApplyConfigAuthProviderInput, type ApplyConfigContextDimensionInput, type ApplyConfigCustomerRoleInput, type ApplyConfigDesignTokensInput, type ApplyConfigEmailCopyInput, type ApplyConfigHookInput, type ApplyConfigInput, type ApplyConfigLocaleInput, type ApplyConfigModelInput, type ApplyConfigOperationInput, type ApplyConfigPlacementInput, type ApplyConfigProjectInput, type ApplyConfigProjectSettingsInput, type ApplyConfigRelyingPartyInput, type ApplyConfigRelyingPartyLoginMethods, type ApplyConfigScheduleInput, type ApplyConfigVariantCatalogEntryInput, type EnumFieldConfig, type EnumFieldDefinitionInput, type EnumFieldOption, type FieldAccessInput, type FieldDefinitionInput, type FieldPlacement, type FieldRelationshipInput, type FoirSecretsConfig, type LookupDefinitionInput, type QuotaRule, type ReferenceFieldConfig, type ReferenceFieldDefinitionInput, type SecretDeclaration, type SecretOwnerKind, type SelectFieldConfig, type SelectFieldDefinitionInput, type SignupModeName, type SignupRestrictionsInput, defineAuthProvider, defineConfig, defineContextDimension, defineDesignTokens, defineEmailCopy, defineEnumField, defineField, defineHook, defineLocale, defineModel, defineOperation, definePlacement, defineRelyingParty, defineSchedule, defineSecrets, defineSelectField, defineVariantCatalogEntry };
@@ -26,6 +26,9 @@ function defineContextDimension(dimension) {
26
26
  function defineLocale(locale) {
27
27
  return locale;
28
28
  }
29
+ function defineEmailCopy(copy) {
30
+ return copy;
31
+ }
29
32
  function defineVariantCatalogEntry(entry) {
30
33
  return entry;
31
34
  }
@@ -52,6 +55,7 @@ export {
52
55
  defineConfig,
53
56
  defineContextDimension,
54
57
  defineDesignTokens,
58
+ defineEmailCopy,
55
59
  defineEnumField,
56
60
  defineField,
57
61
  defineHook,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foir/cli",
3
- "version": "0.64.1",
3
+ "version": "0.66.0",
4
4
  "description": "Universal platform CLI for Foir platform",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -38,6 +38,7 @@
38
38
  "@connectrpc/connect": "^2.0.0",
39
39
  "@connectrpc/connect-node": "^2.0.0",
40
40
  "@modelcontextprotocol/sdk": "^1.29.0",
41
+ "ai": "^7.0.29",
41
42
  "chalk": "^5.3.0",
42
43
  "commander": "^12.1.0",
43
44
  "dotenv": "^16.4.5",
@@ -56,10 +57,11 @@
56
57
  "tsx": "^4.20.0",
57
58
  "typescript": "5.9.2",
58
59
  "vitest": "^3.2.4",
60
+ "@foir/assistant": "0.0.0",
59
61
  "@foir/console-routes": "0.0.0",
60
62
  "@foir/proto-ts": "0.109.0",
61
63
  "@foir/rpc-node": "0.0.0",
62
- "@foir/sdk": "0.7.0"
64
+ "@foir/sdk": "0.9.0"
63
65
  },
64
66
  "engines": {
65
67
  "node": ">=18.0.0"