Skip to content

Integrate & migrate commands - #5

Open
eandreeva-twr wants to merge 15 commits into
mainfrom
integrate-command-prototype
Open

Integrate & migrate commands#5
eandreeva-twr wants to merge 15 commits into
mainfrom
integrate-command-prototype

Conversation

@eandreeva-twr

Copy link
Copy Markdown

No description provided.

@eandreeva-twr
eandreeva-twr marked this pull request as draft July 24, 2026 06:40
Print a one-line notice after every run that sends the usage event,
stating what is collected and how to disable it (--no-telemetry,
ADAPTY_TELEMETRY_DISABLED=1, DO_NOT_TRACK=1). Nothing is sent or
printed when disabled; --copy runs never send anything.
Split drivers.ts into a driver registry (src/lib/agent/drivers/) with one
module per agent and shared runners for the two output protocols
(stream-json and plain text). Adds an interactive agent picker when
several agents are installed, derives --driver options and install/login
hints from the registry, and hardens the runners: idle-timeout kill,
NO_COLOR, bounded stdout tail, anchored [STATUS] parsing, and an
exit-code fallback for Cursor's variable result event.
- integrate now offers an interactive switch to `adapty migrate` when an
  existing billing SDK is detected (before the wizard, so no question runs
  twice); headless runs keep the informational banner
- telemetry disclosure moved to the start of the run, Superwall-style, and
  no longer repeats at the end
- without real store product IDs the agent now creates neither products nor
  paywall/placement - the full ready-to-run command sequence goes to
  ADAPTY_SETUP.md, with the placement ID in code matching those commands
- ADAPTY_SETUP.md items link Adapty docs only where a guide genuinely helps
  (store credentials, server notifications, flows), slugs via llms.txt
… client, telemetry

Unit/integration tests replacing the manual checklist's cheap tier:
platform detection fixtures (incl. iOS name extraction and Unity-over-gradle
precedence), billing detection (incl. the transitive pubspec.lock false
positive), prompt content for headless vs copy mode and the migrate mapping
rules, the RevenueCat client against a local mock API (pagination cursors,
429 retry, builder-paywall classification, incomplete-catalog degradation),
and telemetry gating. Shared useTmpDir fixture helper; token/key leak tests
assert on the real seams (PromptContext and the full catalog object).
The fixture used a hardcoded POSIX path, so on Windows CI the expectation
diverged from the dirname/join output (backslashes). Build both the input
and the expectation with node:path.
A staged, always-skippable interview: a which-stores select of mutually
exclusive combos (irrelevant stores hidden per platform, exhaustive
Record<Platform, stores> mapping), then per-product prompts - App Store ID,
Google Play ID (Enter reuses the App Store one, "-" = not in that store),
period from VALID_PERIODS (now exported from api-schemas), and base plan
only for non-lifetime Google Play subscriptions. Collected products render
into a <store_products> ground-truth prompt block; with them the agent
creates the full dashboard setup instead of deferring to ADAPTY_SETUP.md.

Review fixes baked in: cancel (Esc/Ctrl-C) aborts the command instead of
being folded into skip/defaults (text() now returns null on cancel);
go/no-go gates run before the interview so declined confirms don't discard
typed answers; whitespace IDs are rejected and uppercase Google Play IDs
warned (immutability makes typos unfixable); the create-now prompt branch
keeps the Google Play AAB prerequisite; migrate treats code-found SKUs as
authoritative and the user's list as a complement; the platform playbook is
prefetched during the questions to hide network latency.
@eandreeva-twr
eandreeva-twr marked this pull request as ready for review July 27, 2026 14:24
The notice used to print last, just before the run spinner, so it stayed
pinned above the progress line for the whole integration. Moved into the
wizard right after auth: said once among the other setup lines, it scrolls
away with them. --copy and --no-telemetry still print nothing (and --copy
sends nothing).

Also: auth login declared user.email as required, but the API does not
always return it, so a successful login greeted people with "Authenticated
as undefined". Fields are optional now, with a name fallback and a plain
"Authenticated" when neither is present.
The CLI prompt never said where in the app the paywall should live, so the
agent created a placement hardcoded as "main" and wired the paywall
wherever it happened to look. SKILL.md has a whole step for this (analyze
the project, offer the candidate spots, confirm) which headless runs never
saw - it is read only by interactive sessions, since the CLI appends only
references/<platform>.md.

Now the agent reads the project for the natural spots (onboarding, premium
feature entry points, settings/upgrade, locked content), names the
placement after the one it picks, shows the paywall from there, and records
the choice plus the other candidates in ADAPTY_SETUP.md so the user can
move it without hunting.
The mapping rules, the never-create list and the ADAPTY_SETUP.md contract
lived in a template literal here, duplicating what the skill now carries in
references/migration.md. Two copies of the same rules in two repos drift, and
the skill repo's drift cron cannot see inside TypeScript.

Worse, the copy here was the older one. The skill's spine has since gained
things this prompt never had, each of them fixing a measured failure: the
handoff contract that stops store reconnection and server-notification
rewiring being dropped, historical data import, the rule that an inferred
value is a placeholder in every ready-to-run command rather than a literal,
and the rule that code establishes what the app uses and never what exists in
the source's account. `adapty migrate` was producing migrations without any
of that.

