@nesalia/cli 1.0.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.
Files changed (103) hide show
  1. package/.turbo/turbo-build.log +4 -0
  2. package/dist/commands/auth/index.d.ts +4 -0
  3. package/dist/commands/auth/index.d.ts.map +1 -0
  4. package/dist/commands/auth/index.js +4 -0
  5. package/dist/commands/auth/index.js.map +1 -0
  6. package/dist/commands/auth/login.d.ts +2 -0
  7. package/dist/commands/auth/login.d.ts.map +1 -0
  8. package/dist/commands/auth/login.js +19 -0
  9. package/dist/commands/auth/login.js.map +1 -0
  10. package/dist/commands/auth/logout.d.ts +2 -0
  11. package/dist/commands/auth/logout.d.ts.map +1 -0
  12. package/dist/commands/auth/logout.js +23 -0
  13. package/dist/commands/auth/logout.js.map +1 -0
  14. package/dist/commands/auth/status.d.ts +2 -0
  15. package/dist/commands/auth/status.d.ts.map +1 -0
  16. package/dist/commands/auth/status.js +33 -0
  17. package/dist/commands/auth/status.js.map +1 -0
  18. package/dist/commands/index.d.ts +2 -0
  19. package/dist/commands/index.d.ts.map +1 -0
  20. package/dist/commands/index.js +2 -0
  21. package/dist/commands/index.js.map +1 -0
  22. package/dist/commands/post/by-id.d.ts +6 -0
  23. package/dist/commands/post/by-id.d.ts.map +1 -0
  24. package/dist/commands/post/by-id.js +32 -0
  25. package/dist/commands/post/by-id.js.map +1 -0
  26. package/dist/commands/post/create.d.ts +7 -0
  27. package/dist/commands/post/create.d.ts.map +1 -0
  28. package/dist/commands/post/create.js +35 -0
  29. package/dist/commands/post/create.js.map +1 -0
  30. package/dist/commands/post/index.d.ts +4 -0
  31. package/dist/commands/post/index.d.ts.map +1 -0
  32. package/dist/commands/post/index.js +4 -0
  33. package/dist/commands/post/index.js.map +1 -0
  34. package/dist/commands/post/list.d.ts +2 -0
  35. package/dist/commands/post/list.d.ts.map +1 -0
  36. package/dist/commands/post/list.js +35 -0
  37. package/dist/commands/post/list.js.map +1 -0
  38. package/dist/index.d.ts +3 -0
  39. package/dist/index.d.ts.map +1 -0
  40. package/dist/index.js +22 -0
  41. package/dist/index.js.map +1 -0
  42. package/dist/lib/api/client.d.ts +17 -0
  43. package/dist/lib/api/client.d.ts.map +1 -0
  44. package/dist/lib/api/client.js +18 -0
  45. package/dist/lib/api/client.js.map +1 -0
  46. package/dist/lib/auth/client.d.ts +2748 -0
  47. package/dist/lib/auth/client.d.ts.map +1 -0
  48. package/dist/lib/auth/client.js +11 -0
  49. package/dist/lib/auth/client.js.map +1 -0
  50. package/dist/lib/auth/device-flow/config.d.ts +5 -0
  51. package/dist/lib/auth/device-flow/config.d.ts.map +1 -0
  52. package/dist/lib/auth/device-flow/config.js +5 -0
  53. package/dist/lib/auth/device-flow/config.js.map +1 -0
  54. package/dist/lib/auth/device-flow/device-code.d.ts +10 -0
  55. package/dist/lib/auth/device-flow/device-code.d.ts.map +1 -0
  56. package/dist/lib/auth/device-flow/device-code.js +27 -0
  57. package/dist/lib/auth/device-flow/device-code.js.map +1 -0
  58. package/dist/lib/auth/device-flow/errors.d.ts +8 -0
  59. package/dist/lib/auth/device-flow/errors.d.ts.map +1 -0
  60. package/dist/lib/auth/device-flow/errors.js +26 -0
  61. package/dist/lib/auth/device-flow/errors.js.map +1 -0
  62. package/dist/lib/auth/device-flow/index.d.ts +3 -0
  63. package/dist/lib/auth/device-flow/index.d.ts.map +1 -0
  64. package/dist/lib/auth/device-flow/index.js +13 -0
  65. package/dist/lib/auth/device-flow/index.js.map +1 -0
  66. package/dist/lib/auth/device-flow/polling.d.ts +4 -0
  67. package/dist/lib/auth/device-flow/polling.d.ts.map +1 -0
  68. package/dist/lib/auth/device-flow/polling.js +75 -0
  69. package/dist/lib/auth/device-flow/polling.js.map +1 -0
  70. package/dist/lib/auth/device-flow/types.d.ts +10 -0
  71. package/dist/lib/auth/device-flow/types.d.ts.map +1 -0
  72. package/dist/lib/auth/device-flow/types.js +2 -0
  73. package/dist/lib/auth/device-flow/types.js.map +1 -0
  74. package/dist/lib/auth/index.d.ts +12 -0
  75. package/dist/lib/auth/index.d.ts.map +1 -0
  76. package/dist/lib/auth/index.js +24 -0
  77. package/dist/lib/auth/index.js.map +1 -0
  78. package/dist/lib/auth/storage.d.ts +20 -0
  79. package/dist/lib/auth/storage.d.ts.map +1 -0
  80. package/dist/lib/auth/storage.js +48 -0
  81. package/dist/lib/auth/storage.js.map +1 -0
  82. package/package.json +36 -0
  83. package/src/CLAUDE.md +83 -0
  84. package/src/commands/auth/index.ts +3 -0
  85. package/src/commands/auth/login.ts +20 -0
  86. package/src/commands/auth/logout.ts +25 -0
  87. package/src/commands/auth/status.ts +37 -0
  88. package/src/commands/index.ts +1 -0
  89. package/src/index.ts +32 -0
  90. package/src/lib/api/client.ts +20 -0
  91. package/src/lib/auth/client.ts +14 -0
  92. package/src/lib/auth/device-flow/config.ts +4 -0
  93. package/src/lib/auth/device-flow/device-code.ts +39 -0
  94. package/src/lib/auth/device-flow/errors.ts +29 -0
  95. package/src/lib/auth/device-flow/index.ts +19 -0
  96. package/src/lib/auth/device-flow/polling.ts +91 -0
  97. package/src/lib/auth/device-flow/types.ts +9 -0
  98. package/src/lib/auth/index.ts +41 -0
  99. package/src/lib/auth/storage.ts +69 -0
  100. package/tests/auth.test.ts +123 -0
  101. package/tests/setup.ts +43 -0
  102. package/tsconfig.json +19 -0
  103. package/vitest.config.ts +9 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import { createAuthClient } from "better-auth/client";
