ghagga 2.0.1 → 2.1.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/README.md +52 -14
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +17 -14
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/login.test.d.ts +9 -0
- package/dist/commands/login.test.d.ts.map +1 -0
- package/dist/commands/login.test.js +138 -0
- package/dist/commands/login.test.js.map +1 -0
- package/dist/commands/logout.d.ts.map +1 -1
- package/dist/commands/logout.js +4 -3
- package/dist/commands/logout.js.map +1 -1
- package/dist/commands/logout.test.d.ts +8 -0
- package/dist/commands/logout.test.d.ts.map +1 -0
- package/dist/commands/logout.test.js +61 -0
- package/dist/commands/logout.test.js.map +1 -0
- package/dist/commands/memory/clear.d.ts +11 -0
- package/dist/commands/memory/clear.d.ts.map +1 -0
- package/dist/commands/memory/clear.js +45 -0
- package/dist/commands/memory/clear.js.map +1 -0
- package/dist/commands/memory/clear.test.d.ts +11 -0
- package/dist/commands/memory/clear.test.d.ts.map +1 -0
- package/dist/commands/memory/clear.test.js +178 -0
- package/dist/commands/memory/clear.test.js.map +1 -0
- package/dist/commands/memory/delete.d.ts +11 -0
- package/dist/commands/memory/delete.d.ts.map +1 -0
- package/dist/commands/memory/delete.js +38 -0
- package/dist/commands/memory/delete.js.map +1 -0
- package/dist/commands/memory/delete.test.d.ts +11 -0
- package/dist/commands/memory/delete.test.d.ts.map +1 -0
- package/dist/commands/memory/delete.test.js +176 -0
- package/dist/commands/memory/delete.test.js.map +1 -0
- package/dist/commands/memory/index.d.ts +10 -0
- package/dist/commands/memory/index.d.ts.map +1 -0
- package/dist/commands/memory/index.js +23 -0
- package/dist/commands/memory/index.js.map +1 -0
- package/dist/commands/memory/list.d.ts +11 -0
- package/dist/commands/memory/list.d.ts.map +1 -0
- package/dist/commands/memory/list.js +47 -0
- package/dist/commands/memory/list.js.map +1 -0
- package/dist/commands/memory/list.test.d.ts +10 -0
- package/dist/commands/memory/list.test.d.ts.map +1 -0
- package/dist/commands/memory/list.test.js +135 -0
- package/dist/commands/memory/list.test.js.map +1 -0
- package/dist/commands/memory/search.d.ts +12 -0
- package/dist/commands/memory/search.d.ts.map +1 -0
- package/dist/commands/memory/search.js +44 -0
- package/dist/commands/memory/search.js.map +1 -0
- package/dist/commands/memory/search.test.d.ts +11 -0
- package/dist/commands/memory/search.test.d.ts.map +1 -0
- package/dist/commands/memory/search.test.js +122 -0
- package/dist/commands/memory/search.test.js.map +1 -0
- package/dist/commands/memory/show.d.ts +10 -0
- package/dist/commands/memory/show.d.ts.map +1 -0
- package/dist/commands/memory/show.js +51 -0
- package/dist/commands/memory/show.js.map +1 -0
- package/dist/commands/memory/show.test.d.ts +11 -0
- package/dist/commands/memory/show.test.d.ts.map +1 -0
- package/dist/commands/memory/show.test.js +156 -0
- package/dist/commands/memory/show.test.js.map +1 -0
- package/dist/commands/memory/stats.d.ts +11 -0
- package/dist/commands/memory/stats.d.ts.map +1 -0
- package/dist/commands/memory/stats.js +63 -0
- package/dist/commands/memory/stats.js.map +1 -0
- package/dist/commands/memory/stats.test.d.ts +10 -0
- package/dist/commands/memory/stats.test.d.ts.map +1 -0
- package/dist/commands/memory/stats.test.js +122 -0
- package/dist/commands/memory/stats.test.js.map +1 -0
- package/dist/commands/memory/utils.d.ts +44 -0
- package/dist/commands/memory/utils.d.ts.map +1 -0
- package/dist/commands/memory/utils.js +90 -0
- package/dist/commands/memory/utils.js.map +1 -0
- package/dist/commands/memory/utils.test.d.ts +10 -0
- package/dist/commands/memory/utils.test.d.ts.map +1 -0
- package/dist/commands/memory/utils.test.js +93 -0
- package/dist/commands/memory/utils.test.js.map +1 -0
- package/dist/commands/review.d.ts +1 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +42 -102
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/review.test.js +267 -1
- package/dist/commands/review.test.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +12 -11
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/status.test.d.ts +8 -0
- package/dist/commands/status.test.d.ts.map +1 -0
- package/dist/commands/status.test.js +106 -0
- package/dist/commands/status.test.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +28 -10
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +1 -0
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +1 -1
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/config.test.d.ts +9 -0
- package/dist/lib/config.test.d.ts.map +1 -0
- package/dist/lib/config.test.js +181 -0
- package/dist/lib/config.test.js.map +1 -0
- package/dist/lib/git.d.ts +23 -0
- package/dist/lib/git.d.ts.map +1 -0
- package/dist/lib/git.js +53 -0
- package/dist/lib/git.js.map +1 -0
- package/dist/lib/git.test.d.ts +2 -0
- package/dist/lib/git.test.d.ts.map +1 -0
- package/dist/lib/git.test.js +65 -0
- package/dist/lib/git.test.js.map +1 -0
- package/dist/lib/oauth.test.d.ts +8 -0
- package/dist/lib/oauth.test.d.ts.map +1 -0
- package/dist/lib/oauth.test.js +212 -0
- package/dist/lib/oauth.test.js.map +1 -0
- package/dist/ui/__tests__/format.test.d.ts +7 -0
- package/dist/ui/__tests__/format.test.d.ts.map +1 -0
- package/dist/ui/__tests__/format.test.js +220 -0
- package/dist/ui/__tests__/format.test.js.map +1 -0
- package/dist/ui/__tests__/theme.test.d.ts +7 -0
- package/dist/ui/__tests__/theme.test.d.ts.map +1 -0
- package/dist/ui/__tests__/theme.test.js +79 -0
- package/dist/ui/__tests__/theme.test.js.map +1 -0
- package/dist/ui/__tests__/tui.test.d.ts +9 -0
- package/dist/ui/__tests__/tui.test.d.ts.map +1 -0
- package/dist/ui/__tests__/tui.test.js +222 -0
- package/dist/ui/__tests__/tui.test.js.map +1 -0
- package/dist/ui/format.d.ts +38 -0
- package/dist/ui/format.d.ts.map +1 -0
- package/dist/ui/format.js +136 -0
- package/dist/ui/format.js.map +1 -0
- package/dist/ui/theme.d.ts +26 -0
- package/dist/ui/theme.d.ts.map +1 -0
- package/dist/ui/theme.js +63 -0
- package/dist/ui/theme.js.map +1 -0
- package/dist/ui/tui.d.ts +44 -0
- package/dist/ui/tui.d.ts.map +1 -0
- package/dist/ui/tui.js +121 -0
- package/dist/ui/tui.js.map +1 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
AI-powered code review from the command line. **Free** with GitHub Models.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
|
-
npx
|
|
7
|
-
npx
|
|
6
|
+
npx ghagga login
|
|
7
|
+
npx ghagga review
|
|
8
8
|
```
|
|
9
9
|
|
|
10
10
|
That's it. Zero config, zero cost.
|
|
@@ -24,7 +24,7 @@ GHAGGA is a multi-agent AI code reviewer that analyzes your code changes using L
|
|
|
24
24
|
### 1. Login (one time)
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
|
-
npx
|
|
27
|
+
npx ghagga login
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
This authenticates with GitHub using Device Flow. Your GitHub token gives you **free access** to AI models via [GitHub Models](https://github.com/marketplace/models).
|
|
@@ -33,23 +33,31 @@ This authenticates with GitHub using Device Flow. Your GitHub token gives you **
|
|
|
33
33
|
|
|
34
34
|
```bash
|
|
35
35
|
# Review staged/uncommitted changes (simple mode)
|
|
36
|
-
npx
|
|
36
|
+
npx ghagga review
|
|
37
37
|
|
|
38
38
|
# Thorough review with 5 specialist agents
|
|
39
|
-
npx
|
|
39
|
+
npx ghagga review --mode workflow
|
|
40
40
|
|
|
41
41
|
# Balanced review with for/against/neutral voting
|
|
42
|
-
npx
|
|
42
|
+
npx ghagga review --mode consensus
|
|
43
43
|
|
|
44
44
|
# See detailed progress of each step
|
|
45
|
-
npx
|
|
45
|
+
npx ghagga review --mode workflow --verbose
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
### 3. Check your status
|
|
49
49
|
|
|
50
50
|
```bash
|
|
51
|
-
npx
|
|
52
|
-
npx
|
|
51
|
+
npx ghagga status # Show auth & config
|
|
52
|
+
npx ghagga logout # Clear credentials
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 4. Manage review memory
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
npx ghagga memory list # List stored observations
|
|
59
|
+
npx ghagga memory search "error handling" # Search by content
|
|
60
|
+
npx ghagga memory stats # Database statistics
|
|
53
61
|
```
|
|
54
62
|
|
|
55
63
|
## Global Installation
|
|
@@ -57,21 +65,31 @@ npx @ghagga/cli logout # Clear credentials
|
|
|
57
65
|
If you use it frequently:
|
|
58
66
|
|
|
59
67
|
```bash
|
|
60
|
-
npm install -g
|
|
68
|
+
npm install -g ghagga
|
|
61
69
|
|
|
62
70
|
ghagga login
|
|
63
71
|
ghagga review
|
|
64
72
|
ghagga review -m workflow -v
|
|
65
73
|
```
|
|
66
74
|
|
|
67
|
-
## Options
|
|
75
|
+
## Global Options
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
--plain Disable styled terminal output (auto-enabled in non-TTY/CI)
|
|
79
|
+
--version Show version number
|
|
80
|
+
--help Show help
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
> The CLI uses [`@clack/prompts`](https://github.com/natemoo-re/clack) for styled terminal output (spinners, colored headers). In non-TTY or CI environments, output automatically falls back to plain `console.log`.
|
|
84
|
+
|
|
85
|
+
## Review Options
|
|
68
86
|
|
|
69
87
|
```
|
|
70
88
|
Usage: ghagga review [options] [path]
|
|
71
89
|
|
|
72
90
|
Options:
|
|
73
91
|
-m, --mode <mode> Review mode: simple, workflow, consensus (default: "simple")
|
|
74
|
-
-p, --provider <provider> LLM provider: github, openai,
|
|
92
|
+
-p, --provider <provider> LLM provider: github, anthropic, openai, google, ollama, qwen
|
|
75
93
|
--model <model> LLM model identifier
|
|
76
94
|
--api-key <key> LLM provider API key
|
|
77
95
|
-f, --format <format> Output format: markdown, json (default: "markdown")
|
|
@@ -79,9 +97,23 @@ Options:
|
|
|
79
97
|
--no-semgrep Disable Semgrep static analysis
|
|
80
98
|
--no-trivy Disable Trivy vulnerability scanning
|
|
81
99
|
--no-cpd Disable CPD duplicate detection
|
|
100
|
+
--no-memory Disable review memory (skip search and persist)
|
|
82
101
|
-c, --config <path> Path to .ghagga.json config file
|
|
83
102
|
```
|
|
84
103
|
|
|
104
|
+
## Memory Subcommands
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
ghagga memory list [--repo <owner/repo>] [--type <type>] [--limit <n>]
|
|
108
|
+
ghagga memory search [--repo <owner/repo>] [--limit <n>] <query>
|
|
109
|
+
ghagga memory show <id>
|
|
110
|
+
ghagga memory delete [--force] <id>
|
|
111
|
+
ghagga memory stats
|
|
112
|
+
ghagga memory clear [--repo <owner/repo>] [--force]
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Memory is stored locally at `~/.config/ghagga/memory.db` (SQLite + FTS5). Observations are automatically extracted from reviews and used to provide context in future reviews.
|
|
116
|
+
|
|
85
117
|
## BYOK (Bring Your Own Key)
|
|
86
118
|
|
|
87
119
|
Use any supported LLM provider:
|
|
@@ -102,6 +134,9 @@ ghagga review --provider anthropic --api-key sk-ant-...
|
|
|
102
134
|
|
|
103
135
|
# Google
|
|
104
136
|
ghagga review --provider google --api-key AIza...
|
|
137
|
+
|
|
138
|
+
# Qwen (Alibaba Cloud)
|
|
139
|
+
ghagga review --provider qwen --api-key sk-...
|
|
105
140
|
```
|
|
106
141
|
|
|
107
142
|
## Local Models with Ollama
|
|
@@ -151,8 +186,10 @@ Create a `.ghagga.json` in your project root:
|
|
|
151
186
|
1. Gets your `git diff` (staged or uncommitted changes)
|
|
152
187
|
2. Parses the diff and detects tech stacks
|
|
153
188
|
3. Runs static analysis (Semgrep, Trivy, CPD) if available
|
|
154
|
-
4.
|
|
155
|
-
5.
|
|
189
|
+
4. Searches local memory for relevant past observations (SQLite + FTS5 at `~/.config/ghagga/memory.db`)
|
|
190
|
+
5. Sends the diff + static findings + memory context to the AI review agent
|
|
191
|
+
6. Returns findings with severity, file, line, and suggestions
|
|
192
|
+
7. Persists new observations (decisions, patterns, bug fixes) to local memory for future reviews
|
|
156
193
|
|
|
157
194
|
## Requirements
|
|
158
195
|
|
|
@@ -167,5 +204,6 @@ MIT
|
|
|
167
204
|
## Links
|
|
168
205
|
|
|
169
206
|
- [GitHub Repository](https://github.com/JNZader/ghagga)
|
|
207
|
+
- [Full CLI Guide](https://jnzader.github.io/ghagga/docs/#/cli) — Complete setup guide with troubleshooting
|
|
170
208
|
- [Documentation](https://jnzader.github.io/ghagga/docs/)
|
|
171
209
|
- [Landing Page](https://jnzader.github.io/ghagga/)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAiCH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CA2DlD"}
|
package/dist/commands/login.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { loadConfig, saveConfig } from '../lib/config.js';
|
|
8
8
|
import { requestDeviceCode, pollForAccessToken, fetchGitHubUser, } from '../lib/oauth.js';
|
|
9
|
+
import * as tui from '../ui/tui.js';
|
|
9
10
|
/**
|
|
10
11
|
* Try to open a URL in the default browser.
|
|
11
12
|
* Fails silently if no browser is available (e.g., headless server).
|
|
@@ -30,26 +31,28 @@ export async function loginCommand() {
|
|
|
30
31
|
const config = loadConfig();
|
|
31
32
|
// Check if already logged in
|
|
32
33
|
if (config.githubToken && config.githubLogin) {
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
tui.log.info(`ℹ️ Already logged in as ${config.githubLogin}.`);
|
|
35
|
+
tui.log.info(' Run "ghagga logout" first to switch accounts.\n');
|
|
35
36
|
return;
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
tui.intro('🔐 Authenticating with GitHub');
|
|
38
39
|
try {
|
|
39
40
|
// Step 1: Request device code
|
|
40
41
|
const deviceCode = await requestDeviceCode();
|
|
41
42
|
// Step 2: Show user code and open browser
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
tui.log.step(' ➔ Open this URL in your browser:\n');
|
|
44
|
+
tui.log.message(` https://github.com/login/device\n`);
|
|
45
|
+
tui.log.step(` ➔ Enter this code:\n`);
|
|
46
|
+
tui.log.message(` ${deviceCode.user_code}\n`);
|
|
46
47
|
const opened = await tryOpenBrowser(deviceCode.verification_uri);
|
|
47
48
|
if (opened) {
|
|
48
|
-
|
|
49
|
+
tui.log.info(' (Browser opened automatically)\n');
|
|
49
50
|
}
|
|
50
|
-
|
|
51
|
-
|
|
51
|
+
// Step 3: Poll for access token (with spinner)
|
|
52
|
+
const s = tui.spinner();
|
|
53
|
+
s.start('Waiting for authorization...');
|
|
52
54
|
const tokenResponse = await pollForAccessToken(deviceCode.device_code, deviceCode.interval, deviceCode.expires_in);
|
|
55
|
+
s.stop('Authorization received');
|
|
53
56
|
// Step 4: Fetch user profile
|
|
54
57
|
const user = await fetchGitHubUser(tokenResponse.access_token);
|
|
55
58
|
// Step 5: Save to config
|
|
@@ -60,13 +63,13 @@ export async function loginCommand() {
|
|
|
60
63
|
defaultProvider: 'github',
|
|
61
64
|
defaultModel: 'gpt-4o-mini',
|
|
62
65
|
});
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
+
tui.log.success(`\n✅ Logged in as ${user.login}`);
|
|
67
|
+
tui.log.info(' Provider: github (gpt-4o-mini) — free tier');
|
|
68
|
+
tui.outro('Run "ghagga review ." to review your code!');
|
|
66
69
|
}
|
|
67
70
|
catch (error) {
|
|
68
71
|
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
-
|
|
72
|
+
tui.log.error(`\n❌ Login failed: ${message}\n`);
|
|
70
73
|
process.exit(1);
|
|
71
74
|
}
|
|
72
75
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAClB,eAAe,GAChB,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.test.d.ts","sourceRoot":"","sources":["../../src/commands/login.test.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Login command tests.
|
|
3
|
+
*
|
|
4
|
+
* Tests the GitHub Device Flow login orchestration:
|
|
5
|
+
* already-logged-in shortcut, happy path, spinner lifecycle,
|
|
6
|
+
* browser auto-open, and error handling.
|
|
7
|
+
*/
|
|
8
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
9
|
+
// ─── Mocks ─────────────────────────────────────────────────────
|
|
10
|
+
vi.mock('../lib/config.js', () => ({
|
|
11
|
+
loadConfig: vi.fn(),
|
|
12
|
+
saveConfig: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('../lib/oauth.js', () => ({
|
|
15
|
+
requestDeviceCode: vi.fn(),
|
|
16
|
+
pollForAccessToken: vi.fn(),
|
|
17
|
+
fetchGitHubUser: vi.fn(),
|
|
18
|
+
}));
|
|
19
|
+
const mockSpinner = { start: vi.fn(), stop: vi.fn(), message: vi.fn() };
|
|
20
|
+
vi.mock('../ui/tui.js', () => ({
|
|
21
|
+
intro: vi.fn(),
|
|
22
|
+
outro: vi.fn(),
|
|
23
|
+
log: {
|
|
24
|
+
info: vi.fn(),
|
|
25
|
+
step: vi.fn(),
|
|
26
|
+
message: vi.fn(),
|
|
27
|
+
success: vi.fn(),
|
|
28
|
+
error: vi.fn(),
|
|
29
|
+
warn: vi.fn(),
|
|
30
|
+
},
|
|
31
|
+
spinner: vi.fn(() => mockSpinner),
|
|
32
|
+
}));
|
|
33
|
+
vi.mock('node:child_process', () => ({
|
|
34
|
+
exec: vi.fn(),
|
|
35
|
+
}));
|
|
36
|
+
vi.mock('node:os', () => ({
|
|
37
|
+
platform: vi.fn().mockReturnValue('linux'),
|
|
38
|
+
}));
|
|
39
|
+
import { loadConfig, saveConfig } from '../lib/config.js';
|
|
40
|
+
import { requestDeviceCode, pollForAccessToken, fetchGitHubUser, } from '../lib/oauth.js';
|
|
41
|
+
import * as tui from '../ui/tui.js';
|
|
42
|
+
import { loginCommand } from './login.js';
|
|
43
|
+
const mockLoadConfig = vi.mocked(loadConfig);
|
|
44
|
+
const mockSaveConfig = vi.mocked(saveConfig);
|
|
45
|
+
const mockRequestDeviceCode = vi.mocked(requestDeviceCode);
|
|
46
|
+
const mockPollForAccessToken = vi.mocked(pollForAccessToken);
|
|
47
|
+
const mockFetchGitHubUser = vi.mocked(fetchGitHubUser);
|
|
48
|
+
// ─── Setup ─────────────────────────────────────────────────────
|
|
49
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
50
|
+
let mockExit;
|
|
51
|
+
beforeEach(() => {
|
|
52
|
+
vi.clearAllMocks();
|
|
53
|
+
mockExit = vi.spyOn(process, 'exit').mockImplementation((() => { }));
|
|
54
|
+
});
|
|
55
|
+
afterEach(() => {
|
|
56
|
+
mockExit.mockRestore();
|
|
57
|
+
});
|
|
58
|
+
// ─── Helpers ───────────────────────────────────────────────────
|
|
59
|
+
function setupHappyPath() {
|
|
60
|
+
mockLoadConfig.mockReturnValue({});
|
|
61
|
+
mockRequestDeviceCode.mockResolvedValue({
|
|
62
|
+
device_code: 'dc_123',
|
|
63
|
+
user_code: 'ABCD-1234',
|
|
64
|
+
verification_uri: 'https://github.com/login/device',
|
|
65
|
+
expires_in: 900,
|
|
66
|
+
interval: 5,
|
|
67
|
+
});
|
|
68
|
+
mockPollForAccessToken.mockResolvedValue({
|
|
69
|
+
access_token: 'gho_newtoken',
|
|
70
|
+
token_type: 'bearer',
|
|
71
|
+
scope: '',
|
|
72
|
+
});
|
|
73
|
+
mockFetchGitHubUser.mockResolvedValue({
|
|
74
|
+
login: 'newuser',
|
|
75
|
+
id: 42,
|
|
76
|
+
avatar_url: 'https://github.com/newuser.png',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
// ─── Tests ─────────────────────────────────────────────────────
|
|
80
|
+
describe('loginCommand', () => {
|
|
81
|
+
it('shows "already logged in" when token and login exist', async () => {
|
|
82
|
+
mockLoadConfig.mockReturnValue({
|
|
83
|
+
githubToken: 'gho_existing',
|
|
84
|
+
githubLogin: 'existinguser',
|
|
85
|
+
});
|
|
86
|
+
await loginCommand();
|
|
87
|
+
expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Already logged in as existinguser'));
|
|
88
|
+
expect(mockRequestDeviceCode).not.toHaveBeenCalled();
|
|
89
|
+
expect(mockSaveConfig).not.toHaveBeenCalled();
|
|
90
|
+
});
|
|
91
|
+
it('completes full device flow and saves config on success', async () => {
|
|
92
|
+
setupHappyPath();
|
|
93
|
+
await loginCommand();
|
|
94
|
+
// Verify device code was requested
|
|
95
|
+
expect(mockRequestDeviceCode).toHaveBeenCalledOnce();
|
|
96
|
+
// Verify user code was displayed
|
|
97
|
+
expect(tui.log.message).toHaveBeenCalledWith(expect.stringContaining('ABCD-1234'));
|
|
98
|
+
// Verify poll was called with device code params
|
|
99
|
+
expect(mockPollForAccessToken).toHaveBeenCalledWith('dc_123', 5, 900);
|
|
100
|
+
// Verify user was fetched with the new token
|
|
101
|
+
expect(mockFetchGitHubUser).toHaveBeenCalledWith('gho_newtoken');
|
|
102
|
+
// Verify config was saved with correct data
|
|
103
|
+
expect(mockSaveConfig).toHaveBeenCalledWith({
|
|
104
|
+
githubToken: 'gho_newtoken',
|
|
105
|
+
githubLogin: 'newuser',
|
|
106
|
+
defaultProvider: 'github',
|
|
107
|
+
defaultModel: 'gpt-4o-mini',
|
|
108
|
+
});
|
|
109
|
+
// Verify success message
|
|
110
|
+
expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('newuser'));
|
|
111
|
+
});
|
|
112
|
+
it('manages spinner lifecycle: start → stop', async () => {
|
|
113
|
+
setupHappyPath();
|
|
114
|
+
await loginCommand();
|
|
115
|
+
expect(tui.spinner).toHaveBeenCalledOnce();
|
|
116
|
+
expect(mockSpinner.start).toHaveBeenCalledWith('Waiting for authorization...');
|
|
117
|
+
expect(mockSpinner.stop).toHaveBeenCalledWith('Authorization received');
|
|
118
|
+
// start must be called before stop
|
|
119
|
+
const startOrder = mockSpinner.start.mock.invocationCallOrder[0];
|
|
120
|
+
const stopOrder = mockSpinner.stop.mock.invocationCallOrder[0];
|
|
121
|
+
expect(startOrder).toBeLessThan(stopOrder);
|
|
122
|
+
});
|
|
123
|
+
it('shows browser-opened message when tryOpenBrowser succeeds', async () => {
|
|
124
|
+
setupHappyPath();
|
|
125
|
+
await loginCommand();
|
|
126
|
+
// tryOpenBrowser uses dynamic import of node:child_process
|
|
127
|
+
// When exec doesn't throw, the browser is considered "opened"
|
|
128
|
+
expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Browser opened automatically'));
|
|
129
|
+
});
|
|
130
|
+
it('calls tui.log.error and process.exit(1) on failure', async () => {
|
|
131
|
+
mockLoadConfig.mockReturnValue({});
|
|
132
|
+
mockRequestDeviceCode.mockRejectedValue(new Error('Network error'));
|
|
133
|
+
await loginCommand();
|
|
134
|
+
expect(tui.log.error).toHaveBeenCalledWith(expect.stringContaining('Login failed: Network error'));
|
|
135
|
+
expect(mockExit).toHaveBeenCalledWith(1);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
//# sourceMappingURL=login.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.test.js","sourceRoot":"","sources":["../../src/commands/login.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;
|
|
1
|
+
{"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
|
package/dist/commands/logout.js
CHANGED
|
@@ -2,15 +2,16 @@
|
|
|
2
2
|
* Logout command — clears stored GitHub credentials.
|
|
3
3
|
*/
|
|
4
4
|
import { clearConfig, isLoggedIn, loadConfig } from '../lib/config.js';
|
|
5
|
+
import * as tui from '../ui/tui.js';
|
|
5
6
|
export function logoutCommand() {
|
|
6
7
|
if (!isLoggedIn()) {
|
|
7
|
-
|
|
8
|
+
tui.log.info('ℹ️ Not currently logged in.\n');
|
|
8
9
|
return;
|
|
9
10
|
}
|
|
10
11
|
const config = loadConfig();
|
|
11
12
|
const login = config.githubLogin ?? 'unknown';
|
|
12
13
|
clearConfig();
|
|
13
|
-
|
|
14
|
-
|
|
14
|
+
tui.log.success(`✅ Logged out from ${login}.`);
|
|
15
|
+
tui.log.info(' Stored credentials have been removed.\n');
|
|
15
16
|
}
|
|
16
17
|
//# sourceMappingURL=logout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"logout.js","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,GAAG,MAAM,cAAc,CAAC;AAEpC,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,KAAK,GAAG,MAAM,CAAC,WAAW,IAAI,SAAS,CAAC;IAE9C,WAAW,EAAE,CAAC;IAEd,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,qBAAqB,KAAK,GAAG,CAAC,CAAC;IAC/C,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC;AAC7D,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.test.d.ts","sourceRoot":"","sources":["../../src/commands/logout.test.ts"],"names":[],"mappings":"AAAA;;;;;GAKG"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logout command tests.
|
|
3
|
+
*
|
|
4
|
+
* Tests the logout flow: not-logged-in shortcut, normal logout,
|
|
5
|
+
* and fallback to "unknown" when githubLogin is missing.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
8
|
+
// ─── Mocks ─────────────────────────────────────────────────────
|
|
9
|
+
vi.mock('../lib/config.js', () => ({
|
|
10
|
+
clearConfig: vi.fn(),
|
|
11
|
+
isLoggedIn: vi.fn(),
|
|
12
|
+
loadConfig: vi.fn(),
|
|
13
|
+
}));
|
|
14
|
+
vi.mock('../ui/tui.js', () => ({
|
|
15
|
+
log: {
|
|
16
|
+
info: vi.fn(),
|
|
17
|
+
success: vi.fn(),
|
|
18
|
+
error: vi.fn(),
|
|
19
|
+
warn: vi.fn(),
|
|
20
|
+
step: vi.fn(),
|
|
21
|
+
message: vi.fn(),
|
|
22
|
+
},
|
|
23
|
+
}));
|
|
24
|
+
import { clearConfig, isLoggedIn, loadConfig } from '../lib/config.js';
|
|
25
|
+
import * as tui from '../ui/tui.js';
|
|
26
|
+
import { logoutCommand } from './logout.js';
|
|
27
|
+
const mockIsLoggedIn = vi.mocked(isLoggedIn);
|
|
28
|
+
const mockLoadConfig = vi.mocked(loadConfig);
|
|
29
|
+
const mockClearConfig = vi.mocked(clearConfig);
|
|
30
|
+
// ─── Setup ─────────────────────────────────────────────────────
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
vi.clearAllMocks();
|
|
33
|
+
});
|
|
34
|
+
// ─── Tests ─────────────────────────────────────────────────────
|
|
35
|
+
describe('logoutCommand', () => {
|
|
36
|
+
it('shows "not logged in" when user has no stored credentials', () => {
|
|
37
|
+
mockIsLoggedIn.mockReturnValue(false);
|
|
38
|
+
logoutCommand();
|
|
39
|
+
expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('Not currently logged in'));
|
|
40
|
+
expect(mockClearConfig).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
it('clears config and shows success with login name', () => {
|
|
43
|
+
mockIsLoggedIn.mockReturnValue(true);
|
|
44
|
+
mockLoadConfig.mockReturnValue({
|
|
45
|
+
githubToken: 'gho_tok',
|
|
46
|
+
githubLogin: 'testuser',
|
|
47
|
+
});
|
|
48
|
+
logoutCommand();
|
|
49
|
+
expect(mockClearConfig).toHaveBeenCalledOnce();
|
|
50
|
+
expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('testuser'));
|
|
51
|
+
expect(tui.log.info).toHaveBeenCalledWith(expect.stringContaining('credentials have been removed'));
|
|
52
|
+
});
|
|
53
|
+
it('falls back to "unknown" when githubLogin is not set', () => {
|
|
54
|
+
mockIsLoggedIn.mockReturnValue(true);
|
|
55
|
+
mockLoadConfig.mockReturnValue({ githubToken: 'gho_tok' });
|
|
56
|
+
logoutCommand();
|
|
57
|
+
expect(mockClearConfig).toHaveBeenCalledOnce();
|
|
58
|
+
expect(tui.log.success).toHaveBeenCalledWith(expect.stringContaining('unknown'));
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
//# sourceMappingURL=logout.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logout.test.js","sourceRoot":"","sources":["../../src/commands/logout.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ghagga memory clear` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Deletes all observations (or scoped to a repository) with
|
|
5
|
+
* confirmation prompt. Supports --force for non-interactive use.
|
|
6
|
+
*
|
|
7
|
+
* @see R12, S28–S35, S49
|
|
8
|
+
*/
|
|
9
|
+
import { Command } from 'commander';
|
|
10
|
+
export declare function registerClearCommand(parent: Command): void;
|
|
11
|
+
//# sourceMappingURL=clear.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clear.d.ts","sourceRoot":"","sources":["../../../src/commands/memory/clear.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAIpC,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,IAAI,CAsC1D"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ghagga memory clear` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Deletes all observations (or scoped to a repository) with
|
|
5
|
+
* confirmation prompt. Supports --force for non-interactive use.
|
|
6
|
+
*
|
|
7
|
+
* @see R12, S28–S35, S49
|
|
8
|
+
*/
|
|
9
|
+
import { openMemoryOrExit, confirmOrExit } from './utils.js';
|
|
10
|
+
import * as tui from '../../ui/tui.js';
|
|
11
|
+
export function registerClearCommand(parent) {
|
|
12
|
+
parent
|
|
13
|
+
.command('clear')
|
|
14
|
+
.description('Clear all observations')
|
|
15
|
+
.option('--repo <owner/repo>', 'Scope deletion to a single repository')
|
|
16
|
+
.option('--force', 'Skip confirmation prompt')
|
|
17
|
+
.action(async (opts) => {
|
|
18
|
+
const { storage } = await openMemoryOrExit();
|
|
19
|
+
try {
|
|
20
|
+
const stats = await storage.getStats();
|
|
21
|
+
const total = opts.repo
|
|
22
|
+
? (await storage.listObservations({
|
|
23
|
+
project: opts.repo,
|
|
24
|
+
limit: 100_000,
|
|
25
|
+
})).length
|
|
26
|
+
: stats.totalObservations;
|
|
27
|
+
if (total === 0) {
|
|
28
|
+
tui.log.info('No observations to clear.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const message = opts.repo
|
|
32
|
+
? `Clear ${total} observations for ${opts.repo}? (y/N) `
|
|
33
|
+
: `Clear all ${total} observations? (y/N) `;
|
|
34
|
+
await confirmOrExit(message, opts.force ?? false);
|
|
35
|
+
const deleted = await storage.clearObservations({
|
|
36
|
+
project: opts.repo,
|
|
37
|
+
});
|
|
38
|
+
tui.log.success(`Cleared ${deleted} observations.`);
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
await storage.close();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=clear.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clear.js","sourceRoot":"","sources":["../../../src/commands/memory/clear.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,KAAK,GAAG,MAAM,iBAAiB,CAAC;AAEvC,MAAM,UAAU,oBAAoB,CAAC,MAAe;IAClD,MAAM;SACH,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,wBAAwB,CAAC;SACrC,MAAM,CAAC,qBAAqB,EAAE,uCAAuC,CAAC;SACtE,MAAM,CAAC,SAAS,EAAE,0BAA0B,CAAC;SAC7C,MAAM,CAAC,KAAK,EAAE,IAAwC,EAAE,EAAE;QACzD,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,gBAAgB,EAAE,CAAC;QAC7C,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI;gBACrB,CAAC,CAAC,CACE,MAAM,OAAO,CAAC,gBAAgB,CAAC;oBAC7B,OAAO,EAAE,IAAI,CAAC,IAAI;oBAClB,KAAK,EAAE,OAAO;iBACf,CAAC,CACH,CAAC,MAAM;gBACV,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAE5B,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;gBAChB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;gBAC1C,OAAO;YACT,CAAC;YAED,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI;gBACvB,CAAC,CAAC,SAAS,KAAK,qBAAqB,IAAI,CAAC,IAAI,UAAU;gBACxD,CAAC,CAAC,aAAa,KAAK,uBAAuB,CAAC;YAE9C,MAAM,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,KAAK,IAAI,KAAK,CAAC,CAAC;YAElD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,iBAAiB,CAAC;gBAC9C,OAAO,EAAE,IAAI,CAAC,IAAI;aACnB,CAAC,CAAC;YACH,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,WAAW,OAAO,gBAAgB,CAAC,CAAC;QACtD,CAAC;gBAAS,CAAC;YACT,MAAM,OAAO,CAAC,KAAK,EAAE,CAAC;QACxB,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for `ghagga memory clear` subcommand.
|
|
3
|
+
*
|
|
4
|
+
* Mocks ghagga-core, node:fs, node:readline/promises, and process.stdin.isTTY.
|
|
5
|
+
* Tests happy path clear all, scoped --repo, cancel, force, no observations,
|
|
6
|
+
* non-TTY detection, and no DB.
|
|
7
|
+
*
|
|
8
|
+
* @see T7.7, S28–S35, S49
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=clear.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clear.test.d.ts","sourceRoot":"","sources":["../../../src/commands/memory/clear.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|