skill-source.ts gains loadMigrationReference(source): the spine is required,
references/migration-<source>.md is optional since only RevenueCat ships one
today, and a 404 or ENOENT for it means "use the spine's unknown-source path"
rather than an error. Anything else still throws, so a network failure is
never mistaken for an absent file.

What stays here is only what this CLI knows and the skill cannot: which
source the user picked, whether an RC catalog was actually fetched, and that
--rc-key can automate the dashboard comparison for RevenueCat specifically.

Verified against a local skill checkout via ADAPTY_SKILL_DIR: the spine and
the RevenueCat file both land, the four rules above are present, no
<mapping_rules> block remains, and the platform playbook still comes last.
Migrate prompts run 55-67k chars (~14-17k tokens) depending on platform,
about double the integrate prompt.
…totype

# Conflicts:
#	src/commands/products/create.ts
Finding no agent used to end the command: we printed install hints for five
CLIs and quit, throwing away the answers the user had already given. But most
people run their agent inside an editor - Cursor without cursor-agent, Copilot
in VS Code, Windsurf - so "install one of these and start over" is the wrong
answer for the largest cohort we have.

The wizard now decides who does the work rather than which driver to use:
resolveExecution() returns copyOnly + installSkill, and offerAgentlessPath()
asks whether to put the prompt on the clipboard and whether to install the
Adapty skill into the agent they actually use. Accepting continues the same
wizard run, so the prompt still carries the app ID and SDK key; the skill is
installed after the clipboard, since that install can take a minute.

Commands now branch on setup.copyOnly instead of the --copy flag, which is
what makes the switch safe mid-run: it also disables the go/no-go gate, the
dirty-tree gate, and telemetry. Headless runs keep the old message - there is
nobody there to take a clipboard.
The Flow Builder branch told the agent to create the placement and attach a
flow to it in the dashboard afterwards. That cannot work: a placement carries
a type - flow, paywall, or onboarding - fixed at creation, this CLI can only
create paywall placements (an audience entry requires a paywall_id), and a
developer ID can never be changed or reused. So the placement the agent
created burned the exact ID it had just written into the code, and the user
had to invent another one and edit the code to match.

The migration playbook already carries this rule from the other direction: a
source paywall built in a visual builder gets nothing created, because paywall
and flow placements share one ID namespace. The greenfield path contradicted
it - on the default, recommended approach, and only for users who supplied
real store IDs, since the branch runs after products are created.

Now nothing is created for Flow Builder, and ADAPTY_SETUP.md carries the
dashboard steps with the developer ID the code already uses. The deferred
sequence for unknown store IDs loses its paywalls/placements commands on this
path too - those were the same mistake, one step later.
An agent rewrites files all over the project, and until now it did that on
whatever branch the user happened to be on. Migrate warned about a dirty tree,
which helps read the diff but does nothing for undoing it; integrate did not
even do that.

Both commands now create `adapty-integrate` / `adapty-migrate` before the
agent starts, suffixing the name when a previous run already took it. It is
not a question: the branch costs the user nothing, its absence costs them a
lot, and `git switch -` is the undo we could not otherwise offer. The closing
line names the branch alongside the `git diff` hint.

No git at all is the one case worth stopping for - there is then no way to see
or revert what changed - so that asks, defaulting to no. Headless runs get the
warning and continue, having nobody to ask.

The git plumbing moves into src/lib/project/git.ts, taking migrate's inlined
dirty-tree check with it, and gets tests against real repositories: repo
detection, dirty detection inside and outside a repo, name suffixing, and a
failed branch creation reported rather than thrown.
Users who created their app and migrated its entities in the dashboard
had no path to "just the code changes": the migrate prompt told the
agent to build the dashboard unconditionally, so it recreated products
(rejected - store IDs are immutable) and guessed placement developer
IDs (worse - a wrong one ships and silently returns nothing).

The CLI now snapshots the bound app (access levels, products, paywalls,
placements - the same four list endpoints) and settles the mode before
any agent launches. A populated app asks "use these or create what's
missing", naming the entities so the user can recognize their own
setup; --[no-]code-only overrides in either direction; headless runs
refuse rather than guess, since a wrong guess against a populated app
is unrecoverable while a refused run is cheap to restart. The refusal
is resolved outside the app-binding try/catch on purpose: its --copy
catch downgrades errors to warnings, and a copy prompt generated in
create mode against a populated app is exactly the damage this exists
to prevent.

In code-only mode the placement developer ID is also settled up front -
picked from the app when it can be, asked when it cannot (flow
placements are not in the endpoint yet) - and the prompt forbids
creating any entity, routing genuine gaps to ADAPTY_SETUP.md as
ready-to-run commands. Create mode gains a list-before-create rule,
which also makes a re-run after a half-finished session safe. The
store-product interview is skipped in code-only (Adapty already has
better IDs than the user can type), and --rc-key conflicts with
--code-only at parse time.
@eandreeva-twr
eandreeva-twr force-pushed the integrate-command-prototype branch from 6e1ec7a to 4ba961e Compare August 14, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant