android-midscene-automation 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.
Files changed (78) hide show
  1. package/README.md +160 -0
  2. package/bin/android-midscene-automation.js +27 -0
  3. package/index.html +12 -0
  4. package/package.json +49 -0
  5. package/remote-agent/index.ts +206 -0
  6. package/server/appium-recorder/appium-runner.ts +427 -0
  7. package/server/appium-recorder/repository.ts +228 -0
  8. package/server/appium-recorder/routes.ts +219 -0
  9. package/server/config-store.ts +167 -0
  10. package/server/config.ts +130 -0
  11. package/server/device-locks/repository.ts +147 -0
  12. package/server/device-locks/service.ts +72 -0
  13. package/server/device-locks/types.ts +22 -0
  14. package/server/device-sessions/repository.ts +169 -0
  15. package/server/device-sessions/service.ts +59 -0
  16. package/server/device-sessions/types.ts +24 -0
  17. package/server/http-api.ts +1389 -0
  18. package/server/model-call-usage-importer.ts +108 -0
  19. package/server/model-tester.ts +104 -0
  20. package/server/model-usage-repository.ts +131 -0
  21. package/server/operations/repository.ts +218 -0
  22. package/server/operations/service.ts +84 -0
  23. package/server/operations/types.ts +27 -0
  24. package/server/paths.ts +27 -0
  25. package/server/remote-agents/protocol.ts +38 -0
  26. package/server/remote-agents/registry.ts +136 -0
  27. package/server/remote-agents/routes.ts +89 -0
  28. package/server/script-agent.ts +284 -0
  29. package/server/script-db.ts +281 -0
  30. package/server/script-runner.ts +551 -0
  31. package/server/storage/sqlite.ts +49 -0
  32. package/server/test-case-import/formatter.ts +28 -0
  33. package/server/test-case-import/parsers/excel.ts +69 -0
  34. package/server/test-case-import/parsers/txt.ts +11 -0
  35. package/server/test-case-import/parsers/word.ts +9 -0
  36. package/server/test-case-import/service.ts +58 -0
  37. package/server/test-case-import/text-normalizer.ts +98 -0
  38. package/server/test-case-import/types.ts +24 -0
  39. package/server/test-case-import/validator.ts +34 -0
  40. package/src/App.vue +1450 -0
  41. package/src/api.ts +290 -0
  42. package/src/appium-recorder/AppiumPage.vue +894 -0
  43. package/src/appium-recorder/api.ts +64 -0
  44. package/src/appium-recorder/components/ComponentTree.vue +44 -0
  45. package/src/appium-recorder/components/NodeDetail.vue +152 -0
  46. package/src/appium-recorder/components/RecordedSteps.vue +79 -0
  47. package/src/appium-recorder/tree.ts +129 -0
  48. package/src/appium-recorder/types.ts +88 -0
  49. package/src/assets/device-actions/back.svg +5 -0
  50. package/src/assets/device-actions/home.svg +3 -0
  51. package/src/assets/device-actions/power.svg +5 -0
  52. package/src/assets/device-actions/tasks.svg +3 -0
  53. package/src/assets/device-actions/volume-down.svg +3 -0
  54. package/src/assets/device-actions/volume-up.svg +3 -0
  55. package/src/components/config/ModelUsageChart.vue +188 -0
  56. package/src/components/device/DevicePreviewPanel.vue +266 -0
  57. package/src/components/generator/GeneratedCodePanel.vue +70 -0
  58. package/src/components/generator/TestCaseFileUpload.vue +97 -0
  59. package/src/config/midscene-model-presets.ts +75 -0
  60. package/src/config/prompt-example.ts +6 -0
  61. package/src/main.ts +7 -0
  62. package/src/pages/AiGeneratorPage.vue +90 -0
  63. package/src/pages/AutomationPage.vue +161 -0
  64. package/src/pages/ConfigPage.vue +273 -0
  65. package/src/pages/GeneratorPage.vue +97 -0
  66. package/src/pages/ManualStepsPage.vue +179 -0
  67. package/src/script-generator/codegen.ts +126 -0
  68. package/src/script-generator/index.ts +4 -0
  69. package/src/script-generator/presets.ts +37 -0
  70. package/src/script-generator/step-options.ts +52 -0
  71. package/src/script-generator/types.ts +27 -0
  72. package/src/style.css +1983 -0
  73. package/src/types.ts +157 -0
  74. package/src/vite-env.d.ts +1 -0
  75. package/tsconfig.app.json +8 -0
  76. package/tsconfig.json +11 -0
  77. package/tsconfig.node.json +16 -0
  78. package/vite.config.ts +28 -0
