adaria-ai 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/LICENSE +21 -0
- package/README.md +21 -0
- package/apps.example.yaml +65 -0
- package/dist/agent/audit.d.ts +16 -0
- package/dist/agent/audit.d.ts.map +1 -0
- package/dist/agent/audit.js +42 -0
- package/dist/agent/audit.js.map +1 -0
- package/dist/agent/claude.d.ts +62 -0
- package/dist/agent/claude.d.ts.map +1 -0
- package/dist/agent/claude.js +297 -0
- package/dist/agent/claude.js.map +1 -0
- package/dist/agent/conversation-summary.d.ts +29 -0
- package/dist/agent/conversation-summary.d.ts.map +1 -0
- package/dist/agent/conversation-summary.js +221 -0
- package/dist/agent/conversation-summary.js.map +1 -0
- package/dist/agent/core.d.ts +81 -0
- package/dist/agent/core.d.ts.map +1 -0
- package/dist/agent/core.js +527 -0
- package/dist/agent/core.js.map +1 -0
- package/dist/agent/mcp-launcher.d.ts +42 -0
- package/dist/agent/mcp-launcher.d.ts.map +1 -0
- package/dist/agent/mcp-launcher.js +38 -0
- package/dist/agent/mcp-launcher.js.map +1 -0
- package/dist/agent/mcp-manager.d.ts +81 -0
- package/dist/agent/mcp-manager.d.ts.map +1 -0
- package/dist/agent/mcp-manager.js +136 -0
- package/dist/agent/mcp-manager.js.map +1 -0
- package/dist/agent/memory.d.ts +10 -0
- package/dist/agent/memory.d.ts.map +1 -0
- package/dist/agent/memory.js +95 -0
- package/dist/agent/memory.js.map +1 -0
- package/dist/agent/safety.d.ts +45 -0
- package/dist/agent/safety.d.ts.map +1 -0
- package/dist/agent/safety.js +71 -0
- package/dist/agent/safety.js.map +1 -0
- package/dist/agent/session.d.ts +27 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +124 -0
- package/dist/agent/session.js.map +1 -0
- package/dist/agent/tool-descriptions.d.ts +8 -0
- package/dist/agent/tool-descriptions.d.ts.map +1 -0
- package/dist/agent/tool-descriptions.js +26 -0
- package/dist/agent/tool-descriptions.js.map +1 -0
- package/dist/cli/analyze.d.ts +8 -0
- package/dist/cli/analyze.d.ts.map +1 -0
- package/dist/cli/analyze.js +114 -0
- package/dist/cli/analyze.js.map +1 -0
- package/dist/cli/daemon.d.ts +2 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +91 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/cli/doctor.d.ts +2 -0
- package/dist/cli/doctor.d.ts.map +1 -0
- package/dist/cli/doctor.js +198 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.d.ts.map +1 -0
- package/dist/cli/init.js +459 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/logs.d.ts +4 -0
- package/dist/cli/logs.d.ts.map +1 -0
- package/dist/cli/logs.js +50 -0
- package/dist/cli/logs.js.map +1 -0
- package/dist/cli/monitor-cmd.d.ts +11 -0
- package/dist/cli/monitor-cmd.d.ts.map +1 -0
- package/dist/cli/monitor-cmd.js +59 -0
- package/dist/cli/monitor-cmd.js.map +1 -0
- package/dist/cli/start.d.ts +11 -0
- package/dist/cli/start.d.ts.map +1 -0
- package/dist/cli/start.js +103 -0
- package/dist/cli/start.js.map +1 -0
- package/dist/cli/status.d.ts +9 -0
- package/dist/cli/status.d.ts.map +1 -0
- package/dist/cli/status.js +49 -0
- package/dist/cli/status.js.map +1 -0
- package/dist/cli/stop.d.ts +2 -0
- package/dist/cli/stop.d.ts.map +1 -0
- package/dist/cli/stop.js +34 -0
- package/dist/cli/stop.js.map +1 -0
- package/dist/collectors/appstore.d.ts +51 -0
- package/dist/collectors/appstore.d.ts.map +1 -0
- package/dist/collectors/appstore.js +166 -0
- package/dist/collectors/appstore.js.map +1 -0
- package/dist/collectors/arden-tts.d.ts +60 -0
- package/dist/collectors/arden-tts.d.ts.map +1 -0
- package/dist/collectors/arden-tts.js +83 -0
- package/dist/collectors/arden-tts.js.map +1 -0
- package/dist/collectors/asomobile.d.ts +37 -0
- package/dist/collectors/asomobile.d.ts.map +1 -0
- package/dist/collectors/asomobile.js +88 -0
- package/dist/collectors/asomobile.js.map +1 -0
- package/dist/collectors/eodin-blog.d.ts +90 -0
- package/dist/collectors/eodin-blog.d.ts.map +1 -0
- package/dist/collectors/eodin-blog.js +238 -0
- package/dist/collectors/eodin-blog.js.map +1 -0
- package/dist/collectors/eodin-sdk.d.ts +60 -0
- package/dist/collectors/eodin-sdk.d.ts.map +1 -0
- package/dist/collectors/eodin-sdk.js +112 -0
- package/dist/collectors/eodin-sdk.js.map +1 -0
- package/dist/collectors/fridgify-recipes.d.ts +65 -0
- package/dist/collectors/fridgify-recipes.d.ts.map +1 -0
- package/dist/collectors/fridgify-recipes.js +111 -0
- package/dist/collectors/fridgify-recipes.js.map +1 -0
- package/dist/collectors/playstore.d.ts +46 -0
- package/dist/collectors/playstore.d.ts.map +1 -0
- package/dist/collectors/playstore.js +140 -0
- package/dist/collectors/playstore.js.map +1 -0
- package/dist/collectors/youtube.d.ts +44 -0
- package/dist/collectors/youtube.d.ts.map +1 -0
- package/dist/collectors/youtube.js +107 -0
- package/dist/collectors/youtube.js.map +1 -0
- package/dist/config/apps-schema.d.ts +94 -0
- package/dist/config/apps-schema.d.ts.map +1 -0
- package/dist/config/apps-schema.js +66 -0
- package/dist/config/apps-schema.js.map +1 -0
- package/dist/config/keychain.d.ts +14 -0
- package/dist/config/keychain.d.ts.map +1 -0
- package/dist/config/keychain.js +89 -0
- package/dist/config/keychain.js.map +1 -0
- package/dist/config/load-apps.d.ts +16 -0
- package/dist/config/load-apps.d.ts.map +1 -0
- package/dist/config/load-apps.js +38 -0
- package/dist/config/load-apps.js.map +1 -0
- package/dist/config/schema.d.ts +306 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +220 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/config/store.d.ts +38 -0
- package/dist/config/store.d.ts.map +1 -0
- package/dist/config/store.js +180 -0
- package/dist/config/store.js.map +1 -0
- package/dist/db/queries.d.ts +304 -0
- package/dist/db/queries.d.ts.map +1 -0
- package/dist/db/queries.js +327 -0
- package/dist/db/queries.js.map +1 -0
- package/dist/db/schema.d.ts +15 -0
- package/dist/db/schema.d.ts.map +1 -0
- package/dist/db/schema.js +252 -0
- package/dist/db/schema.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +86 -0
- package/dist/index.js.map +1 -0
- package/dist/messenger/adapter.d.ts +63 -0
- package/dist/messenger/adapter.d.ts.map +1 -0
- package/dist/messenger/adapter.js +7 -0
- package/dist/messenger/adapter.js.map +1 -0
- package/dist/messenger/factory.d.ts +12 -0
- package/dist/messenger/factory.d.ts.map +1 -0
- package/dist/messenger/factory.js +9 -0
- package/dist/messenger/factory.js.map +1 -0
- package/dist/messenger/slack.d.ts +30 -0
- package/dist/messenger/slack.d.ts.map +1 -0
- package/dist/messenger/slack.js +309 -0
- package/dist/messenger/slack.js.map +1 -0
- package/dist/messenger/split.d.ts +17 -0
- package/dist/messenger/split.d.ts.map +1 -0
- package/dist/messenger/split.js +56 -0
- package/dist/messenger/split.js.map +1 -0
- package/dist/orchestrator/dashboard.d.ts +67 -0
- package/dist/orchestrator/dashboard.d.ts.map +1 -0
- package/dist/orchestrator/dashboard.js +113 -0
- package/dist/orchestrator/dashboard.js.map +1 -0
- package/dist/orchestrator/monitor.d.ts +37 -0
- package/dist/orchestrator/monitor.d.ts.map +1 -0
- package/dist/orchestrator/monitor.js +236 -0
- package/dist/orchestrator/monitor.js.map +1 -0
- package/dist/orchestrator/types.d.ts +82 -0
- package/dist/orchestrator/types.d.ts.map +1 -0
- package/dist/orchestrator/types.js +12 -0
- package/dist/orchestrator/types.js.map +1 -0
- package/dist/orchestrator/weekly.d.ts +66 -0
- package/dist/orchestrator/weekly.d.ts.map +1 -0
- package/dist/orchestrator/weekly.js +376 -0
- package/dist/orchestrator/weekly.js.map +1 -0
- package/dist/prompts/loader.d.ts +18 -0
- package/dist/prompts/loader.d.ts.map +1 -0
- package/dist/prompts/loader.js +28 -0
- package/dist/prompts/loader.js.map +1 -0
- package/dist/security/auth.d.ts +14 -0
- package/dist/security/auth.d.ts.map +1 -0
- package/dist/security/auth.js +14 -0
- package/dist/security/auth.js.map +1 -0
- package/dist/security/prompt-guard.d.ts +21 -0
- package/dist/security/prompt-guard.d.ts.map +1 -0
- package/dist/security/prompt-guard.js +54 -0
- package/dist/security/prompt-guard.js.map +1 -0
- package/dist/skills/aso.d.ts +60 -0
- package/dist/skills/aso.d.ts.map +1 -0
- package/dist/skills/aso.js +322 -0
- package/dist/skills/aso.js.map +1 -0
- package/dist/skills/content.d.ts +25 -0
- package/dist/skills/content.d.ts.map +1 -0
- package/dist/skills/content.js +90 -0
- package/dist/skills/content.js.map +1 -0
- package/dist/skills/index.d.ts +65 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +90 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/skills/onboarding.d.ts +58 -0
- package/dist/skills/onboarding.d.ts.map +1 -0
- package/dist/skills/onboarding.js +274 -0
- package/dist/skills/onboarding.js.map +1 -0
- package/dist/skills/registry.d.ts +24 -0
- package/dist/skills/registry.d.ts.map +1 -0
- package/dist/skills/registry.js +66 -0
- package/dist/skills/registry.js.map +1 -0
- package/dist/skills/review.d.ts +33 -0
- package/dist/skills/review.d.ts.map +1 -0
- package/dist/skills/review.js +236 -0
- package/dist/skills/review.js.map +1 -0
- package/dist/skills/sdk-request.d.ts +30 -0
- package/dist/skills/sdk-request.d.ts.map +1 -0
- package/dist/skills/sdk-request.js +72 -0
- package/dist/skills/sdk-request.js.map +1 -0
- package/dist/skills/seo-blog.d.ts +64 -0
- package/dist/skills/seo-blog.d.ts.map +1 -0
- package/dist/skills/seo-blog.js +268 -0
- package/dist/skills/seo-blog.js.map +1 -0
- package/dist/skills/short-form.d.ts +28 -0
- package/dist/skills/short-form.d.ts.map +1 -0
- package/dist/skills/short-form.js +121 -0
- package/dist/skills/short-form.js.map +1 -0
- package/dist/skills/social-publish.d.ts +32 -0
- package/dist/skills/social-publish.d.ts.map +1 -0
- package/dist/skills/social-publish.js +133 -0
- package/dist/skills/social-publish.js.map +1 -0
- package/dist/social/base.d.ts +47 -0
- package/dist/social/base.d.ts.map +1 -0
- package/dist/social/base.js +26 -0
- package/dist/social/base.js.map +1 -0
- package/dist/social/facebook.d.ts +27 -0
- package/dist/social/facebook.d.ts.map +1 -0
- package/dist/social/facebook.js +166 -0
- package/dist/social/facebook.js.map +1 -0
- package/dist/social/factory.d.ts +26 -0
- package/dist/social/factory.d.ts.map +1 -0
- package/dist/social/factory.js +32 -0
- package/dist/social/factory.js.map +1 -0
- package/dist/social/linkedin.d.ts +26 -0
- package/dist/social/linkedin.d.ts.map +1 -0
- package/dist/social/linkedin.js +190 -0
- package/dist/social/linkedin.js.map +1 -0
- package/dist/social/threads.d.ts +21 -0
- package/dist/social/threads.d.ts.map +1 -0
- package/dist/social/threads.js +122 -0
- package/dist/social/threads.js.map +1 -0
- package/dist/social/tiktok.d.ts +23 -0
- package/dist/social/tiktok.d.ts.map +1 -0
- package/dist/social/tiktok.js +110 -0
- package/dist/social/tiktok.js.map +1 -0
- package/dist/social/twitter.d.ts +30 -0
- package/dist/social/twitter.d.ts.map +1 -0
- package/dist/social/twitter.js +189 -0
- package/dist/social/twitter.js.map +1 -0
- package/dist/social/youtube.d.ts +21 -0
- package/dist/social/youtube.d.ts.map +1 -0
- package/dist/social/youtube.js +108 -0
- package/dist/social/youtube.js.map +1 -0
- package/dist/tools/app-info.d.ts +7 -0
- package/dist/tools/app-info.d.ts.map +1 -0
- package/dist/tools/app-info.js +53 -0
- package/dist/tools/app-info.js.map +1 -0
- package/dist/tools/collector-fetch.d.ts +11 -0
- package/dist/tools/collector-fetch.d.ts.map +1 -0
- package/dist/tools/collector-fetch.js +101 -0
- package/dist/tools/collector-fetch.js.map +1 -0
- package/dist/tools/db-query.d.ts +29 -0
- package/dist/tools/db-query.d.ts.map +1 -0
- package/dist/tools/db-query.js +159 -0
- package/dist/tools/db-query.js.map +1 -0
- package/dist/tools/skill-result.d.ts +8 -0
- package/dist/tools/skill-result.d.ts.map +1 -0
- package/dist/tools/skill-result.js +63 -0
- package/dist/tools/skill-result.js.map +1 -0
- package/dist/tools/tool-host.d.ts +12 -0
- package/dist/tools/tool-host.d.ts.map +1 -0
- package/dist/tools/tool-host.js +124 -0
- package/dist/tools/tool-host.js.map +1 -0
- package/dist/types/collectors.d.ts +198 -0
- package/dist/types/collectors.d.ts.map +1 -0
- package/dist/types/collectors.js +28 -0
- package/dist/types/collectors.js.map +1 -0
- package/dist/types/skill.d.ts +60 -0
- package/dist/types/skill.d.ts.map +1 -0
- package/dist/types/skill.js +9 -0
- package/dist/types/skill.js.map +1 -0
- package/dist/utils/circuit-breaker.d.ts +26 -0
- package/dist/utils/circuit-breaker.d.ts.map +1 -0
- package/dist/utils/circuit-breaker.js +67 -0
- package/dist/utils/circuit-breaker.js.map +1 -0
- package/dist/utils/errors.d.ts +44 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +75 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/escape.d.ts +11 -0
- package/dist/utils/escape.d.ts.map +1 -0
- package/dist/utils/escape.js +19 -0
- package/dist/utils/escape.js.map +1 -0
- package/dist/utils/logger.d.ts +19 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +93 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/parse-json.d.ts +13 -0
- package/dist/utils/parse-json.d.ts.map +1 -0
- package/dist/utils/parse-json.js +61 -0
- package/dist/utils/parse-json.js.map +1 -0
- package/dist/utils/paths.d.ts +14 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +19 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/rate-limiter.d.ts +20 -0
- package/dist/utils/rate-limiter.d.ts.map +1 -0
- package/dist/utils/rate-limiter.js +47 -0
- package/dist/utils/rate-limiter.js.map +1 -0
- package/dist/utils/retry.d.ts +26 -0
- package/dist/utils/retry.d.ts.map +1 -0
- package/dist/utils/retry.js +61 -0
- package/dist/utils/retry.js.map +1 -0
- package/launchd/.gitkeep +0 -0
- package/launchd/com.adaria-ai.daemon.plist.template +62 -0
- package/launchd/com.adaria-ai.monitor.plist.template +41 -0
- package/launchd/com.adaria-ai.weekly.plist.template +43 -0
- package/package.json +72 -0
- package/prompts/aso-description.md +44 -0
- package/prompts/aso-inapp-events.md +20 -0
- package/prompts/aso-metadata.md +34 -0
- package/prompts/aso-screenshots.md +20 -0
- package/prompts/onboarding-hypotheses.md +38 -0
- package/prompts/onboarding-review-timing.md +24 -0
- package/prompts/review-clustering.md +19 -0
- package/prompts/review-replies.md +18 -0
- package/prompts/review-sentiment.md +16 -0
- package/prompts/seo-blog-fridgify-recipe.md +116 -0
- package/prompts/seo-blog.md +69 -0
- package/prompts/short-form-ideas.md +50 -0
- package/prompts/social-publish.md +46 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { AsoCompetitorInfo, AsoKeywordRanking, AsoKeywordSuggestion } from "../types/collectors.js";
|
|
2
|
+
/**
|
|
3
|
+
* ASOMobile API collector.
|
|
4
|
+
* Fetches keyword rankings, search volumes, and competition data.
|
|
5
|
+
*
|
|
6
|
+
* @see https://asomobile.net
|
|
7
|
+
*/
|
|
8
|
+
export interface AsoMobileCollectorOptions {
|
|
9
|
+
apiKey: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Test-only overrides. Kept off {@link AsoMobileCollectorOptions} so that
|
|
13
|
+
* production config loaders cannot feed a user-controlled base URL.
|
|
14
|
+
*/
|
|
15
|
+
export interface AsoMobileCollectorTestHooks {
|
|
16
|
+
baseUrl?: string;
|
|
17
|
+
}
|
|
18
|
+
export type AsoPlatform = "ios" | "android";
|
|
19
|
+
export declare class AsoMobileCollector {
|
|
20
|
+
private readonly apiKey;
|
|
21
|
+
private readonly baseUrl;
|
|
22
|
+
constructor(options: AsoMobileCollectorOptions, testHooks?: AsoMobileCollectorTestHooks);
|
|
23
|
+
private request;
|
|
24
|
+
/**
|
|
25
|
+
* Get keyword rankings for an app.
|
|
26
|
+
*/
|
|
27
|
+
getKeywordRankings(appId: string, platform: AsoPlatform, keywords: string[]): Promise<AsoKeywordRanking[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Get keyword suggestions (high volume + low competition).
|
|
30
|
+
*/
|
|
31
|
+
getKeywordSuggestions(appId: string, platform: AsoPlatform, locale?: string): Promise<AsoKeywordSuggestion[]>;
|
|
32
|
+
/**
|
|
33
|
+
* Get competitor metadata.
|
|
34
|
+
*/
|
|
35
|
+
getCompetitorInfo(competitorId: string, platform: AsoPlatform): Promise<AsoCompetitorInfo>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=asomobile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asomobile.d.ts","sourceRoot":"","sources":["../../src/collectors/asomobile.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EACjB,oBAAoB,EACrB,MAAM,wBAAwB,CAAC;AAEhC;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,2BAA2B;IAC1C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,WAAW,GAAG,KAAK,GAAG,SAAS,CAAC;AAgC5C,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAG/B,OAAO,EAAE,yBAAyB,EAClC,SAAS,CAAC,EAAE,2BAA2B;YAS3B,OAAO;IAqCrB;;OAEG;IACG,kBAAkB,CACtB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,WAAW,EACrB,QAAQ,EAAE,MAAM,EAAE,GACjB,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAoB/B;;OAEG;IACG,qBAAqB,CACzB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,WAAW,EACrB,MAAM,SAAO,GACZ,OAAO,CAAC,oBAAoB,EAAE,CAAC;IAiBlC;;OAEG;IACG,iBAAiB,CACrB,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,WAAW,GACpB,OAAO,CAAC,iBAAiB,CAAC;CAa9B"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { ExternalApiError, RateLimitError } from "../utils/errors.js";
|
|
2
|
+
import { parseRetryAfter } from "../utils/retry.js";
|
|
3
|
+
const DEFAULT_BASE_URL = "https://api.asomobile.net/v2";
|
|
4
|
+
const ALLOWED_HOSTS = new Set(["api.asomobile.net"]);
|
|
5
|
+
export class AsoMobileCollector {
|
|
6
|
+
apiKey;
|
|
7
|
+
baseUrl;
|
|
8
|
+
constructor(options, testHooks) {
|
|
9
|
+
if (!options.apiKey) {
|
|
10
|
+
throw new Error("AsoMobileCollector requires apiKey");
|
|
11
|
+
}
|
|
12
|
+
this.apiKey = options.apiKey;
|
|
13
|
+
this.baseUrl = testHooks?.baseUrl ?? DEFAULT_BASE_URL;
|
|
14
|
+
}
|
|
15
|
+
async request(path, params = {}) {
|
|
16
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
17
|
+
if (!ALLOWED_HOSTS.has(url.hostname)) {
|
|
18
|
+
throw new Error(`Untrusted ASOMobile host: ${url.hostname}. Allowed: ${[...ALLOWED_HOSTS].join(", ")}`);
|
|
19
|
+
}
|
|
20
|
+
for (const [key, value] of Object.entries(params)) {
|
|
21
|
+
if (value === undefined || value === "")
|
|
22
|
+
continue;
|
|
23
|
+
url.searchParams.set(key, String(value));
|
|
24
|
+
}
|
|
25
|
+
const response = await fetch(url.toString(), {
|
|
26
|
+
headers: { "X-API-Key": this.apiKey },
|
|
27
|
+
});
|
|
28
|
+
if (response.status === 429) {
|
|
29
|
+
throw new RateLimitError("ASOMobile API rate limited", {
|
|
30
|
+
retryAfterSeconds: parseRetryAfter(response.headers.get("Retry-After")),
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (!response.ok) {
|
|
34
|
+
const body = await response.text();
|
|
35
|
+
throw new ExternalApiError(`ASOMobile API ${String(response.status)}: ${body}`, { statusCode: response.status });
|
|
36
|
+
}
|
|
37
|
+
return (await response.json());
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get keyword rankings for an app.
|
|
41
|
+
*/
|
|
42
|
+
async getKeywordRankings(appId, platform, keywords) {
|
|
43
|
+
if (keywords.length === 0)
|
|
44
|
+
return [];
|
|
45
|
+
const data = await this.request("/keywords/rankings", {
|
|
46
|
+
app_id: appId,
|
|
47
|
+
platform,
|
|
48
|
+
keywords: keywords.join(","),
|
|
49
|
+
});
|
|
50
|
+
return (data.results ?? []).map((r) => ({
|
|
51
|
+
keyword: r.keyword,
|
|
52
|
+
rank: r.rank ?? null,
|
|
53
|
+
searchVolume: r.search_volume ?? 0,
|
|
54
|
+
competition: r.competition ?? null,
|
|
55
|
+
}));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get keyword suggestions (high volume + low competition).
|
|
59
|
+
*/
|
|
60
|
+
async getKeywordSuggestions(appId, platform, locale = "ko") {
|
|
61
|
+
const data = await this.request("/keywords/suggestions", {
|
|
62
|
+
app_id: appId,
|
|
63
|
+
platform,
|
|
64
|
+
locale,
|
|
65
|
+
});
|
|
66
|
+
return (data.suggestions ?? []).map((s) => ({
|
|
67
|
+
keyword: s.keyword,
|
|
68
|
+
searchVolume: s.search_volume ?? 0,
|
|
69
|
+
competition: s.competition ?? null,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Get competitor metadata.
|
|
74
|
+
*/
|
|
75
|
+
async getCompetitorInfo(competitorId, platform) {
|
|
76
|
+
const data = await this.request("/app/info", {
|
|
77
|
+
app_id: competitorId,
|
|
78
|
+
platform,
|
|
79
|
+
});
|
|
80
|
+
return {
|
|
81
|
+
title: data.title ?? "",
|
|
82
|
+
subtitle: data.subtitle ?? "",
|
|
83
|
+
description: data.description ?? "",
|
|
84
|
+
keywords: data.keywords ?? [],
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=asomobile.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"asomobile.js","sourceRoot":"","sources":["../../src/collectors/asomobile.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { BlogListOptions, BlogListResponse, BlogPostDraft, BlogPostUpdate } from "../types/collectors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Eodin Blog + SEO + Analytics API clients — target `api.eodin.app`.
|
|
4
|
+
*
|
|
5
|
+
* Replaces the legacy GitHub Contents API approach with direct M2M API
|
|
6
|
+
* calls against the eodin.app growth endpoints. Three thin clients share
|
|
7
|
+
* the same auth header (`Authorization: Bearer <GROWTH_AGENT_TOKEN>`) and
|
|
8
|
+
* the same SSRF allowlist, so they inherit from a common base.
|
|
9
|
+
*
|
|
10
|
+
* Write operations on {@link EodinBlogPublisher} (create / update /
|
|
11
|
+
* publish / delete) are allowed through approval-gated skill paths only;
|
|
12
|
+
* the collector itself is agnostic and will happily POST if called.
|
|
13
|
+
*
|
|
14
|
+
* @see src/agent/safety.ts — `blog_publish` ApprovalManager gate wraps
|
|
15
|
+
* every `publish` call site in M5+ skills.
|
|
16
|
+
*/
|
|
17
|
+
export interface EodinBlogConfig {
|
|
18
|
+
token: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Test-only overrides — intentionally kept off {@link EodinBlogConfig} so
|
|
22
|
+
* config loaders (M3) cannot inject a user-controlled base URL. SSRF
|
|
23
|
+
* allowlist still applies to the override value.
|
|
24
|
+
*/
|
|
25
|
+
export interface EodinBlogTestHooks {
|
|
26
|
+
baseUrl?: string;
|
|
27
|
+
}
|
|
28
|
+
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
|
|
29
|
+
declare abstract class EodinGrowthClient {
|
|
30
|
+
protected readonly token: string;
|
|
31
|
+
protected readonly baseUrl: string;
|
|
32
|
+
constructor(config: EodinBlogConfig, testHooks?: EodinBlogTestHooks);
|
|
33
|
+
protected request<T>(method: HttpMethod, path: string, body?: unknown): Promise<T>;
|
|
34
|
+
}
|
|
35
|
+
export declare class EodinBlogPublisher extends EodinGrowthClient {
|
|
36
|
+
/**
|
|
37
|
+
* Create a blog post as DRAFT. The skill layer is responsible for
|
|
38
|
+
* obtaining human approval before calling {@link publish}.
|
|
39
|
+
*/
|
|
40
|
+
create(post: BlogPostDraft): Promise<unknown>;
|
|
41
|
+
/**
|
|
42
|
+
* Partial update of an existing blog post by slug.
|
|
43
|
+
*/
|
|
44
|
+
update(slug: string, updates: BlogPostUpdate): Promise<unknown>;
|
|
45
|
+
/**
|
|
46
|
+
* Publish a blog post (DRAFT → PUBLISHED). Triggers FAQ auto-generation
|
|
47
|
+
* and ISR revalidation on eodin.app. Always approval-gated from M5.
|
|
48
|
+
*/
|
|
49
|
+
publish(slug: string): Promise<unknown>;
|
|
50
|
+
get(slug: string): Promise<unknown>;
|
|
51
|
+
list(options?: BlogListOptions): Promise<BlogListResponse>;
|
|
52
|
+
/**
|
|
53
|
+
* Convenience wrapper: returns the slugs of every listed post, or an
|
|
54
|
+
* empty array on any failure. Used for duplicate-slug prevention where
|
|
55
|
+
* "API unavailable" should degrade gracefully to "assume no conflict".
|
|
56
|
+
*/
|
|
57
|
+
listSlugs(): Promise<string[]>;
|
|
58
|
+
delete(slug: string): Promise<unknown>;
|
|
59
|
+
}
|
|
60
|
+
export declare class EodinSeoMetrics extends EodinGrowthClient {
|
|
61
|
+
getOverview(startDate: string, endDate: string): Promise<unknown>;
|
|
62
|
+
getKeywords(startDate: string, endDate: string, limit?: number): Promise<unknown>;
|
|
63
|
+
getPages(startDate: string, endDate: string, limit?: number): Promise<unknown>;
|
|
64
|
+
}
|
|
65
|
+
export declare class EodinAnalytics extends EodinGrowthClient {
|
|
66
|
+
getTraffic(startDate: string, endDate: string): Promise<unknown>;
|
|
67
|
+
getPages(startDate: string, endDate: string): Promise<unknown>;
|
|
68
|
+
getSources(startDate: string, endDate: string): Promise<unknown>;
|
|
69
|
+
getRealtime(): Promise<unknown>;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Convert markdown to basic HTML.
|
|
73
|
+
*
|
|
74
|
+
* Handles: h1-h3, paragraphs, bold, italic, links, unordered lists. The
|
|
75
|
+
* conversion is intentionally minimal — the Eodin blog backend accepts
|
|
76
|
+
* arbitrary HTML, but we keep the surface small so {@link EodinBlogPublisher}
|
|
77
|
+
* callers can predict the exact markup that will land in a draft post.
|
|
78
|
+
*
|
|
79
|
+
* Security: all block-level text flows through {@link inlineReplacements},
|
|
80
|
+
* which HTML-escapes and scheme-whitelists links. See SeoBlogSkill path in
|
|
81
|
+
* M4+ — competitor descriptions and upstream-generated copy reach this
|
|
82
|
+
* function without further sanitization.
|
|
83
|
+
*/
|
|
84
|
+
export declare function markdownToHtml(md: string): string;
|
|
85
|
+
/**
|
|
86
|
+
* Estimate read time from content (200 wpm). Always at least "1 min read".
|
|
87
|
+
*/
|
|
88
|
+
export declare function estimateReadTime(text: string): string;
|
|
89
|
+
export {};
|
|
90
|
+
//# sourceMappingURL=eodin-blog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eodin-blog.d.ts","sourceRoot":"","sources":["../../src/collectors/eodin-blog.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,cAAc,EACf,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;;;;;;;;GAcG;AACH,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD,KAAK,UAAU,GAAG,KAAK,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,CAAC;AAEpD,uBAAe,iBAAiB;IAC9B,SAAS,CAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACjC,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEvB,MAAM,EAAE,eAAe,EAAE,SAAS,CAAC,EAAE,kBAAkB;cAUnD,OAAO,CAAC,CAAC,EACvB,MAAM,EAAE,UAAU,EAClB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,OAAO,GACb,OAAO,CAAC,CAAC,CAAC;CAsCd;AAMD,qBAAa,kBAAmB,SAAQ,iBAAiB;IACvD;;;OAGG;IACG,MAAM,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,OAAO,CAAC;IAYnD;;OAEG;IACG,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,OAAO,CAAC;IAarE;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAQvC,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOnC,IAAI,CAAC,OAAO,GAAE,eAAoB,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAepE;;;;OAIG;IACG,SAAS,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;IAkB9B,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAM7C;AAMD,qBAAa,eAAgB,SAAQ,iBAAiB;IAC9C,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOjE,WAAW,CACf,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,SAAK,GACT,OAAO,CAAC,OAAO,CAAC;IAOb,QAAQ,CACZ,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,KAAK,SAAK,GACT,OAAO,CAAC,OAAO,CAAC;CAMpB;AAMD,qBAAa,cAAe,SAAQ,iBAAiB;IAC7C,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhE,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAO9D,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAOhE,WAAW,IAAI,OAAO,CAAC,OAAO,CAAC;CAGtC;AAkDD;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CA6BjD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIrD"}
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import { ExternalApiError } from "../utils/errors.js";
|
|
2
|
+
import { warn as logWarn } from "../utils/logger.js";
|
|
3
|
+
const DEFAULT_BASE_URL = "https://api.eodin.app/api/v1/growth";
|
|
4
|
+
const ALLOWED_HOSTS = new Set(["api.eodin.app"]);
|
|
5
|
+
const ERROR_BODY_MAX_CHARS = 512;
|
|
6
|
+
class EodinGrowthClient {
|
|
7
|
+
token;
|
|
8
|
+
baseUrl;
|
|
9
|
+
constructor(config, testHooks) {
|
|
10
|
+
if (!config.token) {
|
|
11
|
+
throw new Error("Eodin growth clients require a GROWTH_AGENT_TOKEN config.token");
|
|
12
|
+
}
|
|
13
|
+
this.token = config.token;
|
|
14
|
+
this.baseUrl = testHooks?.baseUrl ?? DEFAULT_BASE_URL;
|
|
15
|
+
}
|
|
16
|
+
async request(method, path, body) {
|
|
17
|
+
const url = new URL(`${this.baseUrl}${path}`);
|
|
18
|
+
if (!ALLOWED_HOSTS.has(url.hostname)) {
|
|
19
|
+
throw new Error(`Untrusted Eodin host: ${url.hostname}. Allowed: ${[...ALLOWED_HOSTS].join(", ")}`);
|
|
20
|
+
}
|
|
21
|
+
const headers = {
|
|
22
|
+
Authorization: `Bearer ${this.token}`,
|
|
23
|
+
Accept: "application/json",
|
|
24
|
+
};
|
|
25
|
+
const init = { method, headers };
|
|
26
|
+
if (body !== undefined && body !== null && method !== "GET") {
|
|
27
|
+
headers["Content-Type"] = "application/json";
|
|
28
|
+
init.body = JSON.stringify(body);
|
|
29
|
+
}
|
|
30
|
+
const response = await fetch(url.toString(), init);
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
const rawBody = await response.text();
|
|
33
|
+
// Redact the bearer token in case the server echoes it back in an
|
|
34
|
+
// error envelope. 4xx from Eodin's auth middleware has historically
|
|
35
|
+
// included the raw token in dev.
|
|
36
|
+
const redacted = rawBody
|
|
37
|
+
.replaceAll(this.token, "[REDACTED]")
|
|
38
|
+
.slice(0, ERROR_BODY_MAX_CHARS);
|
|
39
|
+
throw new ExternalApiError(`Eodin Growth API ${String(response.status)}: ${redacted}`, { statusCode: response.status });
|
|
40
|
+
}
|
|
41
|
+
return (await response.json());
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Blog publisher (write paths — approval-gated in M5)
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export class EodinBlogPublisher extends EodinGrowthClient {
|
|
48
|
+
/**
|
|
49
|
+
* Create a blog post as DRAFT. The skill layer is responsible for
|
|
50
|
+
* obtaining human approval before calling {@link publish}.
|
|
51
|
+
*/
|
|
52
|
+
async create(post) {
|
|
53
|
+
return this.request("POST", "/blogs", {
|
|
54
|
+
slug: post.slug,
|
|
55
|
+
title: post.title,
|
|
56
|
+
description: post.description,
|
|
57
|
+
category: post.category,
|
|
58
|
+
content: post.content,
|
|
59
|
+
thumbnail: post.thumbnail,
|
|
60
|
+
readTime: post.readTime,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Partial update of an existing blog post by slug.
|
|
65
|
+
*/
|
|
66
|
+
async update(slug, updates) {
|
|
67
|
+
if (Object.keys(updates).length === 0) {
|
|
68
|
+
throw new Error("EodinBlogPublisher.update: updates object must not be empty");
|
|
69
|
+
}
|
|
70
|
+
return this.request("PUT", `/blogs/${encodeURIComponent(slug)}`, updates);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Publish a blog post (DRAFT → PUBLISHED). Triggers FAQ auto-generation
|
|
74
|
+
* and ISR revalidation on eodin.app. Always approval-gated from M5.
|
|
75
|
+
*/
|
|
76
|
+
async publish(slug) {
|
|
77
|
+
return this.request("PUT", `/blogs/${encodeURIComponent(slug)}/publish`, { status: "PUBLISHED" });
|
|
78
|
+
}
|
|
79
|
+
async get(slug) {
|
|
80
|
+
return this.request("GET", `/blogs/${encodeURIComponent(slug)}`);
|
|
81
|
+
}
|
|
82
|
+
async list(options = {}) {
|
|
83
|
+
const params = new URLSearchParams();
|
|
84
|
+
if (options.status !== undefined)
|
|
85
|
+
params.set("status", options.status);
|
|
86
|
+
if (options.category !== undefined)
|
|
87
|
+
params.set("category", options.category);
|
|
88
|
+
if (options.page !== undefined)
|
|
89
|
+
params.set("page", String(options.page));
|
|
90
|
+
if (options.limit !== undefined)
|
|
91
|
+
params.set("limit", String(options.limit));
|
|
92
|
+
const query = params.toString();
|
|
93
|
+
return this.request("GET", query ? `/blogs?${query}` : "/blogs");
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Convenience wrapper: returns the slugs of every listed post, or an
|
|
97
|
+
* empty array on any failure. Used for duplicate-slug prevention where
|
|
98
|
+
* "API unavailable" should degrade gracefully to "assume no conflict".
|
|
99
|
+
*/
|
|
100
|
+
async listSlugs() {
|
|
101
|
+
try {
|
|
102
|
+
const result = await this.list({ limit: 100 });
|
|
103
|
+
const total = result.pagination?.total;
|
|
104
|
+
if (typeof total === "number" && total > 100) {
|
|
105
|
+
logWarn(`[eodin-blog] listSlugs truncated — ${String(total)} total posts, only first 100 slugs fetched`);
|
|
106
|
+
}
|
|
107
|
+
return result.data.map((post) => post.slug);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
logWarn("[eodin-blog] listSlugs failed — assuming no slug conflict", {
|
|
111
|
+
error: err instanceof Error ? err.message : String(err),
|
|
112
|
+
});
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async delete(slug) {
|
|
117
|
+
return this.request("DELETE", `/blogs/${encodeURIComponent(slug)}`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// ---------------------------------------------------------------------------
|
|
121
|
+
// SEO (Search Console) metrics client — read-only
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
export class EodinSeoMetrics extends EodinGrowthClient {
|
|
124
|
+
async getOverview(startDate, endDate) {
|
|
125
|
+
return this.request("GET", `/metrics/seo/overview?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}`);
|
|
126
|
+
}
|
|
127
|
+
async getKeywords(startDate, endDate, limit = 25) {
|
|
128
|
+
return this.request("GET", `/metrics/seo/keywords?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}&limit=${String(limit)}`);
|
|
129
|
+
}
|
|
130
|
+
async getPages(startDate, endDate, limit = 25) {
|
|
131
|
+
return this.request("GET", `/metrics/seo/pages?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}&limit=${String(limit)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Analytics (GA4) client — read-only
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
export class EodinAnalytics extends EodinGrowthClient {
|
|
138
|
+
async getTraffic(startDate, endDate) {
|
|
139
|
+
return this.request("GET", `/metrics/analytics/traffic?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}`);
|
|
140
|
+
}
|
|
141
|
+
async getPages(startDate, endDate) {
|
|
142
|
+
return this.request("GET", `/metrics/analytics/pages?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}`);
|
|
143
|
+
}
|
|
144
|
+
async getSources(startDate, endDate) {
|
|
145
|
+
return this.request("GET", `/metrics/analytics/sources?startDate=${encodeURIComponent(startDate)}&endDate=${encodeURIComponent(endDate)}`);
|
|
146
|
+
}
|
|
147
|
+
async getRealtime() {
|
|
148
|
+
return this.request("GET", "/metrics/analytics/realtime");
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Markdown → HTML helpers (used by SeoBlogSkill when preparing drafts)
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
const HTML_ESCAPE_MAP = {
|
|
155
|
+
"&": "&",
|
|
156
|
+
"<": "<",
|
|
157
|
+
">": ">",
|
|
158
|
+
'"': """,
|
|
159
|
+
"'": "'",
|
|
160
|
+
};
|
|
161
|
+
function escapeHtml(text) {
|
|
162
|
+
return text.replace(/[&<>"']/g, (ch) => HTML_ESCAPE_MAP[ch] ?? ch);
|
|
163
|
+
}
|
|
164
|
+
const SAFE_URL_SCHEME = /^(?:https?:|mailto:|\/|#)/i;
|
|
165
|
+
/**
|
|
166
|
+
* Normalize a link URL so Markdown cannot smuggle `javascript:` or other
|
|
167
|
+
* dangerous schemes into the rendered `<a href>`. Allowed: http, https,
|
|
168
|
+
* mailto, absolute (`/…`), and fragments (`#…`). Everything else becomes
|
|
169
|
+
* `#`. The result is HTML-escaped for attribute safety.
|
|
170
|
+
*/
|
|
171
|
+
function safeHref(url) {
|
|
172
|
+
const trimmed = url.trim();
|
|
173
|
+
if (SAFE_URL_SCHEME.test(trimmed)) {
|
|
174
|
+
return escapeHtml(trimmed);
|
|
175
|
+
}
|
|
176
|
+
return "#";
|
|
177
|
+
}
|
|
178
|
+
function inlineReplacements(text) {
|
|
179
|
+
// HTML escape first so the markdown tokens we reintroduce below are the
|
|
180
|
+
// only raw HTML in the final output. `inlineReplacements` is called with
|
|
181
|
+
// attacker-controllable text (competitor descriptions, Claude-generated
|
|
182
|
+
// body copy), and the Eodin blog backend does not re-sanitize on POST.
|
|
183
|
+
const escaped = escapeHtml(text);
|
|
184
|
+
return escaped
|
|
185
|
+
.replace(/\*\*(.+?)\*\*/g, "<strong>$1</strong>")
|
|
186
|
+
.replace(/\*(.+?)\*/g, "<em>$1</em>")
|
|
187
|
+
.replace(/\[(.+?)\]\((.+?)\)/g, (_match, label, url) => `<a href="${safeHref(url)}">${label}</a>`);
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Convert markdown to basic HTML.
|
|
191
|
+
*
|
|
192
|
+
* Handles: h1-h3, paragraphs, bold, italic, links, unordered lists. The
|
|
193
|
+
* conversion is intentionally minimal — the Eodin blog backend accepts
|
|
194
|
+
* arbitrary HTML, but we keep the surface small so {@link EodinBlogPublisher}
|
|
195
|
+
* callers can predict the exact markup that will land in a draft post.
|
|
196
|
+
*
|
|
197
|
+
* Security: all block-level text flows through {@link inlineReplacements},
|
|
198
|
+
* which HTML-escapes and scheme-whitelists links. See SeoBlogSkill path in
|
|
199
|
+
* M4+ — competitor descriptions and upstream-generated copy reach this
|
|
200
|
+
* function without further sanitization.
|
|
201
|
+
*/
|
|
202
|
+
export function markdownToHtml(md) {
|
|
203
|
+
return md
|
|
204
|
+
.split("\n\n")
|
|
205
|
+
.map((rawBlock) => {
|
|
206
|
+
const block = rawBlock.trim();
|
|
207
|
+
if (!block)
|
|
208
|
+
return "";
|
|
209
|
+
if (block.startsWith("### ")) {
|
|
210
|
+
return `<h3>${inlineReplacements(block.slice(4))}</h3>`;
|
|
211
|
+
}
|
|
212
|
+
if (block.startsWith("## ")) {
|
|
213
|
+
return `<h2>${inlineReplacements(block.slice(3))}</h2>`;
|
|
214
|
+
}
|
|
215
|
+
if (block.startsWith("# ")) {
|
|
216
|
+
return `<h1>${inlineReplacements(block.slice(2))}</h1>`;
|
|
217
|
+
}
|
|
218
|
+
if (/^[-*] /m.test(block)) {
|
|
219
|
+
const items = block
|
|
220
|
+
.split("\n")
|
|
221
|
+
.filter((l) => /^[-*] /.test(l))
|
|
222
|
+
.map((l) => `<li>${inlineReplacements(l.replace(/^[-*] /, ""))}</li>`);
|
|
223
|
+
return `<ul>${items.join("")}</ul>`;
|
|
224
|
+
}
|
|
225
|
+
return `<p>${inlineReplacements(block.replace(/\n/g, " "))}</p>`;
|
|
226
|
+
})
|
|
227
|
+
.filter(Boolean)
|
|
228
|
+
.join("\n");
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Estimate read time from content (200 wpm). Always at least "1 min read".
|
|
232
|
+
*/
|
|
233
|
+
export function estimateReadTime(text) {
|
|
234
|
+
const words = text.split(/\s+/).filter(Boolean).length;
|
|
235
|
+
const minutes = Math.max(1, Math.ceil(words / 200));
|
|
236
|
+
return `${String(minutes)} min read`;
|
|
237
|
+
}
|
|
238
|
+
//# sourceMappingURL=eodin-blog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eodin-blog.js","sourceRoot":"","sources":["../../src/collectors/eodin-blog.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { EodinCohort, EodinFunnelData, EodinSummaryRow } from "../types/collectors.js";
|
|
2
|
+
/**
|
|
3
|
+
* Eodin Analytics API collector.
|
|
4
|
+
*
|
|
5
|
+
* Wraps `https://api.eodin.app/api/v1/events` — read-only endpoints exposing
|
|
6
|
+
* daily summaries, funnels, and cohort retention for each registered app.
|
|
7
|
+
*
|
|
8
|
+
* Auth: X-API-Key header (not `Authorization: Bearer`).
|
|
9
|
+
*/
|
|
10
|
+
export interface EodinSdkCollectorOptions {
|
|
11
|
+
apiKey: string;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Test-only overrides. Kept off {@link EodinSdkCollectorOptions} so that
|
|
15
|
+
* production config loaders (M3) cannot feed a user-controlled base URL into
|
|
16
|
+
* the SSRF surface. The SSRF allowlist check still applies to the override —
|
|
17
|
+
* defense in depth.
|
|
18
|
+
*/
|
|
19
|
+
export interface EodinSdkCollectorTestHooks {
|
|
20
|
+
baseUrl?: string;
|
|
21
|
+
}
|
|
22
|
+
export interface EodinSummaryOptions {
|
|
23
|
+
/** Aggregation window. Defaults to `daily`. */
|
|
24
|
+
granularity?: "daily" | "weekly" | "monthly";
|
|
25
|
+
/** Platform filter. Defaults to `all`. */
|
|
26
|
+
os?: "ios" | "android" | "all";
|
|
27
|
+
}
|
|
28
|
+
export interface EodinFunnelOptions {
|
|
29
|
+
source?: string;
|
|
30
|
+
os?: "ios" | "android" | "all";
|
|
31
|
+
}
|
|
32
|
+
export interface EodinCohortOptions {
|
|
33
|
+
granularity?: "daily" | "weekly" | "monthly";
|
|
34
|
+
os?: "ios" | "android" | "all";
|
|
35
|
+
}
|
|
36
|
+
export declare class EodinSdkCollector {
|
|
37
|
+
private readonly apiKey;
|
|
38
|
+
private readonly baseUrl;
|
|
39
|
+
private loggedPercentCohort;
|
|
40
|
+
constructor(options: EodinSdkCollectorOptions, testHooks?: EodinSdkCollectorTestHooks);
|
|
41
|
+
private request;
|
|
42
|
+
/**
|
|
43
|
+
* Daily/weekly/monthly aggregate rows.
|
|
44
|
+
*/
|
|
45
|
+
getSummary(appId: string, startDate: string, endDate: string, options?: EodinSummaryOptions): Promise<EodinSummaryRow[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Aggregate funnel for the period. Step order is fixed by the Eodin API:
|
|
48
|
+
* `app_install → app_open → core_action → paywall_view → subscribe_start`.
|
|
49
|
+
*/
|
|
50
|
+
getFunnel(appId: string, startDate: string, endDate: string, options?: EodinFunnelOptions): Promise<EodinFunnelData>;
|
|
51
|
+
/**
|
|
52
|
+
* Cohort retention. `retention[0]` is always the cohort anchor (100% of
|
|
53
|
+
* the cohort by definition), so any value above 1.5 signals the server
|
|
54
|
+
* returned percents instead of fractions; we detect and normalize so
|
|
55
|
+
* every downstream consumer sees fractions in [0, 1].
|
|
56
|
+
*/
|
|
57
|
+
getCohort(appId: string, startDate: string, endDate: string, options?: EodinCohortOptions): Promise<EodinCohort[]>;
|
|
58
|
+
private normalizeRetention;
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=eodin-sdk.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eodin-sdk.d.ts","sourceRoot":"","sources":["../../src/collectors/eodin-sdk.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,WAAW,EACX,eAAe,EACf,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAEhC;;;;;;;GAOG;AACH,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAMD,MAAM,WAAW,mBAAmB;IAClC,+CAA+C;IAC/C,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7C,0CAA0C;IAC1C,EAAE,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,EAAE,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,WAAW,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,SAAS,CAAC;IAC7C,EAAE,CAAC,EAAE,KAAK,GAAG,SAAS,GAAG,KAAK,CAAC;CAChC;AAmBD,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,mBAAmB,CAAS;gBAGlC,OAAO,EAAE,wBAAwB,EACjC,SAAS,CAAC,EAAE,0BAA0B;YAS1B,OAAO;IAwCrB;;OAEG;IACG,UAAU,CACd,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,eAAe,EAAE,CAAC;IAW7B;;;OAGG;IACG,SAAS,CACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,eAAe,CAAC;IAW3B;;;;;OAKG;IACG,SAAS,CACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,WAAW,EAAE,CAAC;IAezB,OAAO,CAAC,kBAAkB;CAqB3B"}
|