@edgible-team/cli 1.2.4 → 1.2.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 (221) hide show
  1. package/dist/commands/agent/agent-handlers.d.ts +45 -0
  2. package/dist/commands/agent/agent-handlers.d.ts.map +1 -0
  3. package/dist/commands/agent/agent-handlers.js +1159 -0
  4. package/dist/commands/agent/install.d.ts +11 -0
  5. package/dist/commands/agent/install.d.ts.map +1 -0
  6. package/dist/commands/agent/install.js +399 -0
  7. package/dist/commands/agent/logs.d.ts +12 -0
  8. package/dist/commands/agent/logs.d.ts.map +1 -0
  9. package/dist/commands/agent/logs.js +77 -0
  10. package/dist/commands/agent/restart.d.ts +4 -0
  11. package/dist/commands/agent/restart.d.ts.map +1 -0
  12. package/dist/commands/agent/restart.js +33 -0
  13. package/dist/commands/agent/set-log-level.d.ts +8 -0
  14. package/dist/commands/agent/set-log-level.d.ts.map +1 -0
  15. package/dist/commands/agent/set-log-level.js +133 -0
  16. package/dist/commands/agent/setup.d.ts +9 -0
  17. package/dist/commands/agent/setup.d.ts.map +1 -0
  18. package/dist/commands/agent/setup.js +149 -0
  19. package/dist/commands/agent/start.d.ts +12 -0
  20. package/dist/commands/agent/start.d.ts.map +1 -0
  21. package/dist/commands/agent/start.js +308 -0
  22. package/dist/commands/agent/status.d.ts +7 -0
  23. package/dist/commands/agent/status.d.ts.map +1 -0
  24. package/dist/commands/agent/status.js +68 -0
  25. package/dist/commands/agent/stop.d.ts +4 -0
  26. package/dist/commands/agent/stop.d.ts.map +1 -0
  27. package/dist/commands/agent/stop.js +33 -0
  28. package/dist/commands/agent/uninstall.d.ts +7 -0
  29. package/dist/commands/agent/uninstall.d.ts.map +1 -0
  30. package/dist/commands/agent/uninstall.js +168 -0
  31. package/dist/commands/agent.d.ts.map +1 -1
  32. package/dist/commands/agent.js +24 -1190
  33. package/dist/commands/ai/helpers.d.ts +139 -0
  34. package/dist/commands/ai/helpers.d.ts.map +1 -0
  35. package/dist/commands/ai/helpers.js +1470 -0
  36. package/dist/commands/ai/serve.d.ts +6 -0
  37. package/dist/commands/ai/serve.d.ts.map +1 -0
  38. package/dist/commands/ai/serve.js +124 -0
  39. package/dist/commands/ai/setup.d.ts +14 -0
  40. package/dist/commands/ai/setup.d.ts.map +1 -0
  41. package/dist/commands/ai/setup.js +86 -0
  42. package/dist/commands/ai/status.d.ts +2 -0
  43. package/dist/commands/ai/status.d.ts.map +1 -0
  44. package/dist/commands/ai/status.js +160 -0
  45. package/dist/commands/ai/stop.d.ts +2 -0
  46. package/dist/commands/ai/stop.d.ts.map +1 -0
  47. package/dist/commands/ai/stop.js +21 -0
  48. package/dist/commands/ai/teardown.d.ts +5 -0
  49. package/dist/commands/ai/teardown.d.ts.map +1 -0
  50. package/dist/commands/ai/teardown.js +78 -0
  51. package/dist/commands/ai/test.d.ts +4 -0
  52. package/dist/commands/ai/test.d.ts.map +1 -0
  53. package/dist/commands/ai/test.js +65 -0
  54. package/dist/commands/ai.d.ts.map +1 -1
  55. package/dist/commands/ai.js +16 -1938
  56. package/dist/commands/application/api-keys/create.d.ts +6 -0
  57. package/dist/commands/application/api-keys/create.d.ts.map +1 -0
  58. package/dist/commands/application/api-keys/create.js +68 -0
  59. package/dist/commands/application/api-keys/delete.d.ts +6 -0
  60. package/dist/commands/application/api-keys/delete.d.ts.map +1 -0
  61. package/dist/commands/application/api-keys/delete.js +79 -0
  62. package/dist/commands/application/api-keys/list.d.ts +5 -0
  63. package/dist/commands/application/api-keys/list.d.ts.map +1 -0
  64. package/dist/commands/application/api-keys/list.js +65 -0
  65. package/dist/commands/application/api-keys.d.ts +3 -0
  66. package/dist/commands/application/api-keys.d.ts.map +1 -0
  67. package/dist/commands/application/api-keys.js +227 -0
  68. package/dist/commands/application/create-compose.d.ts +3 -0
  69. package/dist/commands/application/create-compose.d.ts.map +1 -0
  70. package/dist/commands/application/create-compose.js +381 -0
  71. package/dist/commands/application/create-docker-compose.d.ts +10 -0
  72. package/dist/commands/application/create-docker-compose.d.ts.map +1 -0
  73. package/dist/commands/application/create-docker-compose.js +334 -0
  74. package/dist/commands/application/create-existing.d.ts +14 -0
  75. package/dist/commands/application/create-existing.d.ts.map +1 -0
  76. package/dist/commands/application/create-existing.js +359 -0
  77. package/dist/commands/application/create-interactive.d.ts +3 -0
  78. package/dist/commands/application/create-interactive.d.ts.map +1 -0
  79. package/dist/commands/application/create-interactive.js +326 -0
  80. package/dist/commands/application/create-managed-process.d.ts +15 -0
  81. package/dist/commands/application/create-managed-process.d.ts.map +1 -0
  82. package/dist/commands/application/create-managed-process.js +371 -0
  83. package/dist/commands/application/create-stubs.d.ts +4 -0
  84. package/dist/commands/application/create-stubs.d.ts.map +1 -0
  85. package/dist/commands/application/create-stubs.js +19 -0
  86. package/dist/commands/application/create-workload.d.ts +5 -0
  87. package/dist/commands/application/create-workload.d.ts.map +1 -0
  88. package/dist/commands/application/create-workload.js +48 -0
  89. package/dist/commands/application/delete.d.ts +6 -0
  90. package/dist/commands/application/delete.d.ts.map +1 -0
  91. package/dist/commands/application/delete.js +76 -0
  92. package/dist/commands/application/get.d.ts +5 -0
  93. package/dist/commands/application/get.d.ts.map +1 -0
  94. package/dist/commands/application/get.js +35 -0
  95. package/dist/commands/application/list.d.ts +4 -0
  96. package/dist/commands/application/list.d.ts.map +1 -0
  97. package/dist/commands/application/list.js +41 -0
  98. package/dist/commands/application/short-codes/create.d.ts +7 -0
  99. package/dist/commands/application/short-codes/create.d.ts.map +1 -0
  100. package/dist/commands/application/short-codes/create.js +69 -0
  101. package/dist/commands/application/short-codes/delete.d.ts +6 -0
  102. package/dist/commands/application/short-codes/delete.d.ts.map +1 -0
  103. package/dist/commands/application/short-codes/delete.js +79 -0
  104. package/dist/commands/application/short-codes/list.d.ts +5 -0
  105. package/dist/commands/application/short-codes/list.d.ts.map +1 -0
  106. package/dist/commands/application/short-codes/list.js +63 -0
  107. package/dist/commands/application/short-codes/toggle.d.ts +5 -0
  108. package/dist/commands/application/short-codes/toggle.d.ts.map +1 -0
  109. package/dist/commands/application/short-codes/toggle.js +71 -0
  110. package/dist/commands/application/short-codes.d.ts +3 -0
  111. package/dist/commands/application/short-codes.d.ts.map +1 -0
  112. package/dist/commands/application/short-codes.js +226 -0
  113. package/dist/commands/application/toggle.d.ts +2 -0
  114. package/dist/commands/application/toggle.d.ts.map +1 -0
  115. package/dist/commands/application/toggle.js +78 -0
  116. package/dist/commands/application/update.d.ts +4 -0
  117. package/dist/commands/application/update.d.ts.map +1 -0
  118. package/dist/commands/application/update.js +11 -0
  119. package/dist/commands/application.d.ts.map +1 -1
  120. package/dist/commands/application.js +32 -1630
  121. package/dist/commands/auth.d.ts.map +1 -1
  122. package/dist/commands/auth.js +31 -49
  123. package/dist/commands/base/BaseCommand.d.ts +3 -3
  124. package/dist/commands/base/BaseCommand.d.ts.map +1 -1
  125. package/dist/commands/base/BaseCommand.js +3 -3
  126. package/dist/commands/base/command-wrapper.d.ts +0 -4
  127. package/dist/commands/base/command-wrapper.d.ts.map +1 -1
  128. package/dist/commands/base/command-wrapper.js +13 -14
  129. package/dist/commands/base/middleware.d.ts +3 -3
  130. package/dist/commands/base/middleware.d.ts.map +1 -1
  131. package/dist/commands/base/middleware.js +4 -4
  132. package/dist/commands/config.d.ts.map +1 -1
  133. package/dist/commands/config.js +15 -32
  134. package/dist/commands/connectivity.d.ts.map +1 -1
  135. package/dist/commands/connectivity.js +6 -11
  136. package/dist/commands/debug.d.ts.map +1 -1
  137. package/dist/commands/debug.js +187 -46
  138. package/dist/commands/discover.d.ts.map +1 -1
  139. package/dist/commands/discover.js +4 -17
  140. package/dist/commands/gateway.d.ts.map +1 -1
  141. package/dist/commands/gateway.js +37 -77
  142. package/dist/commands/managedGateway/create.d.ts +6 -0
  143. package/dist/commands/managedGateway/create.d.ts.map +1 -0
  144. package/dist/commands/managedGateway/create.js +50 -0
  145. package/dist/commands/managedGateway/delete.d.ts +5 -0
  146. package/dist/commands/managedGateway/delete.d.ts.map +1 -0
  147. package/dist/commands/managedGateway/delete.js +57 -0
  148. package/dist/commands/managedGateway/get.d.ts +4 -0
  149. package/dist/commands/managedGateway/get.d.ts.map +1 -0
  150. package/dist/commands/managedGateway/get.js +71 -0
  151. package/dist/commands/managedGateway/haproxy-stats.d.ts +6 -0
  152. package/dist/commands/managedGateway/haproxy-stats.d.ts.map +1 -0
  153. package/dist/commands/managedGateway/haproxy-stats.js +131 -0
  154. package/dist/commands/managedGateway/list.d.ts +4 -0
  155. package/dist/commands/managedGateway/list.d.ts.map +1 -0
  156. package/dist/commands/managedGateway/list.js +50 -0
  157. package/dist/commands/managedGateway/logs.d.ts +10 -0
  158. package/dist/commands/managedGateway/logs.d.ts.map +1 -0
  159. package/dist/commands/managedGateway/logs.js +100 -0
  160. package/dist/commands/managedGateway/reboot.d.ts +5 -0
  161. package/dist/commands/managedGateway/reboot.d.ts.map +1 -0
  162. package/dist/commands/managedGateway/reboot.js +95 -0
  163. package/dist/commands/managedGateway/resync.d.ts +10 -0
  164. package/dist/commands/managedGateway/resync.d.ts.map +1 -0
  165. package/dist/commands/managedGateway/resync.js +69 -0
  166. package/dist/commands/managedGateway/ssh.d.ts +4 -0
  167. package/dist/commands/managedGateway/ssh.d.ts.map +1 -0
  168. package/dist/commands/managedGateway/ssh.js +130 -0
  169. package/dist/commands/managedGateway/wipe-logs.d.ts +4 -0
  170. package/dist/commands/managedGateway/wipe-logs.d.ts.map +1 -0
  171. package/dist/commands/managedGateway/wipe-logs.js +67 -0
  172. package/dist/commands/managedGateway/wireguard.d.ts +4 -0
  173. package/dist/commands/managedGateway/wireguard.d.ts.map +1 -0
  174. package/dist/commands/managedGateway/wireguard.js +68 -0
  175. package/dist/commands/managedGateway.d.ts.map +1 -1
  176. package/dist/commands/managedGateway.js +61 -117
  177. package/dist/commands/utils/config-validator.d.ts +5 -5
  178. package/dist/commands/utils/config-validator.d.ts.map +1 -1
  179. package/dist/commands/utils/config-validator.js +8 -8
  180. package/dist/commands/utils/output-formatter.js +1 -1
  181. package/dist/config/app-config.d.ts +1 -1
  182. package/dist/config/app-config.js +2 -2
  183. package/dist/index.js +0 -3
  184. package/dist/services/LocalAgentManager.d.ts.map +1 -1
  185. package/dist/services/LocalAgentManager.js +4 -2
  186. package/dist/services/agentDeployment/AgentDeploymentService.d.ts +35 -0
  187. package/dist/services/agentDeployment/AgentDeploymentService.d.ts.map +1 -0
  188. package/dist/services/agentDeployment/AgentDeploymentService.js +35 -0
  189. package/dist/services/application/ApplicationService.d.ts +5 -4
  190. package/dist/services/application/ApplicationService.d.ts.map +1 -1
  191. package/dist/services/application/ApplicationService.js +22 -35
  192. package/dist/services/auth/AuthService.d.ts +5 -5
  193. package/dist/services/auth/AuthService.d.ts.map +1 -1
  194. package/dist/services/auth/AuthService.js +11 -58
  195. package/dist/services/daemon/DaemonManagerFactory.d.ts +2 -0
  196. package/dist/services/daemon/DaemonManagerFactory.d.ts.map +1 -1
  197. package/dist/services/daemon/DaemonManagerFactory.js +14 -6
  198. package/dist/services/diagnostics/DiagnosticsService.d.ts +89 -0
  199. package/dist/services/diagnostics/DiagnosticsService.d.ts.map +1 -0
  200. package/dist/services/diagnostics/DiagnosticsService.js +37 -0
  201. package/dist/services/edgible.d.ts +6 -4
  202. package/dist/services/edgible.d.ts.map +1 -1
  203. package/dist/services/edgible.js +36 -86
  204. package/dist/services/gateway/GatewayService.d.ts +5 -6
  205. package/dist/services/gateway/GatewayService.d.ts.map +1 -1
  206. package/dist/services/gateway/GatewayService.js +22 -36
  207. package/dist/services/instances.d.ts +34 -0
  208. package/dist/services/instances.d.ts.map +1 -0
  209. package/dist/services/instances.js +64 -0
  210. package/dist/services/managedGateway/ManagedGatewayService.d.ts +75 -0
  211. package/dist/services/managedGateway/ManagedGatewayService.d.ts.map +1 -0
  212. package/dist/services/managedGateway/ManagedGatewayService.js +44 -0
  213. package/dist/services/token/TokenManager.d.ts +56 -0
  214. package/dist/services/token/TokenManager.d.ts.map +1 -0
  215. package/dist/services/token/TokenManager.js +85 -0
  216. package/dist/types/validation/schemas.d.ts +22 -22
  217. package/dist/utils/PlatformDetector.d.ts +2 -0
  218. package/dist/utils/PlatformDetector.d.ts.map +1 -1
  219. package/dist/utils/PlatformDetector.js +5 -34
  220. package/dist/validation/schemas.d.ts +6 -6
  221. package/package.json +1 -1
