vodia-pharmacy-ai 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/bin/vodia-pharmacy-ai.js +454 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Vodia Pharmacy AI Installer
|
|
2
|
+
|
|
3
|
+
Installer CLI for Vodia Pharmacy AI.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @vodia1980/pharmacy-ai install --demo
|
|
9
|
+
vodia-pharmacy-ai install --demo
|
|
10
|
+
vodia-pharmacy-ai status
|
|
11
|
+
vodia-pharmacy-ai logs
|
|
12
|
+
vodia-pharmacy-ai vodia-script
|
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import crypto from "crypto";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import os from "os";
|
|
6
|
+
import path from "path";
|
|
7
|
+
import { execSync } from "child_process";
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const command = args[0] || "help";
|
|
11
|
+
|
|
12
|
+
function getArg(name, fallback = "") {
|
|
13
|
+
const idx = args.indexOf(name);
|
|
14
|
+
if (idx >= 0 && args[idx + 1]) return args[idx + 1];
|
|
15
|
+
return fallback;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function hasFlag(name) {
|
|
19
|
+
return args.includes(name);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function q(v) {
|
|
23
|
+
return "'" + String(v).replace(/'/g, "'\\''") + "'";
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sh(cmd) {
|
|
27
|
+
execSync(cmd, { stdio: "inherit" });
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function shOut(cmd) {
|
|
31
|
+
return execSync(cmd, { stdio: "pipe", encoding: "utf8" });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function secret(bytes = 32) {
|
|
35
|
+
return crypto.randomBytes(bytes).toString("hex");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readEnv(envPath) {
|
|
39
|
+
const out = {};
|
|
40
|
+
if (!fs.existsSync(envPath)) return out;
|
|
41
|
+
|
|
42
|
+
for (const line of fs.readFileSync(envPath, "utf8").split(/\r?\n/)) {
|
|
43
|
+
if (!line || line.startsWith("#")) continue;
|
|
44
|
+
const i = line.indexOf("=");
|
|
45
|
+
if (i < 0) continue;
|
|
46
|
+
out[line.slice(0, i)] = line.slice(i + 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function help() {
|
|
53
|
+
console.log(`
|
|
54
|
+
Vodia Pharmacy AI Installer
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
vodia-pharmacy-ai install --demo
|
|
58
|
+
vodia-pharmacy-ai install --demo --dir /opt/vodia-pharmacy-ai-demo --port 3200 --domain localhost --app-name vodia-pharmacy-ai-demo
|
|
59
|
+
vodia-pharmacy-ai install --demo --dir /opt/vodia-pharmacy-ai-demo --port 3200 --domain pharmacy.example.com --app-name vodia-pharmacy-ai-demo --caddy
|
|
60
|
+
vodia-pharmacy-ai status --dir /opt/vodia-pharmacy-ai-demo
|
|
61
|
+
vodia-pharmacy-ai logs --app-name vodia-pharmacy-ai-demo
|
|
62
|
+
vodia-pharmacy-ai vodia-script --dir /opt/vodia-pharmacy-ai-demo
|
|
63
|
+
|
|
64
|
+
Caddy / HTTPS:
|
|
65
|
+
--caddy enables Caddy reverse proxy and automatic Let's Encrypt HTTPS.
|
|
66
|
+
Requires a real domain pointed to this server, and ports 80/443 available.
|
|
67
|
+
|
|
68
|
+
Safe default:
|
|
69
|
+
This refuses to overwrite an existing folder unless --force is used.
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function writeServer(installDir) {
|
|
74
|
+
const code = `
|
|
75
|
+
const http = require("http");
|
|
76
|
+
const fs = require("fs");
|
|
77
|
+
const path = require("path");
|
|
78
|
+
|
|
79
|
+
function readEnv() {
|
|
80
|
+
const env = {};
|
|
81
|
+
const envPath = path.join(__dirname, ".env");
|
|
82
|
+
if (!fs.existsSync(envPath)) return env;
|
|
83
|
+
for (const line of fs.readFileSync(envPath, "utf8").split(/\\r?\\n/)) {
|
|
84
|
+
if (!line || line.startsWith("#")) continue;
|
|
85
|
+
const i = line.indexOf("=");
|
|
86
|
+
if (i < 0) continue;
|
|
87
|
+
env[line.slice(0, i)] = line.slice(i + 1);
|
|
88
|
+
}
|
|
89
|
+
return env;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const env = readEnv();
|
|
93
|
+
const port = Number(env.APP_PORT || 3200);
|
|
94
|
+
|
|
95
|
+
function json(res, code, data) {
|
|
96
|
+
res.writeHead(code, { "Content-Type": "application/json" });
|
|
97
|
+
res.end(JSON.stringify(data, null, 2));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function body(req, cb) {
|
|
101
|
+
let data = "";
|
|
102
|
+
req.on("data", c => data += c);
|
|
103
|
+
req.on("end", () => {
|
|
104
|
+
try { cb(null, data ? JSON.parse(data) : {}); }
|
|
105
|
+
catch(e) { cb(e); }
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const server = http.createServer((req, res) => {
|
|
110
|
+
const url = new URL(req.url, "http://localhost");
|
|
111
|
+
|
|
112
|
+
if (url.pathname === "/health") {
|
|
113
|
+
return json(res, 200, {
|
|
114
|
+
success: true,
|
|
115
|
+
app: env.APP_NAME || "Vodia Pharmacy AI Demo",
|
|
116
|
+
mode: env.APP_ENV || "demo",
|
|
117
|
+
port,
|
|
118
|
+
ts: new Date().toISOString()
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (url.pathname === "/" || url.pathname === "/portal") {
|
|
123
|
+
res.writeHead(200, { "Content-Type": "text/html" });
|
|
124
|
+
return res.end(\`
|
|
125
|
+
<!doctype html>
|
|
126
|
+
<html>
|
|
127
|
+
<head>
|
|
128
|
+
<title>Vodia Pharmacy AI Demo</title>
|
|
129
|
+
<style>
|
|
130
|
+
body{font-family:Arial;background:#071d24;color:white;margin:0}
|
|
131
|
+
header{padding:24px;background:linear-gradient(90deg,#06212b,#0f766e)}
|
|
132
|
+
main{padding:24px}
|
|
133
|
+
.card{background:#0d2a34;border:1px solid #1e5561;border-radius:14px;padding:22px;max-width:900px}
|
|
134
|
+
code{color:#5eead4}
|
|
135
|
+
</style>
|
|
136
|
+
</head>
|
|
137
|
+
<body>
|
|
138
|
+
<header><h1>Vodia Pharmacy AI Demo</h1><p>Installer test portal is running.</p></header>
|
|
139
|
+
<main>
|
|
140
|
+
<div class="card">
|
|
141
|
+
<h2>Demo Install Working</h2>
|
|
142
|
+
<p>This safe demo does not touch your live pizza or pharmacy app.</p>
|
|
143
|
+
<p>Health: <code>/health</code></p>
|
|
144
|
+
<p>Portal: <code>/portal</code></p>
|
|
145
|
+
<p>AI endpoints are placeholder test endpoints.</p>
|
|
146
|
+
</div>
|
|
147
|
+
</main>
|
|
148
|
+
</body>
|
|
149
|
+
</html>\`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (req.method === "POST" && url.pathname === "/api/ai/refill-intake") {
|
|
153
|
+
return body(req, (err, b) => {
|
|
154
|
+
if (err) return json(res, 400, { success:false, error:"bad_json" });
|
|
155
|
+
return json(res, 200, {
|
|
156
|
+
success: true,
|
|
157
|
+
demo_only: true,
|
|
158
|
+
request_id: "DEMO-" + Date.now(),
|
|
159
|
+
received: b
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (req.method === "POST" && url.pathname === "/api/ai/previous-request-lookup") {
|
|
165
|
+
return body(req, (err, b) => {
|
|
166
|
+
if (err) return json(res, 400, { success:false, error:"bad_json" });
|
|
167
|
+
return json(res, 200, {
|
|
168
|
+
success: true,
|
|
169
|
+
found: false,
|
|
170
|
+
demo_only: true,
|
|
171
|
+
message: "Demo placeholder has no real database yet.",
|
|
172
|
+
received: b
|
|
173
|
+
});
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (req.method === "POST" && url.pathname === "/api/ai/status-callback-note") {
|
|
178
|
+
return body(req, (err, b) => {
|
|
179
|
+
if (err) return json(res, 400, { success:false, error:"bad_json" });
|
|
180
|
+
return json(res, 200, {
|
|
181
|
+
success: true,
|
|
182
|
+
demo_only: true,
|
|
183
|
+
note_added: true,
|
|
184
|
+
request_id: b.request_id || "DEMO"
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return json(res, 404, { success:false, error:"not_found" });
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
server.listen(port, "0.0.0.0", () => {
|
|
193
|
+
console.log("Vodia Pharmacy AI Demo listening on port " + port);
|
|
194
|
+
});
|
|
195
|
+
`;
|
|
196
|
+
|
|
197
|
+
fs.writeFileSync(path.join(installDir, "server.js"), code.trim() + "\n");
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function portOwner(port) {
|
|
201
|
+
try {
|
|
202
|
+
return shOut("sudo ss -ltnp 2>/dev/null | grep -E ':" + port + "[[:space:]]' || true").trim();
|
|
203
|
+
} catch {
|
|
204
|
+
return "";
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function publicIp() {
|
|
209
|
+
try {
|
|
210
|
+
return shOut("curl -4 -s --max-time 4 https://api.ipify.org || true").trim();
|
|
211
|
+
} catch {
|
|
212
|
+
return "";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function dnsIp(domain) {
|
|
217
|
+
try {
|
|
218
|
+
return shOut("getent ahostsv4 " + q(domain) + " | awk '{print $1; exit}' || true").trim();
|
|
219
|
+
} catch {
|
|
220
|
+
return "";
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function setupCaddy({ domain, port, appName }) {
|
|
225
|
+
if (!domain || domain === "localhost" || domain === "127.0.0.1") {
|
|
226
|
+
console.error("Caddy HTTPS requires a real public domain, not localhost.");
|
|
227
|
+
process.exit(1);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (!hasFlag("--skip-dns-check")) {
|
|
231
|
+
const serverIp = publicIp();
|
|
232
|
+
const resolvedIp = dnsIp(domain);
|
|
233
|
+
|
|
234
|
+
console.log("DNS check:");
|
|
235
|
+
console.log(" Domain:", domain);
|
|
236
|
+
console.log(" DNS IP:", resolvedIp || "not found");
|
|
237
|
+
console.log(" Server public IP:", serverIp || "not found");
|
|
238
|
+
|
|
239
|
+
if (!resolvedIp) {
|
|
240
|
+
console.error("DNS does not resolve yet. Point the domain A record to this server first.");
|
|
241
|
+
console.error("Use --skip-dns-check only if you know DNS is correct.");
|
|
242
|
+
process.exit(1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (serverIp && resolvedIp !== serverIp) {
|
|
246
|
+
console.error("DNS IP does not match this server public IP.");
|
|
247
|
+
console.error("Fix the A record first, or use --skip-dns-check if this is intentional.");
|
|
248
|
+
process.exit(1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const p80 = portOwner("80");
|
|
253
|
+
const p443 = portOwner("443");
|
|
254
|
+
|
|
255
|
+
if (p80 && !p80.toLowerCase().includes("caddy")) {
|
|
256
|
+
console.error("Port 80 is already in use by a non-Caddy service:");
|
|
257
|
+
console.error(p80);
|
|
258
|
+
console.error("Stop Nginx/Apache or use a fresh server before enabling --caddy.");
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (p443 && !p443.toLowerCase().includes("caddy")) {
|
|
263
|
+
console.error("Port 443 is already in use by a non-Caddy service:");
|
|
264
|
+
console.error(p443);
|
|
265
|
+
console.error("Stop Nginx/Apache or use a fresh server before enabling --caddy.");
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log("Installing/configuring Caddy...");
|
|
270
|
+
|
|
271
|
+
sh("command -v caddy >/dev/null 2>&1 || (sudo apt-get update && sudo apt-get install -y caddy)");
|
|
272
|
+
|
|
273
|
+
const safeAppName = appName.replace(/[^a-zA-Z0-9._-]/g, "-");
|
|
274
|
+
const tmpFile = "/tmp/" + safeAppName + ".caddy";
|
|
275
|
+
const caddySnippet = `${domain} {
|
|
276
|
+
encode gzip
|
|
277
|
+
reverse_proxy 127.0.0.1:${port}
|
|
278
|
+
}
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
fs.writeFileSync(tmpFile, caddySnippet);
|
|
282
|
+
|
|
283
|
+
sh("sudo mkdir -p /etc/caddy/conf.d");
|
|
284
|
+
sh("sudo cp " + q(tmpFile) + " " + q("/etc/caddy/conf.d/" + safeAppName + ".caddy"));
|
|
285
|
+
sh("sudo touch /etc/caddy/Caddyfile");
|
|
286
|
+
sh("sudo grep -q '^import /etc/caddy/conf.d/\\*.caddy' /etc/caddy/Caddyfile || echo 'import /etc/caddy/conf.d/*.caddy' | sudo tee -a /etc/caddy/Caddyfile >/dev/null");
|
|
287
|
+
|
|
288
|
+
sh("sudo caddy validate --config /etc/caddy/Caddyfile");
|
|
289
|
+
sh("sudo systemctl enable caddy >/dev/null 2>&1 || true");
|
|
290
|
+
sh("sudo systemctl reload caddy || sudo systemctl restart caddy");
|
|
291
|
+
|
|
292
|
+
console.log("");
|
|
293
|
+
console.log("Caddy configured ✅");
|
|
294
|
+
console.log("HTTPS URL:");
|
|
295
|
+
console.log(" https://" + domain + "/portal");
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function installDemo() {
|
|
299
|
+
const installDir = getArg("--dir", "/opt/vodia-pharmacy-ai-demo");
|
|
300
|
+
const port = getArg("--port", "3200");
|
|
301
|
+
const domain = getArg("--domain", "localhost");
|
|
302
|
+
const appName = getArg("--app-name", "vodia-pharmacy-ai-demo");
|
|
303
|
+
const force = hasFlag("--force");
|
|
304
|
+
const useCaddy = hasFlag("--caddy");
|
|
305
|
+
|
|
306
|
+
if (fs.existsSync(installDir) && !force) {
|
|
307
|
+
console.error("Refusing to overwrite existing folder:");
|
|
308
|
+
console.error(" " + installDir);
|
|
309
|
+
console.error("");
|
|
310
|
+
console.error("Use a new folder or add --force only for demo testing.");
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log("Installing safe demo:");
|
|
315
|
+
console.log(" Folder:", installDir);
|
|
316
|
+
console.log(" Port:", port);
|
|
317
|
+
console.log(" Domain:", domain);
|
|
318
|
+
console.log(" PM2:", appName);
|
|
319
|
+
console.log(" Caddy:", useCaddy ? "yes" : "no");
|
|
320
|
+
|
|
321
|
+
sh("sudo mkdir -p " + q(installDir));
|
|
322
|
+
sh("sudo chown -R " + q(os.userInfo().username) + ":" + q(os.userInfo().username) + " " + q(installDir));
|
|
323
|
+
|
|
324
|
+
fs.mkdirSync(path.join(installDir, "logs"), { recursive: true });
|
|
325
|
+
fs.mkdirSync(path.join(installDir, "backups"), { recursive: true });
|
|
326
|
+
fs.mkdirSync(path.join(installDir, "generated"), { recursive: true });
|
|
327
|
+
|
|
328
|
+
const apiSecret = secret(32);
|
|
329
|
+
const sessionSecret = secret(32);
|
|
330
|
+
const adminPassword = secret(8);
|
|
331
|
+
|
|
332
|
+
const env = [
|
|
333
|
+
"APP_ENV=demo",
|
|
334
|
+
"APP_NAME=Vodia Pharmacy AI Demo",
|
|
335
|
+
"APP_DOMAIN=" + domain,
|
|
336
|
+
"APP_PORT=" + port,
|
|
337
|
+
"PM2_APP_NAME=" + appName,
|
|
338
|
+
"PHARMACY_API_SECRET=" + apiSecret,
|
|
339
|
+
"SESSION_SECRET=" + sessionSecret,
|
|
340
|
+
"ADMIN_EMAIL=admin@example.com",
|
|
341
|
+
"ADMIN_TEMP_PASSWORD=" + adminPassword,
|
|
342
|
+
"SMARTY_ENABLED=false",
|
|
343
|
+
"SMTP_ENABLED=false",
|
|
344
|
+
"CADDY_ENABLED=" + (useCaddy ? "true" : "false")
|
|
345
|
+
].join("\n") + "\n";
|
|
346
|
+
|
|
347
|
+
fs.writeFileSync(path.join(installDir, ".env"), env);
|
|
348
|
+
fs.chmodSync(path.join(installDir, ".env"), 0o600);
|
|
349
|
+
|
|
350
|
+
writeServer(installDir);
|
|
351
|
+
|
|
352
|
+
sh("command -v pm2 >/dev/null 2>&1 || sudo npm install -g pm2");
|
|
353
|
+
sh("pm2 delete " + q(appName) + " >/dev/null 2>&1 || true");
|
|
354
|
+
sh("cd " + q(installDir) + " && pm2 start server.js --name " + q(appName) + " --update-env");
|
|
355
|
+
|
|
356
|
+
if (useCaddy) {
|
|
357
|
+
setupCaddy({ domain, port, appName });
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
console.log("");
|
|
361
|
+
console.log("Demo install complete ✅");
|
|
362
|
+
console.log("");
|
|
363
|
+
console.log("Portal:");
|
|
364
|
+
if (useCaddy && domain !== "localhost") {
|
|
365
|
+
console.log(" https://" + domain + "/portal");
|
|
366
|
+
} else {
|
|
367
|
+
console.log(" http://" + domain + ":" + port + "/portal");
|
|
368
|
+
}
|
|
369
|
+
console.log("");
|
|
370
|
+
console.log("Health:");
|
|
371
|
+
console.log(" curl http://127.0.0.1:" + port + "/health");
|
|
372
|
+
console.log("");
|
|
373
|
+
console.log("Admin demo login:");
|
|
374
|
+
console.log(" admin@example.com");
|
|
375
|
+
console.log(" " + adminPassword);
|
|
376
|
+
console.log("");
|
|
377
|
+
console.log("Vodia script values:");
|
|
378
|
+
console.log(" vodia-pharmacy-ai vodia-script --dir " + installDir);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function status() {
|
|
382
|
+
const installDir = getArg("--dir", "/opt/vodia-pharmacy-ai-demo");
|
|
383
|
+
const env = readEnv(path.join(installDir, ".env"));
|
|
384
|
+
const port = env.APP_PORT || getArg("--port", "3200");
|
|
385
|
+
|
|
386
|
+
console.log("Install dir:", installDir);
|
|
387
|
+
console.log("Port:", port);
|
|
388
|
+
console.log("");
|
|
389
|
+
|
|
390
|
+
try { sh("pm2 list"); } catch {}
|
|
391
|
+
|
|
392
|
+
console.log("");
|
|
393
|
+
console.log("Health:");
|
|
394
|
+
try {
|
|
395
|
+
console.log(shOut("curl -s http://127.0.0.1:" + port + "/health").trim());
|
|
396
|
+
} catch {
|
|
397
|
+
console.log("Health check failed.");
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function logs() {
|
|
402
|
+
const appName = getArg("--app-name", "vodia-pharmacy-ai-demo");
|
|
403
|
+
sh("pm2 logs " + q(appName));
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
function vodiaScript() {
|
|
407
|
+
const installDir = getArg("--dir", "/opt/vodia-pharmacy-ai-demo");
|
|
408
|
+
const env = readEnv(path.join(installDir, ".env"));
|
|
409
|
+
|
|
410
|
+
if (!env.PHARMACY_API_SECRET) {
|
|
411
|
+
console.error("Missing .env or PHARMACY_API_SECRET. Run install first.");
|
|
412
|
+
process.exit(1);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const domain = env.APP_DOMAIN || "localhost";
|
|
416
|
+
const port = env.APP_PORT || "3200";
|
|
417
|
+
const caddyEnabled = env.CADDY_ENABLED === "true";
|
|
418
|
+
const baseUrl =
|
|
419
|
+
caddyEnabled && domain !== "localhost"
|
|
420
|
+
? "https://" + domain
|
|
421
|
+
: "http://" + domain + ":" + port;
|
|
422
|
+
|
|
423
|
+
console.log(`// Generated values for Vodia AI script
|
|
424
|
+
|
|
425
|
+
var pharmacyApiSecret = "${env.PHARMACY_API_SECRET}"
|
|
426
|
+
|
|
427
|
+
var pharmacyRequestUrl = "${baseUrl}/api/ai/refill-intake"
|
|
428
|
+
var customerLookupUrl = "${baseUrl}/api/ai/customer-lookup"
|
|
429
|
+
var customerEnrichUrl = "${baseUrl}/api/ai/refill-request-enrich"
|
|
430
|
+
var fulfillmentUrl = "${baseUrl}/api/ai/request-fulfillment"
|
|
431
|
+
var pharmacyLocationSearchUrl = "${baseUrl}/api/ai/pharmacy-location-search"
|
|
432
|
+
var previousRequestLookupUrl = "${baseUrl}/api/ai/previous-request-lookup"
|
|
433
|
+
var statusCallbackNoteUrl = "${baseUrl}/api/ai/status-callback-note"
|
|
434
|
+
|
|
435
|
+
// Next: bundle the full Vodia AI script template and inject these values automatically.
|
|
436
|
+
`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (command === "help") help();
|
|
440
|
+
else if (command === "install") {
|
|
441
|
+
if (!hasFlag("--demo")) {
|
|
442
|
+
console.error("Use: vodia-pharmacy-ai install --demo");
|
|
443
|
+
process.exit(1);
|
|
444
|
+
}
|
|
445
|
+
installDemo();
|
|
446
|
+
}
|
|
447
|
+
else if (command === "status") status();
|
|
448
|
+
else if (command === "logs") logs();
|
|
449
|
+
else if (command === "vodia-script") vodiaScript();
|
|
450
|
+
else {
|
|
451
|
+
console.error("Unknown command:", command);
|
|
452
|
+
help();
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "vodia-pharmacy-ai",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Installer CLI for Vodia Pharmacy AI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vodia-pharmacy-ai": "./bin/vodia-pharmacy-ai.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/vodia-pharmacy-ai.js",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"keywords": [
|
|
14
|
+
"vodia",
|
|
15
|
+
"pharmacy",
|
|
16
|
+
"ai",
|
|
17
|
+
"pbx",
|
|
18
|
+
"voice",
|
|
19
|
+
"installer",
|
|
20
|
+
"caddy",
|
|
21
|
+
"letsencrypt"
|
|
22
|
+
],
|
|
23
|
+
"author": "Hamlet Collado",
|
|
24
|
+
"license": "UNLICENSED"
|
|
25
|
+
}
|