2
+ import { deviceAuthorizationClient, organizationClient } from "better-auth/client/plugins";
3
+ const BASE_URL = process.env.CLI_AUTH_API_URL ?? process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
4
+ export const authClient = createAuthClient({
5
+ baseURL: BASE_URL,
6
+ plugins: [
7
+ deviceAuthorizationClient(),
8
+ organizationClient(),
9
+ ],
10
+ });
11
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/lib/auth/client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAE3F,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,OAAO,CAAC,GAAG,CAAC,oBAAoB,IAAI,uBAAuB,CAAC;AAE7G,MAAM,CAAC,MAAM,UAAU,GAAG,gBAAgB,CAAC;IACzC,OAAO,EAAE,QAAQ;IACjB,OAAO,EAAE;QACP,yBAAyB,EAAE;QAC3B,kBAAkB,EAAE;KACrB;CACF,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ export declare const CLIENT_ID: string;
2
+ export declare const POLL_TIMEOUT_MS: number;
3
+ export declare const SCOPE = "openid profile email";
4
+ export declare const MAX_NETWORK_RETRIES = 3;
5
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/config.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,SAAS,QAA0C,CAAC;AACjE,eAAO,MAAM,eAAe,QAAiB,CAAC;AAC9C,eAAO,MAAM,KAAK,yBAAyB,CAAC;AAC5C,eAAO,MAAM,mBAAmB,IAAI,CAAC"}
@@ -0,0 +1,5 @@
1
+ export const CLIENT_ID = process.env.CLI_AUTH_CLIENT_ID ?? "cli";
2
+ export const POLL_TIMEOUT_MS = 30 * 60 * 1000; // 30 min max (matches server default expiresIn)
3
+ export const SCOPE = "openid profile email";
4
+ export const MAX_NETWORK_RETRIES = 3;
5
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/config.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,KAAK,CAAC;AACjE,MAAM,CAAC,MAAM,eAAe,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,gDAAgD;AAC/F,MAAM,CAAC,MAAM,KAAK,GAAG,sBAAsB,CAAC;AAC5C,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { AuthClient } from "../client.js";
2
+ export interface DeviceCodeResult {
3
+ deviceCode: string;
4
+ userCode: string;
5
+ verificationUri: string;
6
+ interval: number;
7
+ }
8
+ export declare const requestDeviceCode: (client: AuthClient) => Promise<DeviceCodeResult>;
9
+ export declare const openBrowser: (uri: string) => Promise<void>;
10
+ //# sourceMappingURL=device-code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/device-code.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAI/C,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,iBAAiB,GAAU,QAAQ,UAAU,KAAG,OAAO,CAAC,gBAAgB,CAmBpF,CAAC;AAEF,eAAO,MAAM,WAAW,GAAU,KAAK,MAAM,KAAG,OAAO,CAAC,IAAI,CAI3D,CAAC"}
@@ -0,0 +1,27 @@
1
+ import { log } from "@clack/prompts";
2
+ import open from "open";
3
+ import { CLIENT_ID, SCOPE } from "./config.js";
4
+ import { AuthFlowError } from "./errors.js";
5
+ export const requestDeviceCode = async (client) => {
6
+ log.info("Requesting device authorization...");
7
+ const { data, error } = await client.device.code({
8
+ client_id: CLIENT_ID,
9
+ scope: SCOPE,
10
+ });
11
+ if (error || !data) {
12
+ const msg = error?.error_description ?? "Failed to get device code";
13
+ throw new AuthFlowError(msg);
14
+ }
15
+ return {
16
+ deviceCode: data.device_code,
17
+ userCode: data.user_code,
18
+ verificationUri: data.verification_uri_complete,
19
+ interval: data.interval ?? 5,
20
+ };
21
+ };
22
+ export const openBrowser = async (uri) => {
23
+ await open(uri).catch(() => {
24
+ // Non-fatal: browser may fail to open, user can still use the URL
25
+ });
26
+ };
27
+ //# sourceMappingURL=device-code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"device-code.js","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/device-code.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,IAAI,MAAM,MAAM,CAAC;AAExB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAS5C,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EAAE,MAAkB,EAA6B,EAAE;IACvF,GAAG,CAAC,IAAI,CAAC,oCAAoC,CAAC,CAAC;IAE/C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC;QAC/C,SAAS,EAAE,SAAS;QACpB,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IAEH,IAAI,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,MAAM,GAAG,GAAG,KAAK,EAAE,iBAAiB,IAAI,2BAA2B,CAAC;QACpE,MAAM,IAAI,aAAa,CAAC,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,OAAO;QACL,UAAU,EAAE,IAAI,CAAC,WAAW;QAC5B,QAAQ,EAAE,IAAI,CAAC,SAAS;QACxB,eAAe,EAAE,IAAI,CAAC,yBAAyB;QAC/C,QAAQ,EAAE,IAAI,CAAC,QAAQ,IAAI,CAAC;KAC7B,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,KAAK,EAAE,GAAW,EAAiB,EAAE;IAC9D,MAAM,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE;QACzB,kEAAkE;IACpE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare const TRANSIENT_ERROR_CODES: Set<string>;
2
+ export declare const isTransientError: (error: unknown) => boolean;
3
+ export declare class AuthFlowError extends Error {
4
+ readonly isNetwork: boolean;
5
+ constructor(message: string, isNetwork?: boolean);
6
+ static network: (msg: string) => AuthFlowError;
7
+ }
8
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/errors.ts"],"names":[],"mappings":"AACA,eAAO,MAAM,qBAAqB,aAMhC,CAAC;AAEH,eAAO,MAAM,gBAAgB,GAAI,OAAO,OAAO,KAAG,OAMjD,CAAC;AAGF,qBAAa,aAAc,SAAQ,KAAK;aAGpB,SAAS;gBADzB,OAAO,EAAE,MAAM,EACC,SAAS,UAAQ;IAMnC,MAAM,CAAC,OAAO,GAAI,KAAK,MAAM,KAAG,aAAa,CAAiC;CAC/E"}
@@ -0,0 +1,26 @@
1
+ // Transient network error codes from Node.js
2
+ export const TRANSIENT_ERROR_CODES = new Set([
3
+ "ETIMEDOUT",
4
+ "ECONNRESET",
5
+ "ECONNREFUSED",
6
+ "ENOTFOUND",
7
+ "ENETUNREACH",
8
+ ]);
9
+ export const isTransientError = (error) => {
10
+ if (error && typeof error === "object") {
11
+ const code = error.code;
12
+ if (code && TRANSIENT_ERROR_CODES.has(code))
13
+ return true;
14
+ }
15
+ return false;
16
+ };
17
+ // Generic auth flow error (network or oauth)
18
+ export class AuthFlowError extends Error {
19
+ constructor(message, isNetwork = false) {
20
+ super(message);
21
+ this.isNetwork = isNetwork;
22
+ this.name = "AuthFlowError";
23
+ }
24
+ }
25
+ AuthFlowError.network = (msg) => new AuthFlowError(msg, true);
26
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/errors.ts"],"names":[],"mappings":"AAAA,6CAA6C;AAC7C,MAAM,CAAC,MAAM,qBAAqB,GAAG,IAAI,GAAG,CAAC;IAC3C,WAAW;IACX,YAAY;IACZ,cAAc;IACd,WAAW;IACX,aAAa;CACd,CAAC,CAAC;AAEH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,KAAc,EAAW,EAAE;IAC1D,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACvC,MAAM,IAAI,GAAI,KAA2B,CAAC,IAAI,CAAC;QAC/C,IAAI,IAAI,IAAI,qBAAqB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC,CAAC;AAEF,6CAA6C;AAC7C,MAAM,OAAO,aAAc,SAAQ,KAAK;IACtC,YACE,OAAe,EACC,YAAY,KAAK;QAEjC,KAAK,CAAC,OAAO,CAAC,CAAC;QAFC,cAAS,GAAT,SAAS,CAAQ;QAGjC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;;AAEM,qBAAO,GAAG,CAAC,GAAW,EAAiB,EAAE,CAAC,IAAI,aAAa,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { AuthFlowResult } from "./types.js";
2
+ export declare const startDeviceFlow: () => Promise<AuthFlowResult>;
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,eAAO,MAAM,eAAe,QAAa,OAAO,CAAC,cAAc,CAY9D,CAAA"}
@@ -0,0 +1,13 @@
1
+ import { log } from "@clack/prompts";
2
+ import { authClient } from "../client.js";
3
+ import { requestDeviceCode, openBrowser } from "./device-code.js";
4
+ import { pollForToken } from "./polling.js";
5
+ export const startDeviceFlow = async () => {
6
+ const { deviceCode, userCode, verificationUri, interval } = await requestDeviceCode(authClient);
7
+ log.message(`Open this URL in your browser:\n ${verificationUri}\n` +
8
+ `Or enter the code: ${userCode}`);
9
+ await openBrowser(verificationUri);
10
+ log.info(`Waiting for authorization... (polling every ${interval}s)`);
11
+ return pollForToken(authClient, deviceCode, interval);
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAClE,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,IAA6B,EAAE;IACjE,MAAM,EAAE,UAAU,EAAE,QAAQ,EAAE,eAAe,EAAE,QAAQ,EAAE,GAAG,MAAM,iBAAiB,CAAC,UAAU,CAAC,CAAC;IAEhG,GAAG,CAAC,OAAO,CACT,qCAAqC,eAAe,IAAI;QACxD,sBAAsB,QAAQ,EAAE,CACjC,CAAC;IAEF,MAAM,WAAW,CAAC,eAAe,CAAC,CAAC;IACnC,GAAG,CAAC,IAAI,CAAC,+CAA+C,QAAQ,IAAI,CAAC,CAAC;IAEtE,OAAO,YAAY,CAAC,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;AACxD,CAAC,CAAA"}
@@ -0,0 +1,4 @@
1
+ import type { AuthClient } from "../client.js";
2
+ import type { AuthFlowResult } from "./types.js";
3
+ export declare const pollForToken: (client: AuthClient, deviceCode: string, intervalSeconds: number, startedAt?: number, networkRetries?: number) => Promise<AuthFlowResult>;
4
+ //# sourceMappingURL=polling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/polling.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAmBjD,eAAO,MAAM,YAAY,GACvB,QAAQ,UAAU,EAClB,YAAY,MAAM,EAClB,iBAAiB,MAAM,EACvB,kBAAsB,EACtB,uBAAkB,KACjB,OAAO,CAAC,cAAc,CA6DxB,CAAC"}
@@ -0,0 +1,75 @@
1
+ import { log } from "@clack/prompts";
2
+ import { CLIENT_ID, POLL_TIMEOUT_MS, MAX_NETWORK_RETRIES } from "./config.js";
3
+ import { isTransientError } from "./errors.js";
4
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
5
+ const resolveUser = async (client, accessToken) => {
6
+ const response = await client.getSession({
7
+ fetchOptions: {
8
+ headers: { Authorization: `Bearer ${accessToken}` },
9
+ },
10
+ });
11
+ const user = response?.data?.user;
12
+ if (!user) {
13
+ throw new Error("Could not retrieve user session.");
14
+ }
15
+ return { id: user.id, name: user.name, email: user.email, image: user.image ?? undefined };
16
+ };
17
+ export const pollForToken = async (client, deviceCode, intervalSeconds, startedAt = Date.now(), networkRetries = 0) => {
18
+ if (Date.now() - startedAt > POLL_TIMEOUT_MS) {
19
+ throw new Error("Authorization timed out. Please try again.");
20
+ }
21
+ let data;
22
+ let error;
23
+ try {
24
+ const result = await client.device.token({
25
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
26
+ device_code: deviceCode,
27
+ client_id: CLIENT_ID,
28
+ });
29
+ data = result.data;
30
+ error = result.error;
31
+ }
32
+ catch (err) {
33
+ if (isTransientError(err)) {
34
+ const retries = networkRetries + 1;
35
+ if (retries > MAX_NETWORK_RETRIES) {
36
+ throw new Error(`Network error after ${MAX_NETWORK_RETRIES} retries. Check your connection.`);
37
+ }
38
+ log.warn(`Network error during polling — retry ${retries}/${MAX_NETWORK_RETRIES}.`);
39
+ await sleep(intervalSeconds * 1000);
40
+ return pollForToken(client, deviceCode, intervalSeconds, startedAt, retries);
41
+ }
42
+ throw err;
43
+ }
44
+ if (data?.access_token) {
45
+ log.success("Authorization successful!");
46
+ const user = await resolveUser(client, data.access_token);
47
+ if (!user.id) {
48
+ throw new Error("Could not retrieve user information. Please try again.");
49
+ }
50
+ log.success(`Connected as ${user.name || user.email || "user"}`);
51
+ return { accessToken: data.access_token, user };
52
+ }
53
+ if (error) {
54
+ switch (error.error) {
55
+ case "authorization_pending":
56
+ await sleep(intervalSeconds * 1000);
57
+ return pollForToken(client, deviceCode, intervalSeconds, startedAt);
58
+ case "slow_down":
59
+ const newInterval = intervalSeconds + 5;
60
+ log.warn(`Slowing down polling to ${newInterval}s`);
61
+ await sleep(newInterval * 1000);
62
+ return pollForToken(client, deviceCode, newInterval, startedAt);
63
+ case "access_denied":
64
+ throw new Error("Authorization was denied.");
65
+ case "expired_token":
66
+ throw new Error("The code expired. Please try again.");
67
+ default:
68
+ throw new Error(error.error_description ?? `Unexpected error: ${error.error}`);
69
+ }
70
+ }
71
+ // No data and no error — should not happen, but guard anyway
72
+ await sleep(intervalSeconds * 1000);
73
+ return pollForToken(client, deviceCode, intervalSeconds, startedAt);
74
+ };
75
+ //# sourceMappingURL=polling.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"polling.js","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/polling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAErC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAC9E,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,KAAK,GAAG,CAAC,EAAU,EAAiB,EAAE,CAC1C,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAEpD,MAAM,WAAW,GAAG,KAAK,EAAE,MAAkB,EAAE,WAAmB,EAAmC,EAAE;IACrG,MAAM,QAAQ,GAAG,MAAM,MAAM,CAAC,UAAU,CAAC;QACvC,YAAY,EAAE;YACZ,OAAO,EAAE,EAAE,aAAa,EAAE,UAAU,WAAW,EAAE,EAAE;SACpD;KACF,CAAC,CAAC;IAEH,MAAM,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,IAAI,CAAC;IAClC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,kCAAkC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,IAAI,SAAS,EAAE,CAAC;AAC7F,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAC/B,MAAkB,EAClB,UAAkB,EAClB,eAAuB,EACvB,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EACtB,cAAc,GAAG,CAAC,EACO,EAAE;IAC3B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,eAAe,EAAE,CAAC;QAC7C,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;IAChE,CAAC;IAED,IAAI,IAAgE,CAAC;IACrE,IAAI,KAAkE,CAAC;IAEvE,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YACvC,UAAU,EAAE,8CAA8C;YAC1D,WAAW,EAAE,UAAU;YACvB,SAAS,EAAE,SAAS;SACrB,CAAC,CAAC;QACH,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC;QACnB,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,gBAAgB,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,MAAM,OAAO,GAAG,cAAc,GAAG,CAAC,CAAC;YACnC,IAAI,OAAO,GAAG,mBAAmB,EAAE,CAAC;gBAClC,MAAM,IAAI,KAAK,CAAC,uBAAuB,mBAAmB,kCAAkC,CAAC,CAAC;YAChG,CAAC;YACD,GAAG,CAAC,IAAI,CAAC,wCAAwC,OAAO,IAAI,mBAAmB,GAAG,CAAC,CAAC;YACpF,MAAM,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;YACpC,OAAO,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;QAC/E,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC;IAED,IAAI,IAAI,EAAE,YAAY,EAAE,CAAC;QACvB,GAAG,CAAC,OAAO,CAAC,2BAA2B,CAAC,CAAC;QACzC,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC,MAAM,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAC1D,IAAI,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;QAC5E,CAAC;QACD,GAAG,CAAC,OAAO,CAAC,gBAAgB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,KAAK,IAAI,MAAM,EAAE,CAAC,CAAC;QACjE,OAAO,EAAE,WAAW,EAAE,IAAI,CAAC,YAAY,EAAE,IAAI,EAAE,CAAC;IAClD,CAAC;IAED,IAAI,KAAK,EAAE,CAAC;QACV,QAAQ,KAAK,CAAC,KAAK,EAAE,CAAC;YACpB,KAAK,uBAAuB;gBAC1B,MAAM,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;gBACpC,OAAO,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;YACtE,KAAK,WAAW;gBACd,MAAM,WAAW,GAAG,eAAe,GAAG,CAAC,CAAC;gBACxC,GAAG,CAAC,IAAI,CAAC,2BAA2B,WAAW,GAAG,CAAC,CAAC;gBACpD,MAAM,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;gBAChC,OAAO,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;YAClE,KAAK,eAAe;gBAClB,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;YAC/C,KAAK,eAAe;gBAClB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;YACzD;gBACE,MAAM,IAAI,KAAK,CAAC,KAAK,CAAC,iBAAiB,IAAI,qBAAqB,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC;QACnF,CAAC;IACH,CAAC;IAED,6DAA6D;IAC7D,MAAM,KAAK,CAAC,eAAe,GAAG,IAAI,CAAC,CAAC;IACpC,OAAO,YAAY,CAAC,MAAM,EAAE,UAAU,EAAE,eAAe,EAAE,SAAS,CAAC,CAAC;AACtE,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ export type AuthFlowResult = {
2
+ accessToken: string;
3
+ user: {
4
+ id: string;
5
+ name: string;
6
+ email: string;
7
+ image?: string;
8
+ };
9
+ };
10
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,cAAc,GAAG;IAC3B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;CACH,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../../../src/lib/auth/device-flow/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,12 @@
1
+ export { authClient } from "./client.js";
2
+ export type { AuthClient } from "./client.js";
3
+ export { startDeviceFlow } from "./device-flow/index.js";
4
+ export type { AuthFlowResult } from "./device-flow/types.js";
5
+ export { saveCredentials, loadCredentials, clearCredentials, isExpired, requireAuth, type StoredCredentials, } from "./storage.js";
6
+ import { type StoredCredentials } from "./storage.js";
7
+ /**
8
+ * HOF - wraps an async operation requiring authentication.
9
+ * Checks credentials before execution, exits with error message if not authed.
10
+ */
11
+ export declare function withAuth<T>(fn: (credentials: StoredCredentials) => Promise<T>): Promise<T>;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE7D,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,WAAW,EACX,KAAK,iBAAiB,GACvB,MAAM,cAAc,CAAC;AAGtB,OAAO,EAAgD,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAEpG;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,CAAC,EAC9B,EAAE,EAAE,CAAC,WAAW,EAAE,iBAAiB,KAAK,OAAO,CAAC,CAAC,CAAC,GACjD,OAAO,CAAC,CAAC,CAAC,CAeZ"}
@@ -0,0 +1,24 @@
1
+ // Re-export everything from submodules
2
+ export { authClient } from "./client.js";
3
+ export { startDeviceFlow } from "./device-flow/index.js";
4
+ export { saveCredentials, loadCredentials, clearCredentials, isExpired, requireAuth, } from "./storage.js";
5
+ import { log } from "@clack/prompts";
6
+ import { loadCredentials, clearCredentials, isExpired } from "./storage.js";
7
+ /**
8
+ * HOF - wraps an async operation requiring authentication.
9
+ * Checks credentials before execution, exits with error message if not authed.
10
+ */
11
+ export async function withAuth(fn) {
12
+ const credentials = loadCredentials();
13
+ if (!credentials) {
14
+ log.error("Not logged in. Run 'auth login' first.");
15
+ process.exit(1);
16
+ }
17
+ if (isExpired(credentials)) {
18
+ log.error("Session expired. Run 'auth login' again.");
19
+ clearCredentials();
20
+ process.exit(1);
21
+ }
22
+ return fn(credentials);
23
+ }
24
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/lib/auth/index.ts"],"names":[],"mappings":"AAAA,uCAAuC;AACvC,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAGzC,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAGzD,OAAO,EACL,eAAe,EACf,eAAe,EACf,gBAAgB,EAChB,SAAS,EACT,WAAW,GAEZ,MAAM,cAAc,CAAC;AAEtB,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AACrC,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,SAAS,EAA0B,MAAM,cAAc,CAAC;AAEpG;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,EAAkD;IAElD,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IAEtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,GAAG,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,IAAI,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QACtD,gBAAgB,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,EAAE,CAAC,WAAW,CAAC,CAAC;AACzB,CAAC"}
@@ -0,0 +1,20 @@
1
+ import Conf from "conf";
2
+ export type StoredCredentials = {
3
+ accessToken: string;
4
+ user: {
5
+ id: string;
6
+ email: string;
7
+ name: string;
8
+ image?: string;
9
+ };
10
+ expiresAt: number;
11
+ };
12
+ export declare const storage: Conf<{
13
+ credentials: StoredCredentials | null;
14
+ }>;
15
+ export declare function saveCredentials(credentials: StoredCredentials): void;
16
+ export declare function loadCredentials(): StoredCredentials | null;
17
+ export declare function clearCredentials(): void;
18
+ export declare function isExpired(credentials: StoredCredentials): boolean;
19
+ export declare function requireAuth(): StoredCredentials;
20
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../../src/lib/auth/storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AAGxB,MAAM,MAAM,iBAAiB,GAAG;IAC9B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE;QACJ,EAAE,EAAE,MAAM,CAAC;QACX,KAAK,EAAE,MAAM,CAAC;QACd,IAAI,EAAE,MAAM,CAAC;QACb,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,SAAS,EAAE,MAAM,CAAC;CACnB,CAAC;AAKF,eAAO,MAAM,OAAO;iBAA2B,iBAAiB,GAAG,IAAI;EAOrE,CAAC;AAEH,wBAAgB,eAAe,CAAC,WAAW,EAAE,iBAAiB,GAAG,IAAI,CAEpE;AAED,wBAAgB,eAAe,IAAI,iBAAiB,GAAG,IAAI,CAgB1D;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,iBAAiB,GAAG,OAAO,CAEjE;AAED,wBAAgB,WAAW,IAAI,iBAAiB,CAY/C"}
@@ -0,0 +1,48 @@
1
+ import Conf from "conf";
2
+ import { log } from "@clack/prompts";
3
+ // Support test configuration via environment variable
4
+ const configPath = process.env.CLI_AUTH_CONFIG_PATH;
5
+ export const storage = new Conf({
6
+ projectName: "complete-web-template",
7
+ configName: "auth",
8
+ cwd: configPath, // Use custom path if set (for tests)
9
+ defaults: {
10
+ credentials: null,
11
+ },
12
+ });
13
+ export function saveCredentials(credentials) {
14
+ storage.set("credentials", credentials);
15
+ }
16
+ export function loadCredentials() {
17
+ const raw = storage.get("credentials");
18
+ // Guard against corrupted storage (e.g., old version, partial write)
19
+ if (!raw ||
20
+ typeof raw !== "object" ||
21
+ !("accessToken" in raw) ||
22
+ !("user" in raw) ||
23
+ !("expiresAt" in raw)) {
24
+ clearCredentials();
25
+ return null;
26
+ }
27
+ return raw;
28
+ }
29
+ export function clearCredentials() {
30
+ storage.delete("credentials");
31
+ }
32
+ export function isExpired(credentials) {
33
+ return Date.now() > credentials.expiresAt;
34
+ }
35
+ export function requireAuth() {
36
+ const credentials = loadCredentials();
37
+ if (!credentials) {
38
+ log.error("Not logged in. Run 'auth login' first.");
39
+ process.exit(1);
40
+ }
41
+ if (isExpired(credentials)) {
42
+ log.error("Session expired. Run 'auth login' again.");
43
+ clearCredentials();
44
+ process.exit(1);
45
+ }
46
+ return credentials;
47
+ }
48
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../../src/lib/auth/storage.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,MAAM,CAAC;AACxB,OAAO,EAAE,GAAG,EAAE,MAAM,gBAAgB,CAAC;AAarC,sDAAsD;AACtD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC;AAEpD,MAAM,CAAC,MAAM,OAAO,GAAG,IAAI,IAAI,CAA4C;IACzE,WAAW,EAAE,uBAAuB;IACpC,UAAU,EAAE,MAAM;IAClB,GAAG,EAAE,UAAU,EAAE,qCAAqC;IACtD,QAAQ,EAAE;QACR,WAAW,EAAE,IAAI;KAClB;CACF,CAAC,CAAC;AAEH,MAAM,UAAU,eAAe,CAAC,WAA8B;IAC5D,OAAO,CAAC,GAAG,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAEvC,qEAAqE;IACrE,IACE,CAAC,GAAG;QACJ,OAAO,GAAG,KAAK,QAAQ;QACvB,CAAC,CAAC,aAAa,IAAI,GAAG,CAAC;QACvB,CAAC,CAAC,MAAM,IAAI,GAAG,CAAC;QAChB,CAAC,CAAC,WAAW,IAAI,GAAG,CAAC,EACrB,CAAC;QACD,gBAAgB,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,GAAwB,CAAC;AAClC,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AAChC,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,WAA8B;IACtD,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,WAAW,CAAC,SAAS,CAAC;AAC5C,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,WAAW,GAAG,eAAe,EAAE,CAAC;IACtC,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,GAAG,CAAC,KAAK,CAAC,wCAAwC,CAAC,CAAC;QACpD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,IAAI,SAAS,CAAC,WAAW,CAAC,EAAE,CAAC;QAC3B,GAAG,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC;QACtD,gBAAgB,EAAE,CAAC;QACnB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IACD,OAAO,WAAW,CAAC;AACrB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@nesalia/cli",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "Nesalia CLI — Manage your account and organizations",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "nesalia": "./dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "typecheck": "tsc --noEmit",
14
+ "lint": "echo 'skip'",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:ui": "vitest --ui"
18
+ },
19
+ "dependencies": {
20
+ "@clack/prompts": "^0.8.0",
21
+ "@trpc/client": "^11.17.0",
22
+ "better-auth": "1.6.6",
23
+ "commander": "^13.0.0",
24
+ "conf": "^12.0.0",
25
+ "open": "^10.0.0"
26
+ },
27
+ "devDependencies": {
28
+ "@complete-web-template/api": "workspace:*",
29
+ "@complete-web-template/test-utils": "workspace:*",
30
+ "@types/node": "^22.0.0",
31
+ "vitest": "^4.1.7"
32
+ },
33
+ "publishConfig": {
34
+ "access": "public"
35
+ }
36
+ }
package/src/CLAUDE.md ADDED
@@ -0,0 +1,83 @@
1
+ # CLAUDE.md — @nesalia/cli
2
+
3
+ ## Overview
4
+
5
+ The CLI is a standalone Node.js binary for managing account authentication. It uses OAuth 2.0 Device Authorization Grant (RFC 8628) via Better Auth's `deviceAuthorizationClient` plugin.
6
+
7
+ ## Commands
8
+
9
+ ```
10
+ nesalia auth login — Start device authorization flow
11
+ nesalia auth status — Check authentication status
12
+ nesalia auth logout — Clear stored credentials
13
+ ```
14
+
15
+ Commands are defined with [Commander](https://www.npmjs.com/package/commander). Each command is a separate file under `src/commands/auth/`.
16
+
17
+ ## Architecture
18
+
19
+ ```
20
+ src/
21
+ ├── index.ts # Commander entry point — routes to commands
22
+ └── commands/
23
+ ├── index.ts # Barrel: re-exports login, status, logout
24
+ └── auth/
25
+ ├── index.ts # Barrel: re-exports from login/status/logout
26
+ ├── login.ts # login command
27
+ ├── status.ts # status command
28
+ └── logout.ts # logout command
29
+
30
+ src/lib/auth/
31
+ ├── client.ts # authClient singleton (createAuthClient + deviceAuthorizationClient)
32
+ ├── device-flow.ts # startDeviceFlow() — handles the OAuth2 device flow
33
+ └── storage.ts # saveCredentials / loadCredentials / clearCredentials (conf package)
34
+ ```
35
+
36
+ ## Auth client
37
+
38
+ The client is a singleton exported from `lib/auth/client.ts`. It is created once at module load time with the base URL from environment variables.
39
+
40
+ ```typescript
41
+ import { authClient } from "./lib/auth/client.js";
42
+ ```
43
+
44
+ No factory function — export directly. The client is used by:
45
+ - `device-flow.ts` → for `device.code()` and `device.token()` polling
46
+ - `status.ts` → for `getSession()` to verify the stored token
47
+
48
+ ## Environment Variables
49
+
50
+ | Variable | Default | Purpose |
51
+ |----------|---------|---------|
52
+ | `CLI_AUTH_API_URL` | `http://localhost:3000` | Auth server base URL |
53
+ | `CLI_AUTH_CLIENT_ID` | `"nesalia"` | OAuth client identifier |
54
+ | `CLI_AUTH_CONFIG_PATH` | OS default | Path for `conf` storage (tests only) |
55
+
56
+ ## Output
57
+
58
+ All output uses `@clack/prompts` (`log.info`, `log.success`, `log.warn`, `log.error`). Never use `console.log` / `console.error`.
59
+
60
+ ## Development
61
+
62
+ ```bash
63
+ pnpm --filter @nesalia/cli build # Compile TypeScript
64
+ pnpm --filter @nesalia/cli test # Run tests (Vitest)
65
+ ```
66
+
67
+ Tests mock `@clack/prompts` at the top level. Each test re-imports `log` dynamically to access the mock:
68
+
69
+ ```typescript
70
+ vi.mock("@clack/prompts", () => ({
71
+ log: { info: vi.fn(), success: vi.fn(), ... },
72
+ }));
73
+
74
+ const { log } = await import("@clack/prompts");
75
+ expect(log.info).toHaveBeenCalledWith("...");
76
+ ```
77
+
78
+ ## Key Conventions
79
+
80
+ - **Commands are `const` arrow functions** — not `async function`.
81
+ - **`startDeviceFlow()` takes no arguments** — it reads `baseURL` from the `authClient` singleton.
82
+ - **Polling has a 30-minute timeout** — throws if the user never approves.
83
+ - **Never hardcode credentials** — all auth state flows through `storage.ts`.
@@ -0,0 +1,3 @@
1
+ export { login } from "./login.js";
2
+ export { status } from "./status.js";
3
+ export { logout } from "./logout.js";
@@ -0,0 +1,20 @@
1
+ import { log } from "@clack/prompts";
2
+ import { saveCredentials, type StoredCredentials, startDeviceFlow } from "../../lib/auth/index.js";
3
+
4
+ export const login = async (): Promise<void> => {
5
+ try {
6
+ const result = await startDeviceFlow();
7
+
8
+ const credentials: StoredCredentials = {
9
+ accessToken: result.accessToken,
10
+ user: result.user,
11
+ expiresAt: Date.now() + 30 * 24 * 60 * 60 * 1000, // 30 days
12
+ };
13
+
14
+ saveCredentials(credentials);
15
+ log.success("Successfully logged in!");
16
+ } catch (error) {
17
+ log.error(error instanceof Error ? error.message : "Unknown error");
18
+ process.exit(1);
19
+ }
20
+ };
@@ -0,0 +1,25 @@
1
+ import { log } from "@clack/prompts";
2
+ import { loadCredentials, clearCredentials, authClient } from "../../lib/auth/index.js";
3
+
4
+ export const logout = async (): Promise<void> => {
5
+ const credentials = loadCredentials();
6
+
7
+ if (!credentials) {
8
+ log.info("Not logged in.");
9
+ return;
10
+ }
11
+
12
+ // Invalidate token on the server (best effort)
13
+ try {
14
+ await authClient.signOut({
15
+ fetchOptions: {
16
+ headers: { Authorization: `Bearer ${credentials.accessToken}` },
17
+ },
18
+ });
19
+ } catch {
20
+ // Non-fatal: the token might already be expired or revoked
21
+ }
22
+
23
+ clearCredentials();
24
+ log.success("Successfully logged out.");
25
+ };
@@ -0,0 +1,37 @@
1
+ import { log } from "@clack/prompts";
2
+ import { loadCredentials, clearCredentials, isExpired, authClient } from "../../lib/auth/index.js";
3
+
4
+ export const status = async (): Promise<void> => {
5
+ const credentials = loadCredentials();
6
+
7
+ if (!credentials) {
8
+ log.info("Not logged in. Run 'auth login' to authenticate.");
9
+ return;
10
+ }
11
+
12
+ if (isExpired(credentials)) {
13
+ log.warn("Session expired. Run 'auth login' to authenticate again.");
14
+ clearCredentials();
15
+ return;
16
+ }
17
+
18
+ // Verify the session with the server
19
+ try {
20
+ const response = await authClient.getSession({
21
+ fetchOptions: {
22
+ headers: { Authorization: `Bearer ${credentials.accessToken}` },
23
+ },
24
+ });
25
+
26
+ if (response?.data?.user) {
27
+ const { name, email } = response.data.user;
28
+ log.success(`Logged in as ${name} (${email})`);
29
+ return;
30
+ }
31
+ } catch (error) {
32
+ log.warn(`Could not verify session with server — using cached credentials.`);
33
+ }
34
+
35
+ // Fall back to local credentials
36
+ log.success(`Logged in as ${credentials.user.name} (${credentials.user.email})`);
37
+ };
@@ -0,0 +1 @@
1
+ export { login, status, logout } from "./auth/index.js";