gigadrive 2.3.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.mjs CHANGED
@@ -9,7 +9,7 @@ import { Effect as Effect27, Layer, LogLevel, Logger } from "effect";
9
9
  // package.json
10
10
  var package_default = {
11
11
  name: "gigadrive",
12
- version: "2.3.0",
12
+ version: "2.4.0",
13
13
  repository: {
14
14
  type: "git",
15
15
  url: "https://github.com/Gigadrive/sdk.git",
@@ -35,7 +35,7 @@ var package_default = {
35
35
  dependencies: {
36
36
  "@effect/cli": "^0.75.0",
37
37
  "@effect/platform": "^0.96.0",
38
- "@effect/platform-node": "^0.106.0",
38
+ "@effect/platform-node": "^0.107.0",
39
39
  "@gigadrive/build-utils": "workspace:*",
40
40
  "@gigadrive/commons": "workspace:*",
41
41
  "@gigadrive/network-config": "workspace:*",
@@ -46,8 +46,8 @@ var package_default = {
46
46
  open: "^10.2.0"
47
47
  },
48
48
  devDependencies: {
49
- "@effect/language-service": "^0.84.0",
50
- "@types/archiver": "^6.0.3",
49
+ "@effect/language-service": "^0.86.0",
50
+ "@types/archiver": "^6.0.4",
51
51
  esbuild: "^0.28.0",
52
52
  typescript: "catalog:"
53
53
  },
@@ -90,6 +90,25 @@ var TokenExchangeError = class extends Schema.TaggedError()("TokenExchangeError"
90
90
  statusCode: Schema.optional(Schema.Number)
91
91
  }) {
92
92
  };
93
+ var DeviceAuthorizationError = class extends Schema.TaggedError()(
94
+ "DeviceAuthorizationError",
95
+ {
96
+ message: Schema.String,
97
+ cause: Schema.optional(Schema.String)
98
+ }
99
+ ) {
100
+ };
101
+ var AuthorizationDeniedError = class extends Schema.TaggedError()(
102
+ "AuthorizationDeniedError",
103
+ {
104
+ message: Schema.String
105
+ }
106
+ ) {
107
+ };
108
+ var DeviceCodeExpiredError = class extends Schema.TaggedError()("DeviceCodeExpiredError", {
109
+ message: Schema.String
110
+ }) {
111
+ };
93
112
  var TokenRefreshError = class extends Schema.TaggedError()("TokenRefreshError", {
94
113
  message: Schema.String,
95
114
  statusCode: Schema.optional(Schema.Number)
@@ -286,16 +305,10 @@ import { ApiError, GigadriveClient } from "@gigadrive/sdk";
286
305
  import { Config as Config2, Effect as Effect4 } from "effect";
287
306
 
288
307
  // src/services/auth.ts
289
- import { Console, Effect as Effect3, Option as Option2 } from "effect";
290
- import * as crypto from "crypto";
291
- import * as http from "http";
308
+ import { Console, Duration, Effect as Effect3, Option as Option2, Schema as Schema5 } from "effect";
309
+ import process3 from "process";
292
310
  import open from "open";
293
311
 
294
- // src/services/auth-storage.ts
295
- import { FileSystem, Path } from "@effect/platform";
296
- import { Effect, Option, Schema as Schema3 } from "effect";
297
- import * as os from "os";
298
-
299
312
  // src/domain.ts
300
313
  import { Schema as Schema2 } from "effect";
301
314
  var DeploymentId = Schema2.String.pipe(Schema2.brand("@Gigadrive/DeploymentId"));
@@ -313,9 +326,18 @@ var OAuthClientConfig = Schema2.Struct({
313
326
  issuer: Schema2.String,
314
327
  authorizeUrl: Schema2.String,
315
328
  tokenUrl: Schema2.String,
329
+ deviceAuthorizeUrl: Schema2.String,
316
330
  scope: Schema2.String,
317
331
  userinfoUrl: Schema2.String
318
332
  });
333
+ var DeviceAuthorizationResponse = Schema2.Struct({
334
+ device_code: Schema2.String,
335
+ user_code: Schema2.String,
336
+ verification_uri: Schema2.String,
337
+ verification_uri_complete: Schema2.optional(Schema2.String),
338
+ expires_in: Schema2.Number,
339
+ interval: Schema2.optional(Schema2.Number)
340
+ });
319
341
  var StoredAuthData = Schema2.Struct({
320
342
  refreshToken: Schema2.String,
321
343
  accessToken: Schema2.optional(Schema2.String),
@@ -330,7 +352,47 @@ var DevKeyEntry = Schema2.Struct({
330
352
  });
331
353
  var DevKeyStore = Schema2.Record({ key: Schema2.String, value: DevKeyEntry });
332
354
 
355
+ // src/lib/clipboard.ts
356
+ import { spawn } from "child_process";
357
+ import process2 from "process";
358
+ var clipboardCandidates = () => {
359
+ switch (process2.platform) {
360
+ case "darwin":
361
+ return [{ cmd: "pbcopy", args: [] }];
362
+ case "win32":
363
+ return [{ cmd: "clip", args: [] }];
364
+ default:
365
+ return [
366
+ { cmd: "wl-copy", args: [] },
367
+ { cmd: "xclip", args: ["-selection", "clipboard"] },
368
+ { cmd: "xsel", args: ["--clipboard", "--input"] }
369
+ ];
370
+ }
371
+ };
372
+ function writeClipboard(text) {
373
+ const candidates = clipboardCandidates();
374
+ const tryNext = (index) => {
375
+ if (index >= candidates.length) return;
376
+ const { cmd, args } = candidates[index];
377
+ let proc;
378
+ try {
379
+ proc = spawn(cmd, args, { stdio: ["pipe", "ignore", "ignore"] });
380
+ } catch {
381
+ tryNext(index + 1);
382
+ return;
383
+ }
384
+ proc.on("error", () => tryNext(index + 1));
385
+ proc.stdin?.on("error", () => {
386
+ });
387
+ proc.stdin?.end(text);
388
+ };
389
+ tryNext(0);
390
+ }
391
+
333
392
  // src/services/auth-storage.ts
393
+ import { FileSystem, Path } from "@effect/platform";
394
+ import { Effect, Option, Schema as Schema3 } from "effect";
395
+ import * as os from "os";
334
396
  var AuthStorageService = class extends Effect.Service()("AuthStorageService", {
335
397
  accessors: true,
336
398
  effect: Effect.gen(function* () {
@@ -419,7 +481,8 @@ var OpenIdDiscoveryDocument = Schema4.Struct({
419
481
  issuer: Schema4.optional(Schema4.String),
420
482
  authorization_endpoint: Schema4.optional(Schema4.String),
421
483
  token_endpoint: Schema4.optional(Schema4.String),
422
- userinfo_endpoint: Schema4.optional(Schema4.String)
484
+ userinfo_endpoint: Schema4.optional(Schema4.String),
485
+ device_authorization_endpoint: Schema4.optional(Schema4.String)
423
486
  });
424
487
  var CLI_SCOPES = [
425
488
  // Identity / OIDC
@@ -485,7 +548,7 @@ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
485
548
  })
486
549
  )
487
550
  );
488
- if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint) {
551
+ if (!decoded.authorization_endpoint || !decoded.token_endpoint || !decoded.userinfo_endpoint || !decoded.device_authorization_endpoint) {
489
552
  return yield* Effect2.fail(
490
553
  new OAuthDiscoveryError({
491
554
  message: "OIDC discovery returned incomplete endpoints"
@@ -497,6 +560,7 @@ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
497
560
  issuer: decoded.issuer ?? issuerBase,
498
561
  authorizeUrl: decoded.authorization_endpoint,
499
562
  tokenUrl: decoded.token_endpoint,
563
+ deviceAuthorizeUrl: decoded.device_authorization_endpoint,
500
564
  scope: DEFAULT_SCOPE,
501
565
  userinfoUrl: decoded.userinfo_endpoint
502
566
  };
@@ -510,14 +574,9 @@ var OAuthConfigService = class extends Effect2.Service()("OAuthConfigService", {
510
574
  };
511
575
 
512
576
  // src/services/auth.ts
513
- var escapeHtml = (value) => value.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;").replace(/'/g, "&#39;").replace(/\//g, "&#x2F;");
514
- var base64URLEncode = (buffer) => buffer.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, "");
515
- var generatePKCE = () => {
516
- const codeVerifier = base64URLEncode(crypto.randomBytes(64));
517
- const challengeBytes = crypto.createHash("sha256").update(codeVerifier, "utf8").digest();
518
- const codeChallenge = base64URLEncode(challengeBytes);
519
- return { codeVerifier, codeChallenge };
520
- };
577
+ var DEVICE_CODE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
578
+ var DEFAULT_POLL_INTERVAL_SECONDS = 5;
579
+ var REQUEST_TIMEOUT_SECONDS = 30;
521
580
  var AuthService = class extends Effect3.Service()("AuthService", {
522
581
  accessors: true,
523
582
  dependencies: [OAuthConfigService.Default, AuthStorageService.Default],
@@ -532,115 +591,178 @@ var AuthService = class extends Effect3.Service()("AuthService", {
532
591
  yield* storage.remove;
533
592
  yield* Effect3.log("Logged out");
534
593
  });
535
- const exchangeCodeForTokens = (config, authCode, redirectUri, codeVerifier) => Effect3.gen(function* () {
594
+ const requestDeviceAuthorization = (config) => Effect3.gen(function* () {
536
595
  const response = yield* Effect3.tryPromise({
537
- try: () => fetch(config.tokenUrl, {
596
+ try: (signal) => fetch(config.deviceAuthorizeUrl, {
538
597
  method: "POST",
539
598
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
540
- body: new URLSearchParams({
541
- grant_type: "authorization_code",
542
- client_id: config.clientId,
543
- code: authCode,
544
- redirect_uri: redirectUri,
545
- code_verifier: codeVerifier
546
- }).toString()
599
+ body: new URLSearchParams({ client_id: config.clientId, scope: config.scope }).toString(),
600
+ signal
547
601
  }),
548
- catch: (error) => new TokenExchangeError({
549
- message: `Token exchange request failed: ${error instanceof Error ? error.message : String(error)}`
602
+ catch: (error) => new DeviceAuthorizationError({
603
+ message: `Device authorization request failed: ${error instanceof Error ? error.message : String(error)}`
550
604
  })
551
605
  });
552
606
  if (!response.ok) {
553
607
  const body = yield* Effect3.tryPromise({
554
608
  try: () => response.text(),
555
- catch: () => new TokenExchangeError({ message: "Failed to read token exchange error response" })
609
+ catch: () => new DeviceAuthorizationError({ message: "Failed to read device authorization error response" })
556
610
  });
557
611
  return yield* Effect3.fail(
558
- new TokenExchangeError({
559
- message: `Token exchange failed: ${response.status} ${response.statusText} - ${body}`,
560
- statusCode: response.status
612
+ new DeviceAuthorizationError({
613
+ message: `Device authorization failed: ${response.status} ${response.statusText} - ${body}`
561
614
  })
562
615
  );
563
616
  }
564
- const tokens = yield* Effect3.tryPromise({
617
+ const json = yield* Effect3.tryPromise({
618
+ try: () => response.json(),
619
+ catch: () => new DeviceAuthorizationError({ message: "Failed to parse device authorization response" })
620
+ });
621
+ return yield* Schema5.decodeUnknown(DeviceAuthorizationResponse)(json).pipe(
622
+ Effect3.mapError(
623
+ () => new DeviceAuthorizationError({ message: "Invalid device authorization response schema" })
624
+ )
625
+ );
626
+ }).pipe(
627
+ Effect3.timeoutFail({
628
+ duration: Duration.seconds(REQUEST_TIMEOUT_SECONDS),
629
+ onTimeout: () => new DeviceAuthorizationError({ message: "Device authorization request timed out" })
630
+ })
631
+ );
632
+ const pollTokenOnce = (config, deviceCode) => Effect3.gen(function* () {
633
+ const response = yield* Effect3.tryPromise({
634
+ try: (signal) => fetch(config.tokenUrl, {
635
+ method: "POST",
636
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
637
+ body: new URLSearchParams({
638
+ grant_type: DEVICE_CODE_GRANT_TYPE,
639
+ client_id: config.clientId,
640
+ device_code: deviceCode
641
+ }).toString(),
642
+ signal
643
+ }),
644
+ catch: (error) => new TokenExchangeError({
645
+ message: `Token poll request failed: ${error instanceof Error ? error.message : String(error)}`
646
+ })
647
+ });
648
+ if (response.ok) {
649
+ const tokens = yield* Effect3.tryPromise({
650
+ try: () => response.json(),
651
+ catch: () => new TokenExchangeError({ message: "Failed to parse token response" })
652
+ });
653
+ return { kind: "tokens", tokens };
654
+ }
655
+ const errorBody = yield* Effect3.tryPromise({
565
656
  try: () => response.json(),
566
- catch: () => new TokenExchangeError({ message: "Failed to parse token exchange response" })
657
+ catch: () => new TokenExchangeError({ message: "Failed to parse token poll error response" })
567
658
  });
568
- return tokens;
659
+ switch (errorBody.error) {
660
+ case "authorization_pending":
661
+ return { kind: "pending" };
662
+ case "slow_down":
663
+ return { kind: "slow_down" };
664
+ case "access_denied":
665
+ return { kind: "denied" };
666
+ case "expired_token":
667
+ return { kind: "expired" };
668
+ default:
669
+ return yield* Effect3.fail(
670
+ new TokenExchangeError({
671
+ message: `Token poll failed: ${response.status} ${response.statusText} - ${errorBody.error ?? "unknown_error"}`,
672
+ statusCode: response.status
673
+ })
674
+ );
675
+ }
569
676
  });
570
- const startCallbackServer = (config, codeChallenge, state) => Effect3.async((resume) => {
571
- const server = http.createServer();
572
- const redirectPath = "/callback";
573
- server.listen(0, "127.0.0.1", () => {
574
- const address = server.address();
575
- if (!address || typeof address !== "object") {
576
- server.close(() => {
577
- resume(Effect3.fail(new LoginFlowError({ message: "Failed to determine callback server address" })));
578
- });
579
- return;
677
+ const pollForToken = (config, device) => Effect3.gen(function* () {
678
+ const deadline = Date.now() + device.expires_in * 1e3;
679
+ let interval = device.interval ?? DEFAULT_POLL_INTERVAL_SECONDS;
680
+ while (true) {
681
+ yield* Effect3.sleep(Duration.seconds(interval));
682
+ if (Date.now() >= deadline) {
683
+ return yield* Effect3.fail(
684
+ new DeviceCodeExpiredError({
685
+ message: "The login code expired before it was approved. Please run `gigadrive login` again."
686
+ })
687
+ );
580
688
  }
581
- const port = address.port;
582
- const redirectUri = `http://127.0.0.1:${port}${redirectPath}`;
583
- const authUrl = new URL(config.authorizeUrl);
584
- authUrl.searchParams.append("client_id", config.clientId);
585
- authUrl.searchParams.append("redirect_uri", redirectUri);
586
- authUrl.searchParams.append("response_type", "code");
587
- authUrl.searchParams.append("scope", config.scope);
588
- authUrl.searchParams.append("code_challenge", codeChallenge);
589
- authUrl.searchParams.append("code_challenge_method", "S256");
590
- authUrl.searchParams.append("state", state);
591
- Console.log(`Please open this URL in your browser to log in:
592
- ${authUrl.toString()}
593
- `).pipe(Effect3.runSync);
594
- Console.log("Waiting for login callback...").pipe(Effect3.runSync);
595
- void open(authUrl.toString());
596
- server.on("request", (req, res) => {
597
- const reqUrl = new URL(req.url || "", redirectUri);
598
- if (reqUrl.pathname !== "/callback") {
599
- res.writeHead(404).end("Not Found");
600
- return;
601
- }
602
- const authCode = reqUrl.searchParams.get("code");
603
- const receivedState = reqUrl.searchParams.get("state");
604
- const oauthError = reqUrl.searchParams.get("error");
605
- res.writeHead(200, { "Content-Type": "text/html", Connection: "close" });
606
- if (oauthError) {
607
- const safeOauthError = escapeHtml(oauthError);
608
- res.end(`<h1>Login Failed!</h1><p>Error: ${safeOauthError}</p><p>You can close this tab.</p>`);
609
- server.close();
610
- resume(Effect3.fail(new LoginFlowError({ message: `OAuth error: ${oauthError}` })));
611
- return;
612
- }
613
- if (receivedState !== state) {
614
- res.end(
615
- "<h1>Login Failed!</h1><p>State mismatch. Possible CSRF attack.</p><p>You can close this tab.</p>"
689
+ const result = yield* pollTokenOnce(config, device.device_code).pipe(
690
+ // A hung poll must not stall the loop: on timeout treat it as pending so
691
+ // the next iteration re-checks the expiry deadline.
692
+ Effect3.timeoutTo({
693
+ duration: Duration.seconds(REQUEST_TIMEOUT_SECONDS),
694
+ onSuccess: (value) => value,
695
+ onTimeout: () => ({ kind: "pending" })
696
+ })
697
+ );
698
+ switch (result.kind) {
699
+ case "tokens":
700
+ return result.tokens;
701
+ case "pending":
702
+ continue;
703
+ case "slow_down":
704
+ interval += 5;
705
+ continue;
706
+ case "denied":
707
+ return yield* Effect3.fail(new AuthorizationDeniedError({ message: "The login request was denied." }));
708
+ case "expired":
709
+ return yield* Effect3.fail(
710
+ new DeviceCodeExpiredError({
711
+ message: "The login code expired before it was approved. Please run `gigadrive login` again."
712
+ })
616
713
  );
617
- server.close();
618
- resume(Effect3.fail(new LoginFlowError({ message: "State mismatch" })));
619
- return;
620
- }
621
- if (authCode) {
622
- res.end("<h1>Login Successful!</h1><p>You can close this tab.</p>");
623
- server.close();
624
- resume(Effect3.succeed({ authCode, redirectUri }));
625
- } else {
626
- res.end("<h1>Login Failed!</h1><p>No authorization code received.</p><p>You can close this tab.</p>");
627
- server.close();
628
- resume(Effect3.fail(new LoginFlowError({ message: "No authorization code received" })));
629
- }
630
- });
631
- server.on("error", (err) => {
632
- server.close();
633
- resume(Effect3.fail(new LoginFlowError({ message: `Local server error: ${err.message}` })));
634
- });
714
+ }
715
+ }
716
+ });
717
+ const watchCopyKey = (url) => Effect3.async((resume) => {
718
+ const stdin = process3.stdin;
719
+ if (!stdin.isTTY) {
720
+ return;
721
+ }
722
+ stdin.setRawMode(true);
723
+ stdin.resume();
724
+ stdin.setEncoding("utf8");
725
+ const onData = (key) => {
726
+ if (key === "c" || key === "C") {
727
+ writeClipboard(url);
728
+ Console.log("Copied the login URL to your clipboard.").pipe(Effect3.runSync);
729
+ } else if (key === "") {
730
+ resume(Effect3.fail(new LoginFlowError({ message: "Login canceled." })));
731
+ }
732
+ };
733
+ stdin.on("data", onData);
734
+ return Effect3.sync(() => {
735
+ stdin.off("data", onData);
736
+ if (stdin.isTTY) stdin.setRawMode(false);
737
+ stdin.pause();
635
738
  });
636
739
  });
637
740
  const login = Effect3.gen(function* () {
638
741
  yield* logout;
639
- const { codeVerifier, codeChallenge } = generatePKCE();
640
- const state = crypto.randomBytes(16).toString("hex");
641
742
  const config = yield* oauthConfig.getConfig;
642
- const { authCode, redirectUri } = yield* startCallbackServer(config, codeChallenge, state);
643
- const tokens = yield* exchangeCodeForTokens(config, authCode, redirectUri, codeVerifier);
743
+ const device = yield* requestDeviceAuthorization(config);
744
+ const verificationUri = device.verification_uri;
745
+ const verificationUriComplete = device.verification_uri_complete ?? verificationUri;
746
+ const interactive = process3.stdin.isTTY === true;
747
+ yield* Console.log(
748
+ `
749
+ To sign in, visit:
750
+
751
+ ${verificationUri}
752
+
753
+ and enter the code:
754
+
755
+ ${device.user_code}
756
+ `
757
+ );
758
+ if (interactive) {
759
+ yield* Effect3.tryPromise(() => open(verificationUriComplete)).pipe(Effect3.catchAll(() => Effect3.void));
760
+ yield* Console.log("We tried to open your browser automatically. If it did not open, use the URL above.");
761
+ yield* Console.log('Press "c" to copy the URL to your clipboard, or Ctrl+C to cancel.');
762
+ }
763
+ yield* Console.log("\nWaiting for you to approve the login\u2026");
764
+ const poll = pollForToken(config, device);
765
+ const tokens = yield* interactive ? poll.pipe(Effect3.raceFirst(watchCopyKey(verificationUriComplete))) : poll;
644
766
  if (!tokens.refresh_token) {
645
767
  return yield* Effect3.fail(
646
768
  new TokenExchangeError({ message: "No refresh token received from identity provider" })
@@ -835,7 +957,7 @@ var ApiClientService = class extends Effect4.Service()("ApiClientService", {
835
957
 
836
958
  // src/services/project-link.ts
837
959
  import { FileSystem as FileSystem2, Path as Path2 } from "@effect/platform";
838
- import { Effect as Effect5, Option as Option3, Schema as Schema5 } from "effect";
960
+ import { Effect as Effect5, Option as Option3, Schema as Schema6 } from "effect";
839
961
  var LINK_DIR = ".gigadrive";
840
962
  var LINK_FILE = "project.json";
841
963
  var ProjectLinkService = class extends Effect5.Service()("ProjectLinkService", {
@@ -854,7 +976,7 @@ var ProjectLinkService = class extends Effect5.Service()("ProjectLinkService", {
854
976
  try: () => JSON.parse(data),
855
977
  catch: () => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} contains invalid JSON` })
856
978
  });
857
- const link = yield* Schema5.decodeUnknown(ProjectLink)(json).pipe(
979
+ const link = yield* Schema6.decodeUnknown(ProjectLink)(json).pipe(
858
980
  Effect5.mapError(() => new ProjectLinkReadError({ message: `${LINK_DIR}/${LINK_FILE} has an invalid schema` }))
859
981
  );
860
982
  return Option3.some(link);
@@ -1721,7 +1843,7 @@ import * as os3 from "os";
1721
1843
 
1722
1844
  // src/services/dev-credentials-store.ts
1723
1845
  import { FileSystem as FileSystem7, Path as Path6 } from "@effect/platform";
1724
- import { Effect as Effect15, Option as Option8, Schema as Schema6 } from "effect";
1846
+ import { Effect as Effect15, Option as Option8, Schema as Schema7 } from "effect";
1725
1847
  import * as os2 from "os";
1726
1848
  var DevCredentialsStore = class extends Effect15.Service()("DevCredentialsStore", {
1727
1849
  accessors: true,
@@ -1754,7 +1876,7 @@ var DevCredentialsStore = class extends Effect15.Service()("DevCredentialsStore"
1754
1876
  try: () => JSON.parse(data),
1755
1877
  catch: () => new DevCredentialsStoreReadError({ message: "dev-keys.json contains invalid JSON" })
1756
1878
  });
1757
- return yield* Schema6.decodeUnknown(DevKeyStore)(json).pipe(
1879
+ return yield* Schema7.decodeUnknown(DevKeyStore)(json).pipe(
1758
1880
  Effect15.mapError(() => new DevCredentialsStoreReadError({ message: "dev-keys.json has an invalid schema" }))
1759
1881
  );
1760
1882
  });
@@ -2145,7 +2267,10 @@ var loginCommand = Command9.make(
2145
2267
  yield* Console9.log(`You are now logged in as ${name}.`);
2146
2268
  }).pipe(
2147
2269
  Effect19.catchTags({
2148
- LoginFlowError: (err) => Console9.error(`Login failed: ${err.message}`),
2270
+ LoginFlowError: (err) => Console9.error(err.message),
2271
+ DeviceAuthorizationError: (err) => Console9.error(`Login failed: ${err.message}`),
2272
+ AuthorizationDeniedError: (err) => Console9.error(err.message),
2273
+ DeviceCodeExpiredError: (err) => Console9.error(err.message),
2149
2274
  TokenExchangeError: (err) => Console9.error(`Login failed: ${err.message}`),
2150
2275
  OAuthDiscoveryError: (err) => Console9.error(`Login failed: ${err.message}`),
2151
2276
  AuthStorageWriteError: (err) => Console9.error(`Failed to save credentials: ${err.message}`)
@@ -2178,7 +2303,7 @@ import { Effect as Effect24 } from "effect";
2178
2303
  import { Command as Command11, Options as Options6 } from "@effect/cli";
2179
2304
  import { FileSystem as FileSystem9, Path as Path8 } from "@effect/platform";
2180
2305
  import { formatFileSize } from "@gigadrive/commons";
2181
- import { Console as Console11, Duration, Effect as Effect23, Option as Option12, Schedule, Stream } from "effect";
2306
+ import { Console as Console11, Duration as Duration2, Effect as Effect23, Option as Option12, Schedule, Stream } from "effect";
2182
2307
  import * as os4 from "os";
2183
2308
 
2184
2309
  // src/services/archive.ts
@@ -2578,7 +2703,7 @@ var pollDeployment = (deploymentId) => Effect23.gen(function* () {
2578
2703
  );
2579
2704
  }
2580
2705
  }),
2581
- Schedule.addDelay(Schedule.forever, () => Duration.seconds(1))
2706
+ Schedule.addDelay(Schedule.forever, () => Duration2.seconds(1))
2582
2707
  ).pipe(
2583
2708
  Effect23.catchAll((err) => {
2584
2709
  if (err === "done") return Effect23.void;