@clef-sh/cloud 0.1.18-beta.85

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.
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Try to locate the bundled keyservice binary from the platform-specific npm package.
3
+ * Returns the resolved path or null if the package is not installed.
4
+ */
5
+ export declare function tryBundledKeyservice(): string | null;
6
+ //# sourceMappingURL=bundled.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bundled.d.ts","sourceRoot":"","sources":["../src/bundled.ts"],"names":[],"mappings":"AAOA;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,IAAI,CA4BpD"}
package/dist/cli.d.mts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * CLI plugin entry point for @clef-sh/cloud.
3
+ *
4
+ * Loaded dynamically by @clef-sh/cli via `import("@clef-sh/cloud/cli")`.
5
+ * Registers cloud-specific commands (init, login, status) on the Commander program.
6
+ */
7
+ export { registerCloudCommands } from "./commands/cloud";
8
+ export type { CloudCliDeps } from "./commands/cloud";
9
+ //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts ADDED
@@ -0,0 +1,9 @@
1
+ /**
2
+ * CLI plugin entry point for @clef-sh/cloud.
3
+ *
4
+ * Loaded dynamically by @clef-sh/cli via `import("@clef-sh/cloud/cli")`.
5
+ * Registers cloud-specific commands (init, login, status) on the Commander program.
6
+ */
7
+ export { registerCloudCommands } from "./commands/cloud";
8
+ export type { CloudCliDeps } from "./commands/cloud";
9
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AACzD,YAAY,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/cli.js ADDED
@@ -0,0 +1,604 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/cli.ts
31
+ var cli_exports = {};
32
+ __export(cli_exports, {
33
+ registerCloudCommands: () => registerCloudCommands
34
+ });
35
+ module.exports = __toCommonJS(cli_exports);
36
+
37
+ // src/commands/cloud.ts
38
+ var path4 = __toESM(require("path"));
39
+ var import_core2 = require("@clef-sh/core");
40
+
41
+ // src/keyservice.ts
42
+ var import_child_process = require("child_process");
43
+ var readline = __toESM(require("readline"));
44
+ var PORT_REGEX = /^PORT=(\d+)$/;
45
+ var STARTUP_TIMEOUT_MS = 5e3;
46
+ var SHUTDOWN_TIMEOUT_MS = 3e3;
47
+ async function spawnKeyservice(options) {
48
+ const args = ["--addr", "127.0.0.1:0"];
49
+ if (options.endpoint) {
50
+ args.push("--endpoint", options.endpoint);
51
+ }
52
+ const child = (0, import_child_process.spawn)(options.binaryPath, args, {
53
+ stdio: ["ignore", "pipe", "pipe"],
54
+ env: { ...process.env, CLEF_CLOUD_TOKEN: options.token }
55
+ });
56
+ const port = await readPort(child);
57
+ const addr = `tcp://127.0.0.1:${port}`;
58
+ return {
59
+ addr,
60
+ kill: () => killGracefully(child)
61
+ };
62
+ }
63
+ function readPort(child) {
64
+ return new Promise((resolve, reject) => {
65
+ let settled = false;
66
+ const rl = readline.createInterface({ input: child.stdout });
67
+ function settle() {
68
+ clearTimeout(timer);
69
+ rl.close();
70
+ }
71
+ const timer = setTimeout(() => {
72
+ if (!settled) {
73
+ settled = true;
74
+ settle();
75
+ child.kill("SIGKILL");
76
+ reject(new Error("Keyservice did not start within 5 seconds."));
77
+ }
78
+ }, STARTUP_TIMEOUT_MS);
79
+ rl.on("line", (line) => {
80
+ const match = PORT_REGEX.exec(line);
81
+ if (match && !settled) {
82
+ settled = true;
83
+ settle();
84
+ resolve(parseInt(match[1], 10));
85
+ }
86
+ });
87
+ child.on("error", (err) => {
88
+ if (!settled) {
89
+ settled = true;
90
+ settle();
91
+ reject(new Error(`Failed to start keyservice: ${err.message}`));
92
+ }
93
+ });
94
+ child.on("exit", (code) => {
95
+ if (!settled) {
96
+ settled = true;
97
+ settle();
98
+ reject(new Error(`Keyservice exited unexpectedly with code ${code}.`));
99
+ }
100
+ });
101
+ });
102
+ }
103
+ function killGracefully(child) {
104
+ return new Promise((resolve) => {
105
+ if (child.exitCode !== null) {
106
+ resolve();
107
+ return;
108
+ }
109
+ const timer = setTimeout(() => {
110
+ child.kill("SIGKILL");
111
+ }, SHUTDOWN_TIMEOUT_MS);
112
+ child.on("exit", () => {
113
+ clearTimeout(timer);
114
+ resolve();
115
+ });
116
+ child.kill("SIGTERM");
117
+ });
118
+ }
119
+
120
+ // src/resolver.ts
121
+ var fs2 = __toESM(require("fs"));
122
+ var path2 = __toESM(require("path"));
123
+
124
+ // src/bundled.ts
125
+ var fs = __toESM(require("fs"));
126
+ var path = __toESM(require("path"));
127
+ function tryBundledKeyservice() {
128
+ const platform = process.platform;
129
+ const arch = process.arch;
130
+ const archName = arch === "x64" ? "x64" : arch === "arm64" ? "arm64" : null;
131
+ if (!archName) return null;
132
+ const platformName = platform === "darwin" ? "darwin" : platform === "linux" ? "linux" : platform === "win32" ? "win32" : null;
133
+ if (!platformName) return null;
134
+ const packageName = `@clef-sh/keyservice-${platformName}-${archName}`;
135
+ const binName = platform === "win32" ? "clef-keyservice.exe" : "clef-keyservice";
136
+ try {
137
+ const packageMain = require.resolve(`${packageName}/package.json`);
138
+ const packageDir = path.dirname(packageMain);
139
+ const binPath = path.join(packageDir, "bin", binName);
140
+ return fs.existsSync(binPath) ? binPath : null;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ // src/resolver.ts
147
+ function validateKeyservicePath(candidate) {
148
+ if (!path2.isAbsolute(candidate)) {
149
+ throw new Error(`CLEF_KEYSERVICE_PATH must be an absolute path, got '${candidate}'.`);
150
+ }
151
+ const segments = candidate.split(/[/\\]/);
152
+ if (segments.includes("..")) {
153
+ throw new Error(
154
+ `CLEF_KEYSERVICE_PATH contains '..' path segments ('${candidate}'). Use an absolute path without directory traversal.`
155
+ );
156
+ }
157
+ }
158
+ var cached;
159
+ function resolveKeyservicePath() {
160
+ if (cached) return cached;
161
+ const envPath = process.env.CLEF_KEYSERVICE_PATH?.trim();
162
+ if (envPath) {
163
+ validateKeyservicePath(envPath);
164
+ if (!fs2.existsSync(envPath)) {
165
+ throw new Error(`CLEF_KEYSERVICE_PATH points to '${envPath}' but the file does not exist.`);
166
+ }
167
+ cached = { path: envPath, source: "env" };
168
+ return cached;
169
+ }
170
+ const bundledPath = tryBundledKeyservice();
171
+ if (bundledPath) {
172
+ cached = { path: bundledPath, source: "bundled" };
173
+ return cached;
174
+ }
175
+ cached = { path: "clef-keyservice", source: "system" };
176
+ return cached;
177
+ }
178
+
179
+ // src/credentials.ts
180
+ var fs3 = __toESM(require("fs"));
181
+ var path3 = __toESM(require("path"));
182
+ var os = __toESM(require("os"));
183
+ var YAML = __toESM(require("yaml"));
184
+
185
+ // src/constants.ts
186
+ var CLOUD_DEFAULT_ENDPOINT = "https://api.clef.sh";
187
+
188
+ // src/credentials.ts
189
+ var CREDENTIALS_FILENAME = "credentials.yaml";
190
+ function readCloudCredentials() {
191
+ const credPath = path3.join(os.homedir(), ".clef", CREDENTIALS_FILENAME);
192
+ let raw;
193
+ try {
194
+ raw = YAML.parse(fs3.readFileSync(credPath, "utf-8"));
195
+ } catch {
196
+ return null;
197
+ }
198
+ if (!raw || typeof raw !== "object") return null;
199
+ const obj = raw;
200
+ const refreshToken = typeof obj.refreshToken === "string" ? obj.refreshToken : "";
201
+ const accessToken = typeof obj.accessToken === "string" ? obj.accessToken : void 0;
202
+ const accessTokenExpiry = typeof obj.accessTokenExpiry === "number" ? obj.accessTokenExpiry : void 0;
203
+ const endpoint = typeof obj.endpoint === "string" ? obj.endpoint : CLOUD_DEFAULT_ENDPOINT;
204
+ const cognitoDomain = typeof obj.cognitoDomain === "string" ? obj.cognitoDomain : void 0;
205
+ const clientId = typeof obj.clientId === "string" ? obj.clientId : void 0;
206
+ if (!refreshToken && endpoint === CLOUD_DEFAULT_ENDPOINT) return null;
207
+ return { refreshToken, accessToken, accessTokenExpiry, endpoint, cognitoDomain, clientId };
208
+ }
209
+ function writeCloudCredentials(credentials) {
210
+ const clefDir = path3.join(os.homedir(), ".clef");
211
+ fs3.mkdirSync(clefDir, { recursive: true, mode: 448 });
212
+ const credPath = path3.join(clefDir, CREDENTIALS_FILENAME);
213
+ const content = Object.fromEntries(
214
+ Object.entries(credentials).filter(([, v]) => v !== void 0)
215
+ );
216
+ fs3.writeFileSync(credPath, YAML.stringify(content), { mode: 384 });
217
+ }
218
+
219
+ // src/device-flow.ts
220
+ async function initiateDeviceFlow(endpoint, options) {
221
+ const base = endpoint ?? CLOUD_DEFAULT_ENDPOINT;
222
+ const payload = {
223
+ clientType: "cli",
224
+ clientVersion: options.clientVersion,
225
+ repoName: options.repoName,
226
+ flow: options.flow
227
+ };
228
+ if (options.environment) {
229
+ payload.environment = options.environment;
230
+ }
231
+ let res;
232
+ try {
233
+ res = await fetch(`${base}/api/v1/device/init`, {
234
+ method: "POST",
235
+ headers: { "Content-Type": "application/json" },
236
+ body: JSON.stringify(payload)
237
+ });
238
+ } catch (err) {
239
+ const cause = err instanceof Error ? err.cause : void 0;
240
+ const reason = cause instanceof Error ? cause.message : err instanceof Error ? err.message : String(err);
241
+ throw new Error(`Could not reach Clef Cloud at ${base}: ${reason}`);
242
+ }
243
+ if (!res.ok) {
244
+ const body = await res.text().catch(() => "");
245
+ throw new Error(`Device flow init failed (${res.status}): ${body}`);
246
+ }
247
+ const json = await res.json();
248
+ const session = json.data ?? json;
249
+ if (session.pollUrl && !session.pollUrl.startsWith("http")) {
250
+ session.pollUrl = `${base}${session.pollUrl}`;
251
+ }
252
+ return session;
253
+ }
254
+ async function pollDeviceFlow(pollUrl) {
255
+ let res;
256
+ try {
257
+ res = await fetch(pollUrl);
258
+ } catch (err) {
259
+ const cause = err instanceof Error ? err.cause : void 0;
260
+ const reason = cause instanceof Error ? cause.message : err instanceof Error ? err.message : String(err);
261
+ throw new Error(`Could not reach Clef Cloud poll endpoint: ${reason}`);
262
+ }
263
+ if (!res.ok) {
264
+ const body = await res.text().catch(() => "");
265
+ throw new Error(`Device flow poll failed (${res.status}): ${body}`);
266
+ }
267
+ const json = await res.json();
268
+ return json.data ?? json;
269
+ }
270
+
271
+ // src/pack-client.ts
272
+ var import_core = require("@clef-sh/core");
273
+
274
+ // src/token-refresh.ts
275
+ async function refreshAccessToken(config) {
276
+ const url = `${config.cognitoDomain}/oauth2/token`;
277
+ const body = new URLSearchParams({
278
+ grant_type: "refresh_token",
279
+ client_id: config.clientId,
280
+ refresh_token: config.refreshToken
281
+ });
282
+ const res = await fetch(url, {
283
+ method: "POST",
284
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
285
+ body: body.toString()
286
+ });
287
+ if (!res.ok) {
288
+ const text = await res.text().catch(() => "");
289
+ if (res.status === 400 && text.includes("invalid_grant")) {
290
+ throw new Error(
291
+ "Refresh token expired or revoked. Run 'clef cloud login' to re-authenticate."
292
+ );
293
+ }
294
+ throw new Error(`Token refresh failed (${res.status}): ${text}`);
295
+ }
296
+ const data = await res.json();
297
+ return {
298
+ accessToken: data.access_token,
299
+ idToken: data.id_token,
300
+ expiresIn: data.expires_in
301
+ };
302
+ }
303
+
304
+ // src/sops.ts
305
+ async function resolveAccessToken() {
306
+ const clefToken = process.env.CLEF_TOKEN;
307
+ if (clefToken) {
308
+ const creds2 = readCloudCredentials();
309
+ return { accessToken: clefToken, endpoint: creds2?.endpoint };
310
+ }
311
+ const creds = readCloudCredentials();
312
+ const refreshToken = process.env.CLEF_CLOUD_REFRESH_TOKEN ?? creds?.refreshToken;
313
+ if (!refreshToken) {
314
+ throw new Error("Not authenticated. Run 'clef cloud login' to connect to Clef Cloud.");
315
+ }
316
+ if (creds?.accessToken && creds?.accessTokenExpiry && Date.now() < creds.accessTokenExpiry - 6e4) {
317
+ return { accessToken: creds.accessToken, endpoint: creds?.endpoint };
318
+ }
319
+ if (!creds?.cognitoDomain || !creds?.clientId) {
320
+ throw new Error("Missing Cognito configuration. Run 'clef cloud login' to re-authenticate.");
321
+ }
322
+ const result = await refreshAccessToken({
323
+ cognitoDomain: creds.cognitoDomain,
324
+ clientId: creds.clientId,
325
+ refreshToken
326
+ });
327
+ writeCloudCredentials({
328
+ ...creds,
329
+ refreshToken,
330
+ accessToken: result.accessToken,
331
+ accessTokenExpiry: Date.now() + result.expiresIn * 1e3
332
+ });
333
+ return { accessToken: result.accessToken, endpoint: creds?.endpoint };
334
+ }
335
+
336
+ // src/commands/cloud.ts
337
+ var POLL_INTERVAL_MS = 2e3;
338
+ function registerCloudCommands(program, deps) {
339
+ const { formatter, sym, runner } = deps;
340
+ const cloud = program.command("cloud").description("Manage Clef Cloud integration.");
341
+ cloud.command("status").description("Show Clef Cloud integration status.").action(async () => {
342
+ try {
343
+ const repoRoot = program.opts().dir || process.cwd();
344
+ const parser = new import_core2.ManifestParser();
345
+ let manifest;
346
+ try {
347
+ manifest = parser.parse(path4.join(repoRoot, "clef.yaml"));
348
+ } catch {
349
+ formatter.print(`${sym("info")} No clef.yaml found in ${repoRoot}`);
350
+ return;
351
+ }
352
+ formatter.print(`${sym("clef")} Clef Cloud Status
353
+ `);
354
+ if (manifest.cloud) {
355
+ formatter.print(` Integration: ${manifest.cloud.integrationId}`);
356
+ formatter.print(` Key ID: ${manifest.cloud.keyId}`);
357
+ } else {
358
+ formatter.print(` Cloud: not configured`);
359
+ formatter.hint("\n Run 'clef cloud init --env <environment>' to set up Cloud.");
360
+ return;
361
+ }
362
+ const cloudEnvs = manifest.environments.filter((e) => e.sops?.backend === "cloud");
363
+ const defaultCloud = manifest.sops.default_backend === "cloud";
364
+ if (cloudEnvs.length > 0 || defaultCloud) {
365
+ const envNames = defaultCloud ? manifest.environments.map((e) => e.name) : cloudEnvs.map((e) => e.name);
366
+ formatter.print(` Environments: ${envNames.join(", ")}`);
367
+ } else {
368
+ formatter.print(` Environments: none using cloud backend`);
369
+ }
370
+ const creds = readCloudCredentials();
371
+ if (creds) {
372
+ formatter.print(` Auth: authenticated`);
373
+ formatter.print(` Endpoint: ${creds.endpoint}`);
374
+ } else {
375
+ formatter.print(` Auth: not authenticated`);
376
+ formatter.hint(" Run 'clef cloud login' to authenticate.");
377
+ }
378
+ try {
379
+ const ks = resolveKeyservicePath();
380
+ formatter.print(` Keyservice: ${ks.source} (${ks.path})`);
381
+ } catch {
382
+ formatter.print(` Keyservice: not found`);
383
+ formatter.hint(" Install the cloud package: npm install @clef-sh/cloud");
384
+ }
385
+ } catch (err) {
386
+ const message = err instanceof Error ? err.message : String(err);
387
+ formatter.error(message);
388
+ process.exit(1);
389
+ }
390
+ });
391
+ cloud.command("init").description("Set up Clef Cloud for an environment.").requiredOption("--env <environment>", "Target environment (e.g., production)").action(async (opts) => {
392
+ try {
393
+ const repoRoot = program.opts().dir || process.cwd();
394
+ const parser = new import_core2.ManifestParser();
395
+ const manifest = parser.parse(path4.join(repoRoot, "clef.yaml"));
396
+ const targetEnv = manifest.environments.find((e) => e.name === opts.env);
397
+ if (!targetEnv) {
398
+ formatter.error(
399
+ `Environment '${opts.env}' not found in clef.yaml. Available: ${manifest.environments.map((e) => e.name).join(", ")}`
400
+ );
401
+ process.exit(1);
402
+ return;
403
+ }
404
+ if (targetEnv.sops?.backend === "cloud" && manifest.cloud) {
405
+ formatter.info(
406
+ `Environment '${opts.env}' is already using Cloud backend (${manifest.cloud.keyId}). Nothing to do.`
407
+ );
408
+ return;
409
+ }
410
+ let keyservicePath;
411
+ try {
412
+ keyservicePath = resolveKeyservicePath().path;
413
+ } catch {
414
+ formatter.error(
415
+ "Keyservice binary not found. Install the cloud package: npm install @clef-sh/cloud"
416
+ );
417
+ process.exit(1);
418
+ return;
419
+ }
420
+ formatter.print(`${sym("clef")} Clef Cloud
421
+ `);
422
+ const existingCreds = readCloudCredentials();
423
+ const cloudEndpoint = existingCreds?.endpoint ?? CLOUD_DEFAULT_ENDPOINT;
424
+ formatter.print(` Endpoint: ${cloudEndpoint}`);
425
+ formatter.print(
426
+ ` Creds: ${existingCreds ? `authenticated=${existingCreds.refreshToken ? "yes" : "no"}, endpoint=${existingCreds.endpoint}` : "none"}`
427
+ );
428
+ let integrationId;
429
+ let keyId;
430
+ let deviceFlowAccessToken;
431
+ if (existingCreds && existingCreds.refreshToken && manifest.cloud) {
432
+ integrationId = manifest.cloud.integrationId;
433
+ keyId = manifest.cloud.keyId;
434
+ formatter.print(` Using existing Cloud integration: ${keyId}`);
435
+ } else {
436
+ formatter.print(` Opening browser to set up Cloud for ${opts.env}...`);
437
+ const session = await initiateDeviceFlow(cloudEndpoint, {
438
+ repoName: path4.basename(repoRoot),
439
+ environment: opts.env,
440
+ clientVersion: deps.cliVersion,
441
+ flow: "setup"
442
+ });
443
+ formatter.print(` If the browser doesn't open, visit:
444
+ ${session.loginUrl}
445
+ `);
446
+ await deps.openBrowser(session.loginUrl, runner);
447
+ formatter.print(` Waiting for authorization... (press Ctrl+C to cancel)`);
448
+ const result = await pollUntilComplete(session.pollUrl);
449
+ if (result.status !== "complete" || !result.token || !result.integrationId || !result.keyId) {
450
+ formatter.error(
451
+ result.status === "expired" ? "Session expired. Run 'clef cloud init' again." : "Setup cancelled."
452
+ );
453
+ process.exit(1);
454
+ return;
455
+ }
456
+ integrationId = result.integrationId;
457
+ keyId = result.keyId;
458
+ const creds = {
459
+ refreshToken: result.token,
460
+ endpoint: existingCreds?.endpoint,
461
+ cognitoDomain: result.cognitoDomain,
462
+ clientId: result.clientId
463
+ };
464
+ if (result.accessToken && result.accessTokenExpiresIn) {
465
+ creds.accessToken = result.accessToken;
466
+ creds.accessTokenExpiry = Date.now() + result.accessTokenExpiresIn * 1e3;
467
+ deviceFlowAccessToken = result.accessToken;
468
+ }
469
+ writeCloudCredentials(creds);
470
+ formatter.success("Authorized");
471
+ }
472
+ formatter.print(`
473
+ Provisioning Cloud backend for ${opts.env}...`);
474
+ formatter.print(` ${sym("success")} KMS key provisioned: ${keyId}`);
475
+ formatter.print(`
476
+ Migrating ${opts.env} secrets to Cloud backend...`);
477
+ const cloudManifest = structuredClone(manifest);
478
+ cloudManifest.cloud = { integrationId, keyId };
479
+ const cloudEnv = cloudManifest.environments.find((e) => e.name === opts.env);
480
+ if (cloudEnv) {
481
+ cloudEnv.sops = { backend: "cloud" };
482
+ }
483
+ const matrixManager = new import_core2.MatrixManager();
484
+ const cells = matrixManager.resolveMatrix(manifest, repoRoot).filter((c) => c.environment === opts.env && c.exists);
485
+ if (cells.length === 0) {
486
+ formatter.print(` No encrypted files found for ${opts.env}.`);
487
+ } else {
488
+ const ageSopsClient = await deps.createSopsClient(repoRoot, runner);
489
+ const { accessToken, endpoint: ksEndpoint } = deviceFlowAccessToken ? { accessToken: deviceFlowAccessToken, endpoint: cloudEndpoint } : await resolveAccessToken();
490
+ const ksHandle = await spawnKeyservice({
491
+ binaryPath: keyservicePath,
492
+ token: accessToken,
493
+ endpoint: ksEndpoint
494
+ });
495
+ try {
496
+ const cloudSopsClient = await deps.createSopsClient(repoRoot, runner, ksHandle.addr);
497
+ for (const cell of cells) {
498
+ const decrypted = await ageSopsClient.decrypt(cell.filePath);
499
+ await cloudSopsClient.encrypt(
500
+ cell.filePath,
501
+ decrypted.values,
502
+ cloudManifest,
503
+ cell.environment
504
+ );
505
+ const relPath = path4.relative(repoRoot, cell.filePath);
506
+ formatter.print(` ${sym("success")} ${relPath}`);
507
+ }
508
+ formatter.print(`
509
+ Verifying encrypted files...`);
510
+ for (const cell of cells) {
511
+ await cloudSopsClient.decrypt(cell.filePath);
512
+ const relPath = path4.relative(repoRoot, cell.filePath);
513
+ formatter.print(` ${sym("success")} ${relPath}`);
514
+ }
515
+ } finally {
516
+ await ksHandle.kill();
517
+ }
518
+ }
519
+ const rawManifest = (0, import_core2.readManifestYaml)(repoRoot);
520
+ rawManifest.cloud = { integrationId, keyId };
521
+ const envs = rawManifest.environments;
522
+ const targetRawEnv = envs.find((e) => e.name === opts.env);
523
+ if (targetRawEnv) {
524
+ targetRawEnv.sops = { backend: "cloud" };
525
+ }
526
+ (0, import_core2.writeManifestYaml)(repoRoot, rawManifest);
527
+ formatter.print(`
528
+ ${sym("success")} Cloud setup complete.
529
+ `);
530
+ formatter.print(` Your ${opts.env} environment now uses Clef Cloud for encryption.`);
531
+ formatter.print(` Other environments continue to use age keys locally.
532
+ `);
533
+ formatter.hint(" Commit your changes: git add clef.yaml && git commit");
534
+ } catch (err) {
535
+ const message = err instanceof Error ? err.message : String(err);
536
+ formatter.error(message);
537
+ process.exit(1);
538
+ }
539
+ });
540
+ cloud.command("login").description("Authenticate with Clef Cloud.").action(async () => {
541
+ try {
542
+ formatter.print(`${sym("clef")} Clef Cloud
543
+ `);
544
+ const existingCreds = readCloudCredentials();
545
+ const endpoint = existingCreds?.endpoint;
546
+ const session = await initiateDeviceFlow(endpoint, {
547
+ repoName: path4.basename(process.cwd()),
548
+ clientVersion: deps.cliVersion,
549
+ flow: "login"
550
+ });
551
+ formatter.print(` Opening browser to log in...`);
552
+ formatter.print(` If the browser doesn't open, visit:
553
+ ${session.loginUrl}
554
+ `);
555
+ const opened = await deps.openBrowser(session.loginUrl, runner);
556
+ if (!opened) {
557
+ formatter.warn("Could not open browser automatically. Visit the URL above.");
558
+ }
559
+ formatter.print(` Waiting for authorization... (press Ctrl+C to cancel)`);
560
+ const result = await pollUntilComplete(session.pollUrl);
561
+ if (result.status === "expired") {
562
+ formatter.error("Session expired. Run 'clef cloud login' again.");
563
+ process.exit(1);
564
+ return;
565
+ }
566
+ if (result.status === "cancelled") {
567
+ formatter.info("Login cancelled.");
568
+ return;
569
+ }
570
+ if (result.token) {
571
+ const creds = {
572
+ refreshToken: result.token,
573
+ endpoint,
574
+ cognitoDomain: result.cognitoDomain,
575
+ clientId: result.clientId
576
+ };
577
+ if (result.accessToken && result.accessTokenExpiresIn) {
578
+ creds.accessToken = result.accessToken;
579
+ creds.accessTokenExpiry = Date.now() + result.accessTokenExpiresIn * 1e3;
580
+ }
581
+ writeCloudCredentials(creds);
582
+ formatter.success("Logged in. Credentials saved to ~/.clef/credentials.yaml");
583
+ }
584
+ } catch (err) {
585
+ const message = err instanceof Error ? err.message : String(err);
586
+ formatter.error(message);
587
+ process.exit(1);
588
+ }
589
+ });
590
+ }
591
+ async function pollUntilComplete(pollUrl) {
592
+ for (; ; ) {
593
+ const result = await pollDeviceFlow(pollUrl);
594
+ if (result.status === "complete" || result.status === "expired" || result.status === "cancelled") {
595
+ return result;
596
+ }
597
+ await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
598
+ }
599
+ }
600
+ // Annotate the CommonJS export names for ESM import in node:
601
+ 0 && (module.exports = {
602
+ registerCloudCommands
603
+ });
604
+ //# sourceMappingURL=cli.js.map