@burtson-labs/bandit-engine 2.0.8

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 (184) hide show
  1. package/LICENSE +43 -0
  2. package/README.md +1235 -0
  3. package/dist/aiProviderStore-YWJHSWFA.mjs +9 -0
  4. package/dist/aiProviderStore-YWJHSWFA.mjs.map +1 -0
  5. package/dist/chat-QXB526NZ.mjs +11 -0
  6. package/dist/chat-QXB526NZ.mjs.map +1 -0
  7. package/dist/chunk-AVC6IZJQ.mjs +2157 -0
  8. package/dist/chunk-AVC6IZJQ.mjs.map +1 -0
  9. package/dist/chunk-BIPELT57.mjs +24183 -0
  10. package/dist/chunk-BIPELT57.mjs.map +1 -0
  11. package/dist/chunk-BJTO5JO5.mjs +11 -0
  12. package/dist/chunk-BJTO5JO5.mjs.map +1 -0
  13. package/dist/chunk-IXIM7BNO.mjs +39 -0
  14. package/dist/chunk-IXIM7BNO.mjs.map +1 -0
  15. package/dist/chunk-KCI46M23.mjs +106 -0
  16. package/dist/chunk-KCI46M23.mjs.map +1 -0
  17. package/dist/chunk-WYS5CZVG.mjs +843 -0
  18. package/dist/chunk-WYS5CZVG.mjs.map +1 -0
  19. package/dist/cli/cli.js +1808 -0
  20. package/dist/cli/cli.js.map +1 -0
  21. package/dist/index.d.mts +1636 -0
  22. package/dist/index.d.ts +1636 -0
  23. package/dist/index.js +40601 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/index.mjs +12477 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/memoryUtils-33TZKHSQ.mjs +223 -0
  28. package/dist/memoryUtils-33TZKHSQ.mjs.map +1 -0
  29. package/dist/modelStore-Y3LZWRQC.mjs +9 -0
  30. package/dist/modelStore-Y3LZWRQC.mjs.map +1 -0
  31. package/dist/shared/custom-element.js +73 -0
  32. package/dist/shared/custom-element.js.map +1 -0
  33. package/dist/shared/custom-element.mjs +8 -0
  34. package/dist/shared/custom-element.mjs.map +1 -0
  35. package/docs/00_intro.md +26 -0
  36. package/docs/01_quickstart.md +54 -0
  37. package/docs/02_gateway_api.md +64 -0
  38. package/docs/03_provider_integration.md +38 -0
  39. package/docs/04_local_dev.md +43 -0
  40. package/docs/05_cli_quickstart.md +89 -0
  41. package/docs/05_contributing.md +33 -0
  42. package/docs/06_busl_licensing.md +25 -0
  43. package/docs/README.md +9 -0
  44. package/docs/api_reference/.nojekyll +1 -0
  45. package/docs/api_reference/assets/highlight.css +141 -0
  46. package/docs/api_reference/assets/icons.js +18 -0
  47. package/docs/api_reference/assets/icons.svg +1 -0
  48. package/docs/api_reference/assets/main.js +60 -0
  49. package/docs/api_reference/assets/navigation.js +1 -0
  50. package/docs/api_reference/assets/search.js +1 -0
  51. package/docs/api_reference/assets/style.css +1493 -0
  52. package/docs/api_reference/classes/DebugLogger.html +29 -0
  53. package/docs/api_reference/classes/FeatureFlagService.html +28 -0
  54. package/docs/api_reference/classes/NotificationService.html +21 -0
  55. package/docs/api_reference/classes/StreamingTTSClient.html +19 -0
  56. package/docs/api_reference/classes/VectorDatabaseService.html +63 -0
  57. package/docs/api_reference/classes/VectorMigrationService.html +27 -0
  58. package/docs/api_reference/classes/VoiceService.html +4 -0
  59. package/docs/api_reference/enums/TTSState.html +6 -0
  60. package/docs/api_reference/functions/Chat.html +1 -0
  61. package/docs/api_reference/functions/ChatModal.html +29 -0
  62. package/docs/api_reference/functions/ChatProvider.html +29 -0
  63. package/docs/api_reference/functions/FeatureFlagProvider.html +30 -0
  64. package/docs/api_reference/functions/FeedbackButton.html +29 -0
  65. package/docs/api_reference/functions/FeedbackModal.html +29 -0
  66. package/docs/api_reference/functions/Management.html +1 -0
  67. package/docs/api_reference/functions/NotificationProvider.html +29 -0
  68. package/docs/api_reference/functions/SubscriptionExpiredGuard.html +31 -0
  69. package/docs/api_reference/functions/SubscriptionExpiredModal.html +31 -0
  70. package/docs/api_reference/functions/defineCustomElement.html +1 -0
  71. package/docs/api_reference/functions/getCriticalConfig.html +1 -0
  72. package/docs/api_reference/functions/getFeatureMatrix.html +1 -0
  73. package/docs/api_reference/functions/getStreamingTTSClient.html +1 -0
  74. package/docs/api_reference/functions/getSystemConstants.html +1 -0
  75. package/docs/api_reference/functions/getTTSState.html +1 -0
  76. package/docs/api_reference/functions/handleHttpError.html +2 -0
  77. package/docs/api_reference/functions/handleSubscriptionUpgrade.html +1 -0
  78. package/docs/api_reference/functions/handleValidationError.html +2 -0
  79. package/docs/api_reference/functions/initializeCoreSystem.html +1 -0
  80. package/docs/api_reference/functions/pauseTTS.html +1 -0
  81. package/docs/api_reference/functions/previewTierUpgrade.html +1 -0
  82. package/docs/api_reference/functions/resumeTTS.html +1 -0
  83. package/docs/api_reference/functions/showInfoNotification.html +2 -0
  84. package/docs/api_reference/functions/showSuccessNotification.html +2 -0
  85. package/docs/api_reference/functions/speakWithStreaming.html +1 -0
  86. package/docs/api_reference/functions/stopTTS.html +1 -0
  87. package/docs/api_reference/functions/syncSubscriptionWithAPI.html +1 -0
  88. package/docs/api_reference/functions/updateSubscriptionTier.html +2 -0
  89. package/docs/api_reference/functions/useFeatureFlag.html +2 -0
  90. package/docs/api_reference/functions/useFeatureVisibility.html +2 -0
  91. package/docs/api_reference/functions/useFeatures.html +2 -0
  92. package/docs/api_reference/functions/useGatewayHealth.html +1 -0
  93. package/docs/api_reference/functions/useGatewayMemory.html +1 -0
  94. package/docs/api_reference/functions/useGatewayModels.html +1 -0
  95. package/docs/api_reference/functions/useGlobalTTS.html +2 -0
  96. package/docs/api_reference/functions/useNotification.html +1 -0
  97. package/docs/api_reference/functions/useNotificationService.html +6 -0
  98. package/docs/api_reference/functions/useTTS.html +2 -0
  99. package/docs/api_reference/functions/useVectorStore.html +13 -0
  100. package/docs/api_reference/functions/useVoiceStore.html +8 -0
  101. package/docs/api_reference/functions/useVoices.html +3 -0
  102. package/docs/api_reference/functions/validateEnvironment.html +1 -0
  103. package/docs/api_reference/functions/validateSystemIntegrity.html +1 -0
  104. package/docs/api_reference/index.html +756 -0
  105. package/docs/api_reference/interfaces/AIChatRequest.html +8 -0
  106. package/docs/api_reference/interfaces/AIChatResponse.html +3 -0
  107. package/docs/api_reference/interfaces/AIGenerateRequest.html +5 -0
  108. package/docs/api_reference/interfaces/AIGenerateResponse.html +3 -0
  109. package/docs/api_reference/interfaces/AIMessage.html +4 -0
  110. package/docs/api_reference/interfaces/AIModel.html +6 -0
  111. package/docs/api_reference/interfaces/AIProviderConfig.html +9 -0
  112. package/docs/api_reference/interfaces/ChatConfig.html +5 -0
  113. package/docs/api_reference/interfaces/CreateMemoryOptions.html +7 -0
  114. package/docs/api_reference/interfaces/FeatureEvaluation.html +14 -0
  115. package/docs/api_reference/interfaces/FeatureFlagConfig.html +18 -0
  116. package/docs/api_reference/interfaces/FeatureFlagContextValue.html +16 -0
  117. package/docs/api_reference/interfaces/FeatureFlagProviderProps.html +4 -0
  118. package/docs/api_reference/interfaces/FeedbackButtonProps.html +19 -0
  119. package/docs/api_reference/interfaces/FeedbackCategories.html +6 -0
  120. package/docs/api_reference/interfaces/FeedbackModalProps.html +4 -0
  121. package/docs/api_reference/interfaces/FeedbackPriorities.html +5 -0
  122. package/docs/api_reference/interfaces/FeedbackRequest.html +12 -0
  123. package/docs/api_reference/interfaces/FeedbackResponse.html +7 -0
  124. package/docs/api_reference/interfaces/FileUploadResult.html +4 -0
  125. package/docs/api_reference/interfaces/GatewayChatRequest.html +12 -0
  126. package/docs/api_reference/interfaces/GatewayChatResponse.html +7 -0
  127. package/docs/api_reference/interfaces/GatewayContract.html +4 -0
  128. package/docs/api_reference/interfaces/GatewayGenerateRequest.html +8 -0
  129. package/docs/api_reference/interfaces/GatewayGenerateResponse.html +12 -0
  130. package/docs/api_reference/interfaces/GatewayHealthResponse.html +5 -0
  131. package/docs/api_reference/interfaces/GatewayMemoryRecord.html +7 -0
  132. package/docs/api_reference/interfaces/GatewayMemoryResponse.html +4 -0
  133. package/docs/api_reference/interfaces/GatewayMessage.html +5 -0
  134. package/docs/api_reference/interfaces/GatewayMessageContent.html +4 -0
  135. package/docs/api_reference/interfaces/GatewayModel.html +9 -0
  136. package/docs/api_reference/interfaces/GatewayModelsResponse.html +2 -0
  137. package/docs/api_reference/interfaces/MemorySearchFilters.html +5 -0
  138. package/docs/api_reference/interfaces/MigrationProgress.html +6 -0
  139. package/docs/api_reference/interfaces/MigrationStatus.html +7 -0
  140. package/docs/api_reference/interfaces/NotificationConfig.html +5 -0
  141. package/docs/api_reference/interfaces/NotificationContextType.html +6 -0
  142. package/docs/api_reference/interfaces/NotificationProviderProps.html +4 -0
  143. package/docs/api_reference/interfaces/PackageSettings.html +11 -0
  144. package/docs/api_reference/interfaces/SearchOptions.html +6 -0
  145. package/docs/api_reference/interfaces/SearchResult.html +5 -0
  146. package/docs/api_reference/interfaces/SubscriptionExpiredGuardProps.html +7 -0
  147. package/docs/api_reference/interfaces/SubscriptionExpiredModalProps.html +6 -0
  148. package/docs/api_reference/interfaces/TTSOptions.html +5 -0
  149. package/docs/api_reference/interfaces/TTSProgress.html +6 -0
  150. package/docs/api_reference/interfaces/TrialUsage.html +6 -0
  151. package/docs/api_reference/interfaces/UploadRequest.html +9 -0
  152. package/docs/api_reference/interfaces/UseTTSReturn.html +14 -0
  153. package/docs/api_reference/interfaces/VectorDocument.html +11 -0
  154. package/docs/api_reference/interfaces/VectorMemory.html +12 -0
  155. package/docs/api_reference/interfaces/VectorMemoryMetadata.html +7 -0
  156. package/docs/api_reference/interfaces/VectorStoreStatus.html +7 -0
  157. package/docs/api_reference/interfaces/VoiceModelsResponse.html +4 -0
  158. package/docs/api_reference/interfaces/VoiceState.html +12 -0
  159. package/docs/api_reference/media/02_gateway_api.md +64 -0
  160. package/docs/api_reference/media/05_cli_quickstart.md +89 -0
  161. package/docs/api_reference/media/LICENSE +43 -0
  162. package/docs/api_reference/media/PRE-PUSH-CHECKLIST.md +10 -0
  163. package/docs/api_reference/media/PROTECTION-NOTICE.md +60 -0
  164. package/docs/api_reference/media/PROTECTION-README.md +41 -0
  165. package/docs/api_reference/media/README-1.md +23 -0
  166. package/docs/api_reference/media/README.md +9 -0
  167. package/docs/api_reference/modules.html +123 -0
  168. package/docs/api_reference/types/FeatureKey.html +2 -0
  169. package/docs/api_reference/types/FeatureMatrix.html +2 -0
  170. package/docs/api_reference/types/GatewayQueryOptions.html +1 -0
  171. package/docs/api_reference/types/LogContext.html +14 -0
  172. package/docs/api_reference/types/SubscriptionTier.html +2 -0
  173. package/docs/api_reference/variables/DEFAULT_TIER_FEATURES.html +2 -0
  174. package/docs/api_reference/variables/FeatureFlagContext.html +1 -0
  175. package/docs/api_reference/variables/OSS_DEFAULT_FEATURES.html +2 -0
  176. package/docs/api_reference/variables/SYSTEM_FLAGS.html +1 -0
  177. package/docs/api_reference/variables/authenticationService.html +1 -0
  178. package/docs/api_reference/variables/debugLogger-1.html +1 -0
  179. package/docs/api_reference/variables/featureFlagService-1.html +2 -0
  180. package/docs/api_reference/variables/notificationService-1.html +1 -0
  181. package/docs/api_reference/variables/vectorDatabaseService-1.html +1 -0
  182. package/docs/api_reference/variables/vectorMigrationService-1.html +1 -0
  183. package/docs/api_reference/variables/voiceService-1.html +1 -0
  184. package/package.json +103 -0
