new-doxa-monorepo

This commit is contained in:
kankys committed 2026-08-07 09:48:59 +02:00
1 parent b4401527a3
commit 5eacbb0182
162 files changed
+21820 -2

No files matched your search

+22
View File
@@ -0,0 +1,22 @@
# Node dependency directories
node_modules/
jspm_packages/
web-build/
# Go compiled binaries
doxa-secret
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binaries
*.test
# Local JSON config/database files
*.json
!web/*.json
# Local message outputs
*.txt
+18
View File
@@ -0,0 +1,18 @@
MIT License
Copyright (c) 2026 Kankys
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
associated documentation files (the "Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO
EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
USE OR OTHER DEALINGS IN THE SOFTWARE.
+147
View File
@@ -0,0 +1,147 @@
# 🦅 USER MANUAL (DOXA-SECRET)
**DOXA-SECRET** is a minimalist, local-first command-line text encryption and decryption tool. Designed with security and privacy in mind, it allows you to convert plain text messages into secret, encrypted strings using unique 5-character cipher codes stored locally as JSON files. Written in **Go**, it offers a modern Terminal User Interface (**TUI**) built with Charm's Bubble Tea and Lip Gloss.
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
---
## TABLE OF CONTENTS
1. [Installation & Execution](#1-installation--execution)
2. [Cipher Architecture & Schema](#2-cipher-architecture--schema)
3. [Interactive Interface & States](#3-interactive-interface--states)
4. [Keyboard Shortcuts Reference](#4-keyboard-shortcuts-reference)
5. [Cipher Distribution & Import/Export](#5-cipher-distribution--importexport)
6. [Uninstallation](#6-uninstallation)
---
## 1. INSTALLATION & EXECUTION
### Prerequisites
Before installing, make sure you have **Go** installed on your system (version **1.20** or higher is recommended).
### Installation Steps (Linux / macOS / FreeBSD)
1. **Clone the repository:**
```bash
git clone https://codeberg.org/Kankys/doxa-secret.git
cd doxa-secret
```
2. **Run the installation script:**
```bash
chmod +x install.sh
./install.sh
```
*The script checks your Go installation, downloads all dependencies, compiles the binary, installs the executable `doxa-secret` to `~/.local/bin/`, and prepares the config directory `~/.doxa-secret/`.*
3. **Add to PATH (if necessary):**
If the `doxa-secret` command is not recognized, add the following line to your shell profile (e.g., `~/.bashrc` or `~/.zshrc`):
```bash
export PATH=$PATH:$HOME/.local/bin
```
Then restart your terminal or reload your profile using `source ~/.bashrc`.
### Post-Installation Cleanup
Once completed, you can safely remove the cloned repository directory:
```bash
rm -rf doxa-secret
```
---
## 2. CIPHER ARCHITECTURE & SCHEMA
All ciphers are stored as JSON files inside the configuration directory `~/.doxa-secret/`.
When the application starts for the first time, a `default.json` cipher is automatically generated.
### Cipher JSON Structure
Each cipher maps basic Latin characters (excluding accents/diacritics, which are automatically normalized to basic Latin to avoid cross-system character encoding issues, and including spaces, commas, periods, numbers) to unique, randomly-generated **5-character alphanumeric codes**:
```json
{
"version": "1.0",
"mappings": {
" ": "k9#pA",
",": "L5z2M",
".": "q7R1o",
"a": "H3nK9",
"b": "8vX1m"
}
}
```
---
## 3. INTERACTIVE INTERFACE & STATES
The application supports multiple interactive states within the Terminal User Interface:
### A. Main Menu
Allows you to choose from three main options:
1. **Write Message**: Compose a plaintext message and save its encrypted output as a `.txt` file into the `~/doxa-secret/export/` directory.
2. **Decrypt Message**: Load an encrypted message from a `.txt` file (placed in the `~/doxa-secret/import/` directory) and read its decrypted contents.
3. **Manage Ciphers**: Create, switch, import, or export cipher keys.
### B. Write Screen
Includes a large message editor field and a filename input field. Switching between these fields is done with the `[Tab]` key. Pressing `[Ctrl+S]` encrypts the message and saves it to `~/doxa-secret/export/<filename>`.
### C. Decrypt Screen
Prompts you to enter the name of an encrypted `.txt` file (to be read from `~/doxa-secret/import/`) or an absolute path to a file. The application loads the file, decrypts it using the currently active cipher, and opens a viewport for reading.
---
## 4. KEYBOARD SHORTCUTS REFERENCE
| Key | Context | Action |
|-----|---------|--------|
| `ctrl+c` | Global | Force Quit |
| `l` | Main Menu | Toggle Language (Czech / English) |
| `1` | Main Menu | Open Write Message screen |
| `2` | Main Menu | Open Decrypt/Read Message screen |
| `3` | Main Menu | Open Cipher Management screen |
| `q` / `esc` | Main Menu | Quit application |
| `tab` | Write Screen | Toggle between message content and filename field |
| `ctrl+s` | Write Screen | Save encrypted message to file |
| `esc` | Editor / Forms | Cancel and return to previous menu |
| `up` / `down` / `k` / `j` | Ciphers List | Navigate through ciphers list |
| `enter` | Ciphers List | Activate selected cipher |
| `c` | Ciphers List | Create a new custom cipher |
| `i` | Ciphers List | Import a cipher file |
| `e` | Ciphers List | Export the active cipher file |
| `d` | Ciphers List | Delete the selected cipher |
---
## 5. CIPHER DISTRIBUTION & IMPORT/EXPORT
To share encrypted messages with another person, they must have the exact same cipher JSON file loaded in their `doxa-secret` configuration directory.
### Exporting a Cipher
1. In the **Cipher Management** screen, select the cipher you want to export and make sure it is active.
2. Press `e` to export.
3. Enter the destination path (e.g. `/path/to/my_cipher.json`).
4. Share this file with the recipient.
### Importing a Cipher
1. In the **Cipher Management** screen, press `i` to import.
2. Enter the path to the JSON file you received.
3. The cipher will be automatically validated, copied to your configuration folder, and made active.
---
## 6. UNINSTALLATION
To fully remove the tool and its configuration files from your system, execute:
```bash
rm ~/.local/bin/doxa-secret
rm -rf ~/.doxa-secret
```
*Caution: Deleting `~/.doxa-secret` will permanently erase all your cipher keys.*
+147
View File
@@ -0,0 +1,147 @@
# 🦅 UŽIVATELSKÝ MANUÁL (DOXA-SECRET)
**DOXA-SECRET** je minimalistický, lokální nástroj příkazové řádky pro šifrování a dešifrování textu. Je navržen s ohledem na bezpečnost a soukromí, a umožňuje převádět textové zprávy do tajných šifer definovaných v JSON souborech. Každý znak má přiřazen unikátní 5znakový kód. Aplikace je napsána v jazyce **Go** a nabízí moderní terminálové uživatelské rozhraní (**TUI**) postavené na knihovně Bubble Tea a Lip Gloss.
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
---
## OBSAH
1. [Instalace a spuštění](#1-instalace-a-spuštění)
2. [Struktura šifer a JSON abecedy](#2-struktura-šifer-a-json-abecedy)
3. [Uživatelské rozhraní a stavy](#3-uživatelské-rozhraní-a-stavy)
4. [Přehled klávesových zkratek](#4-přehled-klávesových-zkratek)
5. [Distribuce šifer (import a export)](#5-distribuce-šifer-import-a-export)
6. [Odinstalace](#6-odinstalace)
---
## 1. INSTALACE A SPUŠTĚNÍ
### Požadavky
Před instalací se ujistěte, že máte v systému nainstalované **Go** (doporučená verze **1.20** nebo vyšší).
### Postup instalace (Linux / macOS / FreeBSD)
1. **Naklonujte repozitář:**
```bash
git clone https://codeberg.org/Kankys/doxa-secret.git
cd doxa-secret
```
2. **Spusťte instalační skript:**
```bash
chmod +x install.sh
./install.sh
```
*Skript zkontroluje instalaci Go, stáhne závislosti, zkompiluje binárku, nainstaluje spustitelný soubor `doxa-secret` do `~/.local/bin/` a vytvoří konfigurační složku `~/.doxa-secret/`.*
3. **Přidání do PATH (pokud je to nutné):**
Pokud terminál příkaz `doxa-secret` nerozpozná, přidejte následující řádek do svého profilu (např. `~/.bashrc` nebo `~/.zshrc`):
```bash
export PATH=$PATH:$HOME/.local/bin
```
Poté restartujte terminál nebo načtěte profil příkazem `source ~/.bashrc`.
### Úklid po instalaci
Jakmile je instalace dokončena, můžete složku projektu bezpečně smazat:
```bash
rm -rf doxa-secret
```
---
## 2. STRUKTURA ŠIFER A JSON ABECEDY
Všechny šifry jsou uloženy jako `.json` soubory v konfiguračním adresáři `~/.doxa-secret/`.
Při prvním spuštění aplikace se automaticky vygeneruje výchozí šifra `default.json`.
### Ukázka struktury JSON
Každá šifra přiřazuje základním znakům latinky (bez diakritiky, která je při šifrování automaticky odstraněna a převedena na základní latinku z důvodu předcházení problémům s kódováním na různých platformách, včetně mezer, čárek, teček, číslic) unikátní, náhodně generovaný **5znakový kód**:
```json
{
"version": "1.0",
"mappings": {
" ": "k9#pA",
",": "L5z2M",
".": "q7R1o",
"a": "H3nK9",
"b": "8vX1m"
}
}
```
---
## 3. UŽIVATELSKÉ ROZHRANÍ A STAVY
Aplikace podporuje několik interaktivních stavů v rámci terminálového rozhraní:
### A. Hlavní menu
Umožňuje vybrat ze tří hlavních možností:
1. **Napsat zprávu**: Napíšete zprávu v běžném textu a uložíte její šifrovanou verzi do souboru `.txt` do složky `~/doxa-secret/export/`.
2. **Dešifrovat zprávu**: Načtete šifrovaný text z `.txt` souboru (umístěného ve složce `~/doxa-secret/import/`) a zobrazíte jej v čitelné podobě.
3. **Správa šifer**: Vytváření, přepínání, import a export šifrovacích klíčů.
### B. Psaní zprávy
Obsahuje pole pro zadání textu a pole pro název souboru. Přepínání mezi poli se provádí pomocí klávesy `[Tab]`. Stisknutím `[Ctrl+S]` zprávu zašifrujete a uložíte do `~/doxa-secret/export/<název_souboru>`.
### C. Dešifrování zprávy
Požádá vás o zadání názvu šifrovaného souboru `.txt` (který bude načten ze složky `~/doxa-secret/import/`) nebo absolutní cesty k souboru. Aplikace jej načte, dešifruje pomocí aktuálně aktivní šifry a zobrazí v posuvném prohlížeči.
---
## 4. PŘEHLED KLÁVESOVÝCH ZKRATEK
| Klávesa | Kontext | Akce |
|---------|---------|------|
| `ctrl+c` | Globální | Vynucené ukončení programu |
| `l` | Hlavní menu | Přepnout jazyk (Čeština / Angličtina) |
| `1` | Hlavní menu | Otevřít obrazovku pro psaní zprávy |
| `2` | Hlavní menu | Otevřít obrazovku pro dešifrování zprávy |
| `3` | Hlavní menu | Otevřít správu šifer |
| `q` / `esc` | Hlavní menu | Ukončit program |
| `tab` | Psaní zprávy | Přepnout mezi tělem zprávy a názvem souboru |
| `ctrl+s` | Psaní zprávy | Uložit a zašifrovat zprávu do souboru |
| `esc` | Formuláře | Zrušit akci a vrátit se do předchozího menu |
| `↑` / `↓` / `k` / `j` | Seznam šifer | Pohyb v seznamu šifer |
| `enter` | Seznam šifer | Aktivovat vybranou šifru |
| `c` | Seznam šifer | Vytvořit novou vlastní šifru |
| `i` | Seznam šifer | Importovat soubor šifry |
| `e` | Seznam šifer | Exportovat aktivní šifru |
| `d` | Seznam šifer | Smazat vybranou šifru |
---
## 5. DISTRIBUCE ŠIFER (IMPORT A EXPORT)
Chcete-li sdílet šifrované zprávy s jinou osobou, musí mít druhá strana v aplikaci aktivovanou přesně stejnou šifru.
### Export šifry
1. Ve **Správě šifer** vyberte šifru, kterou chcete exportovat a aktivujte ji.
2. Stiskněte klávesu `e` pro export.
3. Zadejte cílovou cestu (např. `/cesta/k/moje_sifra.json`).
4. Tento soubor pošlete druhé straně.
### Import šifry
1. Ve **Správě šifer** stiskněte klávesu `i` pro import.
2. Zadejte cestu k obdrženému souboru JSON.
3. Šifra bude zvalidována, zkopírována do konfigurační složky a automaticky nastavena jako aktivní.
---
## 6. ODINSTALACE
Pro kompletní odstranění aplikace a jejích konfigurací zadejte:
```bash
rm ~/.local/bin/doxa-secret
rm -rf ~/.doxa-secret
```
*Upozornění: Smazání adresáře `~/.doxa-secret` trvale vymaže všechny vaše šifrovací klíče.*
+94
View File
@@ -0,0 +1,94 @@
# **DOXA secret**
**DOXA secret** je minimalistický, lokální nástroj pro šifrování a ochranu vašich tajných zpráv z příkazové řádky. Umožňuje převádět zprávy do vlastní abecedy definované v JSON formátu. Každý znak zprávy je převeden na unikátní 5znakový kód, čímž je zajištěno soukromí.
Aplikace je napsána v jazyce Go a nabízí přehledné terminálové uživatelské rozhraní (TUI) postavené na knihovně Bubble Tea.
> [!IMPORTANT]
> **Kompatibilita a diakritika:** Aplikace z důvodu stoprocentní kompatibility mezi různými platformami a kódováními pracuje pouze se znaky základní latinky (bez diakritiky). Veškerá česká diakritika (háčky, čárky) je při šifrování automaticky normalizována (např. „ž“ se změní na „z“, „á“ na „a“). Mezery, čárky, tečky a nové řádky jsou plnohodnotně šifrovány a po dešifrování se zobrazí v původní podobě.
---
![homePage](screen/homePage.png)
## **Předpoklady**
Před instalací se ujistěte, že máte na svém systému nainstalovaný **Go** (doporučená verze 1.20 nebo vyšší). Stáhnout Go můžete z oficiálního webu: [https://go.dev/doc/install](https://go.dev/doc/install)
---
## **Instalace**
Pro instalaci DOXA secret na váš systém postupujte podle následujících kroků:
```bash
git clone https://codeberg.org/Kankys/doxa-secret.git
cd doxa-secret
chmod +x install.sh
./install.sh
```
Instalační skript provede následující:
* Zkontroluje přítomnost Go v systému.
* Stáhne potřebné závislosti (Bubble Tea, Lip Gloss atd.).
* Zkompiluje aplikaci.
* Přesune spustitelný soubor `doxa-secret` do adresáře `$HOME/.local/bin/`.
* Vytvoří konfigurační složku `~/.doxa-secret/` a výchozí abecedu `default.json`.
**Přidání do PATH (pokud je nutné):**
Pokud příkaz není po instalaci rozpoznán, přidejte následující řádek do svého profilu shellu (např. `~/.bashrc`, `~/.zshrc`):
```bash
export PATH=$PATH:$HOME/.local/bin
```
Poté restartujte terminál nebo načtěte profil příkazem `source ~/.bashrc`.
### **Úklid po instalaci**
Jakmile je instalace úspěšně dokončena, můžete složku s repozitářem smazat:
```bash
rm -rf doxa-secret
```
---
## **Organizace zpráv (Import & Export)**
Pro lepší přehlednost a organizaci pracuje aplikace se dvěma hlavními složkami ve vašem domovském adresáři:
* **`~/doxa-secret/export/`** – Sem se automaticky ukládají vaše nově napsané a zašifrované zprávy (`.txt`).
* **`~/doxa-secret/import/`** – Sem vkládejte šifrované zprávy, které vám přišly od jiných uživatelů a které chcete v aplikaci dešifrovat a přečíst.
---
## **Konfigurace abecedy**
Pravidla šifrování jsou definována v JSON souborech uvnitř konfiguračního adresáře `~/.doxa-secret/`. Můžete si vytvořit více pojmenovaných šifer a v aplikaci se mezi nimi přepínat.
Ukázka struktury abecedy (všechny kódy mají délku přesně 5 znaků):
```json
{
"version": "1.0",
"mappings": {
" ": "k9#pA",
",": "L5z2M",
".": "q7R1o",
"a": "H3nK9",
"b": "8vX1m"
}
}
```
---
## **Použití**
Po instalaci stačí spustit z jakéhokoliv adresáře příkazem:
```bash
doxa-secret
```
Klávesou `l` v hlavním menu můžete přepínat jazyk rozhraní mezi češtinou a angličtinou. Pro podrobné informace o ovládání a klávesových zkratkách doporučujeme nahlédnout do [Uživatelského manuálu (MANUAL_CZ.md)](file:///home/kankys/Repos/Codeberg/doxa-secret/MANUAL_CZ.md).
---
## **Licence**
Aplikace je open-source a licencována pod MIT licencí.
> *"Skrýt tajemství za fasádou běžného zdání je prvním krokem k ochraně vnitřního klidu."*
+92
View File
@@ -0,0 +1,92 @@
# **DOXA secret**
**DOXA secret** is a minimalist, local-first command-line tool for encrypting and protecting your secret messages. It allows you to convert messages into a custom alphabet defined in JSON format. Each character in the message is converted to a unique 5-character code, ensuring your privacy.
The application is written in Go and offers an intuitive Terminal User Interface (TUI) powered by Charm's Bubble Tea.
> [!IMPORTANT]
> **Compatibility and Diacritics:** To ensure 100% compatibility across different platforms and encodings, the application only operates on basic Latin characters (without diacritics). All Czech accents (e.g. carons and acute accents) are automatically normalized during encryption (e.g. "ž" becomes "z", "á" becomes "a"). Spaces, commas, periods, and newlines are fully encrypted and will be perfectly visible in their original form after decryption.
---
## **Prerequisites**
Before installing, make sure you have **Go** installed on your system (version 1.20 or higher is recommended). You can download Go from the official website: [https://go.dev/doc/install](https://go.dev/doc/install)
---
## **Installation**
To install DOXA secret on your system, follow these steps:
```bash
git clone https://codeberg.org/Kankys/doxa-secret.git
cd doxa-secret
chmod +x install.sh
./install.sh
```
The installation script will:
* Check for Go presence on your system.
* Download necessary dependencies (Bubble Tea, Lip Gloss, etc.).
* Compile the application.
* Move the executable `doxa-secret` to `$HOME/.local/bin/`.
* Create the configuration directory `~/.doxa-secret/` and a default cipher `default.json`.
**Adding to PATH (if necessary):**
If the command is not recognized after installation, add the following line to your shell profile (e.g., `~/.bashrc`, `~/.zshrc`):
```bash
export PATH=$PATH:$HOME/.local/bin
```
Then restart your terminal or reload the profile using `source ~/.bashrc`.
### **Post-Installation Cleanup**
Once the installation is successfully completed, you can safely remove the cloned repository directory:
```bash
rm -rf doxa-secret
```
---
## **Message Organization (Import & Export)**
For better organization, the application interacts with two main folders in your home directory:
* **`~/doxa-secret/export/`** – This is where your newly written and encrypted messages (`.txt`) are automatically saved.
* **`~/doxa-secret/import/`** – Place the encrypted messages received from other users here if you want to decrypt and read them in the application.
---
## **Cipher Configuration**
Encryption rules are defined in JSON files inside the configuration directory `~/.doxa-secret/`. You can create multiple named ciphers and switch between them inside the TUI.
Example alphabet structure (all codes must be exactly 5 characters long):
```json
{
"version": "1.0",
"mappings": {
" ": "k9#pA",
",": "L5z2M",
".": "q7R1o",
"a": "H3nK9",
"b": "8vX1m"
}
}
```
---
## **Usage**
After installation, simply run it from any directory with:
```bash
doxa-secret
```
Press the `l` key in the main menu to toggle the interface language between Czech and English. For detailed information on usage and keyboard shortcuts, please refer to the [User Manual (MANUAL.md)](file:///home/kankys/Repos/Codeberg/doxa-secret/MANUAL.md).
---
## **License**
This application is open-source and licensed under the MIT License.
> *"Hiding secrets behind the facade of common appearance is the first step to protecting inner peace."*
+317
View File
@@ -0,0 +1,317 @@
package main
import (
"crypto/rand"
"encoding/json"
"errors"
"fmt"
"math/big"
"os"
"path/filepath"
)
type CipherConfig struct {
Version string `json:"version"`
Mappings map[string]string `json:"mappings"`
}
const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
// getCipherDir returns the config directory ~/.doxa-secret
func getCipherDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
configDir := filepath.Join(home, ".doxa-secret")
if _, err := os.Stat(configDir); os.IsNotExist(err) {
err = os.MkdirAll(configDir, 0755)
if err != nil {
return "", err
}
}
return configDir, nil
}
// getExportDir returns the export directory ~/doxa-secret/export
func getExportDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
dir := filepath.Join(home, "doxa-secret", "export")
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0755)
if err != nil {
return "", err
}
}
return dir, nil
}
// getImportDir returns the import directory ~/doxa-secret/import
func getImportDir() (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", err
}
dir := filepath.Join(home, "doxa-secret", "import")
if _, err := os.Stat(dir); os.IsNotExist(err) {
err = os.MkdirAll(dir, 0755)
if err != nil {
return "", err
}
}
return dir, nil
}
// generateRandomCode generates a unique 5-character alphanumeric string
func generateRandomCode(existing map[string]bool) (string, error) {
for {
code := make([]byte, 5)
for i := 0; i < 5; i++ {
n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset))))
if err != nil {
return "", err
}
code[i] = charset[n.Int64()]
}
codeStr := string(code)
if !existing[codeStr] {
existing[codeStr] = true
return codeStr, nil
}
}
}
// GenerateNewCipher creates a new cipher configuration with 5-character mappings
func GenerateNewCipher(name string) error {
dir, err := getCipherDir()
if err != nil {
return err
}
destPath := filepath.Join(dir, name+".json")
if _, err := os.Stat(destPath); err == nil {
return fmt.Errorf("šifra s názvem '%s' již existuje", name)
}
// Basic characters to encrypt
charsToMap := []string{
// Space, comma, dot
" ", ",", ".",
// Digits
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
// Lowercase letters
"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v", "w", "x", "y", "z",
// Uppercase letters
"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z",
// Punctuation and special characters
"!", "?", "-", "+", "=", "*", "/", "\\", "_", "@", "#", "$", "%", "^", "&", "(", ")", "[", "]", "{", "}", ":", ";", "\"", "'", "<", ">", "|", "~", "`",
// Control chars (newlines, tabs)
"\n", "\r", "\t",
}
mappings := make(map[string]string)
usedCodes := make(map[string]bool)
for _, ch := range charsToMap {
code, err := generateRandomCode(usedCodes)
if err != nil {
return err
}
mappings[ch] = code
}
config := CipherConfig{
Version: "1.0",
Mappings: mappings,
}
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return err
}
return os.WriteFile(destPath, data, 0644)
}
// LoadCipher loads a cipher by its file name (without path) from the configuration directory
func LoadCipher(name string) (*CipherConfig, error) {
dir, err := getCipherDir()
if err != nil {
return nil, err
}
path := filepath.Join(dir, name+".json")
return LoadCipherFromPath(path)
}
// LoadCipherFromPath loads a cipher from an absolute path
func LoadCipherFromPath(path string) (*CipherConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, err
}
var config CipherConfig
if err := json.Unmarshal(data, &config); err != nil {
return nil, err
}
// Validate cipher: ensure each code has length 5 and is unique
if config.Mappings == nil {
return nil, errors.New("abeceda neobsahuje žádné mapování")
}
used := make(map[string]bool)
for char, code := range config.Mappings {
if len(code) != 5 {
return nil, fmt.Errorf("neplatná délka šifry pro znak '%s': očekáváno 5 znaků, nalezeno %d", char, len(code))
}
if used[code] {
return nil, fmt.Errorf("duplicitní šifra '%s' v mapování", code)
}
used[code] = true
}
return &config, nil
}
// ListCiphers returns a list of all cipher names (without .json extension) in the configuration directory
func ListCiphers() ([]string, error) {
dir, err := getCipherDir()
if err != nil {
return nil, err
}
files, err := os.ReadDir(dir)
if err != nil {
return nil, err
}
var names []string
for _, f := range files {
if !f.IsDir() && filepath.Ext(f.Name()) == ".json" {
name := f.Name()
names = append(names, name[:len(name)-len(".json")])
}
}
return names, nil
}
// RemoveDiacritics converts Czech/accented runes to their basic Latin counterparts
func RemoveDiacritics(text string) string {
replacements := map[rune]rune{
'á': 'a', 'č': 'c', 'ď': 'd', 'é': 'e', 'ě': 'e', 'í': 'i', 'ň': 'n', 'ó': 'o', 'ř': 'r', 'š': 's', 'ť': 't', 'ú': 'u', 'ů': 'u', 'ý': 'y', 'ž': 'z',
'Á': 'A', 'Č': 'C', 'Ď': 'D', 'É': 'E', 'Ě': 'E', 'Í': 'I', 'Ň': 'N', 'Ó': 'O', 'Ř': 'R', 'Š': 'S', 'Ť': 'T', 'Ú': 'U', 'Ů': 'U', 'Ý': 'Y', 'Ž': 'Z',
}
runes := []rune(text)
for i, r := range runes {
if repl, ok := replacements[r]; ok {
runes[i] = repl
}
}
return string(runes)
}
// EncryptMessage encrypts plain text into secret code
func EncryptMessage(plainText string, config *CipherConfig) string {
// Normalize text to Latin alphabet without diacritics
plainText = RemoveDiacritics(plainText)
var encrypted string
// Iterate over the string as runes to correctly handle UTF-8
for _, r := range plainText {
charStr := string(r)
if code, ok := config.Mappings[charStr]; ok {
encrypted += code
} else {
// If not mapped, keep the character as is
encrypted += charStr
}
}
return encrypted
}
// DecryptMessage decrypts secret code back into plain text
func DecryptMessage(encryptedText string, config *CipherConfig) string {
// Create reverse mapping
reverse := make(map[string]string)
for k, v := range config.Mappings {
reverse[v] = k
}
var decrypted string
runes := []rune(encryptedText)
n := len(runes)
for i := 0; i < n; {
// Check if we can extract a 5-character block
if i+5 <= n {
block := string(runes[i : i+5])
if char, ok := reverse[block]; ok {
decrypted += char
i += 5
continue
}
}
// Fallback: if it's not a valid 5-char code block, or not found, consume 1 rune
decrypted += string(runes[i])
i++
}
return decrypted
}
// ImportCipher copies a cipher JSON file from any location to ~/.doxa-secret/
func ImportCipher(srcPath string) (string, error) {
config, err := LoadCipherFromPath(srcPath)
if err != nil {
return "", fmt.Errorf("chyba při načtení/validaci šifry: %v", err)
}
dir, err := getCipherDir()
if err != nil {
return "", err
}
baseName := filepath.Base(srcPath)
nameWithoutExt := baseName[:len(baseName)-len(filepath.Ext(baseName))]
destPath := filepath.Join(dir, baseName)
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
return "", err
}
err = os.WriteFile(destPath, data, 0644)
if err != nil {
return "", err
}
return nameWithoutExt, nil
}
// ExportCipher copies the active cipher JSON to a target path
func ExportCipher(name string, destPath string) error {
dir, err := getCipherDir()
if err != nil {
return err
}
srcPath := filepath.Join(dir, name+".json")
data, err := os.ReadFile(srcPath)
if err != nil {
return err
}
// Ensure destination directory exists
destDir := filepath.Dir(destPath)
if _, err := os.Stat(destDir); os.IsNotExist(err) {
err = os.MkdirAll(destDir, 0755)
if err != nil {
return err
}
}
return os.WriteFile(destPath, data, 0644)
}
+91
View File
@@ -0,0 +1,91 @@
# **DOXA secreet**
**DOXA secret** je minimalistický nástroj pro šifrování a ochranu vašich tajných myšlenek z příkazové řádky. Umožňuje převádět zprávy do vlastní abecedy definované v JSON formátu, čímž zajišťuje soukromí bez nutnosti spoléhat se na složité a neznámé systémy. Aplikace je napsána v jazyce Go a nabízí přehledné terminálové uživatelské rozhraní (TUI) postavené na knihovně Bubbletea.
## **Předpoklady**
Před instalací **DOXA secret** se ujistěte, že máte na svém systému nainstalovaný **Go**. Doporučená verze je 1.20 nebo vyšší. Stáhnout Go můžete z oficiálního webu: [https://go.dev/doc/install](https://go.dev/doc/install)
## **Instalace**
Pro instalaci DOXA secreet na váš systém postupujte podle následujících kroků:
> 1. **Naklonujte repozitář:** Získejte nejnovější verzi kódu z Codebergu:
> Bash
> git clone https://codeberg.org/Kankys/doxa-secret.git
> cd doxa-secret \# Přejděte do adresáře projektu
> 2. **Spusťte instalační skript:**
> Bash
> chmod \+x install.sh
> ./install.sh
Instalační skript provede následující:
* Zkontroluje, zda máte nainstalované Go.
* Stáhne všechny potřebné Go moduly a závislosti projektu (včetně Bubbletea).
* Zkompiluje aplikaci pro váš operační systém.
* Nainstaluje spustitelný soubor doxa-secret do adresáře $HOME/.local/bin/.
* Vytvoří konfigurační adresář ~/.doxa-secret a výchozí soubor s abecedou, pokud neexistují.
**Přidání do PATH (pokud je nutné):**
Po úspěšné instalaci byste měli být schopni spustit aplikaci odkudkoliv. Pokud příkaz není rozpoznán, přidejte následující řádek do svého shell profilu (např. ~/.bashrc, ~/.zshrc):
Bash
export PATH=$PATH:$HOME/.local/bin
Po úpravě souboru profilu restartujte terminál nebo jej načtěte příkazem source ~/.bashrc.
## **Úklid po instalaci**
Jakmile je instalace úspěšně dokončena, zdrojové soubory v adresáři projektu již nejsou potřeba. Celou složku s klonovaným repozitářem můžete smazat:
Bash
rm \-rf doxa-secret
## **Konfigurace abecedy**
Síla DOXA secret spočívá v tom, že si sami definujete vlastní pravidla a šifrovací klíč v souboru alphabet.json uvnitř konfiguračního adresáře ~/.doxa-secreet/. Aplikace se postará o překlad za vás, takže si složité kombinace nemusíte pamatovat.
Ukázka struktury abecedy:
JSON
{
"version": "1.0",
"mappings": {
"a": "x9\#m",
"b": "k2$p",
"ahoj": "z8\*9"
}
}
## **Použití**
Po úspěšné instalaci můžete aplikaci spustit z jakéhokoliv adresáře zadáním:
Bash
doxa-secret
Pro detailní ovládání v TUI rozhraní se řiďte nápovědou zobrazovanou přímo v aplikaci.
## **Záloha**
Pro zachování vašich šifrovacích klíčů a dat je nutné si uložit obsah konfiguračního adresáře ~/.doxa-secreet (především soubory s definicí abecedy) kamkoliv do vašeho záložního úložiště.
## **Odinstalace**
Pro odinstalaci DOXA secreet postupujte takto:
> 1. **Odstraňte spustitelný soubor:**
> Bash
> rm ~/.local/bin/doxa-secreet
> 2. **Odstraňte konfigurační adresář (volitelné):**
> Bash
> rm -rf ~/.doxa-secreet
> *UPOZORNĚNÍ: Tento krok smaže vaše šifrovací klíče a nastavení.*
## **Licence**
Aplikace je open-source a licencována pod MIT licencí.
> *"Skrýt tajemství za fasádou běžného zdání je prvním krokem k ochraně vnitřního klidu."*
+33
View File
@@ -0,0 +1,33 @@
module doxa-secret
go 1.26.5
require (
github.com/charmbracelet/bubbles v1.0.0
github.com/charmbracelet/bubbletea v1.3.10
github.com/charmbracelet/lipgloss v1.1.0
)
require (
github.com/atotto/clipboard v0.1.4 // indirect
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/charmbracelet/colorprofile v0.4.1 // indirect
github.com/charmbracelet/x/ansi v0.11.6 // indirect
github.com/charmbracelet/x/cellbuf v0.0.15 // indirect
github.com/charmbracelet/x/term v0.2.2 // indirect
github.com/clipperhouse/displaywidth v0.9.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
github.com/muesli/termenv v0.16.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/sys v0.38.0 // indirect
golang.org/x/text v0.3.8 // indirect
)
+56
View File
@@ -0,0 +1,56 @@
github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ=
github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE=
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/aymanbagabas/go-udiff v0.3.1 h1:LV+qyBQ2pqe0u42ZsUEtPiCaUoqgA9gYRDs3vj1nolY=
github.com/aymanbagabas/go-udiff v0.3.1/go.mod h1:G0fsKmG+P6ylD0r6N/KgQD/nWzgfnl8ZBcNLgcbrw8E=
github.com/charmbracelet/bubbles v1.0.0 h1:12J8/ak/uCZEMQ6KU7pcfwceyjLlWsDLAxB5fXonfvc=
github.com/charmbracelet/bubbles v1.0.0/go.mod h1:9d/Zd5GdnauMI5ivUIVisuEm3ave1XwXtD1ckyV6r3E=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.4.1 h1:a1lO03qTrSIRaK8c3JRxJDZOvhvIeSco3ej+ngLk1kk=
github.com/charmbracelet/colorprofile v0.4.1/go.mod h1:U1d9Dljmdf9DLegaJ0nGZNJvoXAhayhmidOdcBwAvKk=
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
github.com/charmbracelet/x/ansi v0.11.6 h1:GhV21SiDz/45W9AnV2R61xZMRri5NlLnl6CVF7ihZW8=
github.com/charmbracelet/x/ansi v0.11.6/go.mod h1:2JNYLgQUsyqaiLovhU2Rv/pb8r6ydXKS3NIttu3VGZQ=
github.com/charmbracelet/x/cellbuf v0.0.15 h1:ur3pZy0o6z/R7EylET877CBxaiE1Sp1GMxoFPAIztPI=
github.com/charmbracelet/x/cellbuf v0.0.15/go.mod h1:J1YVbR7MUuEGIFPCaaZ96KDl5NoS0DAWkskup+mOY+Q=
github.com/charmbracelet/x/term v0.2.2 h1:xVRT/S2ZcKdhhOuSP4t5cLi5o+JxklsoEObBSgfgZRk=
github.com/charmbracelet/x/term v0.2.2/go.mod h1:kF8CY5RddLWrsgVwpw4kAa6TESp6EB5y3uxGLeCqzAI=
github.com/clipperhouse/displaywidth v0.9.0 h1:Qb4KOhYwRiN3viMv1v/3cTBlz3AcAZX3+y9OLhMtAtA=
github.com/clipperhouse/displaywidth v0.9.0/go.mod h1:aCAAqTlh4GIVkhQnJpbL0T/WfcrJXHcj8C0yjYcjOZA=
github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs=
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d h1:jtJma62tbqLibJ5sFQz8bKtEM8rJBtfilJ2qTU199MI=
golang.org/x/exp v0.0.0-20231006140011-7918f672742d/go.mod h1:ldy0pHrwJyGW56pPQzzkH36rKxoZW1tw7ZJpeKx+hdo=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
+83
View File
@@ -0,0 +1,83 @@
#!/bin/bash
# DOXA-SECRET Installation Script
# --- Settings ---
APP_NAME="doxa-secret"
INSTALL_DIR="$HOME/.local/bin"
CONFIG_DIR="$HOME/.doxa-secret"
# --- Functions ---
error_exit() {
echo -e "\033[1;31mERROR: $1\033[0m" >&2
exit 1
}
success_message() {
echo -e "\033[1;32mSUCCESS: $1\033[0m"
}
info_message() {
echo -e "\033[1;34mINFO: $1\033[0m"
}
# --- Main Script ---
info_message "Checking Go installation..."
if ! command -v go &> /dev/null
then
error_exit "Go is not installed. Please install Go before running this script."
fi
success_message "Go is installed."
info_message "Downloading project dependencies..."
if ! go mod download
then
error_exit "Failed to download Go dependencies. Check your connection."
fi
success_message "Project dependencies downloaded."
info_message "Compiling application..."
APP_BINARY="$APP_NAME"
if ! go build -o "$APP_BINARY" .
then
error_exit "Failed to compile application."
fi
success_message "Application compiled successfully to './$APP_BINARY'."
info_message "Installing application..."
if ! mkdir -p "$INSTALL_DIR"
then
error_exit "Failed to create installation directory '$INSTALL_DIR'."
fi
if ! cp "$APP_BINARY" "$INSTALL_DIR/"
then
error_exit "Failed to copy binary to '$INSTALL_DIR'."
fi
chmod +x "$INSTALL_DIR/$APP_NAME"
success_message "Application '$APP_NAME' installed to '$INSTALL_DIR'."
info_message "Setting up configuration directory..."
if ! mkdir -p "$CONFIG_DIR"
then
error_exit "Failed to create config directory '$CONFIG_DIR'."
fi
success_message "Configuration directory '$CONFIG_DIR' ready."
info_message "Installation finished."
success_message "You can now run '$APP_NAME' from anywhere in your terminal."
echo ""
echo -e "\033[1;33mPRO TIP:\nIf \`$APP_NAME\` is not recognized, make sure '$INSTALL_DIR' is in your PATH.\nYou can add it by appending the following line to your shell profile (~/.bashrc, ~/.zshrc, or ~/.profile):\n\nexport PATH=\$PATH:$INSTALL_DIR\n\nThen restart your terminal or reload the profile (e.g. \`source ~/.bashrc\`).\033[0m"
echo ""
# Clean up local binary
rm "$APP_BINARY"
exit 0
+18
View File
@@ -0,0 +1,18 @@
package main
import (
"fmt"
"os"
tea "github.com/charmbracelet/bubbletea"
)
func main() {
m := NewModel()
p := tea.NewProgram(m)
if _, err := p.Run(); err != nil {
fmt.Printf("Chyba při spuštění programu: %v\n", err)
os.Exit(1)
}
}
+91
View File
@@ -0,0 +1,91 @@
package main
import (
"os"
"testing"
)
func TestCipherGenerationAndSymmetry(t *testing.T) {
// Create a temp directory for tests
tempDir, err := os.MkdirTemp("", "doxa_secret_test")
if err != nil {
t.Fatalf("Failed to create temp dir: %v", err)
}
defer os.RemoveAll(tempDir)
// Create a cipher mappings map manually or call a test helper
charsToMap := []string{"a", "b", "c", " ", ",", ".", "z", "\n"}
mappings := make(map[string]string)
used := make(map[string]bool)
for _, ch := range charsToMap {
code, err := generateRandomCode(used)
if err != nil {
t.Fatalf("Failed to generate random code: %v", err)
}
mappings[ch] = code
}
config := &CipherConfig{
Version: "1.0",
Mappings: mappings,
}
// Verify all code lengths
for ch, code := range mappings {
if len(code) != 5 {
t.Errorf("Code for '%s' is not 5 characters long: %s (len: %d)", ch, code, len(code))
}
}
// Test encryption & decryption symmetry
originalText := "a b, c. z\n"
encrypted := EncryptMessage(originalText, config)
// Since length of originalText is 10, and each character is mapped to 5 chars (except if unmapped, but all are mapped),
// the encrypted string should be exactly 10 * 5 = 50 characters long.
expectedLen := 10 * 5
if len([]rune(encrypted)) != expectedLen {
t.Errorf("Expected encrypted length %d, got %d", expectedLen, len([]rune(encrypted)))
}
decrypted := DecryptMessage(encrypted, config)
if decrypted != originalText {
t.Errorf("Symmetry test failed!\nOriginal: %q\nDecrypted: %q", originalText, decrypted)
}
}
func TestUnmappedCharacters(t *testing.T) {
mappings := map[string]string{
"a": "12345",
"b": "67890",
}
config := &CipherConfig{
Version: "1.0",
Mappings: mappings,
}
// 'c' is not mapped
original := "abc"
encrypted := EncryptMessage(original, config)
// 'a' -> "12345", 'b' -> "67890", 'c' -> "c"
expectedEncrypted := "1234567890c"
if encrypted != expectedEncrypted {
t.Errorf("Expected encrypted %q, got %q", expectedEncrypted, encrypted)
}
decrypted := DecryptMessage(encrypted, config)
if decrypted != original {
t.Errorf("Expected decrypted %q, got %q", original, decrypted)
}
}
func TestRemoveDiacritics(t *testing.T) {
input := "Příliš žluťoučký kůň úpěl ďábelské ódy."
expected := "Prilis zlutoucky kun upel dabelske ody."
output := RemoveDiacritics(input)
if output != expected {
t.Errorf("Expected %q, got %q", expected, output)
}
}
+22
View File
@@ -0,0 +1,22 @@
![homePage](screen/homePage.png)
## 🇬🇧
![menu](screen/menu.png)
![newPost](screen/newPost.png)
![decrypt](screen/decrypt.png)
![manage](screen/manage.png)
## 🇨🇿
![nastavení](screen/nastavení.png)
![nováZpráva](screen/nováZpráva.png)
![správaKlíčů](screen/správaKlíčů.png)
![šifra](screen/šifra.png)
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

+8
View File
@@ -0,0 +1,8 @@
- [x] web k aplikaci v CZ
- [x] web k aplikaci v EN
- [x] aplikace inspirovaná aplikaci doxa-cli
- [x] instalátor install.sh
- [x] překlad aplikace do EN
- [x] tvorba dokumentace a manuálu
- [x] automatizované testy
- [x] vyřešit problém z distribucí šifrování druhé straně
+173
View File
@@ -0,0 +1,173 @@
package main
type Translation struct {
Title string
StartupSubtitle string
StartupHelp string
MenuWrite string
MenuDecrypt string
MenuManage string
MenuLang string
MenuQuit string
ActiveCipher string
StatusActive string
StatusCreated string
StatusImported string
StatusExported string
StatusSaved string
StatusDecrypted string
StatusError string
// Create Cipher
CreateTitle string
CreatePrompt string
CreateHelp string
// Import Cipher
ImportTitle string
ImportPrompt string
ImportHelp string
// Export Cipher
ExportTitle string
ExportPrompt string
ExportHelp string
// Write Message
WriteTitle string
WritePromptFilename string
WritePromptContent string
WriteHelp string
WriteSavedTo string
// Decrypt Message
DecryptTitle string
DecryptPromptPath string
DecryptHelp string
DecryptResult string
// Cipher List
ManageTitle string
ManageHelp string
ManageCreateKey string
ManageImportKey string
ManageExportKey string
ManageDeleteKey string
ManageBackKey string
NoCiphers string
ConfirmDelete string
ConfirmYes string
ConfirmNo string
}
var Translations = map[string]Translation{
"cs": {
Title: "DOXA-SECRET 🦅",
StartupSubtitle: "Minimalistický nástroj pro šifrování a ochranu vašich tajných zpráv.",
StartupHelp: "Enter: Start | q: Konec",
MenuWrite: "1: Napsat zprávu",
MenuDecrypt: "2: Dešifrovat zprávu",
MenuManage: "3: Správa šifer",
MenuLang: "l: EN",
MenuQuit: "q: Konec",
ActiveCipher: "Aktivní šifra: [%s]",
StatusActive: "Aktivována šifra: %s",
StatusCreated: "Vytvořena nová šifra: %s",
StatusImported: "Importována šifra: %s",
StatusExported: "Šifra exportována do %s",
StatusSaved: "Zpráva zašifrována a uložena do %s",
StatusDecrypted: "Zpráva dešifrována.",
StatusError: "Chyba: %s",
CreateTitle: "VYTVOŘIT NOVOU ŠIFRU",
CreatePrompt: "Zadejte název šifry (bez mezer): ",
CreateHelp: "Enter: Vytvořit | Esc: Zpět",
ImportTitle: "IMPORTOVAT ŠIFRU (JSON)",
ImportPrompt: "Cesta k souboru šifry: ",
ImportHelp: "Enter: Importovat | Esc: Zpět",
ExportTitle: "EXPORTOVAT ŠIFRU (JSON)",
ExportPrompt: "Cílová cesta pro export: ",
ExportHelp: "Enter: Exportovat | Esc: Zpět",
WriteTitle: "NAPSAT A ZAŠIFROVAT ZPRÁVU",
WritePromptFilename: "Název výstupního souboru (např. zprava.txt): ",
WritePromptContent: "Obsah zprávy (stiskněte Tab pro přepnutí na název souboru):",
WriteHelp: "[Tab] Přepnout pole | [Ctrl+S] Uložit a zašifrovat | [Esc] Zrušit",
WriteSavedTo: "Zpráva uložena.",
DecryptTitle: "DEŠIFROVAT A PŘEČÍST ZPRÁVU",
DecryptPromptPath: "Cesta k šifrované zprávě (.txt): ",
DecryptHelp: "Enter: Načíst a dešifrovat | Esc: Zpět",
DecryptResult: "DEŠIFROVANÁ ZPRÁVA:",
ManageTitle: "SPRÁVA ŠIFROVACÍCH KLÍČŮ (JSON)",
ManageHelp: "↑/↓: Vybrat | Enter: Aktivovat | Esc: Zpět",
ManageCreateKey: "c: Nová šifra",
ManageImportKey: "i: Importovat",
ManageExportKey: "e: Exportovat aktivní",
ManageDeleteKey: "d: Smazat vybranou",
ManageBackKey: "esc: Zpět",
NoCiphers: "Žádné šifry nejsou k dispozici. Vytvořte nebo importujte nějakou.",
ConfirmDelete: "Opravdu chcete smazat šifru '%s'?",
ConfirmYes: "y: Ano",
ConfirmNo: "n: Ne / Esc",
},
"en": {
Title: "DOXA-SECRET 🦅",
StartupSubtitle: "Minimalist tool for encrypting and protecting your secret messages.",
StartupHelp: "Enter: Start | q: Quit",
MenuWrite: "1: Write Message",
MenuDecrypt: "2: Decrypt Message",
MenuManage: "3: Manage Ciphers",
MenuLang: "l: CS",
MenuQuit: "q: Quit",
ActiveCipher: "Active Cipher: [%s]",
StatusActive: "Activated cipher: %s",
StatusCreated: "Created new cipher: %s",
StatusImported: "Imported cipher: %s",
StatusExported: "Cipher exported to %s",
StatusSaved: "Message encrypted and saved to %s",
StatusDecrypted: "Message decrypted.",
StatusError: "Error: %s",
CreateTitle: "CREATE NEW CIPHER",
CreatePrompt: "Enter cipher name (no spaces): ",
CreateHelp: "Enter: Create | Esc: Back",
ImportTitle: "IMPORT CIPHER (JSON)",
ImportPrompt: "Path to cipher file: ",
ImportHelp: "Enter: Import | Esc: Back",
ExportTitle: "EXPORT ACTIVE CIPHER (JSON)",
ExportPrompt: "Destination path for export: ",
ExportHelp: "Enter: Export | Esc: Back",
WriteTitle: "WRITE AND ENCRYPT MESSAGE",
WritePromptFilename: "Output filename (e.g. message.txt): ",
WritePromptContent: "Message content (press Tab to switch to filename field):",
WriteHelp: "[Tab] Switch fields | [Ctrl+S] Save and Encrypt | [Esc] Cancel",
WriteSavedTo: "Message saved.",
DecryptTitle: "DECRYPT AND READ MESSAGE",
DecryptPromptPath: "Path to encrypted message (.txt): ",
DecryptHelp: "Enter: Load and decrypt | Esc: Back",
DecryptResult: "DECRYPTED MESSAGE:",
ManageTitle: "MANAGE CIPHER KEYS (JSON)",
ManageHelp: "↑/↓: Select | Enter: Activate | Esc: Back",
ManageCreateKey: "c: New cipher",
ManageImportKey: "i: Import",
ManageExportKey: "e: Export active",
ManageDeleteKey: "d: Delete selected",
ManageBackKey: "esc: Back",
NoCiphers: "No ciphers available. Please create or import one.",
ConfirmDelete: "Are you sure you want to delete cipher '%s'?",
ConfirmYes: "y: Yes",
ConfirmNo: "n: No / Esc",
},
}
+613
View File
@@ -0,0 +1,613 @@
package main
import (
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/charmbracelet/bubbles/textarea"
"github.com/charmbracelet/bubbles/textinput"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
)
var (
appStyle = lipgloss.NewStyle().
Padding(1, 2).
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("62"))
titleStyle = lipgloss.NewStyle().
Bold(true).
Foreground(lipgloss.Color("#FAFAFA")).
Background(lipgloss.Color("#7D56F4")).
Padding(0, 1).
MarginBottom(1)
selectedStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("#7D56F4")).
Bold(true)
statusStyle = lipgloss.NewStyle().
Foreground(lipgloss.Color("205"))
focusedStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("205"))
cursorStyle = focusedStyle.Copy()
confirmBoxStyle = lipgloss.NewStyle().
Border(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("205")).
Padding(1, 2).Align(lipgloss.Center)
)
type AppState int
const (
StateStartup AppState = iota
StateMainMenu
StateWriteMessage
StateDecryptMessage
StateShowDecryptedMessage
StateManageCiphers
StateCreateCipherModal
StateImportCipherModal
StateExportCipherModal
StateConfirmDeleteCipher
)
type model struct {
state AppState
ciphers []string
activeCipher string
activeConfig *CipherConfig
cursor int
textInput textinput.Model // Reusable path/name textinput
textarea textarea.Model // Plaintext message content
filenameInput textinput.Model // Filename input for write screen
viewport viewport.Model // Scrolling viewport for decrypted messages
focusIndex int // For dual fields (e.g. Write Screen)
statusMsg string
lang string
width int
height int
ready bool
}
func NewModel() model {
ti := textinput.New()
ti.Cursor.Style = cursorStyle
ta := textarea.New()
ta.SetWidth(60)
ta.SetHeight(10)
ta.FocusedStyle.CursorLine = lipgloss.NewStyle().Background(lipgloss.Color("62"))
fi := textinput.New()
fi.Cursor.Style = cursorStyle
m := model{
state: StateStartup,
textInput: ti,
textarea: ta,
filenameInput: fi,
lang: "cs",
focusIndex: 0,
}
m.loadCiphersAndActivateDefault()
return m
}
func (m *model) loadCiphersAndActivateDefault() {
cList, err := ListCiphers()
if err != nil {
m.statusMsg = fmt.Sprintf("Error: %v", err)
return
}
if len(cList) == 0 {
err = GenerateNewCipher("default")
if err != nil {
m.statusMsg = fmt.Sprintf("Error generating default: %v", err)
return
}
cList, _ = ListCiphers()
}
m.ciphers = cList
// Find if activeCipher is still in the list, otherwise select first
found := false
for _, c := range cList {
if c == m.activeCipher {
found = true
break
}
}
if !found && len(cList) > 0 {
m.activeCipher = cList[0]
}
if m.activeCipher != "" {
cfg, err := LoadCipher(m.activeCipher)
if err == nil {
m.activeConfig = cfg
} else {
m.statusMsg = fmt.Sprintf("Error: %v", err)
}
}
}
func (m model) Init() tea.Cmd {
return tea.Batch(textinput.Blink, textarea.Blink, tea.EnterAltScreen)
}
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
tr := Translations[m.lang]
switch msg := msg.(type) {
case tea.WindowSizeMsg:
m.width = msg.Width
m.height = msg.Height
m.textarea.SetWidth(msg.Width - 10)
m.textarea.SetHeight(msg.Height - 12)
vpHeight := msg.Height - 8
if vpHeight < 1 {
vpHeight = 1
}
if !m.ready {
m.viewport = viewport.New(msg.Width-6, vpHeight)
m.ready = true
} else {
m.viewport.Width = msg.Width - 6
m.viewport.Height = vpHeight
}
case tea.KeyMsg:
// Global keys
if msg.String() == "ctrl+c" {
return m, tea.Quit
}
switch m.state {
case StateStartup:
if msg.String() == "enter" {
m.state = StateMainMenu
m.statusMsg = ""
return m, nil
}
if msg.String() == "q" {
return m, tea.Quit
}
case StateMainMenu:
switch msg.String() {
case "q", "esc":
return m, tea.Quit
case "l":
if m.lang == "cs" {
m.lang = "en"
} else {
m.lang = "cs"
}
m.statusMsg = ""
return m, nil
case "1":
m.state = StateWriteMessage
m.textarea.SetValue("")
m.textarea.Focus()
m.filenameInput.SetValue(fmt.Sprintf("zprava_%s.txt", time.Now().Format("20060102_150405")))
m.focusIndex = 0
m.statusMsg = ""
return m, nil
case "2":
m.state = StateDecryptMessage
m.textInput.Focus()
m.textInput.SetValue("")
m.statusMsg = ""
return m, nil
case "3":
m.state = StateManageCiphers
m.loadCiphersAndActivateDefault()
// Find cursor index for active cipher
m.cursor = 0
for idx, val := range m.ciphers {
if val == m.activeCipher {
m.cursor = idx
break
}
}
m.statusMsg = ""
return m, nil
}
case StateWriteMessage:
switch msg.String() {
case "esc":
m.state = StateMainMenu
m.statusMsg = ""
return m, nil
case "tab", "shift+tab":
if m.focusIndex == 0 {
m.focusIndex = 1
m.textarea.Blur()
m.filenameInput.Focus()
} else {
m.focusIndex = 0
m.filenameInput.Blur()
m.textarea.Focus()
}
return m, nil
case "ctrl+s":
// Save & encrypt
content := m.textarea.Value()
filename := strings.TrimSpace(m.filenameInput.Value())
if filename == "" {
filename = "zprava.txt"
}
if m.activeConfig == nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, "není vybraná žádná šifra")
return m, nil
}
encrypted := EncryptMessage(content, m.activeConfig)
exportDir, err := getExportDir()
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
return m, nil
}
fullPath := filepath.Join(exportDir, filename)
err = os.WriteFile(fullPath, []byte(encrypted), 0644)
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
} else {
m.statusMsg = fmt.Sprintf(tr.StatusSaved, fullPath)
m.state = StateMainMenu
}
return m, nil
}
// Forward keys to the focused input
if m.focusIndex == 0 {
m.textarea, cmd = m.textarea.Update(msg)
} else {
m.filenameInput, cmd = m.filenameInput.Update(msg)
}
return m, cmd
case StateDecryptMessage:
switch msg.String() {
case "esc":
m.state = StateMainMenu
m.statusMsg = ""
return m, nil
case "enter":
path := strings.TrimSpace(m.textInput.Value())
if path == "" {
return m, nil
}
var fullPath string
if filepath.IsAbs(path) {
fullPath = path
} else {
importDir, err := getImportDir()
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
return m, nil
}
fullPath = filepath.Join(importDir, path)
}
contentBytes, err := os.ReadFile(fullPath)
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
return m, nil
}
if m.activeConfig == nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, "není vybraná žádná šifra")
return m, nil
}
decrypted := DecryptMessage(string(contentBytes), m.activeConfig)
m.viewport.SetContent(decrypted)
m.viewport.YOffset = 0
m.state = StateShowDecryptedMessage
m.statusMsg = tr.StatusDecrypted
return m, nil
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
case StateShowDecryptedMessage:
switch msg.String() {
case "esc", "q":
m.state = StateMainMenu
m.statusMsg = ""
return m, nil
}
m.viewport, cmd = m.viewport.Update(msg)
return m, cmd
case StateManageCiphers:
switch msg.String() {
case "esc", "q":
m.state = StateMainMenu
m.statusMsg = ""
return m, nil
case "up", "k":
if m.cursor > 0 {
m.cursor--
}
case "down", "j":
if m.cursor < len(m.ciphers)-1 {
m.cursor++
}
case "enter":
if len(m.ciphers) > 0 {
m.activeCipher = m.ciphers[m.cursor]
m.loadCiphersAndActivateDefault()
m.statusMsg = fmt.Sprintf(tr.StatusActive, m.activeCipher)
m.state = StateMainMenu
}
case "c":
m.state = StateCreateCipherModal
m.textInput.SetValue("")
m.textInput.Focus()
m.statusMsg = ""
return m, nil
case "i":
m.state = StateImportCipherModal
m.textInput.SetValue("")
m.textInput.Focus()
m.statusMsg = ""
return m, nil
case "e":
if m.activeCipher != "" {
m.state = StateExportCipherModal
m.textInput.SetValue(fmt.Sprintf("%s_export.json", m.activeCipher))
m.textInput.Focus()
m.statusMsg = ""
return m, nil
}
case "d":
if len(m.ciphers) > 0 {
m.state = StateConfirmDeleteCipher
m.statusMsg = ""
return m, nil
}
}
case StateCreateCipherModal:
switch msg.String() {
case "esc":
m.state = StateManageCiphers
m.statusMsg = ""
return m, nil
case "enter":
name := strings.TrimSpace(m.textInput.Value())
if name == "" {
return m, nil
}
err := GenerateNewCipher(name)
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
} else {
m.statusMsg = fmt.Sprintf(tr.StatusCreated, name)
m.activeCipher = name
m.loadCiphersAndActivateDefault()
}
m.state = StateManageCiphers
return m, nil
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
case StateImportCipherModal:
switch msg.String() {
case "esc":
m.state = StateManageCiphers
m.statusMsg = ""
return m, nil
case "enter":
path := strings.TrimSpace(m.textInput.Value())
if path == "" {
return m, nil
}
name, err := ImportCipher(path)
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
} else {
m.statusMsg = fmt.Sprintf(tr.StatusImported, name)
m.activeCipher = name
m.loadCiphersAndActivateDefault()
}
m.state = StateManageCiphers
return m, nil
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
case StateExportCipherModal:
switch msg.String() {
case "esc":
m.state = StateManageCiphers
m.statusMsg = ""
return m, nil
case "enter":
path := strings.TrimSpace(m.textInput.Value())
if path == "" {
return m, nil
}
err := ExportCipher(m.activeCipher, path)
if err != nil {
m.statusMsg = fmt.Sprintf(tr.StatusError, err.Error())
} else {
m.statusMsg = fmt.Sprintf(tr.StatusExported, path)
}
m.state = StateManageCiphers
return m, nil
}
m.textInput, cmd = m.textInput.Update(msg)
return m, cmd
case StateConfirmDeleteCipher:
switch msg.String() {
case "y", "Y":
if len(m.ciphers) > 0 {
selected := m.ciphers[m.cursor]
dir, err := getCipherDir()
if err == nil {
path := filepath.Join(dir, selected+".json")
_ = os.Remove(path)
}
m.loadCiphersAndActivateDefault()
m.cursor = 0
}
m.state = StateManageCiphers
return m, nil
case "n", "N", "esc":
m.state = StateManageCiphers
return m, nil
}
}
}
return m, nil
}
func (m model) View() string {
tr := Translations[m.lang]
var b strings.Builder
// Top Title
b.WriteString(titleStyle.Render(tr.Title))
b.WriteString("\n")
// Status Line
activeName := m.activeCipher
if activeName == "" {
activeName = "none"
}
b.WriteString(fmt.Sprintf(tr.ActiveCipher, activeName) + "\n")
if m.statusMsg != "" {
b.WriteString(statusStyle.Render(m.statusMsg) + "\n")
}
b.WriteString("\n")
switch m.state {
case StateStartup:
eagle := `
_
( ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("`") + `
\ ` + lipgloss.NewStyle().Foreground(lipgloss.Color("205")).Render("\\") + `
/ / _
__ / / ( \
( "---" / / /
\ / / /
\ /-----' /
\ / /
\ / /
'-------'
`
b.WriteString(lipgloss.Place(m.width-12, 14, lipgloss.Center, lipgloss.Center,
eagle+"\n"+
lipgloss.NewStyle().Bold(true).Foreground(lipgloss.Color("#7D56F4")).Render("DOXA-SECRET 🦅")+"\n\n"+
tr.StartupSubtitle+"\n\n"+
tr.StartupHelp+"\n\n"+
"License: MIT | © 2026 Kankys"))
case StateMainMenu:
b.WriteString(fmt.Sprintf(" %s\n %s\n %s\n\n", tr.MenuWrite, tr.MenuDecrypt, tr.MenuManage))
b.WriteString(fmt.Sprintf(" %s | %s\n", tr.MenuLang, tr.MenuQuit))
case StateWriteMessage:
b.WriteString(tr.WriteTitle + "\n\n")
// Render content textarea
taView := m.textarea.View()
if m.focusIndex == 0 {
taView = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("205")).Render(taView)
} else {
taView = lipgloss.NewStyle().Border(lipgloss.NormalBorder()).BorderForeground(lipgloss.Color("240")).Render(taView)
}
b.WriteString(tr.WritePromptContent + "\n" + taView + "\n\n")
// Render filename input
b.WriteString(tr.WritePromptFilename)
if m.focusIndex == 1 {
b.WriteString(focusedStyle.Render(m.filenameInput.View()) + "\n\n")
} else {
b.WriteString(m.filenameInput.View() + "\n\n")
}
b.WriteString(tr.WriteHelp)
case StateDecryptMessage:
b.WriteString(tr.DecryptTitle + "\n\n")
b.WriteString(tr.DecryptPromptPath + m.textInput.View() + "\n\n")
b.WriteString(tr.DecryptHelp)
case StateShowDecryptedMessage:
b.WriteString(tr.DecryptResult + "\n")
b.WriteString(confirmBoxStyle.Render(m.viewport.View()) + "\n\n")
b.WriteString("q/esc: " + tr.MenuQuit)
case StateManageCiphers:
b.WriteString(fmt.Sprintf("--- %s ---\n\n", tr.ManageTitle))
if len(m.ciphers) == 0 {
b.WriteString(" " + tr.NoCiphers + "\n\n")
} else {
for i, c := range m.ciphers {
cursor := " "
if m.cursor == i {
cursor = "> "
}
nameStr := c
if c == m.activeCipher {
nameStr += " (aktivní)"
}
if m.cursor == i {
b.WriteString(selectedStyle.Render(fmt.Sprintf("%s%s", cursor, nameStr)) + "\n")
} else {
b.WriteString(fmt.Sprintf("%s%s\n", cursor, nameStr) + "\n")
}
}
}
b.WriteString("\n" + fmt.Sprintf("%s | %s | %s | %s | %s\n",
tr.ManageCreateKey, tr.ManageImportKey, tr.ManageExportKey, tr.ManageDeleteKey, tr.ManageBackKey))
b.WriteString(tr.ManageHelp)
case StateCreateCipherModal:
b.WriteString(tr.CreateTitle + "\n\n")
b.WriteString(tr.CreatePrompt + m.textInput.View() + "\n\n")
b.WriteString(tr.CreateHelp)
case StateImportCipherModal:
b.WriteString(tr.ImportTitle + "\n\n")
b.WriteString(tr.ImportPrompt + m.textInput.View() + "\n\n")
b.WriteString(tr.ImportHelp)
case StateExportCipherModal:
b.WriteString(tr.ExportTitle + "\n\n")
b.WriteString(tr.ExportPrompt + m.textInput.View() + "\n\n")
b.WriteString(tr.ExportHelp)
case StateConfirmDeleteCipher:
selected := ""
if len(m.ciphers) > m.cursor {
selected = m.ciphers[m.cursor]
}
confirmText := fmt.Sprintf(tr.ConfirmDelete+"\n\n%s | %s", selected, tr.ConfirmYes, tr.ConfirmNo)
b.WriteString(confirmBoxStyle.Render(confirmText))
}
return appStyle.Render(b.String())
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

+362
View File
@@ -0,0 +1,362 @@
<!DOCTYPE html>
<html lang="cs">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="img/logo.jpg" />
<meta name="description"
content="DOXA Secret je minimalistická, local‑first poznámková aplikace s podporou otevřeným formátem JSON. Absolutní soukromí pro vaše myšlenky, které je plně šifrované" />
<link rel="canonical" href="https://secret.doxa-notes.org/index.html" />
<meta property="og:type" content="website" />
<meta property="og:url" content="https://secret.doxa-notes.org/" />
<meta property="og:title" content="DOXA Secret — šifrované Local-First poznámky a minimalistický terminal editor" />
<meta property="og:description"
content="DOXA Secret je minimalistická, local‑first poznámková aplikace s podporou otevřeným formátem JSON. Absolutní soukromí pro vaše myšlenky, které je plně šifrované" />
<!--<meta property="og:image" content="https://secret.doxa-notes.org/img/doxa.png" />
<meta property="og:image:alt" content="DOXA Notes - Local-First Notes & Sovereignty" />
<meta property="og:locale" data-i18n-key="ogLocale" data-i18n-attr="content" content="cs_CZ" />-->
<!--Twitter Card-->
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="DOXA Notes — Local-First poznámky a minimalistický Markdown editor" />
<meta name="twitter:description"
content="DOXA Secret — šifrované Local-First poznámky a minimalistický terminal editor" />
<meta name="twitter:image" content="https://doxa-notes.org/img/logo.jpg" />
<meta name="twitter:image:alt" content="DOXA Secret — šifrované Local-First poznámky a minimalistický terminal editor" />
<title>DOXA secret</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background-color: #0d1117;
color: #c9d1d9;
font-family: 'Courier New', Courier, monospace;
line-height: 1.6;
padding: 2rem 1rem;
}
.container {
max-width: 800px;
margin: 0 auto;
background-color: #161b22;
border: 1px solid #30363d;
border-radius: 6px;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.5);
overflow: hidden;
}
.terminal-header {
background-color: #21262d;
padding: 0.75rem 1rem;
border-bottom: 1px solid #30363d;
display: flex;
align-items: center;
justify-content: space-between;
}
.terminal-header-left {
display: flex;
align-items: center;
}
.terminal-buttons {
display: flex;
gap: 8px;
}
.btn-circle {
width: 12px;
height: 12px;
border-radius: 50%;
}
.btn-close { background-color: #ff5f56; }
.btn-minimize { background-color: #ffbd2e; }
.btn-maximize { background-color: #27c93f; }
.terminal-title {
margin-left: 1rem;
font-size: 0.85rem;
color: #8b949e;
}
.lang-switch {
background-color: #30363d;
border: 1px solid #8b949e;
color: #f0f6fc;
padding: 2px 8px;
border-radius: 4px;
font-size: 0.8rem;
cursor: pointer;
font-family: inherit;
transition: all 0.2s ease;
}
.lang-switch:hover {
background-color: #8b949e;
color: #0d1117;
}
.terminal-body {
padding: 2rem;
}
h1 {
color: #58a6ff;
font-size: 1.5rem;
margin-bottom: 1rem;
}
p {
margin-bottom: 1.5rem;
color: #8b949e;
}
.prompt {
color: #7ee787;
}
.command {
color: #f0f6fc;
}
pre {
background-color: #0d1117;
border: 1px solid #30363d;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
margin-bottom: 1.5rem;
color: #a5d6ff;
}
code {
font-family: 'Courier New', Courier, monospace;
}
.section-title {
color: #ffa657;
margin-top: 1.5rem;
margin-bottom: 0.5rem;
font-size: 1.1rem;
}
ul {
list-style-type: none;
margin-bottom: 1.5rem;
padding-left: 1rem;
}
ul li::before {
content: "> ";
color: #7ee787;
}
ul li {
margin-bottom: 0.5rem;
}
a {
color: #58a6ff;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.manual-highlight {
background-color: rgba(255, 166, 87, 0.1);
border-left: 4px solid #ffa657;
padding: 1rem;
margin: 1.5rem 0;
border-radius: 0 4px 4px 0;
}
.manual-highlight a {
font-weight: bold;
color: #ffa657;
text-decoration: underline;
}
.footer {
margin-top: 2rem;
border-top: 1px solid #30363d;
padding-top: 1rem;
font-size: 0.85rem;
color: #8b949e;
text-align: center;
font-style: italic;
}
/* Lang visibility styles */
[data-lang] {
display: none;
}
</style>
</head>
<body>
<div class="container">
<div class="terminal-header">
<div class="terminal-header-left">
<div class="terminal-buttons">
<div class="btn-circle btn-close"></div>
<div class="btn-circle btn-minimize"></div>
<div class="btn-circle btn-maximize"></div>
</div>
<div class="terminal-title">bash - doxa-secret</div>
</div>
<button class="lang-switch" onclick="toggleLang()" id="langBtn">EN</button>
</div>
<div class="terminal-body">
<h1>DOXA secret</h1>
<!-- CZ Mutation -->
<div data-lang="cs">
<p>Minimalistický a vysoce bezpečný offline nástroj příkazové řádky pro šifrování a dešifrování zpráv. Aplikace pracuje výhradně se základní abecedou latinky (bez diakritiky), což zaručuje bezproblémový přenos a správné zobrazení zpráv na různých operačních systémech a kódováních. Veškerá česká diakritika je při šifrování automaticky normalizována na základní znaky latinky.</p>
</div>
<!-- EN Mutation -->
<div data-lang="en">
<p>A minimalist and highly secure offline command-line tool for encrypting and decrypting messages. The application works exclusively with the basic Latin alphabet (without diacritics), which guarantees smooth transfer and correct rendering of messages across different operating systems and character encodings. All Czech accents are automatically normalized to basic Latin characters during encryption.</p>
</div>
<p align="center">
<img src="https://img.shields.io/badge/go-1.22+-00ADD8?style=flat-square&logo=go&logoColor=white" alt="Go 1.22+">
<img src="https://img.shields.io/badge/license-MIT-blue?style=flat-square" alt="MIT license">
<img src="https://img.shields.io/badge/tested-Linux-brightgreen?style=flat-square" alt="Tested on Linux">
</p>
<p align="center">
<img src="https://img.shields.io/badge/freebsd-AB2B28?style=for-the-badge&logo=freebsd&logoColor=white" alt="freeBSD">
</p>
<p align="center">
<img src="./img/homePage.png" width="100%" alt="homePage">
</p>
<!-- CZ Mutation -->
<div data-lang="cs">
<div class="manual-highlight">
🔥 <strong>Důležité:</strong> Podrobný návod k použití, instalaci a přehled klávesových zkratek naleznete v oficiálním manuálu:
<a href="https://codeberg.org/Kankys/doxa-secret/src/branch/master/MANUAL_CZ.md" target="_blank">Český uživatelský manuál (MANUAL_CZ.md)</a>
</div>
<div class="section-title">$ instalace</div>
<pre><code>git clone https://codeberg.org/Kankys/doxa-secret.git
cd doxa-secret
chmod +x install.sh
./install.sh</code></pre>
<div class="section-title">$ vlastnosti</div>
<ul>
<li>Lokální abeceda definovaná v JSON formátu bez diakritiky (pro maximální kompatibilitu)</li>
<li>Automatická normalizace diakritiky (např. "ž" se automaticky změní na "z")</li>
<li>Přehledné a moderní terminálové rozhraní (TUI) postavené na Bubbletea</li>
<li>Úplná kontrola nad vlastními daty bez cloudu</li>
<li>Oddělená organizace zpráv (šifrované do složky export, dešifrované ze složky import)</li>
</ul>
<div class="section-title">$ použití</div>
<pre><code># Spuštění aplikace
doxa-secret</code></pre>
<div class="section-title">$ odkazy</div>
<p>Zdrojový kód a kompletní repozitář naleznete na <a href="https://codeberg.org/Kankys/doxa-secret" target="_blank">Codebergu</a>.</p>
<p>Web mateřského projektu DOXA Notes naleznete na <a href="https://doxa-notes.org" target="_blank">oficálním webu</a>.</p>
<p>Web doxa-cli naleznete na <a href="https://cli.doxa-notes.org" target="_blank">oficálním webu</a>.</p>
<div class="footer">
"Skrýt tajemství za fasádou běžného zdání je prvním krokem k ochraně vnitřního klidu."
</div>
</div>
<!-- EN Mutation -->
<div data-lang="en">
<div class="manual-highlight">
🔥 <strong>Important:</strong> A detailed guide for installation,and keyboard shortcuts can be found in the official manual:
<a href="https://codeberg.org/Kankys/doxa-secret/src/branch/master/MANUAL.md" target="_blank">English User Manual (MANUAL.md)</a>
</div>
<div class="section-title">$ installation</div>
<pre><code>git clone https://codeberg.org/Kankys/doxa-secret.git
cd doxa-secret
chmod +x install.sh
./install.sh</code></pre>
<div class="section-title">$ features</div>
<ul>
<li>Local alphabet definitions in JSON format without diacritics (for maximum compatibility)</li>
<li>Automatic diacritics normalization (e.g. "á" is automatically normalized to "a")</li>
<li>Clean and modern Terminal User Interface (TUI) powered by Bubbletea</li>
<li>Zero reliance on third-party cloud services</li>
<li>Separate message organization (encrypted saved to export folder, received loaded from import)</li>
</ul>
<div class="section-title">$ usage</div>
<pre><code># Start the application
doxa-secret</code></pre>
<div class="section-title">$ links</div>
<p>Source code and the complete repository can be found on <a href="https://codeberg.org/Kankys/doxa-secret" target="_blank">Codeberg</a>.</p>
<p>DOXA Notes web can be found on the <a href="https://doxa-notes.org" target="_blank">official website</a>.</p>
<p>doxa-cli web can be found on the <a href="https://cli.doxa-notes.org" target="_blank">official website</a>.</p>
<div class="footer">
"Hiding secrets behind the facade of common appearance is the first step to protecting inner peace."
</div>
</div>
</div>
<p class="copyright" style="text-align: center;">Doxa-secret &copy; 2026 | Local-First & Freedom 🦅</p>
</div>
<script>
let currentLang = 'cs';
function applyLang() {
document.querySelectorAll('[data-lang]').forEach(el => {
if (el.getAttribute('data-lang') === currentLang) {
el.style.display = 'block';
} else {
el.style.display = 'none';
}
});
document.getElementById('langBtn').innerText = currentLang === 'cs' ? 'EN' : 'CS';
document.documentElement.lang = currentLang;
}
function toggleLang() {
currentLang = currentLang === 'cs' ? 'en' : 'cs';
localStorage.setItem('doxa-secret-lang', currentLang);
applyLang();
}
// Initialize language
(function initLanguage() {
// 1. Check local storage
let savedLang = localStorage.getItem('doxa-secret-lang');
if (savedLang) {
currentLang = savedLang;
} else {
// 2. Check system language
let sysLang = navigator.language || navigator.userLanguage || 'en';
sysLang = sysLang.toLowerCase();
if (sysLang.startsWith('cs') || sysLang.startsWith('sk')) {
currentLang = 'cs';
} else {
currentLang = 'en';
}
}
applyLang();
})();
</script>
</body>
</html>