@godscene/shared 1.7.11

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 (236) hide show
  1. package/README.md +9 -0
  2. package/dist/es/baseDB.mjs +109 -0
  3. package/dist/es/cli/cli-args.mjs +95 -0
  4. package/dist/es/cli/cli-error.mjs +24 -0
  5. package/dist/es/cli/cli-runner.mjs +122 -0
  6. package/dist/es/cli/index.mjs +4 -0
  7. package/dist/es/common.mjs +37 -0
  8. package/dist/es/constants/example-code.mjs +227 -0
  9. package/dist/es/constants/index.mjs +124 -0
  10. package/dist/es/env/basic.mjs +6 -0
  11. package/dist/es/env/constants.mjs +110 -0
  12. package/dist/es/env/global-config-manager.mjs +94 -0
  13. package/dist/es/env/helper.mjs +43 -0
  14. package/dist/es/env/index.mjs +5 -0
  15. package/dist/es/env/init-debug.mjs +18 -0
  16. package/dist/es/env/model-config-manager.mjs +79 -0
  17. package/dist/es/env/parse-model-config.mjs +165 -0
  18. package/dist/es/env/types.mjs +232 -0
  19. package/dist/es/env/utils.mjs +18 -0
  20. package/dist/es/extractor/constants.mjs +2 -0
  21. package/dist/es/extractor/cs_postmessage.mjs +61 -0
  22. package/dist/es/extractor/customLocator.mjs +641 -0
  23. package/dist/es/extractor/debug.mjs +6 -0
  24. package/dist/es/extractor/dom-util.mjs +96 -0
  25. package/dist/es/extractor/index.mjs +5 -0
  26. package/dist/es/extractor/locator.mjs +250 -0
  27. package/dist/es/extractor/tree.mjs +78 -0
  28. package/dist/es/extractor/util.mjs +245 -0
  29. package/dist/es/extractor/web-extractor.mjs +393 -0
  30. package/dist/es/img/box-select.mjs +824 -0
  31. package/dist/es/img/canvas-fallback.mjs +238 -0
  32. package/dist/es/img/get-photon.mjs +45 -0
  33. package/dist/es/img/get-sharp.mjs +11 -0
  34. package/dist/es/img/index.mjs +4 -0
  35. package/dist/es/img/info.mjs +35 -0
  36. package/dist/es/img/transform.mjs +275 -0
  37. package/dist/es/index.mjs +2 -0
  38. package/dist/es/key-alias-utils.mjs +19 -0
  39. package/dist/es/logger.mjs +64 -0
  40. package/dist/es/mcp/base-server.mjs +282 -0
  41. package/dist/es/mcp/base-tools.mjs +159 -0
  42. package/dist/es/mcp/chrome-path.mjs +35 -0
  43. package/dist/es/mcp/cli-report-session.mjs +78 -0
  44. package/dist/es/mcp/error-formatter.mjs +19 -0
  45. package/dist/es/mcp/index.mjs +9 -0
  46. package/dist/es/mcp/init-arg-utils.mjs +38 -0
  47. package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
  48. package/dist/es/mcp/launcher-helper.mjs +52 -0
  49. package/dist/es/mcp/tool-generator.mjs +419 -0
  50. package/dist/es/mcp/types.mjs +3 -0
  51. package/dist/es/node/fs.mjs +44 -0
  52. package/dist/es/node/index.mjs +2 -0
  53. package/dist/es/node/port.mjs +24 -0
  54. package/dist/es/polyfills/async-hooks.mjs +2 -0
  55. package/dist/es/polyfills/index.mjs +1 -0
  56. package/dist/es/types/index.mjs +3 -0
  57. package/dist/es/us-keyboard-layout.mjs +1414 -0
  58. package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
  59. package/dist/es/utils.mjs +72 -0
  60. package/dist/es/zod-schema-utils.mjs +54 -0
  61. package/dist/lib/baseDB.js +149 -0
  62. package/dist/lib/cli/cli-args.js +138 -0
  63. package/dist/lib/cli/cli-error.js +61 -0
  64. package/dist/lib/cli/cli-runner.js +181 -0
  65. package/dist/lib/cli/index.js +53 -0
  66. package/dist/lib/common.js +93 -0
  67. package/dist/lib/constants/example-code.js +264 -0
  68. package/dist/lib/constants/index.js +221 -0
  69. package/dist/lib/env/basic.js +40 -0
  70. package/dist/lib/env/constants.js +153 -0
  71. package/dist/lib/env/global-config-manager.js +128 -0
  72. package/dist/lib/env/helper.js +80 -0
  73. package/dist/lib/env/index.js +90 -0
  74. package/dist/lib/env/init-debug.js +52 -0
  75. package/dist/lib/env/model-config-manager.js +113 -0
  76. package/dist/lib/env/parse-model-config.js +211 -0
  77. package/dist/lib/env/types.js +572 -0
  78. package/dist/lib/env/utils.js +61 -0
  79. package/dist/lib/extractor/constants.js +42 -0
  80. package/dist/lib/extractor/cs_postmessage.js +98 -0
  81. package/dist/lib/extractor/customLocator.js +693 -0
  82. package/dist/lib/extractor/debug.js +12 -0
  83. package/dist/lib/extractor/dom-util.js +157 -0
  84. package/dist/lib/extractor/index.js +87 -0
  85. package/dist/lib/extractor/locator.js +296 -0
  86. package/dist/lib/extractor/tree.js +124 -0
  87. package/dist/lib/extractor/util.js +336 -0
  88. package/dist/lib/extractor/web-extractor.js +442 -0
  89. package/dist/lib/img/box-select.js +875 -0
  90. package/dist/lib/img/canvas-fallback.js +305 -0
  91. package/dist/lib/img/get-photon.js +82 -0
  92. package/dist/lib/img/get-sharp.js +45 -0
  93. package/dist/lib/img/index.js +95 -0
  94. package/dist/lib/img/info.js +92 -0
  95. package/dist/lib/img/transform.js +364 -0
  96. package/dist/lib/index.js +36 -0
  97. package/dist/lib/key-alias-utils.js +62 -0
  98. package/dist/lib/logger.js +114 -0
  99. package/dist/lib/mcp/base-server.js +332 -0
  100. package/dist/lib/mcp/base-tools.js +193 -0
  101. package/dist/lib/mcp/chrome-path.js +72 -0
  102. package/dist/lib/mcp/cli-report-session.js +121 -0
  103. package/dist/lib/mcp/error-formatter.js +53 -0
  104. package/dist/lib/mcp/index.js +114 -0
  105. package/dist/lib/mcp/init-arg-utils.js +78 -0
  106. package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
  107. package/dist/lib/mcp/launcher-helper.js +86 -0
  108. package/dist/lib/mcp/tool-generator.js +456 -0
  109. package/dist/lib/mcp/types.js +40 -0
  110. package/dist/lib/node/fs.js +97 -0
  111. package/dist/lib/node/index.js +65 -0
  112. package/dist/lib/node/port.js +61 -0
  113. package/dist/lib/polyfills/async-hooks.js +36 -0
  114. package/dist/lib/polyfills/index.js +58 -0
  115. package/dist/lib/types/index.js +37 -0
  116. package/dist/lib/us-keyboard-layout.js +1457 -0
  117. package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
  118. package/dist/lib/utils.js +148 -0
  119. package/dist/lib/zod-schema-utils.js +97 -0
  120. package/dist/types/baseDB.d.ts +25 -0
  121. package/dist/types/cli/cli-args.d.ts +8 -0
  122. package/dist/types/cli/cli-error.d.ts +5 -0
  123. package/dist/types/cli/cli-runner.d.ts +19 -0
  124. package/dist/types/cli/index.d.ts +4 -0
  125. package/dist/types/common.d.ts +12 -0
  126. package/dist/types/constants/example-code.d.ts +2 -0
  127. package/dist/types/constants/index.d.ts +61 -0
  128. package/dist/types/env/basic.d.ts +6 -0
  129. package/dist/types/env/constants.d.ts +50 -0
  130. package/dist/types/env/global-config-manager.d.ts +32 -0
  131. package/dist/types/env/helper.d.ts +4 -0
  132. package/dist/types/env/index.d.ts +4 -0
  133. package/dist/types/env/init-debug.d.ts +1 -0
  134. package/dist/types/env/model-config-manager.d.ts +25 -0
  135. package/dist/types/env/parse-model-config.d.ts +31 -0
  136. package/dist/types/env/types.d.ts +339 -0
  137. package/dist/types/env/utils.d.ts +7 -0
  138. package/dist/types/extractor/constants.d.ts +1 -0
  139. package/dist/types/extractor/cs_postmessage.d.ts +2 -0
  140. package/dist/types/extractor/customLocator.d.ts +69 -0
  141. package/dist/types/extractor/debug.d.ts +1 -0
  142. package/dist/types/extractor/dom-util.d.ts +57 -0
  143. package/dist/types/extractor/index.d.ts +33 -0
  144. package/dist/types/extractor/locator.d.ts +9 -0
  145. package/dist/types/extractor/tree.d.ts +6 -0
  146. package/dist/types/extractor/util.d.ts +47 -0
  147. package/dist/types/extractor/web-extractor.d.ts +24 -0
  148. package/dist/types/img/box-select.d.ts +26 -0
  149. package/dist/types/img/canvas-fallback.d.ts +105 -0
  150. package/dist/types/img/get-photon.d.ts +19 -0
  151. package/dist/types/img/get-sharp.d.ts +3 -0
  152. package/dist/types/img/index.d.ts +3 -0
  153. package/dist/types/img/info.d.ts +34 -0
  154. package/dist/types/img/transform.d.ts +98 -0
  155. package/dist/types/index.d.ts +2 -0
  156. package/dist/types/key-alias-utils.d.ts +9 -0
  157. package/dist/types/logger.d.ts +5 -0
  158. package/dist/types/mcp/base-server.d.ts +93 -0
  159. package/dist/types/mcp/base-tools.d.ts +148 -0
  160. package/dist/types/mcp/chrome-path.d.ts +2 -0
  161. package/dist/types/mcp/cli-report-session.d.ts +12 -0
  162. package/dist/types/mcp/error-formatter.d.ts +12 -0
  163. package/dist/types/mcp/index.d.ts +9 -0
  164. package/dist/types/mcp/init-arg-utils.d.ts +13 -0
  165. package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
  166. package/dist/types/mcp/launcher-helper.d.ts +94 -0
  167. package/dist/types/mcp/tool-generator.d.ts +10 -0
  168. package/dist/types/mcp/types.d.ts +113 -0
  169. package/dist/types/node/fs.d.ts +15 -0
  170. package/dist/types/node/index.d.ts +2 -0
  171. package/dist/types/node/port.d.ts +8 -0
  172. package/dist/types/polyfills/async-hooks.d.ts +6 -0
  173. package/dist/types/polyfills/index.d.ts +4 -0
  174. package/dist/types/types/index.d.ts +36 -0
  175. package/dist/types/us-keyboard-layout.d.ts +32 -0
  176. package/dist/types/utils.d.ts +34 -0
  177. package/dist/types/zod-schema-utils.d.ts +23 -0
  178. package/package.json +125 -0
  179. package/src/baseDB.ts +158 -0
  180. package/src/cli/cli-args.ts +173 -0
  181. package/src/cli/cli-error.ts +24 -0
  182. package/src/cli/cli-runner.ts +230 -0
  183. package/src/cli/index.ts +4 -0
  184. package/src/common.ts +67 -0
  185. package/src/constants/example-code.ts +227 -0
  186. package/src/constants/index.ts +139 -0
  187. package/src/env/basic.ts +12 -0
  188. package/src/env/constants.ts +303 -0
  189. package/src/env/global-config-manager.ts +191 -0
  190. package/src/env/helper.ts +58 -0
  191. package/src/env/index.ts +4 -0
  192. package/src/env/init-debug.ts +34 -0
  193. package/src/env/model-config-manager.ts +149 -0
  194. package/src/env/parse-model-config.ts +357 -0
  195. package/src/env/types.ts +583 -0
  196. package/src/env/utils.ts +39 -0
  197. package/src/extractor/constants.ts +5 -0
  198. package/src/extractor/cs_postmessage.ts +136 -0
  199. package/src/extractor/customLocator.ts +1245 -0
  200. package/src/extractor/debug.ts +10 -0
  201. package/src/extractor/dom-util.ts +231 -0
  202. package/src/extractor/index.ts +50 -0
  203. package/src/extractor/locator.ts +469 -0
  204. package/src/extractor/tree.ts +179 -0
  205. package/src/extractor/util.ts +482 -0
  206. package/src/extractor/web-extractor.ts +617 -0
  207. package/src/img/box-select.ts +588 -0
  208. package/src/img/canvas-fallback.ts +393 -0
  209. package/src/img/get-photon.ts +108 -0
  210. package/src/img/get-sharp.ts +18 -0
  211. package/src/img/index.ts +27 -0
  212. package/src/img/info.ts +102 -0
  213. package/src/img/transform.ts +553 -0
  214. package/src/index.ts +1 -0
  215. package/src/key-alias-utils.ts +23 -0
  216. package/src/logger.ts +96 -0
  217. package/src/mcp/base-server.ts +500 -0
  218. package/src/mcp/base-tools.ts +391 -0
  219. package/src/mcp/chrome-path.ts +48 -0
  220. package/src/mcp/cli-report-session.ts +130 -0
  221. package/src/mcp/error-formatter.ts +52 -0
  222. package/src/mcp/index.ts +9 -0
  223. package/src/mcp/init-arg-utils.ts +105 -0
  224. package/src/mcp/inject-report-html-plugin.ts +119 -0
  225. package/src/mcp/launcher-helper.ts +200 -0
  226. package/src/mcp/tool-generator.ts +658 -0
  227. package/src/mcp/types.ts +131 -0
  228. package/src/node/fs.ts +84 -0
  229. package/src/node/index.ts +2 -0
  230. package/src/node/port.ts +37 -0
  231. package/src/polyfills/async-hooks.ts +6 -0
  232. package/src/polyfills/index.ts +4 -0
  233. package/src/types/index.ts +54 -0
  234. package/src/us-keyboard-layout.ts +723 -0
  235. package/src/utils.ts +149 -0
  236. package/src/zod-schema-utils.ts +133 -0
