sync-cf-secrets 0.1.0 → 0.3.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 CHANGED
@@ -135,7 +135,7 @@ Create a `.sync-cf-secrets.json` in your project root (all fields optional):
135
135
 
136
136
  ### Defaults
137
137
 
138
- - **provider** — auto-detected from available CLIs (`op` or `bw`)
138
+ - **provider** — auto-detected (prefers 1Password SDK when `OP_SERVICE_ACCOUNT_TOKEN` is set, then `op` CLI, then `bw` CLI)
139
139
  - **vault** — project name from `package.json`
140
140
  - **prefix** — same as vault
141
141
  - **wranglerConfig** — auto-searches for `wrangler.toml`, `wrangler.jsonc`, or `wrangler.json` (including `apps/web/`)
@@ -148,7 +148,17 @@ Create a `.sync-cf-secrets.json` in your project root (all fields optional):
148
148
 
149
149
  The easiest way to get started is `sync-cf-secrets init`, which creates Secure Note items with the right fields. You can also create them manually — one Secure Note per environment with custom fields where the **label** is the env var name and the **value** is the secret.
150
150
 
151
- Requires the [1Password CLI](https://developer.1password.com/docs/cli/get-started/) (`op`). Auth is handled via the 1Password desktop app (biometric/Touch ID).
151
+ **Two backends are available:**
152
+
153
+ **1. JavaScript SDK (recommended for CI/AI agents):** When `OP_SERVICE_ACCOUNT_TOKEN` is set, the tool uses the [@1password/sdk](https://www.npmjs.com/package/@1password/sdk) package directly — no CLI binary needed. This works in sandboxed environments (Claude Code, CI containers, etc.) where the `op` CLI can't run.
154
+
155
+ ```bash
156
+ export OP_SERVICE_ACCOUNT_TOKEN="ops_..."
157
+ ```
158
+
159
+ Create a [service account](https://developer.1password.com/docs/service-accounts/) with read/write access to the vault. Service accounts can only access custom vaults (not Personal/Private).
160
+
161
+ **2. CLI (`op`):** For interactive use with biometric/Touch ID auth via the [1Password desktop app](https://developer.1password.com/docs/cli/get-started/). Requires `op` CLI version 2.18.0+. Used automatically when no service account token is set.
152
162
 
153
163
  ### Bitwarden
154
164
 
@@ -172,6 +182,24 @@ interface SecretProvider {
172
182
  }
173
183
  ```
174
184
 
185
+ ## AI Skill
186
+
187
+ Installing this package automatically registers a [Claude Code skill](https://docs.anthropic.com/en/docs/claude-code) at `~/.claude/skills/sync-cf-secrets/`. This teaches AI assistants the full command reference and workflow patterns so they can help manage your secrets.
188
+
189
+ ## Contributing
190
+
191
+ This project uses [changesets](https://github.com/changesets/changesets) for version management.
192
+
193
+ ```bash
194
+ # After making changes, create a changeset
195
+ npm run changeset
196
+ # → interactive prompt: pick patch/minor/major, write a summary
197
+
198
+ # When ready to release
199
+ npm run version # bumps package.json version, updates CHANGELOG.md
200
+ npm run release # builds and publishes to npm
201
+ ```
202
+
175
203
  ## Security
176
204
 
177
205
  - Secret values are piped to wrangler via stdin — never exposed in CLI arguments or process listings
@@ -1,5 +1,5 @@
1
1
  import { error, log, success, warn } from "../utils.js";
2
- import { listSecrets, validateWrangler } from "../wrangler.js";
2
+ import { getWranglerVars, listSecrets, validateWrangler } from "../wrangler.js";
3
3
  export async function diff(provider, config, opts) {
4
4
  const envConfig = config.environments[opts.env];
5
5
  if (!envConfig) {
@@ -8,11 +8,13 @@ export async function diff(provider, config, opts) {
8
8
  }
9
9
  validateWrangler();
10
10
  log(`Comparing ${provider.name} item "${envConfig.item}" vs Cloudflare ${opts.env}...\n`);
11
- // Fetch field names from provider
12
- const providerFields = new Set(await provider.listFields({
11
+ // Fetch field names from provider, excluding wrangler vars
12
+ const wranglerVars = getWranglerVars(opts.env, config.wranglerConfig);
13
+ const allFields = await provider.listFields({
13
14
  vault: config.vault,
14
15
  item: envConfig.item,
15
- }));
16
+ });
17
+ const providerFields = new Set(allFields.filter((name) => !wranglerVars.has(name)));
16
18
  // Fetch deployed secret names from Cloudflare
17
19
  const cfSecrets = new Set(listSecrets(opts.env, config.wranglerConfig));
18
20
  const missingFromCf = [];
@@ -1 +1 @@
1
- {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"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"}
@@ -5,7 +5,7 @@ export type { SecretProvider } from "./types.js";
5
5
  */
6
6
  export declare function getProvider(name: string): SecretProvider;
7
7
  /**
8
- * Auto-detect which provider is available by checking for CLI tools.
8
+ * Auto-detect which provider is available by checking for CLI tools or env vars.
9
9
  */
10
10
  export declare function detectProvider(): SecretProvider;
11
11
  /**
@@ -1,14 +1,22 @@
1
1
  import { cliExists } from "../utils.js";
2
2
  import { BitwardenProvider } from "./bitwarden.js";
3
3
  import { OnePasswordProvider } from "./onepassword.js";
4
- const PROVIDERS = [
5
- () => new OnePasswordProvider(),
6
- () => new BitwardenProvider(),
7
- ];
4
+ import { OnePasswordSDKProvider } from "./onepassword-sdk.js";
5
+ /**
6
+ * Pick the best 1Password provider:
7
+ * - SDK (via @1password/sdk) when OP_SERVICE_ACCOUNT_TOKEN is set — works in non-interactive environments
8
+ * - CLI (via op) as fallback — requires biometric auth or desktop app
9
+ */
10
+ function onePasswordProvider() {
11
+ if (process.env.OP_SERVICE_ACCOUNT_TOKEN) {
12
+ return new OnePasswordSDKProvider();
13
+ }
14
+ return new OnePasswordProvider();
15
+ }
8
16
  const PROVIDER_MAP = {
9
- "1password": () => new OnePasswordProvider(),
10
- onepassword: () => new OnePasswordProvider(),
11
- op: () => new OnePasswordProvider(),
17
+ "1password": onePasswordProvider,
18
+ onepassword: onePasswordProvider,
19
+ op: onePasswordProvider,
12
20
  bitwarden: () => new BitwardenProvider(),
13
21
  bw: () => new BitwardenProvider(),
14
22
  };
@@ -24,19 +32,25 @@ export function getProvider(name) {
24
32
  return factory();
25
33
  }
26
34
  /**
27
- * Auto-detect which provider is available by checking for CLI tools.
35
+ * Auto-detect which provider is available by checking for CLI tools or env vars.
28
36
  */
29
37
  export function detectProvider() {
30
- for (const factory of PROVIDERS) {
31
- const provider = factory();
32
- if (cliExists(provider.cli)) {
33
- return provider;
34
- }
38
+ // Prefer SDK when service account token is available
39
+ if (process.env.OP_SERVICE_ACCOUNT_TOKEN) {
40
+ return new OnePasswordSDKProvider();
41
+ }
42
+ // Fall back to CLI detection
43
+ if (cliExists("op")) {
44
+ return new OnePasswordProvider();
45
+ }
46
+ if (cliExists("bw")) {
47
+ return new BitwardenProvider();
35
48
  }
36
- throw new Error("No supported password manager CLI found.\n" +
37
- "Install one of:\n" +
38
- " - 1Password CLI (op): https://developer.1password.com/docs/cli/\n" +
39
- " - Bitwarden CLI (bw): https://bitwarden.com/help/cli/");
49
+ throw new Error("No supported password manager found.\n" +
50
+ "Options:\n" +
51
+ " - Set OP_SERVICE_ACCOUNT_TOKEN for 1Password (no CLI needed)\n" +
52
+ " - Install 1Password CLI (op): https://developer.1password.com/docs/cli/\n" +
53
+ " - Install Bitwarden CLI (bw): https://bitwarden.com/help/cli/");
40
54
  }
41
55
  /**
42
56
  * Resolve a provider from an optional name — explicit lookup or auto-detect.
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAKvD,MAAM,SAAS,GAAgC;IAC7C,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE;IAC/B,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;CAC9B,CAAC;AAEF,MAAM,YAAY,GAAyC;IACzD,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE;IAC5C,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE;IAC5C,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,mBAAmB,EAAE;IACnC,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;IACxC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;CAClC,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,uBAAuB,KAAK,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,OAAO,EAAE,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,KAAK,MAAM,OAAO,IAAI,SAAS,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,OAAO,EAAE,CAAC;QAC3B,IAAI,SAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC;QAClB,CAAC;IACH,CAAC;IAED,MAAM,IAAI,KAAK,CACb,4CAA4C;QAC1C,mBAAmB;QACnB,qEAAqE;QACrE,yDAAyD,CAC5D,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;AACrD,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/providers/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAK9D;;;;GAIG;AACH,SAAS,mBAAmB;IAC1B,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC;QACzC,OAAO,IAAI,sBAAsB,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,IAAI,mBAAmB,EAAE,CAAC;AACnC,CAAC;AAED,MAAM,YAAY,GAAyC;IACzD,WAAW,EAAE,mBAAmB;IAChC,WAAW,EAAE,mBAAmB;IAChC,EAAE,EAAE,mBAAmB;IACvB,SAAS,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;IACxC,EAAE,EAAE,GAAG,EAAE,CAAC,IAAI,iBAAiB,EAAE;CAClC,CAAC;AAEF;;GAEG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACnD,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,uBAAuB,KAAK,EAAE,CAAC,CAAC;IAC3E,CAAC;IACD,OAAO,OAAO,EAAE,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc;IAC5B,qDAAqD;IACrD,IAAI,OAAO,CAAC,GAAG,CAAC,wBAAwB,EAAE,CAAC;QACzC,OAAO,IAAI,sBAAsB,EAAE,CAAC;IACtC,CAAC;IAED,6BAA6B;IAC7B,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,mBAAmB,EAAE,CAAC;IACnC,CAAC;IACD,IAAI,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,OAAO,IAAI,iBAAiB,EAAE,CAAC;IACjC,CAAC;IAED,MAAM,IAAI,KAAK,CACb,wCAAwC;QACtC,YAAY;QACZ,kEAAkE;QAClE,6EAA6E;QAC7E,iEAAiE,CACpE,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAAC,IAAa;IAC3C,OAAO,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,cAAc,EAAE,CAAC;AACrD,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { FetchOpts, SaveOpts, SecretProvider } from "./types.js";
2
+ export declare class OnePasswordSDKProvider implements SecretProvider {
3
+ name: string;
4
+ cli: string;
5
+ validate(): Promise<void>;
6
+ fetch(opts: FetchOpts): Promise<Map<string, string>>;
7
+ exists(opts: FetchOpts): Promise<boolean>;
8
+ save(opts: SaveOpts): Promise<void>;
9
+ listFields(opts: FetchOpts): Promise<string[]>;
10
+ }
@@ -0,0 +1,116 @@
1
+ let sdkModule = null;
2
+ let clientPromise = null;
3
+ async function getSDK() {
4
+ if (!sdkModule) {
5
+ sdkModule = await import("@1password/sdk");
6
+ }
7
+ return sdkModule;
8
+ }
9
+ async function getClient() {
10
+ if (!clientPromise) {
11
+ clientPromise = (async () => {
12
+ const { createClient } = await getSDK();
13
+ const token = process.env.OP_SERVICE_ACCOUNT_TOKEN;
14
+ if (!token) {
15
+ throw new Error("OP_SERVICE_ACCOUNT_TOKEN not set.\n" +
16
+ "Create a service account: https://developer.1password.com/docs/service-accounts/");
17
+ }
18
+ return createClient({
19
+ auth: token,
20
+ integrationName: "sync-cf-secrets",
21
+ integrationVersion: "1.0.0",
22
+ });
23
+ })();
24
+ }
25
+ return clientPromise;
26
+ }
27
+ /**
28
+ * Resolve a vault name to its ID.
29
+ */
30
+ async function resolveVaultId(vaultName) {
31
+ const client = await getClient();
32
+ const vaults = await client.vaults.list();
33
+ for await (const vault of vaults) {
34
+ if (vault.title === vaultName) {
35
+ return vault.id;
36
+ }
37
+ }
38
+ throw new Error(`Vault "${vaultName}" not found. Check that the service account has access to it.`);
39
+ }
40
+ /**
41
+ * Find all items matching a title in a vault.
42
+ */
43
+ async function findItems(vaultId, title) {
44
+ const client = await getClient();
45
+ const overviews = await client.items.list(vaultId);
46
+ const matches = [];
47
+ for await (const overview of overviews) {
48
+ if (overview.title === title) {
49
+ matches.push({ id: overview.id, vaultId: overview.vaultId });
50
+ }
51
+ }
52
+ return matches;
53
+ }
54
+ export class OnePasswordSDKProvider {
55
+ name = "1Password (SDK)";
56
+ cli = "op"; // Still report op for detection, but we don't use it
57
+ async validate() {
58
+ const client = await getClient();
59
+ // Quick check — list vaults to confirm auth works
60
+ const vaults = await client.vaults.list();
61
+ for await (const _vault of vaults) {
62
+ break;
63
+ }
64
+ }
65
+ async fetch(opts) {
66
+ const client = await getClient();
67
+ const vaultId = await resolveVaultId(opts.vault);
68
+ const matches = await findItems(vaultId, opts.item);
69
+ if (matches.length === 0) {
70
+ throw new Error(`Item "${opts.item}" not found in vault "${opts.vault}".`);
71
+ }
72
+ const item = await client.items.get(matches[0].vaultId, matches[0].id);
73
+ const secrets = new Map();
74
+ for (const field of item.fields) {
75
+ if (!field.title || field.value === undefined || field.value === "")
76
+ continue;
77
+ secrets.set(field.title, field.value);
78
+ }
79
+ return secrets;
80
+ }
81
+ async exists(opts) {
82
+ const vaultId = await resolveVaultId(opts.vault);
83
+ const matches = await findItems(vaultId, opts.item);
84
+ return matches.length > 0;
85
+ }
86
+ async save(opts) {
87
+ const client = await getClient();
88
+ const { ItemFieldType, ItemCategory } = await getSDK();
89
+ const vaultId = await resolveVaultId(opts.vault);
90
+ // Delete ALL existing items with this name
91
+ const existing = await findItems(vaultId, opts.item);
92
+ for (const item of existing) {
93
+ await client.items.delete(item.vaultId, item.id);
94
+ }
95
+ // Create new Secure Note with all fields
96
+ const fields = Array.from(opts.secrets.entries()).map(([label, value]) => ({
97
+ id: label,
98
+ title: label,
99
+ fieldType: ItemFieldType.Concealed,
100
+ value,
101
+ sectionId: "secrets",
102
+ }));
103
+ await client.items.create({
104
+ title: opts.item,
105
+ category: ItemCategory.SecureNote,
106
+ vaultId,
107
+ fields,
108
+ sections: [{ id: "secrets", title: "Secrets" }],
109
+ });
110
+ }
111
+ async listFields(opts) {
112
+ const secrets = await this.fetch(opts);
113
+ return Array.from(secrets.keys());
114
+ }
115
+ }
116
+ //# sourceMappingURL=onepassword-sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onepassword-sdk.js","sourceRoot":"","sources":["../../src/providers/onepassword-sdk.ts"],"names":[],"mappings":"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"}
@@ -18,8 +18,16 @@ export class OnePasswordProvider {
18
18
  exec("op whoami", { stdio: ["pipe", "pipe", "pipe"] });
19
19
  }
20
20
  catch {
21
- throw new Error("Not signed in to 1Password.\n" +
22
- "Run: eval $(op signin)");
21
+ const hasServiceToken = !!process.env.OP_SERVICE_ACCOUNT_TOKEN;
22
+ if (hasServiceToken) {
23
+ throw new Error("OP_SERVICE_ACCOUNT_TOKEN is set but authentication failed.\n" +
24
+ "Check that the token is valid and the service account has vault access.");
25
+ }
26
+ throw new Error("Not signed in to 1Password.\n\n" +
27
+ "Interactive use (biometric):\n" +
28
+ " Open the 1Password desktop app and try again.\n\n" +
29
+ "Non-interactive use (CI, AI agents):\n" +
30
+ " Set OP_SERVICE_ACCOUNT_TOKEN — see https://developer.1password.com/docs/service-accounts/");
23
31
  }
24
32
  }
25
33
  async fetch(opts) {
@@ -1 +1 @@
1
- {"version":3,"file":"onepassword.js","sourceRoot":"","sources":["../../src/providers/onepassword.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"onepassword.js","sourceRoot":"","sources":["../../src/providers/onepassword.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,18 +1,24 @@
1
1
  {
2
2
  "name": "sync-cf-secrets",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Sync Cloudflare Workers secrets from 1Password or Bitwarden. Push, pull, and diff secrets across environments.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "sync-cf-secrets": "./dist/cli.js"
8
8
  },
9
9
  "files": [
10
- "dist"
10
+ "dist",
11
+ "skill",
12
+ "scripts/postinstall.js"
11
13
  ],
12
14
  "scripts": {
13
15
  "build": "tsc",
14
16
  "dev": "tsc --watch",
15
- "prepublishOnly": "tsc"
17
+ "postinstall": "node scripts/postinstall.js",
18
+ "prepublishOnly": "tsc",
19
+ "changeset": "changeset",
20
+ "version": "changeset version",
21
+ "release": "npm run build && changeset publish"
16
22
  },
17
23
  "keywords": [
18
24
  "cloudflare",
@@ -46,7 +52,11 @@
46
52
  }
47
53
  },
48
54
  "devDependencies": {
55
+ "@changesets/cli": "^2.30.0",
49
56
  "@types/node": "^25.5.2",
50
57
  "typescript": "^5.7.0"
58
+ },
59
+ "dependencies": {
60
+ "@1password/sdk": "^0.4.0"
51
61
  }
52
62
  }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Copies the skill file to ~/.claude/skills/sync-cf-secrets/
4
+ // so Claude Code auto-discovers it.
5
+
6
+ import { existsSync, mkdirSync, copyFileSync } from "node:fs";
7
+ import { join, dirname } from "node:path";
8
+ import { homedir } from "node:os";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const skillSource = join(__dirname, "..", "skill", "SKILL.md");
13
+ const skillTarget = join(homedir(), ".claude", "skills", "sync-cf-secrets", "SKILL.md");
14
+
15
+ try {
16
+ if (!existsSync(skillSource)) {
17
+ // Silently skip if skill file not found (e.g. in CI)
18
+ process.exit(0);
19
+ }
20
+
21
+ const targetDir = dirname(skillTarget);
22
+ mkdirSync(targetDir, { recursive: true });
23
+ copyFileSync(skillSource, skillTarget);
24
+ console.log("sync-cf-secrets: Installed Claude Code skill");
25
+ } catch {
26
+ // Don't fail the install if skill copy fails
27
+ }
package/skill/SKILL.md ADDED
@@ -0,0 +1,202 @@
1
+ ---
2
+ name: sync-cf-secrets
3
+ version: 0.1.0
4
+ description: Sync Cloudflare Workers secrets from a password manager (1Password, Bitwarden). Use when the user asks about managing, pushing, pulling, or diffing Cloudflare secrets, or setting up environment variables for Cloudflare Workers.
5
+ requires:
6
+ bins: ["sync-cf-secrets"]
7
+ auth: true
8
+ ---
9
+
10
+ # Cloudflare Secrets Manager
11
+
12
+ Help users manage Cloudflare Workers secrets using the `sync-cf-secrets` CLI, which syncs secrets between a password manager (1Password or Bitwarden) and Cloudflare Workers environments.
13
+
14
+ ## Agent Guidance
15
+
16
+ ### Key Principles
17
+
18
+ - **Always use `--dry-run` first** — preview any destructive operation before committing
19
+ - **Never log or display secret values** — only show field names
20
+ - **Confirm before `init` or `copy`** — these delete and recreate password manager items
21
+ - **The password manager is the source of truth** — Cloudflare is the deployment target; Cloudflare does not expose secret values
22
+ - **Wrangler vars are excluded automatically** — variables defined as `vars` in the wrangler config are non-secret config; `init`, `copy`, and `push` skip them
23
+
24
+ ### Design Principles
25
+
26
+ - Auto-discovers environments from the wrangler config (`wrangler.toml` / `wrangler.jsonc`)
27
+ - Auto-detects the password manager (1Password SDK when `OP_SERVICE_ACCOUNT_TOKEN` is set, `op` CLI, or `bw` CLI)
28
+ - Config is optional — sensible defaults from `package.json` and wrangler config
29
+ - Secret values are piped via stdin to wrangler, never exposed in CLI arguments
30
+
31
+ ### Safety Rules
32
+
33
+ - Always use `--dry-run` before `push`, `init`, or `copy` to preview changes
34
+ - Never run `init` without the user confirming they want to replace existing items
35
+ - Never display secret values in output — only field names
36
+ - Verify the correct vault and environment before pushing
37
+
38
+ ### Workflow Patterns
39
+
40
+ #### First-time Setup
41
+
42
+ ```bash
43
+ # 1. Create password manager items from existing .dev.vars
44
+ sync-cf-secrets init --dry-run # preview first
45
+ sync-cf-secrets init
46
+
47
+ # 2. Copy local values to staging (most are the same)
48
+ sync-cf-secrets copy local staging
49
+
50
+ # 3. User edits staging item in password manager (change AUTH_SECRET, etc.)
51
+
52
+ # 4. Push to Cloudflare
53
+ sync-cf-secrets push staging --dry-run
54
+ sync-cf-secrets push staging
55
+
56
+ # 5. Repeat for production
57
+ sync-cf-secrets copy staging production
58
+ # User edits production item (swap test keys for live keys)
59
+ sync-cf-secrets push production
60
+ ```
61
+
62
+ #### Onboarding a New Developer
63
+
64
+ ```bash
65
+ # Pull local dev secrets from the shared password manager vault
66
+ sync-cf-secrets pull local
67
+ ```
68
+
69
+ #### Adding a New Secret
70
+
71
+ ```bash
72
+ # 1. Add the field to the password manager item(s) manually
73
+ # 2. Push the updated secrets
74
+ sync-cf-secrets push staging
75
+ sync-cf-secrets push production
76
+ ```
77
+
78
+ #### Auditing After a Deployment
79
+
80
+ ```bash
81
+ sync-cf-secrets diff staging
82
+ sync-cf-secrets diff production
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Command Reference
88
+
89
+ ### `init`
90
+
91
+ Create password manager items for all environments discovered from the wrangler config. Reads field names and values from `.dev.vars`. The `local` item gets actual values; other environments get `CHANGE_ME` placeholders. Excludes wrangler `vars`. Prompts before replacing existing items.
92
+
93
+ ```bash
94
+ sync-cf-secrets init
95
+ sync-cf-secrets init --dry-run
96
+ ```
97
+
98
+ ### `copy <from> <to>`
99
+
100
+ Copy secrets from one environment's password manager item to another. Excludes wrangler `vars` for the target environment. Prompts before replacing the target.
101
+
102
+ ```bash
103
+ sync-cf-secrets copy local staging
104
+ sync-cf-secrets copy staging production
105
+ sync-cf-secrets copy local staging --dry-run
106
+ ```
107
+
108
+ ### `push <env>`
109
+
110
+ Push secrets from the password manager to Cloudflare via `wrangler secret put`. Skips wrangler `vars`. Reports progress per secret and continues on individual failures.
111
+
112
+ ```bash
113
+ sync-cf-secrets push staging
114
+ sync-cf-secrets push production
115
+ sync-cf-secrets push staging --dry-run
116
+ ```
117
+
118
+ ### `pull <env>`
119
+
120
+ Fetch secrets from the password manager and write a `.dev.vars` file.
121
+
122
+ ```bash
123
+ sync-cf-secrets pull local
124
+ sync-cf-secrets pull staging --dry-run
125
+ ```
126
+
127
+ ### `list <env>`
128
+
129
+ List secret names deployed to a Cloudflare Workers environment. Values are never exposed.
130
+
131
+ ```bash
132
+ sync-cf-secrets list staging
133
+ sync-cf-secrets list production
134
+ ```
135
+
136
+ ### `diff <env>`
137
+
138
+ Compare secret names in the password manager vs what's deployed to Cloudflare. Shows missing, extra, and matching secrets.
139
+
140
+ ```bash
141
+ sync-cf-secrets diff staging
142
+ sync-cf-secrets diff production --verbose
143
+ ```
144
+
145
+ ### Global Options
146
+
147
+ | Option | Description |
148
+ |---|---|
149
+ | `--provider <name>` | Force a provider: `1password`, `bitwarden` |
150
+ | `--vault <name>` | Override the vault name |
151
+ | `--dry-run` | Preview without making changes |
152
+ | `--verbose` | Show more detail |
153
+
154
+ ---
155
+
156
+ ## Configuration
157
+
158
+ Optional `.sync-cf-secrets.json` in project root:
159
+
160
+ ```json
161
+ {
162
+ "provider": "1password",
163
+ "vault": "myproject",
164
+ "prefix": "myproject",
165
+ "wranglerConfig": "apps/web/wrangler.jsonc",
166
+ "devVarsPath": "apps/web/.dev.vars"
167
+ }
168
+ ```
169
+
170
+ All fields have defaults: `provider` auto-detected, `vault`/`prefix` from `package.json` name, `wranglerConfig` auto-searched, `devVarsPath` next to wrangler config, environments auto-discovered from wrangler config `env` block.
171
+
172
+ ## Authentication
173
+
174
+ ### For AI agents (non-interactive)
175
+
176
+ When `OP_SERVICE_ACCOUNT_TOKEN` is set, the tool uses the 1Password JavaScript SDK directly — no CLI binary needed. This works in sandboxed environments (Claude Code, CI containers) where the `op` CLI can't run.
177
+
178
+ 1. Ask the user to create a service account at https://my.1password.com/ (Developer → Service Accounts)
179
+ 2. The service account needs **read and write** access to the vault used for secrets
180
+ 3. The user sets the token as an environment variable:
181
+ ```bash
182
+ export OP_SERVICE_ACCOUNT_TOKEN="ops_..."
183
+ ```
184
+ 4. Once set, all `sync-cf-secrets` commands work without biometric prompts or the `op` CLI
185
+
186
+ **Important limitations of service accounts:**
187
+ - Cannot access Personal, Private, or the default Shared vault — only custom vaults
188
+ - The vault must be explicitly granted to the service account during creation
189
+ - The token is shown once at creation — it must be saved immediately
190
+
191
+ If `OP_SERVICE_ACCOUNT_TOKEN` is not set and `op whoami` fails, guide the user through this setup.
192
+
193
+ ### For interactive use
194
+
195
+ - **1Password**: `op` CLI installed, authenticated via the desktop app (biometric/Touch ID)
196
+ - **Bitwarden**: `bw` CLI installed, `bw login` + `export BW_SESSION=$(bw unlock --raw)`
197
+
198
+ ### Prerequisites
199
+
200
+ - **1Password**: Either `OP_SERVICE_ACCOUNT_TOKEN` set (uses JS SDK, no CLI needed) or `op` CLI (>= 2.18.0) installed
201
+ - **Bitwarden**: `bw` CLI installed
202
+ - `wrangler` installed (for `push`, `list`, `diff` commands)