@promptbook/cli 0.112.0-135 → 0.112.0-137

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 (64) hide show
  1. package/README.md +20 -0
  2. package/apps/agents-server/README.md +0 -1
  3. package/apps/agents-server/src/app/admin/metadata/MetadataClient.tsx +246 -15
  4. package/apps/agents-server/src/app/admin/update/UpdateClient.tsx +237 -4
  5. package/apps/agents-server/src/app/api/admin/update/log/route.ts +42 -0
  6. package/apps/agents-server/src/app/api/agents/export/route.ts +41 -0
  7. package/apps/agents-server/src/app/api/agents/import/route.ts +168 -0
  8. package/apps/agents-server/src/app/api/federated-agents/route.ts +1 -1
  9. package/apps/agents-server/src/app/api/internal/agent-runner-limits/route.ts +2 -1
  10. package/apps/agents-server/src/app/api/internal/user-chat-jobs/run/route.ts +2 -1
  11. package/apps/agents-server/src/app/api/metadata/export/route.ts +34 -0
  12. package/apps/agents-server/src/app/api/metadata/import/route.ts +46 -0
  13. package/apps/agents-server/src/app/layout.tsx +1 -1
  14. package/apps/agents-server/src/components/Homepage/AgentsList.tsx +31 -1
  15. package/apps/agents-server/src/components/Homepage/AgentsListHeader.tsx +60 -1
  16. package/apps/agents-server/src/components/Homepage/useAgentsListImportExportState.ts +523 -0
  17. package/apps/agents-server/src/components/Homepage/useAgentsListState.ts +16 -0
  18. package/apps/agents-server/src/database/metadataDefaults.ts +0 -21
  19. package/apps/agents-server/src/database/seedCoreAgents.ts +235 -0
  20. package/apps/agents-server/src/database/seedDefaultAgents.ts +32 -1
  21. package/apps/agents-server/src/search/createDefaultServerSearchProviders/createFederatedAgentsSearchProvider.ts +1 -1
  22. package/apps/agents-server/src/tools/$provideAgentCollectionForServer.ts +2 -9
  23. package/apps/agents-server/src/utils/agentsTransfer/createAgentsExportZipStream.ts +68 -0
  24. package/apps/agents-server/src/utils/agentsTransfer/importAgentsFromFiles.ts +852 -0
  25. package/apps/agents-server/src/utils/backup/createBooksBackupZipStream.ts +15 -2
  26. package/apps/agents-server/src/utils/defaultAgents/loadDefaultAgentBooks.ts +46 -4
  27. package/apps/agents-server/src/utils/getFederatedServers.ts +3 -74
  28. package/apps/agents-server/src/utils/getWellKnownAgentUrl.ts +10 -4
  29. package/apps/agents-server/src/utils/localChatRunner/processLocalUserChatJob.ts +1 -1
  30. package/apps/agents-server/src/utils/metadataConfigurationTransfer.ts +426 -0
  31. package/apps/agents-server/src/utils/serverManagement/createManagedServer/bootstrapManagedServer.ts +2 -5
  32. package/apps/agents-server/src/utils/serverManagement/createManagedServer/seedServerCoreAgents.ts +162 -0
  33. package/apps/agents-server/src/utils/session.ts +2 -1
  34. package/apps/agents-server/src/utils/vpsSelfUpdate.ts +110 -0
  35. package/esm/index.es.js +1 -1
  36. package/esm/index.es.js.map +1 -1
  37. package/esm/src/_packages/core.index.d.ts +0 -2
  38. package/esm/src/utils/isTimingSafeEqualString.d.ts +25 -0
  39. package/esm/src/version.d.ts +1 -1
  40. package/package.json +1 -1
  41. package/servers.ts +1 -16
  42. package/src/_packages/core.index.ts +0 -2
  43. package/src/commitments/_common/teamInternalAgentAccess.ts +2 -1
  44. package/src/other/templates/getTemplatesPipelineCollection.ts +789 -716
  45. package/src/utils/isTimingSafeEqualString.ts +43 -0
  46. package/src/version.ts +2 -2
  47. package/src/versions.txt +2 -0
  48. package/umd/index.umd.js +1 -1
  49. package/umd/index.umd.js.map +1 -1
  50. package/umd/src/_packages/core.index.d.ts +0 -2
  51. package/umd/src/utils/isTimingSafeEqualString.d.ts +25 -0
  52. package/umd/src/version.d.ts +1 -1
  53. package/apps/agents-server/src/utils/defaultFederatedAgents/DefaultFederatedAgentsSyncOptions.ts +0 -9
  54. package/apps/agents-server/src/utils/defaultFederatedAgents/ensureDefaultFederatedAgentExists.ts +0 -277
  55. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchCoreOrganizationPayload.ts +0 -39
  56. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchFederatedAgentBook.ts +0 -43
  57. package/apps/agents-server/src/utils/defaultFederatedAgents/fetchWithDefaultFederatedAgentTimeout.ts +0 -28
  58. package/apps/agents-server/src/utils/defaultFederatedAgents/getDefaultFederatedAgentSyncPool.ts +0 -38
  59. package/apps/agents-server/src/utils/defaultFederatedAgents/loadActiveLocalAgentIdsByNormalizedName.ts +0 -41
  60. package/apps/agents-server/src/utils/defaultFederatedAgents/loadDefaultFederatedAgentSyncMetadata.ts +0 -76
  61. package/apps/agents-server/src/utils/defaultFederatedAgents/quoteIdentifier.ts +0 -11
  62. package/apps/agents-server/src/utils/defaultFederatedAgents/scheduleDefaultFederatedAgentsSync.ts +0 -88
  63. package/apps/agents-server/src/utils/defaultFederatedAgents/selectDefaultFederatedAgentsFromOrganizationPayload.ts +0 -181
  64. package/apps/agents-server/src/utils/defaultFederatedAgents/synchronizeDefaultFederatedAgents.ts +0 -77
