sync-cf-secrets 0.2.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 +12 -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 +4 -1
- package/skill/SKILL.md +29 -3
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
|
|
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,6 +1,6 @@
|
|
|
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": {
|
|
@@ -55,5 +55,8 @@
|
|
|
55
55
|
"@changesets/cli": "^2.30.0",
|
|
56
56
|
"@types/node": "^25.5.2",
|
|
57
57
|
"typescript": "^5.7.0"
|
|
58
|
+
},
|
|
59
|
+
"dependencies": {
|
|
60
|
+
"@1password/sdk": "^0.4.0"
|
|
58
61
|
}
|
|
59
62
|
}
|
package/skill/SKILL.md
CHANGED
|
@@ -24,7 +24,7 @@ Help users manage Cloudflare Workers secrets using the `sync-cf-secrets` CLI, wh
|
|
|
24
24
|
### Design Principles
|
|
25
25
|
|
|
26
26
|
- Auto-discovers environments from the wrangler config (`wrangler.toml` / `wrangler.jsonc`)
|
|
27
|
-
- Auto-detects the password manager
|
|
27
|
+
- Auto-detects the password manager (1Password SDK when `OP_SERVICE_ACCOUNT_TOKEN` is set, `op` CLI, or `bw` CLI)
|
|
28
28
|
- Config is optional — sensible defaults from `package.json` and wrangler config
|
|
29
29
|
- Secret values are piped via stdin to wrangler, never exposed in CLI arguments
|
|
30
30
|
|
|
@@ -169,8 +169,34 @@ Optional `.sync-cf-secrets.json` in project root:
|
|
|
169
169
|
|
|
170
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
171
|
|
|
172
|
-
##
|
|
172
|
+
## Authentication
|
|
173
173
|
|
|
174
|
-
|
|
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)
|
|
175
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
|
|
176
202
|
- `wrangler` installed (for `push`, `list`, `diff` commands)
|