@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.
- package/LICENSE +21 -0
- package/README.md +210 -0
- package/bin/adm +26 -0
- package/bin/adm-tui +13 -0
- package/package.json +62 -0
- package/scripts/completion/adm.bash +81 -0
- package/scripts/completion/adm.zsh +89 -0
- package/scripts/install.js +8 -0
- package/src/ai/knowledge.js +61 -0
- package/src/commands/connect.js +76 -0
- package/src/commands/dotfiles.js +72 -0
- package/src/commands/issue.js +80 -0
- package/src/commands/pr.js +55 -0
- package/src/commands/theme.js +103 -0
- package/src/commands/uninstall.js +72 -0
- package/src/commands/upgrade.js +114 -0
- package/src/config/index.js +32 -0
- package/src/data/extensions.json +358 -0
- package/src/installer/git-config.js +34 -0
- package/src/installer/node-installer.js +50 -0
- package/src/installer/package-manager-installer.js +31 -0
- package/src/installer/ssh-setup.js +31 -0
- package/src/installer/system-packages.js +49 -0
- package/src/installers/index.js +66 -0
- package/src/integrations/ai-backend.js +60 -0
- package/src/integrations/ai-providers/registry.js +261 -0
- package/src/integrations/github.js +107 -0
- package/src/integrations/gitlab.js +121 -0
- package/src/plugins/context.js +64 -0
- package/src/plugins/loader.js +50 -0
- package/src/plugins/runner.js +25 -0
- package/src/setup/catalog.js +13 -0
- package/src/setup/installer.js +192 -0
- package/src/setup/wizard.js +220 -0
- package/src/shell/detector.js +10 -0
- package/src/tui/app-state.js +374 -0
- package/src/tui/app.js +442 -0
- package/src/tui/autocomplete.js +57 -0
- package/src/tui/commands/registry.js +823 -0
- package/src/tui/components/SetupScreen.js +292 -0
- package/src/ui/animations.js +24 -0
- package/src/ui/spinner.js +34 -0
- package/src/ui/theme/cyberpunk.js +14 -0
- package/src/ui/theme/dark.js +14 -0
- package/src/ui/theme/forest.js +14 -0
- package/src/ui/theme/light.js +14 -0
- package/src/ui/theme/monokai.js +14 -0
- package/src/ui/theme/nord.js +14 -0
- package/src/ui/theme.js +49 -0
- package/src/utils/command-history.js +43 -0
- package/src/utils/dotfiles-sync.js +89 -0
- package/src/utils/error-handler.js +69 -0
- package/src/utils/file-conflict-handler.js +30 -0
- package/src/utils/fuzzy-search.js +20 -0
- package/src/utils/github-error-handler.js +26 -0
- package/src/utils/keychain.js +97 -0
- package/src/utils/open-url.js +15 -0
- package/src/utils/terminal-detection.js +26 -0
- 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 };
|