ghagga 2.0.0 → 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.
Files changed (137) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +52 -14
  3. package/dist/commands/login.d.ts.map +1 -1
  4. package/dist/commands/login.js +17 -14
  5. package/dist/commands/login.js.map +1 -1
  6. package/dist/commands/login.test.d.ts +9 -0
  7. package/dist/commands/login.test.d.ts.map +1 -0
  8. package/dist/commands/login.test.js +138 -0
  9. package/dist/commands/login.test.js.map +1 -0
  10. package/dist/commands/logout.d.ts.map +1 -1
  11. package/dist/commands/logout.js +4 -3
  12. package/dist/commands/logout.js.map +1 -1
  13. package/dist/commands/logout.test.d.ts +8 -0
  14. package/dist/commands/logout.test.d.ts.map +1 -0
  15. package/dist/commands/logout.test.js +61 -0
  16. package/dist/commands/logout.test.js.map +1 -0
  17. package/dist/commands/memory/clear.d.ts +11 -0
  18. package/dist/commands/memory/clear.d.ts.map +1 -0
  19. package/dist/commands/memory/clear.js +45 -0
  20. package/dist/commands/memory/clear.js.map +1 -0
  21. package/dist/commands/memory/clear.test.d.ts +11 -0
  22. package/dist/commands/memory/clear.test.d.ts.map +1 -0
  23. package/dist/commands/memory/clear.test.js +178 -0
  24. package/dist/commands/memory/clear.test.js.map +1 -0
  25. package/dist/commands/memory/delete.d.ts +11 -0
  26. package/dist/commands/memory/delete.d.ts.map +1 -0
  27. package/dist/commands/memory/delete.js +38 -0
  28. package/dist/commands/memory/delete.js.map +1 -0
  29. package/dist/commands/memory/delete.test.d.ts +11 -0
  30. package/dist/commands/memory/delete.test.d.ts.map +1 -0
  31. package/dist/commands/memory/delete.test.js +176 -0
  32. package/dist/commands/memory/delete.test.js.map +1 -0
  33. package/dist/commands/memory/index.d.ts +10 -0
  34. package/dist/commands/memory/index.d.ts.map +1 -0
  35. package/dist/commands/memory/index.js +23 -0
  36. package/dist/commands/memory/index.js.map +1 -0
  37. package/dist/commands/memory/list.d.ts +11 -0
  38. package/dist/commands/memory/list.d.ts.map +1 -0
  39. package/dist/commands/memory/list.js +47 -0
  40. package/dist/commands/memory/list.js.map +1 -0
  41. package/dist/commands/memory/list.test.d.ts +10 -0
  42. package/dist/commands/memory/list.test.d.ts.map +1 -0
  43. package/dist/commands/memory/list.test.js +135 -0
  44. package/dist/commands/memory/list.test.js.map +1 -0
  45. package/dist/commands/memory/search.d.ts +12 -0
  46. package/dist/commands/memory/search.d.ts.map +1 -0
  47. package/dist/commands/memory/search.js +44 -0
  48. package/dist/commands/memory/search.js.map +1 -0
  49. package/dist/commands/memory/search.test.d.ts +11 -0
  50. package/dist/commands/memory/search.test.d.ts.map +1 -0
  51. package/dist/commands/memory/search.test.js +122 -0
  52. package/dist/commands/memory/search.test.js.map +1 -0
  53. package/dist/commands/memory/show.d.ts +10 -0
  54. package/dist/commands/memory/show.d.ts.map +1 -0
  55. package/dist/commands/memory/show.js +51 -0
  56. package/dist/commands/memory/show.js.map +1 -0
  57. package/dist/commands/memory/show.test.d.ts +11 -0
  58. package/dist/commands/memory/show.test.d.ts.map +1 -0
  59. package/dist/commands/memory/show.test.js +156 -0
  60. package/dist/commands/memory/show.test.js.map +1 -0
  61. package/dist/commands/memory/stats.d.ts +11 -0
  62. package/dist/commands/memory/stats.d.ts.map +1 -0
  63. package/dist/commands/memory/stats.js +63 -0
  64. package/dist/commands/memory/stats.js.map +1 -0
  65. package/dist/commands/memory/stats.test.d.ts +10 -0
  66. package/dist/commands/memory/stats.test.d.ts.map +1 -0
  67. package/dist/commands/memory/stats.test.js +122 -0
  68. package/dist/commands/memory/stats.test.js.map +1 -0
  69. package/dist/commands/memory/utils.d.ts +44 -0
  70. package/dist/commands/memory/utils.d.ts.map +1 -0
  71. package/dist/commands/memory/utils.js +90 -0
  72. package/dist/commands/memory/utils.js.map +1 -0
  73. package/dist/commands/memory/utils.test.d.ts +10 -0
  74. package/dist/commands/memory/utils.test.d.ts.map +1 -0
  75. package/dist/commands/memory/utils.test.js +93 -0
  76. package/dist/commands/memory/utils.test.js.map +1 -0
  77. package/dist/commands/review.d.ts +1 -0
  78. package/dist/commands/review.d.ts.map +1 -1
  79. package/dist/commands/review.js +42 -102
  80. package/dist/commands/review.js.map +1 -1
  81. package/dist/commands/review.test.js +267 -1
  82. package/dist/commands/review.test.js.map +1 -1
  83. package/dist/commands/status.d.ts.map +1 -1
  84. package/dist/commands/status.js +12 -11
  85. package/dist/commands/status.js.map +1 -1
  86. package/dist/commands/status.test.d.ts +8 -0
  87. package/dist/commands/status.test.d.ts.map +1 -0
  88. package/dist/commands/status.test.js +106 -0
  89. package/dist/commands/status.test.js.map +1 -0
  90. package/dist/index.d.ts +1 -1
  91. package/dist/index.js +28 -10
  92. package/dist/index.js.map +1 -1
  93. package/dist/lib/config.d.ts +1 -0
  94. package/dist/lib/config.d.ts.map +1 -1
  95. package/dist/lib/config.js +1 -1
  96. package/dist/lib/config.js.map +1 -1
  97. package/dist/lib/config.test.d.ts +9 -0
  98. package/dist/lib/config.test.d.ts.map +1 -0
  99. package/dist/lib/config.test.js +181 -0
  100. package/dist/lib/config.test.js.map +1 -0
  101. package/dist/lib/git.d.ts +23 -0
  102. package/dist/lib/git.d.ts.map +1 -0
  103. package/dist/lib/git.js +53 -0
  104. package/dist/lib/git.js.map +1 -0
  105. package/dist/lib/git.test.d.ts +2 -0
  106. package/dist/lib/git.test.d.ts.map +1 -0
  107. package/dist/lib/git.test.js +65 -0
  108. package/dist/lib/git.test.js.map +1 -0
  109. package/dist/lib/oauth.test.d.ts +8 -0
  110. package/dist/lib/oauth.test.d.ts.map +1 -0
  111. package/dist/lib/oauth.test.js +212 -0
  112. package/dist/lib/oauth.test.js.map +1 -0
  113. package/dist/ui/__tests__/format.test.d.ts +7 -0
  114. package/dist/ui/__tests__/format.test.d.ts.map +1 -0
  115. package/dist/ui/__tests__/format.test.js +220 -0
  116. package/dist/ui/__tests__/format.test.js.map +1 -0
  117. package/dist/ui/__tests__/theme.test.d.ts +7 -0
  118. package/dist/ui/__tests__/theme.test.d.ts.map +1 -0
  119. package/dist/ui/__tests__/theme.test.js +79 -0
  120. package/dist/ui/__tests__/theme.test.js.map +1 -0
  121. package/dist/ui/__tests__/tui.test.d.ts +9 -0
  122. package/dist/ui/__tests__/tui.test.d.ts.map +1 -0
  123. package/dist/ui/__tests__/tui.test.js +222 -0
  124. package/dist/ui/__tests__/tui.test.js.map +1 -0
  125. package/dist/ui/format.d.ts +38 -0
  126. package/dist/ui/format.d.ts.map +1 -0
  127. package/dist/ui/format.js +136 -0
  128. package/dist/ui/format.js.map +1 -0
  129. package/dist/ui/theme.d.ts +26 -0
  130. package/dist/ui/theme.d.ts.map +1 -0
  131. package/dist/ui/theme.js +63 -0
  132. package/dist/ui/theme.js.map +1 -0
  133. package/dist/ui/tui.d.ts +44 -0
  134. package/dist/ui/tui.d.ts.map +1 -0
  135. package/dist/ui/tui.js +121 -0
  136. package/dist/ui/tui.js.map +1 -0
  137. package/package.json +14 -13
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 JNZader & Gentleman Programming Community
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
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 @ghagga/cli login
7
- npx @ghagga/cli review
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 @ghagga/cli login
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 @ghagga/cli review
36
+ npx ghagga review
37
37
 
