@arthai/agents 1.0.4 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -3
- package/VERSION +1 -1
- package/agents/troubleshooter.md +132 -0
- package/bin/cli.js +366 -0
- package/bundles/canvas.json +1 -1
- package/bundles/compass.json +1 -1
- package/bundles/counsel.json +1 -0
- package/bundles/cruise.json +1 -1
- package/bundles/forge.json +12 -1
- package/bundles/prism.json +1 -0
- package/bundles/scalpel.json +5 -2
- package/bundles/sentinel.json +8 -2
- package/bundles/shield.json +1 -0
- package/bundles/spark.json +1 -0
- package/compiler.sh +14 -0
- package/dist/plugins/canvas/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/canvas/VERSION +1 -0
- package/dist/plugins/canvas/commands/planning.md +100 -11
- package/dist/plugins/canvas/hooks/hooks.json +16 -0
- package/dist/plugins/canvas/hooks/project-setup.sh +109 -0
- package/dist/plugins/canvas/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/canvas/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/compass/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/compass/VERSION +1 -0
- package/dist/plugins/compass/commands/planning.md +100 -11
- package/dist/plugins/compass/hooks/hooks.json +16 -0
- package/dist/plugins/compass/hooks/project-setup.sh +109 -0
- package/dist/plugins/compass/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/compass/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/counsel/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/counsel/VERSION +1 -0
- package/dist/plugins/counsel/hooks/hooks.json +10 -0
- package/dist/plugins/counsel/hooks/project-setup.sh +109 -0
- package/dist/plugins/counsel/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/counsel/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/cruise/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/cruise/VERSION +1 -0
- package/dist/plugins/cruise/hooks/hooks.json +16 -0
- package/dist/plugins/cruise/hooks/project-setup.sh +109 -0
- package/dist/plugins/cruise/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/cruise/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/forge/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/forge/VERSION +1 -0
- package/dist/plugins/forge/agents/troubleshooter.md +132 -0
- package/dist/plugins/forge/commands/implement.md +99 -1
- package/dist/plugins/forge/commands/planning.md +100 -11
- package/dist/plugins/forge/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/forge/hooks/hooks.json +22 -0
- package/dist/plugins/forge/hooks/project-setup.sh +109 -0
- package/dist/plugins/forge/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/forge/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/prime/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/prime/VERSION +1 -0
- package/dist/plugins/prime/agents/troubleshooter.md +132 -0
- package/dist/plugins/prime/commands/calibrate.md +20 -0
- package/dist/plugins/prime/commands/ci-fix.md +36 -0
- package/dist/plugins/prime/commands/fix.md +23 -0
- package/dist/plugins/prime/commands/implement.md +99 -1
- package/dist/plugins/prime/commands/planning.md +100 -11
- package/dist/plugins/prime/commands/qa-incident.md +54 -0
- package/dist/plugins/prime/commands/restart.md +186 -30
- package/dist/plugins/prime/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/prime/hooks/hooks.json +60 -0
- package/dist/plugins/prime/hooks/post-config-change-restart-reminder.sh +86 -0
- package/dist/plugins/prime/hooks/post-server-crash-watch.sh +120 -0
- package/dist/plugins/prime/hooks/pre-server-port-guard.sh +110 -0
- package/dist/plugins/prime/hooks/project-setup.sh +109 -0
- package/dist/plugins/prime/hooks/sync-agents.sh +99 -12
- package/dist/plugins/prime/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/prime/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/prism/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/prism/VERSION +1 -0
- package/dist/plugins/prism/commands/qa-incident.md +54 -0
- package/dist/plugins/prism/hooks/hooks.json +12 -0
- package/dist/plugins/prism/hooks/project-setup.sh +109 -0
- package/dist/plugins/prism/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/prism/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/scalpel/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/scalpel/VERSION +1 -0
- package/dist/plugins/scalpel/agents/troubleshooter.md +132 -0
- package/dist/plugins/scalpel/commands/ci-fix.md +36 -0
- package/dist/plugins/scalpel/commands/fix.md +23 -0
- package/dist/plugins/scalpel/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/scalpel/hooks/hooks.json +24 -0
- package/dist/plugins/scalpel/hooks/project-setup.sh +109 -0
- package/dist/plugins/scalpel/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/scalpel/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/sentinel/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/sentinel/VERSION +1 -0
- package/dist/plugins/sentinel/agents/troubleshooter.md +132 -0
- package/dist/plugins/sentinel/commands/restart.md +186 -30
- package/dist/plugins/sentinel/hooks/escalation-guard.sh +177 -0
- package/dist/plugins/sentinel/hooks/hooks.json +64 -0
- package/dist/plugins/sentinel/hooks/post-config-change-restart-reminder.sh +86 -0
- package/dist/plugins/sentinel/hooks/post-server-crash-watch.sh +120 -0
- package/dist/plugins/sentinel/hooks/pre-server-port-guard.sh +110 -0
- package/dist/plugins/sentinel/hooks/project-setup.sh +109 -0
- package/dist/plugins/sentinel/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/sentinel/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/shield/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/shield/VERSION +1 -0
- package/dist/plugins/shield/hooks/hooks.json +22 -12
- package/dist/plugins/shield/hooks/project-setup.sh +109 -0
- package/dist/plugins/shield/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/shield/templates/CLAUDE.md.template +111 -0
- package/dist/plugins/spark/.claude-plugin/plugin.json +1 -1
- package/dist/plugins/spark/VERSION +1 -0
- package/dist/plugins/spark/commands/calibrate.md +20 -0
- package/dist/plugins/spark/hooks/hooks.json +10 -0
- package/dist/plugins/spark/hooks/project-setup.sh +109 -0
- package/dist/plugins/spark/templates/CLAUDE.md.managed-block +123 -0
- package/dist/plugins/spark/templates/CLAUDE.md.template +111 -0
- package/hook-defs.json +31 -0
- package/hooks/escalation-guard.sh +177 -0
- package/hooks/post-config-change-restart-reminder.sh +86 -0
- package/hooks/post-server-crash-watch.sh +120 -0
- package/hooks/pre-server-port-guard.sh +110 -0
- package/hooks/project-setup.sh +109 -0
- package/hooks/sync-agents.sh +99 -12
- package/install.sh +2 -2
- package/package.json +1 -1
- package/portable.manifest +7 -1
- package/skills/calibrate/SKILL.md +20 -0
- package/skills/ci-fix/SKILL.md +36 -0
- package/skills/fix/SKILL.md +23 -0
- package/skills/implement/SKILL.md +99 -1
- package/skills/license/SKILL.md +159 -0
- package/skills/planning/SKILL.md +100 -11
- package/skills/publish/SKILL.md +3 -0
- package/skills/qa-incident/SKILL.md +54 -0
- package/skills/restart/SKILL.md +187 -31
package/README.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
AI development toolkit for Claude Code — agents, skills, and hooks organized into installable bundles.
|
|
4
4
|
|
|
5
|
+
## Quick Start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# 1. Activate your license (one time)
|
|
9
|
+
npx @arthai/agents activate ARTH-XXXX-XXXX-XXXX-XXXX
|
|
10
|
+
|
|
11
|
+
# 2. Install a bundle into your project
|
|
12
|
+
npx @arthai/agents install forge .
|
|
13
|
+
|
|
14
|
+
# 3. Open Claude Code — skills are ready
|
|
15
|
+
/calibrate # auto-configure for your codebase
|
|
16
|
+
/planning my-feature # start building
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Don't have a key? Contact us at arthai.dev/pricing
|
|
20
|
+
|
|
5
21
|
## Install
|
|
6
22
|
|
|
7
23
|
```bash
|
|
@@ -11,10 +27,10 @@ npx @arthai/agents install forge .
|
|
|
11
27
|
# Install multiple bundles
|
|
12
28
|
npx @arthai/agents install forge spark shield .
|
|
13
29
|
|
|
14
|
-
# See all available bundles
|
|
30
|
+
# See all available bundles (no license needed)
|
|
15
31
|
npx @arthai/agents list
|
|
16
32
|
|
|
17
|
-
# Show what's in a bundle
|
|
33
|
+
# Show what's in a bundle (no license needed)
|
|
18
34
|
npx @arthai/agents info forge
|
|
19
35
|
```
|
|
20
36
|
|
|
@@ -90,6 +106,28 @@ After installing, use skills in Claude Code:
|
|
|
90
106
|
/restart # restart local dev servers
|
|
91
107
|
```
|
|
92
108
|
|
|
109
|
+
## Uninstall
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
npx @arthai/agents uninstall forge .
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Remove everything:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
rm -rf .claude/skills .claude/agents .claude/hooks
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Note: hooks merged into `.claude/settings.json` need to be removed manually.
|
|
122
|
+
|
|
123
|
+
## Troubleshooting
|
|
124
|
+
|
|
125
|
+
| Problem | Fix |
|
|
126
|
+
|---------|-----|
|
|
127
|
+
| Skills not showing | Restart Claude Code in the project |
|
|
128
|
+
| `/planning` says unknown skill | Verify files exist: `ls .claude/skills/planning/SKILL.md` |
|
|
129
|
+
| Install says "not found in dist/" | Update: `npx @arthai/agents@latest install forge .` |
|
|
130
|
+
|
|
93
131
|
## Also available as Claude Code plugin
|
|
94
132
|
|
|
95
133
|
```
|
|
@@ -97,8 +135,22 @@ After installing, use skills in Claude Code:
|
|
|
97
135
|
/plugin install forge@arthai-marketplace
|
|
98
136
|
```
|
|
99
137
|
|
|
138
|
+
## License Activation
|
|
139
|
+
|
|
140
|
+
A license key is required to install bundles. Browse and explore bundles without a key using `list` and `info`.
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
# Activate (one time — saves key locally)
|
|
144
|
+
npx @arthai/agents activate ARTH-XXXX-XXXX-XXXX-XXXX
|
|
145
|
+
|
|
146
|
+
# Key is stored at ~/.arthai/license — all future installs use it automatically
|
|
147
|
+
# You can also set ARTHAI_LICENSE_KEY env var (useful for CI/teams)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
**Key precedence:** `ARTHAI_LICENSE_KEY` env var > `~/.arthai/license` file > `--key` flag
|
|
151
|
+
|
|
100
152
|
## Requirements
|
|
101
153
|
|
|
102
154
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code)
|
|
103
155
|
- Node.js 18+
|
|
104
|
-
- License key (
|
|
156
|
+
- License key (get one at arthai.dev/pricing)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.6
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: troubleshooter
|
|
3
|
+
description: "Specialized debugging agent for when other agents get stuck. Performs root cause analysis using error context, knowledge base, git history, and CLAUDE.md. Produces structured diagnosis with confidence level and recommended fix."
|
|
4
|
+
model: sonnet
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Troubleshooter Agent
|
|
8
|
+
|
|
9
|
+
You are a specialized debugging agent. You are called when another agent or workflow
|
|
10
|
+
has failed multiple times and needs expert diagnosis.
|
|
11
|
+
|
|
12
|
+
## When You Are Spawned
|
|
13
|
+
|
|
14
|
+
Another agent has hit a wall — they've tried 2-3 fixes and keep failing. Your job
|
|
15
|
+
is to diagnose the root cause and provide a fix with confidence rating.
|
|
16
|
+
|
|
17
|
+
## Your Process (follow in order)
|
|
18
|
+
|
|
19
|
+
### 1. Understand the Problem (DO NOT SKIP)
|
|
20
|
+
|
|
21
|
+
Read the error context provided in your spawn prompt. Extract:
|
|
22
|
+
- **Exact error message** (not paraphrased)
|
|
23
|
+
- **What was being attempted** (the goal, not just the command)
|
|
24
|
+
- **What has already been tried** (and why each attempt failed)
|
|
25
|
+
- **The file(s) involved**
|
|
26
|
+
|
|
27
|
+
### 2. Consult Knowledge Base (BEFORE forming any hypothesis)
|
|
28
|
+
|
|
29
|
+
Check these sources in order:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
.claude/knowledge/qa-knowledge/ → past incidents with error signatures
|
|
33
|
+
.claude/knowledge/shared/conventions.md → project-specific gotchas and rules
|
|
34
|
+
.claude/knowledge/shared/patterns.md → architecture patterns that may explain the error
|
|
35
|
+
.claude/knowledge/agents/ → per-agent learning files
|
|
36
|
+
CLAUDE.md → project configuration, test commands, services
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Search for:
|
|
40
|
+
- The exact error message (or key phrases)
|
|
41
|
+
- The file/module involved
|
|
42
|
+
- The command that failed
|
|
43
|
+
- Similar past incidents
|
|
44
|
+
|
|
45
|
+
**If you find a match:** Follow the documented fix. Do not reinvent.
|
|
46
|
+
**If no match:** Proceed to step 3.
|
|
47
|
+
|
|
48
|
+
### 3. Gather Fresh Evidence
|
|
49
|
+
|
|
50
|
+
Read the actual source code around the error:
|
|
51
|
+
- The file mentioned in the error (read 50+ lines of context, not just the error line)
|
|
52
|
+
- Related files (imports, callers, configuration)
|
|
53
|
+
- Recent changes: `git log --oneline -10 -- <file>` and `git diff HEAD -- <file>`
|
|
54
|
+
|
|
55
|
+
Check the environment:
|
|
56
|
+
- `git status` — are there uncommitted changes that might cause the issue?
|
|
57
|
+
- Check if the right dependencies are installed (node_modules, venv, etc.)
|
|
58
|
+
- Check if services are running (ports, Docker containers)
|
|
59
|
+
- Check environment variables that the code expects
|
|
60
|
+
|
|
61
|
+
### 4. Form Hypothesis (evidence-based only)
|
|
62
|
+
|
|
63
|
+
Based on steps 2-3, form ONE primary hypothesis and optionally one alternative.
|
|
64
|
+
Each hypothesis MUST cite evidence:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
HYPOTHESIS: [what I think is wrong]
|
|
68
|
+
EVIDENCE:
|
|
69
|
+
- [source]: [what I found that supports this]
|
|
70
|
+
- [source]: [what I found that supports this]
|
|
71
|
+
CONFIDENCE: HIGH / MEDIUM / LOW
|
|
72
|
+
- HIGH: evidence directly explains the error, fix is clear
|
|
73
|
+
- MEDIUM: evidence is consistent but not conclusive
|
|
74
|
+
- LOW: best guess based on limited evidence
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 5. Recommend Fix
|
|
78
|
+
|
|
79
|
+
Provide a specific, actionable fix:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
RECOMMENDED FIX:
|
|
83
|
+
File: [exact file path]
|
|
84
|
+
Change: [what to modify — be specific, not vague]
|
|
85
|
+
Why: [how this addresses the root cause]
|
|
86
|
+
Verify: [command to run to confirm the fix works]
|
|
87
|
+
|
|
88
|
+
ALTERNATIVE FIX (if confidence < HIGH):
|
|
89
|
+
File: [exact file path]
|
|
90
|
+
Change: [what to modify]
|
|
91
|
+
Why: [different hypothesis this addresses]
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### 6. Output Format
|
|
95
|
+
|
|
96
|
+
Always produce this structured output:
|
|
97
|
+
|
|
98
|
+
```markdown
|
|
99
|
+
## Troubleshooter Diagnosis
|
|
100
|
+
|
|
101
|
+
**Error:** [exact error]
|
|
102
|
+
**Root Cause:** [1-2 sentence explanation]
|
|
103
|
+
**Confidence:** HIGH / MEDIUM / LOW
|
|
104
|
+
|
|
105
|
+
### Evidence
|
|
106
|
+
- [source 1]: [finding]
|
|
107
|
+
- [source 2]: [finding]
|
|
108
|
+
- Knowledge base: [match found / no match]
|
|
109
|
+
|
|
110
|
+
### Recommended Fix
|
|
111
|
+
- File: [path]
|
|
112
|
+
- Change: [specific change]
|
|
113
|
+
- Verify: [command]
|
|
114
|
+
|
|
115
|
+
### What Was Wrong With Previous Attempts
|
|
116
|
+
- Attempt 1: [why it didn't work — specific reason]
|
|
117
|
+
- Attempt 2: [why it didn't work — specific reason]
|
|
118
|
+
|
|
119
|
+
### If This Doesn't Work
|
|
120
|
+
- [Next diagnostic step to try]
|
|
121
|
+
- [What data to gather]
|
|
122
|
+
- [Whether to escalate to user — and what to ask them]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Rules
|
|
126
|
+
|
|
127
|
+
1. **Never guess.** Every claim must cite evidence from code, logs, KB, or git history.
|
|
128
|
+
2. **Check KB first.** If a past incident matches, use that fix. Don't reinvent.
|
|
129
|
+
3. **Be specific.** "Check the config" is not a fix. "Change line 42 of config.ts from X to Y" is.
|
|
130
|
+
4. **Explain why previous attempts failed.** This is as valuable as the fix itself.
|
|
131
|
+
5. **Know when to escalate.** If confidence is LOW and you can't gather more evidence, say so. Recommend what data to ask the user for.
|
|
132
|
+
6. **Don't try the fix yourself.** Your job is diagnosis. The calling agent implements the fix.
|
package/bin/cli.js
CHANGED
|
@@ -13,10 +13,21 @@ const BUNDLES_DIR = path.join(TOOLKIT_DIR, 'bundles');
|
|
|
13
13
|
const PLUGINS_DIR = path.join(TOOLKIT_DIR, 'dist', 'plugins');
|
|
14
14
|
const INSTALL_SH = path.join(TOOLKIT_DIR, 'install.sh');
|
|
15
15
|
|
|
16
|
+
const WORKER_URL = 'https://license-worker.muddassar-shaikh.workers.dev';
|
|
17
|
+
const KEY_PATTERN = /^ARTH-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
|
|
18
|
+
const ARTHAI_DIR = path.join(process.env.HOME || '~', '.arthai');
|
|
19
|
+
const LICENSE_FILE_ARTHAI = path.join(ARTHAI_DIR, 'license');
|
|
20
|
+
const LICENSE_FILE_AGENTS = path.join(process.env.HOME || '~', '.claude-agents', '.license');
|
|
21
|
+
const VALIDATION_CACHE_FILE = path.join(ARTHAI_DIR, 'validation-cache');
|
|
22
|
+
const VALIDATION_CACHE_TTL = 86400; // 24 hours in seconds
|
|
23
|
+
|
|
16
24
|
const args = process.argv.slice(2);
|
|
17
25
|
const command = args[0];
|
|
18
26
|
|
|
19
27
|
switch (command) {
|
|
28
|
+
case 'activate':
|
|
29
|
+
handleActivate(args[1]);
|
|
30
|
+
break;
|
|
20
31
|
case 'install':
|
|
21
32
|
handleInstall(args.slice(1));
|
|
22
33
|
break;
|
|
@@ -26,6 +37,10 @@ switch (command) {
|
|
|
26
37
|
case 'info':
|
|
27
38
|
handleInfo(args[1]);
|
|
28
39
|
break;
|
|
40
|
+
case 'uninstall':
|
|
41
|
+
case 'remove':
|
|
42
|
+
handleUninstall(args.slice(1));
|
|
43
|
+
break;
|
|
29
44
|
case 'legacy':
|
|
30
45
|
handleLegacy(args.slice(1));
|
|
31
46
|
break;
|
|
@@ -84,6 +99,24 @@ function handleInstall(rawArgs) {
|
|
|
84
99
|
process.exit(1);
|
|
85
100
|
}
|
|
86
101
|
|
|
102
|
+
// License gate — must validate before any installation
|
|
103
|
+
const licenseKey = readLicenseKey(rawArgs);
|
|
104
|
+
if (!licenseKey) {
|
|
105
|
+
console.error('License required. Run: npx @arthai/agents activate ARTH-XXXX-XXXX-XXXX-XXXX');
|
|
106
|
+
console.error('Get a license at arthai.dev/pricing');
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
const licenseResult = validateWithWorker(licenseKey);
|
|
110
|
+
if (!licenseResult.valid) {
|
|
111
|
+
if (licenseResult.networkError) {
|
|
112
|
+
console.error('License server unavailable. Cannot install without a validated license.');
|
|
113
|
+
console.error('Check your internet connection and try again.');
|
|
114
|
+
} else {
|
|
115
|
+
console.error('Invalid license key. Check your key or get one at arthai.dev/pricing');
|
|
116
|
+
}
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
|
|
87
120
|
// Resolve dependencies (topological order)
|
|
88
121
|
const resolved = resolveDependencies(bundleNames);
|
|
89
122
|
console.log(`Installing: ${resolved.join(' → ')}`);
|
|
@@ -119,6 +152,9 @@ function handleInstall(rawArgs) {
|
|
|
119
152
|
totalHooks += counts.hooks;
|
|
120
153
|
}
|
|
121
154
|
|
|
155
|
+
// Post-install project setup (parity with install.sh)
|
|
156
|
+
setupProjectClaude(targetDir);
|
|
157
|
+
|
|
122
158
|
console.log(`\nDone. ${resolved.length} bundle(s) installed: ${totalCommands} skills, ${totalAgents} agents, ${totalHooks} hooks`);
|
|
123
159
|
}
|
|
124
160
|
|
|
@@ -207,6 +243,246 @@ function handleLegacy(legacyArgs) {
|
|
|
207
243
|
process.exit(result.status || 0);
|
|
208
244
|
}
|
|
209
245
|
|
|
246
|
+
// ── activate ──────────────────────────────────────────────────────────────────
|
|
247
|
+
|
|
248
|
+
function handleActivate(key) {
|
|
249
|
+
if (!key) {
|
|
250
|
+
console.error('Usage: arthai activate <ARTH-XXXX-XXXX-XXXX-XXXX>');
|
|
251
|
+
process.exit(1);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!KEY_PATTERN.test(key)) {
|
|
255
|
+
console.error(`Invalid key format: "${key}"`);
|
|
256
|
+
console.error('Keys must be in the format ARTH-XXXX-XXXX-XXXX-XXXX (uppercase letters and digits)');
|
|
257
|
+
process.exit(1);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const machineId = computeMachineId();
|
|
261
|
+
const result = postToWorker('/activate', { key, machine_id: machineId });
|
|
262
|
+
|
|
263
|
+
if (result.networkError) {
|
|
264
|
+
console.error('Activation failed: License server unavailable.');
|
|
265
|
+
console.error('Check your internet connection and try again.');
|
|
266
|
+
process.exit(1);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
if (!result.valid) {
|
|
270
|
+
const reason = result.reason === 'revoked'
|
|
271
|
+
? 'This license key has been revoked.'
|
|
272
|
+
: 'Invalid license key. Check your key or get one at arthai.dev/pricing';
|
|
273
|
+
console.error(`Activation failed: ${reason}`);
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
fs.mkdirSync(ARTHAI_DIR, { recursive: true });
|
|
278
|
+
fs.writeFileSync(LICENSE_FILE_ARTHAI, key + '\n', { mode: 0o600 });
|
|
279
|
+
|
|
280
|
+
// Store validation cache immediately so first install doesn't re-validate
|
|
281
|
+
writeValidationCache(key);
|
|
282
|
+
|
|
283
|
+
console.log(`License activated (org: ${result.org}, tier: ${result.tier})`);
|
|
284
|
+
console.log(`Key stored at: ${LICENSE_FILE_ARTHAI}`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// ── license helpers ───────────────────────────────────────────────────────────
|
|
288
|
+
|
|
289
|
+
// Read license key in precedence order:
|
|
290
|
+
// 1. ARTHAI_LICENSE_KEY env var
|
|
291
|
+
// 2. ~/.claude-agents/.license (existing clone-install path)
|
|
292
|
+
// 3. ~/.arthai/license (npm activate path)
|
|
293
|
+
// 4. --key flag in args
|
|
294
|
+
function readLicenseKey(rawArgs) {
|
|
295
|
+
if (process.env.ARTHAI_LICENSE_KEY) {
|
|
296
|
+
return process.env.ARTHAI_LICENSE_KEY.trim() || null;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (fs.existsSync(LICENSE_FILE_AGENTS)) {
|
|
300
|
+
const key = fs.readFileSync(LICENSE_FILE_AGENTS, 'utf8').trim();
|
|
301
|
+
if (key) return key;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (fs.existsSync(LICENSE_FILE_ARTHAI)) {
|
|
305
|
+
const key = fs.readFileSync(LICENSE_FILE_ARTHAI, 'utf8').trim();
|
|
306
|
+
if (key) return key;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (Array.isArray(rawArgs)) {
|
|
310
|
+
const flagIdx = rawArgs.indexOf('--key');
|
|
311
|
+
if (flagIdx !== -1 && rawArgs[flagIdx + 1]) {
|
|
312
|
+
return rawArgs[flagIdx + 1].trim() || null;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// POST to Worker synchronously using curl via stdin (payload not visible in process list).
|
|
320
|
+
// Returns { valid, org, tier, reason, networkError } — never throws.
|
|
321
|
+
function postToWorker(endpoint, body) {
|
|
322
|
+
try {
|
|
323
|
+
const payload = JSON.stringify(body);
|
|
324
|
+
const result = spawnSync(
|
|
325
|
+
'curl',
|
|
326
|
+
[
|
|
327
|
+
'--silent',
|
|
328
|
+
'--max-time', '10',
|
|
329
|
+
'--connect-timeout', '5',
|
|
330
|
+
'--fail-with-body',
|
|
331
|
+
'--request', 'POST',
|
|
332
|
+
'--header', 'Content-Type: application/json',
|
|
333
|
+
'--data', '@-', // read payload from stdin, not command line
|
|
334
|
+
WORKER_URL + endpoint,
|
|
335
|
+
],
|
|
336
|
+
{
|
|
337
|
+
input: payload, // passed as stdin — not visible in ps/proc
|
|
338
|
+
timeout: 12000,
|
|
339
|
+
}
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
if (result.status !== 0 || result.error) {
|
|
343
|
+
return { valid: false, networkError: true };
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const stdout = result.stdout.toString().trim();
|
|
347
|
+
if (!stdout) return { valid: false, networkError: true };
|
|
348
|
+
|
|
349
|
+
const json = JSON.parse(stdout);
|
|
350
|
+
return { ...json, networkError: false };
|
|
351
|
+
} catch (e) {
|
|
352
|
+
return { valid: false, networkError: true };
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Validate with Worker, using 24h cache for subsequent calls.
|
|
357
|
+
// First call (no cache): fail-closed — Worker must respond.
|
|
358
|
+
function validateWithWorker(key) {
|
|
359
|
+
const cached = readValidationCache(key);
|
|
360
|
+
if (cached !== null) {
|
|
361
|
+
return { valid: cached, networkError: false };
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
const result = postToWorker('/validate', { key });
|
|
365
|
+
|
|
366
|
+
if (result.networkError) {
|
|
367
|
+
// No cache exists — fail closed for first install
|
|
368
|
+
return { valid: false, networkError: true };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (result.valid) {
|
|
372
|
+
writeValidationCache(key);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Returns true/false if cache is fresh, null if cache is missing or expired.
|
|
379
|
+
// Cache stores full SHA-256 key_hash (not plaintext, not truncated) to detect key changes.
|
|
380
|
+
function readValidationCache(key) {
|
|
381
|
+
try {
|
|
382
|
+
if (!fs.existsSync(VALIDATION_CACHE_FILE)) return null;
|
|
383
|
+
const cache = JSON.parse(fs.readFileSync(VALIDATION_CACHE_FILE, 'utf8'));
|
|
384
|
+
const nowSec = Math.floor(Date.now() / 1000);
|
|
385
|
+
const { createHash } = require('crypto');
|
|
386
|
+
const keyHash = createHash('sha256').update(key).digest('hex');
|
|
387
|
+
if (cache.key_hash !== keyHash) return null;
|
|
388
|
+
// Reject future timestamps — prevents cache poisoning via pre-written cache files
|
|
389
|
+
if (typeof cache.timestamp !== 'number' || cache.timestamp > nowSec) return null;
|
|
390
|
+
if ((nowSec - cache.timestamp) >= VALIDATION_CACHE_TTL) return null;
|
|
391
|
+
return cache.valid === true;
|
|
392
|
+
} catch (e) {
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
function writeValidationCache(key) {
|
|
398
|
+
try {
|
|
399
|
+
fs.mkdirSync(ARTHAI_DIR, { recursive: true });
|
|
400
|
+
const { createHash } = require('crypto');
|
|
401
|
+
// Full SHA-256 hash — not truncated, prevents brute-force of short hashes
|
|
402
|
+
const keyHash = createHash('sha256').update(key).digest('hex');
|
|
403
|
+
const cache = { key_hash: keyHash, valid: true, timestamp: Math.floor(Date.now() / 1000) };
|
|
404
|
+
fs.writeFileSync(VALIDATION_CACHE_FILE, JSON.stringify(cache) + '\n', { mode: 0o600 });
|
|
405
|
+
} catch (e) {
|
|
406
|
+
// Non-fatal — validation will just re-check next time
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function computeMachineId() {
|
|
411
|
+
try {
|
|
412
|
+
const { createHash } = require('crypto');
|
|
413
|
+
const hostname = require('os').hostname();
|
|
414
|
+
const username = process.env.USER || process.env.USERNAME || '';
|
|
415
|
+
return createHash('sha256').update(hostname + username).digest('hex');
|
|
416
|
+
} catch (e) {
|
|
417
|
+
return 'unknown';
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── uninstall ────────────────────────────────────────────────────────────────
|
|
422
|
+
|
|
423
|
+
function handleUninstall(rawArgs) {
|
|
424
|
+
if (rawArgs.length === 0) {
|
|
425
|
+
console.error('Usage: arthai uninstall <bundle> [bundle...] [target-path]');
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const targetDir = looksLikePath(rawArgs[rawArgs.length - 1])
|
|
430
|
+
? rawArgs.pop()
|
|
431
|
+
: process.cwd();
|
|
432
|
+
|
|
433
|
+
const bundleNames = rawArgs.filter(a => isValidBundleName(a));
|
|
434
|
+
if (bundleNames.length === 0) {
|
|
435
|
+
console.error('No valid bundle names provided.');
|
|
436
|
+
process.exit(1);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
440
|
+
const skillsDir = path.join(claudeDir, 'skills');
|
|
441
|
+
const agentsDir = path.join(claudeDir, 'agents');
|
|
442
|
+
|
|
443
|
+
for (const name of bundleNames) {
|
|
444
|
+
const pluginDir = path.resolve(PLUGINS_DIR, name);
|
|
445
|
+
if (!fs.existsSync(pluginDir)) {
|
|
446
|
+
console.error(`Bundle '${name}' not found in dist/plugins/.`);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let removed = 0;
|
|
451
|
+
|
|
452
|
+
// Remove skills that came from this bundle's commands/
|
|
453
|
+
const commandsDir = path.join(pluginDir, 'commands');
|
|
454
|
+
if (fs.existsSync(commandsDir)) {
|
|
455
|
+
for (const file of fs.readdirSync(commandsDir)) {
|
|
456
|
+
if (!file.endsWith('.md')) continue;
|
|
457
|
+
const skillName = file.replace('.md', '');
|
|
458
|
+
const skillPath = path.join(skillsDir, skillName);
|
|
459
|
+
if (fs.existsSync(skillPath)) {
|
|
460
|
+
fs.rmSync(skillPath, { recursive: true });
|
|
461
|
+
removed++;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// Remove agents that came from this bundle
|
|
467
|
+
const srcAgents = path.join(pluginDir, 'agents');
|
|
468
|
+
if (fs.existsSync(srcAgents)) {
|
|
469
|
+
for (const file of fs.readdirSync(srcAgents)) {
|
|
470
|
+
if (!file.endsWith('.md')) continue;
|
|
471
|
+
const agentPath = path.join(agentsDir, file);
|
|
472
|
+
if (fs.existsSync(agentPath)) {
|
|
473
|
+
fs.unlinkSync(agentPath);
|
|
474
|
+
removed++;
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
console.log(` Removed ${name}: ${removed} files`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
console.log('\nNote: hooks in .claude/settings.json were not removed — edit manually if needed.');
|
|
483
|
+
console.log('Done.');
|
|
484
|
+
}
|
|
485
|
+
|
|
210
486
|
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
211
487
|
|
|
212
488
|
// Validate bundle names: only lowercase alphanumeric and hyphens, max 64 chars.
|
|
@@ -337,6 +613,95 @@ function installBundle(pluginDir, claudeDir, skillsDir, agentsDir, name) {
|
|
|
337
613
|
return counts;
|
|
338
614
|
}
|
|
339
615
|
|
|
616
|
+
// ── project setup (M6 — parity with install.sh) ───────────────────────────────
|
|
617
|
+
|
|
618
|
+
const MANAGED_BLOCK_START = '<!-- >>> claude-agents toolkit (DO NOT EDIT THIS BLOCK) >>> -->';
|
|
619
|
+
const MANAGED_BLOCK_END = '<!-- <<< claude-agents toolkit <<< -->';
|
|
620
|
+
const GITIGNORE_BLOCK_START = '# >>> claude-agents managed (DO NOT EDIT THIS BLOCK) >>>';
|
|
621
|
+
const GITIGNORE_BLOCK_END = '# <<< claude-agents managed <<<';
|
|
622
|
+
|
|
623
|
+
function setupProjectClaude(targetDir) {
|
|
624
|
+
const claudeDir = path.join(targetDir, '.claude');
|
|
625
|
+
const version = readVersion();
|
|
626
|
+
|
|
627
|
+
// 1. CLAUDE.md — create from template if missing, inject managed block if absent
|
|
628
|
+
const claudeMdPath = path.join(targetDir, 'CLAUDE.md');
|
|
629
|
+
const templatePath = path.join(TOOLKIT_DIR, 'templates', 'CLAUDE.md.template');
|
|
630
|
+
const managedBlockPath = path.join(TOOLKIT_DIR, 'templates', 'CLAUDE.md.managed-block');
|
|
631
|
+
|
|
632
|
+
if (!fs.existsSync(claudeMdPath) && fs.existsSync(templatePath)) {
|
|
633
|
+
const projectName = path.basename(targetDir);
|
|
634
|
+
const content = fs.readFileSync(templatePath, 'utf8').replace(/\{\{PROJECT_NAME\}\}/g, projectName);
|
|
635
|
+
fs.writeFileSync(claudeMdPath, content);
|
|
636
|
+
console.log(' Created CLAUDE.md from template');
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
if (fs.existsSync(claudeMdPath) && fs.existsSync(managedBlockPath)) {
|
|
640
|
+
injectOrUpdateManagedBlock(claudeMdPath, managedBlockPath, version);
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// 2. .gitignore — inject toolkit marker block if missing
|
|
644
|
+
const gitignorePath = path.join(targetDir, '.gitignore');
|
|
645
|
+
injectGitignoreBlock(gitignorePath);
|
|
646
|
+
|
|
647
|
+
// 3. .claude/.claude-agents.conf — record installed bundles for re-sync tracking
|
|
648
|
+
// (Only write if we've already created the .claude dir; it should exist by now)
|
|
649
|
+
if (fs.existsSync(claudeDir)) {
|
|
650
|
+
const confPath = path.join(claudeDir, '.claude-agents.conf');
|
|
651
|
+
if (!fs.existsSync(confPath)) {
|
|
652
|
+
fs.writeFileSync(confPath, `# claude-agents configuration\nversion=${version}\n`);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function injectOrUpdateManagedBlock(claudeMdPath, managedBlockPath, version) {
|
|
658
|
+
const existing = fs.readFileSync(claudeMdPath, 'utf8');
|
|
659
|
+
const blockContent = fs.readFileSync(managedBlockPath, 'utf8').trimEnd();
|
|
660
|
+
|
|
661
|
+
const newBlock = `${MANAGED_BLOCK_START}\n<!-- version: ${version} -->\n${blockContent}\n${MANAGED_BLOCK_END}`;
|
|
662
|
+
|
|
663
|
+
const startIdx = existing.indexOf(MANAGED_BLOCK_START);
|
|
664
|
+
const endIdx = existing.indexOf(MANAGED_BLOCK_END);
|
|
665
|
+
|
|
666
|
+
if (startIdx === -1) {
|
|
667
|
+
// Block missing — append it
|
|
668
|
+
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
669
|
+
fs.writeFileSync(claudeMdPath, existing + separator + newBlock + '\n');
|
|
670
|
+
console.log(' Injected toolkit managed block into CLAUDE.md');
|
|
671
|
+
} else if (startIdx !== -1 && endIdx !== -1) {
|
|
672
|
+
// Block exists — check if version is stale
|
|
673
|
+
const existingBlock = existing.slice(startIdx, endIdx + MANAGED_BLOCK_END.length);
|
|
674
|
+
const versionMatch = existingBlock.match(/<!-- version: ([^>]+) -->/);
|
|
675
|
+
const existingVersion = versionMatch ? versionMatch[1].trim() : null;
|
|
676
|
+
if (existingVersion !== version) {
|
|
677
|
+
const updated = existing.slice(0, startIdx) + newBlock + existing.slice(endIdx + MANAGED_BLOCK_END.length);
|
|
678
|
+
fs.writeFileSync(claudeMdPath, updated);
|
|
679
|
+
console.log(` Updated toolkit managed block in CLAUDE.md (${existingVersion} → ${version})`);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function injectGitignoreBlock(gitignorePath) {
|
|
685
|
+
const lines = [
|
|
686
|
+
GITIGNORE_BLOCK_START,
|
|
687
|
+
'.claude/.toolkit-last-seen-sha',
|
|
688
|
+
'.claude/.toolkit-setup-done',
|
|
689
|
+
'.claude/.claude-agents.conf',
|
|
690
|
+
GITIGNORE_BLOCK_END,
|
|
691
|
+
].join('\n');
|
|
692
|
+
|
|
693
|
+
if (!fs.existsSync(gitignorePath)) {
|
|
694
|
+
fs.writeFileSync(gitignorePath, lines + '\n');
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const existing = fs.readFileSync(gitignorePath, 'utf8');
|
|
699
|
+
if (existing.includes(GITIGNORE_BLOCK_START)) return; // Already present
|
|
700
|
+
|
|
701
|
+
const separator = existing.endsWith('\n') ? '\n' : '\n\n';
|
|
702
|
+
fs.writeFileSync(gitignorePath, existing + separator + lines + '\n');
|
|
703
|
+
}
|
|
704
|
+
|
|
340
705
|
function copyDirRecursive(src, dest) {
|
|
341
706
|
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
342
707
|
const srcPath = path.join(src, entry.name);
|
|
@@ -363,6 +728,7 @@ function printUsage() {
|
|
|
363
728
|
console.log(`arthai v${version} — AI-powered development toolkit for Claude Code
|
|
364
729
|
|
|
365
730
|
Usage:
|
|
731
|
+
arthai activate <key> Activate your license key
|
|
366
732
|
arthai install <bundle> [bundle...] [path] Install bundle(s) into a project
|
|
367
733
|
arthai list List available bundles
|
|
368
734
|
arthai info <bundle> Show bundle details
|
package/bundles/canvas.json
CHANGED
package/bundles/compass.json
CHANGED
package/bundles/counsel.json
CHANGED
package/bundles/cruise.json
CHANGED
package/bundles/forge.json
CHANGED
|
@@ -18,9 +18,20 @@
|
|
|
18
18
|
"skills/precheck",
|
|
19
19
|
"skills/review-pr"
|
|
20
20
|
],
|
|
21
|
+
"agents": [
|
|
22
|
+
"agents/architect.md",
|
|
23
|
+
"agents/product-manager.md",
|
|
24
|
+
"agents/python-backend.md",
|
|
25
|
+
"agents/frontend.md",
|
|
26
|
+
"agents/qa.md",
|
|
27
|
+
"agents/code-reviewer.md",
|
|
28
|
+
"agents/troubleshooter.md"
|
|
29
|
+
],
|
|
21
30
|
"hooks": [
|
|
31
|
+
"project-setup",
|
|
22
32
|
"triage-router",
|
|
23
|
-
"post-test-summary"
|
|
33
|
+
"post-test-summary",
|
|
34
|
+
"escalation-guard"
|
|
24
35
|
],
|
|
25
36
|
"dependencies": []
|
|
26
37
|
}
|