@@ -1,56 +1,13 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
- var __importDefault = (this && this.__importDefault) || function (mod) {
36
- return (mod && mod.__esModule) ? mod : { "default": mod };
37
- };
38
2
  Object.defineProperty(exports, "__esModule", { value: true });
39
3
  exports.setupAiCommands = setupAiCommands;
40
- const chalk_1 = __importDefault(require("chalk"));
41
- const child_process_1 = require("child_process");
42
- const os = __importStar(require("os"));
43
- const path = __importStar(require("path"));
44
- const fs = __importStar(require("fs"));
45
- const inquirer_1 = __importDefault(require("inquirer"));
46
- const node_fetch_1 = __importDefault(require("node-fetch"));
47
4
  const command_wrapper_1 = require("./base/command-wrapper");
48
- const container_1 = require("../di/container");
49
- const types_1 = require("../di/types");
50
- const SystemCapabilityDetector_1 = require("../detection/SystemCapabilityDetector");
51
- const DaemonManagerFactory_1 = require("../services/daemon/DaemonManagerFactory");
52
- const config_validator_1 = require("./utils/config-validator");
53
- const compose_constants_1 = require("../generated/compose-constants");
5
+ const setup_1 = require("./ai/setup");
6
+ const stop_1 = require("./ai/stop");
7
+ const serve_1 = require("./ai/serve");
8
+ const teardown_1 = require("./ai/teardown");
9
+ const status_1 = require("./ai/status");
10
+ const test_1 = require("./ai/test");
54
11
  function setupAiCommands(program) {
55
12
  const aiCommand = program
56
13
  .command('ai')
@@ -70,80 +27,8 @@ function setupAiCommands(program) {
70
27
  .option('--webui-deployment <type>', 'WebUI deployment: local or remote')
71
28
  .option('--no-interactive', 'Run in non-interactive mode')
72
29
  .action((0, command_wrapper_1.wrapCommand)(async (options) => {
73
- const container = (0, container_1.getContainer)();
74
- const logger = container.get(types_1.TYPES.Logger);
75
- logger.info('Setting up Ollama AI service', { model: options.model, autoInstall: options.autoInstall });
76
- console.log(chalk_1.default.blue('\n🤖 Ollama AI Setup'));
77
- console.log(chalk_1.default.gray('This will install Ollama, check system capabilities, and start with a selected model.\n'));
78
- // Check if daemon is running (required before setup)
79
- console.log(chalk_1.default.blue('Prerequisite: Checking daemon status...\n'));
80
- const configRepository = container.get(types_1.TYPES.ConfigRepository);
81
- const config = configRepository.getConfig();
82
- let daemonRunning = false;
83
- // Check daemon status if installation type is configured
84
- if (config.agentInstallationType) {
85
- try {
86
- const daemonManager = DaemonManagerFactory_1.DaemonManagerFactory.fromConfig(config.agentInstallationType);
87
- if (daemonManager) {
88
- const daemonStatus = await daemonManager.status();
89
- daemonRunning = daemonStatus.running;
90
- if (daemonRunning) {
91
- console.log(chalk_1.default.green('✓ Daemon is running\n'));
92
- }
93
- else {
94
- console.log(chalk_1.default.red('✗ Daemon is not running\n'));
95
- }
96
- }
97
- }
98
- catch (error) {
99
- console.log(chalk_1.default.yellow('⚠ Could not check daemon status\n'));
100
- console.log(chalk_1.default.gray(` Error: ${error instanceof Error ? error.message : String(error)}\n`));
101
- }
102
- }
103
- else {
104
- // Fallback: check agent status file if no daemon type is configured
105
- console.log(chalk_1.default.gray(' No daemon installation type configured, checking agent status file...\n'));
106
- const agentManager = container.get(types_1.TYPES.LocalAgentManager);
107
- const agentStatus = await agentManager.checkLocalAgentStatus();
108
- daemonRunning = agentStatus.running;
109
- if (daemonRunning) {
110
- console.log(chalk_1.default.green('✓ Agent is running\n'));
111
- }
112
- else {
113
- console.log(chalk_1.default.red('✗ Agent is not running\n'));
114
- }
115
- }
116
- if (!daemonRunning) {
117
- console.log(chalk_1.default.yellow('The device agent daemon must be running before setting up AI services.\n'));
118
- console.log(chalk_1.default.blue('Please start the agent first:'));
119
- console.log(chalk_1.default.gray(' edgible agent start\n'));
120
- console.log(chalk_1.default.gray('Then run this setup command again.\n'));
121
- throw new Error('Device agent daemon is not running. Please start it with: edgible agent start');
122
- }
123
- // Phase 1: Local Ollama Setup
124
- const { capabilities } = await setupLocalOllama({
125
- autoInstall: options.autoInstall,
126
- model: options.model,
127
- });
128
- // Phase 2: Platform Integration
129
- const { ollamaUrl, webUIUrl, createdOllamaApp, createdWebUIApp, deviceName, deviceId, ollamaModelName } = await setupPlatformIntegration({
130
- model: options.model,
131
- capabilities,
132
- container,
133
- logger,
134
- });
135
- // Phase 3: Display Summary
136
- displaySetupSummary({
137
- ollamaModelName,
138
- ollamaUrl,
139
- webUIUrl,
140
- deviceName,
141
- deviceId,
142
- createdOllamaApp,
143
- createdWebUIApp,
144
- });
30
+ await (0, setup_1.handleAiSetup)(options);
145
31
  }, {
146
- configRepository: (0, container_1.getContainer)().get(types_1.TYPES.ConfigRepository),
147
32
  requireAuth: false,
148
33
  requireOrganization: false,
149
34
  }));
@@ -151,20 +36,8 @@ function setupAiCommands(program) {
151
36
  .command('stop')
152
37
  .description('Stop Ollama service')
153
38
  .action((0, command_wrapper_1.wrapCommand)(async () => {
154
- const container = (0, container_1.getContainer)();
155
- const logger = container.get(types_1.TYPES.Logger);
156
- logger.info('Stopping Ollama service');
157
- console.log(chalk_1.default.blue('\n🛑 Stopping Ollama service...\n'));
158
- const isRunning = await checkOllamaRunning();
159
- if (!isRunning) {
160
- console.log(chalk_1.default.yellow('Ollama is not running\n'));
161
- return;
162
- }
163
- await stopOllama();
164
- console.log(chalk_1.default.green('✓ Ollama service stopped\n'));
165
- }, {
166
- configRepository: (0, container_1.getContainer)().get(types_1.TYPES.ConfigRepository),
167
- }));
39
+ await (0, stop_1.handleAiStop)();
40
+ }, {}));
168
41
  aiCommand
169
42
  .command('serve')
170
43
  .description('Start Open WebUI connected to local Ollama service')
@@ -172,361 +45,22 @@ function setupAiCommands(program) {
172
45
  .option('--ollama-url <url>', 'Override Ollama URL (default: auto-detect host)')
173
46
  .option('-d, --detached', 'Run in detached mode (default: true)', true)
174
47
  .action((0, command_wrapper_1.wrapCommand)(async (options) => {
175
- const container = (0, container_1.getContainer)();
176
- const logger = container.get(types_1.TYPES.Logger);
177
- const configRepository = container.get(types_1.TYPES.ConfigRepository);
178
- logger.info('Starting Open WebUI with Ollama');
179
- console.log(chalk_1.default.blue('\n🌐 Starting Open WebUI'));
180
- console.log(chalk_1.default.gray('This will start the Open WebUI interface connected to your local Ollama service.\n'));
181
- // Step 1: Check if Ollama is installed
182
- console.log(chalk_1.default.blue('Step 1: Checking Ollama installation...\n'));
183
- const isOllamaInstalled = await checkOllamaInstalled();
184
- if (!isOllamaInstalled) {
185
- console.log(chalk_1.default.red('✗ Ollama is not installed'));
186
- console.log(chalk_1.default.yellow('\nPlease run: edgible ai setup\n'));
187
- throw new Error('Ollama is required but not installed');
188
- }
189
- console.log(chalk_1.default.green('✓ Ollama is installed\n'));
190
- // Step 2: Check if Ollama is running
191
- console.log(chalk_1.default.blue('Step 2: Checking if Ollama is running...\n'));
192
- const isOllamaRunning = await checkOllamaRunning();
193
- if (!isOllamaRunning) {
194
- console.log(chalk_1.default.yellow('⚠ Ollama is not running'));
195
- const answer = await inquirer_1.default.prompt([
196
- {
197
- type: 'confirm',
198
- name: 'start',
199
- message: 'Would you like to start Ollama now?',
200
- default: true,
201
- },
202
- ]);
203
- if (answer.start) {
204
- console.log(chalk_1.default.yellow('Starting Ollama...\n'));
205
- await startOllama();
206
- // Wait and verify
207
- await new Promise(resolve => setTimeout(resolve, 2000));
208
- const isRunningNow = await checkOllamaRunning();
209
- if (!isRunningNow) {
210
- throw new Error('Failed to start Ollama. Please start it manually.');
211
- }
212
- console.log(chalk_1.default.green('✓ Ollama started\n'));
213
- }
214
- else {
215
- throw new Error('Ollama must be running. Please start it with: edgible ai setup');
216
- }
217
- }
218
- else {
219
- console.log(chalk_1.default.green('✓ Ollama is running\n'));
220
- }
221
- // Step 2.5: Check and fix Ollama binding if needed (for Docker access)
222
- console.log(chalk_1.default.blue('Step 2.5: Checking Ollama network binding...\n'));
223
- const listeningAddress = await checkOllamaListeningAddress();
224
- if (listeningAddress === '127.0.0.1') {
225
- console.log(chalk_1.default.yellow('⚠ Ollama is listening on localhost only (127.0.0.1:11434)'));
226
- console.log(chalk_1.default.gray(' Docker containers cannot access Ollama on localhost.\n'));
227
- console.log(chalk_1.default.yellow(' Reconfiguring Ollama to listen on all interfaces (0.0.0.0:11434)...\n'));
228
- const fixed = await fixOllamaBinding();
229
- if (fixed) {
230
- // Wait a moment and verify it's now on 0.0.0.0
231
- await new Promise((resolve) => setTimeout(resolve, 2000));
232
- const newAddress = await checkOllamaListeningAddress();
233
- if (newAddress === '0.0.0.0') {
234
- console.log(chalk_1.default.green('✓ Ollama reconfigured to listen on 0.0.0.0:11434\n'));
235
- }
236
- else {
237
- console.log(chalk_1.default.yellow('⚠ Could not verify binding change. Continuing anyway...\n'));
238
- }
239
- }
240
- else {
241
- console.log(chalk_1.default.yellow('⚠ Could not automatically reconfigure Ollama binding.\n'));
242
- console.log(chalk_1.default.gray(' Docker may not be able to connect. Please manually set OLLAMA_HOST=0.0.0.0:11434 and restart Ollama.\n'));
243
- }
244
- }
245
- else if (listeningAddress === '0.0.0.0') {
246
- console.log(chalk_1.default.green('✓ Ollama is listening on all interfaces (accessible to Docker)\n'));
247
- }
248
- else {
249
- console.log(chalk_1.default.gray(' Could not determine Ollama binding address (assuming accessible)\n'));
250
- }
251
- // Step 3: Check if any models are available
252
- console.log(chalk_1.default.blue('Step 3: Checking available models...\n'));
253
- const hasModels = await checkHasModels();
254
- if (!hasModels) {
255
- console.log(chalk_1.default.yellow('⚠ No Ollama models found'));
256
- console.log(chalk_1.default.yellow('Please pull a model first with: edgible ai setup\n'));
257
- throw new Error('At least one Ollama model is required');
258
- }
259
- console.log(chalk_1.default.green('✓ Models available\n'));
260
- // Step 4: Check if Docker is installed
261
- console.log(chalk_1.default.blue('Step 4: Checking Docker installation...\n'));
262
- const isDockerInstalled = await checkDockerInstalled();
263
- if (!isDockerInstalled) {
264
- console.log(chalk_1.default.red('✗ Docker is not installed'));
265
- console.log(chalk_1.default.yellow('Please install Docker from: https://docs.docker.com/get-docker/\n'));
266
- throw new Error('Docker is required to run Open WebUI');
267
- }
268
- console.log(chalk_1.default.green('✓ Docker is installed\n'));
269
- // Step 5: Determine Ollama URL for Docker
270
- console.log(chalk_1.default.blue('Step 5: Configuring connection...\n'));
271
- const ollamaUrl = options.ollamaUrl || await detectOllamaUrlForDocker();
272
- console.log(chalk_1.default.gray(` Using Ollama URL: ${ollamaUrl}\n`));
273
- // Step 6: Start Docker Compose
274
- console.log(chalk_1.default.blue('Step 6: Starting Open WebUI...\n'));
275
- const composeDir = getComposeDirectory();
276
- const port = parseInt(options.port || '3200', 10);
277
- await startOpenWebUI(composeDir, {
278
- OLLAMA_BASE_URL: ollamaUrl,
279
- OPEN_WEBUI_PORT: port.toString(),
280
- });
281
- console.log(chalk_1.default.green('✓ Open WebUI started successfully!\n'));
282
- console.log(chalk_1.default.blue('🎉 Setup Complete!\n'));
283
- console.log(chalk_1.default.white('Access Open WebUI at:'));
284
- console.log(chalk_1.default.cyan(` http://localhost:${port}\n`));
285
- console.log(chalk_1.default.gray('Useful commands:'));
286
- console.log(chalk_1.default.gray(` edgible ai status # Check service status`));
287
- console.log(chalk_1.default.gray(` edgible ai teardown # Stop Open WebUI`));
288
- console.log(chalk_1.default.gray(` docker logs open-webui # View logs\n`));
289
- }, {
290
- configRepository: (0, container_1.getContainer)().get(types_1.TYPES.ConfigRepository),
291
- }));
48
+ await (0, serve_1.handleAiServe)(options);
49
+ }, {}));
292
50
  aiCommand
