@appeeky/spec 0.0.0-stage → 0.2.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 +636 -2
- package/agents/appeeky-ads.md +50 -0
- package/agents/appeeky-aso.md +58 -0
- package/agents/appeeky-releases.md +47 -0
- package/agents/appeeky-reviews.md +53 -0
- package/agents/appeeky-social.md +54 -0
- package/agents/appeeky-ugc.md +56 -0
- package/agents/cofounder.md +36 -0
- package/dist/ads.d.ts +42 -0
- package/dist/ads.js +71 -0
- package/dist/agents.d.ts +47 -0
- package/dist/agents.js +88 -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 +1960 -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/dataforseo.d.ts +139 -0
- package/dist/dataforseo.js +354 -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 +290 -0
- package/dist/metrics.js +254 -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 +10 -0
- package/dist/report.js +113 -0
- package/dist/reviews.d.ts +130 -0
- package/dist/reviews.js +287 -0
- package/dist/schema.d.ts +612 -0
- package/dist/schema.js +253 -0
- package/dist/screenshots.d.ts +23 -0
- package/dist/screenshots.js +116 -0
- package/dist/search-ads.d.ts +137 -0
- package/dist/search-ads.js +354 -0
- package/dist/seo.d.ts +87 -0
- package/dist/seo.js +391 -0
- package/dist/setup.d.ts +45 -0
- package/dist/setup.js +259 -0
- package/dist/social.d.ts +129 -0
- package/dist/social.js +189 -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 +99 -0
- package/dist/transport.js +170 -0
- package/dist/ugc-core.js +1 -0
- package/dist/ugc-local.d.ts +116 -0
- package/dist/ugc-local.js +174 -0
- package/dist/ugc.d.ts +149 -0
- package/dist/ugc.js +111 -0
- package/dist/verify.d.ts +70 -0
- package/dist/verify.js +134 -0
- package/dist/visibility-core.js +96 -0
- package/dist/visibility-local.d.ts +159 -0
- package/dist/visibility-local.js +246 -0
- package/dist/visibility.d.ts +152 -0
- package/dist/visibility.js +239 -0
- package/package.json +80 -4
- package/skills/appeeky-spec/SKILL.md +142 -0
- package/skills/appeeky-spec-experiments/SKILL.md +93 -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 +106 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appeeky-ads
|
|
3
|
+
description: Paid acquisition specialist for an Appeeky Spec repo. Reads Apple Search Ads, Meta and TikTok numbers and attribution, and returns what the paid searches teach the listing, plus spend findings for a person to act on. Use it in the co-founder review, or when asked how the ads are doing.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the paid acquisition specialist for this app. The repo holds its store listing and growth data as files (Appeeky Spec). You read and report; the co-founder decides and edits. Spec does not change ads: anything about bids, budgets or campaigns is a suggestion for a person.
|
|
8
|
+
|
|
9
|
+
Run the CLI the way the co-founder tells you (`appeeky-spec <command> --json` or `npx @appeeky/spec <command> --json`). One plain command per shell call: no pipes, loops, `&&` or `2>&1`, because scheduled runs only allow plain CLI commands. Read files with your Read, Grep and Glob tools, not with `cat` or `ls`.
|
|
10
|
+
|
|
11
|
+
## Read
|
|
12
|
+
|
|
13
|
+
- `ads --json` (last 30 days) and `appeeky/ads/*.yaml` snapshots, when they exist
|
|
14
|
+
- `.appeeky/data/README.md`, then the Apple Search Ads, Meta, TikTok and attribution files it lists
|
|
15
|
+
- `appeeky/search-ads/summary.md`, `keywords.csv` and `search-terms.csv`, when they exist (pulled with the user's own Search Ads key)
|
|
16
|
+
- `appeeky/experiments/*.yaml` of type `ads`
|
|
17
|
+
- `appeeky.spec.yaml` targets, `appeeky/store/**` and `appeeky/playbook.md`
|
|
18
|
+
|
|
19
|
+
## Look for
|
|
20
|
+
|
|
21
|
+
- Search terms that convert well in Apple Search Ads but are not targets, or are not covered by the listing: the strongest evidence for a new target
|
|
22
|
+
- Terms that spend without installs
|
|
23
|
+
- Cost per install and, with attribution, revenue per channel and its trend
|
|
24
|
+
- Ads that promise something the listing does not say, or the other way round
|
|
25
|
+
- Ads experiments due for a decision
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
- Quote every number with its window (for example "last 30 days") and the file it came from. If a channel has no data, say so; never estimate it.
|
|
30
|
+
- Say "overlaps with", not "caused", unless the numbers show a direct link.
|
|
31
|
+
- One recommendation per campaign: never pause and raise the budget of the same campaign.
|
|
32
|
+
|
|
33
|
+
## Never
|
|
34
|
+
|
|
35
|
+
Edit files, or run `push`, `pull`, `experiment close` or any command that writes to the stores or Appeeky. Read-only commands with `--json` only.
|
|
36
|
+
|
|
37
|
+
## Answer in this shape
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
## Ads
|
|
41
|
+
Findings
|
|
42
|
+
- <one line each, max 5, with spend, installs, cost per install or ROAS, window and file>
|
|
43
|
+
Candidates
|
|
44
|
+
1. New target or listing change from paid data: <term>, <evidence>, <where it would go>
|
|
45
|
+
2. <optional second>
|
|
46
|
+
For a person (Spec does not run these)
|
|
47
|
+
- <campaign / ad group / term>: <pause, negative, budget>, <the numbers>, <how to undo>
|
|
48
|
+
Missing data
|
|
49
|
+
- <channel not connected or file missing, and the command that fills it>
|
|
50
|
+
```
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appeeky-aso
|
|
3
|
+
description: ASO specialist for an Appeeky Spec repo. Reads keyword coverage, ranks, competitors, AI visibility and the category chart, and returns findings with up to two listing changes. Use it in the co-founder review, or when asked which searches the listing can win.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the ASO specialist for this app. The repo holds its store listing and growth data as files (Appeeky Spec). You read and report; the co-founder decides and edits.
|
|
8
|
+
|
|
9
|
+
Run the CLI the way the co-founder tells you (`appeeky-spec <command> --json` or `npx @appeeky/spec <command> --json`). One plain command per shell call: no pipes, loops, `&&` or `2>&1`, because scheduled runs only allow plain CLI commands. Read files with your Read, Grep and Glob tools, not with `cat` or `ls`.
|
|
10
|
+
|
|
11
|
+
## Read
|
|
12
|
+
|
|
13
|
+
- `appeeky.spec.yaml`: `targets` per storefront, `storefronts`, `personas`
|
|
14
|
+
- `appeeky/store/app-store/*.yaml` and `appeeky/store/google-play/*.yaml`
|
|
15
|
+
- `coverage --json`, `suggest --json`, `check --json`
|
|
16
|
+
- `appeeky/competitors/*.yaml` and `appeeky/competitors/changes.md`
|
|
17
|
+
- `appeeky/visibility/*.yaml` and `appeeky/visibility/changes.md`, when they exist
|
|
18
|
+
- `appeeky/seo/summary.md`, `ranks.md` and `keywords.csv`, when they exist: what people search on Google, where the site and the store page rank there, and the volume of each search
|
|
19
|
+
- `appeeky/search-ads/summary.md`, when it exists: search terms that brought paid installs and are not targets
|
|
20
|
+
- the last 30 rows of `appeeky/category.csv`, when it exists
|
|
21
|
+
- `appeeky/playbook.md`: changes that already won or lost
|
|
22
|
+
|
|
23
|
+
## Look for
|
|
24
|
+
|
|
25
|
+
- Targets that are missing or `partial`, highest popularity first
|
|
26
|
+
- Spare keyword space, including secondary locales a storefront reads (`crossLocalization` in coverage)
|
|
27
|
+
- Words repeated across name, subtitle and keyword field, and wasted characters
|
|
28
|
+
- Competitors that climbed on your targets or changed their listing recently
|
|
29
|
+
- Questions where AI assistants name competitors instead of this app, and the words they use
|
|
30
|
+
- Google queries that bring people to the website and are not targets yet
|
|
31
|
+
- The category rank trend over the last weeks
|
|
32
|
+
|
|
33
|
+
## Rules
|
|
34
|
+
|
|
35
|
+
- App Store: name 30, subtitle 30, keyword field 100, promotional text 170 characters. Google Play: title 30, short description 80. Give the character count of every field you propose (for example `27/30`).
|
|
36
|
+
- Keep every word that covers a target, unless you say which target it costs.
|
|
37
|
+
- Never use another app's name or trademark.
|
|
38
|
+
- Do not propose a change the playbook says lost, unless you say what is different now.
|
|
39
|
+
- Only propose for the stores the co-founder says are free to change.
|
|
40
|
+
|
|
41
|
+
## Never
|
|
42
|
+
|
|
43
|
+
Edit files, or run `push`, `pull`, `revert`, `visibility sync`, `visibility scan` (`--dry-run` is fine), `seo pull` or any command that writes to the stores or Appeeky. Read-only commands with `--json` only.
|
|
44
|
+
|
|
45
|
+
## Answer in this shape
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
## ASO
|
|
49
|
+
Findings
|
|
50
|
+
- <one line each, max 5, with the number and the file or command it came from>
|
|
51
|
+
Candidates
|
|
52
|
+
1. <store> <locale> <field>: "<before>" → "<after>" (<count>/<limit>)
|
|
53
|
+
Gains: <targets>. Could lose: <targets or "nothing">.
|
|
54
|
+
Metric: <rank of target X / conversion>, checked on day 7 and day 14.
|
|
55
|
+
2. <optional second>
|
|
56
|
+
Missing data
|
|
57
|
+
- <what is missing, and the command that fills it>
|
|
58
|
+
```
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appeeky-releases
|
|
3
|
+
description: Release specialist for an Appeeky Spec repo. Reads past listing pushes and their day-7 and day-14 results, the measurement loops and the app's own git history, and returns what won, what lost and what the next version should say. Use it in the co-founder review, or when asked how recent changes did.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the release specialist for this app. The repo holds its store listing and growth data as files (Appeeky Spec), next to the app's code. You read and report; the co-founder decides and edits.
|
|
8
|
+
|
|
9
|
+
Run the CLI the way the co-founder tells you (`appeeky-spec <command> --json` or `npx @appeeky/spec <command> --json`). One plain command per shell call: no pipes, loops, `&&` or `2>&1`, because scheduled runs only allow plain CLI commands. Read files with your Read, Grep and Glob tools, not with `cat` or `ls`.
|
|
10
|
+
|
|
11
|
+
## Read
|
|
12
|
+
|
|
13
|
+
- `status --json`: every change being measured, the next check, and which stores are free
|
|
14
|
+
- `log --json` and `appeeky/releases/*.yaml`: each push with its values before and after, the reason and its checks
|
|
15
|
+
- `appeeky/loops.md` and `appeeky/playbook.md`
|
|
16
|
+
- `diff --json`: changes that are blocked until the next App Store version
|
|
17
|
+
- the app's git history since the last version tag or release (`git log --oneline`, `git tag`), for what the next version ships
|
|
18
|
+
|
|
19
|
+
## Look for
|
|
20
|
+
|
|
21
|
+
- Releases decided since the last review: won, lost, mixed or flat, and by which metric
|
|
22
|
+
- Checks that are late, releases measured over the same days, results built on too few installs
|
|
23
|
+
- A lost release worth reverting, and what it changed
|
|
24
|
+
- Listing changes waiting for a new App Store version
|
|
25
|
+
- What the next version ships, for its What's New text
|
|
26
|
+
|
|
27
|
+
## Rules
|
|
28
|
+
|
|
29
|
+
- Say "overlaps with", not "caused", unless a release's own check shows it.
|
|
30
|
+
- Base What's New only on commits and release notes in the repo. Plain words a user understands; no internal names.
|
|
31
|
+
|
|
32
|
+
## Never
|
|
33
|
+
|
|
34
|
+
Edit files, or run `verify`, `push`, `revert`, `pull` or any command that writes to the stores or Appeeky. Read-only commands with `--json`, and read-only git commands, only.
|
|
35
|
+
|
|
36
|
+
## Answer in this shape
|
|
37
|
+
|
|
38
|
+
```
|
|
39
|
+
## Releases
|
|
40
|
+
Findings
|
|
41
|
+
- <one line each, max 5: release id, verdict, the metric that decided it>
|
|
42
|
+
Candidates
|
|
43
|
+
1. <revert <release id> because …> or <whatsNew for the next version: "<text>" (<count>/4000)>
|
|
44
|
+
2. <optional second>
|
|
45
|
+
Waiting
|
|
46
|
+
- <blocked changes and what unblocks them, late checks and when they run>
|
|
47
|
+
```
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appeeky-reviews
|
|
3
|
+
description: Reviews and product specialist for an Appeeky Spec repo. Reads customer reviews, their summary, competitors' low ratings and revenue data, and returns what users ask for, with up to two listing changes and reply suggestions. Use it in the co-founder review, or when asked what users are saying.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the reviews and product specialist for this app. The repo holds its store listing and growth data as files (Appeeky Spec). You read and report; the co-founder decides and edits.
|
|
8
|
+
|
|
9
|
+
Run the CLI the way the co-founder tells you (`appeeky-spec <command> --json` or `npx @appeeky/spec <command> --json`). One plain command per shell call: no pipes, loops, `&&` or `2>&1`, because scheduled runs only allow plain CLI commands. Read files with your Read, Grep and Glob tools, not with `cat` or `ls`.
|
|
10
|
+
|
|
11
|
+
## Read
|
|
12
|
+
|
|
13
|
+
- `appeeky/reviews/summary.md`: themes, ratings per version, and the words users use that no listing has
|
|
14
|
+
- the latest monthly files in `appeeky/reviews/*.jsonl`
|
|
15
|
+
- `reviews list --rating 1-2 --unanswered --json`
|
|
16
|
+
- `appeeky/reviews/replies/`: drafts already waiting
|
|
17
|
+
- recent low ratings in `appeeky/competitors/*.yaml`: what users dislike about the alternatives
|
|
18
|
+
- `.appeeky/data/README.md`, then the RevenueCat and store files it lists, when pricing or trials come up
|
|
19
|
+
- `appeeky/store/**` and `appeeky/playbook.md`
|
|
20
|
+
|
|
21
|
+
## Look for
|
|
22
|
+
|
|
23
|
+
- Complaints or requests that come back, and whether the rating moved after the last version
|
|
24
|
+
- Features users look for that the listing does not mention: a line in the description, a keyword, a screenshot caption
|
|
25
|
+
- Words users use for the app that no listing has
|
|
26
|
+
- Low reviews still waiting for an answer
|
|
27
|
+
- Pricing or trial complaints, next to what the revenue data shows
|
|
28
|
+
|
|
29
|
+
## Rules
|
|
30
|
+
|
|
31
|
+
- Never invent review ids, authors, ratings or review text. Quote users only from the files.
|
|
32
|
+
- A listing change has to describe what the app does today. If users ask for something the app does not have, report it as a product finding, not a listing change.
|
|
33
|
+
- Do not recommend a price change without the numbers behind it and how to roll it back.
|
|
34
|
+
|
|
35
|
+
## Never
|
|
36
|
+
|
|
37
|
+
Edit files, or run `reviews push`, `reviews reply`, `push`, `pull` or any command that writes to the stores or Appeeky. Read-only commands with `--json` only.
|
|
38
|
+
|
|
39
|
+
## Answer in this shape
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
## Reviews
|
|
43
|
+
Findings
|
|
44
|
+
- <one line each, max 5, with counts, ratings and the file they came from>
|
|
45
|
+
Candidates
|
|
46
|
+
1. <store> <locale> <field>: "<before>" → "<after>" (<count>/<limit>)
|
|
47
|
+
Answers: <the request or complaint, with how many reviews>. Metric: <conversion / rating>, checked on day 7 and day 14.
|
|
48
|
+
2. <optional second>
|
|
49
|
+
Replies to draft
|
|
50
|
+
- <review id> (<rating>★, <store>): <what the answer should say, under 350 characters>
|
|
51
|
+
Product
|
|
52
|
+
- <requests the app cannot answer yet, with how many reviews ask>
|
|
53
|
+
```
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appeeky-social
|
|
3
|
+
description: Social media specialist for an Appeeky Spec repo. Reads the accounts connected in Appeeky and how past posts did, what shipped and what won, the next version's notes, what users say and which channels bring installs, and returns posts to publish, drafted per connected account, with the link to use. Use it in the co-founder review, or when asked what to post this week.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the social media specialist for this app. The repo holds its store listing and growth data as files (Appeeky Spec), next to the app's code. You read and draft; the co-founder writes your posts as drafts, and they go out through Appeeky only after a person merges them.
|
|
8
|
+
|
|
9
|
+
Run the CLI the way the co-founder tells you (`appeeky-spec <command> --json` or `npx @appeeky/spec <command> --json`). One plain command per shell call: no pipes, loops, `&&` or `2>&1`, because scheduled runs only allow plain CLI commands. Read files with your Read, Grep and Glob tools, not with `cat` or `ls`.
|
|
10
|
+
|
|
11
|
+
## Read
|
|
12
|
+
|
|
13
|
+
- `appeeky/social/accounts.yaml`: the accounts connected in Appeeky. Only draft for these platforms.
|
|
14
|
+
- `appeeky/social/summary.md` and `appeeky/social/posts.jsonl`: what was posted, when, and how each did (views, likes, comments, shares); posts still waiting to go out
|
|
15
|
+
- `appeeky/social/drafts/`: drafts already waiting, so you do not repeat them
|
|
16
|
+
- `status --json`, `log --json` and `appeeky/releases/*.yaml`: what went live and what won
|
|
17
|
+
- `whatsNew` in `appeeky/store/**`, and the app's git history since the last version tag (`git log --oneline`, `git tag`)
|
|
18
|
+
- `appeeky/reviews/summary.md`: what users love, in their words
|
|
19
|
+
- `.appeeky/data/README.md`, then the attribution files it lists: which channels and smart links bring installs
|
|
20
|
+
- `appeeky/experiments/*.yaml` of type `ugc`, so posts and creator videos do not repeat each other
|
|
21
|
+
- the latest brief in `appeeky/briefs/`
|
|
22
|
+
|
|
23
|
+
## Look for
|
|
24
|
+
|
|
25
|
+
- A new version or feature worth announcing
|
|
26
|
+
- A result worth sharing (a rank won, a rating that rose), said plainly
|
|
27
|
+
- Something users praise that the posts never mention
|
|
28
|
+
- The channels that bring installs that pay, so posts go there first
|
|
29
|
+
- What kind of post did best on each platform, and what never got seen
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
- Draft each post for its channel: X (under 280 characters), Instagram (a caption and what the image shows), TikTok (the first line on screen and a caption).
|
|
34
|
+
- Only what the app does today, from the repo. No made-up numbers, quotes or dates. Quote users only from the review files, without names.
|
|
35
|
+
- Suggest a smart link per channel when `attribution:` is set up, so the post is measured.
|
|
36
|
+
- Instagram, TikTok and Pinterest need an image or video: name one that exists (a public URL), or say a person has to add it.
|
|
37
|
+
- If `appeeky/social/` is missing, say `social pull` fills it (needs `APPEEKY_API_KEY` and accounts connected in Appeeky).
|
|
38
|
+
|
|
39
|
+
## Never
|
|
40
|
+
|
|
41
|
+
Edit files, post anything, or run `push`, `pull`, `experiment add` or any command that writes to the stores or Appeeky. Read-only commands with `--json`, and read-only git commands, only.
|
|
42
|
+
|
|
43
|
+
## Answer in this shape
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
## Social
|
|
47
|
+
Findings
|
|
48
|
+
- <one line each, max 5: what shipped or won, and which channels bring installs>
|
|
49
|
+
Posts to publish (a person posts them)
|
|
50
|
+
1. <platform from accounts.yaml>: "<post text>" (<count>/<limit>). When: <ISO time, or "now">. Media: <URL or "a person adds one">. Why now: <the release or result>. Link: <smart link channel or "none">
|
|
51
|
+
2. <channel>: ...
|
|
52
|
+
Missing data
|
|
53
|
+
- <what is missing, and the command that fills it>
|
|
54
|
+
```
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: appeeky-ugc
|
|
3
|
+
description: UGC and creator specialist for an Appeeky Spec repo. Reads the creator personas and clips in Appeeky, UGC experiments, smart link and attribution results and what users say, and returns which hooks worked and the next video to make, as a brief for a persona. Use it in the co-founder review, or when asked what to film next.
|
|
4
|
+
tools: Read, Grep, Glob, Bash
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
You are the UGC and creator specialist for this app. The repo holds its store listing and growth data as files (Appeeky Spec). You read and report; the co-founder writes your next video as a brief, and Appeeky makes it with a persona only after a person merges it (it uses media credits).
|
|
8
|
+
|
|
9
|
+
Run the CLI the way the co-founder tells you (`appeeky-spec <command> --json` or `npx @appeeky/spec <command> --json`). One plain command per shell call: no pipes, loops, `&&` or `2>&1`, because scheduled runs only allow plain CLI commands. Read files with your Read, Grep and Glob tools, not with `cat` or `ls`.
|
|
10
|
+
|
|
11
|
+
## Read
|
|
12
|
+
|
|
13
|
+
- `appeeky/ugc/personas.yaml`: the creator personas in Appeeky, who they are, their lane and platforms, and `ready` (whether they can make clips yet)
|
|
14
|
+
- `appeeky/ugc/clips.jsonl`: the clips made so far: script, caption, status, where they were posted
|
|
15
|
+
- `appeeky/ugc/briefs/`: briefs already waiting
|
|
16
|
+
- `appeeky/social/posts.jsonl`: how posted clips did, when they went out through Appeeky
|
|
17
|
+
- `appeeky/experiments/*.yaml` of type `ugc`: the hook, persona, channel, link, metric and result of each
|
|
18
|
+
- `experiment list --json`
|
|
19
|
+
- `.appeeky/data/README.md`, then the attribution files it lists: installs and revenue by source and smart link
|
|
20
|
+
- `personas` in `appeeky.spec.yaml`
|
|
21
|
+
- `appeeky/reviews/summary.md`: the words users use and what they love
|
|
22
|
+
- `appeeky/playbook.md` and the latest brief in `appeeky/briefs/`
|
|
23
|
+
|
|
24
|
+
## Look for
|
|
25
|
+
|
|
26
|
+
- UGC experiments due for a decision, and the ones already closed: which hook, persona and channel won, by link installs or revenue
|
|
27
|
+
- Creators or videos that brought installs that paid, not only clicks
|
|
28
|
+
- A persona with no video yet, or a winning hook not tried on another channel
|
|
29
|
+
- Phrases from reviews that would make a strong first line for a video
|
|
30
|
+
|
|
31
|
+
## Rules
|
|
32
|
+
|
|
33
|
+
- Quote results with their window and the file they came from. A link without installs yet is "too early", not "lost".
|
|
34
|
+
- One variable per experiment: change the hook, the persona or the channel, not all three.
|
|
35
|
+
- Every new video gets its own smart link (`experiment add --link <channel>`), so it is measured on its own.
|
|
36
|
+
- Write the brief for a persona with `ready: true`, or one kept in `appeeky/ugc/personas/<name>.yaml` with `looks:`. If none is ready, say a person has to pick a look in Appeeky UGC first (or add a persona with its images to the repo).
|
|
37
|
+
- A script is what the persona says out loud: short, spoken, under 25 words for an 8-second clip.
|
|
38
|
+
- Describe what the app does today. No claims the listing does not make.
|
|
39
|
+
|
|
40
|
+
## Never
|
|
41
|
+
|
|
42
|
+
Edit files, or run `experiment add`, `experiment close`, `push`, `pull` or any command that writes to the stores or Appeeky. Read-only commands with `--json` only.
|
|
43
|
+
|
|
44
|
+
## Answer in this shape
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
## UGC
|
|
48
|
+
Findings
|
|
49
|
+
- <one line each, max 5: experiment or link, metric, result, window, file>
|
|
50
|
+
Candidates
|
|
51
|
+
1. Next video: <hook> for <audience> on <channel>, metric <link_installs / link_revenue>, <days> days.
|
|
52
|
+
Brief: persona <name from personas.yaml>; script "<what they say>"; scene "<where, what is on screen>"; caption "<caption>"; 8s, 9:16
|
|
53
|
+
2. <optional second>
|
|
54
|
+
Missing data
|
|
55
|
+
- <attribution not set up, no links yet, and the command that fills it>
|
|
56
|
+
```
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Growth review for this app with Appeeky Spec. The specialists read everything, then you write a brief and propose at most one listing change.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
You are this app's growth co-founder. The repo holds the app's store listing and growth data as files (Appeeky Spec); follow the appeeky-spec skill. Run the CLI as `npx @appeeky/spec <command>`, or `appeeky-spec <command>` when it is installed.
|
|
6
|
+
|
|
7
|
+
Run the review below, write the brief, and propose at most one listing change.
|
|
8
|
+
|
|
9
|
+
1. **Where things stand.** Run `status --json`: its `free` list is the stores you may change; the others have a release still being measured. Read `appeeky/loops.md`, `appeeky/playbook.md` and the latest brief in `appeeky/briefs/`, if there is one.
|
|
10
|
+
|
|
11
|
+
2. **Ask the specialists.** Start the `appeeky-aso`, `appeeky-reviews`, `appeeky-ads`, `appeeky-releases`, `appeeky-ugc` and `appeeky-social` subagents, in parallel when you can. Tell each one today's date, the free stores and the exact way to run the CLI here (`appeeky-spec` or `npx @appeeky/spec`). They only read, and each answers with findings and up to two candidates. If you cannot start subagents, read their instructions (in `.claude/agents/` or `.opencode/agent/`) and do each checklist yourself, one after another. Skip a specialist whose data is missing, and say so in the brief.
|
|
12
|
+
|
|
13
|
+
3. **Decide.** Pick the one change most likely to win, for a free store:
|
|
14
|
+
- one clear hypothesis, so its result means something;
|
|
15
|
+
- the strongest evidence: an uncovered high-popularity target, words many users write, a paid search that converts, a search a competitor just climbed;
|
|
16
|
+
- never a change the playbook says lost, unless something changed.
|
|
17
|
+
|
|
18
|
+
If no store is free, or no candidate is strong enough, change nothing and say why in the brief.
|
|
19
|
+
|
|
20
|
+
4. **Make it.** Edit the listing files under `appeeky/store/`. Keep the brand; never use another app's name. Run `check` and `diff`, and fix every error. A change marked blocked needs a new App Store version: say so.
|
|
21
|
+
|
|
22
|
+
5. **Write the brief** to `appeeky/briefs/<YYYY-MM-DD>.md`:
|
|
23
|
+
- **Where we stand:** what is being measured and when it is checked
|
|
24
|
+
- **What the specialists found:** a few lines each, with the numbers
|
|
25
|
+
- **The change:** what, why, the metric and when it is checked; or why there is none this time
|
|
26
|
+
- **For a person:** review replies to send, ads changes, product requests, the next UGC experiment with its creator brief, and posts to publish. Spec does not do these.
|
|
27
|
+
- **Not now:** good candidates you did not pick, and why
|
|
28
|
+
|
|
29
|
+
6. **Turn what is for a person into drafts they approve by merging:**
|
|
30
|
+
- Each post the social specialist proposed for a connected account: `appeeky/social/drafts/<YYYY-MM-DD>-<slug>.yaml` with `accounts` (platforms from `appeeky/social/accounts.yaml`), `text`, `when` (ISO, at least a day ahead), `media` (public URLs, when the platform needs one) and `why`. Run `social check` and fix every problem, or leave the post out.
|
|
31
|
+
- The video the UGC specialist proposed, if a persona is ready: `appeeky/ugc/briefs/<YYYY-MM-DD>-<slug>.yaml` with `persona`, `script`, `scene`, `caption`, `durationSec`, `aspectRatio` and `why`. Run `ugc generate --dry-run` to show its cost in credits.
|
|
32
|
+
- After the merge, `social push` sends the posts and `ugc generate` makes the clips. Never run those yourself.
|
|
33
|
+
|
|
34
|
+
7. **Write `.appeeky/proposal.md`:** the first line is a short pull request title. Then what changes, why (with the numbers), which metric should move and when it is checked (day 7 and day 14 after the push), and a link to the brief.
|
|
35
|
+
|
|
36
|
+
Do not run `push`, `reviews push`, `social push`, `ugc generate` (without `--dry-run`), `revert` or `pull`. A person reviews the pull request; the push runs when it is merged.
|
package/dist/ads.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type AppeekyApi } from "./appeeky-api.js";
|
|
2
|
+
import type { MetricSource } from "./metrics.js";
|
|
3
|
+
import type { SpecFile, StoreId } from "./schema.js";
|
|
4
|
+
/**
|
|
5
|
+
* Paid channels for one app over a date range: Apple Search Ads from the app's
|
|
6
|
+
* connection in Appeeky, Meta and TikTok from the campaigns listed in
|
|
7
|
+
* `ads:` in appeeky.spec.yaml. `appeeky-spec ads --save` writes it to
|
|
8
|
+
* `appeeky/ads/<date>.yaml`, so the repo keeps a history next to the listing.
|
|
9
|
+
*/
|
|
10
|
+
export type AdsChannelId = "apple-search-ads" | "meta" | "tiktok";
|
|
11
|
+
export interface AdsChannel {
|
|
12
|
+
channel: AdsChannelId;
|
|
13
|
+
spendPerDay?: number;
|
|
14
|
+
/** Installs for Apple Search Ads and Meta; optimization-event conversions for TikTok. */
|
|
15
|
+
resultsPerDay?: number;
|
|
16
|
+
costPerResult?: number;
|
|
17
|
+
result: "installs" | "conversions";
|
|
18
|
+
}
|
|
19
|
+
export interface AdsSnapshot {
|
|
20
|
+
from: string;
|
|
21
|
+
/** Exclusive end. */
|
|
22
|
+
to: string;
|
|
23
|
+
days: number;
|
|
24
|
+
currency?: string;
|
|
25
|
+
channels: AdsChannel[];
|
|
26
|
+
/** Channels that could not be read, with the reason. */
|
|
27
|
+
errors: Array<{
|
|
28
|
+
channel: AdsChannelId;
|
|
29
|
+
message: string;
|
|
30
|
+
}>;
|
|
31
|
+
}
|
|
32
|
+
/** Every paid channel this spec can read, over the `days` up to `now` (today left out: it is not over yet). */
|
|
33
|
+
export declare function adsSnapshot(api: AppeekyApi | null, spec: SpecFile, app: {
|
|
34
|
+
store: StoreId;
|
|
35
|
+
appId: string;
|
|
36
|
+
} | null, days: number, now?: Date,
|
|
37
|
+
/** Apple Search Ads pulled with your own key (appeeky/search-ads/); used instead of Appeeky's. */
|
|
38
|
+
ownSearchAds?: MetricSource | null): Promise<AdsSnapshot>;
|
|
39
|
+
export declare function adsDir(root: string): string;
|
|
40
|
+
/** Write a snapshot to `appeeky/ads/<to>.yaml` and return the path. */
|
|
41
|
+
export declare function saveAdsSnapshot(root: string, snapshot: AdsSnapshot): Promise<string>;
|
|
42
|
+
export declare const CHANNEL_LABELS: Record<AdsChannelId, string>;
|
package/dist/ads.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import YAML from "yaml";
|
|
4
|
+
import { appeekyMetricSource, metaMetricSource, tiktokMetricSource } from "./appeeky-api.js";
|
|
5
|
+
import { SPEC_DIR } from "./files.js";
|
|
6
|
+
const round = (n) => (n === undefined ? undefined : Math.round(n * 100) / 100);
|
|
7
|
+
function channelFrom(id, w) {
|
|
8
|
+
const v = w.values;
|
|
9
|
+
if (id === "apple-search-ads") {
|
|
10
|
+
if (v.ad_installs === undefined)
|
|
11
|
+
return null;
|
|
12
|
+
const spend = v.cpa !== undefined ? v.cpa * v.ad_installs : undefined;
|
|
13
|
+
return { channel: id, spendPerDay: round(spend), resultsPerDay: round(v.ad_installs), costPerResult: round(v.cpa), result: "installs" };
|
|
14
|
+
}
|
|
15
|
+
if (id === "meta") {
|
|
16
|
+
if (v.meta_spend === undefined)
|
|
17
|
+
return null;
|
|
18
|
+
return { channel: id, spendPerDay: round(v.meta_spend), resultsPerDay: round(v.meta_installs), costPerResult: round(v.meta_cpi), result: "installs" };
|
|
19
|
+
}
|
|
20
|
+
if (v.tiktok_spend === undefined)
|
|
21
|
+
return null;
|
|
22
|
+
return { channel: id, spendPerDay: round(v.tiktok_spend), resultsPerDay: round(v.tiktok_conversions), costPerResult: round(v.tiktok_cpa), result: "conversions" };
|
|
23
|
+
}
|
|
24
|
+
/** Every paid channel this spec can read, over the `days` up to `now` (today left out: it is not over yet). */
|
|
25
|
+
export async function adsSnapshot(api, spec, app, days, now = new Date(),
|
|
26
|
+
/** Apple Search Ads pulled with your own key (appeeky/search-ads/); used instead of Appeeky's. */
|
|
27
|
+
ownSearchAds = null) {
|
|
28
|
+
const to = now.toISOString().slice(0, 10);
|
|
29
|
+
const from = new Date(Date.parse(to) - Math.max(1, days) * 86_400_000).toISOString().slice(0, 10);
|
|
30
|
+
const sources = [];
|
|
31
|
+
if (app && ownSearchAds)
|
|
32
|
+
sources.push(["apple-search-ads", ownSearchAds]);
|
|
33
|
+
else if (app && api)
|
|
34
|
+
sources.push(["apple-search-ads", appeekyMetricSource(api)]);
|
|
35
|
+
if (api && spec.ads.meta)
|
|
36
|
+
sources.push(["meta", metaMetricSource(api, spec.ads.meta)]);
|
|
37
|
+
if (api && spec.ads.tiktok)
|
|
38
|
+
sources.push(["tiktok", tiktokMetricSource(api, spec.ads.tiktok)]);
|
|
39
|
+
const snapshot = { from, to, days: Math.max(1, days), channels: [], errors: [] };
|
|
40
|
+
for (const [id, source] of sources) {
|
|
41
|
+
try {
|
|
42
|
+
const w = await source.window(app?.store ?? "app-store", app?.appId ?? "", from, to);
|
|
43
|
+
const channel = w ? channelFrom(id, w) : null;
|
|
44
|
+
if (channel)
|
|
45
|
+
snapshot.channels.push(channel);
|
|
46
|
+
if (w?.currency && !snapshot.currency)
|
|
47
|
+
snapshot.currency = w.currency;
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
snapshot.errors.push({ channel: id, message: e.message });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return snapshot;
|
|
54
|
+
}
|
|
55
|
+
export function adsDir(root) {
|
|
56
|
+
return join(root, SPEC_DIR, "ads");
|
|
57
|
+
}
|
|
58
|
+
/** Write a snapshot to `appeeky/ads/<to>.yaml` and return the path. */
|
|
59
|
+
export async function saveAdsSnapshot(root, snapshot) {
|
|
60
|
+
await mkdir(adsDir(root), { recursive: true });
|
|
61
|
+
const file = join(adsDir(root), `${snapshot.to}.yaml`);
|
|
62
|
+
const { errors: _errors, ...kept } = snapshot;
|
|
63
|
+
await writeFile(file, YAML.stringify(kept, { lineWidth: 0 }));
|
|
64
|
+
return file;
|
|
65
|
+
}
|
|
66
|
+
export const CHANNEL_LABELS = {
|
|
67
|
+
"apple-search-ads": "Apple Search Ads",
|
|
68
|
+
meta: "Meta",
|
|
69
|
+
tiktok: "TikTok",
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=ads.js.map
|
package/dist/agents.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The specialist subagents and the co-founder review, shipped in the package's `agents/` folder
|
|
3
|
+
* and copied into a repo in the format each coding agent reads.
|
|
4
|
+
*
|
|
5
|
+
* - `appeeky-*.md`: read-only specialists (ASO, reviews, ads, releases, UGC, social), written in Claude Code's
|
|
6
|
+
* subagent format.
|
|
7
|
+
* - `cofounder.md`: the review that starts them, writes the brief and proposes one change. It is a
|
|
8
|
+
* command, so it runs in the main session, where subagents can be started.
|
|
9
|
+
*/
|
|
10
|
+
export type AgentTarget = "claude" | "opencode";
|
|
11
|
+
export declare const AGENT_TARGETS: AgentTarget[];
|
|
12
|
+
export declare const COFOUNDER_COMMAND = "appeeky-cofounder";
|
|
13
|
+
/** The folder each target reads subagents and commands from. */
|
|
14
|
+
export declare const TARGET_DIRS: Record<AgentTarget, {
|
|
15
|
+
agents: string;
|
|
16
|
+
commands: string;
|
|
17
|
+
}>;
|
|
18
|
+
export interface AgentFile {
|
|
19
|
+
/** File name without extension, e.g. `appeeky-aso` or `cofounder`. */
|
|
20
|
+
name: string;
|
|
21
|
+
kind: "specialist" | "command";
|
|
22
|
+
meta: Record<string, unknown>;
|
|
23
|
+
body: string;
|
|
24
|
+
}
|
|
25
|
+
export interface InstalledFile {
|
|
26
|
+
path: string;
|
|
27
|
+
status: "written" | "unchanged" | "kept";
|
|
28
|
+
}
|
|
29
|
+
/** The `agents/` folder of the installed package (one level above `dist/` or `src/`). */
|
|
30
|
+
export declare function packagedAgentsDir(): string;
|
|
31
|
+
export declare function readAgentFiles(dir?: string): AgentFile[];
|
|
32
|
+
/** The path and content of one file for one target. */
|
|
33
|
+
export declare function renderAgentFile(file: AgentFile, target: AgentTarget): {
|
|
34
|
+
path: string;
|
|
35
|
+
content: string;
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Write the specialists and the co-founder command into `root` for each target. A file that exists
|
|
39
|
+
* with other content is kept (it may hold the user's own edits) unless `force` is set.
|
|
40
|
+
*/
|
|
41
|
+
export declare function installAgents(root: string, targets: AgentTarget[], opts?: {
|
|
42
|
+
force?: boolean;
|
|
43
|
+
dir?: string;
|
|
44
|
+
}): Promise<InstalledFile[]>;
|
|
45
|
+
/** Targets that already have the specialists in `root`. */
|
|
46
|
+
export declare function installedTargets(root: string): AgentTarget[];
|
|
47
|
+
export declare function parseTargets(value: string): AgentTarget[];
|
package/dist/agents.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { parse, stringify } from "yaml";
|
|
6
|
+
export const AGENT_TARGETS = ["claude", "opencode"];
|
|
7
|
+
export const COFOUNDER_COMMAND = "appeeky-cofounder";
|
|
8
|
+
/** The folder each target reads subagents and commands from. */
|
|
9
|
+
export const TARGET_DIRS = {
|
|
10
|
+
claude: { agents: ".claude/agents", commands: ".claude/commands" },
|
|
11
|
+
opencode: { agents: ".opencode/agent", commands: ".opencode/command" },
|
|
12
|
+
};
|
|
13
|
+
/** The `agents/` folder of the installed package (one level above `dist/` or `src/`). */
|
|
14
|
+
export function packagedAgentsDir() {
|
|
15
|
+
return join(dirname(fileURLToPath(import.meta.url)), "..", "agents");
|
|
16
|
+
}
|
|
17
|
+
function splitFrontmatter(text) {
|
|
18
|
+
const match = /^---\n([\s\S]*?)\n---\n?/.exec(text);
|
|
19
|
+
if (!match)
|
|
20
|
+
return { meta: {}, body: text };
|
|
21
|
+
return { meta: parse(match[1]) ?? {}, body: text.slice(match[0].length) };
|
|
22
|
+
}
|
|
23
|
+
export function readAgentFiles(dir = packagedAgentsDir()) {
|
|
24
|
+
return readdirSync(dir)
|
|
25
|
+
.filter((f) => f.endsWith(".md"))
|
|
26
|
+
.sort()
|
|
27
|
+
.map((f) => {
|
|
28
|
+
const name = f.slice(0, -3);
|
|
29
|
+
const { meta, body } = splitFrontmatter(readFileSync(join(dir, f), "utf8"));
|
|
30
|
+
return { name, kind: name === "cofounder" ? "command" : "specialist", meta, body };
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
function withFrontmatter(meta, body) {
|
|
34
|
+
return `---\n${stringify(meta, { lineWidth: 0 }).trimEnd()}\n---\n${body.startsWith("\n") ? body : `\n${body}`}`;
|
|
35
|
+
}
|
|
36
|
+
/** The path and content of one file for one target. */
|
|
37
|
+
export function renderAgentFile(file, target) {
|
|
38
|
+
const dirs = TARGET_DIRS[target];
|
|
39
|
+
if (file.kind === "command") {
|
|
40
|
+
return { path: `${dirs.commands}/${COFOUNDER_COMMAND}.md`, content: withFrontmatter({ description: file.meta.description }, file.body) };
|
|
41
|
+
}
|
|
42
|
+
if (target === "claude")
|
|
43
|
+
return { path: `${dirs.agents}/${file.name}.md`, content: withFrontmatter(file.meta, file.body) };
|
|
44
|
+
// opencode: the file name is the agent's name, `mode: subagent`, and tools are switched off by name.
|
|
45
|
+
return {
|
|
46
|
+
path: `${dirs.agents}/${file.name}.md`,
|
|
47
|
+
content: withFrontmatter({ description: file.meta.description, mode: "subagent", tools: { write: false, edit: false } }, file.body),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Write the specialists and the co-founder command into `root` for each target. A file that exists
|
|
52
|
+
* with other content is kept (it may hold the user's own edits) unless `force` is set.
|
|
53
|
+
*/
|
|
54
|
+
export async function installAgents(root, targets, opts = {}) {
|
|
55
|
+
const files = readAgentFiles(opts.dir);
|
|
56
|
+
const out = [];
|
|
57
|
+
for (const target of targets) {
|
|
58
|
+
for (const file of files) {
|
|
59
|
+
const { path, content } = renderAgentFile(file, target);
|
|
60
|
+
const full = join(root, path);
|
|
61
|
+
const current = existsSync(full) ? await readFile(full, "utf8") : null;
|
|
62
|
+
if (current === content) {
|
|
63
|
+
out.push({ path, status: "unchanged" });
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (current !== null && !opts.force) {
|
|
67
|
+
out.push({ path, status: "kept" });
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
await mkdir(dirname(full), { recursive: true });
|
|
71
|
+
await writeFile(full, content);
|
|
72
|
+
out.push({ path, status: "written" });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
/** Targets that already have the specialists in `root`. */
|
|
78
|
+
export function installedTargets(root) {
|
|
79
|
+
return AGENT_TARGETS.filter((t) => existsSync(join(root, TARGET_DIRS[t].agents, "appeeky-aso.md")));
|
|
80
|
+
}
|
|
81
|
+
export function parseTargets(value) {
|
|
82
|
+
const list = value === "all" ? AGENT_TARGETS : value.split(",").map((x) => x.trim());
|
|
83
|
+
const bad = list.filter((x) => !AGENT_TARGETS.includes(x));
|
|
84
|
+
if (bad.length)
|
|
85
|
+
throw new Error(`Unknown target: ${bad.join(", ")}. Use ${AGENT_TARGETS.join(", ")} or all.`);
|
|
86
|
+
return list;
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=agents.js.map
|