@appeeky/spec 0.1.0 → 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.
Files changed (46) hide show
  1. package/README.md +158 -5
  2. package/agents/appeeky-ads.md +50 -0
  3. package/agents/appeeky-aso.md +58 -0
  4. package/agents/appeeky-releases.md +47 -0
  5. package/agents/appeeky-reviews.md +53 -0
  6. package/agents/appeeky-social.md +54 -0
  7. package/agents/appeeky-ugc.md +56 -0
  8. package/agents/cofounder.md +36 -0
  9. package/dist/ads.d.ts +5 -2
  10. package/dist/ads.js +8 -4
  11. package/dist/agents.d.ts +47 -0
  12. package/dist/agents.js +88 -0
  13. package/dist/cli.js +582 -9
  14. package/dist/dataforseo.d.ts +139 -0
  15. package/dist/dataforseo.js +354 -0
  16. package/dist/experiments.d.ts +4 -4
  17. package/dist/metrics.d.ts +20 -0
  18. package/dist/metrics.js +4 -0
  19. package/dist/report.d.ts +5 -0
  20. package/dist/report.js +42 -0
  21. package/dist/reviews.d.ts +2 -2
  22. package/dist/schema.d.ts +95 -2
  23. package/dist/schema.js +35 -0
  24. package/dist/search-ads.d.ts +137 -0
  25. package/dist/search-ads.js +354 -0
  26. package/dist/seo.d.ts +87 -0
  27. package/dist/seo.js +391 -0
  28. package/dist/setup.js +39 -6
  29. package/dist/social.d.ts +129 -0
  30. package/dist/social.js +189 -0
  31. package/dist/transport.d.ts +1 -0
  32. package/dist/transport.js +1 -1
  33. package/dist/ugc-core.js +1 -0
  34. package/dist/ugc-local.d.ts +116 -0
  35. package/dist/ugc-local.js +174 -0
  36. package/dist/ugc.d.ts +149 -0
  37. package/dist/ugc.js +111 -0
  38. package/dist/visibility-core.js +96 -0
  39. package/dist/visibility-local.d.ts +159 -0
  40. package/dist/visibility-local.js +246 -0
  41. package/dist/visibility.d.ts +3 -0
  42. package/dist/visibility.js +1 -1
  43. package/package.json +7 -3
  44. package/skills/appeeky-spec/SKILL.md +14 -18
  45. package/skills/appeeky-spec-experiments/SKILL.md +12 -2
  46. package/skills/appeeky-spec-visibility/SKILL.md +33 -6
package/dist/schema.d.ts CHANGED
@@ -30,16 +30,40 @@ export declare const specFileSchema: z.ZodObject<{
30
30
  via: z.ZodDefault<z.ZodEnum<["auto", "local", "hosted"]>>;
31
31
  /** Named account of the local `appstore` CLI. */
32
32
  account: z.ZodOptional<z.ZodString>;
33
+ /**
34
+ * Apple Search Ads on your own API key (ASA_CLIENT_ID, ASA_TEAM_ID, ASA_KEY_ID, ASA_PRIVATE_KEY):
35
+ * `search-ads pull` reads this app's campaigns. Without it, Search Ads comes from Appeeky.
36
+ */
37
+ searchAds: z.ZodOptional<z.ZodObject<{
38
+ /** The organization, when the key reaches more than one. */
39
+ orgId: z.ZodOptional<z.ZodString>;
40
+ /** Only these campaign ids; default: every campaign for this app. */
41
+ campaigns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
42
+ }, "strict", z.ZodTypeAny, {
43
+ orgId?: string | undefined;
44
+ campaigns?: string[] | undefined;
45
+ }, {
46
+ orgId?: string | undefined;
47
+ campaigns?: string[] | undefined;
48
+ }>>;
33
49
  }, "strict", z.ZodTypeAny, {
34
50
  appId: string;
35
51
  platform: "IOS" | "MAC_OS" | "TV_OS" | "VISION_OS";
36
52
  via: "auto" | "local" | "hosted";
37
53
  account?: string | undefined;
54
+ searchAds?: {
55
+ orgId?: string | undefined;
56
+ campaigns?: string[] | undefined;
57
+ } | undefined;
38
58
  }, {
39
59
  appId: string;
40
60
  platform?: "IOS" | "MAC_OS" | "TV_OS" | "VISION_OS" | undefined;
41
61
  via?: "auto" | "local" | "hosted" | undefined;
42
62
  account?: string | undefined;
63
+ searchAds?: {
64
+ orgId?: string | undefined;
65
+ campaigns?: string[] | undefined;
66
+ } | undefined;
43
67
  }>>;
