@yaop/obsidian-r2-sync 0.0.1 → 0.1.1

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 +27 -1
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Read the pre-built Worker bundle.
3
+ *
4
+ * When installed from npm, the worker bundle is at dist/worker/index.js
5
+ * (copied there by the prepack script). In the monorepo during development,
6
+ * it falls back to the workspace-relative path.
7
+ */
8
+ export declare function readWorkerBundle(bundlePath?: string): string;
9
+ export interface ProvisioningConfig {
10
+ apiToken: string;
11
+ accountId: string;
12
+ bucketName: string;
13
+ workerName: string;
14
+ }
15
+ /**
16
+ * Wrapper around the Cloudflare SDK for infrastructure provisioning.
17
+ */
18
+ export declare class CloudflareClient {
19
+ private client;
20
+ private accountId;
21
+ constructor(apiToken: string, accountId: string);
22
+ /**
23
+ * List available accounts to auto-detect account ID.
24
+ */
25
+ listAccounts(): Promise<Array<{
26
+ id: string;
27
+ name: string;
28
+ }>>;
29
+ /**
30
+ * Create an R2 bucket (idempotent — returns existing if it already exists).
31
+ */
32
+ ensureBucket(bucketName: string): Promise<{
33
+ name: string;
34
+ created: boolean;
35
+ }>;
36
+ /**
37
+ * Delete an R2 bucket.
38
+ */
39
+ deleteBucket(bucketName: string): Promise<void>;
40
+ /**
41
+ * Create an R2-scoped API token and derive S3 credentials from it.
42
+ *
43
+ * Uses the account-level token API (POST /accounts/{id}/tokens) instead of
44
+ * the user-level API, so it only requires normal account permissions — no
45
+ * special "User API Tokens: Edit" permission needed.
46
+ *
47
+ * The Cloudflare API token's `id` becomes the Access Key ID,
48
+ * and `SHA-256(token value)` becomes the Secret Access Key.
49
+ */
50
+ createR2Token(bucketName: string): Promise<{
51
+ accessKeyId: string;
52
+ secretAccessKey: string;
53
+ tokenId: string;
54
+ }>;
55
+ /**
56
+ * Deploy a Worker script.
57
+ * For MVP, we upload a pre-built worker bundle.
58
+ */
59
+ deployWorker(workerName: string, scriptContent: string, bindings: {
60
+ r2BucketName: string;
61
+ authSecret: string;
62
+ cfAccountId?: string;
63
+ cfAccessKeyId?: string;
64
+ cfSecretAccessKey?: string;
65
+ }): Promise<{
66
+ url: string;
67
+ }>;
68
+ /**
69
+ * Delete a Worker.
70
+ */
71
+ deleteWorker(workerName: string): Promise<void>;
72
+ /**
73
+ * Update a single secret on a deployed Worker without redeploying the whole script.
74
+ */
75
+ putSecret(workerName: string, secretName: string, secretValue: string): Promise<void>;
76
+ /**
77
+ * Generate an HMAC-based auth token for a device.
78
+ */
79
+ static generateToken(authSecret: string, deviceId: string): Promise<string>;
80
+ }
81
+ //# sourceMappingURL=cloudflare.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.d.ts","sourceRoot":"","sources":["../../src/lib/cloudflare.ts"],"names":[],"mappings":"AAQA;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,CA6B5D;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,SAAS,CAAS;gBAEd,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM;IAK/C;;OAEG;IACG,YAAY,IAAI,OAAO,CAAC,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAWlE;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAmBnF;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD;;;;;;;;;OASG;IACG,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC;QAC/C,WAAW,EAAE,MAAM,CAAC;QACpB,eAAe,EAAE,MAAM,CAAC;QACxB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAgEF;;;OAGG;IACG,YAAY,CAChB,UAAU,EAAE,MAAM,EAClB,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE;QACR,YAAY,EAAE,MAAM,CAAC;QACrB,UAAU,EAAE,MAAM,CAAC;QACnB,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;KAC5B,GACA,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAA;KAAE,CAAC;IA2D3B;;OAEG;IACG,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAMrD;;OAEG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS3F;;OAEG;WACU,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAelF"}
@@ -0,0 +1,245 @@
1
+ import Cloudflare from "cloudflare";
2
+ import { createHash } from "node:crypto";
3
+ import { readFileSync } from "node:fs";
4
+ import { resolve, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ /**
8
+ * Read the pre-built Worker bundle.
9
+ *
10
+ * When installed from npm, the worker bundle is at dist/worker/index.js
11
+ * (copied there by the prepack script). In the monorepo during development,
12
+ * it falls back to the workspace-relative path.
13
+ */
14
+ export function readWorkerBundle(bundlePath) {
15
+ if (bundlePath) {
16
+ try {
17
+ return readFileSync(bundlePath, "utf-8");
18
+ }
19
+ catch {
20
+ throw new Error(`Worker bundle not found at ${bundlePath}.`);
21
+ }
22
+ }
23
+ // 1. Check bundled location (npm install / npx)
24
+ const bundledPath = resolve(__dirname, "../worker/index.js");
25
+ try {
26
+ return readFileSync(bundledPath, "utf-8");
27
+ }
28
+ catch {
29
+ // not found — fall through
30
+ }
31
+ // 2. Fall back to monorepo-relative path (local dev)
32
+ const monoRepoPath = resolve(__dirname, "../../../worker/dist/index.js");
33
+ try {
34
+ return readFileSync(monoRepoPath, "utf-8");
35
+ }
36
+ catch {
37
+ throw new Error(`Worker bundle not found.\n` +
38
+ ` Checked: ${bundledPath}\n` +
39
+ ` Checked: ${monoRepoPath}\n` +
40
+ `Run "pnpm --filter @obsidian-r2-sync/worker build" first.`);
41
+ }
42
+ }
43
+ /**
44
+ * Wrapper around the Cloudflare SDK for infrastructure provisioning.
45
+ */
46
+ export class CloudflareClient {
47
+ client;
48
+ accountId;
49
+ constructor(apiToken, accountId) {
50
+ this.client = new Cloudflare({ apiToken });
51
+ this.accountId = accountId;
52
+ }
53
+ /**
54
+ * List available accounts to auto-detect account ID.
55
+ */
56
+ async listAccounts() {
57
+ // Use a single page request instead of the auto-paginating iterator,
58
+ // which has a bug where it infinitely re-fetches the same page.
59
+ const page = await this.client.accounts.list();
60
+ const result = [];
61
+ for (const account of page.result) {
62
+ result.push({ id: account.id, name: account.name });
63
+ }
64
+ return result;
65
+ }
66
+ /**
67
+ * Create an R2 bucket (idempotent — returns existing if it already exists).
68
+ */
69
+ async ensureBucket(bucketName) {
70
+ try {
71
+ // Check if bucket already exists
72
+ const buckets = await this.client.r2.buckets.list({ account_id: this.accountId });
73
+ const existing = buckets.buckets?.find((b) => b.name === bucketName);
74
+ if (existing) {
75
+ return { name: bucketName, created: false };
76
+ }
77
+ }
78
+ catch {
79
+ // List failed, try creating
80
+ }
81
+ await this.client.r2.buckets.create({
82
+ account_id: this.accountId,
83
+ name: bucketName,
84
+ });
85
+ return { name: bucketName, created: true };
86
+ }
87
+ /**
88
+ * Delete an R2 bucket.
89
+ */
90
+ async deleteBucket(bucketName) {
91
+ await this.client.r2.buckets.delete(bucketName, {
92
+ account_id: this.accountId,
93
+ });
94
+ }
95
+ /**
96
+ * Create an R2-scoped API token and derive S3 credentials from it.
97
+ *
98
+ * Uses the account-level token API (POST /accounts/{id}/tokens) instead of
99
+ * the user-level API, so it only requires normal account permissions — no
100
+ * special "User API Tokens: Edit" permission needed.
101
+ *
102
+ * The Cloudflare API token's `id` becomes the Access Key ID,
103
+ * and `SHA-256(token value)` becomes the Secret Access Key.
104
+ */
105
+ async createR2Token(bucketName) {
106
+ // Step 1: Find the R2 permission group IDs via the account-level endpoint.
107
+ // Use .result directly to avoid the SDK's auto-paginating iterator bug.
108
+ const permissionGroupsPage = await this.client.accounts.tokens.permissionGroups.list({
109
+ account_id: this.accountId,
110
+ });
111
+ const permissionGroups = [...permissionGroupsPage.result];
112
+ // Look for R2 object read/write permissions (scoped to r2.bucket)
113
+ const r2WriteGroup = permissionGroups.find((g) => g.name?.includes("Workers R2 Storage Bucket Item Write"));
114
+ const r2ReadGroup = permissionGroups.find((g) => g.name?.includes("Workers R2 Storage Bucket Item Read") &&
115
+ !g.name?.includes("Write"));
116
+ if (!r2WriteGroup?.id) {
117
+ throw new Error("Could not find R2 write permission group. " +
118
+ "Available groups: " +
119
+ permissionGroups
120
+ .filter((g) => g.name?.includes("R2"))
121
+ .map((g) => g.name)
122
+ .join(", "));
123
+ }
124
+ // Step 2: Create the token with R2 bucket-scoped permissions
125
+ const policyPermissions = [{ id: r2WriteGroup.id }];
126
+ if (r2ReadGroup?.id && r2ReadGroup.id !== r2WriteGroup.id) {
127
+ policyPermissions.push({ id: r2ReadGroup.id });
128
+ }
129
+ const token = await this.client.accounts.tokens.create({
130
+ account_id: this.accountId,
131
+ name: `obsidian-r2-sync-${bucketName}`,
132
+ policies: [
133
+ {
134
+ effect: "allow",
135
+ permission_groups: policyPermissions,
136
+ resources: {
137
+ [`com.cloudflare.edge.r2.bucket.${this.accountId}_default_${bucketName}`]: "*",
138
+ },
139
+ },
140
+ ],
141
+ });
142
+ const tokenValue = token.value;
143
+ const tokenId = token.id;
144
+ // Step 3: Derive S3 credentials
145
+ // Access Key ID = token ID
146
+ // Secret Access Key = SHA-256(token value)
147
+ const secretAccessKey = createHash("sha256").update(tokenValue).digest("hex");
148
+ return {
149
+ accessKeyId: tokenId,
150
+ secretAccessKey,
151
+ tokenId,
152
+ };
153
+ }
154
+ /**
155
+ * Deploy a Worker script.
156
+ * For MVP, we upload a pre-built worker bundle.
157
+ */
158
+ async deployWorker(workerName, scriptContent, bindings) {
159
+ // Build the bindings list
160
+ // Using `as never` to work around the SDK's strict union types for bindings
161
+ const workerBindings = [
162
+ {
163
+ type: "r2_bucket",
164
+ name: "BUCKET",
165
+ bucket_name: bindings.r2BucketName,
166
+ },
167
+ {
168
+ type: "secret_text",
169
+ name: "AUTH_SECRET",
170
+ text: bindings.authSecret,
171
+ },
172
+ {
173
+ type: "plain_text",
174
+ name: "BUCKET_NAME",
175
+ text: bindings.r2BucketName,
176
+ },
177
+ ...(bindings.cfAccountId
178
+ ? [{ type: "secret_text", name: "CF_ACCOUNT_ID", text: bindings.cfAccountId }]
179
+ : []),
180
+ ...(bindings.cfAccessKeyId
181
+ ? [{ type: "secret_text", name: "CF_ACCESS_KEY_ID", text: bindings.cfAccessKeyId }]
182
+ : []),
183
+ ...(bindings.cfSecretAccessKey
184
+ ? [{ type: "secret_text", name: "CF_SECRET_ACCESS_KEY", text: bindings.cfSecretAccessKey }]
185
+ : []),
186
+ ];
187
+ // Upload the worker script as an ES module
188
+ await this.client.workers.scripts.update(workerName, {
189
+ account_id: this.accountId,
190
+ metadata: {
191
+ main_module: "index.js",
192
+ compatibility_date: "2025-02-01",
193
+ bindings: workerBindings,
194
+ },
195
+ files: {
196
+ "index.js": new File([scriptContent], "index.js", {
197
+ type: "application/javascript+module",
198
+ }),
199
+ },
200
+ });
201
+ // Enable the workers.dev route
202
+ await this.client.workers.scripts.subdomain.create(workerName, {
203
+ account_id: this.accountId,
204
+ enabled: true,
205
+ });
206
+ // Get the account's workers.dev subdomain to build the URL
207
+ const subdomainInfo = await this.client.workers.accountSettings.get({
208
+ account_id: this.accountId,
209
+ });
210
+ const accountSubdomain = subdomainInfo.subdomain;
211
+ return { url: `https://${workerName}.${accountSubdomain}.workers.dev` };
212
+ }
213
+ /**
214
+ * Delete a Worker.
215
+ */
216
+ async deleteWorker(workerName) {
217
+ await this.client.workers.scripts.delete(workerName, {
218
+ account_id: this.accountId,
219
+ });
220
+ }
221
+ /**
222
+ * Update a single secret on a deployed Worker without redeploying the whole script.
223
+ */
224
+ async putSecret(workerName, secretName, secretValue) {
225
+ await this.client.workers.scripts.secrets.update(workerName, {
226
+ account_id: this.accountId,
227
+ name: secretName,
228
+ text: secretValue,
229
+ type: "secret_text",
230
+ });
231
+ }
232
+ /**
233
+ * Generate an HMAC-based auth token for a device.
234
+ */
235
+ static async generateToken(authSecret, deviceId) {
236
+ const encoder = new TextEncoder();
237
+ const key = await crypto.subtle.importKey("raw", encoder.encode(authSecret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
238
+ const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(deviceId));
239
+ const hmacHex = Array.from(new Uint8Array(signature))
240
+ .map((b) => b.toString(16).padStart(2, "0"))
241
+ .join("");
242
+ return `${deviceId}:${hmacHex}`;
243
+ }
244
+ }
245
+ //# sourceMappingURL=cloudflare.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflare.js","sourceRoot":"","sources":["../../src/lib/cloudflare.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ export interface CliConfig {
2
+ apiToken?: string;
3
+ accountId?: string;
4
+ workerName?: string;
5
+ bucketName?: string;
6
+ authSecret?: string;
7
+ r2AccessKeyId?: string;
8
+ r2SecretAccessKey?: string;
9
+ workerUrl?: string;
10
+ }
11
+ /**
12
+ * Load saved CLI config from ~/.obsidian-r2-sync.json
13
+ */
14
+ export declare function loadConfig(): CliConfig;
15
+ /**
16
+ * Save CLI config to ~/.obsidian-r2-sync.json
17
+ */
18
+ export declare function saveConfig(config: CliConfig): void;
19
+ /**
20
+ * Merge new values into the existing config and save.
21
+ */
22
+ export declare function updateConfig(partial: Partial<CliConfig>): void;
23
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAMA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,SAAS,CAOtC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAElD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,SAAS,CAAC,GAAG,IAAI,CAG9D"}
@@ -0,0 +1,30 @@
1
+ import { readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ const CONFIG_FILE = join(homedir(), ".obsidian-r2-sync.json");
5
+ /**
6
+ * Load saved CLI config from ~/.obsidian-r2-sync.json
7
+ */
8
+ export function loadConfig() {
9
+ try {
10
+ const content = readFileSync(CONFIG_FILE, "utf-8");
11
+ return JSON.parse(content);
12
+ }
13
+ catch {
14
+ return {};
15
+ }
16
+ }
17
+ /**
18
+ * Save CLI config to ~/.obsidian-r2-sync.json
19
+ */
20
+ export function saveConfig(config) {
21
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
22
+ }
23
+ /**
24
+ * Merge new values into the existing config and save.
25
+ */
26
+ export function updateConfig(partial) {
27
+ const existing = loadConfig();
28
+ saveConfig({ ...existing, ...partial });
29
+ }
30
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACtD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,wBAAwB,CAAC,CAAC;AAa9D;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAc,CAAC;IAC1C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAiB;IAC1C,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,CAAC,CAAC;AAC9E,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,OAA2B;IACtD,MAAM,QAAQ,GAAG,UAAU,EAAE,CAAC;IAC9B,UAAU,CAAC,EAAE,GAAG,QAAQ,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,10 @@
1
+ export declare function promptApiToken(): Promise<string>;
2
+ export declare function promptAccountId(accounts: Array<{
3
+ id: string;
4
+ name: string;
5
+ }>): Promise<string>;
6
+ export declare function promptBucketName(): Promise<string>;
7
+ export declare function promptWorkerName(): Promise<string>;
8
+ export declare function promptDeviceId(): Promise<string>;
9
+ export declare function confirmAction(message: string): Promise<boolean>;
10
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/lib/prompts.ts"],"names":[],"mappings":"AAEA,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAWtD;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,KAAK,CAAC;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC,GAC5C,OAAO,CAAC,MAAM,CAAC,CAcjB;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAiBxD;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAUxD;AAED,wBAAsB,cAAc,IAAI,OAAO,CAAC,MAAM,CAAC,CAUtD;AAED,wBAAsB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAUrE"}
@@ -0,0 +1,80 @@
1
+ import inquirer from "inquirer";
2
+ export async function promptApiToken() {
3
+ const { token } = await inquirer.prompt([
4
+ {
5
+ type: "password",
6
+ name: "token",
7
+ message: "Enter your Cloudflare API token:",
8
+ mask: "*",
9
+ validate: (input) => input.length > 0 || "API token is required",
10
+ },
11
+ ]);
12
+ return token;
13
+ }
14
+ export async function promptAccountId(accounts) {
15
+ if (accounts.length === 1) {
16
+ return accounts[0].id;
17
+ }
18
+ const { accountId } = await inquirer.prompt([
19
+ {
20
+ type: "list",
21
+ name: "accountId",
22
+ message: "Select a Cloudflare account:",
23
+ choices: accounts.map((a) => ({ name: `${a.name} (${a.id})`, value: a.id })),
24
+ },
25
+ ]);
26
+ return accountId;
27
+ }
28
+ export async function promptBucketName() {
29
+ const { name } = await inquirer.prompt([
30
+ {
31
+ type: "input",
32
+ name: "name",
33
+ message: "R2 bucket name:",
34
+ default: "obsidian-vault-sync",
35
+ validate: (input) => {
36
+ if (input.length < 3)
37
+ return "Bucket name must be at least 3 characters";
38
+ if (!/^[a-z0-9][a-z0-9-]*[a-z0-9]$/.test(input)) {
39
+ return "Bucket name must be lowercase alphanumeric with hyphens";
40
+ }
41
+ return true;
42
+ },
43
+ },
44
+ ]);
45
+ return name;
46
+ }
47
+ export async function promptWorkerName() {
48
+ const { name } = await inquirer.prompt([
49
+ {
50
+ type: "input",
51
+ name: "name",
52
+ message: "Worker name:",
53
+ default: "obsidian-r2-sync",
54
+ },
55
+ ]);
56
+ return name;
57
+ }
58
+ export async function promptDeviceId() {
59
+ const { id } = await inquirer.prompt([
60
+ {
61
+ type: "input",
62
+ name: "id",
63
+ message: "Device identifier (e.g., macbook, phone, ipad):",
64
+ validate: (input) => input.length > 0 || "Device ID is required",
65
+ },
66
+ ]);
67
+ return `device-${id.toLowerCase().replace(/[^a-z0-9]/g, "-")}`;
68
+ }
69
+ export async function confirmAction(message) {
70
+ const { confirmed } = await inquirer.prompt([
71
+ {
72
+ type: "confirm",
73
+ name: "confirmed",
74
+ message,
75
+ default: false,
76
+ },
77
+ ]);
78
+ return confirmed;
79
+ }
80
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/lib/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ This folder contains the built output assets for the worker "obsidian-r2-sync" generated at 2026-02-24T20:31:42.461Z.