diff --git a/docs/__tests__/buildNav.test.js b/docs/__tests__/buildNav.test.js deleted file mode 100644 index 26a57684..00000000 --- a/docs/__tests__/buildNav.test.js +++ /dev/null @@ -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'); -}); diff --git a/docs/__tests__/loadConfig.test.js b/docs/__tests__/loadConfig.test.js deleted file mode 100644 index d96b23f5..00000000 --- a/docs/__tests__/loadConfig.test.js +++ /dev/null @@ -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 }); -}); diff --git a/docs/__tests__/pluginHooks.test.js b/docs/__tests__/pluginHooks.test.js deleted file mode 100644 index dd0dfb54..00000000 --- a/docs/__tests__/pluginHooks.test.js +++ /dev/null @@ -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 }); -}); diff --git a/docs/__tests__/renderMarkdown.test.js b/docs/__tests__/renderMarkdown.test.js deleted file mode 100644 index 58c4c8f1..00000000 --- a/docs/__tests__/renderMarkdown.test.js +++ /dev/null @@ -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, '
'); - } - } - }; -}); - -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(' 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 }); -}); diff --git a/docs/__tests__/responsive.test.js b/docs/__tests__/responsive.test.js deleted file mode 100644 index 44b24402..00000000 --- a/docs/__tests__/responsive.test.js +++ /dev/null @@ -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, - `
` - ); - } - } - }; -}); - -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'); -}); diff --git a/docs/bin/create-archivox.js b/docs/bin/create-archivox.js deleted file mode 100755 index 66c74815..00000000 --- a/docs/bin/create-archivox.js +++ /dev/null @@ -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(); diff --git a/docs/build-docs.js b/docs/build-docs.js deleted file mode 100755 index c41a2bb8..00000000 --- a/docs/build-docs.js +++ /dev/null @@ -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); - } -})(); diff --git a/docs/docs/config.yaml b/docs/docs/config.yaml deleted file mode 100644 index 952a12aa..00000000 --- a/docs/docs/config.yaml +++ /dev/null @@ -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/" - diff --git a/docs/docs/content/01-getting-started/01-advanced_cli.md b/docs/docs/content/01-getting-started/01-advanced_cli.md deleted file mode 100644 index dde456c9..00000000 --- a/docs/docs/content/01-getting-started/01-advanced_cli.md +++ /dev/null @@ -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 `) 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 ` | 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 me@example.com` | -| 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 ` | -| Switch profile | `fingerprint switch` | `seedpass fingerprint switch ` | - -### 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 ` | - ---- - -## Detailed Command Descriptions - -### `entry` Commands - -- **`seedpass entry list`** – List entries in the vault, optionally sorted or filtered. -- **`seedpass entry search `** – Search across labels, usernames, URLs and notes. Results show the entry type before each label. -- **`seedpass entry get `** – Retrieve the password or TOTP code for one matching entry, depending on the entry's type. -- **`seedpass entry add