apero-kit-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +252 -0
- package/bin/ak.js +78 -0
- package/package.json +61 -0
- package/src/commands/add.js +126 -0
- package/src/commands/doctor.js +129 -0
- package/src/commands/init.js +223 -0
- package/src/commands/list.js +190 -0
- package/src/commands/status.js +113 -0
- package/src/commands/update.js +183 -0
- package/src/index.js +8 -0
- package/src/kits/index.js +122 -0
- package/src/utils/copy.js +194 -0
- package/src/utils/hash.js +74 -0
- package/src/utils/paths.js +166 -0
- package/src/utils/prompts.js +235 -0
- package/src/utils/state.js +136 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Apero Team
|
|
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,252 @@
|
|
|
1
|
+
# Apero Kit CLI
|
|
2
|
+
|
|
3
|
+
> Scaffold AI agent projects with pre-configured kits for Claude Code, OpenCode, and Codex.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/apero-kit-cli)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g apero-kit-cli
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
# Initialize a new project with the engineer kit
|
|
18
|
+
ak init my-app --kit engineer
|
|
19
|
+
|
|
20
|
+
# Or use interactive mode
|
|
21
|
+
ak init my-app
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
### `ak init [project-name]`
|
|
27
|
+
|
|
28
|
+
Initialize a new project with an agent kit.
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
ak init my-app --kit engineer # Full-stack development kit
|
|
32
|
+
ak init my-app --kit researcher # Research and analysis kit
|
|
33
|
+
ak init my-app --kit designer # UI/UX design kit
|
|
34
|
+
ak init my-app --kit minimal # Lightweight essential kit
|
|
35
|
+
ak init my-app --kit full # Everything included
|
|
36
|
+
ak init my-app # Interactive mode
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**Options:**
|
|
40
|
+
| Flag | Description |
|
|
41
|
+
|------|-------------|
|
|
42
|
+
| `-k, --kit <type>` | Kit type: engineer, researcher, designer, minimal, full, custom |
|
|
43
|
+
| `-t, --target <target>` | Target folder: claude (default), opencode, generic |
|
|
44
|
+
| `-s, --source <path>` | Custom source path for templates |
|
|
45
|
+
| `-f, --force` | Overwrite existing directory |
|
|
46
|
+
|
|
47
|
+
### `ak add <type>:<name>`
|
|
48
|
+
|
|
49
|
+
Add an agent, skill, or command to an existing project.
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
ak add skill:databases # Add databases skill
|
|
53
|
+
ak add agent:debugger # Add debugger agent
|
|
54
|
+
ak add command:fix/ci # Add fix/ci command
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### `ak list [type]`
|
|
58
|
+
|
|
59
|
+
List available kits, agents, skills, or commands.
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
ak list # Show available list commands
|
|
63
|
+
ak list kits # List all kits
|
|
64
|
+
ak list agents # List available agents
|
|
65
|
+
ak list skills # List available skills
|
|
66
|
+
ak list commands # List available commands
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### `ak update`
|
|
70
|
+
|
|
71
|
+
Update/sync from source templates. Only updates unchanged files (safe by default).
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
ak update # Update from configured source
|
|
75
|
+
ak update --source ~/AGENTS.md # Update from specific source
|
|
76
|
+
ak update --skills # Update only skills
|
|
77
|
+
ak update --dry-run # Preview what would be updated
|
|
78
|
+
ak update --force # Update without confirmation
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Options:**
|
|
82
|
+
| Flag | Description |
|
|
83
|
+
|------|-------------|
|
|
84
|
+
| `-s, --source <path>` | Source path to sync from |
|
|
85
|
+
| `--agents` | Update only agents |
|
|
86
|
+
| `--skills` | Update only skills |
|
|
87
|
+
| `--commands` | Update only commands |
|
|
88
|
+
| `-n, --dry-run` | Show what would be updated without making changes |
|
|
89
|
+
| `-f, --force` | Force update without confirmation |
|
|
90
|
+
|
|
91
|
+
### `ak status`
|
|
92
|
+
|
|
93
|
+
Show project status and file changes.
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
ak status # Show status summary
|
|
97
|
+
ak status --verbose # Show all files
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### `ak doctor`
|
|
101
|
+
|
|
102
|
+
Check project health and diagnose issues.
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
ak doctor
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Available Kits
|
|
109
|
+
|
|
110
|
+
| Kit | Description | Agents | Skills | Commands |
|
|
111
|
+
|-----|-------------|--------|--------|----------|
|
|
112
|
+
| 🛠️ **engineer** | Full-stack development | 7 | 7 | 17 |
|
|
113
|
+
| 🔬 **researcher** | Research and analysis | 6 | 4 | 10 |
|
|
114
|
+
| 🎨 **designer** | UI/UX design | 3 | 3 | 5 |
|
|
115
|
+
| 📦 **minimal** | Lightweight essentials | 2 | 2 | 3 |
|
|
116
|
+
| 🚀 **full** | Everything included | ALL | ALL | ALL |
|
|
117
|
+
| 🔧 **custom** | Pick your own | - | - | - |
|
|
118
|
+
|
|
119
|
+
## How It Works
|
|
120
|
+
|
|
121
|
+
### Source Detection
|
|
122
|
+
|
|
123
|
+
The CLI automatically finds your source templates by searching up from the current directory:
|
|
124
|
+
|
|
125
|
+
```
|
|
126
|
+
cwd → parent → git root
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
It looks for:
|
|
130
|
+
- `AGENTS.md` file
|
|
131
|
+
- `.claude/` directory
|
|
132
|
+
- `.opencode/` directory
|
|
133
|
+
|
|
134
|
+
You can also specify a custom source with `--source`.
|
|
135
|
+
|
|
136
|
+
### State Tracking
|
|
137
|
+
|
|
138
|
+
Each project stores its state in `.ak/state.json`:
|
|
139
|
+
- What kit was used
|
|
140
|
+
- Source location
|
|
141
|
+
- Installed components
|
|
142
|
+
- File hashes for safe updates
|
|
143
|
+
|
|
144
|
+
### Safe Updates
|
|
145
|
+
|
|
146
|
+
When you run `ak update`:
|
|
147
|
+
1. Files you haven't modified → **Updated**
|
|
148
|
+
2. Files you've modified → **Skipped** (preserves your changes)
|
|
149
|
+
3. New files from source → **Added**
|
|
150
|
+
|
|
151
|
+
## Project Structure
|
|
152
|
+
|
|
153
|
+
After `ak init my-app --kit engineer`:
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
my-app/
|
|
157
|
+
├── .claude/
|
|
158
|
+
│ ├── agents/ # AI agent definitions
|
|
159
|
+
│ ├── commands/ # Slash commands
|
|
160
|
+
│ ├── skills/ # Knowledge packages
|
|
161
|
+
│ ├── router/ # Decision logic
|
|
162
|
+
│ ├── hooks/ # Automation scripts
|
|
163
|
+
│ ├── README.md
|
|
164
|
+
│ └── settings.json
|
|
165
|
+
├── .ak/
|
|
166
|
+
│ └── state.json # Project state for updates
|
|
167
|
+
└── AGENTS.md # Core ruleset
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Examples
|
|
171
|
+
|
|
172
|
+
### Workflow: Create and Develop
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# 1. Initialize project
|
|
176
|
+
ak init my-api --kit engineer
|
|
177
|
+
cd my-api
|
|
178
|
+
|
|
179
|
+
# 2. Start coding with Claude Code
|
|
180
|
+
# ... develop features ...
|
|
181
|
+
|
|
182
|
+
# 3. Check status
|
|
183
|
+
ak status
|
|
184
|
+
|
|
185
|
+
# 4. Add more skills as needed
|
|
186
|
+
ak add skill:databases
|
|
187
|
+
ak add skill:devops
|
|
188
|
+
|
|
189
|
+
# 5. Update when source has new content
|
|
190
|
+
ak update
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Workflow: Team Sharing
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Team lead: publish CLI with your custom kits
|
|
197
|
+
npm publish
|
|
198
|
+
|
|
199
|
+
# Team members: install and use
|
|
200
|
+
npm install -g apero-kit-cli
|
|
201
|
+
ak init my-project --kit engineer
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## Configuration
|
|
205
|
+
|
|
206
|
+
### Global Config (Optional)
|
|
207
|
+
|
|
208
|
+
Create `~/.ak-cli.json`:
|
|
209
|
+
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"defaultSource": "/path/to/your/AGENTS.md",
|
|
213
|
+
"defaultKit": "engineer"
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Troubleshooting
|
|
218
|
+
|
|
219
|
+
Run `ak doctor` to diagnose issues:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
$ ak doctor
|
|
223
|
+
|
|
224
|
+
Apero Kit Doctor
|
|
225
|
+
|
|
226
|
+
Checking project health...
|
|
227
|
+
|
|
228
|
+
✓ ak project detected
|
|
229
|
+
✓ State file (.ak/state.json) exists
|
|
230
|
+
✓ Target directory exists (.claude)
|
|
231
|
+
✓ AGENTS.md exists
|
|
232
|
+
✓ Source directory accessible
|
|
233
|
+
✓ agents/ exists (15 items)
|
|
234
|
+
✓ commands/ exists (40 items)
|
|
235
|
+
✓ skills/ exists (25 items)
|
|
236
|
+
|
|
237
|
+
────────────────────────────────────────
|
|
238
|
+
|
|
239
|
+
✓ All checks passed!
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## License
|
|
243
|
+
|
|
244
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
245
|
+
|
|
246
|
+
## Contributing
|
|
247
|
+
|
|
248
|
+
Contributions are welcome! Please open an issue or submit a pull request.
|
|
249
|
+
|
|
250
|
+
## Acknowledgments
|
|
251
|
+
|
|
252
|
+
Built for use with [Claude Code](https://claude.ai/code), [OpenCode](https://opencode.dev), and similar AI coding assistants.
|
package/bin/ak.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { program } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { initCommand } from '../src/commands/init.js';
|
|
6
|
+
import { addCommand } from '../src/commands/add.js';
|
|
7
|
+
import { listCommand } from '../src/commands/list.js';
|
|
8
|
+
import { updateCommand } from '../src/commands/update.js';
|
|
9
|
+
import { statusCommand } from '../src/commands/status.js';
|
|
10
|
+
import { doctorCommand } from '../src/commands/doctor.js';
|
|
11
|
+
|
|
12
|
+
const VERSION = '1.0.0';
|
|
13
|
+
|
|
14
|
+
console.log(chalk.cyan.bold('\n Apero Kit CLI') + chalk.gray(` v${VERSION}\n`));
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('ak')
|
|
18
|
+
.description('Scaffold AI agent projects with pre-configured kits')
|
|
19
|
+
.version(VERSION);
|
|
20
|
+
|
|
21
|
+
// ak init [project-name] --kit <kit-type>
|
|
22
|
+
program
|
|
23
|
+
.command('init [project-name]')
|
|
24
|
+
.description('Initialize a new project with an agent kit')
|
|
25
|
+
.option('-k, --kit <type>', 'Kit type (engineer, researcher, designer, minimal, full, custom)')
|
|
26
|
+
.option('-t, --target <target>', 'Target folder (claude, opencode, generic)', 'claude')
|
|
27
|
+
.option('-s, --source <path>', 'Custom source path for templates')
|
|
28
|
+
.option('-f, --force', 'Overwrite existing directory')
|
|
29
|
+
.action(initCommand);
|
|
30
|
+
|
|
31
|
+
// ak add <type>:<name>
|
|
32
|
+
program
|
|
33
|
+
.command('add <item>')
|
|
34
|
+
.description('Add agent, skill, or command (e.g., ak add skill:databases)')
|
|
35
|
+
.option('-s, --source <path>', 'Custom source path')
|
|
36
|
+
.option('-p, --path <path>', 'Target project path', '.')
|
|
37
|
+
.action(addCommand);
|
|
38
|
+
|
|
39
|
+
// ak list [type]
|
|
40
|
+
program
|
|
41
|
+
.command('list [type]')
|
|
42
|
+
.description('List available kits, agents, skills, or commands')
|
|
43
|
+
.option('-s, --source <path>', 'Custom source path')
|
|
44
|
+
.action(listCommand);
|
|
45
|
+
|
|
46
|
+
// ak update
|
|
47
|
+
program
|
|
48
|
+
.command('update')
|
|
49
|
+
.description('Update/sync from source templates')
|
|
50
|
+
.option('-s, --source <path>', 'Source path to sync from')
|
|
51
|
+
.option('--agents', 'Update only agents')
|
|
52
|
+
.option('--skills', 'Update only skills')
|
|
53
|
+
.option('--commands', 'Update only commands')
|
|
54
|
+
.option('--all', 'Update everything')
|
|
55
|
+
.option('-n, --dry-run', 'Show what would be updated without making changes')
|
|
56
|
+
.option('-f, --force', 'Force update without confirmation')
|
|
57
|
+
.action(updateCommand);
|
|
58
|
+
|
|
59
|
+
// ak status
|
|
60
|
+
program
|
|
61
|
+
.command('status')
|
|
62
|
+
.description('Show project status and file changes')
|
|
63
|
+
.option('-v, --verbose', 'Show all files')
|
|
64
|
+
.action(statusCommand);
|
|
65
|
+
|
|
66
|
+
// ak doctor
|
|
67
|
+
program
|
|
68
|
+
.command('doctor')
|
|
69
|
+
.description('Check project health and diagnose issues')
|
|
70
|
+
.action(doctorCommand);
|
|
71
|
+
|
|
72
|
+
// ak kits - alias
|
|
73
|
+
program
|
|
74
|
+
.command('kits')
|
|
75
|
+
.description('List all available kits')
|
|
76
|
+
.action(() => listCommand('kits'));
|
|
77
|
+
|
|
78
|
+
program.parse();
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "apero-kit-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CLI tool to scaffold AI agent projects with pre-configured kits (Claude, OpenCode, Codex)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ak": "bin/ak.js",
|
|
8
|
+
"apero-kit": "bin/ak.js"
|
|
9
|
+
},
|
|
10
|
+
"main": "src/index.js",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.js",
|
|
13
|
+
"./kits": "./src/kits/index.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"start": "node bin/ak.js",
|
|
17
|
+
"link": "npm link",
|
|
18
|
+
"unlink": "npm unlink",
|
|
19
|
+
"test": "node --test src/**/*.test.js",
|
|
20
|
+
"prepublishOnly": "echo 'Ready to publish!'"
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"bin",
|
|
24
|
+
"src",
|
|
25
|
+
"README.md",
|
|
26
|
+
"LICENSE"
|
|
27
|
+
],
|
|
28
|
+
"keywords": [
|
|
29
|
+
"ai",
|
|
30
|
+
"agent",
|
|
31
|
+
"claude",
|
|
32
|
+
"claude-code",
|
|
33
|
+
"opencode",
|
|
34
|
+
"codex",
|
|
35
|
+
"cli",
|
|
36
|
+
"scaffold",
|
|
37
|
+
"template",
|
|
38
|
+
"llm",
|
|
39
|
+
"apero"
|
|
40
|
+
],
|
|
41
|
+
"author": "Apero Team",
|
|
42
|
+
"license": "MIT",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/Thanh-apero/apero-kit-cli.git"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/Thanh-apero/apero-kit-cli#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/Thanh-apero/apero-kit-cli/issues"
|
|
50
|
+
},
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"chalk": "^5.3.0",
|
|
56
|
+
"commander": "^12.1.0",
|
|
57
|
+
"fs-extra": "^11.2.0",
|
|
58
|
+
"inquirer": "^9.2.15",
|
|
59
|
+
"ora": "^8.0.1"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
import { resolveSource, isAkProject } from '../utils/paths.js';
|
|
6
|
+
import { copyItems, copyAllOfType, listAvailable } from '../utils/copy.js';
|
|
7
|
+
import { loadState, updateState } from '../utils/state.js';
|
|
8
|
+
import { hashDirectory } from '../utils/hash.js';
|
|
9
|
+
|
|
10
|
+
export async function addCommand(item, options = {}) {
|
|
11
|
+
// Parse item format: type:name (e.g., skill:databases, agent:debugger)
|
|
12
|
+
const parts = item.split(':');
|
|
13
|
+
if (parts.length !== 2) {
|
|
14
|
+
console.log(chalk.red('Invalid format. Use: ak add <type>:<name>'));
|
|
15
|
+
console.log(chalk.gray('Examples:'));
|
|
16
|
+
console.log(chalk.gray(' ak add skill:databases'));
|
|
17
|
+
console.log(chalk.gray(' ak add agent:debugger'));
|
|
18
|
+
console.log(chalk.gray(' ak add command:fix/ci'));
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const [type, name] = parts;
|
|
23
|
+
const validTypes = ['agent', 'agents', 'skill', 'skills', 'command', 'commands', 'workflow', 'workflows'];
|
|
24
|
+
|
|
25
|
+
if (!validTypes.includes(type)) {
|
|
26
|
+
console.log(chalk.red(`Invalid type: ${type}`));
|
|
27
|
+
console.log(chalk.gray(`Valid types: agent, skill, command, workflow`));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Normalize type to plural
|
|
32
|
+
const typeMap = {
|
|
33
|
+
agent: 'agents',
|
|
34
|
+
agents: 'agents',
|
|
35
|
+
skill: 'skills',
|
|
36
|
+
skills: 'skills',
|
|
37
|
+
command: 'commands',
|
|
38
|
+
commands: 'commands',
|
|
39
|
+
workflow: 'workflows',
|
|
40
|
+
workflows: 'workflows'
|
|
41
|
+
};
|
|
42
|
+
const normalizedType = typeMap[type];
|
|
43
|
+
|
|
44
|
+
const projectDir = options.path || process.cwd();
|
|
45
|
+
|
|
46
|
+
// Check if in ak project
|
|
47
|
+
if (!isAkProject(projectDir)) {
|
|
48
|
+
console.log(chalk.red('Not in an ak project.'));
|
|
49
|
+
console.log(chalk.gray('Run "ak init" first or use --path to specify project directory.'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Load state
|
|
54
|
+
const state = await loadState(projectDir);
|
|
55
|
+
if (!state) {
|
|
56
|
+
console.log(chalk.yellow('Warning: No state file found. Creating fresh state after add.'));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Resolve source
|
|
60
|
+
const sourceFlag = options.source || (state ? state.source : null);
|
|
61
|
+
const source = resolveSource(sourceFlag);
|
|
62
|
+
if (source.error) {
|
|
63
|
+
console.log(chalk.red(`Error: ${source.error}`));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check if item exists in source
|
|
68
|
+
const available = listAvailable(normalizedType, source.claudeDir);
|
|
69
|
+
const exists = available.some(a => a.name === name);
|
|
70
|
+
|
|
71
|
+
if (!exists) {
|
|
72
|
+
console.log(chalk.red(`${type} "${name}" not found in source.`));
|
|
73
|
+
console.log(chalk.gray(`Use "ak list ${normalizedType}" to see available options.`));
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Determine target directory
|
|
78
|
+
const targetFolder = state?.target || '.claude';
|
|
79
|
+
const targetDir = join(projectDir, targetFolder);
|
|
80
|
+
|
|
81
|
+
// Check if already installed
|
|
82
|
+
const destPath = join(targetDir, normalizedType, name);
|
|
83
|
+
const destPathMd = destPath + '.md';
|
|
84
|
+
if (fs.existsSync(destPath) || fs.existsSync(destPathMd)) {
|
|
85
|
+
console.log(chalk.yellow(`${type} "${name}" already exists in project.`));
|
|
86
|
+
console.log(chalk.gray('Use "ak update" to refresh from source.'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Copy
|
|
91
|
+
const spinner = ora(`Adding ${type} "${name}"...`).start();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const result = await copyItems([name], normalizedType, source.claudeDir, targetDir);
|
|
95
|
+
|
|
96
|
+
if (result.copied.length > 0) {
|
|
97
|
+
spinner.succeed(chalk.green(`Added ${type}: ${name}`));
|
|
98
|
+
|
|
99
|
+
// Update state
|
|
100
|
+
if (state) {
|
|
101
|
+
const installed = state.installed || {};
|
|
102
|
+
installed[normalizedType] = installed[normalizedType] || [];
|
|
103
|
+
if (!installed[normalizedType].includes(name)) {
|
|
104
|
+
installed[normalizedType].push(name);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Recalculate hashes
|
|
108
|
+
const newHashes = await hashDirectory(targetDir);
|
|
109
|
+
|
|
110
|
+
await updateState(projectDir, {
|
|
111
|
+
installed,
|
|
112
|
+
originalHashes: newHashes
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(chalk.gray(`Location: ${targetFolder}/${normalizedType}/${name}`));
|
|
117
|
+
} else if (result.skipped.length > 0) {
|
|
118
|
+
spinner.fail(chalk.red(`Could not find ${type}: ${name}`));
|
|
119
|
+
} else if (result.errors.length > 0) {
|
|
120
|
+
spinner.fail(chalk.red(`Error adding ${type}: ${result.errors[0].error}`));
|
|
121
|
+
}
|
|
122
|
+
} catch (error) {
|
|
123
|
+
spinner.fail(chalk.red(`Failed to add ${type}`));
|
|
124
|
+
console.error(chalk.red(error.message));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import { isAkProject, resolveSource, TARGETS } from '../utils/paths.js';
|
|
5
|
+
import { loadState } from '../utils/state.js';
|
|
6
|
+
|
|
7
|
+
export async function doctorCommand(options = {}) {
|
|
8
|
+
const projectDir = process.cwd();
|
|
9
|
+
|
|
10
|
+
console.log(chalk.cyan.bold('\nApero Kit Doctor\n'));
|
|
11
|
+
console.log(chalk.gray('Checking project health...\n'));
|
|
12
|
+
|
|
13
|
+
let issues = 0;
|
|
14
|
+
let warnings = 0;
|
|
15
|
+
|
|
16
|
+
// Check 1: Is this an ak project?
|
|
17
|
+
const isProject = isAkProject(projectDir);
|
|
18
|
+
if (isProject) {
|
|
19
|
+
console.log(chalk.green('✓ ak project detected'));
|
|
20
|
+
} else {
|
|
21
|
+
console.log(chalk.red('✗ Not an ak project'));
|
|
22
|
+
issues++;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Check 2: State file
|
|
26
|
+
const state = await loadState(projectDir);
|
|
27
|
+
if (state) {
|
|
28
|
+
console.log(chalk.green('✓ State file (.ak/state.json) exists'));
|
|
29
|
+
} else if (isProject) {
|
|
30
|
+
console.log(chalk.yellow('⚠ No state file - project may have been created manually'));
|
|
31
|
+
warnings++;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check 3: Target directory
|
|
35
|
+
let targetDir = null;
|
|
36
|
+
for (const [name, folder] of Object.entries(TARGETS)) {
|
|
37
|
+
const dir = join(projectDir, folder);
|
|
38
|
+
if (fs.existsSync(dir)) {
|
|
39
|
+
targetDir = { name, folder, dir };
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (targetDir) {
|
|
45
|
+
console.log(chalk.green(`✓ Target directory exists (${targetDir.folder})`));
|
|
46
|
+
} else {
|
|
47
|
+
console.log(chalk.red('✗ No target directory (.claude/, .opencode/, .agent/)'));
|
|
48
|
+
issues++;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check 4: AGENTS.md
|
|
52
|
+
const agentsMd = join(projectDir, 'AGENTS.md');
|
|
53
|
+
if (fs.existsSync(agentsMd)) {
|
|
54
|
+
console.log(chalk.green('✓ AGENTS.md exists'));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(chalk.yellow('⚠ AGENTS.md not found (optional but recommended)'));
|
|
57
|
+
warnings++;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check 5: Source availability
|
|
61
|
+
if (state && state.source) {
|
|
62
|
+
if (fs.existsSync(state.source)) {
|
|
63
|
+
console.log(chalk.green(`✓ Source directory accessible`));
|
|
64
|
+
} else {
|
|
65
|
+
console.log(chalk.red(`✗ Source directory not found: ${state.source}`));
|
|
66
|
+
issues++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Check 6: Key subdirectories
|
|
71
|
+
if (targetDir) {
|
|
72
|
+
const checkDirs = ['agents', 'commands', 'skills'];
|
|
73
|
+
for (const dir of checkDirs) {
|
|
74
|
+
const fullPath = join(targetDir.dir, dir);
|
|
75
|
+
if (fs.existsSync(fullPath)) {
|
|
76
|
+
const items = fs.readdirSync(fullPath).length;
|
|
77
|
+
console.log(chalk.green(`✓ ${dir}/ exists (${items} items)`));
|
|
78
|
+
} else {
|
|
79
|
+
console.log(chalk.yellow(`⚠ ${dir}/ not found`));
|
|
80
|
+
warnings++;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Check 7: Auto-detect source in parent directories
|
|
86
|
+
console.log('');
|
|
87
|
+
console.log(chalk.gray('Source detection:'));
|
|
88
|
+
const source = resolveSource();
|
|
89
|
+
if (source.error) {
|
|
90
|
+
console.log(chalk.yellow(` ⚠ ${source.error}`));
|
|
91
|
+
warnings++;
|
|
92
|
+
} else {
|
|
93
|
+
console.log(chalk.green(` ✓ Found source: ${source.path}`));
|
|
94
|
+
console.log(chalk.gray(` Type: ${source.type || 'unknown'}`));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Summary
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log(chalk.cyan('─'.repeat(40)));
|
|
100
|
+
|
|
101
|
+
if (issues === 0 && warnings === 0) {
|
|
102
|
+
console.log(chalk.green.bold('\n✓ All checks passed!\n'));
|
|
103
|
+
} else if (issues === 0) {
|
|
104
|
+
console.log(chalk.yellow(`\n⚠ ${warnings} warning(s), no critical issues\n`));
|
|
105
|
+
} else {
|
|
106
|
+
console.log(chalk.red(`\n✗ ${issues} issue(s), ${warnings} warning(s)\n`));
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Suggestions
|
|
110
|
+
if (issues > 0 || warnings > 0) {
|
|
111
|
+
console.log(chalk.cyan('Suggestions:'));
|
|
112
|
+
|
|
113
|
+
if (!isProject) {
|
|
114
|
+
console.log(chalk.white(' • Run "ak init ." to initialize this directory'));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!state && isProject) {
|
|
118
|
+
console.log(chalk.white(' • State file is missing. Re-run "ak init" or create manually'));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (state && state.source && !fs.existsSync(state.source)) {
|
|
122
|
+
console.log(chalk.white(' • Update source path: ak update --source <new-path>'));
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log('');
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { issues, warnings };
|
|
129
|
+
}
|