{"id":50677582,"url":"https://github.com/sctg-development/techno-viewer","last_synced_at":"2026-06-08T16:34:18.728Z","repository":{"id":357196832,"uuid":"1235790024","full_name":"sctg-development/techno-viewer","owner":"sctg-development","description":null,"archived":false,"fork":false,"pushed_at":"2026-05-20T17:35:48.000Z","size":13034,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-20T21:49:16.738Z","etag":null,"topics":["age-encryption","dxf","gerber","react","vite"],"latest_commit_sha":null,"homepage":"https://sctg-development.github.io/techno-viewer/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sctg-development.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-11T16:53:03.000Z","updated_at":"2026-05-20T17:35:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/sctg-development/techno-viewer","commit_stats":null,"previous_names":["sctg-development/techno-viewer"],"tags_count":1,"template":true,"template_full_name":null,"purl":"pkg:github/sctg-development/techno-viewer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Ftechno-viewer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Ftechno-viewer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Ftechno-viewer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Ftechno-viewer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sctg-development","download_url":"https://codeload.github.com/sctg-development/techno-viewer/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sctg-development%2Ftechno-viewer/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34071656,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-08T02:00:07.615Z","response_time":111,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["age-encryption","dxf","gerber","react","vite"],"created_at":"2026-06-08T16:34:17.920Z","updated_at":"2026-06-08T16:34:18.720Z","avatar_url":"https://github.com/sctg-development.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Techno-Viewer\n\n**Techno-Viewer** is an open-source, client-side secure portal for browsing, decrypting and viewing encrypted engineering assets — DXF drawings, PDF documentation, XLSX spreadsheets, and source code — directly in the browser. No private key ever leaves the client.\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE.md)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.x-blue)](https://www.typescriptlang.org/)\n[![React](https://img.shields.io/badge/React-19-blue)](https://react.dev/)\n[![Vite](https://img.shields.io/badge/Vite-8-646cff)](https://vitejs.dev/)\n[![AGE encryption](https://img.shields.io/badge/AGE-v1-green)](https://age-encryption.org/)\n[![Code](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/fufuni?type=Rust,TypeScript,TSX,html\u0026category=code)]()\n[![Comments](https://tokeisrv.sctg.eu.org/b1/github.com/sctg-development/fufuni?type=Rust,ypeScript,TSX,html\u0026category=comments)]()\n\n---\n\n## Why Techno-Viewer?\n\nEngineering teams routinely share sensitive assets — circuit schematics, mechanical drawings, firmware source — with a limited set of partners or customers. Techno-Viewer lets you:\n\n- **Publish encrypted assets on any static host** (Cloudflare Pages, GitHub Pages, S3…)\n- **Grant access per-user via AGE key pairs** — revoke by re-encrypting without the user's key\n- **View DXF, PDF, XLSX and source code inline**, with AI-assisted code explanation and translation\n- **Never expose plaintext files** to the server or CDN — decryption happens 100% in the browser\n\n---\n\n## Features\n\n| Feature | Details |\n|---|---|\n| AGE decryption | Client-side, browser-native, zero server key handling |\n| DXF viewer | Full CAD rendering via `@mlightcad/cad-viewer` |\n| PDF viewer | Inline PDF rendering |\n| XLSX viewer | Spreadsheet rendering via `xlsx` |\n| Source code viewer | Monaco Editor with syntax highlighting |\n| AI translation | Translate code comments to English (requires AI proxy or BYOK) |\n| AI explanation | Structured technical explanation of source files |\n| AI junior comments | Annotate code for junior developers |\n| **AI Settings (BYOK)** | **Configure a custom OpenAI-compatible provider with your own API key; auto-discovers models from the `/models` endpoint; falls back gracefully on 403** |\n| Batch download | Download selected files as a ZIP archive |\n| Persistent cache | Encrypted IndexedDB cache (LRU, 300 MiB default) |\n| i18n | French, English, Chinese UI |\n| Analytics | Optional PostHog file-view events (best-effort, backend-enriched) |\n\n---\n\n## Live Demo\n\n\u003e Copy the private key below and paste it into the login screen of your deployed instance.\n\n**Test user 1 key** (decrypts the sample assets in this repository):\n\n```\nAGE-SECRET-KEY-1TUTW5YJN0NZ52ZJFVV9QHELJH5MY9ZL00YNG9LTK0QMP0YQJDFUQ5Y8NTH\n```\n\nThe matching public key is `age102v6m86rmsv2hcqwhdk2zg5u6nsluh9jhrueletsl6zjkvkjg54s4yaj67`.  \nAll five test key pairs are in [keys/private/](keys/private/) and [keys/public/](keys/public/).\n\nThe sample encrypted assets include:\n- `drawings/` — DXF technical drawing (`test.dxf`)\n- `schematics/` — Electronic schematic DXF (`BMS_Buck-Boost.dxf`)\n- `doc/` — PDF calculation note (`LM2587_BuckBoost_50W_Note_de_Calcul.pdf`)\n- `agro-crypt/` — [agro-crypt](https://github.com/sctg-development/agro-crypt) C source code\n\n---\n\n## Screenshots\n\n- Pseudo Login\n\u003cimg width=\"1886\" height=\"1614\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d7618122-b9c3-4013-b45c-dfa2782e54e0\" /\u003e\n\n- DXF Viewer\n\u003cimg width=\"1883\" height=\"1615\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7cd59e89-aef8-4837-b556-6bb1f99fe2b4\" /\u003e\n\n- Code Viewer\n\u003cimg width=\"1885\" height=\"1617\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b502c9fb-7c85-41e0-9824-27e9d3ccbbf2\" /\u003e\n\n- Gerber viewer\n\u003cimg width=\"1887\" height=\"1613\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3bb618cb-bdd6-4aae-9eba-e4a2090006d7\" /\u003e\n\n- AI Settings\n\u003cimg width=\"1732\" height=\"1335\" alt=\"image\" src=\"https://github.com/user-attachments/assets/8e4afa5c-b3ae-4e40-8c9d-0b0adf5a413a\" /\u003e\n\n\n---\n\n## Architecture\n\n```\nBrowser (React 19 + Vite 8)\n  │\n  ├── Auth: AGE private key entered by user, stored in sessionStorage\n  ├── Tree: encrypted manifest (public/files.json.age) fetched and decrypted\n  ├── Files: /encrypted/*.age fetched on demand, decrypted in-browser\n  └── Viewers: DXF / PDF / XLSX / Monaco rendered from decrypted bytes\n                         │\n                    (optional)\n              Cloudflare Pages Function\n              /api/file-viewed → PostHog\n```\n\n**Key source locations:**\n\n| Path | Role |\n|---|---|\n| [src/config.ts](src/config.ts) | **Single-file template configuration** (branding, languages, theme, AI) |\n| [src/main.tsx](src/main.tsx) | App entry point |\n| [src/App.tsx](src/App.tsx) | Router shell |\n| [src/context/AuthContext.tsx](src/context/AuthContext.tsx) | AGE key session state |\n| [src/hooks/useAgeDecrypt.ts](src/hooks/useAgeDecrypt.ts) | File decryption flow |\n| [src/hooks/useFileTree.ts](src/hooks/useFileTree.ts) | Encrypted manifest loading |\n| [src/services/encryptedFileCache.ts](src/services/encryptedFileCache.ts) | IndexedDB LRU cache |\n| [src/services/aiSettings.ts](src/services/aiSettings.ts) | AI provider config \u0026 BYOK service |\n| [src/pages/AiSettings.tsx](src/pages/AiSettings.tsx) | AI Settings page (/settings) |\n| [src/services/fileViewAnalytics.ts](src/services/fileViewAnalytics.ts) | Analytics client |\n| [functions/api/file-viewed.js](functions/api/file-viewed.js) | Cloudflare Pages Function |\n| [rencrypt/](rencrypt/) | Rust CLI — key gen, encryption, manifest |\n\n---\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js 20+\n- Rust toolchain (for `rencrypt`)\n\n### 1. Install dependencies\n\n```bash\nnpm install\n```\n\n### 2. Build rencrypt\n\n```bash\ncd rencrypt \u0026\u0026 cargo build --release \u0026\u0026 cd ..\n```\n\n### 3. Generate key pairs\n\n```bash\nrencrypt/target/release/rencrypt -v generate-keys --keys=keys --count=5\n```\n\n### 4. Add your assets and encrypt\n\n```bash\n# Place your files under drawings/, schematics/, doc/, or any custom path\nrencrypt/target/release/rencrypt encrypt \\\n  --path drawings/ \\\n  --path schematics/ \\\n  --path doc/ \\\n  --keep-plaintext-manifest -v\n```\n\nThis writes encrypted files to `encrypted/` and the manifest to `public/files.json.age`.\n\n### 5. Configure environment\n\n```bash\ncp .env.example .env\n# Edit .env with your values\n```\n\n### 6. Run in development\n\n```bash\nnpm run dev:env\n```\n\n### 7. Build for production\n\n```bash\nnpm run build:env\n```\n\n---\n\n## Template Integration \u0026 Reuse\n\nTechno-Viewer is designed to be forked and rebranded with minimal effort.\n**All user-facing configuration lives in a single file: [`src/config.ts`](src/config.ts).**  \nEvery constant is JSDoc-documented with its effect, the files that consume it, and an example value.\n\n### Quick-start checklist\n\n1. Fork / clone the repository.\n2. Open **`src/config.ts`** and work through the sections below.\n3. Replace **`public/favicon.svg`** with your own icon.\n4. Update **`src/index.css`** if you change the accent colour.\n5. Add or remove locale files under `src/i18n/locales/` to match your language set.\n6. Run `npm run dev:env` and verify the result.\n\n---\n\n### 1 — Branding\n\nAll text identity of the application is controlled by four constants in `src/config.ts`:\n\n| Constant | Where it appears | Example |\n|---|---|---|\n| `APP_NAME` | Navbar header strip (between coordinate markers) | `'MY PRODUCT'` |\n| `APP_TITLE` | Browser tab caption (`document.title`) | `'MY PRODUCT — Engineering Portal'` |\n| `APP_DESCRIPTION` | HTML `\u003cmeta name=\"description\"\u003e` (SEO / share cards) | `'Secure viewer for…'` |\n| `APP_FOOTER_LABEL` | Page footer, left side | `'MY PRODUCT — Acme Corp'` |\n\n`APP_TITLE` is applied at runtime by `main.tsx`.  Keep the `\u003ctitle\u003e` tag in `index.html` in sync to avoid a visible flash before React hydrates (a comment in that file reminds you).\n\n**Home page hero content** (title, subtitle, tagline, quick-links labels, spec table values) is fully\ntranslatable — edit the matching keys in `src/i18n/locales/\u003clang\u003e.json` (see §5 below).\n\n---\n\n### 2 — Favicon \u0026 app icon\n\nReplace `public/favicon.svg` with your own SVG (or PNG — update the `\u003clink\u003e` tag in `index.html` accordingly).\n\nThe default icon uses the same two brand colours as the theme (`#c0392b` accent on `#f4eed7` paper).  If you change the accent colour (§3), update the icon too.\n\n```svg\n\u003c!-- public/favicon.svg — minimal example --\u003e\n\u003csvg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 32 32\"\u003e\n  \u003crect width=\"32\" height=\"32\" rx=\"4\" fill=\"#f4eed7\"/\u003e\n  \u003c!-- replace with your mark; use THEME_ACCENT_COLOR (#c0392b by default) --\u003e\n\u003c/svg\u003e\n```\n\n---\n\n### 3 — Theme (colours \u0026 fonts)\n\nThe visual identity is defined in two places that **must stay in sync**:\n\n#### `src/config.ts` — JavaScript side\n\n```ts\n// Colours used in inline SVG illustrations (CSS variables are not\n// accessible inside SVG stroke/fill attributes).\nexport const THEME_ACCENT_COLOR = '#c0392b'  // primary / accent\nexport const THEME_PAPER_COLOR  = '#f4eed7'  // warm paper fill\n```\n\nThese values are read at runtime by `Home.tsx` (the decorative schematic drawing).\n\n#### `src/index.css` — CSS side\n\nThree places to update when changing the accent colour:\n\n```css\n@theme {\n  --color-tech-red:       #c0392b;  /* Tailwind utility: text-tech-red, border-tech-red … */\n  --color-tech-red-dark:  #922b21;\n  --color-tech-red-light: #e74c3c;\n}\n\n.light {\n  --accent:   #c0392b;  /* HeroUI semantic token */\n  --primary:  #c0392b;\n  /* primary-foreground should be legible on top of the accent colour */\n}\n```\n\n**To replace the paper palette** (cream tones), update the `--color-paper-*` variables in `@theme` and the `--background`, `--surface`, `--muted` tokens in `.light`.\n\n**To change fonts**, replace the Google Fonts `\u003clink\u003e` in `index.html` and update `--font-sans` / `--font-mono` in `@theme`.\n\n---\n\n### 4 — Languages\n\nThe language switcher and the i18n runtime are both driven by `src/config.ts`:\n\n```ts\nexport const LANGUAGES = [\n  { code: 'fr', label: 'FR' },\n  { code: 'en', label: 'EN' },\n  { code: 'zh', label: '中' },\n] as const\n\nexport const DEFAULT_LANGUAGE: Language = 'fr'\n```\n\nThe `Language` type is **automatically derived** from this array — adding an entry widens the type without any manual union edit.\n\n#### Adding a language\n\n```bash\n# 1. Append an entry to LANGUAGES in src/config.ts:\n#    { code: 'de', label: 'DE' }\n\n# 2. Create the locale file (copy English as a starting point):\ncp src/i18n/locales/en.json src/i18n/locales/de.json\n# … then translate the values\n\n# 3. Register the import in src/i18n/index.ts:\n#    import de from './locales/de.json'\n#    resources: { …, de: { translation: de } }\n```\n\n#### Removing a language\n\nRemove the entry from `LANGUAGES` and delete (or keep) the locale JSON — unused imports are tree-shaken by Vite.\n\n#### i18n key reference\n\nAll page content that can be customised lives in `src/i18n/locales/\u003clang\u003e.json`.\nKey sections:\n\n| Key prefix | Content |\n|---|---|\n| `hero.*` | Home page title, subtitle, tagline |\n| `home.*` | Brand label, quick-link labels, spec table, schematic labels |\n| `nav.*` | Navigation link labels |\n| `contact.*` | Company name, e-mail, address |\n| `auth.*` | Login modal text |\n| `ai.*` | AI settings page labels |\n\n---\n\n### 5 — AI proxy\n\n```ts\n// src/config.ts\nexport const AI_PROXY_BASE_URL            = 'https://ai-proxy.inet.pp.ua'\nexport const AI_PROXY_DEFAULT_PROVIDER_ID = 'groq'\nexport const AI_PROXY_DEFAULT_MODEL       = 'meta-llama/llama-4-scout-17b-16e-instruct'\n```\n\n`AI_PROXY_BASE_URL` can also be set at **build time** via the environment variable `AI_PROXY_URL` (takes precedence over the constant — useful for CI/CD without touching source files).\n\nThe provider preset list shown in the AI Settings page UI is defined in `src/services/aiSettings.ts` (`PROVIDER_PRESETS`).  Add or remove entries there to control which shortcuts appear.\n\nYou can deploy your own AI proxy by reusing the [sctg-development/ai-proxy-cloudflare](https://github.com/sctg-development/ai-proxy-cloudflare) project, or any OpenAI-compatible proxy that supports the `/models` endpoint for live model discovery.\n\n---\n\n### 6 — Storage prefix\n\n```ts\nexport const STORAGE_PREFIX = 'MY-APP'\n```\n\nThis single string is prepended to every browser-storage key:\n\n| Storage | Key pattern |\n|---|---|\n| `localStorage` | `MY-APP_ai_settings_v1` |\n| `IndexedDB` | `MY-APP-encrypted-cache` |\n| Monaco AI cache | `MY-APP_monaco_ai_v1` |\n\n\u003e **Warning:** changing `STORAGE_PREFIX` after a production deployment invalidates all users' cached data and AI settings in the browser.  Users will need to reconfigure their AI provider.\n\n---\n\n### 7 — Navigation \u0026 routes\n\nThe application route table is in [`src/App.tsx`](src/App.tsx).  Each route maps a URL to a page component.  To remove a section (e.g. Schematics):\n\n1. Delete the `\u003cRoute path=\"/schematics\" …\u003e` entry in `App.tsx`.\n2. Remove the matching nav link from the array in `AppNavbar.tsx` (lines 75–82).\n3. Remove the `nav.schematics` translation key if desired.\n\nTo add a page, reverse the steps: create the component, add a `\u003cRoute\u003e`, add a nav entry, and add the translation key.\n\n---\n\n## Encryption Model\n\n```\nSource file ──► rencrypt encrypt ──► encrypted/\u003csha256\u003e.age\n                     │\n                     └── AGE v1 X25519, encrypted for every public key in keys/public/\n\nManifest (files.json) ──► rencrypt encrypt ──► public/files.json.age\n```\n\n- Each file is encrypted once per recipient public key stored in `keys/public/`\n- The manifest maps virtual paths (e.g. `drawings/en/test.dxf`) to encrypted blob hashes\n- Revoke access by removing a public key and re-running `rencrypt encrypt`\n\n### Rencrypt CLI reference\n\n```bash\n# Generate N key pairs\nrencrypt generate-keys --keys=keys --count=5\n\n# Encrypt source trees\nrencrypt encrypt --path ./drawings --path ./doc [--keys keys] [--encrypted-dir encrypted]\n\n# Extract DXF strings to glossary CSV\nrencrypt extract --path drawings/en --glossary glossaire.csv\n\n# AI-translate glossary\nrencrypt translate --lang=en,cn --ai-json-enc \u003curl\u003e --ai-cryptoken \u003ctoken\u003e\n```\n\nSee [USAGE.md](USAGE.md) for the full workflow.\n\n---\n\n## Deployment\n\nThe project deploys to Cloudflare Pages out of the box. The GitHub Actions workflow in [.github/workflows/](.github/workflows/) injects `POSTHOG_PROJECT_TOKEN` and `POSTHOG_HOST` from repository secrets before running `wrangler pages deploy`.\n\nAny static host with custom headers support works — just ensure:\n\n1. `encrypted/` directory is served with `Cache-Control: no-store`\n2. SPA fallback routes all 404s to `index.html`\n\nSee [public/_headers](public/_headers) and [public/_redirects](public/_redirects).\n\n---\n\n## AI Features (Optional)\n\nThe Monaco viewer can translate, explain and annotate source code using an LLM via a configurable proxy. Set `AI_PROXY_URL` in `.env` (or hardcode the proxy URL in the source). The proxy must be compatible with the OpenAI chat completions API.\n\nA compatible proxy: [sctg-development/ai-proxy-cloudflare](https://github.com/sctg-development/ai-proxy-cloudflare)\n\nAuthentication uses the user's AGE private key as a bearer token — no additional credential required.\n\n### BYOK — Bring Your Own Key\n\nIf the built-in proxy returns **HTTP 403** (quota exceeded, key revoked, etc.), users can configure their own OpenAI-compatible provider directly in the browser via the **AI Settings** page (gear icon in the navbar):\n\n- **Provider presets**: OpenAI, Google Gemini, Groq, OpenRouter, Mistral AI, Together AI — or any custom URL.\n- **Live model discovery**: clicking \"Fetch Models\" queries the provider's `/models` endpoint with the entered API key and populates the model selector automatically.\n- **Proxy key override**: users can also provide a dedicated API key for the built-in proxy without switching to a custom provider.\n- All settings are stored in `localStorage` and read at request time — no page reload required.\n\nSettings are managed by `src/services/aiSettings.ts` and the page is at `/settings` (`src/pages/AiSettings.tsx`).\n\n---\n\n## Persistent Encrypted Cache\n\nTo reduce repeated network fetches, the app stores encrypted payloads in IndexedDB:\n\n- Backend: IndexedDB with `StorageManager` persistence request\n- Eviction: LRU\n- Default budget: 300 MiB\n- Cache metrics visible in the UI (entries, size, hits/misses, origin quota)\n\nOnly encrypted bytes are cached — decrypted content lives only in memory during the active session.\n\n---\n\n## Analytics\n\nEvery file opened emits a `File Viewed` event; every ZIP download emits a `Files Downloading` event. Both are sent to a Cloudflare Pages Function at `/api/file-viewed` which enriches them with IP and geo data before posting to PostHog.\n\nThe browser never sends the user's private key to the backend — it derives and sends only the public key.\n\n---\n\n## Technology Stack\n\n| Layer | Technology |\n|---|---|\n| Frontend framework | React 19 + TypeScript |\n| Build tool | Vite 8 |\n| UI components | HeroUI, Tailwind CSS 4 |\n| CAD rendering | `@mlightcad/cad-viewer`, `@sctg/tracespace-view` |\n| Code editor | Monaco Editor |\n| Encryption | `age-encryption` (AGE v1 X25519) |\n| Archiving | JSZip |\n| i18n | i18next + react-i18next |\n| Backend | Cloudflare Pages Functions |\n| Analytics | PostHog |\n| Encryption CLI | Rust (`rencrypt`) |\n\n---\n\n## Repository Layout\n\n```\ntechno-viewer/\n├── src/                  React application source\n│   ├── components/       UI components (viewers, layout…)\n│   ├── context/          Auth context\n│   ├── hooks/            useAgeDecrypt, useFileTree, …\n│   ├── i18n/             Translation resources (fr, en, zh)\n│   ├── pages/            Home, Drawings, Documentation, Contact\n│   └── services/         Cache, analytics\n├── public/               Static assets + files.json.age manifest\n├── encrypted/            AGE-encrypted file blobs (generated)\n├── drawings/             Source DXF drawings (plaintext, not committed in prod)\n├── schematics/           Source electronic schematics\n├── doc/                  Source documentation (PDF…)\n├── agro-crypt/           Sample C source (agro-crypt project)\n├── keys/\n│   ├── public/           AGE public keys (committed, one per user)\n│   └── private/          AGE private keys (NEVER commit to a public repo)\n├── rencrypt/             Rust CLI source\n├── functions/            Cloudflare Pages Functions\n└── tools/                Python helper scripts (DXF→PDF, AI translation…)\n```\n\n\u003e **Security note:** The `keys/private/` directory in this repository contains **test-only** keys generated for demonstration purposes. For a production deployment, generate fresh keys, **never commit private keys**, and distribute them to users through a secure channel.\n\n---\n\n## Contributing\n\nContributions are welcome. Please open an issue to discuss significant changes before submitting a pull request.\n\n---\n\n## License\n\nMIT — see [LICENSE.md](LICENSE.md).\n\nCopyright (c) 2024-2026 Ronan Le Meillat — SCTG Development\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsctg-development%2Ftechno-viewer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsctg-development%2Ftechno-viewer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsctg-development%2Ftechno-viewer/lists"}