applaunchflow 0.3.1
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 +110 -0
- package/build/catalog.js +5 -0
- package/build/catalog.test.js +13 -0
- package/build/cli-core.js +20 -0
- package/build/cli-core.test.js +24 -0
- package/build/cli.js +71 -0
- package/build/client/api.js +341 -0
- package/build/http.js +178 -0
- package/build/http.test.js +111 -0
- package/build/index.js +127 -0
- package/build/prompts/register.js +66 -0
- package/build/read-receipt.test.js +36 -0
- package/build/resources/data.js +723 -0
- package/build/resources/register.js +115 -0
- package/build/social-template-previews.js +84 -0
- package/build/template-previews.js +83 -0
- package/build/tool-metadata.js +112 -0
- package/build/tool-metadata.test.js +32 -0
- package/build/tools/assets.js +311 -0
- package/build/tools/graphics.js +475 -0
- package/build/tools/keywords.js +132 -0
- package/build/tools/layouts.js +283 -0
- package/build/tools/localization.js +59 -0
- package/build/tools/mockups.js +324 -0
- package/build/tools/projects.js +113 -0
- package/build/tools/promovideo.js +199 -0
- package/build/tools/screenshots.js +307 -0
- package/build/tools/templates.js +195 -0
- package/build/tools/utils.js +212 -0
- package/build/tools/variants.js +71 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# AppLaunchFlow MCP
|
|
2
|
+
|
|
3
|
+
MCP server for AppLaunchFlow — create App Store & Google Play screenshots with AI.
|
|
4
|
+
|
|
5
|
+
## Connect
|
|
6
|
+
|
|
7
|
+
AppLaunchFlow is a hosted MCP connector with OAuth 2.1 and PKCE. No API key,
|
|
8
|
+
local token, or scoped npm package name is required.
|
|
9
|
+
|
|
10
|
+
### Codex
|
|
11
|
+
|
|
12
|
+
The shortest setup command configures the hosted connector and opens OAuth:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx -y applaunchflow connect codex
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Useful follow-up commands:
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx -y applaunchflow status
|
|
22
|
+
npx -y applaunchflow disconnect
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
The equivalent native Codex commands are:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
codex mcp add applaunchflow --url https://mcp.applaunchflow.com/mcp
|
|
29
|
+
codex mcp login applaunchflow
|
|
30
|
+
codex mcp get applaunchflow
|
|
31
|
+
codex mcp remove applaunchflow
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### ChatGPT
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npx -y applaunchflow connect chatgpt
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Paste the displayed URL when creating a custom MCP connector in ChatGPT. The
|
|
41
|
+
same URL is also available at any time with `npx -y applaunchflow url`.
|
|
42
|
+
|
|
43
|
+
### Other MCP clients
|
|
44
|
+
|
|
45
|
+
Use this Streamable HTTP endpoint and enable OAuth when prompted:
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
https://mcp.applaunchflow.com/mcp
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Hosted service
|
|
52
|
+
|
|
53
|
+
The public Streamable HTTP service uses OAuth 2.1 authorization code flow with
|
|
54
|
+
PKCE through the AppLaunchFlow dashboard.
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
npm ci
|
|
58
|
+
npm run build
|
|
59
|
+
APPLAUNCHFLOW_BASE_URL=https://dashboard.applaunchflow.com \
|
|
60
|
+
APPLAUNCHFLOW_MCP_PUBLIC_URL=https://mcp.applaunchflow.com \
|
|
61
|
+
PORT=8787 \
|
|
62
|
+
npm run start:http
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Public endpoints:
|
|
66
|
+
|
|
67
|
+
- MCP: `https://mcp.applaunchflow.com/mcp`
|
|
68
|
+
- Protected resource metadata: `https://mcp.applaunchflow.com/.well-known/oauth-protected-resource`
|
|
69
|
+
- Health: `https://mcp.applaunchflow.com/healthz`
|
|
70
|
+
|
|
71
|
+
`APPLAUNCHFLOW_MCP_PUBLIC_URL` may be either the origin or the full `/mcp`
|
|
72
|
+
URL; both services normalize it to the same canonical resource URL. Set
|
|
73
|
+
`APPLAUNCHFLOW_MCP_PUBLIC_URL=https://mcp.applaunchflow.com/mcp` and
|
|
74
|
+
`NEXT_PUBLIC_APP_URL=https://dashboard.applaunchflow.com` on the dashboard.
|
|
75
|
+
|
|
76
|
+
The included `Dockerfile` produces a non-root OCI image for the hosted server.
|
|
77
|
+
The MCP host and dashboard must both be served through public HTTPS in
|
|
78
|
+
production. Do not expose the Node process directly without a TLS-terminating
|
|
79
|
+
platform or reverse proxy.
|
|
80
|
+
|
|
81
|
+
## Personalized style workflow
|
|
82
|
+
|
|
83
|
+
Screenshot and social-graphics styles are prepared once for the selected app screenshots, then the chosen style is applied from cache. This lets users compare styles without paying for another AI call when they choose one.
|
|
84
|
+
|
|
85
|
+
For App Store screenshots:
|
|
86
|
+
|
|
87
|
+
1. `list_source_screenshots`
|
|
88
|
+
2. `prepare_screenshot_styles` with 3-7 ordered paths
|
|
89
|
+
3. `browse_templates` with the returned `templateIds`, `generationId`, and `catalogKey`
|
|
90
|
+
4. `apply_screenshot_style` with the returned `catalogKey`
|
|
91
|
+
|
|
92
|
+
For social graphics:
|
|
93
|
+
|
|
94
|
+
1. `list_source_screenshots`
|
|
95
|
+
2. `prepare_social_graphics_styles` with 3-7 ordered paths
|
|
96
|
+
3. `browse_social_templates` with the returned `templateIds`, `generationId`, and `catalogKey`
|
|
97
|
+
4. `apply_social_graphics_style` with the returned `catalogKey`
|
|
98
|
+
|
|
99
|
+
The screenshot result includes phone, tablet, and desktop. The social result includes all six supported formats. `generate_layouts` and `generate_graphics` remain available for legacy direct single-template calls.
|
|
100
|
+
|
|
101
|
+
## Development
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
npm ci
|
|
105
|
+
npm run dev
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Run `npm test` before publishing or deploying. See
|
|
109
|
+
[`docs/openai-submission.md`](docs/openai-submission.md) for the final OpenAI
|
|
110
|
+
submission checklist and manual test cases.
|
package/build/catalog.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { listPublicTemplateIds } from "./catalog.js";
|
|
4
|
+
test("listPublicTemplateIds returns sorted public templates", () => {
|
|
5
|
+
assert.deepEqual(listPublicTemplateIds({
|
|
6
|
+
beauty: {},
|
|
7
|
+
__layoutVersion: "v1",
|
|
8
|
+
action: {},
|
|
9
|
+
}), ["action", "beauty"]);
|
|
10
|
+
});
|
|
11
|
+
test("listPublicTemplateIds handles an absent catalog", () => {
|
|
12
|
+
assert.deepEqual(listPublicTemplateIds(undefined), []);
|
|
13
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const APPLAUNCHFLOW_MCP_NAME = "applaunchflow";
|
|
2
|
+
export const APPLAUNCHFLOW_MCP_URL = "https://mcp.applaunchflow.com/mcp";
|
|
3
|
+
export function codexAddArgs() {
|
|
4
|
+
return [
|
|
5
|
+
"mcp",
|
|
6
|
+
"add",
|
|
7
|
+
APPLAUNCHFLOW_MCP_NAME,
|
|
8
|
+
"--url",
|
|
9
|
+
APPLAUNCHFLOW_MCP_URL,
|
|
10
|
+
];
|
|
11
|
+
}
|
|
12
|
+
export function codexLoginArgs() {
|
|
13
|
+
return ["mcp", "login", APPLAUNCHFLOW_MCP_NAME];
|
|
14
|
+
}
|
|
15
|
+
export function codexStatusArgs() {
|
|
16
|
+
return ["mcp", "get", APPLAUNCHFLOW_MCP_NAME];
|
|
17
|
+
}
|
|
18
|
+
export function codexDisconnectArgs() {
|
|
19
|
+
return ["mcp", "remove", APPLAUNCHFLOW_MCP_NAME];
|
|
20
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
import { APPLAUNCHFLOW_MCP_NAME, APPLAUNCHFLOW_MCP_URL, codexAddArgs, codexDisconnectArgs, codexLoginArgs, codexStatusArgs, } from "./cli-core.js";
|
|
4
|
+
test("Codex convenience commands use the hosted OAuth connector", () => {
|
|
5
|
+
assert.deepEqual(codexAddArgs(), [
|
|
6
|
+
"mcp",
|
|
7
|
+
"add",
|
|
8
|
+
APPLAUNCHFLOW_MCP_NAME,
|
|
9
|
+
"--url",
|
|
10
|
+
APPLAUNCHFLOW_MCP_URL,
|
|
11
|
+
]);
|
|
12
|
+
assert.deepEqual(codexLoginArgs(), ["mcp", "login", "applaunchflow"]);
|
|
13
|
+
assert.deepEqual(codexStatusArgs(), ["mcp", "get", "applaunchflow"]);
|
|
14
|
+
assert.deepEqual(codexDisconnectArgs(), [
|
|
15
|
+
"mcp",
|
|
16
|
+
"remove",
|
|
17
|
+
"applaunchflow",
|
|
18
|
+
]);
|
|
19
|
+
});
|
|
20
|
+
test("the public connector URL is a secure MCP endpoint", () => {
|
|
21
|
+
const url = new URL(APPLAUNCHFLOW_MCP_URL);
|
|
22
|
+
assert.equal(url.protocol, "https:");
|
|
23
|
+
assert.equal(url.pathname, "/mcp");
|
|
24
|
+
});
|
package/build/cli.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from "node:child_process";
|
|
3
|
+
import { APPLAUNCHFLOW_MCP_URL, codexAddArgs, codexDisconnectArgs, codexLoginArgs, codexStatusArgs, } from "./cli-core.js";
|
|
4
|
+
function printHelp() {
|
|
5
|
+
console.log(`AppLaunchFlow MCP
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
applaunchflow connect codex
|
|
9
|
+
applaunchflow connect chatgpt
|
|
10
|
+
applaunchflow status
|
|
11
|
+
applaunchflow disconnect
|
|
12
|
+
applaunchflow url`);
|
|
13
|
+
}
|
|
14
|
+
function runCodex(args) {
|
|
15
|
+
const result = spawnSync("codex", args, { stdio: "inherit" });
|
|
16
|
+
if (result.error) {
|
|
17
|
+
throw new Error(`Could not run Codex: ${result.error.message}`);
|
|
18
|
+
}
|
|
19
|
+
if (result.status !== 0) {
|
|
20
|
+
throw new Error(`Codex exited with status ${result.status ?? "unknown"}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function codexIsConfigured() {
|
|
24
|
+
const result = spawnSync("codex", codexStatusArgs(), { stdio: "ignore" });
|
|
25
|
+
if (result.error) {
|
|
26
|
+
throw new Error(`Could not run Codex: ${result.error.message}`);
|
|
27
|
+
}
|
|
28
|
+
return result.status === 0;
|
|
29
|
+
}
|
|
30
|
+
function connectCodex() {
|
|
31
|
+
if (!codexIsConfigured()) {
|
|
32
|
+
runCodex(codexAddArgs());
|
|
33
|
+
}
|
|
34
|
+
runCodex(codexLoginArgs());
|
|
35
|
+
}
|
|
36
|
+
function connectChatGpt() {
|
|
37
|
+
console.log(`Add a custom MCP connector in ChatGPT using this URL:\n${APPLAUNCHFLOW_MCP_URL}`);
|
|
38
|
+
}
|
|
39
|
+
function main() {
|
|
40
|
+
const [command, target] = process.argv.slice(2);
|
|
41
|
+
if (command === "connect" && target === "codex") {
|
|
42
|
+
connectCodex();
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
if (command === "connect" && target === "chatgpt") {
|
|
46
|
+
connectChatGpt();
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (command === "status") {
|
|
50
|
+
runCodex(codexStatusArgs());
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (command === "disconnect") {
|
|
54
|
+
runCodex(codexDisconnectArgs());
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (command === "url") {
|
|
58
|
+
console.log(APPLAUNCHFLOW_MCP_URL);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
printHelp();
|
|
62
|
+
if (command)
|
|
63
|
+
process.exitCode = 1;
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
main();
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
}
|
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
export class AppLaunchFlowApiError extends Error {
|
|
2
|
+
status;
|
|
3
|
+
body;
|
|
4
|
+
constructor(message, status, body) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "AppLaunchFlowApiError";
|
|
7
|
+
this.status = status;
|
|
8
|
+
this.body = body;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function buildSearchParams(query) {
|
|
12
|
+
if (!query) {
|
|
13
|
+
return "";
|
|
14
|
+
}
|
|
15
|
+
const params = new URLSearchParams();
|
|
16
|
+
for (const [key, value] of Object.entries(query)) {
|
|
17
|
+
if (value === undefined || value === null) {
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(value)) {
|
|
21
|
+
value.forEach((item) => params.append(key, String(item)));
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
params.set(key, String(value));
|
|
25
|
+
}
|
|
26
|
+
const serialized = params.toString();
|
|
27
|
+
return serialized ? `?${serialized}` : "";
|
|
28
|
+
}
|
|
29
|
+
export class AppLaunchFlowClient {
|
|
30
|
+
credentials;
|
|
31
|
+
constructor(credentials) {
|
|
32
|
+
this.credentials = credentials;
|
|
33
|
+
}
|
|
34
|
+
buildHeaders(extraHeaders) {
|
|
35
|
+
const headers = new Headers(extraHeaders);
|
|
36
|
+
headers.set("Authorization", `Bearer ${this.credentials.token}`);
|
|
37
|
+
return headers;
|
|
38
|
+
}
|
|
39
|
+
async requestJson(path, options = {}) {
|
|
40
|
+
const url = `${this.credentials.baseUrl}${path}${buildSearchParams(options.query)}`;
|
|
41
|
+
const headers = this.buildHeaders(options.headers);
|
|
42
|
+
if (options.body !== undefined && !headers.has("Content-Type")) {
|
|
43
|
+
headers.set("Content-Type", "application/json");
|
|
44
|
+
}
|
|
45
|
+
const response = await fetch(url, {
|
|
46
|
+
method: options.method || "GET",
|
|
47
|
+
headers,
|
|
48
|
+
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
|
|
49
|
+
});
|
|
50
|
+
const contentType = response.headers.get("content-type") || "";
|
|
51
|
+
const payload = contentType.includes("application/json")
|
|
52
|
+
? await response.json()
|
|
53
|
+
: await response.text();
|
|
54
|
+
if (!response.ok) {
|
|
55
|
+
const message = typeof payload === "string"
|
|
56
|
+
? payload
|
|
57
|
+
: payload?.error ||
|
|
58
|
+
payload?.message ||
|
|
59
|
+
`Request failed with status ${response.status}`;
|
|
60
|
+
throw new AppLaunchFlowApiError(message, response.status, payload);
|
|
61
|
+
}
|
|
62
|
+
return payload;
|
|
63
|
+
}
|
|
64
|
+
async createSignedUpload(args) {
|
|
65
|
+
return this.requestJson("/api/assets/upload/signed-url", {
|
|
66
|
+
method: "POST",
|
|
67
|
+
body: args,
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
async uploadBinary(uploadUrl, buffer, contentType) {
|
|
71
|
+
const response = await fetch(uploadUrl, {
|
|
72
|
+
method: "PUT",
|
|
73
|
+
headers: {
|
|
74
|
+
"Content-Type": contentType,
|
|
75
|
+
},
|
|
76
|
+
body: new Uint8Array(buffer),
|
|
77
|
+
});
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
throw new Error(`Upload failed with status ${response.status}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
listProjects() {
|
|
83
|
+
return this.requestJson("/api/projects");
|
|
84
|
+
}
|
|
85
|
+
createProject(body) {
|
|
86
|
+
return this.requestJson("/api/projects", {
|
|
87
|
+
method: "POST",
|
|
88
|
+
body,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
deleteProject(projectId) {
|
|
92
|
+
return this.requestJson(`/api/projects/${projectId}`, {
|
|
93
|
+
method: "DELETE",
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
getProject(projectId) {
|
|
97
|
+
return this.requestJson(`/api/app/${projectId}`);
|
|
98
|
+
}
|
|
99
|
+
listScreenshots(query) {
|
|
100
|
+
return this.requestJson("/api/screenshots/list", { query });
|
|
101
|
+
}
|
|
102
|
+
listProjectScreenshots(projectId) {
|
|
103
|
+
return this.requestJson(`/api/projects/${projectId}/screenshots`);
|
|
104
|
+
}
|
|
105
|
+
generateLayouts(body) {
|
|
106
|
+
return this.requestJson("/api/screenshots/generate", {
|
|
107
|
+
method: "POST",
|
|
108
|
+
body,
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
applyScreenshotTemplate(body) {
|
|
112
|
+
return this.requestJson("/api/screenshots/apply-template", {
|
|
113
|
+
method: "POST",
|
|
114
|
+
body,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
regenerateLayouts(body) {
|
|
118
|
+
return this.requestJson("/api/screenshots/regenerate", {
|
|
119
|
+
method: "POST",
|
|
120
|
+
body,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
getLayout(query) {
|
|
124
|
+
return this.requestJson("/api/translations", {
|
|
125
|
+
query: {
|
|
126
|
+
generationId: query.generationId,
|
|
127
|
+
language: query.language,
|
|
128
|
+
variantId: query.variantId,
|
|
129
|
+
sign: query.sign ? 1 : undefined,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
saveLayout(body) {
|
|
134
|
+
return this.requestJson("/api/translations", {
|
|
135
|
+
method: "POST",
|
|
136
|
+
body,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
transformLayout(body) {
|
|
140
|
+
return this.requestJson("/api/mcp/transform", {
|
|
141
|
+
method: "POST",
|
|
142
|
+
body,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
listTemplates() {
|
|
146
|
+
return this.requestJson("/api/mcp/templates");
|
|
147
|
+
}
|
|
148
|
+
getTemplate(templateId) {
|
|
149
|
+
return this.requestJson(`/api/mcp/templates/${templateId}`);
|
|
150
|
+
}
|
|
151
|
+
listSocialTemplates() {
|
|
152
|
+
return this.requestJson("/api/mcp/social-templates");
|
|
153
|
+
}
|
|
154
|
+
getSocialTemplate(templateId) {
|
|
155
|
+
return this.requestJson(`/api/mcp/social-templates/${templateId}`);
|
|
156
|
+
}
|
|
157
|
+
getPromoVideo(generationId, variantId) {
|
|
158
|
+
return this.requestJson("/api/promovideo/load", {
|
|
159
|
+
query: { generationId, variantId },
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
generatePromoVideo(body) {
|
|
163
|
+
return this.requestJson("/api/promovideo/generate", {
|
|
164
|
+
method: "POST",
|
|
165
|
+
body,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
updatePromoVideo(body) {
|
|
169
|
+
return this.requestJson("/api/promovideo/update", {
|
|
170
|
+
method: "POST",
|
|
171
|
+
body,
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
clearPromoVideo(body) {
|
|
175
|
+
return this.requestJson("/api/promovideo/clear", {
|
|
176
|
+
method: "POST",
|
|
177
|
+
body,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
createMockupAnimation(body) {
|
|
181
|
+
return this.requestJson("/api/mockups/setup", {
|
|
182
|
+
method: "POST",
|
|
183
|
+
body,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
getMockupAnimation(generationId, variantId) {
|
|
187
|
+
return this.requestJson("/api/mockups", {
|
|
188
|
+
query: { generationId, variantId },
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
updateMockupAnimation(body) {
|
|
192
|
+
return this.requestJson("/api/mockups", {
|
|
193
|
+
method: "POST",
|
|
194
|
+
body,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
listMockupMedia(projectId) {
|
|
198
|
+
return this.requestJson("/api/mockup-media/list", {
|
|
199
|
+
query: { projectId },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
getMockupThemeColors(generationId) {
|
|
203
|
+
return this.requestJson("/api/mockups/theme-colors", {
|
|
204
|
+
query: { generationId },
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
lookupAppStore(id, country = "us") {
|
|
208
|
+
return this.requestJson("/api/itunes/lookup", {
|
|
209
|
+
query: { id, country },
|
|
210
|
+
headers: {},
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
translateLayouts(body) {
|
|
214
|
+
return this.requestJson("/api/screenshots/translate", {
|
|
215
|
+
method: "POST",
|
|
216
|
+
body,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
listVariants(generationId, contentType) {
|
|
220
|
+
return this.requestJson("/api/variants", {
|
|
221
|
+
query: { generationId, contentType },
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
createVariant(body) {
|
|
225
|
+
return this.requestJson("/api/variants", {
|
|
226
|
+
method: "POST",
|
|
227
|
+
body,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
switchVariant(variantId) {
|
|
231
|
+
return this.requestJson(`/api/variants/${variantId}`, {
|
|
232
|
+
method: "PATCH",
|
|
233
|
+
body: { isActive: true },
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
duplicateVariant(variantId) {
|
|
237
|
+
return this.requestJson(`/api/variants/${variantId}/duplicate`, {
|
|
238
|
+
method: "POST",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
deleteVariant(variantId) {
|
|
242
|
+
return this.requestJson(`/api/variants/${variantId}`, {
|
|
243
|
+
method: "DELETE",
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
getGraphics(projectId, variantId) {
|
|
247
|
+
return this.requestJson("/api/graphics", {
|
|
248
|
+
query: { projectId, variantId },
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
getGraphicsFormat(projectId, format, variantId) {
|
|
252
|
+
return this.requestJson("/api/graphics", {
|
|
253
|
+
query: { projectId, variantId, format },
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
generateGraphics(body) {
|
|
257
|
+
return this.requestJson("/api/graphics/generate", {
|
|
258
|
+
method: "POST",
|
|
259
|
+
body,
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
applyGraphicsTemplate(body) {
|
|
263
|
+
return this.requestJson("/api/graphics/apply-template", {
|
|
264
|
+
method: "POST",
|
|
265
|
+
body,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
saveGraphics(body) {
|
|
269
|
+
return this.requestJson("/api/graphics", {
|
|
270
|
+
method: "POST",
|
|
271
|
+
body,
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
saveGraphicsFormat(body) {
|
|
275
|
+
return this.requestJson("/api/graphics/format", {
|
|
276
|
+
method: "POST",
|
|
277
|
+
body,
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
getAsoCopy(generationId, variantId) {
|
|
281
|
+
return this.requestJson("/api/aso/copy", {
|
|
282
|
+
query: { generationId, variantId },
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
generateAsoCopy(body) {
|
|
286
|
+
return this.requestJson("/api/aso/copy", {
|
|
287
|
+
method: "POST",
|
|
288
|
+
body,
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
updateAsoCopy(body) {
|
|
292
|
+
return this.requestJson("/api/aso/copy", {
|
|
293
|
+
method: "PUT",
|
|
294
|
+
body,
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
translateAsoCopy(body) {
|
|
298
|
+
return this.requestJson("/api/aso/translate", {
|
|
299
|
+
method: "POST",
|
|
300
|
+
body,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
suggestCompetitors(body) {
|
|
304
|
+
return this.requestJson("/api/aso/competitors/suggest", {
|
|
305
|
+
method: "POST",
|
|
306
|
+
body,
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
listSharedIllustrations(query) {
|
|
310
|
+
return this.requestJson("/api/illustrations/shared", { query });
|
|
311
|
+
}
|
|
312
|
+
listProjectIllustrations(projectId) {
|
|
313
|
+
return this.requestJson("/api/illustrations/list", {
|
|
314
|
+
query: { projectId },
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
listKeywords(query) {
|
|
318
|
+
return this.requestJson("/api/keywords", { query });
|
|
319
|
+
}
|
|
320
|
+
listKeywordCompetitors(query) {
|
|
321
|
+
return this.requestJson("/api/keywords/competitors", { query });
|
|
322
|
+
}
|
|
323
|
+
getKeywordHistory(query) {
|
|
324
|
+
return this.requestJson("/api/keywords/history", { query });
|
|
325
|
+
}
|
|
326
|
+
addKeywords(body) {
|
|
327
|
+
return this.requestJson("/api/keywords", { method: "POST", body });
|
|
328
|
+
}
|
|
329
|
+
/** List files in a project's asset subfolder (panorama, illustrations, backgrounds, etc.) */
|
|
330
|
+
async listProjectAssetFolder(projectId, folder) {
|
|
331
|
+
// Use the app endpoint for known folders, or fall back to storage listing
|
|
332
|
+
if (folder === "illustrations") {
|
|
333
|
+
return this.listProjectIllustrations(projectId);
|
|
334
|
+
}
|
|
335
|
+
// For panorama/backgrounds/logo — use the screenshots list with a folder hint
|
|
336
|
+
// These are stored under {projectId}/{folder}/ in the screenshots bucket
|
|
337
|
+
return this.requestJson(`/api/app/${projectId}/assets`, {
|
|
338
|
+
query: { folder },
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|