tuya-platform-cli 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/README.md +218 -0
- package/examples/rag-workflow.json +63 -0
- package/examples/simple-llm.json +37 -0
- package/package.json +40 -0
- package/src/cli.js +78 -0
- package/src/lib/analyze.js +213 -0
- package/src/lib/cdp-client.js +115 -0
- package/src/lib/chrome.js +115 -0
- package/src/lib/commands/auto-basic.js +482 -0
- package/src/lib/commands/configure.js +158 -0
- package/src/lib/commands/doctor.js +184 -0
- package/src/lib/commands/list-libraries.js +33 -0
- package/src/lib/commands/manual-record.js +82 -0
- package/src/lib/commands/publish.js +63 -0
- package/src/lib/commands/sample-branch-edges.js +391 -0
- package/src/lib/commands/sample-extra-nodes.js +204 -0
- package/src/lib/commands/sample-trial-inputs.js +173 -0
- package/src/lib/commands/shared.js +457 -0
- package/src/lib/config.js +204 -0
- package/src/lib/recorder.js +316 -0
- package/src/lib/report.js +309 -0
- package/src/lib/schema-builder.js +431 -0
- package/src/lib/selectors.js +12 -0
- package/src/lib/steps.js +50 -0
- package/src/lib/util.js +93 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureDir, writeJson } from "../util.js";
|
|
4
|
+
import {
|
|
5
|
+
createWorkflowViaPage,
|
|
6
|
+
debugOriginForRegion,
|
|
7
|
+
executeTrialRun,
|
|
8
|
+
getBrowserContext,
|
|
9
|
+
getWorkflowViaPage,
|
|
10
|
+
pageFetchJson,
|
|
11
|
+
readWorkflowSchema,
|
|
12
|
+
requireApiSuccess,
|
|
13
|
+
updateWorkflowViaPage,
|
|
14
|
+
} from "./shared.js";
|
|
15
|
+
import { validateDefinition, buildSchemaFromDefinition } from "../schema-builder.js";
|
|
16
|
+
|
|
17
|
+
export async function runConfigure(config) {
|
|
18
|
+
if (!config.definitionFile) {
|
|
19
|
+
throw new Error("configure requires --definition-file");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const definitionText = await fs.readFile(config.definitionFile, "utf-8");
|
|
23
|
+
const definition = JSON.parse(definitionText);
|
|
24
|
+
|
|
25
|
+
validateDefinition(definition);
|
|
26
|
+
|
|
27
|
+
const schemaJson = buildSchemaFromDefinition(definition);
|
|
28
|
+
|
|
29
|
+
await ensureDir(config.outputDir);
|
|
30
|
+
|
|
31
|
+
const { browser, page } = await getBrowserContext(config.remoteDebuggingPort);
|
|
32
|
+
const region = definition.region ?? config.region;
|
|
33
|
+
const baseOrigin = debugOriginForRegion(region, config.baseUrl);
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
await page.bringToFront();
|
|
37
|
+
await page.goto(`${baseOrigin}/ai/workflow`, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
38
|
+
await page.waitForTimeout(4_000);
|
|
39
|
+
|
|
40
|
+
let workflowId = config.workflowId;
|
|
41
|
+
|
|
42
|
+
if (!workflowId) {
|
|
43
|
+
const created = await createWorkflowViaPage(
|
|
44
|
+
page,
|
|
45
|
+
{
|
|
46
|
+
workflowName: definition.name,
|
|
47
|
+
workflowDesc: definition.description ?? "",
|
|
48
|
+
workflowRegion: region,
|
|
49
|
+
workflowType: definition.type ?? 1,
|
|
50
|
+
},
|
|
51
|
+
{ baseOrigin },
|
|
52
|
+
);
|
|
53
|
+
workflowId = created.workflowId;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const detailBefore = await getWorkflowViaPage(page, workflowId, { baseOrigin });
|
|
57
|
+
const editVersionNo = detailBefore.json?.result?.editVersionNo ?? 1;
|
|
58
|
+
|
|
59
|
+
await updateWorkflowViaPage(
|
|
60
|
+
page,
|
|
61
|
+
{
|
|
62
|
+
workflowId,
|
|
63
|
+
editVersionNo,
|
|
64
|
+
schemaJson,
|
|
65
|
+
},
|
|
66
|
+
{ baseOrigin },
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const detailAfter = await getWorkflowViaPage(page, workflowId, { baseOrigin });
|
|
70
|
+
const savedSchema = readWorkflowSchema(detailAfter);
|
|
71
|
+
|
|
72
|
+
const expectedNodeCount = 1 + definition.nodes.length + definition.ends.length;
|
|
73
|
+
const verified =
|
|
74
|
+
(savedSchema.nodes ?? []).length >= expectedNodeCount &&
|
|
75
|
+
(savedSchema.edges ?? []).length >= definition.edges.length;
|
|
76
|
+
|
|
77
|
+
const result = {
|
|
78
|
+
workflowId,
|
|
79
|
+
verified,
|
|
80
|
+
nodeCount: (savedSchema.nodes ?? []).length,
|
|
81
|
+
edgeCount: (savedSchema.edges ?? []).length,
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
if (definition.trialRun) {
|
|
85
|
+
await page
|
|
86
|
+
.goto(`${baseOrigin}/ai/workflow/debug?workflowId=${workflowId}®ion=${region}`, {
|
|
87
|
+
waitUntil: "domcontentloaded",
|
|
88
|
+
})
|
|
89
|
+
.catch(() => {});
|
|
90
|
+
await page.waitForTimeout(4_000);
|
|
91
|
+
|
|
92
|
+
const preCheck = await pageFetchJson(page, "/micro-app/ai/api/aigc/workflow/run-pre-check", {
|
|
93
|
+
body: {
|
|
94
|
+
params: {
|
|
95
|
+
workflowId,
|
|
96
|
+
region,
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
baseOrigin,
|
|
100
|
+
});
|
|
101
|
+
requireApiSuccess("run-pre-check", preCheck);
|
|
102
|
+
|
|
103
|
+
const testConfig = await pageFetchJson(page, "/micro-app/ai/api/aigc/workflow/test/config", {
|
|
104
|
+
body: { workflowId },
|
|
105
|
+
baseOrigin,
|
|
106
|
+
});
|
|
107
|
+
requireApiSuccess("test/config", testConfig);
|
|
108
|
+
|
|
109
|
+
const endpointType = testConfig.json?.result?.endpointType;
|
|
110
|
+
const endpointId = testConfig.json?.result?.endpointId;
|
|
111
|
+
if (!endpointType || !endpointId) {
|
|
112
|
+
throw new Error("test/config failed: missing endpointType or endpointId");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const configSave = await pageFetchJson(
|
|
116
|
+
page,
|
|
117
|
+
"/micro-app/ai/api/aigc/workflow/test/config/save",
|
|
118
|
+
{
|
|
119
|
+
body: {
|
|
120
|
+
params: {
|
|
121
|
+
workflowId,
|
|
122
|
+
endpointType,
|
|
123
|
+
endpointId,
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
baseOrigin,
|
|
127
|
+
},
|
|
128
|
+
);
|
|
129
|
+
requireApiSuccess("test/config/save", configSave);
|
|
130
|
+
|
|
131
|
+
const trialResult = await executeTrialRun(page, {
|
|
132
|
+
baseOrigin,
|
|
133
|
+
workflowId,
|
|
134
|
+
endpointType,
|
|
135
|
+
endpointId,
|
|
136
|
+
inputArgs: definition.trialRun.inputs,
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const latestProgress = trialResult.progress[trialResult.progress.length - 1] ?? null;
|
|
140
|
+
result.trialRun = {
|
|
141
|
+
executeId: trialResult.executeId,
|
|
142
|
+
status: latestProgress?.json?.result?.executeStatus ?? null,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
await writeJson(path.join(config.outputDir, `configure-${workflowId}.json`), {
|
|
147
|
+
generatedAt: new Date().toISOString(),
|
|
148
|
+
definition,
|
|
149
|
+
schemaJson,
|
|
150
|
+
savedSchema,
|
|
151
|
+
result,
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
console.log(JSON.stringify(result, null, 2));
|
|
155
|
+
} finally {
|
|
156
|
+
await browser.close();
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { ensureDir, writeJson, writeText } from "../util.js";
|
|
4
|
+
import {
|
|
5
|
+
debugOriginForRegion,
|
|
6
|
+
getBrowserContext,
|
|
7
|
+
getWorkflowViaPage,
|
|
8
|
+
pageFetchJson,
|
|
9
|
+
requireApiSuccess,
|
|
10
|
+
} from "./shared.js";
|
|
11
|
+
|
|
12
|
+
function recordCheck(checks, name, ok, detail) {
|
|
13
|
+
checks.push({
|
|
14
|
+
name,
|
|
15
|
+
ok,
|
|
16
|
+
detail,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function buildDoctorMarkdown(report) {
|
|
21
|
+
return [
|
|
22
|
+
"# CLI Doctor",
|
|
23
|
+
"",
|
|
24
|
+
`- Generated at: \`${report.generatedAt}\``,
|
|
25
|
+
`- Remote debugging port: \`${report.remoteDebuggingPort}\``,
|
|
26
|
+
`- Base URL: \`${report.baseUrl}\``,
|
|
27
|
+
`- Region: \`${report.region}\``,
|
|
28
|
+
`- Healthy: \`${report.ok}\``,
|
|
29
|
+
"",
|
|
30
|
+
"## Checks",
|
|
31
|
+
...report.checks.map((check) => `- [${check.ok ? "x" : " "}] ${check.name}: ${check.detail}`),
|
|
32
|
+
"",
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function runDoctor(config) {
|
|
37
|
+
await ensureDir(config.outputDir);
|
|
38
|
+
|
|
39
|
+
const checks = [];
|
|
40
|
+
let browser = null;
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
if (config.imagePath) {
|
|
44
|
+
await fs.access(config.imagePath);
|
|
45
|
+
const stat = await fs.stat(config.imagePath);
|
|
46
|
+
recordCheck(checks, "image-path", stat.size > 0, `${config.imagePath} (${stat.size} bytes)`);
|
|
47
|
+
if (stat.size <= 0) {
|
|
48
|
+
throw new Error("image-path check failed");
|
|
49
|
+
}
|
|
50
|
+
} else {
|
|
51
|
+
recordCheck(checks, "image-path", true, "not provided");
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const browserContext = await getBrowserContext(config.remoteDebuggingPort);
|
|
55
|
+
browser = browserContext.browser;
|
|
56
|
+
const { page } = browserContext;
|
|
57
|
+
|
|
58
|
+
recordCheck(checks, "cdp-connect", true, `connected to 127.0.0.1:${config.remoteDebuggingPort}`);
|
|
59
|
+
|
|
60
|
+
await page.bringToFront();
|
|
61
|
+
await page.goto(config.baseUrl, { waitUntil: "domcontentloaded" });
|
|
62
|
+
await page.waitForTimeout(2_000);
|
|
63
|
+
|
|
64
|
+
const currentUrl = page.url();
|
|
65
|
+
recordCheck(checks, "base-url-open", /platform\.tuya\.com\/ai\/workflow/.test(currentUrl), currentUrl);
|
|
66
|
+
if (!/platform\.tuya\.com\/ai\/workflow/.test(currentUrl)) {
|
|
67
|
+
throw new Error(`base-url-open failed: landed on ${currentUrl}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const listResponse = await pageFetchJson(page, "/micro-app/exp/api/aigc/workflow/list", {
|
|
71
|
+
body: {
|
|
72
|
+
params: {
|
|
73
|
+
pageNo: 1,
|
|
74
|
+
pageSize: 20,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
requireApiSuccess("workflow/list", listResponse);
|
|
79
|
+
const totalCount = listResponse.json?.result?.totalCount ?? null;
|
|
80
|
+
recordCheck(checks, "workflow-list", true, `workflow/list ok, totalCount=${totalCount ?? "unknown"}`);
|
|
81
|
+
|
|
82
|
+
const pageText = (await page.locator("body").innerText().catch(() => "")).slice(0, 4_000);
|
|
83
|
+
const loginIndicators = ["创建工作流", "工作流名称", "流程类型"];
|
|
84
|
+
const matchedIndicators = loginIndicators.filter((item) => pageText.includes(item));
|
|
85
|
+
const loginOk = matchedIndicators.length > 0;
|
|
86
|
+
recordCheck(
|
|
87
|
+
checks,
|
|
88
|
+
"workflow-page-content",
|
|
89
|
+
loginOk,
|
|
90
|
+
loginOk ? `matched ${matchedIndicators.join(", ")}` : "expected workflow page text not found",
|
|
91
|
+
);
|
|
92
|
+
if (!loginOk) {
|
|
93
|
+
throw new Error("workflow-page-content failed");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (config.workflowId) {
|
|
97
|
+
const detail = await getWorkflowViaPage(page, config.workflowId);
|
|
98
|
+
const workflowName = detail.json?.result?.workflowName ?? "unknown";
|
|
99
|
+
recordCheck(checks, "workflow-get", true, `workflowId=${config.workflowId}, name=${workflowName}`);
|
|
100
|
+
|
|
101
|
+
const preCheck = await pageFetchJson(page, "/micro-app/ai/api/aigc/workflow/run-pre-check", {
|
|
102
|
+
body: {
|
|
103
|
+
params: {
|
|
104
|
+
workflowId: config.workflowId,
|
|
105
|
+
region: config.region,
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
requireApiSuccess("run-pre-check", preCheck);
|
|
110
|
+
const preCheckOk = preCheck.json?.result?.isSuccess === 1;
|
|
111
|
+
recordCheck(
|
|
112
|
+
checks,
|
|
113
|
+
"run-pre-check",
|
|
114
|
+
preCheckOk,
|
|
115
|
+
preCheckOk
|
|
116
|
+
? `workflowId=${config.workflowId} passed`
|
|
117
|
+
: JSON.stringify(preCheck.json?.result?.errors ?? []),
|
|
118
|
+
);
|
|
119
|
+
if (!preCheckOk) {
|
|
120
|
+
throw new Error("run-pre-check failed");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const debugOrigin = debugOriginForRegion(config.region, config.baseUrl);
|
|
124
|
+
const testConfig = await pageFetchJson(page, "/micro-app/ai/api/aigc/workflow/test/config", {
|
|
125
|
+
body: {
|
|
126
|
+
workflowId: config.workflowId,
|
|
127
|
+
},
|
|
128
|
+
baseOrigin: debugOrigin,
|
|
129
|
+
});
|
|
130
|
+
requireApiSuccess("test/config", testConfig);
|
|
131
|
+
const endpointType = testConfig.json?.result?.endpointType ?? null;
|
|
132
|
+
const endpointId = testConfig.json?.result?.endpointId ?? null;
|
|
133
|
+
const configOk = Boolean(endpointType && endpointId);
|
|
134
|
+
recordCheck(
|
|
135
|
+
checks,
|
|
136
|
+
"test-config",
|
|
137
|
+
configOk,
|
|
138
|
+
configOk
|
|
139
|
+
? `endpointType=${endpointType}, endpointId=${endpointId}`
|
|
140
|
+
: "missing endpointType or endpointId",
|
|
141
|
+
);
|
|
142
|
+
if (!configOk) {
|
|
143
|
+
throw new Error("test-config failed");
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
recordCheck(checks, "workflow-get", true, "skipped: no --workflow-id");
|
|
147
|
+
recordCheck(checks, "run-pre-check", true, "skipped: no --workflow-id");
|
|
148
|
+
recordCheck(checks, "test-config", true, "skipped: no --workflow-id");
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const report = {
|
|
152
|
+
generatedAt: new Date().toISOString(),
|
|
153
|
+
ok: true,
|
|
154
|
+
remoteDebuggingPort: config.remoteDebuggingPort,
|
|
155
|
+
baseUrl: config.baseUrl,
|
|
156
|
+
region: config.region,
|
|
157
|
+
workflowId: config.workflowId ?? null,
|
|
158
|
+
imagePath: config.imagePath ?? null,
|
|
159
|
+
checks,
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
await writeJson(path.join(config.outputDir, "doctor.json"), report);
|
|
163
|
+
await writeText(path.join(config.outputDir, "doctor.md"), buildDoctorMarkdown(report));
|
|
164
|
+
console.log(JSON.stringify(report, null, 2));
|
|
165
|
+
} catch (error) {
|
|
166
|
+
const report = {
|
|
167
|
+
generatedAt: new Date().toISOString(),
|
|
168
|
+
ok: false,
|
|
169
|
+
remoteDebuggingPort: config.remoteDebuggingPort,
|
|
170
|
+
baseUrl: config.baseUrl,
|
|
171
|
+
region: config.region,
|
|
172
|
+
workflowId: config.workflowId ?? null,
|
|
173
|
+
imagePath: config.imagePath ?? null,
|
|
174
|
+
checks,
|
|
175
|
+
error: error.message,
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
await writeJson(path.join(config.outputDir, "doctor.json"), report);
|
|
179
|
+
await writeText(path.join(config.outputDir, "doctor.md"), buildDoctorMarkdown(report));
|
|
180
|
+
throw error;
|
|
181
|
+
} finally {
|
|
182
|
+
await browser?.close().catch(() => {});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import {
|
|
2
|
+
debugOriginForRegion,
|
|
3
|
+
getBrowserContext,
|
|
4
|
+
listKnowledgeLibrariesViaPage,
|
|
5
|
+
} from "./shared.js";
|
|
6
|
+
|
|
7
|
+
export async function runListLibraries(config) {
|
|
8
|
+
const { browser, page } = await getBrowserContext(config.remoteDebuggingPort);
|
|
9
|
+
const baseOrigin = debugOriginForRegion(config.region, config.baseUrl);
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await page.bringToFront();
|
|
13
|
+
await page.goto(`${baseOrigin}/ai/workflow`, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
14
|
+
await page.waitForTimeout(4_000);
|
|
15
|
+
|
|
16
|
+
const response = await listKnowledgeLibrariesViaPage(page, { baseOrigin });
|
|
17
|
+
|
|
18
|
+
const libraries = Array.isArray(response.json?.result)
|
|
19
|
+
? response.json.result.map((lib) => ({
|
|
20
|
+
libCode: lib.libCode,
|
|
21
|
+
libName: lib.libName,
|
|
22
|
+
supportCategory: lib.supportCategory,
|
|
23
|
+
libCategories: lib.libCategories ?? [],
|
|
24
|
+
}))
|
|
25
|
+
: [];
|
|
26
|
+
|
|
27
|
+
const result = { libraries };
|
|
28
|
+
|
|
29
|
+
console.log(JSON.stringify(result, null, 2));
|
|
30
|
+
} finally {
|
|
31
|
+
await browser.close();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { prepareProfileSnapshot, launchChrome, cleanupProfileSnapshot } from "../chrome.js";
|
|
4
|
+
import { StepRecorder } from "../recorder.js";
|
|
5
|
+
import { STEPS } from "../steps.js";
|
|
6
|
+
import { ensureDir, prompt, sleep, writeJson } from "../util.js";
|
|
7
|
+
import { writeReport } from "../report.js";
|
|
8
|
+
import { writeStaticAnalysis } from "../analyze.js";
|
|
9
|
+
|
|
10
|
+
async function validatePaths(config) {
|
|
11
|
+
await fs.access(config.chromePath);
|
|
12
|
+
await fs.access(path.join(config.chromeProfileRoot, config.chromeProfileName));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function runManualRecord(config) {
|
|
16
|
+
await validatePaths(config);
|
|
17
|
+
await ensureDir(config.outputDir);
|
|
18
|
+
|
|
19
|
+
console.log(`Output directory: ${config.outputDir}`);
|
|
20
|
+
console.log("Preparing Chrome profile snapshot...");
|
|
21
|
+
|
|
22
|
+
const profile = await prepareProfileSnapshot(config);
|
|
23
|
+
let chromeProcess = null;
|
|
24
|
+
const stepNetworks = [];
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const chrome = await launchChrome({
|
|
28
|
+
...config,
|
|
29
|
+
tempProfileRoot: profile.tempProfileRoot,
|
|
30
|
+
chromeProfileName: config.chromeProfileName,
|
|
31
|
+
outputDir: config.outputDir,
|
|
32
|
+
});
|
|
33
|
+
chromeProcess = chrome.chromeProcess;
|
|
34
|
+
|
|
35
|
+
console.log("Connecting to Chrome CDP...");
|
|
36
|
+
const recorder = new StepRecorder(config);
|
|
37
|
+
await recorder.initialize();
|
|
38
|
+
await sleep(2_000);
|
|
39
|
+
|
|
40
|
+
console.log(
|
|
41
|
+
"Chrome is ready. Complete each step in the browser window, then return here and press Enter.",
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
for (const step of STEPS) {
|
|
45
|
+
console.log(`\n=== ${step.title} ===`);
|
|
46
|
+
console.log(step.instruction);
|
|
47
|
+
await prompt("Press Enter to start recording this step...");
|
|
48
|
+
await recorder.startStep(step);
|
|
49
|
+
await prompt("Perform the action in Chrome, then press Enter to finish this step...");
|
|
50
|
+
const network = await recorder.finishStep(step);
|
|
51
|
+
stepNetworks.push({ step, network });
|
|
52
|
+
console.log(`Captured ${network.length} requests for ${step.title}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
await recorder.close();
|
|
56
|
+
|
|
57
|
+
await writeJson(path.join(config.outputDir, "summary.json"), {
|
|
58
|
+
generatedAt: new Date().toISOString(),
|
|
59
|
+
command: "manual",
|
|
60
|
+
stepCount: stepNetworks.length,
|
|
61
|
+
steps: stepNetworks.map(({ step, network }) => ({
|
|
62
|
+
id: step.id,
|
|
63
|
+
title: step.title,
|
|
64
|
+
requestCount: network.length,
|
|
65
|
+
})),
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
await writeReport(config.outputDir, stepNetworks);
|
|
69
|
+
await writeStaticAnalysis(config.outputDir, stepNetworks);
|
|
70
|
+
|
|
71
|
+
console.log(`\nReport written to ${path.join(config.outputDir, "report.md")}`);
|
|
72
|
+
console.log(
|
|
73
|
+
`Static analysis written to ${path.join(config.outputDir, "static-analysis.md")}`,
|
|
74
|
+
);
|
|
75
|
+
} finally {
|
|
76
|
+
if (chromeProcess) {
|
|
77
|
+
chromeProcess.kill("SIGTERM");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
await cleanupProfileSnapshot(profile?.tempRoot);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { ensureDir, writeJson } from "../util.js";
|
|
3
|
+
import {
|
|
4
|
+
debugOriginForRegion,
|
|
5
|
+
getBrowserContext,
|
|
6
|
+
getWorkflowViaPage,
|
|
7
|
+
publishAgentViaPage,
|
|
8
|
+
} from "./shared.js";
|
|
9
|
+
|
|
10
|
+
export async function runPublish(config) {
|
|
11
|
+
if (!config.workflowId) {
|
|
12
|
+
throw new Error("publish requires --workflow-id");
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
await ensureDir(config.outputDir);
|
|
16
|
+
|
|
17
|
+
const { browser, page } = await getBrowserContext(config.remoteDebuggingPort);
|
|
18
|
+
const baseOrigin = debugOriginForRegion(config.region, config.baseUrl);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await page.bringToFront();
|
|
22
|
+
await page.goto(`${baseOrigin}/ai/workflow`, { waitUntil: "domcontentloaded" }).catch(() => {});
|
|
23
|
+
await page.waitForTimeout(4_000);
|
|
24
|
+
|
|
25
|
+
const detailResponse = await getWorkflowViaPage(page, config.workflowId, { baseOrigin });
|
|
26
|
+
const projectCode =
|
|
27
|
+
config.projectCode ??
|
|
28
|
+
detailResponse.json?.result?.projectCode ??
|
|
29
|
+
(() => {
|
|
30
|
+
const schemaText = detailResponse.json?.result?.schemaJson;
|
|
31
|
+
if (typeof schemaText === "string") {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(schemaText)?.workflow?.projectCode ?? null;
|
|
34
|
+
} catch {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return null;
|
|
39
|
+
})();
|
|
40
|
+
|
|
41
|
+
if (!projectCode) {
|
|
42
|
+
throw new Error("publish failed: unable to determine projectCode. Use --project-code to specify.");
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
await publishAgentViaPage(page, { projectCode, region: config.region }, { baseOrigin });
|
|
46
|
+
|
|
47
|
+
const result = {
|
|
48
|
+
workflowId: config.workflowId,
|
|
49
|
+
projectCode,
|
|
50
|
+
region: config.region,
|
|
51
|
+
published: true,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
await writeJson(path.join(config.outputDir, `publish-${config.workflowId}.json`), {
|
|
55
|
+
generatedAt: new Date().toISOString(),
|
|
56
|
+
result,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
console.log(JSON.stringify(result, null, 2));
|
|
60
|
+
} finally {
|
|
61
|
+
await browser.close();
|
|
62
|
+
}
|
|
63
|
+
}
|