@ai.weget.jp/bot 0.1.8 → 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/README.md +13 -4
  2. package/dist/src/config/systemConfig.js +1 -0
  3. package/dist/src/ipc/registerHandlers.js +1 -1
  4. package/dist/src/main.js +1 -1
  5. package/dist/src/preload.cjs +26 -8
  6. package/dist/src/renderer/app.js +1 -1
  7. package/dist/src/renderer/index.html +317 -13
  8. package/dist/src/renderer/styles.css +451 -2
  9. package/dist/src/services/aiChatService.js +1 -0
  10. package/dist/src/services/authApiService.js +1 -1
  11. package/dist/src/services/trade/gmoCoinGateway.js +1 -0
  12. package/dist/src/services/trade/gmoKlineService.js +1 -0
  13. package/dist/src/services/trade/gmoWindowService.js +1 -0
  14. package/dist/src/services/trade/tradeConfigService.js +1 -0
  15. package/dist/src/services/trade/tradeStatusService.js +1 -0
  16. package/dist/src/services/trade/types.js +1 -0
  17. package/dist/src/services/windowManagerService.js +1 -1
  18. package/dist/src/wsClient.js +1 -1
  19. package/package.json +1 -7
  20. package/dist/src/renderer/chat.css +0 -128
  21. package/dist/src/renderer/chat.html +0 -27
  22. package/dist/src/renderer/chat.js +0 -1
  23. package/dist/src/services/aiMemoryService.js +0 -1
  24. package/dist/src/services/commandOrchestratorService.js +0 -1
  25. package/dist/src/services/debugLogService.js +0 -1
  26. package/dist/src/services/taskExecutorService.js +0 -1
  27. package/dist/src/services/taskParsingService.js +0 -1
  28. package/dist/src/services/textService.js +0 -1
  29. package/dist/src/services/workflowService.js +0 -1
  30. package/scripts/backtest-loop.cjs +0 -137
  31. package/scripts/prepare-playwright-browsers.cjs +0 -181
  32. package/scripts/run-workflow.cjs +0 -381
  33. package/scripts/workflows/kaitori-login.json +0 -17
