@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.
Files changed (45) hide show
  1. package/README.md +109 -1
  2. package/dist/commands/add-device.d.ts +3 -0
  3. package/dist/commands/add-device.d.ts.map +1 -0
  4. package/dist/commands/add-device.js +26 -0
  5. package/dist/commands/add-device.js.map +1 -0
  6. package/dist/commands/deploy.d.ts +3 -0
  7. package/dist/commands/deploy.d.ts.map +1 -0
  8. package/dist/commands/deploy.js +75 -0
  9. package/dist/commands/deploy.js.map +1 -0
  10. package/dist/commands/rotate-secret.d.ts +3 -0
  11. package/dist/commands/rotate-secret.d.ts.map +1 -0
  12. package/dist/commands/rotate-secret.js +58 -0
  13. package/dist/commands/rotate-secret.js.map +1 -0
  14. package/dist/commands/setup.d.ts +3 -0
  15. package/dist/commands/setup.d.ts.map +1 -0
  16. package/dist/commands/setup.js +111 -0
  17. package/dist/commands/setup.js.map +1 -0
  18. package/dist/commands/status.d.ts +3 -0
  19. package/dist/commands/status.d.ts.map +1 -0
  20. package/dist/commands/status.js +35 -0
  21. package/dist/commands/status.js.map +1 -0
  22. package/dist/commands/teardown.d.ts +3 -0
  23. package/dist/commands/teardown.d.ts.map +1 -0
  24. package/dist/commands/teardown.js +59 -0
  25. package/dist/commands/teardown.js.map +1 -0
  26. package/dist/index.d.ts +3 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +26 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/lib/cloudflare.d.ts +81 -0
  31. package/dist/lib/cloudflare.d.ts.map +1 -0
  32. package/dist/lib/cloudflare.js +245 -0
  33. package/dist/lib/cloudflare.js.map +1 -0
  34. package/dist/lib/config.d.ts +23 -0
  35. package/dist/lib/config.d.ts.map +1 -0
  36. package/dist/lib/config.js +30 -0
  37. package/dist/lib/config.js.map +1 -0
  38. package/dist/lib/prompts.d.ts +10 -0
  39. package/dist/lib/prompts.d.ts.map +1 -0
  40. package/dist/lib/prompts.js +80 -0
  41. package/dist/lib/prompts.js.map +1 -0
  42. package/dist/worker/README.md +1 -0
  43. package/dist/worker/index.js +2640 -0
  44. package/dist/worker/index.js.map +8 -0
  45. package/package.json +26 -1
package/README.md CHANGED
@@ -1 +1,109 @@
1
- This package is a placeholder. The real release is coming soon.
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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const addDeviceCommand: Command;
3
+ //# sourceMappingURL=add-device.d.ts.map
@@ -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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const deployCommand: Command;
3
+ //# sourceMappingURL=deploy.d.ts.map
@@ -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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const rotateSecretCommand: Command;
3
+ //# sourceMappingURL=rotate-secret.d.ts.map
@@ -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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const setupCommand: Command;
3
+ //# sourceMappingURL=setup.d.ts.map
@@ -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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const statusCommand: Command;
3
+ //# sourceMappingURL=status.d.ts.map
@@ -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,3 @@
1
+ import { Command } from "commander";
2
+ export declare const teardownCommand: Command;
3
+ //# sourceMappingURL=teardown.d.ts.map
@@ -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":"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"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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"}