adapty 0.8.9 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +147 -2
  2. package/dist/cli/base/adapty/adapty-command.d.ts +3 -2
  3. package/dist/cli/base/adapty/adapty-command.js +8 -5
  4. package/dist/cli/base/adapty/build.d.ts +6 -1
  5. package/dist/cli/base/adapty/build.js +1 -0
  6. package/dist/cli/base/adapty/index.d.ts +1 -0
  7. package/dist/cli/base/adapty/index.js +1 -0
  8. package/dist/cli/base/adapty/migration-command.d.ts +11 -0
  9. package/dist/cli/base/adapty/migration-command.js +17 -0
  10. package/dist/cli/base/base-command.d.ts +6 -0
  11. package/dist/cli/base/base-command.js +14 -0
  12. package/dist/cli/commands/apps/create.d.ts +1 -1
  13. package/dist/cli/commands/apps/get.js +1 -1
  14. package/dist/cli/commands/apps/list.js +1 -1
  15. package/dist/cli/commands/apps/update.js +1 -1
  16. package/dist/cli/commands/attribution/report/command.d.ts +18 -0
  17. package/dist/cli/commands/attribution/report/command.js +93 -0
  18. package/dist/cli/commands/attribution/report/index.d.ts +1 -18
  19. package/dist/cli/commands/attribution/report/index.js +1 -93
  20. package/dist/cli/commands/attribution/values.js +1 -1
  21. package/dist/cli/commands/auth/login.js +3 -2
  22. package/dist/cli/commands/auth/{logout.d.ts → logout/command.d.ts} +1 -1
  23. package/dist/cli/commands/auth/logout/command.js +33 -0
  24. package/dist/cli/commands/auth/logout/index.d.ts +1 -0
  25. package/dist/cli/commands/auth/logout/index.js +1 -0
  26. package/dist/cli/commands/auth/logout/lib/cleanup.d.ts +3 -0
  27. package/dist/cli/commands/auth/logout/lib/cleanup.js +21 -0
  28. package/dist/cli/commands/auth/{revoke.d.ts → revoke/command.d.ts} +1 -1
  29. package/dist/cli/commands/auth/{revoke.js → revoke/command.js} +7 -7
  30. package/dist/cli/commands/auth/revoke/index.d.ts +1 -0
  31. package/dist/cli/commands/auth/revoke/index.js +1 -0
  32. package/dist/cli/commands/auth/revoke/lib/cleanup.d.ts +4 -0
  33. package/dist/cli/commands/auth/revoke/lib/cleanup.js +29 -0
  34. package/dist/cli/commands/auth/status/command.d.ts +7 -0
  35. package/dist/cli/commands/auth/status/command.js +23 -0
  36. package/dist/cli/commands/auth/status/index.d.ts +1 -7
  37. package/dist/cli/commands/auth/status/index.js +1 -22
  38. package/dist/cli/commands/auth/status/lib/render.js +3 -0
  39. package/dist/cli/commands/auth/status/lib/result.d.ts +1 -0
  40. package/dist/cli/commands/migrations/close/command.d.ts +16 -0
  41. package/dist/cli/commands/migrations/close/command.js +58 -0
  42. package/dist/cli/commands/migrations/close/index.d.ts +1 -0
  43. package/dist/cli/commands/migrations/close/index.js +1 -0
  44. package/dist/cli/commands/migrations/create/command.d.ts +20 -0
  45. package/dist/cli/commands/migrations/create/command.js +86 -0
  46. package/dist/cli/commands/migrations/create/index.d.ts +1 -0
  47. package/dist/cli/commands/migrations/create/index.js +1 -0
  48. package/dist/cli/commands/migrations/current/command.d.ts +12 -0
  49. package/dist/cli/commands/migrations/current/command.js +25 -0
  50. package/dist/cli/commands/migrations/current/index.d.ts +1 -0
  51. package/dist/cli/commands/migrations/current/index.js +1 -0
  52. package/dist/cli/commands/migrations/list/command.d.ts +11 -0
  53. package/dist/cli/commands/migrations/list/command.js +27 -0
  54. package/dist/cli/commands/migrations/list/index.d.ts +1 -0
  55. package/dist/cli/commands/migrations/list/index.js +1 -0
  56. package/dist/cli/commands/migrations/list/lib/render.d.ts +3 -0
  57. package/dist/cli/commands/migrations/list/lib/render.js +77 -0
  58. package/dist/cli/commands/migrations/run/command.d.ts +25 -0
  59. package/dist/cli/commands/migrations/run/command.js +141 -0
  60. package/dist/cli/commands/migrations/run/index.d.ts +1 -0
  61. package/dist/cli/commands/migrations/run/index.js +1 -0
  62. package/dist/cli/commands/migrations/run/lib/action-view.d.ts +7 -0
  63. package/dist/cli/commands/migrations/run/lib/action-view.js +2 -0
  64. package/dist/cli/commands/migrations/run/lib/actions.d.ts +4 -0
  65. package/dist/cli/commands/migrations/run/lib/actions.js +20 -0
  66. package/dist/cli/commands/migrations/run/lib/https.d.ts +1 -0
  67. package/dist/cli/commands/migrations/run/lib/https.js +8 -0
  68. package/dist/cli/commands/migrations/run/lib/input.d.ts +10 -0
  69. package/dist/cli/commands/migrations/run/lib/input.js +51 -0
  70. package/dist/cli/commands/migrations/run/lib/open-link.d.ts +7 -0
  71. package/dist/cli/commands/migrations/run/lib/open-link.js +11 -0
  72. package/dist/cli/commands/migrations/show/command.d.ts +17 -0
  73. package/dist/cli/commands/migrations/show/command.js +47 -0
  74. package/dist/cli/commands/migrations/show/index.d.ts +1 -0
  75. package/dist/cli/commands/migrations/show/index.js +1 -0
  76. package/dist/cli/commands/migrations/show/lib/render.d.ts +4 -0
  77. package/dist/cli/commands/migrations/show/lib/render.js +27 -0
  78. package/dist/cli/commands/migrations/status/command.d.ts +18 -0
  79. package/dist/cli/commands/migrations/status/command.js +54 -0
  80. package/dist/cli/commands/migrations/status/index.d.ts +1 -0
  81. package/dist/cli/commands/migrations/status/index.js +1 -0
  82. package/dist/cli/commands/migrations/status/lib/flags.d.ts +8 -0
  83. package/dist/cli/commands/migrations/status/lib/flags.js +29 -0
  84. package/dist/cli/commands/migrations/status/lib/notice.d.ts +3 -0
  85. package/dist/cli/commands/migrations/status/lib/notice.js +10 -0
  86. package/dist/cli/commands/migrations/steps/command.d.ts +14 -0
  87. package/dist/cli/commands/migrations/steps/command.js +34 -0
  88. package/dist/cli/commands/migrations/steps/index.d.ts +1 -0
  89. package/dist/cli/commands/migrations/steps/index.js +1 -0
  90. package/dist/cli/commands/migrations/steps/lib/render.d.ts +3 -0
  91. package/dist/cli/commands/migrations/steps/lib/render.js +28 -0
  92. package/dist/cli/commands/migrations/unuse/command.d.ts +11 -0
  93. package/dist/cli/commands/migrations/unuse/command.js +23 -0
  94. package/dist/cli/commands/migrations/unuse/index.d.ts +1 -0
  95. package/dist/cli/commands/migrations/unuse/index.js +1 -0
  96. package/dist/cli/commands/migrations/use/command.d.ts +14 -0
  97. package/dist/cli/commands/migrations/use/command.js +24 -0
  98. package/dist/cli/commands/migrations/use/index.d.ts +1 -0
  99. package/dist/cli/commands/migrations/use/index.js +1 -0
  100. package/dist/cli/context/migration/current.d.ts +32 -0
  101. package/dist/cli/context/migration/current.js +30 -0
  102. package/dist/cli/context/migration/index.d.ts +4 -0
  103. package/dist/cli/context/migration/index.js +2 -0
  104. package/dist/cli/context/migration/model.d.ts +14 -0
  105. package/dist/cli/context/migration/model.js +30 -0
  106. package/dist/cli/context/migration/resolve.d.ts +16 -0
  107. package/dist/cli/context/migration/resolve.js +26 -0
  108. package/dist/cli/context/migration/store.d.ts +9 -0
  109. package/dist/cli/context/migration/store.js +99 -0
  110. package/dist/cli/errors/wizard.d.ts +5 -0
  111. package/dist/cli/errors/wizard.js +54 -0
  112. package/dist/cli/errors.d.ts +37 -4
  113. package/dist/cli/errors.js +58 -4
  114. package/dist/cli/input/app.d.ts +8 -0
  115. package/dist/cli/{flags.js → input/app.js} +2 -9
  116. package/dist/cli/input/migration.d.ts +7 -0
  117. package/dist/cli/input/migration.js +17 -0
  118. package/dist/cli/input/pagination.d.ts +11 -0
  119. package/dist/cli/input/pagination.js +8 -0
  120. package/dist/cli/views/migrations/envelope/action-block.d.ts +2 -0
  121. package/dist/cli/views/migrations/envelope/action-block.js +32 -0
  122. package/dist/cli/views/migrations/envelope/envelope.d.ts +3 -0
  123. package/dist/cli/views/migrations/envelope/envelope.js +40 -0
  124. package/dist/cli/views/migrations/envelope/issue-block.d.ts +2 -0
  125. package/dist/cli/views/migrations/envelope/issue-block.js +13 -0
  126. package/dist/cli/views/migrations/notices.d.ts +7 -0
  127. package/dist/cli/views/migrations/notices.js +7 -0
  128. package/dist/commands/access-levels/create.js +1 -1
  129. package/dist/commands/access-levels/get.js +1 -1
  130. package/dist/commands/access-levels/list.js +1 -1
  131. package/dist/commands/access-levels/update.js +1 -1
  132. package/dist/commands/auth/logout.d.ts +1 -1
  133. package/dist/commands/auth/logout.js +1 -1
  134. package/dist/commands/auth/revoke.d.ts +1 -1
  135. package/dist/commands/auth/revoke.js +1 -1
  136. package/dist/commands/flows/config/get.js +1 -1
  137. package/dist/commands/flows/config/update.js +1 -1
  138. package/dist/commands/flows/config/validate.js +1 -1
  139. package/dist/commands/flows/create.js +1 -1
  140. package/dist/commands/flows/get.js +1 -1
  141. package/dist/commands/flows/list.js +1 -1
  142. package/dist/commands/flows/media/upload.js +1 -1
  143. package/dist/commands/flows/publish.js +1 -1
  144. package/dist/commands/flows/update.js +1 -1
  145. package/dist/commands/migrations/close.d.ts +1 -0
  146. package/dist/commands/migrations/close.js +2 -0
  147. package/dist/commands/migrations/create.d.ts +1 -0
  148. package/dist/commands/migrations/create.js +2 -0
  149. package/dist/commands/migrations/current.d.ts +1 -0
  150. package/dist/commands/migrations/current.js +1 -0
  151. package/dist/commands/migrations/list.d.ts +1 -0
  152. package/dist/commands/migrations/list.js +2 -0
  153. package/dist/commands/migrations/run.d.ts +1 -0
  154. package/dist/commands/migrations/run.js +2 -0
  155. package/dist/commands/migrations/show.d.ts +1 -0
  156. package/dist/commands/migrations/show.js +2 -0
  157. package/dist/commands/migrations/status.d.ts +1 -0
  158. package/dist/commands/migrations/status.js +2 -0
  159. package/dist/commands/migrations/steps.d.ts +1 -0
  160. package/dist/commands/migrations/steps.js +2 -0
  161. package/dist/commands/migrations/unuse.d.ts +1 -0
  162. package/dist/commands/migrations/unuse.js +1 -0
  163. package/dist/commands/migrations/use.d.ts +1 -0
  164. package/dist/commands/migrations/use.js +1 -0
  165. package/dist/commands/paywalls/create.js +1 -1
  166. package/dist/commands/paywalls/get.js +1 -1
  167. package/dist/commands/paywalls/list.js +1 -1
  168. package/dist/commands/paywalls/placements.js +1 -1
  169. package/dist/commands/paywalls/update.js +1 -1
  170. package/dist/commands/placements/create.js +1 -1
  171. package/dist/commands/placements/get.js +1 -1
  172. package/dist/commands/placements/list.js +1 -1
  173. package/dist/commands/placements/update.js +1 -1
  174. package/dist/commands/products/create.js +1 -1
  175. package/dist/commands/products/get.js +1 -1
  176. package/dist/commands/products/list.js +1 -1
  177. package/dist/commands/products/update.js +1 -1
  178. package/dist/commands/segments/get.js +1 -1
  179. package/dist/commands/segments/list.js +1 -1
  180. package/dist/lib/config.d.ts +1 -1
  181. package/dist/sdk/adapty/auth/model.d.ts +0 -1
  182. package/dist/sdk/adapty/errors.d.ts +4 -0
  183. package/dist/sdk/adapty/errors.js +25 -0
  184. package/dist/sdk/adapty/index.d.ts +5 -0
  185. package/dist/sdk/adapty/index.js +29 -3
  186. package/dist/sdk/adapty/migrations/action.d.ts +15 -0
  187. package/dist/sdk/adapty/migrations/action.js +17 -0
  188. package/dist/sdk/adapty/migrations/close.d.ts +16 -0
  189. package/dist/sdk/adapty/migrations/close.js +7 -0
  190. package/dist/sdk/adapty/migrations/create.d.ts +16 -0
  191. package/dist/sdk/adapty/migrations/create.js +43 -0
  192. package/dist/sdk/adapty/migrations/index.d.ts +9 -0
  193. package/dist/sdk/adapty/migrations/index.js +4 -0
  194. package/dist/sdk/adapty/migrations/model.d.ts +100 -0
  195. package/dist/sdk/adapty/migrations/model.js +1 -0
  196. package/dist/sdk/adapty/migrations/resource.d.ts +27 -0
  197. package/dist/sdk/adapty/migrations/resource.js +52 -0
  198. package/dist/sdk/adapty/migrations/wait.d.ts +6 -0
  199. package/dist/sdk/adapty/migrations/wait.js +9 -0
  200. package/dist/sdk/core/session.d.ts +1 -1
  201. package/dist/sdk/core/session.js +4 -3
  202. package/oclif.manifest.json +1002 -397
  203. package/package.json +4 -1
  204. package/dist/cli/commands/auth/logout.js +0 -28
  205. package/dist/cli/flags.d.ts +0 -20
