mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-10-31 09:52:03 +00:00
update
This commit is contained in:
1 parent
b298eb017c
commit
966701fb1a
32 files changed
+820
-10027
No files matched your search
@@ -1,34 +0,0 @@
|
||||
const { buildNav } = require('../src/generator');
|
||||
|
||||
test('generates navigation tree', () => {
|
||||
const pages = [
|
||||
{ file: 'guide/install.md', data: { title: 'Install', order: 1 } },
|
||||
{ file: 'guide/usage.md', data: { title: 'Usage', order: 2 } },
|
||||
{ file: 'guide/nested/info.md', data: { title: 'Info', order: 1 } }
|
||||
];
|
||||
const tree = buildNav(pages);
|
||||
const guide = tree.find(n => n.name === 'guide');
|
||||
expect(guide).toBeDefined();
|
||||
expect(guide.children.length).toBe(3);
|
||||
const install = guide.children.find(c => c.name === 'install.md');
|
||||
expect(install.path).toBe('/guide/install.html');
|
||||
});
|
||||
|
||||
test('adds display names and section flags', () => {
|
||||
const pages = [
|
||||
{ file: '02-api.md', data: { title: 'API', order: 2 } },
|
||||
{ file: '01-guide/index.md', data: { title: 'Guide', order: 1 } },
|
||||
{ file: '01-guide/setup.md', data: { title: 'Setup', order: 2 } },
|
||||
{ file: 'index.md', data: { title: 'Home', order: 10 } }
|
||||
];
|
||||
const nav = buildNav(pages);
|
||||
expect(nav[0].name).toBe('index.md');
|
||||
const guide = nav.find(n => n.name === '01-guide');
|
||||
expect(guide.displayName).toBe('Guide');
|
||||
expect(guide.isSection).toBe(true);
|
||||
const api = nav.find(n => n.name === '02-api.md');
|
||||
expect(api.displayName).toBe('API');
|
||||
// alphabetical within same order
|
||||
expect(nav[1].name).toBe('01-guide');
|
||||
expect(nav[2].name).toBe('02-api.md');
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const loadConfig = require('../src/config/loadConfig');
|
||||
|
||||
test('loads configuration and merges defaults', () => {
|
||||
const dir = fs.mkdtempSync(path.join(__dirname, 'cfg-'));
|
||||
const file = path.join(dir, 'config.yaml');
|
||||
fs.writeFileSync(file, 'site:\n title: Test Site\n');
|
||||
const cfg = loadConfig(file);
|
||||
expect(cfg.site.title).toBe('Test Site');
|
||||
expect(cfg.navigation.search).toBe(true);
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
@@ -1,23 +0,0 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const loadPlugins = require('../src/config/loadPlugins');
|
||||
|
||||
test('plugin hook modifies data', async () => {
|
||||
const dir = fs.mkdtempSync(path.join(require('os').tmpdir(), 'plugins-'));
|
||||
const pluginFile = path.join(dir, 'test.plugin.js');
|
||||
fs.writeFileSync(
|
||||
pluginFile,
|
||||
"module.exports = { onParseMarkdown: ({ content }) => ({ content: content + '!!' }) };\n"
|
||||
);
|
||||
|
||||
const plugins = loadPlugins({ pluginsDir: dir, plugins: ['test.plugin'] });
|
||||
let data = { content: 'hello' };
|
||||
for (const plugin of plugins) {
|
||||
if (typeof plugin.onParseMarkdown === 'function') {
|
||||
const res = await plugin.onParseMarkdown(data);
|
||||
if (res !== undefined) data = res;
|
||||
}
|
||||
}
|
||||
expect(data.content).toBe('hello!!');
|
||||
fs.rmSync(dir, { recursive: true, force: true });
|
||||
});
|
||||
@@ -1,77 +0,0 @@
|
||||
jest.mock('@11ty/eleventy', () => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
return class Eleventy {
|
||||
constructor(input, output) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
setConfig() {}
|
||||
async write() {
|
||||
const walk = d => {
|
||||
const entries = fs.readdirSync(d, { withFileTypes: true });
|
||||
let files = [];
|
||||
for (const e of entries) {
|
||||
const p = path.join(d, e.name);
|
||||
if (e.isDirectory()) files = files.concat(walk(p));
|
||||
else if (p.endsWith('.md')) files.push(p);
|
||||
}
|
||||
return files;
|
||||
};
|
||||
for (const file of walk(this.input)) {
|
||||
const rel = path.relative(this.input, file).replace(/\.md$/, '.html');
|
||||
const dest = path.join(this.output, rel);
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.writeFileSync(dest, '<header></header><aside class="sidebar"></aside>');
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const { generate } = require('../src/generator');
|
||||
|
||||
function getPaths(tree) {
|
||||
const paths = [];
|
||||
for (const node of tree) {
|
||||
if (node.path) paths.push(node.path);
|
||||
if (node.children) paths.push(...getPaths(node.children));
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
test('markdown files render with layout and appear in nav/search', async () => {
|
||||
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'df-test-'));
|
||||
const contentDir = path.join(tmp, 'content');
|
||||
const outputDir = path.join(tmp, '_site');
|
||||
fs.mkdirSync(path.join(contentDir, 'guide'), { recursive: true });
|
||||
fs.writeFileSync(path.join(contentDir, 'index.md'), '# Home\nWelcome');
|
||||
fs.writeFileSync(path.join(contentDir, 'guide', 'install.md'), '# Install\nSteps');
|
||||
const configPath = path.join(tmp, 'config.yaml');
|
||||
fs.writeFileSync(configPath, 'site:\n title: Test\n');
|
||||
|
||||
await generate({ contentDir, outputDir, configPath });
|
||||
|
||||
const indexHtml = fs.readFileSync(path.join(outputDir, 'index.html'), 'utf8');
|
||||
const installHtml = fs.readFileSync(path.join(outputDir, 'guide', 'install.html'), 'utf8');
|
||||
expect(indexHtml).toContain('<header');
|
||||
expect(indexHtml).toContain('<aside class="sidebar"');
|
||||
expect(installHtml).toContain('<header');
|
||||
expect(installHtml).toContain('<aside class="sidebar"');
|
||||
|
||||
const nav = JSON.parse(fs.readFileSync(path.join(outputDir, 'navigation.json'), 'utf8'));
|
||||
const navPaths = getPaths(nav);
|
||||
expect(navPaths).toContain('/index.html');
|
||||
expect(navPaths).toContain('/guide/install.html');
|
||||
|
||||
const search = JSON.parse(fs.readFileSync(path.join(outputDir, 'search-index.json'), 'utf8'));
|
||||
const docs = search.docs.map(d => d.id);
|
||||
expect(docs).toContain('index.html');
|
||||
expect(docs).toContain('guide/install.html');
|
||||
const installDoc = search.docs.find(d => d.id === 'guide/install.html');
|
||||
expect(installDoc.body).toContain('Steps');
|
||||
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
@@ -1,128 +0,0 @@
|
||||
jest.mock('@11ty/eleventy', () => {
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
return class Eleventy {
|
||||
constructor(input, output) {
|
||||
this.input = input;
|
||||
this.output = output;
|
||||
}
|
||||
setConfig() {}
|
||||
async write() {
|
||||
const walk = d => {
|
||||
const entries = fs.readdirSync(d, { withFileTypes: true });
|
||||
let files = [];
|
||||
for (const e of entries) {
|
||||
const p = path.join(d, e.name);
|
||||
if (e.isDirectory()) files = files.concat(walk(p));
|
||||
else if (p.endsWith('.md')) files.push(p);
|
||||
}
|
||||
return files;
|
||||
};
|
||||
for (const file of walk(this.input)) {
|
||||
const rel = path.relative(this.input, file).replace(/\.md$/, '.html');
|
||||
const dest = path.join(this.output, rel);
|
||||
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
||||
fs.writeFileSync(
|
||||
dest,
|
||||
`<!DOCTYPE html><html><head><link rel="stylesheet" href="/assets/theme.css"></head><body><header><button id="sidebar-toggle" class="sidebar-toggle">☰</button></header><div class="container"><aside class="sidebar"></aside><main></main></div><script src="/assets/theme.js"></script></body></html>`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const http = require('http');
|
||||
const os = require('os');
|
||||
const puppeteer = require('puppeteer');
|
||||
const { generate } = require('../src/generator');
|
||||
|
||||
jest.setTimeout(30000);
|
||||
|
||||
let server;
|
||||
let browser;
|
||||
let port;
|
||||
let tmp;
|
||||
|
||||
beforeAll(async () => {
|
||||
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'df-responsive-'));
|
||||
const contentDir = path.join(tmp, 'content');
|
||||
const outputDir = path.join(tmp, '_site');
|
||||
fs.mkdirSync(contentDir, { recursive: true });
|
||||
fs.writeFileSync(path.join(contentDir, 'index.md'), '# Home\n');
|
||||
await generate({ contentDir, outputDir });
|
||||
fs.cpSync(path.join(__dirname, '../assets'), path.join(outputDir, 'assets'), { recursive: true });
|
||||
|
||||
server = http.createServer((req, res) => {
|
||||
let filePath = path.join(outputDir, req.url === '/' ? 'index.html' : req.url);
|
||||
if (req.url.startsWith('/assets')) {
|
||||
filePath = path.join(outputDir, req.url);
|
||||
}
|
||||
fs.readFile(filePath, (err, data) => {
|
||||
if (err) {
|
||||
res.writeHead(404);
|
||||
res.end('Not found');
|
||||
return;
|
||||
}
|
||||
const ext = path.extname(filePath).slice(1);
|
||||
const type = { html: 'text/html', js: 'text/javascript', css: 'text/css' }[ext] || 'application/octet-stream';
|
||||
res.writeHead(200, { 'Content-Type': type });
|
||||
res.end(data);
|
||||
});
|
||||
});
|
||||
await new Promise(resolve => {
|
||||
server.listen(0, () => {
|
||||
port = server.address().port;
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
|
||||
browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] });
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
if (browser) await browser.close();
|
||||
if (server) server.close();
|
||||
fs.rmSync(tmp, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test('sidebar opens on small screens', async () => {
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 500, height: 800 });
|
||||
await page.goto(`http://localhost:${port}/`);
|
||||
await page.waitForSelector('#sidebar-toggle');
|
||||
await page.click('#sidebar-toggle');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
const bodyClass = await page.evaluate(() => document.body.classList.contains('sidebar-open'));
|
||||
const sidebarLeft = await page.evaluate(() => getComputedStyle(document.querySelector('.sidebar')).left);
|
||||
expect(bodyClass).toBe(true);
|
||||
expect(sidebarLeft).toBe('0px');
|
||||
});
|
||||
|
||||
test('clicking outside closes sidebar on small screens', async () => {
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 500, height: 800 });
|
||||
await page.goto(`http://localhost:${port}/`);
|
||||
await page.waitForSelector('#sidebar-toggle');
|
||||
await page.click('#sidebar-toggle');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
await page.click('main');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
const bodyClass = await page.evaluate(() => document.body.classList.contains('sidebar-open'));
|
||||
expect(bodyClass).toBe(false);
|
||||
});
|
||||
|
||||
test('sidebar toggles on large screens', async () => {
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width: 1024, height: 800 });
|
||||
await page.goto(`http://localhost:${port}/`);
|
||||
await page.waitForSelector('#sidebar-toggle');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
let sidebarWidth = await page.evaluate(() => getComputedStyle(document.querySelector('.sidebar')).width);
|
||||
expect(sidebarWidth).toBe('240px');
|
||||
await page.click('#sidebar-toggle');
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
sidebarWidth = await page.evaluate(() => getComputedStyle(document.querySelector('.sidebar')).width);
|
||||
expect(sidebarWidth).toBe('0px');
|
||||
});
|
||||
@@ -1,45 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function copyDir(src, dest) {
|
||||
fs.mkdirSync(dest, { recursive: true });
|
||||
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
||||
const srcPath = path.join(src, entry.name);
|
||||
const destPath = path.join(dest, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
copyDir(srcPath, destPath);
|
||||
} else {
|
||||
fs.copyFileSync(srcPath, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
const args = process.argv.slice(2);
|
||||
const install = args.includes('--install');
|
||||
const targetArg = args.find(a => !a.startsWith('-')) || '.';
|
||||
const targetDir = path.resolve(process.cwd(), targetArg);
|
||||
|
||||
const templateDir = path.join(__dirname, '..', 'starter');
|
||||
copyDir(templateDir, targetDir);
|
||||
|
||||
const pkgPath = path.join(targetDir, 'package.json');
|
||||
if (fs.existsSync(pkgPath)) {
|
||||
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
||||
const version = require('../package.json').version;
|
||||
if (pkg.dependencies && pkg.dependencies.archivox)
|
||||
pkg.dependencies.archivox = `^${version}`;
|
||||
pkg.name = path.basename(targetDir);
|
||||
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
|
||||
}
|
||||
|
||||
if (install) {
|
||||
execSync('npm install', { cwd: targetDir, stdio: 'inherit' });
|
||||
}
|
||||
|
||||
console.log(`Archivox starter created at ${targetDir}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,15 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
const path = require('path');
|
||||
const { generate } = require('./src/generator');
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const contentDir = path.join(__dirname, 'docs', 'content');
|
||||
const configPath = path.join(__dirname, 'docs', 'config.yaml');
|
||||
const outputDir = path.join(__dirname, '_site');
|
||||
await generate({ contentDir, outputDir, configPath });
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
@@ -1,12 +0,0 @@
|
||||
site:
|
||||
title: "SeedPass Docs"
|
||||
description: "One seed to rule them all."
|
||||
|
||||
navigation:
|
||||
search: true
|
||||
|
||||
footer:
|
||||
links:
|
||||
- text: "SeedPass"
|
||||
url: "https://seedpass.me/"
|
||||
|
||||
@@ -1,227 +0,0 @@
|
||||
# SeedPass Advanced CLI and API Documentation
|
||||
|
||||
## Overview
|
||||
|
||||
Welcome to the **Advanced CLI and API Documentation** for **SeedPass**, a secure, deterministic password manager built on Bitcoin's BIP‑85 standard. This guide is designed for power users, developers, and system administrators who wish to leverage the full capabilities of SeedPass through the command line for scripting, automation, and integration.
|
||||
|
||||
SeedPass uses a `noun-verb` command structure (e.g., `seedpass entry get <query>`) for a clear, scalable, and discoverable interface. You can explore the available actions for any command group with the `--help` flag (for example, `seedpass entry --help`).
|
||||
|
||||
The commands in this document reflect the Typer-based CLI shipped with SeedPass. Each command accepts the optional `--fingerprint` flag to operate on a specific seed profile.
|
||||
|
||||
---
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Global Options](#global-options)
|
||||
2. [Command Group Reference](#command-group-reference)
|
||||
- [Entry Commands](#entry-commands)
|
||||
- [Vault Commands](#vault-commands)
|
||||
- [Nostr Commands](#nostr-commands)
|
||||
- [Config Commands](#config-commands)
|
||||
- [Fingerprint Commands](#fingerprint-commands)
|
||||
- [Utility Commands](#utility-commands)
|
||||
- [API Commands](#api-commands)
|
||||
3. [Detailed Command Descriptions](#detailed-command-descriptions)
|
||||
4. [API Integration](#api-integration)
|
||||
5. [Usage Guidelines](#usage-guidelines)
|
||||
|
||||
---
|
||||
|
||||
## Global Options
|
||||
|
||||
These options can be used with any command.
|
||||
|
||||
| Flag | Description |
|
||||
| :--- | :--- |
|
||||
| `--fingerprint <fp>` | Specify which seed profile to use. If omitted, the most recently used profile is selected. |
|
||||
| `--help`, `-h` | Display help information for a command or subcommand. |
|
||||
|
||||
---
|
||||
|
||||
## Command Group Reference
|
||||
|
||||
### Entry Commands
|
||||
|
||||
Manage individual entries within a vault.
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| List entries | `entry list` | `seedpass entry list --sort label` |
|
||||
| Search for entries | `entry search` | `seedpass entry search "GitHub"` |
|
||||
| Retrieve an entry's secret (password or TOTP code) | `entry get` | `seedpass entry get "GitHub"` |
|
||||
| Add a password entry | `entry add` | `seedpass entry add Example --length 16 --no-special --exclude-ambiguous` |
|
||||
| Add a TOTP entry | `entry add-totp` | `seedpass entry add-totp Email --secret JBSW...` |
|
||||
| Add an SSH key entry | `entry add-ssh` | `seedpass entry add-ssh Server --index 0` |
|
||||
| Add a PGP key entry | `entry add-pgp` | `seedpass entry add-pgp Personal --user-id [email protected]` |
|
||||
| Add a Nostr key entry | `entry add-nostr` | `seedpass entry add-nostr Chat` |
|
||||
| Add a seed phrase entry | `entry add-seed` | `seedpass entry add-seed Backup --words 24` |
|
||||
| Add a key/value entry | `entry add-key-value` | `seedpass entry add-key-value "API Token" --key api --value abc123` |
|
||||
| Add a managed account entry | `entry add-managed-account` | `seedpass entry add-managed-account Trading` |
|
||||
| Modify an entry | `entry modify` | `seedpass entry modify 1 --key new --value updated` |
|
||||
| Archive an entry | `entry archive` | `seedpass entry archive 1` |
|
||||
| Unarchive an entry | `entry unarchive` | `seedpass entry unarchive 1` |
|
||||
| Export all TOTP secrets | `entry export-totp` | `seedpass entry export-totp --file totp.json` |
|
||||
| Show all TOTP codes | `entry totp-codes` | `seedpass entry totp-codes` |
|
||||
|
||||
### Vault Commands
|
||||
|
||||
Manage the entire vault for a profile.
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| Export the vault | `vault export` | `seedpass vault export --file backup.json` |
|
||||
| Import a vault | `vault import` | `seedpass vault import --file backup.json` *(also syncs with Nostr)* |
|
||||
| Change the master password | `vault change-password` | `seedpass vault change-password` |
|
||||
| Lock the vault | `vault lock` | `seedpass vault lock` |
|
||||
| Show profile statistics | `vault stats` | `seedpass vault stats` |
|
||||
| Reveal or back up the parent seed | `vault reveal-parent-seed` | `seedpass vault reveal-parent-seed --file backup.enc` |
|
||||
|
||||
### Nostr Commands
|
||||
|
||||
Interact with the Nostr network for backup and synchronization. Offline mode is enabled by default, so disable it with `seedpass config toggle-offline` before using these commands.
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| Sync with relays | `nostr sync` | `seedpass nostr sync` |
|
||||
| Get public key | `nostr get-pubkey` | `seedpass nostr get-pubkey` |
|
||||
|
||||
### Config Commands
|
||||
|
||||
Manage profile‑specific settings.
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| Get a setting value | `config get` | `seedpass config get kdf_iterations` |
|
||||
| Set a setting value | `config set` | `seedpass config set backup_interval 3600` |
|
||||
| Toggle offline mode | `config toggle-offline` | `seedpass config toggle-offline` |
|
||||
|
||||
### Fingerprint Commands
|
||||
|
||||
Manage seed profiles (fingerprints).
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| List all profiles | `fingerprint list` | `seedpass fingerprint list` |
|
||||
| Add a profile | `fingerprint add` | `seedpass fingerprint add` |
|
||||
| Remove a profile | `fingerprint remove` | `seedpass fingerprint remove <fp>` |
|
||||
| Switch profile | `fingerprint switch` | `seedpass fingerprint switch <fp>` |
|
||||
|
||||
### Utility Commands
|
||||
|
||||
Miscellaneous helper commands.
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| Generate a password | `util generate-password` | `seedpass util generate-password --length 24 --special-mode safe --exclude-ambiguous` |
|
||||
| Verify script checksum | `util verify-checksum` | `seedpass util verify-checksum` |
|
||||
| Update script checksum | `util update-checksum` | `seedpass util update-checksum` |
|
||||
|
||||
If you see a startup warning about a script checksum mismatch,
|
||||
run `seedpass util update-checksum` or choose "Generate Script Checksum"
|
||||
from the Settings menu to update the stored value.
|
||||
|
||||
### API Commands
|
||||
|
||||
Run or stop the local HTTP API.
|
||||
|
||||
| Action | Command | Examples |
|
||||
| :--- | :--- | :--- |
|
||||
| Start the API | `api start` | `seedpass api start --host 0.0.0.0 --port 8000` |
|
||||
| Stop the API | `api stop --token TOKEN` | `seedpass api stop --token <token>` |
|
||||
|
||||
---
|
||||
|
||||
## Detailed Command Descriptions
|
||||
|
||||
### `entry` Commands
|
||||
|
||||
- **`seedpass entry list`** – List entries in the vault, optionally sorted or filtered.
|
||||
- **`seedpass entry search <query>`** – Search across labels, usernames, URLs and notes. Results show the entry type before each label.
|
||||
- **`seedpass entry get <query>`** – Retrieve the password or TOTP code for one matching entry, depending on the entry's type.
|
||||
- **`seedpass entry add <label>`** – Create a new password entry. Use `--length` and flags like `--no-special`, `--special-mode safe`, or `--exclude-ambiguous` to override the global policy.
|
||||
- **`seedpass entry add-totp <label>`** – Create a TOTP entry. Use `--secret` to import an existing secret or `--index` to derive from the seed.
|
||||
- **`seedpass entry add-ssh <label>`** – Create an SSH key entry derived from the seed.
|
||||
- **`seedpass entry add-pgp <label>`** – Create a PGP key entry. Provide `--user-id` and `--key-type` as needed.
|
||||
- **`seedpass entry add-nostr <label>`** – Create a Nostr key entry for decentralised chat.
|
||||
- **`seedpass entry add-seed <label>`** – Store a derived seed phrase. Use `--words` to set the word count.
|
||||
- **`seedpass entry add-key-value <label>`** – Store arbitrary data with `--key` and `--value`.
|
||||
- **`seedpass entry add-managed-account <label>`** – Store a BIP‑85 derived account seed.
|
||||
- **`seedpass entry modify <id>`** – Update an entry's fields. For key/value entries you can change the label, key and value.
|
||||
- **`seedpass entry archive <id>`** – Mark an entry as archived so it is hidden from normal lists.
|
||||
- **`seedpass entry unarchive <id>`** – Restore an archived entry.
|
||||
- **`seedpass entry export-totp --file <path>`** – Export all stored TOTP secrets to a JSON file.
|
||||
- **`seedpass entry totp-codes`** – Display all current TOTP codes with remaining time.
|
||||
|
||||
Example retrieving a TOTP code:
|
||||
|
||||
```bash
|
||||
$ seedpass entry get "email"
|
||||
[##########----------] 15s
|
||||
Code: 123456
|
||||
```
|
||||
|
||||
### Viewing Entry Details
|
||||
|
||||
Picking an entry from `entry list` or `entry search` displays its metadata first
|
||||
so you can review the label, username and notes. Sensitive fields are hidden
|
||||
until you confirm you want to reveal them. After showing the secret, the details
|
||||
view offers the same actions as `entry get`—edit the entry, archive it or show
|
||||
QR codes for supported types.
|
||||
|
||||
### `vault` Commands
|
||||
|
||||
- **`seedpass vault export`** – Export the entire vault to an encrypted JSON file.
|
||||
- **`seedpass vault import`** – Import a vault from an encrypted JSON file and automatically sync via Nostr.
|
||||
- **`seedpass vault change-password`** – Change the master password used for encryption.
|
||||
- **`seedpass vault lock`** – Clear sensitive data from memory and require reauthentication.
|
||||
- **`seedpass vault stats`** – Display statistics about the active seed profile.
|
||||
- **`seedpass vault reveal-parent-seed`** – Print the parent seed or write an encrypted backup with `--file`.
|
||||
|
||||
### `nostr` Commands
|
||||
|
||||
- **`seedpass nostr sync`** – Perform a two‑way sync with configured Nostr relays.
|
||||
- **`seedpass nostr get-pubkey`** – Display the Nostr public key for the active profile.
|
||||
|
||||
### `config` Commands
|
||||
|
||||
- **`seedpass config get <key>`** – Retrieve a configuration value such as `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, `clipboard_clear_delay`, `additional_backup_path`, `relays`, `quick_unlock`, `nostr_max_retries`, `nostr_retry_delay`, or password policy fields like `min_uppercase`.
|
||||
- **`seedpass config set <key> <value>`** – Update a configuration option. Example: `seedpass config set kdf_iterations 200000`. Use keys like `min_uppercase`, `min_lowercase`, `min_digits`, `min_special`, `include_special_chars`, `allowed_special_chars`, `special_mode`, `exclude_ambiguous`, `nostr_max_retries`, `nostr_retry_delay`, or `quick_unlock` to adjust settings.
|
||||
- **`seedpass config toggle-secret-mode`** – Interactively enable or disable Secret Mode and set the clipboard delay.
|
||||
- **`seedpass config toggle-offline`** – Enable or disable offline mode to skip Nostr operations.
|
||||
|
||||
### `fingerprint` Commands
|
||||
|
||||
- **`seedpass fingerprint list`** – List available profiles by fingerprint.
|
||||
- **`seedpass fingerprint add`** – Create a new seed profile.
|
||||
- **`seedpass fingerprint remove <fp>`** – Delete the specified profile.
|
||||
- **`seedpass fingerprint switch <fp>`** – Switch the active profile.
|
||||
|
||||
### `util` Commands
|
||||
|
||||
- **`seedpass util generate-password`** – Generate a strong password of the requested length.
|
||||
- **`seedpass util verify-checksum`** – Verify the SeedPass script checksum.
|
||||
- **`seedpass util update-checksum`** – Regenerate the script checksum file.
|
||||
|
||||
---
|
||||
|
||||
## API Integration
|
||||
|
||||
SeedPass provides a small REST API for automation. Run `seedpass api start` to launch the server. The command prints a one‑time token which clients must include in the `Authorization` header.
|
||||
|
||||
Set the `SEEDPASS_CORS_ORIGINS` environment variable to a comma‑separated list of allowed origins when you need cross‑origin requests:
|
||||
|
||||
```bash
|
||||
SEEDPASS_CORS_ORIGINS=http://localhost:3000 seedpass api start
|
||||
```
|
||||
|
||||
Shut down the server with `seedpass api stop --token <token>`.
|
||||
|
||||
---
|
||||
|
||||
## Usage Guidelines
|
||||
|
||||
- Use the `--help` flag for details on any command.
|
||||
- Set a strong master password and regularly export encrypted backups.
|
||||
- Adjust configuration values like `kdf_iterations`, `backup_interval`, `inactivity_timeout`, `secret_mode_enabled`, `nostr_max_retries`, `nostr_retry_delay`, or `quick_unlock` through the `config` commands.
|
||||
- Customize the global password policy with commands like `config set min_uppercase 3` or `config set special_mode safe`. When adding a password interactively you can override these values, choose a safe special-character set, and exclude ambiguous characters.
|
||||
- `entry get` is script‑friendly and can be piped into other commands.
|
||||
@@ -1,299 +0,0 @@
|
||||
# SeedPass REST API Reference
|
||||
|
||||
This guide covers how to start the SeedPass API, authenticate requests, and interact with the available endpoints.
|
||||
|
||||
**Note:** All UI layers, including the CLI, BeeWare GUI, and future adapters, consume this REST API through service classes in `seedpass.core`. See [docs/gui_adapter.md](docs/gui_adapter.md) for more details on the GUI integration.
|
||||
|
||||
|
||||
## Starting the API
|
||||
|
||||
Run `seedpass api start` from your terminal. The command prints a short‑lived JWT token used for authentication:
|
||||
|
||||
```bash
|
||||
$ seedpass api start
|
||||
API token: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...
|
||||
```
|
||||
|
||||
Keep this token secret and avoid logging it. Tokens expire after a few minutes and every request must include one in the `Authorization` header using the `Bearer` scheme.
|
||||
|
||||
## Endpoints
|
||||
|
||||
- `GET /api/v1/entry?query=<text>` – Search entries matching a query.
|
||||
- `GET /api/v1/entry/{id}` – Retrieve a single entry by its index. Requires an `X-SeedPass-Password` header.
|
||||
- `POST /api/v1/entry` – Create a new entry of any supported type.
|
||||
- `PUT /api/v1/entry/{id}` – Modify an existing entry.
|
||||
- `PUT /api/v1/config/{key}` – Update a configuration value.
|
||||
- `POST /api/v1/secret-mode` – Enable or disable Secret Mode and set the clipboard delay.
|
||||
- `POST /api/v1/entry/{id}/archive` – Archive an entry.
|
||||
- `POST /api/v1/entry/{id}/unarchive` – Unarchive an entry.
|
||||
- `GET /api/v1/config/{key}` – Return the value for a configuration key.
|
||||
- `GET /api/v1/fingerprint` – List available seed fingerprints.
|
||||
- `POST /api/v1/fingerprint` – Add a new seed fingerprint.
|
||||
- `DELETE /api/v1/fingerprint/{fp}` – Remove a fingerprint.
|
||||
- `POST /api/v1/fingerprint/select` – Switch the active fingerprint.
|
||||
- `GET /api/v1/totp/export` – Export all TOTP entries as JSON. Requires an `X-SeedPass-Password` header.
|
||||
- `GET /api/v1/totp` – Return current TOTP codes and remaining time. Requires an `X-SeedPass-Password` header.
|
||||
- `GET /api/v1/stats` – Return statistics about the active seed profile.
|
||||
- `GET /api/v1/notifications` – Retrieve and clear queued notifications. Messages appear in the persistent notification box but remain queued until fetched.
|
||||
- `GET /api/v1/nostr/pubkey` – Fetch the Nostr public key for the active seed.
|
||||
- `POST /api/v1/checksum/verify` – Verify the checksum of the running script.
|
||||
- `POST /api/v1/checksum/update` – Update the stored script checksum.
|
||||
- `POST /api/v1/change-password` – Change the master password for the active profile.
|
||||
- `POST /api/v1/vault/import` – Import a vault backup from a file or path.
|
||||
- `POST /api/v1/vault/export` – Export the vault and download the encrypted file. Requires an additional `X-SeedPass-Password` header.
|
||||
- `POST /api/v1/vault/backup-parent-seed` – Save an encrypted backup of the parent seed. Requires a `confirm` flag in the request body and an `X-SeedPass-Password` header.
|
||||
- `POST /api/v1/vault/lock` – Lock the vault and clear sensitive data from memory.
|
||||
- `GET /api/v1/relays` – List configured Nostr relays.
|
||||
- `POST /api/v1/relays` – Add a relay URL.
|
||||
- `DELETE /api/v1/relays/{idx}` – Remove the relay at the given index (1‑based).
|
||||
- `POST /api/v1/relays/reset` – Reset the relay list to defaults.
|
||||
- `POST /api/v1/shutdown` – Stop the server gracefully.
|
||||
|
||||
|
||||
## Secure Deployment
|
||||
|
||||
Always run the API behind HTTPS. Use a reverse proxy such as Nginx or Caddy to terminate TLS and forward requests to SeedPass. Example Nginx configuration:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 443 ssl;
|
||||
ssl_certificate /etc/letsencrypt/live/example.com/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.com/privkey.pem;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For local testing, Uvicorn can serve TLS directly:
|
||||
|
||||
```
|
||||
uvicorn seedpass.api:app --ssl-certfile=cert.pem --ssl-keyfile=key.pem
|
||||
```
|
||||
|
||||
## Example Requests
|
||||
|
||||
Send requests with the token in the header:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
"https://127.0.0.1:8000/api/v1/entry?query=email"
|
||||
```
|
||||
|
||||
### Creating an Entry
|
||||
|
||||
`POST /api/v1/entry` accepts a JSON body with at least a `label` field. Set
|
||||
`type` (or `kind`) to choose the entry variant (`password`, `totp`, `ssh`, `pgp`,
|
||||
`nostr`, `seed`, `key_value`, or `managed_account`). Additional fields vary by
|
||||
type:
|
||||
|
||||
- **password** – `length`, optional `username`, `url` and `notes`
|
||||
- **totp** – `secret` or `index`, optional `period`, `digits`, `notes`, `archived`
|
||||
- **ssh/nostr/seed/managed_account** – `index`, optional `notes`, `archived`
|
||||
- **pgp** – `index`, `key_type`, `user_id`, optional `notes`, `archived`
|
||||
- **key_value** – `value`, optional `notes`
|
||||
|
||||
Example creating a TOTP entry:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/entry \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"type": "totp", "label": "Email", "secret": "JBSW..."}'
|
||||
```
|
||||
|
||||
### Updating an Entry
|
||||
|
||||
Use `PUT /api/v1/entry/{id}` to change fields such as `label`, `username`,
|
||||
`url`, `notes`, `period`, `digits` or `value` depending on the entry type.
|
||||
|
||||
```bash
|
||||
curl -X PUT http://127.0.0.1:8000/api/v1/entry/1 \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"username": "alice"}'
|
||||
```
|
||||
|
||||
### Updating Configuration
|
||||
|
||||
Send a JSON body containing a `value` field to `PUT /api/v1/config/{key}`:
|
||||
|
||||
```bash
|
||||
curl -X PUT http://127.0.0.1:8000/api/v1/config/inactivity_timeout \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"value": 300}'
|
||||
```
|
||||
|
||||
To raise the PBKDF2 work factor or change how often backups are written:
|
||||
|
||||
```bash
|
||||
curl -X PUT http://127.0.0.1:8000/api/v1/config/kdf_iterations \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"value": 200000}'
|
||||
|
||||
curl -X PUT http://127.0.0.1:8000/api/v1/config/backup_interval \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"value": 3600}'
|
||||
```
|
||||
|
||||
Using fewer iterations or a long interval reduces security, so adjust these values carefully.
|
||||
|
||||
### Toggling Secret Mode
|
||||
|
||||
Send both `enabled` and `delay` values to `/api/v1/secret-mode`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/secret-mode \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"enabled": true, "delay": 20}'
|
||||
```
|
||||
|
||||
### Switching Fingerprints
|
||||
|
||||
Change the active seed profile via `POST /api/v1/fingerprint/select`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/fingerprint/select \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"fingerprint": "abc123"}'
|
||||
```
|
||||
|
||||
### Exporting the Vault
|
||||
|
||||
Download an encrypted vault backup via `POST /api/v1/vault/export`:
|
||||
|
||||
```bash
|
||||
curl -X POST https://127.0.0.1:8000/api/v1/vault/export \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "X-SeedPass-Password: <master-password>" \
|
||||
-o backup.json
|
||||
```
|
||||
|
||||
### Importing a Vault
|
||||
|
||||
Restore a backup with `POST /api/v1/vault/import`. Use `-F` to upload a file:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/vault/import \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-F file=@backup.json
|
||||
```
|
||||
|
||||
### Locking the Vault
|
||||
|
||||
Clear sensitive data from memory using `/api/v1/vault/lock`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/vault/lock \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Backing Up the Parent Seed
|
||||
|
||||
Trigger an encrypted seed backup with `/api/v1/vault/backup-parent-seed`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/vault/backup-parent-seed \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "X-SeedPass-Password: <master password>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"path": "seed_backup.enc", "confirm": true}'
|
||||
```
|
||||
|
||||
### Retrieving Vault Statistics
|
||||
|
||||
Get profile stats such as entry counts with `GET /api/v1/stats`:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
http://127.0.0.1:8000/api/v1/stats
|
||||
```
|
||||
|
||||
### Checking Notifications
|
||||
|
||||
Get queued messages with `GET /api/v1/notifications`:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: Bearer <token>" \
|
||||
http://127.0.0.1:8000/api/v1/notifications
|
||||
```
|
||||
|
||||
The TUI displays these alerts in a persistent notification box for 10 seconds,
|
||||
but the endpoint returns all queued messages even if they have already
|
||||
disappeared from the screen.
|
||||
|
||||
### Changing the Master Password
|
||||
|
||||
Update the vault password via `POST /api/v1/change-password`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/change-password \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Verifying the Script Checksum
|
||||
|
||||
Check that the running script matches the stored checksum:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/checksum/verify \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Updating the Script Checksum
|
||||
|
||||
Regenerate the stored checksum using `/api/v1/checksum/update`:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/checksum/update \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Managing Relays
|
||||
|
||||
List, add, or remove Nostr relays:
|
||||
|
||||
```bash
|
||||
# list
|
||||
curl -H "Authorization: Bearer <token>" http://127.0.0.1:8000/api/v1/relays
|
||||
|
||||
# add
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/relays \
|
||||
-H "Authorization: Bearer <token>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"url": "wss://relay.example.com"}'
|
||||
|
||||
# remove first relay
|
||||
curl -X DELETE http://127.0.0.1:8000/api/v1/relays/1 \
|
||||
-H "Authorization: Bearer <token>"
|
||||
|
||||
# reset to defaults
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/relays/reset \
|
||||
-H "Authorization: Bearer <token>"
|
||||
```
|
||||
|
||||
### Enabling CORS
|
||||
|
||||
Cross‑origin requests are disabled by default. Set `SEEDPASS_CORS_ORIGINS` to a comma‑separated list of allowed origins before starting the API:
|
||||
|
||||
```bash
|
||||
SEEDPASS_CORS_ORIGINS=http://localhost:3000 seedpass api start
|
||||
```
|
||||
|
||||
Browsers can then call the API from the specified origins, for example using JavaScript:
|
||||
|
||||
```javascript
|
||||
fetch('http://127.0.0.1:8000/api/v1/entry?query=email', {
|
||||
headers: { Authorization: 'Bearer <token>' }
|
||||
});
|
||||
```
|
||||
|
||||
Without CORS enabled, only same‑origin or command‑line tools like `curl` can access the API.
|
||||
@@ -1,596 +0,0 @@
|
||||
# SeedPass JSON Entry Management and Extensibility
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Introduction](#introduction)
|
||||
- [JSON Schema for Individual Entries](#json-schema-for-individual-entries)
|
||||
- [General Structure](#general-structure)
|
||||
- [Field Descriptions](#field-descriptions)
|
||||
- [Example Entries](#example-entries)
|
||||
- [Handling `kind` Types and Extensibility](#handling-kind-types-and-extensibility)
|
||||
- [Extensible JSON Schema Design](#extensible-json-schema-design)
|
||||
- [Ensuring Backward Compatibility](#ensuring-backward-compatibility)
|
||||
- [Best Practices for Adding New Kinds](#best-practices-for-adding-new-kinds)
|
||||
- [Adding New `kind` Types](#adding-new-kind-types)
|
||||
- [Example: Adding `cryptocurrency_wallet`](#example-adding-cryptocurrency_wallet)
|
||||
- [Backup and Rollback Mechanism](#backup-and-rollback-mechanism)
|
||||
- [Security Considerations](#security-considerations)
|
||||
- [Updated Roadmap](#updated-roadmap)
|
||||
- [Phase 1: Core Functionality and Security Enhancements](#phase-1-core-functionality-and-security-enhancements)
|
||||
- [Phase 2: Enhanced Security and Data Management](#phase-2-enhanced-security-and-data-management)
|
||||
- [Phase 3: Advanced CLI Functionalities](#phase-3-advanced-cli-functionalities)
|
||||
- [Phase 4: Data Management Enhancements and Integrations](#phase-4-data-management-enhancements-and-integrations)
|
||||
- [Phase 5: Documentation, Testing, and Finalization](#phase-5-documentation-testing-and-finalization)
|
||||
- [Future Phases (Beyond Initial Roadmap)](#future-phases-beyond-initial-roadmap)
|
||||
- [Summary](#summary)
|
||||
- [Contact](#contact)
|
||||
|
||||
---
|
||||
|
||||
## Introduction
|
||||
|
||||
**SeedPass** is a secure password generator and manager leveraging **Bitcoin's BIP-85 standard** and integrating with the **Nostr network** for decentralized synchronization. Instead of pushing one large index file, SeedPass posts **snapshot chunks** of the index followed by lightweight **delta events** whenever changes occur. This chunked approach improves reliability and keeps bandwidth usage minimal. To enhance modularity, scalability, and security, SeedPass stores all entries in a single encrypted index file named `seedpass_entries_db.json.enc`. This document outlines the entry management system, ensuring that new `kind` types can be added seamlessly without disrupting existing functionalities.
|
||||
|
||||
---
|
||||
|
||||
## Index File Format
|
||||
|
||||
All entries belonging to a seed profile are stored in an encrypted file named `seedpass_entries_db.json.enc`. This index uses `schema_version` `3` and contains an `entries` object keyed by numeric identifiers.
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 3,
|
||||
"entries": {
|
||||
"0": {
|
||||
"label": "example.com",
|
||||
"length": 8,
|
||||
"username": "user",
|
||||
"url": "https://example.com",
|
||||
"archived": false,
|
||||
"type": "password",
|
||||
"kind": "password",
|
||||
"notes": "",
|
||||
"custom_fields": [],
|
||||
"origin": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## JSON Schema for Individual Entries
|
||||
|
||||
Each entry is stored within `seedpass_entries_db.json.enc` under the `entries` dictionary. The structure supports diverse entry types (`kind`) and allows for future expansions.
|
||||
|
||||
### General Structure
|
||||
|
||||
```json
|
||||
{
|
||||
"label": "Example",
|
||||
"length": 8,
|
||||
"username": "[email protected]",
|
||||
"url": "https://example.com",
|
||||
"archived": false,
|
||||
"type": "password",
|
||||
"kind": "password",
|
||||
"notes": "",
|
||||
"custom_fields": [],
|
||||
"origin": "",
|
||||
"tags": [],
|
||||
"index": 0
|
||||
}
|
||||
```
|
||||
|
||||
### Field Descriptions
|
||||
|
||||
- **label** (`string`): Descriptive name for the entry (e.g., website or service).
|
||||
- **length** (`integer`, optional): Desired password length for generated passwords.
|
||||
- **username** (`string`, optional): Username associated with the entry.
|
||||
- **url** (`string`, optional): Website or service URL.
|
||||
- **archived** (`boolean`): Marks the entry as archived when `true`.
|
||||
- **type** (`string`): The entry type (`password`, `totp`, `ssh`, `seed`, `pgp`, `nostr`, `note`, `key_value`).
|
||||
- **kind** (`string`): Synonym for `type` kept for backward compatibility.
|
||||
- **notes** (`string`): Free-form notes.
|
||||
- **custom_fields** (`array`, optional): Additional user-defined fields.
|
||||
- **origin** (`string`, optional): Source identifier for imported data.
|
||||
- **value** (`string`, optional): For `key_value` entries, stores the secret value.
|
||||
- **key** (`string`, optional): Name of the key for `key_value` entries.
|
||||
- **index** (`integer`, optional): BIP-85 derivation index for entries that derive material from a seed.
|
||||
- **word_count** (`integer`, managed_account only): Number of words in the child seed. Managed accounts always use `12`.
|
||||
- **fingerprint** (`string`, managed_account only): Identifier of the child profile, used for its directory name.
|
||||
- **tags** (`array`, optional): Category labels to aid in organization and search.
|
||||
|
||||
#### Password Policy Fields
|
||||
|
||||
- **min_uppercase** (`integer`, default `2`): Minimum required uppercase letters.
|
||||
- **min_lowercase** (`integer`, default `2`): Minimum required lowercase letters.
|
||||
- **min_digits** (`integer`, default `2`): Minimum required digits.
|
||||
- **min_special** (`integer`, default `2`): Minimum required special characters.
|
||||
- **include_special_chars** (`boolean`, default `true`): Enable or disable any punctuation in generated passwords.
|
||||
- **allowed_special_chars** (`string`, optional): Restrict punctuation to this exact set.
|
||||
- **special_mode** (`string`, default `"standard"`): Choose `"safe"` for the `SAFE_SPECIAL_CHARS` set (`!@#$%^*-_+=?`), otherwise the full `string.punctuation` is used.
|
||||
- **exclude_ambiguous** (`boolean`, default `false`): Omit confusing characters like `O0Il1`.
|
||||
Example:
|
||||
|
||||
```json
|
||||
"custom_fields": [
|
||||
{"name": "account_id", "value": "123"},
|
||||
{"name": "recovery_hint", "value": "mother's maiden name"}
|
||||
]
|
||||
```
|
||||
|
||||
### Example Entries
|
||||
|
||||
#### 1. Generated Password
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 0,
|
||||
"index_num": 0,
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "generated_password",
|
||||
"data": {
|
||||
"title": "Example Website",
|
||||
"username": "[email protected]",
|
||||
"email": "[email protected]",
|
||||
"url": "https://example.com",
|
||||
"password": "<encrypted_password>"
|
||||
},
|
||||
"custom_fields": [
|
||||
{"name": "department", "value": "finance"}
|
||||
],
|
||||
"tags": ["work"],
|
||||
"timestamp": "2024-04-27T12:34:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:34:56Z",
|
||||
"updated_at": "2024-04-27T12:34:56Z",
|
||||
"checksum": "abc123def456"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Stored Password
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 1,
|
||||
"index_num": "q1wec4d426fs",
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "stored_password",
|
||||
"data": {
|
||||
"title": "Another Service",
|
||||
"username": "another_user",
|
||||
"password": "<encrypted_password>"
|
||||
},
|
||||
"timestamp": "2024-04-27T12:35:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:35:56Z",
|
||||
"updated_at": "2024-04-27T12:35:56Z",
|
||||
"checksum": "def789ghi012"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Password Entry with Policy Overrides
|
||||
|
||||
```json
|
||||
{
|
||||
"label": "Custom Policy",
|
||||
"length": 16,
|
||||
"include_special_chars": false,
|
||||
"exclude_ambiguous": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. Managed User
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 2,
|
||||
"index_num": "a1b2c3d4e5f6",
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "managed_user",
|
||||
"data": {
|
||||
"users_password": "<encrypted_users_password>"
|
||||
},
|
||||
"timestamp": "2024-04-27T12:36:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:36:56Z",
|
||||
"updated_at": "2024-04-27T12:36:56Z",
|
||||
"checksum": "ghi345jkl678"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. 12 Word Seed
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 3,
|
||||
"index_num": "f7g8h9i0j1k2",
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "12_word_seed",
|
||||
"data": {
|
||||
"seed_phrase": "<encrypted_seed_phrase>"
|
||||
},
|
||||
"timestamp": "2024-04-27T12:37:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:37:56Z",
|
||||
"updated_at": "2024-04-27T12:37:56Z",
|
||||
"checksum": "jkl901mno234"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 5. Nostr Keys
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 4,
|
||||
"index_num": "l3m4n5o6p7q8",
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "nostr_keys",
|
||||
"data": {
|
||||
"public_key": "<public_key>",
|
||||
"private_key": "<encrypted_private_key>"
|
||||
},
|
||||
"timestamp": "2024-04-27T12:38:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:38:56Z",
|
||||
"updated_at": "2024-04-27T12:38:56Z",
|
||||
"checksum": "mno567pqr890"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. Note
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 5,
|
||||
"index_num": "r9s0t1u2v3w4",
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "note",
|
||||
"data": {
|
||||
"content": "This is a secure note.",
|
||||
"tags": ["personal", "secure"]
|
||||
},
|
||||
"timestamp": "2024-04-27T12:39:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:39:56Z",
|
||||
"updated_at": "2024-04-27T12:39:56Z",
|
||||
"checksum": "pqr345stu678"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Key/Value
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 6,
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "key_value",
|
||||
"data": {
|
||||
"key": "api_key",
|
||||
"value": "<encrypted_value>"
|
||||
},
|
||||
"tags": ["api"],
|
||||
"timestamp": "2024-04-27T12:40:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
#### 8. Managed Account
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 7,
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "managed_account",
|
||||
"data": {
|
||||
"account": "[email protected]",
|
||||
"password": "<encrypted_password>"
|
||||
},
|
||||
"timestamp": "2024-04-27T12:41:56Z"
|
||||
}
|
||||
```
|
||||
|
||||
Managed accounts store a child seed derived from the parent profile. The entry is saved under
|
||||
`.seedpass/<parent_fp>/accounts/<child_fp>` where `<child_fp>` is the managed account's
|
||||
fingerprint. When loaded, the CLI displays a breadcrumb like `<parent_fp> > Managed Account > <child_fp>`.
|
||||
Press **Enter** on the main menu to exit back to the parent profile.
|
||||
|
||||
The `key` field is purely descriptive, while `value` holds the sensitive string
|
||||
such as an API token. Notes and custom fields may also be included alongside the
|
||||
standard metadata.
|
||||
|
||||
---
|
||||
|
||||
## Handling `kind` Types and Extensibility
|
||||
|
||||
### Extensible JSON Schema Design
|
||||
|
||||
The JSON schema is designed to be **extensible** and **forward-compatible**, allowing new `kind` types to be added without impacting existing functionalities.
|
||||
|
||||
#### a. Core Structure
|
||||
|
||||
Each entry is encapsulated in its own JSON file with a standardized structure:
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 0,
|
||||
"index_num": 0,
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "generated_password",
|
||||
"data": {
|
||||
// Fields specific to the kind
|
||||
},
|
||||
"timestamp": "2024-04-27T12:34:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:34:56Z",
|
||||
"updated_at": "2024-04-27T12:34:56Z",
|
||||
"checksum": "<checksum_value>"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### b. The `kind` Field
|
||||
|
||||
- **Purpose:** Specifies the type of entry.
|
||||
- **Flexibility:** As a simple string identifier, new `kind` values can be introduced without altering the existing schema.
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"kind": "cryptocurrency_wallet"
|
||||
```
|
||||
|
||||
#### c. The `data` Object
|
||||
|
||||
- **Purpose:** Contains fields specific to the `kind`.
|
||||
- **Extensibility:** Each `kind` can define its unique set of fields without affecting others.
|
||||
|
||||
**Example for a New Kind (`cryptocurrency_wallet`):**
|
||||
```json
|
||||
"data": {
|
||||
"wallet_name": "My Bitcoin Wallet",
|
||||
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
|
||||
"private_key": "<encrypted_private_key>"
|
||||
}
|
||||
```
|
||||
|
||||
### Ensuring Backward Compatibility
|
||||
|
||||
To maintain compatibility as new `kind` types are introduced, implement the following practices:
|
||||
|
||||
#### a. Graceful Handling of Unknown Kinds
|
||||
|
||||
- **Implementation:** When encountering an unrecognized `kind`, handle it gracefully by ignoring the entry, logging a warning, or providing a default handling mechanism.
|
||||
- **Benefit:** Prevents the application from crashing or misbehaving due to unrecognized `kind` types.
|
||||
|
||||
**Pseudo-Code Example:**
|
||||
```python
|
||||
def process_entry(entry):
|
||||
kind = entry.get("kind")
|
||||
data = entry.get("data")
|
||||
fingerprint = entry.get("fingerprint")
|
||||
|
||||
if kind == "generated_password":
|
||||
handle_generated_password(data, fingerprint)
|
||||
elif kind == "stored_password":
|
||||
handle_stored_password(data, fingerprint)
|
||||
# ... other known kinds ...
|
||||
else:
|
||||
log_warning(f"Unknown kind: {kind}. Skipping entry.")
|
||||
```
|
||||
|
||||
#### b. Versioning the Schema
|
||||
|
||||
- **Implementation:** Introduce a `schema_version` or `seedpass_version` field to indicate the version of the JSON schema being used.
|
||||
- **Benefit:** Facilitates future updates and migrations by clearly identifying the schema version.
|
||||
|
||||
**Example:**
|
||||
```json
|
||||
"seedpass_version": "1.0.0"
|
||||
```
|
||||
|
||||
#### c. Documentation and Standards
|
||||
|
||||
- **Maintain Clear Documentation:** Keep comprehensive documentation for each `kind`, detailing required and optional fields.
|
||||
- **Adhere to Standards:** Follow consistent naming conventions and data types to ensure uniformity across different `kind` types.
|
||||
|
||||
### Best Practices for Adding New Kinds
|
||||
|
||||
To ensure seamless integration of new `kind` types in the future, consider the following best practices:
|
||||
|
||||
#### a. Consistent Naming Conventions
|
||||
|
||||
- **Use Clear and Descriptive Names:** Aids in readability and maintenance.
|
||||
- **Avoid Reserved Keywords:** Ensure `kind` names do not clash with existing or future reserved keywords within the application or JSON standards.
|
||||
|
||||
#### b. Modular Code Architecture
|
||||
|
||||
- **Separate Handlers:** Implement separate functions or modules for handling each `kind`. Promotes code modularity and easier maintenance.
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
# handlers.py
|
||||
|
||||
def handle_generated_password(data, fingerprint):
|
||||
# Implementation
|
||||
|
||||
def handle_stored_password(data, fingerprint):
|
||||
# Implementation
|
||||
|
||||
def handle_cryptocurrency_wallet(data, fingerprint):
|
||||
# Implementation
|
||||
```
|
||||
|
||||
#### c. Validation and Error Handling
|
||||
|
||||
- **Validate Data Fields:** Ensure each `kind` has the necessary fields before processing.
|
||||
- **Handle Missing or Extra Fields:** Implement logic to manage incomplete or unexpected data gracefully.
|
||||
|
||||
**Example:**
|
||||
```python
|
||||
def handle_cryptocurrency_wallet(data, fingerprint):
|
||||
required_fields = ["wallet_name", "address", "private_key"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
raise ValueError(f"Missing required field '{field}' in cryptocurrency_wallet entry.")
|
||||
# Proceed with processing
|
||||
```
|
||||
|
||||
#### d. Backward Compatibility Testing
|
||||
|
||||
- **Automated Tests:** Develop tests that verify the application's ability to handle both existing and new `kind` types.
|
||||
- **Regression Testing:** Ensure adding new kinds does not inadvertently affect existing functionalities.
|
||||
|
||||
---
|
||||
|
||||
## Adding New `kind` Types
|
||||
|
||||
Adding new `kind` types is straightforward due to the extensible JSON schema design. Below is a step-by-step guide to adding a new `kind` without affecting existing functionalities.
|
||||
|
||||
### Example: Adding `cryptocurrency_wallet`
|
||||
|
||||
#### a. Define the New Kind Structure
|
||||
|
||||
Create a JSON file following the standardized structure with the new `kind` value.
|
||||
|
||||
```json
|
||||
{
|
||||
"entry_num": 6,
|
||||
"index_num": "x1y2z3a4b5c6",
|
||||
"fingerprint": "a1b2c3d4",
|
||||
"kind": "cryptocurrency_wallet",
|
||||
"data": {
|
||||
"wallet_name": "My Bitcoin Wallet",
|
||||
"address": "1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa",
|
||||
"private_key": "<encrypted_private_key>"
|
||||
},
|
||||
"timestamp": "2024-04-27T12:40:56Z",
|
||||
"metadata": {
|
||||
"created_at": "2024-04-27T12:40:56Z",
|
||||
"updated_at": "2024-04-27T12:40:56Z",
|
||||
"checksum": "stu901vwx234"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### b. Update the Application to Handle the New Kind
|
||||
|
||||
**Implement Handler Function:**
|
||||
```python
|
||||
def handle_cryptocurrency_wallet(data, fingerprint):
|
||||
wallet_name = data.get("wallet_name")
|
||||
address = data.get("address")
|
||||
private_key = decrypt(data.get("private_key"))
|
||||
# Process the cryptocurrency wallet entry
|
||||
# e.g., store in memory, display to user, etc.
|
||||
```
|
||||
|
||||
**Integrate the Handler:**
|
||||
```python
|
||||
def process_entry(entry):
|
||||
kind = entry.get("kind")
|
||||
data = entry.get("data")
|
||||
fingerprint = entry.get("fingerprint")
|
||||
|
||||
if kind == "generated_password":
|
||||
handle_generated_password(data, fingerprint)
|
||||
elif kind == "stored_password":
|
||||
handle_stored_password(data, fingerprint)
|
||||
elif kind == "cryptocurrency_wallet":
|
||||
handle_cryptocurrency_wallet(data, fingerprint)
|
||||
# ... other known kinds ...
|
||||
else:
|
||||
log_warning(f"Unknown kind: {kind}. Skipping entry.")
|
||||
```
|
||||
|
||||
#### c. No Impact on Existing Kinds
|
||||
|
||||
Existing kinds such as `generated_password`, `stored_password`, etc., continue to operate without any changes. The introduction of `cryptocurrency_wallet` is additive and does not interfere with the processing of other kinds.
|
||||
|
||||
---
|
||||
|
||||
## Backup and Rollback Mechanism
|
||||
|
||||
To ensure data integrity and provide recovery options, SeedPass implements a robust backup and rollback system within the **Fingerprint-Based Backup and Local Storage** framework.
|
||||
|
||||
### Backup Directory Structure
|
||||
|
||||
All backups are organized based on fingerprints, ensuring that each seed's data remains isolated and secure.
|
||||
|
||||
```
|
||||
~/.seedpass/
|
||||
├── a1b2c3d4/
|
||||
│ ├── entries/
|
||||
│ │ ├── entry_0.json
|
||||
│ │ ├── entry_1.json
|
||||
│ │ └── ...
|
||||
│ ├── backups/
|
||||
│ │ ├── entry_0_v1.json
|
||||
│ │ ├── entry_0_v2.json
|
||||
│ │ ├── entry_1_v1.json
|
||||
│ │ └── ...
|
||||
│ ├── parent_seed.enc
|
||||
│ ├── seedpass_entries_db_checksum.txt
|
||||
│ └── seedpass_entries_db.json
|
||||
├── b5c6d7e8/
|
||||
│ ├── entries/
|
||||
│ │ ├── entry_0.json
|
||||
│ │ ├── entry_1.json
|
||||
│ │ └── ...
|
||||
│ ├── backups/
|
||||
│ │ ├── entry_0_v1.json
|
||||
│ │ ├── entry_0_v2.json
|
||||
│ │ ├── entry_1_v1.json
|
||||
│ │ └── ...
|
||||
│ ├── parent_seed.enc
|
||||
│ ├── seedpass_entries_db_checksum.txt
|
||||
│ └── seedpass_entries_db.json
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Backup Process
|
||||
|
||||
1. **Upon Modifying an Entry:**
|
||||
- The current version of the entry is copied to the `backups/` directory within the corresponding fingerprint folder with a version suffix (e.g., `entry_0_v1.json`).
|
||||
- The modified entry is saved in the `entries/` directory within the same fingerprint folder.
|
||||
|
||||
2. **Versioning:**
|
||||
- Each backup file includes a version number to track changes over time.
|
||||
|
||||
### Rollback Functionality
|
||||
|
||||
- **Restoring an Entry:**
|
||||
- Users can select a backup version from the `backups/` directory within the specific fingerprint folder.
|
||||
- The selected backup file is copied back to the `entries/` directory, replacing the current version.
|
||||
|
||||
**Example Command:**
|
||||
```bash
|
||||
seedpass rollback --fingerprint a1b2c3d4 --file entry_0_v1.json
|
||||
```
|
||||
|
||||
**Example Directory Structure After Rollback:**
|
||||
```
|
||||
~/.seedpass/
|
||||
├── a1b2c3d4/
|
||||
│ ├── entries/
|
||||
│ │ ├── entry_0.json # Restored from entry_0_v1.json
|
||||
│ │ ├── entry_1.json
|
||||
│ │ └── ...
|
||||
│ ├── backups/
|
||||
│ │ ├── entry_0_v1.json
|
||||
│ │ ├── entry_0_v2.json
|
||||
│ │ ├── entry_1_v1.json
|
||||
│ │ └── ...
|
||||
│ ├── parent_seed.enc
|
||||
│ ├── seedpass_script_checksum.txt
|
||||
│ ├── seedpass_entries_db_checksum.txt
|
||||
│ └── seedpass_entries_db.json
|
||||
├── ...
|
||||
```
|
||||
|
||||
*Note: Restoring a backup overwrites the current entry. Ensure that you intend to revert to the selected backup before proceeding.*
|
||||
@@ -1,49 +0,0 @@
|
||||
# Index Migrations
|
||||
|
||||
SeedPass stores its password index in an encrypted JSON file. Each index contains
|
||||
a `schema_version` field so the application knows how to upgrade older files.
|
||||
|
||||
> **Note:** Recent releases derive passwords and other artifacts using a new deterministic algorithm that works consistently across Python versions. Artifacts produced with older versions will not match outputs from this release and must be regenerated.
|
||||
|
||||
## How migrations work
|
||||
|
||||
When the vault loads the index, `Vault.load_index()` checks the version and
|
||||
applies migrations defined in `password_manager/migrations.py`. The
|
||||
`apply_migrations()` function iterates through registered migrations until the
|
||||
file reaches `LATEST_VERSION`.
|
||||
|
||||
If an old file lacks `schema_version`, it is treated as version 0 and upgraded
|
||||
to the latest format. Attempting to load an index from a future version will
|
||||
raise an error.
|
||||
|
||||
## Upgrading an index
|
||||
|
||||
1. The JSON is decrypted and parsed.
|
||||
2. `apply_migrations()` applies any necessary steps, such as injecting the
|
||||
`schema_version` field on first upgrade.
|
||||
3. After migration, the updated index is saved back to disk.
|
||||
|
||||
This process happens automatically; users only need to open their vault to
|
||||
upgrade older indices.
|
||||
|
||||
### Legacy Fernet migration
|
||||
|
||||
Older versions stored the vault index in a file named
|
||||
`seedpass_passwords_db.json.enc` encrypted with Fernet. When opening such a
|
||||
vault, SeedPass now automatically decrypts the legacy file, re‑encrypts it using
|
||||
AES‑GCM, and saves it under the new name `seedpass_entries_db.json.enc`.
|
||||
The original Fernet file is preserved as
|
||||
`seedpass_entries_db.json.enc.fernet` and the legacy checksum file, if present,
|
||||
is renamed to `seedpass_entries_db_checksum.txt.fernet`.
|
||||
|
||||
No additional command is required – simply open your existing vault and the
|
||||
conversion happens transparently.
|
||||
|
||||
### Parent seed backup migration
|
||||
|
||||
If your vault contains a `parent_seed.enc` file that was encrypted with Fernet,
|
||||
SeedPass performs a similar upgrade. Upon loading the vault, the application
|
||||
decrypts the old file, re‑encrypts it with AES‑GCM, and writes the result back to
|
||||
`parent_seed.enc`. The legacy Fernet file is preserved as
|
||||
`parent_seed.enc.fernet` so you can revert if needed. No manual steps are
|
||||
required – simply unlock your vault and the conversion runs automatically.
|
||||
@@ -1,29 +0,0 @@
|
||||
# Packaging the GUI with Briefcase
|
||||
|
||||
This project uses [BeeWare's Briefcase](https://beeware.org) to generate
|
||||
platform‑native installers. Once your development environment is set up,
|
||||
package the GUI by running the following commands from the repository root:
|
||||
|
||||
```bash
|
||||
# Create the application scaffold for your platform
|
||||
briefcase create
|
||||
|
||||
# Compile dependencies and produce a distributable bundle
|
||||
briefcase build
|
||||
|
||||
# Run the packaged application
|
||||
briefcase run
|
||||
```
|
||||
|
||||
## Command Overview
|
||||
|
||||
- **`briefcase create`** — generates the project scaffold for your
|
||||
operating system. Run this once per platform.
|
||||
- **`briefcase build`** — compiles dependencies and produces the
|
||||
distributable bundle.
|
||||
- **`briefcase run`** — launches the packaged application so you can test
|
||||
it locally.
|
||||
|
||||
After the initial creation step you can repeatedly run `briefcase build`
|
||||
followed by `briefcase run` to test your packaged application on Windows,
|
||||
macOS or Linux.
|
||||
@@ -1,165 +0,0 @@
|
||||
# BeeWare GUI Adapter
|
||||
|
||||
SeedPass ships with a proof-of-concept graphical interface built using [BeeWare](https://beeware.org). The GUI interacts with the same core services as the CLI by instantiating wrappers around `PasswordManager`.
|
||||
|
||||
|
||||
## Getting Started with the GUI
|
||||
|
||||
After installing the project dependencies, launch the desktop interface with one
|
||||
of the following commands:
|
||||
|
||||
```bash
|
||||
seedpass gui
|
||||
python -m seedpass_gui
|
||||
seedpass-gui
|
||||
```
|
||||
|
||||
GUI dependencies are optional. Install them alongside SeedPass with:
|
||||
|
||||
```bash
|
||||
pip install "seedpass[gui]"
|
||||
|
||||
# or when working from a local checkout
|
||||
pip install -e .[gui]
|
||||
```
|
||||
|
||||
After installing the optional GUI extras, add the BeeWare backend for your
|
||||
platform:
|
||||
|
||||
```bash
|
||||
# Linux
|
||||
pip install toga-gtk
|
||||
|
||||
# If installation fails with cairo errors, install libcairo2-dev or the
|
||||
# cairo development package using your distro's package manager.
|
||||
|
||||
# Windows
|
||||
pip install toga-winforms
|
||||
|
||||
# macOS
|
||||
pip install toga-cocoa
|
||||
```
|
||||
|
||||
The GUI shares the same encrypted vault and configuration as the command line tool.
|
||||
|
||||
To generate a packaged binary, run `briefcase build` (after the initial `briefcase create`).
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
core["seedpass.core"]
|
||||
cli["CLI"]
|
||||
api["FastAPI server"]
|
||||
gui["BeeWare GUI"]
|
||||
ext["Browser Extension"]
|
||||
|
||||
cli --> core
|
||||
gui --> core
|
||||
api --> core
|
||||
ext --> api
|
||||
```
|
||||
|
||||
## VaultService and EntryService
|
||||
|
||||
`VaultService` provides thread-safe access to vault operations like exporting, importing, unlocking and locking the vault. `EntryService` exposes methods for listing, searching and modifying entries. Both classes live in `seedpass.core.api` and hold a `PasswordManager` instance protected by a `threading.Lock` to ensure safe concurrent access.
|
||||
|
||||
```python
|
||||
class VaultService:
|
||||
"""Thread-safe wrapper around vault operations."""
|
||||
def __init__(self, manager: PasswordManager) -> None:
|
||||
self._manager = manager
|
||||
self._lock = Lock()
|
||||
```
|
||||
|
||||
```python
|
||||
class EntryService:
|
||||
"""Thread-safe wrapper around entry operations."""
|
||||
def __init__(self, manager: PasswordManager) -> None:
|
||||
self._manager = manager
|
||||
self._lock = Lock()
|
||||
```
|
||||
|
||||
## BeeWare Windows
|
||||
|
||||
The GUI defines two main windows in `src/seedpass_gui/app.py`. `LockScreenWindow` prompts for the master password and then opens `MainWindow` to display the vault entries.
|
||||
|
||||
```python
|
||||
class LockScreenWindow(toga.Window):
|
||||
"""Window prompting for the master password."""
|
||||
def __init__(self, app: SeedPassApp, vault: VaultService, entries: EntryService) -> None:
|
||||
super().__init__("Unlock Vault")
|
||||
self.app = app
|
||||
self.vault = vault
|
||||
self.entries = entries
|
||||
...
|
||||
```
|
||||
|
||||
```python
|
||||
class MainWindow(toga.Window):
|
||||
"""Main application window showing vault entries."""
|
||||
def __init__(self, app: SeedPassApp, vault: VaultService, entries: EntryService) -> None:
|
||||
super().__init__("SeedPass")
|
||||
self.app = app
|
||||
self.vault = vault
|
||||
self.entries = entries
|
||||
...
|
||||
```
|
||||
|
||||
Each window receives the service instances and calls methods such as `vault.unlock()` or `entries.add_entry()` when buttons are pressed. This keeps the UI thin while reusing the core logic.
|
||||
|
||||
## Asynchronous Synchronization
|
||||
|
||||
`PasswordManager` performs network synchronization with Nostr using `asyncio`. Methods like `start_background_vault_sync()` create a coroutine that calls `sync_vault_async()` in a background thread or task without blocking the UI.
|
||||
|
||||
```python
|
||||
async def sync_vault_async(self, alt_summary: str | None = None) -> dict[str, list[str] | str] | None:
|
||||
"""Publish the current vault contents to Nostr and return event IDs."""
|
||||
...
|
||||
```
|
||||
|
||||
```python
|
||||
def start_background_vault_sync(self, alt_summary: str | None = None) -> None:
|
||||
if getattr(self, "offline_mode", False):
|
||||
return
|
||||
def _worker() -> None:
|
||||
asyncio.run(self.sync_vault_async(alt_summary=alt_summary))
|
||||
try:
|
||||
loop = asyncio.get_running_loop()
|
||||
except RuntimeError:
|
||||
threading.Thread(target=_worker, daemon=True).start()
|
||||
else:
|
||||
asyncio.create_task(self.sync_vault_async(alt_summary=alt_summary))
|
||||
```
|
||||
|
||||
This approach ensures synchronization happens asynchronously whether the GUI is running inside or outside an existing event loop.
|
||||
|
||||
## Relay Manager and Status Bar
|
||||
|
||||
The *Relays* button opens a dialog for adding or removing Nostr relay URLs. The
|
||||
status bar at the bottom of the main window shows when the last synchronization
|
||||
completed. It updates automatically when `sync_started` and `sync_finished`
|
||||
events are published on the internal pubsub bus.
|
||||
|
||||
When a ``vault_locked`` event is emitted, the GUI automatically returns to the
|
||||
lock screen so the session can be reopened with the master password.
|
||||
|
||||
|
||||
## Event Handling
|
||||
|
||||
The GUI subscribes to a few core events so the interface reacts automatically when the vault changes state. When `MainWindow` is created it registers callbacks for `sync_started`, `sync_finished` and `vault_locked` on the global pubsub `bus`:
|
||||
|
||||
```python
|
||||
bus.subscribe("sync_started", self.sync_started)
|
||||
bus.subscribe("sync_finished", self.sync_finished)
|
||||
bus.subscribe("vault_locked", self.vault_locked)
|
||||
```
|
||||
|
||||
Each handler updates the status bar or returns to the lock screen. The `cleanup` method removes these hooks when the window closes:
|
||||
|
||||
```python
|
||||
def cleanup(self, *args: object, **kwargs: object) -> None:
|
||||
bus.unsubscribe("sync_started", self.sync_started)
|
||||
bus.unsubscribe("sync_finished", self.sync_finished)
|
||||
bus.unsubscribe("vault_locked", self.vault_locked)
|
||||
```
|
||||
|
||||
The [TOTP window](../../02-api_reference.md#totp) demonstrates how such events keep the UI fresh: it shows live two-factor codes that reflect the latest vault data after synchronization.
|
||||
@@ -1,610 +0,0 @@
|
||||
# SeedPass
|
||||
|
||||
**SeedPass** is a secure password generator and manager built on **Bitcoin's BIP-85 standard**. It uses deterministic key derivation to generate **passwords that are never stored**, but can be easily regenerated when needed. By integrating with the **Nostr network**, SeedPass compresses your encrypted vault and splits it into 50 KB chunks. Each chunk is published as a parameterised replaceable event (`kind 30071`), with a manifest (`kind 30070`) describing the snapshot and deltas (`kind 30072`) capturing changes between snapshots. This allows secure password recovery across devices without exposing your data.
|
||||
|
||||
[Tip Jar](https://nostrtipjar.netlify.app/?n=npub16y70nhp56rwzljmr8jhrrzalsx5x495l4whlf8n8zsxww204k8eqrvamnp)
|
||||
|
||||
---
|
||||
|
||||
**⚠️ Disclaimer**
|
||||
|
||||
This software was not developed by an experienced security expert and should be used with caution. There may be bugs and missing features. Each vault chunk is limited to 50 KB and SeedPass periodically publishes a new snapshot to keep accumulated deltas small. The security of the program's memory management and logs has not been evaluated and may leak sensitive information. Loss or exposure of the parent seed places all derived passwords, accounts, and other artifacts at risk.
|
||||
|
||||
**🚨 Breaking Change**
|
||||
|
||||
Recent releases derive passwords and other artifacts using a fully deterministic algorithm that behaves consistently across Python versions. This improvement means artifacts generated with earlier versions of SeedPass will not match those produced now. Regenerate any previously derived data or retain the old version if you need to reproduce older passwords or keys.
|
||||
|
||||
---
|
||||
### Supported OS
|
||||
|
||||
✔ Windows 10/11 • macOS 12+ • Any modern Linux
|
||||
SeedPass now uses the `portalocker` library for cross-platform file locking. No WSL or Cygwin required.
|
||||
|
||||
```mermaid
|
||||
graph TD
|
||||
core(seedpass.core)
|
||||
cli(CLI/TUI)
|
||||
gui(BeeWare GUI)
|
||||
ext(Browser extension)
|
||||
cli --> core
|
||||
gui --> core
|
||||
ext --> core
|
||||
```
|
||||
|
||||
SeedPass uses a modular design with a single core library that handles all
|
||||
security-critical logic. The current CLI/TUI adapter communicates with
|
||||
`seedpass.core`, and future interfaces like a BeeWare GUI and a browser
|
||||
extension can hook into the same layer. This architecture keeps the codebase
|
||||
maintainable while enabling a consistent experience on multiple platforms.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Features](#features)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Installation](#installation)
|
||||
- [1. Clone the Repository](#1-clone-the-repository)
|
||||
- [2. Create a Virtual Environment](#2-create-a-virtual-environment)
|
||||
- [3. Activate the Virtual Environment](#3-activate-the-virtual-environment)
|
||||
- [4. Install Dependencies](#4-install-dependencies)
|
||||
- [Usage](#usage)
|
||||
- [Running the Application](#running-the-application)
|
||||
- [Managing Multiple Seeds](#managing-multiple-seeds)
|
||||
- [Additional Entry Types](#additional-entry-types)
|
||||
- [Recovery](#recovery)
|
||||
- [Security Considerations](#security-considerations)
|
||||
- [Contributing](#contributing)
|
||||
- [License](#license)
|
||||
- [Contact](#contact)
|
||||
|
||||
## Features
|
||||
|
||||
- **Deterministic Password Generation:** Utilize BIP-85 for generating deterministic and secure passwords.
|
||||
- **Encrypted Storage:** All seeds, login passwords, and sensitive index data are encrypted locally.
|
||||
- **Nostr Integration:** Post and retrieve your encrypted password index to/from the Nostr network.
|
||||
- **Chunked Snapshots:** Encrypted vaults are compressed and split into 50 KB chunks published as `kind 30071` events with a `kind 30070` manifest and `kind 30072` deltas. The manifest's `delta_since` field stores the UNIX timestamp of the latest delta event.
|
||||
- **Automatic Checksum Generation:** The script generates and verifies a SHA-256 checksum to detect tampering.
|
||||
- **Multiple Seed Profiles:** Manage separate seed profiles and switch between them seamlessly.
|
||||
- **Nested Managed Account Seeds:** SeedPass can derive nested managed account seeds.
|
||||
- **Interactive TUI:** Navigate through menus to add, retrieve, and modify entries as well as configure Nostr settings.
|
||||
- **SeedPass 2FA:** Generate TOTP codes with a real-time countdown progress bar.
|
||||
- **2FA Secret Issuance & Import:** Derive new TOTP secrets from your seed or import existing `otpauth://` URIs.
|
||||
- **Export 2FA Codes:** Save all stored TOTP entries to an encrypted JSON file for use with other apps.
|
||||
- **Display TOTP Codes:** Show all active 2FA codes with a countdown timer.
|
||||
- **Optional External Backup Location:** Configure a second directory where backups are automatically copied.
|
||||
- **Auto‑Lock on Inactivity:** Vault locks after a configurable timeout for additional security.
|
||||
- **Quick Unlock:** Optionally skip the password prompt after verifying once. Startup delay is unaffected.
|
||||
- **Secret Mode:** Copy retrieved passwords directly to your clipboard and automatically clear it after a delay.
|
||||
- **Tagging Support:** Organize entries with optional tags and find them quickly via search.
|
||||
- **Typed Search Results:** Searches display each entry's type for easier scanning.
|
||||
- **Manual Vault Export/Import:** Create encrypted backups or restore them using the CLI or API.
|
||||
- **Parent Seed Backup:** Securely save an encrypted copy of the master seed.
|
||||
- **Manual Vault Locking:** Instantly clear keys from memory when needed.
|
||||
- **Vault Statistics:** View counts for entries and other profile metrics.
|
||||
- **Change Master Password:** Rotate your encryption password at any time.
|
||||
- **Checksum Verification Utilities:** Verify or regenerate the script checksum.
|
||||
- **Relay Management:** List, add, remove or reset configured Nostr relays.
|
||||
- **Offline Mode (default):** SeedPass runs without network sync until you explicitly enable it.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Python 3.8+** (3.11 or 3.12 recommended): Install Python from [python.org](https://www.python.org/downloads/) and be sure to check **"Add Python to PATH"** during setup. Using Python 3.13 is currently discouraged because some dependencies do not ship wheels for it yet, which can cause build failures on Windows unless you install the Visual C++ Build Tools.
|
||||
*Windows only:* Install the [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and select the **C++ build tools** workload.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
### Quick Installer
|
||||
|
||||
Use the automated installer to download SeedPass and its dependencies in one step.
|
||||
If GTK packages are missing, the installer will try to install them using your
|
||||
system's package manager (`apt`, `yum`, `pacman`, or Homebrew).
|
||||
|
||||
**Linux and macOS:**
|
||||
```bash
|
||||
bash -c "$(curl -sSL https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.sh)"
|
||||
```
|
||||
*Install the beta branch:*
|
||||
```bash
|
||||
bash -c "$(curl -sSL https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.sh)" _ -b beta
|
||||
```
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; $scriptContent = (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.ps1'); & ([scriptblock]::create($scriptContent))
|
||||
```
|
||||
Before running the script, install **Python 3.11** or **3.12** from [python.org](https://www.python.org/downloads/windows/) and tick **"Add Python to PATH"**. You should also install the [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) with the **C++ build tools** workload so dependencies compile correctly.
|
||||
The Windows installer will attempt to install Git automatically if it is not already available. It also tries to
|
||||
install Python 3 using `winget`, `choco`, or `scoop` when Python is missing and recognizes the `py` launcher if `python`
|
||||
isn't on your PATH. If these tools are unavailable you'll see a link to download Python directly from
|
||||
<https://www.python.org/downloads/windows/>. When Python 3.13 or newer is detected without the Microsoft C++ build tools,
|
||||
the installer now attempts to download Python 3.12 automatically so you don't have to compile packages from source.
|
||||
|
||||
**Note:** If this fallback fails, install Python 3.12 manually or install the [Microsoft Visual C++ Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and rerun the installer.
|
||||
|
||||
#### Installer Dependency Checks
|
||||
|
||||
The installer verifies that core build tooling—C/C++ build tools, Rust, CMake, and the imaging/GTK libraries—are available before completing. Pass `--no-gui` to skip installing GUI packages. On Linux, ensure `xclip` or `wl-clipboard` is installed for clipboard support.
|
||||
|
||||
### Uninstall
|
||||
|
||||
Run the matching uninstaller if you need to remove a previous installation or clean up an old `seedpass` command:
|
||||
|
||||
**Linux and macOS:**
|
||||
```bash
|
||||
bash -c "$(curl -sSL https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/uninstall.sh)"
|
||||
```
|
||||
If the script warns that it couldn't remove an executable, delete that file manually.
|
||||
|
||||
**Windows (PowerShell):**
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; $scriptContent = (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/uninstall.ps1'); & ([scriptblock]::create($scriptContent))
|
||||
```
|
||||
|
||||
*Install the beta branch:*
|
||||
```powershell
|
||||
Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; $scriptContent = (New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/PR0M3TH3AN/SeedPass/main/scripts/install.ps1'); & ([scriptblock]::create($scriptContent)) -Branch beta
|
||||
```
|
||||
|
||||
### Manual Setup
|
||||
Follow these steps to set up SeedPass on your local machine.
|
||||
|
||||
### 1. Clone the Repository
|
||||
|
||||
First, clone the SeedPass repository from GitHub:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/PR0M3TH3AN/SeedPass.git
|
||||
```
|
||||
|
||||
Navigate to the project directory:
|
||||
|
||||
```bash
|
||||
cd SeedPass
|
||||
```
|
||||
|
||||
### 2. Create a Virtual Environment
|
||||
|
||||
It's recommended to use a virtual environment to manage your project's dependencies. Create a virtual environment named `venv`:
|
||||
|
||||
```bash
|
||||
python3 -m venv venv
|
||||
```
|
||||
|
||||
### 3. Activate the Virtual Environment
|
||||
|
||||
Activate the virtual environment using the appropriate command for your operating system.
|
||||
|
||||
- **On Linux and macOS:**
|
||||
|
||||
```bash
|
||||
source venv/bin/activate
|
||||
```
|
||||
|
||||
- **On Windows:**
|
||||
|
||||
```bash
|
||||
venv\Scripts\activate
|
||||
```
|
||||
|
||||
Once activated, your terminal prompt should be prefixed with `(venv)` indicating that the virtual environment is active.
|
||||
|
||||
### 4. Install Dependencies
|
||||
|
||||
Install the required Python packages and build dependencies using `pip`.
|
||||
When upgrading pip, use `python -m pip` inside the virtual environment so that pip can update itself cleanly:
|
||||
|
||||
```bash
|
||||
python -m pip install --upgrade pip
|
||||
python -m pip install --require-hashes -r requirements.lock
|
||||
python -m pip install -e .
|
||||
```
|
||||
|
||||
#### Linux Clipboard Support
|
||||
|
||||
On Linux, `pyperclip` relies on external utilities like `xclip` or `xsel`.
|
||||
SeedPass does not install these tools automatically. To use clipboard features
|
||||
such as secret mode, install **xclip** manually:
|
||||
|
||||
```bash
|
||||
sudo apt install xclip
|
||||
```
|
||||
|
||||
After installing `xclip`, restart SeedPass to enable clipboard support.
|
||||
|
||||
## Quick Start
|
||||
|
||||
After installing dependencies, activate your virtual environment and install
|
||||
the package so the `seedpass` command is available, then launch SeedPass and
|
||||
create a backup:
|
||||
|
||||
```bash
|
||||
# Start the application
|
||||
seedpass
|
||||
|
||||
# Export your index
|
||||
seedpass vault export --file "~/seedpass_backup.json"
|
||||
|
||||
# Later you can restore it
|
||||
seedpass vault import --file "~/seedpass_backup.json"
|
||||
# Import also performs a Nostr sync to pull any changes
|
||||
|
||||
# Quickly find or retrieve entries
|
||||
seedpass search "github"
|
||||
seedpass search --tags "work,personal"
|
||||
seedpass get "github"
|
||||
# Search results show the entry type, e.g. "1: Password - GitHub"
|
||||
# Retrieve a TOTP entry
|
||||
seedpass entry get "email"
|
||||
# The code is printed and copied to your clipboard
|
||||
|
||||
# Sort or filter the list view
|
||||
seedpass list --sort label
|
||||
seedpass list --filter totp
|
||||
|
||||
# Use the **Settings** menu to configure an extra backup directory
|
||||
# on an external drive.
|
||||
```
|
||||
|
||||
For additional command examples, see [01-getting-started/01-advanced_cli.md](01-getting-started/01-advanced_cli.md).
|
||||
Details on the REST API can be found in [01-getting-started/02-api_reference.md](01-getting-started/02-api_reference.md).
|
||||
|
||||
### Vault JSON Layout
|
||||
|
||||
The encrypted index file `seedpass_entries_db.json.enc` begins with `schema_version` `2` and stores an `entries` map keyed by entry numbers.
|
||||
|
||||
```json
|
||||
{
|
||||
"schema_version": 2,
|
||||
"entries": {
|
||||
"0": {
|
||||
"label": "example.com",
|
||||
"length": 8,
|
||||
"type": "password",
|
||||
"notes": ""
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Usage
|
||||
|
||||
After successfully installing the dependencies, launch the interactive TUI with:
|
||||
|
||||
```bash
|
||||
seedpass
|
||||
```
|
||||
|
||||
You can also run directly from the repository using:
|
||||
|
||||
```bash
|
||||
python src/main.py
|
||||
```
|
||||
|
||||
You can explore other CLI commands using:
|
||||
```bash
|
||||
seedpass --help
|
||||
```
|
||||
For a full list of commands see [01-getting-started/01-advanced_cli.md](01-getting-started/01-advanced_cli.md). The REST API is described in [01-getting-started/02-api_reference.md](01-getting-started/02-api_reference.md).
|
||||
|
||||
### Running the Application
|
||||
|
||||
1. **Start the Application:**
|
||||
|
||||
```bash
|
||||
seedpass
|
||||
```
|
||||
*(or `python src/main.py` if running directly from the repository)*
|
||||
|
||||
2. **Follow the Prompts:**
|
||||
|
||||
- **Seed Profile Selection:** If you have existing seed profiles, you'll be prompted to select one or add a new one.
|
||||
- **Enter Your Password:** This password is crucial as it is used to encrypt and decrypt your parent seed and seed index data.
|
||||
- **Select an Option:** Navigate through the menu by entering the number corresponding to your desired action.
|
||||
|
||||
Example menu:
|
||||
|
||||
```bash
|
||||
Select an option:
|
||||
1. Add Entry
|
||||
2. Retrieve Entry
|
||||
3. Search Entries
|
||||
4. List Entries
|
||||
5. Modify an Existing Entry
|
||||
6. 2FA Codes
|
||||
7. Settings
|
||||
|
||||
Enter your choice (1-7) or press Enter to exit:
|
||||
```
|
||||
|
||||
When choosing **Add Entry**, you can now select from:
|
||||
|
||||
- **Password**
|
||||
- **2FA (TOTP)**
|
||||
- **SSH Key**
|
||||
- **Seed Phrase**
|
||||
- **Nostr Key Pair**
|
||||
- **PGP Key**
|
||||
- **Key/Value**
|
||||
- **Managed Account**
|
||||
|
||||
### Adding a Password Entry
|
||||
|
||||
After selecting **Password**, SeedPass asks you to choose a mode:
|
||||
|
||||
1. **Quick** – enter only a label, username, URL, desired length, and whether to include special characters. All other fields use defaults.
|
||||
2. **Advanced** – continue through prompts for notes, tags, custom fields, and detailed password policy settings.
|
||||
|
||||
Both modes generate the password, display it (or copy it to the clipboard in Secret Mode), and save the entry to your encrypted vault.
|
||||
|
||||
### Adding a 2FA Entry
|
||||
|
||||
1. From the main menu choose **Add Entry** and select **2FA (TOTP)**.
|
||||
2. Pick **Make 2FA** to derive a new secret from your seed or **Import 2FA** to paste an existing `otpauth://` URI or secret.
|
||||
3. Provide a label for the account (for example, `GitHub`).
|
||||
4. SeedPass automatically chooses the next available derivation index when deriving.
|
||||
5. Optionally specify the TOTP period and digit count.
|
||||
6. SeedPass displays the URI and secret, along with a QR code you can scan to import it into your authenticator app.
|
||||
|
||||
### Modifying a 2FA Entry
|
||||
|
||||
1. From the main menu choose **Modify an Existing Entry** and enter the index of the 2FA code you want to edit.
|
||||
2. SeedPass will show the current label, period, digit count, and archived status.
|
||||
3. Enter new values or press **Enter** to keep the existing settings.
|
||||
4. When retrieving a 2FA entry you can press **E** to edit the label, period or digit count, or **A** to archive/unarchive it.
|
||||
5. The updated entry is saved back to your encrypted vault.
|
||||
6. Archived entries are hidden from lists but can be viewed or restored from the **List Archived** menu.
|
||||
7. When editing an archived entry you'll be prompted to restore it after saving your changes.
|
||||
|
||||
### Using Secret Mode
|
||||
|
||||
When **Secret Mode** is enabled, SeedPass copies retrieved passwords directly to your clipboard instead of displaying them on screen. The clipboard clears automatically after the delay you choose.
|
||||
|
||||
1. From the main menu open **Settings** and select **Toggle Secret Mode**.
|
||||
2. Choose how many seconds to keep passwords on the clipboard.
|
||||
3. Retrieve an entry and SeedPass will confirm the password was copied.
|
||||
|
||||
### Additional Entry Types
|
||||
|
||||
SeedPass supports storing more than just passwords and 2FA secrets. You can also create entries for:
|
||||
- **SSH Key** – deterministically derive an Ed25519 key pair for servers or git hosting platforms.
|
||||
- **Seed Phrase** – store only the BIP-85 index and word count. The mnemonic is regenerated on demand.
|
||||
- **PGP Key** – derive an OpenPGP key pair from your master seed.
|
||||
- **Nostr Key Pair** – store the index used to derive an `npub`/`nsec` pair for Nostr clients.
|
||||
When you retrieve one of these entries, SeedPass can display QR codes for the
|
||||
keys. The `npub` is wrapped in the `nostr:` URI scheme so any client can scan
|
||||
it, while the `nsec` QR is shown only after a security warning.
|
||||
- **Key/Value** – store a simple key and value for miscellaneous secrets or configuration data.
|
||||
- **Managed Account** – derive a child seed under the current profile. Loading a managed account switches to a nested profile and the header shows `<parent_fp> > Managed Account > <child_fp>`. Press Enter on the main menu to return to the parent profile.
|
||||
|
||||
The table below summarizes the extra fields stored for each entry type. Every
|
||||
entry includes a `label`, while only password entries track a `url`.
|
||||
|
||||
| Entry Type | Extra Fields |
|
||||
|---------------|---------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Password | `username`, `url`, `length`, `archived`, optional `notes`, optional `custom_fields` (may include hidden fields), optional `tags` |
|
||||
| 2FA (TOTP) | `index` or `secret`, `period`, `digits`, `archived`, optional `notes`, optional `tags` |
|
||||
| SSH Key | `index`, `archived`, optional `notes`, optional `tags` |
|
||||
| Seed Phrase | `index`, `word_count` *(mnemonic regenerated; never stored)*, `archived`, optional `notes`, optional `tags` |
|
||||
| PGP Key | `index`, `key_type`, `archived`, optional `user_id`, optional `notes`, optional `tags` |
|
||||
| Nostr Key Pair| `index`, `archived`, optional `notes`, optional `tags` |
|
||||
| Key/Value | `key`, `value`, `archived`, optional `notes`, optional `custom_fields`, optional `tags` |
|
||||
| Managed Account | `index`, `word_count`, `fingerprint`, `archived`, optional `notes`, optional `tags` |
|
||||
|
||||
|
||||
### Managing Multiple Seeds
|
||||
|
||||
SeedPass allows you to manage multiple seed profiles (previously referred to as "fingerprints"). Each seed profile has its own parent seed and associated data, enabling you to compartmentalize your passwords.
|
||||
|
||||
- **Add a New Seed Profile:**
|
||||
- From the main menu, select **Settings** then **Profiles** and choose "Add a New Seed Profile".
|
||||
- Choose to paste in a full seed, enter one word at a time, or generate a new seed.
|
||||
- When entering a seed word by word, each word is hidden with `*` and the screen refreshes after every entry for clarity. You'll review the completed phrase after the last word and can correct mistakes before it is saved.
|
||||
- If generating a new seed, you'll be provided with a 12-word BIP-85 seed phrase. **Ensure you write this down and store it securely.**
|
||||
|
||||
- **Switch Between Seed Profiles:**
|
||||
- From the **Profiles** menu, select "Switch Seed Profile".
|
||||
- You'll see a list of available seed profiles.
|
||||
- Enter the number corresponding to the seed profile you wish to switch to.
|
||||
- Enter the master password associated with that seed profile.
|
||||
|
||||
- **List All Seed Profiles:**
|
||||
- In the **Profiles** menu, choose "List All Seed Profiles" to view all existing profiles.
|
||||
- **Set Seed Profile Name:**
|
||||
- In the **Profiles** menu, choose "Set Seed Profile Name" to assign a label to the current profile. The name is stored locally and shown next to the fingerprint.
|
||||
|
||||
**Note:** The term "seed profile" is used to represent different sets of seeds you can manage within SeedPass. This provides an intuitive way to handle multiple identities or sets of passwords.
|
||||
|
||||
|
||||
### Recovery
|
||||
|
||||
If you previously backed up your vault to Nostr you can restore it during the
|
||||
initial setup. You must provide both your 12 -word master seed and the master
|
||||
password that encrypted the vault; without the correct password the retrieved
|
||||
data cannot be decrypted.
|
||||
|
||||
1. Start SeedPass and choose option **4** when prompted to set up a seed.
|
||||
2. Paste your BIP‑85 seed phrase when asked.
|
||||
3. Enter the master password associated with that seed.
|
||||
4. SeedPass initializes the profile and attempts to download the encrypted
|
||||
vault from the configured relays.
|
||||
5. A success message confirms the vault was restored. If no data is found a
|
||||
failure message is shown and a new empty vault is created.
|
||||
|
||||
### Configuration File and Settings
|
||||
|
||||
SeedPass keeps per-profile settings in an encrypted file named `seedpass_config.json.enc` inside each profile directory under `~/.seedpass/`. This file stores your chosen Nostr relays and the optional settings PIN. New profiles start with the following default relays:
|
||||
|
||||
```
|
||||
wss://relay.snort.social
|
||||
wss://nostr.oxtr.dev
|
||||
wss://relay.primal.net
|
||||
```
|
||||
|
||||
You can manage your relays and sync with Nostr from the **Settings** menu:
|
||||
|
||||
1. From the main menu choose `6` (**Settings**).
|
||||
2. Select `2` (**Nostr**) to open the Nostr submenu.
|
||||
3. Choose `1` to back up your encrypted index to Nostr.
|
||||
4. Select `2` to restore the index from Nostr.
|
||||
5. Choose `3` to view your current relays.
|
||||
6. Select `4` to add a new relay URL.
|
||||
7. Choose `5` to remove a relay by number.
|
||||
8. Select `6` to reset to the default relay list.
|
||||
9. Choose `7` to display your Nostr public key.
|
||||
10. Select `8` to return to the Settings menu.
|
||||
|
||||
Back in the Settings menu you can:
|
||||
|
||||
* Select `3` to change your master password.
|
||||
* Choose `4` to verify the script checksum.
|
||||
* Select `5` to generate a new script checksum.
|
||||
* Choose `6` to back up the parent seed.
|
||||
* Select `7` to export the database to an encrypted file.
|
||||
* Choose `8` to import a database from a backup file. This also performs a Nostr sync automatically.
|
||||
* Select `9` to export all 2FA codes.
|
||||
* Choose `10` to set an additional backup location. A backup is created
|
||||
immediately after the directory is configured.
|
||||
* Select `11` to change the inactivity timeout.
|
||||
* Choose `12` to lock the vault and require re-entry of your password.
|
||||
* Select `13` to view seed profile stats. The summary lists counts for
|
||||
passwords, TOTP codes, SSH keys, seed phrases, and PGP keys. It also shows
|
||||
whether both the encrypted database and the script itself pass checksum
|
||||
validation.
|
||||
* Choose `14` to toggle Secret Mode and set the clipboard clear delay.
|
||||
* Select `15` to toggle Offline Mode. SeedPass starts offline; disable it here to enable Nostr syncing.
|
||||
* Choose `16` to toggle Quick Unlock so subsequent actions skip the password prompt. Startup delay is unchanged.
|
||||
* Select `17` to return to the main menu.
|
||||
|
||||
## Running Tests
|
||||
|
||||
SeedPass includes a small suite of unit tests located under `src/tests`. **Before running `pytest`, be sure to install the test requirements.** Activate your virtual environment and run `pip install --require-hashes -r requirements.lock` to ensure all testing dependencies are available. Then run the tests with **pytest**. Use `-vv` to see INFO-level log messages from each passing test:
|
||||
|
||||
|
||||
```bash
|
||||
pip install --require-hashes -r requirements.lock
|
||||
pytest -vv
|
||||
```
|
||||
|
||||
`test_fuzz_key_derivation.py` uses Hypothesis to generate random passwords,
|
||||
seeds and configuration data. It performs round-trip encryption tests with the
|
||||
`EncryptionManager` to catch edge cases automatically. These fuzz tests run in
|
||||
CI alongside the rest of the suite.
|
||||
|
||||
### Exploring Nostr Index Size Limits
|
||||
|
||||
`test_nostr_index_size.py` demonstrates how SeedPass rotates snapshots after too many delta events.
|
||||
Each chunk is limited to 50 KB, so the test gradually grows the vault to observe
|
||||
when a new snapshot is triggered. Use the `NOSTR_TEST_DELAY` environment
|
||||
variable to control the delay between publishes when experimenting with large vaults.
|
||||
|
||||
```bash
|
||||
pytest -vv -s -n 0 src/tests/test_nostr_index_size.py --desktop --max-entries=1000
|
||||
```
|
||||
|
||||
### Generating a Test Profile
|
||||
|
||||
Use the helper script below to populate a profile with sample entries for testing:
|
||||
|
||||
```bash
|
||||
python scripts/generate_test_profile.py --profile demo_profile --count 100
|
||||
```
|
||||
|
||||
The script determines the fingerprint from the generated seed and stores the
|
||||
vault under `~/.seedpass/tests/<fingerprint>`. SeedPass only discovers profiles
|
||||
inside `~/.seedpass/`, so copy the fingerprint directory out of the `tests`
|
||||
subfolder (or adjust `APP_DIR` in `constants.py`) if you want to use the
|
||||
generated seed with the main application. The fingerprint is printed after
|
||||
creation and the encrypted index is published to Nostr. Use that same seed
|
||||
phrase to load SeedPass. The app checks Nostr on startup and pulls any newer
|
||||
snapshot so your vault stays in sync across machines. Synchronization also runs
|
||||
in the background after unlocking or when switching profiles.
|
||||
|
||||
### Automatically Updating the Script Checksum
|
||||
|
||||
SeedPass stores a SHA-256 checksum for the main program in `~/.seedpass/seedpass_script_checksum.txt`.
|
||||
To keep this value in sync with the source code, install the pre‑push git hook:
|
||||
|
||||
```bash
|
||||
pre-commit install -t pre-push
|
||||
```
|
||||
|
||||
After running this command, every `git push` will execute `scripts/update_checksum.py`,
|
||||
updating the checksum file automatically.
|
||||
|
||||
If the checksum file is missing, generate it manually:
|
||||
|
||||
```bash
|
||||
python scripts/update_checksum.py
|
||||
```
|
||||
|
||||
If SeedPass reports a "script checksum mismatch" warning on startup,
|
||||
regenerate the checksum with `seedpass util update-checksum` or select
|
||||
"Generate Script Checksum" from the Settings menu.
|
||||
|
||||
To run mutation tests locally, generate coverage data first and then execute `mutmut`:
|
||||
|
||||
```bash
|
||||
pytest --cov=src src/tests
|
||||
python -m mutmut run --paths-to-mutate src --tests-dir src/tests --runner "python -m pytest -q" --use-coverage --no-progress
|
||||
python -m mutmut results
|
||||
```
|
||||
|
||||
Mutation testing is disabled in the GitHub workflow due to reliability issues and should be run on a desktop environment instead.
|
||||
|
||||
## Security Considerations
|
||||
|
||||
**Important:** The password you use to encrypt your parent seed is also required to decrypt the seed index data retrieved from Nostr. **It is imperative to remember this password** and be sure to use it with the same seed, as losing it means you won't be able to access your stored index. Secure your 12-word seed **and** your master password.
|
||||
|
||||
- **Backup Your Data:** Regularly back up your encrypted data and checksum files to prevent data loss.
|
||||
- **Backup the Settings PIN:** Your settings PIN is stored in the encrypted configuration file. Keep a copy of this file or remember the PIN, as losing it will require deleting the file and reconfiguring your relays.
|
||||
- **Protect Your Passwords:** Do not share your master password or seed phrases with anyone and ensure they are strong and unique.
|
||||
- **Backing Up the Parent Seed:** Use the CLI `vault reveal-parent-seed` command or the `/api/v1/vault/backup-parent-seed` endpoint with explicit confirmation to create an encrypted backup. The API does not return the seed directly.
|
||||
- **No PBKDF2 Salt Needed:** SeedPass deliberately omits an explicit PBKDF2 salt. Every password is derived from a unique 512-bit BIP-85 child seed, which already provides stronger per-password uniqueness than a conventional 128-bit salt.
|
||||
- **Checksum Verification:** Always verify the script's checksum to ensure its integrity and protect against unauthorized modifications.
|
||||
- **Potential Bugs and Limitations:** Be aware that the software may contain bugs and lacks certain features. Snapshot chunks are capped at 50 KB and the client rotates snapshots after enough delta events accumulate. The security of memory management and logs has not been thoroughly evaluated and may pose risks of leaking sensitive information.
|
||||
- **Multiple Seeds Management:** While managing multiple seeds adds flexibility, it also increases the responsibility to secure each seed and its associated password.
|
||||
- **No PBKDF2 Salt Required:** SeedPass deliberately omits an explicit PBKDF2 salt. Every password is derived from a unique 512-bit BIP-85 child seed, which already provides stronger per-password uniqueness than a conventional 128-bit salt.
|
||||
- **Default KDF Iterations:** New profiles start with 50,000 PBKDF2 iterations. Use `seedpass config set kdf_iterations` to change this.
|
||||
- **Offline Mode (default):** Nostr sync is disabled until you explicitly enable it via the Settings menu or `seedpass config toggle-offline`.
|
||||
- **Quick Unlock:** Store a hashed copy of your password so future actions skip the prompt. Startup delay no longer changes. Use with caution on shared systems.
|
||||
|
||||
## Contributing
|
||||
|
||||
Contributions are welcome! If you have suggestions for improvements, bug fixes, or new features, please follow these steps:
|
||||
|
||||
1. **Fork the Repository:** Click the "Fork" button on the top right of the repository page.
|
||||
|
||||
2. **Create a Branch:** Create a new branch for your feature or bugfix.
|
||||
|
||||
```bash
|
||||
git checkout -b feature/YourFeatureName
|
||||
```
|
||||
|
||||
3. **Commit Your Changes:** Make your changes and commit them with clear messages.
|
||||
|
||||
```bash
|
||||
git commit -m "Add feature X"
|
||||
```
|
||||
|
||||
4. **Push to GitHub:** Push your changes to your forked repository.
|
||||
|
||||
```bash
|
||||
git push origin feature/YourFeatureName
|
||||
```
|
||||
|
||||
5. **Create a Pull Request:** Navigate to the original repository and create a pull request describing your changes.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the [MIT License](LICENSE). See the [LICENSE](LICENSE) file for details.
|
||||
|
||||
## Contact
|
||||
|
||||
For any questions, suggestions, or support, please open an issue on the [GitHub repository](https://github.com/PR0M3TH3AN/SeedPass/issues) or contact the maintainer directly on [Nostr](https://primal.net/p/npub15jnttpymeytm80hatjqcvhhqhzrhx6gxp8pq0wn93rhnu8s9h9dsha32lx).
|
||||
|
||||
---
|
||||
|
||||
*Stay secure and keep your passwords safe with SeedPass!*
|
||||
|
||||
---
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "eleventy --serve",
|
||||
"build": "node node_modules/archivox/src/generator/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"archivox": "^1.0.0"
|
||||
}
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
[build]
|
||||
command = "node build-docs.js"
|
||||
publish = "_site"
|
||||
Generated
-6357
File diff suppressed because it is too large.
Load diff
@@ -1,25 +0,0 @@
|
||||
{
|
||||
"name": "archivox",
|
||||
"version": "1.0.0",
|
||||
"description": "Archivox static site generator",
|
||||
"scripts": {
|
||||
"dev": "eleventy --serve",
|
||||
"build": "node src/generator/index.js",
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@11ty/eleventy": "^2.0.1",
|
||||
"gray-matter": "^4.0.3",
|
||||
"marked": "^11.1.1",
|
||||
"lunr": "^2.3.9",
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"jest": "^29.6.1",
|
||||
"puppeteer": "^24.12.1"
|
||||
},
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"create-archivox": "./bin/create-archivox.js"
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
module.exports = {
|
||||
onPageRendered: async ({ html, file }) => {
|
||||
// Example: inject analytics script into each page
|
||||
const snippet = '\n<script>console.log("Page viewed: ' + file + '")</script>';
|
||||
return { html: html.replace('</body>', `${snippet}</body>`) };
|
||||
}
|
||||
};
|
||||
@@ -1,6 +0,0 @@
|
||||
site:
|
||||
title: "Archivox Docs"
|
||||
description: "Simple static docs."
|
||||
|
||||
navigation:
|
||||
search: true
|
||||
@@ -1,3 +0,0 @@
|
||||
# Install
|
||||
|
||||
Run `npm install` then `npm run build` to generate your site.
|
||||
@@ -1,3 +0,0 @@
|
||||
# Getting Started
|
||||
|
||||
This section helps you begin with Archivox.
|
||||
@@ -1,3 +0,0 @@
|
||||
# Welcome to Archivox
|
||||
|
||||
This is your new documentation site. Start editing files in the `content/` folder.
|
||||
@@ -1,11 +0,0 @@
|
||||
{
|
||||
"name": "my-archivox-site",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "eleventy --serve",
|
||||
"build": "node node_modules/archivox/src/generator/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"archivox": "*"
|
||||
}
|
||||
}
|
||||
Vendored
-23
@@ -1,23 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" data-theme="{% if config.theme.darkMode %}dark{% else %}light{% endif %}">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>{{ title | default(config.site.title) }}</title>
|
||||
<link rel="stylesheet" href="/assets/theme.css" />
|
||||
</head>
|
||||
<body>
|
||||
{% include "partials/header.njk" %}
|
||||
<div id="sidebar-overlay" class="sidebar-overlay"></div>
|
||||
<div class="container">
|
||||
{% include "partials/sidebar.njk" %}
|
||||
<main id="content">
|
||||
<nav id="breadcrumbs" class="breadcrumbs"></nav>
|
||||
{{ content | safe }}
|
||||
</main>
|
||||
</div>
|
||||
{% include "partials/footer.njk" %}
|
||||
<script src="/assets/lunr.js"></script>
|
||||
<script src="/assets/theme.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Vendored
-14
@@ -1,14 +0,0 @@
|
||||
<footer class="footer">
|
||||
{% if config.footer.links %}
|
||||
<nav class="footer-links">
|
||||
{% for link in config.footer.links %}
|
||||
<a href="{{ link.url }}">{{ link.text }}</a>
|
||||
{% endfor %}
|
||||
</nav>
|
||||
{% endif %}
|
||||
<p>© {{ config.site.title }}</p>
|
||||
<div class="footer-permanent-links">
|
||||
<a href="https://github.com/PR0M3TH3AN/Archivox">GitHub</a>
|
||||
<a href="https://nostrtipjar.netlify.app/?n=npub15jnttpymeytm80hatjqcvhhqhzrhx6gxp8pq0wn93rhnu8s9h9dsha32lx">Tip Jar</a>
|
||||
</div>
|
||||
</footer>
|
||||
Vendored
-7
@@ -1,7 +0,0 @@
|
||||
<header class="header">
|
||||
<button id="sidebar-toggle" class="sidebar-toggle" aria-label="Toggle navigation">☰</button>
|
||||
<a href="/" class="logo">{{ config.site.title }}</a>
|
||||
<input id="search-input" class="search-input" type="search" placeholder="Search..." aria-label="Search" />
|
||||
<button id="theme-toggle" class="theme-toggle" aria-label="Toggle dark mode">🌓</button>
|
||||
<div id="search-results" class="search-results"></div>
|
||||
</header>
|
||||
Vendored
-29
@@ -1,29 +0,0 @@
|
||||
{% macro renderNav(items, pageUrl) %}
|
||||
<ul>
|
||||
{% for item in items %}
|
||||
<li>
|
||||
{% if item.children and item.children.length %}
|
||||
{% set sectionPath = item.path | replace('index.html', '') %}
|
||||
<details class="nav-section" {% if pageUrl.startsWith(sectionPath) %}open{% endif %}>
|
||||
<summary>
|
||||
<a href="{{ item.path }}" class="nav-link{% if item.path === pageUrl %} active{% endif %}">
|
||||
{{ item.displayName or item.page.title }}
|
||||
</a>
|
||||
</summary>
|
||||
{{ renderNav(item.children, pageUrl) }}
|
||||
</details>
|
||||
{% else %}
|
||||
<a href="{{ item.path }}" class="nav-link{% if item.path === pageUrl %} active{% endif %}">
|
||||
{{ item.displayName or item.page.title }}
|
||||
</a>
|
||||
{% endif %}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endmacro %}
|
||||
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<nav>
|
||||
{{ renderNav(navigation, page.url) }}
|
||||
</nav>
|
||||
</aside>
|
||||
@@ -0,0 +1,243 @@
|
||||
/* docs.css - Specific styles for the documentation viewer */
|
||||
|
||||
.docs-wrapper {
|
||||
max-width: 1200px;
|
||||
/* Wider for docs */
|
||||
}
|
||||
|
||||
/* Base Layout */
|
||||
.docs-layout {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 20px;
|
||||
gap: 20px;
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
@media (max-width: 850px) {
|
||||
.docs-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar Navigation */
|
||||
.docs-sidebar {
|
||||
width: 250px;
|
||||
flex-shrink: 0;
|
||||
margin-bottom: 0;
|
||||
border-right: 1px dotted var(--border-color);
|
||||
padding-right: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
@media (max-width: 850px) {
|
||||
.docs-sidebar {
|
||||
width: 100%;
|
||||
border-right: none;
|
||||
border-bottom: 1px dotted var(--border-color);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
margin-bottom: 15px;
|
||||
color: var(--text-bright);
|
||||
letter-spacing: 1px;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px dotted var(--border-color);
|
||||
}
|
||||
|
||||
.docs-nav-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.docs-nav-list a {
|
||||
display: block;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9rem;
|
||||
padding: 5px 8px;
|
||||
border-radius: 3px;
|
||||
transition: all 0.2s;
|
||||
border-left: 2px solid transparent;
|
||||
}
|
||||
|
||||
.docs-nav-list a:hover {
|
||||
color: var(--text-bright);
|
||||
background: rgba(14, 165, 233, 0.05);
|
||||
/* very light blue hover */
|
||||
border-left-color: var(--accent-secondary);
|
||||
}
|
||||
|
||||
.docs-nav-list a.active {
|
||||
color: var(--text-primary);
|
||||
font-weight: bold;
|
||||
background: rgba(14, 165, 233, 0.1);
|
||||
border-left-color: var(--accent-primary);
|
||||
}
|
||||
|
||||
/* Document Content Area */
|
||||
.docs-content {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 0;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
/* Loaders / States */
|
||||
.loading-state,
|
||||
.error-state {
|
||||
padding: 40px;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.error-state {
|
||||
color: var(--accent-danger);
|
||||
border: 1px dashed var(--accent-danger);
|
||||
background: rgba(239, 68, 68, 0.05);
|
||||
}
|
||||
|
||||
/* Markdown Rendering Styles */
|
||||
.markdown-body {
|
||||
line-height: 1.7;
|
||||
font-size: 0.95rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.markdown-body h1,
|
||||
.markdown-body h2,
|
||||
.markdown-body h3,
|
||||
.markdown-body h4,
|
||||
.markdown-body h5,
|
||||
.markdown-body h6 {
|
||||
color: var(--text-bright);
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.8em;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.markdown-body h1 {
|
||||
font-size: 2rem;
|
||||
border-bottom: 1px dotted var(--border-color);
|
||||
padding-bottom: 0.3em;
|
||||
color: var(--accent-primary);
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.markdown-body h2 {
|
||||
font-size: 1.5rem;
|
||||
border-bottom: 1px dotted var(--border-color);
|
||||
padding-bottom: 0.3em;
|
||||
}
|
||||
|
||||
.markdown-body h3 {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.markdown-body p,
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
.markdown-body a {
|
||||
color: var(--accent-secondary);
|
||||
text-decoration: underline;
|
||||
text-decoration-style: dotted;
|
||||
text-underline-offset: 4px;
|
||||
}
|
||||
|
||||
.markdown-body a:hover {
|
||||
color: var(--accent-primary);
|
||||
text-decoration-style: solid;
|
||||
}
|
||||
|
||||
.markdown-body ul,
|
||||
.markdown-body ol {
|
||||
padding-left: 2em;
|
||||
}
|
||||
|
||||
.markdown-body li {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
.markdown-body blockquote {
|
||||
margin: 0 0 1.2em 0;
|
||||
padding: 10px 20px;
|
||||
color: var(--text-muted);
|
||||
border-left: 4px solid var(--accent-secondary);
|
||||
background: #0b0f13;
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
/* Override default highlight.js to match our theme */
|
||||
.markdown-body pre {
|
||||
background: #0b0f13 !important;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 15px;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1.2em;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
background: transparent !important;
|
||||
padding: 0;
|
||||
font-size: 0.9rem;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-bright);
|
||||
}
|
||||
|
||||
/* Inline code blocks */
|
||||
.markdown-body :not(pre)>code {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
font-size: 0.9em;
|
||||
color: var(--accent-secondary);
|
||||
border: 1px solid var(--border-highlight);
|
||||
}
|
||||
|
||||
/* Tables */
|
||||
.markdown-body table {
|
||||
width: 100%;
|
||||
margin-bottom: 1.5em;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.markdown-body table th,
|
||||
.markdown-body table td {
|
||||
padding: 10px 15px;
|
||||
border: 1px solid var(--border-color);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-body table th {
|
||||
background: rgba(14, 165, 233, 0.05);
|
||||
color: var(--text-bright);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/* Horizontal Rule */
|
||||
.markdown-body hr {
|
||||
height: 1px;
|
||||
border: none;
|
||||
background: var(--border-color);
|
||||
margin: 2em 0;
|
||||
}
|
||||
|
||||
/* Images */
|
||||
.markdown-body img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
+87
-101
@@ -1,105 +1,91 @@
|
||||
// Dark Mode Toggle with Persistence
|
||||
const toggleCheckbox = document.getElementById('dark-mode-checkbox');
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// --- Boot Sequence Animation ---
|
||||
const bootLines = [
|
||||
"INITIALIZING SYSTEM MODULES...",
|
||||
"KERNEL BOOT... [OK]",
|
||||
"MOUNTING ENCRYPTED FILE SYSTEM... [OK]",
|
||||
"ESTABLISHING NOSTR RELAY CONNECTION... [OK]",
|
||||
"VERIFYING BIP-85 DERIVATION TREE... [OK]",
|
||||
"DECRYPTING VAULT... [OK]",
|
||||
"SYSTEM READY. LAUNCHING TUI..."
|
||||
];
|
||||
|
||||
// Check for saved user preference
|
||||
if (localStorage.getItem('dark-mode') === 'enabled') {
|
||||
toggleCheckbox.checked = true;
|
||||
document.body.classList.add('dark-mode');
|
||||
}
|
||||
const bootTextElement = document.getElementById('boot-text');
|
||||
const bootSequenceElement = document.getElementById('boot-sequence');
|
||||
const mainContentElement = document.getElementById('main-content');
|
||||
|
||||
toggleCheckbox.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
document.body.classList.add('dark-mode');
|
||||
localStorage.setItem('dark-mode', 'enabled');
|
||||
} else {
|
||||
document.body.classList.remove('dark-mode');
|
||||
localStorage.setItem('dark-mode', 'disabled');
|
||||
let currentLine = 0;
|
||||
|
||||
function typeLine() {
|
||||
if (!bootTextElement) return;
|
||||
|
||||
if (currentLine < bootLines.length) {
|
||||
bootTextElement.textContent += bootLines[currentLine] + "\n";
|
||||
currentLine++;
|
||||
|
||||
// Random delay between lines to simulate processing (50ms to 250ms)
|
||||
const delay = Math.random() * 200 + 50;
|
||||
setTimeout(typeLine, delay);
|
||||
} else {
|
||||
// End boot sequence
|
||||
setTimeout(() => {
|
||||
bootSequenceElement.style.opacity = '0';
|
||||
bootSequenceElement.style.transition = 'opacity 0.5s ease-out';
|
||||
|
||||
setTimeout(() => {
|
||||
bootSequenceElement.style.display = 'none';
|
||||
if(mainContentElement) {
|
||||
mainContentElement.style.display = 'flex';
|
||||
}
|
||||
}, 500);
|
||||
}, 600); // Wait 600ms after last line
|
||||
}
|
||||
}
|
||||
|
||||
// Start boot sequence slightly after load
|
||||
setTimeout(typeLine, 300);
|
||||
|
||||
// --- Copy to Clipboard Functionality ---
|
||||
const copyBtn = document.getElementById('copy-btn');
|
||||
const installCmd = document.getElementById('install-cmd');
|
||||
const copyFeedback = document.getElementById('copy-feedback');
|
||||
|
||||
if (copyBtn && installCmd && copyFeedback) {
|
||||
copyBtn.addEventListener('click', () => {
|
||||
// Create a temporary textarea to hold the text to copy
|
||||
const tempTextArea = document.createElement('textarea');
|
||||
tempTextArea.value = installCmd.textContent;
|
||||
document.body.appendChild(tempTextArea);
|
||||
tempTextArea.select();
|
||||
|
||||
try {
|
||||
// Execute the copy command
|
||||
document.execCommand('copy');
|
||||
copyFeedback.textContent = '>> Command copied to clipboard <<';
|
||||
copyFeedback.style.color = 'var(--accent-primary)';
|
||||
|
||||
// Change icon temporarily to checkmark
|
||||
const icon = copyBtn.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = 'fas fa-check';
|
||||
}
|
||||
copyBtn.style.color = 'var(--accent-primary)';
|
||||
|
||||
// Reset after 2 seconds
|
||||
setTimeout(() => {
|
||||
copyFeedback.textContent = '';
|
||||
if (icon) {
|
||||
icon.className = 'far fa-copy';
|
||||
}
|
||||
copyBtn.style.color = '';
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
copyFeedback.textContent = '[ERROR: Failed to copy]';
|
||||
copyFeedback.style.color = 'var(--accent-danger)';
|
||||
}
|
||||
|
||||
// Remove the temporary textarea
|
||||
document.body.removeChild(tempTextArea);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Responsive Navbar Toggle
|
||||
const menuToggle = document.querySelector('.menu-toggle');
|
||||
const navLinks = document.querySelector('.nav-links');
|
||||
|
||||
if (menuToggle) {
|
||||
menuToggle.addEventListener('click', () => {
|
||||
navLinks.classList.toggle('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Active Link Highlighting Based on Scroll
|
||||
const sections = document.querySelectorAll('section');
|
||||
const navLinksArray = document.querySelectorAll('.nav-links a');
|
||||
|
||||
window.addEventListener('scroll', () => {
|
||||
let current = '';
|
||||
|
||||
sections.forEach(section => {
|
||||
const sectionTop = section.offsetTop - 70;
|
||||
if (pageYOffset >= sectionTop) {
|
||||
current = section.getAttribute('id');
|
||||
}
|
||||
});
|
||||
|
||||
navLinksArray.forEach(link => {
|
||||
link.classList.remove('active');
|
||||
if (link.getAttribute('href') === `#${current}`) {
|
||||
link.classList.add('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Smooth Scroll for Older Browsers
|
||||
document.querySelectorAll('a[href^="#"]').forEach(anchor => {
|
||||
anchor.addEventListener('click', function(e) {
|
||||
e.preventDefault();
|
||||
const target = document.querySelector(this.getAttribute('href'));
|
||||
if (target) {
|
||||
window.scrollTo({
|
||||
top: target.offsetTop - 60, // Adjust for navbar height
|
||||
behavior: 'smooth'
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Log when the page is loaded
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
document.querySelectorAll('[data-copy-target]').forEach((button) => {
|
||||
const buttonText = button.querySelector('.copy-button-text');
|
||||
const buttonIcon = button.querySelector('i');
|
||||
const status = button.closest('.install-command-shell')?.querySelector('.install-copy-status');
|
||||
|
||||
button.addEventListener('click', async () => {
|
||||
const targetId = button.getAttribute('data-copy-target');
|
||||
const target = document.getElementById(targetId);
|
||||
if (!target) return;
|
||||
|
||||
const text = target.textContent.trim();
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
} catch (_err) {
|
||||
const temp = document.createElement('textarea');
|
||||
temp.value = text;
|
||||
document.body.appendChild(temp);
|
||||
temp.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(temp);
|
||||
}
|
||||
|
||||
button.classList.add('copied');
|
||||
if (buttonText) buttonText.textContent = 'Copied';
|
||||
if (buttonIcon) buttonIcon.className = 'fas fa-check';
|
||||
if (status) status.textContent = 'Install command copied to clipboard.';
|
||||
setTimeout(() => {
|
||||
button.classList.remove('copied');
|
||||
if (buttonText) buttonText.textContent = 'Copy';
|
||||
if (buttonIcon) buttonIcon.className = 'fas fa-copy';
|
||||
if (status) status.textContent = '';
|
||||
}, 1800);
|
||||
});
|
||||
});
|
||||
|
||||
console.log('SeedPass landing page loaded');
|
||||
});
|
||||
+490
-1102
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user