44
68
  googlePlay: z.ZodOptional<z.ZodObject<{
45
69
  packageName: z.ZodString;
@@ -65,6 +89,10 @@ export declare const specFileSchema: z.ZodObject<{
65
89
  platform: "IOS" | "MAC_OS" | "TV_OS" | "VISION_OS";
66
90
  via: "auto" | "local" | "hosted";
67
91
  account?: string | undefined;
92
+ searchAds?: {
93
+ orgId?: string | undefined;
94
+ campaigns?: string[] | undefined;
95
+ } | undefined;
68
96
  } | undefined;
69
97
  googlePlay?: {
70
98
  via: "auto" | "local" | "hosted";
@@ -78,6 +106,10 @@ export declare const specFileSchema: z.ZodObject<{
78
106
  platform?: "IOS" | "MAC_OS" | "TV_OS" | "VISION_OS" | undefined;
79
107
  via?: "auto" | "local" | "hosted" | undefined;
80
108
  account?: string | undefined;
109
+ searchAds?: {
110
+ orgId?: string | undefined;
111
+ campaigns?: string[] | undefined;
112
+ } | undefined;
81
113
  } | undefined;
82
114
  googlePlay?: {
83
115
  packageName: string;
@@ -251,6 +283,10 @@ export declare const specFileSchema: z.ZodObject<{
251
283
  description?: string | undefined;
252
284
  prompts?: string[] | undefined;
253
285
  }>, "many">>>;
286
+ /** Assistants `visibility scan --local` asks, among those with a key set; defaults to all of them. */
287
+ models: z.ZodOptional<z.ZodArray<z.ZodEnum<["chatgpt", "claude", "gemini", "perplexity"]>, "many">>;
288
+ /** Other names the assistants may use for the app, e.g. the brand without the subtitle. */
289
+ aliases: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
254
290
  }, "strict", z.ZodTypeAny, {
255
291
  intents: Record<string, {
256
292
  label: string;
@@ -259,6 +295,8 @@ export declare const specFileSchema: z.ZodObject<{
259
295
  }[]>;
260
296
  store?: "app-store" | "google-play" | undefined;
261
297
  countries?: string[] | undefined;
298
+ models?: ("chatgpt" | "claude" | "gemini" | "perplexity")[] | undefined;
299
+ aliases?: string[] | undefined;
262
300
  }, {
263
301
  store?: "app-store" | "google-play" | undefined;
264
302
  countries?: string[] | undefined;
@@ -267,6 +305,35 @@ export declare const specFileSchema: z.ZodObject<{
267
305
  description?: string | undefined;
268
306
  prompts?: string[] | undefined;
269
307
  }[]> | undefined;
308
+ models?: ("chatgpt" | "claude" | "gemini" | "perplexity")[] | undefined;
309
+ aliases?: string[] | undefined;
310
+ }>>;
311
+ /**
312
+ * The app's website. `seo check` reads it for search and AI readiness; `seo pull` records
313
+ * its Google Search Console clicks with your own service account.
314
+ */
315
+ web: z.ZodOptional<z.ZodObject<{
316
+ url: z.ZodString;
317
+ /** Search Console property: "sc-domain:example.com" or "https://example.com/"; defaults to the url's domain. */
318
+ searchConsole: z.ZodOptional<z.ZodString>;
319
+ /** Google searches to watch with `seo ranks` and `seo keywords`, per storefront; default: the store targets. */
320
+ keywords: z.ZodOptional<z.ZodRecord<z.ZodEffects<z.ZodString, string, string>, z.ZodArray<z.ZodString, "many">>>;
321
+ /** Storefronts to watch; default: the first one under keywords or targets. */
322
+ countries: z.ZodOptional<z.ZodArray<z.ZodEffects<z.ZodString, string, string>, "many">>;
323
+ /** The results page to read; default mobile, where people look for apps. */
324
+ device: z.ZodOptional<z.ZodEnum<["mobile", "desktop"]>>;
325
+ }, "strict", z.ZodTypeAny, {
326
+ url: string;
327
+ countries?: string[] | undefined;
328
+ searchConsole?: string | undefined;
329
+ keywords?: Record<string, string[]> | undefined;
330
+ device?: "mobile" | "desktop" | undefined;
331
+ }, {
332
+ url: string;
333
+ countries?: string[] | undefined;
334
+ searchConsole?: string | undefined;
335
+ keywords?: Record<string, string[]> | undefined;
336
+ device?: "mobile" | "desktop" | undefined;
270
337
  }>>;
271
338
  /** Rank in the app's category chart (top 100), recorded daily by `category pull`. */
272
339
  category: z.ZodOptional<z.ZodObject<{
@@ -319,6 +386,10 @@ export declare const specFileSchema: z.ZodObject<{
319
386
  platform: "IOS" | "MAC_OS" | "TV_OS" | "VISION_OS";
320
387
  via: "auto" | "local" | "hosted";
321
388
  account?: string | undefined;
389
+ searchAds?: {
390
+ orgId?: string | undefined;
391
+ campaigns?: string[] | undefined;
392
+ } | undefined;
322
393
  } | undefined;
323
394
  googlePlay?: {
324
395
  via: "auto" | "local" | "hosted";
@@ -369,6 +440,15 @@ export declare const specFileSchema: z.ZodObject<{
369
440
  }[]>;
370
441
  store?: "app-store" | "google-play" | undefined;
371
442
  countries?: string[] | undefined;
443
+ models?: ("chatgpt" | "claude" | "gemini" | "perplexity")[] | undefined;
444
+ aliases?: string[] | undefined;
445
+ } | undefined;
446
+ web?: {
447
+ url: string;
448
+ countries?: string[] | undefined;
449
+ searchConsole?: string | undefined;
450
+ keywords?: Record<string, string[]> | undefined;
451
+ device?: "mobile" | "desktop" | undefined;
372
452
  } | undefined;
373
453
  category?: {
374
454
  appStore?: string | undefined;
@@ -388,6 +468,10 @@ export declare const specFileSchema: z.ZodObject<{
388
468
  platform?: "IOS" | "MAC_OS" | "TV_OS" | "VISION_OS" | undefined;
389
469
  via?: "auto" | "local" | "hosted" | undefined;
390
470
  account?: string | undefined;
471
+ searchAds?: {
472
+ orgId?: string | undefined;
473
+ campaigns?: string[] | undefined;
474
+ } | undefined;
391
475
  } | undefined;
392
476
  googlePlay?: {
393
477
  packageName: string;
@@ -432,6 +516,15 @@ export declare const specFileSchema: z.ZodObject<{
432
516
  description?: string | undefined;
433
517
  prompts?: string[] | undefined;
434
518
  }[]> | undefined;
519
+ models?: ("chatgpt" | "claude" | "gemini" | "perplexity")[] | undefined;
520
+ aliases?: string[] | undefined;
521
+ } | undefined;
522
+ web?: {
523
+ url: string;
524
+ countries?: string[] | undefined;
525
+ searchConsole?: string | undefined;
526
+ keywords?: Record<string, string[]> | undefined;
527
+ device?: "mobile" | "desktop" | undefined;
435
528
  } | undefined;
436
529
  category?: {
437
530
  appStore?: string | undefined;
@@ -464,8 +557,8 @@ export declare const appStoreListingSchema: z.ZodObject<{
464
557
  }, "strict", z.ZodTypeAny, {
465
558
  description?: string | undefined;
466
559
  name?: string | undefined;
467
- subtitle?: string | undefined;
468
560
  keywords?: string | undefined;
561
+ subtitle?: string | undefined;
469
562
  promotionalText?: string | undefined;
470
563
  whatsNew?: string | undefined;
471
564
  marketingUrl?: string | undefined;
@@ -473,8 +566,8 @@ export declare const appStoreListingSchema: z.ZodObject<{
473
566
  }, {
474
567
  description?: string | undefined;
475
568
  name?: string | undefined;
476
- subtitle?: string | undefined;
477
569
  keywords?: string | undefined;
570
+ subtitle?: string | undefined;
478
571
  promotionalText?: string | undefined;
479
572
  whatsNew?: string | undefined;
480
573
  marketingUrl?: string | undefined;
package/dist/schema.js CHANGED
@@ -15,6 +15,19 @@ const appStoreConfig = z
15
15
  via: transportMode.default("auto"),
16
16
  /** Named account of the local `appstore` CLI. */
17
17
  account: z.string().optional(),
18
+ /**
19
+ * Apple Search Ads on your own API key (ASA_CLIENT_ID, ASA_TEAM_ID, ASA_KEY_ID, ASA_PRIVATE_KEY):
20
+ * `search-ads pull` reads this app's campaigns. Without it, Search Ads comes from Appeeky.
21
+ */
22
+ searchAds: z
23
+ .object({
24
+ /** The organization, when the key reaches more than one. */
25
+ orgId: z.string().regex(/^\d+$/, "Use the numeric org id from Apple Search Ads → Account Settings").optional(),
26
+ /** Only these campaign ids; default: every campaign for this app. */
27
+ campaigns: z.array(z.string().regex(/^\d+$/)).optional(),
28
+ })
29
+ .strict()
30
+ .optional(),
18
31
  })
19
32
  .strict();
20
33
  const googlePlayConfig = z
@@ -140,6 +153,28 @@ export const specFileSchema = z
140
153
  countries: z.array(countryCode).min(1).optional(),
141
154
  /** What people ask AI assistants, per storefront. `visibility sync` sends them to Appeeky. */
142
155
  intents: z.record(countryCode, z.array(aiIntent)).default({}),
156
+ /** Assistants `visibility scan --local` asks, among those with a key set; defaults to all of them. */
157
+ models: z.array(z.enum(["chatgpt", "claude", "gemini", "perplexity"])).min(1).optional(),
158
+ /** Other names the assistants may use for the app, e.g. the brand without the subtitle. */
159
+ aliases: z.array(z.string().min(2)).optional(),
160
+ })
161
+ .strict()
162
+ .optional(),
163
+ /**
164
+ * The app's website. `seo check` reads it for search and AI readiness; `seo pull` records
165
+ * its Google Search Console clicks with your own service account.
166
+ */
167
+ web: z
168
+ .object({
169
+ url: z.string().url(),
170
+ /** Search Console property: "sc-domain:example.com" or "https://example.com/"; defaults to the url's domain. */
171
+ searchConsole: z.string().min(3).optional(),
172
+ /** Google searches to watch with `seo ranks` and `seo keywords`, per storefront; default: the store targets. */
173
+ keywords: z.record(countryCode, z.array(z.string().min(1))).optional(),
174
+ /** Storefronts to watch; default: the first one under keywords or targets. */
175
+ countries: z.array(countryCode).min(1).optional(),
176
+ /** The results page to read; default mobile, where people look for apps. */
177
+ device: z.enum(["mobile", "desktop"]).optional(),
143
178
  })
144
179
  .strict()
145
180
  .optional(),
@@ -0,0 +1,137 @@
1
+ import { type Runner } from "./transport.js";
2
+ import type { MetricSource } from "./metrics.js";
3
+ import type { Project } from "./schema.js";
4
+ export declare const searchAdsDir: (root: string) => string;
5
+ export interface SearchAdsCredentials {
6
+ clientId: string;
7
+ teamId: string;
8
+ keyId: string;
9
+ privateKey: string;
10
+ orgId?: string;
11
+ }
12
+ /** ASA_* (or the APPLE_SEARCH_ADS_* spelling); null when no key is set. */
13
+ export declare function readSearchAdsCredentials(env: Record<string, string | undefined>, orgId?: string): Promise<SearchAdsCredentials | null>;
14
+ /** The client secret Apple wants: a JWT signed with the API key (ES256). */
15
+ export declare function clientSecret(creds: SearchAdsCredentials, now?: Date): string;
16
+ /** One Search Ads API call: returns Apple's JSON body, or throws with Apple's message. */
17
+ export type SearchAdsRequest = (method: string, path: string, body?: unknown) => Promise<{
18
+ data?: unknown;
19
+ }>;
20
+ /** Calls Apple directly with a key from the environment (ASA_*). */
21
+ export declare function directRequest(creds: SearchAdsCredentials, fetcher?: typeof fetch): {
22
+ request: SearchAdsRequest;
23
+ org: () => Promise<string>;
24
+ };
25
+ /**
26
+ * Calls Apple through the App Store Connect CLI (`appstore search-ads call`), with the Search Ads key
27
+ * saved on an account next to its App Store Connect key, so the private key never leaves the CLI:
28
+ * appstore accounts search-ads <account> --client-id … --team-id … --key-id … --private-key-path …
29
+ */
30
+ export declare function cliRequest(options?: {
31
+ account?: string;
32
+ orgId?: string;
33
+ runner?: Runner;
34
+ useNpx?: boolean;
35
+ }): {
36
+ request: SearchAdsRequest;
37
+ org: () => Promise<string>;
38
+ };
39
+ /**
40
+ * Where the Search Ads key comes from: ASA_* in the environment (called directly), else the
41
+ * App Store Connect CLI account in stores.appStore.account (or its default account).
42
+ */
43
+ export declare function searchAdsSource(project: Project, env?: Record<string, string | undefined>, runner?: Runner): Promise<{
44
+ label: string;
45
+ source: SearchAdsCredentials;
46
+ } | {
47
+ label: string;
48
+ source: {
49
+ request: SearchAdsRequest;
50
+ org: () => Promise<string>;
51
+ };
52
+ }>;
53
+ export declare class SearchAdsClient {
54
+ private readonly request;
55
+ private readonly resolveOrg;
56
+ /** A key from the environment (calls Apple directly), or another way to call it, such as the appstore CLI. */
57
+ constructor(source: SearchAdsCredentials | {
58
+ request: SearchAdsRequest;
59
+ org: () => Promise<string>;
60
+ }, fetcher?: typeof fetch);
61
+ private call;
62
+ /** The organization the calls go to. */
63
+ org(): Promise<string>;
64
+ campaigns(): Promise<Campaign[]>;
65
+ /** Per campaign and day. */
66
+ daily(from: string, to: string): Promise<CampaignReportRow[]>;
67
+ /** Totals per keyword (or per search term) for one campaign. */
68
+ totals(campaignId: number | string, kind: "keywords" | "searchterms", from: string, to: string): Promise<TotalsRow[]>;
69
+ }
70
+ export interface Campaign {
71
+ id: number;
72
+ name: string;
73
+ adamId: number;
74
+ status?: string;
75
+ displayStatus?: string;
76
+ countriesOrRegions?: string[];
77
+ /** Where the ads show: only APPSTORE_SEARCH_RESULTS campaigns have keywords and search terms. */
78
+ supplySources?: string[];
79
+ adChannelType?: string;
80
+ }
81
+ interface Metrics {
82
+ impressions?: number;
83
+ taps?: number;
84
+ totalInstalls?: number;
85
+ installs?: number;
86
+ tapInstalls?: number;
87
+ localSpend?: {
88
+ amount: string;
89
+ currency: string;
90
+ };
91
+ }
92
+ export interface CampaignReportRow {
93
+ metadata: {
94
+ campaignId: number;
95
+ campaignName?: string;
96
+ adamId?: number;
97
+ };
98
+ granularity?: Array<Metrics & {
99
+ date: string;
100
+ }>;
101
+ }
102
+ export interface TotalsRow {
103
+ metadata: {
104
+ keyword?: string;
105
+ searchTermText?: string | null;
106
+ matchType?: string;
107
+ countryOrRegion?: string;
108
+ countriesOrRegions?: string[];
109
+ keywordStatus?: string;
110
+ };
111
+ total?: Metrics;
112
+ }
113
+ export interface SearchAdsPullResult {
114
+ orgId: string;
115
+ campaigns: Array<{
116
+ id: number;
117
+ name: string;
118
+ status?: string;
119
+ }>;
120
+ currency?: string;
121
+ last30: {
122
+ spend: number;
123
+ installs: number;
124
+ taps: number;
125
+ impressions: number;
126
+ };
127
+ keywords: number;
128
+ searchTerms: number;
129
+ /** Reports that could not be read; the rest were written. */
130
+ errors: string[];
131
+ files: string[];
132
+ }
133
+ /** The App Store app's campaigns only: ads for other apps in the same organization stay out. */
134
+ export declare function pullSearchAds(project: Project, client: SearchAdsClient, now?: Date, days?: number): Promise<SearchAdsPullResult>;
135
+ /** `ad_installs` and `cpa` per day from appeeky/search-ads/daily.csv, for the App Store app. */
136
+ export declare function searchAdsMetricSource(root: string): MetricSource | null;
137
+ export {};