@@ -0,0 +1,1808 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __copyProps = (to, from, except, desc) => {
10
+ if (from && typeof from === "object" || typeof from === "function") {
11
+ for (let key of __getOwnPropNames(from))
12
+ if (!__hasOwnProp.call(to, key) && key !== except)
13
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
14
+ }
15
+ return to;
16
+ };
17
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
18
+ // If the importer is in node compatibility mode or this is not an ESM
19
+ // file that has been converted to a CommonJS file using a Babel-
20
+ // compatible transform (i.e. "__esModule" has not been set), then set
21
+ // "default" to the CommonJS "module.exports" for node compatibility.
22
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
+ mod
24
+ ));
25
+
26
+ // src/cli/index.ts
27
+ var import_node_path3 = __toESM(require("path"));
28
+ var import_commander = require("commander");
29
+
30
+ // package.json
31
+ var package_default = {
32
+ name: "@burtson-labs/bandit-engine",
33
+ version: "2.0.8",
34
+ license: "BUSL-1.1",
35
+ main: "dist/index.js",
36
+ module: "dist/index.mjs",
37
+ types: "dist/index.d.ts",
38
+ bin: {
39
+ bandit: "dist/cli/cli.js"
40
+ },
41
+ files: [
42
+ "dist",
43
+ "dist/cli",
44
+ "docs",
45
+ "LICENSE",
46
+ "README.md"
47
+ ],
48
+ repository: {
49
+ type: "git",
50
+ url: "https://github.com/Burtson-Labs/bandit.git",
51
+ directory: "packages/bandit-engine"
52
+ },
53
+ author: "Burtson Labs LLC <team@burtson.ai>",
54
+ maintainers: [
55
+ {
56
+ name: "Burtson Labs LLC",
57
+ email: "team@burtson.ai"
58
+ }
59
+ ],
60
+ scripts: {
61
+ build: "tsup",
62
+ dev: "tsup --watch",
63
+ docs: "typedoc src --out docs/api_reference --skipErrorChecking",
64
+ lint: 'eslint "src/**/*.{ts,tsx}"',
65
+ test: "vitest",
66
+ protect: "node scripts/add-license-headers.js",
67
+ "validate-protection": "node scripts/validate-protection.js"
68
+ },
69
+ overrides: {
70
+ "sha.js": "^2.4.12"
71
+ },
72
+ dependencies: {
73
+ "@emotion/react": "^11.14.0",
74
+ "@emotion/styled": "^11.14.0",
75
+ "@hello-pangea/dnd": "^18.0.1",
76
+ "@mui/icons-material": "^7.0.1",
77
+ "@mui/material": "^7.1.0",
78
+ "@tanstack/react-query": "^5.66.3",
79
+ axios: "^1.7.9",
80
+ "crypto-browserify": "^3.12.1",
81
+ commander: "^12.1.0",
82
+ "fs-extra": "^11.3.0",
83
+ idb: "latest",
84
+ mammoth: "^1.9.0",
85
+ "pdfjs-dist": "^5.2.133",
86
+ prompts: "^2.4.2",
87
+ react: "^19.0.0",
88
+ "react-dom": "^19.0.0",
89
+ "react-markdown": "^10.1.0",
90
+ "react-router-dom": "^7.5.0",
91
+ "rehype-raw": "^7.0.0",
92
+ "rehype-sanitize": "^6.0.0",
93
+ lowlight: "^3.1.0",
94
+ "highlight.js": "^11.10.0",
95
+ "remark-gfm": "^4.0.1",
96
+ "remark-mark": "^0.0.0",
97
+ rxjs: "^7.8.2",
98
+ uuid: "^11.1.0",
99
+ zustand: "^4.5.6"
100
+ },
101
+ devDependencies: {
102
+ "@esbuild-plugins/node-globals-polyfill": "^0.2.3",
103
+ "@testing-library/jest-dom": "^6.6.3",
104
+ "@testing-library/react": "^16.3.0",
105
+ "@types/node": "^24.0.3",
106
+ "@types/react": "^18.3.22",
107
+ "@types/react-dom": "^18.2.17",
108
+ "@types/react-router-dom": "^5.3.3",
109
+ "@types/fs-extra": "^11.0.4",
110
+ "@types/prompts": "^2.4.9",
111
+ "@types/uuid": "^10.0.0",
112
+ "@vitejs/plugin-react": "^4.6.0",
113
+ "@vitest/ui": "^3.2.4",
114
+ typedoc: "^0.26.11",
115
+ eslint: "^8.57.0",
116
+ "eslint-plugin-react": "^7.34.1",
117
+ jsdom: "^26.1.0",
118
+ tsup: "^8.5.0",
119
+ typescript: "5.5.4",
120
+ vitest: "^3.2.4"
121
+ },
122
+ peerDependencies: {
123
+ react: ">=18",
124
+ "@mui/material": ">=5",
125
+ zustand: ">=4"
126
+ },
127
+ exports: {
128
+ ".": {
129
+ import: "./dist/index.mjs",
130
+ require: "./dist/index.js"
131
+ }
132
+ }
133
+ };
134
+
135
+ // src/cli/createQuickstart.ts
136
+ var import_node_path2 = __toESM(require("path"));
137
+ var import_fs_extra = __toESM(require("fs-extra"));
138
+ var import_prompts = __toESM(require("prompts"));
139
+
140
+ // src/cli/utils.ts
141
+ var import_node_path = __toESM(require("path"));
142
+ var toKebabCase = (value) => {
143
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/[\s_./]+/g, "-").replace(/[^a-zA-Z0-9-]+/g, "").replace(/-{2,}/g, "-").replace(/^-+|-+$/g, "").toLowerCase();
144
+ };
145
+ var toTitleCase = (value) => {
146
+ const cleaned = value.replace(/[-_/]+/g, " ").replace(/\s+/g, " ").trim();
147
+ if (!cleaned) {
148
+ return "";
149
+ }
150
+ return cleaned.replace(/\b\w/g, (char) => char.toUpperCase());
151
+ };
152
+ var formatJson = (value) => `${JSON.stringify(value, null, 2)}
153
+ `;
154
+ var sanitizeModelIdentifier = (value) => {
155
+ const trimmed = value.trim();
156
+ if (!trimmed.includes(":")) {
157
+ return trimmed.toLowerCase();
158
+ }
159
+ const [provider, model] = trimmed.split(/:(.+)/).filter(Boolean);
160
+ const cleanModel = model.replace(/[^a-zA-Z0-9_.-]/g, "-").replace(/-+/g, "-").toLowerCase();
161
+ return `${provider.toLowerCase()}:${cleanModel}`;
162
+ };
163
+ var normalizeLineEndings = (content) => content.replace(/\r\n/g, "\n");
164
+ var ensureTrailingNewline = (content) => content.endsWith("\n") ? content : `${content}
165
+ `;
166
+
167
+ // src/cli/templates.ts
168
+ var QUOTE = '"';
169
+ var buildPackageJson = (ctx) => formatJson({
170
+ name: ctx.packageName,
171
+ private: true,
172
+ version: "0.1.0",
173
+ type: "module",
174
+ scripts: {
175
+ dev: 'concurrently -k "npm run dev:gateway" "npm run dev:web"',
176
+ "dev:web": "vite",
177
+ "dev:gateway": "node server/gateway.js",
178
+ build: "vite build",
179
+ preview: "vite preview"
180
+ },
181
+ dependencies: {
182
+ "@burtson-labs/bandit-engine": `^${ctx.engineVersion}`,
183
+ "@emotion/react": "^11.14.0",
184
+ "@emotion/styled": "^11.14.0",
185
+ "@mui/material": "^7.1.0",
186
+ "@tanstack/react-query": "^5.59.20",
187
+ "cors": "^2.8.5",
188
+ "dotenv": "^16.4.5",
189
+ "express": "^4.19.2",
190
+ "react": "^19.0.0",
191
+ "react-dom": "^19.0.0",
192
+ "react-router-dom": "^7.5.0",
193
+ "zustand": "^4.5.6"
194
+ },
195
+ devDependencies: {
196
+ "@types/express": "^4.17.21",
197
+ "@types/node": "^20.17.7",
198
+ "@types/react": "^18.3.22",
199
+ "@types/react-dom": "^18.2.18",
200
+ "@vitejs/plugin-react": "^5.0.0",
201
+ "concurrently": "^8.2.2",
202
+ "typescript": "^5.5.4",
203
+ "vite": "^7.1.9"
204
+ }
205
+ });
206
+ var buildEnvExample = (ctx) => ensureTrailingNewline(
207
+ normalizeLineEndings(
208
+ `# Frontend configuration
209
+ VITE_DEV_PORT=${ctx.frontendPort}
210
+ VITE_GATEWAY_URL=${ctx.defaultGatewayUrl}
211
+ VITE_DEFAULT_MODEL=${ctx.defaultModelId}
212
+ VITE_FALLBACK_MODEL=${ctx.fallbackModelId ?? ""}
213
+ VITE_GATEWAY_PROVIDER=${ctx.defaultProvider}
214
+ VITE_BRANDING_TEXT=${ctx.brandingText}
215
+
216
+ # Gateway configuration
217
+ # OPENAI_API_KEY=sk-................................
218
+ # OLLAMA_URL=http://localhost:11434
219
+ # PORT=${ctx.gatewayPort}
220
+ `
221
+ )
222
+ );
223
+ var buildTsConfig = () => formatJson({
224
+ compilerOptions: {
225
+ target: "ESNext",
226
+ useDefineForClassFields: true,
227
+ lib: ["DOM", "DOM.Iterable", "ESNext"],
228
+ allowJs: false,
229
+ skipLibCheck: true,
230
+ esModuleInterop: true,
231
+ allowSyntheticDefaultImports: true,
232
+ strict: true,
233
+ forceConsistentCasingInFileNames: true,
234
+ module: "ESNext",
235
+ moduleResolution: "Node",
236
+ resolveJsonModule: true,
237
+ isolatedModules: true,
238
+ noEmit: true,
239
+ jsx: "react-jsx"
240
+ },
241
+ include: ["src"]
242
+ });
243
+ var buildEnvDts = () => ensureTrailingNewline(
244
+ `/// <reference types="vite/client" />
245
+
246
+ interface ImportMetaEnv {
247
+ readonly VITE_GATEWAY_URL?: string;
248
+ readonly VITE_GATEWAY_PROVIDER?: string;
249
+ readonly VITE_DEFAULT_MODEL?: string;
250
+ readonly VITE_FALLBACK_MODEL?: string;
251
+ readonly VITE_FEEDBACK_EMAIL?: string;
252
+ readonly VITE_BRANDING_TEXT?: string;
253
+ }
254
+
255
+ interface ImportMeta {
256
+ readonly env: ImportMetaEnv;
257
+ }
258
+ `
259
+ );
260
+ var buildViteConfig = (ctx) => ensureTrailingNewline(
261
+ normalizeLineEndings(
262
+ `import { defineConfig, loadEnv } from "vite";
263
+ import react from "@vitejs/plugin-react";
264
+
265
+ export default defineConfig(({ mode }) => {
266
+ const env = loadEnv(mode, process.cwd(), "");
267
+ const parsedPort = Number(env.VITE_DEV_PORT || env.PORT || ${ctx.frontendPort});
268
+ const port = Number.isFinite(parsedPort) ? parsedPort : ${ctx.frontendPort};
269
+
270
+ return {
271
+ plugins: [react()],
272
+ resolve: {
273
+ dedupe: [
274
+ "react",
275
+ "react-dom",
276
+ "@mui/material",
277
+ "@mui/system",
278
+ "@emotion/react",
279
+ "@emotion/styled",
280
+ "react-router-dom"
281
+ ],
282
+ },
283
+ optimizeDeps: {
284
+ include: ["@burtson-labs/bandit-engine"],
285
+ },
286
+ server: {
287
+ port,
288
+ },
289
+ };
290
+ });
291
+ `
292
+ )
293
+ );
294
+ var buildMainTsx = () => ensureTrailingNewline(
295
+ normalizeLineEndings(
296
+ `import React from "react";
297
+ import ReactDOM from "react-dom/client";
298
+ import { BrowserRouter } from "react-router-dom";
299
+ import App from "./App";
300
+ import "./index.css";
301
+
302
+ ReactDOM.createRoot(document.getElementById("root")!).render(
303
+ <React.StrictMode>
304
+ <BrowserRouter>
305
+ <App />
306
+ </BrowserRouter>
307
+ </React.StrictMode>
308
+ );
309
+ `
310
+ )
311
+ );
312
+ var buildIndexCss = () => ensureTrailingNewline(
313
+ normalizeLineEndings(
314
+ `:root {
315
+ font-family: "Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
316
+ background: radial-gradient(circle at top left, rgba(120, 119, 255, 0.12), transparent 45%),
317
+ radial-gradient(circle at bottom right, rgba(244, 114, 182, 0.1), transparent 55%),
318
+ #05070f;
319
+ color: #f8fafc;
320
+ min-height: 100vh;
321
+ }
322
+
323
+ body {
324
+ margin: 0;
325
+ }
326
+
327
+ * {
328
+ box-sizing: border-box;
329
+ }
330
+ `
331
+ )
332
+ );
333
+ var buildIndexHtml = () => ensureTrailingNewline(
334
+ normalizeLineEndings(
335
+ `<!doctype html>
336
+ <html lang="en">
337
+ <head>
338
+ <meta charset="UTF-8" />
339
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
340
+ <link rel="icon" href="https://cdn.burtson.ai/images/bandit-head.png" />
341
+ <title>Bandit Quickstart</title>
342
+ </head>
343
+ <body>
344
+ <div id="root"></div>
345
+ <script type="module" src="/src/main.tsx"></script>
346
+ </body>
347
+ </html>
348
+ `
349
+ )
350
+ );
351
+ var buildThemeTs = () => ensureTrailingNewline(
352
+ normalizeLineEndings(
353
+ `import { createTheme } from "@mui/material/styles";
354
+
355
+ export const banditQuickstartTheme = createTheme({
356
+ palette: {
357
+ mode: "dark",
358
+ primary: {
359
+ main: "#f97316",
360
+ },
361
+ secondary: {
362
+ main: "#6366f1",
363
+ },
364
+ background: {
365
+ default: "#05070f",
366
+ paper: "rgba(15, 23, 42, 0.78)",
367
+ },
368
+ },
369
+ typography: {
370
+ fontFamily: '"Inter", system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif',
371
+ h1: {
372
+ fontWeight: 700,
373
+ },
374
+ h2: {
375
+ fontWeight: 600,
376
+ },
377
+ },
378
+ components: {
379
+ MuiPaper: {
380
+ styleOverrides: {
381
+ root: {
382
+ backdropFilter: "blur(18px)",
383
+ backgroundImage: "linear-gradient(145deg, rgba(15, 23, 42, 0.92), rgba(2, 6, 23, 0.92))",
384
+ },
385
+ },
386
+ },
387
+ },
388
+ });
389
+ `
390
+ )
391
+ );
392
+ var buildAppTsx = (ctx) => {
393
+ const responseStatusExpr = "${response.status}";
394
+ const gatewayErrorExpr = '${gatewayError ?? "Unknown"}';
395
+ const template = `
396
+ import { useEffect, useState, useMemo, useCallback, type ReactNode } from "react";
397
+ import {
398
+ CssBaseline,
399
+ AppBar,
400
+ Toolbar,
401
+ Typography,
402
+ Container,
403
+ Box,
404
+ Button,
405
+ Chip,
406
+ Tooltip,
407
+ Stack,
408
+ Card,
409
+ CardContent
410
+ } from "@mui/material";
411
+ import { ThemeProvider } from "@mui/material/styles";
412
+ import { Routes, Route, Navigate, Link as RouterLink, useLocation } from "react-router-dom";
413
+ import { ChatProvider, Chat, ChatModal, Management } from "@burtson-labs/bandit-engine";
414
+ import * as BanditEngine from "@burtson-labs/bandit-engine";
415
+ import { banditQuickstartTheme } from "./theme";
416
+
417
+ const gatewayBaseUrl = (import.meta.env.VITE_GATEWAY_URL ?? "${ctx.defaultGatewayUrl}").replace(/\\/$/, "");
418
+ const defaultModelId = import.meta.env.VITE_DEFAULT_MODEL ?? "${ctx.defaultModelId}";
419
+ const fallbackModelId = import.meta.env.VITE_FALLBACK_MODEL ?? ${ctx.fallbackModelId ? `${QUOTE}${ctx.fallbackModelId}${QUOTE}` : "undefined"};
420
+ const brandingText = import.meta.env.VITE_BRANDING_TEXT ?? "${ctx.brandingText}";
421
+ const provider = (import.meta.env.VITE_GATEWAY_PROVIDER ?? "${ctx.defaultProvider}") as "openai" | "ollama";
422
+
423
+ const gatewayApiUrl = gatewayBaseUrl.endsWith("/api") ? gatewayBaseUrl : gatewayBaseUrl + "/api";
424
+ const banditHeadLogoUrl = "https://cdn.burtson.ai/images/bandit-head.png";
425
+ const burtsonLabsLogoUrl = "https://cdn.burtson.ai/logos/burtson-labs-logo-alt.png";
426
+ const healthEndpoint = gatewayApiUrl + "/health";
427
+
428
+ // Move packageSettings outside the component to prevent recreation on every render
429
+ const packageSettings = {
430
+ defaultModel: defaultModelId,
431
+ fallbackModel: fallbackModelId,
432
+ gatewayApiUrl: gatewayApiUrl,
433
+ brandingConfigUrl: "/config.json",
434
+ aiProvider: {
435
+ type: "gateway" as const,
436
+ gatewayUrl: gatewayApiUrl,
437
+ provider,
438
+ tokenFactory: () => {
439
+ return localStorage.getItem("authToken");
440
+ }
441
+ },
442
+ feedbackEmail: import.meta.env.VITE_FEEDBACK_EMAIL,
443
+ featureFlags: {
444
+ subscriptionType: "premium" as const,
445
+ rolesClaimKey: "roles",
446
+ subscriptionTypeClaimKey: "subscriptionType",
447
+ isSubscribedClaimKey: "isSubscribed",
448
+ jwtStorageKey: "authToken",
449
+ adminRole: "admin",
450
+ debug: true,
451
+ featureMatrix: {
452
+ tts: false,
453
+ stt: false,
454
+ semanticSearchSimple: false,
455
+ semanticSearchPremium: false,
456
+ advancedSearch: false,
457
+ advancedMemories: false,
458
+ },
459
+ },
460
+ };
461
+
462
+ function App() {
463
+ const location = useLocation();
464
+ const [isModalOpen, setIsModalOpen] = useState(false);
465
+ const [gatewayStatus, setGatewayStatus] = useState<"checking" | "healthy" | "error">("checking");
466
+ const [gatewayError, setGatewayError] = useState<string | null>(null);
467
+
468
+ useEffect(() => {
469
+ if (typeof window !== "undefined") {
470
+ const applyAuthToken = (token: string) => {
471
+ localStorage.setItem("authToken", token);
472
+ const maybeService = (BanditEngine as { authenticationService?: { setToken: (token: string) => void } }).authenticationService;
473
+ try {
474
+ maybeService?.setToken(token);
475
+ } catch (error) {
476
+ console.warn("Bandit quickstart: failed to seed authentication service token", error);
477
+ }
478
+ };
479
+
480
+ const ensureAuthToken = () => {
481
+ const existing = localStorage.getItem("authToken");
482
+ if (existing) {
483
+ applyAuthToken(existing);
484
+ return;
485
+ }
486
+
487
+ const header = {
488
+ alg: "HS256",
489
+ typ: "JWT",
490
+ };
491
+ const payload = {
492
+ exp: Math.floor(Date.now() / 1000) + 60 * 60 * 8,
493
+ roles: ["admin"],
494
+ iat: Math.floor(Date.now() / 1000),
495
+ email: "quickstart@burtson.ai",
496
+ sub: "123456789012345678901",
497
+ };
498
+ const encodeSegment = (value: unknown) =>
499
+ btoa(JSON.stringify(value))
500
+ .replace(/=+$/g, "")
501
+ .replace(/\\+/g, "-")
502
+ .replace(/\\//g, "_");
503
+ const mockToken = \`${"${"}encodeSegment(header)}.${"${"}encodeSegment(payload)}.quickstart\`;
504
+ applyAuthToken(mockToken);
505
+ };
506
+
507
+ ensureAuthToken();
508
+ }
509
+ }, []);
510
+
511
+ // Separate effect for health checking to avoid re-renders
512
+ useEffect(() => {
513
+ let cancelled = false;
514
+
515
+ const checkHealth = async () => {
516
+ try {
517
+ const response = await fetch(healthEndpoint, { headers: { "Content-Type": "application/json" } });
518
+ if (!response.ok) {
519
+ throw new Error(\`HTTP __RESPONSE_STATUS__\`);
520
+ }
521
+ await response.json();
522
+ if (!cancelled) {
523
+ setGatewayStatus(prevStatus => prevStatus !== "healthy" ? "healthy" : prevStatus);
524
+ setGatewayError(prevError => prevError !== null ? null : prevError);
525
+ }
526
+ } catch (error) {
527
+ if (!cancelled) {
528
+ const errorMessage = error instanceof Error ? error.message : String(error);
529
+ setGatewayStatus(prevStatus => prevStatus !== "error" ? "error" : prevStatus);
530
+ setGatewayError(prevError => prevError !== errorMessage ? errorMessage : prevError);
531
+ }
532
+ }
533
+ };
534
+
535
+ // Initial check
536
+ checkHealth();
537
+
538
+ // Set up interval for periodic checks
539
+ const interval = setInterval(checkHealth, 15000);
540
+
541
+ return () => {
542
+ cancelled = true;
543
+ clearInterval(interval);
544
+ };
545
+ }, []);
546
+
547
+ const gatewayChip = useMemo(() => (
548
+ <Tooltip
549
+ title={
550
+ gatewayStatus === "error"
551
+ ? \`Gateway health check failed. Last error: __GATEWAY_ERROR__\`
552
+ : gatewayStatus === "healthy"
553
+ ? "Gateway reachable"
554
+ : "Checking gateway health..."
555
+ }
556
+ >
557
+ <Chip
558
+ size="small"
559
+ color={gatewayStatus === "healthy" ? "success" : gatewayStatus === "error" ? "error" : "default"}
560
+ variant={gatewayStatus === "healthy" ? "filled" : "outlined"}
561
+ label={
562
+ gatewayStatus === "healthy"
563
+ ? "Gateway: Healthy"
564
+ : gatewayStatus === "error"
565
+ ? "Gateway: Unreachable"
566
+ : "Gateway: Checking..."
567
+ }
568
+ sx={{ fontWeight: 600 }}
569
+ />
570
+ </Tooltip>
571
+ ), [gatewayStatus, gatewayError]);
572
+
573
+ const handleOpenModal = useCallback(() => setIsModalOpen(true), []);
574
+ const handleCloseModal = useCallback(() => setIsModalOpen(false), []);
575
+
576
+ const HomePage = ({ onOpenModal }: { onOpenModal: () => void }) => (
577
+ <Container maxWidth="lg" sx={{ py: { xs: 4, md: 6 } }}>
578
+ <Stack spacing={{ xs: 5, md: 7 }}>
579
+ <Box
580
+ sx={{
581
+ display: "flex",
582
+ flexDirection: { xs: "column-reverse", md: "row" },
583
+ alignItems: "center",
584
+ gap: { xs: 4, md: 8 },
585
+ }}
586
+ >
587
+ <Stack spacing={3} sx={{ flex: 1, width: "100%" }}>
588
+ <Box sx={{ display: "flex", alignItems: "center", gap: 2 }}>
589
+ <Box
590
+ component="img"
591
+ src={banditHeadLogoUrl}
592
+ alt="Bandit AI logo"
593
+ sx={{ height: 48, width: 48, borderRadius: 3, boxShadow: "0 18px 50px rgba(99, 102, 241, 0.35)" }}
594
+ />
595
+ <Typography variant="overline" color="primary.light" sx={{ letterSpacing: 2 }}>
596
+ Powered by Bandit Engine
597
+ </Typography>
598
+ </Box>
599
+ <Typography variant="h3" fontWeight={700}>
600
+ {brandingText}
601
+ </Typography>
602
+ <Typography variant="body1" color="text.secondary">
603
+ Build, brand, and launch your assistant with a drop-in chat surface plus a secure gateway for OpenAI or Ollama.
604
+ </Typography>
605
+ <Stack direction={{ xs: "column", sm: "row" }} spacing={2}>
606
+ <Button component={RouterLink} to="/chat" variant="contained" color="primary">
607
+ Go to chat demo
608
+ </Button>
609
+ <Button variant="outlined" color="secondary" onClick={onOpenModal}>
610
+ Open modal assistant
611
+ </Button>
612
+ </Stack>
613
+ </Stack>
614
+ <Box
615
+ component="img"
616
+ src={burtsonLabsLogoUrl}
617
+ alt="Burtson Labs logo"
618
+ sx={{
619
+ width: "100%",
620
+ maxWidth: 320,
621
+ mx: { xs: "auto", md: 0 },
622
+ display: "block",
623
+ filter: "drop-shadow(0 25px 45px rgba(15, 23, 42, 0.45))",
624
+ }}
625
+ />
626
+ </Box>
627
+ <Box
628
+ sx={{
629
+ display: "grid",
630
+ gap: 3,
631
+ gridTemplateColumns: { xs: "1fr", md: "repeat(3, minmax(0, 1fr))" },
632
+ }}
633
+ >
634
+ <Card sx={{ height: "100%", backdropFilter: "blur(12px)", backgroundColor: "rgba(15, 23, 42, 0.64)" }}>
635
+ <CardContent>
636
+ <Typography variant="h6" gutterBottom>
637
+ Configure in minutes
638
+ </Typography>
639
+ <Typography variant="body2" color="text.secondary">
640
+ Edit <code>public/config.json</code> and <code>.env</code> to tailor models, personas, and branding for your product.
641
+ </Typography>
642
+ </CardContent>
643
+ </Card>
644
+ <Card sx={{ height: "100%", backdropFilter: "blur(12px)", backgroundColor: "rgba(15, 23, 42, 0.64)" }}>
645
+ <CardContent>
646
+ <Typography variant="h6" gutterBottom>
647
+ Ship secure gateways
648
+ </Typography>
649
+ <Typography variant="body2" color="text.secondary">
650
+ Keep API keys server-side while proxying requests to OpenAI or Ollama through the included Express gateway.
651
+ </Typography>
652
+ </CardContent>
653
+ </Card>
654
+ <Card sx={{ height: "100%", backdropFilter: "blur(12px)", backgroundColor: "rgba(15, 23, 42, 0.64)" }}>
655
+ <CardContent>
656
+ <Typography variant="h6" gutterBottom>
657
+ Manage knowledge freely
658
+ </Typography>
659
+ <Typography variant="body2" color="text.secondary">
660
+ Add memories, files, and tools directly in the management console that ships with this quickstart.
661
+ </Typography>
662
+ <Button component={RouterLink} to="/management" variant="text" color="secondary" sx={{ mt: 2, px: 0 }}>
663
+ Explore management console
664
+ </Button>
665
+ </CardContent>
666
+ </Card>
667
+ </Box>
668
+ </Stack>
669
+ </Container>
670
+ );
671
+
672
+ const ChatPage = ({ onOpenModal }: { onOpenModal: () => void }) => (
673
+ <Container maxWidth="lg" sx={{ py: 4, display: "flex", flexDirection: "column", gap: 3 }}>
674
+ <Stack
675
+ direction={{ xs: "column", md: "row" }}
676
+ spacing={2}
677
+ alignItems={{ xs: "stretch", md: "center" }}
678
+ justifyContent="space-between"
679
+ >
680
+ <Box>
681
+ <Typography variant="h4" fontWeight={700} gutterBottom>
682
+ Chat demo
683
+ </Typography>
684
+ <Typography variant="body2" color="text.secondary">
685
+ This route renders the full <code>{\`<Chat />\`}</code> surface powered by your quickstart gateway.
686
+ </Typography>
687
+ </Box>
688
+ <Stack direction={{ xs: "column", sm: "row" }} spacing={1.5}>
689
+ <Button component={RouterLink} to="/management" variant="outlined" color="secondary">
690
+ Management console
691
+ </Button>
692
+ <Button variant="contained" color="primary" onClick={onOpenModal}>
693
+ Open modal assistant
694
+ </Button>
695
+ </Stack>
696
+ </Stack>
697
+ <Box
698
+ sx={{
699
+ flexGrow: 1,
700
+ minHeight: 540,
701
+ borderRadius: 3,
702
+ overflow: "hidden",
703
+ boxShadow: "0 35px 90px rgba(15, 23, 42, 0.55)",
704
+ }}
705
+ >
706
+ <Chat />
707
+ </Box>
708
+ </Container>
709
+ );
710
+
711
+ const Header = ({ gatewayChip }: { gatewayChip: ReactNode }) => (
712
+ <AppBar
713
+ position="sticky"
714
+ color="transparent"
715
+ elevation={0}
716
+ sx={{ borderBottom: "1px solid rgba(148, 163, 184, 0.16)", backdropFilter: "blur(18px)" }}
717
+ >
718
+ <Toolbar sx={{ gap: 2, flexWrap: "wrap" }}>
719
+ <Button
720
+ component={RouterLink}
721
+ to="/"
722
+ color="inherit"
723
+ sx={{
724
+ display: "flex",
725
+ alignItems: "center",
726
+ gap: 1.5,
727
+ py: 1,
728
+ px: 1.5,
729
+ borderRadius: 2,
730
+ textTransform: "none",
731
+ bgcolor: "transparent",
732
+ "&:hover": { bgcolor: "rgba(99, 102, 241, 0.12)" },
733
+ }}
734
+ >
735
+ <Typography variant="h6" sx={{ fontWeight: 600 }}>
736
+ {brandingText}
737
+ </Typography>
738
+ </Button>
739
+ <Box sx={{ flexGrow: 1 }} />
740
+ {gatewayChip}
741
+ <Stack direction="row" spacing={1} flexWrap="wrap" justifyContent="flex-end">
742
+ <Button component={RouterLink} to="/" color="inherit">
743
+ Home
744
+ </Button>
745
+ <Button component={RouterLink} to="/management" color="inherit">
746
+ Management
747
+ </Button>
748
+ <Button component={RouterLink} to="/chat" variant="contained" color="primary">
749
+ Go to chat
750
+ </Button>
751
+ </Stack>
752
+ </Toolbar>
753
+ </AppBar>
754
+ );
755
+
756
+ return (
757
+ <Routes>
758
+ <Route path="/management" element={
759
+ <ChatProvider packageSettings={packageSettings}>
760
+ <Management />
761
+ </ChatProvider>
762
+ } />
763
+ <Route path="/chat" element={
764
+ <ThemeProvider theme={banditQuickstartTheme}>
765
+ <CssBaseline />
766
+ <ChatProvider packageSettings={packageSettings}>
767
+ <Box display="flex" flexDirection="column" minHeight="100vh">
768
+ <Box component="main" sx={{ flexGrow: 1, display: "flex" }}>
769
+ <ChatPage onOpenModal={handleOpenModal} />
770
+ </Box>
771
+ <ChatModal open={isModalOpen} onClose={handleCloseModal} />
772
+ </Box>
773
+ </ChatProvider>
774
+ </ThemeProvider>
775
+ } />
776
+ <Route path="/*" element={
777
+ <ThemeProvider theme={banditQuickstartTheme}>
778
+ <CssBaseline />
779
+ <ChatProvider packageSettings={packageSettings}>
780
+ <Box display="flex" flexDirection="column" minHeight="100vh">
781
+ <Header gatewayChip={gatewayChip} />
782
+ <Box component="main" sx={{ flexGrow: 1, display: "flex" }}>
783
+ <Routes>
784
+ <Route path="/" element={<HomePage onOpenModal={handleOpenModal} />} />
785
+ <Route path="*" element={<Navigate to="/" replace />} />
786
+ </Routes>
787
+ </Box>
788
+ <ChatModal open={isModalOpen} onClose={handleCloseModal} />
789
+ </Box>
790
+ </ChatProvider>
791
+ </ThemeProvider>
792
+ } />
793
+ </Routes>
794
+ );
795
+ }
796
+
797
+ export default App;
798
+ `;
799
+ const withResponse = template.replace(/__RESPONSE_STATUS__/g, responseStatusExpr);
800
+ const withGatewayError = withResponse.replace(/__GATEWAY_ERROR__/g, gatewayErrorExpr);
801
+ return ensureTrailingNewline(normalizeLineEndings(withGatewayError));
802
+ };
803
+ var buildBrandingConfig = (ctx) => formatJson({
804
+ branding: {
805
+ logoBase64: ctx.isDefaultLogo ? null : ctx.logoBase64,
806
+ brandingText: ctx.brandingText,
807
+ theme: "bandit-dark",
808
+ hasTransparentLogo: ctx.isDefaultLogo ? true : ctx.hasTransparentLogo
809
+ },
810
+ knowledgeDocs: []
811
+ });
812
+ var buildGatewayServer = (ctx) => {
813
+ const modelsDefinition = JSON.stringify(ctx.gatewayModels, null, 2);
814
+ const gatewaySource = `import express from "express";
815
+ import cors from "cors";
816
+ import dotenv from "dotenv";
817
+
818
+ dotenv.config();
819
+
820
+ const app = express();
821
+ app.use(cors());
822
+ app.use(express.json({ limit: '50mb' }));
823
+ app.use(express.urlencoded({ limit: '50mb', extended: true }));
824
+
825
+ const QUICKSTART_VERSION = "0.1.0";
826
+ const DEFAULT_PROVIDER = "${ctx.defaultProvider}";
827
+ const BASE_GATEWAY_MODELS = ${modelsDefinition};
828
+ const OLLAMA_BASE_URL = (process.env.OLLAMA_URL ?? "http://localhost:11434").replace(/\\/$/, "");
829
+
830
+ const toGatewayModels = () =>
831
+ BASE_GATEWAY_MODELS.map((model) => ({
832
+ ...model,
833
+ created: Date.now(),
834
+ modified_at: new Date().toISOString(),
835
+ size: 0,
836
+ digest: "",
837
+ details: {
838
+ format: "chat",
839
+ family: model.provider,
840
+ families: [model.provider],
841
+ parameter_size: "",
842
+ quantization_level: "",
843
+ },
844
+ }));
845
+
846
+ const requireOpenAIKey = () => {
847
+ const key = process.env.OPENAI_API_KEY;
848
+ if (!key) {
849
+ throw new Error("Missing OPENAI_API_KEY. Add it to your .env file to route requests to OpenAI.");
850
+ }
851
+ return key;
852
+ };
853
+
854
+ // Utility function to handle streaming responses
855
+ const handleStreamingResponse = async (upstreamResponse, res) => {
856
+ res.setHeader('Content-Type', 'text/event-stream');
857
+ res.setHeader('Cache-Control', 'no-cache');
858
+ res.setHeader('Connection', 'keep-alive');
859
+ res.setHeader('Access-Control-Allow-Origin', '*');
860
+
861
+ try {
862
+ // Get the readable stream from the response
863
+ const reader = upstreamResponse.body.getReader();
864
+
865
+ while (true) {
866
+ const { done, value } = await reader.read();
867
+ if (done) break;
868
+
869
+ // Write the chunk to the response
870
+ res.write(value);
871
+ }
872
+
873
+ res.end();
874
+ } catch (error) {
875
+ console.error('Streaming error:', error);
876
+ // Fallback to non-streaming
877
+ const text = await upstreamResponse.text();
878
+ res.send(text);
879
+ }
880
+ };
881
+
882
+ // ============================================================================
883
+ // GENERAL HEALTH & MODELS
884
+ // ============================================================================
885
+
886
+ app.get("/api/health", async (_req, res) => {
887
+ const providers = [];
888
+
889
+ // Check OpenAI
890
+ try {
891
+ const openaiKey = process.env.OPENAI_API_KEY;
892
+ if (openaiKey) {
893
+ const response = await fetch("https://api.openai.com/v1/models", {
894
+ headers: { "Authorization": \`Bearer \${openaiKey}\` }
895
+ });
896
+ providers.push({
897
+ name: "openai",
898
+ status: response.ok ? "healthy" : "unhealthy",
899
+ provider: "openai"
900
+ });
901
+ } else {
902
+ providers.push({
903
+ name: "openai",
904
+ status: "unconfigured",
905
+ provider: "openai",
906
+ error: "API key not configured"
907
+ });
908
+ }
909
+ } catch (error) {
910
+ providers.push({
911
+ name: "openai",
912
+ status: "unhealthy",
913
+ provider: "openai",
914
+ error: error.message
915
+ });
916
+ }
917
+
918
+ // Check Ollama
919
+ try {
920
+ console.log(\`Checking Ollama health at: \${OLLAMA_BASE_URL}/api/tags\`);
921
+ const response = await fetch(\`\${OLLAMA_BASE_URL}/api/tags\`);
922
+ const status = response.ok ? "healthy" : "unhealthy";
923
+ console.log(\`Ollama health check result: \${status}\`);
924
+ providers.push({
925
+ name: "ollama",
926
+ status: status,
927
+ provider: "ollama",
928
+ url: OLLAMA_BASE_URL
929
+ });
930
+ } catch (error) {
931
+ console.log(\`Ollama health check error: \${error.message}\`);
932
+ providers.push({
933
+ name: "ollama",
934
+ status: "offline",
935
+ provider: "ollama",
936
+ error: error.message,
937
+ url: OLLAMA_BASE_URL
938
+ });
939
+ }
940
+
941
+ const overallHealthy = providers.some(p => p.status === "healthy");
942
+
943
+ res.json({
944
+ status: overallHealthy ? "healthy" : "unhealthy",
945
+ version: QUICKSTART_VERSION,
946
+ uptime: Math.round(process.uptime()),
947
+ providers
948
+ });
949
+ });
950
+
951
+ app.get("/api/models", (_req, res) => {
952
+ res.json({ models: toGatewayModels() });
953
+ });
954
+
955
+ // ============================================================================
956
+ // OPENAI ROUTES
957
+ // ============================================================================
958
+
959
+ // OpenAI Health Check
960
+ app.get("/api/openai/health", async (_req, res) => {
961
+ try {
962
+ const openaiKey = process.env.OPENAI_API_KEY;
963
+ if (!openaiKey) {
964
+ return res.status(503).json({
965
+ status: "unhealthy",
966
+ openai_status: false,
967
+ error: "OpenAI API key not configured",
968
+ provider: "openai"
969
+ });
970
+ }
971
+
972
+ const response = await fetch("https://api.openai.com/v1/models", {
973
+ headers: { "Authorization": \`Bearer \${openaiKey}\` }
974
+ });
975
+
976
+ const isHealthy = response.ok;
977
+ res.json({
978
+ status: isHealthy ? "healthy" : "unhealthy",
979
+ openai_status: isHealthy,
980
+ provider: "openai"
981
+ });
982
+ } catch (error) {
983
+ res.status(503).json({
984
+ status: "unhealthy",
985
+ openai_status: false,
986
+ error: error.message,
987
+ provider: "openai"
988
+ });
989
+ }
990
+ });
991
+
992
+ // OpenAI Chat Completions
993
+ app.post("/api/openai/chat/completions", async (req, res) => {
994
+ try {
995
+ const openaiKey = requireOpenAIKey();
996
+ const isStreaming = req.body?.stream === true;
997
+
998
+ // Strip the openai: prefix from model name and remove provider field
999
+ const { provider, ...cleanBody } = req.body;
1000
+ const requestBody = {
1001
+ ...cleanBody,
1002
+ model: req.body?.model?.replace(/^openai:/, "") || "gpt-4o"
1003
+ };
1004
+
1005
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
1006
+ method: "POST",
1007
+ headers: {
1008
+ "Content-Type": "application/json",
1009
+ "Authorization": \`Bearer \${openaiKey}\`
1010
+ },
1011
+ body: JSON.stringify(requestBody)
1012
+ });
1013
+
1014
+ if (!response.ok) {
1015
+ const errorText = await response.text();
1016
+ return res.status(response.status).json({
1017
+ error: \`OpenAI chat failed: \${response.status}\`,
1018
+ details: errorText
1019
+ });
1020
+ }
1021
+
1022
+ if (isStreaming) {
1023
+ await handleStreamingResponse(response, res);
1024
+ } else {
1025
+ const text = await response.text();
1026
+ res.setHeader('Content-Type', 'application/json');
1027
+ res.send(text);
1028
+ }
1029
+ } catch (error) {
1030
+ res.status(500).json({ error: error.message });
1031
+ }
1032
+ });
1033
+
1034
+ // OpenAI Chat (alternative route)
1035
+ app.post("/api/openai/chat", async (req, res) => {
1036
+ // Route to the completions endpoint for compatibility
1037
+ req.url = "/api/openai/chat/completions";
1038
+ return app._router.handle(req, res);
1039
+ });
1040
+
1041
+ // OpenAI Completions
1042
+ app.post("/api/openai/completions", async (req, res) => {
1043
+ try {
1044
+ const openaiKey = requireOpenAIKey();
1045
+ const isStreaming = req.body?.stream === true;
1046
+
1047
+ // Strip the openai: prefix from model name and remove provider field
1048
+ const { provider, ...cleanBody } = req.body;
1049
+ const requestBody = {
1050
+ ...cleanBody,
1051
+ model: req.body?.model?.replace(/^openai:/, "") || "gpt-3.5-turbo-instruct"
1052
+ };
1053
+
1054
+ const response = await fetch("https://api.openai.com/v1/completions", {
1055
+ method: "POST",
1056
+ headers: {
1057
+ "Content-Type": "application/json",
1058
+ "Authorization": \`Bearer \${openaiKey}\`
1059
+ },
1060
+ body: JSON.stringify(requestBody)
1061
+ });
1062
+
1063
+ if (!response.ok) {
1064
+ const errorText = await response.text();
1065
+ return res.status(response.status).json({
1066
+ error: \`OpenAI completions failed: \${response.status}\`,
1067
+ details: errorText
1068
+ });
1069
+ }
1070
+
1071
+ if (isStreaming) {
1072
+ await handleStreamingResponse(response, res);
1073
+ } else {
1074
+ const text = await response.text();
1075
+ res.setHeader('Content-Type', 'application/json');
1076
+ res.send(text);
1077
+ }
1078
+ } catch (error) {
1079
+ res.status(500).json({ error: error.message });
1080
+ }
1081
+ });
1082
+
1083
+ // OpenAI Generate (converts to chat format for conversation starters)
1084
+ app.post("/api/openai/generate", async (req, res) => {
1085
+ try {
1086
+ const openaiKey = requireOpenAIKey();
1087
+ const prompt = req.body?.prompt || "";
1088
+ const model = req.body?.model?.replace(/^openai:/, "") || "gpt-4o";
1089
+ const isStreaming = req.body?.stream === true;
1090
+
1091
+ // Convert generate request to chat format
1092
+ const chatBody = {
1093
+ model: model,
1094
+ messages: [
1095
+ {
1096
+ role: "user",
1097
+ content: prompt
1098
+ }
1099
+ ],
1100
+ stream: isStreaming,
1101
+ max_tokens: req.body?.max_tokens || 150,
1102
+ temperature: req.body?.temperature || 0.7
1103
+ };
1104
+
1105
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
1106
+ method: "POST",
1107
+ headers: {
1108
+ "Content-Type": "application/json",
1109
+ "Authorization": \`Bearer \${openaiKey}\`
1110
+ },
1111
+ body: JSON.stringify(chatBody)
1112
+ });
1113
+
1114
+ if (!response.ok) {
1115
+ const errorText = await response.text();
1116
+ return res.status(response.status).json({
1117
+ error: \`OpenAI generate failed: \${response.status}\`,
1118
+ details: errorText
1119
+ });
1120
+ }
1121
+
1122
+ if (isStreaming) {
1123
+ await handleStreamingResponse(response, res);
1124
+ } else {
1125
+ const data = await response.json();
1126
+ // Convert chat response back to generate format
1127
+ const generateResponse = {
1128
+ model: model,
1129
+ created_at: new Date().toISOString(),
1130
+ response: data.choices?.[0]?.message?.content || "",
1131
+ done: true,
1132
+ context: [],
1133
+ total_duration: 0,
1134
+ load_duration: 0,
1135
+ prompt_eval_count: 0,
1136
+ prompt_eval_duration: 0,
1137
+ eval_count: data.usage?.completion_tokens || 0,
1138
+ eval_duration: 0
1139
+ };
1140
+ res.json(generateResponse);
1141
+ }
1142
+ } catch (error) {
1143
+ res.status(500).json({ error: error.message });
1144
+ }
1145
+ });
1146
+
1147
+ // OpenAI Models
1148
+ app.get("/api/openai/models", async (_req, res) => {
1149
+ try {
1150
+ const openaiKey = requireOpenAIKey();
1151
+
1152
+ const response = await fetch("https://api.openai.com/v1/models", {
1153
+ headers: { "Authorization": \`Bearer \${openaiKey}\` }
1154
+ });
1155
+
1156
+ if (!response.ok) {
1157
+ const errorText = await response.text();
1158
+ return res.status(response.status).json({
1159
+ error: \`OpenAI models failed: \${response.status}\`,
1160
+ details: errorText
1161
+ });
1162
+ }
1163
+
1164
+ const text = await response.text();
1165
+ res.setHeader('Content-Type', 'application/json');
1166
+ res.send(text);
1167
+ } catch (error) {
1168
+ res.status(500).json({ error: error.message });
1169
+ }
1170
+ });
1171
+
1172
+ // ============================================================================
1173
+ // OLLAMA ROUTES
1174
+ // ============================================================================
1175
+
1176
+ // Ollama Health Check
1177
+ app.get("/api/ollama/health", async (_req, res) => {
1178
+ try {
1179
+ console.log(\`Ollama health check at: \${OLLAMA_BASE_URL}/api/tags\`);
1180
+ const response = await fetch(\`\${OLLAMA_BASE_URL}/api/tags\`);
1181
+ const isHealthy = response.ok;
1182
+
1183
+ res.json({
1184
+ status: isHealthy ? "healthy" : "unhealthy",
1185
+ ollama_status: isHealthy,
1186
+ provider: "ollama",
1187
+ url: OLLAMA_BASE_URL
1188
+ });
1189
+ } catch (error) {
1190
+ console.log(\`Ollama health check error: \${error.message}\`);
1191
+ res.status(503).json({
1192
+ status: "offline",
1193
+ ollama_status: false,
1194
+ error: error.message,
1195
+ provider: "ollama",
1196
+ url: OLLAMA_BASE_URL
1197
+ });
1198
+ }
1199
+ });
1200
+
1201
+ // Ollama Chat
1202
+ app.post("/api/ollama/chat", async (req, res) => {
1203
+ try {
1204
+ const response = await fetch(\`\${OLLAMA_BASE_URL}/api/chat\`, {
1205
+ method: "POST",
1206
+ headers: { "Content-Type": "application/json" },
1207
+ body: JSON.stringify(req.body)
1208
+ });
1209
+
1210
+ if (!response.ok) {
1211
+ const errorText = await response.text();
1212
+ return res.status(response.status).json({
1213
+ error: \`Ollama chat failed: \${response.status}\`,
1214
+ details: errorText
1215
+ });
1216
+ }
1217
+
1218
+ const isStreaming = req.body?.stream === true;
1219
+ if (isStreaming) {
1220
+ await handleStreamingResponse(response, res);
1221
+ } else {
1222
+ const text = await response.text();
1223
+ res.setHeader('Content-Type', 'application/json');
1224
+ res.send(text);
1225
+ }
1226
+ } catch (error) {
1227
+ res.status(500).json({ error: error.message });
1228
+ }
1229
+ });
1230
+
1231
+ // Ollama Generate
1232
+ app.post("/api/ollama/generate", async (req, res) => {
1233
+ try {
1234
+ const response = await fetch(\`\${OLLAMA_BASE_URL}/api/generate\`, {
1235
+ method: "POST",
1236
+ headers: { "Content-Type": "application/json" },
1237
+ body: JSON.stringify(req.body)
1238
+ });
1239
+
1240
+ if (!response.ok) {
1241
+ const errorText = await response.text();
1242
+ return res.status(response.status).json({
1243
+ error: \`Ollama generate failed: \${response.status}\`,
1244
+ details: errorText
1245
+ });
1246
+ }
1247
+
1248
+ const isStreaming = req.body?.stream === true;
1249
+ if (isStreaming) {
1250
+ await handleStreamingResponse(response, res);
1251
+ } else {
1252
+ const text = await response.text();
1253
+ res.setHeader('Content-Type', 'application/json');
1254
+ res.send(text);
1255
+ }
1256
+ } catch (error) {
1257
+ res.status(500).json({ error: error.message });
1258
+ }
1259
+ });
1260
+
1261
+ // Ollama Models
1262
+ app.get("/api/ollama/models", async (_req, res) => {
1263
+ try {
1264
+ const response = await fetch(\`\${OLLAMA_BASE_URL}/api/tags\`);
1265
+
1266
+ if (!response.ok) {
1267
+ const errorText = await response.text();
1268
+ return res.status(response.status).json({
1269
+ error: \`Ollama models failed: \${response.status}\`,
1270
+ details: errorText
1271
+ });
1272
+ }
1273
+
1274
+ const text = await response.text();
1275
+ res.setHeader('Content-Type', 'application/json');
1276
+ res.send(text);
1277
+ } catch (error) {
1278
+ res.status(500).json({ error: error.message });
1279
+ }
1280
+ });
1281
+
1282
+ // Ollama Embedding
1283
+ app.post("/api/ollama/embed", async (req, res) => {
1284
+ try {
1285
+ const response = await fetch(\`\${OLLAMA_BASE_URL}/api/embeddings\`, {
1286
+ method: "POST",
1287
+ headers: { "Content-Type": "application/json" },
1288
+ body: JSON.stringify(req.body)
1289
+ });
1290
+
1291
+ if (!response.ok) {
1292
+ const errorText = await response.text();
1293
+ return res.status(response.status).json({
1294
+ error: \`Ollama embed failed: \${response.status}\`,
1295
+ details: errorText
1296
+ });
1297
+ }
1298
+
1299
+ const text = await response.text();
1300
+ res.setHeader('Content-Type', 'application/json');
1301
+ res.send(text);
1302
+ } catch (error) {
1303
+ res.status(500).json({ error: error.message });
1304
+ }
1305
+ });
1306
+
1307
+ // ============================================================================
1308
+ // TTS ROUTES (not implemented - placeholder for compatibility)
1309
+ // ============================================================================
1310
+
1311
+ // TTS Main endpoint
1312
+ app.post("/api/tts", async (_req, res) => {
1313
+ res.status(501).json({
1314
+ error: "TTS integration not implemented",
1315
+ message: "Text-to-speech functionality is not available in this quickstart gateway"
1316
+ });
1317
+ });
1318
+
1319
+ // TTS Stream endpoint
1320
+ app.post("/api/tts/stream", async (_req, res) => {
1321
+ res.status(501).json({
1322
+ error: "TTS streaming not implemented",
1323
+ message: "Text-to-speech streaming functionality is not available in this quickstart gateway"
1324
+ });
1325
+ });
1326
+
1327
+ // TTS Real-time stream endpoint
1328
+ app.post("/api/tts/stream-realtime", async (_req, res) => {
1329
+ res.status(501).json({
1330
+ error: "TTS real-time streaming not implemented",
1331
+ message: "Text-to-speech real-time streaming functionality is not available in this quickstart gateway"
1332
+ });
1333
+ });
1334
+
1335
+ // TTS Models endpoint - returns empty models
1336
+ app.get("/api/tts/models", async (_req, res) => {
1337
+ res.json({
1338
+ models: []
1339
+ });
1340
+ });
1341
+
1342
+ // TTS Available models endpoint - returns empty models with defaults
1343
+ app.get("/api/tts/available-models", async (_req, res) => {
1344
+ res.json({
1345
+ models: [],
1346
+ defaultModel: null,
1347
+ fallbackModel: null
1348
+ });
1349
+ });
1350
+
1351
+ // ============================================================================
1352
+ // MCP (Model Context Protocol) TOOL ROUTES
1353
+ // ============================================================================
1354
+
1355
+ // MCP Health Check
1356
+ app.get("/api/mcp/health", async (_req, res) => {
1357
+ res.json({
1358
+ status: "healthy",
1359
+ timestamp: new Date().toISOString(),
1360
+ totalTools: 0,
1361
+ enabledTools: 0,
1362
+ availableTools: [],
1363
+ message: "MCP tools are not implemented in this quickstart gateway"
1364
+ });
1365
+ });
1366
+
1367
+ // Get available MCP tools
1368
+ app.get("/api/mcp/tools", async (_req, res) => {
1369
+ res.json([]);
1370
+ });
1371
+
1372
+ // News endpoint - placeholder implementation
1373
+ app.get("/api/mcp/news", async (req, res) => {
1374
+ const { topic = "general", count = 10, headlines = false } = req.query;
1375
+
1376
+ res.status(501).json({
1377
+ error: "News service not implemented",
1378
+ message: "MCP news functionality is not available in this quickstart gateway",
1379
+ suggestion: "Implement this endpoint to connect to a news API service",
1380
+ requestedParams: { topic, count, headlines }
1381
+ });
1382
+ });
1383
+
1384
+ // Weather endpoint - placeholder implementation
1385
+ app.get("/api/mcp/weather", async (req, res) => {
1386
+ const { zip, latitude, longitude } = req.query;
1387
+
1388
+ res.status(501).json({
1389
+ error: "Weather service not implemented",
1390
+ message: "MCP weather functionality is not available in this quickstart gateway",
1391
+ suggestion: "Implement this endpoint to connect to a weather API service",
1392
+ requestedParams: { zip, latitude, longitude }
1393
+ });
1394
+ });
1395
+
1396
+ // Documentation search endpoint - placeholder implementation
1397
+ app.get("/api/mcp/docs", async (req, res) => {
1398
+ const { query, framework, count = 10 } = req.query;
1399
+
1400
+ if (!query) {
1401
+ return res.status(400).json({
1402
+ error: "Query parameter is required",
1403
+ message: "Please provide a search query"
1404
+ });
1405
+ }
1406
+
1407
+ res.status(501).json({
1408
+ error: "Documentation search not implemented",
1409
+ message: "MCP docs functionality is not available in this quickstart gateway",
1410
+ suggestion: "Implement this endpoint to connect to a documentation search service",
1411
+ requestedParams: { query, framework, count }
1412
+ });
1413
+ });
1414
+
1415
+ // Get supported documentation frameworks
1416
+ app.get("/api/mcp/docs/frameworks", async (_req, res) => {
1417
+ res.json({
1418
+ frameworks: [],
1419
+ message: "Documentation frameworks not configured in this quickstart gateway"
1420
+ });
1421
+ });
1422
+
1423
+ // Sports scores endpoint - placeholder implementation
1424
+ app.get("/api/mcp/sports", async (req, res) => {
1425
+ const { league, date } = req.query;
1426
+
1427
+ res.status(501).json({
1428
+ error: "Sports service not implemented",
1429
+ message: "MCP sports functionality is not available in this quickstart gateway",
1430
+ suggestion: "Implement this endpoint to connect to a sports API service",
1431
+ requestedParams: { league, date }
1432
+ });
1433
+ });
1434
+
1435
+ // Get supported sports leagues
1436
+ app.get("/api/mcp/sports/leagues", async (_req, res) => {
1437
+ res.json({
1438
+ leagues: [],
1439
+ message: "Sports leagues not configured in this quickstart gateway"
1440
+ });
1441
+ });
1442
+
1443
+ // Image generation endpoint - placeholder implementation
1444
+ app.post("/api/mcp/generate-image", async (req, res) => {
1445
+ const { prompt, size, quality, style } = req.body;
1446
+
1447
+ if (!prompt) {
1448
+ return res.status(400).json({
1449
+ error: "Prompt is required",
1450
+ message: "Please provide a prompt for image generation"
1451
+ });
1452
+ }
1453
+
1454
+ res.status(501).json({
1455
+ success: false,
1456
+ error: "Image generation not implemented",
1457
+ message: "MCP image generation functionality is not available in this quickstart gateway",
1458
+ suggestion: "Implement this endpoint to connect to an image generation API service",
1459
+ requestedParams: { prompt, size, quality, style }
1460
+ });
1461
+ });
1462
+
1463
+ // ============================================================================
1464
+ // NOT IMPLEMENTED ROUTES (for graceful degradation)
1465
+ // ============================================================================
1466
+
1467
+ app.all("/api/anthropic/*", (_req, res) => {
1468
+ res.status(501).json({
1469
+ error: "Anthropic integration not implemented",
1470
+ message: "This quickstart gateway only supports OpenAI and Ollama providers"
1471
+ });
1472
+ });
1473
+
1474
+ app.all("/api/azure/*", (_req, res) => {
1475
+ res.status(501).json({
1476
+ error: "Azure OpenAI integration not implemented",
1477
+ message: "This quickstart gateway only supports OpenAI and Ollama providers"
1478
+ });
1479
+ });
1480
+
1481
+ const port = Number(process.env.PORT ?? ${ctx.gatewayPort});
1482
+ app.listen(port, () => {
1483
+ console.log("\u26A1 Bandit quickstart gateway ready on http://localhost:" + port);
1484
+ console.log("\u{1F4E1} Supported providers: OpenAI, Ollama");
1485
+ console.log("\u{1F517} Provider-specific routes:");
1486
+ console.log(" \u2022 /api/openai/* - OpenAI endpoints");
1487
+ console.log(" \u2022 /api/ollama/* - Ollama endpoints");
1488
+ console.log(" \u2022 /api/health - Overall health check");
1489
+ });
1490
+ `;
1491
+ return ensureTrailingNewline(normalizeLineEndings(gatewaySource));
1492
+ };
1493
+ var buildGitignore = () => ensureTrailingNewline(
1494
+ normalizeLineEndings(
1495
+ `node_modules
1496
+ .env
1497
+ .vite
1498
+ .idea
1499
+ .DS_Store
1500
+ coverage
1501
+ dist
1502
+ `
1503
+ )
1504
+ );
1505
+ var buildNpmrc = () => {
1506
+ const tokenExpr = "${GITHUB_NPM_TOKEN}";
1507
+ const template = `@burtson-labs:registry=https://npm.pkg.github.com/
1508
+ //npm.pkg.github.com/:_authToken=__NPM_TOKEN__
1509
+ `;
1510
+ return ensureTrailingNewline(
1511
+ normalizeLineEndings(template.replace(/__NPM_TOKEN__/g, tokenExpr))
1512
+ );
1513
+ };
1514
+ var buildReadme = (ctx) => ensureTrailingNewline(
1515
+ normalizeLineEndings(
1516
+ `# ${ctx.projectTitle} \u2014 Bandit Quickstart
1517
+
1518
+ This project was generated by the Bandit Engine CLI. It ships with a React + Vite frontend that consumes \`@burtson-labs/bandit-engine\` and a lightweight Express gateway you can adapt for production.
1519
+
1520
+ ## \u{1F680} Next steps
1521
+ - \`npm install\`
1522
+ - \`cp .env.example .env\`
1523
+ - Fill in \`OPENAI_API_KEY\` (or point \`OLLAMA_URL\` at your local server)
1524
+ - \`npm run dev\`
1525
+
1526
+ The command runs the gateway and the frontend together. Visit http://localhost:${ctx.frontendPort} to see the chat and modal in action.
1527
+
1528
+ ## \u{1F527} Customizing your assistant
1529
+ - **Branding & personas**: edit \`public/config.json\` to tweak logos, colors, and starter models.
1530
+ - **Provider defaults**: update \`.env\` to switch providers or change the default upstream model IDs.
1531
+ - **Gateway routes**: open \`server/gateway.js\` to add auth, logging, or connect additional providers.
1532
+
1533
+ ## \u{1F4E6} What\u2019s inside
1534
+ - React + Vite 5 with Material UI theming
1535
+ - Bandit chat surface + modal wired via \`ChatProvider\`
1536
+ - Express gateway proxying OpenAI or Ollama to keep API keys server-side
1537
+ - Friendly defaults you can evolve into your production stack
1538
+
1539
+ Need more? Run \`npx @burtson-labs/bandit-engine create --help\` to explore additional options.
1540
+ `
1541
+ )
1542
+ );
1543
+
1544
+ // src/cli/createQuickstart.ts
1545
+ var createQuickstartProject = async (options) => {
1546
+ const resolvedDir = import_node_path2.default.resolve(process.cwd(), options.targetDir);
1547
+ const rawProjectName = options.projectName ?? import_node_path2.default.basename(resolvedDir);
1548
+ const packageName = normalizePackageName(rawProjectName);
1549
+ const projectTitle = toTitleCase(rawProjectName) || "Bandit Quickstart";
1550
+ await ensureWritableDirectory(resolvedDir, Boolean(options.force));
1551
+ const provider = normalizeProvider(options.provider);
1552
+ const promptAnswers = options.skipPrompts ? {} : await promptForMissingData({
1553
+ brandingText: options.brandingText,
1554
+ provider
1555
+ });
1556
+ const brandingText = options.brandingText ?? (typeof promptAnswers.brandingText === "string" && promptAnswers.brandingText.trim().length > 0 ? promptAnswers.brandingText.trim() : `${projectTitle} Assistant`);
1557
+ const logoResolution = {
1558
+ dataUrl: "",
1559
+ fileName: "",
1560
+ fileContent: Buffer.alloc(0),
1561
+ hasTransparentLogo: true,
1562
+ isDefault: true
1563
+ };
1564
+ const gatewayPort = sanitizePort(options.gatewayPort ?? promptAnswers.gatewayPort ?? 5151);
1565
+ const frontendPort = sanitizePort(options.frontendPort ?? promptAnswers.frontendPort ?? 5183);
1566
+ const defaultModelId = sanitizeModelIdentifier(
1567
+ options.defaultModelId ?? inferDefaultModelId(provider)
1568
+ );
1569
+ const fallbackModelId = options.fallbackModelId ? sanitizeModelIdentifier(options.fallbackModelId) : inferFallbackModelId(provider, defaultModelId);
1570
+ const inputs = {
1571
+ targetDir: resolvedDir,
1572
+ projectTitle,
1573
+ packageName,
1574
+ brandingText,
1575
+ provider,
1576
+ defaultModelId,
1577
+ fallbackModelId,
1578
+ gatewayPort,
1579
+ frontendPort,
1580
+ logo: logoResolution
1581
+ };
1582
+ const createdFiles = await writeProject(inputs);
1583
+ return {
1584
+ projectDir: resolvedDir,
1585
+ packageName,
1586
+ brandingText,
1587
+ defaultModelId,
1588
+ fallbackModelId,
1589
+ gatewayPort,
1590
+ frontendPort,
1591
+ createdFiles
1592
+ };
1593
+ };
1594
+ var normalizePackageName = (input) => {
1595
+ const fallback = "bandit-quickstart";
1596
+ const kebab = toKebabCase(input || fallback);
1597
+ if (!kebab) {
1598
+ return fallback;
1599
+ }
1600
+ return /^[a-z]/.test(kebab) ? kebab : `bandit-${kebab}`;
1601
+ };
1602
+ var ensureWritableDirectory = async (dir, force) => {
1603
+ const exists = await import_fs_extra.default.pathExists(dir);
1604
+ if (!exists) {
1605
+ await import_fs_extra.default.ensureDir(dir);
1606
+ return;
1607
+ }
1608
+ const entries = await import_fs_extra.default.readdir(dir);
1609
+ if (entries.length > 0 && !force) {
1610
+ throw new Error(
1611
+ `Target directory "${dir}" is not empty. Re-run with --force to overwrite or choose a new folder.`
1612
+ );
1613
+ }
1614
+ };
1615
+ var normalizeProvider = (value) => {
1616
+ const normalized = (value ?? "openai").toLowerCase();
1617
+ return normalized === "ollama" ? "ollama" : "openai";
1618
+ };
1619
+ var inferDefaultModelId = (provider) => {
1620
+ return provider === "ollama" ? "ollama:llama3.1" : "openai:gpt-4o-mini";
1621
+ };
1622
+ var inferFallbackModelId = (provider, defaultId) => {
1623
+ if (provider === "ollama") {
1624
+ return defaultId === "ollama:llama3" ? "ollama:llama2" : "ollama:llama3";
1625
+ }
1626
+ return defaultId === "openai:gpt-4.1-mini" ? "openai:gpt-4o-mini" : "openai:gpt-4.1-mini";
1627
+ };
1628
+ var sanitizePort = (value) => {
1629
+ const port = Number(value);
1630
+ if (Number.isNaN(port) || port <= 0 || port >= 65535) {
1631
+ return 8080;
1632
+ }
1633
+ return port;
1634
+ };
1635
+ var promptForMissingData = async (options) => {
1636
+ const questions = [];
1637
+ if (!options.brandingText) {
1638
+ questions.push({
1639
+ type: "text",
1640
+ name: "brandingText",
1641
+ message: "What should we display for the app name? (Press Enter to accept)",
1642
+ initial: "Bandit Quickstart"
1643
+ });
1644
+ }
1645
+ const defaultGatewayPort = options.provider === "ollama" ? 11435 : 8080;
1646
+ const defaultFrontendPort = 5183;
1647
+ questions.push({
1648
+ type: "text",
1649
+ name: "frontendPort",
1650
+ message: "Frontend port (Press Enter to accept)",
1651
+ initial: String(defaultFrontendPort),
1652
+ validate: (value) => {
1653
+ if (typeof value !== "string" || value.trim().length === 0) {
1654
+ return true;
1655
+ }
1656
+ const numericValue = Number(value);
1657
+ return Number.isFinite(numericValue) && numericValue > 0 && numericValue < 65535 ? true : "Enter a port between 1 and 65535";
1658
+ }
1659
+ });
1660
+ questions.push({
1661
+ type: "text",
1662
+ name: "gatewayPort",
1663
+ message: "Gateway port (Press Enter to accept)",
1664
+ initial: String(defaultGatewayPort),
1665
+ validate: (value) => {
1666
+ if (typeof value !== "string" || value.trim().length === 0) {
1667
+ return true;
1668
+ }
1669
+ const numericValue = Number(value);
1670
+ return Number.isFinite(numericValue) && numericValue > 0 && numericValue < 65535 ? true : "Enter a port between 1 and 65535";
1671
+ }
1672
+ });
1673
+ const onCancel = () => {
1674
+ throw new Error("Command cancelled.");
1675
+ };
1676
+ const answers = await (0, import_prompts.default)(questions, { onCancel });
1677
+ const parsedGatewayPort = typeof answers.gatewayPort === "number" ? answers.gatewayPort : typeof answers.gatewayPort === "string" && answers.gatewayPort.trim().length > 0 ? Number(answers.gatewayPort) : defaultGatewayPort;
1678
+ const parsedFrontendPort = typeof answers.frontendPort === "number" ? answers.frontendPort : typeof answers.frontendPort === "string" && answers.frontendPort.trim().length > 0 ? Number(answers.frontendPort) : defaultFrontendPort;
1679
+ return {
1680
+ brandingText: typeof answers.brandingText === "string" ? answers.brandingText : void 0,
1681
+ gatewayPort: Number.isFinite(parsedGatewayPort) ? parsedGatewayPort : defaultGatewayPort,
1682
+ frontendPort: Number.isFinite(parsedFrontendPort) ? parsedFrontendPort : defaultFrontendPort
1683
+ };
1684
+ };
1685
+ var writeProject = async (inputs) => {
1686
+ const { targetDir } = inputs;
1687
+ const createdFiles = [];
1688
+ const context = {
1689
+ packageName: inputs.packageName,
1690
+ projectTitle: inputs.projectTitle,
1691
+ engineVersion: package_default.version,
1692
+ brandingText: inputs.brandingText,
1693
+ logoBase64: inputs.logo.dataUrl,
1694
+ hasTransparentLogo: inputs.logo.hasTransparentLogo,
1695
+ isDefaultLogo: inputs.logo.isDefault,
1696
+ gatewayPort: inputs.gatewayPort,
1697
+ frontendPort: inputs.frontendPort,
1698
+ defaultProvider: inputs.provider,
1699
+ defaultGatewayUrl: `http://localhost:${inputs.gatewayPort}`,
1700
+ defaultModelId: inputs.defaultModelId,
1701
+ fallbackModelId: inputs.fallbackModelId,
1702
+ gatewayModels: buildGatewayModels(inputs)
1703
+ };
1704
+ const files = {
1705
+ "package.json": buildPackageJson(context),
1706
+ "tsconfig.json": buildTsConfig(),
1707
+ "src/env.d.ts": buildEnvDts(),
1708
+ "vite.config.ts": buildViteConfig(context),
1709
+ "src/main.tsx": buildMainTsx(),
1710
+ "index.html": buildIndexHtml(),
1711
+ "src/App.tsx": buildAppTsx(context),
1712
+ "src/index.css": buildIndexCss(),
1713
+ "src/theme.ts": buildThemeTs(),
1714
+ "public/config.json": buildBrandingConfig(context),
1715
+ "server/gateway.js": buildGatewayServer(context),
1716
+ ".env.example": buildEnvExample(context),
1717
+ ".gitignore": buildGitignore(),
1718
+ ".npmrc": buildNpmrc(),
1719
+ "README.md": buildReadme(context)
1720
+ };
1721
+ if (!inputs.logo.isDefault && inputs.logo.fileName) {
1722
+ files[import_node_path2.default.posix.join("public", inputs.logo.fileName)] = inputs.logo.fileContent;
1723
+ }
1724
+ for (const [relativePath, content] of Object.entries(files)) {
1725
+ const destination = import_node_path2.default.join(targetDir, relativePath);
1726
+ await import_fs_extra.default.ensureDir(import_node_path2.default.dirname(destination));
1727
+ if (Buffer.isBuffer(content)) {
1728
+ await import_fs_extra.default.writeFile(destination, content);
1729
+ } else {
1730
+ await import_fs_extra.default.writeFile(destination, ensureTrailingNewline(content), "utf8");
1731
+ }
1732
+ createdFiles.push(relativePath);
1733
+ }
1734
+ return createdFiles;
1735
+ };
1736
+ var buildGatewayModels = (inputs) => {
1737
+ const seen = /* @__PURE__ */ new Set();
1738
+ const models = [];
1739
+ const pushModel = (modelId) => {
1740
+ if (!modelId) return;
1741
+ if (seen.has(modelId)) return;
1742
+ seen.add(modelId);
1743
+ const provider = modelId.includes(":") ? modelId.split(":")[0] : inputs.provider;
1744
+ const nameSegment = modelId.includes(":") ? modelId.split(":")[1] : modelId;
1745
+ models.push({
1746
+ id: modelId,
1747
+ name: toTitleCase(nameSegment.replace(/[-_.]/g, " ")),
1748
+ provider
1749
+ });
1750
+ };
1751
+ pushModel(inputs.defaultModelId);
1752
+ pushModel(inputs.fallbackModelId);
1753
+ return models;
1754
+ };
1755
+
1756
+ // src/cli/index.ts
1757
+ var logIntro = () => {
1758
+ console.log("\u{1F977} Bandit CLI \u2014 Burtson Labs \u{1F9EA}");
1759
+ };
1760
+ var program = new import_commander.Command();
1761
+ program.name("bandit").description("Bandit Engine developer utilities").version(package_default.version).showHelpAfterError();
1762
+ program.command("create").description("Scaffold a Bandit quickstart project with a frontend and gateway").argument("[directory]", "Relative path for your new project", "bandit-quickstart").option("-f, --force", "Overwrite the target directory if it already contains files", false).option("--branding-text <text>", "Assistant display name shown in the UI").option(
1763
+ "--frontend-port <port>",
1764
+ "Frontend dev server port (default: 5183)",
1765
+ (value) => parseInt(value, 10)
1766
+ ).option("--gateway-port <port>", "Gateway port (default: 8080)", (value) => parseInt(value, 10)).option("-y, --yes", "Skip interactive prompts and accept defaults", false).option("--skip-prompts", "Alias for --yes", false).action(async (directory, cmdOptions) => {
1767
+ try {
1768
+ const targetDir = directory ?? "bandit-quickstart";
1769
+ const projectName = import_node_path3.default.basename(import_node_path3.default.resolve(process.cwd(), targetDir));
1770
+ logIntro();
1771
+ const skipPrompts = Boolean(cmdOptions.skipPrompts ?? cmdOptions.yes);
1772
+ const result = await createQuickstartProject({
1773
+ targetDir,
1774
+ projectName,
1775
+ force: Boolean(cmdOptions.force),
1776
+ brandingText: cmdOptions.brandingText,
1777
+ frontendPort: Number.isFinite(cmdOptions.frontendPort) ? cmdOptions.frontendPort : void 0,
1778
+ gatewayPort: Number.isFinite(cmdOptions.gatewayPort) ? cmdOptions.gatewayPort : void 0,
1779
+ skipPrompts
1780
+ });
1781
+ const relativeDir = import_node_path3.default.relative(process.cwd(), result.projectDir) || ".";
1782
+ console.log("\n\u2705 Quickstart ready!");
1783
+ console.log(` Location: ${result.projectDir}`);
1784
+ console.log(` Package: ${result.packageName}`);
1785
+ console.log(` App name: ${result.brandingText}`);
1786
+ console.log(` Frontend: http://localhost:${result.frontendPort}`);
1787
+ console.log(` Gateway: http://localhost:${result.gatewayPort}`);
1788
+ console.log("\nNext steps:");
1789
+ console.log(` cd ${relativeDir}`);
1790
+ console.log(" npm install");
1791
+ console.log(" cp .env.example .env");
1792
+ console.log(" npm run dev");
1793
+ console.log("");
1794
+ } catch (error) {
1795
+ const message = error instanceof Error ? error.message : "Failed to create Bandit quickstart project.";
1796
+ console.error(`
1797
+ \u274C ${message}`);
1798
+ process.exitCode = 1;
1799
+ }
1800
+ });
1801
+ async function main() {
1802
+ await program.parseAsync(process.argv);
1803
+ }
1804
+ main().catch((error) => {
1805
+ console.error(error instanceof Error ? error.message : error);
1806
+ process.exit(1);
1807
+ });
1808
+ //# sourceMappingURL=cli.js.map