@crystalplatforms/adm 0.2.3

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +210 -0
  3. package/bin/adm +26 -0
  4. package/bin/adm-tui +13 -0
  5. package/package.json +62 -0
  6. package/scripts/completion/adm.bash +81 -0
  7. package/scripts/completion/adm.zsh +89 -0
  8. package/scripts/install.js +8 -0
  9. package/src/ai/knowledge.js +61 -0
  10. package/src/commands/connect.js +76 -0
  11. package/src/commands/dotfiles.js +72 -0
  12. package/src/commands/issue.js +80 -0
  13. package/src/commands/pr.js +55 -0
  14. package/src/commands/theme.js +103 -0
  15. package/src/commands/uninstall.js +72 -0
  16. package/src/commands/upgrade.js +114 -0
  17. package/src/config/index.js +32 -0
  18. package/src/data/extensions.json +358 -0
  19. package/src/installer/git-config.js +34 -0
  20. package/src/installer/node-installer.js +50 -0
  21. package/src/installer/package-manager-installer.js +31 -0
  22. package/src/installer/ssh-setup.js +31 -0
  23. package/src/installer/system-packages.js +49 -0
  24. package/src/installers/index.js +66 -0
  25. package/src/integrations/ai-backend.js +60 -0
  26. package/src/integrations/ai-providers/registry.js +261 -0
  27. package/src/integrations/github.js +107 -0
  28. package/src/integrations/gitlab.js +121 -0
  29. package/src/plugins/context.js +64 -0
  30. package/src/plugins/loader.js +50 -0
  31. package/src/plugins/runner.js +25 -0
  32. package/src/setup/catalog.js +13 -0
  33. package/src/setup/installer.js +192 -0
  34. package/src/setup/wizard.js +220 -0
  35. package/src/shell/detector.js +10 -0
  36. package/src/tui/app-state.js +374 -0
  37. package/src/tui/app.js +442 -0
  38. package/src/tui/autocomplete.js +57 -0
  39. package/src/tui/commands/registry.js +823 -0
  40. package/src/tui/components/SetupScreen.js +292 -0
  41. package/src/ui/animations.js +24 -0
  42. package/src/ui/spinner.js +34 -0
  43. package/src/ui/theme/cyberpunk.js +14 -0
  44. package/src/ui/theme/dark.js +14 -0
  45. package/src/ui/theme/forest.js +14 -0
  46. package/src/ui/theme/light.js +14 -0
  47. package/src/ui/theme/monokai.js +14 -0
  48. package/src/ui/theme/nord.js +14 -0
  49. package/src/ui/theme.js +49 -0
  50. package/src/utils/command-history.js +43 -0
  51. package/src/utils/dotfiles-sync.js +89 -0
  52. package/src/utils/error-handler.js +69 -0
  53. package/src/utils/file-conflict-handler.js +30 -0
  54. package/src/utils/fuzzy-search.js +20 -0
  55. package/src/utils/github-error-handler.js +26 -0
  56. package/src/utils/keychain.js +97 -0
  57. package/src/utils/open-url.js +15 -0
  58. package/src/utils/terminal-detection.js +26 -0
  59. package/src/utils/token-encryption.js +96 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024-2026 CrystalGamesStudio
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 ADDED
@@ -0,0 +1,210 @@
1
+ # ADM CLI
2
+
3
+ **ADM** is a TUI-only developer tool that automates environment setup on new machines and provides an AI-powered daily assistant for development workflows. One command — `adm` — opens a full-screen terminal interface where everything happens: AI chat, setup, GitHub/GitLab management, tool installation, and configuration.
4
+
5
+ Targets Node.js/web developers on macOS, Linux, and Windows — reducing initial dev setup from hours to minutes while staying available as an intelligent companion throughout development.
6
+
7
+ ---
8
+
9
+ ## Installation
10
+
11
+ ### npm (recommended)
12
+
13
+ ```bash
14
+ npm install -g @crystalgames/adm
15
+ ```
16
+
17
+ ### macOS / Linux (curl)
18
+
19
+ ```bash
20
+ curl -fsSL https://raw.githubusercontent.com/CrystalGamesStudio/ADM-CLI/main/scripts/installer.sh | sh
21
+ ```
22
+
23
+ ### Homebrew (macOS / Linux)
24
+
25
+ ```bash
26
+ brew tap CrystalGamesStudio/tap
27
+ brew install CrystalGamesStudio/tap/adm
28
+ ```
29
+
30
+ ### Windows (PowerShell)
31
+
32
+ ```powershell
33
+ irm https://raw.githubusercontent.com/CrystalGamesStudio/ADM-CLI/main/scripts/install.ps1 | iex
34
+ ```
35
+
36
+ **Prerequisites:** Node.js 18+ must be installed. The curl and PowerShell installers will check for this and provide installation instructions if missing.
37
+
38
+ ---
39
+
40
+ ## Quick Start
41
+
42
+ ```bash
43
+ # Launch full-screen TUI
44
+ adm
45
+
46
+ # Inside the TUI:
47
+ /help — Show all commands
48
+ /ai — Toggle AI mode (blue input, chat with GLM)
49
+ /ai <question> — One-off AI query without toggling mode
50
+ /github status — GitHub status
51
+ /gitlab status — GitLab status
52
+ /download — Launch extension setup wizard
53
+ /feedback — Open feedback page in browser
54
+ /theme cyberpunk — Switch theme
55
+ /exit — Quit ADM
56
+ ```
57
+
58
+ ---
59
+
60
+ ## TUI Commands
61
+
62
+ | Command | Description |
63
+ |---------|-------------|
64
+ | `/help` | Show command reference |
65
+ | `/exit` | Exit ADM |
66
+ | `/clear` | Clear message history |
67
+ | `/theme` | List or switch themes (dark, light, cyberpunk, nord, forest, monokai) |
68
+ | `/config` | Show current configuration |
69
+ | `/ai` | Toggle AI mode ON/OFF — input turns blue, questions go to GLM API |
70
+ | `/ai <question>` | One-off AI query without toggling mode |
71
+ | `/model` | Show or switch AI provider |
72
+ | `/github` | GitHub operations: status, pr, issue, commit |
73
+ | `/gitlab` | GitLab operations: status, mr, issue, commit |
74
+ | `/download` | Launch extension setup wizard |
75
+ | `/feedback` | Open feedback page in browser |
76
+ | `/connect` | Connect to GitHub or GitLab |
77
+ | `/dotfiles` | Sync dotfiles from repo |
78
+ | `/upgrade` | Check for and install updates |
79
+
80
+ ### AI Mode
81
+
82
+ When AI mode is ON (`/ai`):
83
+ - Input bar turns **blue** with `AI>` prompt
84
+ - Status bar shows **AI: ON** (green)
85
+ - Everything typed goes directly to GLM API
86
+ - Responses appear with **GLM:** prefix
87
+ - Press `Esc` or type `exit` to leave AI mode
88
+
89
+ AI knowledge system: on first run, ADM reads its own docs (README, PRD) and caches a compact summary in `~/.adm/ai-knowledge.json`. This knowledge is injected as a system message in every AI query, so the AI knows about ADM itself. Cache auto-updates when ADM version changes.
90
+
91
+ ---
92
+
93
+ ## Technical Architecture
94
+
95
+ ### Stack
96
+ - **Runtime**: Node.js 18+
97
+ - **TUI**: ink v5 + React 18 (full-screen terminal UI)
98
+ - **AI**: GLM API (Zhipu AI) — `glm-4.7-flash` model with automatic fallback
99
+ - **GitHub**: `@octokit/rest`
100
+ - **Styling**: chalk v4, 6 built-in themes
101
+ - **Testing**: Jest
102
+
103
+ ### Project Structure
104
+
105
+ ```
106
+ src/
107
+ ├── tui/ # Full-screen TUI (ink + React)
108
+ │ ├── app.js # Main App component (status bar, messages, input)
109
+ │ ├── app-state.js # State management + AI mode logic
110
+ │ └── commands/
111
+ │ └── registry.js # Unified command dispatch + autocomplete
112
+ ├── ai/
113
+ │ └── knowledge.js # AI knowledge cache (reads docs, invalidates on version)
114
+ ├── integrations/
115
+ │ ├── ai-backend.js # GLM API client (query, retry, fallback models)
116
+ │ ├── github.js # GitHub API (PRs, issues)
117
+ │ └── gitlab.js # GitLab API (MRs, issues)
118
+ ├── installer/ # Node.js, package managers, system packages
119
+ ├── config/ # Configuration system (~/.adm/config.json)
120
+ ├── plugins/ # Plugin loader (~/.adm/plugins/)
121
+ ├── ui/ # Themes, ASCII clock, animations, spinner
122
+ └── utils/ # Keychain, encryption, dotfiles sync, fuzzy search
123
+ ```
124
+
125
+ ### User State
126
+
127
+ ```
128
+ ~/.adm/
129
+ ├── config.json — Settings (theme, AI provider, tokens)
130
+ ├── ai-knowledge.json — Cached ADM docs for AI context
131
+ ├── plugins/ — Custom command plugins (.js)
132
+ ├── tokens.enc — Encrypted service tokens
133
+ └── history — Command history
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Key Features
139
+
140
+ ### Full-Screen TUI
141
+ Single `adm` command launches an ink-based terminal UI with:
142
+ - **Status bar** — theme name, AI mode indicator, version
143
+ - **Message area** — scrollable command output and AI responses
144
+ - **Input bar** — green `>` prompt (blue `AI>` in AI mode)
145
+
146
+ ### AI Mode + Knowledge
147
+ - `/ai` toggles continuous AI chat mode
148
+ - `/ai <question>` for one-off queries
149
+ - GLM API with automatic model fallback on rate limits
150
+ - Knowledge system: ADM docs cached and injected as AI context
151
+ - Friendly error messages on API failures
152
+
153
+ ### 6 Themes
154
+ dark (default), light, cyberpunk, nord, forest, monokai — switch with `/theme <name>`
155
+
156
+ ### GitHub & GitLab Integration
157
+ - `/github` — PR listing, creation, commenting, issues
158
+ - `/gitlab` — MR listing, creation, commenting, issues
159
+ - Secure token storage (OS keychain on macOS, encrypted file on Linux)
160
+
161
+ ### Extension Setup Wizard
162
+ `/download` launches an interactive wizard that installs:
163
+ - Node.js (via nvm wrapper)
164
+ - Package managers (pnpm, npm)
165
+ - Build tools (Vite, esbuild)
166
+ - System packages (git, gh CLI)
167
+ - SSH keys (ED25519)
168
+ - Git config (user.name, user.email)
169
+ - Dotfiles sync from repo
170
+
171
+ ### Feedback
172
+ `/feedback` opens the feedback page in your default browser.
173
+
174
+ ### Plugin System
175
+ Drop `.js` files in `~/.adm/plugins/` — each exports `{ execute(args, context) }` to add custom commands.
176
+
177
+ ### Dotfiles Sync
178
+ `/dotfiles sync` — clone/sync dotfiles from a git repo, apply symlinks or copies for `.bashrc`, `.zshrc`, `.gitconfig`, `.ssh/config`.
179
+
180
+ ---
181
+
182
+ ## Development
183
+
184
+ ```bash
185
+ # Install dependencies
186
+ npm install
187
+
188
+ # Run tests
189
+ npm test
190
+
191
+ # Run TUI locally
192
+ npm start
193
+
194
+ # Run specific test suite
195
+ npx jest tests/unit/tui/ --no-coverage
196
+
197
+ # Link globally for development
198
+ npm link
199
+ ```
200
+
201
+ ### Test Structure
202
+ - `tests/unit/` — Unit tests
203
+ - `tests/integration/` — Integration tests
204
+ - `tests/e2e/` — End-to-end smoke tests
205
+
206
+ ---
207
+
208
+ ## License
209
+
210
+ MIT — see [LICENSE](./LICENSE).
package/bin/adm ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env node
2
+ const chalk = require('chalk');
3
+ const { handleAdmError, formatError } = require('../src/utils/error-handler');
4
+
5
+ const pkg = require('../package.json');
6
+ const args = process.argv.slice(2);
7
+
8
+ // ── adm -v / adm --version ──────────────────────────────────
9
+ if (args[0] === '-v' || args[0] === '--version') {
10
+ console.log(pkg.version);
11
+ process.exit(0);
12
+ }
13
+
14
+ // ── adm (no args) → TUI ────────────────────────────────────
15
+ if (args.length === 0) {
16
+ const { boot } = require('../src/tui/app');
17
+ boot().catch(err => {
18
+ console.error(chalk.red(err.message));
19
+ process.exit(1);
20
+ });
21
+ return;
22
+ }
23
+
24
+ // ── unknown command ──────────────────────────────────────────
25
+ console.error(chalk.red(`Unknown command: ${args[0]}. Run ${chalk.bold('adm --help')} for available commands.`));
26
+ process.exit(1);
package/bin/adm-tui ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ADM TUI — Full-screen terminal UI
4
+ * Replaces Commander.js CLI with ink-based TUI for v2.
5
+ */
6
+ 'use strict';
7
+
8
+ const { boot } = require('../src/tui/app');
9
+
10
+ boot().catch(err => {
11
+ console.error(err.message);
12
+ process.exit(1);
13
+ });
package/package.json ADDED
@@ -0,0 +1,62 @@
1
+ {
2
+ "name": "@crystalplatforms/adm",
3
+ "version": "0.2.3",
4
+ "description": "ADM developer CLI — environment setup, GitHub integration, and AI-powered daily assistant",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/CrystalGamesStudio/ADM-CLI.git"
9
+ },
10
+ "homepage": "https://github.com/CrystalGamesStudio/ADM-CLI#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/CrystalGamesStudio/ADM-CLI/issues"
13
+ },
14
+ "bin": {
15
+ "adm": "bin/adm"
16
+ },
17
+ "files": [
18
+ "bin/",
19
+ "src/",
20
+ "scripts/completion/",
21
+ "scripts/install.js"
22
+ ],
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "engines": {
27
+ "node": ">=18.0.0"
28
+ },
29
+ "scripts": {
30
+ "start": "node ./bin/adm",
31
+ "test": "jest --config=jest.config.js",
32
+ "test:unit": "jest tests/unit --runInBand",
33
+ "test:integration": "jest tests/integration --runInBand",
34
+ "test:shell": "echo 'Install bats separately to run shell tests' && exit 0",
35
+ "prepublishOnly": "npm test"
36
+ },
37
+ "keywords": [
38
+ "cli",
39
+ "developer-tools",
40
+ "setup",
41
+ "github",
42
+ "gitlab",
43
+ "ai",
44
+ "adm"
45
+ ],
46
+ "dependencies": {
47
+ "@octokit/rest": "^20.0.2",
48
+ "chalk": "^4.1.2",
49
+ "commander": "^11.1.0",
50
+ "execa": "^7.1.1",
51
+ "ink": "^5.2.1",
52
+ "inquirer": "^8.2.6",
53
+ "ora": "^5.4.1",
54
+ "react": "^18.3.1",
55
+ "react-dom": "^18.3.1"
56
+ },
57
+ "devDependencies": {
58
+ "jest": "^29.6.0",
59
+ "mock-fs": "^5.2.0",
60
+ "sinon": "^15.2.0"
61
+ }
62
+ }
@@ -0,0 +1,81 @@
1
+ # Bash completion for adm (TUI mode — all commands use / prefix)
2
+ _adm_completion() {
3
+ local cur prev
4
+ COMPREPLY=()
5
+ cur="${COMP_WORDS[COMP_CWORD]}"
6
+ prev="${COMP_WORDS[COMP_CWORD-1]}"
7
+
8
+ local commands="help exit clear theme config status ai model setup connect pr mr issue commit clock dotfiles uninstall plugins"
9
+
10
+ # Top-level completion (first word or after adm)
11
+ if [[ ${COMP_CWORD} -eq 1 ]]; then
12
+ COMPREPLY=( $(compgen -W "${commands}" -- ${cur}) )
13
+ return 0
14
+ fi
15
+
16
+ # Subcommands
17
+ case "${COMP_WORDS[1]}" in
18
+ help|exit|clear|uninstall|plugins|status)
19
+ return 0
20
+ ;;
21
+ setup)
22
+ COMPREPLY=( $(compgen -W "--dry-run" -- ${cur}) )
23
+ return 0
24
+ ;;
25
+ connect)
26
+ local connect_subs="github gitlab list disconnect"
27
+ if [[ ${COMP_CWORD} -eq 2 ]]; then
28
+ COMPREPLY=( $(compgen -W "${connect_subs}" -- ${cur}) )
29
+ elif [[ "${COMP_WORDS[2]}" == "github" || "${COMP_WORDS[2]}" == "gitlab" ]]; then
30
+ COMPREPLY=( $(compgen -W "--token" -- ${cur}) )
31
+ fi
32
+ return 0
33
+ ;;
34
+ pr)
35
+ local pr_subs="list draft comment"
36
+ if [[ ${COMP_CWORD} -eq 2 ]]; then
37
+ COMPREPLY=( $(compgen -W "${pr_subs}" -- ${cur}) )
38
+ fi
39
+ return 0
40
+ ;;
41
+ mr)
42
+ local mr_subs="list draft comment"
43
+ if [[ ${COMP_CWORD} -eq 2 ]]; then
44
+ COMPREPLY=( $(compgen -W "${mr_subs}" -- ${cur}) )
45
+ fi
46
+ return 0
47
+ ;;
48
+ issue)
49
+ COMPREPLY=( $(compgen -W "list" -- ${cur}) )
50
+ return 0
51
+ ;;
52
+ commit)
53
+ COMPREPLY=( $(compgen -W "suggest" -- ${cur}) )
54
+ return 0
55
+ ;;
56
+ theme)
57
+ local themes="dark light cyberpunk nord forest monokai"
58
+ COMPREPLY=( $(compgen -W "${themes}" -- ${cur}) )
59
+ return 0
60
+ ;;
61
+ model)
62
+ local providers="glm-free glm-pro openai anthropic ollama list"
63
+ COMPREPLY=( $(compgen -W "${providers}" -- ${cur}) )
64
+ return 0
65
+ ;;
66
+ dotfiles)
67
+ COMPREPLY=( $(compgen -W "sync --repo --copy" -- ${cur}) )
68
+ return 0
69
+ ;;
70
+ clock)
71
+ return 0
72
+ ;;
73
+ ai)
74
+ return 0
75
+ ;;
76
+ config)
77
+ return 0
78
+ ;;
79
+ esac
80
+ }
81
+ complete -F _adm_completion adm
@@ -0,0 +1,89 @@
1
+ #compdef adm
2
+
3
+ _adm() {
4
+ local -a commands subcommands opts
5
+
6
+ commands=(
7
+ 'help:Show command reference'
8
+ 'exit:Exit ADM'
9
+ 'clear:Clear message history'
10
+ 'theme:List or switch themes'
11
+ 'config:Show current configuration'
12
+ 'status:Show git status'
13
+ 'ai:Toggle AI mode or ask a question'
14
+ 'model:Show or switch AI provider'
15
+ 'setup:Launch extension setup wizard'
16
+ 'connect:Connect to GitHub or GitLab'
17
+ 'pr:Pull request operations'
18
+ 'mr:Merge request operations (GitLab)'
19
+ 'issue:List issues from connected platform'
20
+ 'commit:Commit subcommands'
21
+ 'clock:Show ASCII clock'
22
+ 'dotfiles:Sync dotfiles from repo'
23
+ 'uninstall:Remove ADM CLI config'
24
+ 'plugins:List loaded plugins'
25
+ )
26
+
27
+ _arguments -C \
28
+ '1:command:->command' \
29
+ '*::arg:->args'
30
+
31
+ case $state in
32
+ command)
33
+ _describe 'command' commands
34
+ ;;
35
+ args)
36
+ case $words[1] in
37
+ setup)
38
+ _arguments '--dry-run[Preview planned actions without executing]'
39
+ ;;
40
+ connect)
41
+ local -a connect_subs
42
+ connect_subs=('github:Connect GitHub account' 'gitlab:Connect GitLab account' 'list:List connected services' 'disconnect:Disconnect a service')
43
+ if (( CURRENT == 2 )); then
44
+ _describe 'subcommand' connect_subs
45
+ else
46
+ case $words[2] in
47
+ github|gitlab) _arguments '--token[Access token]:token:' ;;
48
+ esac
49
+ fi
50
+ ;;
51
+ pr)
52
+ local -a pr_subs
53
+ pr_subs=('list:List open pull requests' 'draft:Create a draft PR' 'comment:Comment on a pull request')
54
+ if (( CURRENT == 2 )); then
55
+ _describe 'subcommand' pr_subs
56
+ fi
57
+ ;;
58
+ mr)
59
+ local -a mr_subs
60
+ mr_subs=('list:List open merge requests' 'draft:Create a draft MR' 'comment:Comment on a merge request')
61
+ if (( CURRENT == 2 )); then
62
+ _describe 'subcommand' mr_subs
63
+ fi
64
+ ;;
65
+ issue)
66
+ _arguments '1:subcommand:(list)'
67
+ ;;
68
+ commit)
69
+ _arguments '1:subcommand:(suggest)'
70
+ ;;
71
+ dotfiles)
72
+ local -a dotfiles_subs
73
+ dotfiles_subs=('sync:Clone/pull dotfiles repo and symlink files')
74
+ if (( CURRENT == 2 )); then
75
+ _describe 'subcommand' dotfiles_subs
76
+ fi
77
+ ;;
78
+ theme)
79
+ _arguments '1:theme:(dark light cyberpunk nord forest monokai)'
80
+ ;;
81
+ model)
82
+ _arguments '1:provider:(glm-free glm-pro openai anthropic ollama list)'
83
+ ;;
84
+ esac
85
+ ;;
86
+ esac
87
+ }
88
+
89
+ _adm "$@"
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ const chalk = require('chalk');
3
+ console.log('');
4
+ console.log(chalk.green(' ADM CLI installed successfully!'));
5
+ console.log('');
6
+ console.log(' Run ' + chalk.bold('adm') + ' to launch the TUI.');
7
+ console.log(' Type ' + chalk.bold('/download') + ' inside ADM to set up your dev environment.');
8
+ console.log('');
@@ -0,0 +1,61 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const KNOWLEDGE_DIR = path.join(os.homedir(), '.adm');
6
+ const KNOWLEDGE_FILE = path.join(KNOWLEDGE_DIR, 'ai-knowledge.json');
7
+
8
+ const SOURCE_DOCS = ['README.md', path.join('plans', 'prd-v2.md')];
9
+
10
+ function buildKnowledge(version, projectRoot) {
11
+ const parts = [];
12
+
13
+ for (const doc of SOURCE_DOCS) {
14
+ const filePath = path.join(projectRoot, doc);
15
+ try {
16
+ const content = fs.readFileSync(filePath, 'utf8');
17
+ // Take first 500 chars per doc for compactness
18
+ const compact = content.substring(0, 500).trim();
19
+ if (compact) {
20
+ parts.push(`--- ${path.basename(doc)} ---\n${compact}`);
21
+ }
22
+ } catch {
23
+ // doc missing — skip
24
+ }
25
+ }
26
+
27
+ if (parts.length === 0) return null;
28
+
29
+ const summary = parts.join('\n\n');
30
+ const cache = {
31
+ version,
32
+ summary,
33
+ updatedAt: new Date().toISOString(),
34
+ };
35
+
36
+ // Ensure ~/.adm/ exists
37
+ if (!fs.existsSync(KNOWLEDGE_DIR)) {
38
+ fs.mkdirSync(KNOWLEDGE_DIR, { recursive: true });
39
+ }
40
+ fs.writeFileSync(KNOWLEDGE_FILE, JSON.stringify(cache, null, 2), 'utf8');
41
+
42
+ return summary;
43
+ }
44
+
45
+ function getKnowledge(version, projectRoot) {
46
+ // Try reading cache
47
+ try {
48
+ const raw = fs.readFileSync(KNOWLEDGE_FILE, 'utf8');
49
+ const cached = JSON.parse(raw);
50
+ if (cached.version === version) {
51
+ return cached.summary;
52
+ }
53
+ // Version changed — rebuild
54
+ } catch {
55
+ // No cache — build fresh
56
+ }
57
+
58
+ return buildKnowledge(version, projectRoot);
59
+ }
60
+
61
+ module.exports = { getKnowledge, buildKnowledge, KNOWLEDGE_DIR, KNOWLEDGE_FILE };
@@ -0,0 +1,76 @@
1
+ const inquirer = require('inquirer');
2
+ const chalk = require('chalk');
3
+ const ora = require('ora');
4
+ const github = require('../integrations/github');
5
+ const gitlab = require('../integrations/gitlab');
6
+ const { listStoredServices, retrieveToken } = require('../utils/keychain');
7
+
8
+ async function connectGithub({ token } = {}) {
9
+ if (!token) {
10
+ ({ token } = await inquirer.prompt([{
11
+ type: 'password',
12
+ name: 'token',
13
+ message: 'Enter GitHub Personal Access Token (PAT):',
14
+ mask: '*',
15
+ }]));
16
+ }
17
+ const s = ora('Validating GitHub token...').start();
18
+ try {
19
+ const result = await github.connect(token);
20
+ s.succeed(`Connected to GitHub as ${chalk.green(result.user.login)}`);
21
+ return { ok: true, user: result.user.login };
22
+ } catch (err) {
23
+ s.fail(err.message);
24
+ throw err;
25
+ }
26
+ }
27
+
28
+ async function listConnections() {
29
+ const services = await listStoredServices();
30
+ if (services.length === 0) {
31
+ console.log(chalk.yellow(' No connected services.'));
32
+ return [];
33
+ }
34
+ for (const name of services) {
35
+ const token = await retrieveToken(name);
36
+ const masked = token ? token.substring(0, 4) + '****' + token.slice(-4) : '(none)';
37
+ console.log(` ${chalk.green(name)}: connected (token: ${masked})`);
38
+ }
39
+ return services;
40
+ }
41
+
42
+ async function disconnect(service) {
43
+ if (service === 'github') {
44
+ await github.disconnect();
45
+ console.log(chalk.green(' GitHub disconnected.'));
46
+ return true;
47
+ }
48
+ if (service === 'gitlab') {
49
+ await gitlab.disconnect();
50
+ console.log(chalk.green(' GitLab disconnected.'));
51
+ return true;
52
+ }
53
+ throw new Error(`Unknown service: ${service}. Supported: github, gitlab`);
54
+ }
55
+
56
+ async function connectGitlab({ token } = {}) {
57
+ if (!token) {
58
+ ({ token } = await inquirer.prompt([{
59
+ type: 'password',
60
+ name: 'token',
61
+ message: 'Enter GitLab Access Token:',
62
+ mask: '*',
63
+ }]));
64
+ }
65
+ const s = ora('Validating GitLab token...').start();
66
+ try {
67
+ const result = await gitlab.connect(token);
68
+ s.succeed(`Connected to GitLab as ${chalk.green(result.user.username)}`);
69
+ return { ok: true, user: result.user.username };
70
+ } catch (err) {
71
+ s.fail(err.message);
72
+ throw err;
73
+ }
74
+ }
75
+
76
+ module.exports = { connectGithub, connectGitlab, listConnections, disconnect };