package/README.md CHANGED
@@ -33,8 +33,8 @@ Other auth commands:
33
33
  ```sh
34
34
  adapty auth whoami # verify token, show user info
35
35
  adapty auth status # show local auth state
36
- adapty auth logout # clear stored token (local only)
37
- adapty auth revoke # revoke active token and clear any matching stored session
36
+ adapty auth logout # clear stored credentials and migration selection (local only)
37
+ adapty auth revoke # revoke active token and clear matching credentials and selection
38
38
  ```
39
39
 
40
40
  `auth revoke` uses `ADAPTY_TOKEN` when set, otherwise the stored token. A different token in the
@@ -42,6 +42,14 @@ session file is preserved. After revoking an environment token, unset `ADAPTY_TO
42
42
  stored session will then become active again. With no token, revoke succeeds without a request
43
43
  and returns `{"status":"not_authenticated"}` under `--json`.
44
44
 
45
+ `auth logout` removes the saved migration selection even without stored credentials or with a
46
+ malformed context. `auth revoke` removes selection only after server success and only when it
47
+ belongs to the revoked token. Failed revocation preserves both files. Both cleanup operations
48
+ are attempted independently; incomplete cleanup returns exit 1 (`auth_cleanup_failed`). If the
49
+ server already revoked the token, the error says so: fix local files without repeating revocation.
50
+ Environment variables remain in the parent shell; unset `ADAPTY_TOKEN` and `ADAPTY_MIGRATION`
51
+ there when needed.
52
+
45
53
  ## Commands
46
54
 
47
55
  All resource commands require `--app APP_ID` (UUID). Use `adapty apps list` to find your app ID.
@@ -105,6 +113,142 @@ adapty access-levels create --app UUID [flags]
105
113
  adapty access-levels update --app UUID ACCESS_LEVEL_ID [flags]
106
114
  ```