package/src/baseDB.ts ADDED
@@ -0,0 +1,158 @@
1
+ // Generic database operations class
2
+ export class IndexedDBManager {
3
+ private dbPromise: Promise<IDBDatabase>;
4
+ private dbName: string;
5
+ private version: number;
6
+ private storeConfigs: Array<{ name: string; keyPath: string }>;
7
+
8
+ constructor(
9
+ dbName: string,
10
+ version: number,
11
+ storeConfigs: Array<{ name: string; keyPath: string }>,
12
+ ) {
13
+ this.dbName = dbName;
14
+ this.version = version;
15
+ this.storeConfigs = storeConfigs;
16
+ this.dbPromise = this.initDB();
17
+ }
18
+
19
+ private initDB(): Promise<IDBDatabase> {
20
+ return new Promise((resolve, reject) => {
21
+ const request = indexedDB.open(this.dbName, this.version);
22
+
23
+ request.onerror = () => reject(request.error);
24
+ request.onsuccess = () => resolve(request.result);
25
+
26
+ request.onupgradeneeded = (event) => {
27
+ const db = (event.target as IDBOpenDBRequest).result;
28
+
29
+ // Create stores if they don't exist
30
+ this.storeConfigs.forEach(({ name, keyPath }) => {
31
+ if (!db.objectStoreNames.contains(name)) {
32
+ const store = db.createObjectStore(name, { keyPath });
33
+ store.createIndex('timestamp', 'timestamp', { unique: false });
34
+ }
35
+ });
36
+ };
37
+ });
38
+ }
39
+
40
+ private async withTransaction<T>(
41
+ storeNames: string | string[],
42
+ mode: IDBTransactionMode,
43
+ operation: (stores: IDBObjectStore | IDBObjectStore[]) => Promise<T>,
44
+ ): Promise<T> {
45
+ const db = await this.dbPromise;
46
+ const transaction = db.transaction(storeNames, mode);
47
+
48
+ const stores = Array.isArray(storeNames)
49
+ ? storeNames.map((name) => transaction.objectStore(name))
50
+ : transaction.objectStore(storeNames);
51
+
52
+ return operation(stores);
53
+ }
54
+
55
+ private promisifyRequest<T>(request: IDBRequest<T>): Promise<T> {
56
+ return new Promise((resolve, reject) => {
57
+ request.onsuccess = () => resolve(request.result);
58
+ request.onerror = () => reject(request.error);
59
+ });
60
+ }
61
+
62
+ async put<T>(storeName: string, data: T): Promise<void> {
63
+ await this.withTransaction(storeName, 'readwrite', async (store) => {
64
+ await this.promisifyRequest((store as IDBObjectStore).put(data));
65
+ });
66
+ }
67
+
68
+ async get<T>(storeName: string, key: string): Promise<T | undefined> {
69
+ return this.withTransaction(storeName, 'readonly', async (store) => {
70
+ return this.promisifyRequest((store as IDBObjectStore).get(key));
71
+ });
72
+ }
73
+
74
+ async getAll<T>(storeName: string, sortByTimestamp = true): Promise<T[]> {
75
+ return this.withTransaction(storeName, 'readonly', async (store) => {
76
+ const objectStore = store as IDBObjectStore;
77
+ const results = sortByTimestamp
78
+ ? await this.promisifyRequest(objectStore.index('timestamp').getAll())
79
+ : await this.promisifyRequest(objectStore.getAll());
80
+
81
+ return sortByTimestamp
82
+ ? results.sort((a: any, b: any) => a.timestamp - b.timestamp)
83
+ : results;
84
+ });
85
+ }
86
+
87
+ async clear(storeName: string): Promise<void> {
88
+ await this.withTransaction(storeName, 'readwrite', async (store) => {
89
+ await this.promisifyRequest((store as IDBObjectStore).clear());
90
+ });
91
+ }
92
+
93
+ async delete(storeName: string, key: string): Promise<void> {
94
+ await this.withTransaction(storeName, 'readwrite', async (store) => {
95
+ await this.promisifyRequest((store as IDBObjectStore).delete(key));
96
+ });
97
+ }
98
+
99
+ async count(storeName: string): Promise<number> {
100
+ return this.withTransaction(storeName, 'readonly', async (store) => {
101
+ return this.promisifyRequest((store as IDBObjectStore).count());
102
+ });
103
+ }
104
+
105
+ getDBPromise(): Promise<IDBDatabase> {
106
+ return this.dbPromise;
107
+ }
108
+ }
109
+
110
+ // Generic error handler wrapper
111
+ export const withErrorHandling = async <T>(
112
+ operation: () => Promise<T>,
113
+ errorMessage: string,
114
+ defaultValue?: T,
115
+ onQuotaExceeded?: () => Promise<void>,
116
+ ): Promise<T | undefined> => {
117
+ try {
118
+ return await operation();
119
+ } catch (e) {
120
+ console.error(errorMessage, e);
121
+ if (
122
+ e instanceof Error &&
123
+ e.name === 'QuotaExceededError' &&
124
+ onQuotaExceeded
125
+ ) {
126
+ console.log('Storage quota exceeded, running cleanup...');
127
+ await onQuotaExceeded();
128
+ }
129
+ return defaultValue;
130
+ }
131
+ };
132
+
133
+ // Base cleanup function for managing storage space
134
+ export const createCleanupFunction = <
135
+ T extends { id: string; timestamp: number },
136
+ >(
137
+ dbManager: IndexedDBManager,
138
+ storeName: string,
139
+ maxItems: number,
140
+ ) => {
141
+ return async (): Promise<void> => {
142
+ try {
143
+ const results = await dbManager.getAll<T>(storeName);
144
+
145
+ if (results.length > maxItems) {
146
+ const toDelete = results
147
+ .sort((a, b) => a.timestamp - b.timestamp)
148
+ .slice(0, results.length - maxItems);
149
+
150
+ await Promise.all(
151
+ toDelete.map((item) => dbManager.delete(storeName, item.id)),
152
+ );
153
+ }
154
+ } catch (e) {
155
+ console.error(`Failed to cleanup ${storeName}:`, e);
156
+ }
157
+ };
158
+ };
@@ -0,0 +1,173 @@
1
+ import { z } from 'zod';
2
+ import { getKeyAliases } from '../key-alias-utils';
3
+ import type { ToolCliOption, ToolDefinition } from '../mcp/types';
4
+
5
+ export function parseValue(raw: string): unknown {
6
+ if (raw.startsWith('{') || raw.startsWith('[')) {
7
+ try {
8
+ return JSON.parse(raw);
9
+ } catch {
10
+ // Not valid JSON, treat as string below
11
+ }
12
+ }
13
+
14
+ if (/^-?\d+(\.\d+)?$/.test(raw)) {
15
+ return Number(raw);
16
+ }
17
+
18
+ return raw;
19
+ }
20
+
21
+ function walkCliArgs(
22
+ args: string[],
23
+ setArgValue: (key: string, value: unknown) => void,
24
+ ): void {
25
+ for (let i = 0; i < args.length; i++) {
26
+ const arg = args[i];
27
+ if (!arg.startsWith('--')) continue;
28
+
29
+ const body = arg.slice(2);
30
+ const eqIdx = body.indexOf('=');
31
+
32
+ if (eqIdx >= 0) {
33
+ setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
34
+ } else if (args[i + 1] && !args[i + 1].startsWith('--')) {
35
+ i++;
36
+ setArgValue(body, parseValue(args[i]));
37
+ } else {
38
+ setArgValue(body, true);
39
+ }
40
+ }
41
+ }
42
+
43
+ export function parseCliArgs(args: string[]): Record<string, unknown> {
44
+ const result: Record<string, unknown> = {};
45
+
46
+ walkCliArgs(args, (key, value) => {
47
+ result[key] = value;
48
+ });
49
+
50
+ return result;
51
+ }
52
+
53
+ function formatCliOptionName(name: string): string {
54
+ return `--${name}`;
55
+ }
56
+
57
+ export function getCliOptionDisplay(
58
+ key: string,
59
+ cliOption?: ToolCliOption,
60
+ ): { label: string; aliases: string[] } {
61
+ const label = formatCliOptionName(cliOption?.preferredName ?? key);
62
+ const aliases = [...new Set(cliOption?.aliases ?? [])]
63
+ .map((alias) => formatCliOptionName(alias))
64
+ .filter((alias) => alias !== label);
65
+
66
+ return { label, aliases };
67
+ }
68
+
69
+ function getAcceptedCliOptionNames(
70
+ key: string,
71
+ cliOption?: ToolCliOption,
72
+ ): string[] {
73
+ return [
74
+ ...new Set(
75
+ cliOption
76
+ ? [cliOption.preferredName ?? key, ...(cliOption.aliases ?? [])]
77
+ : [key, ...getKeyAliases(key)],
78
+ ),
79
+ ];
80
+ }
81
+
82
+ function toOptionalCliSchemaField(field: unknown): z.ZodTypeAny {
83
+ if (
84
+ typeof field === 'object' &&
85
+ field !== null &&
86
+ typeof (field as z.ZodTypeAny).optional === 'function'
87
+ ) {
88
+ return (field as z.ZodTypeAny).optional();
89
+ }
90
+
91
+ const description =
92
+ typeof field === 'object' &&
93
+ field !== null &&
94
+ 'description' in field &&
95
+ typeof (field as { description?: unknown }).description === 'string'
96
+ ? (field as { description: string }).description
97
+ : undefined;
98
+ return description ? z.any().describe(description) : z.any();
99
+ }
100
+
101
+ function buildCliArgSchema(def: ToolDefinition): Record<string, z.ZodTypeAny> {
102
+ return Object.fromEntries(
103
+ Object.entries(def.schema).flatMap(([key, zodType]) =>
104
+ getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey) => [
105
+ cliKey,
106
+ toOptionalCliSchemaField(zodType),
107
+ ]),
108
+ ),
109
+ );
110
+ }
111
+
112
+ function buildDisallowedCliSpellings(def: ToolDefinition): Set<string> {
113
+ const disallowedSpellings = new Set<string>();
114
+
115
+ for (const [key] of Object.entries(def.schema)) {
116
+ const cliOption = def.cli?.options?.[key];
117
+ const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
118
+ const knownSpellings = new Set<string>([
119
+ key,
120
+ ...getKeyAliases(key),
121
+ ...(cliOption?.preferredName
122
+ ? getKeyAliases(cliOption.preferredName)
123
+ : []),
124
+ ...(cliOption?.aliases ?? []),
125
+ ]);
126
+
127
+ for (const spelling of knownSpellings) {
128
+ if (!acceptedNames.has(spelling)) {
129
+ disallowedSpellings.add(spelling);
130
+ }
131
+ }
132
+ }
133
+
134
+ return disallowedSpellings;
135
+ }
136
+
137
+ export function formatCliValidationError(
138
+ scriptName: string,
139
+ commandName: string,
140
+ def: ToolDefinition,
141
+ rawArgs: Record<string, unknown>,
142
+ ): string | undefined {
143
+ if (Object.keys(def.schema).length === 0) {
144
+ return undefined;
145
+ }
146
+
147
+ const cliSchema = z.object(buildCliArgSchema(def)).strict();
148
+ const parsed = cliSchema.safeParse(rawArgs);
149
+ if (parsed.success) {
150
+ return undefined;
151
+ }
152
+
153
+ const disallowedSpellings = buildDisallowedCliSpellings(def);
154
+ const unknownKeys = parsed.error.issues.flatMap((issue) =>
155
+ issue.code === 'unrecognized_keys' ? issue.keys : [],
156
+ );
157
+
158
+ if (unknownKeys.length > 0) {
159
+ return unknownKeys
160
+ .map((key) => {
161
+ if (disallowedSpellings.has(key)) {
162
+ return `Unsupported option "--${key}" for ${scriptName} ${commandName}.`;
163
+ }
164
+ return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
165
+ })
166
+ .join('\n');
167
+ }
168
+
169
+ const [issue] = parsed.error.issues;
170
+ const optionName =
171
+ typeof issue?.path[0] === 'string' ? `--${issue.path[0]}` : 'CLI arguments';
172
+ return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
173
+ }
@@ -0,0 +1,24 @@
1
+ export class CLIError extends Error {
2
+ constructor(
3
+ message: string,
4
+ public exitCode = 1,
5
+ ) {
6
+ super(message);
7
+ }
8
+ }
9
+
10
+ export function reportCLIError(
11
+ error: unknown,
12
+ log: (
13
+ message?: unknown,
14
+ ...optionalParams: unknown[]
15
+ ) => void = console.error,
16
+ ): number {
17
+ if (error instanceof CLIError) {
18
+ log(error.message);
19
+ return error.exitCode;
20
+ }
21
+
22
+ log(error);
23
+ return 1;
24
+ }
@@ -0,0 +1,230 @@
1
+ import { existsSync, writeFileSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import dotenv from 'dotenv';
5
+ import { getDebug } from '../logger';
6
+ import type { BaseMidsceneTools } from '../mcp/base-tools';
7
+ import type {
8
+ ToolDefinition,
9
+ ToolResult,
10
+ ToolResultContent,
11
+ } from '../mcp/types';
12
+ import {
13
+ formatCliValidationError,
14
+ getCliOptionDisplay,
15
+ parseCliArgs,
16
+ } from './cli-args';
17
+ import { CLIError } from './cli-error';
18
+
19
+ const debug = getDebug('cli-runner');
20
+
21
+ interface CLICommand {
22
+ name: string;
23
+ def: ToolDefinition;
24
+ hidden?: boolean;
25
+ }
26
+
27
+ export interface CLIExtraCommand {
28
+ name: string;
29
+ def: ToolDefinition;
30
+ aliases?: string[];
31
+ hidden?: boolean;
32
+ }
33
+
34
+ export interface CLIRunnerOptions {
35
+ stripPrefix?: string;
36
+ argv?: string[];
37
+ version?: string;
38
+ extraCommands?: CLIExtraCommand[];
39
+ }
40
+
41
+ export { parseCliArgs, parseValue } from './cli-args';
42
+ export { CLIError, reportCLIError } from './cli-error';
43
+
44
+ function outputContentItem(item: ToolResultContent, isError: boolean): void {
45
+ switch (item.type) {
46
+ case 'text':
47
+ if (isError) {
48
+ console.error(item.text);
49
+ } else {
50
+ console.log(item.text);
51
+ }
52
+ break;
53
+
54
+ case 'image': {
55
+ const filename = `screenshot-${Date.now()}.png`;
56
+ const filepath = join(tmpdir(), filename);
57
+ writeFileSync(filepath, Buffer.from(item.data, 'base64'));
58
+ console.log(`Screenshot saved: ${filepath}`);
59
+ break;
60
+ }
61
+
62
+ default:
63
+ console.log(`[${item.type} content not displayed in CLI]`);
64
+ }
65
+ }
66
+
67
+ function outputResult(result: ToolResult): void {
68
+ for (const item of result.content) {
69
+ outputContentItem(item, result.isError ?? false);
70
+ }
71
+ }
72
+
73
+ export function removePrefix(name: string, prefix?: string): string {
74
+ if (prefix && name.startsWith(prefix)) {
75
+ return name.slice(prefix.length);
76
+ }
77
+ return name;
78
+ }
79
+
80
+ function printCommandHelp(scriptName: string, cmd: CLICommand): void {
81
+ const { def } = cmd;
82
+ console.log(`\nUsage: ${scriptName} ${cmd.name} [options]\n`);
83
+ console.log(def.description);
84
+
85
+ const schemaEntries = Object.entries(def.schema);
86
+ if (schemaEntries.length > 0) {
87
+ const optionWidth = Math.max(
88
+ 22,
89
+ ...schemaEntries.map(
90
+ ([key]) =>
91
+ getCliOptionDisplay(key, def.cli?.options?.[key]).label.length,
92
+ ),
93
+ );
94
+ console.log('\nOptions:');
95
+ for (const [key, zodType] of schemaEntries) {
96
+ const { label, aliases } = getCliOptionDisplay(
97
+ key,
98
+ def.cli?.options?.[key],
99
+ );
100
+ const desc = zodType.description ?? '';
101
+ const aliasText =
102
+ aliases.length > 0 ? ` (aliases: ${aliases.join(', ')})` : '';
103
+ console.log(` ${label.padEnd(optionWidth)} ${desc}${aliasText}`);
104
+ }
105
+ }
106
+ }
107
+
108
+ function printVersion(scriptName: string, version: string): void {
109
+ console.log(`${scriptName} v${version}`);
110
+ }
111
+
112
+ function printHelp(
113
+ scriptName: string,
114
+ commands: CLICommand[],
115
+ version?: string,
116
+ ): void {
117
+ if (version) {
118
+ printVersion(scriptName, version);
119
+ console.log('');
120
+ }
121
+ console.log(`\nUsage: ${scriptName} <command> [options]\n`);
122
+ console.log('Commands:');
123
+ for (const { name, def } of commands.filter((command) => !command.hidden)) {
124
+ console.log(` ${name.padEnd(30)} ${def.description}`);
125
+ }
126
+ console.log(` ${'version'.padEnd(30)} Show CLI version`);
127
+ console.log(`\nRun "${scriptName} <command> --help" for more info.`);
128
+ }
129
+
130
+ type AnyMidsceneTools = BaseMidsceneTools<any, any>;
131
+
132
+ export async function runToolsCLI(
133
+ tools: AnyMidsceneTools,
134
+ scriptName: string,
135
+ options?: CLIRunnerOptions,
136
+ ): Promise<void> {
137
+ const rawArgs = options?.argv ?? process.argv.slice(2);
138
+ debug('CLI invoked: %s %s', scriptName, rawArgs.join(' '));
139
+
140
+ // Load .env from cwd before any tool initialization
141
+ const envFile = join(process.cwd(), '.env');
142
+ if (existsSync(envFile)) {
143
+ dotenv.config({ path: envFile });
144
+ }
145
+
146
+ await tools.initTools();
147
+
148
+ const commands: CLICommand[] = tools.getToolDefinitions().map((def) => ({
149
+ name: removePrefix(def.name, options?.stripPrefix).toLowerCase(),
150
+ def,
151
+ }));
152
+ if (options?.extraCommands?.length) {
153
+ commands.push(
154
+ ...options.extraCommands.flatMap((cmd) => [
155
+ {
156
+ name: cmd.name.toLowerCase(),
157
+ def: cmd.def,
158
+ hidden: cmd.hidden,
159
+ },
160
+ ...(cmd.aliases ?? []).map((alias) => ({
161
+ name: alias.toLowerCase(),
162
+ def: cmd.def,
163
+ hidden: true,
164
+ })),
165
+ ]),
166
+ );
167
+ }
168
+ const cliVersion = options?.version;
169
+
170
+ const [commandName, ...restArgs] = rawArgs;
171
+
172
+ if (!commandName || commandName === '--help' || commandName === '-h') {
173
+ debug('showing help (no command or --help flag)');
174
+ printHelp(scriptName, commands, cliVersion);
175
+ return;
176
+ }
177
+
178
+ if (
179
+ commandName === '--version' ||
180
+ commandName === '-v' ||
181
+ commandName.toLowerCase() === 'version'
182
+ ) {
183
+ if (!cliVersion) {
184
+ throw new CLIError('Failed to determine CLI version');
185
+ }
186
+ printVersion(scriptName, cliVersion);
187
+ return;
188
+ }
189
+
190
+ const match = commands.find(
191
+ (c) => c.name.toLowerCase() === commandName.toLowerCase(),
192
+ );
193
+ if (!match) {
194
+ debug('unknown command: %s', commandName);
195
+ console.error(`Unknown command: ${commandName}`);
196
+ printHelp(scriptName, commands, cliVersion);
197
+ throw new CLIError(`Unknown command: ${commandName}`);
198
+ }
199
+
200
+ const parsedArgs = parseCliArgs(restArgs);
201
+ if (parsedArgs.help === true) {
202
+ debug('showing command help for: %s', match.name);
203
+ printCommandHelp(scriptName, match);
204
+ return;
205
+ }
206
+
207
+ const cliValidationError = formatCliValidationError(
208
+ scriptName,
209
+ match.name,
210
+ match.def,
211
+ parsedArgs,
212
+ );
213
+ if (cliValidationError) {
214
+ throw new CLIError(cliValidationError);
215
+ }
216
+
217
+ debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
218
+
219
+ const result = await match.def.handler(parsedArgs);
220
+ debug(
221
+ 'command %s completed, isError: %s',
222
+ match.name,
223
+ result.isError ?? false,
224
+ );
225
+ outputResult(result);
226
+ await tools.destroy();
227
+ if (result.isError) {
228
+ throw new CLIError('Command failed', 1);
229
+ }
230
+ }
@@ -0,0 +1,4 @@
1
+ export { CLIError, reportCLIError } from './cli-error';
2
+ export { parseCliArgs, parseValue } from './cli-args';
3
+ export { runToolsCLI, removePrefix } from './cli-runner';
4
+ export type { CLIRunnerOptions, CLIExtraCommand } from './cli-runner';
package/src/common.ts ADDED
@@ -0,0 +1,67 @@
1
+ import { existsSync, mkdirSync } from 'node:fs';
2
+ import { tmpdir } from 'node:os';
3
+ import path from 'node:path';
4
+ // do not import getBasicEnvValue and MIDSCENE_RUN_DIR directly from ./env,
5
+ // because it will cause circular dependency
6
+ import { getBasicEnvValue } from './env/basic';
7
+ import { MIDSCENE_RUN_DIR } from './env/types';
8
+ import { ifInNode } from './utils';
9
+
10
+ export const defaultRunDirName = 'midscene_run';
11
+ // Define locally for now to avoid import issues
12
+
13
+ export const getMidsceneRunDir = () => {
14
+ if (!ifInNode) {
15
+ return '';
16
+ }
17
+
18
+ return getBasicEnvValue(MIDSCENE_RUN_DIR) || defaultRunDirName;
19
+ };
20
+
21
+ export const getMidsceneRunBaseDir = () => {
22
+ if (!ifInNode) {
23
+ return '';
24
+ }
25
+
26
+ let basePath = path.resolve(process.cwd(), getMidsceneRunDir());
27
+
28
+ // Create a base directory
29
+ if (!existsSync(basePath)) {
30
+ try {
31
+ mkdirSync(basePath, { recursive: true });
32
+ } catch (error) {
33
+ // console.error(`Failed to create ${runDirName} directory: ${error}`);
34
+ basePath = path.join(tmpdir(), defaultRunDirName);
35
+ mkdirSync(basePath, { recursive: true });
36
+ }
37
+ }
38
+
39
+ return basePath;
40
+ };
41
+
42
+ /**
43
+ * Get the path to the midscene_run directory or a subdirectory within it.
44
+ * Creates the directory if it doesn't exist.
45
+ *
46
+ * @param subdir - Optional subdirectory name (e.g., 'log', 'report')
47
+ * @returns The absolute path to the requested directory
48
+ */
49
+ export const getMidsceneRunSubDir = (
50
+ subdir: 'dump' | 'cache' | 'report' | 'tmp' | 'log' | 'output',
51
+ ): string => {
52
+ if (!ifInNode) {
53
+ return '';
54
+ }
55
+
56
+ // Create a log directory
57
+ const basePath = getMidsceneRunBaseDir();
58
+ const logPath = path.join(basePath, subdir);
59
+ if (!existsSync(logPath)) {
60
+ mkdirSync(logPath, { recursive: true });
61
+ }
62
+
63
+ return logPath;
64
+ };
65
+
66
+ export const ERROR_CODE_NOT_IMPLEMENTED_AS_DESIGNED =
67
+ 'NOT_IMPLEMENTED_AS_DESIGNED';