@promptbook/cli 0.112.0-93 → 0.112.0-96

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 (52) hide show
  1. package/apps/agents-server/next.config.ts +8 -1
  2. package/apps/agents-server/playwright.config.ts +2 -0
  3. package/apps/agents-server/src/app/admin/_components/AdminConfigurationShell.tsx +13 -7
  4. package/apps/agents-server/src/app/admin/code-runners/CodeRunnersClient.tsx +225 -0
  5. package/apps/agents-server/src/app/admin/code-runners/page.tsx +14 -0
  6. package/apps/agents-server/src/app/admin/database/DatabaseAdminClient.tsx +38 -0
  7. package/apps/agents-server/src/app/admin/database/DatabaseAdminStudioSurface.tsx +42 -0
  8. package/apps/agents-server/src/app/admin/database/page.tsx +33 -0
  9. package/apps/agents-server/src/app/admin/environment/EnvironmentVariablesClient.tsx +259 -0
  10. package/apps/agents-server/src/app/admin/environment/page.tsx +21 -0
  11. package/apps/agents-server/src/app/admin/logs/LogsClient.tsx +78 -0
  12. package/apps/agents-server/src/app/admin/logs/page.tsx +14 -0
  13. package/apps/agents-server/src/app/admin/servers/ServersClient.tsx +64 -33
  14. package/apps/agents-server/src/app/admin/servers/ServersRegistryApi.ts +5 -0
  15. package/apps/agents-server/src/app/admin/servers/ServersRegistryTable.tsx +15 -2
  16. package/apps/agents-server/src/app/admin/servers/page.tsx +3 -3
  17. package/apps/agents-server/src/app/admin/servers/useServersRegistryState.ts +12 -2
  18. package/apps/agents-server/src/app/api/admin/code-runners/route.ts +104 -0
  19. package/apps/agents-server/src/app/api/admin/database/studio/route.ts +113 -0
  20. package/apps/agents-server/src/app/api/admin/environment/route.ts +65 -0
  21. package/apps/agents-server/src/app/api/admin/logs/route.ts +24 -0
  22. package/apps/agents-server/src/app/api/admin/servers/[serverId]/route.ts +79 -3
  23. package/apps/agents-server/src/app/api/admin/servers/route.ts +36 -1
  24. package/apps/agents-server/src/app/layout.tsx +1 -0
  25. package/apps/agents-server/src/app/page.tsx +101 -1
  26. package/apps/agents-server/src/components/Header/buildHeaderSystemMenuItems.ts +27 -4
  27. package/apps/agents-server/src/database/$provideClientSql.ts +2 -6
  28. package/apps/agents-server/src/database/$provideDatabaseAdminExecutor.ts +273 -0
  29. package/apps/agents-server/src/database/resolvePostgresConnectionString.ts +26 -0
  30. package/apps/agents-server/src/database/sqlite/$provideAgentsServerSqliteDatabase.ts +83 -0
  31. package/apps/agents-server/src/database/sqlite/$provideLocalSqliteSupabase.ts +20 -71
  32. package/apps/agents-server/src/languages/ServerTranslationKeys.ts +5 -0
  33. package/apps/agents-server/src/languages/translations/czech.yaml +5 -0
  34. package/apps/agents-server/src/languages/translations/english.yaml +5 -0
  35. package/apps/agents-server/src/tools/$provideServer.ts +27 -0
  36. package/apps/agents-server/src/utils/serverRegistry.ts +20 -1
  37. package/apps/agents-server/src/utils/session.ts +123 -2
  38. package/apps/agents-server/src/utils/vpsConfiguration.ts +550 -0
  39. package/esm/index.es.js +1 -1
  40. package/esm/index.es.js.map +1 -1
  41. package/esm/src/book-components/Chat/utils/renderMarkdown.test.d.ts +1 -0
  42. package/esm/src/version.d.ts +1 -1
  43. package/package.json +3 -1
  44. package/src/book-components/Chat/MarkdownContent/MarkdownContent.tsx +9 -398
  45. package/src/book-components/Chat/utils/renderMarkdown.ts +323 -8
  46. package/src/other/templates/getTemplatesPipelineCollection.ts +712 -829
  47. package/src/version.ts +2 -2
  48. package/src/versions.txt +2 -0
  49. package/umd/index.umd.js +1 -1
  50. package/umd/index.umd.js.map +1 -1
  51. package/umd/src/book-components/Chat/utils/renderMarkdown.test.d.ts +1 -0
  52. package/umd/src/version.d.ts +1 -1