107
115
 
116
+ ### Migrations
117
+
118
+ Manage migrations into Adapty: catalog, transactions and store events. The server provides the
119
+ steps, available actions and input schemas; use the current response to choose what to do next.
120
+
121
+ #### Start or find a migration
122
+
123
+ ```sh
124
+ adapty migrations list
125
+ adapty migrations create --name "Acme Fitness" --json
126
+ ```
127
+
128
+ `--name` starts a catalog migration into a new Adapty app. For an existing app, choose a flow and
129
+ its app from `list` and pass both flags (replace `FLOW` and `APP_ID` with those values):
130
+
131
+ ```sh
132
+ adapty migrations create --flow FLOW --app APP_ID --json
133
+ ```
134
+
135
+ These are alternative creation modes: `--name` cannot be combined with `--flow` or `--app`.
136
+ Creation starts the flow and saves it as current; the returned JSON contains its ID in `migration.id`.
137
+ Use `--no-select` to create without changing the saved selection. If creation succeeds but saving
138
+ fails, the command still succeeds and prints a warning with the explicit continuation command.
139
+
140
+ Commands choose a migration in this order: `-m, --migration`, non-empty `ADAPTY_MIGRATION`,
141
+ then the saved selection for the current token. Explicit IDs do not change the saved selection.
142
+ Replace `mig_7x2` below with an ID from `create` or `list`; agents should pass `-m` explicitly.
143
+
144
+ #### Manage a saved selection
145
+
146
+ ```sh
147
+ adapty migrations use mig_7x2
148
+ adapty migrations current --json
149
+ adapty migrations unuse
150
+ ```
151
+
152
+ `use` verifies access through the API, then saves the returned ID for the current token.
153
+ `current` reads the effective selection locally: `ADAPTY_MIGRATION` takes precedence over the
154
+ saved context. `unuse` removes the saved selection without authentication; an environment override
155
+ must be unset in your shell. Changing tokens makes the previous token's selection inapplicable.
156
+
157
+ The selection is shared across terminals and survives restarting the CLI. After `use` or `create`,
158
+ you can run `adapty migrations status`, `steps`, `show`, `run` or `close` without `-m`.
159
+ An operation keeps its initially selected ID even if another terminal changes the selection.
160
+ `run` and `close` print the target on stderr before a mutation that uses saved selection.
161
+ Scripts should pass explicit IDs and use `create --no-select` to preserve the shared default.
162
+
163
+ #### Inspect and run an action
164
+
165
+ ```sh
166
+ adapty migrations status -m mig_7x2 --json
167
+ adapty migrations steps -m mig_7x2
168
+ adapty migrations show -m mig_7x2
169
+ ```
170
+
171
+ `steps` shows the checklist. `show` lists readable resource names; pass one as `RESOURCE` to read
172
+ its data. Choose `ACTION_ID` from `next_actions` or `available_actions` in the status response.
173
+ For an input action, read its `reads` resources and prepare a JSON object matching `input_schema`:
174
+
175
+ ```sh
176
+ adapty migrations show RESOURCE -m mig_7x2
177
+ adapty migrations run ACTION_ID -m mig_7x2 --input-file ./decisions.json --json
178
+ ```
179
+
180
+ Alternatively, use `--input-file -` with stdin (`< ./decisions.json`), or `--input '{}'` if the
181
+ schema allows an empty object. Omitting input also sends `{}`. Use only one input option.
182
+
183
+ Read the action's full `confirm` text in `status` or `status --json` before adding `--yes`. Input actions
184
+ requiring confirmation exit **6** without it; there is no interactive prompt. The CLI reads
185
+ status before refusing, but does not send the action request. After an action, check status again.
186
+ After `revision_conflict`, read status and review the current action, input and confirmation before retrying.
187
+
188
+ For an `external` action, complete the step in the browser, then check status:
189
+
190
+ ```sh
191
+ adapty migrations run ACTION_ID -m mig_7x2 --no-browser
192
+ ```
193
+
194
+ `--no-browser` prints the action details and link. By default, the browser opens in an interactive
195
+ terminal; pipes and `--json` require `--open`. `BROWSER=none` disables opening, and only HTTPS
196
+ links are supported. External actions do not send an action request; their JSON response reflects
197
+ the migration before the browser step. Passing `--input` or `--input-file` to an external action
198
+ returns a usage error (exit 2). File uploads are marked unsupported in `status`: use the dashboard or an
199
+ offered Cloud Export action. `list --all` is also unsupported.
200
+
201
+ #### Wait for a change or close a migration
202
+
203
+ ```sh
204
+ adapty migrations status -m mig_7x2 --wait --timeout 5m --json
205
+ ```
206
+
207
+ `--wait` returns when the revision changes, the state is no longer `running`, or the polling budget
208
+ cannot accommodate another pause. `--timeout` requires `--wait`: default 120s, range 1–600s,
209
+ with formats such as `300`, `300s` or `5m`. In-flight requests and retries may take longer.
210
+ Exit **0** means the request succeeded, even when the migration is still running or has failed;
211
+ inspect `migration.state`. Progress goes to stderr; Ctrl+C exits **130**.
212
+
213
+ To permanently mark a migration as completed:
214
+
215
+ ```sh
216
+ adapty migrations close -m mig_7x2 --outcome finish --yes
217
+ ```
218
+
219
+ To abandon it instead:
220
+
221
+ ```sh
222
+ adapty migrations close -m mig_7x2 --outcome cancel --yes
223
+ ```
224
+
225
+ Both outcomes require `--yes`; there is no confirmation prompt.
226
+
227
+ #### JSON output
228
+
229
+ With `--json`, `list` returns `{items, available}`. All other migration commands return the full
230
+ migration response (the envelope):
231
+
232
+ | Data | JSON field |
233
+ | --- | --- |
234
+ | Migration ID, state and revision | `migration` |
235
+ | Next and optional actions | `next_actions`, `available_actions` |
236
+ | Checklist | `steps` |
237
+ | Readable resource names | `resources` |
238
+ | Data from `show RESOURCE` | `result` (may be `null`) |
239
+
240
+ Without `--json`, `show RESOURCE` prints just the resource data as JSON, or a message if empty.
241
+ Wizard Service errors preserve `detail`, `fields`, `next_step`, `request_id`, `retryable` and
242
+ `retry_after_seconds` under `error` in JSON when supplied. Human output includes details, field
243
+ errors, the next step and request ID. Retry metadata in the response does not change automatic retry behavior.
244
+
245
+ To extract specific fields from the full response, these examples require the separate `jq` utility:
246
+
247
+ ```sh
248
+ adapty migrations steps -m mig_7x2 --json | jq '.steps'
249
+ adapty migrations show RESOURCE -m mig_7x2 --json | jq '.result'
250
+ ```
251
+
108
252
  ### Apple Search Ads
