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 +30 -2
- package/dist/commands/diff.js +6 -4
- package/dist/commands/diff.js.map +1 -1
- package/dist/providers/index.d.ts +1 -1
- package/dist/providers/index.js +31 -17
- package/dist/providers/index.js.map +1 -1
- package/dist/providers/onepassword-sdk.d.ts +10 -0
- package/dist/providers/onepassword-sdk.js +116 -0
- package/dist/providers/onepassword-sdk.js.map +1 -0
- package/dist/providers/onepassword.js +10 -2
- package/dist/providers/onepassword.js.map +1 -1
- package/package.json +13 -3
- package/scripts/postinstall.js +27 -0
- package/skill/SKILL.md +202 -0
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
|
|
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
|
-
|
|
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
|
package/dist/commands/diff.js
CHANGED
|
@@ -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
|
|
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":"AAEA,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;
|
|
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
|
/**
|
package/dist/providers/index.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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":
|
|
10
|
-
onepassword:
|
|
11
|
-
op:
|
|
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
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
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
|
|
37
|
-
"
|
|
38
|
-
" - 1Password CLI
|
|
39
|
-
" -
|
|
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;
|
|
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
|
-
|
|
22
|
-
|
|
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":"AAAA,OAAO,EAAE,YAAY,EAAY,MAAM,oBAAoB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAG9C,2DAA2D;AAC3D,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC;IAChC,UAAU;IACV,UAAU;IACV,YAAY;CACb,CAAC,CAAC;AAgBH,MAAM,OAAO,mBAAmB;IAC9B,IAAI,GAAG,WAAW,CAAC;IACnB,GAAG,GAAG,IAAI,CAAC;IAEX,KAAK,CAAC,QAAQ;QACZ,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,iCAAiC;gBAC/B,gEAAgE,CACnE,CAAC;QACJ,CAAC;QAED,IAAI,CAAC;YACH,IAAI,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,KAAK,CACb
|
|
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.
|
|
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
|
-
"
|
|
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)
|