293
51
  .command('teardown')
294
52
  .description('Stop AI services and optionally remove platform applications')
295
53
  .option('--stop-ollama', 'Also stop the Ollama service')
296
54
  .option('--remove-volumes', 'Remove data volumes (deletes all data)')
297
- .option('--remove-apps', 'Remove platform applications (Ollama API, Open WebUI)')
298
55
  .action((0, command_wrapper_1.wrapCommand)(async (options) => {
299
- const container = (0, container_1.getContainer)();
300
- const logger = container.get(types_1.TYPES.Logger);
301
- const configRepository = container.get(types_1.TYPES.ConfigRepository);
302
- logger.info('Tearing down AI services');
303
- console.log(chalk_1.default.blue('\n🛑 AI Services Teardown\n'));
304
- // Stop local Open WebUI if running
305
- const composeDir = getComposeDirectory();
306
- const isRunning = await checkOpenWebUIRunning();
307
- if (!isRunning) {
308
- console.log(chalk_1.default.gray(' Local Open WebUI is not running\n'));
309
- }
310
- else {
311
- console.log(chalk_1.default.yellow('Stopping local Open WebUI...\n'));
312
- await stopOpenWebUI(composeDir, options.removeVolumes || false);
313
- console.log(chalk_1.default.green('✓ Local Open WebUI stopped\n'));
314
- }
315
- // Optionally stop Ollama
316
- if (options.stopOllama) {
317
- console.log(chalk_1.default.yellow('Stopping Ollama service...\n'));
318
- const isOllamaRunning = await checkOllamaRunning();
319
- if (isOllamaRunning) {
320
- await stopOllama();
321
- console.log(chalk_1.default.green('✓ Ollama stopped\n'));
322
- }
323
- else {
324
- console.log(chalk_1.default.gray('Ollama is not running\n'));
325
- }
326
- }
327
- // Optionally remove platform applications
328
- if (options.removeApps) {
329
- console.log(chalk_1.default.yellow('\nRemoving platform applications...\n'));
330
- try {
331
- (0, config_validator_1.validateConfig)(configRepository, {
332
- requireAuth: true,
333
- requireOrganization: true,
334
- });
335
- const applicationService = container.get(types_1.TYPES.ApplicationService);
336
- const applications = await applicationService.getApplications();
337
- // Find AI-related applications
338
- const ollamaApp = applications.find(app => app.name === 'ollama-api');
339
- const webUIApp = applications.find(app => app.name === 'open-webui');
340
- if (ollamaApp) {
341
- console.log(chalk_1.default.yellow(` Removing ollama-api (${ollamaApp.id})...`));
342
- await applicationService.deleteApplication(ollamaApp.id);
343
- console.log(chalk_1.default.green(' ✓ Removed ollama-api'));
344
- }
345
- if (webUIApp) {
346
- console.log(chalk_1.default.yellow(` Removing open-webui (${webUIApp.id})...`));
347
- await applicationService.deleteApplication(webUIApp.id);
348
- console.log(chalk_1.default.green(' ✓ Removed open-webui'));
349
- }
350
- if (!ollamaApp && !webUIApp) {
351
- console.log(chalk_1.default.gray(' No AI applications found\n'));
352
- }
353
- else {
354
- console.log(chalk_1.default.green('\n✓ Platform applications removed\n'));
355
- }
356
- }
357
- catch (error) {
358
- if (error instanceof Error && error.message.includes('auth')) {
359
- console.log(chalk_1.default.yellow(' ⚠ Not logged in - skipping platform application removal\n'));
360
- }
361
- else {
362
- console.log(chalk_1.default.red(` ✗ Error removing applications: ${error instanceof Error ? error.message : 'Unknown error'}\n`));
363
- }
364
- }
365
- }
366
- console.log(chalk_1.default.green('✅ Teardown complete!\n'));
367
- if (options.removeVolumes) {
368
- console.log(chalk_1.default.yellow('⚠ Data volumes were removed. All local WebUI data has been deleted.\n'));
369
- }
370
- }, {
371
- configRepository: (0, container_1.getContainer)().get(types_1.TYPES.ConfigRepository),
372
- }));
56
+ await (0, teardown_1.handleAiTeardown)(options);
57
+ }, {}));
373
58
  aiCommand
374
59
  .command('status')
375
60
  .description('Check status of AI services (Ollama and Open WebUI)')
376
61
  .action((0, command_wrapper_1.wrapCommand)(async () => {
377
- const container = (0, container_1.getContainer)();
378
- const logger = container.get(types_1.TYPES.Logger);
379
- const configRepository = container.get(types_1.TYPES.ConfigRepository);
380
- logger.info('Checking AI services status');
381
- console.log(chalk_1.default.blue('\n📊 AI Services Status\n'));
382
- // Check Ollama Local
383
- console.log(chalk_1.default.white('Ollama (Local):'));
384
- const isOllamaInstalled = await checkOllamaInstalled();
385
- const isOllamaRunning = isOllamaInstalled ? await checkOllamaRunning() : false;
386
- if (!isOllamaInstalled) {
387
- console.log(chalk_1.default.red(' ✗ Not installed'));
388
- }
389
- else if (isOllamaRunning) {
390
- console.log(chalk_1.default.green(' ✓ Running'));
391
- // Check what address it's listening on
392
- try {
393
- const output = (0, child_process_1.execSync)('ss -tlnp 2>/dev/null | grep 11434 || netstat -tlnp 2>/dev/null | grep 11434', {
394
- encoding: 'utf8',
395
- timeout: 2000
396
- });
397
- if (output.includes('0.0.0.0:11434') || output.includes('*:11434')) {
398
- console.log(chalk_1.default.gray(' Listening on: 0.0.0.0:11434 (accessible from network)'));
399
- }
400
- else if (output.includes('127.0.0.1:11434')) {
401
- console.log(chalk_1.default.yellow(' Listening on: 127.0.0.1:11434 (localhost only)'));
402
- }
403
- }
404
- catch {
405
- // Ignore if ss/netstat fails
406
- }
407
- // Show available models
408
- try {
409
- const output = (0, child_process_1.execSync)('ollama list', { encoding: 'utf8', timeout: 5000 });
410
- const lines = output.trim().split('\n');
411
- if (lines.length > 1) {
412
- console.log(chalk_1.default.gray(` Models: ${lines.length - 1} available`));
413
- }
414
- }
415
- catch {
416
- // Ignore
417
- }
418
- }
419
- else {
420
- console.log(chalk_1.default.yellow(' ⚠ Installed but not running'));
421
- }
422
- console.log('');
423
- // Check Ollama Platform Application
424
- console.log(chalk_1.default.white('Ollama (Platform):'));
425
- try {
426
- (0, config_validator_1.validateConfig)(configRepository, {
427
- requireAuth: true,
428
- requireOrganization: true,
429
- });
430
- const applicationService = container.get(types_1.TYPES.ApplicationService);
431
- const applications = await applicationService.getApplications();
432
- const ollamaApp = applications.find(app => app.name === 'ollama-api');
433
- if (ollamaApp) {
434
- console.log(chalk_1.default.green(' ✓ Application exists'));
435
- console.log(chalk_1.default.gray(` ID: ${ollamaApp.id}`));
436
- // Extract URL from application
437
- let ollamaUrl;
438
- if (ollamaApp.url) {
439
- // Use URL directly if available
440
- ollamaUrl = ollamaApp.url;
441
- }
442
- else if (ollamaApp.servingIp && ollamaApp.servingIp !== 'unknown') {
443
- // Construct URL from servingIp
444
- const protocol = ollamaApp.protocol === 'https' ? 'https' : 'http';
445
- const port = ollamaApp.port === 443 || ollamaApp.port === 80 ? '' : `:${ollamaApp.port}`;
446
- ollamaUrl = `${protocol}://${ollamaApp.servingIp}${port}`;
447
- }
448
- if (ollamaUrl) {
449
- console.log(chalk_1.default.cyan(` URL: ${ollamaUrl}`));
450
- // Test if it's reachable
451
- const isReachable = await checkOllamaUrlReachable(ollamaUrl);
452
- if (isReachable) {
453
- console.log(chalk_1.default.green(' ✓ Reachable and responding'));
454
- }
455
- else {
456
- console.log(chalk_1.default.red(' ✗ Not reachable or not responding'));
457
- }
458
- }
459
- else {
460
- console.log(chalk_1.default.yellow(' ⚠ URL not available'));
461
- }
462
- }
463
- else {
464
- console.log(chalk_1.default.gray(' ○ No platform application found'));
465
- }
466
- }
467
- catch (error) {
468
- if (error instanceof Error && error.message.includes('auth')) {
469
- console.log(chalk_1.default.gray(' ○ Not logged in (cannot check platform apps)'));
470
- }
471
- else {
472
- console.log(chalk_1.default.yellow(` ⚠ Error checking platform: ${error instanceof Error ? error.message : 'Unknown'}`));
473
- }
474
- }
475
- console.log('');
476
- // Check Open WebUI
477
- console.log(chalk_1.default.white('Open WebUI:'));
478
- const webUIInfo = await getOpenWebUIInfo();
479
- if (webUIInfo) {
480
- console.log(chalk_1.default.green(' ✓ Running (Local)'));
481
- console.log(chalk_1.default.cyan(` URL: http://localhost:${webUIInfo.port}`));
482
- }
483
- else {
484
- // Check for platform application
485
- try {
486
- (0, config_validator_1.validateConfig)(configRepository, {
487
- requireAuth: true,
488
- requireOrganization: true,
489
- });
490
- const applicationService = container.get(types_1.TYPES.ApplicationService);
491
- const applications = await applicationService.getApplications();
492
- const webUIApp = applications.find(app => app.name === 'open-webui');
493
- if (webUIApp) {
494
- console.log(chalk_1.default.green(' ✓ Application exists (Platform)'));
495
- console.log(chalk_1.default.gray(` ID: ${webUIApp.id}`));
496
- let webUIUrl;
497
- if (webUIApp.url) {
498
- // Use URL directly if available
499
- webUIUrl = webUIApp.url;
500
- }
501
- else if (webUIApp.servingIp && webUIApp.servingIp !== 'unknown') {
502
- // Construct URL from servingIp
503
- const protocol = webUIApp.protocol === 'https' ? 'https' : 'http';
504
- const port = webUIApp.port === 443 || webUIApp.port === 80 ? '' : `:${webUIApp.port}`;
505
- webUIUrl = `${protocol}://${webUIApp.servingIp}${port}`;
506
- }
507
- if (webUIUrl) {
508
- console.log(chalk_1.default.cyan(` URL: ${webUIUrl}`));
509
- // Test if it's reachable
510
- const isReachable = await checkUrlReachable(webUIUrl);
511
- if (isReachable) {
512
- console.log(chalk_1.default.green(' ✓ Reachable'));
513
- }
514
- else {
515
- console.log(chalk_1.default.red(' ✗ Not reachable'));
516
- }
517
- }
518
- }
519
- else {
520
- console.log(chalk_1.default.gray(' ○ Not running (local or platform)'));
521
- }
522
- }
523
- catch {
524
- console.log(chalk_1.default.gray(' ○ Not running'));
525
- }
526
- }
527
- console.log('');
62
+ await (0, status_1.handleAiStatus)();
528
63
  }, {
529
- configRepository: (0, container_1.getContainer)().get(types_1.TYPES.ConfigRepository),
530
64
  requireAuth: false,
531
65
  requireOrganization: false,
532
66
  }));
