@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.
- package/README.md +158 -5
- 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 +5 -2
- package/dist/ads.js +8 -4
- package/dist/agents.d.ts +47 -0
- package/dist/agents.js +88 -0
- package/dist/cli.js +582 -9
- package/dist/dataforseo.d.ts +139 -0
- package/dist/dataforseo.js +354 -0
- package/dist/experiments.d.ts +4 -4
- package/dist/metrics.d.ts +20 -0
- package/dist/metrics.js +4 -0
- package/dist/report.d.ts +5 -0
- package/dist/report.js +42 -0
- package/dist/reviews.d.ts +2 -2
- package/dist/schema.d.ts +95 -2
- package/dist/schema.js +35 -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.js +39 -6
- package/dist/social.d.ts +129 -0
- package/dist/social.js +189 -0
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +1 -1
- 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/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 +3 -0
- package/dist/visibility.js +1 -1
- package/package.json +7 -3
- package/skills/appeeky-spec/SKILL.md +14 -18
- package/skills/appeeky-spec-experiments/SKILL.md +12 -2
- package/skills/appeeky-spec-visibility/SKILL.md +33 -6
package/dist/ads.js
CHANGED
|
@@ -22,15 +22,19 @@ function channelFrom(id, w) {
|
|
|
22
22
|
return { channel: id, spendPerDay: round(v.tiktok_spend), resultsPerDay: round(v.tiktok_conversions), costPerResult: round(v.tiktok_cpa), result: "conversions" };
|
|
23
23
|
}
|
|
24
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()
|
|
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) {
|
|
26
28
|
const to = now.toISOString().slice(0, 10);
|
|
27
29
|
const from = new Date(Date.parse(to) - Math.max(1, days) * 86_400_000).toISOString().slice(0, 10);
|
|
28
30
|
const sources = [];
|
|
29
|
-
if (app)
|
|
31
|
+
if (app && ownSearchAds)
|
|
32
|
+
sources.push(["apple-search-ads", ownSearchAds]);
|
|
33
|
+
else if (app && api)
|
|
30
34
|
sources.push(["apple-search-ads", appeekyMetricSource(api)]);
|
|
31
|
-
if (spec.ads.meta)
|
|
35
|
+
if (api && spec.ads.meta)
|
|
32
36
|
sources.push(["meta", metaMetricSource(api, spec.ads.meta)]);
|
|
33
|
-
if (spec.ads.tiktok)
|
|
37
|
+
if (api && spec.ads.tiktok)
|
|
34
38
|
sources.push(["tiktok", tiktokMetricSource(api, spec.ads.tiktok)]);
|
|
35
39
|
const snapshot = { from, to, days: Math.max(1, days), channels: [], errors: [] };
|
|
36
40
|
for (const [id, source] of sources) {
|
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
|