package/README.md CHANGED
@@ -27,6 +27,26 @@ Create persistent AI agents that turn your company's scattered knowledge into ac
27
27
 
28
28
 
29
29
 
30
+ ### Standalone VPS
31
+
32
+ Run the standalone VPS installer only on a fresh server. Interactive mode asks for the installation values:
33
+
34
+ ```bash
35
+ sudo curl -fsSL https://raw.githubusercontent.com/webgptorg/promptbook/refs/heads/main/other/vps/install.sh | bash
36
+ ```
37
+
38
+ Non-interactive mode takes defaults from command-line options and skips initial code-runner CLI setup, which can be configured later from the UI or SSH:
39
+
40
+ ```bash
41
+ sudo curl -fsSL https://raw.githubusercontent.com/webgptorg/promptbook/refs/heads/main/other/vps/install.sh | bash -s -- \
42
+ --non-interactive \
43
+ --yes-i-understand-that-script-should-be-run-on-fresh-server \
44
+ --domain s24.ptbk.io \
45
+ --openai-api-key sk-proj-abcdef \
46
+ --sentry-dsn https://abc@def.ingest.de.sentry.io/123 \
47
+ --admin-password xxx
48
+ ```
49
+
30
50
 
31
51
 
32
52
  <blockquote style="color: #ff8811">
@@ -57,4 +57,3 @@ When creating new Agents server, search across the repository for [☁]
57
57
  - [☁] If using `USE PROJECT` auto-auth, configure [GitHub App integration](./GITHUB_APP.md)