109
253
 
110
254
  Apple Search Ads commands live under `adapty asa` and talk to the ASA service rather than the Developer
@@ -457,6 +601,7 @@ the flags and the size ceiling.
457
601
  | `ADAPTY_TOKEN` | Override stored auth token |
458
602
  | `ADAPTY_API_URL` | Override Developer API base URL (default: `https://api-admin.adapty.io/api/v1/developer`) |
459
603
  | `ADAPTY_ASA_API_URL` | Override Apple Search Ads base URL (default: `https://api-asa-admin.adapty.io/api/v1/cli`) |
604
+ | `ADAPTY_MIGRATION` | Migration id used by `adapty migrations` when `-m` is not given |
460
605
  | `ADAPTY_ATTRIBUTION_API_URL` | Override attribution base URL (default: `https://api-ua.adapty.io/api/v1/cli`) |
461
606
  | `ADAPTY_APP_URL` | Override dashboard base URL (default: `https://app.adapty.io`). Used by `flows config preview` for the fixed `/flow-preview` route, and by `auth login` to keep the verification link on that host |
462
607
 
@@ -1,6 +1,6 @@
1
1
  import { BaseCommand } from '../base-command.js';
2
2
  import type { Adapty } from '../../../sdk/adapty/index.js';
3
- import type { AuthenticatedSession } from '../session.js';
3
+ import type { AuthenticatedSession, ResolvedSession } from '../session.js';
4
4
  /**
5
5
  * Commands that require a token; auth commands other than `whoami` also work without one.
6
6
  *
@@ -10,8 +10,9 @@ import type { AuthenticatedSession } from '../session.js';
10
10
  */