38
38
  # Thorough review with 5 specialist agents
39
- npx @ghagga/cli review --mode workflow
39
+ npx ghagga review --mode workflow
40
40
 
41
41
  # Balanced review with for/against/neutral voting
42
- npx @ghagga/cli review --mode consensus
42
+ npx ghagga review --mode consensus
43
43
 
44
44
  # See detailed progress of each step
45
- npx @ghagga/cli review --mode workflow --verbose
45
+ npx ghagga review --mode workflow --verbose
46
46
  ```
47
47
 
48
48
  ### 3. Check your status
49
49
 
50
50
  ```bash
51
- npx @ghagga/cli status # Show auth & config
52
- npx @ghagga/cli logout # Clear credentials
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 @ghagga/cli
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, anthropic, google
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. Sends the diff + context to the AI review agent
155
- 5. Returns findings with severity, file, line, and suggestions
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;AAgCH,wBAAsB,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC,CAwDlD"}
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"}
@@ -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
- console.log(`\u2139\ufe0f Already logged in as ${config.githubLogin}.`);
34
- console.log(' Run "ghagga logout" first to switch accounts.\n');
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
- console.log('\ud83d\udd10 Authenticating with GitHub...\n');
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
- console.log(' \u2794 Open this URL in your browser:\n');
43
- console.log(` \x1b[1m\x1b[36mhttps://github.com/login/device\x1b[0m\n`);
44
- console.log(` \u2794 Enter this code:\n`);
45
- console.log(` \x1b[1m\x1b[33m${deviceCode.user_code}\x1b[0m\n`);
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
- console.log(' (Browser opened automatically)\n');
49
+ tui.log.info(' (Browser opened automatically)\n');
49
50
  }
