gigadrive 2.2.0 → 2.4.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/dist/index.cjs CHANGED
@@ -24,15 +24,15 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  ));
25
25
 
26
26
  // src/index.ts
27
- var import_cli14 = require("@effect/cli");
27
+ var import_cli15 = require("@effect/cli");
28
28
  var import_platform_node = require("@effect/platform-node");
29
29
  var import_network_config2 = require("@gigadrive/network-config");
30
- var import_effect25 = require("effect");
30
+ var import_effect29 = require("effect");
31
31
 
32
32
  // package.json
33
33
  var package_default = {
34
34
  name: "gigadrive",
35
- version: "2.2.0",
35
+ version: "2.4.0",
36
36
  repository: {
37
37
  type: "git",
38
38
  url: "https://github.com/Gigadrive/sdk.git",
@@ -58,7 +58,7 @@ var package_default = {
58
58
  dependencies: {
59
59
  "@effect/cli": "^0.75.0",
60
60
  "@effect/platform": "^0.96.0",
61
- "@effect/platform-node": "^0.106.0",
61
+ "@effect/platform-node": "^0.107.0",
62
62
  "@gigadrive/build-utils": "workspace:*",
63
63
  "@gigadrive/commons": "workspace:*",
64
64
  "@gigadrive/network-config": "workspace:*",
@@ -69,8 +69,8 @@ var package_default = {
69
69
  open: "^10.2.0"
70
70
  },
71
71
  devDependencies: {
72
- "@effect/language-service": "^0.84.0",
73
- "@types/archiver": "^6.0.3",
72
+ "@effect/language-service": "^0.86.0",
73
+ "@types/archiver": "^6.0.4",
74
74
  esbuild: "^0.28.0",
75
75
  typescript: "catalog:"
76
76
  },
@@ -113,6 +113,25 @@ var TokenExchangeError = class extends import_effect.Schema.TaggedError()("Token
113
113
  statusCode: import_effect.Schema.optional(import_effect.Schema.Number)
114
114
  }) {
115
115
  };
116
+ var DeviceAuthorizationError = class extends import_effect.Schema.TaggedError()(
117
+ "DeviceAuthorizationError",
118
+ {
119
+ message: import_effect.Schema.String,
120
+ cause: import_effect.Schema.optional(import_effect.Schema.String)
121
+ }
122
+ ) {
123
+ };
124
+ var AuthorizationDeniedError = class extends import_effect.Schema.TaggedError()(
125
+ "AuthorizationDeniedError",
126
+ {
127
+ message: import_effect.Schema.String
128
+ }
129
+ ) {
130
+ };
131
+ var DeviceCodeExpiredError = class extends import_effect.Schema.TaggedError()("DeviceCodeExpiredError", {
132
+ message: import_effect.Schema.String
133
+ }) {
134
+ };
116
135
  var TokenRefreshError = class extends import_effect.Schema.TaggedError()("TokenRefreshError", {
117
136
  message: import_effect.Schema.String,
118
137
  statusCode: import_effect.Schema.optional(import_effect.Schema.Number)
@@ -158,6 +177,31 @@ var ApplicationNotFoundError = class extends import_effect.Schema.TaggedError()(
158
177
  }
159
178
  ) {
160
179
  };
180
+ var EnvFileReadError = class extends import_effect.Schema.TaggedError()("EnvFileReadError", {
181
+ message: import_effect.Schema.String,
182
+ cause: import_effect.Schema.optional(import_effect.Schema.String)
183
+ }) {
184
+ };
185
+ var EnvFileWriteError = class extends import_effect.Schema.TaggedError()("EnvFileWriteError", {
186
+ message: import_effect.Schema.String,
187
+ cause: import_effect.Schema.optional(import_effect.Schema.String)
188
+ }) {
189
+ };
190
+ var DevCredentialsStoreReadError = class extends import_effect.Schema.TaggedError()(
191
+ "DevCredentialsStoreReadError",
192
+ {
193
+ message: import_effect.Schema.String
194
+ }
195
+ ) {
196
+ };
197
+ var DevCredentialsStoreWriteError = class extends import_effect.Schema.TaggedError()(
198
+ "DevCredentialsStoreWriteError",
199
+ {
200
+ message: import_effect.Schema.String,
201
+ cause: import_effect.Schema.optional(import_effect.Schema.String)
202
+ }
203
+ ) {
204
+ };
161
205
  var ConfigNotFoundError = class extends import_effect.Schema.TaggedError()("ConfigNotFoundError", {
162
206
  message: import_effect.Schema.String,
163
207
  directory: import_effect.Schema.String
@@ -285,15 +329,9 @@ var import_effect6 = require("effect");
285
329
 
286
330
  // src/services/auth.ts
287
331
  var import_effect5 = require("effect");
288
- var crypto = __toESM(require("crypto"), 1);
289
- var http = __toESM(require("http"), 1);
332
+ var import_node_process2 = __toESM(require("process"), 1);
290
333
  var import_open = __toESM(require("open"), 1);
291
334
 
292
- // src/services/auth-storage.ts
293
- var import_platform = require("@effect/platform");
294
- var import_effect3 = require("effect");
295
- var os = __toESM(require("os"), 1);
296
-
297
335
  // src/domain.ts
298
336
  var import_effect2 = require("effect");
299
337
  var DeploymentId = import_effect2.Schema.String.pipe(import_effect2.Schema.brand("@Gigadrive/DeploymentId"));
@@ -311,9 +349,18 @@ var OAuthClientConfig = import_effect2.Schema.Struct({
311
349
  issuer: import_effect2.Schema.String,
312
350
  authorizeUrl: import_effect2.Schema.String,
313
351
  tokenUrl: import_effect2.Schema.String,
352
+ deviceAuthorizeUrl: import_effect2.Schema.String,
314
353
  scope: import_effect2.Schema.String,
315
354
  userinfoUrl: import_effect2.Schema.String
316
355
  });
356
+ var DeviceAuthorizationResponse = import_effect2.Schema.Struct({
357
+ device_code: import_effect2.Schema.String,
358
+ user_code: import_effect2.Schema.String,
359
+ verification_uri: import_effect2.Schema.String,
360
+ verification_uri_complete: import_effect2.Schema.optional(import_effect2.Schema.String),
361
+ expires_in: import_effect2.Schema.Number,
362
+ interval: import_effect2.Schema.optional(import_effect2.Schema.Number)
363
+ });
317
364
  var StoredAuthData = import_effect2.Schema.Struct({
318
365
  refreshToken: import_effect2.Schema.String,
319
366
  accessToken: import_effect2.Schema.optional(import_effect2.Schema.String),
@@ -323,8 +370,52 @@ var ProjectLink = import_effect2.Schema.Struct({
323
370
  applicationId: import_effect2.Schema.String,
324
371
  organizationId: import_effect2.Schema.optional(import_effect2.Schema.String)
325
372
  });
373
+ var DevKeyEntry = import_effect2.Schema.Struct({
374
+ apiKeyId: import_effect2.Schema.String
375
+ });
376
+ var DevKeyStore = import_effect2.Schema.Record({ key: import_effect2.Schema.String, value: DevKeyEntry });
377
+
378
+ // src/lib/clipboard.ts
379
+ var import_node_child_process = require("child_process");
380
+ var import_node_process = __toESM(require("process"), 1);
381
+ var clipboardCandidates = () => {
382
+ switch (import_node_process.default.platform) {
383
+ case "darwin":
384
+ return [{ cmd: "pbcopy", args: [] }];
385
+ case "win32":
386
+ return [{ cmd: "clip", args: [] }];
387
+ default:
388
+ return [
389
+ { cmd: "wl-copy", args: [] },
390
+ { cmd: "xclip", args: ["-selection", "clipboard"] },
391
+ { cmd: "xsel", args: ["--clipboard", "--input"] }
392
+ ];
393
+ }
394
+ };
395
+ function writeClipboard(text) {
396
+ const candidates = clipboardCandidates();
397
+ const tryNext = (index) => {
398
+ if (index >= candidates.length) return;
399
+ const { cmd, args } = candidates[index];
400
+ let proc;
401
+ try {
402
+ proc = (0, import_node_child_process.spawn)(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
403
+ } catch {
404
+ tryNext(index + 1);
405
+ return;
406
+ }
407
+ proc.on("error", () => tryNext(index + 1));
408
+ proc.stdin?.on("error", () => {
409
+ });
410
+ proc.stdin?.end(text);
411
+ };
412
+ tryNext(0);
413
+ }
326
414
 
327
415
  // src/services/auth-storage.ts
416
+ var import_platform = require("@effect/platform");
417
+ var import_effect3 = require("effect");
418
+ var os = __toESM(require("os"), 1);
328
419
  var AuthStorageService = class extends import_effect3.Effect.Service()("AuthStorageService", {
329
420
  accessors: true,
330
421
  effect: import_effect3.Effect.gen(function* () {
@@ -403,15 +494,46 @@ var AuthStorageService = class extends import_effect3.Effect.Service()("AuthStor
403
494
  var import_effect4 = require("effect");
404
495
  var OAuthEnvConfig = import_effect4.Config.all({
405
496
  issuerUrl: import_effect4.Config.string("GIGADRIVE_NETWORK_OAUTH_ISSUER_URL").pipe(import_effect4.Config.withDefault("https://idp.gigadrive.de")),
406
- clientId: import_effect4.Config.string("GIGADRIVE_NETWORK_OAUTH_CLIENT_ID").pipe(import_effect4.Config.withDefault("todo_add_client_id"))
497
+ // Default is the first-party CLI's public OAuth client (seeded in the Network
498
+ // repo). Override with GIGADRIVE_NETWORK_OAUTH_CLIENT_ID per environment.
499
+ clientId: import_effect4.Config.string("GIGADRIVE_NETWORK_OAUTH_CLIENT_ID").pipe(
500
+ import_effect4.Config.withDefault("0195c11c-0000-7000-8000-000000000001")
501
+ )
407
502
  });
408
503
  var OpenIdDiscoveryDocument = import_effect4.Schema.Struct({
409
504
  issuer: import_effect4.Schema.optional(import_effect4.Schema.String),
410
505
  authorization_endpoint: import_effect4.Schema.optional(import_effect4.Schema.String),
411
506
  token_endpoint: import_effect4.Schema.optional(import_effect4.Schema.String),
412
- userinfo_endpoint: import_effect4.Schema.optional(import_effect4.Schema.String)
507
+ userinfo_endpoint: import_effect4.Schema.optional(import_effect4.Schema.String),
508
+ device_authorization_endpoint: import_effect4.Schema.optional(import_effect4.Schema.String)
413
509
  });
414
- var DEFAULT_SCOPE = "offline_access openid profile email";
510
+ var CLI_SCOPES = [
511
+ // Identity / OIDC
512
+ "offline_access",
513
+ "openid",
514
+ "profile",
515
+ "email",
516
+ // Network API capabilities
517
+ "network:applications:read",
518
+ "network:env_vars:read",
519
+ "network:env_vars:write",
520
+ "network:env_vars:delete",
521
+ "network:deployments:read",
522
+ "network:deployments:write",
523
+ "network:deployments:trigger",
524
+ "network:ai_gateway:chat",
525
+ "network:ai_gateway:models",
526
+ "network:ai_gateway:usage:read",
527
+ "network:ai_gateway:budgets:read",
528
+ "network:ai_gateway:budgets:write",
529
+ "network:ai_gateway:policies:read",
530
+ "network:ai_gateway:policies:write",
531
+ // Provisioning local-dev API credentials
532
+ "platform:api_keys:read",
533
+ "platform:api_keys:write",
534
+ "platform:api_keys:delete"
535
+ ];
536
+ var DEFAULT_SCOPE = CLI_SCOPES.join(" ");
415
537
  var OAuthConfigService = class extends import_effect4.Effect.Service()("OAuthConfigService", {
416
538
  accessors: true,
417
539
  effect: import_effect4.Effect.gen(function* () {
@@ -449,7 +571,7 @@ var OAuthConfigService = class extends import_effect4.Effect.Service()("OAuthCon
449
571
  })
450
572
  )
451
573
  );
452
- if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint) {
574
+ if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint || !decoded.device_authorization_endpoint) {
453
575
  return yield* import_effect4.Effect.fail(
454
576
  new OAuthDiscoveryError({
455
577
  message: "OIDC discovery returned incomplete endpoints"
@@ -461,6 +583,7 @@ var OAuthConfigService = class extends import_effect4.Effect.Service()("OAuthCon
461
583
  issuer: decoded.issuer ?? issuerBase,
462
584
  authorizeUrl: decoded.authorization_endpoint,
463
585
  tokenUrl: decoded.token_endpoint,
586
+ deviceAuthorizeUrl: decoded.device_authorization_endpoint,
464
587
  scope: DEFAULT_SCOPE,
465
588
  userinfoUrl: decoded.userinfo_endpoint
466
589
  };
@@ -474,14 +597,9 @@ var OAuthConfigService = class extends import_effect4.Effect.Service()("OAuthCon
474
597
  };
475
598
 
476
599
  // src/services/auth.ts
477
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\//g, "&#x2F;");
478
- var base64URLEncode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
479
- var generatePKCE = () => {
480
- const codeVerifier = base64URLEncode(crypto.randomBytes(64));
481
- const challengeBytes = crypto.createHash("sha256").update(codeVerifier, "utf8").digest();
482
- const codeChallenge = base64URLEncode(challengeBytes);
483
- return { codeVerifier, codeChallenge };
484
- };
600
+ var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
601
+ var DEFAULT_POLL_INTERVAL_SECONDS = 5;
602
+ var REQUEST_TIMEOUT_SECONDS = 30;
485
603
  var AuthService = class extends import_effect5.Effect.Service()("AuthService", {
486
604
  accessors: true,
487
605
  dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
@@ -496,115 +614,178 @@ var AuthService = class extends import_effect5.Effect.Service()("AuthService", {
496
614
  yield* storage.remove;
497
615
  yield* import_effect5.Effect.log("Logged out");
498
616
  });
499
- const exchangeCodeForTokens = (config, authCode, redirectUri, codeVerifier) => import_effect5.Effect.gen(function* () {
617
+ const requestDeviceAuthorization = (config) => import_effect5.Effect.gen(function* () {
500
618
  const response = yield* import_effect5.Effect.tryPromise({
501
- try: () => fetch(config.tokenUrl, {
619
+ try: (signal) => fetch(config.deviceAuthorizeUrl, {
502
620
  method: "POST",
503
621
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
504
- body: new URLSearchParams({
505
- grant_type: "authorization_code",
506
- client_id: config.clientId,
507
- code: authCode,
508
- redirect_uri: redirectUri,
509
- code_verifier: codeVerifier
510
- }).toString()
622
+ body: new URLSearchParams({ client_id: config.clientId, scope: config.scope }).toString(),
623
+ signal
511
624
  }),
512
- catch: (error) => new TokenExchangeError({
513
- message: `Token exchange request failed: ${error instanceof Error ? error.message : String(error)}`
625
+ catch: (error) => new DeviceAuthorizationError({
626
+ message: `Device authorization request failed: ${error instanceof Error ? error.message : String(error)}`
514
627
  })
515
628
  });
516
629
  if (!response.ok) {
517
630
  const body = yield* import_effect5.Effect.tryPromise({
518
631
  try: () => response.text(),
519
- catch: () => new TokenExchangeError({ message: "Failed to read token exchange error response" })
632
+ catch: () => new DeviceAuthorizationError({ message: "Failed to read device authorization error response" })
520
633
  });
521
634
  return yield* import_effect5.Effect.fail(
522
- new TokenExchangeError({
523
- message: `Token exchange failed: ${response.status} ${response.statusText} - ${body}`,
524
- statusCode: response.status
635
+ new DeviceAuthorizationError({
636
+ message: `Device authorization failed: ${response.status} ${response.statusText} - ${body}`
525
637
  })
526
638
  );
527
639
  }
528
- const tokens = yield* import_effect5.Effect.tryPromise({
640
+ const json = yield* import_effect5.Effect.tryPromise({
641
+ try: () => response.json(),
642
+ catch: () => new DeviceAuthorizationError({ message: "Failed to parse device authorization response" })
643
+ });
644
+ return yield* import_effect5.Schema.decodeUnknown(DeviceAuthorizationResponse)(json).pipe(
645
+ import_effect5.Effect.mapError(
646
+ () => new DeviceAuthorizationError({ message: "Invalid device authorization response schema" })
647
+ )
648
+ );
649
+ }).pipe(
650
+ import_effect5.Effect.timeoutFail({
651
+ duration: import_effect5.Duration.seconds(REQUEST_TIMEOUT_SECONDS),
652
+ onTimeout: () => new DeviceAuthorizationError({ message: "Device authorization request timed out" })
653
+ })
654
+ );
655
+ const pollTokenOnce = (config, deviceCode) => import_effect5.Effect.gen(function* () {
656
+ const response = yield* import_effect5.Effect.tryPromise({
657
+ try: (signal) => fetch(config.tokenUrl, {
658
+ method: "POST",
659
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
660
+ body: new URLSearchParams({
661
+ grant_type: DEVICE_CODE_GRANT_TYPE,
662
+ client_id: config.clientId,
663
+ device_code: deviceCode
664
+ }).toString(),
665
+ signal
666
+ }),
667
+ catch: (error) => new TokenExchangeError({
668
+ message: `Token poll request failed: ${error instanceof Error ? error.message : String(error)}`
669
+ })
670
+ });
671
+ if (response.ok) {
672
+ const tokens = yield* import_effect5.Effect.tryPromise({
673
+ try: () => response.json(),
674
+ catch: () => new TokenExchangeError({ message: "Failed to parse token response" })
675
+ });
676
+ return { kind: "tokens", tokens };
677
+ }
678
+ const errorBody = yield* import_effect5.Effect.tryPromise({
529
679
  try: () => response.json(),
530
- catch: () => new TokenExchangeError({ message: "Failed to parse token exchange response" })
680
+ catch: () => new TokenExchangeError({ message: "Failed to parse token poll error response" })
531
681
  });
532
- return tokens;
682
+ switch (errorBody.error) {
683
+ case "authorization_pending":
684
+ return { kind: "pending" };
685
+ case "slow_down":
686
+ return { kind: "slow_down" };
687
+ case "access_denied":
688
+ return { kind: "denied" };
689
+ case "expired_token":
690
+ return { kind: "expired" };
691
+ default:
692
+ return yield* import_effect5.Effect.fail(
693
+ new TokenExchangeError({
694
+ message: `Token poll failed: ${response.status} ${response.statusText} - ${errorBody.error ?? "unknown_error"}`,
695
+ statusCode: response.status
696
+ })
697
+ );
698
+ }
533
699
  });
534
- const startCallbackServer = (config, codeChallenge, state) => import_effect5.Effect.async((resume) => {
535
- const server = http.createServer();
536
- const redirectPath = "/callback";
537
- server.listen(0, "127.0.0.1", () => {
538
- const address = server.address();
539
- if (!address || typeof address !== "object") {
540
- server.close(() => {
541
- resume(import_effect5.Effect.fail(new LoginFlowError({ message: "Failed to determine callback server address" })));
542
- });
543
- return;
700
+ const pollForToken = (config, device) => import_effect5.Effect.gen(function* () {
701
+ const deadline = Date.now() + device.expires_in * 1e3;
702
+ let interval = device.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;
703
+ while (true) {
704
+ yield* import_effect5.Effect.sleep(import_effect5.Duration.seconds(interval));
705
+ if (Date.now() >= deadline) {
706
+ return yield* import_effect5.Effect.fail(
707
+ new DeviceCodeExpiredError({
708
+ message: "The login code expired before it was approved. Please run `gigadrive login` again."
709
+ })
710
+ );
544
711
  }
545
- const port = address.port;
546
- const redirectUri = `http://127.0.0.1:${port}${redirectPath}`;
547
- const authUrl = new URL(config.authorizeUrl);
548
- authUrl.searchParams.append("client_id", config.clientId);
549
- authUrl.searchParams.append("redirect_uri", redirectUri);
550
- authUrl.searchParams.append("response_type", "code");
551
- authUrl.searchParams.append("scope", config.scope);
552
- authUrl.searchParams.append("code_challenge", codeChallenge);
553
- authUrl.searchParams.append("code_challenge_method", "S256");
554
- authUrl.searchParams.append("state", state);
555
- import_effect5.Console.log(`Please open this URL in your browser to log in:
556
- ${authUrl.toString()}
557
- `).pipe(import_effect5.Effect.runSync);
558
- import_effect5.Console.log("Waiting for login callback...").pipe(import_effect5.Effect.runSync);
559
- void (0, import_open.default)(authUrl.toString());
560
- server.on("request", (req, res) => {
561
- const reqUrl = new URL(req.url || "", redirectUri);
562
- if (reqUrl.pathname !== "/callback") {
563
- res.writeHead(404).end("Not Found");
564
- return;
565
- }
566
- const authCode = reqUrl.searchParams.get("code");
567
- const receivedState = reqUrl.searchParams.get("state");
568
- const oauthError = reqUrl.searchParams.get("error");
569
- res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
570
- if (oauthError) {
571
- const safeOauthError = escapeHtml(oauthError);
572
- res.end(`<h1>Login Failed!</h1><p>Error: ${safeOauthError}</p><p>You can close this tab.</p>`);
573
- server.close();
574
- resume(import_effect5.Effect.fail(new LoginFlowError({ message: `OAuth error: ${oauthError}` })));
575
- return;
576
- }
577
- if (receivedState !== state) {
578
- res.end(
579
- "<h1>Login Failed!</h1><p>State mismatch. Possible CSRF attack.</p><p>You can close this tab.</p>"
712
+ const result = yield* pollTokenOnce(config, device.device_code).pipe(
713
+ // A hung poll must not stall the loop: on timeout treat it as pending so
714
+ // the next iteration re-checks the expiry deadline.
715
+ import_effect5.Effect.timeoutTo({
716
+ duration: import_effect5.Duration.seconds(REQUEST_TIMEOUT_SECONDS),
717
+ onSuccess: (value) => value,
718
+ onTimeout: () => ({ kind: "pending" })
719
+ })
720
+ );
721
+ switch (result.kind) {
722
+ case "tokens":
723
+ return result.tokens;
724
+ case "pending":
725
+ continue;
726
+ case "slow_down":
727
+ interval += 5;
728
+ continue;
729
+ case "denied":
730
+ return yield* import_effect5.Effect.fail(new AuthorizationDeniedError({ message: "The login request was denied." }));
731
+ case "expired":
732
+ return yield* import_effect5.Effect.fail(
733
+ new DeviceCodeExpiredError({
734
+ message: "The login code expired before it was approved. Please run `gigadrive login` again."
735
+ })
580
736
  );
581
- server.close();
582
- resume(import_effect5.Effect.fail(new LoginFlowError({ message: "State mismatch" })));
583
- return;
584
- }
585
- if (authCode) {
586
- res.end("<h1>Login Successful!</h1><p>You can close this tab.</p>");
587
- server.close();
588
- resume(import_effect5.Effect.succeed({ authCode, redirectUri }));
589
- } else {
590
- res.end("<h1>Login Failed!</h1><p>No authorization code received.</p><p>You can close this tab.</p>");
591
- server.close();
592
- resume(import_effect5.Effect.fail(new LoginFlowError({ message: "No authorization code received" })));
593
- }
594
- });
595
- server.on("error", (err) => {
596
- server.close();
597
- resume(import_effect5.Effect.fail(new LoginFlowError({ message: `Local server error: ${err.message}` })));
598
- });
737
+ }
738
+ }
739
+ });
740
+ const watchCopyKey = (url) => import_effect5.Effect.async((resume) => {
741
+ const stdin = import_node_process2.default.stdin;
742
+ if (!stdin.isTTY) {
743
+ return;
744
+ }
745
+ stdin.setRawMode(true);
746
+ stdin.resume();
747
+ stdin.setEncoding("utf8");
748
+ const onData = (key) => {
749
+ if (key === "c" || key === "C") {
750
+ writeClipboard(url);
751
+ import_effect5.Console.log("Copied the login URL to your clipboard.").pipe(import_effect5.Effect.runSync);
752
+ } else if (key === "") {
753
+ resume(import_effect5.Effect.fail(new LoginFlowError({ message: "Login canceled." })));
754
+ }
755
+ };
756
+ stdin.on("data", onData);
757
+ return import_effect5.Effect.sync(() => {
758
+ stdin.off("data", onData);
759
+ if (stdin.isTTY) stdin.setRawMode(false);
760
+ stdin.pause();
599
761
  });
600
762
  });
601
763
  const login = import_effect5.Effect.gen(function* () {
602
764
  yield* logout;
603
- const { codeVerifier, codeChallenge } = generatePKCE();
604
- const state = crypto.randomBytes(16).toString("hex");
605
765
  const config = yield* oauthConfig.getConfig;
606
- const { authCode, redirectUri } = yield* startCallbackServer(config, codeChallenge, state);
607
- const tokens = yield* exchangeCodeForTokens(config, authCode, redirectUri, codeVerifier);
766
+ const device = yield* requestDeviceAuthorization(config);
767
+ const verificationUri = device.verification_uri;
768
+ const verificationUriComplete = device.verification_uri_complete ?? verificationUri;
769
+ const interactive = import_node_process2.default.stdin.isTTY === true;
770
+ yield* import_effect5.Console.log(
771
+ `
772
+ To sign in, visit:
773
+
774
+ ${verificationUri}
775
+
776
+ and enter the code:
777
+
778
+ ${device.user_code}
779
+ `
780
+ );
781
+ if (interactive) {
782
+ yield* import_effect5.Effect.tryPromise(() => (0, import_open.default)(verificationUriComplete)).pipe(import_effect5.Effect.catchAll(() => import_effect5.Effect.void));
783
+ yield* import_effect5.Console.log("We tried to open your browser automatically. If it did not open, use the URL above.");
784
+ yield* import_effect5.Console.log('Press "c" to copy the URL to your clipboard, or Ctrl+C to cancel.');
785
+ }
786
+ yield* import_effect5.Console.log("\nWaiting for you to approve the login\u2026");
787
+ const poll = pollForToken(config, device);
788
+ const tokens = yield* interactive ? poll.pipe(import_effect5.Effect.raceFirst(watchCopyKey(verificationUriComplete))) : poll;
608
789
  if (!tokens.refresh_token) {
609
790
  return yield* import_effect5.Effect.fail(
610
791
  new TokenExchangeError({ message: "No refresh token received from identity provider" })
@@ -1555,7 +1736,230 @@ var deploymentsCommand = deploymentsBase.pipe(
1555
1736
 
1556
1737
  // src/commands/env/index.ts
1557
1738
  var import_cli7 = require("@effect/cli");
1739
+ var import_effect19 = require("effect");
1740
+
1741
+ // src/services/env-file.ts
1742
+ var import_platform6 = require("@effect/platform");
1558
1743
  var import_effect16 = require("effect");
1744
+
1745
+ // src/lib/dotenv.ts
1746
+ var DEFAULT_ENV_HEADER = "# Generated by `gigadrive env pull` \u2014 do not edit by hand.";
1747
+ var UNQUOTED_SAFE = /^[A-Za-z0-9_.\-/@:+]*$/;
1748
+ var formatEnvValue = (value) => {
1749
+ if (UNQUOTED_SAFE.test(value)) {
1750
+ return value;
1751
+ }
1752
+ const escaped = value.replace(/\\/g, "\\\\").replace(/"/g, '\\"').replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\t/g, "\\t");
1753
+ return `"${escaped}"`;
1754
+ };
1755
+ var serializeEnv = (entries, opts) => {
1756
+ const header = opts?.header ?? DEFAULT_ENV_HEADER;
1757
+ if (entries.length === 0) {
1758
+ return `${header}
1759
+ `;
1760
+ }
1761
+ const body = entries.map((e) => `${e.key}=${formatEnvValue(e.value)}`).join("\n");
1762
+ return `${header}
1763
+ ${body}
1764
+ `;
1765
+ };
1766
+ var parseEnv = (content) => {
1767
+ const result = {};
1768
+ for (const rawLine of content.split("\n")) {
1769
+ const line = rawLine.trim();
1770
+ if (line.length === 0 || line.startsWith("#")) {
1771
+ continue;
1772
+ }
1773
+ const eq = line.indexOf("=");
1774
+ if (eq <= 0) {
1775
+ continue;
1776
+ }
1777
+ const key = line.slice(0, eq).trim();
1778
+ let value = line.slice(eq + 1).trim();
1779
+ if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
1780
+ value = value.slice(1, -1).replace(/\\(\\|"|n|r|t)/g, (_, ch) => {
1781
+ switch (ch) {
1782
+ case "n":
1783
+ return "\n";
1784
+ case "r":
1785
+ return "\r";
1786
+ case "t":
1787
+ return " ";
1788
+ case '"':
1789
+ return '"';
1790
+ default:
1791
+ return "\\";
1792
+ }
1793
+ });
1794
+ }
1795
+ result[key] = value;
1796
+ }
1797
+ return result;
1798
+ };
1799
+
1800
+ // src/services/env-file.ts
1801
+ var EnvFileService = class extends import_effect16.Effect.Service()("EnvFileService", {
1802
+ accessors: true,
1803
+ effect: import_effect16.Effect.gen(function* () {
1804
+ const fs = yield* import_platform6.FileSystem.FileSystem;
1805
+ const pathService = yield* import_platform6.Path.Path;
1806
+ const exists = import_effect16.Effect.fn("EnvFileService.exists")(function* (filePath) {
1807
+ return yield* fs.exists(filePath).pipe(import_effect16.Effect.mapError(() => new EnvFileReadError({ message: `Failed to check whether ${filePath} exists` })));
1808
+ });
1809
+ const read = import_effect16.Effect.fn("EnvFileService.read")(function* (filePath) {
1810
+ const present = yield* exists(filePath);
1811
+ if (!present) return import_effect16.Option.none();
1812
+ const contents = yield* fs.readFileString(filePath, "utf8").pipe(import_effect16.Effect.mapError(() => new EnvFileReadError({ message: `Failed to read ${filePath}` })));
1813
+ return import_effect16.Option.some(contents);
1814
+ });
1815
+ const write = import_effect16.Effect.fn("EnvFileService.write")(function* (filePath, entries, opts) {
1816
+ const dir = pathService.dirname(filePath);
1817
+ yield* fs.makeDirectory(dir, { recursive: true }).pipe(
1818
+ import_effect16.Effect.mapError(
1819
+ (error) => new EnvFileWriteError({ message: `Failed to create directory for ${filePath}`, cause: String(error) })
1820
+ )
1821
+ );
1822
+ yield* fs.writeFileString(filePath, serializeEnv(entries, opts), { mode: 384 }).pipe(
1823
+ import_effect16.Effect.mapError(
1824
+ (error) => new EnvFileWriteError({ message: `Failed to write ${filePath}`, cause: String(error) })
1825
+ )
1826
+ );
1827
+ yield* fs.chmod(filePath, 384).pipe(
1828
+ import_effect16.Effect.mapError(
1829
+ (error) => new EnvFileWriteError({ message: `Failed to secure ${filePath}`, cause: String(error) })
1830
+ )
1831
+ );
1832
+ });
1833
+ const ensureGitignored = import_effect16.Effect.fn("EnvFileService.ensureGitignored")(function* (dir, entry) {
1834
+ const gitignorePath = pathService.join(dir, ".gitignore");
1835
+ const existing = yield* read(gitignorePath);
1836
+ const current = import_effect16.Option.getOrElse(existing, () => "");
1837
+ const alreadyIgnored = current.split("\n").some((line) => line.trim() === entry);
1838
+ if (alreadyIgnored) {
1839
+ return false;
1840
+ }
1841
+ const separator = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
1842
+ yield* fs.writeFileString(gitignorePath, `${current}${separator}${entry}
1843
+ `).pipe(
1844
+ import_effect16.Effect.mapError(
1845
+ (error) => new EnvFileWriteError({ message: "Failed to update .gitignore", cause: String(error) })
1846
+ )
1847
+ );
1848
+ return true;
1849
+ });
1850
+ return { exists, read, write, ensureGitignored };
1851
+ })
1852
+ }) {
1853
+ };
1854
+
1855
+ // src/services/local-credentials.ts
1856
+ var import_effect18 = require("effect");
1857
+ var os3 = __toESM(require("os"), 1);
1858
+
1859
+ // src/services/dev-credentials-store.ts
1860
+ var import_platform7 = require("@effect/platform");
1861
+ var import_effect17 = require("effect");
1862
+ var os2 = __toESM(require("os"), 1);
1863
+ var DevCredentialsStore = class extends import_effect17.Effect.Service()("DevCredentialsStore", {
1864
+ accessors: true,
1865
+ effect: import_effect17.Effect.gen(function* () {
1866
+ const fs = yield* import_platform7.FileSystem.FileSystem;
1867
+ const pathService = yield* import_platform7.Path.Path;
1868
+ const homeDir = os2.homedir();
1869
+ const storeDir = pathService.join(homeDir, ".gigadrive");
1870
+ const storeFile = pathService.join(storeDir, "dev-keys.json");
1871
+ const ensureDirectory = import_effect17.Effect.gen(function* () {
1872
+ yield* fs.makeDirectory(storeDir, { recursive: true }).pipe(
1873
+ import_effect17.Effect.mapError(
1874
+ (error) => new DevCredentialsStoreWriteError({ message: "Failed to create ~/.gigadrive", cause: String(error) })
1875
+ )
1876
+ );
1877
+ yield* fs.chmod(storeDir, 448).pipe(
1878
+ import_effect17.Effect.mapError(
1879
+ (error) => new DevCredentialsStoreWriteError({
1880
+ message: "Failed to set ~/.gigadrive permissions",
1881
+ cause: String(error)
1882
+ })
1883
+ )
1884
+ );
1885
+ });
1886
+ const load = import_effect17.Effect.gen(function* () {
1887
+ const present = yield* fs.exists(storeFile).pipe(import_effect17.Effect.mapError(() => new DevCredentialsStoreReadError({ message: "Failed to check dev-keys.json" })));
1888
+ if (!present) return {};
1889
+ const data = yield* fs.readFileString(storeFile, "utf8").pipe(import_effect17.Effect.mapError(() => new DevCredentialsStoreReadError({ message: "Failed to read dev-keys.json" })));
1890
+ const json = yield* import_effect17.Effect.try({
1891
+ try: () => JSON.parse(data),
1892
+ catch: () => new DevCredentialsStoreReadError({ message: "dev-keys.json contains invalid JSON" })
1893
+ });
1894
+ return yield* import_effect17.Schema.decodeUnknown(DevKeyStore)(json).pipe(
1895
+ import_effect17.Effect.mapError(() => new DevCredentialsStoreReadError({ message: "dev-keys.json has an invalid schema" }))
1896
+ );
1897
+ });
1898
+ const get = import_effect17.Effect.fn("DevCredentialsStore.get")(function* (applicationId) {
1899
+ const store = yield* load;
1900
+ const entry = store[applicationId];
1901
+ return entry ? import_effect17.Option.some(entry) : import_effect17.Option.none();
1902
+ });
1903
+ const set = import_effect17.Effect.fn("DevCredentialsStore.set")(function* (applicationId, entry) {
1904
+ const store = yield* load;
1905
+ const next = { ...store, [applicationId]: entry };
1906
+ yield* ensureDirectory;
1907
+ yield* fs.writeFileString(storeFile, `${JSON.stringify(next, null, 2)}
1908
+ `, { mode: 384 }).pipe(
1909
+ import_effect17.Effect.mapError(
1910
+ (error) => new DevCredentialsStoreWriteError({ message: "Failed to write dev-keys.json", cause: String(error) })
1911
+ )
1912
+ );
1913
+ });
1914
+ return { get, set };
1915
+ })
1916
+ }) {
1917
+ };
1918
+
1919
+ // src/services/local-credentials.ts
1920
+ var DEV_KEY_SCOPES = ["network:env_vars:read"];
1921
+ var ApiBaseUrl2 = import_effect18.Config.string("GIGADRIVE_API_BASE_URL").pipe(import_effect18.Config.withDefault("https://api.gigadrive.network"));
1922
+ var credentialEntries = (clientId, clientSecret, baseUrl) => [
1923
+ { key: "GIGADRIVE_CLIENT_ID", value: clientId },
1924
+ { key: "GIGADRIVE_CLIENT_SECRET", value: clientSecret },
1925
+ { key: "GIGADRIVE_API_BASE_URL", value: baseUrl }
1926
+ ];
1927
+ var LocalCredentialsService = class extends import_effect18.Effect.Service()("LocalCredentialsService", {
1928
+ accessors: true,
1929
+ dependencies: [ApiClientService.Default, DevCredentialsStore.Default],
1930
+ effect: import_effect18.Effect.gen(function* () {
1931
+ const apiClient = yield* ApiClientService;
1932
+ const store = yield* DevCredentialsStore;
1933
+ const baseUrl = yield* ApiBaseUrl2;
1934
+ const keyName = (applicationId) => `cli-dev:${os3.hostname()}:${applicationId}`;
1935
+ const provision = import_effect18.Effect.fn("LocalCredentialsService.provision")(function* (input) {
1936
+ const stored = yield* store.get(input.applicationId);
1937
+ const existing = input.existingContent ? parseEnv(input.existingContent) : {};
1938
+ const existingClientId = existing["GIGADRIVE_CLIENT_ID"];
1939
+ const existingClientSecret = existing["GIGADRIVE_CLIENT_SECRET"];
1940
+ const canReuse = !input.rotate && existingClientId !== void 0 && existingClientSecret !== void 0 && import_effect18.Option.isSome(stored) && stored.value.apiKeyId === existingClientId;
1941
+ if (canReuse) {
1942
+ return credentialEntries(existingClientId, existingClientSecret, baseUrl);
1943
+ }
1944
+ if (import_effect18.Option.isSome(stored)) {
1945
+ yield* apiClient.request((client) => client.apiKeys.delete(stored.value.apiKeyId)).pipe(import_effect18.Effect.catchTag("ApiRequestError", () => import_effect18.Effect.void));
1946
+ }
1947
+ const created = yield* apiClient.request(
1948
+ (client) => client.apiKeys.create({
1949
+ name: keyName(input.applicationId),
1950
+ applicationId: input.applicationId,
1951
+ scopes: DEV_KEY_SCOPES
1952
+ })
1953
+ );
1954
+ yield* store.set(input.applicationId, { apiKeyId: created.id });
1955
+ return credentialEntries(created.id, created.secret, baseUrl);
1956
+ });
1957
+ return { provision };
1958
+ })
1959
+ }) {
1960
+ };
1961
+
1962
+ // src/commands/env/index.ts
1559
1963
  var appOption3 = import_cli7.Options.text("app").pipe(
1560
1964
  import_cli7.Options.withAlias("a"),
1561
1965
  import_cli7.Options.withDescription("Operate on this application (overrides the linked application)"),
@@ -1578,51 +1982,70 @@ var keyValueArg = import_cli7.Args.text({ name: "key=value" }).pipe(
1578
1982
  import_cli7.Args.withDescription("Variable assignment, e.g. DATABASE_URL=postgres://user:pass@host/db")
1579
1983
  );
1580
1984
  var keyOrIdArg = import_cli7.Args.text({ name: "key-or-id" }).pipe(import_cli7.Args.withDescription("Variable key or ID to remove"));
1581
- var resolveScope = (app, org) => import_effect16.Effect.gen(function* () {
1582
- if (import_effect16.Option.isSome(org)) {
1985
+ var fileArg = import_cli7.Args.text({ name: "file" }).pipe(
1986
+ import_cli7.Args.withDescription("Output file to write (default: .env.local)"),
1987
+ import_cli7.Args.optional
1988
+ );
1989
+ var environmentOption = import_cli7.Options.text("environment").pipe(
1990
+ import_cli7.Options.withAlias("e"),
1991
+ import_cli7.Options.withDescription("Environment slug to resolve overrides for (e.g. preview). Omit for the local baseline"),
1992
+ import_cli7.Options.optional
1993
+ );
1994
+ var yesOption = import_cli7.Options.boolean("yes").pipe(
1995
+ import_cli7.Options.withAlias("y"),
1996
+ import_cli7.Options.withDescription("Overwrite an existing file without confirmation")
1997
+ );
1998
+ var withCredentialsOption = import_cli7.Options.boolean("with-credentials").pipe(
1999
+ import_cli7.Options.withDescription("Also provision API-key credentials so the local app can call the API as this application")
2000
+ );
2001
+ var rotateOption = import_cli7.Options.boolean("rotate").pipe(
2002
+ import_cli7.Options.withDescription("Force a fresh API key when provisioning credentials, revoking the previous one")
2003
+ );
2004
+ var resolveScope = (app, org) => import_effect19.Effect.gen(function* () {
2005
+ if (import_effect19.Option.isSome(org)) {
1583
2006
  return { kind: "organization", id: org.value };
1584
2007
  }
1585
2008
  const projectLink = yield* ProjectLinkService;
1586
- const id = yield* import_effect16.Option.match(app, {
1587
- onSome: (value) => import_effect16.Effect.succeed(value),
1588
- onNone: () => projectLink.resolve(process.cwd()).pipe(import_effect16.Effect.map((link) => link.applicationId))
2009
+ const id = yield* import_effect19.Option.match(app, {
2010
+ onSome: (value) => import_effect19.Effect.succeed(value),
2011
+ onNone: () => projectLink.resolve(process.cwd()).pipe(import_effect19.Effect.map((link) => link.applicationId))
1589
2012
  });
1590
2013
  return { kind: "application", id };
1591
2014
  });
1592
2015
  var envListCommand = import_cli7.Command.make(
1593
2016
  "list",
1594
2017
  { app: appOption3, org: orgOption4 },
1595
- ({ app, org }) => import_effect16.Effect.gen(function* () {
2018
+ ({ app, org }) => import_effect19.Effect.gen(function* () {
1596
2019
  const apiClient = yield* ApiClientService;
1597
2020
  const scope = yield* resolveScope(app, org);
1598
2021
  const { items } = yield* apiClient.request(
1599
2022
  (client) => scope.kind === "organization" ? client.organizations.envVars.list(scope.id) : client.applications.envVars.list(scope.id)
1600
2023
  );
1601
2024
  if (items.length === 0) {
1602
- yield* import_effect16.Console.log("No environment variables set.");
2025
+ yield* import_effect19.Console.log("No environment variables set.");
1603
2026
  return;
1604
2027
  }
1605
2028
  for (const v of items) {
1606
- yield* import_effect16.Console.log(`${v.key}=${v.sensitive ? "***" : v.value ?? ""}`);
2029
+ yield* import_effect19.Console.log(`${v.key}=${v.sensitive ? "***" : v.value ?? ""}`);
1607
2030
  }
1608
2031
  }).pipe(
1609
- import_effect16.Effect.catchTags({
1610
- NotAuthenticatedError: (err) => import_effect16.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1611
- import_effect16.Effect.andThen(import_effect16.Effect.fail(err))
2032
+ import_effect19.Effect.catchTags({
2033
+ NotAuthenticatedError: (err) => import_effect19.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
2034
+ import_effect19.Effect.andThen(import_effect19.Effect.fail(err))
1612
2035
  ),
1613
- ApiRequestError: (err) => import_effect16.Console.error(`Failed to list environment variables: ${err.message}`).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1614
- ProjectNotLinkedError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1615
- ProjectLinkReadError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err)))
2036
+ ApiRequestError: (err) => import_effect19.Console.error(`Failed to list environment variables: ${err.message}`).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2037
+ ProjectNotLinkedError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2038
+ ProjectLinkReadError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err)))
1616
2039
  })
1617
2040
  )
1618
2041
  );
1619
2042
  var envSetCommand = import_cli7.Command.make(
1620
2043
  "set",
1621
2044
  { keyValue: keyValueArg, app: appOption3, org: orgOption4, sensitive: sensitiveOption, env: envIdOption },
1622
- ({ keyValue, app, org, sensitive, env }) => import_effect16.Effect.gen(function* () {
2045
+ ({ keyValue, app, org, sensitive, env }) => import_effect19.Effect.gen(function* () {
1623
2046
  const separator = keyValue.indexOf("=");
1624
2047
  if (separator <= 0) {
1625
- return yield* import_effect16.Effect.fail(
2048
+ return yield* import_effect19.Effect.fail(
1626
2049
  new InvalidEnvVarFormatError({
1627
2050
  message: 'Invalid format. Use KEY=VALUE, e.g. "gigadrive env set DATABASE_URL=postgres://...".'
1628
2051
  })
@@ -1632,27 +2055,27 @@ var envSetCommand = import_cli7.Command.make(
1632
2055
  const value = keyValue.slice(separator + 1);
1633
2056
  const apiClient = yield* ApiClientService;
1634
2057
  const scope = yield* resolveScope(app, org);
1635
- const environmentId = import_effect16.Option.getOrUndefined(env);
2058
+ const environmentId = import_effect19.Option.getOrUndefined(env);
1636
2059
  const created = yield* apiClient.request(
1637
2060
  (client) => scope.kind === "organization" ? client.organizations.envVars.create(scope.id, { key, value, sensitive }) : client.applications.envVars.create(scope.id, { key, value, sensitive, environmentId })
1638
2061
  );
1639
- yield* import_effect16.Console.log(`Set ${created.key}.`);
2062
+ yield* import_effect19.Console.log(`Set ${created.key}.`);
1640
2063
  }).pipe(
1641
- import_effect16.Effect.catchTags({
1642
- InvalidEnvVarFormatError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1643
- NotAuthenticatedError: (err) => import_effect16.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1644
- import_effect16.Effect.andThen(import_effect16.Effect.fail(err))
2064
+ import_effect19.Effect.catchTags({
2065
+ InvalidEnvVarFormatError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2066
+ NotAuthenticatedError: (err) => import_effect19.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
2067
+ import_effect19.Effect.andThen(import_effect19.Effect.fail(err))
1645
2068
  ),
1646
- ApiRequestError: (err) => import_effect16.Console.error(`Failed to set environment variable: ${err.message}`).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1647
- ProjectNotLinkedError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1648
- ProjectLinkReadError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err)))
2069
+ ApiRequestError: (err) => import_effect19.Console.error(`Failed to set environment variable: ${err.message}`).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2070
+ ProjectNotLinkedError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2071
+ ProjectLinkReadError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err)))
1649
2072
  })
1650
2073
  )
1651
2074
  );
1652
2075
  var envRmCommand = import_cli7.Command.make(
1653
2076
  "rm",
1654
2077
  { keyOrId: keyOrIdArg, app: appOption3, org: orgOption4 },
1655
- ({ keyOrId, app, org }) => import_effect16.Effect.gen(function* () {
2078
+ ({ keyOrId, app, org }) => import_effect19.Effect.gen(function* () {
1656
2079
  const apiClient = yield* ApiClientService;
1657
2080
  const scope = yield* resolveScope(app, org);
1658
2081
  const { items } = yield* apiClient.request(
@@ -1660,32 +2083,106 @@ var envRmCommand = import_cli7.Command.make(
1660
2083
  );
1661
2084
  const target = items.find((v) => v.id === keyOrId || v.key === keyOrId);
1662
2085
  if (target === void 0) {
1663
- return yield* import_effect16.Effect.fail(
2086
+ return yield* import_effect19.Effect.fail(
1664
2087
  new EnvVarNotFoundError({ message: `No environment variable matching "${keyOrId}".` })
1665
2088
  );
1666
2089
  }
1667
2090
  yield* apiClient.request(
1668
2091
  (client) => scope.kind === "organization" ? client.organizations.envVars.delete(scope.id, target.id) : client.applications.envVars.delete(scope.id, target.id)
1669
2092
  );
1670
- yield* import_effect16.Console.log(`Removed ${target.key}.`);
2093
+ yield* import_effect19.Console.log(`Removed ${target.key}.`);
1671
2094
  }).pipe(
1672
- import_effect16.Effect.catchTags({
1673
- EnvVarNotFoundError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1674
- NotAuthenticatedError: (err) => import_effect16.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1675
- import_effect16.Effect.andThen(import_effect16.Effect.fail(err))
2095
+ import_effect19.Effect.catchTags({
2096
+ EnvVarNotFoundError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2097
+ NotAuthenticatedError: (err) => import_effect19.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
2098
+ import_effect19.Effect.andThen(import_effect19.Effect.fail(err))
1676
2099
  ),
1677
- ApiRequestError: (err) => import_effect16.Console.error(`Failed to remove environment variable: ${err.message}`).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1678
- ProjectNotLinkedError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err))),
1679
- ProjectLinkReadError: (err) => import_effect16.Console.error(err.message).pipe(import_effect16.Effect.andThen(import_effect16.Effect.fail(err)))
2100
+ ApiRequestError: (err) => import_effect19.Console.error(`Failed to remove environment variable: ${err.message}`).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2101
+ ProjectNotLinkedError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2102
+ ProjectLinkReadError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err)))
1680
2103
  })
1681
2104
  )
1682
2105
  );
1683
- var envBase = import_cli7.Command.make("env", {}, () => import_effect16.Effect.void);
1684
- var envCommand = envBase.pipe(import_cli7.Command.withSubcommands([envListCommand, envSetCommand, envRmCommand]));
2106
+ var envPullCommand = import_cli7.Command.make(
2107
+ "pull",
2108
+ {
2109
+ file: fileArg,
2110
+ app: appOption3,
2111
+ environment: environmentOption,
2112
+ yes: yesOption,
2113
+ withCredentials: withCredentialsOption,
2114
+ rotate: rotateOption
2115
+ },
2116
+ ({ file, app, environment, yes, withCredentials, rotate }) => import_effect19.Effect.gen(function* () {
2117
+ const apiClient = yield* ApiClientService;
2118
+ const projectLink = yield* ProjectLinkService;
2119
+ const envFile = yield* EnvFileService;
2120
+ const applicationId = yield* import_effect19.Option.match(app, {
2121
+ onSome: (value) => import_effect19.Effect.succeed(value),
2122
+ onNone: () => projectLink.resolve(process.cwd()).pipe(import_effect19.Effect.map((link) => link.applicationId))
2123
+ });
2124
+ const target = import_effect19.Option.getOrElse(file, () => ".env.local");
2125
+ const environmentSlug = import_effect19.Option.getOrUndefined(environment);
2126
+ const { items, omittedSensitive } = yield* apiClient.request(
2127
+ (client) => client.applications.envVars.pull(applicationId, environmentSlug ? { environment: environmentSlug } : void 0)
2128
+ );
2129
+ const existing = yield* envFile.read(target);
2130
+ if (import_effect19.Option.isSome(existing) && !yes) {
2131
+ const overwrite = yield* import_cli7.Prompt.run(
2132
+ import_cli7.Prompt.confirm({ message: `${target} already exists. Overwrite?`, initial: false })
2133
+ );
2134
+ if (!overwrite) {
2135
+ return yield* import_effect19.Console.log("Cancelled.");
2136
+ }
2137
+ }
2138
+ let credentialEntries2 = [];
2139
+ if (withCredentials) {
2140
+ const localCredentials = yield* LocalCredentialsService;
2141
+ credentialEntries2 = yield* localCredentials.provision({
2142
+ applicationId,
2143
+ rotate,
2144
+ existingContent: import_effect19.Option.getOrUndefined(existing)
2145
+ });
2146
+ }
2147
+ const entries = [...items.map((v) => ({ key: v.key, value: v.value })), ...credentialEntries2];
2148
+ yield* envFile.write(target, entries);
2149
+ const gitignored = yield* envFile.ensureGitignored(process.cwd(), target);
2150
+ yield* import_effect19.Console.log(
2151
+ `Pulled ${items.length} environment variable(s)${withCredentials ? " and API credentials" : ""} to ${target}.`
2152
+ );
2153
+ if (omittedSensitive > 0) {
2154
+ yield* import_effect19.Console.log(
2155
+ `Skipped ${omittedSensitive} sensitive variable(s); secrets live only in production/preview environments.`
2156
+ );
2157
+ }
2158
+ if (gitignored) {
2159
+ yield* import_effect19.Console.log(`Added ${target} to .gitignore.`);
2160
+ }
2161
+ }).pipe(
2162
+ import_effect19.Effect.catchTags({
2163
+ // Ctrl+C during the overwrite prompt is a deliberate cancel — exit 0.
2164
+ QuitException: () => import_effect19.Console.log("Cancelled."),
2165
+ NotAuthenticatedError: (err) => import_effect19.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
2166
+ import_effect19.Effect.andThen(import_effect19.Effect.fail(err))
2167
+ ),
2168
+ ApiRequestError: (err) => import_effect19.Console.error(`Failed to pull environment variables: ${err.message}`).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2169
+ ProjectNotLinkedError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2170
+ ProjectLinkReadError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2171
+ EnvFileReadError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2172
+ EnvFileWriteError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2173
+ DevCredentialsStoreReadError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err))),
2174
+ DevCredentialsStoreWriteError: (err) => import_effect19.Console.error(err.message).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err)))
2175
+ })
2176
+ )
2177
+ );
2178
+ var envBase = import_cli7.Command.make("env", {}, () => import_effect19.Effect.void);
2179
+ var envCommand = envBase.pipe(
2180
+ import_cli7.Command.withSubcommands([envListCommand, envSetCommand, envRmCommand, envPullCommand])
2181
+ );
1685
2182
 
1686
2183
  // src/commands/link/index.ts
1687
2184
  var import_cli8 = require("@effect/cli");
1688
- var import_effect17 = require("effect");
2185
+ var import_effect20 = require("effect");
1689
2186
  var appOption4 = import_cli8.Options.text("app").pipe(
1690
2187
  import_cli8.Options.withAlias("a"),
1691
2188
  import_cli8.Options.withDescription("Application ID to link (skips the interactive picker)"),
@@ -1699,26 +2196,26 @@ var orgOption5 = import_cli8.Options.text("org").pipe(
1699
2196
  var linkCommand = import_cli8.Command.make(
1700
2197
  "link",
1701
2198
  { app: appOption4, org: orgOption5 },
1702
- ({ app, org }) => import_effect17.Effect.gen(function* () {
2199
+ ({ app, org }) => import_effect20.Effect.gen(function* () {
1703
2200
  const apiClient = yield* ApiClientService;
1704
2201
  const projectLink = yield* ProjectLinkService;
1705
2202
  const cwd = process.cwd();
1706
- const organizationId = import_effect17.Option.getOrUndefined(org);
2203
+ const organizationId = import_effect20.Option.getOrUndefined(org);
1707
2204
  const { items: apps } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
1708
2205
  if (apps.length === 0) {
1709
- return yield* import_effect17.Effect.fail(
2206
+ return yield* import_effect20.Effect.fail(
1710
2207
  new NoApplicationsFoundError({
1711
2208
  message: 'No applications found. Create one in the Gigadrive console, then run "gigadrive link" again.'
1712
2209
  })
1713
2210
  );
1714
2211
  }
1715
- const explicitAppId = import_effect17.Option.getOrUndefined(app);
2212
+ const explicitAppId = import_effect20.Option.getOrUndefined(app);
1716
2213
  let applicationId;
1717
2214
  let linkedOrganizationId;
1718
2215
  if (explicitAppId !== void 0) {
1719
2216
  const match = apps.find((a) => a.id === explicitAppId);
1720
2217
  if (match === void 0) {
1721
- return yield* import_effect17.Effect.fail(
2218
+ return yield* import_effect20.Effect.fail(
1722
2219
  new ApplicationNotFoundError({
1723
2220
  message: `Application "${explicitAppId}" was not found among your applications.`
1724
2221
  })
@@ -1740,106 +2237,109 @@ var linkCommand = import_cli8.Command.make(
1740
2237
  linkedOrganizationId = chosen.organization.id;
1741
2238
  }
1742
2239
  yield* projectLink.save(cwd, { applicationId, organizationId: linkedOrganizationId });
1743
- yield* import_effect17.Console.log(`Linked this directory to application ${applicationId}.`);
1744
- yield* import_effect17.Console.log('Tip: add ".gigadrive/" to your .gitignore.');
2240
+ yield* import_effect20.Console.log(`Linked this directory to application ${applicationId}.`);
2241
+ yield* import_effect20.Console.log('Tip: add ".gigadrive/" to your .gitignore.');
1745
2242
  }).pipe(
1746
- import_effect17.Effect.catchTags({
2243
+ import_effect20.Effect.catchTags({
1747
2244
  // Ctrl+C during the picker is a deliberate cancel — exit 0.
1748
- QuitException: () => import_effect17.Console.log("Cancelled."),
1749
- NotAuthenticatedError: (err) => import_effect17.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
1750
- import_effect17.Effect.andThen(import_effect17.Effect.fail(err))
2245
+ QuitException: () => import_effect20.Console.log("Cancelled."),
2246
+ NotAuthenticatedError: (err) => import_effect20.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
2247
+ import_effect20.Effect.andThen(import_effect20.Effect.fail(err))
1751
2248
  ),
1752
- ApiRequestError: (err) => import_effect17.Console.error(`Failed to list applications: ${err.message}`).pipe(import_effect17.Effect.andThen(import_effect17.Effect.fail(err))),
1753
- NoApplicationsFoundError: (err) => import_effect17.Console.error(err.message).pipe(import_effect17.Effect.andThen(import_effect17.Effect.fail(err))),
1754
- ApplicationNotFoundError: (err) => import_effect17.Console.error(err.message).pipe(import_effect17.Effect.andThen(import_effect17.Effect.fail(err))),
1755
- ProjectLinkWriteError: (err) => import_effect17.Console.error(err.message).pipe(import_effect17.Effect.andThen(import_effect17.Effect.fail(err)))
2249
+ ApiRequestError: (err) => import_effect20.Console.error(`Failed to list applications: ${err.message}`).pipe(import_effect20.Effect.andThen(import_effect20.Effect.fail(err))),
2250
+ NoApplicationsFoundError: (err) => import_effect20.Console.error(err.message).pipe(import_effect20.Effect.andThen(import_effect20.Effect.fail(err))),
2251
+ ApplicationNotFoundError: (err) => import_effect20.Console.error(err.message).pipe(import_effect20.Effect.andThen(import_effect20.Effect.fail(err))),
2252
+ ProjectLinkWriteError: (err) => import_effect20.Console.error(err.message).pipe(import_effect20.Effect.andThen(import_effect20.Effect.fail(err)))
1756
2253
  })
1757
2254
  )
1758
2255
  );
1759
2256
  var unlinkCommand = import_cli8.Command.make(
1760
2257
  "unlink",
1761
2258
  {},
1762
- () => import_effect17.Effect.gen(function* () {
2259
+ () => import_effect20.Effect.gen(function* () {
1763
2260
  const projectLink = yield* ProjectLinkService;
1764
2261
  yield* projectLink.remove(process.cwd());
1765
- yield* import_effect17.Console.log("Unlinked this directory.");
2262
+ yield* import_effect20.Console.log("Unlinked this directory.");
1766
2263
  }).pipe(
1767
- import_effect17.Effect.catchTags({
1768
- ProjectLinkWriteError: (err) => import_effect17.Console.error(err.message).pipe(import_effect17.Effect.andThen(import_effect17.Effect.fail(err)))
2264
+ import_effect20.Effect.catchTags({
2265
+ ProjectLinkWriteError: (err) => import_effect20.Console.error(err.message).pipe(import_effect20.Effect.andThen(import_effect20.Effect.fail(err)))
1769
2266
  })
1770
2267
  )
1771
2268
  );
1772
2269
 
1773
2270
  // src/commands/login/index.ts
1774
2271
  var import_cli9 = require("@effect/cli");
1775
- var import_effect18 = require("effect");
2272
+ var import_effect21 = require("effect");
1776
2273
  var loginCommand = import_cli9.Command.make(
1777
2274
  "login",
1778
2275
  {},
1779
- () => import_effect18.Effect.gen(function* () {
1780
- yield* import_effect18.Console.log("Initiating login...");
2276
+ () => import_effect21.Effect.gen(function* () {
2277
+ yield* import_effect21.Console.log("Initiating login...");
1781
2278
  const auth = yield* AuthService;
1782
2279
  yield* auth.login;
1783
- const info = yield* auth.getUserInfo.pipe(import_effect18.Effect.catchAll(() => import_effect18.Effect.succeed({})));
2280
+ const info = yield* auth.getUserInfo.pipe(import_effect21.Effect.catchAll(() => import_effect21.Effect.succeed({})));
1784
2281
  const name = auth.inferUserName(info);
1785
- yield* import_effect18.Console.log(`You are now logged in as ${name}.`);
2282
+ yield* import_effect21.Console.log(`You are now logged in as ${name}.`);
1786
2283
  }).pipe(
1787
- import_effect18.Effect.catchTags({
1788
- LoginFlowError: (err) => import_effect18.Console.error(`Login failed: ${err.message}`),
1789
- TokenExchangeError: (err) => import_effect18.Console.error(`Login failed: ${err.message}`),
1790
- OAuthDiscoveryError: (err) => import_effect18.Console.error(`Login failed: ${err.message}`),
1791
- AuthStorageWriteError: (err) => import_effect18.Console.error(`Failed to save credentials: ${err.message}`)
2284
+ import_effect21.Effect.catchTags({
2285
+ LoginFlowError: (err) => import_effect21.Console.error(err.message),
2286
+ DeviceAuthorizationError: (err) => import_effect21.Console.error(`Login failed: ${err.message}`),
2287
+ AuthorizationDeniedError: (err) => import_effect21.Console.error(err.message),
2288
+ DeviceCodeExpiredError: (err) => import_effect21.Console.error(err.message),
2289
+ TokenExchangeError: (err) => import_effect21.Console.error(`Login failed: ${err.message}`),
2290
+ OAuthDiscoveryError: (err) => import_effect21.Console.error(`Login failed: ${err.message}`),
2291
+ AuthStorageWriteError: (err) => import_effect21.Console.error(`Failed to save credentials: ${err.message}`)
1792
2292
  })
1793
2293
  )
1794
2294
  );
1795
2295
 
1796
2296
  // src/commands/logout/index.ts
1797
2297
  var import_cli10 = require("@effect/cli");
1798
- var import_effect19 = require("effect");
2298
+ var import_effect22 = require("effect");
1799
2299
  var logoutCommand = import_cli10.Command.make(
1800
2300
  "logout",
1801
2301
  {},
1802
- () => import_effect19.Effect.gen(function* () {
2302
+ () => import_effect22.Effect.gen(function* () {
1803
2303
  const auth = yield* AuthService;
1804
2304
  yield* auth.logout;
1805
- yield* import_effect19.Console.log("Logged out.");
2305
+ yield* import_effect22.Console.log("Logged out.");
1806
2306
  }).pipe(
1807
- import_effect19.Effect.catchTags({
1808
- AuthStorageWriteError: (err) => import_effect19.Console.error(`Failed to log out: ${err.message}`).pipe(import_effect19.Effect.andThen(import_effect19.Effect.fail(err)))
2307
+ import_effect22.Effect.catchTags({
2308
+ AuthStorageWriteError: (err) => import_effect22.Console.error(`Failed to log out: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err)))
1809
2309
  })
1810
2310
  )
1811
2311
  );
1812
2312
 
1813
2313
  // src/commands/platform/index.ts
1814
2314
  var import_cli12 = require("@effect/cli");
1815
- var import_effect23 = require("effect");
2315
+ var import_effect26 = require("effect");
1816
2316
 
1817
2317
  // src/commands/platform/deploy/index.ts
1818
2318
  var import_cli11 = require("@effect/cli");
1819
- var import_platform7 = require("@effect/platform");
2319
+ var import_platform9 = require("@effect/platform");
1820
2320
  var import_commons = require("@gigadrive/commons");
1821
- var import_effect22 = require("effect");
1822
- var os2 = __toESM(require("os"), 1);
2321
+ var import_effect25 = require("effect");
2322
+ var os4 = __toESM(require("os"), 1);
1823
2323
 
1824
2324
  // src/services/archive.ts
1825
- var import_platform6 = require("@effect/platform");
2325
+ var import_platform8 = require("@effect/platform");
1826
2326
  var import_build_utils3 = require("@gigadrive/build-utils");
1827
2327
  var import_archiver = __toESM(require("archiver"), 1);
1828
- var import_effect20 = require("effect");
2328
+ var import_effect23 = require("effect");
1829
2329
  var import_ignore = __toESM(require("ignore"), 1);
1830
2330
  var nodeFs = __toESM(require("fs"), 1);
1831
2331
  var nodeFsPromises = __toESM(require("fs/promises"), 1);
1832
2332
  var IGNORE_FILE_NAMES = [".gigadriveignore", ".vercelignore", ".dockerignore", ".nowignore"];
1833
2333
  var readIgnoreFile = (fs, ignorePath) => fs.readFileString(ignorePath, "utf8").pipe(
1834
- import_effect20.Effect.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
1835
- import_effect20.Effect.catchAll(() => import_effect20.Effect.succeed([]))
2334
+ import_effect23.Effect.map((content) => content.split("\n").filter((line) => line.trim() !== "")),
2335
+ import_effect23.Effect.catchAll(() => import_effect23.Effect.succeed([]))
1836
2336
  );
1837
- var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => import_effect20.Effect.gen(function* () {
1838
- const items = yield* fs.readDirectory(dir).pipe(import_effect20.Effect.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
2337
+ var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => import_effect23.Effect.gen(function* () {
2338
+ const items = yield* fs.readDirectory(dir).pipe(import_effect23.Effect.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${dir}` })));
1839
2339
  for (const item of items) {
1840
2340
  const fullPath = pathService.join(dir, item);
1841
2341
  const relativePath = pathService.relative(baseDir, fullPath);
1842
- const stat = yield* import_effect20.Effect.tryPromise({
2342
+ const stat = yield* import_effect23.Effect.tryPromise({
1843
2343
  try: () => nodeFsPromises.lstat(fullPath),
1844
2344
  catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
1845
2345
  });
@@ -1859,8 +2359,8 @@ var collectIgnorePatterns = (fs, pathService, dir, baseDir, ignoreRules) => impo
1859
2359
  yield* collectIgnorePatterns(fs, pathService, fullPath, baseDir, ignoreRules);
1860
2360
  }
1861
2361
  }
1862
- }).pipe(import_effect20.Effect.catchAll(() => import_effect20.Effect.void));
1863
- var initializeIgnoreRules = (fs, pathService, baseDir) => import_effect20.Effect.gen(function* () {
2362
+ }).pipe(import_effect23.Effect.catchAll(() => import_effect23.Effect.void));
2363
+ var initializeIgnoreRules = (fs, pathService, baseDir) => import_effect23.Effect.gen(function* () {
1864
2364
  const ignoreRules = (0, import_ignore.default)();
1865
2365
  ignoreRules.add([".git/", "**/.DS_Store", "**/Thumbs.db"]);
1866
2366
  for (const ignoreFileName of IGNORE_FILE_NAMES) {
@@ -1875,14 +2375,14 @@ var initializeIgnoreRules = (fs, pathService, baseDir) => import_effect20.Effect
1875
2375
  yield* collectIgnorePatterns(fs, pathService, baseDir, baseDir, ignoreRules);
1876
2376
  return ignoreRules;
1877
2377
  });
1878
- var getFilesToInclude = (fs, pathService, dir, ignoreRules) => import_effect20.Effect.gen(function* () {
2378
+ var getFilesToInclude = (fs, pathService, dir, ignoreRules) => import_effect23.Effect.gen(function* () {
1879
2379
  const filesToInclude = [];
1880
- const walkDir = (currentDir) => import_effect20.Effect.gen(function* () {
1881
- const items = yield* fs.readDirectory(currentDir).pipe(import_effect20.Effect.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
2380
+ const walkDir = (currentDir) => import_effect23.Effect.gen(function* () {
2381
+ const items = yield* fs.readDirectory(currentDir).pipe(import_effect23.Effect.mapError(() => new ArchiveCreateError({ message: `Failed to read directory: ${currentDir}` })));
1882
2382
  for (const item of items) {
1883
2383
  const fullPath = pathService.join(currentDir, item);
1884
2384
  const relativePath = pathService.relative(dir, fullPath);
1885
- const stat = yield* import_effect20.Effect.tryPromise({
2385
+ const stat = yield* import_effect23.Effect.tryPromise({
1886
2386
  try: () => nodeFsPromises.lstat(fullPath),
1887
2387
  catch: () => new ArchiveCreateError({ message: `Failed to stat: ${fullPath}` })
1888
2388
  });
@@ -1896,21 +2396,21 @@ var getFilesToInclude = (fs, pathService, dir, ignoreRules) => import_effect20.E
1896
2396
  }
1897
2397
  }
1898
2398
  }
1899
- }).pipe(import_effect20.Effect.catchAll(() => import_effect20.Effect.void));
2399
+ }).pipe(import_effect23.Effect.catchAll(() => import_effect23.Effect.void));
1900
2400
  yield* walkDir(dir);
1901
2401
  return filesToInclude;
1902
2402
  });
1903
- var ArchiveService = class extends import_effect20.Effect.Service()("ArchiveService", {
2403
+ var ArchiveService = class extends import_effect23.Effect.Service()("ArchiveService", {
1904
2404
  accessors: true,
1905
- effect: import_effect20.Effect.gen(function* () {
1906
- const fs = yield* import_platform6.FileSystem.FileSystem;
1907
- const pathService = yield* import_platform6.Path.Path;
1908
- const createZipArchive = import_effect20.Effect.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
1909
- yield* import_effect20.Effect.annotateCurrentSpan("inputDir", inputDir);
1910
- yield* import_effect20.Effect.annotateCurrentSpan("outputFile", outputFile);
1911
- yield* import_effect20.Effect.log("Creating zip archive", { inputDir, outputFile });
2405
+ effect: import_effect23.Effect.gen(function* () {
2406
+ const fs = yield* import_platform8.FileSystem.FileSystem;
2407
+ const pathService = yield* import_platform8.Path.Path;
2408
+ const createZipArchive = import_effect23.Effect.fn("ArchiveService.createZipArchive")(function* (inputDir, outputFile, options = {}) {
2409
+ yield* import_effect23.Effect.annotateCurrentSpan("inputDir", inputDir);
2410
+ yield* import_effect23.Effect.annotateCurrentSpan("outputFile", outputFile);
2411
+ yield* import_effect23.Effect.log("Creating zip archive", { inputDir, outputFile });
1912
2412
  const ignoreRules = options.useIgnoreFiles !== false ? yield* initializeIgnoreRules(fs, pathService, inputDir) : (0, import_ignore.default)();
1913
- const filesToInclude = options.whitelist ? yield* import_effect20.Effect.tryPromise({
2413
+ const filesToInclude = options.whitelist ? yield* import_effect23.Effect.tryPromise({
1914
2414
  try: () => Promise.all(
1915
2415
  options.whitelist.map((file) => (0, import_build_utils3.getFilesForPattern)(file, inputDir, options.excludeFiles ?? []))
1916
2416
  ),
@@ -1918,25 +2418,25 @@ var ArchiveService = class extends import_effect20.Effect.Service()("ArchiveServ
1918
2418
  message: "Failed to resolve whitelist patterns",
1919
2419
  cause: error instanceof Error ? error.message : String(error)
1920
2420
  })
1921
- }) : yield* import_effect20.Effect.gen(function* () {
2421
+ }) : yield* import_effect23.Effect.gen(function* () {
1922
2422
  if (options.excludeFiles && options.excludeFiles.length > 0) {
1923
2423
  ignoreRules.add(options.excludeFiles);
1924
2424
  }
1925
2425
  return yield* getFilesToInclude(fs, pathService, inputDir, ignoreRules).pipe(
1926
- import_effect20.Effect.map((files) => [files])
2426
+ import_effect23.Effect.map((files) => [files])
1927
2427
  );
1928
2428
  });
1929
- const archiveExists = yield* fs.exists(outputFile).pipe(import_effect20.Effect.catchAll(() => import_effect20.Effect.succeed(false)));
2429
+ const archiveExists = yield* fs.exists(outputFile).pipe(import_effect23.Effect.catchAll(() => import_effect23.Effect.succeed(false)));
1930
2430
  if (archiveExists) {
1931
2431
  yield* fs.remove(outputFile).pipe(
1932
- import_effect20.Effect.mapError(
2432
+ import_effect23.Effect.mapError(
1933
2433
  (error) => new ArchiveCreateError({
1934
2434
  message: `Failed to remove existing archive: ${String(error)}`
1935
2435
  })
1936
2436
  )
1937
2437
  );
1938
2438
  }
1939
- yield* import_effect20.Effect.tryPromise({
2439
+ yield* import_effect23.Effect.tryPromise({
1940
2440
  try: () => new Promise((resolve, reject) => {
1941
2441
  const output = nodeFs.createWriteStream(outputFile);
1942
2442
  const archive = (0, import_archiver.default)("zip", { zlib: { level: 9 } });
@@ -1955,14 +2455,14 @@ var ArchiveService = class extends import_effect20.Effect.Service()("ArchiveServ
1955
2455
  })
1956
2456
  });
1957
2457
  const fileInfo = yield* fs.stat(outputFile).pipe(
1958
- import_effect20.Effect.mapError(
2458
+ import_effect23.Effect.mapError(
1959
2459
  (error) => new ArchiveCreateError({
1960
2460
  message: `Failed to stat archive: ${String(error)}`
1961
2461
  })
1962
2462
  )
1963
2463
  );
1964
2464
  const size = Number(fileInfo.size);
1965
- yield* import_effect20.Effect.log("Archive created", { size });
2465
+ yield* import_effect23.Effect.log("Archive created", { size });
1966
2466
  return { path: outputFile, size };
1967
2467
  });
1968
2468
  return { createZipArchive };
@@ -1971,32 +2471,32 @@ var ArchiveService = class extends import_effect20.Effect.Service()("ArchiveServ
1971
2471
  };
1972
2472
 
1973
2473
  // src/services/deployment-api.ts
1974
- var import_effect21 = require("effect");
1975
- var DeploymentApiService = class extends import_effect21.Effect.Service()("DeploymentApiService", {
2474
+ var import_effect24 = require("effect");
2475
+ var DeploymentApiService = class extends import_effect24.Effect.Service()("DeploymentApiService", {
1976
2476
  accessors: true,
1977
2477
  dependencies: [ApiClientService.Default],
1978
- effect: import_effect21.Effect.gen(function* () {
2478
+ effect: import_effect24.Effect.gen(function* () {
1979
2479
  const apiClient = yield* ApiClientService;
1980
2480
  const getClient = apiClient.getClient.pipe(
1981
- import_effect21.Effect.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
2481
+ import_effect24.Effect.mapError((err) => new DeploymentAuthError({ message: `Authentication failed: ${err.message}` }))
1982
2482
  );
1983
- const createDeployment = import_effect21.Effect.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
1984
- yield* import_effect21.Effect.annotateCurrentSpan("applicationId", applicationId);
1985
- yield* import_effect21.Effect.log("Creating deployment", { applicationId });
2483
+ const createDeployment = import_effect24.Effect.fn("DeploymentApiService.createDeployment")(function* (applicationId) {
2484
+ yield* import_effect24.Effect.annotateCurrentSpan("applicationId", applicationId);
2485
+ yield* import_effect24.Effect.log("Creating deployment", { applicationId });
1986
2486
  const client = yield* getClient;
1987
- const deployment = yield* import_effect21.Effect.tryPromise({
2487
+ const deployment = yield* import_effect24.Effect.tryPromise({
1988
2488
  try: () => client.deployments.create({ applicationId }),
1989
2489
  catch: (error) => new DeploymentCreateError({
1990
2490
  message: `Failed to create deployment: ${error instanceof Error ? error.message : String(error)}`
1991
2491
  })
1992
2492
  });
1993
- yield* import_effect21.Effect.log("Deployment created", { deploymentId: deployment.id });
2493
+ yield* import_effect24.Effect.log("Deployment created", { deploymentId: deployment.id });
1994
2494
  return deployment.id;
1995
2495
  });
1996
- const startMultipartUpload = import_effect21.Effect.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
1997
- yield* import_effect21.Effect.annotateCurrentSpan("deploymentId", deploymentId);
2496
+ const startMultipartUpload = import_effect24.Effect.fn("DeploymentApiService.startMultipartUpload")(function* (deploymentId) {
2497
+ yield* import_effect24.Effect.annotateCurrentSpan("deploymentId", deploymentId);
1998
2498
  const client = yield* getClient;
1999
- const result = yield* import_effect21.Effect.tryPromise({
2499
+ const result = yield* import_effect24.Effect.tryPromise({
2000
2500
  try: () => client.deployments.startUpload(deploymentId),
2001
2501
  catch: (error) => new UploadStartError({
2002
2502
  message: `Failed to start multipart upload: ${error instanceof Error ? error.message : String(error)}`
@@ -2004,9 +2504,9 @@ var DeploymentApiService = class extends import_effect21.Effect.Service()("Deplo
2004
2504
  });
2005
2505
  return result.uploadId;
2006
2506
  });
2007
- const getPresignedUrl = import_effect21.Effect.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
2507
+ const getPresignedUrl = import_effect24.Effect.fn("DeploymentApiService.getPresignedUrl")(function* (deploymentId, uploadId, partNumber) {
2008
2508
  const client = yield* getClient;
2009
- const result = yield* import_effect21.Effect.tryPromise({
2509
+ const result = yield* import_effect24.Effect.tryPromise({
2010
2510
  try: () => client.deployments.getPresignedUrl(deploymentId, uploadId, partNumber),
2011
2511
  catch: (error) => new PresignedUrlError({
2012
2512
  message: `Failed to get presigned URL: ${error instanceof Error ? error.message : String(error)}`,
@@ -2015,11 +2515,11 @@ var DeploymentApiService = class extends import_effect21.Effect.Service()("Deplo
2015
2515
  });
2016
2516
  return result.url;
2017
2517
  });
2018
- const uploadPart = import_effect21.Effect.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
2019
- yield* import_effect21.Effect.annotateCurrentSpan("partNumber", partNumber);
2020
- yield* import_effect21.Effect.annotateCurrentSpan("size", data.length);
2518
+ const uploadPart = import_effect24.Effect.fn("DeploymentApiService.uploadPart")(function* (presignedUrl, data, partNumber) {
2519
+ yield* import_effect24.Effect.annotateCurrentSpan("partNumber", partNumber);
2520
+ yield* import_effect24.Effect.annotateCurrentSpan("size", data.length);
2021
2521
  const client = yield* getClient;
2022
- const result = yield* import_effect21.Effect.tryPromise({
2522
+ const result = yield* import_effect24.Effect.tryPromise({
2023
2523
  try: () => client.deployments.uploadPart(presignedUrl, data, partNumber),
2024
2524
  catch: (error) => new UploadPartError({
2025
2525
  message: `Failed to upload part ${partNumber}: ${error instanceof Error ? error.message : String(error)}`,
@@ -2028,21 +2528,21 @@ var DeploymentApiService = class extends import_effect21.Effect.Service()("Deplo
2028
2528
  });
2029
2529
  return result;
2030
2530
  });
2031
- const completeUpload = import_effect21.Effect.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
2032
- yield* import_effect21.Effect.annotateCurrentSpan("deploymentId", deploymentId);
2033
- yield* import_effect21.Effect.annotateCurrentSpan("partsCount", parts.length);
2531
+ const completeUpload = import_effect24.Effect.fn("DeploymentApiService.completeUpload")(function* (deploymentId, uploadId, parts) {
2532
+ yield* import_effect24.Effect.annotateCurrentSpan("deploymentId", deploymentId);
2533
+ yield* import_effect24.Effect.annotateCurrentSpan("partsCount", parts.length);
2034
2534
  const client = yield* getClient;
2035
- yield* import_effect21.Effect.tryPromise({
2535
+ yield* import_effect24.Effect.tryPromise({
2036
2536
  try: () => client.deployments.completeUpload(deploymentId, uploadId, parts),
2037
2537
  catch: (error) => new UploadCompleteError({
2038
2538
  message: `Failed to complete upload: ${error instanceof Error ? error.message : String(error)}`
2039
2539
  })
2040
2540
  });
2041
- yield* import_effect21.Effect.log("Upload completed successfully");
2541
+ yield* import_effect24.Effect.log("Upload completed successfully");
2042
2542
  });
2043
- const getDeploymentStatus = import_effect21.Effect.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
2543
+ const getDeploymentStatus = import_effect24.Effect.fn("DeploymentApiService.getDeploymentStatus")(function* (deploymentId) {
2044
2544
  const client = yield* getClient;
2045
- const deployment = yield* import_effect21.Effect.tryPromise({
2545
+ const deployment = yield* import_effect24.Effect.tryPromise({
2046
2546
  try: () => client.deployments.get(deploymentId),
2047
2547
  catch: (error) => new DeploymentStatusError({
2048
2548
  message: `Failed to get deployment status: ${error instanceof Error ? error.message : String(error)}`
@@ -2050,9 +2550,9 @@ var DeploymentApiService = class extends import_effect21.Effect.Service()("Deplo
2050
2550
  });
2051
2551
  return deployment.status;
2052
2552
  });
2053
- const getLogs = import_effect21.Effect.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
2553
+ const getLogs = import_effect24.Effect.fn("DeploymentApiService.getLogs")(function* (deploymentId, options) {
2054
2554
  const client = yield* getClient;
2055
- const result = yield* import_effect21.Effect.tryPromise({
2555
+ const result = yield* import_effect24.Effect.tryPromise({
2056
2556
  try: () => client.deployments.getLogs(deploymentId, options),
2057
2557
  catch: (error) => new DeploymentLogsFetchError({
2058
2558
  message: `Failed to get logs: ${error instanceof Error ? error.message : String(error)}`
@@ -2060,9 +2560,9 @@ var DeploymentApiService = class extends import_effect21.Effect.Service()("Deplo
2060
2560
  });
2061
2561
  return result;
2062
2562
  });
2063
- const getHostnames = import_effect21.Effect.fn("DeploymentApiService.getHostnames")(function* (deploymentId) {
2563
+ const getHostnames = import_effect24.Effect.fn("DeploymentApiService.getHostnames")(function* (deploymentId) {
2064
2564
  const client = yield* getClient;
2065
- const result = yield* import_effect21.Effect.tryPromise({
2565
+ const result = yield* import_effect24.Effect.tryPromise({
2066
2566
  try: () => client.deployments.getHostnames(deploymentId),
2067
2567
  catch: (error) => new DeploymentHostnamesFetchError({
2068
2568
  message: `Failed to get hostnames: ${error instanceof Error ? error.message : String(error)}`
@@ -2093,86 +2593,86 @@ var appOption5 = import_cli11.Options.text("app").pipe(
2093
2593
  var deployCommand = import_cli11.Command.make(
2094
2594
  "deploy",
2095
2595
  { app: appOption5 },
2096
- ({ app }) => import_effect22.Effect.gen(function* () {
2596
+ ({ app }) => import_effect25.Effect.gen(function* () {
2097
2597
  const projectConfig = yield* ProjectConfigService;
2098
2598
  const deploymentApi = yield* DeploymentApiService;
2099
2599
  const archiveService = yield* ArchiveService;
2100
2600
  const projectLink = yield* ProjectLinkService;
2101
- const pathService = yield* import_platform7.Path.Path;
2601
+ const pathService = yield* import_platform9.Path.Path;
2102
2602
  const cwd = process.cwd();
2103
- const applicationId = yield* import_effect22.Option.match(app, {
2104
- onSome: (id) => import_effect22.Effect.succeed(id),
2105
- onNone: () => projectLink.resolve(cwd).pipe(import_effect22.Effect.map((link) => link.applicationId))
2603
+ const applicationId = yield* import_effect25.Option.match(app, {
2604
+ onSome: (id) => import_effect25.Effect.succeed(id),
2605
+ onNone: () => projectLink.resolve(cwd).pipe(import_effect25.Effect.map((link) => link.applicationId))
2106
2606
  });
2107
- yield* import_effect22.Effect.log("Starting deploy command");
2607
+ yield* import_effect25.Effect.log("Starting deploy command");
2108
2608
  const { config } = yield* projectConfig.resolve(cwd);
2109
- yield* import_effect22.Console.log("Creating deployment...");
2609
+ yield* import_effect25.Console.log("Creating deployment...");
2110
2610
  const deploymentId = yield* deploymentApi.createDeployment(applicationId);
2111
- yield* import_effect22.Console.log(`Deployment ID: ${deploymentId}`);
2112
- yield* import_effect22.Console.log("Creating archive...");
2113
- const archivePath = pathService.join(os2.tmpdir(), `project-${Date.now()}.zip`);
2611
+ yield* import_effect25.Console.log(`Deployment ID: ${deploymentId}`);
2612
+ yield* import_effect25.Console.log("Creating archive...");
2613
+ const archivePath = pathService.join(os4.tmpdir(), `project-${Date.now()}.zip`);
2114
2614
  const archive = yield* archiveService.createZipArchive(config.userArchive?.rootOverwrite || cwd, archivePath, {
2115
2615
  whitelist: config.userArchive?.fileWhitelist,
2116
2616
  excludeFiles: config.excludeFiles,
2117
2617
  useIgnoreFiles: false,
2118
2618
  useManagedIgnore: false
2119
2619
  });
2120
- yield* import_effect22.Console.log(`Archive created (${(0, import_commons.formatFileSize)(archive.size)})`);
2121
- yield* import_effect22.Console.log("Uploading archive...");
2620
+ yield* import_effect25.Console.log(`Archive created (${(0, import_commons.formatFileSize)(archive.size)})`);
2621
+ yield* import_effect25.Console.log("Uploading archive...");
2122
2622
  yield* uploadArchive(deploymentId, archivePath, archive.size);
2123
- yield* import_effect22.Console.log("Upload complete.");
2124
- yield* import_effect22.Console.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
2623
+ yield* import_effect25.Console.log("Upload complete.");
2624
+ yield* import_effect25.Console.log("The deployment pipeline is now being provisioned. This may take a few seconds.");
2125
2625
  yield* pollDeployment(deploymentId);
2126
2626
  }).pipe(
2127
- import_effect22.Effect.catchTags({
2128
- ProjectNotLinkedError: (err) => import_effect22.Console.error(err.message).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2129
- ProjectLinkReadError: (err) => import_effect22.Console.error(err.message).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2130
- ConfigNotFoundError: (err) => import_effect22.Console.error(err.message).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2131
- ConfigParseError: (err) => import_effect22.Console.error(`Config parse error: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2132
- ConfigValidationError: (err) => import_effect22.Effect.gen(function* () {
2627
+ import_effect25.Effect.catchTags({
2628
+ ProjectNotLinkedError: (err) => import_effect25.Console.error(err.message).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2629
+ ProjectLinkReadError: (err) => import_effect25.Console.error(err.message).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2630
+ ConfigNotFoundError: (err) => import_effect25.Console.error(err.message).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2631
+ ConfigParseError: (err) => import_effect25.Console.error(`Config parse error: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2632
+ ConfigValidationError: (err) => import_effect25.Effect.gen(function* () {
2133
2633
  for (const e of err.errors) {
2134
- yield* import_effect22.Console.error(e);
2634
+ yield* import_effect25.Console.error(e);
2135
2635
  }
2136
- yield* import_effect22.Console.error(err.message);
2137
- }).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2138
- DeploymentCreateError: (err) => import_effect22.Console.error(`Deployment creation failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2139
- ArchiveCreateError: (err) => import_effect22.Console.error(`Archive creation failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2140
- UploadStartError: (err) => import_effect22.Console.error(`Upload failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2141
- UploadPartError: (err) => import_effect22.Console.error(`Upload failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2142
- UploadCompleteError: (err) => import_effect22.Console.error(`Upload failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2143
- PresignedUrlError: (err) => import_effect22.Console.error(`Upload failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2144
- DeploymentAuthError: (err) => import_effect22.Console.error(`Authentication failed: ${err.message}`).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err))),
2145
- DeploymentFailedError: (err) => import_effect22.Console.error(err.message).pipe(import_effect22.Effect.andThen(import_effect22.Effect.fail(err)))
2636
+ yield* import_effect25.Console.error(err.message);
2637
+ }).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2638
+ DeploymentCreateError: (err) => import_effect25.Console.error(`Deployment creation failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2639
+ ArchiveCreateError: (err) => import_effect25.Console.error(`Archive creation failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2640
+ UploadStartError: (err) => import_effect25.Console.error(`Upload failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2641
+ UploadPartError: (err) => import_effect25.Console.error(`Upload failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2642
+ UploadCompleteError: (err) => import_effect25.Console.error(`Upload failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2643
+ PresignedUrlError: (err) => import_effect25.Console.error(`Upload failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2644
+ DeploymentAuthError: (err) => import_effect25.Console.error(`Authentication failed: ${err.message}`).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err))),
2645
+ DeploymentFailedError: (err) => import_effect25.Console.error(err.message).pipe(import_effect25.Effect.andThen(import_effect25.Effect.fail(err)))
2146
2646
  })
2147
2647
  )
2148
2648
  );
2149
- var uploadArchive = (deploymentId, archivePath, fileSize) => import_effect22.Effect.gen(function* () {
2649
+ var uploadArchive = (deploymentId, archivePath, fileSize) => import_effect25.Effect.gen(function* () {
2150
2650
  const deploymentApi = yield* DeploymentApiService;
2151
- const fs = yield* import_platform7.FileSystem.FileSystem;
2651
+ const fs = yield* import_platform9.FileSystem.FileSystem;
2152
2652
  const uploadId = yield* deploymentApi.startMultipartUpload(deploymentId);
2153
2653
  const FILE_CHUNK_SIZE = 10 * 1024 * 1024;
2154
2654
  const numChunks = Math.ceil(fileSize / FILE_CHUNK_SIZE);
2155
- yield* import_effect22.Effect.log("Starting multipart upload", { uploadId, numChunks, fileSize });
2156
- const parts = yield* import_effect22.Effect.all(
2655
+ yield* import_effect25.Effect.log("Starting multipart upload", { uploadId, numChunks, fileSize });
2656
+ const parts = yield* import_effect25.Effect.all(
2157
2657
  Array.from({ length: numChunks }, (_, i) => {
2158
2658
  const partNumber = i + 1;
2159
2659
  const start = i * FILE_CHUNK_SIZE;
2160
2660
  const bytesToRead = partNumber < numChunks ? FILE_CHUNK_SIZE : fileSize - start;
2161
- return import_effect22.Effect.gen(function* () {
2661
+ return import_effect25.Effect.gen(function* () {
2162
2662
  const presignedUrl = yield* deploymentApi.getPresignedUrl(deploymentId, uploadId, partNumber);
2163
- const chunkData = yield* import_effect22.Stream.runCollect(fs.stream(archivePath, { offset: start, bytesToRead })).pipe(
2164
- import_effect22.Effect.map((chunks) => {
2663
+ const chunkData = yield* import_effect25.Stream.runCollect(fs.stream(archivePath, { offset: start, bytesToRead })).pipe(
2664
+ import_effect25.Effect.map((chunks) => {
2165
2665
  const arrays = Array.from(chunks);
2166
2666
  return Buffer.concat(arrays);
2167
2667
  }),
2168
- import_effect22.Effect.mapError(
2668
+ import_effect25.Effect.mapError(
2169
2669
  (error) => new UploadPartError({
2170
2670
  message: `Failed to read chunk ${partNumber}: ${String(error)}`,
2171
2671
  partNumber
2172
2672
  })
2173
2673
  )
2174
2674
  );
2175
- yield* import_effect22.Effect.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
2675
+ yield* import_effect25.Effect.log(`Uploading part ${partNumber}/${numChunks}`, { size: chunkData.length });
2176
2676
  return yield* deploymentApi.uploadPart(presignedUrl, chunkData, partNumber);
2177
2677
  });
2178
2678
  }),
@@ -2180,20 +2680,20 @@ var uploadArchive = (deploymentId, archivePath, fileSize) => import_effect22.Eff
2180
2680
  );
2181
2681
  yield* deploymentApi.completeUpload(deploymentId, uploadId, parts);
2182
2682
  });
2183
- var pollDeployment = (deploymentId) => import_effect22.Effect.gen(function* () {
2683
+ var pollDeployment = (deploymentId) => import_effect25.Effect.gen(function* () {
2184
2684
  const deploymentApi = yield* DeploymentApiService;
2185
2685
  let lastStatus = "PENDING";
2186
2686
  let logOffset = 0;
2187
- yield* import_effect22.Console.log("Status: Pending");
2188
- yield* import_effect22.Effect.repeat(
2189
- import_effect22.Effect.gen(function* () {
2190
- const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(import_effect22.Effect.catchTag("DeploymentStatusError", () => import_effect22.Effect.succeed(lastStatus)));
2687
+ yield* import_effect25.Console.log("Status: Pending");
2688
+ yield* import_effect25.Effect.repeat(
2689
+ import_effect25.Effect.gen(function* () {
2690
+ const newStatus = yield* deploymentApi.getDeploymentStatus(deploymentId).pipe(import_effect25.Effect.catchTag("DeploymentStatusError", () => import_effect25.Effect.succeed(lastStatus)));
2191
2691
  if (newStatus !== lastStatus) {
2192
2692
  lastStatus = newStatus;
2193
2693
  const label = newStatus.charAt(0) + newStatus.slice(1).toLowerCase();
2194
- yield* import_effect22.Console.log(`Status: ${label}`);
2694
+ yield* import_effect25.Console.log(`Status: ${label}`);
2195
2695
  }
2196
- const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(import_effect22.Effect.catchTag("DeploymentLogsFetchError", () => import_effect22.Effect.succeed(null)));
2696
+ const logsPage = yield* deploymentApi.getLogs(deploymentId, { offset: logOffset, limit: 100 }).pipe(import_effect25.Effect.catchTag("DeploymentLogsFetchError", () => import_effect25.Effect.succeed(null)));
2197
2697
  if (logsPage) {
2198
2698
  for (const logEntry of logsPage.items) {
2199
2699
  logOffset++;
@@ -2201,68 +2701,211 @@ var pollDeployment = (deploymentId) => import_effect22.Effect.gen(function* () {
2201
2701
  }
2202
2702
  }
2203
2703
  if (newStatus === "ACTIVE") {
2204
- const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(import_effect22.Effect.catchTag("DeploymentHostnamesFetchError", () => import_effect22.Effect.succeed([])));
2704
+ const hostnames = yield* deploymentApi.getHostnames(deploymentId).pipe(import_effect25.Effect.catchTag("DeploymentHostnamesFetchError", () => import_effect25.Effect.succeed([])));
2205
2705
  const active = hostnames.find((h) => h.active) ?? hostnames.at(0);
2206
2706
  if (active) {
2207
- yield* import_effect22.Console.log(`Deployed to https://${active.hostname}`);
2707
+ yield* import_effect25.Console.log(`Deployed to https://${active.hostname}`);
2208
2708
  } else {
2209
- yield* import_effect22.Console.log("Deployment is live.");
2709
+ yield* import_effect25.Console.log("Deployment is live.");
2210
2710
  }
2211
- return yield* import_effect22.Effect.fail("done");
2711
+ return yield* import_effect25.Effect.fail("done");
2212
2712
  }
2213
2713
  if (newStatus === "FAILED") {
2214
- return yield* import_effect22.Effect.fail(
2714
+ return yield* import_effect25.Effect.fail(
2215
2715
  new DeploymentFailedError({
2216
2716
  message: "The deployment failed. Please check the logs for more information."
2217
2717
  })
2218
2718
  );
2219
2719
  }
2220
2720
  }),
2221
- import_effect22.Schedule.addDelay(import_effect22.Schedule.forever, () => import_effect22.Duration.seconds(1))
2721
+ import_effect25.Schedule.addDelay(import_effect25.Schedule.forever, () => import_effect25.Duration.seconds(1))
2222
2722
  ).pipe(
2223
- import_effect22.Effect.catchAll((err) => {
2224
- if (err === "done") return import_effect22.Effect.void;
2225
- return import_effect22.Effect.fail(err);
2723
+ import_effect25.Effect.catchAll((err) => {
2724
+ if (err === "done") return import_effect25.Effect.void;
2725
+ return import_effect25.Effect.fail(err);
2226
2726
  })
2227
2727
  );
2228
2728
  });
2229
2729
  var printLog = (logEntry) => {
2230
2730
  switch (logEntry.type) {
2231
2731
  case "ERROR":
2232
- return import_effect22.Console.error(logEntry.message);
2732
+ return import_effect25.Console.error(logEntry.message);
2233
2733
  case "WARN":
2234
- return import_effect22.Console.warn(logEntry.message);
2734
+ return import_effect25.Console.warn(logEntry.message);
2235
2735
  default:
2236
- return import_effect22.Console.log(logEntry.message);
2736
+ return import_effect25.Console.log(logEntry.message);
2237
2737
  }
2238
2738
  };
2239
2739
 
2240
2740
  // src/commands/platform/index.ts
2241
- var platformBase = import_cli12.Command.make("platform", {}, () => import_effect23.Effect.void);
2741
+ var platformBase = import_cli12.Command.make("platform", {}, () => import_effect26.Effect.void);
2242
2742
  var platformCommand = platformBase.pipe(import_cli12.Command.withSubcommands([deployCommand]));
2243
2743
 
2244
- // src/commands/whoami/index.ts
2744
+ // src/commands/setup/index.ts
2245
2745
  var import_cli13 = require("@effect/cli");
2246
- var import_effect24 = require("effect");
2247
- var whoamiCommand = import_cli13.Command.make(
2746
+ var import_effect27 = require("effect");
2747
+ var fileArg2 = import_cli13.Args.text({ name: "file" }).pipe(
2748
+ import_cli13.Args.withDescription("Output file to write (default: .env.local)"),
2749
+ import_cli13.Args.optional
2750
+ );
2751
+ var appOption6 = import_cli13.Options.text("app").pipe(
2752
+ import_cli13.Options.withAlias("a"),
2753
+ import_cli13.Options.withDescription("Application ID to set up (skips the interactive picker)"),
2754
+ import_cli13.Options.optional
2755
+ );
2756
+ var orgOption6 = import_cli13.Options.text("org").pipe(
2757
+ import_cli13.Options.withAlias("o"),
2758
+ import_cli13.Options.withDescription("Only list applications belonging to this organization ID when picking"),
2759
+ import_cli13.Options.optional
2760
+ );
2761
+ var environmentOption2 = import_cli13.Options.text("environment").pipe(
2762
+ import_cli13.Options.withAlias("e"),
2763
+ import_cli13.Options.withDescription("Environment slug to resolve overrides for (e.g. preview)"),
2764
+ import_cli13.Options.optional
2765
+ );
2766
+ var yesOption2 = import_cli13.Options.boolean("yes").pipe(
2767
+ import_cli13.Options.withAlias("y"),
2768
+ import_cli13.Options.withDescription("Overwrite an existing file without confirmation")
2769
+ );
2770
+ var rotateOption2 = import_cli13.Options.boolean("rotate").pipe(
2771
+ import_cli13.Options.withDescription("Force a fresh API key, revoking the previous one")
2772
+ );
2773
+ var setupCommand = import_cli13.Command.make(
2774
+ "setup",
2775
+ {
2776
+ file: fileArg2,
2777
+ app: appOption6,
2778
+ org: orgOption6,
2779
+ environment: environmentOption2,
2780
+ yes: yesOption2,
2781
+ rotate: rotateOption2
2782
+ },
2783
+ ({ file, app, org, environment, yes, rotate }) => import_effect27.Effect.gen(function* () {
2784
+ const apiClient = yield* ApiClientService;
2785
+ const projectLink = yield* ProjectLinkService;
2786
+ const envFile = yield* EnvFileService;
2787
+ const localCredentials = yield* LocalCredentialsService;
2788
+ const cwd = process.cwd();
2789
+ const existingLink = yield* projectLink.load(cwd);
2790
+ let applicationId;
2791
+ if (import_effect27.Option.isSome(existingLink)) {
2792
+ applicationId = existingLink.value.applicationId;
2793
+ if (import_effect27.Option.isSome(app) || import_effect27.Option.isSome(org)) {
2794
+ yield* import_effect27.Console.log(
2795
+ `Note: this directory is already linked to ${applicationId}; ignoring --app/--org. Run "gigadrive unlink" first to target a different application.`
2796
+ );
2797
+ }
2798
+ } else {
2799
+ const organizationId = import_effect27.Option.getOrUndefined(org);
2800
+ const { items: apps } = yield* apiClient.request((client) => client.applications.list({ organizationId }));
2801
+ if (apps.length === 0) {
2802
+ return yield* import_effect27.Effect.fail(
2803
+ new NoApplicationsFoundError({
2804
+ message: 'No applications found. Create one in the Gigadrive console, then run "gigadrive setup" again.'
2805
+ })
2806
+ );
2807
+ }
2808
+ const explicitAppId = import_effect27.Option.getOrUndefined(app);
2809
+ let chosen;
2810
+ if (explicitAppId !== void 0) {
2811
+ const match = apps.find((a) => a.id === explicitAppId);
2812
+ if (match === void 0) {
2813
+ return yield* import_effect27.Effect.fail(
2814
+ new ApplicationNotFoundError({
2815
+ message: `Application "${explicitAppId}" was not found among your applications.`
2816
+ })
2817
+ );
2818
+ }
2819
+ chosen = match;
2820
+ } else if (apps.length === 1) {
2821
+ chosen = apps[0];
2822
+ } else {
2823
+ chosen = yield* import_cli13.Prompt.run(
2824
+ import_cli13.Prompt.select({
2825
+ message: "Select an application to set up",
2826
+ choices: apps.map((a) => ({ title: `${a.name} (${a.organization.name})`, value: a }))
2827
+ })
2828
+ );
2829
+ }
2830
+ applicationId = chosen.id;
2831
+ yield* projectLink.save(cwd, { applicationId, organizationId: chosen.organization.id });
2832
+ yield* import_effect27.Console.log(`Linked this directory to application ${applicationId}.`);
2833
+ }
2834
+ const target = import_effect27.Option.getOrElse(file, () => ".env.local");
2835
+ const environmentSlug = import_effect27.Option.getOrUndefined(environment);
2836
+ const { items, omittedSensitive } = yield* apiClient.request(
2837
+ (client) => client.applications.envVars.pull(applicationId, environmentSlug ? { environment: environmentSlug } : void 0)
2838
+ );
2839
+ const existing = yield* envFile.read(target);
2840
+ if (import_effect27.Option.isSome(existing) && !yes) {
2841
+ const overwrite = yield* import_cli13.Prompt.run(
2842
+ import_cli13.Prompt.confirm({ message: `${target} already exists. Overwrite?`, initial: false })
2843
+ );
2844
+ if (!overwrite) {
2845
+ return yield* import_effect27.Console.log("Cancelled.");
2846
+ }
2847
+ }
2848
+ const credentialEntries2 = yield* localCredentials.provision({
2849
+ applicationId,
2850
+ rotate,
2851
+ existingContent: import_effect27.Option.getOrUndefined(existing)
2852
+ });
2853
+ const entries = [...items.map((v) => ({ key: v.key, value: v.value })), ...credentialEntries2];
2854
+ yield* envFile.write(target, entries);
2855
+ const gitignored = yield* envFile.ensureGitignored(cwd, target);
2856
+ yield* import_effect27.Console.log("\nLocal dev environment ready.");
2857
+ yield* import_effect27.Console.log(` \u2022 Wrote ${items.length} variable(s) and API credentials to ${target}.`);
2858
+ if (omittedSensitive > 0) {
2859
+ yield* import_effect27.Console.log(
2860
+ ` \u2022 Skipped ${omittedSensitive} sensitive variable(s) (only production/preview hold secrets).`
2861
+ );
2862
+ }
2863
+ if (gitignored) {
2864
+ yield* import_effect27.Console.log(` \u2022 Added ${target} to .gitignore.`);
2865
+ }
2866
+ yield* import_effect27.Console.log(" \u2022 Your app can now call the Gigadrive API via the SDK (GIGADRIVE_CLIENT_ID/SECRET).");
2867
+ }).pipe(
2868
+ import_effect27.Effect.catchTags({
2869
+ // Ctrl+C during a prompt is a deliberate cancel — exit 0.
2870
+ QuitException: () => import_effect27.Console.log("Cancelled."),
2871
+ NotAuthenticatedError: (err) => import_effect27.Console.error('You are not logged in. Run "gigadrive login" to authenticate.').pipe(
2872
+ import_effect27.Effect.andThen(import_effect27.Effect.fail(err))
2873
+ ),
2874
+ ApiRequestError: (err) => import_effect27.Console.error(`Setup failed: ${err.message}`).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2875
+ NoApplicationsFoundError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2876
+ ApplicationNotFoundError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2877
+ ProjectLinkReadError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2878
+ ProjectLinkWriteError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2879
+ EnvFileReadError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2880
+ EnvFileWriteError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2881
+ DevCredentialsStoreReadError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err))),
2882
+ DevCredentialsStoreWriteError: (err) => import_effect27.Console.error(err.message).pipe(import_effect27.Effect.andThen(import_effect27.Effect.fail(err)))
2883
+ })
2884
+ )
2885
+ );
2886
+
2887
+ // src/commands/whoami/index.ts
2888
+ var import_cli14 = require("@effect/cli");
2889
+ var import_effect28 = require("effect");
2890
+ var whoamiCommand = import_cli14.Command.make(
2248
2891
  "whoami",
2249
2892
  {},
2250
- () => import_effect24.Effect.gen(function* () {
2893
+ () => import_effect28.Effect.gen(function* () {
2251
2894
  const auth = yield* AuthService;
2252
2895
  const info = yield* auth.getUserInfo;
2253
- yield* import_effect24.Console.log(JSON.stringify(info, null, 2));
2896
+ yield* import_effect28.Console.log(JSON.stringify(info, null, 2));
2254
2897
  }).pipe(
2255
- import_effect24.Effect.catchTags({
2256
- NotAuthenticatedError: () => import_effect24.Console.log('You are not logged in. Run "gigadrive login" to authenticate.'),
2257
- UserInfoFetchError: (err) => import_effect24.Console.error(`Failed to retrieve user info: ${err.message}`),
2258
- OAuthDiscoveryError: (err) => import_effect24.Console.error(`Failed to retrieve user info: ${err.message}`)
2898
+ import_effect28.Effect.catchTags({
2899
+ NotAuthenticatedError: () => import_effect28.Console.log('You are not logged in. Run "gigadrive login" to authenticate.'),
2900
+ UserInfoFetchError: (err) => import_effect28.Console.error(`Failed to retrieve user info: ${err.message}`),
2901
+ OAuthDiscoveryError: (err) => import_effect28.Console.error(`Failed to retrieve user info: ${err.message}`)
2259
2902
  })
2260
2903
  )
2261
2904
  );
2262
2905
 
2263
2906
  // src/index.ts
2264
- var gigadrive = import_cli14.Command.make("gigadrive", {}, () => import_effect25.Effect.void).pipe(
2265
- import_cli14.Command.withSubcommands([
2907
+ var gigadrive = import_cli15.Command.make("gigadrive", {}, () => import_effect29.Effect.void).pipe(
2908
+ import_cli15.Command.withSubcommands([
2266
2909
  loginCommand,
2267
2910
  logoutCommand,
2268
2911
  whoamiCommand,
@@ -2274,27 +2917,31 @@ var gigadrive = import_cli14.Command.make("gigadrive", {}, () => import_effect25
2274
2917
  aiCommand,
2275
2918
  buildCommand,
2276
2919
  debugCommand,
2277
- platformCommand
2920
+ platformCommand,
2921
+ setupCommand
2278
2922
  ])
2279
2923
  );
2280
- var cli = import_cli14.Command.run(gigadrive, {
2924
+ var cli = import_cli15.Command.run(gigadrive, {
2281
2925
  name: "Gigadrive CLI",
2282
2926
  version: package_default.version
2283
2927
  });
2284
- var BaseServices = import_effect25.Layer.mergeAll(
2928
+ var BaseServices = import_effect29.Layer.mergeAll(
2285
2929
  OAuthConfigService.Default,
2286
2930
  AuthStorageService.Default,
2287
2931
  PackageManagerService.Default,
2288
2932
  ArchiveService.Default,
2289
2933
  import_network_config2.NetworkConfigLive,
2290
2934
  ProjectConfigService.Default,
2291
- ProjectLinkService.Default
2292
- ).pipe(import_effect25.Layer.provideMerge(AuthService.Default));
2293
- var ApiClientLive = import_effect25.Layer.provide(ApiClientService.Default, BaseServices);
2294
- var DeploymentApiLive = import_effect25.Layer.provide(DeploymentApiService.Default, BaseServices);
2295
- var ServicesLive = import_effect25.Layer.mergeAll(BaseServices, ApiClientLive, DeploymentApiLive);
2296
- var AppLive = import_effect25.Layer.mergeAll(ServicesLive, import_effect25.Logger.minimumLogLevel(import_effect25.LogLevel.Info)).pipe(
2297
- import_effect25.Layer.provideMerge(import_platform_node.NodeContext.layer)
2298
- );
2299
- import_platform_node.NodeRuntime.runMain(import_effect25.Effect.provide(cli(process.argv), AppLive));
2935
+ ProjectLinkService.Default,
2936
+ EnvFileService.Default,
2937
+ DevCredentialsStore.Default
2938
+ ).pipe(import_effect29.Layer.provideMerge(AuthService.Default));
2939
+ var ApiClientLive = import_effect29.Layer.provide(ApiClientService.Default, BaseServices);
2940
+ var DeploymentApiLive = import_effect29.Layer.provide(DeploymentApiService.Default, BaseServices);
2941
+ var LocalCredentialsLive = import_effect29.Layer.provide(LocalCredentialsService.Default, BaseServices);
2942
+ var ServicesLive = import_effect29.Layer.mergeAll(BaseServices, ApiClientLive, DeploymentApiLive, LocalCredentialsLive);
2943
+ var AppLive = import_effect29.Layer.mergeAll(ServicesLive, import_effect29.Logger.minimumLogLevel(import_effect29.LogLevel.Info)).pipe(
2944
+ import_effect29.Layer.provideMerge(import_platform_node.NodeContext.layer)
2945
+ );
2946
+ import_platform_node.NodeRuntime.runMain(import_effect29.Effect.provide(cli(process.argv), AppLive));
2300
2947
  //# sourceMappingURL=index.cjs.map