11
11
  export declare abstract class AdaptyCommand extends BaseCommand {
12
12
  #private;
13
- static enableJsonFlag: boolean;
14
13
  protected get adapty(): Adapty;
14
+ /** Effective session without enforcing auth, for local input resolution before SDK access. */
15
+ protected get resolvedSession(): ResolvedSession;
15
16
  /** Narrowed once here, so nothing downstream re-checks the token. */
16
17
  protected get session(): AuthenticatedSession;
17
18
  protected init(): Promise<void>;
@@ -10,27 +10,30 @@ import { openSession } from './openSession.js';
10
10
  * branch into all 75 commands, and one of them would forget it.
11
11
  */
12
12
  export class AdaptyCommand extends BaseCommand {
13
- // Keep one own static so oclif's manifest cache walks through this intermediate class and
14
- // includes statics inherited from BaseCommand.
15
- static enableJsonFlag = true;
16
13
  #adapty;
17
14
  #resolved;
18
15
  get adapty() {
19
16
  this.#adapty ??= build(this.session, {
20
17
  config: this.config,
18
+ interactive: this.interactive,
21
19
  signal: this.signal,
22
20
  warn: message => this.warn(message),
23
21
  });
24
22
  return this.#adapty;
25
23
  }
26
- /** Narrowed once here, so nothing downstream re-checks the token. */
27
- get session() {
24
+ /** Effective session without enforcing auth, for local input resolution before SDK access. */
25
+ get resolvedSession() {
28
26
  const resolved = this.#resolved;
29
27
  if (resolved === undefined) {
30
28
  // A bug, not a user error, and deliberately not an SdkError: it lands as exit 1 with a
31
29
  // stack instead of passing for a normal scenario.
32
30
  throw new Error('session is available only after init()');
33
31
  }
32
+ return resolved;
33
+ }
34
+ /** Narrowed once here, so nothing downstream re-checks the token. */
35
+ get session() {
36
+ const resolved = this.resolvedSession;
34
37
  if (resolved.token === undefined) {
35
38
  throw new AuthRequiredError('missing');
36
39
  }
@@ -1,5 +1,10 @@
1
1
  import type { Adapty } from '../../../sdk/adapty/index.js';
2
2
  import type { CommandContext } from '../base-command.js';
3
3
  import type { ResolvedSession } from '../session.js';
4
+ /** The Adapty sdk also tells the server whether a person is watching (`X-Adapty-Interactive`). */
5
+ type AdaptyCommandContext = CommandContext & {
6
+ interactive: boolean;
7
+ };
4
8
  /** Shared by authenticated commands and login/revoke, which can run without a token. */
5
- export declare const build: (session: ResolvedSession, context: CommandContext) => Adapty;
9
+ export declare const build: (session: ResolvedSession, context: AdaptyCommandContext) => Adapty;
10
+ export {};
@@ -3,6 +3,7 @@ import { createAdapty } from '../../../sdk/adapty/index.js';
3
3
  /** Shared by authenticated commands and login/revoke, which can run without a token. */
4
4
  export const build = (session, context) => createAdapty({
5
5
  baseUrl: session.apiUrl,
6
+ interactive: context.interactive,
6
7
  onRetry: ({ attempt, delayMs }) => {
7
8
  context.warn(`Request failed, retrying in ${delayMs / 1000}s (attempt ${attempt + 1})`);
8
9
  },
@@ -1,4 +1,5 @@
1
1
  export { AdaptyCommand } from './adapty-command.js';
2
2
  export { build } from './build.js';
3
+ export { MigrationCommand } from './migration-command.js';
3
4
  export { openSession, resolveSession } from './openSession.js';
4
5
  export type { AuthenticatedSession, ResolvedSession } from '../session.js';
@@ -1,3 +1,4 @@
1
1
  export { AdaptyCommand } from './adapty-command.js';
2
2
  export { build } from './build.js';
3
+ export { MigrationCommand } from './migration-command.js';
3
4
  export { openSession, resolveSession } from './openSession.js';
@@ -0,0 +1,11 @@
1
+ import { AdaptyCommand } from './adapty-command.js';
2
+ import type { CurrentMigration } from '../../context/migration/index.js';
3
+ /**
4
+ * `AdaptyCommand` plus the saved migration selection. One topic out of ten stays a base of its own
5
+ * rather than a getter on `AdaptyCommand`, where all 75 commands would pay for it.
6
+ */
7
+ export declare abstract class MigrationCommand extends AdaptyCommand {
8
+ #private;
9
+ /** A getter, not a field: field initializers run in the constructor, before init() has a session. */
10
+ protected get currentMigration(): CurrentMigration;
11
+ }
@@ -0,0 +1,17 @@
1
+ import { openCurrentMigration } from '../../context/migration/index.js';
2
+ import { AdaptyCommand } from './adapty-command.js';
3
+ /**
4
+ * `AdaptyCommand` plus the saved migration selection. One topic out of ten stays a base of its own
5
+ * rather than a getter on `AdaptyCommand`, where all 75 commands would pay for it.
6
+ */
7
+ export class MigrationCommand extends AdaptyCommand {
8
+ #current;
9
+ /** A getter, not a field: field initializers run in the constructor, before init() has a session. */
10
+ get currentMigration() {
11
+ this.#current ??= openCurrentMigration({
12
+ configDir: this.config.configDir,
13
+ session: this.resolvedSession,
14
+ });
15
+ return this.#current;
16
+ }
17
+ }
@@ -18,6 +18,12 @@ export declare abstract class BaseCommand extends Command {
18
18
  #private;
19
19
  /** --json is a property of the CLI, not a per-command opt-in; `auth login` opts back out. */
20
20
  static enableJsonFlag: boolean;
21
+ /**
22
+ * Whether a person is watching this run. --json means a program is reading, even from a
23
+ * terminal, and a pipe means one is reading whatever the flags say. The sdk sends the answer
24
+ * as `X-Adapty-Interactive`; `migrations run` asks it before opening a browser.
25
+ */
26
+ protected get interactive(): boolean;
21
27
  protected get signal(): AbortSignal;
22
28
  protected catch(error: Error): Promise<unknown>;
23
29
  protected toErrorJson(error: unknown): {
@@ -1,5 +1,11 @@
1
1
  import { Command } from '@oclif/core';
2
2
  import { CliError, toCliError } from '../errors.js';
3
+ /**
4
+ * On a pipe Node leaves `isTTY` absent, not false, while @types/node promises a boolean. Taken at
5
+ * its word, a piped run answers `undefined` — a third answer to a two-answer question. The
6
+ * parameter type is the one place to say so.
7
+ */
8
+ const attachedToTerminal = (stream) => stream.isTTY === true;
3
9
  /**
4
10
  * What every command gets and nothing more: the output channel, cancellation, the single place
5
11
  * where sdk errors become CLI errors. Product SDKs and sessions belong to their adapters.
@@ -16,6 +22,14 @@ export class BaseCommand extends Command {
16
22
  #onSigint = () => {
17
23
  this.#abort.abort();
18
24
  };
25
+ /**
26
+ * Whether a person is watching this run. --json means a program is reading, even from a
27
+ * terminal, and a pipe means one is reading whatever the flags say. The sdk sends the answer
28
+ * as `X-Adapty-Interactive`; `migrations run` asks it before opening a browser.
29
+ */
30
+ get interactive() {
31
+ return attachedToTerminal(process.stdout) && !this.jsonEnabled();
32
+ }
19
33
  get signal() {
20
34
  return this.#abort.signal;
21
35
  }
@@ -5,7 +5,7 @@ export default class AppsCreate extends AdaptyCommand {
5
5
  static examples: string[];
6
6
  static flags: {
7
7
  title: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
8
- platform: import("@oclif/core/interfaces").OptionFlag<("ios" | "android")[], import("@oclif/core/interfaces").CustomOptions>;
8
+ platform: import("@oclif/core/interfaces").OptionFlag<("android" | "ios")[], import("@oclif/core/interfaces").CustomOptions>;
9
9
  'apple-bundle-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
10
10
  'google-bundle-id': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
11
  };
@@ -1,5 +1,5 @@
1
1
  import { AdaptyCommand } from '../../base/adapty/index.js';
2
- import { appIdArg } from '../../flags.js';
2
+ import { appIdArg } from '../../input/app.js';
3
3
  import { renderRecord } from '../../views/record.js';
4
4
  export default class AppsGet extends AdaptyCommand {
5
5
  static args = { ...appIdArg };
@@ -1,5 +1,5 @@
1
1
  import { AdaptyCommand } from '../../base/adapty/index.js';
2
- import { pageParams, paginationFlags } from '../../flags.js';
2
+ import { pageParams, paginationFlags } from '../../input/pagination.js';
3
3
  import { renderPage } from '../../views/list.js';
4
4
  export default class AppsList extends AdaptyCommand {
5
5
  static description = 'List Adapty apps';
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
2
2
  import { validateUpdateApp } from '../../../sdk/adapty/apps/index.js';
3
3
  import { assertValid } from '../../../sdk/core/validation.js';
4
4
  import { AdaptyCommand } from '../../base/adapty/index.js';
5
- import { appIdArg } from '../../flags.js';
5
+ import { appIdArg } from '../../input/app.js';
6
6
  import { renderRecord } from '../../views/record.js';
7
7
  export default class AppsUpdate extends AdaptyCommand {
8
8
  static args = { ...appIdArg };
@@ -0,0 +1,18 @@
1
+ import { AttributionCommand } from '../../../base/attribution/index.js';
2
+ import type { ReportFilter, ReportResponse, ReportSort } from '../../../../sdk/attribution/index.js';
3
+ export default class AttributionReport extends AttributionCommand {
4
+ static description: string;
5
+ static examples: string[];
6
+ static flags: {
7
+ sort: import("@oclif/core/interfaces").OptionFlag<ReportSort | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
+ 'revenue-basis': import("@oclif/core/interfaces").OptionFlag<"gross" | "proceeds" | "net" | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
+ metrics: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
10
+ 'group-by': import("@oclif/core/interfaces").OptionFlag<("store" | "date" | "campaign" | "adset" | "ad" | "keyword" | "channel" | "country")[], import("@oclif/core/interfaces").CustomOptions>;
11
+ granularity: import("@oclif/core/interfaces").OptionFlag<"day" | "week" | "month" | "quarter" | "year" | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
+ filter: import("@oclif/core/interfaces").OptionFlag<ReportFilter[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
+ 'date-from': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
+ 'date-to': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
+ app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
+ };
17
+ run(): Promise<ReportResponse>;
18
+ }
@@ -0,0 +1,93 @@
1
+ import { Flags } from '@oclif/core';
2
+ import { granularities, reportGroupBy, sortDirections, validateReport } from '../../../../sdk/attribution/index.js';
3
+ import { assertValid } from '../../../../sdk/core/validation.js';
4
+ import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../../base/attribution/index.js';
5
+ import { usageError } from '../../../errors.js';
6
+ import { appIdFlag } from '../../../input/app.js';
7
+ import { renderReport } from './lib/render.js';
8
+ /** A comma not preceded by a backslash: `\,` keeps a comma inside a value, as oclif's own delimiter does. */
9
+ const VALUE_SEPARATOR = /(?<!\\),/;
10
+ /** `dimension=value[,value]`: one value filters by equality, several by any of them. */
11
+ const parseFilter = (input) => {
12
+ const separator = input.indexOf('=');
13
+ const dimension = separator === -1 ? '' : input.slice(0, separator).trim();
14
+ const values = separator === -1
15
+ ? []
16
+ : input.slice(separator + 1).split(VALUE_SEPARATOR).map(value => value.replaceAll('\\,', ',').trim())
17
+ .filter(value => value !== '');
18
+ return dimension === '' || values.length === 0
19
+ ? Promise.reject(usageError(`Expected dimension=value[,value], got "${input}".`))
20
+ : Promise.resolve({ dimension, values });
21
+ };
22
+ const isDirection = (value) => sortDirections.includes(value);
23
+ /** `field[:asc|desc]`, ascending when the direction is left out. */
24
+ const parseSort = (input) => {
25
+ const separator = input.lastIndexOf(':');
26
+ const field = (separator === -1 ? input : input.slice(0, separator)).trim();
27
+ const direction = separator === -1 ? 'asc' : input.slice(separator + 1).trim();
28
+ if (field === '') {
29
+ return Promise.reject(usageError(`Expected field[:asc|desc], got "${input}".`));
30
+ }
31
+ return isDirection(direction)
32
+ ? Promise.resolve({ direction, field })
33
+ : Promise.reject(usageError(`The sort direction must be asc or desc, got "${direction}".`));
34
+ };
35
+ export default class AttributionReport extends AttributionCommand {
36
+ static description = 'Run an Attribution report for an app: metrics over a period, grouped by dimensions';
37
+ static examples = [
38
+ '<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign',
39
+ '<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend --group-by date --granularity week --filter country=US,GB --sort spend:desc',
40
+ '<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign --json',
41
+ ];
42
+ // Input shape here; the rules (dates in order, a granularity exactly when grouping by date) live
43
+ // in sdk/attribution/report.ts, and which names exist is the backend catalog's knowledge.
44
+ static flags = {
45
+ ...appIdFlag,
46
+ ...periodFlags,
47
+ 'metrics': Flags.string({
48
+ delimiter: ',',
49
+ description: 'Metric names, as `adapty attribution metrics` lists them (repeatable or comma-separated)',
50
+ multiple: true,
51
+ required: true,
52
+ }),
53
+ 'group-by': Flags.option({
54
+ delimiter: ',',
55
+ description: 'Dimensions to group rows by (repeatable or comma-separated)',
56
+ multiple: true,
57
+ options: reportGroupBy,
58
+ required: true,
59
+ })(),
60
+ 'granularity': Flags.option({
61
+ description: 'Date bucket; required with --group-by date, and allowed only with it',
62
+ options: granularities,
63
+ })(),
64
+ 'filter': Flags.custom({
65
+ description: 'Keep rows whose dimension has one of the values: dimension=value[,value] (repeatable; `\\,` for a comma inside a value)',
66
+ multiple: true,
67
+ parse: parseFilter,
68
+ })(),
69
+ ...revenueBasisFlag,
70
+ 'sort': Flags.custom({
71
+ description: 'Sort rows by a requested metric or dimension: field[:asc|desc] (ascending by default)',
72
+ parse: parseSort,
73
+ })(),
74
+ };
75
+ async run() {
76
+ const { flags } = await this.parse(AttributionReport);
77
+ const input = {
78
+ appId: flags.app,
79
+ ...periodParams(flags),
80
+ filters: flags.filter,
81
+ granularity: flags.granularity,
82
+ groupBy: flags['group-by'],
83
+ metrics: flags.metrics.filter(metric => metric !== ''),
84
+ revenueBasis: flags['revenue-basis'],
85
+ sort: flags.sort,
86
+ };
87
+ // Before this.attribution: bad input is exit 2 even without a token
88
+ assertValid(validateReport(input));
89
+ const report = await this.attribution.report(input);
90
+ this.render(report, renderReport);
91
+ return report;
92
+ }
93
+ }
@@ -1,18 +1 @@
1
- import { AttributionCommand } from '../../../base/attribution/index.js';
2
- import type { ReportFilter, ReportResponse, ReportSort } from '../../../../sdk/attribution/index.js';
3
- export default class AttributionReport extends AttributionCommand {
4
- static description: string;
5
- static examples: string[];
6
- static flags: {
7
- sort: import("@oclif/core/interfaces").OptionFlag<ReportSort | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
- 'revenue-basis': import("@oclif/core/interfaces").OptionFlag<"gross" | "proceeds" | "net" | undefined, import("@oclif/core/interfaces").CustomOptions>;
9
- metrics: import("@oclif/core/interfaces").OptionFlag<string[], import("@oclif/core/interfaces").CustomOptions>;
10
- 'group-by': import("@oclif/core/interfaces").OptionFlag<("store" | "date" | "campaign" | "adset" | "ad" | "keyword" | "channel" | "country")[], import("@oclif/core/interfaces").CustomOptions>;
11
- granularity: import("@oclif/core/interfaces").OptionFlag<"day" | "week" | "month" | "quarter" | "year" | undefined, import("@oclif/core/interfaces").CustomOptions>;
12
- filter: import("@oclif/core/interfaces").OptionFlag<ReportFilter[] | undefined, import("@oclif/core/interfaces").CustomOptions>;
13
- 'date-from': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
- 'date-to': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
- app: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
16
- };
17
- run(): Promise<ReportResponse>;
18
- }
1
+ export { default } from './command.js';
@@ -1,93 +1 @@
1
- import { Flags } from '@oclif/core';
2
- import { granularities, reportGroupBy, sortDirections, validateReport } from '../../../../sdk/attribution/index.js';
3
- import { assertValid } from '../../../../sdk/core/validation.js';
4
- import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../../base/attribution/index.js';
5
- import { usageError } from '../../../errors.js';
6
- import { appIdFlag } from '../../../flags.js';
7
- import { renderReport } from './lib/render.js';
8
- /** A comma not preceded by a backslash: `\,` keeps a comma inside a value, as oclif's own delimiter does. */
9
- const VALUE_SEPARATOR = /(?<!\\),/;
10
- /** `dimension=value[,value]`: one value filters by equality, several by any of them. */
11
- const parseFilter = (input) => {
12
- const separator = input.indexOf('=');
13
- const dimension = separator === -1 ? '' : input.slice(0, separator).trim();
14
- const values = separator === -1
15
- ? []
16
- : input.slice(separator + 1).split(VALUE_SEPARATOR).map(value => value.replaceAll('\\,', ',').trim())
17
- .filter(value => value !== '');
18
- return dimension === '' || values.length === 0
19
- ? Promise.reject(usageError(`Expected dimension=value[,value], got "${input}".`))
20
- : Promise.resolve({ dimension, values });
21
- };
22
- const isDirection = (value) => sortDirections.includes(value);
23
- /** `field[:asc|desc]`, ascending when the direction is left out. */
24
- const parseSort = (input) => {
25
- const separator = input.lastIndexOf(':');
26
- const field = (separator === -1 ? input : input.slice(0, separator)).trim();
27
- const direction = separator === -1 ? 'asc' : input.slice(separator + 1).trim();
28
- if (field === '') {
29
- return Promise.reject(usageError(`Expected field[:asc|desc], got "${input}".`));
30
- }
31
- return isDirection(direction)
32
- ? Promise.resolve({ direction, field })
33
- : Promise.reject(usageError(`The sort direction must be asc or desc, got "${direction}".`));
34
- };
35
- export default class AttributionReport extends AttributionCommand {
36
- static description = 'Run an Attribution report for an app: metrics over a period, grouped by dimensions';
37
- static examples = [
38
- '<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign',
39
- '<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend --group-by date --granularity week --filter country=US,GB --sort spend:desc',
40
- '<%= config.bin %> attribution report --app APP_UUID --date-from 2026-08-01 --date-to 2026-08-31 --metrics spend,installs,d7_roas --group-by campaign --json',
41
- ];
42
- // Input shape here; the rules (dates in order, a granularity exactly when grouping by date) live
43
- // in sdk/attribution/report.ts, and which names exist is the backend catalog's knowledge.
44
- static flags = {
45
- ...appIdFlag,
46
- ...periodFlags,
47
- 'metrics': Flags.string({
48
- delimiter: ',',
49
- description: 'Metric names, as `adapty attribution metrics` lists them (repeatable or comma-separated)',
50
- multiple: true,
51
- required: true,
52
- }),
53
- 'group-by': Flags.option({
54
- delimiter: ',',
55
- description: 'Dimensions to group rows by (repeatable or comma-separated)',
56
- multiple: true,
57
- options: reportGroupBy,
58
- required: true,
59
- })(),
60
- 'granularity': Flags.option({
61
- description: 'Date bucket; required with --group-by date, and allowed only with it',
62
- options: granularities,
63
- })(),
64
- 'filter': Flags.custom({
65
- description: 'Keep rows whose dimension has one of the values: dimension=value[,value] (repeatable; `\\,` for a comma inside a value)',
66
- multiple: true,
67
- parse: parseFilter,
68
- })(),
69
- ...revenueBasisFlag,
70
- 'sort': Flags.custom({
71
- description: 'Sort rows by a requested metric or dimension: field[:asc|desc] (ascending by default)',
72
- parse: parseSort,
73
- })(),
74
- };
75
- async run() {
76
- const { flags } = await this.parse(AttributionReport);
77
- const input = {
78
- appId: flags.app,
79
- ...periodParams(flags),
80
- filters: flags.filter,
81
- granularity: flags.granularity,
82
- groupBy: flags['group-by'],
83
- metrics: flags.metrics.filter(metric => metric !== ''),
84
- revenueBasis: flags['revenue-basis'],
85
- sort: flags.sort,
86
- };
87
- // Before this.attribution: bad input is exit 2 even without a token
88
- assertValid(validateReport(input));
89
- const report = await this.attribution.report(input);
90
- this.render(report, renderReport);
91
- return report;
92
- }
93
- }
1
+ export { default } from './command.js';
@@ -2,7 +2,7 @@ import { Flags } from '@oclif/core';
2
2
  import { validateValues } from '../../../sdk/attribution/index.js';
3
3
  import { assertValid } from '../../../sdk/core/validation.js';
4
4
  import { AttributionCommand, periodFlags, periodParams, revenueBasisFlag } from '../../base/attribution/index.js';
5
- import { appIdFlag } from '../../flags.js';
5
+ import { appIdFlag } from '../../input/app.js';
6
6
  const describeItem = (item) => {
7
7
  if ('value' in item) {
8
8
  return item.value ?? '—';
@@ -14,11 +14,12 @@ export default class AuthLogin extends BaseCommand {
14
14
  await this.parse(AuthLogin);
15
15
  const session = await openSession(this.config);
16
16
  if (session.user !== undefined) {
17
- this.log(`Already authenticated as ${session.user.email}. Re-authenticating...`);
17
+ this.log(`Already authenticated as ${session.user.name}. Re-authenticating...`);
18
18
  }
19
19
  // No token yet: the same factory without one, which leaves only auth reachable
20
20
  const adapty = build({ ...session, token: undefined }, {
21
21
  config: this.config,
22
+ interactive: this.interactive,
22
23
  signal: this.signal,
23
24
  warn: message => this.warn(message),
24
25
  });
@@ -44,7 +45,7 @@ export default class AuthLogin extends BaseCommand {
44
45
  signal: this.signal,
45
46
  });
46
47
  await session.store.save({ token: issued.accessToken, user: issued.user });
47
- this.log(`\nAuthenticated as ${issued.user.email}`);
48
+ this.log(`\nAuthenticated as ${issued.user.name}`);
48
49
  this.log(`Session saved to ${session.store.path}`);
49
50
  }
50
51
  /** ADAPTY_APP_URL is the CLI's business: the sdk knows nothing about browsers or dashboards. */