58
58
  - [☁] Add the server to [the list of our servers](https://docs.google.com/spreadsheets/d/1X26iMQqubsxftqD1EJNSlzPYFS94QjCFPXyKdHHDeVs/edit?gid=848307752#gid=848307752)
59
59
  - [☁] Run migration script _(run new instance)_
60
- - [☁] For testing servers, look at the server and change the `CORE_SERVER` to `https://core-test.ptbk.io/`
@@ -1,15 +1,26 @@
1
1
  'use client';
2
2
 
3
- import { FileTextIcon, HashIcon, ImageIcon, ListIcon, ShieldIcon, ToggleLeftIcon, TypeIcon, Upload } from 'lucide-react';
3
+ import {
4
+ DownloadIcon,
5
+ FileTextIcon,
6
+ HashIcon,
7
+ ImageIcon,
8
+ ListIcon,
9
+ ShieldIcon,
10
+ ToggleLeftIcon,
11
+ TypeIcon,
12
+ UploadIcon,
13
+ } from 'lucide-react';
4
14
  import Link from 'next/link';
5
15
  import { Fragment, useEffect, useRef, useState } from 'react';
6
16
  import { showConfirm } from '../../../components/AsyncDialogs/asyncDialogs';
7
17
  import { useFileUploadAvailability } from '../../../components/FileUploadAvailability/FileUploadAvailabilityContext';
18
+ import { getDeprecatedLimitMetadataDefinition, type DeprecatedLimitMetadataDefinition } from '../../../constants/serverLimits';
8
19
  import { getMetadataDefinition, metadataDefaults, type MetadataDefinition } from '../../../database/metadataDefaults';
9
20
  import { getSafeCdnPath } from '../../../utils/cdn/utils/getSafeCdnPath';
21
+ import { downloadBlob, parseFilenameFromContentDisposition } from '../../../utils/download/browserFileDownload';
10
22
  import { normalizeUploadFilename } from '../../../utils/normalization/normalizeUploadFilename';
11
23
  import { buildDefaultUserFileUploadPath, uploadFileToServer } from '../../../utils/upload/uploadFileToServer';
12
- import { getDeprecatedLimitMetadataDefinition, type DeprecatedLimitMetadataDefinition } from '../../../constants/serverLimits';
13
24
 
14
25
  /**
15
26
  * Type describing metadata entry.
@@ -26,6 +37,47 @@ type MetadataEntry = {
26
37
  deprecatedLimitMetadata?: DeprecatedLimitMetadataDefinition | null;
27
38
  };
28
39
 
40
+ /**
41
+ * Endpoint serving standalone metadata JSON exports.
42
+ *
43
+ * @private
44
+ */
45
+ const METADATA_EXPORT_ENDPOINT = '/api/metadata/export';
46
+
47
+ /**
48
+ * Endpoint accepting standalone metadata JSON imports.
49
+ *
50
+ * @private
51
+ */
52
+ const METADATA_IMPORT_ENDPOINT = '/api/metadata/import';
53
+
54
+ /**
55
+ * Fallback filename used when the export route omits a `Content-Disposition` filename.
56
+ *
57
+ * @private
58
+ */
59
+ const DEFAULT_METADATA_EXPORT_FILENAME = 'promptbook-agents-server.metadata.json';
60
+
61
+ /**
62
+ * Minimal API error payload returned by metadata transfer routes.
63
+ *
64
+ * @private
65
+ */
66
+ type MetadataTransferErrorPayload = {
67
+ error?: string;
68
+ message?: string;
69
+ };
70
+
71
+ /**
72
+ * Import summary returned by the metadata import route.
73
+ *
74
+ * @private
75
+ */
76
+ type MetadataImportSummary = {
77
+ importedCount?: number;
78
+ resetCount?: number;
79
+ };
80
+
29
81
  /**
30
82
  * Shared class names for single-line metadata inputs.
31
83
  *
@@ -70,6 +122,30 @@ const createEmptyFormState = (): MetadataFormState => ({
70
122
  note: '',
71
123
  });
72
124
 
125
+ /**
126
+ * Reads a user-facing error message from a failed metadata transfer response.
127
+ *
128
+ * @param response - Failed HTTP response.
129
+ * @param fallbackMessage - Message used when the response body is not useful JSON.
130
+ * @returns Error message for the admin UI.
131
+ *
132
+ * @private
133
+ */
134
+ async function resolveMetadataTransferErrorMessage(response: Response, fallbackMessage: string): Promise<string> {
135
+ try {
136
+ const payload = (await response.json()) as MetadataTransferErrorPayload;
137
+ const message = payload.message || payload.error;
138
+
139
+ if (message && message.trim().length > 0) {
140
+ return message.trim();
141
+ }
142
+ } catch {
143
+ // Keep fallback when the failed response is not JSON.
144
+ }
145
+
146
+ return fallbackMessage;
147
+ }
148
+
73
149
  /**
74
150
  * Validates whether the provided string is an IPv4, IPv6, or CIDR range.
75
151
  *
@@ -185,7 +261,7 @@ const MetadataValueField = ({
185
261
  disabled={isUploading || !isUploadAvailable}
186
262
  className="bg-gray-100 text-gray-700 px-3 py-2 rounded-md border border-gray-300 hover:bg-gray-200 flex items-center space-x-2 min-w-max"
187
263
  >
188
- <Upload className="w-4 h-4" />
264
+ <UploadIcon className="w-4 h-4" />
189
265
  <span>{isUploading ? 'Uploading...' : 'Upload Image'}</span>
190
266
  </button>
191
267
  </div>
@@ -345,13 +421,18 @@ export function MetadataClient() {
345
421
  const [metadata, setMetadata] = useState<MetadataEntry[]>([]);
346
422
  const [loading, setLoading] = useState(true);
347
423
  const [error, setError] = useState<string | null>(null);
424
+ const [successMessage, setSuccessMessage] = useState<string | null>(null);
348
425
  const [editingKey, setEditingKey] = useState<string | null>(null);
349
426
  const [isAddUploading, setIsAddUploading] = useState(false);
350
427
  const [isEditUploading, setIsEditUploading] = useState(false);
428
+ const [isExportingMetadata, setIsExportingMetadata] = useState(false);
429
+ const [isImportingMetadata, setIsImportingMetadata] = useState(false);
351
430
  const addFileInputRef = useRef<HTMLInputElement | null>(null);
352
431
  const editFileInputRef = useRef<HTMLInputElement | null>(null);
432
+ const importFileInputRef = useRef<HTMLInputElement | null>(null);
353
433
  const [addFormState, setAddFormState] = useState<MetadataFormState>(createEmptyFormState);
354
434
  const [editingFormState, setEditingFormState] = useState<MetadataFormState>(createEmptyFormState);
435
+ const isMetadataTransferBusy = isExportingMetadata || isImportingMetadata;
355
436
 
356
437
  const fetchMetadata = async () => {
357
438
  try {
@@ -365,8 +446,8 @@ export function MetadataClient() {
365
446
  const mergedData = mergeMetadataWithDefaults(data);
366
447
 
367
448
  setMetadata(mergedData);
368
- } catch (err) {
369
- setError(err instanceof Error ? err.message : 'An error occurred');
449
+ } catch (error) {
450
+ setError(error instanceof Error ? error.message : 'An error occurred');
370
451
  } finally {
371
452
  setLoading(false);
372
453
  }
@@ -394,16 +475,123 @@ export function MetadataClient() {
394
475
  }
395
476
  };
396
477
 
478
+ /**
479
+ * Downloads the current metadata configuration as standalone JSON.
480
+ *
481
+ * @private
482
+ */
483
+ const handleExportMetadata = async () => {
484
+ if (isMetadataTransferBusy) {
485
+ return;
486
+ }
487
+
488
+ setError(null);
489
+ setSuccessMessage(null);
490
+ setIsExportingMetadata(true);
491
+
492
+ try {
493
+ const response = await fetch(METADATA_EXPORT_ENDPOINT, { method: 'GET' });
494
+
495
+ if (!response.ok) {
496
+ setError(await resolveMetadataTransferErrorMessage(response, 'Failed to export metadata.'));
497
+ return;
498
+ }
499
+
500
+ const filename =
501
+ parseFilenameFromContentDisposition(response.headers.get('Content-Disposition')) ||
502
+ DEFAULT_METADATA_EXPORT_FILENAME;
503
+
504
+ downloadBlob(await response.blob(), filename);
505
+ setSuccessMessage('Metadata export downloaded.');
506
+ } catch (error) {
507
+ setError(error instanceof Error ? error.message : 'Metadata export failed.');
508
+ } finally {
509
+ setIsExportingMetadata(false);
510
+ }
511
+ };
512
+
513
+ /**
514
+ * Opens the native file picker for metadata imports.
515
+ *
516
+ * @private
517
+ */
518
+ const handleImportMetadataClick = () => {
519
+ if (isMetadataTransferBusy) {
520
+ return;
521
+ }
522
+
523
+ importFileInputRef.current?.click();
524
+ };
525
+
526
+ /**
527
+ * Imports selected standalone metadata JSON and refreshes the table.
528
+ *
529
+ * @private
530
+ */
531
+ const handleImportMetadataFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
532
+ const file = event.target.files?.[0];
533
+ if (!file || isMetadataTransferBusy) {
534
+ return;
535
+ }
536
+
537
+ const isConfirmed = await showConfirm({
538
+ title: 'Import metadata',
539
+ message: 'Import metadata from this JSON file? Existing built-in metadata not present in the file will be reset to defaults.',
540
+ confirmLabel: 'Import metadata',
541
+ cancelLabel: 'Cancel',
542
+ }).catch(() => false);
543
+
544
+ if (!isConfirmed) {
545
+ if (importFileInputRef.current) {
546
+ importFileInputRef.current.value = '';
547
+ }
548
+ return;
549
+ }
550
+
551
+ setError(null);
552
+ setSuccessMessage(null);
553
+ setIsImportingMetadata(true);
554
+
555
+ try {
556
+ const response = await fetch(METADATA_IMPORT_ENDPOINT, {
557
+ method: 'POST',
558
+ headers: { 'Content-Type': 'application/json' },
559
+ body: await file.text(),
560
+ });
561
+
562
+ if (!response.ok) {
563
+ setError(await resolveMetadataTransferErrorMessage(response, 'Failed to import metadata.'));
564
+ return;
565
+ }
566
+
567
+ const importSummary = (await response.json()) as MetadataImportSummary;
568
+ await fetchMetadata();
569
+ setSuccessMessage(
570
+ `Imported ${importSummary.importedCount ?? 0} metadata entries and reset ${
571
+ importSummary.resetCount ?? 0
572
+ } built-in defaults.`,
573
+ );
574
+ } catch (error) {
575
+ setError(error instanceof Error ? error.message : 'Metadata import failed.');
576
+ } finally {
577
+ setIsImportingMetadata(false);
578
+ if (importFileInputRef.current) {
579
+ importFileInputRef.current.value = '';
580
+ }
581
+ }
582
+ };
583
+
397
584
  const handleAddSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