@@ -0,0 +1,550 @@
1
+ import { execFile } from 'child_process';
2
+ import { constants as filesystemConstants } from 'fs';
3
+ import { access, mkdir, readFile, writeFile } from 'fs/promises';
4
+ import { dirname, join, resolve } from 'path';
5
+ import { promisify } from 'util';
6
+ import { spaceTrim } from 'spacetrim';
7
+ import { NotAllowed } from '../../../../src/errors/NotAllowed';
8
+ import { normalizeServerDomain } from './serverRegistry';
9
+
10
+ const execFileAsync = promisify(execFile);
11
+
12
+ /**
13
+ * Mask rendered for sensitive environment values in the UI.
14
+ */
15
+ export const HIDDEN_ENVIRONMENT_VALUE = '********';
16
+
17
+ /**
18
+ * Environment variable names that can be managed from the standalone VPS UI.
19
+ */
20
+ export const VPS_ENVIRONMENT_VARIABLE_KEYS = [
21
+ 'SERVERS',
22
+ 'NEXT_PUBLIC_SITE_URL',
23
+ 'ADMIN_PASSWORD',
24
+ 'OPENAI_API_KEY',
25
+ 'PTBK_AGENT',
26
+ 'PTBK_MODEL',
27
+ 'PTBK_THINKING_LEVEL',
28
+ 'PORT',
29
+ 'PTBK_HOSTNAME',
30
+ 'PTBK_PUBLIC_IP_ADDRESS',
31
+ 'PTBK_PM2_APP_NAME',
32
+ 'PTBK_NGINX_SITE_NAME',
33
+ 'LETS_ENCRYPT_EMAIL',
34
+ 'PTBK_AGENTS_SERVER_DATABASE',
35
+ 'PTBK_AGENTS_SERVER_SQLITE_PATH',
36
+ 'SUPABASE_TABLE_PREFIX',
37
+ 'SUPABASE_AUTO_MIGRATE',
38
+ 'COPILOT_GITHUB_TOKEN',
39
+ 'GH_TOKEN',
40
+ 'ANTHROPIC_CLAUDE_API_KEY',
41
+ 'GOOGLE_GENERATIVE_AI_API_KEY',
42
+ 'AZUREOPENAI_API_KEY',
43
+ ] as const;
44
+
45
+ /**
46
+ * Editable environment variable key supported by the standalone VPS UI.
47
+ */
48
+ export type VpsEnvironmentVariableKey = (typeof VPS_ENVIRONMENT_VARIABLE_KEYS)[number];
49
+
50
+ /**
51
+ * One environment variable row safe to send to the browser.
52
+ */
53
+ export type VpsEnvironmentVariableRecord = {
54
+ /**
55
+ * Environment variable name.
56
+ */
57
+ readonly key: VpsEnvironmentVariableKey;
58
+
59
+ /**
60
+ * Masked or plain UI value.
61
+ */
62
+ readonly value: string;
63
+
64
+ /**
65
+ * Whether the real stored value is sensitive and hidden.
66
+ */
67
+ readonly isSensitive: boolean;
68
+
69
+ /**
70
+ * Whether the variable exists in the `.env` file or current process.
71
+ */
72
+ readonly isDefined: boolean;
73
+ };
74
+
75
+ /**
76
+ * Result of running a VPS command from the admin UI.
77
+ */
78
+ export type VpsCommandResult = {
79
+ /**
80
+ * Whether the command was attempted on this host.
81
+ */
82
+ readonly isAvailable: boolean;
83
+
84
+ /**
85
+ * Process exit output or skipped reason.
86
+ */
87
+ readonly output: string;
88
+ };
89
+
90
+ /**
91
+ * Parsed `.env` line representation used to preserve comments and ordering.
92
+ */
93
+ type ParsedEnvLine =
94
+ | {
95
+ readonly type: 'entry';
96
+ readonly raw: string;
97
+ readonly key: string;
98
+ readonly value: string;
99
+ }
100
+ | {
101
+ readonly type: 'raw';
102
+ readonly raw: string;
103
+ };
104
+
105
+ /**
106
+ * Returns the installed Agents Server `.env` file path.
107
+ *
108
+ * @returns Absolute path to the editable `.env` file.
109
+ */
110
+ export function resolveVpsEnvironmentFilePath(): string {
111
+ const explicitEnvFile = process.env.PTBK_AGENTS_SERVER_ENV_FILE?.trim();
112
+ if (explicitEnvFile) {
113
+ return resolve(explicitEnvFile);
114
+ }
115
+
116
+ const explicitInstallDirectory = process.env.PTBK_INSTALL_DIR?.trim();
117
+ if (explicitInstallDirectory) {
118
+ return resolve(explicitInstallDirectory, '.env');
119
+ }
120
+
121
+ const sqlitePath = process.env.PTBK_AGENTS_SERVER_SQLITE_PATH?.trim();
122
+ if (sqlitePath) {
123
+ return resolve(dirname(dirname(resolve(sqlitePath))), '.env');
124
+ }
125
+
126
+ return resolve(process.cwd(), '.env');
127
+ }
128
+
129
+ /**
130
+ * Reads managed environment variables with sensitive values masked.
131
+ *
132
+ * @returns Safe environment snapshot for the admin UI.
133
+ */
134
+ export async function listVpsEnvironmentVariables(): Promise<{
135
+ readonly envFilePath: string;
136
+ readonly variables: ReadonlyArray<VpsEnvironmentVariableRecord>;
137
+ }> {
138
+ const envFilePath = resolveVpsEnvironmentFilePath();
139
+ const envValues = await readVpsEnvironmentMap(envFilePath);
140
+
141
+ return {
142
+ envFilePath,
143
+ variables: VPS_ENVIRONMENT_VARIABLE_KEYS.map((key) => {
144
+ const rawValue = envValues.get(key) ?? process.env[key] ?? '';
145
+ const isSensitive = isSensitiveEnvironmentVariable(key);
146
+ return {
147
+ key,
148
+ value: isSensitive && rawValue ? HIDDEN_ENVIRONMENT_VALUE : rawValue,
149
+ isSensitive,
150
+ isDefined: rawValue !== '',
151
+ };
152
+ }),
153
+ };
154
+ }
155
+
156
+ /**
157
+ * Updates supported variables in the installed `.env` file.
158
+ *
159
+ * Sensitive variables are updated only when the submitted value is not empty and not the mask.
160
+ *
161
+ * @param updates - Key/value pairs to persist.
162
+ * @returns Safe environment snapshot after writing.
163
+ */
164
+ export async function updateVpsEnvironmentVariables(
165
+ updates: Readonly<Record<string, string>>,
166
+ ): Promise<Awaited<ReturnType<typeof listVpsEnvironmentVariables>>> {
167
+ const normalizedUpdates = normalizeVpsEnvironmentUpdates(updates);
168
+ const envFilePath = resolveVpsEnvironmentFilePath();
169
+ const existingContent = await readOptionalTextFile(envFilePath);
170
+ const nextContent = serializeUpdatedEnvFile(existingContent, normalizedUpdates);
171
+
172
+ await mkdir(dirname(envFilePath), { recursive: true });
173
+ await writeFile(envFilePath, nextContent, { encoding: 'utf-8', mode: 0o600 });
174
+
175
+ for (const [key, value] of normalizedUpdates) {
176
+ process.env[key] = value;
177
+ }
178
+
179
+ return listVpsEnvironmentVariables();
180
+ }
181
+
182
+ /**
183
+ * Reads domains from the installed `.env` file or current process.
184
+ *
185
+ * @returns Normalized configured domains.
186
+ */
187
+ export async function listConfiguredVpsDomains(): Promise<Array<string>> {
188
+ const envValues = await readVpsEnvironmentMap(resolveVpsEnvironmentFilePath());
189
+ const rawServers = envValues.get('SERVERS') ?? process.env.SERVERS ?? '';
190
+
191
+ return parseDomainsCsv(rawServers);
192
+ }
193
+
194
+ /**
195
+ * Replaces the standalone VPS domain list in `.env`.
196
+ *
197
+ * @param domains - Domains to store in `SERVERS`.
198
+ * @returns Safe environment snapshot after writing.
199
+ */
200
+ export async function updateConfiguredVpsDomains(
201
+ domains: ReadonlyArray<string>,
202
+ ): Promise<Awaited<ReturnType<typeof listVpsEnvironmentVariables>>> {
203
+ const normalizedDomains = normalizeDomains(domains);
204
+ const primaryDomain = normalizedDomains[0] ?? '';
205
+ const updates: Record<string, string> = {
206
+ SERVERS: normalizedDomains.join(','),
207
+ };
208
+
209
+ if (primaryDomain) {
210
+ updates.NEXT_PUBLIC_SITE_URL = `https://${primaryDomain}`;
211
+ updates.SUPABASE_TABLE_PREFIX = buildDomainTablePrefix(primaryDomain);
212
+ } else {
213
+ const publicIpAddress = process.env.PTBK_PUBLIC_IP_ADDRESS?.trim();
214
+ updates.NEXT_PUBLIC_SITE_URL = publicIpAddress
215
+ ? `http://${publicIpAddress}`
216
+ : '';
217
+ updates.SUPABASE_TABLE_PREFIX = '';
218
+ }
219
+
220
+ return updateVpsEnvironmentVariables(updates);
221
+ }
222
+
223
+ /**
224
+ * Applies Nginx, Certbot, and process-manager changes through the shared VPS installer script.
225
+ *
226
+ * @returns Command output or a skipped reason when not running on a Linux VPS.
227
+ */
228
+ export async function applyVpsRuntimeConfiguration(): Promise<VpsCommandResult> {
229
+ return runVpsInstallerCommand('apply-domains', 'VPS runtime configuration can only be applied on Linux.');
230
+ }
231
+
232
+ /**
233
+ * Installs/configures the selected local code runner through the shared VPS installer script.
234
+ *
235
+ * @returns Command output or a skipped reason when not running on a Linux VPS.
236
+ */
237
+ export async function applyVpsCodeRunnerConfiguration(): Promise<VpsCommandResult> {
238
+ return runVpsInstallerCommand('apply-runner', 'VPS code-runner configuration can only be applied on Linux.');
239
+ }
240
+
241
+ /**
242
+ * Reads recent pm2 logs for the configured Agents Server process.
243
+ *
244
+ * @param lineCount - Number of lines to request.
245
+ * @returns Log output or a skipped reason.
246
+ */
247
+ export async function readVpsPm2Logs(lineCount = 200): Promise<VpsCommandResult> {
248
+ if (process.platform !== 'linux') {
249
+ return {
250
+ isAvailable: false,
251
+ output: 'pm2 logs are available only on the Linux VPS runtime.',
252
+ };
253
+ }
254
+
255
+ const appName = process.env.PTBK_PM2_APP_NAME?.trim() || 'promptbook-agents-server';
256
+ const { stdout, stderr } = await execFileAsync('pm2', ['logs', appName, '--nostream', '--lines', String(lineCount)], {
257
+ maxBuffer: 1024 * 1024,
258
+ });
259
+
260
+ return {
261
+ isAvailable: true,
262
+ output: [stdout, stderr].filter(Boolean).join('\n').trim(),
263
+ };
264
+ }
265
+
266
+ /**
267
+ * Runs one maintenance subcommand from the shared VPS installer script.
268
+ *
269
+ * @param command - Installer subcommand.
270
+ * @param unavailableOutput - Message returned on non-Linux platforms.
271
+ * @returns Command output or unavailable reason.
272
+ */
273
+ async function runVpsInstallerCommand(command: string, unavailableOutput: string): Promise<VpsCommandResult> {
274
+ if (process.platform !== 'linux') {
275
+ return {
276
+ isAvailable: false,
277
+ output: unavailableOutput,
278
+ };
279
+ }
280
+
281
+ const scriptPath = await resolveVpsInstallerScriptPath();
282
+ if (!scriptPath) {
283
+ return {
284
+ isAvailable: false,
285
+ output: 'The VPS installer script could not be found on this server.',
286
+ };
287
+ }
288
+
289
+ try {
290
+ const { stdout, stderr } = await execFileAsync('bash', [scriptPath, command], {
291
+ env: {
292
+ ...process.env,
293
+ PTBK_NON_INTERACTIVE: '1',
294
+ },
295
+ maxBuffer: 1024 * 1024,
296
+ });
297
+
298
+ return {
299
+ isAvailable: true,
300
+ output: [stdout, stderr].filter(Boolean).join('\n').trim(),
301
+ };
302
+ } catch (error) {
303
+ const commandError = error as Error & { readonly stdout?: string; readonly stderr?: string };
304
+ const commandOutput = [commandError.stdout, commandError.stderr, commandError.message]
305
+ .filter(Boolean)
306
+ .join('\n')
307
+ .trim();
308
+
309
+ throw new Error(
310
+ spaceTrim(`
311
+ Failed to run VPS installer command \`${command}\`.
312
+
313
+ ${commandOutput}
314
+ `),
315
+ );
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Returns whether an environment variable must be masked in the UI.
321
+ *
322
+ * @param key - Environment variable name.
323
+ * @returns `true` when the value is sensitive.
324
+ */
325
+ export function isSensitiveEnvironmentVariable(key: string): boolean {
326
+ return /(?:PASSWORD|SECRET|TOKEN|API_KEY|PRIVATE_KEY|CREDENTIAL)/iu.test(key);
327
+ }
328
+
329
+ /**
330
+ * Parses a comma-separated domain list.
331
+ *
332
+ * @param value - Raw `SERVERS` value.
333
+ * @returns Normalized unique domains.
334
+ */
335
+ export function parseDomainsCsv(value: string): Array<string> {
336
+ return normalizeDomains(value.split(','));
337
+ }
338
+
339
+ /**
340
+ * Builds the deterministic table prefix used by standalone domain records.
341
+ *
342
+ * @param domain - Normalized domain.
343
+ * @returns Table prefix for the server namespace.
344
+ */
345
+ export function buildDomainTablePrefix(domain: string): string {
346
+ const prefixSuffix = domain
347
+ .toLowerCase()
348
+ .replace(/-/gu, '_dash_')
349
+ .replace(/\./gu, '_')
350
+ .replace(/:/gu, '_port_')
351
+ .replace(/[^a-z0-9_]/gu, '_')
352
+ .replace(/_+/gu, '_')
353
+ .replace(/^_+|_+$/gu, '');
354
+
355
+ return `server_${prefixSuffix}_`;
356
+ }
357
+
358
+ /**
359
+ * Normalizes a list of raw domain values and removes duplicates.
360
+ *
361
+ * @param domains - Raw domains.
362
+ * @returns Normalized unique domains.
363
+ */
364
+ function normalizeDomains(domains: ReadonlyArray<string>): Array<string> {
365
+ const normalizedDomains: Array<string> = [];
366
+
367
+ for (const rawDomain of domains) {
368
+ const normalizedDomain = normalizeServerDomain(rawDomain);
369
+ if (!normalizedDomain || normalizedDomains.includes(normalizedDomain)) {
370
+ continue;
371
+ }
372
+
373
+ normalizedDomains.push(normalizedDomain);
374
+ }
375
+
376
+ return normalizedDomains;
377
+ }
378
+
379
+ /**
380
+ * Reads a `.env` file into a map.
381
+ *
382
+ * @param envFilePath - File to parse.
383
+ * @returns Parsed environment map.
384
+ */
385
+ async function readVpsEnvironmentMap(envFilePath: string): Promise<Map<string, string>> {
386
+ const content = await readOptionalTextFile(envFilePath);
387
+ const envMap = new Map<string, string>();
388
+
389
+ for (const line of parseEnvLines(content)) {
390
+ if (line.type === 'entry') {
391
+ envMap.set(line.key, line.value);
392
+ }
393
+ }
394
+
395
+ return envMap;
396
+ }
397
+
398
+ /**
399
+ * Normalizes update payloads and rejects unsupported keys.
400
+ *
401
+ * @param updates - Raw request updates.
402
+ * @returns Supported update entries.
403
+ */
404
+ function normalizeVpsEnvironmentUpdates(updates: Readonly<Record<string, string>>): Map<string, string> {
405
+ const normalizedUpdates = new Map<string, string>();
406
+
407
+ for (const [rawKey, rawValue] of Object.entries(updates)) {
408
+ if (!isVpsEnvironmentVariableKey(rawKey)) {
409
+ throw new NotAllowed(
410
+ spaceTrim(`
411
+ Environment variable \`${rawKey}\` is not editable through the Agents Server UI.
412
+ `),
413
+ );
414
+ }
415
+
416
+ if (typeof rawValue !== 'string') {
417
+ continue;
418
+ }
419
+
420
+ const value = rawValue.trim();
421
+ if (isSensitiveEnvironmentVariable(rawKey) && (value === '' || value === HIDDEN_ENVIRONMENT_VALUE)) {
422
+ continue;
423
+ }
424
+
425
+ normalizedUpdates.set(rawKey, value);
426
+ }
427
+
428
+ return normalizedUpdates;
429
+ }
430
+
431
+ /**
432
+ * Checks whether a raw key is supported by the VPS UI.
433
+ *
434
+ * @param key - Candidate environment variable key.
435
+ * @returns `true` when supported.
436
+ */
437
+ function isVpsEnvironmentVariableKey(key: string): key is VpsEnvironmentVariableKey {
438
+ return VPS_ENVIRONMENT_VARIABLE_KEYS.includes(key as VpsEnvironmentVariableKey);
439
+ }
440
+
441
+ /**
442
+ * Serializes an updated `.env` file while preserving unrelated lines.
443
+ *
444
+ * @param existingContent - Current file content.
445
+ * @param updates - Normalized updates to apply.
446
+ * @returns Next file content.
447
+ */
448
+ function serializeUpdatedEnvFile(existingContent: string, updates: ReadonlyMap<string, string>): string {
449
+ const remainingUpdates = new Map(updates);
450
+ const nextLines = parseEnvLines(existingContent).map((line) => {
451
+ if (line.type !== 'entry' || !remainingUpdates.has(line.key)) {
452
+ return line.raw;
453
+ }
454
+
455
+ const nextValue = remainingUpdates.get(line.key)!;
456
+ remainingUpdates.delete(line.key);
457
+ return `${line.key}=${nextValue}`;
458
+ });
459
+
460
+ for (const [key, value] of remainingUpdates) {
461
+ nextLines.push(`${key}=${value}`);
462
+ }
463
+
464
+ return `${nextLines.join('\n').replace(/\n+$/u, '')}\n`;
465
+ }
466
+
467
+ /**
468
+ * Parses `.env` content into structured lines.
469
+ *
470
+ * @param content - Raw `.env` content.
471
+ * @returns Parsed line records.
472
+ */
473
+ function parseEnvLines(content: string): Array<ParsedEnvLine> {
474
+ return content.split(/\r?\n/u).map((raw) => {
475
+ const match = raw.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=(.*)$/u);
476
+ if (!match) {
477
+ return { type: 'raw', raw };
478
+ }
479
+
480
+ const [, key, rawValue = ''] = match;
481
+ return {
482
+ type: 'entry',
483
+ raw,
484
+ key,
485
+ value: parseEnvValue(rawValue),
486
+ };
487
+ });
488
+ }
489
+
490
+ /**
491
+ * Parses the common `.env` value quoting forms.
492
+ *
493
+ * @param rawValue - Raw text after `=`.
494
+ * @returns Unquoted value.
495
+ */
496
+ function parseEnvValue(rawValue: string): string {
497
+ const trimmedValue = rawValue.trim();
498
+ if (
499
+ (trimmedValue.startsWith('"') && trimmedValue.endsWith('"')) ||
500
+ (trimmedValue.startsWith("'") && trimmedValue.endsWith("'"))
501
+ ) {
502
+ return trimmedValue.slice(1, -1);
503
+ }
504
+
505
+ return trimmedValue;
506
+ }
507
+
508
+ /**
509
+ * Reads a text file and returns an empty string when it does not exist.
510
+ *
511
+ * @param filePath - File to read.
512
+ * @returns File content or empty string.
513
+ */
514
+ async function readOptionalTextFile(filePath: string): Promise<string> {
515
+ try {
516
+ return await readFile(filePath, 'utf-8');
517
+ } catch (error) {
518
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
519
+ return '';
520
+ }
521
+
522
+ throw error;
523
+ }
524
+ }
525
+
526
+ /**
527
+ * Resolves the shared VPS installer script path.
528
+ *
529
+ * @returns Script path or `null` when unavailable.
530
+ */
531
+ async function resolveVpsInstallerScriptPath(): Promise<string | null> {
532
+ const candidates = [
533
+ process.env.PTBK_VPS_INSTALL_SCRIPT?.trim(),
534
+ process.env.PTBK_REPOSITORY_DIR ? join(process.env.PTBK_REPOSITORY_DIR, 'other/vps/install.sh') : '',
535
+ join(process.cwd(), 'other/vps/install.sh'),
536
+ join(process.cwd(), '../../other/vps/install.sh'),
537
+ ].filter((candidate): candidate is string => Boolean(candidate));
538
+
539
+ for (const candidate of candidates) {
540
+ const absoluteCandidate = resolve(candidate);
541
+ try {
542
+ await access(absoluteCandidate, filesystemConstants.R_OK);
543
+ return absoluteCandidate;
544
+ } catch {
545
+ // Try the next candidate.
546
+ }
547
+ }
548
+
549
+ return null;
550
+ }
package/esm/index.es.js CHANGED
@@ -58,7 +58,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
58
58
  * @generated
59
59
  * @see https://github.com/webgptorg/promptbook
60
60
  */
61
- const PROMPTBOOK_ENGINE_VERSION = '0.112.0-93';
61
+ const PROMPTBOOK_ENGINE_VERSION = '0.112.0-96';
62
62
  /**
63
63
  * TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
64
64
  * Note: [💞] Ignore a discrepancy between file name and entity name