@wizzlethorpe/vaults 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +125 -0
- package/dist/api.js +42 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.js +62 -0
- package/dist/auth.js.map +1 -0
- package/dist/build.js +758 -0
- package/dist/build.js.map +1 -0
- package/dist/commands/build.js +23 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/init.js +67 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/password.js +74 -0
- package/dist/commands/password.js.map +1 -0
- package/dist/commands/preview.js +60 -0
- package/dist/commands/preview.js.map +1 -0
- package/dist/commands/push.js +191 -0
- package/dist/commands/push.js.map +1 -0
- package/dist/commands/role.js +122 -0
- package/dist/commands/role.js.map +1 -0
- package/dist/config.js +79 -0
- package/dist/config.js.map +1 -0
- package/dist/favicon.js +91 -0
- package/dist/favicon.js.map +1 -0
- package/dist/images.js +47 -0
- package/dist/images.js.map +1 -0
- package/dist/index.js +154 -0
- package/dist/index.js.map +1 -0
- package/dist/obsidian.js +47 -0
- package/dist/obsidian.js.map +1 -0
- package/dist/render/auth-template.js +677 -0
- package/dist/render/auth-template.js.map +1 -0
- package/dist/render/callouts.js +65 -0
- package/dist/render/callouts.js.map +1 -0
- package/dist/render/embed.js +190 -0
- package/dist/render/embed.js.map +1 -0
- package/dist/render/layout.js +414 -0
- package/dist/render/layout.js.map +1 -0
- package/dist/render/mcp-template.js +239 -0
- package/dist/render/mcp-template.js.map +1 -0
- package/dist/render/pipeline.js +59 -0
- package/dist/render/pipeline.js.map +1 -0
- package/dist/render/preview.js +81 -0
- package/dist/render/preview.js.map +1 -0
- package/dist/render/slug.js +12 -0
- package/dist/render/slug.js.map +1 -0
- package/dist/render/styles.js +383 -0
- package/dist/render/styles.js.map +1 -0
- package/dist/render/types.js +2 -0
- package/dist/render/types.js.map +1 -0
- package/dist/render/wikilink.js +55 -0
- package/dist/render/wikilink.js.map +1 -0
- package/dist/scan.js +45 -0
- package/dist/scan.js.map +1 -0
- package/dist/settings.js +157 -0
- package/dist/settings.js.map +1 -0
- package/dist/util.js +60 -0
- package/dist/util.js.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wizzlethorpe
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# vaults
|
|
2
|
+
|
|
3
|
+
Sync an Obsidian vault to a Cloudflare-hosted wiki. The CLI renders your notes locally to HTML and deploys them to your own Cloudflare Pages account. Supports role-based access (public, patron, dm, …) so different parts of the same vault can be visible to different audiences.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @wizzlethorpe/vaults
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 22 or newer. Works on macOS, Linux, and Windows.
|
|
12
|
+
|
|
13
|
+
## Quickstart
|
|
14
|
+
|
|
15
|
+
From any Obsidian vault:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd ~/Documents/MyVault
|
|
19
|
+
|
|
20
|
+
vaults init # write a settings.md the renderer will read
|
|
21
|
+
vaults role add public # default tier (anyone can read)
|
|
22
|
+
vaults role add patron # tier above public, password-gated
|
|
23
|
+
vaults role add dm # top tier
|
|
24
|
+
vaults password patron # set a password
|
|
25
|
+
vaults password dm
|
|
26
|
+
vaults push # render + deploy to Cloudflare Pages
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
The first push prompts for a Pages project name and runs `wrangler login` if you aren't authenticated. After that it just renders and deploys.
|
|
30
|
+
|
|
31
|
+
## How it works
|
|
32
|
+
|
|
33
|
+
```
|
|
34
|
+
~/MyVault/ ← Obsidian vault (source of truth)
|
|
35
|
+
│ vaults push
|
|
36
|
+
▼
|
|
37
|
+
Cloudflare Pages ← per-user, your account
|
|
38
|
+
├── _variants/<role>/ ← rendered HTML, scoped by access tier
|
|
39
|
+
├── styles.css, login.html
|
|
40
|
+
└── functions/_middleware.js ← auth gate (cookie/bearer based)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
- **Per-tier deploys.** A page tagged `role: dm` in its frontmatter only ships to the dm variant. Public visitors *cannot* fetch it — the file structurally doesn't exist in their variant.
|
|
44
|
+
- **Images are gated too.** Only images embedded by visible pages are copied into a given variant.
|
|
45
|
+
- **Incremental sync.** External clients (the [Foundry VTT module](https://github.com/wizzlethorpe/vaults-foundry)) can pull changes via `/_manifest.json` + `/_batch` endpoints — the CLI computes content hashes so the diff is minimal.
|
|
46
|
+
|
|
47
|
+
## Commands
|
|
48
|
+
|
|
49
|
+
| Command | What it does |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `vaults init` | Write a `settings.md` with sensible defaults. |
|
|
52
|
+
| `vaults build` | Render the vault to a local directory (no deploy). |
|
|
53
|
+
| `vaults preview` | Render + serve locally via `wrangler pages dev` so you can click around with auth working. |
|
|
54
|
+
| `vaults push` | Render + deploy to Cloudflare Pages. |
|
|
55
|
+
| `vaults role add <name>` | Add an access tier. The first role becomes the default (no password). |
|
|
56
|
+
| `vaults role remove <name>` | Remove an access tier. |
|
|
57
|
+
| `vaults role list` | List configured roles. |
|
|
58
|
+
| `vaults role promote <name>` / `demote <name>` | Reorder tiers. |
|
|
59
|
+
| `vaults password <role>` | Set or change a role's password (PBKDF2-SHA256). |
|
|
60
|
+
| `vaults push --rotate-secret` | Generate a fresh `SESSION_SECRET`, invalidating every issued auth token at once. |
|
|
61
|
+
| `vaults push --all-warnings` / `vaults build --all-warnings` | Don't truncate the broken-link / missing-image report. |
|
|
62
|
+
|
|
63
|
+
Run any command with `--help` for the full flag list.
|
|
64
|
+
|
|
65
|
+
## Settings
|
|
66
|
+
|
|
67
|
+
`settings.md` lives at the root of your vault and is the single user-editable config:
|
|
68
|
+
|
|
69
|
+
```yaml
|
|
70
|
+
---
|
|
71
|
+
vault_name: My Wiki
|
|
72
|
+
default_role: public
|
|
73
|
+
accent_color: "#7a4a8c"
|
|
74
|
+
accent_color_dark: "#b58af5"
|
|
75
|
+
favicon: assets/icons/wiki.png
|
|
76
|
+
inline_title: true
|
|
77
|
+
default_image_width: 50vw
|
|
78
|
+
center_images: true
|
|
79
|
+
ignore:
|
|
80
|
+
- Templates/**
|
|
81
|
+
- "*.draft.md"
|
|
82
|
+
---
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Open it in Obsidian — the frontmatter shows up as a Properties form.
|
|
86
|
+
|
|
87
|
+
## Page frontmatter
|
|
88
|
+
|
|
89
|
+
A page's frontmatter controls its access tier and how it's surfaced:
|
|
90
|
+
|
|
91
|
+
```yaml
|
|
92
|
+
---
|
|
93
|
+
role: dm # required to view; default is settings.default_role
|
|
94
|
+
title: Optional override # default: filename or first H1
|
|
95
|
+
aliases: # extra names that resolve to this page from wikilinks
|
|
96
|
+
- Pale Mountains
|
|
97
|
+
- The Pale Mountains
|
|
98
|
+
---
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Wikilinks (`[[Page]]`, `[[Page|alias]]`, `[[NPCs/Page#section]]`), image embeds (`![[image.png]]`), transclusions (`![[Page]]`), and Obsidian callouts all render the same way they do in Obsidian.
|
|
102
|
+
|
|
103
|
+
## Auth
|
|
104
|
+
|
|
105
|
+
Multi-role deploys ship with a small Cloudflare Pages Function (`_middleware.js`) that:
|
|
106
|
+
|
|
107
|
+
- **Gates per-role variants** via a signed cookie (`SameSite=None; Secure; Partitioned`).
|
|
108
|
+
- **Issues bearer tokens** through an OAuth-style `/connect` flow used by the [Foundry module](https://github.com/wizzlethorpe/vaults-foundry).
|
|
109
|
+
- **Exposes** `/_batch` (text) and `/_batch-images` (binary) for bulk content sync.
|
|
110
|
+
|
|
111
|
+
Tokens are stateless HMAC-signed JWTs; revocation = rotate `SESSION_SECRET` via `vaults push --rotate-secret`.
|
|
112
|
+
|
|
113
|
+
## Files this CLI manages locally
|
|
114
|
+
|
|
115
|
+
| File | Tracked in git? | What it holds |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `settings.md` | yes | User-editable settings. |
|
|
118
|
+
| `.vaultrc.json` | **no** | CLI-managed: `SESSION_SECRET`, role password hashes, project name, cached settings. |
|
|
119
|
+
| `.vault-cache/` | **no** | Build cache: rendered output, image webp cache. |
|
|
120
|
+
|
|
121
|
+
`vaults init` adds `.vaultrc.json` and `.vault-cache` to `.gitignore` if your vault is a git repo.
|
|
122
|
+
|
|
123
|
+
## License
|
|
124
|
+
|
|
125
|
+
MIT
|
package/dist/api.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
export class ApiClient {
|
|
2
|
+
cfg;
|
|
3
|
+
constructor(cfg) {
|
|
4
|
+
this.cfg = cfg;
|
|
5
|
+
}
|
|
6
|
+
async getManifest() {
|
|
7
|
+
const res = await fetch(this.url("/api/manifest"), { headers: this.headers() });
|
|
8
|
+
if (!res.ok)
|
|
9
|
+
throw new Error(`GET /api/manifest failed: ${res.status} ${await res.text()}`);
|
|
10
|
+
const { files } = (await res.json());
|
|
11
|
+
return files;
|
|
12
|
+
}
|
|
13
|
+
async putSource(path, body, contentType) {
|
|
14
|
+
const res = await fetch(this.url(`/admin/source/${encodePath(path)}`), {
|
|
15
|
+
method: "PUT",
|
|
16
|
+
headers: { ...this.headers(), "Content-Type": contentType },
|
|
17
|
+
body: new Uint8Array(body),
|
|
18
|
+
});
|
|
19
|
+
if (!res.ok)
|
|
20
|
+
throw new Error(`PUT ${path} failed: ${res.status} ${await res.text()}`);
|
|
21
|
+
}
|
|
22
|
+
async deleteSource(path) {
|
|
23
|
+
const res = await fetch(this.url(`/admin/source/${encodePath(path)}`), {
|
|
24
|
+
method: "DELETE",
|
|
25
|
+
headers: this.headers(),
|
|
26
|
+
});
|
|
27
|
+
if (!res.ok && res.status !== 404) {
|
|
28
|
+
throw new Error(`DELETE ${path} failed: ${res.status} ${await res.text()}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
url(path) {
|
|
32
|
+
const base = this.cfg.url.replace(/\/$/, "");
|
|
33
|
+
return `${base}${path}`;
|
|
34
|
+
}
|
|
35
|
+
headers() {
|
|
36
|
+
return { Authorization: `Bearer ${this.cfg.apiKey}` };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function encodePath(path) {
|
|
40
|
+
return path.split("/").map(encodeURIComponent).join("/");
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=api.js.map
|
package/dist/api.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAUA,MAAM,OAAO,SAAS;IACA;IAApB,YAAoB,GAAgB;QAAhB,QAAG,GAAH,GAAG,CAAa;IAAG,CAAC;IAExC,KAAK,CAAC,WAAW;QACf,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,eAAe,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAChF,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,6BAA6B,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC5F,MAAM,EAAE,KAAK,EAAE,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAA+B,CAAC;QACnE,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,IAAY,EAAE,IAAyB,EAAE,WAAmB;QAC1E,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YACrE,MAAM,EAAE,KAAK;YACb,OAAO,EAAE,EAAE,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,cAAc,EAAE,WAAW,EAAE;YAC3D,IAAI,EAAE,IAAI,UAAU,CAAC,IAAI,CAAC;SAC3B,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE;YAAE,MAAM,IAAI,KAAK,CAAC,OAAO,IAAI,YAAY,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,KAAK,CAAC,YAAY,CAAC,IAAY;QAC7B,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,iBAAiB,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE;YACrE,MAAM,EAAE,QAAQ;YAChB,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE;SACxB,CAAC,CAAC;QACH,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,UAAU,IAAI,YAAY,GAAG,CAAC,MAAM,IAAI,MAAM,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QAC9E,CAAC;IACH,CAAC;IAEO,GAAG,CAAC,IAAY;QACtB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QAC7C,OAAO,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;IAC1B,CAAC;IAEO,OAAO;QACb,OAAO,EAAE,aAAa,EAAE,UAAU,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;IACxD,CAAC;CACF;AAED,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC3D,CAAC"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { webcrypto } from "node:crypto";
|
|
2
|
+
// Password hashing: PBKDF2-SHA256. The Cloudflare Workers runtime caps
|
|
3
|
+
// PBKDF2 iterations at 100k (NotSupportedError above that), so we hash at
|
|
4
|
+
// 100k here too — the CLI and the edge Function must use the same algorithm
|
|
5
|
+
// for stored hashes to verify.
|
|
6
|
+
const ITERATIONS = 100_000;
|
|
7
|
+
const SALT_BYTES = 16;
|
|
8
|
+
const HASH_BYTES = 32; // SHA-256 output length
|
|
9
|
+
export async function hashPassword(password) {
|
|
10
|
+
const salt = webcrypto.getRandomValues(new Uint8Array(SALT_BYTES));
|
|
11
|
+
const hash = await pbkdf2(password, salt, ITERATIONS);
|
|
12
|
+
return `${ITERATIONS}:${toHex(salt)}:${toHex(hash)}`;
|
|
13
|
+
}
|
|
14
|
+
export async function verifyPassword(password, encoded) {
|
|
15
|
+
const parsed = parseEncoded(encoded);
|
|
16
|
+
if (!parsed)
|
|
17
|
+
return false;
|
|
18
|
+
const salt = fromHex(parsed.saltHex);
|
|
19
|
+
const expected = fromHex(parsed.hashHex);
|
|
20
|
+
const actual = await pbkdf2(password, salt, parsed.iterations);
|
|
21
|
+
return constantTimeEqual(actual, expected);
|
|
22
|
+
}
|
|
23
|
+
function parseEncoded(encoded) {
|
|
24
|
+
const parts = encoded.split(":");
|
|
25
|
+
if (parts.length !== 3)
|
|
26
|
+
return null;
|
|
27
|
+
const iterations = Number(parts[0]);
|
|
28
|
+
if (!Number.isFinite(iterations) || iterations < 1000)
|
|
29
|
+
return null;
|
|
30
|
+
return { iterations, saltHex: parts[1], hashHex: parts[2] };
|
|
31
|
+
}
|
|
32
|
+
async function pbkdf2(password, salt, iterations) {
|
|
33
|
+
const key = await webcrypto.subtle.importKey("raw", new TextEncoder().encode(password), { name: "PBKDF2" }, false, ["deriveBits"]);
|
|
34
|
+
const bits = await webcrypto.subtle.deriveBits({ name: "PBKDF2", salt: salt, hash: "SHA-256", iterations }, key, HASH_BYTES * 8);
|
|
35
|
+
return new Uint8Array(bits);
|
|
36
|
+
}
|
|
37
|
+
function toHex(bytes) {
|
|
38
|
+
return Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
39
|
+
}
|
|
40
|
+
function fromHex(hex) {
|
|
41
|
+
const out = new Uint8Array(hex.length / 2);
|
|
42
|
+
for (let i = 0; i < out.length; i++)
|
|
43
|
+
out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function constantTimeEqual(a, b) {
|
|
47
|
+
if (a.length !== b.length)
|
|
48
|
+
return false;
|
|
49
|
+
let diff = 0;
|
|
50
|
+
for (let i = 0; i < a.length; i++)
|
|
51
|
+
diff |= a[i] ^ b[i];
|
|
52
|
+
return diff === 0;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Generate a random secret used to sign session tokens. Stored as a wrangler
|
|
56
|
+
* secret on push, never in settings.md or the static deployment.
|
|
57
|
+
*/
|
|
58
|
+
export function generateSessionSecret() {
|
|
59
|
+
const bytes = webcrypto.getRandomValues(new Uint8Array(32));
|
|
60
|
+
return toHex(bytes);
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|