@yaop/obsidian-r2-sync 0.0.1 → 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/README.md +109 -1
- package/dist/commands/add-device.d.ts +3 -0
- package/dist/commands/add-device.d.ts.map +1 -0
- package/dist/commands/add-device.js +26 -0
- package/dist/commands/add-device.js.map +1 -0
- package/dist/commands/deploy.d.ts +3 -0
- package/dist/commands/deploy.d.ts.map +1 -0
- package/dist/commands/deploy.js +75 -0
- package/dist/commands/deploy.js.map +1 -0
- package/dist/commands/rotate-secret.d.ts +3 -0
- package/dist/commands/rotate-secret.d.ts.map +1 -0
- package/dist/commands/rotate-secret.js +58 -0
- package/dist/commands/rotate-secret.js.map +1 -0
- package/dist/commands/setup.d.ts +3 -0
- package/dist/commands/setup.d.ts.map +1 -0
- package/dist/commands/setup.js +111 -0
- package/dist/commands/setup.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +35 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/teardown.d.ts +3 -0
- package/dist/commands/teardown.d.ts.map +1 -0
- package/dist/commands/teardown.js +59 -0
- package/dist/commands/teardown.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/cloudflare.d.ts +81 -0
- package/dist/lib/cloudflare.d.ts.map +1 -0
- package/dist/lib/cloudflare.js +245 -0
- package/dist/lib/cloudflare.js.map +1 -0
- package/dist/lib/config.d.ts +23 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +30 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/prompts.d.ts +10 -0
- package/dist/lib/prompts.d.ts.map +1 -0
- package/dist/lib/prompts.js +80 -0
- package/dist/lib/prompts.js.map +1 -0
- package/dist/worker/README.md +1 -0
- package/dist/worker/index.js +2640 -0
- package/dist/worker/index.js.map +8 -0
- package/package.json +26 -1
package/README.md
CHANGED
|
@@ -1 +1,109 @@
|
|
|
1
|
-
|
|
1
|
+
# @yaop/obsidian-r2-sync
|
|
2
|
+
|
|
3
|
+
CLI for provisioning [Obsidian R2 Vault Sync](https://github.com/canavandl/obsidian-r2-sync) infrastructure on Cloudflare.
|
|
4
|
+
|
|
5
|
+
Sync your Obsidian vault across devices using Cloudflare R2 storage and a Cloudflare Worker — all on the free tier.
|
|
6
|
+
|
|
7
|
+
## Prerequisites
|
|
8
|
+
|
|
9
|
+
- **Node.js** >= 20
|
|
10
|
+
- A **Cloudflare account** with [R2 enabled](https://dash.cloudflare.com) (free tier works)
|
|
11
|
+
- A **Cloudflare API token** with these permissions:
|
|
12
|
+
- Account / Workers Scripts: Edit
|
|
13
|
+
- Account / Workers R2 Storage: Edit
|
|
14
|
+
- Account / Account API Tokens: Edit
|
|
15
|
+
|
|
16
|
+
See the [full setup guide](https://github.com/canavandl/obsidian-r2-sync#setup-guide) for step-by-step instructions on creating your API token.
|
|
17
|
+
|
|
18
|
+
## Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @yaop/obsidian-r2-sync setup
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install globally:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @yaop/obsidian-r2-sync
|
|
28
|
+
obsidian-r2-sync setup
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Commands
|
|
32
|
+
|
|
33
|
+
### `setup`
|
|
34
|
+
|
|
35
|
+
Full provisioning wizard. Creates an R2 bucket, deploys the Cloudflare Worker, and generates your first device token.
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
npx @yaop/obsidian-r2-sync setup
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The wizard will output:
|
|
42
|
+
- **Endpoint URL** — the Worker URL to configure in the Obsidian plugin
|
|
43
|
+
- **Token** — paste this into the plugin settings
|
|
44
|
+
- **Auth Secret** — save this to add more devices later
|
|
45
|
+
|
|
46
|
+
### `deploy`
|
|
47
|
+
|
|
48
|
+
Redeploy the Cloudflare Worker (e.g. after updating to a new version).
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npx @yaop/obsidian-r2-sync deploy
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### `add-device`
|
|
55
|
+
|
|
56
|
+
Generate an auth token for an additional device.
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npx @yaop/obsidian-r2-sync add-device
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### `status`
|
|
63
|
+
|
|
64
|
+
Check the health of your deployed Worker.
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npx @yaop/obsidian-r2-sync status
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### `rotate-secret`
|
|
71
|
+
|
|
72
|
+
Generate a new auth secret and redeploy the Worker. **This invalidates all existing device tokens** — you'll need to run `add-device` again for each device.
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npx @yaop/obsidian-r2-sync rotate-secret
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### `teardown`
|
|
79
|
+
|
|
80
|
+
Remove the Cloudflare Worker and optionally delete the R2 bucket.
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx @yaop/obsidian-r2-sync teardown
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## How It Works
|
|
87
|
+
|
|
88
|
+
This CLI provisions the server-side infrastructure for Obsidian R2 Vault Sync:
|
|
89
|
+
|
|
90
|
+
1. **R2 Bucket** — stores your vault files and sync manifest
|
|
91
|
+
2. **Cloudflare Worker** — API layer handling authentication, manifest management, and presigned URL generation
|
|
92
|
+
3. **HMAC tokens** — each device gets a unique token for authentication
|
|
93
|
+
|
|
94
|
+
The Obsidian plugin then syncs your vault files directly to/from R2 via presigned URLs.
|
|
95
|
+
|
|
96
|
+
## Pricing
|
|
97
|
+
|
|
98
|
+
Everything runs on Cloudflare's free tier. For a typical personal vault (~500 MB, 2-3 devices), you'll pay **$0/month**.
|
|
99
|
+
|
|
100
|
+
| Resource | Free Allowance | Typical Usage |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| Workers requests | 100,000/day | ~50-200/day |
|
|
103
|
+
| R2 storage | 10 GB/month | ~500 MB |
|
|
104
|
+
| R2 Class A ops | 1M/month | ~1,000-3,000/month |
|
|
105
|
+
| R2 Class B ops | 10M/month | ~3,000-10,000/month |
|
|
106
|
+
|
|
107
|
+
## License
|
|
108
|
+
|
|
109
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-device.d.ts","sourceRoot":"","sources":["../../src/commands/add-device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,eAAO,MAAM,gBAAgB,SAsBzB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { CloudflareClient } from "../lib/cloudflare.js";
|
|
4
|
+
import { promptDeviceId } from "../lib/prompts.js";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
export const addDeviceCommand = new Command("add-device")
|
|
7
|
+
.description("Generate auth token for a new device")
|
|
8
|
+
.action(async () => {
|
|
9
|
+
console.log(chalk.bold("\n🔑 Add Device\n"));
|
|
10
|
+
const { secret } = await inquirer.prompt([
|
|
11
|
+
{
|
|
12
|
+
type: "password",
|
|
13
|
+
name: "secret",
|
|
14
|
+
message: "Enter the auth secret (from setup):",
|
|
15
|
+
mask: "*",
|
|
16
|
+
validate: (input) => input.length > 0 || "Auth secret is required",
|
|
17
|
+
},
|
|
18
|
+
]);
|
|
19
|
+
const deviceId = await promptDeviceId();
|
|
20
|
+
const token = await CloudflareClient.generateToken(secret, deviceId);
|
|
21
|
+
console.log(chalk.bold("\n✅ Device token generated!\n"));
|
|
22
|
+
console.log(` ${chalk.cyan("Device ID:")} ${deviceId}`);
|
|
23
|
+
console.log(` ${chalk.cyan("Token:")} ${token}\n`);
|
|
24
|
+
console.log(chalk.dim("Add this token to the Obsidian plugin settings on the new device.\n"));
|
|
25
|
+
});
|
|
26
|
+
//# sourceMappingURL=add-device.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add-device.js","sourceRoot":"","sources":["../../src/commands/add-device.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,QAAQ,MAAM,UAAU,CAAC;AAEhC,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC;KACtD,WAAW,CAAC,sCAAsC,CAAC;KACnD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAE7C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC;QACvC;YACE,IAAI,EAAE,UAAU;YAChB,IAAI,EAAE,QAAQ;YACd,OAAO,EAAE,qCAAqC;YAC9C,IAAI,EAAE,GAAG;YACT,QAAQ,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,yBAAyB;SAC3E;KACF,CAAC,CAAC;IAEH,MAAM,QAAQ,GAAG,MAAM,cAAc,EAAE,CAAC;IACxC,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,aAAa,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAErE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,+BAA+B,CAAC,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,QAAQ,EAAE,CAAC,CAAC;IACzD,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,KAAK,IAAI,CAAC,CAAC;IACxD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC,CAAC;AAChG,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.d.ts","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,aAAa,SAwEtB,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { CloudflareClient, readWorkerBundle } from "../lib/cloudflare.js";
|
|
5
|
+
import { loadConfig } from "../lib/config.js";
|
|
6
|
+
import { promptApiToken, promptAccountId } from "../lib/prompts.js";
|
|
7
|
+
export const deployCommand = new Command("deploy")
|
|
8
|
+
.description("Build and deploy the Worker")
|
|
9
|
+
.option("--token <token>", "Cloudflare API token")
|
|
10
|
+
.option("--account-id <id>", "Cloudflare account ID")
|
|
11
|
+
.option("--name <name>", "Worker name")
|
|
12
|
+
.option("--bucket <bucket>", "R2 bucket name")
|
|
13
|
+
.option("--bundle <path>", "Path to pre-built Worker bundle")
|
|
14
|
+
.action(async (options) => {
|
|
15
|
+
console.log(chalk.bold("\n📦 Deploy Worker\n"));
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
// Read the worker bundle
|
|
18
|
+
const bundleSpinner = ora("Reading Worker bundle...").start();
|
|
19
|
+
let bundle;
|
|
20
|
+
try {
|
|
21
|
+
bundle = readWorkerBundle(options.bundle);
|
|
22
|
+
bundleSpinner.succeed(`Worker bundle loaded (${(bundle.length / 1024).toFixed(1)} KB)`);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
bundleSpinner.fail(error.message);
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Auth
|
|
29
|
+
const apiToken = options.token || config.apiToken || (await promptApiToken());
|
|
30
|
+
let accountId = options.accountId || config.accountId;
|
|
31
|
+
if (!accountId) {
|
|
32
|
+
const tempClient = new CloudflareClient(apiToken, "");
|
|
33
|
+
const accounts = await tempClient.listAccounts();
|
|
34
|
+
accountId = await promptAccountId(accounts);
|
|
35
|
+
}
|
|
36
|
+
const cf = new CloudflareClient(apiToken, accountId);
|
|
37
|
+
// Use saved secret or prompt
|
|
38
|
+
let secret;
|
|
39
|
+
if (config.authSecret) {
|
|
40
|
+
secret = config.authSecret;
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
const inquirer = await import("inquirer");
|
|
44
|
+
const response = await inquirer.default.prompt([
|
|
45
|
+
{
|
|
46
|
+
type: "password",
|
|
47
|
+
name: "secret",
|
|
48
|
+
message: "Enter the auth secret (from setup):",
|
|
49
|
+
mask: "*",
|
|
50
|
+
validate: (input) => input.length > 0 || "Auth secret is required",
|
|
51
|
+
},
|
|
52
|
+
]);
|
|
53
|
+
secret = response.secret;
|
|
54
|
+
}
|
|
55
|
+
const workerName = options.name || config.workerName || "obsidian-r2-sync";
|
|
56
|
+
const bucketName = options.bucket || config.bucketName || "obsidian-vault-sync";
|
|
57
|
+
// Deploy
|
|
58
|
+
const deploySpinner = ora(`Deploying Worker "${workerName}"...`).start();
|
|
59
|
+
try {
|
|
60
|
+
const { url } = await cf.deployWorker(workerName, bundle, {
|
|
61
|
+
r2BucketName: bucketName,
|
|
62
|
+
authSecret: secret,
|
|
63
|
+
cfAccountId: accountId,
|
|
64
|
+
cfAccessKeyId: config.r2AccessKeyId,
|
|
65
|
+
cfSecretAccessKey: config.r2SecretAccessKey,
|
|
66
|
+
});
|
|
67
|
+
deploySpinner.succeed(`Worker deployed at ${chalk.cyan(url)}`);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
deploySpinner.fail("Failed to deploy Worker");
|
|
71
|
+
console.error(chalk.red(` ${error.message}`));
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=deploy.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deploy.js","sourceRoot":"","sources":["../../src/commands/deploy.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rotate-secret.d.ts","sourceRoot":"","sources":["../../src/commands/rotate-secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,mBAAmB,SAgE5B,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { CloudflareClient } from "../lib/cloudflare.js";
|
|
5
|
+
import { loadConfig, updateConfig } from "../lib/config.js";
|
|
6
|
+
import { promptApiToken, confirmAction } from "../lib/prompts.js";
|
|
7
|
+
export const rotateSecretCommand = new Command("rotate-secret")
|
|
8
|
+
.description("Generate a new AUTH_SECRET and update it on the Worker")
|
|
9
|
+
.option("--token <token>", "Cloudflare API token")
|
|
10
|
+
.option("--account-id <id>", "Cloudflare account ID")
|
|
11
|
+
.option("--name <name>", "Worker name", "obsidian-r2-sync")
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
console.log(chalk.bold("\n🔑 Rotate Auth Secret\n"));
|
|
14
|
+
console.log(chalk.red("⚠️ This will invalidate ALL existing device tokens.\n" +
|
|
15
|
+
" After rotating, run `add-device` for each device to issue new tokens.\n"));
|
|
16
|
+
const confirmed = await confirmAction("Are you sure you want to continue?");
|
|
17
|
+
if (!confirmed) {
|
|
18
|
+
console.log("Cancelled.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
// Resolve credentials from options → config → interactive prompt
|
|
22
|
+
const config = loadConfig();
|
|
23
|
+
const apiToken = options.token || config.apiToken || (await promptApiToken());
|
|
24
|
+
let accountId = options.accountId || config.accountId;
|
|
25
|
+
if (!accountId) {
|
|
26
|
+
const tempClient = new CloudflareClient(apiToken, "");
|
|
27
|
+
const accounts = await tempClient.listAccounts();
|
|
28
|
+
if (accounts.length === 1) {
|
|
29
|
+
accountId = accounts[0].id;
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
const { promptAccountId } = await import("../lib/prompts.js");
|
|
33
|
+
accountId = await promptAccountId(accounts);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const workerName = options.name || config.workerName || "obsidian-r2-sync";
|
|
37
|
+
// Generate new secret (same approach as setup.ts)
|
|
38
|
+
const newSecret = crypto.randomUUID() + crypto.randomUUID();
|
|
39
|
+
// Update the secret on the Worker
|
|
40
|
+
const cf = new CloudflareClient(apiToken, accountId);
|
|
41
|
+
const spinner = ora("Updating AUTH_SECRET on Worker...").start();
|
|
42
|
+
try {
|
|
43
|
+
await cf.putSecret(workerName, "AUTH_SECRET", newSecret);
|
|
44
|
+
spinner.succeed("AUTH_SECRET updated on Worker");
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
spinner.fail("Failed to update AUTH_SECRET on Worker");
|
|
48
|
+
console.error(chalk.red(` ${error.message}`));
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Persist new secret locally only after the Worker update succeeds
|
|
52
|
+
updateConfig({ authSecret: newSecret });
|
|
53
|
+
console.log(chalk.bold("\n✅ Secret rotated successfully!\n"));
|
|
54
|
+
console.log(` ${chalk.cyan("New secret:")} ${newSecret}\n`);
|
|
55
|
+
console.log(chalk.dim(" All previous device tokens are now invalid.\n" +
|
|
56
|
+
" Run `obsidian-r2-sync add-device` for each device to generate new tokens.\n"));
|
|
57
|
+
});
|
|
58
|
+
//# sourceMappingURL=rotate-secret.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rotate-secret.js","sourceRoot":"","sources":["../../src/commands/rotate-secret.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAapC,eAAO,MAAM,YAAY,SA+GrB,CAAC"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { CloudflareClient, readWorkerBundle } from "../lib/cloudflare.js";
|
|
5
|
+
import { updateConfig } from "../lib/config.js";
|
|
6
|
+
import { promptApiToken, promptAccountId, promptBucketName, promptWorkerName, promptDeviceId, } from "../lib/prompts.js";
|
|
7
|
+
export const setupCommand = new Command("setup")
|
|
8
|
+
.description("Provision R2 bucket and deploy Worker")
|
|
9
|
+
.action(async () => {
|
|
10
|
+
console.log(chalk.bold("\n🚀 Obsidian R2 Sync — Setup\n"));
|
|
11
|
+
// Step 1: Get API token
|
|
12
|
+
const apiToken = await promptApiToken();
|
|
13
|
+
const spinner = ora("Verifying API token...").start();
|
|
14
|
+
let cf;
|
|
15
|
+
let accountId;
|
|
16
|
+
try {
|
|
17
|
+
// Step 2: Detect account
|
|
18
|
+
const tempClient = new CloudflareClient(apiToken, "");
|
|
19
|
+
const accounts = await tempClient.listAccounts();
|
|
20
|
+
spinner.succeed("API token verified");
|
|
21
|
+
accountId = await promptAccountId(accounts);
|
|
22
|
+
cf = new CloudflareClient(apiToken, accountId);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
spinner.fail("Invalid API token");
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
// Step 3: Create R2 bucket
|
|
29
|
+
const bucketName = await promptBucketName();
|
|
30
|
+
const bucketSpinner = ora(`Creating R2 bucket "${bucketName}"...`).start();
|
|
31
|
+
try {
|
|
32
|
+
const { created } = await cf.ensureBucket(bucketName);
|
|
33
|
+
if (created) {
|
|
34
|
+
bucketSpinner.succeed(`Created R2 bucket "${bucketName}"`);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
bucketSpinner.succeed(`R2 bucket "${bucketName}" already exists`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
bucketSpinner.fail("Failed to create R2 bucket");
|
|
42
|
+
console.error(error);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
// Step 4: Create R2 API token for presigned URLs
|
|
46
|
+
const r2TokenSpinner = ora("Creating R2 API token for presigned URLs...").start();
|
|
47
|
+
let r2AccessKeyId;
|
|
48
|
+
let r2SecretAccessKey;
|
|
49
|
+
try {
|
|
50
|
+
const r2Creds = await cf.createR2Token(bucketName);
|
|
51
|
+
r2AccessKeyId = r2Creds.accessKeyId;
|
|
52
|
+
r2SecretAccessKey = r2Creds.secretAccessKey;
|
|
53
|
+
r2TokenSpinner.succeed("R2 API token created");
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
r2TokenSpinner.fail("Failed to create R2 API token (presigned URLs won't work until this is configured)");
|
|
57
|
+
console.error(chalk.red(` ${error.message}`));
|
|
58
|
+
console.error(chalk.dim(" Ensure your API token has 'Account / Account API Tokens: Edit' permission."));
|
|
59
|
+
console.error(chalk.dim(" Or create an R2 API token manually:"));
|
|
60
|
+
console.error(chalk.dim(" 1. Go to https://dash.cloudflare.com → R2 → Manage R2 API Tokens"));
|
|
61
|
+
console.error(chalk.dim(" 2. Create a token with 'Object Read & Write' for your bucket"));
|
|
62
|
+
console.error(chalk.dim(" 3. Run 'pnpm cli deploy' with the R2 credentials to update the Worker"));
|
|
63
|
+
// Don't exit — Worker can still be deployed, presigned URLs just won't work
|
|
64
|
+
}
|
|
65
|
+
// Step 5: Generate auth secret
|
|
66
|
+
const authSecret = crypto.randomUUID() + crypto.randomUUID();
|
|
67
|
+
// Step 6: Deploy Worker
|
|
68
|
+
const workerName = await promptWorkerName();
|
|
69
|
+
let workerUrl = "";
|
|
70
|
+
const workerSpinner = ora(`Deploying Worker "${workerName}"...`).start();
|
|
71
|
+
try {
|
|
72
|
+
const bundle = readWorkerBundle();
|
|
73
|
+
const { url } = await cf.deployWorker(workerName, bundle, {
|
|
74
|
+
r2BucketName: bucketName,
|
|
75
|
+
authSecret,
|
|
76
|
+
cfAccountId: accountId,
|
|
77
|
+
cfAccessKeyId: r2AccessKeyId,
|
|
78
|
+
cfSecretAccessKey: r2SecretAccessKey,
|
|
79
|
+
});
|
|
80
|
+
workerUrl = url;
|
|
81
|
+
workerSpinner.succeed(`Worker deployed at ${chalk.cyan(url)}`);
|
|
82
|
+
}
|
|
83
|
+
catch (error) {
|
|
84
|
+
workerSpinner.fail("Failed to deploy Worker");
|
|
85
|
+
console.error(chalk.red(` ${error.message}`));
|
|
86
|
+
console.error(chalk.dim(" You can deploy manually later with: obsidian-r2-sync deploy"));
|
|
87
|
+
// Don't exit — continue to generate the device token
|
|
88
|
+
}
|
|
89
|
+
// Step 7: Generate first device token
|
|
90
|
+
const deviceId = await promptDeviceId();
|
|
91
|
+
const token = await CloudflareClient.generateToken(authSecret, deviceId);
|
|
92
|
+
// Save config for future CLI commands
|
|
93
|
+
updateConfig({
|
|
94
|
+
apiToken,
|
|
95
|
+
accountId,
|
|
96
|
+
workerName,
|
|
97
|
+
bucketName,
|
|
98
|
+
authSecret,
|
|
99
|
+
r2AccessKeyId,
|
|
100
|
+
r2SecretAccessKey,
|
|
101
|
+
workerUrl: workerUrl || undefined,
|
|
102
|
+
});
|
|
103
|
+
// Output results
|
|
104
|
+
console.log(chalk.bold("\n✅ Setup complete!\n"));
|
|
105
|
+
console.log(chalk.dim("Add these to your Obsidian plugin settings:\n"));
|
|
106
|
+
console.log(` ${chalk.cyan("Endpoint:")} ${workerUrl || `https://${workerName}.<your-subdomain>.workers.dev`}`);
|
|
107
|
+
console.log(` ${chalk.cyan("Token:")} ${token}`);
|
|
108
|
+
console.log(chalk.dim("\n⚠️ Save the auth secret — you'll need it to add more devices:"));
|
|
109
|
+
console.log(` ${chalk.cyan("Secret:")} ${authSecret}\n`);
|
|
110
|
+
});
|
|
111
|
+
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"setup.js","sourceRoot":"","sources":["../../src/commands/setup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.d.ts","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAKpC,eAAO,MAAM,aAAa,SA+BtB,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { loadConfig } from "../lib/config.js";
|
|
5
|
+
export const statusCommand = new Command("status")
|
|
6
|
+
.description("Check Worker health")
|
|
7
|
+
.option("--endpoint <url>", "Worker endpoint URL")
|
|
8
|
+
.action(async (options) => {
|
|
9
|
+
const config = loadConfig();
|
|
10
|
+
const endpoint = options.endpoint || config.workerUrl;
|
|
11
|
+
if (!endpoint) {
|
|
12
|
+
console.error(chalk.red("Error: No endpoint specified."));
|
|
13
|
+
console.error(chalk.dim(" Provide --endpoint <url> or run setup first."));
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const spinner = ora("Checking Worker health...").start();
|
|
17
|
+
try {
|
|
18
|
+
const response = await fetch(`${endpoint}/health`);
|
|
19
|
+
const data = (await response.json());
|
|
20
|
+
if (data.ok) {
|
|
21
|
+
spinner.succeed("Worker is healthy");
|
|
22
|
+
console.log(` ${chalk.cyan("Endpoint:")} ${endpoint}`);
|
|
23
|
+
console.log(` ${chalk.cyan("Version:")} ${data.version}`);
|
|
24
|
+
console.log(` ${chalk.cyan("Timestamp:")} ${data.timestamp}`);
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
spinner.fail("Worker returned unhealthy status");
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
spinner.fail("Failed to reach Worker");
|
|
32
|
+
console.error(chalk.red(` Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
//# sourceMappingURL=status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"status.js","sourceRoot":"","sources":["../../src/commands/status.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE9C,MAAM,CAAC,MAAM,aAAa,GAAG,IAAI,OAAO,CAAC,QAAQ,CAAC;KAC/C,WAAW,CAAC,qBAAqB,CAAC;KAClC,MAAM,CAAC,kBAAkB,EAAE,qBAAqB,CAAC;KACjD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,MAAM,CAAC,SAAS,CAAC;IAEtD,IAAI,CAAC,QAAQ,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,+BAA+B,CAAC,CAAC,CAAC;QAC1D,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC,CAAC;QAC3E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,GAAG,CAAC,2BAA2B,CAAC,CAAC,KAAK,EAAE,CAAC;IAEzD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,SAAS,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAwD,CAAC;QAE5F,IAAI,IAAI,CAAC,EAAE,EAAE,CAAC;YACZ,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,KAAK,QAAQ,EAAE,CAAC,CAAC;YACzD,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;YAC7D,OAAO,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC;QACjE,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC;QACnD,CAAC;IACH,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACvC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,YAAY,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,CAAC;IACnG,CAAC;AACH,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teardown.d.ts","sourceRoot":"","sources":["../../src/commands/teardown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,eAAe,SAyDxB,CAAC"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
import { CloudflareClient } from "../lib/cloudflare.js";
|
|
5
|
+
import { promptApiToken } from "../lib/prompts.js";
|
|
6
|
+
import { confirmAction } from "../lib/prompts.js";
|
|
7
|
+
export const teardownCommand = new Command("teardown")
|
|
8
|
+
.description("Remove Worker and optionally R2 bucket")
|
|
9
|
+
.option("--token <token>", "Cloudflare API token")
|
|
10
|
+
.option("--account-id <id>", "Cloudflare account ID")
|
|
11
|
+
.option("--name <name>", "Worker name", "obsidian-r2-sync")
|
|
12
|
+
.option("--bucket <name>", "R2 bucket name", "obsidian-vault-sync")
|
|
13
|
+
.action(async (options) => {
|
|
14
|
+
console.log(chalk.bold("\n🗑️ Teardown\n"));
|
|
15
|
+
console.log(chalk.red("⚠️ This will permanently delete your sync infrastructure.\n"));
|
|
16
|
+
const confirmed = await confirmAction("Are you sure you want to continue?");
|
|
17
|
+
if (!confirmed) {
|
|
18
|
+
console.log("Cancelled.");
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const apiToken = options.token || (await promptApiToken());
|
|
22
|
+
let accountId = options.accountId;
|
|
23
|
+
if (!accountId) {
|
|
24
|
+
const tempClient = new CloudflareClient(apiToken, "");
|
|
25
|
+
const accounts = await tempClient.listAccounts();
|
|
26
|
+
if (accounts.length === 1) {
|
|
27
|
+
accountId = accounts[0].id;
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
const { promptAccountId } = await import("../lib/prompts.js");
|
|
31
|
+
accountId = await promptAccountId(accounts);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
const cf = new CloudflareClient(apiToken, accountId);
|
|
35
|
+
// Delete Worker
|
|
36
|
+
const workerSpinner = ora(`Deleting Worker "${options.name}"...`).start();
|
|
37
|
+
try {
|
|
38
|
+
await cf.deleteWorker(options.name);
|
|
39
|
+
workerSpinner.succeed(`Deleted Worker "${options.name}"`);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
workerSpinner.fail(`Failed to delete Worker "${options.name}"`);
|
|
43
|
+
}
|
|
44
|
+
// Optionally delete bucket
|
|
45
|
+
const deleteBucket = await confirmAction(`Delete R2 bucket "${options.bucket}"? (This will delete ALL synced data!)`);
|
|
46
|
+
if (deleteBucket) {
|
|
47
|
+
const bucketSpinner = ora(`Deleting R2 bucket "${options.bucket}"...`).start();
|
|
48
|
+
try {
|
|
49
|
+
await cf.deleteBucket(options.bucket);
|
|
50
|
+
bucketSpinner.succeed(`Deleted R2 bucket "${options.bucket}"`);
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
bucketSpinner.fail(`Failed to delete R2 bucket "${options.bucket}"`);
|
|
54
|
+
console.error(chalk.dim(" You may need to empty the bucket first."));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
console.log(chalk.bold("\n✅ Teardown complete.\n"));
|
|
58
|
+
});
|
|
59
|
+
//# sourceMappingURL=teardown.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"teardown.js","sourceRoot":"","sources":["../../src/commands/teardown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAGlD,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,OAAO,CAAC,UAAU,CAAC;KACnD,WAAW,CAAC,wCAAwC,CAAC;KACrD,MAAM,CAAC,iBAAiB,EAAE,sBAAsB,CAAC;KACjD,MAAM,CAAC,mBAAmB,EAAE,uBAAuB,CAAC;KACpD,MAAM,CAAC,eAAe,EAAE,aAAa,EAAE,kBAAkB,CAAC;KAC1D,MAAM,CAAC,iBAAiB,EAAE,gBAAgB,EAAE,qBAAqB,CAAC;KAClE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC,CAAC;IAC7C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,8DAA8D,CAAC,CAAC,CAAC;IAEvF,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,oCAAoC,CAAC,CAAC;IAC5E,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QAC1B,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,IAAI,CAAC,MAAM,cAAc,EAAE,CAAC,CAAC;IAE3D,IAAI,SAAS,GAAG,OAAO,CAAC,SAAS,CAAC;IAClC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,UAAU,GAAG,IAAI,gBAAgB,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACtD,MAAM,QAAQ,GAAG,MAAM,UAAU,CAAC,YAAY,EAAE,CAAC;QACjD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,SAAS,GAAG,QAAQ,CAAC,CAAC,CAAE,CAAC,EAAE,CAAC;QAC9B,CAAC;aAAM,CAAC;YACN,MAAM,EAAE,eAAe,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;YAC9D,SAAS,GAAG,MAAM,eAAe,CAAC,QAAQ,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED,MAAM,EAAE,GAAG,IAAI,gBAAgB,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;IAErD,gBAAgB;IAChB,MAAM,aAAa,GAAG,GAAG,CAAC,oBAAoB,OAAO,CAAC,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;IAC1E,IAAI,CAAC;QACH,MAAM,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;QACpC,aAAa,CAAC,OAAO,CAAC,mBAAmB,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAC5D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,aAAa,CAAC,IAAI,CAAC,4BAA4B,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAClE,CAAC;IAED,2BAA2B;IAC3B,MAAM,YAAY,GAAG,MAAM,aAAa,CACtC,qBAAqB,OAAO,CAAC,MAAM,wCAAwC,CAC5E,CAAC;IACF,IAAI,YAAY,EAAE,CAAC;QACjB,MAAM,aAAa,GAAG,GAAG,CAAC,uBAAuB,OAAO,CAAC,MAAM,MAAM,CAAC,CAAC,KAAK,EAAE,CAAC;QAC/E,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YACtC,aAAa,CAAC,OAAO,CAAC,sBAAsB,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;QACjE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,+BAA+B,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC;YACrE,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,2CAA2C,CAAC,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;AACtD,CAAC,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
|
+
import { setupCommand } from "./commands/setup.js";
|
|
7
|
+
import { deployCommand } from "./commands/deploy.js";
|
|
8
|
+
import { addDeviceCommand } from "./commands/add-device.js";
|
|
9
|
+
import { statusCommand } from "./commands/status.js";
|
|
10
|
+
import { teardownCommand } from "./commands/teardown.js";
|
|
11
|
+
import { rotateSecretCommand } from "./commands/rotate-secret.js";
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
13
|
+
const { version: PACKAGE_VERSION } = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
|
|
14
|
+
const program = new Command();
|
|
15
|
+
program
|
|
16
|
+
.name("obsidian-r2-sync")
|
|
17
|
+
.description("CLI for managing Obsidian R2 Vault Sync infrastructure")
|
|
18
|
+
.version(PACKAGE_VERSION);
|
|
19
|
+
program.addCommand(setupCommand);
|
|
20
|
+
program.addCommand(deployCommand);
|
|
21
|
+
program.addCommand(addDeviceCommand);
|
|
22
|
+
program.addCommand(statusCommand);
|
|
23
|
+
program.addCommand(teardownCommand);
|
|
24
|
+
program.addCommand(rotateSecretCommand);
|
|
25
|
+
program.parse();
|
|
26
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAElE,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,EAAE,OAAO,EAAE,eAAe,EAAE,GAAG,IAAI,CAAC,KAAK,CAC7C,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CAC7D,CAAC;AAEF,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,kBAAkB,CAAC;KACxB,WAAW,CAAC,wDAAwD,CAAC;KACrE,OAAO,CAAC,eAAe,CAAC,CAAC;AAE5B,OAAO,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;AACjC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC;AACrC,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;AAClC,OAAO,CAAC,UAAU,CAAC,eAAe,CAAC,CAAC;AACpC,OAAO,CAAC,UAAU,CAAC,mBAAmB,CAAC,CAAC;AAExC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|