50
- console.log(' Waiting for authorization...');
51
- // Step 3: Poll for access token
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
- console.log(`\n\u2705 Logged in as \x1b[1m${user.login}\x1b[0m`);
64
- console.log(' Provider: github (gpt-4o-mini) — free tier');
65
- console.log('\n Run "ghagga review ." to review your code!\n');
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
- console.error(`\n\u274c Login failed: ${message}\n`);
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":"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"}
1
+ {"version":3,"file":"login.js","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
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
+ export {};
9
+ //# sourceMappingURL=login.test.d.ts.map
@@ -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;AAIH,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
1
+ {"version":3,"file":"logout.d.ts","sourceRoot":"","sources":["../../src/commands/logout.ts"],"names":[],"mappings":"AAAA;;GAEG;AAKH,wBAAgB,aAAa,IAAI,IAAI,CAapC"}
@@ -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
- console.log('\u2139\ufe0f Not currently logged in.\n');
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
- console.log(`\u2705 Logged out from ${login}.`);
14
- console.log(' Stored credentials have been removed.\n');
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;AAEvE,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,UAAU,EAAE,EAAE,CAAC;QAClB,OAAO,CAAC,GAAG,CAAC,0CAA0C,CAAC,CAAC;QACxD,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,OAAO,CAAC,GAAG,CAAC,0BAA0B,KAAK,GAAG,CAAC,CAAC;IAChD,OAAO,CAAC,GAAG,CAAC,4CAA4C,CAAC,CAAC;AAC5D,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,8 @@
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
+ export {};
8
+ //# sourceMappingURL=logout.test.d.ts.map
@@ -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"}