@@ -0,0 +1,281 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { appPath } from './paths';
5
+
6
+ export type ScriptStepRecord = {
7
+ id?: string;
8
+ type: string;
9
+ label: string;
10
+ prompt: string;
11
+ outputVar?: string;
12
+ value?: string;
13
+ observePrompt?: string;
14
+ repeat?: number;
15
+ enabled?: boolean;
16
+ };
17
+
18
+ export type ScriptRecord = {
19
+ id: string;
20
+ name: string;
21
+ promptTitle: string;
22
+ sourcePrompt: string;
23
+ code: string;
24
+ filePath: string;
25
+ steps: ScriptStepRecord[];
26
+ createdAt: string;
27
+ updatedAt: string;
28
+ };
29
+
30
+ type ScriptRow = {
31
+ id: string;
32
+ name: string;
33
+ prompt_title: string;
34
+ source_prompt: string;
35
+ code: string;
36
+ file_path: string;
37
+ steps_json: string;
38
+ created_at: string;
39
+ updated_at: string;
40
+ };
41
+
42
+ const dbPath = appPath('.midscene-app', 'script-cache.sqlite');
43
+ const legacyJsonPath = appPath('midscene_run', 'script-db.json');
44
+ const sqliteBin = process.env.SQLITE3_BIN || '/usr/bin/sqlite3';
45
+ let initialized = false;
46
+
47
+ function createId() {
48
+ return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
49
+ }
50
+
51
+ function ensureDbDir() {
52
+ fs.mkdirSync(path.dirname(dbPath), { recursive: true });
53
+ }
54
+
55
+ function sqlString(value: string) {
56
+ return `'${value.replace(/'/g, "''")}'`;
57
+ }
58
+
59
+ function runSql(sql: string) {
60
+ ensureDbDir();
61
+ execFileSync(sqliteBin, [dbPath], {
62
+ input: sql,
63
+ maxBuffer: 20 * 1024 * 1024,
64
+ });
65
+ }
66
+
67
+ function querySql<T>(sql: string) {
68
+ ensureDbDir();
69
+ const output = execFileSync(sqliteBin, ['-json', dbPath, sql], {
70
+ encoding: 'utf8',
71
+ maxBuffer: 20 * 1024 * 1024,
72
+ });
73
+ return JSON.parse(output || '[]') as T[];
74
+ }
75
+
76
+ function rowToRecord(row: ScriptRow): ScriptRecord {
77
+ return {
78
+ id: row.id,
79
+ name: row.name,
80
+ promptTitle: row.prompt_title,
81
+ sourcePrompt: row.source_prompt,
82
+ code: row.code,
83
+ filePath: row.file_path,
84
+ steps: JSON.parse(row.steps_json || '[]') as ScriptStepRecord[],
85
+ createdAt: row.created_at,
86
+ updatedAt: row.updated_at,
87
+ };
88
+ }
89
+
90
+ function migrateLegacyJson() {
91
+ if (!fs.existsSync(legacyJsonPath)) {
92
+ return;
93
+ }
94
+
95
+ const count = querySql<{ count: number }>('SELECT COUNT(*) AS count FROM scripts;')[0]?.count || 0;
96
+ if (count > 0) {
97
+ return;
98
+ }
99
+
100
+ const parsed = JSON.parse(fs.readFileSync(legacyJsonPath, 'utf8')) as {
101
+ scripts?: ScriptRecord[];
102
+ };
103
+ const scripts = Array.isArray(parsed.scripts) ? parsed.scripts : [];
104
+
105
+ for (const script of scripts) {
106
+ upsertScriptRecord({
107
+ name: script.name,
108
+ promptTitle: script.promptTitle,
109
+ sourcePrompt: script.sourcePrompt,
110
+ code: script.code,
111
+ filePath: script.filePath,
112
+ steps: script.steps,
113
+ });
114
+ }
115
+ }
116
+
117
+ function initDb() {
118
+ if (initialized) return;
119
+ initialized = true;
120
+ runSql(`
121
+ PRAGMA journal_mode = WAL;
122
+ CREATE TABLE IF NOT EXISTS scripts (
123
+ id TEXT PRIMARY KEY,
124
+ name TEXT NOT NULL UNIQUE,
125
+ prompt_title TEXT NOT NULL,
126
+ source_prompt TEXT NOT NULL DEFAULT '',
127
+ code TEXT NOT NULL,
128
+ file_path TEXT NOT NULL,
129
+ steps_json TEXT NOT NULL DEFAULT '[]',
130
+ created_at TEXT NOT NULL,
131
+ updated_at TEXT NOT NULL
132
+ );
133
+ CREATE INDEX IF NOT EXISTS idx_scripts_updated_at ON scripts(updated_at DESC);
134
+ `);
135
+ migrateLegacyJson();
136
+ }
137
+
138
+ export function listScriptRecords() {
139
+ initDb();
140
+ return querySql<ScriptRow>(`
141
+ SELECT
142
+ id,
143
+ name,
144
+ prompt_title,
145
+ source_prompt,
146
+ code,
147
+ file_path,
148
+ steps_json,
149
+ created_at,
150
+ updated_at
151
+ FROM scripts
152
+ ORDER BY updated_at DESC;
153
+ `).map(rowToRecord);
154
+ }
155
+
156
+ export function getScriptRecord(id: string) {
157
+ initDb();
158
+ const row = querySql<ScriptRow>(`
159
+ SELECT
160
+ id,
161
+ name,
162
+ prompt_title,
163
+ source_prompt,
164
+ code,
165
+ file_path,
166
+ steps_json,
167
+ created_at,
168
+ updated_at
169
+ FROM scripts
170
+ WHERE id = ${sqlString(id)}
171
+ LIMIT 1;
172
+ `)[0];
173
+
174
+ return row ? rowToRecord(row) : null;
175
+ }
176
+
177
+ export function upsertScriptRecord(input: {
178
+ name: string;
179
+ promptTitle: string;
180
+ sourcePrompt: string;
181
+ code: string;
182
+ filePath: string;
183
+ steps?: ScriptStepRecord[];
184
+ }) {
185
+ initDb();
186
+ const now = new Date().toISOString();
187
+ const id = createId();
188
+ const stepsJson = JSON.stringify(input.steps || []);
189
+
190
+ runSql(`
191
+ INSERT INTO scripts (
192
+ id,
193
+ name,
194
+ prompt_title,
195
+ source_prompt,
196
+ code,
197
+ file_path,
198
+ steps_json,
199
+ created_at,
200
+ updated_at
201
+ )
202
+ VALUES (
203
+ ${sqlString(id)},
204
+ ${sqlString(input.name)},
205
+ ${sqlString(input.promptTitle || input.name)},
206
+ ${sqlString(input.sourcePrompt || '')},
207
+ ${sqlString(input.code)},
208
+ ${sqlString(input.filePath)},
209
+ ${sqlString(stepsJson)},
210
+ ${sqlString(now)},
211
+ ${sqlString(now)}
212
+ )
213
+ ON CONFLICT(name) DO UPDATE SET
214
+ prompt_title = excluded.prompt_title,
215
+ source_prompt = excluded.source_prompt,
216
+ code = excluded.code,
217
+ file_path = excluded.file_path,
218
+ steps_json = excluded.steps_json,
219
+ updated_at = excluded.updated_at;
220
+ `);
221
+
222
+ const row = querySql<ScriptRow>(`
223
+ SELECT
224
+ id,
225
+ name,
226
+ prompt_title,
227
+ source_prompt,
228
+ code,
229
+ file_path,
230
+ steps_json,
231
+ created_at,
232
+ updated_at
233
+ FROM scripts
234
+ WHERE name = ${sqlString(input.name)}
235
+ LIMIT 1;
236
+ `)[0];
237
+
238
+ return rowToRecord(row);
239
+ }
240
+
241
+ export function updateScriptRecordCode(input: { id: string; code: string; filePath?: string }) {
242
+ initDb();
243
+ const now = new Date().toISOString();
244
+
245
+ runSql(`
246
+ UPDATE scripts
247
+ SET
248
+ code = ${sqlString(input.code)},
249
+ file_path = COALESCE(${input.filePath ? sqlString(input.filePath) : 'NULL'}, file_path),
250
+ steps_json = '[]',
251
+ updated_at = ${sqlString(now)}
252
+ WHERE id = ${sqlString(input.id)};
253
+ `);
254
+
255
+ return getScriptRecord(input.id);
256
+ }
257
+
258
+ export function removeScriptRecord(id: string) {
259
+ initDb();
260
+ const row = querySql<ScriptRow>(`
261
+ SELECT
262
+ id,
263
+ name,
264
+ prompt_title,
265
+ source_prompt,
266
+ code,
267
+ file_path,
268
+ steps_json,
269
+ created_at,
270
+ updated_at
271
+ FROM scripts
272
+ WHERE id = ${sqlString(id)}
273
+ LIMIT 1;
274
+ `)[0];
275
+ runSql(`DELETE FROM scripts WHERE id = ${sqlString(id)};`);
276
+ return row ? rowToRecord(row) : null;
277
+ }
278
+
279
+ export function getScriptDbPath() {
280
+ return dbPath;
281
+ }