@@ -535,1463 +69,7 @@ function setupAiCommands(program) {
535
69
  .description('Test Ollama model connectivity and response')
536
70
  .option('--model <model>', 'Model name to test (optional, tests first available if not specified)')
537
71
  .action((0, command_wrapper_1.wrapCommand)(async (options) => {
538
- const container = (0, container_1.getContainer)();
539
- const logger = container.get(types_1.TYPES.Logger);
540
- logger.info('Testing Ollama model connectivity', { model: options.model });
541
- console.log(chalk_1.default.blue('\n🧪 Testing Ollama Model\n'));
542
- // Check if Ollama is running
543
- console.log(chalk_1.default.blue('Step 1: Checking Ollama service...\n'));
544
- const isRunning = await checkOllamaRunning();
545
- if (!isRunning) {
546
- console.log(chalk_1.default.red('✗ Ollama is not running'));
547
- console.log(chalk_1.default.yellow('\nPlease start Ollama first with: edgible ai setup\n'));
548
- throw new Error('Ollama service is not running');
549
- }
550
- console.log(chalk_1.default.green('✓ Ollama is running\n'));
551
- // Determine which model to test
552
- let testModel = options.model;
553
- if (!testModel) {
554
- console.log(chalk_1.default.blue('Step 2: Finding available models...\n'));
555
- try {
556
- const output = (0, child_process_1.execSync)('ollama list', { encoding: 'utf8', timeout: 5000 });
557
- const lines = output.trim().split('\n');
558
- if (lines.length > 1) {
559
- // Parse first model from list (skip header)
560
- const modelLine = lines[1];
561
- testModel = modelLine.split(/\s+/)[0];
562
- console.log(chalk_1.default.gray(` Using first available model: ${testModel}\n`));
563
- }
564
- else {
565
- console.log(chalk_1.default.red('✗ No models found'));
566
- console.log(chalk_1.default.yellow('\nPlease pull a model first with: edgible ai setup\n'));
567
- throw new Error('No Ollama models available');
568
- }
569
- }
570
- catch (error) {
571
- console.log(chalk_1.default.red('✗ Failed to list models\n'));
572
- throw error;
573
- }
574
- }
575
- // Test model connectivity
576
- console.log(chalk_1.default.blue(`Step 3: Testing model "${testModel}"...\n`));
577
- const modelWorks = await testModelConnectivity(testModel, true);
578
- if (modelWorks) {
579
- console.log(chalk_1.default.green(`\n✅ Model "${testModel}" is working correctly!\n`));
580
- console.log(chalk_1.default.white('The model is ready to use with:'));
581
- console.log(chalk_1.default.gray(` edgible ai serve # Start Open WebUI`));
582
- console.log(chalk_1.default.gray(` ollama run ${testModel} # Use in terminal\n`));
583
- }
584
- else {
585
- console.log(chalk_1.default.red(`\n✗ Model "${testModel}" failed to respond\n`));
586
- console.log(chalk_1.default.yellow('Troubleshooting tips:'));
587
- console.log(chalk_1.default.gray(' 1. Check if the model exists: ollama list'));
588
- console.log(chalk_1.default.gray(' 2. Try pulling the model again: ollama pull ' + testModel));
589
- console.log(chalk_1.default.gray(' 3. Check Ollama logs for errors'));
590
- console.log(chalk_1.default.gray(' 4. Restart Ollama: edgible ai stop && edgible ai setup\n'));
591
- }
592
- }, {
593
- configRepository: (0, container_1.getContainer)().get(types_1.TYPES.ConfigRepository),
594
- }));
595
- }
596
- /**
597
- * Select a model based on capabilities and user input
598
- */
599
- async function selectModel(providedModel, capabilities) {
600
- let selectedModel = providedModel;
601
- if (!selectedModel) {
602
- // Filter to only excellent and good recommendations
603
- const suitableModels = capabilities.recommendedModels.filter((m) => m.suitability === 'excellent' || m.suitability === 'good');
604
- if (suitableModels.length === 0) {
605
- console.log(chalk_1.default.yellow('⚠ No models are well-suited for your system.'));
606
- console.log(chalk_1.default.yellow('You can still run smaller models, but performance may be limited.\n'));
607
- const allModels = capabilities.recommendedModels.map((m) => ({
608
- name: `${m.modelName} (${m.size}) - ${m.suitability}`,
609
- value: m.modelName.toLowerCase().replace(/\s+/g, '-').replace(/[()]/g, ''),
610
- }));
611
- // Add custom model option
612
- allModels.push({
613
- name: 'Enter custom model name',
614
- value: '__custom__',
615
- });
616
- const answer = await inquirer_1.default.prompt([
617
- {
618
- type: 'list',
619
- name: 'model',
620
- message: 'Select a model to use:',
621
- choices: allModels,
622
- },
623
- ]);
624
- if (answer.model === '__custom__') {
625
- const customAnswer = await inquirer_1.default.prompt([
626
- {
627
- type: 'input',
628
- name: 'model',
629
- message: 'Enter Ollama model name (e.g., deepseek-r1:8b, llama3.2:3b):',
630
- validate: (input) => {
631
- if (!input || input.trim().length === 0) {
632
- return 'Model name cannot be empty';
633
- }
634
- return true;
635
- },
636
- },
637
- ]);
638
- selectedModel = customAnswer.model.trim();
639
- }
640
- else {
641
- selectedModel = answer.model;
642
- }
643
- }
644
- else {
645
- console.log(chalk_1.default.green('Recommended models for your system:\n'));
646
- suitableModels.forEach((model) => {
647
- const icon = model.suitability === 'excellent' ? '✓' : '•';
648
- const color = model.suitability === 'excellent' ? chalk_1.default.green : chalk_1.default.cyan;
649
- console.log(color(` ${icon} ${model.modelName} (${model.size})`));
650
- console.log(chalk_1.default.gray(` ${model.reasoning}\n`));
651
- });
652
- const modelChoices = suitableModels.map((m) => ({
653
- name: `${m.modelName} (${m.size}) - ${m.suitability === 'excellent' ? 'Recommended' : 'Good fit'}`,
654
- value: m.modelName.toLowerCase().replace(/\s+/g, '-').replace(/[()]/g, ''),
655
- }));
656
- // Add option to see all models
657
- modelChoices.push({
658
- name: 'Show all models (including marginal/insufficient)',
659
- value: '__all__',
660
- });
661
- // Add custom model option
662
- modelChoices.push({
663
- name: 'Enter custom model name',
664
- value: '__custom__',
665
- });
666
- const answer = await inquirer_1.default.prompt([
667
- {
668
- type: 'list',
669
- name: 'model',
670
- message: 'Select a model to use:',
671
- choices: modelChoices,
672
- },
673
- ]);
674
- if (answer.model === '__custom__') {
675
- const customAnswer = await inquirer_1.default.prompt([
676
- {
677
- type: 'input',
678
- name: 'model',
679
- message: 'Enter Ollama model name (e.g., deepseek-r1:8b, llama3.2:3b):',
680
- validate: (input) => {
681
- if (!input || input.trim().length === 0) {
682
- return 'Model name cannot be empty';
683
- }
684
- return true;
685
- },
686
- },
687
- ]);
688
- selectedModel = customAnswer.model.trim();
689
- }
690
- else if (answer.model === '__all__') {
691
- const allModels = capabilities.recommendedModels.map((m) => ({
692
- name: `${m.modelName} (${m.size}) - ${m.suitability}`,
693
- value: m.modelName.toLowerCase().replace(/\s+/g, '-').replace(/[()]/g, ''),
694
- }));
695
- // Add custom model option to all models list too
696
- allModels.push({
697
- name: 'Enter custom model name',
698
- value: '__custom__',
699
- });
700
- const allAnswer = await inquirer_1.default.prompt([
701
- {
702
- type: 'list',
703
- name: 'model',
704
- message: 'Select a model to use:',
705
- choices: allModels,
706
- },
707
- ]);
708
- if (allAnswer.model === '__custom__') {
709
- const customAnswer = await inquirer_1.default.prompt([
710
- {
711
- type: 'input',
712
- name: 'model',
713
- message: 'Enter Ollama model name (e.g., deepseek-r1:8b, llama3.2:3b):',
714
- validate: (input) => {
715
- if (!input || input.trim().length === 0) {
716
- return 'Model name cannot be empty';
717
- }
718
- return true;
719
- },
720
- },
721
- ]);
722
- selectedModel = customAnswer.model.trim();
723
- }
724
- else {
725
- selectedModel = allAnswer.model;
726
- }
727
- }
728
- else {
729
- selectedModel = answer.model;
730
- }
731
- }
732
- }
733
- // Normalize model name (Ollama uses lowercase with dashes)
734
- if (!selectedModel) {
735
- throw new Error('No model selected');
736
- }
737
- // If it's already a custom model (contains : or /), use as-is
738
- // Otherwise normalize it (recommended models need to be mapped to Ollama format)
739
- let ollamaModelName;
740
- if (selectedModel.includes(':') || selectedModel.includes('/')) {
741
- // Custom model name format (e.g., deepseek-r1:14b, llama3.2:3b, or org/model:tag)
742
- ollamaModelName = selectedModel;
743
- }
744
- else {
745
- // Normalize recommended model names to Ollama format (e.g., deepseek-r1-14b -> deepseek-r1:14b)
746
- ollamaModelName = normalizeModelName(selectedModel);
747
- }
748
- return ollamaModelName;
749
- }
750
- /**
751
- * Setup local Ollama installation and configuration
752
- */
753
- async function setupLocalOllama(options) {
754
- // Step 1: Check if Ollama is installed
755
- console.log(chalk_1.default.blue('Step 1: Checking Ollama installation...\n'));
756
- const isOllamaInstalled = await checkOllamaInstalled();
757
- if (!isOllamaInstalled) {
758
- let shouldInstall = options.autoInstall || false;
759
- if (!shouldInstall) {
760
- const answer = await inquirer_1.default.prompt([
761
- {
762
- type: 'confirm',
763
- name: 'install',
764
- message: 'Ollama is not installed. Would you like to install it now?',
765
- default: true,
766
- },
767
- ]);
768
- shouldInstall = answer.install;
769
- }
770
- if (shouldInstall) {
771
- console.log(chalk_1.default.yellow('Installing Ollama...\n'));
772
- await installOllama();
773
- console.log(chalk_1.default.green('✓ Ollama installed successfully\n'));
774
- }
775
- else {
776
- throw new Error('Ollama is required but not installed. Please install it manually from https://ollama.com');
777
- }
778
- }
779
- else {
780
- console.log(chalk_1.default.green('✓ Ollama is already installed\n'));
781
- }
782
- // Step 2: Discover system capabilities
783
- console.log(chalk_1.default.blue('Step 2: Discovering system capabilities...\n'));
784
- const capabilities = await SystemCapabilityDetector_1.SystemCapabilityDetector.detectCapabilities();
785
- // Show GPU driver support status
786
- if (capabilities.gpuDriverSupport.ollamaGpuReady) {
787
- console.log(chalk_1.default.green('✓ GPU acceleration is ready for Ollama'));
788
- if (capabilities.gpuDriverSupport.ollamaGpuReason) {
789
- console.log(chalk_1.default.gray(` ${capabilities.gpuDriverSupport.ollamaGpuReason}\n`));
790
- }
791
- }
792
- else {
793
- console.log(chalk_1.default.yellow('⚠ GPU acceleration not detected - Ollama may run in CPU mode'));
794
- if (capabilities.gpuDriverSupport.ollamaGpuReason) {
795
- console.log(chalk_1.default.gray(` ${capabilities.gpuDriverSupport.ollamaGpuReason}\n`));
796
- }
797
- }
798
- // Step 5: Start Ollama service
799
- // console.log(chalk.blue('Step 5: Starting Ollama service...\n'));
800
- // await startOllama();
801
- //
802
- // // Step 6: Verify Ollama is running
803
- // console.log(chalk.blue('Step 6: Verifying Ollama is running...\n'));
804
- // const isRunning = await checkOllamaRunning();
805
- //
806
- // if (!isRunning) {
807
- // throw new Error('Ollama service failed to start. Please check the logs.');
808
- // }
809
- //
810
- // console.log(chalk.green('✓ Ollama is running\n'));
811
- //
812
- // // Step 6.5: Check and fix Ollama binding if needed
813
- // console.log(chalk.blue('Step 6.5: Checking Ollama network binding...\n'));
814
- // const listeningAddress = await checkOllamaListeningAddress();
815
- //
816
- // if (listeningAddress === '127.0.0.1') {
817
- // console.log(chalk.yellow('⚠ Ollama is listening on localhost only (127.0.0.1:11434)'));
818
- // console.log(chalk.gray(' This will prevent Docker containers and network access from reaching Ollama.\n'));
819
- // console.log(chalk.yellow(' Reconfiguring Ollama to listen on all interfaces (0.0.0.0:11434)...\n'));
820
- //
821
- // const fixed = await fixOllamaBinding();
822
- // if (fixed) {
823
- // // Wait a moment and verify it's now on 0.0.0.0
824
- // await new Promise((resolve) => setTimeout(resolve, 2000));
825
- // const newAddress = await checkOllamaListeningAddress();
826
- // if (newAddress === '0.0.0.0') {
827
- // console.log(chalk.green('✓ Ollama reconfigured to listen on 0.0.0.0:11434\n'));
828
- // } else {
829
- // console.log(chalk.yellow('⚠ Could not verify binding change. Ollama may need manual configuration.\n'));
830
- // }
831
- // } else {
832
- // console.log(chalk.yellow('⚠ Could not automatically reconfigure Ollama binding.\n'));
833
- // console.log(chalk.gray(' Please manually set OLLAMA_HOST=0.0.0.0:11434 and restart Ollama.\n'));
834
- // }
835
- // } else if (listeningAddress === '0.0.0.0') {
836
- // console.log(chalk.green('✓ Ollama is listening on all interfaces (0.0.0.0:11434)\n'));
837
- // } else {
838
- // console.log(chalk.gray(' Could not determine Ollama binding address (this is usually fine)\n'));
839
- // }
840
- //
841
- // Step 7: Test model connectivity
842
- // console.log(chalk.blue('Step 7: Testing model connectivity...\n'));
843
- // const modelWorks = await testModelConnectivity(ollamaModelName);
844
- //
845
- // if (modelWorks) {
846
- // console.log(chalk.green('✓ Model is accessible and ready to use\n'));
847
- // } else {
848
- // console.log(chalk.yellow('⚠ Model may not be fully loaded yet\n'));
849
- // console.log(chalk.gray(` This is normal for large models. The model will load on first use.\n`));
850
- // }
851
- //
852
- console.log(chalk_1.default.green('✅ Phase 1: Local Ollama Setup Complete!\n'));
853
- return { capabilities };
854
- }
855
- /**
856
- * Setup platform integration (create applications)
857
- */
858
- async function setupPlatformIntegration(options) {
859
- const { model, capabilities, container, logger } = options;
860
- // Always require auth and organization for platform integration
861
- const configRepository = container.get(types_1.TYPES.ConfigRepository);
862
- try {
863
- (0, config_validator_1.validateConfig)(configRepository, {
864
- requireAuth: true,
865
- requireOrganization: true,
866
- requireDeviceId: true,
867
- });
868
- }
869
- catch (error) {
870
- console.log(chalk_1.default.yellow('\n⚠ Platform integration requires authentication'));
871
- console.log(chalk_1.default.blue('\nPlease login first:'));
872
- console.log(chalk_1.default.gray(' edgible auth login\n'));
873
- console.log(chalk_1.default.gray('Then run setup again to continue.\n'));
874
- throw error;
875
- }
876
- // Get device ID from config (same device as agent)
877
- const deviceId = (0, config_validator_1.requireDeviceId)(configRepository);
878
- const deviceInfo = await container.get(types_1.TYPES.EdgibleService).getDevice(deviceId);
879
- const deviceName = deviceInfo.device?.name || deviceId;
880
- console.log(chalk_1.default.blue('\n📡 Creating Platform Applications\n'));
881
- const applicationService = container.get(types_1.TYPES.ApplicationService);
882
- const gatewayService = container.get(types_1.TYPES.GatewayService);
883
- const edgibleService = container.get(types_1.TYPES.EdgibleService);
884
- // Step 3: Select model based on recommendations (before creating application)
885
- console.log(chalk_1.default.blue('Step 3: Selecting model...\n'));
886
- const ollamaModelName = await selectModel(model, capabilities);
887
- console.log(chalk_1.default.blue(`\nSelected model: ${ollamaModelName}\n`));
888
- // Step 7: Create Ollama API Application
889
- console.log(chalk_1.default.blue('Step 7: Creating Ollama API application...\n'));
890
- console.log(chalk_1.default.gray(` Device: ${deviceName} (${deviceId.substring(0, 8)}...)\n`));
891
- console.log(chalk_1.default.gray(` Gateway: None (local/internal access only)\n`));
892
- console.log(chalk_1.default.gray(` Protocol: HTTPS\n`));
893
- const ollamaResult = await createOllamaApplication({
894
- modelName: ollamaModelName,
895
- deviceId: deviceId,
896
- configRepository,
897
- applicationService,
898
- gatewayService,
899
- edgibleService,
900
- logger,
901
- });
902
- const createdOllamaApp = ollamaResult.app;
903
- // Ensure URL has https:// protocol
904
- const ollamaUrl = ollamaResult.url.startsWith('http://') || ollamaResult.url.startsWith('https://')
905
- ? ollamaResult.url
906
- : `https://${ollamaResult.url}`;
907
- console.log(chalk_1.default.green('✓ Ollama API application created'));
908
- console.log(chalk_1.default.cyan(` URL: ${ollamaUrl}\n`));
909
- // Step 8: Verify endpoint is accessible (retry mechanism)
910
- console.log(chalk_1.default.blue('Step 8: Verifying endpoint is accessible...\n'));
911
- console.log(chalk_1.default.gray(` Checking ${ollamaUrl}/api/tags...\n`));
912
- let endpointAccessible = false;
913
- const maxRetries = 20;
914
- const retryDelay = 5000; // 5 seconds
915
- for (let attempt = 1; attempt <= maxRetries; attempt++) {
916
- try {
917
- const controller = new AbortController();
918
- const timeout = setTimeout(() => controller.abort(), 5000);
919
- const response = await (0, node_fetch_1.default)(`${ollamaUrl}/api/tags`, {
920
- method: 'GET',
921
- signal: controller.signal,
922
- });
923
- clearTimeout(timeout);
924
- if (response.ok) {
925
- endpointAccessible = true;
926
- console.log(chalk_1.default.green(`✓ Endpoint is accessible (attempt ${attempt}/${maxRetries})\n`));
927
- break;
928
- }
929
- }
930
- catch (error) {
931
- // Endpoint not ready yet, continue retrying
932
- if (attempt < maxRetries) {
933
- console.log(chalk_1.default.gray(` Attempt ${attempt}/${maxRetries} failed, retrying in ${retryDelay / 1000} seconds...\n`));
934
- await new Promise((resolve) => setTimeout(resolve, retryDelay));
935
- }
936
- }
937
- }
938
- if (!endpointAccessible) {
939
- console.log(chalk_1.default.yellow(`⚠ Endpoint not accessible after ${maxRetries} attempts\n`));
940
- console.log(chalk_1.default.gray(` The application may still be starting. This can take several minutes.\n`));
941
- console.log(chalk_1.default.gray(` You can check status with: curl ${ollamaUrl}/api/tags\n`));
942
- }
943
- // Step 4: Check if model is already pulled
944
- console.log(chalk_1.default.blue('Step 4: Checking if model is available...\n'));
945
- const isModelAvailable = await checkModelAvailable(ollamaModelName);
946
- if (!isModelAvailable) {
947
- console.log(chalk_1.default.yellow(`Model ${ollamaModelName} is not available locally.`));
948
- const answer = await inquirer_1.default.prompt([
949
- {
950
- type: 'confirm',
951
- name: 'pull',
952
- message: `Would you like to download ${ollamaModelName} now? (This may take a while)`,
953
- default: true,
954
- },
955
- ]);
956
- if (answer.pull) {
957
- console.log(chalk_1.default.yellow(`\nDownloading ${ollamaModelName}...\n`));
958
- await pullModel(ollamaModelName);
959
- console.log(chalk_1.default.green(`✓ Model ${ollamaModelName} downloaded successfully\n`));
960
- }
961
- else {
962
- throw new Error(`Model ${ollamaModelName} is not available. Please pull it manually with: ollama pull ${ollamaModelName}`);
963
- }
964
- }
965
- else {
966
- console.log(chalk_1.default.green(`✓ Model ${ollamaModelName} is available\n`));
967
- }
968
- // Step 9: Create Open WebUI Application
969
- console.log(chalk_1.default.blue('Step 9: Creating Open WebUI application...\n'));
970
- console.log(chalk_1.default.gray(` Device: ${deviceName} (${deviceId.substring(0, 8)}...)\n`));
971
- console.log(chalk_1.default.gray(` Gateway: Managed Gateway\n`));
972
- console.log(chalk_1.default.gray(` Protocol: HTTPS\n`));
973
- console.log(chalk_1.default.gray(` Connected to Ollama: ${ollamaUrl}\n`));
974
- const webUIResult = await createOpenWebUIApplication({
975
- ollamaUrl: ollamaUrl,
976
- deviceId: deviceId,
977
- configRepository: configRepository,
978
- applicationService,
979
- gatewayService,
980
- edgibleService,
981
- logger,
982
- });
983
- const createdWebUIApp = webUIResult.app;
984
- const webUIUrl = webUIResult.url;
985
- console.log(chalk_1.default.green('✓ Open WebUI application created'));
986
- console.log(chalk_1.default.cyan(` URL: ${webUIUrl}\n`));
987
- return {
988
- ollamaUrl,
989
- webUIUrl,
990
- createdOllamaApp,
991
- createdWebUIApp,
992
- deviceName,
993
- deviceId,
994
- ollamaModelName,
995
- };
996
- }
997
- /**
998
- * Display setup summary
999
- */
1000
- function displaySetupSummary(options) {
1001
- const { ollamaModelName, ollamaUrl, webUIUrl, deviceName, deviceId, createdOllamaApp, createdWebUIApp } = options;
1002
- console.log(chalk_1.default.blue('\n🎉 AI Setup Complete!\n'));
1003
- console.log(chalk_1.default.white('📋 Summary:\n'));
1004
- console.log(chalk_1.default.gray(` Model: ${ollamaModelName}`));
1005
- console.log(chalk_1.default.gray(` Ollama API: ${ollamaUrl} (local/internal only)`));
1006
- if (webUIUrl) {
1007
- console.log(chalk_1.default.gray(` Open WebUI: ${webUIUrl} (public via managed gateway)`));
1008
- }
1009
- console.log(chalk_1.default.gray(` Device: ${deviceName} (${deviceId.substring(0, 8)}...)`));
1010
- if (createdOllamaApp || createdWebUIApp) {
1011
- console.log(chalk_1.default.white('\n📱 Created Applications:\n'));
1012
- if (createdOllamaApp) {
1013
- console.log(chalk_1.default.gray(` • ollama-api (${createdOllamaApp.id}) - No gateway (local only)`));
1014
- }
1015
- if (createdWebUIApp) {
1016
- console.log(chalk_1.default.gray(` • open-webui (${createdWebUIApp.id}) - Managed gateway (public)`));
1017
- }
1018
- }
1019
- console.log(chalk_1.default.white('\n🔧 Next Steps:\n'));
1020
- console.log(chalk_1.default.gray(` • Test endpoint: curl ${ollamaUrl}/api/tags`));
1021
- console.log(chalk_1.default.gray(` • Test model: edgible ai test --model ${ollamaModelName}`));
1022
- console.log(chalk_1.default.gray(' • Status: edgible ai status'));
1023
- console.log(chalk_1.default.gray(' • List apps: edgible application list'));
1024
- if (createdOllamaApp) {
1025
- console.log(chalk_1.default.gray(' • Teardown: edgible ai teardown --remove-apps'));
1026
- }
1027
- }
1028
- /**
1029
- * Check if Ollama is installed
1030
- */
1031
- async function checkOllamaInstalled() {
1032
- try {
1033
- (0, child_process_1.execSync)('ollama --version', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1034
- return true;
1035
- }
1036
- catch {
1037
- return false;
1038
- }
1039
- }
1040
- /**
1041
- * Install Ollama based on platform
1042
- */
1043
- async function installOllama() {
1044
- const platform = os.platform();
1045
- try {
1046
- if (platform === 'linux') {
1047
- // Use official Ollama install script
1048
- (0, child_process_1.execSync)('curl -fsSL https://ollama.com/install.sh | sh', {
1049
- encoding: 'utf8',
1050
- stdio: 'inherit',
1051
- });
1052
- }
1053
- else if (platform === 'darwin') {
1054
- // macOS - check for Homebrew
1055
- try {
1056
- (0, child_process_1.execSync)('brew --version', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1057
- (0, child_process_1.execSync)('brew install ollama', {
1058
- encoding: 'utf8',
1059
- stdio: 'inherit',
1060
- });
1061
- }
1062
- catch {
1063
- // Fallback to manual install instructions
1064
- console.log(chalk_1.default.yellow('Homebrew not found. Please install Ollama manually:'));
1065
- console.log(chalk_1.default.gray(' Visit: https://ollama.com/download\n'));
1066
- throw new Error('Ollama installation requires Homebrew or manual installation');
1067
- }
1068
- }
1069
- else if (platform === 'win32') {
1070
- // Windows - use winget or provide instructions
1071
- try {
1072
- (0, child_process_1.execSync)('winget --version', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1073
- (0, child_process_1.execSync)('winget install Ollama.Ollama', {
1074
- encoding: 'utf8',
1075
- stdio: 'inherit',
1076
- });
1077
- }
1078
- catch {
1079
- console.log(chalk_1.default.yellow('winget not found. Please install Ollama manually:'));
1080
- console.log(chalk_1.default.gray(' Visit: https://ollama.com/download\n'));
1081
- throw new Error('Ollama installation requires winget or manual installation');
1082
- }
1083
- }
1084
- else {
1085
- throw new Error(`Unsupported platform: ${platform}`);
1086
- }
1087
- }
1088
- catch (error) {
1089
- console.error(chalk_1.default.red('Failed to install Ollama:'), error);
1090
- throw error;
1091
- }
1092
- }
1093
- /**
1094
- * Check if a model is available locally
1095
- */
1096
- async function checkModelAvailable(modelName) {
1097
- try {
1098
- const output = (0, child_process_1.execSync)('ollama list', { encoding: 'utf8', timeout: 5000 });
1099
- // Check if model name appears in the list
1100
- return output.toLowerCase().includes(modelName.toLowerCase());
1101
- }
1102
- catch {
1103
- return false;
1104
- }
1105
- }
1106
- /**
1107
- * Pull a model from Ollama
1108
- */
1109
- async function pullModel(modelName) {
1110
- try {
1111
- (0, child_process_1.execSync)(`OLLAMA_HOST=127.0.0.1:11435 ollama pull ${modelName}`, {
1112
- encoding: 'utf8',
1113
- stdio: 'inherit',
1114
- });
1115
- }
1116
- catch (error) {
1117
- console.error(chalk_1.default.red(`Failed to pull model ${modelName}:`), error);
1118
- throw error;
1119
- }
1120
- }
1121
- /**
1122
- * Start Ollama service
1123
- * Configures Ollama to listen on 0.0.0.0:11434 so Docker containers can access it
1124
- */
1125
- async function startOllama() {
1126
- const platform = os.platform();
1127
- try {
1128
- if (platform === 'linux') {
1129
- // Check for systemd service (user or system)
1130
- let serviceType = null;
1131
- let serviceName = 'ollama';
1132
- // Check user service first
1133
- try {
1134
- (0, child_process_1.execSync)('systemctl --user is-enabled ollama > /dev/null 2>&1', { encoding: 'utf8', timeout: 2000 });
1135
- serviceType = 'user';
1136
- }
1137
- catch {
1138
- // Check system service
1139
- try {
1140
- (0, child_process_1.execSync)('systemctl is-enabled ollama > /dev/null 2>&1', { encoding: 'utf8', timeout: 2000 });
1141
- serviceType = 'system';
1142
- }
1143
- catch {
1144
- // No systemd service found
1145
- }
1146
- }
1147
- if (serviceType) {
1148
- // Configure systemd service to listen on all interfaces
1149
- const systemctlCmd = serviceType === 'user' ? 'systemctl --user' : 'sudo systemctl';
1150
- const serviceFile = serviceType === 'user'
1151
- ? `${os.homedir()}/.config/systemd/user/ollama.service.d/override.conf`
1152
- : '/etc/systemd/system/ollama.service.d/override.conf';
1153
- const serviceDir = path.dirname(serviceFile);
1154
- try {
1155
- // Create override directory if it doesn't exist
1156
- if (!fs.existsSync(serviceDir)) {
1157
- (0, child_process_1.execSync)(`mkdir -p "${serviceDir}"`, { encoding: 'utf8' });
1158
- }
1159
- // Check if override already has OLLAMA_HOST
1160
- let needsUpdate = true;
1161
- if (fs.existsSync(serviceFile)) {
1162
- const content = fs.readFileSync(serviceFile, 'utf8');
1163
- if (content.includes('OLLAMA_HOST=0.0.0.0:11434')) {
1164
- needsUpdate = false;
1165
- }
1166
- }
1167
- if (needsUpdate) {
1168
- // Write override file
1169
- const overrideContent = `[Service]
1170
- Environment="OLLAMA_HOST=0.0.0.0:11434"
1171
- `;
1172
- fs.writeFileSync(serviceFile, overrideContent);
1173
- console.log(chalk_1.default.gray(`Configured systemd service to listen on 0.0.0.0:11434\n`));
1174
- }
1175
- // Reload and restart
1176
- (0, child_process_1.execSync)(`${systemctlCmd} daemon-reload`, { encoding: 'utf8', timeout: 3000 });
1177
- (0, child_process_1.execSync)(`${systemctlCmd} restart ollama`, { encoding: 'utf8', timeout: 5000 });
1178
- await new Promise((resolve) => setTimeout(resolve, 2000));
1179
- console.log(chalk_1.default.gray('Started Ollama via systemd listening on 0.0.0.0:11434 (accessible to Docker)\n'));
1180
- return;
1181
- }
1182
- catch (error) {
1183
- console.log(chalk_1.default.yellow(`⚠ Could not configure systemd service: ${error instanceof Error ? error.message : 'Unknown error'}`));
1184
- console.log(chalk_1.default.gray('Falling back to manual start...\n'));
1185
- // Fall through to manual start
1186
- }
1187
- }
1188
- // Fallback: start ollama serve in background with OLLAMA_HOST set
1189
- try {
1190
- // Kill any existing ollama processes first (if not managed by systemd)
1191
- try {
1192
- (0, child_process_1.execSync)('pkill -f "ollama serve"', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1193
- await new Promise((resolve) => setTimeout(resolve, 1000));
1194
- }
1195
- catch {
1196
- // Ignore if no process to kill
1197
- }
1198
- // Start Ollama with host binding to all interfaces
1199
- (0, child_process_1.execSync)('OLLAMA_HOST=0.0.0.0:11434 nohup ollama serve > /dev/null 2>&1 &', {
1200
- encoding: 'utf8',
1201
- timeout: 1000,
1202
- shell: '/bin/bash'
1203
- });
1204
- // Wait a bit for it to start
1205
- await new Promise((resolve) => setTimeout(resolve, 2000));
1206
- console.log(chalk_1.default.gray('Started Ollama listening on 0.0.0.0:11434 (accessible to Docker)\n'));
1207
- }
1208
- catch {
1209
- // Ignore - may already be running
1210
- }
1211
- }
1212
- else if (platform === 'darwin') {
1213
- // macOS - try launchctl or start directly
1214
- try {
1215
- (0, child_process_1.execSync)('launchctl start com.ollama.ollama', { encoding: 'utf8', timeout: 3000 });
1216
- console.log(chalk_1.default.gray('Note: Started via launchd. To allow Docker access, set OLLAMA_HOST in launchd config\n'));
1217
- }
1218
- catch {
1219
- // Fallback: start ollama serve with host binding
1220
- try {
1221
- // Kill any existing ollama processes first
1222
- try {
1223
- (0, child_process_1.execSync)('pkill -f "ollama serve"', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1224
- await new Promise((resolve) => setTimeout(resolve, 1000));
1225
- }
1226
- catch {
1227
- // Ignore if no process to kill
1228
- }
1229
- (0, child_process_1.execSync)('OLLAMA_HOST=0.0.0.0:11434 nohup ollama serve > /dev/null 2>&1 &', {
1230
- encoding: 'utf8',
1231
- timeout: 1000,
1232
- shell: '/bin/bash'
1233
- });
1234
- await new Promise((resolve) => setTimeout(resolve, 2000));
1235
- console.log(chalk_1.default.gray('Started Ollama listening on 0.0.0.0:11434 (accessible to Docker)\n'));
1236
- }
1237
- catch {
1238
- // Ignore
1239
- }
1240
- }
1241
- }
1242
- else if (platform === 'win32') {
1243
- // Windows - Ollama typically runs as a service
1244
- try {
1245
- (0, child_process_1.execSync)('net start Ollama', { encoding: 'utf8', timeout: 3000 });
1246
- console.log(chalk_1.default.gray('Note: Started as Windows service. To allow Docker access, set OLLAMA_HOST environment variable\n'));
1247
- }
1248
- catch {
1249
- // Service might already be running or not installed as service
1250
- // Try to start it directly with host binding
1251
- try {
1252
- (0, child_process_1.execSync)('set OLLAMA_HOST=0.0.0.0:11434 && start /B ollama serve', {
1253
- encoding: 'utf8',
1254
- timeout: 1000
1255
- });
1256
- await new Promise((resolve) => setTimeout(resolve, 2000));
1257
- console.log(chalk_1.default.gray('Started Ollama listening on 0.0.0.0:11434 (accessible to Docker)\n'));
1258
- }
1259
- catch {
1260
- // Ignore
1261
- }
1262
- }
1263
- }
1264
- }
1265
- catch (error) {
1266
- // Ollama might already be running, which is fine
1267
- console.log(chalk_1.default.gray('Note: Ollama service may already be running\n'));
1268
- }
1269
- }
1270
- /**
1271
- * Check if Ollama is running
1272
- */
1273
- async function checkOllamaRunning() {
1274
- try {
1275
- // Try to query Ollama API
1276
- const controller = new AbortController();
1277
- const timeout = setTimeout(() => controller.abort(), 3000);
1278
- try {
1279
- const response = await (0, node_fetch_1.default)('http://localhost:11434/api/tags', {
1280
- method: 'GET',
1281
- signal: controller.signal,
1282
- });
1283
- clearTimeout(timeout);
1284
- return response.ok;
1285
- }
1286
- catch {
1287
- clearTimeout(timeout);
1288
- throw new Error('Fetch failed');
1289
- }
1290
- }
1291
- catch {
1292
- // Try alternative: check if ollama process is running
1293
- try {
1294
- const platform = os.platform();
1295
- if (platform === 'linux' || platform === 'darwin') {
1296
- (0, child_process_1.execSync)('pgrep -f ollama', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1297
- return true;
1298
- }
1299
- else if (platform === 'win32') {
1300
- (0, child_process_1.execSync)('tasklist /FI "IMAGENAME eq ollama.exe"', {
1301
- encoding: 'utf8',
1302
- timeout: 2000,
1303
- stdio: 'ignore',
1304
- });
1305
- return true;
1306
- }
1307
- }
1308
- catch {
1309
- return false;
1310
- }
1311
- return false;
1312
- }
1313
- }
1314
- /**
1315
- * Check what address Ollama is listening on
1316
- * @returns '0.0.0.0' if listening on all interfaces, '127.0.0.1' if localhost only, null if unknown
1317
- */
1318
- async function checkOllamaListeningAddress() {
1319
- try {
1320
- const platform = os.platform();
1321
- let output;
1322
- if (platform === 'linux' || platform === 'darwin') {
1323
- // Try ss first (modern), fallback to netstat
1324
- try {
1325
- output = (0, child_process_1.execSync)('ss -tlnp 2>/dev/null | grep 11434', {
1326
- encoding: 'utf8',
1327
- timeout: 2000
1328
- });
1329
- }
1330
- catch {
1331
- try {
1332
- output = (0, child_process_1.execSync)('netstat -tlnp 2>/dev/null | grep 11434', {
1333
- encoding: 'utf8',
1334
- timeout: 2000
1335
- });
1336
- }
1337
- catch {
1338
- return null;
1339
- }
1340
- }
1341
- }
1342
- else if (platform === 'win32') {
1343
- try {
1344
- output = (0, child_process_1.execSync)('netstat -an | findstr :11434', {
1345
- encoding: 'utf8',
1346
- timeout: 2000,
1347
- });
1348
- }
1349
- catch {
1350
- return null;
1351
- }
1352
- }
1353
- else {
1354
- return null;
1355
- }
1356
- if (output.includes('0.0.0.0:11434') || output.includes('*:11434') || output.includes('[::]:11434')) {
1357
- return '0.0.0.0';
1358
- }
1359
- else if (output.includes('127.0.0.1:11434') || output.includes('::1:11434')) {
1360
- return '127.0.0.1';
1361
- }
1362
- return null;
1363
- }
1364
- catch {
1365
- return null;
1366
- }
1367
- }
1368
- /**
1369
- * Fix Ollama binding if it's listening on localhost only
1370
- * Attempts to reconfigure Ollama to listen on 0.0.0.0:11434
1371
- */
1372
- async function fixOllamaBinding() {
1373
- const platform = os.platform();
1374
- try {
1375
- if (platform === 'linux') {
1376
- // Check for systemd service first
1377
- let serviceType = null;
1378
- try {
1379
- (0, child_process_1.execSync)('systemctl --user is-enabled ollama > /dev/null 2>&1', { encoding: 'utf8', timeout: 2000 });
1380
- serviceType = 'user';
1381
- }
1382
- catch {
1383
- try {
1384
- (0, child_process_1.execSync)('systemctl is-enabled ollama > /dev/null 2>&1', { encoding: 'utf8', timeout: 2000 });
1385
- serviceType = 'system';
1386
- }
1387
- catch {
1388
- // No systemd service
1389
- }
1390
- }
1391
- if (serviceType) {
1392
- // Configure systemd service
1393
- const systemctlCmd = serviceType === 'user' ? 'systemctl --user' : 'sudo systemctl';
1394
- const serviceFile = serviceType === 'user'
1395
- ? `${os.homedir()}/.config/systemd/user/ollama.service.d/override.conf`
1396
- : '/etc/systemd/system/ollama.service.d/override.conf';
1397
- const serviceDir = path.dirname(serviceFile);
1398
- try {
1399
- // Create override directory if it doesn't exist
1400
- if (!fs.existsSync(serviceDir)) {
1401
- (0, child_process_1.execSync)(`mkdir -p "${serviceDir}"`, { encoding: 'utf8' });
1402
- }
1403
- // Check if override already has OLLAMA_HOST
1404
- let needsUpdate = true;
1405
- if (fs.existsSync(serviceFile)) {
1406
- const content = fs.readFileSync(serviceFile, 'utf8');
1407
- if (content.includes('OLLAMA_HOST=0.0.0.0:11434')) {
1408
- needsUpdate = false;
1409
- }
1410
- }
1411
- if (needsUpdate) {
1412
- // Write override file
1413
- const overrideContent = `[Service]
1414
- Environment="OLLAMA_HOST=0.0.0.0:11434"
1415
- `;
1416
- fs.writeFileSync(serviceFile, overrideContent);
1417
- // Reload and restart
1418
- (0, child_process_1.execSync)(`${systemctlCmd} daemon-reload`, { encoding: 'utf8', timeout: 3000 });
1419
- (0, child_process_1.execSync)(`${systemctlCmd} restart ollama`, { encoding: 'utf8', timeout: 5000 });
1420
- await new Promise((resolve) => setTimeout(resolve, 2000));
1421
- return true;
1422
- }
1423
- }
1424
- catch (error) {
1425
- // Fall through to manual restart
1426
- }
1427
- }
1428
- // Fallback: kill and restart with OLLAMA_HOST
1429
- try {
1430
- (0, child_process_1.execSync)('pkill -f "ollama serve"', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1431
- await new Promise((resolve) => setTimeout(resolve, 1000));
1432
- }
1433
- catch {
1434
- // Ignore if no process to kill
1435
- }
1436
- (0, child_process_1.execSync)('OLLAMA_HOST=0.0.0.0:11434 nohup ollama serve > /dev/null 2>&1 &', {
1437
- encoding: 'utf8',
1438
- timeout: 1000,
1439
- shell: '/bin/bash'
1440
- });
1441
- await new Promise((resolve) => setTimeout(resolve, 2000));
1442
- return true;
1443
- }
1444
- else if (platform === 'darwin') {
1445
- // macOS - try to restart with OLLAMA_HOST
1446
- try {
1447
- (0, child_process_1.execSync)('pkill -f "ollama serve"', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1448
- await new Promise((resolve) => setTimeout(resolve, 1000));
1449
- }
1450
- catch {
1451
- // Ignore
1452
- }
1453
- (0, child_process_1.execSync)('OLLAMA_HOST=0.0.0.0:11434 nohup ollama serve > /dev/null 2>&1 &', {
1454
- encoding: 'utf8',
1455
- timeout: 1000,
1456
- shell: '/bin/bash'
1457
- });
1458
- await new Promise((resolve) => setTimeout(resolve, 2000));
1459
- return true;
1460
- }
1461
- else if (platform === 'win32') {
1462
- // Windows - try to restart with OLLAMA_HOST
1463
- try {
1464
- (0, child_process_1.execSync)('taskkill /F /IM ollama.exe', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1465
- await new Promise((resolve) => setTimeout(resolve, 1000));
1466
- }
1467
- catch {
1468
- // Ignore
1469
- }
1470
- (0, child_process_1.execSync)('set OLLAMA_HOST=0.0.0.0:11434 && start /B ollama serve', {
1471
- encoding: 'utf8',
1472
- timeout: 1000
1473
- });
1474
- await new Promise((resolve) => setTimeout(resolve, 2000));
1475
- return true;
1476
- }
1477
- return false;
1478
- }
1479
- catch (error) {
1480
- return false;
1481
- }
1482
- }
1483
- /**
1484
- * Stop Ollama service
1485
- */
1486
- async function stopOllama() {
1487
- const platform = os.platform();
1488
- try {
1489
- if (platform === 'linux') {
1490
- // Try systemd service first
1491
- try {
1492
- (0, child_process_1.execSync)('systemctl --user stop ollama', { encoding: 'utf8', timeout: 3000 });
1493
- return;
1494
- }
1495
- catch {
1496
- // Fallback to killing process
1497
- }
1498
- // Fallback: kill ollama processes
1499
- try {
1500
- (0, child_process_1.execSync)('pkill -f ollama', { encoding: 'utf8', timeout: 2000 });
1501
- }
1502
- catch {
1503
- // Process might not be running
1504
- }
1505
- }
1506
- else if (platform === 'darwin') {
1507
- // macOS
1508
- try {
1509
- (0, child_process_1.execSync)('launchctl stop com.ollama.ollama', { encoding: 'utf8', timeout: 3000 });
1510
- }
1511
- catch {
1512
- // Fallback
1513
- try {
1514
- (0, child_process_1.execSync)('pkill -f ollama', { encoding: 'utf8', timeout: 2000 });
1515
- }
1516
- catch {
1517
- // Ignore
1518
- }
1519
- }
1520
- }
1521
- else if (platform === 'win32') {
1522
- // Windows
1523
- try {
1524
- (0, child_process_1.execSync)('net stop Ollama', { encoding: 'utf8', timeout: 3000 });
1525
- }
1526
- catch {
1527
- // Fallback
1528
- try {
1529
- (0, child_process_1.execSync)('taskkill /F /IM ollama.exe', { encoding: 'utf8', timeout: 2000 });
1530
- }
1531
- catch {
1532
- // Ignore
1533
- }
1534
- }
1535
- }
1536
- }
1537
- catch (error) {
1538
- console.error(chalk_1.default.yellow('Warning: Error stopping Ollama service:'), error);
1539
- }
1540
- }
1541
- /**
1542
- * Normalize model name for Ollama (lowercase, dashes, no special chars)
1543
- * Maps recommended model names to their Ollama model names
1544
- * Converts last dash before size to colon (e.g., llama-3.2-3b -> llama3.2:3b)
1545
- */
1546
- function normalizeModelName(modelName) {
1547
- // First normalize: lowercase, replace spaces with dashes, remove parentheses
1548
- // Keep dots for version numbers (e.g., 3.2, 2.5)
1549
- const normalized = modelName.toLowerCase().replace(/\s+/g, '-').replace(/[()]/g, '');
1550
- // Map model names to Ollama model names
1551
- const modelMap = {
1552
- 'llama-32-1b': 'llama3.2:1b',
1553
- 'llama-32-3b': 'llama3.2:3b',
1554
- 'llama-31-8b': 'llama3.1:8b',
1555
- 'llama-31-70b': 'llama3.1:70b',
1556
- 'llama3.2-1b': 'llama3.2:1b',
1557
- 'llama3.2-3b': 'llama3.2:3b',
1558
- 'llama3.1-8b': 'llama3.1:8b',
1559
- 'llama3.1-70b': 'llama3.1:70b',
1560
- 'mistral-7b': 'mistral:7b',
1561
- 'phi-3-mini-38b': 'phi3:mini',
1562
- 'phi-3-mini-3.8b': 'phi3:mini',
1563
- 'qwen25-05b': 'qwen2.5:0.5b',
1564
- 'qwen25-7b': 'qwen2.5:7b',
1565
- 'qwen2.5-0.5b': 'qwen2.5:0.5b',
1566
- 'qwen2.5-7b': 'qwen2.5:7b',
1567
- 'deepseek-r1-15b': 'deepseek-r1:1.5b',
1568
- 'deepseek-r1-7b': 'deepseek-r1:7b',
1569
- 'deepseek-r1-8b': 'deepseek-r1:8b',
1570
- 'deepseek-r1-14b': 'deepseek-r1:14b',
1571
- 'deepseek-r1-32b': 'deepseek-r1:32b',
1572
- 'deepseek-r1-70b': 'deepseek-r1:70b',
1573
- 'deepseek-r1-671b': 'deepseek-r1:671b',
1574
- 'deepseek-r1-1.5b': 'deepseek-r1:1.5b',
1575
- };
1576
- // Check if we have a direct mapping
1577
- if (modelMap[normalized]) {
1578
- return modelMap[normalized];
1579
- }
1580
- // Fallback: try to convert last dash before size indicator to colon
1581
- // Matches patterns like: model-name-XXb or model-name-X.Xb
1582
- const match = normalized.match(/^(.+)-(\d+(?:\.\d+)?b)$/);
1583
- if (match) {
1584
- return `${match[1]}:${match[2]}`;
1585
- }
1586
- // Return as-is if no pattern matches
1587
- return normalized;
1588
- }
1589
- /**
1590
- * Check if Docker is installed
1591
- */
1592
- async function checkDockerInstalled() {
1593
- try {
1594
- (0, child_process_1.execSync)('docker --version', { encoding: 'utf8', timeout: 2000, stdio: 'ignore' });
1595
- return true;
1596
- }
1597
- catch {
1598
- return false;
1599
- }
1600
- }
1601
- /**
1602
- * Check if any Ollama models are available
1603
- */
1604
- async function checkHasModels() {
1605
- try {
1606
- const output = (0, child_process_1.execSync)('ollama list', { encoding: 'utf8', timeout: 5000 });
1607
- const lines = output.trim().split('\n');
1608
- // First line is header, so check if there's more than one line
1609
- return lines.length > 1;
1610
- }
1611
- catch {
1612
- return false;
1613
- }
1614
- }
1615
- /**
1616
- * Detect the appropriate Ollama URL for Docker containers to use
1617
- */
1618
- async function detectOllamaUrlForDocker() {
1619
- const platform = os.platform();
1620
- if (platform === 'darwin' || platform === 'win32') {
1621
- // macOS and Windows: Docker Desktop provides host.docker.internal
1622
- return 'http://host.docker.internal:11434';
1623
- }
1624
- // Linux: host.docker.internal may not work, need to detect host IP
1625
- try {
1626
- // Try to get the docker0 bridge IP (typically 172.17.0.1)
1627
- const output = (0, child_process_1.execSync)("ip -4 addr show docker0 | grep -oP '(?<=inet\\s)\\d+(\\.\\d+){3}'", { encoding: 'utf8', timeout: 2000 });
1628
- const ip = output.trim();
1629
- if (ip) {
1630
- return `http://${ip}:11434`;
1631
- }
1632
- }
1633
- catch {
1634
- // Fallback: try to get the default gateway IP
1635
- try {
1636
- const output = (0, child_process_1.execSync)("ip route | grep default | awk '{print $3}'", { encoding: 'utf8', timeout: 2000 });
1637
- const ip = output.trim();
1638
- if (ip) {
1639
- return `http://${ip}:11434`;
1640
- }
1641
- }
1642
- catch {
1643
- // Last resort
1644
- }
1645
- }
1646
- // Fallback to host.docker.internal (works with newer Docker versions on Linux)
1647
- return 'http://host.docker.internal:11434';
1648
- }
1649
- /**
1650
- * Get the path to the docker-compose directory
1651
- */
1652
- function getComposeDirectory() {
1653
- // When packaged as npm module, recipes are in package root
1654
- const packageRecipes = path.join(__dirname, '..', '..', 'recipes', 'compose', 'open-webui');
1655
- // If copied to dist during build (optional)
1656
- const distRecipes = path.join(__dirname, '..', 'recipes', 'compose', 'open-webui');
1657
- // Development location (root level)
1658
- const devRecipes = path.join(process.cwd(), 'recipes', 'compose', 'open-webui');
1659
- // Check in order of likelihood
1660
- if (fs.existsSync(packageRecipes)) {
1661
- return packageRecipes;
1662
- }
1663
- if (fs.existsSync(distRecipes)) {
1664
- return distRecipes;
1665
- }
1666
- if (fs.existsSync(devRecipes)) {
1667
- return devRecipes;
1668
- }
1669
- throw new Error('Could not locate Open WebUI compose directory');
1670
- }
1671
- /**
1672
- * Start Open WebUI with docker-compose
1673
- */
1674
- async function startOpenWebUI(composeDir, env) {
1675
- const composeFile = path.join(composeDir, 'docker-compose.yml');
1676
- if (!fs.existsSync(composeFile)) {
1677
- throw new Error(`Docker compose file not found: ${composeFile}`);
1678
- }
1679
- try {
1680
- // Set environment variables
1681
- const envVars = Object.entries(env)
1682
- .map(([key, value]) => `${key}=${value}`)
1683
- .join(' ');
1684
- // Run docker compose up
1685
- (0, child_process_1.execSync)(`${envVars} docker compose -f "${composeFile}" up -d`, {
1686
- encoding: 'utf8',
1687
- stdio: 'inherit',
1688
- cwd: composeDir,
1689
- });
1690
- }
1691
- catch (error) {
1692
- console.error(chalk_1.default.red('Failed to start Open WebUI:'), error);
1693
- throw error;
1694
- }
1695
- }
1696
- /**
1697
- * Stop Open WebUI
1698
- */
1699
- async function stopOpenWebUI(composeDir, removeVolumes) {
1700
- const composeFile = path.join(composeDir, 'docker-compose.yml');
1701
- try {
1702
- const volumeFlag = removeVolumes ? '-v' : '';
1703
- (0, child_process_1.execSync)(`docker compose -f "${composeFile}" down ${volumeFlag}`, {
1704
- encoding: 'utf8',
1705
- stdio: 'inherit',
1706
- cwd: composeDir,
1707
- });
1708
- }
1709
- catch (error) {
1710
- console.error(chalk_1.default.red('Failed to stop Open WebUI:'), error);
1711
- throw error;
1712
- }
1713
- }
1714
- /**
1715
- * Check if Open WebUI is running
1716
- */
1717
- async function checkOpenWebUIRunning() {
1718
- try {
1719
- const output = (0, child_process_1.execSync)('docker ps --format "{{.Names}}"', {
1720
- encoding: 'utf8',
1721
- timeout: 2000,
1722
- });
1723
- return output.includes('open-webui');
1724
- }
1725
- catch {
1726
- return false;
1727
- }
1728
- }
1729
- /**
1730
- * Get Open WebUI information if running
1731
- */
1732
- async function getOpenWebUIInfo() {
1733
- try {
1734
- const output = (0, child_process_1.execSync)('docker ps --filter "name=open-webui" --format "{{.Ports}}"', {
1735
- encoding: 'utf8',
1736
- timeout: 2000,
1737
- });
1738
- if (!output) {
1739
- return null;
1740
- }
1741
- // Parse port from output like "0.0.0.0:3200->8080/tcp"
1742
- const portMatch = output.match(/0\.0\.0\.0:(\d+)->/);
1743
- if (portMatch) {
1744
- return { port: parseInt(portMatch[1], 10) };
1745
- }
1746
- // Default port if we can't parse
1747
- return { port: 3200 };
1748
- }
1749
- catch {
1750
- return null;
1751
- }
1752
- }
1753
- /**
1754
- * Test if a model can be accessed and generates responses
1755
- * @param modelName - The Ollama model name to test
1756
- * @param verbose - Show detailed output
1757
- * @returns true if model responds successfully
1758
- */
1759
- async function testModelConnectivity(modelName, verbose = false) {
1760
- try {
1761
- if (verbose) {
1762
- console.log(chalk_1.default.gray(` Sending test prompt to ${modelName}...`));
1763
- }
1764
- const controller = new AbortController();
1765
- const timeout = setTimeout(() => controller.abort(), 30000); // 30 second timeout for model loading
1766
- try {
1767
- const response = await (0, node_fetch_1.default)('http://localhost:11434/api/generate', {
1768
- method: 'POST',
1769
- headers: {
1770
- 'Content-Type': 'application/json',
1771
- },
1772
- body: JSON.stringify({
1773
- model: modelName,
1774
- prompt: 'Hello',
1775
- stream: false,
1776
- }),
1777
- signal: controller.signal,
1778
- });
1779
- clearTimeout(timeout);
1780
- if (response.ok) {
1781
- const data = await response.json();
1782
- if (data.error) {
1783
- if (verbose) {
1784
- console.log(chalk_1.default.red(` ✗ Model error: ${data.error}`));
1785
- }
1786
- return false;
1787
- }
1788
- if (data.response) {
1789
- if (verbose) {
1790
- console.log(chalk_1.default.green(' ✓ Model responded successfully'));
1791
- console.log(chalk_1.default.gray(` Response: "${data.response.substring(0, 50)}${data.response.length > 50 ? '...' : ''}"`));
1792
- }
1793
- return true;
1794
- }
1795
- }
1796
- else {
1797
- if (verbose) {
1798
- console.log(chalk_1.default.red(` ✗ HTTP ${response.status}: ${response.statusText}`));
1799
- }
1800
- return false;
1801
- }
1802
- }
1803
- catch (error) {
1804
- clearTimeout(timeout);
1805
- if (verbose) {
1806
- if (error instanceof Error && error.name === 'AbortError') {
1807
- console.log(chalk_1.default.yellow(' ⚠ Request timed out (model may be loading)'));
1808
- }
1809
- else {
1810
- console.log(chalk_1.default.red(` ✗ Connection error: ${error instanceof Error ? error.message : 'Unknown error'}`));
1811
- }
1812
- }
1813
- return false;
1814
- }
1815
- return false;
1816
- }
1817
- catch (error) {
1818
- if (verbose) {
1819
- console.log(chalk_1.default.red(` ✗ Test failed: ${error instanceof Error ? error.message : 'Unknown error'}`));
1820
- }
1821
- return false;
1822
- }
1823
- }
1824
- /**
1825
- * Verify model is available on endpoint
1826
- */
1827
- async function verifyModelOnEndpoint(endpointUrl, modelName) {
1828
- try {
1829
- const controller = new AbortController();
1830
- const timeout = setTimeout(() => controller.abort(), 10000);
1831
- const response = await (0, node_fetch_1.default)(`${endpointUrl}/api/tags`, {
1832
- method: 'GET',
1833
- signal: controller.signal,
1834
- });
1835
- clearTimeout(timeout);
1836
- if (response.ok) {
1837
- const data = await response.json();
1838
- if (data.models) {
1839
- // Check if model exists (exact match or starts with model name)
1840
- const modelExists = data.models.some(m => {
1841
- const modelFullName = m.name;
1842
- return modelFullName === modelName ||
1843
- modelFullName.startsWith(modelName + ':') ||
1844
- modelFullName === modelName.split(':')[0];
1845
- });
1846
- return modelExists;
1847
- }
1848
- }
1849
- return false;
1850
- }
1851
- catch (error) {
1852
- return false;
1853
- }
1854
- }
1855
- /**
1856
- * Helper to parse gateway IDs from comma-separated string
1857
- */
1858
- function parseGatewayIds(ids) {
1859
- if (!ids)
1860
- return [];
1861
- return ids.split(',').map(s => s.trim()).filter(Boolean);
1862
- }
1863
- /**
1864
- * Create Ollama application on Edgible platform
1865
- */
1866
- async function createOllamaApplication(config) {
1867
- // Device ID is already provided from config (same device as agent)
1868
- const ollamaDeviceId = config.deviceId;
1869
- // No gateway - local/internal access only
1870
- const useManagedGateway = false;
1871
- // Build configuration for managed-process
1872
- // Ollama should be installed and available in PATH on the device
1873
- const configuration = {
1874
- command: 'ollama serve',
1875
- env: {
1876
- OLLAMA_HOST: '0.0.0.0:11435',
1877
- },
1878
- };
1879
- // Create application as managed-process without authentication
1880
- const result = await config.applicationService.createApplicationProgrammatically({
1881
- name: 'ollama-api',
1882
- description: `Ollama AI API (${config.modelName}) - Managed Process`,
1883
- port: 11435,
1884
- protocol: 'https',
1885
- deviceIds: [ollamaDeviceId],
1886
- gatewayIds: [], // No gateway - local/internal access only
1887
- useManagedGateway: false,
1888
- subtype: 'managed-process',
1889
- configuration,
1890
- authModes: [], // No authentication required
1891
- });
1892
- return {
1893
- app: result,
1894
- url: result.url || 'https://ollama-api.your-domain.com',
1895
- };
1896
- }
1897
- /**
1898
- * Create Open WebUI application on Edgible platform
1899
- */
1900
- async function createOpenWebUIApplication(config) {
1901
- // Always use the same device as Ollama (same device as agent)
1902
- const webuiDeviceId = config.deviceId;
1903
- // Use managed gateway for public access
1904
- const useManagedGateway = true;
1905
- // Create application
1906
- // Note: Backend should configure OLLAMA_BASE_URL environment variable to ${config.ollamaUrl}
1907
- // This URL should be the platform Ollama application URL (not localhost) for remote deployments
1908
- // Use generated base64 constant (no runtime file reading needed)
1909
- const dockerComposePathValue = (0, compose_constants_1.getOpenWebUIComposeValue)();
1910
- const envVars = {
1911
- 'OLLAMA_BASE_URL': config.ollamaUrl
1912
- };
1913
- const result = await config.applicationService.createApplicationProgrammatically({
1914
- name: 'open-webui',
1915
- description: `Open WebUI - AI Chat Interface (OLLAMA_BASE_URL: ${config.ollamaUrl})`,
1916
- port: 3200,
1917
- protocol: 'https',
1918
- deviceIds: [webuiDeviceId],
1919
- gatewayIds: undefined, // Managed gateway for public access
1920
- useManagedGateway: true,
1921
- subtype: 'docker-compose',
1922
- configuration: {
1923
- 'dockerComposePath': dockerComposePathValue, // base64:... constant
1924
- 'env': envVars,
1925
- 'isWorking': true
1926
- },
1927
- requireOrgAuth: true // Enable authentication - X-Auth-Email header already passed by auth system
1928
- });
1929
- return {
1930
- app: result,
1931
- url: result.url || 'https://open-webui.your-domain.com',
1932
- };
1933
- }
1934
- /**
1935
- * Start Open WebUI locally with docker-compose
1936
- */
1937
- async function startOpenWebUILocal(ollamaUrl) {
1938
- // Check if Docker is installed
1939
- const isDockerInstalled = await checkDockerInstalled();
1940
- if (!isDockerInstalled) {
1941
- throw new Error('Docker is required to run Open WebUI locally. Please install Docker first.');
1942
- }
1943
- const composeDir = getComposeDirectory();
1944
- await startOpenWebUI(composeDir, {
1945
- OLLAMA_BASE_URL: ollamaUrl,
1946
- OPEN_WEBUI_PORT: '3200',
1947
- });
1948
- }
1949
- /**
1950
- * Check if an Ollama URL is reachable and responding
1951
- */
1952
- async function checkOllamaUrlReachable(url) {
1953
- try {
1954
- const controller = new AbortController();
1955
- const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout
1956
- try {
1957
- const response = await (0, node_fetch_1.default)(`${url}/api/tags`, {
1958
- method: 'GET',
1959
- signal: controller.signal,
1960
- });
1961
- clearTimeout(timeout);
1962
- return response.ok;
1963
- }
1964
- catch (error) {
1965
- clearTimeout(timeout);
1966
- return false;
1967
- }
1968
- }
1969
- catch {
1970
- return false;
1971
- }
1972
- }
1973
- /**
1974
- * Check if a URL is reachable (general purpose)
1975
- */
1976
- async function checkUrlReachable(url) {
1977
- try {
1978
- const controller = new AbortController();
1979
- const timeout = setTimeout(() => controller.abort(), 5000); // 5 second timeout
1980
- try {
1981
- const response = await (0, node_fetch_1.default)(url, {
1982
- method: 'GET',
1983
- signal: controller.signal,
1984
- });
1985
- clearTimeout(timeout);
1986
- return response.ok || response.status < 500; // Accept redirects and client errors as "reachable"
1987
- }
1988
- catch (error) {
1989
- clearTimeout(timeout);
1990
- return false;
1991
- }
1992
- }
1993
- catch {
1994
- return false;
1995
- }
72
+ await (0, test_1.handleAiTest)(options);
73
+ }, {}));
1996
74
  }
1997
75
  //# sourceMappingURL=ai.js.map