@@ -1,381 +0,0 @@
1
- const fs = require('node:fs');
2
- const path = require('node:path');
3
- const crypto = require('node:crypto');
4
- const dotenv = require('dotenv');
5
- const { chromium } = require('playwright');
6
-
7
- dotenv.config({ path: path.resolve(process.cwd(), '.env'), quiet: true });
8
-
9
- const getArg = (name, fallback = '') => {
10
- const key = `--${name}`;
11
- const idx = process.argv.findIndex((x) => x === key);
12
- if (idx >= 0 && idx + 1 < process.argv.length) return String(process.argv[idx + 1] || '');
13
- const pair = process.argv.find((x) => x.startsWith(`${key}=`));
14
- if (pair) return String(pair.slice(key.length + 1));
15
- return fallback;
16
- };
17
-
18
- const env = {
19
- loginApiUrl: process.env.BOT_LOGIN_API_URL || '',
20
- headless: String(process.env.BOT_PLAYWRIGHT_HEADLESS || 'true').toLowerCase() === 'true',
21
- navTimeoutMs: Number(process.env.BOT_PLAYWRIGHT_NAV_TIMEOUT_MS || 30000),
22
- channel: String(process.env.BOT_PLAYWRIGHT_CHANNEL || 'msedge').trim(),
23
- debugLogPath: String(process.env.BOT_DEBUG_LOG_PATH || '').trim(),
24
- };
25
-
26
- const email = getArg('email', process.env.BOT_SCRIPT_EMAIL || '').trim();
27
- const password = getArg('password', process.env.BOT_SCRIPT_PASSWORD || '');
28
- const botId = getArg('bot-id', process.env.BOT_SCRIPT_BOT_ID || '').trim();
29
- const workflowFile = getArg('workflow-file', process.env.BOT_SCRIPT_WORKFLOW_FILE || '');
30
- const workflowJson = getArg('workflow-json', process.env.BOT_SCRIPT_WORKFLOW_JSON || '');
31
- const lineText = getArg('line-text', process.env.BOT_SCRIPT_LINE_TEXT || '').trim();
32
- const runId = getArg('run-id', crypto.randomUUID()).trim() || crypto.randomUUID();
33
-
34
- if (!email || !password || !botId) {
35
- process.stderr.write(JSON.stringify({ ok: false, error: 'missing --email --password --bot-id' }) + '\n');
36
- process.exit(1);
37
- }
38
- if (!env.loginApiUrl) {
39
- process.stderr.write(JSON.stringify({ ok: false, error: 'missing BOT_LOGIN_API_URL' }) + '\n');
40
- process.exit(1);
41
- }
42
-
43
- const parseJson = (raw) => {
44
- try {
45
- return JSON.parse(raw);
46
- } catch {
47
- return null;
48
- }
49
- };
50
-
51
- const loadWorkflowInput = () => {
52
- if (workflowJson) return parseJson(workflowJson);
53
- if (!workflowFile) return null;
54
- const raw = fs.readFileSync(path.resolve(workflowFile), 'utf8');
55
- return parseJson(raw);
56
- };
57
-
58
- const normalizeWorkflow = (input) => {
59
- const rows = Array.isArray(input) ? input : Array.isArray(input?.steps_json) ? input.steps_json : [];
60
- return rows
61
- .map((x, i) => ({
62
- id: String(x?.id || `s${i + 1}`),
63
- title: String(x?.title || `Step ${i + 1}`).trim(),
64
- instruction: String(x?.instruction || '').trim(),
65
- }))
66
- .filter((x) => x.instruction);
67
- };
68
-
69
- const extractFirstUrl = (text) => {
70
- const input = String(text || '');
71
- const m =
72
- input.match(/https?:\/\/[^\s"'<>`]+/i) ||
73
- input.match(/[a-z0-9.-]+\.[a-z]{2,}(?:\/[^\s"'<>`]*)?/i);
74
- if (!m?.[0]) return '';
75
- const raw = m[0].trim();
76
- if (/^https?:\/\//i.test(raw)) return raw;
77
- return `https://${raw}`;
78
- };
79
-
80
- const extractCredentials = (text) => {
81
- const input = String(text || '');
82
- const userMatch =
83
- input.match(/(?:用户名|用户|ユーザー名|user(?:name)?|login|id)\s*[::]?\s*([^\s\n]+)/i);
84
- const passMatch = input.match(/(?:密码|パスワード|password|pass)\s*[::]?\s*([^\s\n]+)/i);
85
- const username = String(userMatch?.[1] || '').trim();
86
- const pwd = String(passMatch?.[1] || '').trim();
87
- if (!username || !pwd) return null;
88
- return { username, password: pwd };
89
- };
90
-
91
- const isBasicStep = (title, instruction) =>
92
- /(basic|ベーシック|basic认证|基本认证)/i.test(`${title}\n${instruction}`);
93
-
94
- const isScreenshotStep = (title, instruction) =>
95
- /(截图|截屏|screenshot|screen shot|キャプチャ)/i.test(`${title}\n${instruction}`);
96
-
97
- const signIn = async () => {
98
- const response = await fetch(env.loginApiUrl, {
99
- method: 'POST',
100
- headers: { 'Content-Type': 'application/json' },
101
- body: JSON.stringify({ email, password, client: 'bot-script' }),
102
- });
103
- const payload = await response.json().catch(() => ({}));
104
- if (!response.ok) {
105
- throw new Error(payload.error || payload.message || `login failed: ${response.status}`);
106
- }
107
- return payload;
108
- };
109
-
110
- const run = async () => {
111
- const workflowInput = loadWorkflowInput();
112
- const steps = normalizeWorkflow(workflowInput);
113
- if (!steps.length) throw new Error('invalid workflow input');
114
-
115
- await signIn();
116
-
117
- const logs = [];
118
- const artifacts = [];
119
- const debugEntries = [];
120
- const authByHost = new Map();
121
- const credByHost = new Map();
122
- let lastUrl = '';
123
-
124
- const emitDebug = (phase, event, data = {}, stepIndex = 0, stepTitle = '') => {
125
- const entry = {
126
- ts: new Date().toISOString(),
127
- level: 'info',
128
- phase,
129
- event,
130
- run_id: runId,
131
- workflow_id: `script-${botId}`,
132
- workflow_name: 'script_workflow',
133
- step_index: stepIndex,
134
- step_total: steps.length,
135
- step_title: stepTitle,
136
- bot_id: botId,
137
- user_id: '',
138
- data,
139
- };
140
- debugEntries.push(entry);
141
- if (env.debugLogPath) {
142
- const debugPath = path.resolve(process.cwd(), env.debugLogPath);
143
- fs.mkdirSync(path.dirname(debugPath), { recursive: true });
144
- fs.appendFileSync(debugPath, `${JSON.stringify(entry)}\n`, 'utf8');
145
- }
146
- };
147
-
148
- const browser = await chromium.launch({
149
- headless: env.headless,
150
- channel: env.channel || undefined,
151
- });
152
- let context = await browser.newContext({ viewport: { width: 1366, height: 900 } });
153
- let page = await context.newPage();
154
- page.setDefaultNavigationTimeout(env.navTimeoutMs);
155
- const recreateContextWithCredentials = async (credentials = null) => {
156
- try {
157
- await page.close();
158
- } catch {
159
- // ignore
160
- }
161
- try {
162
- await context.close();
163
- } catch {
164
- // ignore
165
- }
166
- context = await browser.newContext(
167
- credentials
168
- ? { viewport: { width: 1366, height: 900 }, httpCredentials: credentials }
169
- : { viewport: { width: 1366, height: 900 } },
170
- );
171
- page = await context.newPage();
172
- page.setDefaultNavigationTimeout(env.navTimeoutMs);
173
- };
174
-
175
- const fillFirst = async (selectors, value) => {
176
- for (const sel of selectors) {
177
- const locator = page.locator(sel).first();
178
- if ((await locator.count()) === 0) continue;
179
- try {
180
- await locator.fill(String(value || ''));
181
- return sel;
182
- } catch {
183
- // continue
184
- }
185
- }
186
- return '';
187
- };
188
-
189
- const waitAndFillFirst = async (selectors, value, timeoutMs = 10000) => {
190
- const deadline = Date.now() + Math.max(500, Number(timeoutMs || 10000));
191
- while (Date.now() < deadline) {
192
- const matched = await fillFirst(selectors, value);
193
- if (matched) return matched;
194
- await page.waitForTimeout(250);
195
- }
196
- return '';
197
- };
198
-
199
- for (let i = 0; i < steps.length; i += 1) {
200
- const step = steps[i];
201
- const title = step.title;
202
- const instruction = step.instruction;
203
- const url = extractFirstUrl(instruction);
204
- const cred = extractCredentials(instruction);
205
- logs.push({ run_id: runId, step: i + 1, step_title: title, event: 'step_started' });
206
- emitDebug('step_start', 'step_started', { step_instruction: instruction }, i + 1, title);
207
-
208
- if (url) {
209
- if (cred && isBasicStep(title, instruction)) {
210
- const host = new URL(url).host.toLowerCase();
211
- const token = Buffer.from(`${cred.username}:${cred.password}`, 'utf8').toString('base64');
212
- authByHost.set(host, `Basic ${token}`);
213
- credByHost.set(host, { username: cred.username, password: cred.password });
214
- }
215
- const host = new URL(url).host.toLowerCase();
216
- const authHeader = authByHost.get(host) || '';
217
- const hostCred = credByHost.get(host) || null;
218
- await page.setExtraHTTPHeaders(authHeader ? { Authorization: authHeader } : {});
219
- try {
220
- await page.goto(url, { waitUntil: 'domcontentloaded' });
221
- } catch (error) {
222
- const msg = error instanceof Error ? error.message : String(error);
223
- if (!hostCred || !/ERR_INVALID_AUTH_CREDENTIALS|401/i.test(msg)) throw error;
224
- await recreateContextWithCredentials(hostCred);
225
- await page.goto(url, { waitUntil: 'domcontentloaded' });
226
- }
227
- await page.waitForTimeout(1200);
228
- lastUrl = String(page.url() || url);
229
- logs.push({ run_id: runId, step: i + 1, step_title: title, event: 'open_url_ok', url: lastUrl });
230
- emitDebug('action_end', 'action_completed', { action_type: 'open_url', url: lastUrl }, i + 1, title);
231
- continue;
232
- }
233
-
234
- if (cred && isBasicStep(title, instruction)) {
235
- if (!lastUrl) throw new Error(`step ${i + 1}: basic auth provided but no previous URL`);
236
- const host = new URL(lastUrl).host.toLowerCase();
237
- const token = Buffer.from(`${cred.username}:${cred.password}`, 'utf8').toString('base64');
238
- authByHost.set(host, `Basic ${token}`);
239
- credByHost.set(host, { username: cred.username, password: cred.password });
240
- await recreateContextWithCredentials({ username: cred.username, password: cred.password });
241
- await page.goto(lastUrl, { waitUntil: 'domcontentloaded' });
242
- await page.waitForTimeout(1200);
243
- logs.push({ run_id: runId, step: i + 1, step_title: title, event: 'basic_auth_applied', host });
244
- emitDebug('action_end', 'action_completed', { action_type: 'set_http_credentials', host }, i + 1, title);
245
- continue;
246
- }
247
-
248
- if (cred) {
249
- const userSel = await waitAndFillFirst(
250
- [
251
- 'input[name*="user" i]',
252
- 'input[name*="login" i]',
253
- 'input[name*="id" i]',
254
- 'input[autocomplete="username"]',
255
- 'input[id*="user" i]',
256
- 'input[id*="login" i]',
257
- 'input[id*="id" i]',
258
- 'input[type="text"]',
259
- 'input[type="email"]',
260
- 'input:not([type])',
261
- ],
262
- cred.username,
263
- );
264
- const passSel = await waitAndFillFirst(
265
- [
266
- 'input[type="password"]',
267
- 'input[autocomplete="current-password"]',
268
- 'input[name*="pass" i]',
269
- 'input[id*="pass" i]',
270
- ],
271
- cred.password,
272
- );
273
- if (!userSel || !passSel) {
274
- const passwordInputCount = await page
275
- .locator('input[type="password"]')
276
- .count()
277
- .catch(() => 0);
278
- if (!passwordInputCount) {
279
- logs.push({
280
- run_id: runId,
281
- step: i + 1,
282
- step_title: title,
283
- event: 'auto_login_form_skipped',
284
- reason: 'login_form_not_present',
285
- url: String(page.url() || ''),
286
- });
287
- emitDebug(
288
- 'action_end',
289
- 'action_skipped',
290
- { action_type: 'auto_login_form', reason: 'login_form_not_present' },
291
- i + 1,
292
- title,
293
- );
294
- continue;
295
- }
296
- const inputCount = await page.locator('input').count().catch(() => -1);
297
- const currentUrl = String(page.url() || '');
298
- throw new Error(
299
- `step ${i + 1}: login form input not found (url=${currentUrl}, input_count=${inputCount}, user_sel=${userSel || 'none'}, pass_sel=${passSel || 'none'})`,
300
- );
301
- }
302
- const submitSelectors = ['button[type="submit"]', 'input[type="submit"]', 'button[name*="login" i]', 'button'];
303
- let submitSel = '';
304
- for (const sel of submitSelectors) {
305
- const loc = page.locator(sel).first();
306
- if ((await loc.count()) === 0) continue;
307
- try {
308
- await loc.click();
309
- submitSel = sel;
310
- break;
311
- } catch {
312
- // continue
313
- }
314
- }
315
- if (!submitSel) await page.keyboard.press('Enter');
316
- await page.waitForTimeout(1500);
317
- logs.push({
318
- run_id: runId,
319
- step: i + 1,
320
- step_title: title,
321
- event: 'auto_login_form_ok',
322
- user_selector: userSel,
323
- pass_selector: passSel,
324
- submit_selector: submitSel || 'keyboard:Enter',
325
- });
326
- emitDebug(
327
- 'action_end',
328
- 'action_completed',
329
- {
330
- action_type: 'auto_login_form',
331
- user_selector: userSel,
332
- pass_selector: passSel,
333
- submit_selector: submitSel || 'keyboard:Enter',
334
- },
335
- i + 1,
336
- title,
337
- );
338
- continue;
339
- }
340
-
341
- if (isScreenshotStep(title, instruction)) {
342
- const dir = path.join(process.cwd(), 'tmp', 'artifacts');
343
- fs.mkdirSync(dir, { recursive: true });
344
- const file = path.join(dir, `script-shot-${runId}-${i + 1}.png`);
345
- await page.screenshot({ path: file, fullPage: true, type: 'png' });
346
- artifacts.push({ step: i + 1, type: 'image', local_path: file });
347
- logs.push({ run_id: runId, step: i + 1, step_title: title, event: 'screenshot_ok', local_path: file });
348
- emitDebug('action_end', 'action_completed', { action_type: 'screenshot', local_path: file }, i + 1, title);
349
- continue;
350
- }
351
-
352
- logs.push({ run_id: runId, step: i + 1, step_title: title, event: 'step_noop' });
353
- emitDebug('action_end', 'action_noop', {}, i + 1, title);
354
- }
355
-
356
- await context.close();
357
- await browser.close();
358
-
359
- const output = {
360
- ok: true,
361
- run_id: runId,
362
- bot_id: botId,
363
- step_total: steps.length,
364
- artifacts,
365
- logs,
366
- answer: 'workflow script executed',
367
- image_url: artifacts.find((x) => x.type === 'image')?.local_path || '',
368
- };
369
- if (env.debugLogPath) {
370
- const debugPath = path.resolve(process.cwd(), env.debugLogPath);
371
- fs.mkdirSync(path.dirname(debugPath), { recursive: true });
372
- const lines = debugEntries.map((x) => JSON.stringify(x)).join('\n');
373
- if (lines) fs.appendFileSync(debugPath, `${lines}\n`, 'utf8');
374
- }
375
- process.stdout.write(`${JSON.stringify(output, null, 2)}\n`);
376
- };
377
-
378
- run().catch((error) => {
379
- process.stderr.write(`${JSON.stringify({ ok: false, error: error instanceof Error ? error.message : String(error) }, null, 2)}\n`);
380
- process.exit(1);
381
- });
@@ -1,17 +0,0 @@
1
- [
2
- {
3
- "id": "s1",
4
- "title": "设置basic并打开页面",
5
- "instruction": "Basic认证信息:用户名xxx 密码xxx。打开URL:https://admin.test.com/"
6
- },
7
- {
8
- "id": "s2",
9
- "title": "输入admin认证信息并提交",
10
- "instruction": "在当前页面自动填写登录表单。用户名xxx 密码xxx,然后提交。"
11
- },
12
- {
13
- "id": "s3",
14
- "title": "截图",
15
- "instruction": "把登录后站点截屏"
16
- }
17
- ]