398
585
  e.preventDefault();
399
586
  setError(null);
587
+ setSuccessMessage(null);
400
588
 
401
589
  try {
402
590
  await saveMetadata('POST', addFormState);
403
591
  setAddFormState(createEmptyFormState());
404
592
  fetchMetadata();
405
- } catch (err) {
406
- setError(err instanceof Error ? err.message : 'An error occurred');
593
+ } catch (error) {
594
+ setError(error instanceof Error ? error.message : 'An error occurred');
407
595
  }
408
596
  };
409
597
 
@@ -411,6 +599,7 @@ export function MetadataClient() {
411
599
  e.preventDefault();
412
600
  if (editingKey === null) return;
413
601
  setError(null);
602
+ setSuccessMessage(null);
414
603
 
415
604
  try {
416
605
  const isPersistedEntry = metadata.some((entry) => entry.key === editingKey && !entry.isDefault);
@@ -418,8 +607,8 @@ export function MetadataClient() {
418
607
  await saveMetadata(method, editingFormState);
419
608
  handleCancel();
420
609
  fetchMetadata();
421
- } catch (err) {
422
- setError(err instanceof Error ? err.message : 'An error occurred');
610
+ } catch (error) {
611
+ setError(error instanceof Error ? error.message : 'An error occurred');
423
612
  }
424
613
  };
425
614
 
@@ -433,15 +622,16 @@ export function MetadataClient() {
433
622
  };
434
623
 
435
624
  const handleDelete = async (key: string) => {
436
- const confirmed = await showConfirm({
625
+ const isConfirmed = await showConfirm({
437
626
  title: 'Delete metadata',
438
627
  message: 'Are you sure you want to delete this metadata?',
439
628
  confirmLabel: 'Delete metadata',
440
629
  cancelLabel: 'Cancel',
441
630
  }).catch(() => false);
442
- if (!confirmed) return;
631
+ if (!isConfirmed) return;
443
632
 
444
633
  try {
634
+ setSuccessMessage(null);
445
635
  const response = await fetch(`/api/metadata?key=${encodeURIComponent(key)}`, {
446
636
  method: 'DELETE',
447
637
  });
@@ -452,8 +642,8 @@ export function MetadataClient() {
452
642
  }
453
643
 
454
644
  fetchMetadata();
455
- } catch (err) {
456
- setError(err instanceof Error ? err.message : 'An error occurred');
645
+ } catch (error) {
646
+ setError(error instanceof Error ? error.message : 'An error occurred');
457
647
  }
458
648
  };
459
649
 
@@ -549,8 +739,8 @@ export function MetadataClient() {
549
739
 
550
740
  const shortFileUrl = fileUrl.split(LONG_URL).join(SHORT_URL);
551
741
  setFormState((prev) => ({ ...prev, value: shortFileUrl }));
552
- } catch (err) {
553
- setError(err instanceof Error ? err.message : 'Failed to upload image');
742
+ } catch (error) {
743
+ setError(error instanceof Error ? error.message : 'Failed to upload image');
554
744
  } finally {
555
745
  setUploading(false);
556
746
  if (fileInputRef.current) {
@@ -586,6 +776,47 @@ export function MetadataClient() {
586
776
  {error && (
587
777
  <div className="bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded mb-6">{error}</div>
588
778
  )}
779
+ {successMessage && (
780
+ <div className="bg-green-100 border border-green-300 text-green-800 px-4 py-3 rounded mb-6">
781
+ {successMessage}
782
+ </div>
783
+ )}
784
+
785
+ <div className="bg-white shadow rounded-lg p-6">
786
+ <div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
787
+ <div>
788
+ <h2 className="text-xl font-semibold">Import / Export Metadata</h2>
789
+ </div>
790
+ <div className="flex flex-wrap gap-3">
791
+ <button
792
+ type="button"
793
+ onClick={() => void handleExportMetadata()}
794
+ disabled={isMetadataTransferBusy}
795
+ className="inline-flex items-center gap-2 rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-60"
796
+ >
797
+ <DownloadIcon className="h-4 w-4" />
798
+ <span>{isExportingMetadata ? 'Exporting...' : 'Export Metadata'}</span>
799
+ </button>
800
+ <input
801
+ type="file"
802
+ accept="application/json,.json"
803
+ className="hidden"
804
+ ref={importFileInputRef}
805
+ onChange={(event) => void handleImportMetadataFileChange(event)}
806
+ disabled={isMetadataTransferBusy}
807
+ />
808
+ <button
809
+ type="button"
810
+ onClick={handleImportMetadataClick}
811
+ disabled={isMetadataTransferBusy}
812
+ className="inline-flex items-center gap-2 rounded-md border border-gray-300 bg-gray-100 px-4 py-2 text-sm font-medium text-gray-800 transition-colors hover:bg-gray-200 disabled:cursor-not-allowed disabled:opacity-60"
813
+ >
814
+ <UploadIcon className="h-4 w-4" />
815
+ <span>{isImportingMetadata ? 'Importing...' : 'Import Metadata'}</span>
816
+ </button>
817
+ </div>
818
+ </div>
819
+ </div>
589
820
 
590
821
  <div className="rounded-2xl border border-amber-200 bg-amber-50 px-4 py-4 text-sm text-amber-800">
591
822
  Deprecated limit-backed metadata stays visible here for backward compatibility. Update operational