@salsita-ai/sdk 0.49.0-develop.fc391028c9 → 0.50.0-develop.da8d27f5d7
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/CHANGELOG.md +479 -0
- package/dist/docs/index.json +1 -1
- package/dist/docs/phoenix/state-management.mdx +1 -1
- package/dist/phoenix/cli.js +1 -1
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,485 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to the @salsita-ai/sdk package will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.49.0]
|
|
6
|
+
|
|
7
|
+
fix: configuration reopens properly from URL cid when `power` is `on` on initialization
|
|
8
|
+
|
|
9
|
+
feat: Postprocessing is applied to client-side images rendered with `imageRenderingSystem` `generateAndUploadDeclaredClientSideImages` and `generateDeclaredClientSideImages`
|
|
10
|
+
|
|
11
|
+
feat: add the experimental `extras/assistant` entry point (CON-2852)
|
|
12
|
+
|
|
13
|
+
`useAssistantChat` and the unstyled `AssistantChat` component talk to the server's experimental assistant routes for the current configuration: streamed replies, reasoning summaries, tool calls, stop, and reload that repaints the thread from the server and rejoins a reply still streaming. They need a customer with the AI Assistant feature flag on in Studio (CON-2960). `AuthorizedPhoenixApiClient` gains `getAssistantChatConnection`.
|
|
14
|
+
|
|
15
|
+
chore: bump `openai` to 7.23.0
|
|
16
|
+
|
|
17
|
+
feat: the experimental `assistant:` section declares the AI settings and instructions of the assistant, and preparing a deployment registers them (CON-2854)
|
|
18
|
+
|
|
19
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
|
|
20
|
+
|
|
21
|
+
```yaml
|
|
22
|
+
assistant:
|
|
23
|
+
ai:
|
|
24
|
+
provider: openai
|
|
25
|
+
model: balanced
|
|
26
|
+
reasoning: low
|
|
27
|
+
instructions: |
|
|
28
|
+
Tables are sold in pairs.
|
|
29
|
+
entities:
|
|
30
|
+
Table:
|
|
31
|
+
length:
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- `assistant.ai` sets what the assistant runs on: `provider` (`openai`), `model` (a tier: `frontier`, `advanced`, `balanced` or `fast`; the server maps it to a concrete model) and `reasoning` (`low`, `medium` or `high`). All three are required.
|
|
35
|
+
- `assistant.instructions` is optional free text for the assistant about this configurator. It is added to the system prompt after the schema.
|
|
36
|
+
- `deployment:prepare`, `deployment:prepare-local` and the development prepare of `phoenix dev` send the GraphQL schema, a prompt generated by the SDK, the instructions and the AI settings to the server (API `1.41.0` or newer). `deployment:prepare` logs the schema and the character count of the deployment's part of the prompt. A project without an `assistant:` section sends nothing and has no assistant chat.
|
|
37
|
+
- `ai` and `instructions` are read at prepare time only: they are not in `.phoenix/domain-spec.ts` or the runtime `AssistantManifest`.
|
|
38
|
+
|
|
39
|
+
change!: `assistant.ai` is required in every `assistant:` section
|
|
40
|
+
|
|
41
|
+
A spec with an `assistant:` section and no `ai` block no longer parses; the error names the missing key and its allowed values.
|
|
42
|
+
|
|
43
|
+
Migration:
|
|
44
|
+
|
|
45
|
+
- Add an `ai` block with `provider`, `model` and `reasoning` to every `assistant:` section, as shown above.
|
|
46
|
+
|
|
47
|
+
change!: the experimental `extras/assistant` chat no longer registers the `getViewport` browser tool
|
|
48
|
+
|
|
49
|
+
Migration:
|
|
50
|
+
|
|
51
|
+
- None needed in a project: the tool was registered by `useAssistantChat` itself. Use a server that includes this change, which no longer offers `getViewport` to the model.
|
|
52
|
+
|
|
53
|
+
chore: bump `@tanstack/ai` to 0.64.0 and `@tanstack/ai-react` to 0.29.4
|
|
54
|
+
|
|
55
|
+
chore: bump `vitest` to 4.1.11, also in generated project `package.json` files
|
|
56
|
+
|
|
57
|
+
feat: experimental `assistant` spec section with a generated GraphQL schema and a GraphQL editor panel
|
|
58
|
+
|
|
59
|
+
**Experimental and unstable:** the `assistant` section is a proof of concept for the conversational configurator and will be replaced by the full manifest without a deprecation period. Do not rely on it in production projects.
|
|
60
|
+
|
|
61
|
+
```yaml
|
|
62
|
+
assistant:
|
|
63
|
+
fieldName: hello
|
|
64
|
+
result: world
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The section generates a GraphQL schema (`type HelloWorld { hello: String! }`) at parse time. The schema reaches the configurator through `.phoenix/domain-spec.ts` and editor hot reload, and the new `graphQLSystem` configurator system executes queries against it (`{ hello }` answers `"world"`). The editor has a new GraphQL panel for running queries against the live configurator; several named operations in the panel are split into one operation list and run in order, and `phoenix deployment:prepare` logs the generated schema before registering the deployment.
|
|
68
|
+
|
|
69
|
+
feat: the experimental `assistant` section whitelists stored entity fields for the generated GraphQL schema
|
|
70
|
+
|
|
71
|
+
**Experimental and unstable:** the section is part of the conversational configurator work and can still change without a deprecation period. The hello-world shape (`fieldName`, `result`) is removed. Replace it with `entities`.
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
assistant:
|
|
75
|
+
entities:
|
|
76
|
+
Table:
|
|
77
|
+
length:
|
|
78
|
+
description: Tabletop length in millimeters
|
|
79
|
+
Top:
|
|
80
|
+
thickness:
|
|
81
|
+
as: topThickness
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
The schema is built from the latest model. Each alias becomes an interface implementing `AnyDomainEntity`, and each entity type becomes an object type implementing every alias that contains it. Every type exposes `id`, `type` and its whitelisted stored fields. A field whitelisted on an alias is inherited by every member type.
|
|
85
|
+
|
|
86
|
+
Field types come from the shape:
|
|
87
|
+
|
|
88
|
+
- numbers, integers, strings and booleans become the matching scalars;
|
|
89
|
+
- `DomainEntityId` references become `ID`;
|
|
90
|
+
- string enums become `String`, with the allowed values in the field description;
|
|
91
|
+
- fields are non-null unless the schema admits `null`, read through phoenix definitions and common types;
|
|
92
|
+
- everything else is opaque `JSON`.
|
|
93
|
+
|
|
94
|
+
A manifest `description` overrides the JSON Schema description, and `as` exposes a field under another name.
|
|
95
|
+
|
|
96
|
+
The query has four entry points:
|
|
97
|
+
|
|
98
|
+
- `root`, typed as the root entity type itself when there is only one, so its fields need no inline fragment;
|
|
99
|
+
- `entities(ids:)`, which returns every entity, or the given ids in order with `null` for an unknown id. An id that is not of the form `<Type>-<number>` is an error;
|
|
100
|
+
- `selectedEntity`;
|
|
101
|
+
- `navigation`, with one field for each state kind that has more than one value (`navigationState`, `viewState`, `toolState`). It is left out when no kind has more than one value.
|
|
102
|
+
|
|
103
|
+
The configurator `graphQLSystem` resolves these against the committed state.
|
|
104
|
+
|
|
105
|
+
Manifest mistakes are parse diagnostics:
|
|
106
|
+
|
|
107
|
+
- an unknown entity type, alias or field;
|
|
108
|
+
- a `$` expression entry (not supported yet);
|
|
109
|
+
- a field exposed as the reserved `id`, `type` or `meta`;
|
|
110
|
+
- one GraphQL name mapped to two stored fields;
|
|
111
|
+
- an entity type or alias named like a built-in schema type.
|
|
112
|
+
|
|
113
|
+
feat: the experimental assistant GraphQL schema gets its first mutation, `deleteEntity`, and `GraphQLSystem` commits a list of operations as one undo step
|
|
114
|
+
|
|
115
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. `GraphQLSystem.execute` now returns `{ results, committed, message? }` instead of the bare results array.
|
|
116
|
+
|
|
117
|
+
```yaml
|
|
118
|
+
assistant:
|
|
119
|
+
deletable:
|
|
120
|
+
Cabinet:
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`assistant.deletable` names the entity types or aliases the assistant may delete; an alias stands for all of its members. It adds `deleteEntity(id: ID!): [ID!]!` to a new `Mutation` type. The field takes no selection and returns the ids of every entity the deletion removed, the given one first, followed by the entities removed with it, such as composition children. Without `deletable`, the schema has no `Mutation` type. Do not declare projected entity types deletable: the configurator re-creates them after every change. `Mutation` is now a reserved type name, and an unknown `deletable` key reports `ASSISTANT_UNKNOWN_TYPE`.
|
|
124
|
+
|
|
125
|
+
- `GraphQLSystem.prepare(state, operations)` runs a list of single-operation documents through one working state without committing anything. A query reads the current working state. Mutation fields run in order, as GraphQL runs them: each computes its changes from the state the previous field left and produces its own emission, and a field that would leave the configuration invalid fails. The first operation that fails stops the list; every later operation reports that it did not run. Each operation comes back with its result and the emissions of its mutation fields.
|
|
126
|
+
- `GraphQLSystem.execute(operations)` prepares the list from the committed state and commits it as one emission, synthesized from every field emission: one undo step, with `onBeforeCommit` hooks run once. If the configuration changed in the meantime, the list is prepared again once from the new state. A commit an `onBeforeCommit` hook holds is reported in `message` and is not retried.
|
|
127
|
+
- `StoreSystem.commitPrepared(emission)` commits an emission prepared outside the store. It returns `'conflict'` when the committed state is no longer the emission's `previousState`, and `'held'` when a hook vetoes it. A held emission still commits through the hook's deferred `emit`, but only while the committed state is unchanged; otherwise it is dropped with a warning.
|
|
128
|
+
- Mutation implementations are registered in `assistantMutations`. `deleteEntityUpdateTag` is shared by the assistant and `useDeletedEntity`.
|
|
129
|
+
- The editor GraphQL panel commits on Run and has a "Dry run" toggle that prepares against the committed state without committing.
|
|
130
|
+
|
|
131
|
+
change: in the experimental assistant GraphQL schema, a reference to a common type is typed as the schema behind it instead of `JSON`
|
|
132
|
+
|
|
133
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
model:
|
|
137
|
+
latest:
|
|
138
|
+
commonTypes:
|
|
139
|
+
GunColor:
|
|
140
|
+
enum: [Black, Tan, Green]
|
|
141
|
+
shapes:
|
|
142
|
+
Gun:
|
|
143
|
+
gunColor:
|
|
144
|
+
$ref: 'common:GunColor'
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
An exposed `gunColor` is now `String!` with `One of: "Black", "Tan", "Green".` in its description, and its `GunDelta` field is `String`. Before, both were `JSON`.
|
|
148
|
+
|
|
149
|
+
- A reference to a common type maps exactly as if the common type's schema were written inline: a string enum is `String` with its values listed, a primitive is its scalar, a list of either is a list. This holds for both reference forms (`common:Name` and `#/model/<version>/commonTypes/Name`), through a chain of references and for list items, in stored fields, declared expressions and `<Type>Delta` inputs.
|
|
150
|
+
- A union (`anyOf` or `oneOf`) whose non-null members are all string enums is `String`, and the description lists the allowed values once, in member order: every value of an `anyOf`, and of a `oneOf` the values of exactly one member, since `oneOf` rejects a value that two members allow. Other unions with more than one non-null member stay `JSON`, and so does a schema that has both `anyOf` and `oneOf`.
|
|
151
|
+
- A common type that is an object, a custom `tsType`, a mixed union or part of a reference cycle stays `JSON`.
|
|
152
|
+
- A field with no description of its own and none in the manifest takes the description of the common type it references, directly or as the only non-null member of a union (a nullable reference). A list does not inherit the description of its items' common type.
|
|
153
|
+
- Values are returned as before and only the schema text changes, with one exception: a common type that names a localizable phoenix definition now resolves to a string in the current locale, like an inline reference to that definition. Remove hand-written value lists from manifest descriptions, since the generated ones now follow them. A deployment must be prepared again for the assistant to see the new schema.
|
|
154
|
+
- A `JSON` value is plain serializable data. An expression exposed as `JSON` that returns a value containing itself now fails its field with an error naming the expression and the entity, instead of a stack overflow.
|
|
155
|
+
|
|
156
|
+
feat: the experimental assistant GraphQL schema lists the choices of a writable field under `meta`, `modify<Type>` rejects a value outside them, and `hints` say when a list can be reused
|
|
157
|
+
|
|
158
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. The shape of `assistant.childRecipes` changes with it; see the last point.
|
|
159
|
+
|
|
160
|
+
```yaml
|
|
161
|
+
assistant:
|
|
162
|
+
writable:
|
|
163
|
+
KitchenRange:
|
|
164
|
+
color:
|
|
165
|
+
options:
|
|
166
|
+
componentType: materialSwatch
|
|
167
|
+
instanceName: color
|
|
168
|
+
as: enamelColors
|
|
169
|
+
validate: false
|
|
170
|
+
hints:
|
|
171
|
+
- sameForAllInstances
|
|
172
|
+
- constantDuringSession
|
|
173
|
+
childRecipes:
|
|
174
|
+
KitchenRange:
|
|
175
|
+
hints:
|
|
176
|
+
- constantDuringSession
|
|
177
|
+
groups:
|
|
178
|
+
base:
|
|
179
|
+
description: Base units that can be added to the range
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
`options` on a `writable` field names the component instance that holds the field's choices. `componentType` is `materialSwatch`, `attributeSelection` or `distanceDimensionSelect`, and `instanceName` its instance on the entity type; an alias key adds the edge to every member, and every member must have the instance.
|
|
183
|
+
|
|
184
|
+
- Each such field adds the nullable edge `<field>Options: [FieldOption!]` to `<Type>Meta`, named after the delta field; `as` replaces the name. An entity type now gets `meta` when it has an options edge or a recipe group.
|
|
185
|
+
- `FieldOption` is `{ value: JSON!, name, description, imageUrl, color }`. `materialSwatch` maps the swatch key to `value`, `swatchName` to `name`, `thumbnailUrl` to `imageUrl`, and `color`; `attributeSelection` maps `value`, `label` to `name`, `description`, and `img` to `imageUrl`; `distanceDimensionSelect` maps `value` and `description`. Texts are in the current language.
|
|
186
|
+
- The edge reads the state its entity was read on, so it also works under a recipe dry-run. It is `null` when the component is not active on the entity; component filters apply as they do in the UI.
|
|
187
|
+
- `modify<Type>` checks each delta field that declares `options` against the choices its component offers in the state the mutation field starts from, by strict equality and before anything is applied. A value that is not among them fails the field with a message listing the valid values. The check is skipped for `null`, when the component is not active on the entity, and with `validate: false`. When the choices of one field depend on another field, write them as two mutation fields of one operation.
|
|
188
|
+
- `hints` is a list of `sameForAllInstances` and `constantDuringSession`, accepted inside `options` and on a `childRecipes` type. Each hint appends a sentence to the generated description of the edge; nothing verifies it. Hints on an alias apply to every member, and a member with its own `childRecipes` entry gets the union.
|
|
189
|
+
- New diagnostics `ASSISTANT_UNKNOWN_OPTIONS_COMPONENT`, `ASSISTANT_RESERVED_OPTIONS_EDGE_NAME` (an edge named `childRecipes`) and `ASSISTANT_DUPLICATE_OPTIONS_EDGE` (an entity type that would get one edge name, or the options of one field, twice, from its own key and from an alias). `ASSISTANT_META_TYPE_NAME_CONFLICT` also covers a type that gets `meta` from an options edge and is reported at the entry that gives the type its `meta`. `FieldOption` is now a reserved name for entity types and aliases of an application with an `assistant` section.
|
|
190
|
+
- The generated assistant prompt explains how to find the values a field can take when the schema has an options edge, so a deployment must be prepared again for the model to learn about it.
|
|
191
|
+
- New domain exports for the GraphQL system: `findAssistantFieldOptions`, `readAssistantFieldOptions` and the `AssistantFieldOption` and `AssistantFieldOptions` types.
|
|
192
|
+
- **Manifests written for the child recipes of this unreleased feature must be migrated:** the recipe groups of a type move under `groups` (`childRecipes: { Room: { cabinets: } }` becomes `childRecipes: { Room: { groups: { cabinets: } } }`). The old shape is a parse error, and the paths of the recipe group diagnostics gain the `groups` segment.
|
|
193
|
+
|
|
194
|
+
feat: the experimental assistant GraphQL schema gets a `modify<Type>` mutation for every entity type with writable fields
|
|
195
|
+
|
|
196
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
|
|
197
|
+
|
|
198
|
+
```yaml
|
|
199
|
+
assistant:
|
|
200
|
+
writable:
|
|
201
|
+
Table:
|
|
202
|
+
length:
|
|
203
|
+
width:
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`assistant.writable` names, per entity type or alias, the stored fields the assistant may write; an alias stands for all of its members, and a writable field does not have to be exposed in `entities`. Each entity type with at least one writable field gets an input `<Type>Delta` with those fields, all optional, and `modify<Type>(id: ID!, delta: <Type>Delta!): <Type>` on `Mutation`. Aliases get no mutation of their own. Do not declare projected entity types writable: reconciliation may overwrite their values.
|
|
207
|
+
|
|
208
|
+
- A delta field is named as the field is exposed in `entities` (including `as:`), or by its property name when it is not exposed. When several names expose the property, the one from the type's own `entities` entry wins, then the first inherited from an alias. A localized field takes the raw stored value as `JSON`.
|
|
209
|
+
- A field changes one entity. An omitted delta field stays unchanged, an explicit `null` sets `null`, and an empty delta is refused. Values are in stored units; control hooks (unit conversion, snapping) are bypassed, transformers still run.
|
|
210
|
+
- The changed entity is checked against its shape schema before anything is applied, and the field fails with the validator's messages when it does not fit. An invalid resulting configuration fails the field as for `deleteEntity`.
|
|
211
|
+
- The result is the entity as it is after the field, including transformer adjustments and recomputed expressions, or `null` when the change made a transformer remove the entity.
|
|
212
|
+
- Once a mutation field fails, the later mutation fields of the same operation are not run and each reports a "Not run" error. This holds for every mutation, including nullable ones such as `modify<Type>`, where GraphQL alone would continue.
|
|
213
|
+
- The updates carry the `modifyEntityUpdateTag` (`'modifyEntity'`) update tag.
|
|
214
|
+
- New diagnostic `ASSISTANT_NOT_WRITABLE` for an expression, `id` or `type` under `writable`; `ASSISTANT_UNKNOWN_TYPE`, `ASSISTANT_UNKNOWN_FIELD` and `ASSISTANT_FIELD_NAME_CONFLICT` also cover `writable` entries; the conflict is reported at the `writable` entry and says how to fix it there.
|
|
215
|
+
- `assistantMutations` is replaced by `findAssistantMutation(fieldName, manifest)`, and mutation implementations receive the domain in their context.
|
|
216
|
+
|
|
217
|
+
feat: the experimental assistant GraphQL schema lists the recipes of whitelisted `childEntityRecipes` groups, dry-runs them and adds children with `addChild`
|
|
218
|
+
|
|
219
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
|
|
220
|
+
|
|
221
|
+
```yaml
|
|
222
|
+
assistant:
|
|
223
|
+
childRecipes:
|
|
224
|
+
Room:
|
|
225
|
+
groups:
|
|
226
|
+
cabinets:
|
|
227
|
+
sofas:
|
|
228
|
+
as: seating
|
|
229
|
+
description: Add sofas to the room
|
|
230
|
+
allowDryRuns: false
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
`assistant.childRecipes` names, per entity type or alias, the instances of the `childEntityRecipes` component whose recipes the assistant may add; an alias stands for all of its members, and every member must have the component instance. `as` is the id the assistant addresses the group by (the instance name by default), `description` replaces the component's group description, and `allowDryRuns: false` turns dry-runs off for a group too large to try out.
|
|
234
|
+
|
|
235
|
+
- Each such entity type gets `meta: <Type>Meta!` with `childRecipes: [RecipeGroup!]!`. A group is listed only while its component is active on the entity; component filters apply as they do in the UI.
|
|
236
|
+
- A recipe list item without recipes adds nothing and is not listed, unlike in the UI.
|
|
237
|
+
- A `Recipe` has `key`, `name`, `description`, `imageUrl` and `dryRun`. `key` is the new optional `key` of the recipe list item, or the title in the current language without one, with ` 2`, ` 3`, … on duplicates. Declare `key` for recipes that instructions or tests name, since a title key changes with the language.
|
|
238
|
+
- `dryRun` cooks the recipe on the state the query reads, the way the add controls do, and changes nothing: `ok`, the `root` and `newEntities` of the resulting configuration (everything selected under them reads that configuration), and `errors` with the violations of the failing severity (the fatal ones, or the fixable ones when none is fatal), or one `uncaught` entry when validation threw. It is `null` for a group with `allowDryRuns: false`.
|
|
239
|
+
- `addChild(parentId: ID!, recipeGroupId: String!, recipeKey: String!): [AnyDomainEntity!]!` makes the same updates as the add controls (`createRecipe` update tag, the component's collection) and returns every entity the change added to the configuration: the recipe's own first, the ones under the parent before the rest, then whatever transformers added in response, projected entities included.
|
|
240
|
+
- New diagnostics `ASSISTANT_UNKNOWN_RECIPE_GROUP`, `ASSISTANT_DUPLICATE_RECIPE_GROUP` and `ASSISTANT_META_TYPE_NAME_CONFLICT`; `ASSISTANT_UNKNOWN_TYPE` also covers `childRecipes` keys. `RecipeGroup`, `Recipe`, `RecipeDryRun` and `RecipeDryRunError` are now reserved names for entity types and aliases of an application with an `assistant` section, as is `<Type>Meta` for a type with recipe groups.
|
|
241
|
+
- The generated assistant prompt explains how to add entities when the schema has `addChild`, so a deployment must be prepared again for the model to learn about it.
|
|
242
|
+
- New domain exports for the GraphQL system: `findAssistantRecipeGroups`, `readAssistantRecipeGroup`, `prepareAssistantRecipeUpdates` and `assistantAddedEntityIds`.
|
|
243
|
+
- `RecipeListItem` gains the optional `key`, also on replace and sketching recipe lists, where nothing reads it yet.
|
|
244
|
+
|
|
245
|
+
feat: the experimental assistant reads and changes the configuration through the `query` and `apply` tools (CON-2870)
|
|
246
|
+
|
|
247
|
+
`useAssistantChat` runs the model's tool calls in the browser against the configurator's GraphQL schema. `query` runs a list of operations from the committed state and discards every change, so mutations in it answer "what if" questions. `apply` runs the same list and commits its changes as one undo step; outside owner mode it refuses without running anything. Both return the GraphQL results with `committed` and, when nothing was committed, a `message` saying why. The prompt generated at `deployment:prepare` gains a section on the two tools, so a deployment has to be prepared again for the model to learn about them.
|
|
248
|
+
|
|
249
|
+
`AssistantChat` renders nothing when the application spec has no `assistant` section. The new `useSpecAssistant` hook of the `react` entry point returns the spec's `assistant` manifest, or nothing when the spec has none.
|
|
250
|
+
|
|
251
|
+
change!: `useAssistantChat` and `AssistantChatState` move from `@salsita-ai/sdk/extras/assistant` to `@salsita-ai/sdk/configurator` (CON-2870)
|
|
252
|
+
|
|
253
|
+
The hook now reads the configurator's systems, so it belongs to the configurator entry point and must be rendered inside the configurator. `extras/assistant` keeps `AssistantChat`. The hook never shipped in a release from its old path.
|
|
254
|
+
|
|
255
|
+
Migration:
|
|
256
|
+
|
|
257
|
+
- Import `useAssistantChat` and `AssistantChatState` from `@salsita-ai/sdk/configurator` instead of `@salsita-ai/sdk/extras/assistant`.
|
|
258
|
+
|
|
259
|
+
change!: client-side image exports without Postprocessing tone map the finished image, not each material
|
|
260
|
+
|
|
261
|
+
Exports rendered by `imageRenderingSystem.renderImage` now run the renderer's tone mapping, exposure and sRGB transfer once over the finished offscreen frame. The scene renders over a transparent background, and a background colour (a Color `scene.background`, the export's `backgroundColor`, or white for JPEG) is composited behind it afterwards. These canvas behaviours, which 0.48.0 preserved by drawing exports on the live canvas, no longer hold without Postprocessing:
|
|
262
|
+
|
|
263
|
+
- material overrides with `toneMapped: false` are tone mapped in exported images;
|
|
264
|
+
- a texture or environment `scene.background` in an sRGB colour space is tone mapped in exported images, where the canvas leaves it untouched;
|
|
265
|
+
- semi-transparent materials blend in linear space before tone mapping, so they can come out lighter than on the canvas;
|
|
266
|
+
- additive, multiply and other blend modes that read the destination colour (`blendingMode` in a material override) render against a transparent background and do not blend with the export's background colour, so a multiply-blended shadow over a plain background can disappear and an additive glow can darken it;
|
|
267
|
+
- transmissive materials (`transmission`, e.g. glass) refract a neutral half-transparent white where the canvas refracts the background colour.
|
|
268
|
+
|
|
269
|
+
`CustomToneMapping` keeps applying, through the same `tonemapping_pars_fragment` chunk override that materials use.
|
|
270
|
+
|
|
271
|
+
Migration: compare the exports of projects that rely on `toneMapped: false` for exact colours (unlit labels, logos, engravings), on an image background, or on semi-transparent, additive, multiply-blended or transmissive materials against the previous release. There is no per-material opt-out in exports: keep such colours exact by choosing them so they survive the renderer's tone mapping, or set `toneMapping: NoToneMapping` on the renderer when the scene does not need it. On-canvas rendering is unchanged.
|
|
272
|
+
|
|
273
|
+
fix: client-side image exports no longer flash export frames on the live canvas
|
|
274
|
+
|
|
275
|
+
`imageRenderingSystem.renderImage` renders offscreen again, with or without Postprocessing, and keeps the exposure and transparent backgrounds of the current canvas-based images. It backs declared `clientSideImage` components, save-template thumbnails and editor screenshots. While images generate, the live canvas shows a still of the current view. Standalone `renderImage` calls now pause the frameloop for the duration of the export. The pause is `exportSystem.pauseFrameloop`, which `runExportJobs` holds too, so exports that overlap share it and the loop resumes with the last of them.
|
|
276
|
+
|
|
277
|
+
chore: regenerate the API client types for the SDK-builder build report endpoint (API `1.34.0`)
|
|
278
|
+
|
|
279
|
+
feat: add `phoenix deployment:build-envs`, which writes the environment for a build that runs apart from the deploy
|
|
280
|
+
|
|
281
|
+
For a deployment prepared with `deployment:prepare`, it writes only what a build running project code without platform secrets may see: the customer's environment variables, their Clerk/OAuth settings and the public platform values. The file has the format of `.env.generated` and is written there by default, so the project builds with it unchanged; move the full `.env.generated` that `deployment:prepare` wrote out of the folder first, or set `OUT_FILE`. Requires a server with API `1.35.0` or newer.
|
|
282
|
+
|
|
283
|
+
feat: add `phoenix login <workspace>` and `phoenix logout` for SDK-builder workspaces
|
|
284
|
+
|
|
285
|
+
`phoenix login` opens Studio, where you approve the sign-in. The CLI then receives a revocable API token through a PKCE-protected callback on `127.0.0.1` and stores it in `~/.phoenix/credentials.json`, readable only by you. Signing in again revokes the token it replaces. `phoenix logout` revokes the token and removes it. Both use the EU region by default; `--region us` selects the US region, and `--serverUrl` / `--studioUrl` point at a local or custom server.
|
|
286
|
+
|
|
287
|
+
feat: add `phoenix init --scaffold` and `phoenix deploy` for SDK-builder workspaces
|
|
288
|
+
|
|
289
|
+
`phoenix init [folder] --scaffold` only writes the starter project: a `package.json` that depends on the CLI's SDK version when the folder has none, and the starter spec. It records no development environment and doesn't sync. `phoenix deploy` validates the spec, uploads the project files and follows the server-side build until the configurator is live. See "SDK Builder Workflow" in the docs.
|
|
290
|
+
|
|
291
|
+
feat: add `phoenix deployment:extract-sources` for build pipelines
|
|
292
|
+
|
|
293
|
+
`phoenix deployment:extract-sources <archive> <folder>` validates the source archive of a `phoenix deploy` and extracts its project files. It refuses links, special files, unsafe paths, colliding entries, oversized content and archives it cannot read to the end, and leaves out entries that are not project files.
|
|
294
|
+
|
|
295
|
+
fix: new application-spec projects get a webhooks entry point that builds
|
|
296
|
+
|
|
297
|
+
The scaffolded `src/webhooks/index.ts` built a webhook with no handler, which throws. It now registers an anonymous auth hook on `phx.webhooks`, which stays inert until the spec sets `useAuthHook`. Existing projects keep their file.
|
|
298
|
+
|
|
299
|
+
feat: the experimental `assistant` section whitelists expressions and relation edges, and localizable values resolve to strings
|
|
300
|
+
|
|
301
|
+
**Experimental and unstable:** the section is part of the conversational configurator work and can still change without a deprecation period. The `.phoenix` field side table (`AssistantManifest.fields`) now maps each GraphQL field to `{ source, localized? }` instead of a stored field name.
|
|
302
|
+
|
|
303
|
+
```yaml
|
|
304
|
+
assistant:
|
|
305
|
+
entities:
|
|
306
|
+
Table:
|
|
307
|
+
$price:
|
|
308
|
+
description: Total price in EUR
|
|
309
|
+
$top:
|
|
310
|
+
as: tableTop
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
A `$` entry whitelists an expression declared in `expressions`, `constants` or `relations`. An entity type can also whitelist expressions declared on its aliases, while an alias can whitelist only its own. An expression is exposed as `_name` by default, and `as` renames it.
|
|
314
|
+
|
|
315
|
+
- Relation expressions become edges. The target is an entity's object type, an alias's interface, or, for an alias with a single member, that member's object type. `1-1` edges are `T!`, `0-1` edges are `T`, and `0-N` and `1-N` edges are `[T!]!`.
|
|
316
|
+
- Other expressions are typed from their declared schema like stored fields. Constraints that component `$read` inference adds do not change the type. Everything that is not a primitive, a string enum or a list of those is `JSON`. A `$` entity inside a `JSON` value is returned as its stored entity.
|
|
317
|
+
- Refs to the localizable phoenix definitions (`Localizable`, `EnhancedLocalizable`, `MessageDescriptor`, `EnhancedMessageDescriptor`, `MultilingualString`) are `String`, on stored fields and expressions alike. They resolve in the current locale, and lists resolve item by item. A `tsType` alone stays `JSON`.
|
|
318
|
+
- An expression that throws fails its field with an error naming the expression and the entity. The `null` propagates by GraphQL's rules, so a non-null field also nulls its parent.
|
|
319
|
+
|
|
320
|
+
A `$msg` expression value is now inferred as `{ $ref: 'phx:MessageDescriptor' }` rather than a bare `MessageDescriptor` `tsType`. The generated TypeScript types are unchanged.
|
|
321
|
+
|
|
322
|
+
Manifest diagnostics for `$` entries:
|
|
323
|
+
|
|
324
|
+
- `ASSISTANT_UNKNOWN_FIELD` for a `$` entry that is not declared where it is whitelisted, including an expression only inferred from a component `$read`;
|
|
325
|
+
- `ASSISTANT_UNSUPPORTED_EXPRESSION` for `$violations`, which gets its own edge later.
|
|
326
|
+
|
|
327
|
+
fix: dragging against a rotated `disjoint` peer no longer drops the frame rate — auto-snap reads the proposed drag state without validating it, so invariants are evaluated once per tick on the constrained state instead of also on the overlapping one
|
|
328
|
+
|
|
329
|
+
feat: give every configurator session a user identity (CON-2897)
|
|
330
|
+
|
|
331
|
+
The SDK keeps a random browser id under the localStorage key `configurator-user-id` (in memory when storage is unavailable) and sends it with every session authentication, so a session without a signed-in user belongs to the browser's anonymous user. It is separate from the analytics `user_id`, which stays unchanged. `UnauthenticatedPhoenixApiClient.authenticateSession` gains an optional `browserUserId` parameter.
|
|
332
|
+
|
|
333
|
+
Auth handlers may return the signed-in user's id as `userId`. The auth handler builder's Clerk handlers return the Clerk user id (it also stays in `data.id`), and the builder marks it as a Clerk id; ids from every other handler are custom ids, so a custom handler cannot pass as a Clerk user. A custom refresh handler that omits `userId` turns the session into the browser's anonymous user, so return it from every flow that knows the user.
|
|
334
|
+
|
|
335
|
+
feat: ship coding-agent guidance as Phoenix agent skills and a starter `AGENTS.md` (CON-2911)
|
|
336
|
+
|
|
337
|
+
Sync, dev and build now write the `phoenix`, `phoenix-cli` and `phoenix-review` skills to `.claude/skills/` and `.agents/skills/` and keep them in step with the installed SDK, so Claude Code, Codex and Cursor all pick them up. A short `AGENTS.md` with the build commands, the do-not-edit list and a pointer to the skills is written once when missing and is the project's to edit afterwards. Phoenix only manages `phoenix` and `phoenix-*` skill folders; any other skill folder is left alone.
|
|
338
|
+
|
|
339
|
+
The CLI no longer generates `CLAUDE.md`, `.cursor/rules/phoenix.mdc` or the `commands/review.md` files. On the next sync, the `[PHOENIX-SDK-START]`/`[PHOENIX-SDK-END]` block is removed from `CLAUDE.md` and `.cursor/rules/phoenix.mdc` with your own text around it kept, a file that held nothing else is deleted, and both `commands/review.md` files are deleted. Use the `phoenix-review` skill where you used `/review`.
|
|
340
|
+
|
|
341
|
+
feat: add `phoenix init` to start a project in a folder where the SDK is installed (CON-2911)
|
|
342
|
+
|
|
343
|
+
`phoenix init` writes a starter spec with one resizable, recolorable box, asks for the Studio development-environment values (`SERVER_URL`, `PROJECT_SLUG`, `ENVIRONMENT_SLUG`, `DEVELOPMENT_KEY`) that are not already in `.env` and appends them there, switches `package.json` to the application-spec pipeline, and runs `phoenix sync`. The development key is read without echo. Each value can also be passed as a flag; without a terminal, missing values fail the command instead of prompting. `phoenix init` refuses to run when a `spec/` folder already exists.
|
|
344
|
+
|
|
345
|
+
change!: use generic names for bundled theme presets and remove customer-specific commentary from published package content.
|
|
346
|
+
|
|
347
|
+
Migration: select `RoundedRed` for the red rounded preset in `themePreset` configuration and use `getPhoenixThemes().RoundedRed` in TypeScript. The previous red preset key is removed without an alias. All other public preset keys and theme styles are unchanged.
|
|
348
|
+
|
|
349
|
+
change!: publish the SDK publicly as `@salsita-ai/sdk`, the one package a project depends on, under a proprietary licence
|
|
350
|
+
|
|
351
|
+
The SDK is now public on npm under the Salsita SDK License (see `LICENSE`). It re-exports every entry point of `@salsita-ai/common` (`./common/{utils,env,eslint,prettier}`) and `@salsita-ai/contracts` (`./contracts/{api,clerk,domain,chat,assistant,webhook}`), so a project declares `@salsita-ai/sdk` alone; generated code imports through it, and sync drops the superseded dependency names from `package.json`. Published code is minified and ships without source maps, and the archive no longer carries the SDK's own test fixtures and snapshots. Builds from `develop` publish under the `canary` tag, `publish_to_npm` PR builds under `beta`. `@salsita-ai/platform-project-sdk`, `@salsita-ai/platform-common` and `@salsita-ai/platform-contracts` receive no further releases. The `phoenix` CLI, the `phx` global and the `.phoenix` generated folder are unchanged.
|
|
352
|
+
|
|
353
|
+
Migration:
|
|
354
|
+
|
|
355
|
+
- In `package.json`, replace the three old dependencies with `"@salsita-ai/sdk": "<version>"` alone, then run `pnpm install` and `pnpm phoenix sync`. Sync regenerates the project files and removes the old dependency names if they are still declared.
|
|
356
|
+
- Replace imports in your own code and YAML `tsType` imports:
|
|
357
|
+
- `@salsita-ai/platform-project-sdk/<path>` → `@salsita-ai/sdk/<path>`
|
|
358
|
+
- `@salsita-ai/platform-common/<path>` → `@salsita-ai/sdk/common/<path>` (`utils`, `env`, `eslint`, `prettier`)
|
|
359
|
+
- `@salsita-ai/platform-contracts/<path>` → `@salsita-ai/sdk/contracts/<path>` (`api`, `clerk`, `domain`, `chat`, `assistant`, `webhook`)
|
|
360
|
+
- Remove the `@salsita-ai` registry token from `.npmrc` and CI once no restricted package is installed; the public package needs none.
|
|
361
|
+
|
|
362
|
+
feat: add the `phoenix-create-configurator` agent skill (CON-2924)
|
|
363
|
+
|
|
364
|
+
Sync, dev and build now also write the `phoenix-create-configurator` skill to `.claude/skills/` and `.agents/skills/`, with its `references/` files beside it. It walks a coding agent from the builder's product documents and assets to a reviewed configurator in five stages: a representative first slice, a plan of the whole configuration experience, options and features added in small increments, finalization, and a complete review. The agent studies the SDK documentation through `pnpm phoenix docs` before building, keeps a `configurator-spec.md` in the project as the record of decisions and open questions, and asks the builder only for decisions it cannot derive from the materials.
|
|
365
|
+
|
|
366
|
+
Files inside a Phoenix-owned skill folder that the installed SDK no longer emits are now removed on sync, the same way stale `phoenix-*` skill folders are.
|
|
367
|
+
|
|
368
|
+
feat: `phoenix dev` works from the `phoenix login` sign-in, without a development key (CON-2948)
|
|
369
|
+
|
|
370
|
+
With no `DEVELOPMENT_KEY` set, `phoenix dev` uses the stored sign-in: on start it asks the server for your development environment in the project and works there. The server creates the environment on the first run, and every user gets their own. No `.env` and no environment in Studio are needed. The server is `--serverUrl`, then `SERVER_URL`, then the region (`--region` or `PHOENIX_REGION`). Projects with a `DEVELOPMENT_KEY` in `.env` work as before.
|
|
371
|
+
|
|
372
|
+
feat: the application spec names its server project with `project` (CON-2948)
|
|
373
|
+
|
|
374
|
+
`project` in a spec file is a project slug, or `default` for the default project of the workspace you are signed in to. `phoenix dev` resolves the project from `--projectSlug`, then `PROJECT_SLUG`, then `project`, and fails when none names one. `default` needs the sign-in; with a development key, set `PROJECT_SLUG` or name the slug. The field is optional, belongs in one spec file, is read only by the CLI and does not reach the built application.
|
|
375
|
+
|
|
376
|
+
feat: `phoenix init` only writes the starter project (CON-2948)
|
|
377
|
+
|
|
378
|
+
`phoenix init [folder]` writes `package.json` when the folder has none and the starter spec, which now begins with `project: default`. It no longer prompts for the Studio development-environment values, writes `.env` or runs `phoenix sync`, and the `--serverUrl`, `--projectSlug`, `--environmentSlug` and `--developmentKey` flags are removed. This replaces the prompting `phoenix init` and the `--scaffold` mode added earlier in this release; `--scaffold` is still accepted and changes nothing. To develop against a Studio development environment, paste its `.env` block into `.env` after `init`.
|
|
379
|
+
|
|
380
|
+
feat: the experimental assistant GraphQL schema lists the recipes an entity can be replaced with under `meta { replaceRecipes }`, dry-runs them and replaces the entity with `replaceEntity`
|
|
381
|
+
|
|
382
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period.
|
|
383
|
+
|
|
384
|
+
```yaml
|
|
385
|
+
assistant:
|
|
386
|
+
replaceRecipes:
|
|
387
|
+
CookingTop:
|
|
388
|
+
hints:
|
|
389
|
+
- stable
|
|
390
|
+
groups:
|
|
391
|
+
replace:
|
|
392
|
+
as: others
|
|
393
|
+
description: Other cooking components this one can be replaced with
|
|
394
|
+
allowDryRuns: false
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
`replaceRecipes` whitelists `replaceEntityRecipes` component instances per entity type or alias, in the shape `childRecipes` has: `groups` keyed by the instance name, each with `as`, `description` and `allowDryRuns`, and `hints` on the type.
|
|
398
|
+
|
|
399
|
+
- An entity type with a replace group gets `replaceRecipes: [RecipeGroup!]!` on its `<Type>Meta`, beside `childRecipes` when it has both. `RecipeGroup`, `Recipe`, `RecipeDryRun` and `RecipeDryRunError` are shared by both edges; their descriptions name the recipe mutations the schema has and no longer speak of adding only, so the SDL of a schema with `childRecipes` changes too.
|
|
400
|
+
- The schema gets `replaceEntity(entityId: ID!, recipeGroupId: String!, recipeKey: String!): [AnyDomainEntity!]!` when any entity type exposes a replace group. It makes the updates the configurator's replace controls make, with the `replaceRecipe` update tag and the component's `keepChildren`, and returns every entity that is new after the change and still exists: the replacement first, then what its recipe created under it, then what transformers added in response.
|
|
401
|
+
- The replacement is a new entity with a new id and the field values of its recipe. `dryRun` tries the replacement on the state the query reads.
|
|
402
|
+
- `replaceEntity` fails with a message that says how to correct the request when the entity does not exist, its type has no such group, the group is not active on the entity, the group has no recipe with that key, or the recipe does not cook to exactly one root entity. Such a recipe is still listed, and its `dryRun` reports `ok: false` with one `uncaught` error.
|
|
403
|
+
- Group ids are unique per entity type within `replaceRecipes`; a child group and a replace group of one type may share an id.
|
|
404
|
+
- `ASSISTANT_UNKNOWN_TYPE`, `ASSISTANT_UNKNOWN_RECIPE_GROUP` and `ASSISTANT_DUPLICATE_RECIPE_GROUP` are also reported for `replaceRecipes` entries and name the section, the component and the mutation of the entry. `ASSISTANT_RESERVED_OPTIONS_EDGE_NAME` also covers an options edge named `replaceRecipes`, and `ASSISTANT_META_TYPE_NAME_CONFLICT` a type that gets `meta` from a replace group.
|
|
405
|
+
- The generated assistant prompt explains how to replace entities when the schema has `replaceEntity`, so a deployment must be prepared again for the model to learn about it.
|
|
406
|
+
- The runtime manifest gains `replaceRecipes`. The domain exports `findAssistantRecipeGroups` and `readAssistantRecipeGroup` take the recipe kind (`'child'` or `'replace'`, the new `AssistantRecipeKind` type), and `AssistantRecipeGroupSnapshot` holds the component's `collection` or `keepChildren` under `target`.
|
|
407
|
+
|
|
408
|
+
feat: add `phoenix feedback <message>` and the `phoenix-feedback` agent skill (CON-2964)
|
|
409
|
+
|
|
410
|
+
`phoenix feedback "<message>" --type bug|idea|other` sends feedback about the SDK, the CLI, the documentation or Studio to the Phoenix team. It uses the `phoenix login` sign-in, so the server knows the workspace, user and API token it comes from. `--contact` leaves a contact for a reply, `--agent` names the coding agent that wrote the message, and `--projectSlug` (or `PROJECT_SLUG`, or `project` of the spec) names the workspace project it is about. Besides the message, only the SDK version, operating system and Node.js version are sent. It needs a server with API 1.40.0 or newer.
|
|
411
|
+
|
|
412
|
+
Sync, dev and build now also write the `phoenix-feedback` skill to `.claude/skills/` and `.agents/skills/`. It tells coding agents when to draft feedback, to show the draft and send it only after the user approves it, how to write it, and never to include secrets, customer data or the project's source.
|
|
413
|
+
|
|
414
|
+
feat: the options of a writable field in the experimental assistant GraphQL schema can be dry-run, and option and recipe dry-runs share one `DryRun` result with `changedEntities` and `deletedEntityIds`
|
|
415
|
+
|
|
416
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. The dry-run result of recipes changes with it; see the last point.
|
|
417
|
+
|
|
418
|
+
```yaml
|
|
419
|
+
assistant:
|
|
420
|
+
writable:
|
|
421
|
+
KitchenRange:
|
|
422
|
+
color:
|
|
423
|
+
options:
|
|
424
|
+
componentType: materialSwatch
|
|
425
|
+
instanceName: color
|
|
426
|
+
allowDryRuns: false
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
- `FieldOption` gains `dryRun: DryRun`. It tries the single write `modify<Type>` would make for the option's `value` on the state the entity was read on, the way a recipe dry-run tries a recipe, and changes nothing. It does not repeat the options check of `modify<Type>`, but it checks the entity against its shape first: a value that does not fit the field reports `ok: false` with one `uncaught` error.
|
|
430
|
+
- `allowDryRuns: false` inside `options` turns the dry-run of that field's options off: `dryRun` is `null`, and the generated description of the edge says its options cannot be dry-run. The default is `true`. Each selected dry-run is a full emission and nothing limits their number.
|
|
431
|
+
- `DryRun` is `{ ok: Boolean!, root, changedEntities: [AnyDomainEntity!]!, deletedEntityIds: [ID!]!, errors: [DryRunError!]! }`. `changedEntities` lists every entity the change would add or modify, read from the resulting state and in its order; an entity whose id the configuration does not have yet is new. `deletedEntityIds` lists the ids the change would remove. Both are empty when `ok` is false. Entities are compared by instance, not by value, so an entity that is written to is listed even when it gets the values it already has; the option that holds a field's current value reports its entity as changed.
|
|
432
|
+
- The generated assistant prompt describes the dry-run result by its new fields and explains the option dry-run and its cost, so a deployment must be prepared again for the model to learn about it.
|
|
433
|
+
- `DryRun` and `DryRunError` are now reserved names for entity types and aliases of an application with an `assistant` section; `RecipeDryRun` and `RecipeDryRunError` no longer are.
|
|
434
|
+
- **Queries and instructions written against the recipe dry-run of this unreleased feature must be migrated:** the types `RecipeDryRun` and `RecipeDryRunError` are renamed to `DryRun` and `DryRunError`, and `newEntities` is removed. Select `changedEntities` instead; it also holds the entities the change would modify, is ordered as the resulting state rather than with the recipe's own entities first, and a new entity is told by its id. `addChild`, `replaceEntity` and `modify<Type>` return what they did before.
|
|
435
|
+
|
|
436
|
+
chore: follow-ups to the experimental assistant schema: the hint `stable` is named `constantDuringSession`, `Recipe.entityType` is gone, and descriptions are more precise (CON-2981)
|
|
437
|
+
|
|
438
|
+
**Experimental and unstable:** this is part of the conversational configurator work and can still change without a deprecation period. Neither the hint nor the field has shipped in a release, so there is no deprecation period: write `- constantDuringSession` where a manifest had `- stable` under `hints` (the old name is a parse error), and remove `entityType` from queries that select it on a `Recipe`; the type of what a recipe creates is the `type` of the entities under `dryRun { newEntities }`.
|
|
439
|
+
|
|
440
|
+
- The generated hint sentence is unchanged ("They do not change during the session.").
|
|
441
|
+
- An entity type that inherits a string enum field from an alias whose members declare different enums lists only its own values in the field description; the alias interface keeps listing all of them. The generated SDL changes for every such field.
|
|
442
|
+
- A field that references a common type which is itself a nullable reference to another common type inherits the inner description, as a nullable reference written on the field already did.
|
|
443
|
+
- The `RecipeDryRun` descriptions of `ok` and `newEntities` say that the result holds for the configuration the query reads and that ids may differ in the real mutation.
|
|
444
|
+
- The generated prompt says that an `apply` result with `committed: false` and no `message` means the operations changed nothing.
|
|
445
|
+
- `deleteEntity`, `modify<Type>`, `addChild` and `replaceEntity` parse their arguments with zod schemas. A malformed entity id is now rejected for its format before the entity is looked up; the "does not exist" messages are unchanged.
|
|
446
|
+
- The SDL or the generated prompt changes with each of these, so a deployment must be prepared again for the model to learn about them.
|
|
447
|
+
|
|
448
|
+
fix: a fresh SDK-builder project installs, previews and deploys with pnpm 11
|
|
449
|
+
|
|
450
|
+
- `phoenix dev` hands the server it prepared the development deployment on to the preview as `SERVER_URL`, so the preview of a signed-in project without a `.env` no longer calls `http://localhost:3000`.
|
|
451
|
+
- `phoenix init` writes a `pnpm-workspace.yaml` that decides the build scripts of the SDK's dependencies (`@swc/core`, `esbuild`, `es5-ext`) and exempts `@salsita-ai/*` from pnpm's minimum release age, so the first `pnpm install` no longer fails with `ERR_PNPM_IGNORED_BUILDS`. `phoenix sync` and `phoenix deploy` add the same entries to a standalone project that lacks them and keep the values the project set. A project inside a pnpm monorepo is left to its workspace root.
|
|
452
|
+
- `phoenix deploy` packs `pnpm-workspace.yaml`, so the server-side install follows the project's own pnpm settings and no longer rejects a same-day SDK build with `ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION`.
|
|
453
|
+
|
|
454
|
+
feat: `phoenix dev` prints the preview address and `phoenix deploy` links to the build in Studio
|
|
455
|
+
|
|
456
|
+
`phoenix dev` prints `Preview: <url>` once the dev server is up. `phoenix login` stores the Studio URL of the sign-in, and `phoenix deploy` prints `Follow the build in Studio: <url>` when the build starts; sign in again to get the link for a sign-in stored by an older CLI. The generated `phoenix` agent skill tells the coding agent to open the preview in the user's browser before its first change and to open the build page after a deploy starts.
|
|
457
|
+
|
|
458
|
+
chore: regenerate the API types for the build log token and the build log write endpoint
|
|
459
|
+
|
|
460
|
+
feat: `phoenix dev` takes `--skipUpload` (env: `SKIP_UPLOAD`)
|
|
461
|
+
|
|
462
|
+
When `phoenix dev` prepares its development deployment at startup, it uploads the project's assets to S3. With `--skipUpload` the deployment is prepared without the upload, like `phoenix deployment:prepare-dev --skipUpload`. Defaults to `false`, preserving existing behavior. The flag also reaches the legacy pipeline's start-up prepare. A project whose `.env` already sets `SKIP_UPLOAD=true` for the `deployment:prepare-*` commands now skips the upload in `phoenix dev` too; remove the variable or pass `--skipUpload=false` to keep uploading.
|
|
463
|
+
|
|
464
|
+
feat: the `phoenix-feedback` agent skill asks for a rating after a live deploy
|
|
465
|
+
|
|
466
|
+
When `phoenix deploy` reports the configurator live, the coding agent asks the user once per conversation to rate building with Phoenix from 0 to 10 (10 is the best), with an optional comment. It says that the answer goes to the Phoenix team, shows the message of three labelled lines (`Rating`, `Comment`, `Stage`) and sends it with `phoenix feedback` once the user approves; it sends nothing when the user skips the question. Run `phoenix sync` to get the updated skill.
|
|
467
|
+
|
|
468
|
+
chore: the `phoenix` agent skill checks for the latest canary build during the internal beta
|
|
469
|
+
|
|
470
|
+
In a project generated by a canary build of the SDK from npm (not a workspace link, a stable release or a PR beta), once per conversation the coding agent compares the installed `@salsita-ai/sdk` with the newest `canary` build and, when they differ, tells the user, updates the project and runs `phoenix sync`. It is temporary and goes away at launch.
|
|
471
|
+
|
|
472
|
+
feat: preserve live camera distance, zoom, and rotation across matching lock groups
|
|
473
|
+
|
|
474
|
+
Perspective cameras can opt into distance preservation with `distanceLockGroup`, orthographic cameras can preserve zoom with `zoomLockGroup`, and both camera types can preserve rotation with `rotationLockGroup`. Matching explicit groups carry the live view into the destination camera while respecting its limits; Reset bypasses preservation for that invocation.
|
|
475
|
+
|
|
476
|
+
fix: compile production frontend bundles with React's production implementation
|
|
477
|
+
|
|
478
|
+
Phoenix builds now replace `process.env.NODE_ENV` with `"production"` for configurator, admin, embedded, and editor outputs, removing development-only dependency code and avoiding React development mode in deployed applications.
|
|
479
|
+
|
|
480
|
+
fix: show children passed to `Recipe.Item` through `slotProps.image` again
|
|
481
|
+
|
|
482
|
+
Since the overridable icons were added, `Recipe.Item` passed its placeholder icon as the children of the image slot, which replaced any children given through `slotProps.image`. A custom `image` slot that renders its children, for example layered thumbnails, showed only the placeholder. Children from `slotProps.image` now win over `imagePlaceholderIcon`, as they do in `Recipe.GroupTitle` and `Summary.Item`.
|
|
483
|
+
|
|
5
484
|
## [0.48.0]
|
|
6
485
|
|
|
7
486
|
feat: expose the domain-aware localizable resolver to webhook handlers and use its `$lang` fallback in task builders
|
package/dist/docs/index.json
CHANGED
|
@@ -61,7 +61,7 @@ interface ConfigurationSchema {
|
|
|
61
61
|
|
|
62
62
|
When you save work, Phoenix validates the entity data against this schema using AJV (Another JSON Schema Validator). If the data doesn't match the schema rules, the save fails. This prevents corrupt or mismatched data from entering your system.
|
|
63
63
|
|
|
64
|
-
Phoenix also checks the schema hash during deployment registration. If you try to deploy the same model version with different schema content, registration fails
|
|
64
|
+
Phoenix also checks the schema hash during deployment registration. If you try to deploy the same model version with different schema content, registration fails with a message that names the model version and the environment: a deployed model version cannot change, because saved configurations depend on it. Keep the deployed model as that version, put the change in a new model version and add a [migration](?path=/docs/phoenix-migrations--docs). Development and testing environments replace the deployed schema instead, as does the `--overrideSchema` flag, which is for development and testing only.
|
|
65
65
|
|
|
66
66
|
### Revisions: Your Version History
|
|
67
67
|
|
package/dist/phoenix/cli.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{bool as e,str as o}from"envalid";import{readFileSync as t}from"fs";import{readFile as i}from"fs/promises";import{arch as r,platform as n}from"os";import{dirname as a,join as s,resolve as l}from"path";import{P as p,match as d}from"ts-pattern";import{fileURLToPath as c}from"url";import u from"yargs";import{hideBin as E}from"yargs/helpers";import{z as v}from"zod";import{createEnvReader as f}from"@salsita-ai/common/env";import{DEFAULT_PROJECT_ALIAS as g,FeedbackType as m}from"@salsita-ai/contracts/domain";import{phoenixCli as y}from"#package/legacy";import{readSpecProject as R}from"./application-spec/read-spec-project.js";import{prepareDevelopmentEnvironment as h,prepareLocalEnvironment as S,runActivateDeploymentCommand as _,runBuildCommand as U,runChangelogCommand as O,runCheckCommand as P,runCleanCommand as b,runCleanupEnvironmentCommand as L,runCreateEnvironmentCommand as I,runDataCommand as N,runDeployCommand as T,runDeploymentBuildEnvsCommand as w,runDevCommand as k,runDocsCommand as D,runExtractSourcesCommand as j,runFeedbackCommand as G,runFinishDeploymentCommand as C,runFixCommand as A,runInitCommand as K,runInspectAssetsCommand as V,runLinkCommand as M,runListEnvironmentsCommand as J,runLoginCommand as B,runLogoutCommand as F,runPrepareCiCommand as Y,runPrepareDeploymentCommand as q,runRegisterDeploymentCommand as x,runSceneCommand as H,runScreenshotCommand as W,runStateCommand as X,runStoryCommand as $,runSyncCommand as z,runUnlinkCommand as Q}from"./commands/index.js";import{DEFAULT_PHOENIX_REGION as Z,PHOENIX_REGIONS as ee,PUBLIC_PHOENIX_REGIONS as eo,getRegionServerUrl as et,getRegionStudioUrl as ei,resolveSignedInDevelopmentEnvironment as er}from"./services/index.js";import{getIsEditorEnabled as en}from"./utils/get-is-editor-enabled.js";import{createLogger as ea}from"./utils/index.js";let es=(e,o,t)=>{e?.info(`Using ${o?"application-spec":"domain-spec"} flow (${t})`)},el=(e,o,t)=>{let i=t?.logger??ea();if(void 0!==e)return i.detail(`Application spec: ${e} (from CLI flag)`),es(t?.logger,e,"from CLI flag"),e;let r=process.env.USE_APPLICATION_SPEC;if(void 0!==r){let e="true"===r||"1"===r;return i.detail(`Application spec: ${e} (from environment variable)`),es(t?.logger,e,"from environment variable"),e}return void 0!==o?(i.detail(`Application spec: ${o} (from package.json phoenix.applicationFlow)`),es(t?.logger,o,"from package.json phoenix.applicationFlow"),o):(i.detail("Application spec: false (default)"),es(t?.logger,!1,"default"),!1)},ep=t(s(a(c(import.meta.url)),"../../package.json"),"utf-8");if(!ep)throw Error("Project package.json is required, please provide it in the package.json file");let ed=v.object({version:v.string({required_error:"Project version is required, please provide it in the package.json file"}),name:v.string({required_error:"Project name is required, please provide it in the package.json file"})}).parse(JSON.parse(ep)),ec=async()=>{try{let e=await i("./package.json","utf-8"),o=JSON.parse(e);return{name:o.name,applicationFlow:o.phoenix?.applicationFlow}}catch{return{name:void 0,applicationFlow:void 0}}};await u(E(process.argv)).scriptName("phoenix").usage("$0 <command> [options]").command(["build","b"],"Build the project",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, defaults to project name in package.json)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("onlyFrontend",{alias:"F",type:"boolean",description:"Build only frontend bundle"}).option("onlyWebhook",{alias:"W",type:"boolean",description:"Build only webhooks"}).option("force",{alias:"f",type:"boolean",description:"Force full rebuild, skip cache (env: FORCE_REBUILD, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i,r=f({PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),ENABLE_EDITOR:e({default:!0}),DEBUG:e({default:!1}),BUILD_TARGET:o({choices:["frontend","webhook","both"],default:"both"}),FORCE_REBUILD:e({default:!1})})(),n=ea({debug:t.debug??r.DEBUG}),a=en(t.editor,r.ENABLE_EDITOR),s=t.force??r.FORCE_REBUILD,l=await ec(),p=el(t.applicationSpec,l.applicationFlow,{logger:n}),d=t.projectSlug??r.PROJECT_SLUG??l.name,c=t.environmentSlug??r.ENVIRONMENT_SLUG??l.name;if(!d)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!c)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");let u=t.onlyFrontend,E=t.onlyWebhook;if(u&&E)throw Error("Cannot use --onlyFrontend and --onlyWebhook flags together, they are mutually exclusive");i=u?"frontend":E?"webhook":r.BUILD_TARGET,await (p?U:y.runBuildCommand)({sdkVersion:ed.version,projectSlug:d,environmentSlug:c,cliUseWorkspace:t.workspace,enableEditor:a,buildTarget:i,force:s},{logger:n})}).command(["clean","cl"],"Clean build artifacts, reinstall dependencies, and rebuild specs",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, defaults to project name in package.json)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i=f({PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),ENABLE_EDITOR:e({default:!0}),DEBUG:e({default:!1})})(),r=ea({debug:t.debug??i.DEBUG}),n=en(t.editor,i.ENABLE_EDITOR),a=await ec(),s=el(t.applicationSpec,a.applicationFlow,{logger:r}),l=t.projectSlug??i.PROJECT_SLUG??a.name,p=t.environmentSlug??i.ENVIRONMENT_SLUG??a.name;if(!l)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!p)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");await (s?b:y.runCleanCommand)({sdkVersion:ed.version,projectSlug:l,environmentSlug:p,cliUseWorkspace:t.workspace,enableEditor:n},{logger:r})}).command(["dev","d"],"Start development mode (Vite, spec watching, and the webhook tunnel)",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("editorWsPort",{alias:"ep",type:"string",description:"Port for the editor WebSocket server (defaults to EDITOR_WS_PORT env or 3020)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG; required in server mode unless --local; offline modes default to the package name)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG; required in server mode unless --local; offline modes default to the package name)"}).option("withLingui",{alias:"wl",type:"boolean",description:"Enable or disable i18n messages extraction and compilation (defaults to WITH_LINGUI env or false)"}).option("persistence",{alias:"P",type:"string",description:"Persistence mode for the configurator (defaults to PERSISTENCE_MODE env or server)",choices:["memory","local-storage","server"]}).option("force",{alias:"f",type:"boolean",description:"Force full rebuild, skip cache (env: FORCE_REBUILD, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}).option("tui",{alias:"T",type:"boolean",description:"Render logs in a terminal UI with per-stream pages and split view; requires an interactive terminal (env: PHOENIX_TUI, default: false)"}).option("local",{type:"boolean",description:"Use local-mode development environment defaults (env: PHOENIX_LOCAL, default: false)"}).option("region",{choices:eo,description:`Phoenix region of the workspace you are signed in to, used when .env has no SERVER_URL (default: ${Z})`}).option("serverUrl",{type:"string",description:"URL of the Phoenix server, instead of SERVER_URL or the region default"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket when the development deployment is prepared (env: SKIP_UPLOAD, default: false)"}),async t=>{let i=f({ENABLE_EDITOR:e({default:!0}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),EDITOR_WS_PORT:o({default:"3020"}),DEBUG:e({default:!1}),WITH_LINGUI:e({default:!1}),PERSISTENCE_MODE:o({default:void 0}),FORCE_REBUILD:e({default:!1}),PHOENIX_TUI:e({default:!1}),SERVER_URL:o({default:void 0}),DEVELOPMENT_KEY:o({default:void 0}),PHOENIX_LOCAL:e({default:!1}),PHOENIX_REGION:o({choices:ee,default:void 0}),SKIP_UPLOAD:e({default:!1})})(),r=t.debug??i.DEBUG,n=ea({debug:r}),a=en(t.editor,i.ENABLE_EDITOR),s=t.editorWsPort??i.EDITOR_WS_PORT,l=t.force??i.FORCE_REBUILD,c=t.withLingui??i.WITH_LINGUI,u=t.persistence??i.PERSISTENCE_MODE,E=!u||"server"===u,v=t.tui??i.PHOENIX_TUI,m=await ec(),h=el(t.applicationSpec,m.applicationFlow,{logger:n}),S=t.local??i.PHOENIX_LOCAL,_=t.projectSlug??i.PROJECT_SLUG,U=E&&h&&!S&&!_?.length?await R(process.cwd()):void 0,{serverUrl:O,developmentKey:P,apiToken:b,projectSlug:L,environmentSlug:I}=await d({useServer:E,local:S,serverUrl:t.serverUrl??i.SERVER_URL,developmentKey:i.DEVELOPMENT_KEY,projectSlug:_?.length?_:U,environmentSlug:t.environmentSlug??i.ENVIRONMENT_SLUG}).with({useServer:!1},async({serverUrl:e,developmentKey:o,projectSlug:t,environmentSlug:i})=>({serverUrl:e,developmentKey:o,apiToken:void 0,projectSlug:t??m.name,environmentSlug:i??m.name})).with({local:!0},async({serverUrl:e,developmentKey:o,projectSlug:t,environmentSlug:i})=>({serverUrl:e?.length?e:"http://localhost:3000",developmentKey:o?.length?o:"dev_local-dev-key-12345",apiToken:void 0,projectSlug:t?.length?t:"local",environmentSlug:i?.length?i:"dev-local"})).with({developmentKey:p.string.minLength(1)},()=>U===g,()=>{throw Error(`phoenix dev has a DEVELOPMENT_KEY, but the spec says \`project: ${g}\`, which only a signed-in workspace can resolve. Set PROJECT_SLUG in .env by pasting the .env block of your Studio development environment, or name the project's slug in the spec.`)}).with({serverUrl:p.string.minLength(1),developmentKey:p.string.minLength(1),projectSlug:p.string.minLength(1),environmentSlug:p.string.minLength(1)},async({serverUrl:e,developmentKey:o,projectSlug:t,environmentSlug:i})=>({serverUrl:e,developmentKey:o,apiToken:void 0,projectSlug:t,environmentSlug:i})).with({developmentKey:p.union(void 0,""),projectSlug:p.string.minLength(1)},()=>h,async({serverUrl:e,projectSlug:o})=>{let r=e?.length?e:et(t.region??i.PHOENIX_REGION??Z),a=await er({serverUrl:r,project:o});return n.info(`Developing in ${a.projectSlug}/${a.environmentSlug} of workspace "${a.workspaceName}" on ${r}`),{serverUrl:r,developmentKey:void 0,apiToken:a.apiToken,projectSlug:a.projectSlug,environmentSlug:a.environmentSlug}}).with({developmentKey:p.union(void 0,""),projectSlug:p.union(void 0,"")},()=>h,()=>{throw Error(`phoenix dev does not know which project to develop in. Add \`project: ${g}\` to spec/application.yaml to use the default project of the workspace you are signed in to, or paste the .env block of a Studio development environment into .env. Pass --local to use the local-mode server.`)}).otherwise(()=>{throw Error("phoenix dev needs SERVER_URL, PROJECT_SLUG, ENVIRONMENT_SLUG, and DEVELOPMENT_KEY in .env. Create a development environment for your project in Studio and paste its .env block, or pass --local to use the local-mode server.")});if(!L)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!I)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");let N={sdkVersion:ed.version,projectSlug:L,environmentSlug:I,cliUseWorkspace:t.workspace,enableEditor:a,editorWsPort:s,debug:r,withLingui:c,persistenceMode:u,force:l,tui:v,serverUrl:O,skipUpload:t.skipUpload??i.SKIP_UPLOAD};if(h){let e=P?{developmentKey:P}:b?{apiToken:b}:void 0;await k({...N,credential:e},{logger:n})}else await y.runDevCommand({...N,developmentKey:P},{logger:n})}).command(["link","l"],"Link local SDK for development",e=>e.option("sdk-path",{type:"string",description:"Path to the SDK repository",default:"../configurator-phoenix"}),async e=>{let o=ea();await M({sdkPath:e.sdkPath},{logger:o})}).command(["unlink","ul"],"Remove local SDK linking",()=>{},async()=>{let e=ea();await Q({logger:e})}).command(["sync","s"],"Sync project files and compile specs without building vite/webhooks",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, defaults to project name in package.json)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("force",{alias:"f",type:"boolean",description:"Force full rebuild, skip cache (env: FORCE_REBUILD, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}).option("analyze",{alias:"a",type:"boolean",description:"Print pipeline metrics flamegraph after build (requires --applicationSpec)"}),async t=>{let i=f({ENABLE_EDITOR:e({default:!0}),DEBUG:e({default:!1}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),FORCE_REBUILD:e({default:!1})})(),r=ea({debug:t.debug??i.DEBUG}),n=en(t.editor,i.ENABLE_EDITOR),a=t.force??i.FORCE_REBUILD,s=await ec(),l=el(t.applicationSpec,s.applicationFlow,{logger:r}),p=t.projectSlug??i.PROJECT_SLUG??s.name,d=t.environmentSlug??i.ENVIRONMENT_SLUG??s.name;if(!p)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!d)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");await (l?z:y.runSyncCommand)({sdkVersion:ed.version,projectSlug:p,environmentSlug:d,cliUseWorkspace:t.workspace,enableEditor:n,force:a,analyze:t.analyze},{logger:r})}).command("init [folder]","Start a Phoenix project: write package.json when the folder has none, a starter spec and pnpm-workspace.yaml with the pnpm settings the install needs. Continue with phoenix sync, phoenix login and phoenix dev",e=>e.positional("folder",{type:"string",description:"Folder to create the project in (default: the working directory)"}).option("scaffold",{type:"boolean",hidden:!0,description:"Accepted for compatibility and ignored: init always only writes the starter project"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging"}),async e=>{let o=ea({debug:e.debug??!1});try{await K({sdkVersion:ed.version,projectRoot:void 0===e.folder?void 0:l(e.folder)},{logger:o})}catch(e){o.error(e instanceof Error?e.message:String(e)),process.exit(1)}}).command(["story","st"],"Storybook example utilities",e=>$(e),()=>{}).command(["check","c"],"Run checks (TypeScript, ESLint, Prettier). Runs all by default, or only selected when any of --types/--lint/--format is passed.",e=>e.option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("types",{type:"boolean",description:"Run TypeScript check"}).option("lint",{type:"boolean",description:"Run ESLint check"}).option("format",{type:"boolean",description:"Run Prettier check"}),async o=>{let t=f({DEBUG:e({default:!1})})(),i=ea({debug:o.debug??t.DEBUG});await P({types:o.types,lint:o.lint,format:o.format},{logger:i})}).command(["fix","f"],"Fix linting and formatting issues. Runs all by default, or only selected when any of --lint/--format is passed.",e=>e.option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("lint",{type:"boolean",description:"Run ESLint fix"}).option("format",{type:"boolean",description:"Run Prettier fix"}),async o=>{let t=f({DEBUG:e({default:!1})})(),i=ea({debug:o.debug??t.DEBUG});await A({lint:o.lint,format:o.format},{logger:i})}).command(["changelog","clog"],"Display SDK changelog",e=>e.option("ver",{type:"string",description:"Show specific version (e.g., 0.36.0)"}).option("all",{alias:"a",type:"boolean",description:"Show all versions (warning: large output)"}).option("page",{alias:"p",type:"number",description:"Page number (3 versions per page)"}).option("list",{alias:"l",type:"boolean",description:"List version numbers only"}),async e=>{let o=ea();await O({version:e.ver,all:e.all,page:e.page,list:e.list},{logger:o})}).command(["inspect:assets","ia"],"Inspect 3D assets (glTF/GLB) — nodes, transforms, bounds, origin, materials, morph targets, animations",e=>e.option("file",{alias:"f",type:"string",description:"Path to specific glTF/GLB file"}).option("objects",{alias:"O",type:"boolean",description:"Show only object/node names"}).option("materials",{alias:"m",type:"boolean",description:"Show only material names"}).option("morphs",{alias:"M",type:"boolean",description:"Show only morph target names"}).option("animations",{alias:"a",type:"boolean",description:"Show only animation names"}).option("json",{alias:"j",type:"boolean",description:"Output as JSON"}),async e=>{let o=ea();await V({file:e.file,objects:e.objects,materials:e.materials,morphs:e.morphs,animations:e.animations,json:e.json},{logger:o})}).command(["screenshot","ss"],"Capture a screenshot of the configurator canvas",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (defaults to SCREENSHOT_OUTPUT env or ./screenshot.png)"}).option("width",{alias:"w",type:"number",description:"Width in pixels (default: 1920)"}).option("height",{alias:"H",type:"number",description:"Height in pixels (default: 1080)"}).option("format",{alias:"f",type:"string",description:"Image format: PNG or JPEG (default: PNG)"}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"}),SCREENSHOT_OUTPUT:o({default:"./screenshot.png"})})(),i=ea();await W({port:e.port??t.EDITOR_WS_PORT,output:e.output??t.SCREENSHOT_OUTPUT,width:e.width,height:e.height,format:e.format},{logger:i})}).command(["inspect:state","is"],"Get the current editor state as JSON (entities, navigation, validation)",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (prints to stdout if omitted)"}).option("sections",{alias:"s",type:"string",description:"Comma-separated sections: entities,navigation,validation (default: all)"}).option("pretty",{alias:"P",type:"boolean",description:"Pretty-print JSON output (default: true)",default:!0}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"})})(),i=ea();await X({port:e.port??t.EDITOR_WS_PORT,output:e.output,sections:e.sections,pretty:e.pretty},{logger:i})}).command(["inspect:data","id"],"Get the current editor context data as JSON (published, frontend, backend, session, access, web component)",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (prints to stdout if omitted)"}).option("pretty",{alias:"P",type:"boolean",description:"Pretty-print JSON output (default: true)",default:!0}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"})})(),i=ea();await N({port:e.port??t.EDITOR_WS_PORT,output:e.output,pretty:e.pretty},{logger:i})}).command(["inspect:scene","isc"],"Get the current Three.js scene graph as JSON",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (prints to stdout if omitted)"}).option("pretty",{alias:"P",type:"boolean",description:"Pretty-print JSON output (default: true)",default:!0}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"})})(),i=ea();await H({port:e.port??t.EDITOR_WS_PORT,output:e.output,pretty:e.pretty},{logger:i})}).command(["docs","doc"],"Browse SDK documentation",e=>e.option("read",{alias:"r",type:"string",description:"Read a specific doc file (e.g., docs/phoenix/admin.mdx)"}).option("category",{alias:"c",type:"string",description:"Show all docs in a category"}).option("list",{alias:"l",type:"boolean",description:"List all docs with paths and titles"}).option("search",{alias:"s",type:"string",description:"Search doc content for a term (case-insensitive)"}).option("dir",{type:"boolean",description:"Print the absolute path of the docs directory"}),async e=>{let o=ea();await D({read:e.read,category:e.category,list:e.list,search:e.search,dir:e.dir},{logger:o})}).command(["prepare:ci","pci","ci"],"Prepare CI environment by copying .env.ci to .env",()=>{},async()=>{let e=ea();await Y({logger:e})}).command({command:"environment:create",aliases:["env:create"],describe:"Create a new deployment environment",deprecated:"Switch to a new workflow and use deployment:prepare instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("name",{alias:"n",type:"string",description:"Name of the environment to be created"}).option("slug",{type:"string",description:"Slug of the customer to be created"}).option("uuid",{alias:"u",type:"string",description:"UUID of the environment to be created"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment UUID to",default:".env.generated"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await I({server:o.server,apiKey:o.apiKey,name:o.name,slug:o.slug,uuid:o.uuid,outFile:o.outFile,debug:i},{logger:r})}}).command({command:"environment:list",aliases:["env:list"],describe:"List all deployment environments",deprecated:"Switch to a new workflow and use the prepare environment flow instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await J({server:o.server,apiKey:o.apiKey,debug:i},{logger:r})}}).command(["environment:cleanup","env:cleanup","ec"],"Cleanup a transient deployment environment",e=>e.option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key (env: PROJECT_KEY, required)"}).option("projectSlug",{alias:"p",type:"string",description:"Project slug (env: PROJECT_SLUG, required)"}).option("environmentSlug",{alias:"e",type:"string",description:"Environment slug (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write cleanup result env variables to (env: OUT_FILE, default: .env.generated)",default:".env.generated"}),async t=>{let i=f({DEBUG:e({default:!1}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),SERVER_URL:o({default:void 0}),OUT_FILE:o({default:void 0})})(),r=t.debug??i.DEBUG,n=t.apiKey??i.ENVIRONMENT_API_KEY,a=t.projectKey??i.PROJECT_KEY,s=t.projectSlug??i.PROJECT_SLUG,l=await ec(),p=t.environmentSlug??i.ENVIRONMENT_SLUG??l.name,d=t.serverUrl??i.SERVER_URL,c=t.outFile??i.OUT_FILE??".env.generated";if(!n)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");if(!a)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");if(!s)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");if(!p)throw Error("Environment slug is required, provide via --environmentSlug or -e, or set ENVIRONMENT_SLUG environment variable");if(!d)throw Error("Server URL is required, provide via --serverUrl or -su, or set SERVER_URL environment variable");let u=ea({debug:r});await L({server:d,apiKey:n,projectKey:a,projectSlug:s,environmentSlug:p,debug:r,outFile:c},{logger:u})}).command(["login <customer>"],"Sign the CLI in to an SDK-builder workspace; you approve it in Studio",e=>e.positional("customer",{type:"string",demandOption:!0,description:"Slug of the SDK-builder workspace (customer)"}).option("region",{choices:eo,description:`Phoenix region the workspace lives in (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}).option("studioUrl",{type:"string",description:"URL of Studio for the workspace, instead of the region default"}).option("open",{type:"boolean",default:!0,description:"Open the approval page in the default browser (--no-open to only print the link)"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0})})(),i=e.region??t.PHOENIX_REGION??Z;await B({customer:e.customer,serverUrl:e.serverUrl??et(i),studioUrl:e.studioUrl??ei(i,e.customer),open:e.open},{logger:ea()})}).command(["deploy"],"Upload this SDK-builder project and follow its build until it is live",e=>e.option("region",{choices:eo,description:`Phoenix region the workspace lives in (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}).option("wait",{type:"boolean",default:!0,description:"Follow the build until it finishes (--no-wait to return once it starts)"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0})})(),i=e.region??t.PHOENIX_REGION??Z;await T({serverUrl:e.serverUrl??et(i),sdkVersion:ed.version,wait:e.wait,projectDir:process.cwd(),pollIntervalMs:3e3},{logger:ea()})}).command(["logout"],"Revoke the API token of an SDK-builder workspace and forget it on this computer",e=>e.option("region",{choices:eo,description:`Phoenix region to sign out of (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0})})(),i=e.region??t.PHOENIX_REGION??Z;await F({serverUrl:e.serverUrl??et(i)},{logger:ea()})}).command(["feedback <message>"],"Send feedback about the SDK, CLI, docs or Studio to the Phoenix team",e=>e.positional("message",{type:"string",demandOption:!0,description:"What you want to tell the Phoenix team"}).option("type",{choices:[m.Bug,m.Idea,m.Other],default:m.Other,description:"What the feedback is about"}).option("contact",{type:"string",description:"How to reach you, when you want a reply"}).option("agent",{type:"string",description:"Name of the coding agent that writes the feedback"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the workspace project the feedback is about (env: PROJECT_SLUG, defaults to `project` of the application spec)"}).option("region",{choices:eo,description:`Phoenix region the workspace lives in (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0}),PROJECT_SLUG:o({default:void 0})})(),i=ea(),a=e.region??t.PHOENIX_REGION??Z,s=e.projectSlug??t.PROJECT_SLUG,l=s?.length?s:await R(process.cwd()).catch(()=>{i.warn("The feedback is sent without a project, because `project` could not be read from the application spec. Pass --projectSlug to name the project.")});await G({serverUrl:e.serverUrl??et(a),message:e.message,type:e.type,contact:e.contact,agent:e.agent,project:l,sdkVersion:ed.version,clientInfo:`${n()} ${r()}, Node.js ${process.versions.node}`},{logger:i})}).command({command:"deployment:register",aliases:["dep:register","dr"],describe:"Register deployment configuration",deprecated:"Switch to a new workflow and use deployment:prepare and deployment:finish instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the deployment ID to",default:".env.generated"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip uploading assets",default:!1}).option("activate",{alias:"a",type:"boolean",description:"Activate deployment after registration",default:!0}).option("overrideSchema",{alias:"os",type:"boolean",description:"Override existing schema",default:!1}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await x({server:o.server,apiKey:o.apiKey,outFile:o.outFile,skipUpload:o.skipUpload,activate:o.activate,overrideSchema:o.overrideSchema,debug:i},{logger:r})}}).command({command:"deployment:activate",aliases:["dep:activate","da"],describe:"Activate a deployment",deprecated:"Switch to a new workflow and use deployment:prepare and deployment:finish instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("uuid",{alias:"u",type:"string",description:"UUID of the deployment to activate"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await _({server:o.server,apiKey:o.apiKey,uuid:o.uuid,debug:i},{logger:r})}}).command(["deployment:prepare","dep:prepare","dp"],"Prepare environment and register deployment (env: ENVIRONMENT_API_KEY, PROJECT_KEY, PROJECT_SLUG, PROJECT_REF, PROJECT_SHA, ENVIRONMENT_SLUG, OUT_FILE, SKIP_UPLOAD, OVERRIDE_SCHEMA, SERVER_URL)",e=>e.option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (will be created if it does not exist) (env: ENVIRONMENT_SLUG, required)"}).option("projectRef",{type:"string",description:"Source branch, tag, or ref recorded as deployment provenance (env: PROJECT_REF)"}).option("projectSha",{type:"string",description:"Full commit SHA of the checked-out project revision (env: PROJECT_SHA)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("overrideSchema",{alias:"os",type:"boolean",description:"Allows to override deployment schemas (env: OVERRIDE_SCHEMA, default: false)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("uploadLinksFile",{alias:"ulf",type:"string",description:"File to write upload links to (used by finish command) (default: .upload-links.json)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, required)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),PROJECT_REF:o({default:void 0}),PROJECT_SHA:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),OUT_FILE:o({default:".env.generated"}),SKIP_UPLOAD:e({default:!1}),OVERRIDE_SCHEMA:e({default:!1}),SERVER_URL:o({default:void 0})})(),r=t.debug??i.DEBUG,n=t.apiKey??i.ENVIRONMENT_API_KEY;if(!n)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let a=t.projectKey??i.PROJECT_KEY;if(!a)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let s=t.projectSlug??i.PROJECT_SLUG;if(!s)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");let l=t.environmentSlug??i.ENVIRONMENT_SLUG;if(!l)throw Error("Environment slug is required, provide via --environmentSlug or -e, or set ENVIRONMENT_SLUG environment variable");let p=t.serverUrl??i.SERVER_URL;if(!p)throw Error("Server URL is required, provide via --serverUrl or -s, or set SERVER_URL environment variable");let d=await ec(),c=ea({debug:r}),u=el(t.applicationSpec,d.applicationFlow,{logger:c});await (u?q:y.runPrepareDeploymentCommand)({apiKey:n,projectKey:a,projectSlug:s,projectRef:t.projectRef??i.PROJECT_REF,projectSha:t.projectSha??i.PROJECT_SHA,environmentSlug:l,outFile:t.outFile??i.OUT_FILE,skipUpload:t.skipUpload??i.SKIP_UPLOAD,overrideSchema:t.overrideSchema??i.OVERRIDE_SCHEMA,uploadLinksFile:t.uploadLinksFile??".upload-links.json",serverUrl:p,debug:r,projectVersion:ed.version},{logger:c})}).command(["deployment:prepare-dev","dep:prepare-dev","dpd"],"Prepare a development deployment (env: SERVER_URL, PROJECT_SLUG, ENVIRONMENT_SLUG, DEVELOPMENT_KEY, OUT_FILE, SKIP_UPLOAD, OVERRIDE_SCHEMA)",e=>e.option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required unless --local)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the development environment (env: ENVIRONMENT_SLUG, required unless --local)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("overrideSchema",{alias:"os",type:"boolean",description:"Allows overriding deployment schemas (env: OVERRIDE_SCHEMA, default: true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}).option("local",{type:"boolean",description:"Use local-mode development environment defaults (env: PHOENIX_LOCAL, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),SERVER_URL:o({default:void 0}),DEVELOPMENT_KEY:o({default:void 0}),PHOENIX_LOCAL:e({default:!1}),OUT_FILE:o({default:".env.generated"}),SKIP_UPLOAD:e({default:!1}),OVERRIDE_SCHEMA:e({default:!0})})(),r=t.debug??i.DEBUG,n=ea({debug:r}),a=t.local??i.PHOENIX_LOCAL,s=i.SERVER_URL,l=t.projectSlug??i.PROJECT_SLUG,p=t.environmentSlug??i.ENVIRONMENT_SLUG,d=i.DEVELOPMENT_KEY;if(a&&(s=s?.length?s:"http://localhost:3000",l=l?.length?l:"local",p=p?.length?p:"dev-local",d=d?.length?d:"dev_local-dev-key-12345"),!s||!l||!p||!d)throw Error("phoenix deployment:prepare-dev needs SERVER_URL, PROJECT_SLUG, ENVIRONMENT_SLUG, and DEVELOPMENT_KEY in .env. Create a development environment for your project in Studio and paste its .env block, or pass --local to use the local-mode server.");let c=await ec(),u=el(t.applicationSpec,c.applicationFlow,{logger:n}),E={serverUrl:s,projectSlug:l,environmentSlug:p,outFile:t.outFile??i.OUT_FILE,skipUpload:t.skipUpload??i.SKIP_UPLOAD,overrideSchema:t.overrideSchema??i.OVERRIDE_SCHEMA,debug:r,projectVersion:ed.version};try{u?await h({...E,credential:{developmentKey:d}},{logger:n}):await y.prepareDevelopmentEnvironment({...E,developmentKey:d},{logger:n})}catch(e){n.error(`Development environment preparation failed: ${e instanceof Error?e.message:String(e)}`,e),process.exit(1)}}).command(["deployment:prepare-local","dep:prepare-local","dpl"],"Prepare local environment for development (env: PROJECT_KEY, PROJECT_SLUG, OUT_FILE, SKIP_UPLOAD, OVERRIDE_SCHEMA, SERVER_URL, LAMBDA_URL)",e=>e.option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, default: local)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, default: project name in package.json)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("overrideSchema",{alias:"os",type:"boolean",description:"Allows to override deployment schemas (env: OVERRIDE_SCHEMA, default: true)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, default: http://localhost:3000)"}).option("lambdaUrl",{alias:"l",type:"string",description:"Webhook URL (env: LAMBDA_URL, default: http://localhost:8080)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),ENVIRONMENT_SLUG:o({default:void 0}),PROJECT_KEY:o({default:"project_local-dev-key-12345"}),PROJECT_SLUG:o({default:"local"}),OUT_FILE:o({default:".env.generated"}),SKIP_UPLOAD:e({default:!1}),OVERRIDE_SCHEMA:e({default:!0}),SERVER_URL:o({default:"http://localhost:3000"}),LAMBDA_URL:o({default:"http://localhost:8080"}),ENVIRONMENT_API_KEY:o({default:"local-dev-key-12345"})})(),r=t.debug??i.DEBUG,n=t.projectKey??i.PROJECT_KEY;if(!n)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let a=await ec(),s=t.environmentSlug??i.ENVIRONMENT_SLUG??a.name;if(!s)throw Error("Environment slug is required, provide via --environmentSlug or -e, or set ENVIRONMENT_SLUG environment variable");let l=t.apiKey??i.ENVIRONMENT_API_KEY;if(!l)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let p=ea({debug:r}),d=el(t.applicationSpec,a.applicationFlow,{logger:p});try{await (d?S:y.prepareLocalEnvironment)({apiKey:l,projectKey:n,projectSlug:t.projectSlug??i.PROJECT_SLUG,environmentSlug:s,outFile:t.outFile??i.OUT_FILE,skipUpload:t.skipUpload??i.SKIP_UPLOAD,overrideSchema:t.overrideSchema??i.OVERRIDE_SCHEMA,serverUrl:t.serverUrl??i.SERVER_URL,lambdaUrl:t.lambdaUrl??i.LAMBDA_URL,debug:r,projectVersion:ed.version},{logger:p})}catch(e){p.error(`Local environment preparation failed: ${e instanceof Error?e.message:String(e)}`,e),process.exit(1)}}).command(["deployment:extract-sources <archive> <folder>"],"Validate the source archive of a phoenix deploy and extract its project files into a new folder. Links, special files, unsafe paths and oversized content are refused; entries that are not project files are left out",e=>e.positional("archive",{type:"string",demandOption:!0,description:"The source archive (.tgz)"}).positional("folder",{type:"string",demandOption:!0,description:"New or empty folder to extract the project files into"}),async e=>{let o=ea();try{await j({archivePath:e.archive,targetDir:e.folder},{logger:o})}catch(e){o.error(e instanceof Error?e.message:String(e)),process.exit(1)}}).command(["deployment:build-envs","dep:build-envs","dbe"],"Write the environment variables for the build step of a prepared deployment that builds apart from its deploy (env: DEPLOYMENT_ID, ENVIRONMENT_API_KEY, PROJECT_KEY, PROJECT_SLUG, OUT_FILE, SERVER_URL)",e=>e.option("uuid",{alias:"u",type:"string",description:"Id of the prepared deployment (env: DEPLOYMENT_ID, required)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, required)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),DEPLOYMENT_ID:o({default:void 0}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),OUT_FILE:o({default:".env.generated"}),SERVER_URL:o({default:void 0})})(),r=t.debug??i.DEBUG,n=t.uuid??i.DEPLOYMENT_ID;if(!n)throw Error("Deployment ID is required, provide via --uuid or -u, or set DEPLOYMENT_ID environment variable");let a=t.apiKey??i.ENVIRONMENT_API_KEY;if(!a)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let s=t.projectKey??i.PROJECT_KEY;if(!s)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let l=t.projectSlug??i.PROJECT_SLUG;if(!l)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");let p=t.serverUrl??i.SERVER_URL;if(!p)throw Error("Server URL is required, provide via --serverUrl or -s, or set SERVER_URL environment variable");let d=ea({debug:r});await w({uuid:n,apiKey:a,projectKey:s,projectSlug:l,outFile:t.outFile??i.OUT_FILE,serverUrl:p,debug:r},{logger:d})}).command(["deployment:finish","dep:finish","df"],"Upload files to S3 and activate deployment (env: DEPLOYMENT_ID, ENVIRONMENT_API_KEY, PROJECT_KEY, PROJECT_SLUG, LAMBDA_URL, SERVER_URL, SKIP_UPLOAD)",e=>e.option("uuid",{alias:"u",type:"string",description:"Id of the deployment to be activated (env: DEPLOYMENT_ID, required)"}).option("uploadLinksFile",{alias:"ulf",type:"string",description:"File containing upload links (generated by prepare command) (default: .upload-links.json)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required)"}).option("lambdaUrl",{alias:"l",type:"string",description:"Lambda webhook URL for the deployment (env: LAMBDA_URL, required)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, required)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),DEPLOYMENT_ID:o({default:void 0}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),LAMBDA_URL:o({default:void 0}),SERVER_URL:o({default:void 0}),SKIP_UPLOAD:e({default:!1})})(),r=t.debug??i.DEBUG,n=t.uuid??i.DEPLOYMENT_ID;if(!n)throw Error("Deployment ID is required, provide via --uuid or -u, or set DEPLOYMENT_ID environment variable");let a=t.apiKey??i.ENVIRONMENT_API_KEY;if(!a)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let s=t.projectKey??i.PROJECT_KEY;if(!s)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let l=t.projectSlug??i.PROJECT_SLUG;if(!l)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");let p=t.lambdaUrl??i.LAMBDA_URL;if(!p)throw Error("Lambda URL is required, provide via --lambdaUrl or -l, or set LAMBDA_URL environment variable");let d=t.serverUrl??i.SERVER_URL;if(!d)throw Error("Server URL is required, provide via --serverUrl or -s, or set SERVER_URL environment variable");let c=ea({debug:r});await C({uuid:n,uploadLinksFile:t.uploadLinksFile??".upload-links.json",apiKey:a,projectKey:s,projectSlug:l,lambdaUrl:p,serverUrl:d,skipUpload:t.skipUpload??i.SKIP_UPLOAD,debug:r},{logger:c})}).demandCommand(1,"Please specify a command").help().alias("help","h").version(ed.version).alias("version","v").strict().parse();
|
|
2
|
+
import{bool as e,str as o}from"envalid";import{readFileSync as t}from"fs";import{readFile as i}from"fs/promises";import{arch as r,platform as n}from"os";import{dirname as a,join as s,resolve as l}from"path";import{P as p,match as d}from"ts-pattern";import{fileURLToPath as c}from"url";import u from"yargs";import{hideBin as E}from"yargs/helpers";import{z as v}from"zod";import{createEnvReader as f}from"@salsita-ai/common/env";import{DEFAULT_PROJECT_ALIAS as g,FeedbackType as m}from"@salsita-ai/contracts/domain";import{phoenixCli as y}from"#package/legacy";import{readSpecProject as h}from"./application-spec/read-spec-project.js";import{prepareDevelopmentEnvironment as R,prepareLocalEnvironment as S,runActivateDeploymentCommand as _,runBuildCommand as U,runChangelogCommand as O,runCheckCommand as P,runCleanCommand as b,runCleanupEnvironmentCommand as L,runCreateEnvironmentCommand as I,runDataCommand as N,runDeployCommand as T,runDeploymentBuildEnvsCommand as w,runDevCommand as k,runDocsCommand as D,runExtractSourcesCommand as j,runFeedbackCommand as G,runFinishDeploymentCommand as C,runFixCommand as A,runInitCommand as K,runInspectAssetsCommand as V,runLinkCommand as M,runListEnvironmentsCommand as J,runLoginCommand as B,runLogoutCommand as F,runPrepareCiCommand as Y,runPrepareDeploymentCommand as q,runRegisterDeploymentCommand as x,runSceneCommand as H,runScreenshotCommand as W,runStateCommand as $,runStoryCommand as X,runSyncCommand as z,runUnlinkCommand as Q}from"./commands/index.js";import{DEFAULT_PHOENIX_REGION as Z,PHOENIX_REGIONS as ee,PUBLIC_PHOENIX_REGIONS as eo,getRegionServerUrl as et,getRegionStudioUrl as ei,resolveSignedInDevelopmentEnvironment as er}from"./services/index.js";import{getIsEditorEnabled as en}from"./utils/get-is-editor-enabled.js";import{createLogger as ea}from"./utils/index.js";let es=(e,o,t)=>{e?.info(`Using ${o?"application-spec":"domain-spec"} flow (${t})`)},el=(e,o,t)=>{let i=t?.logger??ea();if(void 0!==e)return i.detail(`Application spec: ${e} (from CLI flag)`),es(t?.logger,e,"from CLI flag"),e;let r=process.env.USE_APPLICATION_SPEC;if(void 0!==r){let e="true"===r||"1"===r;return i.detail(`Application spec: ${e} (from environment variable)`),es(t?.logger,e,"from environment variable"),e}return void 0!==o?(i.detail(`Application spec: ${o} (from package.json phoenix.applicationFlow)`),es(t?.logger,o,"from package.json phoenix.applicationFlow"),o):(i.detail("Application spec: false (default)"),es(t?.logger,!1,"default"),!1)},ep=t(s(a(c(import.meta.url)),"../../package.json"),"utf-8");if(!ep)throw Error("Project package.json is required, please provide it in the package.json file");let ed=v.object({version:v.string({required_error:"Project version is required, please provide it in the package.json file"}),name:v.string({required_error:"Project name is required, please provide it in the package.json file"})}).parse(JSON.parse(ep)),ec=async()=>{try{let e=await i("./package.json","utf-8"),o=JSON.parse(e);return{name:o.name,applicationFlow:o.phoenix?.applicationFlow}}catch{return{name:void 0,applicationFlow:void 0}}},eu=e=>{if(!(e instanceof Error))return String(e);let o=(e=>{if(!(e instanceof Error))return;if(e.message.length>0)return e.message;let o=(e instanceof AggregateError?e.errors:[]).find(e=>e instanceof Error&&e.message.length>0);return o instanceof Error?o.message:"code"in e&&"string"==typeof e.code?e.code:void 0})(e.cause);return void 0===o?e.message:`${e.message}: ${o}`};await u(E(process.argv)).scriptName("phoenix").usage("$0 <command> [options]").command(["build","b"],"Build the project",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, defaults to project name in package.json)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("onlyFrontend",{alias:"F",type:"boolean",description:"Build only frontend bundle"}).option("onlyWebhook",{alias:"W",type:"boolean",description:"Build only webhooks"}).option("force",{alias:"f",type:"boolean",description:"Force full rebuild, skip cache (env: FORCE_REBUILD, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i,r=f({PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),ENABLE_EDITOR:e({default:!0}),DEBUG:e({default:!1}),BUILD_TARGET:o({choices:["frontend","webhook","both"],default:"both"}),FORCE_REBUILD:e({default:!1})})(),n=ea({debug:t.debug??r.DEBUG}),a=en(t.editor,r.ENABLE_EDITOR),s=t.force??r.FORCE_REBUILD,l=await ec(),p=el(t.applicationSpec,l.applicationFlow,{logger:n}),d=t.projectSlug??r.PROJECT_SLUG??l.name,c=t.environmentSlug??r.ENVIRONMENT_SLUG??l.name;if(!d)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!c)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");let u=t.onlyFrontend,E=t.onlyWebhook;if(u&&E)throw Error("Cannot use --onlyFrontend and --onlyWebhook flags together, they are mutually exclusive");i=u?"frontend":E?"webhook":r.BUILD_TARGET,await (p?U:y.runBuildCommand)({sdkVersion:ed.version,projectSlug:d,environmentSlug:c,cliUseWorkspace:t.workspace,enableEditor:a,buildTarget:i,force:s},{logger:n})}).command(["clean","cl"],"Clean build artifacts, reinstall dependencies, and rebuild specs",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, defaults to project name in package.json)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i=f({PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),ENABLE_EDITOR:e({default:!0}),DEBUG:e({default:!1})})(),r=ea({debug:t.debug??i.DEBUG}),n=en(t.editor,i.ENABLE_EDITOR),a=await ec(),s=el(t.applicationSpec,a.applicationFlow,{logger:r}),l=t.projectSlug??i.PROJECT_SLUG??a.name,p=t.environmentSlug??i.ENVIRONMENT_SLUG??a.name;if(!l)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!p)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");await (s?b:y.runCleanCommand)({sdkVersion:ed.version,projectSlug:l,environmentSlug:p,cliUseWorkspace:t.workspace,enableEditor:n},{logger:r})}).command(["dev","d"],"Start development mode (Vite, spec watching, and the webhook tunnel)",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("editorWsPort",{alias:"ep",type:"string",description:"Port for the editor WebSocket server (defaults to EDITOR_WS_PORT env or 3020)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG; required in server mode unless --local; offline modes default to the package name)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG; required in server mode unless --local; offline modes default to the package name)"}).option("withLingui",{alias:"wl",type:"boolean",description:"Enable or disable i18n messages extraction and compilation (defaults to WITH_LINGUI env or false)"}).option("persistence",{alias:"P",type:"string",description:"Persistence mode for the configurator (defaults to PERSISTENCE_MODE env or server)",choices:["memory","local-storage","server"]}).option("force",{alias:"f",type:"boolean",description:"Force full rebuild, skip cache (env: FORCE_REBUILD, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}).option("tui",{alias:"T",type:"boolean",description:"Render logs in a terminal UI with per-stream pages and split view; requires an interactive terminal (env: PHOENIX_TUI, default: false)"}).option("local",{type:"boolean",description:"Use local-mode development environment defaults (env: PHOENIX_LOCAL, default: false)"}).option("region",{choices:eo,description:`Phoenix region of the workspace you are signed in to, used when .env has no SERVER_URL (default: ${Z})`}).option("serverUrl",{type:"string",description:"URL of the Phoenix server, instead of SERVER_URL or the region default"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket when the development deployment is prepared (env: SKIP_UPLOAD, default: false)"}),async t=>{let i=f({ENABLE_EDITOR:e({default:!0}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),EDITOR_WS_PORT:o({default:"3020"}),DEBUG:e({default:!1}),WITH_LINGUI:e({default:!1}),PERSISTENCE_MODE:o({default:void 0}),FORCE_REBUILD:e({default:!1}),PHOENIX_TUI:e({default:!1}),SERVER_URL:o({default:void 0}),DEVELOPMENT_KEY:o({default:void 0}),PHOENIX_LOCAL:e({default:!1}),PHOENIX_REGION:o({choices:ee,default:void 0}),SKIP_UPLOAD:e({default:!1})})(),r=t.debug??i.DEBUG,n=ea({debug:r}),a=en(t.editor,i.ENABLE_EDITOR),s=t.editorWsPort??i.EDITOR_WS_PORT,l=t.force??i.FORCE_REBUILD,c=t.withLingui??i.WITH_LINGUI,u=t.persistence??i.PERSISTENCE_MODE,E=!u||"server"===u,v=t.tui??i.PHOENIX_TUI,m=await ec(),R=el(t.applicationSpec,m.applicationFlow,{logger:n}),S=t.local??i.PHOENIX_LOCAL,_=t.projectSlug??i.PROJECT_SLUG,U=E&&R&&!S&&!_?.length?await h(process.cwd()):void 0,{serverUrl:O,developmentKey:P,apiToken:b,projectSlug:L,environmentSlug:I}=await d({useServer:E,local:S,serverUrl:t.serverUrl??i.SERVER_URL,developmentKey:i.DEVELOPMENT_KEY,projectSlug:_?.length?_:U,environmentSlug:t.environmentSlug??i.ENVIRONMENT_SLUG}).with({useServer:!1},async({serverUrl:e,developmentKey:o,projectSlug:t,environmentSlug:i})=>({serverUrl:e,developmentKey:o,apiToken:void 0,projectSlug:t??m.name,environmentSlug:i??m.name})).with({local:!0},async({serverUrl:e,developmentKey:o,projectSlug:t,environmentSlug:i})=>({serverUrl:e?.length?e:"http://localhost:3000",developmentKey:o?.length?o:"dev_local-dev-key-12345",apiToken:void 0,projectSlug:t?.length?t:"local",environmentSlug:i?.length?i:"dev-local"})).with({developmentKey:p.string.minLength(1)},()=>U===g,()=>{throw Error(`phoenix dev has a DEVELOPMENT_KEY, but the spec says \`project: ${g}\`, which only a signed-in workspace can resolve. Set PROJECT_SLUG in .env by pasting the .env block of your Studio development environment, or name the project's slug in the spec.`)}).with({serverUrl:p.string.minLength(1),developmentKey:p.string.minLength(1),projectSlug:p.string.minLength(1),environmentSlug:p.string.minLength(1)},async({serverUrl:e,developmentKey:o,projectSlug:t,environmentSlug:i})=>({serverUrl:e,developmentKey:o,apiToken:void 0,projectSlug:t,environmentSlug:i})).with({developmentKey:p.union(void 0,""),projectSlug:p.string.minLength(1)},()=>R,async({serverUrl:e,projectSlug:o})=>{let r=e?.length?e:et(t.region??i.PHOENIX_REGION??Z),a=await er({serverUrl:r,project:o});return n.info(`Developing in ${a.projectSlug}/${a.environmentSlug} of workspace "${a.workspaceName}" on ${r}`),{serverUrl:r,developmentKey:void 0,apiToken:a.apiToken,projectSlug:a.projectSlug,environmentSlug:a.environmentSlug}}).with({developmentKey:p.union(void 0,""),projectSlug:p.union(void 0,"")},()=>R,()=>{throw Error(`phoenix dev does not know which project to develop in. Add \`project: ${g}\` to spec/application.yaml to use the default project of the workspace you are signed in to, or paste the .env block of a Studio development environment into .env. Pass --local to use the local-mode server.`)}).otherwise(()=>{throw Error("phoenix dev needs SERVER_URL, PROJECT_SLUG, ENVIRONMENT_SLUG, and DEVELOPMENT_KEY in .env. Create a development environment for your project in Studio and paste its .env block, or pass --local to use the local-mode server.")});if(!L)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!I)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");let N={sdkVersion:ed.version,projectSlug:L,environmentSlug:I,cliUseWorkspace:t.workspace,enableEditor:a,editorWsPort:s,debug:r,withLingui:c,persistenceMode:u,force:l,tui:v,serverUrl:O,skipUpload:t.skipUpload??i.SKIP_UPLOAD};if(R){let e=P?{developmentKey:P}:b?{apiToken:b}:void 0;await k({...N,credential:e},{logger:n})}else await y.runDevCommand({...N,developmentKey:P},{logger:n})}).command(["link","l"],"Link local SDK for development",e=>e.option("sdk-path",{type:"string",description:"Path to the SDK repository",default:"../configurator-phoenix"}),async e=>{let o=ea();await M({sdkPath:e.sdkPath},{logger:o})}).command(["unlink","ul"],"Remove local SDK linking",()=>{},async()=>{let e=ea();await Q({logger:e})}).command(["sync","s"],"Sync project files and compile specs without building vite/webhooks",e=>e.option("workspace",{alias:"w",type:"boolean",description:"Override workspace mode (uses .phoenix.json config by default)"}).option("editor",{type:"boolean",description:"Enable or disable editor files generation (defaults to ENABLE_EDITOR env or true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, defaults to project name in package.json)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("force",{alias:"f",type:"boolean",description:"Force full rebuild, skip cache (env: FORCE_REBUILD, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}).option("analyze",{alias:"a",type:"boolean",description:"Print pipeline metrics flamegraph after build (requires --applicationSpec)"}),async t=>{let i=f({ENABLE_EDITOR:e({default:!0}),DEBUG:e({default:!1}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),FORCE_REBUILD:e({default:!1})})(),r=ea({debug:t.debug??i.DEBUG}),n=en(t.editor,i.ENABLE_EDITOR),a=t.force??i.FORCE_REBUILD,s=await ec(),l=el(t.applicationSpec,s.applicationFlow,{logger:r}),p=t.projectSlug??i.PROJECT_SLUG??s.name,d=t.environmentSlug??i.ENVIRONMENT_SLUG??s.name;if(!p)throw Error("Project slug is required, provide via --projectSlug, set PROJECT_SLUG env, or ensure package.json has a name field");if(!d)throw Error("Environment slug is required, provide via --environmentSlug, set ENVIRONMENT_SLUG env, or ensure package.json has a name field");await (l?z:y.runSyncCommand)({sdkVersion:ed.version,projectSlug:p,environmentSlug:d,cliUseWorkspace:t.workspace,enableEditor:n,force:a,analyze:t.analyze},{logger:r})}).command("init [folder]","Start a Phoenix project: write package.json when the folder has none, a starter spec and pnpm-workspace.yaml with the pnpm settings the install needs. Continue with phoenix sync, phoenix login and phoenix dev",e=>e.positional("folder",{type:"string",description:"Folder to create the project in (default: the working directory)"}).option("scaffold",{type:"boolean",hidden:!0,description:"Accepted for compatibility and ignored: init always only writes the starter project"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging"}),async e=>{let o=ea({debug:e.debug??!1});try{await K({sdkVersion:ed.version,projectRoot:void 0===e.folder?void 0:l(e.folder)},{logger:o})}catch(e){o.error(e instanceof Error?e.message:String(e)),process.exit(1)}}).command(["story","st"],"Storybook example utilities",e=>X(e),()=>{}).command(["check","c"],"Run checks (TypeScript, ESLint, Prettier). Runs all by default, or only selected when any of --types/--lint/--format is passed.",e=>e.option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("types",{type:"boolean",description:"Run TypeScript check"}).option("lint",{type:"boolean",description:"Run ESLint check"}).option("format",{type:"boolean",description:"Run Prettier check"}),async o=>{let t=f({DEBUG:e({default:!1})})(),i=ea({debug:o.debug??t.DEBUG});await P({types:o.types,lint:o.lint,format:o.format},{logger:i})}).command(["fix","f"],"Fix linting and formatting issues. Runs all by default, or only selected when any of --lint/--format is passed.",e=>e.option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("lint",{type:"boolean",description:"Run ESLint fix"}).option("format",{type:"boolean",description:"Run Prettier fix"}),async o=>{let t=f({DEBUG:e({default:!1})})(),i=ea({debug:o.debug??t.DEBUG});await A({lint:o.lint,format:o.format},{logger:i})}).command(["changelog","clog"],"Display SDK changelog",e=>e.option("ver",{type:"string",description:"Show specific version (e.g., 0.36.0)"}).option("all",{alias:"a",type:"boolean",description:"Show all versions (warning: large output)"}).option("page",{alias:"p",type:"number",description:"Page number (3 versions per page)"}).option("list",{alias:"l",type:"boolean",description:"List version numbers only"}),async e=>{let o=ea();await O({version:e.ver,all:e.all,page:e.page,list:e.list},{logger:o})}).command(["inspect:assets","ia"],"Inspect 3D assets (glTF/GLB) — nodes, transforms, bounds, origin, materials, morph targets, animations",e=>e.option("file",{alias:"f",type:"string",description:"Path to specific glTF/GLB file"}).option("objects",{alias:"O",type:"boolean",description:"Show only object/node names"}).option("materials",{alias:"m",type:"boolean",description:"Show only material names"}).option("morphs",{alias:"M",type:"boolean",description:"Show only morph target names"}).option("animations",{alias:"a",type:"boolean",description:"Show only animation names"}).option("json",{alias:"j",type:"boolean",description:"Output as JSON"}),async e=>{let o=ea();await V({file:e.file,objects:e.objects,materials:e.materials,morphs:e.morphs,animations:e.animations,json:e.json},{logger:o})}).command(["screenshot","ss"],"Capture a screenshot of the configurator canvas",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (defaults to SCREENSHOT_OUTPUT env or ./screenshot.png)"}).option("width",{alias:"w",type:"number",description:"Width in pixels (default: 1920)"}).option("height",{alias:"H",type:"number",description:"Height in pixels (default: 1080)"}).option("format",{alias:"f",type:"string",description:"Image format: PNG or JPEG (default: PNG)"}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"}),SCREENSHOT_OUTPUT:o({default:"./screenshot.png"})})(),i=ea();await W({port:e.port??t.EDITOR_WS_PORT,output:e.output??t.SCREENSHOT_OUTPUT,width:e.width,height:e.height,format:e.format},{logger:i})}).command(["inspect:state","is"],"Get the current editor state as JSON (entities, navigation, validation)",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (prints to stdout if omitted)"}).option("sections",{alias:"s",type:"string",description:"Comma-separated sections: entities,navigation,validation (default: all)"}).option("pretty",{alias:"P",type:"boolean",description:"Pretty-print JSON output (default: true)",default:!0}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"})})(),i=ea();await $({port:e.port??t.EDITOR_WS_PORT,output:e.output,sections:e.sections,pretty:e.pretty},{logger:i})}).command(["inspect:data","id"],"Get the current editor context data as JSON (published, frontend, backend, session, access, web component)",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (prints to stdout if omitted)"}).option("pretty",{alias:"P",type:"boolean",description:"Pretty-print JSON output (default: true)",default:!0}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"})})(),i=ea();await N({port:e.port??t.EDITOR_WS_PORT,output:e.output,pretty:e.pretty},{logger:i})}).command(["inspect:scene","isc"],"Get the current Three.js scene graph as JSON",e=>e.option("port",{alias:"p",type:"string",description:"WebSocket server port (defaults to EDITOR_WS_PORT env or 3020)"}).option("output",{alias:"o",type:"string",description:"Output file path (prints to stdout if omitted)"}).option("pretty",{alias:"P",type:"boolean",description:"Pretty-print JSON output (default: true)",default:!0}),async e=>{let t=f({EDITOR_WS_PORT:o({default:"3020"})})(),i=ea();await H({port:e.port??t.EDITOR_WS_PORT,output:e.output,pretty:e.pretty},{logger:i})}).command(["docs","doc"],"Browse SDK documentation",e=>e.option("read",{alias:"r",type:"string",description:"Read a specific doc file (e.g., docs/phoenix/admin.mdx)"}).option("category",{alias:"c",type:"string",description:"Show all docs in a category"}).option("list",{alias:"l",type:"boolean",description:"List all docs with paths and titles"}).option("search",{alias:"s",type:"string",description:"Search doc content for a term (case-insensitive)"}).option("dir",{type:"boolean",description:"Print the absolute path of the docs directory"}),async e=>{let o=ea();await D({read:e.read,category:e.category,list:e.list,search:e.search,dir:e.dir},{logger:o})}).command(["prepare:ci","pci","ci"],"Prepare CI environment by copying .env.ci to .env",()=>{},async()=>{let e=ea();await Y({logger:e})}).command({command:"environment:create",aliases:["env:create"],describe:"Create a new deployment environment",deprecated:"Switch to a new workflow and use deployment:prepare instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("name",{alias:"n",type:"string",description:"Name of the environment to be created"}).option("slug",{type:"string",description:"Slug of the customer to be created"}).option("uuid",{alias:"u",type:"string",description:"UUID of the environment to be created"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment UUID to",default:".env.generated"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await I({server:o.server,apiKey:o.apiKey,name:o.name,slug:o.slug,uuid:o.uuid,outFile:o.outFile,debug:i},{logger:r})}}).command({command:"environment:list",aliases:["env:list"],describe:"List all deployment environments",deprecated:"Switch to a new workflow and use the prepare environment flow instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await J({server:o.server,apiKey:o.apiKey,debug:i},{logger:r})}}).command(["environment:cleanup","env:cleanup","ec"],"Cleanup a transient deployment environment",e=>e.option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key (env: PROJECT_KEY, required)"}).option("projectSlug",{alias:"p",type:"string",description:"Project slug (env: PROJECT_SLUG, required)"}).option("environmentSlug",{alias:"e",type:"string",description:"Environment slug (env: ENVIRONMENT_SLUG, defaults to project name in package.json)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write cleanup result env variables to (env: OUT_FILE, default: .env.generated)",default:".env.generated"}),async t=>{let i=f({DEBUG:e({default:!1}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),SERVER_URL:o({default:void 0}),OUT_FILE:o({default:void 0})})(),r=t.debug??i.DEBUG,n=t.apiKey??i.ENVIRONMENT_API_KEY,a=t.projectKey??i.PROJECT_KEY,s=t.projectSlug??i.PROJECT_SLUG,l=await ec(),p=t.environmentSlug??i.ENVIRONMENT_SLUG??l.name,d=t.serverUrl??i.SERVER_URL,c=t.outFile??i.OUT_FILE??".env.generated";if(!n)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");if(!a)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");if(!s)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");if(!p)throw Error("Environment slug is required, provide via --environmentSlug or -e, or set ENVIRONMENT_SLUG environment variable");if(!d)throw Error("Server URL is required, provide via --serverUrl or -su, or set SERVER_URL environment variable");let u=ea({debug:r});await L({server:d,apiKey:n,projectKey:a,projectSlug:s,environmentSlug:p,debug:r,outFile:c},{logger:u})}).command(["login <customer>"],"Sign the CLI in to an SDK-builder workspace; you approve it in Studio",e=>e.positional("customer",{type:"string",demandOption:!0,description:"Slug of the SDK-builder workspace (customer)"}).option("region",{choices:eo,description:`Phoenix region the workspace lives in (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}).option("studioUrl",{type:"string",description:"URL of Studio for the workspace, instead of the region default"}).option("open",{type:"boolean",default:!0,description:"Open the approval page in the default browser (--no-open to only print the link)"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0})})(),i=e.region??t.PHOENIX_REGION??Z,r=ea();try{await B({customer:e.customer,serverUrl:e.serverUrl??et(i),studioUrl:e.studioUrl??ei(i,e.customer),open:e.open},{logger:r})}catch(e){r.error(eu(e)),process.exit(1)}}).command(["deploy"],"Upload this SDK-builder project and follow its build until it is live",e=>e.option("region",{choices:eo,description:`Phoenix region the workspace lives in (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}).option("wait",{type:"boolean",default:!0,description:"Follow the build until it finishes (--no-wait to return once it starts)"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0})})(),i=e.region??t.PHOENIX_REGION??Z,r=ea();try{await T({serverUrl:e.serverUrl??et(i),sdkVersion:ed.version,wait:e.wait,projectDir:process.cwd(),pollIntervalMs:3e3},{logger:r})}catch(e){r.error(eu(e)),process.exit(1)}}).command(["logout"],"Revoke the API token of an SDK-builder workspace and forget it on this computer",e=>e.option("region",{choices:eo,description:`Phoenix region to sign out of (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0})})(),i=e.region??t.PHOENIX_REGION??Z,r=ea();try{await F({serverUrl:e.serverUrl??et(i)},{logger:r})}catch(e){r.error(eu(e)),process.exit(1)}}).command(["feedback <message>"],"Send feedback about the SDK, CLI, docs or Studio to the Phoenix team",e=>e.positional("message",{type:"string",demandOption:!0,description:"What you want to tell the Phoenix team"}).option("type",{choices:[m.Bug,m.Idea,m.Other],default:m.Other,description:"What the feedback is about"}).option("contact",{type:"string",description:"How to reach you, when you want a reply"}).option("agent",{type:"string",description:"Name of the coding agent that writes the feedback"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the workspace project the feedback is about (env: PROJECT_SLUG, defaults to `project` of the application spec)"}).option("region",{choices:eo,description:`Phoenix region the workspace lives in (default: ${Z})`}).option("serverUrl",{alias:"su",type:"string",description:"URL of the Phoenix server, instead of the region default"}),async e=>{let t=f({PHOENIX_REGION:o({choices:ee,default:void 0}),PROJECT_SLUG:o({default:void 0})})(),i=ea(),a=e.region??t.PHOENIX_REGION??Z,s=e.projectSlug??t.PROJECT_SLUG,l=s?.length?s:await h(process.cwd()).catch(()=>{i.warn("The feedback is sent without a project, because `project` could not be read from the application spec. Pass --projectSlug to name the project.")});try{await G({serverUrl:e.serverUrl??et(a),message:e.message,type:e.type,contact:e.contact,agent:e.agent,project:l,sdkVersion:ed.version,clientInfo:`${n()} ${r()}, Node.js ${process.versions.node}`},{logger:i})}catch(e){i.error(eu(e)),process.exit(1)}}).command({command:"deployment:register",aliases:["dep:register","dr"],describe:"Register deployment configuration",deprecated:"Switch to a new workflow and use deployment:prepare and deployment:finish instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the deployment ID to",default:".env.generated"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip uploading assets",default:!1}).option("activate",{alias:"a",type:"boolean",description:"Activate deployment after registration",default:!0}).option("overrideSchema",{alias:"os",type:"boolean",description:"Override existing schema",default:!1}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await x({server:o.server,apiKey:o.apiKey,outFile:o.outFile,skipUpload:o.skipUpload,activate:o.activate,overrideSchema:o.overrideSchema,debug:i},{logger:r})}}).command({command:"deployment:activate",aliases:["dep:activate","da"],describe:"Activate a deployment",deprecated:"Switch to a new workflow and use deployment:prepare and deployment:finish instead",builder:e=>e.option("server",{alias:"s",type:"string",description:"URL of the Phoenix server (defaults to SERVER_URL env)"}).option("apiKey",{alias:"k",type:"string",description:"API key for server access (defaults to ENVIRONMENT_API_KEY env)"}).option("uuid",{alias:"u",type:"string",description:"UUID of the deployment to activate"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (defaults to DEBUG env or false)"}),handler:async o=>{let t=f({DEBUG:e({default:!1})})(),i=o.debug??t.DEBUG,r=ea({debug:i});await _({server:o.server,apiKey:o.apiKey,uuid:o.uuid,debug:i},{logger:r})}}).command(["deployment:prepare","dep:prepare","dp"],"Prepare environment and register deployment (env: ENVIRONMENT_API_KEY, PROJECT_KEY, PROJECT_SLUG, PROJECT_REF, PROJECT_SHA, ENVIRONMENT_SLUG, OUT_FILE, SKIP_UPLOAD, OVERRIDE_SCHEMA, SERVER_URL)",e=>e.option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (will be created if it does not exist) (env: ENVIRONMENT_SLUG, required)"}).option("projectRef",{type:"string",description:"Source branch, tag, or ref recorded as deployment provenance (env: PROJECT_REF)"}).option("projectSha",{type:"string",description:"Full commit SHA of the checked-out project revision (env: PROJECT_SHA)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("overrideSchema",{alias:"os",type:"boolean",description:"Allows to override deployment schemas (env: OVERRIDE_SCHEMA, default: false)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("uploadLinksFile",{alias:"ulf",type:"string",description:"File to write upload links to (used by finish command) (default: .upload-links.json)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, required)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),PROJECT_REF:o({default:void 0}),PROJECT_SHA:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),OUT_FILE:o({default:".env.generated"}),SKIP_UPLOAD:e({default:!1}),OVERRIDE_SCHEMA:e({default:!1}),SERVER_URL:o({default:void 0})})(),r=t.debug??i.DEBUG,n=t.apiKey??i.ENVIRONMENT_API_KEY;if(!n)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let a=t.projectKey??i.PROJECT_KEY;if(!a)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let s=t.projectSlug??i.PROJECT_SLUG;if(!s)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");let l=t.environmentSlug??i.ENVIRONMENT_SLUG;if(!l)throw Error("Environment slug is required, provide via --environmentSlug or -e, or set ENVIRONMENT_SLUG environment variable");let p=t.serverUrl??i.SERVER_URL;if(!p)throw Error("Server URL is required, provide via --serverUrl or -s, or set SERVER_URL environment variable");let d=await ec(),c=ea({debug:r}),u=el(t.applicationSpec,d.applicationFlow,{logger:c});await (u?q:y.runPrepareDeploymentCommand)({apiKey:n,projectKey:a,projectSlug:s,projectRef:t.projectRef??i.PROJECT_REF,projectSha:t.projectSha??i.PROJECT_SHA,environmentSlug:l,outFile:t.outFile??i.OUT_FILE,skipUpload:t.skipUpload??i.SKIP_UPLOAD,overrideSchema:t.overrideSchema??i.OVERRIDE_SCHEMA,uploadLinksFile:t.uploadLinksFile??".upload-links.json",serverUrl:p,debug:r,projectVersion:ed.version},{logger:c})}).command(["deployment:prepare-dev","dep:prepare-dev","dpd"],"Prepare a development deployment (env: SERVER_URL, PROJECT_SLUG, ENVIRONMENT_SLUG, DEVELOPMENT_KEY, OUT_FILE, SKIP_UPLOAD, OVERRIDE_SCHEMA)",e=>e.option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required unless --local)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the development environment (env: ENVIRONMENT_SLUG, required unless --local)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("overrideSchema",{alias:"os",type:"boolean",description:"Allows overriding deployment schemas (env: OVERRIDE_SCHEMA, default: true)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}).option("local",{type:"boolean",description:"Use local-mode development environment defaults (env: PHOENIX_LOCAL, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),PROJECT_SLUG:o({default:void 0}),ENVIRONMENT_SLUG:o({default:void 0}),SERVER_URL:o({default:void 0}),DEVELOPMENT_KEY:o({default:void 0}),PHOENIX_LOCAL:e({default:!1}),OUT_FILE:o({default:".env.generated"}),SKIP_UPLOAD:e({default:!1}),OVERRIDE_SCHEMA:e({default:!0})})(),r=t.debug??i.DEBUG,n=ea({debug:r}),a=t.local??i.PHOENIX_LOCAL,s=i.SERVER_URL,l=t.projectSlug??i.PROJECT_SLUG,p=t.environmentSlug??i.ENVIRONMENT_SLUG,d=i.DEVELOPMENT_KEY;if(a&&(s=s?.length?s:"http://localhost:3000",l=l?.length?l:"local",p=p?.length?p:"dev-local",d=d?.length?d:"dev_local-dev-key-12345"),!s||!l||!p||!d)throw Error("phoenix deployment:prepare-dev needs SERVER_URL, PROJECT_SLUG, ENVIRONMENT_SLUG, and DEVELOPMENT_KEY in .env. Create a development environment for your project in Studio and paste its .env block, or pass --local to use the local-mode server.");let c=await ec(),u=el(t.applicationSpec,c.applicationFlow,{logger:n}),E={serverUrl:s,projectSlug:l,environmentSlug:p,outFile:t.outFile??i.OUT_FILE,skipUpload:t.skipUpload??i.SKIP_UPLOAD,overrideSchema:t.overrideSchema??i.OVERRIDE_SCHEMA,debug:r,projectVersion:ed.version};try{u?await R({...E,credential:{developmentKey:d}},{logger:n}):await y.prepareDevelopmentEnvironment({...E,developmentKey:d},{logger:n})}catch(e){n.error(`Development environment preparation failed: ${e instanceof Error?e.message:String(e)}`,e),process.exit(1)}}).command(["deployment:prepare-local","dep:prepare-local","dpl"],"Prepare local environment for development (env: PROJECT_KEY, PROJECT_SLUG, OUT_FILE, SKIP_UPLOAD, OVERRIDE_SCHEMA, SERVER_URL, LAMBDA_URL)",e=>e.option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, default: local)"}).option("environmentSlug",{alias:"e",type:"string",description:"Slug of the environment (env: ENVIRONMENT_SLUG, default: project name in package.json)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("overrideSchema",{alias:"os",type:"boolean",description:"Allows to override deployment schemas (env: OVERRIDE_SCHEMA, default: true)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, default: http://localhost:3000)"}).option("lambdaUrl",{alias:"l",type:"string",description:"Webhook URL (env: LAMBDA_URL, default: http://localhost:8080)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("applicationSpec",{alias:"as",type:"boolean",description:"Use application-spec pipeline (env: USE_APPLICATION_SPEC, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),ENVIRONMENT_SLUG:o({default:void 0}),PROJECT_KEY:o({default:"project_local-dev-key-12345"}),PROJECT_SLUG:o({default:"local"}),OUT_FILE:o({default:".env.generated"}),SKIP_UPLOAD:e({default:!1}),OVERRIDE_SCHEMA:e({default:!0}),SERVER_URL:o({default:"http://localhost:3000"}),LAMBDA_URL:o({default:"http://localhost:8080"}),ENVIRONMENT_API_KEY:o({default:"local-dev-key-12345"})})(),r=t.debug??i.DEBUG,n=t.projectKey??i.PROJECT_KEY;if(!n)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let a=await ec(),s=t.environmentSlug??i.ENVIRONMENT_SLUG??a.name;if(!s)throw Error("Environment slug is required, provide via --environmentSlug or -e, or set ENVIRONMENT_SLUG environment variable");let l=t.apiKey??i.ENVIRONMENT_API_KEY;if(!l)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let p=ea({debug:r}),d=el(t.applicationSpec,a.applicationFlow,{logger:p});try{await (d?S:y.prepareLocalEnvironment)({apiKey:l,projectKey:n,projectSlug:t.projectSlug??i.PROJECT_SLUG,environmentSlug:s,outFile:t.outFile??i.OUT_FILE,skipUpload:t.skipUpload??i.SKIP_UPLOAD,overrideSchema:t.overrideSchema??i.OVERRIDE_SCHEMA,serverUrl:t.serverUrl??i.SERVER_URL,lambdaUrl:t.lambdaUrl??i.LAMBDA_URL,debug:r,projectVersion:ed.version},{logger:p})}catch(e){p.error(`Local environment preparation failed: ${e instanceof Error?e.message:String(e)}`,e),process.exit(1)}}).command(["deployment:extract-sources <archive> <folder>"],"Validate the source archive of a phoenix deploy and extract its project files into a new folder. Links, special files, unsafe paths and oversized content are refused; entries that are not project files are left out",e=>e.positional("archive",{type:"string",demandOption:!0,description:"The source archive (.tgz)"}).positional("folder",{type:"string",demandOption:!0,description:"New or empty folder to extract the project files into"}),async e=>{let o=ea();try{await j({archivePath:e.archive,targetDir:e.folder},{logger:o})}catch(e){o.error(e instanceof Error?e.message:String(e)),process.exit(1)}}).command(["deployment:build-envs","dep:build-envs","dbe"],"Write the environment variables for the build step of a prepared deployment that builds apart from its deploy (env: DEPLOYMENT_ID, ENVIRONMENT_API_KEY, PROJECT_KEY, PROJECT_SLUG, OUT_FILE, SERVER_URL)",e=>e.option("uuid",{alias:"u",type:"string",description:"Id of the prepared deployment (env: DEPLOYMENT_ID, required)"}).option("outFile",{alias:"o",type:"string",description:"Output file to write the environment variables to (env: OUT_FILE, default: .env.generated)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, required)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),DEPLOYMENT_ID:o({default:void 0}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),OUT_FILE:o({default:".env.generated"}),SERVER_URL:o({default:void 0})})(),r=t.debug??i.DEBUG,n=t.uuid??i.DEPLOYMENT_ID;if(!n)throw Error("Deployment ID is required, provide via --uuid or -u, or set DEPLOYMENT_ID environment variable");let a=t.apiKey??i.ENVIRONMENT_API_KEY;if(!a)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let s=t.projectKey??i.PROJECT_KEY;if(!s)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let l=t.projectSlug??i.PROJECT_SLUG;if(!l)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");let p=t.serverUrl??i.SERVER_URL;if(!p)throw Error("Server URL is required, provide via --serverUrl or -s, or set SERVER_URL environment variable");let d=ea({debug:r});await w({uuid:n,apiKey:a,projectKey:s,projectSlug:l,outFile:t.outFile??i.OUT_FILE,serverUrl:p,debug:r},{logger:d})}).command(["deployment:finish","dep:finish","df"],"Upload files to S3 and activate deployment (env: DEPLOYMENT_ID, ENVIRONMENT_API_KEY, PROJECT_KEY, PROJECT_SLUG, LAMBDA_URL, SERVER_URL, SKIP_UPLOAD)",e=>e.option("uuid",{alias:"u",type:"string",description:"Id of the deployment to be activated (env: DEPLOYMENT_ID, required)"}).option("uploadLinksFile",{alias:"ulf",type:"string",description:"File containing upload links (generated by prepare command) (default: .upload-links.json)"}).option("apiKey",{alias:"a",type:"string",description:"API key for server access (env: ENVIRONMENT_API_KEY, required)"}).option("projectKey",{alias:"k",type:"string",description:"Project key to access the Project resources (env: PROJECT_KEY, required)"}).option("projectSlug",{alias:"p",type:"string",description:"Slug of the project (env: PROJECT_SLUG, required)"}).option("lambdaUrl",{alias:"l",type:"string",description:"Lambda webhook URL for the deployment (env: LAMBDA_URL, required)"}).option("serverUrl",{alias:"s",type:"string",description:"PHX server URL (env: SERVER_URL, required)"}).option("skipUpload",{alias:"su",type:"boolean",description:"Skip upload of resources to S3 bucket (env: SKIP_UPLOAD, default: false)"}).option("debug",{alias:"D",type:"boolean",description:"Enable debug mode for detailed logging (env: DEBUG, default: false)"}),async t=>{let i=f({DEBUG:e({default:!1}),DEPLOYMENT_ID:o({default:void 0}),ENVIRONMENT_API_KEY:o({default:void 0}),PROJECT_KEY:o({default:void 0}),PROJECT_SLUG:o({default:void 0}),LAMBDA_URL:o({default:void 0}),SERVER_URL:o({default:void 0}),SKIP_UPLOAD:e({default:!1})})(),r=t.debug??i.DEBUG,n=t.uuid??i.DEPLOYMENT_ID;if(!n)throw Error("Deployment ID is required, provide via --uuid or -u, or set DEPLOYMENT_ID environment variable");let a=t.apiKey??i.ENVIRONMENT_API_KEY;if(!a)throw Error("API key is required, provide via --apiKey or -a, or set ENVIRONMENT_API_KEY environment variable");let s=t.projectKey??i.PROJECT_KEY;if(!s)throw Error("Project key is required, provide via --projectKey or -k, or set PROJECT_KEY environment variable");let l=t.projectSlug??i.PROJECT_SLUG;if(!l)throw Error("Project slug is required, provide via --projectSlug or -p, or set PROJECT_SLUG environment variable");let p=t.lambdaUrl??i.LAMBDA_URL;if(!p)throw Error("Lambda URL is required, provide via --lambdaUrl or -l, or set LAMBDA_URL environment variable");let d=t.serverUrl??i.SERVER_URL;if(!d)throw Error("Server URL is required, provide via --serverUrl or -s, or set SERVER_URL environment variable");let c=ea({debug:r});await C({uuid:n,uploadLinksFile:t.uploadLinksFile??".upload-links.json",apiKey:a,projectKey:s,projectSlug:l,lambdaUrl:p,serverUrl:d,skipUpload:t.skipUpload??i.SKIP_UPLOAD,debug:r},{logger:c})}).demandCommand(1,"Please specify a command").help().alias("help","h").version(ed.version).alias("version","v").strict().parse();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@salsita-ai/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.50.0-develop.da8d27f5d7",
|
|
4
4
|
"description": "TypeScript SDK and CLI for building, running, and deploying Phoenix configurator applications.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"salsita",
|
|
@@ -295,8 +295,8 @@
|
|
|
295
295
|
"yaml": "2.9.0",
|
|
296
296
|
"yargs": "17.7.2",
|
|
297
297
|
"zod": "3.25.76",
|
|
298
|
-
"@salsita-ai/common": "0.
|
|
299
|
-
"@salsita-ai/contracts": "0.
|
|
298
|
+
"@salsita-ai/common": "0.50.0-develop.da8d27f5d7",
|
|
299
|
+
"@salsita-ai/contracts": "0.50.0-develop.da8d27f5d7"
|
|
300
300
|
},
|
|
301
301
|
"devDependencies": {
|
|
302
302
|
"@clerk/clerk-react": "5.61.8",
|