diff --git a/.env.example b/.env.example index 7a550fe..4267a2f 100644 --- a/.env.example +++ b/.env.example @@ -1,9 +1,14 @@ -# GEMINI_API_KEY: Required for Gemini AI API calls. -# AI Studio automatically injects this at runtime from user secrets. -# Users configure this via the Secrets panel in the AI Studio UI. +# GEMINI_API_KEY: Required for live Gemini AI API calls. +# AI Studio can inject this at runtime from user secrets. GEMINI_API_KEY="MY_GEMINI_API_KEY" +# GEMINI_MODEL: Optional model override for the TerKix command endpoint. +# Defaults to gemini-2.5-flash when omitted. +GEMINI_MODEL="gemini-2.5-flash" + +# PORT: Optional local/server port. +PORT="3000" + # APP_URL: The URL where this applet is hosted. -# AI Studio automatically injects this at runtime with the Cloud Run service URL. -# Used for self-referential links, OAuth callbacks, and API endpoints. +# AI Studio can inject this at runtime with the Cloud Run service URL. APP_URL="MY_APP_URL" diff --git a/README.md b/README.md index e69de29..347e841 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,98 @@ +# TerKix Terminal OS + +TerKix Terminal OS is a Termux-inspired, AI-native development workspace built with React, Vite, Express, and the Google Gemini API. It keeps the TerKix brand front-and-center while providing a terminal-first interface for managing virtual projects, files, agent workflows, deployments, plugins, contacts, and telemetry dashboards. + +## What this repo contains + +- **React 19 SPA** for the TerKix terminal, workspace explorer, dashboard, plugin hub, contacts manager, and telemetry views. +- **TerKix logo asset** at `public/terkix-logo.svg` for the favicon, nav rail, cockpit header, and dashboard hero. +- **Installable PWA shell** via `public/manifest.webmanifest` and `public/sw.js` for app-like launch and offline shell caching. +- **Terminal productivity upgrades** including persisted command history, arrow-key recall, online/offline status, and cache-safe service worker routing. +- **Express + Vite server** for local development and production static hosting. +- **Gemini command API** at `/api/gemini/command` for multi-agent workflow generation with an offline simulator fallback in the UI. +- **Safe local persistence** for projects, terminal history, active workspace, and command metrics. + +## Requirements + +- Node.js 22 or newer is recommended. +- npm 10 or newer. +- A Gemini API key when you want live AI responses. Without a configured key, the client falls back to the built-in simulator. + +## Environment setup + +Copy the example environment file and fill in your values: + +```bash +cp .env.example .env +``` + +Supported variables: + +| Variable | Required | Default | Description | +| --- | --- | --- | --- | +| `GEMINI_API_KEY` | Yes for live AI | _none_ | API key used by `@google/genai`. | +| `GEMINI_MODEL` | No | `gemini-2.5-flash` | Model used by the server command endpoint. | +| `PORT` | No | `3000` | Port for the Express/Vite server. | +| `APP_URL` | No | _none_ | Public URL used by hosted environments. | + +## Development + +Install dependencies: + +```bash +npm install +``` + +Run the app locally: + +```bash +npm run dev +``` + +Open `http://localhost:3000`. + +## Quality checks + +Run TypeScript validation: + +```bash +npm run typecheck +``` + +Run the full production build: + +```bash +npm run build +``` + +Start the production build: + +```bash +npm start +``` + +Check server health when the app is running: + +```bash +curl http://localhost:3000/api/health +``` + +## Project structure + +```text +. +├── server.ts # Express server and Gemini command endpoint +├── src/App.tsx # Main TerKix UI shell and workspace logic +├── src/components/ # Dashboard, project, plugin, contact, and chart modules +├── src/data/presets.ts # Default virtual projects and sample workspace files +├── src/types.ts # Shared domain types +├── src/utils/storage.ts # Defensive localStorage helpers +└── public/ # TerKix logo, web manifest, and service worker +``` + +## Notes for contributors + +- Keep generated workspace file paths under `workspace/project/...` so the TerKix virtual file explorer can mount them correctly. +- Keep dependencies that are only needed for builds or tooling in `devDependencies`. +- Do not commit `.env` files or real API keys. +- Run `npm run typecheck` and `npm run build` before opening a pull request. diff --git a/index.html b/index.html index 21dfe69..2912561 100644 --- a/index.html +++ b/index.html @@ -3,11 +3,17 @@ - My Google AI Studio App + + + + + + + + TerKix Terminal OS
- diff --git a/metadata.json b/metadata.json index 9285442..daaf11a 100644 --- a/metadata.json +++ b/metadata.json @@ -1,6 +1,10 @@ { - "name": "RKix Terminal OS", - "description": "AI-native terminal-based software development environment with autonomous planner, builder, designer, debugger, and deployer agents.", - "requestFramePermissions": [], - "majorCapabilities": ["MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API"] + "name": "TerKix Terminal OS", + "description": "AI-native terminal-based software development environment with autonomous planner, builder, designer, debugger, deployer, plugin, contact, and telemetry agents.", + "requestFramePermissions": [ + "microphone" + ], + "majorCapabilities": [ + "MAJOR_CAPABILITY_SERVER_SIDE_GEMINI_API" + ] } diff --git a/package-lock.json b/package-lock.json index de6fc61..8ab441b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,29 +1,28 @@ { - "name": "react-example", - "version": "0.0.0", + "name": "terkix-terminal-os", + "version": "1.0.5", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "react-example", - "version": "0.0.0", + "name": "terkix-terminal-os", + "version": "1.0.5", "dependencies": { "@google/genai": "^2.4.0", - "@tailwindcss/vite": "^4.1.14", - "@vitejs/plugin-react": "^5.0.4", "d3": "^7.9.0", "dotenv": "^17.2.3", "express": "^4.21.2", "lucide-react": "^0.546.0", "motion": "^12.23.24", "react": "^19.0.1", - "react-dom": "^19.0.1", - "vite": "^6.2.3" + "react-dom": "^19.0.1" }, "devDependencies": { + "@tailwindcss/vite": "^4.1.14", "@types/d3": "^7.4.3", "@types/express": "^4.17.21", "@types/node": "^22.14.0", + "@vitejs/plugin-react": "^5.0.4", "autoprefixer": "^10.4.21", "esbuild": "^0.25.0", "tailwindcss": "^4.1.14", @@ -36,6 +35,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.29.7", @@ -50,6 +50,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -59,6 +60,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -89,6 +91,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.29.7", @@ -105,6 +108,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/compat-data": "^7.29.7", @@ -121,6 +125,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -130,6 +135,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.7", @@ -143,6 +149,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-module-imports": "^7.29.7", @@ -160,6 +167,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -169,6 +177,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -178,6 +187,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -187,6 +197,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -196,6 +207,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.29.7", @@ -209,6 +221,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.29.7" @@ -224,6 +237,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -239,6 +253,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.29.7" @@ -254,6 +269,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -268,6 +284,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.7", @@ -286,6 +303,7 @@ "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.29.7", @@ -302,6 +320,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -318,6 +337,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -334,6 +354,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -350,6 +371,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -366,6 +388,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -382,6 +405,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -398,6 +422,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -414,6 +439,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -430,6 +456,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -446,6 +473,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -462,6 +490,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -478,6 +507,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -494,6 +524,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -510,6 +541,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -526,6 +558,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -542,6 +575,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -558,6 +592,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -574,6 +609,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -590,6 +626,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -606,6 +643,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -622,6 +660,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -638,6 +677,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -654,6 +694,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -670,6 +711,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -686,6 +728,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -702,6 +745,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -739,6 +783,7 @@ "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -749,6 +794,7 @@ "version": "2.3.5", "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -759,6 +805,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.0.0" @@ -768,12 +815,14 @@ "version": "1.5.5", "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -847,6 +896,7 @@ "version": "1.0.0-rc.3", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.3.tgz", "integrity": "sha512-eybk3TjzzzV97Dlj5c+XrBFW57eTNhzod66y9HrBlzJ6NsCrWCp/2kaPS3K9wJmurBC0Tdw4yPjXKZqlznim3Q==", + "dev": true, "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { @@ -856,6 +906,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -869,6 +920,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -882,6 +934,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -895,6 +948,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -908,6 +962,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -921,6 +976,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -934,6 +990,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -947,6 +1004,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -960,6 +1018,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -973,6 +1032,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -986,6 +1046,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -999,6 +1060,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1012,6 +1074,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1025,6 +1088,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1038,6 +1102,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1051,6 +1116,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1064,6 +1130,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1077,6 +1144,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1090,6 +1158,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1103,6 +1172,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1116,6 +1186,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1129,6 +1200,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1142,6 +1214,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1155,6 +1228,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1168,6 +1242,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1178,6 +1253,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.0.tgz", "integrity": "sha512-aFb4gUhFOgdh9AXo4IzBEOzBkkAxm9VigwDJnMIYv3lcfXCJVesNfbEaBl4BNgVRyid92AmdviqwBUBRKSeY3g==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", @@ -1193,6 +1269,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.0.tgz", "integrity": "sha512-F7HZGBeN9I0/AuuJS5PwcD8xayx5ri5GhjYUDBEVYUkexyA/giwbDNjRVrxSezE3T250OU2K/wp/ltWx3UOefg==", + "dev": true, "license": "MIT", "engines": { "node": ">= 20" @@ -1219,6 +1296,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1235,6 +1313,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1251,6 +1330,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1267,6 +1347,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1283,6 +1364,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1299,6 +1381,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1315,6 +1398,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1331,6 +1415,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1347,6 +1432,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1371,6 +1457,7 @@ "cpu": [ "wasm32" ], + "dev": true, "license": "MIT", "optional": true, "dependencies": { @@ -1392,6 +1479,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1408,6 +1496,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1421,6 +1510,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.3.0.tgz", "integrity": "sha512-t6J3OrB5Fc0ExuhohouH0fWUGMYL6PTLhW+E7zIk/pdbnJARZDCwjBznFnkh5ynRnIRSI4YjtTH0t6USjJISrw==", + "dev": true, "license": "MIT", "dependencies": { "@tailwindcss/node": "4.3.0", @@ -1435,6 +1525,7 @@ "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.20.7", @@ -1448,6 +1539,7 @@ "version": "7.27.0", "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.0.0" @@ -1457,6 +1549,7 @@ "version": "7.4.4", "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.1.0", @@ -1467,6 +1560,7 @@ "version": "7.28.0", "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, "license": "MIT", "dependencies": { "@babel/types": "^7.28.2" @@ -1781,6 +1875,7 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, "license": "MIT" }, "node_modules/@types/express": { @@ -1896,6 +1991,7 @@ "version": "5.2.0", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-5.2.0.tgz", "integrity": "sha512-YmKkfhOAi3wsB1PhJq5Scj3GXMn3WvtQ/JC0xoopuHoXSdmtdStOpFrYaT1kie2YgFBcIe64ROzMYRjCrYOdYw==", + "dev": true, "license": "MIT", "dependencies": { "@babel/core": "^7.29.0", @@ -2001,6 +2097,7 @@ "version": "2.10.34", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.34.tgz", "integrity": "sha512-IMDedajPifLnHNY0X9n8hKxRTQ6/eTHwr5bDo04WnuqxyKw6LYtQywCuuqPZwhl3aBXMvQpJov42GLCwRRdQzw==", + "dev": true, "license": "Apache-2.0", "bin": { "baseline-browser-mapping": "dist/cli.cjs" @@ -2061,6 +2158,7 @@ "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2138,6 +2236,7 @@ "version": "1.0.30001797", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001797.tgz", "integrity": "sha512-l8xKG+gwAIExZGl9FrF7KUwuOmk6wbEPC9Xoy/RtnWv1XG0Q4LFlagaLpUv3Kiza3W/wm27zy0yWJEieYKAP6w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -2188,6 +2287,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, "node_modules/cookie": { @@ -2676,6 +2776,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, "license": "Apache-2.0", "engines": { "node": ">=8" @@ -2726,6 +2827,7 @@ "version": "1.5.368", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.368.tgz", "integrity": "sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==", + "dev": true, "license": "ISC" }, "node_modules/encodeurl": { @@ -2741,6 +2843,7 @@ "version": "5.23.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.23.0.tgz", "integrity": "sha512-yJN/BOOLxcOW2aQgeif9mSnaUB8KtvmMMp56oA1kx1CRfBKbhZm2pJ+NBY+3eOboHxix8lfjWpHE0Ei5U8RbSA==", + "dev": true, "license": "MIT", "dependencies": { "graceful-fs": "^4.2.4", @@ -2784,6 +2887,7 @@ "version": "0.25.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -2825,6 +2929,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -2916,6 +3021,7 @@ "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, "license": "MIT", "engines": { "node": ">=12.0.0" @@ -3060,6 +3166,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -3111,6 +3218,7 @@ "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" @@ -3195,6 +3303,7 @@ "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, "license": "ISC" }, "node_modules/has-symbols": { @@ -3294,6 +3403,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "dev": true, "license": "MIT", "bin": { "jiti": "lib/jiti-cli.mjs" @@ -3303,12 +3413,14 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, "license": "MIT", "bin": { "jsesc": "bin/jsesc" @@ -3330,6 +3442,7 @@ "version": "2.2.3", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, "license": "MIT", "bin": { "json5": "lib/cli.js" @@ -3363,6 +3476,7 @@ "version": "1.32.0", "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -3395,6 +3509,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3415,6 +3530,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3435,6 +3551,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3455,6 +3572,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3475,6 +3593,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3495,6 +3614,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3515,6 +3635,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3535,6 +3656,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3555,6 +3677,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3575,6 +3698,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3595,6 +3719,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MPL-2.0", "optional": true, "os": [ @@ -3618,6 +3743,7 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, "license": "ISC", "dependencies": { "yallist": "^3.0.2" @@ -3636,6 +3762,7 @@ "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" @@ -3761,6 +3888,7 @@ "version": "3.3.12", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz", "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==", + "dev": true, "funding": [ { "type": "github", @@ -3826,6 +3954,7 @@ "version": "2.0.47", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.47.tgz", "integrity": "sha512-Uzmd6LXpouKo8EUK68IjH4+E01w/hXyV3R3g/geCJo+rXLNfh1xucB+LOzYEOQPSiUK3h/xZf0cQGcSsmyL2Og==", + "dev": true, "license": "MIT", "engines": { "node": ">=18" @@ -3887,12 +4016,14 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, "license": "ISC" }, "node_modules/picomatch": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, "license": "MIT", "engines": { "node": ">=12" @@ -3905,6 +4036,7 @@ "version": "8.5.15", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4037,6 +4169,7 @@ "version": "0.18.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.18.0.tgz", "integrity": "sha512-QgT5//D3jfjJb6Gsjxv0Slpj23ip+HtOpnNgnb2S5zU3CB26G/IDPGoy4RJB42wzFE46DRsstbW6tKHoKbhAxw==", + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4061,6 +4194,7 @@ "version": "4.61.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.61.1.tgz", "integrity": "sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==", + "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.9" @@ -4143,6 +4277,7 @@ "version": "6.3.1", "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -4284,6 +4419,7 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4302,12 +4438,14 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.0.tgz", "integrity": "sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==", + "dev": true, "license": "MIT" }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "dev": true, "license": "MIT", "engines": { "node": ">=6" @@ -4321,6 +4459,7 @@ "version": "0.2.17", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", @@ -4352,7 +4491,7 @@ "version": "4.22.4", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", - "devOptional": true, + "dev": true, "license": "MIT", "dependencies": { "esbuild": "~0.28.0" @@ -4374,6 +4513,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4390,6 +4530,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4406,6 +4547,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4422,6 +4564,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4438,6 +4581,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4454,6 +4598,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4470,6 +4615,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4486,6 +4632,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4502,6 +4649,7 @@ "cpu": [ "arm" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4518,6 +4666,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4534,6 +4683,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4550,6 +4700,7 @@ "cpu": [ "loong64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4566,6 +4717,7 @@ "cpu": [ "mips64el" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4582,6 +4734,7 @@ "cpu": [ "ppc64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4598,6 +4751,7 @@ "cpu": [ "riscv64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4614,6 +4768,7 @@ "cpu": [ "s390x" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4630,6 +4785,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4646,6 +4802,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4662,6 +4819,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4678,6 +4836,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4694,6 +4853,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4710,6 +4870,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4726,6 +4887,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4742,6 +4904,7 @@ "cpu": [ "arm64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4758,6 +4921,7 @@ "cpu": [ "ia32" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4774,6 +4938,7 @@ "cpu": [ "x64" ], + "dev": true, "license": "MIT", "optional": true, "os": [ @@ -4787,7 +4952,7 @@ "version": "0.28.0", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "devOptional": true, + "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -4871,6 +5036,7 @@ "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, "funding": [ { "type": "opencollective", @@ -4919,6 +5085,7 @@ "version": "6.4.3", "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", @@ -5023,6 +5190,7 @@ "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, "license": "ISC" } } diff --git a/package.json b/package.json index 9073fbe..2535bb7 100644 --- a/package.json +++ b/package.json @@ -1,27 +1,26 @@ { - "name": "react-example", + "name": "terkix-terminal-os", "private": true, - "version": "0.0.0", + "version": "1.0.5", "type": "module", "scripts": { "dev": "tsx server.ts", "build": "vite build && esbuild server.ts --bundle --platform=node --format=cjs --packages=external --sourcemap --outfile=dist/server.cjs", "start": "node dist/server.cjs", + "preview": "vite preview", "clean": "rm -rf dist server.js", - "lint": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "lint": "npm run typecheck" }, "dependencies": { "@google/genai": "^2.4.0", - "@tailwindcss/vite": "^4.1.14", - "@vitejs/plugin-react": "^5.0.4", "d3": "^7.9.0", "dotenv": "^17.2.3", "express": "^4.21.2", "lucide-react": "^0.546.0", "motion": "^12.23.24", "react": "^19.0.1", - "react-dom": "^19.0.1", - "vite": "^6.2.3" + "react-dom": "^19.0.1" }, "devDependencies": { "@types/d3": "^7.4.3", @@ -32,6 +31,9 @@ "tailwindcss": "^4.1.14", "tsx": "^4.21.0", "typescript": "~5.8.2", - "vite": "^6.2.3" - } + "vite": "^6.2.3", + "@tailwindcss/vite": "^4.1.14", + "@vitejs/plugin-react": "^5.0.4" + }, + "description": "Termux-inspired AI-native TerKix software development workspace with autonomous planner, builder, designer, debugger, and deployment agents." } diff --git a/public/manifest.webmanifest b/public/manifest.webmanifest new file mode 100644 index 0000000..c54c772 --- /dev/null +++ b/public/manifest.webmanifest @@ -0,0 +1,36 @@ +{ + "name": "TerKix Terminal OS", + "short_name": "TerKix", + "description": "A Termux-inspired AI-native terminal workspace for building, previewing, and deploying software with agent workflows.", + "start_url": "/", + "scope": "/", + "display": "standalone", + "orientation": "portrait-primary", + "background_color": "#030508", + "theme_color": "#030508", + "categories": ["developer", "productivity", "utilities"], + "icons": [ + { + "src": "/terkix-logo.svg", + "sizes": "any", + "type": "image/svg+xml", + "purpose": "any maskable" + } + ], + "shortcuts": [ + { + "name": "Open Terminal", + "short_name": "Terminal", + "description": "Open the TerKix command shell.", + "url": "/?section=terminal", + "icons": [{ "src": "/terkix-logo.svg", "sizes": "any", "type": "image/svg+xml" }] + }, + { + "name": "Workspace Files", + "short_name": "Files", + "description": "Open the TerKix workspace files view.", + "url": "/?section=files", + "icons": [{ "src": "/terkix-logo.svg", "sizes": "any", "type": "image/svg+xml" }] + } + ] +} diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..9a85a8e --- /dev/null +++ b/public/sw.js @@ -0,0 +1,60 @@ +const CACHE_NAME = "terkix-shell-v2"; +const SHELL_ASSETS = ["/", "/index.html", "/terkix-logo.svg", "/manifest.webmanifest"]; + +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => cache.addAll(SHELL_ASSETS)).then(() => self.skipWaiting()), + ); +}); + +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys() + .then((keys) => Promise.all(keys.filter((key) => key !== CACHE_NAME).map((key) => caches.delete(key)))) + .then(() => self.clients.claim()), + ); +}); + +async function networkFirst(request) { + const cache = await caches.open(CACHE_NAME); + try { + const response = await fetch(request); + cache.put(request, response.clone()); + return response; + } catch (_error) { + return (await cache.match(request)) || cache.match("/index.html"); + } +} + +async function staleWhileRevalidate(request) { + const cache = await caches.open(CACHE_NAME); + const cached = await cache.match(request); + const network = fetch(request) + .then((response) => { + if (response.ok) { + cache.put(request, response.clone()); + } + return response; + }) + .catch(() => cached); + + return cached || network; +} + +self.addEventListener("fetch", (event) => { + if (event.request.method !== "GET") { + return; + } + + const requestUrl = new URL(event.request.url); + if (requestUrl.origin !== self.location.origin || requestUrl.pathname.startsWith("/api/")) { + return; + } + + if (event.request.mode === "navigate") { + event.respondWith(networkFirst(event.request)); + return; + } + + event.respondWith(staleWhileRevalidate(event.request)); +}); diff --git a/public/terkix-logo.svg b/public/terkix-logo.svg new file mode 100644 index 0000000..6cf9f86 --- /dev/null +++ b/public/terkix-logo.svg @@ -0,0 +1,30 @@ + + TerKix logo + A neon terminal shield with the letters TK and a Termux-inspired command prompt. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/server.ts b/server.ts index 8d68ed6..7955b04 100644 --- a/server.ts +++ b/server.ts @@ -3,20 +3,50 @@ * SPDX-License-Identifier: Apache-2.0 */ +import dotenv from "dotenv"; import express from "express"; import path from "path"; -import { createServer as createViteServer } from "vite"; import { GoogleGenAI, Type } from "@google/genai"; -import dotenv from "dotenv"; +import { createServer as createViteServer } from "vite"; dotenv.config(); +interface WorkspaceFilePayload { + path: string; + name?: string; + content: string; + language?: string; +} + +interface ProjectContextPayload { + name?: string; + description?: string; +} + +interface GeminiCommandRequest { + command?: string; + currentFiles?: WorkspaceFilePayload[]; + projectContext?: ProjectContextPayload; + activeBranch?: string; + thinkingMode?: boolean; +} + const app = express(); -const PORT = 3000; +const parsedPort = Number.parseInt(process.env.PORT ?? "", 10); +const PORT = Number.isInteger(parsedPort) && parsedPort > 0 ? parsedPort : 3000; +const GEMINI_MODEL = process.env.GEMINI_MODEL || "gemini-2.5-flash"; app.use(express.json({ limit: "50mb" })); -// Lazy initializer for Google GenAI client +app.get("/api/health", (_req, res) => { + res.json({ + ok: true, + service: "TerKix Terminal OS", + geminiModel: GEMINI_MODEL, + }); +}); + +// Lazy initializer for Google GenAI client. let aiClient: GoogleGenAI | null = null; function getGenAI(): GoogleGenAI { if (!aiClient) { @@ -24,6 +54,7 @@ function getGenAI(): GoogleGenAI { if (!key || key === "MY_GEMINI_API_KEY") { throw new Error("GEMINI_API_KEY environment variable is not configured. Please add it via the Secrets panel in AI Studio settings."); } + aiClient = new GoogleGenAI({ apiKey: key, httpOptions: { @@ -33,26 +64,49 @@ function getGenAI(): GoogleGenAI { }, }); } + return aiClient; } -// REST route for Terminal command processing with Gemini Agent orchestrating +function normalizeFiles(files: unknown): WorkspaceFilePayload[] { + if (!Array.isArray(files)) { + return []; + } + + return files.filter((file): file is WorkspaceFilePayload => + typeof file?.path === "string" && typeof file?.content === "string" + ); +} + +function parseGeminiJson(text: string): unknown { + const trimmed = text.trim(); + const unfenced = trimmed + .replace(/^```(?:json)?\s*/i, "") + .replace(/\s*```$/i, "") + .trim(); + + return JSON.parse(unfenced); +} + +// REST route for Terminal command processing with Gemini Agent orchestrating. app.post("/api/gemini/command", async (req, res) => { try { - const { command, currentFiles, projectContext, activeBranch, outputMimeType, thinkingMode } = req.body; - - if (!command) { + const { command, currentFiles, projectContext, activeBranch, thinkingMode } = req.body as GeminiCommandRequest; + const cleanCommand = command?.trim(); + + if (!cleanCommand) { return res.status(400).json({ error: "Command is required" }); } const ai = getGenAI(); + const normalizedFiles = normalizeFiles(currentFiles); - // Construct the context representing the current virtual project directory structure and code bases - const filesContext = currentFiles && currentFiles.length > 0 - ? currentFiles.map((f: any) => `\n--- File: ${f.path} ---\nLanguage: ${f.language}\nContent:\n${f.content}`).join("\n\n") + // Construct the context representing the current virtual project directory structure and code bases. + const filesContext = normalizedFiles.length > 0 + ? normalizedFiles.map((file) => `\n--- File: ${file.path} ---\nLanguage: ${file.language || "text"}\nContent:\n${file.content}`).join("\n\n") : "[The workspace is currently empty. No files exist yet.]"; - const systemInstruction = `You are the core RKix Terminal OS agent, directing a multi-agent software development terminal. + const systemInstruction = `You are the core TerKix Terminal OS agent, directing a multi-agent software development terminal. You receive: 1. A natural language command or terminal prompt from the developer (e.g. "build an online resume website", "create navbar.tsx", "edit homepage.tsx", "deploy production"). 2. The current list of virtual files inside the active workspace directory. @@ -68,7 +122,7 @@ Your goal is to parse this input and simulate the response of a highly professio - Deploy: Simulates build output, artifact bundling, and cloud service links. - Research: Explains documentation, searches appropriate modules. -You MUST always return a JSON object sticking strictly to the response schema. +You MUST always return a JSON object sticking strictly to the response schema. Provide a realistic developer workflow inside "agentWorkflow" with detailed logs of what different agents did. Provide "workspaceChanges" containing: - filesToCreate: Array of new files. ALWAYS include the fully fleshed out code content (e.g., beautiful Tailwind HTML layouts, React components or style configurations). Avoid placeholders! Provide complete source code! @@ -85,7 +139,7 @@ IMPORTANT: Act as a real developer-centric Operating System. Be extremely litera const userPrompt = ` *** ACTIVE PROJECT CONTEXT *** -Project Name: ${projectContext?.name || "RKix Sandbox"} +Project Name: ${projectContext?.name || "TerKix Sandbox"} Description: ${projectContext?.description || "A clean development workspace"} Active Branch: ${activeBranch || "main"} THINKING_MODE_ACTIVE: ${thinkingMode ? "TRUE" : "FALSE"} @@ -94,13 +148,13 @@ THINKING_MODE_ACTIVE: ${thinkingMode ? "TRUE" : "FALSE"} ${filesContext} *** DEVELOPER INPUT COMMAND *** -${command} +${cleanCommand} Generate the agent workflow, workspace updates, detailed reasoning (if thinking mode is true), and terminal outputs. Make sure to generate detailed, fully-written file contents without leaving any comment-based placeholders (e.g., do not write "// code goes here", write the real complete code). `; const response = await ai.models.generateContent({ - model: "gemini-3.5-flash", + model: GEMINI_MODEL, contents: userPrompt, config: { systemInstruction, @@ -111,16 +165,16 @@ Generate the agent workflow, workspace updates, detailed reasoning (if thinking commandParsed: { type: Type.OBJECT, properties: { - intent: { + intent: { type: Type.STRING, - description: "Categorize input intent: create_project, create_file, edit_file, delete_file, deploy, git_command, general_query" + description: "Categorize input intent: create_project, create_file, edit_file, delete_file, deploy, git_command, general_query", }, - target: { + target: { type: Type.STRING, - description: "The specific filename, branch name, or folder path targeted, if applicable." - } + description: "The specific filename, branch name, or folder path targeted, if applicable.", + }, }, - required: ["intent", "target"] + required: ["intent", "target"], }, agentWorkflow: { type: Type.ARRAY, @@ -130,10 +184,10 @@ Generate the agent workflow, workspace updates, detailed reasoning (if thinking properties: { agent: { type: Type.STRING, description: "Role name, e.g. Planner, Builder, Designer, Debugger, Deploy, Research" }, action: { type: Type.STRING, description: "Action text e.g. Scaffolding structure, Refining CSS layout" }, - log: { type: Type.STRING, description: "Log details representing thoughts or actual work files processed" } + log: { type: Type.STRING, description: "Log details representing thoughts or actual work files processed" }, }, - required: ["agent", "action", "log"] - } + required: ["agent", "action", "log"], + }, }, workspaceChanges: { type: Type.OBJECT, @@ -146,10 +200,10 @@ Generate the agent workflow, workspace updates, detailed reasoning (if thinking path: { type: Type.STRING, description: "The full path starting with 'workspace/project/'" }, name: { type: Type.STRING }, content: { type: Type.STRING, description: "The COMPLETE, functional file code" }, - language: { type: Type.STRING, description: "e.g. html, css, tsx, js, json, md" } + language: { type: Type.STRING, description: "e.g. html, css, tsx, js, json, md" }, }, - required: ["path", "name", "content", "language"] - } + required: ["path", "name", "content", "language"], + }, }, filesToEdit: { type: Type.ARRAY, @@ -157,34 +211,34 @@ Generate the agent workflow, workspace updates, detailed reasoning (if thinking type: Type.OBJECT, properties: { path: { type: Type.STRING, description: "Path of existing file to update" }, - content: { type: Type.STRING, description: "The updated FULL contents of the file" } + content: { type: Type.STRING, description: "The updated FULL contents of the file" }, }, - required: ["path", "content"] - } + required: ["path", "content"], + }, }, filesToDelete: { type: Type.ARRAY, - items: { type: Type.STRING } - } + items: { type: Type.STRING }, + }, }, - required: ["filesToCreate", "filesToEdit", "filesToDelete"] + required: ["filesToCreate", "filesToEdit", "filesToDelete"], }, - terminalOutput: { - type: Type.STRING, - description: "Simulated text to append to the system command prompt, with color tags or standard console structure." + terminalOutput: { + type: Type.STRING, + description: "Simulated text to append to the system command prompt, with color tags or standard console structure.", }, - explanation: { - type: Type.STRING, - description: "A summary explaining the actions taken in a polite, professional developer voice." + explanation: { + type: Type.STRING, + description: "A summary explaining the actions taken in a polite, professional developer voice.", }, detailedReasoning: { type: Type.STRING, - description: "Comprehensive multi-agent thinking, architectural debate, and planning process when thinkingMode is active (in Vietnamese or English matching user language)." - } + description: "Comprehensive multi-agent thinking, architectural debate, and planning process when thinkingMode is active (in Vietnamese or English matching user language).", + }, }, - required: ["commandParsed", "agentWorkflow", "workspaceChanges", "terminalOutput", "explanation"] - } - } + required: ["commandParsed", "agentWorkflow", "workspaceChanges", "terminalOutput", "explanation"], + }, + }, }); const resultText = response.text; @@ -192,18 +246,15 @@ Generate the agent workflow, workspace updates, detailed reasoning (if thinking throw new Error("Empty response from Gemini API"); } - const commandResult = JSON.parse(resultText.trim()); - return res.json(commandResult); - - } catch (error: any) { + return res.json(parseGeminiJson(resultText)); + } catch (error) { + const message = error instanceof Error ? error.message : "An error occurred while communicating with the AI Core."; console.error("Gemini Command processing failed:", error); - return res.status(500).json({ - error: error.message || "An error occurred while communicating with the AI Core." - }); + return res.status(500).json({ error: message }); } }); -// Configure Vite middleware or Static files hosting +// Configure Vite middleware or static file hosting. async function startServer() { if (process.env.NODE_ENV !== "production") { const vite = await createViteServer({ @@ -214,13 +265,13 @@ async function startServer() { } else { const distPath = path.join(process.cwd(), "dist"); app.use(express.static(distPath)); - app.get("*", (req, res) => { + app.get("*", (_req, res) => { res.sendFile(path.join(distPath, "index.html")); }); } app.listen(PORT, "0.0.0.0", () => { - console.log(`RKix Terminal OS server booted successfully on port ${PORT}`); + console.log(`TerKix Terminal OS server booted successfully on port ${PORT} using ${GEMINI_MODEL}`); }); } diff --git a/src/App.tsx b/src/App.tsx index 8e31b67..8199fb9 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -46,6 +46,7 @@ import { MicOff } from "lucide-react"; import { Project, WorkspaceFile, Agent, TerminalLine, Deployment, GitCommit } from "./types"; +import { readJsonStorage, readNumberStorage, readStringStorage, writeStorage } from "./utils/storage"; import { PRESET_PROJECTS } from "./data/presets"; import DashboardOverview from "./components/DashboardOverview"; import ProjectList from "./components/ProjectList"; @@ -53,6 +54,12 @@ import ContactsManager, { Contact } from "./components/ContactsManager"; import PluginManager, { CustomPlugin } from "./components/PluginManager"; import TelemetryD3Chart from "./components/TelemetryD3Chart"; + +interface BeforeInstallPromptEvent extends Event { + prompt: () => Promise; + userChoice: Promise<{ outcome: "accepted" | "dismissed"; platform: string }>; +} + const DEFAULT_AGENTS: Agent[] = [ { id: "planner", name: "Planner Agent", role: "Planner", description: "Requirement analysis, task decomposition, and workflow plan generation.", status: "idle", lastAction: "Standby.", color: "bg-[#58A6FF]" }, { id: "builder", name: "Builder Agent", role: "Builder", description: "Source code generation, project scaffolding, and system initialization.", status: "idle", lastAction: "Standby.", color: "bg-[#F0883E]" }, @@ -63,43 +70,89 @@ const DEFAULT_AGENTS: Agent[] = [ ]; const INITIAL_LINES: TerminalLine[] = [ - { id: "init-1", type: "system", text: "RKix Terminal OS [Version 1.0.4] - Secure Dev Kernel", timestamp: new Date().toLocaleTimeString() }, + { id: "init-1", type: "system", text: "TerKix Terminal OS [Version 1.0.4] - Secure Dev Kernel", timestamp: new Date().toLocaleTimeString() }, { id: "init-2", type: "system", text: "Initializing isolated multi-agent software sandboxes...", timestamp: new Date().toLocaleTimeString() }, { id: "init-3", type: "success", text: "Core kernel boot completed inside micro-container in 12ms.", timestamp: new Date().toLocaleTimeString() }, { id: "init-4", type: "agent-info", text: "[PLANNER] Directives compiled. Ready for requirements parsing.", timestamp: new Date().toLocaleTimeString(), agent: "Planner" }, { id: "init-5", type: "agent-info", text: "[BUILDER] Scaffolding matrices loaded. Standby state verified.", timestamp: new Date().toLocaleTimeString(), agent: "Builder" }, { id: "init-6", type: "agent-success", text: "All 6 autonomous agents registered and synchronized.", timestamp: new Date().toLocaleTimeString(), agent: "Designer" }, - { id: "init-7", type: "warning", text: "Type 'help' to review RKix custom terminal commands or enter a natural prompt to spawn assets.", timestamp: new Date().toLocaleTimeString() }, + { id: "init-7", type: "warning", text: "Type 'help' to review TerKix custom terminal commands or enter a natural prompt to spawn assets.", timestamp: new Date().toLocaleTimeString() }, +]; + +const TERKIX_FLOW_STEPS = [ + { + label: "01 COMMAND", + text: "Gõ ý tưởng Termux/terminal", + detail: "Prompt tiếng Việt hoặc shell command", + color: "text-[#3FB950] border-[#3FB950]/25 bg-[#3FB950]/8", + }, + { + label: "02 AGENTS", + text: "Planner + Builder xử lý", + detail: "Tự động chia việc cho agent stack", + color: "text-[#58A6FF] border-[#58A6FF]/25 bg-[#58A6FF]/8", + }, + { + label: "03 WORKSPACE", + text: "Tạo file, preview, chỉnh sửa", + detail: "Duyệt project và xem live output", + color: "text-[#F0883E] border-[#F0883E]/25 bg-[#F0883E]/8", + }, + { + label: "04 DEPLOY", + text: "Đóng gói & chia sẻ link", + detail: "Mô phỏng release production", + color: "text-[#BC8CFF] border-[#BC8CFF]/25 bg-[#BC8CFF]/8", + }, +]; + +const TERKIX_QUICK_ACTIONS = [ + { label: "Tạo app Termux", command: "create a polished Termux-style mobile developer dashboard for TerKix" }, + { label: "Tối ưu UI", command: "optimize the current app UI for mobile, dark terminal contrast, and clearer command flow" }, + { label: "Sinh landing page", command: "build a premium TerKix landing page with hero, features, pricing, and deploy CTA" }, + { label: "Debug toàn bộ", command: "scan all workspace files, fix UI bugs, and summarize quality issues" }, ]; +const TERKIX_APP_CAPABILITIES = ["PWA installable", "Offline shell cache", "Mobile keyboard dock", "Agent command deck"]; + + export default function App() { - const rkixRootRef = useRef(null); + const terkixRootRef = useRef(null); // Persistence state loaders - const [projects, setProjects] = useState(() => { - const saved = localStorage.getItem("rkix_projects"); - return saved ? JSON.parse(saved) : PRESET_PROJECTS; - }); + const [projects, setProjects] = useState(() => + readJsonStorage("terkix_projects", PRESET_PROJECTS, (value): value is Project[] => + Array.isArray(value) && value.every((item) => typeof item?.id === "string" && Array.isArray(item?.files)), + ["rkix_projects"] + ) + ); - const [activeProjectId, setActiveProjectId] = useState(() => { - const saved = localStorage.getItem("rkix_active_project_id"); - if (saved) return saved; - return PRESET_PROJECTS[0]?.id || ""; - }); + const [activeProjectId, setActiveProjectId] = useState(() => + readStringStorage("terkix_active_project_id", PRESET_PROJECTS[0]?.id || "", ["rkix_active_project_id"]) + ); const [currentSection, setCurrentSection] = useState("terminal"); - const [terminalLines, setTerminalLines] = useState(() => { - const saved = localStorage.getItem("rkix_terminal_lines"); - return saved ? JSON.parse(saved) : INITIAL_LINES; - }); + const [terminalLines, setTerminalLines] = useState(() => + readJsonStorage("terkix_terminal_lines", INITIAL_LINES, (value): value is TerminalLine[] => + Array.isArray(value) && value.every((item) => typeof item?.id === "string" && typeof item?.text === "string"), + ["rkix_terminal_lines"] + ) + ); const [agents, setAgents] = useState(DEFAULT_AGENTS); const [commandText, setCommandText] = useState(""); + const [commandHistory, setCommandHistory] = useState(() => + readJsonStorage("terkix_command_history", [], (value): value is string[] => + Array.isArray(value) && value.every((item) => typeof item === "string"), + ["rkix_command_history"] + ) + ); + const [historyCursor, setHistoryCursor] = useState(null); + const [isOnline, setIsOnline] = useState(() => typeof navigator === "undefined" ? true : navigator.onLine); const [isProcessing, setIsProcessing] = useState(false); - const [totalCommandsRun, setTotalCommandsRun] = useState(() => { - const saved = localStorage.getItem("rkix_total_commands"); - return saved ? parseInt(saved, 10) : 0; - }); + const [totalCommandsRun, setTotalCommandsRun] = useState(() => + readNumberStorage("terkix_total_commands", 0, ["rkix_total_commands"]) + ); // File explorer states const [selectedFilePath, setSelectedFilePath] = useState(""); @@ -119,8 +172,10 @@ export default function App() { const [showContext, setShowContext] = useState(false); const [isMicActive, setIsMicActive] = useState(false); const [micStream, setMicStream] = useState(null); + const [installPromptEvent, setInstallPromptEvent] = useState(null); + const [pwaInstallState, setPwaInstallState] = useState<"ready" | "available" | "installed">("ready"); - const longPressTimerRef = useRef(null); + const longPressTimerRef = useRef | null>(null); const [longPressedTriggered, setLongPressedTriggered] = useState(false); const startLongPressTimer = () => { @@ -220,6 +275,47 @@ export default function App() { } }, [isMicActive, micStream]); + useEffect(() => { + const handleBeforeInstallPrompt = (event: Event) => { + event.preventDefault(); + setInstallPromptEvent(event as BeforeInstallPromptEvent); + setPwaInstallState("available"); + }; + + const handleAppInstalled = () => { + setInstallPromptEvent(null); + setPwaInstallState("installed"); + }; + + window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt); + window.addEventListener("appinstalled", handleAppInstalled); + + return () => { + window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt); + window.removeEventListener("appinstalled", handleAppInstalled); + }; + }, []); + + const handleInstallApp = async () => { + if (!installPromptEvent) { + setTerminalLines(prev => [ + ...prev, + { + id: `pwa-${Date.now()}`, + type: "system", + text: "TerKix PWA shell is configured. If your browser supports installation, use Add to Home Screen / Install App from the browser menu.", + timestamp: new Date().toLocaleTimeString() + } + ]); + return; + } + + await installPromptEvent.prompt(); + const choice = await installPromptEvent.userChoice; + setPwaInstallState(choice.outcome === "accepted" ? "installed" : "ready"); + setInstallPromptEvent(null); + }; + // Termux UI responsive states const [isNavOpen, setIsNavOpen] = useState(false); const [consoleFontSize, setConsoleFontSize] = useState<"sm" | "base" | "lg">("base"); @@ -240,7 +336,7 @@ export default function App() { { resourceName: "people/c1", name: "Nguyễn Văn Hùng", - email: "hung.nguyen@rkix.dev", + email: "hung.nguyen@terkix.dev", phone: "+84 901 234 567", role: "Lead Architect", isCollaborating: true @@ -248,7 +344,7 @@ export default function App() { { resourceName: "people/c2", name: "Trần Thị Mai", - email: "mai.tran@rkix.dev", + email: "mai.tran@terkix.dev", phone: "+84 912 345 678", role: "Designer Agent", isCollaborating: true @@ -278,7 +374,7 @@ export default function App() { name: "Standard Uptime", type: "command", triggerCommand: "uptime", - responseOutput: "[SYSTEM STATUS] RKix Terminal OS uptime: 14h 32m 11s. Core server container response delay: 2.13ms.", + responseOutput: "[SYSTEM STATUS] TerKix Terminal OS uptime: 14h 32m 11s. Core server container response delay: 2.13ms.", isEnabled: true } ]); @@ -400,27 +496,55 @@ export default function App() { }, 1200); }; - const activeProject = projects.find(p => p.id === activeProjectId) || projects[0]; + const activeProject = projects.find(p => p.id === activeProjectId) || projects[0] || PRESET_PROJECTS[0]; useEffect(() => { - localStorage.setItem("rkix_projects", JSON.stringify(projects)); + if (projects.length === 0) { + setProjects(PRESET_PROJECTS); + setActiveProjectId(PRESET_PROJECTS[0]?.id || ""); + return; + } + + if (!projects.some((project) => project.id === activeProjectId)) { + setActiveProjectId(projects[0].id); + } + }, [activeProjectId, projects]); + + useEffect(() => { + writeStorage("terkix_projects", projects); }, [projects]); useEffect(() => { - localStorage.setItem("rkix_active_project_id", activeProjectId); + writeStorage("terkix_active_project_id", activeProjectId); if (activeProject && activeProject.files.length > 0) { - setSelectedFilePath(activeProject.files[0].path); + setSelectedFilePath((current) => + activeProject.files.some((file) => file.path === current) ? current : activeProject.files[0].path + ); } }, [activeProjectId, activeProject]); useEffect(() => { - localStorage.setItem("rkix_terminal_lines", JSON.stringify(terminalLines)); + writeStorage("terkix_terminal_lines", terminalLines); }, [terminalLines]); useEffect(() => { - localStorage.setItem("rkix_total_commands", totalCommandsRun.toString()); + writeStorage("terkix_total_commands", totalCommandsRun.toString()); }, [totalCommandsRun]); + useEffect(() => { + writeStorage("terkix_command_history", commandHistory.slice(0, 30)); + }, [commandHistory]); + + useEffect(() => { + const syncOnlineState = () => setIsOnline(navigator.onLine); + window.addEventListener("online", syncOnlineState); + window.addEventListener("offline", syncOnlineState); + return () => { + window.removeEventListener("online", syncOnlineState); + window.removeEventListener("offline", syncOnlineState); + }; + }, []); + // Update Real-time Telemetry Metrics periodically to drive live D3 visualizations useEffect(() => { const interval = setInterval(() => { @@ -476,6 +600,27 @@ export default function App() { } }, [chatMessages, currentSection]); + const recallCommandHistory = (direction: "previous" | "next") => { + if (commandHistory.length === 0) return; + + if (direction === "previous") { + const nextCursor = historyCursor === null ? 0 : Math.min(historyCursor + 1, commandHistory.length - 1); + setHistoryCursor(nextCursor); + setCommandText(commandHistory[nextCursor]); + return; + } + + if (historyCursor === null) return; + const nextCursor = historyCursor - 1; + if (nextCursor < 0) { + setHistoryCursor(null); + setCommandText(""); + } else { + setHistoryCursor(nextCursor); + setCommandText(commandHistory[nextCursor]); + } + }; + // Handle Termux custom interactive key modifiers const handleTermKeyAction = (action: string) => { if (action === "ESC") { @@ -525,6 +670,10 @@ export default function App() { else if (consoleFontSize === "base") setConsoleFontSize("lg"); } else if (action === "CLEAR") { setTerminalLines([]); + } else if (action === "↑") { + recallCommandHistory("previous"); + } else if (action === "↓") { + recallCommandHistory("next"); } else if (action === "PGUP") { if (terminalEndRef.current) { terminalEndRef.current.parentNode?.dispatchEvent(new CustomEvent('scroll-up')); @@ -555,6 +704,8 @@ export default function App() { if (!cleanCmd) return; setCommandText(""); + setHistoryCursor(null); + setCommandHistory(prev => [cleanCmd, ...prev.filter((entry) => entry !== cleanCmd)].slice(0, 30)); // Add prompt user input visual line to terminal console const inputTimestamp = new Date().toLocaleTimeString(); @@ -611,7 +762,7 @@ export default function App() { { id: respId, type: "system", - text: `\nRKix Command Line Reference Guide:\n` + + text: `\nTerKix Command Line Reference Guide:\n` + `---------------------------------------------------------------------------------\n` + `$ clear - Flush clean the active console log stream.\n` + `$ help - Display this system reference manual.\n` + @@ -683,7 +834,7 @@ export default function App() { const newCommit: GitCommit = { hash: Math.random().toString(16).slice(2, 9), message: commitMsg.replace(/['"]/g, ""), - author: "nvht2505@gmail.com ", + author: "nvht2505@gmail.com ", date: new Date().toISOString() }; @@ -1083,13 +1234,13 @@ export default function App() { - RKix Software Output + TerKix Software Output

Active Sandbox Terminal Live

-

$ rkix command execute --success

+

$ terkix command execute --success

Directive:

@@ -1098,7 +1249,7 @@ export default function App() {
  • • File "workspace/project/index.html" was updated with fresh styling.
  • • Workspace file system regenerated with standard assets.
  • -
  • • Compiled successfully under RKix development environments.
  • +
  • • Compiled successfully under TerKix development environments.
@@ -1156,7 +1307,7 @@ export default function App() { const newF: WorkspaceFile = { path: pathInput, name, - content: `// New file ${name} - RKix Terminal OS`, + content: `// New file ${name} - TerKix Terminal OS`, language: pathInput.endsWith(".html") ? "html" : pathInput.endsWith(".css") ? "css" : "tsx" }; @@ -1211,7 +1362,7 @@ export default function App() { { hash: Math.random().toString(16).slice(2, 9), message: "Boilerplate workspace compiled successfully", - author: "RKix Planner Agent", + author: "TerKix Planner Agent", date: new Date().toISOString() } ], @@ -1270,12 +1421,15 @@ export default function App() { // Active file HTML output extraction for iframe srcDoc const indexHtmlCode = activeProject.files.find(f => f.name === "index.html" || f.path.endsWith("index.html"))?.content || ""; + const latestTelemetry = telemetryHistory[telemetryHistory.length - 1] || { cpu: 2.13, ping: 1.94 }; + const liveDeploymentsCount = activeProject.deployments.filter((deployment) => deployment.status === "live").length; + const activeAgentCount = agents.filter((agent) => agent.status === "running" || agent.status === "completed").length; return (
{/* Absolute CRT monitor phosphor raster grid overlay */} @@ -1296,19 +1450,25 @@ export default function App() { > {/* Overlay Header Banner */}
-
-
- -

- RKix Automated Sandbox Core +

+ TerKix logo +
+
+ +

+ TerKix Termux Sandbox Core +

+ + Prompt → Build → Preview → Deploy + +
+

+ TERKIX APP COCKPIT +

+

+ Termux-style shell UI • Active Sandbox Layer Matrix • Realtime Sync Verified

-

- RKIX U-SYSTEM COCKPIT -

-

- Active Sandbox Layer Matrix • Realtime Sync Verified -

{/* Close Action - Highly visible Quay lại/X action */} @@ -1635,7 +1795,7 @@ export default function App() { { hash: Math.random().toString(16).slice(2, 9), message: msg, - author: "nvht2505@gmail.com ", + author: "nvht2505@gmail.com ", date: new Date().toISOString() }, ...p.commitHistory @@ -1938,7 +2098,7 @@ export default function App() {

- RKix AI Agent Architectures + TerKix AI Agent Architectures

Configure and monitor autonomous roles inside your compiler sandbox pipeline. @@ -2155,11 +2315,9 @@ export default function App() { {/* Legacy sidebars rendered only if showLegacySidebar is checked */} {showLegacySidebar && ( -