@promptbook/cli 0.100.0-1 → 0.100.0-12
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/esm/index.es.js +145 -216
- package/esm/index.es.js.map +1 -1
- package/esm/typings/src/config.d.ts +0 -10
- package/esm/typings/src/version.d.ts +1 -1
- package/esm/typings/src/wizard/wizard.d.ts +14 -4
- package/package.json +1 -1
- package/umd/index.umd.js +145 -216
- package/umd/index.umd.js.map +1 -1
- package/esm/typings/src/remote-server/connection-improvements.test.d.ts +0 -1
- package/esm/typings/src/remote-server/utils/connectionProgress.d.ts +0 -72
package/esm/index.es.js
CHANGED
|
@@ -47,7 +47,7 @@ const BOOK_LANGUAGE_VERSION = '1.0.0';
|
|
|
47
47
|
* @generated
|
|
48
48
|
* @see https://github.com/webgptorg/promptbook
|
|
49
49
|
*/
|
|
50
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.100.0-
|
|
50
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.100.0-12';
|
|
51
51
|
/**
|
|
52
52
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
53
53
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -221,12 +221,9 @@ const SMALL_NUMBER = 0.001;
|
|
|
221
221
|
/**
|
|
222
222
|
* Timeout for the connections in milliseconds
|
|
223
223
|
*
|
|
224
|
-
* Note: Increased from 7 seconds to 30 seconds to accommodate OAuth flows
|
|
225
|
-
* like Facebook login which may require user interaction and redirects
|
|
226
|
-
*
|
|
227
224
|
* @private within the repository - too low-level in comparison with other `MAX_...`
|
|
228
225
|
*/
|
|
229
|
-
const CONNECTION_TIMEOUT_MS =
|
|
226
|
+
const CONNECTION_TIMEOUT_MS = 7 * 1000;
|
|
230
227
|
// <- TODO: [⏳] Standardize timeouts, Make DEFAULT_TIMEOUT_MS as global constant
|
|
231
228
|
/**
|
|
232
229
|
* How many times to retry the connections
|
|
@@ -2201,8 +2198,27 @@ class FileCacheStorage {
|
|
|
2201
2198
|
throw new UnexpectedError(`The "${key}" you want to store in JSON file is not serializable as JSON`);
|
|
2202
2199
|
}
|
|
2203
2200
|
const fileContent = stringifyPipelineJson(value);
|
|
2204
|
-
|
|
2205
|
-
|
|
2201
|
+
// Note: Try to create cache directory and write file, but don't fail if filesystem is read-only or has permission issues
|
|
2202
|
+
try {
|
|
2203
|
+
await mkdir(dirname(filename), { recursive: true }); // <- [0]
|
|
2204
|
+
await writeFile(filename, fileContent, 'utf-8');
|
|
2205
|
+
}
|
|
2206
|
+
catch (error) {
|
|
2207
|
+
// Note: If we can't write to cache, silently ignore the error
|
|
2208
|
+
// This handles read-only filesystems, permission issues, and missing parent directories
|
|
2209
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
2210
|
+
error.message.includes('read-only') ||
|
|
2211
|
+
error.message.includes('EACCES') ||
|
|
2212
|
+
error.message.includes('EPERM') ||
|
|
2213
|
+
error.message.includes('ENOENT'))) {
|
|
2214
|
+
// Silently ignore filesystem errors - caching is optional
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
else {
|
|
2218
|
+
// Re-throw other unexpected errors
|
|
2219
|
+
throw error;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2206
2222
|
}
|
|
2207
2223
|
/**
|
|
2208
2224
|
* Removes the key/value pair with the given key from the storage, if a key/value pair with the given key exists.
|
|
@@ -2563,39 +2579,13 @@ async function createRemoteClient(options) {
|
|
|
2563
2579
|
transports: ['polling', 'websocket' /*, <- TODO: [🌬] Allow to pass `transports`, add 'webtransport' */],
|
|
2564
2580
|
});
|
|
2565
2581
|
// console.log('Connecting to', this.options.remoteServerUrl.href, { socket });
|
|
2566
|
-
let isResolved = false;
|
|
2567
2582
|
socket.on('connect', () => {
|
|
2568
|
-
|
|
2569
|
-
isResolved = true;
|
|
2570
|
-
resolve(socket);
|
|
2571
|
-
}
|
|
2572
|
-
});
|
|
2573
|
-
socket.on('connect_error', (error) => {
|
|
2574
|
-
if (!isResolved) {
|
|
2575
|
-
isResolved = true;
|
|
2576
|
-
reject(new Error(`Failed to connect to ${remoteServerUrl}: ${error.message || error}`));
|
|
2577
|
-
}
|
|
2578
|
-
});
|
|
2579
|
-
socket.on('disconnect', (reason) => {
|
|
2580
|
-
if (!isResolved) {
|
|
2581
|
-
isResolved = true;
|
|
2582
|
-
reject(new Error(`Connection to ${remoteServerUrl} was disconnected: ${reason}`));
|
|
2583
|
-
}
|
|
2583
|
+
resolve(socket);
|
|
2584
2584
|
});
|
|
2585
|
-
// Better timeout handling
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
isResolved = true;
|
|
2589
|
-
socket.disconnect();
|
|
2590
|
-
reject(new Error(`Connection timeout after ${CONNECTION_TIMEOUT_MS / 1000} seconds while connecting to ${remoteServerUrl}. ` +
|
|
2591
|
-
`This may indicate network issues or the server may be experiencing high load. ` +
|
|
2592
|
-
`For authentication flows like social login, ensure sufficient time is allowed for user interaction.`));
|
|
2593
|
-
}
|
|
2585
|
+
// TODO: [💩] Better timeout handling
|
|
2586
|
+
setTimeout(() => {
|
|
2587
|
+
reject(new Error(`Timeout while connecting to ${remoteServerUrl}`));
|
|
2594
2588
|
}, CONNECTION_TIMEOUT_MS);
|
|
2595
|
-
// Clean up timeout if connection succeeds
|
|
2596
|
-
socket.on('connect', () => {
|
|
2597
|
-
clearTimeout(timeoutId);
|
|
2598
|
-
});
|
|
2599
2589
|
});
|
|
2600
2590
|
}
|
|
2601
2591
|
|
|
@@ -2624,33 +2614,8 @@ class RemoteLlmExecutionTools {
|
|
|
2624
2614
|
* Check the configuration of all execution tools
|
|
2625
2615
|
*/
|
|
2626
2616
|
async checkConfiguration() {
|
|
2627
|
-
|
|
2628
|
-
|
|
2629
|
-
socket.disconnect();
|
|
2630
|
-
}
|
|
2631
|
-
catch (error) {
|
|
2632
|
-
if (error instanceof Error) {
|
|
2633
|
-
// Provide user-friendly error messages for common connection issues
|
|
2634
|
-
if (error.message.includes('timeout') || error.message.includes('Timeout')) {
|
|
2635
|
-
throw new Error(`Connection to Promptbook server timed out. This may happen during authentication flows like Facebook login. ` +
|
|
2636
|
-
`Please ensure: 1) Server is running at ${this.options.remoteServerUrl}, ` +
|
|
2637
|
-
`2) Network connection is stable, 3) Authentication process is completed within the timeout period. ` +
|
|
2638
|
-
`Original error: ${error.message}`);
|
|
2639
|
-
}
|
|
2640
|
-
if (error.message.includes('connect') || error.message.includes('ECONNREFUSED')) {
|
|
2641
|
-
throw new Error(`Cannot connect to Promptbook server at ${this.options.remoteServerUrl}. ` +
|
|
2642
|
-
`Please check if the server is running and accessible. ` +
|
|
2643
|
-
`Original error: ${error.message}`);
|
|
2644
|
-
}
|
|
2645
|
-
if (error.message.includes('authentication') || error.message.includes('auth')) {
|
|
2646
|
-
throw new Error(`Authentication failed when connecting to Promptbook server. ` +
|
|
2647
|
-
`This may happen if social login (like Facebook) was not completed properly. ` +
|
|
2648
|
-
`Please retry the authentication process. ` +
|
|
2649
|
-
`Original error: ${error.message}`);
|
|
2650
|
-
}
|
|
2651
|
-
}
|
|
2652
|
-
throw error; // Re-throw if not a recognized error pattern
|
|
2653
|
-
}
|
|
2617
|
+
const socket = await createRemoteClient(this.options);
|
|
2618
|
+
socket.disconnect();
|
|
2654
2619
|
// TODO: [main] !!3 Check version of the remote server and compatibility
|
|
2655
2620
|
// TODO: [🎍] Send checkConfiguration
|
|
2656
2621
|
}
|
|
@@ -7760,12 +7725,58 @@ async function makeKnowledgeSourceHandler(knowledgeSource, tools, options) {
|
|
|
7760
7725
|
// <- TODO: [🥬] Encapsulate sha256 to some private utility function
|
|
7761
7726
|
const rootDirname = join(process.cwd(), DEFAULT_DOWNLOAD_CACHE_DIRNAME);
|
|
7762
7727
|
const filepath = join(...nameToSubfolderPath(hash /* <- TODO: [🎎] Maybe add some SHA256 prefix */), `${basename.substring(0, MAX_FILENAME_LENGTH)}.${mimeTypeToExtension(mimeType)}`);
|
|
7763
|
-
|
|
7728
|
+
// Note: Try to create cache directory, but don't fail if filesystem has issues
|
|
7729
|
+
try {
|
|
7730
|
+
await tools.fs.mkdir(dirname(join(rootDirname, filepath)), { recursive: true });
|
|
7731
|
+
}
|
|
7732
|
+
catch (error) {
|
|
7733
|
+
// Note: If we can't create cache directory, we'll handle it when trying to write the file
|
|
7734
|
+
// This handles read-only filesystems, permission issues, and missing parent directories
|
|
7735
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
7736
|
+
error.message.includes('read-only') ||
|
|
7737
|
+
error.message.includes('EACCES') ||
|
|
7738
|
+
error.message.includes('EPERM') ||
|
|
7739
|
+
error.message.includes('ENOENT'))) ;
|
|
7740
|
+
else {
|
|
7741
|
+
// Re-throw other unexpected errors
|
|
7742
|
+
throw error;
|
|
7743
|
+
}
|
|
7744
|
+
}
|
|
7764
7745
|
const fileContent = Buffer.from(await response.arrayBuffer());
|
|
7765
7746
|
if (fileContent.length > DEFAULT_MAX_FILE_SIZE /* <- TODO: Allow to pass different value to remote server */) {
|
|
7766
7747
|
throw new LimitReachedError(`File is too large (${Math.round(fileContent.length / 1024 / 1024)}MB). Maximum allowed size is ${Math.round(DEFAULT_MAX_FILE_SIZE / 1024 / 1024)}MB.`);
|
|
7767
7748
|
}
|
|
7768
|
-
|
|
7749
|
+
// Note: Try to cache the downloaded file, but don't fail if the filesystem is read-only
|
|
7750
|
+
try {
|
|
7751
|
+
await tools.fs.writeFile(join(rootDirname, filepath), fileContent);
|
|
7752
|
+
}
|
|
7753
|
+
catch (error) {
|
|
7754
|
+
// Note: If we can't write to cache, we'll process the file directly from memory
|
|
7755
|
+
// This handles read-only filesystems like Vercel
|
|
7756
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
7757
|
+
error.message.includes('read-only') ||
|
|
7758
|
+
error.message.includes('EACCES') ||
|
|
7759
|
+
error.message.includes('EPERM') ||
|
|
7760
|
+
error.message.includes('ENOENT'))) {
|
|
7761
|
+
// Return a handler that works directly with the downloaded content
|
|
7762
|
+
return {
|
|
7763
|
+
source: name,
|
|
7764
|
+
filename: null,
|
|
7765
|
+
url,
|
|
7766
|
+
mimeType,
|
|
7767
|
+
async asJson() {
|
|
7768
|
+
return JSON.parse(fileContent.toString('utf-8'));
|
|
7769
|
+
},
|
|
7770
|
+
async asText() {
|
|
7771
|
+
return fileContent.toString('utf-8');
|
|
7772
|
+
},
|
|
7773
|
+
};
|
|
7774
|
+
}
|
|
7775
|
+
else {
|
|
7776
|
+
// Re-throw other unexpected errors
|
|
7777
|
+
throw error;
|
|
7778
|
+
}
|
|
7779
|
+
}
|
|
7769
7780
|
// TODO: [💵] Check the file security
|
|
7770
7781
|
// TODO: [🧹][🧠] Delete the file after the scraping is done
|
|
7771
7782
|
return makeKnowledgeSourceHandler({ name, knowledgeSourceContent: filepath }, tools, {
|
|
@@ -13382,7 +13393,23 @@ async function $getCompiledBook(tools, pipelineSource, options) {
|
|
|
13382
13393
|
...options,
|
|
13383
13394
|
});
|
|
13384
13395
|
const compiledFilePath = filePath.replace('.book.md', '.book').replace('.book', '.bookc');
|
|
13385
|
-
|
|
13396
|
+
// Note: Try to save the compiled book to disk for caching, but don't fail if the filesystem is read-only
|
|
13397
|
+
try {
|
|
13398
|
+
await saveArchive(compiledFilePath, [pipelineJson], fs);
|
|
13399
|
+
}
|
|
13400
|
+
catch (error) {
|
|
13401
|
+
// Note: Ignore filesystem errors (like EROFS on read-only systems like Vercel)
|
|
13402
|
+
// The compiled book can still be used even if it can't be cached
|
|
13403
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
13404
|
+
error.message.includes('read-only') ||
|
|
13405
|
+
error.message.includes('EACCES') ||
|
|
13406
|
+
error.message.includes('EPERM') ||
|
|
13407
|
+
error.message.includes('ENOENT'))) ;
|
|
13408
|
+
else {
|
|
13409
|
+
// Re-throw other unexpected errors
|
|
13410
|
+
throw error;
|
|
13411
|
+
}
|
|
13412
|
+
}
|
|
13386
13413
|
return pipelineJson;
|
|
13387
13414
|
}
|
|
13388
13415
|
}
|
|
@@ -14683,152 +14710,6 @@ function startRemoteServer(options) {
|
|
|
14683
14710
|
response.status(400).send({ error: serializeError(error) });
|
|
14684
14711
|
}
|
|
14685
14712
|
});
|
|
14686
|
-
// OAuth Authentication Endpoints
|
|
14687
|
-
// These endpoints provide social authentication support (Facebook, Google, etc.)
|
|
14688
|
-
app.get('/auth/:provider', async (request, response) => {
|
|
14689
|
-
const { provider } = request.params;
|
|
14690
|
-
if (!isApplicationModeAllowed) {
|
|
14691
|
-
response.status(400).json({
|
|
14692
|
-
error: 'Application mode is not allowed',
|
|
14693
|
-
message: 'Social authentication requires application mode to be enabled'
|
|
14694
|
-
});
|
|
14695
|
-
return;
|
|
14696
|
-
}
|
|
14697
|
-
try {
|
|
14698
|
-
// Get OAuth configuration from query params or environment
|
|
14699
|
-
const { redirectUri, clientId, appId } = request.query;
|
|
14700
|
-
if (!redirectUri || !clientId) {
|
|
14701
|
-
response.status(400).json({
|
|
14702
|
-
error: 'Missing OAuth parameters',
|
|
14703
|
-
message: 'redirectUri and clientId are required for OAuth flow'
|
|
14704
|
-
});
|
|
14705
|
-
return;
|
|
14706
|
-
}
|
|
14707
|
-
let authUrl;
|
|
14708
|
-
const state = Buffer.from(JSON.stringify({
|
|
14709
|
-
appId: appId || 'default',
|
|
14710
|
-
timestamp: Date.now()
|
|
14711
|
-
})).toString('base64');
|
|
14712
|
-
switch (provider.toLowerCase()) {
|
|
14713
|
-
case 'facebook':
|
|
14714
|
-
authUrl = `https://www.facebook.com/v18.0/dialog/oauth?` +
|
|
14715
|
-
`client_id=${encodeURIComponent(clientId)}&` +
|
|
14716
|
-
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
|
|
14717
|
-
`scope=email,public_profile&` +
|
|
14718
|
-
`response_type=code&` +
|
|
14719
|
-
`state=${encodeURIComponent(state)}`;
|
|
14720
|
-
break;
|
|
14721
|
-
case 'google':
|
|
14722
|
-
authUrl = `https://accounts.google.com/o/oauth2/v2/auth?` +
|
|
14723
|
-
`client_id=${encodeURIComponent(clientId)}&` +
|
|
14724
|
-
`redirect_uri=${encodeURIComponent(redirectUri)}&` +
|
|
14725
|
-
`scope=openid%20email%20profile&` +
|
|
14726
|
-
`response_type=code&` +
|
|
14727
|
-
`state=${encodeURIComponent(state)}`;
|
|
14728
|
-
break;
|
|
14729
|
-
default:
|
|
14730
|
-
response.status(400).json({
|
|
14731
|
-
error: 'Unsupported provider',
|
|
14732
|
-
message: `Social authentication provider '${provider}' is not supported. Supported providers: facebook, google`
|
|
14733
|
-
});
|
|
14734
|
-
return;
|
|
14735
|
-
}
|
|
14736
|
-
// Log the OAuth attempt for debugging
|
|
14737
|
-
if (isVerbose) {
|
|
14738
|
-
console.info(colors.cyan(`OAuth ${provider} flow started for app ${appId || 'default'}`));
|
|
14739
|
-
}
|
|
14740
|
-
response.json({
|
|
14741
|
-
authUrl,
|
|
14742
|
-
provider,
|
|
14743
|
-
state,
|
|
14744
|
-
message: `Redirect user to authUrl to complete ${provider} authentication`
|
|
14745
|
-
});
|
|
14746
|
-
}
|
|
14747
|
-
catch (error) {
|
|
14748
|
-
assertsError(error);
|
|
14749
|
-
console.warn(`OAuth ${provider} initialization failed:`, error);
|
|
14750
|
-
response.status(500).json({
|
|
14751
|
-
error: 'OAuth initialization failed',
|
|
14752
|
-
message: error.message
|
|
14753
|
-
});
|
|
14754
|
-
}
|
|
14755
|
-
});
|
|
14756
|
-
app.post('/auth/:provider/callback', async (request, response) => {
|
|
14757
|
-
const { provider } = request.params;
|
|
14758
|
-
if (!isApplicationModeAllowed || login === null) {
|
|
14759
|
-
response.status(400).json({
|
|
14760
|
-
error: 'Application mode is not allowed',
|
|
14761
|
-
message: 'Social authentication requires application mode and login handler to be configured'
|
|
14762
|
-
});
|
|
14763
|
-
return;
|
|
14764
|
-
}
|
|
14765
|
-
try {
|
|
14766
|
-
const { code, state, error: oauthError } = request.body;
|
|
14767
|
-
if (oauthError) {
|
|
14768
|
-
response.status(400).json({
|
|
14769
|
-
isSuccess: false,
|
|
14770
|
-
error: 'OAuth authorization failed',
|
|
14771
|
-
message: `${provider} authentication was denied or failed: ${oauthError}`
|
|
14772
|
-
});
|
|
14773
|
-
return;
|
|
14774
|
-
}
|
|
14775
|
-
if (!code || !state) {
|
|
14776
|
-
response.status(400).json({
|
|
14777
|
-
isSuccess: false,
|
|
14778
|
-
error: 'Missing OAuth callback parameters',
|
|
14779
|
-
message: 'code and state parameters are required'
|
|
14780
|
-
});
|
|
14781
|
-
return;
|
|
14782
|
-
}
|
|
14783
|
-
// Decode state to get app information
|
|
14784
|
-
let appInfo;
|
|
14785
|
-
try {
|
|
14786
|
-
appInfo = JSON.parse(Buffer.from(state, 'base64').toString());
|
|
14787
|
-
}
|
|
14788
|
-
catch (_a) {
|
|
14789
|
-
response.status(400).json({
|
|
14790
|
-
isSuccess: false,
|
|
14791
|
-
error: 'Invalid state parameter',
|
|
14792
|
-
message: 'The OAuth state parameter is malformed'
|
|
14793
|
-
});
|
|
14794
|
-
return;
|
|
14795
|
-
}
|
|
14796
|
-
// Log the OAuth callback for debugging
|
|
14797
|
-
if (isVerbose) {
|
|
14798
|
-
console.info(colors.cyan(`OAuth ${provider} callback received for app ${appInfo.appId}`));
|
|
14799
|
-
}
|
|
14800
|
-
// Note: In a real implementation, you would:
|
|
14801
|
-
// 1. Exchange the code for an access token with the OAuth provider
|
|
14802
|
-
// 2. Use the access token to get user information
|
|
14803
|
-
// 3. Create or find the user in your system
|
|
14804
|
-
// 4. Call the login function with the user's information
|
|
14805
|
-
// For now, we provide a framework that the implementer can extend
|
|
14806
|
-
const mockUserInfo = {
|
|
14807
|
-
username: `${provider}_user_${code.substring(0, 8)}`,
|
|
14808
|
-
password: '',
|
|
14809
|
-
appId: appInfo.appId
|
|
14810
|
-
};
|
|
14811
|
-
const loginResult = await login({
|
|
14812
|
-
...mockUserInfo,
|
|
14813
|
-
rawRequest: request,
|
|
14814
|
-
rawResponse: response,
|
|
14815
|
-
});
|
|
14816
|
-
response.status(200).json({
|
|
14817
|
-
...loginResult,
|
|
14818
|
-
provider,
|
|
14819
|
-
message: loginResult.message || `${provider} authentication completed`,
|
|
14820
|
-
});
|
|
14821
|
-
}
|
|
14822
|
-
catch (error) {
|
|
14823
|
-
assertsError(error);
|
|
14824
|
-
console.warn(`OAuth ${provider} callback failed:`, error);
|
|
14825
|
-
response.status(500).json({
|
|
14826
|
-
isSuccess: false,
|
|
14827
|
-
error: 'OAuth callback processing failed',
|
|
14828
|
-
message: error.message
|
|
14829
|
-
});
|
|
14830
|
-
}
|
|
14831
|
-
});
|
|
14832
14713
|
app.get(`/books`, async (request, response) => {
|
|
14833
14714
|
if (collection === null) {
|
|
14834
14715
|
response.status(500).send('No collection available');
|
|
@@ -18823,7 +18704,23 @@ async function getScraperIntermediateSource(source, options) {
|
|
|
18823
18704
|
.join('/') +
|
|
18824
18705
|
'.' +
|
|
18825
18706
|
extension;
|
|
18826
|
-
|
|
18707
|
+
// Note: Try to create cache directory, but don't fail if filesystem has issues
|
|
18708
|
+
try {
|
|
18709
|
+
await mkdir(dirname(cacheFilename), { recursive: true });
|
|
18710
|
+
}
|
|
18711
|
+
catch (error) {
|
|
18712
|
+
// Note: If we can't create cache directory, continue without it
|
|
18713
|
+
// This handles read-only filesystems, permission issues, and missing parent directories
|
|
18714
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
18715
|
+
error.message.includes('read-only') ||
|
|
18716
|
+
error.message.includes('EACCES') ||
|
|
18717
|
+
error.message.includes('EPERM') ||
|
|
18718
|
+
error.message.includes('ENOENT'))) ;
|
|
18719
|
+
else {
|
|
18720
|
+
// Re-throw other unexpected errors
|
|
18721
|
+
throw error;
|
|
18722
|
+
}
|
|
18723
|
+
}
|
|
18827
18724
|
let isDestroyed = true;
|
|
18828
18725
|
const fileHandler = {
|
|
18829
18726
|
filename: cacheFilename,
|
|
@@ -19673,7 +19570,23 @@ class MarkitdownScraper {
|
|
|
19673
19570
|
// <- TODO: [🍀] Make MarkitdownError
|
|
19674
19571
|
}
|
|
19675
19572
|
// console.log('!!', { result, cacheFilehandler });
|
|
19676
|
-
|
|
19573
|
+
// Note: Try to cache the converted content, but don't fail if the filesystem is read-only
|
|
19574
|
+
try {
|
|
19575
|
+
await this.tools.fs.writeFile(cacheFilehandler.filename, result.text_content);
|
|
19576
|
+
}
|
|
19577
|
+
catch (error) {
|
|
19578
|
+
// Note: If we can't write to cache, we'll continue without caching
|
|
19579
|
+
// This handles read-only filesystems like Vercel
|
|
19580
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
19581
|
+
error.message.includes('read-only') ||
|
|
19582
|
+
error.message.includes('EACCES') ||
|
|
19583
|
+
error.message.includes('EPERM') ||
|
|
19584
|
+
error.message.includes('ENOENT'))) ;
|
|
19585
|
+
else {
|
|
19586
|
+
// Re-throw other unexpected errors
|
|
19587
|
+
throw error;
|
|
19588
|
+
}
|
|
19589
|
+
}
|
|
19677
19590
|
}
|
|
19678
19591
|
return cacheFilehandler;
|
|
19679
19592
|
}
|
|
@@ -19961,7 +19874,23 @@ class WebsiteScraper {
|
|
|
19961
19874
|
extension: 'html',
|
|
19962
19875
|
isVerbose,
|
|
19963
19876
|
});
|
|
19964
|
-
|
|
19877
|
+
// Note: Try to cache the scraped content, but don't fail if the filesystem is read-only
|
|
19878
|
+
try {
|
|
19879
|
+
await this.tools.fs.writeFile(cacheFilehandler.filename, html, 'utf-8');
|
|
19880
|
+
}
|
|
19881
|
+
catch (error) {
|
|
19882
|
+
// Note: If we can't write to cache, we'll continue without caching
|
|
19883
|
+
// This handles read-only filesystems like Vercel
|
|
19884
|
+
if (error instanceof Error && (error.message.includes('EROFS') ||
|
|
19885
|
+
error.message.includes('read-only') ||
|
|
19886
|
+
error.message.includes('EACCES') ||
|
|
19887
|
+
error.message.includes('EPERM') ||
|
|
19888
|
+
error.message.includes('ENOENT'))) ;
|
|
19889
|
+
else {
|
|
19890
|
+
// Re-throw other unexpected errors
|
|
19891
|
+
throw error;
|
|
19892
|
+
}
|
|
19893
|
+
}
|
|
19965
19894
|
const markdown = this.showdownConverter.makeMarkdown(html, jsdom.window.document);
|
|
19966
19895
|
return { ...cacheFilehandler, markdown };
|
|
19967
19896
|
}
|