ghagga 2.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.
@@ -0,0 +1,110 @@
1
+ /**
2
+ * GitHub OAuth Device Flow for CLI authentication.
3
+ *
4
+ * Implements RFC 8628 (Device Authorization Grant) against GitHub's
5
+ * OAuth endpoints. The user visits github.com/login/device, enters
6
+ * a short code, and the CLI receives an access token.
7
+ *
8
+ * @see https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#device-flow
9
+ */
10
+ /** GHAGGA OAuth App Client ID (public — safe to embed in code) */
11
+ export const GITHUB_CLIENT_ID = 'Ov23liyYpSgDqOLUFa5k';
12
+ // ─── Device Flow Steps ──────────────────────────────────────────
13
+ /**
14
+ * Step 1: Request device and user verification codes from GitHub.
15
+ */
16
+ export async function requestDeviceCode() {
17
+ const response = await fetch('https://github.com/login/device/code', {
18
+ method: 'POST',
19
+ headers: {
20
+ Accept: 'application/json',
21
+ 'Content-Type': 'application/json',
22
+ },
23
+ body: JSON.stringify({
24
+ client_id: GITHUB_CLIENT_ID,
25
+ scope: '', // No scopes needed — GitHub Models uses the token as-is
26
+ }),
27
+ });
28
+ if (!response.ok) {
29
+ const text = await response.text();
30
+ throw new Error(`Failed to request device code: ${response.status} ${text}`);
31
+ }
32
+ return response.json();
33
+ }
34
+ /**
35
+ * Step 3: Poll GitHub for the access token until the user authorizes.
36
+ *
37
+ * Respects the polling interval and handles slow_down errors by
38
+ * increasing the interval as required by the spec.
39
+ *
40
+ * @param deviceCode - The device_code from Step 1
41
+ * @param interval - Polling interval in seconds from Step 1
42
+ * @param expiresIn - Expiration time in seconds from Step 1
43
+ * @returns The access token on success
44
+ * @throws On timeout, access_denied, or other fatal errors
45
+ */
46
+ export async function pollForAccessToken(deviceCode, interval, expiresIn) {
47
+ let currentInterval = interval;
48
+ const deadline = Date.now() + expiresIn * 1000;
49
+ while (Date.now() < deadline) {
50
+ // Wait for the polling interval
51
+ await sleep(currentInterval * 1000);
52
+ const response = await fetch('https://github.com/login/oauth/access_token', {
53
+ method: 'POST',
54
+ headers: {
55
+ Accept: 'application/json',
56
+ 'Content-Type': 'application/json',
57
+ },
58
+ body: JSON.stringify({
59
+ client_id: GITHUB_CLIENT_ID,
60
+ device_code: deviceCode,
61
+ grant_type: 'urn:ietf:params:oauth:grant-type:device_code',
62
+ }),
63
+ });
64
+ const data = await response.json();
65
+ // Check if we got a token
66
+ if ('access_token' in data && data.access_token) {
67
+ return data;
68
+ }
69
+ // Handle error responses
70
+ const error = data;
71
+ switch (error.error) {
72
+ case 'authorization_pending':
73
+ // User hasn't entered the code yet — keep polling
74
+ continue;
75
+ case 'slow_down':
76
+ // GitHub wants us to slow down — add 5 seconds
77
+ currentInterval = (error.interval ?? currentInterval) + 5;
78
+ continue;
79
+ case 'expired_token':
80
+ throw new Error('Device code expired. Please try logging in again.');
81
+ case 'access_denied':
82
+ throw new Error('Authorization was denied. The user cancelled the login.');
83
+ default:
84
+ throw new Error(`OAuth error: ${error.error}${error.error_description ? ` — ${error.error_description}` : ''}`);
85
+ }
86
+ }
87
+ throw new Error('Device code expired (timeout). Please try logging in again.');
88
+ }
89
+ // ─── GitHub API ─────────────────────────────────────────────────
90
+ /**
91
+ * Fetch the authenticated user's profile from GitHub API.
92
+ */
93
+ export async function fetchGitHubUser(token) {
94
+ const response = await fetch('https://api.github.com/user', {
95
+ headers: {
96
+ Authorization: `Bearer ${token}`,
97
+ Accept: 'application/vnd.github.v3+json',
98
+ 'User-Agent': 'GHAGGA-CLI/2.0.0',
99
+ },
100
+ });
101
+ if (!response.ok) {
102
+ throw new Error(`Failed to fetch GitHub user: ${response.status}`);
103
+ }
104
+ return response.json();
105
+ }
106
+ // ─── Helpers ────────────────────────────────────────────────────
107
+ function sleep(ms) {
108
+ return new Promise((resolve) => setTimeout(resolve, ms));
109
+ }
110
+ //# sourceMappingURL=oauth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"oauth.js","sourceRoot":"","sources":["../../src/lib/oauth.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "ghagga",
3
+ "version": "2.0.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "AI-powered code review from the command line — free with GitHub Models",
7
+ "author": "JNZader",
8
+ "license": "MIT",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/JNZader/ghagga.git",
12
+ "directory": "apps/cli"
13
+ },
14
+ "homepage": "https://github.com/JNZader/ghagga#readme",
15
+ "bugs": {
16
+ "url": "https://github.com/JNZader/ghagga/issues"
17
+ },
18
+ "keywords": [
19
+ "ghagga",
20
+ "code-review",
21
+ "ai",
22
+ "cli",
23
+ "github",
24
+ "llm",
25
+ "static-analysis",
26
+ "multi-agent",
27
+ "gpt",
28
+ "developer-tools"
29
+ ],
30
+ "engines": {
31
+ "node": ">=20.0.0"
32
+ },
33
+ "bin": {
34
+ "ghagga": "./dist/index.js"
35
+ },
36
+ "files": [
37
+ "dist",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "tsc",
42
+ "dev": "tsx src/index.ts",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "typecheck": "tsc --noEmit",
46
+ "clean": "rm -rf dist",
47
+ "prepublishOnly": "npm run build"
48
+ },
49
+ "dependencies": {
50
+ "ghagga-core": "workspace:^",
51
+ "commander": "^13.0.0",
52
+ "dotenv": "^16.4.0"
53
+ },
54
+ "devDependencies": {
55
+ "tsx": "^4.19.0",
56
+ "typescript": "^5.7.0",
57
+ "vitest": "^3.0.0",
58
+ "@types/node": "^22.0.0"
59
+ }
60
+ }