Initial commit: FLM proxy server for AMD NPU
This commit is contained in:
33
.gitignore
vendored
Normal file
33
.gitignore
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
node_modules/
|
||||
daemon/
|
||||
.env
|
||||
FastFlowLM/
|
||||
miniforge3/
|
||||
ImageModerationService/
|
||||
VisionScannerService/
|
||||
Desktop/
|
||||
Documents/
|
||||
Downloads/
|
||||
Music/
|
||||
Pictures/
|
||||
Videos/
|
||||
Favorites/
|
||||
Links/
|
||||
AppData/
|
||||
"Application Data"
|
||||
Cookies/
|
||||
"Local Settings"
|
||||
"My Documents"
|
||||
NetHood/
|
||||
PrintHood/
|
||||
Recent/
|
||||
"Saved Games"
|
||||
SendTo/
|
||||
"Start Menu"
|
||||
Templates/
|
||||
NTUSER*
|
||||
ntuser*
|
||||
*.dat
|
||||
*.LOG*
|
||||
*.blf
|
||||
*.regtrans-ms
|
||||
88
CLAUDE.md
Normal file
88
CLAUDE.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
## Overview
|
||||
|
||||
This is a FastFlowLM proxy server setup that runs LLM models on an AMD NPU (Neural Processing Unit). The proxy auto-starts the model on first request and stops it after idle timeout to free RAM.
|
||||
|
||||
## Architecture
|
||||
|
||||
- **`flm-proxy.js`** — Node.js HTTP proxy (port 8000) that sits in front of FastFlowLM (port 8001). It lazily spawns `flm.exe`, polls until the model is ready, proxies all requests, and kills the process after 5 minutes of inactivity. Exposes `/status` and `/stop` control endpoints.
|
||||
- **`FastFlowLM/flm.exe`** — Pre-built binary that serves OpenAI-compatible API (`/v1/models`, `/v1/chat/completions`, etc.) using NPU-accelerated models. Not source code — do not modify.
|
||||
- **`flm-service-install.js` / `flm-service-uninstall.js`** — Install/uninstall the proxy as a Windows service via `node-windows`.
|
||||
- **`daemon/`** — Windows service wrapper files generated by `node-windows` (exe, logs, config).
|
||||
- **`flm-start.bat` / `flm-stop.bat`** — Simple batch scripts to run FLM directly (bypassing the proxy).
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
# Run the proxy (foreground)
|
||||
node flm-proxy.js
|
||||
|
||||
# Install as Windows service
|
||||
node flm-service-install.js
|
||||
|
||||
# Uninstall Windows service
|
||||
node flm-service-uninstall.js
|
||||
|
||||
# Install dependencies
|
||||
npm install
|
||||
|
||||
# Check service logs
|
||||
cat ~/daemon/flmvisionproxy.out.log
|
||||
cat ~/daemon/flmvisionproxy.err.log
|
||||
```
|
||||
|
||||
## Key Configuration (in flm-proxy.js)
|
||||
|
||||
- `MODEL` — currently `qwen2.5vl-it:3b` (Qwen2.5 Vision-Language 3B)
|
||||
- `PROXY_PORT` — 8000 (external-facing)
|
||||
- `FLM_PORT` — 8001 (internal FLM server)
|
||||
- `IDLE_TIMEOUT_MS` — 5 minutes
|
||||
- `HOST` — `0.0.0.0` (listens on all interfaces)
|
||||
|
||||
## Available Models
|
||||
|
||||
See `FastFlowLM/model_list.json` for the full catalog. Model identifiers use the format `family:size` (e.g., `qwen3:4b`, `llama3.2:3b`). Vision models have `"vlm": true`. Thinking models have `"think": true`.
|
||||
|
||||
## Services
|
||||
|
||||
All services are TypeScript/Express apps with the same build pattern:
|
||||
|
||||
```bash
|
||||
cd <ServiceDir>
|
||||
npm install # install deps
|
||||
npm run build # tsc → dist/
|
||||
npm start # node dist/server.js
|
||||
npm run dev # tsx watch (hot-reload)
|
||||
|
||||
# Windows service management
|
||||
node service-install.js
|
||||
node service-uninstall.js
|
||||
```
|
||||
|
||||
### ImageModerationService (port 8100)
|
||||
|
||||
Checks uploaded images for NSFW/explicit content using the local vision LLM. When an image is flagged unsafe, fires callbacks to the upload service (to replace the image) and to Parochia (to flag the user).
|
||||
|
||||
- **Endpoints:** `POST /moderate` (multipart: `file`, `context`, `imagePath`, `userId`, `siteId`), `GET /health`
|
||||
- **Vision model:** `gemma3:4b` via FLM proxy at `localhost:8000`
|
||||
- **Callbacks:** Configurable in `.env` — upload service replace URL + Parochia moderation callback
|
||||
- **Source:** `src/moderate.ts` (moderation logic), `src/server.ts` (Express app)
|
||||
|
||||
### VisionScannerService (port 8002)
|
||||
|
||||
Scans shelf/pantry photos to extract product information and prices using the vision LLM. Uses ChromaDB for embeddings storage and Ollama for embedding generation. Supports image tiling for high-res photos.
|
||||
|
||||
- **Endpoints:** `POST /scan/shelf` (multipart: `image`, `store_name`), `POST /scan/pantry` (multipart: `image`), `GET /health`
|
||||
- **Vision model:** `qwen2.5vl-it:3b` via FLM proxy at `localhost:8000`
|
||||
- **External deps:** Ollama (`192.168.0.15:11434`, `nomic-embed-text`), ChromaDB (`192.168.0.15:8000`), optional Gemini API
|
||||
- **Source:** `src/vision.ts` (LLM calls), `src/tiling.ts` (image tiling), `src/shelf.ts` / `src/pantry.ts` (scan logic), `src/embeddings.ts` + `src/chroma.ts` (vector storage), `src/matching.ts` (product matching), `src/parsing.ts` (response parsing), `src/gemini.ts` (Gemini fallback), `src/config.ts`
|
||||
|
||||
## Environment
|
||||
|
||||
- Windows 11, AMD NPU hardware
|
||||
- Node.js with `node-windows` dependency
|
||||
- FLM binary path: `C:\Users\sshuser\FastFlowLM\flm.exe`
|
||||
- All paths are hardcoded to `C:\Users\sshuser\`
|
||||
160
flm-proxy.js
Normal file
160
flm-proxy.js
Normal file
@@ -0,0 +1,160 @@
|
||||
const http = require("http");
|
||||
const { spawn, execSync } = require("child_process");
|
||||
|
||||
const FLM_PATH = "C:\\Users\\sshuser\\FastFlowLM\\flm.exe";
|
||||
const MODEL = "qwen2.5vl-it:3b";
|
||||
const HOST = "0.0.0.0";
|
||||
const PROXY_PORT = 8000;
|
||||
const FLM_PORT = 8001;
|
||||
const IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
let flmProcess = null;
|
||||
let idleTimer = null;
|
||||
let starting = false;
|
||||
let ready = false;
|
||||
|
||||
function log(msg) {
|
||||
console.log(`[${new Date().toLocaleTimeString()}] ${msg}`);
|
||||
}
|
||||
|
||||
function resetIdleTimer() {
|
||||
if (idleTimer) clearTimeout(idleTimer);
|
||||
idleTimer = setTimeout(() => {
|
||||
log("Idle timeout reached. Stopping model...");
|
||||
stopFlm();
|
||||
}, IDLE_TIMEOUT_MS);
|
||||
}
|
||||
|
||||
function stopFlm() {
|
||||
ready = false;
|
||||
starting = false;
|
||||
if (idleTimer) clearTimeout(idleTimer);
|
||||
if (flmProcess) {
|
||||
try { execSync('taskkill /IM flm.exe /F', { stdio: 'ignore' }); } catch {}
|
||||
flmProcess = null;
|
||||
log("Model stopped. RAM freed.");
|
||||
}
|
||||
}
|
||||
|
||||
function startFlm() {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (ready) return resolve();
|
||||
if (starting) {
|
||||
const wait = setInterval(() => {
|
||||
if (ready) { clearInterval(wait); resolve(); }
|
||||
}, 500);
|
||||
return;
|
||||
}
|
||||
|
||||
starting = true;
|
||||
log("Starting model on NPU...");
|
||||
|
||||
flmProcess = spawn(FLM_PATH, [
|
||||
"serve", MODEL,
|
||||
"--host", "127.0.0.1",
|
||||
"--port", String(FLM_PORT),
|
||||
"--pmode", "performance"
|
||||
], { stdio: ["pipe", "pipe", "pipe"] });
|
||||
|
||||
flmProcess.stderr.on("data", (d) => {
|
||||
const s = d.toString();
|
||||
if (s.includes("ERROR")) log("FLM: " + s.trim());
|
||||
});
|
||||
|
||||
flmProcess.on("exit", (code) => {
|
||||
log(`FLM exited (code ${code})`);
|
||||
flmProcess = null;
|
||||
ready = false;
|
||||
starting = false;
|
||||
});
|
||||
|
||||
// Poll until the server responds
|
||||
const check = setInterval(() => {
|
||||
const req = http.get(`http://127.0.0.1:${FLM_PORT}/v1/models`, (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
clearInterval(check);
|
||||
ready = true;
|
||||
starting = false;
|
||||
log("Model ready!");
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
req.on("error", () => {});
|
||||
req.setTimeout(1000, () => req.destroy());
|
||||
}, 1000);
|
||||
|
||||
// Timeout after 60s
|
||||
setTimeout(() => {
|
||||
if (!ready) {
|
||||
clearInterval(check);
|
||||
reject(new Error("Model failed to start within 60s"));
|
||||
}
|
||||
}, 60000);
|
||||
});
|
||||
}
|
||||
|
||||
function proxy(clientReq, clientRes) {
|
||||
const options = {
|
||||
hostname: "127.0.0.1",
|
||||
port: FLM_PORT,
|
||||
path: clientReq.url,
|
||||
method: clientReq.method,
|
||||
headers: clientReq.headers
|
||||
};
|
||||
|
||||
const proxyReq = http.request(options, (proxyRes) => {
|
||||
clientRes.writeHead(proxyRes.statusCode, proxyRes.headers);
|
||||
proxyRes.pipe(clientRes);
|
||||
});
|
||||
|
||||
proxyReq.on("error", (e) => {
|
||||
clientRes.writeHead(502);
|
||||
clientRes.end(JSON.stringify({ error: "Model backend error: " + e.message }));
|
||||
});
|
||||
|
||||
clientReq.pipe(proxyReq);
|
||||
}
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
// CORS headers
|
||||
res.setHeader("Access-Control-Allow-Origin", "*");
|
||||
res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
|
||||
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
||||
if (req.method === "OPTIONS") { res.writeHead(204); res.end(); return; }
|
||||
|
||||
// Status endpoint
|
||||
if (req.url === "/status") {
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ model: MODEL, ready, starting, pid: flmProcess?.pid || null }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop endpoint
|
||||
if (req.url === "/stop") {
|
||||
stopFlm();
|
||||
res.writeHead(200, { "Content-Type": "application/json" });
|
||||
res.end(JSON.stringify({ status: "stopped" }));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
resetIdleTimer();
|
||||
if (!ready) {
|
||||
log(`Request received. Waking up model...`);
|
||||
await startFlm();
|
||||
}
|
||||
proxy(req, res);
|
||||
} catch (e) {
|
||||
res.writeHead(503);
|
||||
res.end(JSON.stringify({ error: e.message }));
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(PROXY_PORT, HOST, () => {
|
||||
log(`Proxy listening on ${HOST}:${PROXY_PORT}`);
|
||||
log(`Model will auto-start on first request, auto-stop after ${IDLE_TIMEOUT_MS / 60000}m idle`);
|
||||
log(`Endpoints: /status, /stop`);
|
||||
});
|
||||
|
||||
process.on("SIGINT", () => { stopFlm(); process.exit(); });
|
||||
process.on("SIGTERM", () => { stopFlm(); process.exit(); });
|
||||
27
flm-service-install.js
Normal file
27
flm-service-install.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const Service = require("node-windows").Service;
|
||||
|
||||
const svc = new Service({
|
||||
name: "FLM Vision Proxy",
|
||||
description: "Auto-start/stop proxy for FastFlowLM vision model on NPU",
|
||||
script: "C:\\Users\\sshuser\\flm-proxy.js",
|
||||
nodeOptions: [],
|
||||
env: [{
|
||||
name: "PATH",
|
||||
value: process.env.PATH
|
||||
}]
|
||||
});
|
||||
|
||||
svc.on("install", () => {
|
||||
console.log("Service installed. Starting...");
|
||||
svc.start();
|
||||
});
|
||||
|
||||
svc.on("start", () => {
|
||||
console.log("Service started!");
|
||||
});
|
||||
|
||||
svc.on("error", (err) => {
|
||||
console.error("Error:", err);
|
||||
});
|
||||
|
||||
svc.install();
|
||||
12
flm-service-uninstall.js
Normal file
12
flm-service-uninstall.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const Service = require("node-windows").Service;
|
||||
|
||||
const svc = new Service({
|
||||
name: "FLM Vision Proxy",
|
||||
script: "C:\\Users\\sshuser\\flm-proxy.js"
|
||||
});
|
||||
|
||||
svc.on("uninstall", () => {
|
||||
console.log("Service uninstalled.");
|
||||
});
|
||||
|
||||
svc.uninstall();
|
||||
4
flm-start.bat
Normal file
4
flm-start.bat
Normal file
@@ -0,0 +1,4 @@
|
||||
@echo off
|
||||
echo Starting Vision AI on port 8000...
|
||||
start /B "" "C:\Users\sshuser\FastFlowLM\flm.exe" serve qwen2.5vl-it:3b --host 0.0.0.0 --port 8000 --pmode performance
|
||||
echo Vision AI started. Access at http://192.168.0.208:8000
|
||||
3
flm-stop.bat
Normal file
3
flm-stop.bat
Normal file
@@ -0,0 +1,3 @@
|
||||
@echo off
|
||||
taskkill /IM flm.exe /F >nul 2>&1
|
||||
echo AI stopped.
|
||||
205
package-lock.json
generated
Normal file
205
package-lock.json
generated
Normal file
@@ -0,0 +1,205 @@
|
||||
{
|
||||
"name": "sshuser",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"dependencies": {
|
||||
"node-windows": "^1.0.0-beta.8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
|
||||
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-convert": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cliui": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
|
||||
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"string-width": "^4.2.0",
|
||||
"strip-ansi": "^6.0.1",
|
||||
"wrap-ansi": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
|
||||
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"color-name": "~1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
|
||||
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/escalade": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
|
||||
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/get-caller-file": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
|
||||
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": "6.* || 8.* || >= 10.*"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
|
||||
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/node-windows": {
|
||||
"version": "1.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/node-windows/-/node-windows-1.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-uLekXnSeem3nW5escID224Fd0U/1VtvE796JpSpOY+c73Cslz/Qn2WUHRJyPQJEMrNGAy/FMRFjjhh4z1alZTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"xml": "1.0.1",
|
||||
"yargs": "^17.5.1"
|
||||
}
|
||||
},
|
||||
"node_modules/require-directory": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
|
||||
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
|
||||
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^4.0.0",
|
||||
"string-width": "^4.1.0",
|
||||
"strip-ansi": "^6.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha512-huCv9IH9Tcf95zuYCsQraZtWnJvBtLVE0QHMOs8bWyZAFZNDcYjsPq1nEx8jKA9y+Beo9v+7OBPRisQTjinQMw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/y18n": {
|
||||
"version": "5.0.8",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
|
||||
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs": {
|
||||
"version": "17.7.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
|
||||
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/yargs-parser": {
|
||||
"version": "21.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
|
||||
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
package.json
Normal file
5
package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"node-windows": "^1.0.0-beta.8"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user