@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.
- package/README.md +9 -0
- package/dist/es/baseDB.mjs +109 -0
- package/dist/es/cli/cli-args.mjs +95 -0
- package/dist/es/cli/cli-error.mjs +24 -0
- package/dist/es/cli/cli-runner.mjs +122 -0
- package/dist/es/cli/index.mjs +4 -0
- package/dist/es/common.mjs +37 -0
- package/dist/es/constants/example-code.mjs +227 -0
- package/dist/es/constants/index.mjs +124 -0
- package/dist/es/env/basic.mjs +6 -0
- package/dist/es/env/constants.mjs +110 -0
- package/dist/es/env/global-config-manager.mjs +94 -0
- package/dist/es/env/helper.mjs +43 -0
- package/dist/es/env/index.mjs +5 -0
- package/dist/es/env/init-debug.mjs +18 -0
- package/dist/es/env/model-config-manager.mjs +79 -0
- package/dist/es/env/parse-model-config.mjs +165 -0
- package/dist/es/env/types.mjs +232 -0
- package/dist/es/env/utils.mjs +18 -0
- package/dist/es/extractor/constants.mjs +2 -0
- package/dist/es/extractor/cs_postmessage.mjs +61 -0
- package/dist/es/extractor/customLocator.mjs +641 -0
- package/dist/es/extractor/debug.mjs +6 -0
- package/dist/es/extractor/dom-util.mjs +96 -0
- package/dist/es/extractor/index.mjs +5 -0
- package/dist/es/extractor/locator.mjs +250 -0
- package/dist/es/extractor/tree.mjs +78 -0
- package/dist/es/extractor/util.mjs +245 -0
- package/dist/es/extractor/web-extractor.mjs +393 -0
- package/dist/es/img/box-select.mjs +824 -0
- package/dist/es/img/canvas-fallback.mjs +238 -0
- package/dist/es/img/get-photon.mjs +45 -0
- package/dist/es/img/get-sharp.mjs +11 -0
- package/dist/es/img/index.mjs +4 -0
- package/dist/es/img/info.mjs +35 -0
- package/dist/es/img/transform.mjs +275 -0
- package/dist/es/index.mjs +2 -0
- package/dist/es/key-alias-utils.mjs +19 -0
- package/dist/es/logger.mjs +64 -0
- package/dist/es/mcp/base-server.mjs +282 -0
- package/dist/es/mcp/base-tools.mjs +159 -0
- package/dist/es/mcp/chrome-path.mjs +35 -0
- package/dist/es/mcp/cli-report-session.mjs +78 -0
- package/dist/es/mcp/error-formatter.mjs +19 -0
- package/dist/es/mcp/index.mjs +9 -0
- package/dist/es/mcp/init-arg-utils.mjs +38 -0
- package/dist/es/mcp/inject-report-html-plugin.mjs +53 -0
- package/dist/es/mcp/launcher-helper.mjs +52 -0
- package/dist/es/mcp/tool-generator.mjs +419 -0
- package/dist/es/mcp/types.mjs +3 -0
- package/dist/es/node/fs.mjs +44 -0
- package/dist/es/node/index.mjs +2 -0
- package/dist/es/node/port.mjs +24 -0
- package/dist/es/polyfills/async-hooks.mjs +2 -0
- package/dist/es/polyfills/index.mjs +1 -0
- package/dist/es/types/index.mjs +3 -0
- package/dist/es/us-keyboard-layout.mjs +1414 -0
- package/dist/es/us-keyboard-layout.mjs.LICENSE.txt +5 -0
- package/dist/es/utils.mjs +72 -0
- package/dist/es/zod-schema-utils.mjs +54 -0
- package/dist/lib/baseDB.js +149 -0
- package/dist/lib/cli/cli-args.js +138 -0
- package/dist/lib/cli/cli-error.js +61 -0
- package/dist/lib/cli/cli-runner.js +181 -0
- package/dist/lib/cli/index.js +53 -0
- package/dist/lib/common.js +93 -0
- package/dist/lib/constants/example-code.js +264 -0
- package/dist/lib/constants/index.js +221 -0
- package/dist/lib/env/basic.js +40 -0
- package/dist/lib/env/constants.js +153 -0
- package/dist/lib/env/global-config-manager.js +128 -0
- package/dist/lib/env/helper.js +80 -0
- package/dist/lib/env/index.js +90 -0
- package/dist/lib/env/init-debug.js +52 -0
- package/dist/lib/env/model-config-manager.js +113 -0
- package/dist/lib/env/parse-model-config.js +211 -0
- package/dist/lib/env/types.js +572 -0
- package/dist/lib/env/utils.js +61 -0
- package/dist/lib/extractor/constants.js +42 -0
- package/dist/lib/extractor/cs_postmessage.js +98 -0
- package/dist/lib/extractor/customLocator.js +693 -0
- package/dist/lib/extractor/debug.js +12 -0
- package/dist/lib/extractor/dom-util.js +157 -0
- package/dist/lib/extractor/index.js +87 -0
- package/dist/lib/extractor/locator.js +296 -0
- package/dist/lib/extractor/tree.js +124 -0
- package/dist/lib/extractor/util.js +336 -0
- package/dist/lib/extractor/web-extractor.js +442 -0
- package/dist/lib/img/box-select.js +875 -0
- package/dist/lib/img/canvas-fallback.js +305 -0
- package/dist/lib/img/get-photon.js +82 -0
- package/dist/lib/img/get-sharp.js +45 -0
- package/dist/lib/img/index.js +95 -0
- package/dist/lib/img/info.js +92 -0
- package/dist/lib/img/transform.js +364 -0
- package/dist/lib/index.js +36 -0
- package/dist/lib/key-alias-utils.js +62 -0
- package/dist/lib/logger.js +114 -0
- package/dist/lib/mcp/base-server.js +332 -0
- package/dist/lib/mcp/base-tools.js +193 -0
- package/dist/lib/mcp/chrome-path.js +72 -0
- package/dist/lib/mcp/cli-report-session.js +121 -0
- package/dist/lib/mcp/error-formatter.js +53 -0
- package/dist/lib/mcp/index.js +114 -0
- package/dist/lib/mcp/init-arg-utils.js +78 -0
- package/dist/lib/mcp/inject-report-html-plugin.js +98 -0
- package/dist/lib/mcp/launcher-helper.js +86 -0
- package/dist/lib/mcp/tool-generator.js +456 -0
- package/dist/lib/mcp/types.js +40 -0
- package/dist/lib/node/fs.js +97 -0
- package/dist/lib/node/index.js +65 -0
- package/dist/lib/node/port.js +61 -0
- package/dist/lib/polyfills/async-hooks.js +36 -0
- package/dist/lib/polyfills/index.js +58 -0
- package/dist/lib/types/index.js +37 -0
- package/dist/lib/us-keyboard-layout.js +1457 -0
- package/dist/lib/us-keyboard-layout.js.LICENSE.txt +5 -0
- package/dist/lib/utils.js +148 -0
- package/dist/lib/zod-schema-utils.js +97 -0
- package/dist/types/baseDB.d.ts +25 -0
- package/dist/types/cli/cli-args.d.ts +8 -0
- package/dist/types/cli/cli-error.d.ts +5 -0
- package/dist/types/cli/cli-runner.d.ts +19 -0
- package/dist/types/cli/index.d.ts +4 -0
- package/dist/types/common.d.ts +12 -0
- package/dist/types/constants/example-code.d.ts +2 -0
- package/dist/types/constants/index.d.ts +61 -0
- package/dist/types/env/basic.d.ts +6 -0
- package/dist/types/env/constants.d.ts +50 -0
- package/dist/types/env/global-config-manager.d.ts +32 -0
- package/dist/types/env/helper.d.ts +4 -0
- package/dist/types/env/index.d.ts +4 -0
- package/dist/types/env/init-debug.d.ts +1 -0
- package/dist/types/env/model-config-manager.d.ts +25 -0
- package/dist/types/env/parse-model-config.d.ts +31 -0
- package/dist/types/env/types.d.ts +339 -0
- package/dist/types/env/utils.d.ts +7 -0
- package/dist/types/extractor/constants.d.ts +1 -0
- package/dist/types/extractor/cs_postmessage.d.ts +2 -0
- package/dist/types/extractor/customLocator.d.ts +69 -0
- package/dist/types/extractor/debug.d.ts +1 -0
- package/dist/types/extractor/dom-util.d.ts +57 -0
- package/dist/types/extractor/index.d.ts +33 -0
- package/dist/types/extractor/locator.d.ts +9 -0
- package/dist/types/extractor/tree.d.ts +6 -0
- package/dist/types/extractor/util.d.ts +47 -0
- package/dist/types/extractor/web-extractor.d.ts +24 -0
- package/dist/types/img/box-select.d.ts +26 -0
- package/dist/types/img/canvas-fallback.d.ts +105 -0
- package/dist/types/img/get-photon.d.ts +19 -0
- package/dist/types/img/get-sharp.d.ts +3 -0
- package/dist/types/img/index.d.ts +3 -0
- package/dist/types/img/info.d.ts +34 -0
- package/dist/types/img/transform.d.ts +98 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/key-alias-utils.d.ts +9 -0
- package/dist/types/logger.d.ts +5 -0
- package/dist/types/mcp/base-server.d.ts +93 -0
- package/dist/types/mcp/base-tools.d.ts +148 -0
- package/dist/types/mcp/chrome-path.d.ts +2 -0
- package/dist/types/mcp/cli-report-session.d.ts +12 -0
- package/dist/types/mcp/error-formatter.d.ts +12 -0
- package/dist/types/mcp/index.d.ts +9 -0
- package/dist/types/mcp/init-arg-utils.d.ts +13 -0
- package/dist/types/mcp/inject-report-html-plugin.d.ts +18 -0
- package/dist/types/mcp/launcher-helper.d.ts +94 -0
- package/dist/types/mcp/tool-generator.d.ts +10 -0
- package/dist/types/mcp/types.d.ts +113 -0
- package/dist/types/node/fs.d.ts +15 -0
- package/dist/types/node/index.d.ts +2 -0
- package/dist/types/node/port.d.ts +8 -0
- package/dist/types/polyfills/async-hooks.d.ts +6 -0
- package/dist/types/polyfills/index.d.ts +4 -0
- package/dist/types/types/index.d.ts +36 -0
- package/dist/types/us-keyboard-layout.d.ts +32 -0
- package/dist/types/utils.d.ts +34 -0
- package/dist/types/zod-schema-utils.d.ts +23 -0
- package/package.json +125 -0
- package/src/baseDB.ts +158 -0
- package/src/cli/cli-args.ts +173 -0
- package/src/cli/cli-error.ts +24 -0
- package/src/cli/cli-runner.ts +230 -0
- package/src/cli/index.ts +4 -0
- package/src/common.ts +67 -0
- package/src/constants/example-code.ts +227 -0
- package/src/constants/index.ts +139 -0
- package/src/env/basic.ts +12 -0
- package/src/env/constants.ts +303 -0
- package/src/env/global-config-manager.ts +191 -0
- package/src/env/helper.ts +58 -0
- package/src/env/index.ts +4 -0
- package/src/env/init-debug.ts +34 -0
- package/src/env/model-config-manager.ts +149 -0
- package/src/env/parse-model-config.ts +357 -0
- package/src/env/types.ts +583 -0
- package/src/env/utils.ts +39 -0
- package/src/extractor/constants.ts +5 -0
- package/src/extractor/cs_postmessage.ts +136 -0
- package/src/extractor/customLocator.ts +1245 -0
- package/src/extractor/debug.ts +10 -0
- package/src/extractor/dom-util.ts +231 -0
- package/src/extractor/index.ts +50 -0
- package/src/extractor/locator.ts +469 -0
- package/src/extractor/tree.ts +179 -0
- package/src/extractor/util.ts +482 -0
- package/src/extractor/web-extractor.ts +617 -0
- package/src/img/box-select.ts +588 -0
- package/src/img/canvas-fallback.ts +393 -0
- package/src/img/get-photon.ts +108 -0
- package/src/img/get-sharp.ts +18 -0
- package/src/img/index.ts +27 -0
- package/src/img/info.ts +102 -0
- package/src/img/transform.ts +553 -0
- package/src/index.ts +1 -0
- package/src/key-alias-utils.ts +23 -0
- package/src/logger.ts +96 -0
- package/src/mcp/base-server.ts +500 -0
- package/src/mcp/base-tools.ts +391 -0
- package/src/mcp/chrome-path.ts +48 -0
- package/src/mcp/cli-report-session.ts +130 -0
- package/src/mcp/error-formatter.ts +52 -0
- package/src/mcp/index.ts +9 -0
- package/src/mcp/init-arg-utils.ts +105 -0
- package/src/mcp/inject-report-html-plugin.ts +119 -0
- package/src/mcp/launcher-helper.ts +200 -0
- package/src/mcp/tool-generator.ts +658 -0
- package/src/mcp/types.ts +131 -0
- package/src/node/fs.ts +84 -0
- package/src/node/index.ts +2 -0
- package/src/node/port.ts +37 -0
- package/src/polyfills/async-hooks.ts +6 -0
- package/src/polyfills/index.ts +4 -0
- package/src/types/index.ts +54 -0
- package/src/us-keyboard-layout.ts +723 -0
- package/src/utils.ts +149 -0
- 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
|
+
}
|
package/src/cli/index.ts
ADDED
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';
|