@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.
- package/.turbo/turbo-build.log +4 -0
- package/dist/commands/auth/index.d.ts +4 -0
- package/dist/commands/auth/index.d.ts.map +1 -0
- package/dist/commands/auth/index.js +4 -0
- package/dist/commands/auth/index.js.map +1 -0
- package/dist/commands/auth/login.d.ts +2 -0
- package/dist/commands/auth/login.d.ts.map +1 -0
- package/dist/commands/auth/login.js +19 -0
- package/dist/commands/auth/login.js.map +1 -0
- package/dist/commands/auth/logout.d.ts +2 -0
- package/dist/commands/auth/logout.d.ts.map +1 -0
- package/dist/commands/auth/logout.js +23 -0
- package/dist/commands/auth/logout.js.map +1 -0
- package/dist/commands/auth/status.d.ts +2 -0
- package/dist/commands/auth/status.d.ts.map +1 -0
- package/dist/commands/auth/status.js +33 -0
- package/dist/commands/auth/status.js.map +1 -0
- package/dist/commands/index.d.ts +2 -0
- package/dist/commands/index.d.ts.map +1 -0
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/post/by-id.d.ts +6 -0
- package/dist/commands/post/by-id.d.ts.map +1 -0
- package/dist/commands/post/by-id.js +32 -0
- package/dist/commands/post/by-id.js.map +1 -0
- package/dist/commands/post/create.d.ts +7 -0
- package/dist/commands/post/create.d.ts.map +1 -0
- package/dist/commands/post/create.js +35 -0
- package/dist/commands/post/create.js.map +1 -0
- package/dist/commands/post/index.d.ts +4 -0
- package/dist/commands/post/index.d.ts.map +1 -0
- package/dist/commands/post/index.js +4 -0
- package/dist/commands/post/index.js.map +1 -0
- package/dist/commands/post/list.d.ts +2 -0
- package/dist/commands/post/list.d.ts.map +1 -0
- package/dist/commands/post/list.js +35 -0
- package/dist/commands/post/list.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api/client.d.ts +17 -0
- package/dist/lib/api/client.d.ts.map +1 -0
- package/dist/lib/api/client.js +18 -0
- package/dist/lib/api/client.js.map +1 -0
- package/dist/lib/auth/client.d.ts +2748 -0
- package/dist/lib/auth/client.d.ts.map +1 -0
- package/dist/lib/auth/client.js +11 -0
- package/dist/lib/auth/client.js.map +1 -0
- package/dist/lib/auth/device-flow/config.d.ts +5 -0
- package/dist/lib/auth/device-flow/config.d.ts.map +1 -0
- package/dist/lib/auth/device-flow/config.js +5 -0
- package/dist/lib/auth/device-flow/config.js.map +1 -0
- package/dist/lib/auth/device-flow/device-code.d.ts +10 -0
- package/dist/lib/auth/device-flow/device-code.d.ts.map +1 -0
- package/dist/lib/auth/device-flow/device-code.js +27 -0
- package/dist/lib/auth/device-flow/device-code.js.map +1 -0
- package/dist/lib/auth/device-flow/errors.d.ts +8 -0
- package/dist/lib/auth/device-flow/errors.d.ts.map +1 -0
- package/dist/lib/auth/device-flow/errors.js +26 -0
- package/dist/lib/auth/device-flow/errors.js.map +1 -0
- package/dist/lib/auth/device-flow/index.d.ts +3 -0
- package/dist/lib/auth/device-flow/index.d.ts.map +1 -0
- package/dist/lib/auth/device-flow/index.js +13 -0
- package/dist/lib/auth/device-flow/index.js.map +1 -0
- package/dist/lib/auth/device-flow/polling.d.ts +4 -0
- package/dist/lib/auth/device-flow/polling.d.ts.map +1 -0
- package/dist/lib/auth/device-flow/polling.js +75 -0
- package/dist/lib/auth/device-flow/polling.js.map +1 -0
- package/dist/lib/auth/device-flow/types.d.ts +10 -0
- package/dist/lib/auth/device-flow/types.d.ts.map +1 -0
- package/dist/lib/auth/device-flow/types.js +2 -0
- package/dist/lib/auth/device-flow/types.js.map +1 -0
- package/dist/lib/auth/index.d.ts +12 -0
- package/dist/lib/auth/index.d.ts.map +1 -0
- package/dist/lib/auth/index.js +24 -0
- package/dist/lib/auth/index.js.map +1 -0
- package/dist/lib/auth/storage.d.ts +20 -0
- package/dist/lib/auth/storage.d.ts.map +1 -0
- package/dist/lib/auth/storage.js +48 -0
- package/dist/lib/auth/storage.js.map +1 -0
- package/package.json +36 -0
- package/src/CLAUDE.md +83 -0
- package/src/commands/auth/index.ts +3 -0
- package/src/commands/auth/login.ts +20 -0
- package/src/commands/auth/logout.ts +25 -0
- package/src/commands/auth/status.ts +37 -0
- package/src/commands/index.ts +1 -0
- package/src/index.ts +32 -0
- package/src/lib/api/client.ts +20 -0
- package/src/lib/auth/client.ts +14 -0
- package/src/lib/auth/device-flow/config.ts +4 -0
- package/src/lib/auth/device-flow/device-code.ts +39 -0
- package/src/lib/auth/device-flow/errors.ts +29 -0
- package/src/lib/auth/device-flow/index.ts +19 -0
- package/src/lib/auth/device-flow/polling.ts +91 -0
- package/src/lib/auth/device-flow/types.ts +9 -0
- package/src/lib/auth/index.ts +41 -0
- package/src/lib/auth/storage.ts +69 -0
- package/tests/auth.test.ts +123 -0
- package/tests/setup.ts +43 -0
- package/tsconfig.json +19 -0
- 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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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,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";
|