@appeeky/spec 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/assets/cover.gif +0 -0
- package/LICENSE +110 -0
- package/README.md +483 -2
- package/dist/ads.d.ts +39 -0
- package/dist/ads.js +67 -0
- package/dist/appeeky-api.d.ts +62 -0
- package/dist/appeeky-api.js +240 -0
- package/dist/attribution-setup.d.ts +28 -0
- package/dist/attribution-setup.js +65 -0
- package/dist/attribution.d.ts +121 -0
- package/dist/attribution.js +170 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +4 -0
- package/dist/category.d.ts +71 -0
- package/dist/category.js +198 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +1387 -0
- package/dist/competitors.d.ts +74 -0
- package/dist/competitors.js +143 -0
- package/dist/control.d.ts +21 -0
- package/dist/control.js +53 -0
- package/dist/core.d.ts +15 -0
- package/dist/core.js +15 -0
- package/dist/coverage.d.ts +46 -0
- package/dist/coverage.js +115 -0
- package/dist/data.d.ts +48 -0
- package/dist/data.js +298 -0
- package/dist/diff.d.ts +11 -0
- package/dist/diff.js +33 -0
- package/dist/experiments.d.ts +115 -0
- package/dist/experiments.js +85 -0
- package/dist/files.d.ts +27 -0
- package/dist/files.js +129 -0
- package/dist/history.d.ts +43 -0
- package/dist/history.js +55 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +24 -0
- package/dist/limits.d.ts +15 -0
- package/dist/limits.js +30 -0
- package/dist/lint.d.ts +16 -0
- package/dist/lint.js +159 -0
- package/dist/loops.d.ts +37 -0
- package/dist/loops.js +197 -0
- package/dist/measure.d.ts +40 -0
- package/dist/measure.js +159 -0
- package/dist/metrics.d.ts +270 -0
- package/dist/metrics.js +250 -0
- package/dist/output.d.ts +21 -0
- package/dist/output.js +79 -0
- package/dist/project.d.ts +19 -0
- package/dist/project.js +35 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +71 -0
- package/dist/reviews.d.ts +130 -0
- package/dist/reviews.js +287 -0
- package/dist/schema.d.ts +519 -0
- package/dist/schema.js +218 -0
- package/dist/screenshots.d.ts +23 -0
- package/dist/screenshots.js +116 -0
- package/dist/setup.d.ts +45 -0
- package/dist/setup.js +226 -0
- package/dist/storefronts.d.ts +15 -0
- package/dist/storefronts.js +89 -0
- package/dist/stores/app-store.d.ts +33 -0
- package/dist/stores/app-store.js +159 -0
- package/dist/stores/google-play.d.ts +13 -0
- package/dist/stores/google-play.js +61 -0
- package/dist/stores/types.d.ts +34 -0
- package/dist/stores/types.js +2 -0
- package/dist/suggest.d.ts +73 -0
- package/dist/suggest.js +135 -0
- package/dist/text.d.ts +18 -0
- package/dist/text.js +55 -0
- package/dist/transport.d.ts +98 -0
- package/dist/transport.js +170 -0
- package/dist/verify.d.ts +70 -0
- package/dist/verify.js +134 -0
- package/dist/visibility.d.ts +149 -0
- package/dist/visibility.js +239 -0
- package/package.json +76 -4
- package/skills/appeeky-spec/SKILL.md +146 -0
- package/skills/appeeky-spec-experiments/SKILL.md +83 -0
- package/skills/appeeky-spec-localize/SKILL.md +52 -0
- package/skills/appeeky-spec-review/SKILL.md +75 -0
- package/skills/appeeky-spec-targets/SKILL.md +66 -0
- package/skills/appeeky-spec-visibility/SKILL.md +79 -0
|
Binary file
|
package/LICENSE
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-MIT
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Appeeky
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the MIT license that is effective on the second anniversary of the date we make
|
|
91
|
+
the Software available. On or after that date, you may use the Software under
|
|
92
|
+
the MIT license, in which case the following will apply:
|
|
93
|
+
|
|
94
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
95
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
96
|
+
the Software without restriction, including without limitation the rights to
|
|
97
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
98
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
99
|
+
so, subject to the following conditions:
|
|
100
|
+
|
|
101
|
+
The above copyright notice and this permission notice shall be included in all
|
|
102
|
+
copies or substantial portions of the Software.
|
|
103
|
+
|
|
104
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
105
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
106
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
107
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
108
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
109
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
110
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,484 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Appeeky Spec
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<img src="https://cdn.jsdelivr.net/npm/@appeeky/spec/.github/assets/cover.gif" alt="Animated pixel-art night sky over a hilltop: a puffin in a scarf hops between Grok, Claude Code, Codex and Cursor on the left and Gemini CLI, opencode, T3 Code and Zed on the right. Above them: App growth, run by your coding agent, the features with their logos and the places it runs, from GitHub Actions and Docker to Hetzner, DigitalOcean, AWS, Google Cloud, Railway, Fly.io and Render." width="100%" />
|
|
4
|
+
|
|
5
|
+
**App growth, run by your coding agent.**
|
|
6
|
+
|
|
7
|
+
Appeeky Spec puts your app's whole growth loop in one folder of your repo: the store listing and the searches it should win, reviews and competitors, what AI assistants say about the app, ads, experiments, and what every past change did. Your coding agent (Claude Code, Codex, Cursor, Gemini CLI and others) reads it and proposes the next change as a pull request. You merge it. The change is checked against the store rules, pushed to App Store Connect and Google Play, and measured on day 7 and day 14, and the result goes into a playbook the next change starts from.
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@appeeky/spec) [](https://github.com/appeeky/appeeky-spec/actions/workflows/ci.yml) [](LICENSE)
|
|
10
|
+
|
|
11
|
+
> **Status: 0.x.** Commands and file formats can still change between minor versions. See the [changelog](CHANGELOG.md).
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npx @appeeky/spec init # finds your app, pulls the listing, suggests targets, adds CI
|
|
17
|
+
npx skills add appeeky/appeeky-spec # teaches your coding agent the workflow
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then ask your agent something like *"we're launching in Germany next week"* or *"why did we drop for white noise?"*. It works with the same commands you can run yourself:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npx @appeeky/spec doctor # what is set up, what is missing, how to fix it
|
|
24
|
+
npx @appeeky/spec coverage # which searches each storefront is found for
|
|
25
|
+
npx @appeeky/spec check # store limits and ASO rules
|
|
26
|
+
npx @appeeky/spec diff # the spec against the live store
|
|
27
|
+
npx @appeeky/spec push --note "Lead with white noise in the US"
|
|
28
|
+
npx @appeeky/spec verify # day 7 and day 14: did it work?
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`pnpm dlx`, `yarn dlx` and `bunx` work the same way. Node 20 or later.
|
|
32
|
+
|
|
33
|
+
`init` with no options asks for the app's name, finds it on the App Store, checks the Google Play package, pulls every store it can reach, suggests five targets from what the app already ranks for, offers the GitHub workflows (`--ci`) and the agent skills, and ends on the first coverage table. With options (`--name`, `--app-store`, `--google-play`, `--pull`) it does the same without questions, for scripts. It also adds `.appeeky/` to `.gitignore`.
|
|
34
|
+
|
|
35
|
+
## What it does
|
|
36
|
+
|
|
37
|
+
| Feature | What you get |
|
|
38
|
+
| --- | --- |
|
|
39
|
+
| **Listing as code** | Name, subtitle, keywords, descriptions and what's new for every locale, as YAML in your repo. Changes go through pull requests like code. |
|
|
40
|
+
| **Checked before it ships** | Store limits, wasted keyword space, words repeated between name, subtitle and keyword field, Google Play title policy and screenshot sizes. |
|
|
41
|
+
| **Keyword coverage** | Which of your target searches each storefront indexes, weighted by popularity, with words to add and the spare space in secondary locales. |
|
|
42
|
+
| **Pushed to both stores** | App Store Connect and Google Play, with your own keys or through your Appeeky connection. Every push writes a release file; `revert` puts the old values back. |
|
|
43
|
+
| **Measured** | Day 7 and day 14: search ranks, conversion, downloads, revenue and ads, against storefronts the release did not change. The verdict goes into `appeeky/playbook.md`. |
|
|
44
|
+
| **What users and rivals say** | Reviews with a summary and drafted replies, competitors' listings and moves, and the category chart rank, one row a day. |
|
|
45
|
+
| **AI visibility** | Whether ChatGPT, Claude, Gemini and Perplexity name your app, who they name instead and the pages they cite. |
|
|
46
|
+
| **Ads and data** | Apple Search Ads, Meta and TikTok spend and installs, App Store Connect, Google Play and RevenueCat numbers as CSV your agent can read. |
|
|
47
|
+
| **A weekly proposal** | Once a week an agent opens a pull request with one listing change, for a store with no release still being measured. It can run on your Claude plan. |
|
|
48
|
+
|
|
49
|
+
## How a change goes live
|
|
50
|
+
|
|
51
|
+
```mermaid
|
|
52
|
+
flowchart LR
|
|
53
|
+
A["Your agent edits<br/>appeeky/store/"] --> B["Pull request<br/>check + coverage comment"]
|
|
54
|
+
B --> C{"You merge"}
|
|
55
|
+
C --> D["push to App Store Connect<br/>and Google Play"]
|
|
56
|
+
D --> E["verify on day 7 and 14<br/>with a control group"]
|
|
57
|
+
E --> F["playbook.md<br/>what worked"]
|
|
58
|
+
F --> A
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Nothing reaches the stores until a person merges. The merge is the approval.
|
|
62
|
+
|
|
63
|
+
## Where it runs
|
|
64
|
+
|
|
65
|
+
| Where | How |
|
|
66
|
+
| --- | --- |
|
|
67
|
+
| **GitHub Actions** | `init --ci` writes three workflows: a check on every pull request, a push on merge, and a daily run that measures and commits. See [In pull requests](#in-pull-requests). |
|
|
68
|
+
| **Your own server** | `docker compose up -d` on any VM: Hetzner, DigitalOcean, AWS, Google Cloud, Railway, Fly.io, Render or a machine in your office. See [Running without GitHub Actions](#running-without-github-actions). |
|
|
69
|
+
| **Your computer** | Every command runs locally with `npx`. `examples/weekly/` has a cron script for the weekly proposal. |
|
|
70
|
+
| **Other CI** | GitLab, Bitbucket or CircleCI: the same three triggers and commands. |
|
|
71
|
+
|
|
72
|
+
Powered by [Appeeky](https://appeeky.com). Works with your own store keys on your machine, or with your Appeeky connection when you'd rather not handle keys.
|
|
73
|
+
|
|
74
|
+
## Layout
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
appeeky.spec.yaml # app, stores, target keywords per storefront
|
|
78
|
+
appeeky/
|
|
79
|
+
store/
|
|
80
|
+
app-store/en-US.yaml # name, subtitle, keywords, promotionalText, description, whatsNew, URLs
|
|
81
|
+
google-play/en-US.yaml # title, shortDescription, fullDescription, video
|
|
82
|
+
releases/ # one file per push: what changed, where, why
|
|
83
|
+
reviews/ # customer reviews (JSONL per month), summary.md, replies/ drafted for review
|
|
84
|
+
competitors/ # one file per competitor: listing, ranks, recent reviews
|
|
85
|
+
visibility/<country>.yaml # what ChatGPT, Claude, Gemini and Perplexity answer: named or not, who instead, cited pages
|
|
86
|
+
category.csv # rank in the category chart, one row a day
|
|
87
|
+
playbook.md # what worked and what did not for this app
|
|
88
|
+
.appeeky/data/ # analytics, ads and revenue as CSV (gitignored; data.commit moves it to appeeky/data/)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
`appeeky.spec.yaml`:
|
|
92
|
+
|
|
93
|
+
```yaml
|
|
94
|
+
version: 1
|
|
95
|
+
app:
|
|
96
|
+
name: Sleep Sounds
|
|
97
|
+
primaryLocale: en-US
|
|
98
|
+
stores:
|
|
99
|
+
appStore:
|
|
100
|
+
appId: "1234567890"
|
|
101
|
+
via: auto # auto | local | hosted
|
|
102
|
+
googlePlay:
|
|
103
|
+
packageName: com.example.sleep
|
|
104
|
+
via: auto
|
|
105
|
+
targets: # the searches this app should be found for, per storefront
|
|
106
|
+
us: [sleep sounds, white noise, rain sounds, brown noise]
|
|
107
|
+
tr: [uyku sesleri, beyaz gürültü]
|
|
108
|
+
storefronts: # optional: which App Store listing locales a storefront indexes
|
|
109
|
+
us: [en-US, es-MX]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
A field you leave out of a listing file is not managed by the spec: it is never diffed or pushed. You can adopt the spec one field or one locale at a time.
|
|
113
|
+
|
|
114
|
+
## Commands
|
|
115
|
+
|
|
116
|
+
| Command | What it does |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `init` | Create `appeeky.spec.yaml` and `appeeky/`. `--pull` fetches the live listings right away. |
|
|
119
|
+
| `pull` | Write the live listings into `appeeky/store/` (overwrites local files). |
|
|
120
|
+
| `check` | Store limits, ASO rules and screenshot sizes. Exits 1 on errors (`--strict`: on warnings too). |
|
|
121
|
+
| `coverage` | Which target keywords each storefront indexes, plus tips for storefronts that read a second locale (the US also reads Spanish (Mexico)). |
|
|
122
|
+
| `suggest` | Words to add to a keyword field: missing target words first, then Appeeky keyword opportunities. `--apply <words…>` adds them. |
|
|
123
|
+
| `diff` | Field-level differences between the spec and the live store, and what cannot be pushed yet. |
|
|
124
|
+
| `push` | Check, diff, confirm, record search ranks, push, and write `appeeky/releases/<id>.yaml`. `--note` stores why, `--create-version 2.5.0` opens the next App Store version first. |
|
|
125
|
+
| `verify` | On day 7 and day 14 after a push, compare ranks with the ones recorded before it and write the verdict into the release. With `APPEEKY_API_KEY`, also conversion, downloads, revenue and ads (Apple Search Ads, Meta, TikTok). `--playbook` adds a line to `appeeky/playbook.md`. |
|
|
126
|
+
| `ads` | Spend, installs and cost per install of your paid channels over the last `--days` (default 30): Apple Search Ads, plus the Meta and TikTok campaigns under `ads:`. `--save` writes them to `appeeky/ads/<date>.yaml`. Needs `APPEEKY_API_KEY`. |
|
|
127
|
+
| `reviews pull\|list\|reply\|push` | Customer reviews from both stores into `appeeky/reviews/`, with `summary.md`. `reply <id> --text` drafts an answer in `appeeky/reviews/replies/` for a pull request; `push` sends the drafts. |
|
|
128
|
+
| `competitors discover\|pull` | `discover` lists apps like yours (`--add <n>` puts them in the spec). `pull` writes each competitor's listing, the searches it ranks for and its recent reviews to `appeeky/competitors/`. |
|
|
129
|
+
| `visibility init\|sync\|pull\|scan` | AI visibility: whether ChatGPT, Claude, Gemini and Perplexity name your app. `init` writes the intents and prompts Appeeky tracks into the spec, `sync` sends your edits back, `pull` writes the answers to `appeeky/visibility/`, `scan` asks the assistants now. Needs `APPEEKY_API_KEY`. |
|
|
130
|
+
| `category pull` | Record today's rank in the category chart (top 100) in `appeeky/category.csv`. App Store needs no key; Google Play needs `APPEEKY_API_KEY` and `category.googlePlay`. |
|
|
131
|
+
| `data pull` | App Store Connect, Google Play, Apple Search Ads, RevenueCat, Meta and TikTok numbers per day, and attribution by source, keyword and smart link, as CSV files with an index (`--days`, default 90). |
|
|
132
|
+
| `status` | Every change being measured, in one list: releases and experiments, the metric that decides each, the next check and the result so far. Flags checks that are late, releases measured over the same days, and results built on too few installs. Rewrites `appeeky/loops.md`. |
|
|
133
|
+
| `log` | Releases pushed from this spec. `--markdown` for changelogs and PR descriptions. |
|
|
134
|
+
| `revert <release>` | Put the values from before a release back into the spec. Then `push`. |
|
|
135
|
+
| `experiment add\|list\|close` | Track growth experiments outside the listing (screenshots, UGC, ads, pricing) with one metric and a check date. |
|
|
136
|
+
| `screenshots pull\|check` | Download the live App Store screenshots into `appeeky/screenshots/` and check their sizes. |
|
|
137
|
+
| `report` | Markdown for a pull request: listing changes, check results and coverage against `--base`. |
|
|
138
|
+
|
|
139
|
+
Global options: `--json` for machine-readable output, `--cwd <dir>`, `--via auto|local|hosted`.
|
|
140
|
+
|
|
141
|
+
## Connecting to the stores
|
|
142
|
+
|
|
143
|
+
Appeeky Spec talks to the stores through the Appeeky store CLIs, which expose the same operations locally and on Appeeky's hosted gateways:
|
|
144
|
+
|
|
145
|
+
| `via` | How | Needs |
|
|
146
|
+
| --- | --- | --- |
|
|
147
|
+
| `local` | Runs [`appstore`](https://github.com/appeeky/app-store-connect-cli) and [`gps`](https://github.com/appeeky/google-play-console-cli) on your machine with your own keys. Falls back to `npx` when they are not installed. | An App Store Connect API key (`appstore accounts add`) or a Google Play service account. |
|
|
148
|
+
| `hosted` | Calls `asc-api.appeeky.com` and `gps-api.appeeky.com`. Your store keys stay in Appeeky. | `APPEEKY_API_KEY` and a connected store in Appeeky. |
|
|
149
|
+
| `auto` | Local when the CLI is installed, hosted when `APPEEKY_API_KEY` is set, `npx` otherwise. | |
|
|
150
|
+
|
|
151
|
+
**Your own keys, saved once.** Both CLIs keep named accounts in your user config (`~/.config/appstore/`, `~/.config/gps/`), never in the repo. `init` offers to save them for you; by hand:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
npx -p @appeeky/app-store-connect-cli appstore accounts add my-team \
|
|
155
|
+
--key-id ABC123DEFG --issuer-id <issuer-id> --private-key-path ~/AuthKey_ABC123DEFG.p8 --default
|
|
156
|
+
npx -p @appeeky/google-play-store-cli gps accounts add my-co \
|
|
157
|
+
--credentials ~/play-service-account.json --default-package com.example.sleep --default
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
With several teams or clients, pick the account per app in the spec: `stores.appStore.account` and `stores.googlePlay.account`. In CI, pass the same keys as environment variables instead (`ASC_KEY_ID`, `ASC_ISSUER_ID`, `ASC_PRIVATE_KEY`, `GOOGLE_APPLICATION_CREDENTIALS`). The CLIs also work on their own, and as MCP servers for your agent (`appstore-mcp`, `gps-mcp`).
|
|
161
|
+
|
|
162
|
+
### What can change when
|
|
163
|
+
|
|
164
|
+
- **App Store:** name, subtitle, keywords, description, what's new and URLs change only with a new version. Create the version in App Store Connect (or `appstore versions create 2.5.0 --confirm`), then push. Promotional text changes on the live version at any time. `diff` marks changes that are blocked and says why.
|
|
165
|
+
- **Google Play:** listing changes go live without an app update. A new language needs a title, short description and full description.
|
|
166
|
+
|
|
167
|
+
## Measuring a release
|
|
168
|
+
|
|
169
|
+
`push` records the search rank of every target right before it changes anything. `verify` checks them again on day 7 and day 14:
|
|
170
|
+
|
|
171
|
+
- **won**: more targets rose than fell
|
|
172
|
+
- **lost**: one top-10 target or two top-20 targets fell out of the top 20 by 10+ places (consider `revert`)
|
|
173
|
+
- **mixed** or **flat** otherwise
|
|
174
|
+
|
|
175
|
+
**A control group.** Storefronts whose listing the release did not change are measured over the same days. A push to en-US changes the US store; the German store reads de-DE and did not change, but its ranks still move with the store's algorithm, the season and competitors. When the changed storefronts gained less than 2 places per target beyond the unchanged ones, a "won" becomes **mixed**: the store moved, not the listing. The check records both moves and the net (`control`).
|
|
176
|
+
|
|
177
|
+
**Normal moves.** Before comparing conversion, installs or revenue, `verify` reads the four windows before the push and learns how much each metric moves on its own. A change inside that range is **flat**, and the output says so: `downloads/day 100 → 120 (+20%) (moves ±41% on its own)`.
|
|
178
|
+
|
|
179
|
+
When no target moved (or the release has no targets), the outcomes decide instead: organic installs and their revenue with `attribution:`, otherwise store conversion, otherwise downloads, otherwise the category rank from `appeeky/category.csv`. Better is **won**, worse is **lost**, and the check says so (`decidedBy`).
|
|
180
|
+
|
|
181
|
+
Ranks come from the Appeeky API when `APPEEKY_API_KEY` is set (both stores), or from Apple's public search for the App Store. Run `verify --playbook` on a schedule (CI or cron) and the playbook fills itself.
|
|
182
|
+
|
|
183
|
+
With `APPEEKY_API_KEY`, `verify` also compares the days before and after the push for the data you connected in Appeeky: conversion, downloads and proceeds from App Store Connect or Google Play, revenue and trials from RevenueCat, installs and cost per install from Apple Search Ads, spend, installs and cost per install of the Meta and TikTok campaigns listed under `ads:` (see below), and how often AI assistants name the app (`ai_visibility`, see below). Ranks tell you whether more people find the app; these tell you whether more of them install and pay. Experiments on these metrics (`experiment add --metric trial_conversion`) measure their own baseline and result.
|
|
184
|
+
|
|
185
|
+
## Paid channels
|
|
186
|
+
|
|
187
|
+
Apple Search Ads is matched to the app in Appeeky, so it needs nothing in the spec. Meta and TikTok ad accounts usually promote more than one app, so list the campaigns that belong to this one:
|
|
188
|
+
|
|
189
|
+
```yaml
|
|
190
|
+
ads:
|
|
191
|
+
meta:
|
|
192
|
+
adAccount: act_1234567890 # optional: defaults to the account picked in Appeeky
|
|
193
|
+
campaigns: ["120210000000001"]
|
|
194
|
+
tiktok:
|
|
195
|
+
advertiser: "7000000000000" # optional: defaults to the advertiser picked in Appeeky
|
|
196
|
+
campaigns: ["1790000000000"]
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Connect Meta and TikTok in Appeeky first. Then:
|
|
200
|
+
|
|
201
|
+
- `appeeky-spec ads` prints spend, installs (conversions for TikTok) and cost per result per channel. `--save` keeps a copy in `appeeky/ads/` so the repo has the history next to the listing.
|
|
202
|
+
- `verify` compares those numbers before and after every push, so a listing change and an ad change are judged on the same days.
|
|
203
|
+
- Experiments can use `meta_cpi`, `meta_installs`, `tiktok_cpa`, `tiktok_conversions`, `cpa` or `ad_installs` as their metric: `experiment add --type ads --metric meta_cpi` takes its baseline from the last days and `verify` closes it.
|
|
204
|
+
|
|
205
|
+
## Reviews, competitors and data
|
|
206
|
+
|
|
207
|
+
Everything an agent needs to decide the next change, as plain files next to the listing. All three need `APPEEKY_API_KEY` and the stores connected in Appeeky.
|
|
208
|
+
|
|
209
|
+
**Reviews.** `reviews pull` writes every review to `appeeky/reviews/<store>/<YYYY-MM>.jsonl`, one JSON object per line (id, rating, title, body, territory or language, version, date, reply). Pulling again adds new reviews and replaces edited or answered ones. `summary.md` has the last 30 days against the 30 before, star counts, ratings per version, the low ratings still waiting for a reply, and the words users use that your listing does not: phrases that come up in three or more reviews and appear nowhere in any listing, with how many came from 1–3 star reviews. They are candidates for targets, or for the product. Replies go through review like the listing:
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
appeeky-spec reviews list --rating 1-2 --unanswered
|
|
213
|
+
appeeky-spec reviews reply 1234567 --text "Sorry about that. 3.1 fixes the crash on launch."
|
|
214
|
+
git checkout -b reviews/replies && git add appeeky/reviews && git commit -m "Reply to crash reports"
|
|
215
|
+
# after the pull request is approved
|
|
216
|
+
appeeky-spec reviews push
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
`verify` also compares the average rating and the share of 1–2 star reviews before and after each push, from the pulled files.
|
|
220
|
+
|
|
221
|
+
**Competitors.** List them in `appeeky.spec.yaml`, or find them with `competitors discover --add 3`:
|
|
222
|
+
|
|
223
|
+
```yaml
|
|
224
|
+
competitors:
|
|
225
|
+
- name: Calm Night
|
|
226
|
+
appStore: "1234567891"
|
|
227
|
+
googlePlay: com.calmnight.app
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
`competitors pull` writes `appeeky/competitors/calm-night.yaml`: title, rating, version and release notes, the first part of the description, the 25 searches it ranks best for in your first targets country, where it ranks for your own targets, and its recent low ratings. Each pull compares with the last one and adds what changed to `appeeky/competitors/changes.md`: a new title, a new version and its notes, a rewritten description, a climb into the top 10 for one of your targets. `status` warns a release being measured when a competitor changed something inside its window.
|
|
231
|
+
|
|
232
|
+
**A weekly proposal.** An agent can propose one listing change a week as a pull request, for a store with no release still being measured: `status --json` lists those stores as `free`, so a new change never mixes with an open one. [`examples/weekly/`](examples/weekly) has a GitHub Actions workflow, the prompt it gives the agent, and the same thing as a script for cron on a machine of your own. The agent reads `loops.md`, the playbook, competitor changes, reviews and your numbers, edits the listing, runs `check` and `diff`, and writes why in the pull request. Nothing reaches the stores until someone merges it.
|
|
233
|
+
|
|
234
|
+
**Data.** `data pull` writes CSV files per source, merged by date on every pull, with a `README.md` index:
|
|
235
|
+
|
|
236
|
+
| File | From |
|
|
237
|
+
| --- | --- |
|
|
238
|
+
| `app-store-analytics.csv`, `google-play-analytics.csv` | impressions, page views, downloads, conversion, proceeds, trials, subscriptions |
|
|
239
|
+
| `app-store-traffic-sources.csv` | where product page views came from |
|
|
240
|
+
| `app-store-sales.csv`, `app-store-countries.csv` | App Store Connect sales reports, by day and by country |
|
|
241
|
+
| `apple-search-ads.csv` | spend, impressions, taps, installs |
|
|
242
|
+
| `revenuecat.csv` | revenue, MRR, trials, new customers, trial conversion, churn, actives |
|
|
243
|
+
| `meta-ads.csv`, `tiktok-ads.csv` | the campaigns under `ads:`, per day |
|
|
244
|
+
|
|
245
|
+
They are your private business numbers, so they stay in `.appeeky/data/` (gitignored). Set `data: { commit: true }` to write them to `appeeky/data/` and version them with the spec, for example in a private repo.
|
|
246
|
+
|
|
247
|
+
No CI is needed: an agent runs these commands when it needs fresh numbers. To keep the files current on their own, run them on a schedule:
|
|
248
|
+
|
|
249
|
+
```yaml
|
|
250
|
+
# .github/workflows/appeeky-spec-daily.yml
|
|
251
|
+
on:
|
|
252
|
+
schedule: [{ cron: "0 6 * * *" }]
|
|
253
|
+
jobs:
|
|
254
|
+
refresh:
|
|
255
|
+
runs-on: ubuntu-latest
|
|
256
|
+
permissions: { contents: write }
|
|
257
|
+
steps:
|
|
258
|
+
- uses: actions/checkout@v4
|
|
259
|
+
- run: npx @appeeky/spec category pull && npx @appeeky/spec visibility pull && npx @appeeky/spec reviews pull && npx @appeeky/spec competitors pull && npx @appeeky/spec verify --playbook
|
|
260
|
+
env: { APPEEKY_API_KEY: "${{ secrets.APPEEKY_API_KEY }}" }
|
|
261
|
+
- run: git add appeeky && git commit -m "Daily category, AI visibility, reviews, competitors and verify" && git push || true
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
## AI visibility
|
|
265
|
+
|
|
266
|
+
People ask ChatGPT, Claude, Gemini and Perplexity for apps, not only the store. Appeeky AI Visibility asks them on a schedule, per storefront, and records whether they name your app. The questions live in the spec: an **intent** is what someone wants, its **prompts** are how they ask.
|
|
267
|
+
|
|
268
|
+
```yaml
|
|
269
|
+
aiVisibility:
|
|
270
|
+
countries: [us] # default: the first storefront under targets
|
|
271
|
+
intents:
|
|
272
|
+
us:
|
|
273
|
+
- label: fall asleep to rain sounds
|
|
274
|
+
prompts:
|
|
275
|
+
- what app plays rain sounds to help me sleep
|
|
276
|
+
- best free rain sounds app for iPhone
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
- `visibility init` writes the intents Appeeky already tracks into the spec. With none yet, Appeeky writes them from your listing; run `init` again a few minutes later.
|
|
280
|
+
- `visibility sync` sends the spec's intents and prompts to Appeeky. Ones only in Appeeky are listed, and archived only with `--prune`. `--dry-run` shows the plan.
|
|
281
|
+
- `visibility pull` writes `appeeky/visibility/us.yaml`. It shows each intent as `winning`, `weak` or `missing`, the share of answers that name you per assistant, who they name instead, the pages they cite (`yours: true` for your own site), and the fixes Appeeky suggests. What moved since the last pull goes to `appeeky/visibility/changes.md`.
|
|
282
|
+
- `visibility scan` asks the assistants now instead of waiting for the schedule. It uses AI Visibility credits.
|
|
283
|
+
|
|
284
|
+
`verify` reads the daily series as `ai_visibility`, the share of answers that name the app, before and after a push. A description that says what the app is for in the words people use can move it, and so can the pages the assistants cite.
|
|
285
|
+
|
|
286
|
+
## Category rank
|
|
287
|
+
|
|
288
|
+
`category pull` records the app's rank in its category chart for each storefront under `targets`, one row a day in `appeeky/category.csv`. The rank is public data, so the file is committed with the spec. On the App Store the genre and chart come from the app (a paid app is read from top paid), straight from Apple's charts and with no key. On Google Play, set the category:
|
|
289
|
+
|
|
290
|
+
```yaml
|
|
291
|
+
category:
|
|
292
|
+
googlePlay: HEALTH_AND_FITNESS # appStore: "6013" to override the genre
|
|
293
|
+
chart: top-free # top-free | top-paid | top-grossing
|
|
294
|
+
countries: [us, gb] # default: the storefronts under targets
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
Charts list the top 100; below that is an empty rank. `verify` reads the file as `category_rank` (lower is better). When no target moved and there is no analytics data, the category rank decides the release. Run it daily (see above) so there is a history before each push.
|
|
298
|
+
|
|
299
|
+
## Attribution
|
|
300
|
+
|
|
301
|
+
With [Appeeky Analytics](https://appeeky.com/attribution) in the app, the spec knows where each install came from (organic store search, an Apple Search Ads keyword, a smart link, the Google Play referrer) and what it paid. Run the server yourself on Cloudflare, or use Appeeky's:
|
|
302
|
+
|
|
303
|
+
```yaml
|
|
304
|
+
attribution:
|
|
305
|
+
via: self # your own server: set ATTRIBUTION_TOKEN to its ADMIN_TOKEN
|
|
306
|
+
url: https://analytics.sleepsounds.workers.dev
|
|
307
|
+
app: sleep-sounds # the app's id in Appeeky Analytics
|
|
308
|
+
# or
|
|
309
|
+
attribution:
|
|
310
|
+
via: hosted # Appeeky's service, with APPEEKY_API_KEY
|
|
311
|
+
app: "1234567890"
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`attribution init` sets this up: hosted, it turns attribution on for each store's app in Appeeky; with `--self <url>`, it finds or creates the apps on your server (with `ATTRIBUTION_TOKEN`). It writes `attribution:` and prints the SDK key and the RevenueCat webhook to add.
|
|
315
|
+
|
|
316
|
+
An app in Appeeky Analytics is one platform. For an app on both stores, give one id per store; each release is measured with its own store's app, and `data pull` writes one set of files per store:
|
|
317
|
+
|
|
318
|
+
```yaml
|
|
319
|
+
attribution:
|
|
320
|
+
via: self
|
|
321
|
+
url: https://analytics.sleepsounds.workers.dev
|
|
322
|
+
app:
|
|
323
|
+
app-store: app_ios123
|
|
324
|
+
google-play: app_and456
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
- `verify` measures what a listing change is for: **organic installs per day, the revenue they brought and the share that paid**, before and after each push. The server counts installs first seen in a window, so revenue covers what those installs have paid so far; older installs have had longer to pay, which makes a rise after a push a conservative reading.
|
|
328
|
+
- `data pull` adds `attribution-sources.csv` (installs, trials, payers and revenue by source and campaign), `attribution-keywords.csv` (Search Ads keyword ids with the revenue they led to; Apple reports ids, the words are in your Search Ads account), `attribution-campaigns.csv`, `attribution-countries.csv` and `smart-links.csv`. It works with attribution alone, without an Appeeky key.
|
|
329
|
+
- `experiment add --link [channel]` creates a smart link for the experiment: one URL that sends each visitor to the right store, with its installs and revenue counted to it. Use `--metric link_installs`, `link_revenue` or `link_clicks` and `experiment close` fills the value in. On iOS the App Store passes nothing from a click to a new install, so link installs are, in practice, Google Play installs (the SDK reads the link only on the app's first launch); for an App Store only app, measure `link_clicks` (or `organic_installs`).
|
|
330
|
+
|
|
331
|
+
```bash
|
|
332
|
+
appeeky-spec experiment add --type ugc --link tiktok --hypothesis "Before/after hooks get installs" --metric link_installs --target 50
|
|
333
|
+
# Started 2026-11-04-before-after-hooks-get-installs
|
|
334
|
+
# link https://analytics.sleepsounds.workers.dev/l/ab12xy
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
## Experiments and personas
|
|
338
|
+
|
|
339
|
+
Listing pushes are experiments already. Everything else goes in `appeeky/experiments/`:
|
|
340
|
+
|
|
341
|
+
```bash
|
|
342
|
+
appeeky-spec experiment add --type ugc --persona sleepless-parent \
|
|
343
|
+
--hypothesis "Before/after hooks get more installs" --metric install_per_1k_views --baseline 1.8 --target 2.5
|
|
344
|
+
appeeky-spec experiment close 2026-11-01-before-after --value 2.7 --playbook
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Personas live in `appeeky.spec.yaml` so screenshots, UGC and ads speak to the same people:
|
|
348
|
+
|
|
349
|
+
```yaml
|
|
350
|
+
personas:
|
|
351
|
+
- id: sleepless-parent
|
|
352
|
+
name: Sleepless parent
|
|
353
|
+
searches: [baby sleep sounds, white noise for baby]
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
## In pull requests
|
|
357
|
+
|
|
358
|
+
```yaml
|
|
359
|
+
# .github/workflows/appeeky-spec.yml
|
|
360
|
+
on: pull_request
|
|
361
|
+
permissions:
|
|
362
|
+
contents: read
|
|
363
|
+
pull-requests: write
|
|
364
|
+
jobs:
|
|
365
|
+
spec:
|
|
366
|
+
runs-on: ubuntu-latest
|
|
367
|
+
steps:
|
|
368
|
+
- uses: actions/checkout@v4
|
|
369
|
+
with:
|
|
370
|
+
fetch-depth: 0
|
|
371
|
+
- uses: appeeky/appeeky-spec@v0
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
The action comments the listing changes, check results and coverage before → after, and fails when `check` finds errors.
|
|
375
|
+
|
|
376
|
+
## Running without GitHub Actions
|
|
377
|
+
|
|
378
|
+
Everything the workflows do lives in the repo, so any machine that can run Docker can do it: a server of your own, Railway, Fly, Render. Nothing listens on a port; the container only calls out to GitHub, Appeeky and the stores.
|
|
379
|
+
|
|
380
|
+
**On your own server.** Put [`docker/docker-compose.yml`](docker/docker-compose.yml) and a `.env` filled from [`docker/.env.example`](docker/.env.example) in one folder:
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
docker compose up -d # runs the routines on a schedule, and again after a reboot
|
|
384
|
+
docker compose logs -f
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
It runs `daily` at 06:00 UTC and checks for merged listing changes every 15 minutes. `DAILY_AT`, `PUSH_EVERY` and `WEEKLY_AT` change the schedule. Set `HEALTHCHECK_URL` (for example from healthchecks.io) to get an email when a daily run fails or does not happen. For more than one app, copy the service and give each its own env file.
|
|
388
|
+
|
|
389
|
+
**With a scheduler you already have** (cron, Cloud Run Jobs, Railway cron), run one routine per job:
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
docker run --rm --env-file app.env ghcr.io/appeeky/appeeky-spec daily
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
| Routine | Run it | Does |
|
|
396
|
+
| --- | --- | --- |
|
|
397
|
+
| `daily` | once a day | `category pull`, `visibility pull`, `verify --playbook`, `reviews pull`, `competitors pull`, then commits |
|
|
398
|
+
| `push` | every 10–15 minutes | pushes listing changes merged to `main` and commits the release. It remembers the last commit it handled as the `appeeky-spec/pushed` tag. The first run only sets the tag, so it never pushes what was on `main` before. A run that fails leaves the tag, so the next run tries again. |
|
|
399
|
+
| `weekly` | once a week | the weekly proposal: an agent opens a pull request with one listing change. Use the `latest-agent` image (adds Claude Code and `gh`), set `CLAUDE_CODE_OAUTH_TOKEN` (run `claude setup-token` on your computer: it uses your Claude Pro or Max plan) or `ANTHROPIC_API_KEY`, and copy [`examples/weekly/propose.md`](examples/weekly/propose.md) to `.github/appeeky-propose.md`. |
|
|
400
|
+
|
|
401
|
+
`PUSH_DRY_RUN=1` shows what `push` would change without changing anything: a safe first run. `GITHUB_TOKEN` needs contents write on the repo (and pull requests write for `weekly`); it is passed to git through a credential helper, never in a URL. Pull request comments still need GitHub: keep the action above for those. To build the image yourself: `docker build -f docker/Dockerfile -t appeeky-spec .` (`--build-arg AGENT=1` for the agent).
|
|
402
|
+
|
|
403
|
+
## Keyword popularity
|
|
404
|
+
|
|
405
|
+
`coverage` ranks targets by search popularity and difficulty from the [Appeeky API](https://appeeky.com) when `APPEEKY_API_KEY` is set. Results are cached for 7 days in `.appeeky/cache/` (add it to `.gitignore`). Without a key, coverage still works; it just is not weighted.
|
|
406
|
+
|
|
407
|
+
## Using it with coding agents
|
|
408
|
+
|
|
409
|
+
Every command takes `--json`. A typical agent loop:
|
|
410
|
+
|
|
411
|
+
1. `appeeky-spec coverage --json` and `appeeky-spec check --json` to find gaps.
|
|
412
|
+
2. Edit the listing files and explain the change.
|
|
413
|
+
3. `appeeky-spec diff --json` to confirm what will go live.
|
|
414
|
+
4. A human runs `appeeky-spec push --note "<hypothesis>"`, or the agent runs it with `--yes` when you allow it.
|
|
415
|
+
|
|
416
|
+
Install the bundled skills so your agent knows the workflow:
|
|
417
|
+
|
|
418
|
+
```bash
|
|
419
|
+
npx skills add appeeky/appeeky-spec
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
| Skill | Use it for |
|
|
423
|
+
| --- | --- |
|
|
424
|
+
| `appeeky-spec` | Set up the spec, `doctor`, the workflows or other automation, the weekly routine, find gaps, edit, check and push listing changes, screenshots |
|
|
425
|
+
| `appeeky-spec-targets` | Personas, keyword research and which searches to target, competitor moves |
|
|
426
|
+
| `appeeky-spec-localize` | A new language or country, and spare keyword space in secondary locales |
|
|
427
|
+
| `appeeky-spec-review` | Day-7/14 results, keep or revert, playbook, closing experiments |
|
|
428
|
+
| `appeeky-spec-visibility` | Whether AI assistants name your app, which questions to track and how to get named; the category chart rank |
|
|
429
|
+
| `appeeky-spec-experiments` | Screenshot, UGC, ad and pricing experiments, attribution and smart links, ads and data for analysis |
|
|
430
|
+
|
|
431
|
+
Full documentation: [docs.appeeky.com/docs/spec](https://docs.appeeky.com/docs/spec).
|
|
432
|
+
|
|
433
|
+
## Library
|
|
434
|
+
|
|
435
|
+
The same logic is available as a library. `@appeeky/spec/core` has no Node dependencies and runs in the browser (editors, previews):
|
|
436
|
+
|
|
437
|
+
```ts
|
|
438
|
+
import { lintAppStore, appStoreCoverage } from "@appeeky/spec/core";
|
|
439
|
+
import { loadProject, planStore, storeHandle } from "@appeeky/spec";
|
|
440
|
+
|
|
441
|
+
const project = await loadProject();
|
|
442
|
+
const plan = await planStore(project, storeHandle(project, "app-store"));
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
## Development
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
git clone https://github.com/appeeky/appeeky-spec.git
|
|
449
|
+
cd appeeky-spec
|
|
450
|
+
npm install
|
|
451
|
+
npm test
|
|
452
|
+
npm run build
|
|
453
|
+
node dist/bin.js --help
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
### Releases
|
|
457
|
+
|
|
458
|
+
Merging to `main` releases automatically. After the tests pass, CI reads the [Conventional Commits](https://www.conventionalcommits.org) since the last `v*` tag and bumps the version:
|
|
459
|
+
|
|
460
|
+
| Commits since the last release | Release |
|
|
461
|
+
| --- | --- |
|
|
462
|
+
| only `docs:`, `chore:`, `test:`, `ci:` | none |
|
|
463
|
+
| `fix:` or `perf:` | patch, `0.1.0` → `0.1.1` |
|
|
464
|
+
| `feat:` | minor, `0.1.0` → `0.2.0` |
|
|
465
|
+
| a breaking change (`feat!:` or `BREAKING CHANGE:`) | minor while at `0.x`, then major |
|
|
466
|
+
|
|
467
|
+
It then:
|
|
468
|
+
- publishes `@appeeky/spec` to npm,
|
|
469
|
+
- turns `## Unreleased` in the changelog into the release section (or lists the commits when nothing was written there),
|
|
470
|
+
- tags `v<version>` and moves `v0`, the tag the GitHub Action is used by,
|
|
471
|
+
- creates the GitHub release,
|
|
472
|
+
- publishes the `ghcr.io/appeeky/appeeky-spec` images.
|
|
473
|
+
|
|
474
|
+
`npm run release:dry` shows what the next release would be, and `npm run pack:check` what the package would ship. CI needs the `NPM_TOKEN` secret, an npm automation token with publish rights on `@appeeky`.
|
|
475
|
+
|
|
476
|
+
## License
|
|
477
|
+
|
|
478
|
+
Appeeky Spec is licensed under the [Functional Source License, Version 1.1, MIT Future License](./LICENSE) (FSL-1.1-MIT).
|
|
479
|
+
|
|
480
|
+
You can use, change and run it for your own apps and inside your company, including commercially. What it does not allow is offering it, or a product built from it, as a competing product or service.
|
|
481
|
+
|
|
482
|
+
---
|
|
483
|
+
|
|
484
|
+
Appeeky Spec is an independent tool and is not affiliated with, endorsed by, or sponsored by Apple Inc. or Google LLC. App Store and App Store Connect are trademarks of Apple Inc. Google Play is a trademark of Google LLC.
|
package/dist/ads.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type AppeekyApi } from "./appeeky-api.js";
|
|
2
|
+
import type { SpecFile, StoreId } from "./schema.js";
|
|
3
|
+
/**
|
|
4
|
+
* Paid channels for one app over a date range: Apple Search Ads from the app's
|
|
5
|
+
* connection in Appeeky, Meta and TikTok from the campaigns listed in
|
|
6
|
+
* `ads:` in appeeky.spec.yaml. `appeeky-spec ads --save` writes it to
|
|
7
|
+
* `appeeky/ads/<date>.yaml`, so the repo keeps a history next to the listing.
|
|
8
|
+
*/
|
|
9
|
+
export type AdsChannelId = "apple-search-ads" | "meta" | "tiktok";
|
|
10
|
+
export interface AdsChannel {
|
|
11
|
+
channel: AdsChannelId;
|
|
12
|
+
spendPerDay?: number;
|
|
13
|
+
/** Installs for Apple Search Ads and Meta; optimization-event conversions for TikTok. */
|
|
14
|
+
resultsPerDay?: number;
|
|
15
|
+
costPerResult?: number;
|
|
16
|
+
result: "installs" | "conversions";
|
|
17
|
+
}
|
|
18
|
+
export interface AdsSnapshot {
|
|
19
|
+
from: string;
|
|
20
|
+
/** Exclusive end. */
|
|
21
|
+
to: string;
|
|
22
|
+
days: number;
|
|
23
|
+
currency?: string;
|
|
24
|
+
channels: AdsChannel[];
|
|
25
|
+
/** Channels that could not be read, with the reason. */
|
|
26
|
+
errors: Array<{
|
|
27
|
+
channel: AdsChannelId;
|
|
28
|
+
message: string;
|
|
29
|
+
}>;
|
|
30
|
+
}
|
|
31
|
+
/** Every paid channel this spec can read, over the `days` up to `now` (today left out: it is not over yet). */
|
|
32
|
+
export declare function adsSnapshot(api: AppeekyApi, spec: SpecFile, app: {
|
|
33
|
+
store: StoreId;
|
|
34
|
+
appId: string;
|
|
35
|
+
} | null, days: number, now?: Date): Promise<AdsSnapshot>;
|
|
36
|
+
export declare function adsDir(root: string): string;
|
|
37
|
+
/** Write a snapshot to `appeeky/ads/<to>.yaml` and return the path. */
|
|
38
|
+
export declare function saveAdsSnapshot(root: string, snapshot: AdsSnapshot): Promise<string>;
|
|
39
|
+
export declare const CHANNEL_LABELS: Record<AdsChannelId, string>;
|