@eximia-ventures/claude-code-toolkit 3.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/assets/skill/aios-integrate.md +169 -0
- package/assets/statusline/statusline.sh +220 -0
- package/bin/cli.js +5 -0
- package/package.json +35 -0
- package/src/commands/doctor.js +166 -0
- package/src/commands/uninstall.js +91 -0
- package/src/index.js +67 -0
- package/src/installer.js +196 -0
- package/src/modules/aios.js +84 -0
- package/src/modules/handoff.js +47 -0
- package/src/modules/index.js +28 -0
- package/src/modules/statusline.js +86 -0
- package/src/utils/file-installer.js +43 -0
- package/src/utils/logger.js +46 -0
- package/src/utils/settings-merger.js +61 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# /aios-integrate — Gerenciador de Artefatos AIOS
|
|
2
|
+
|
|
3
|
+
Você é um assistente especializado em integrar, listar e exportar artefatos AIOS (agents e squads).
|
|
4
|
+
|
|
5
|
+
## Instruções
|
|
6
|
+
|
|
7
|
+
Quando o usuário executar `/aios-integrate`, siga este fluxo:
|
|
8
|
+
|
|
9
|
+
### 1. Pergunte a ação desejada
|
|
10
|
+
|
|
11
|
+
Apresente as opções:
|
|
12
|
+
1. **Integrar artefato externo** — Importar um agent ou squad de um caminho/URL
|
|
13
|
+
2. **Listar artefatos instalados** — Mostrar agents e squads registrados
|
|
14
|
+
3. **Exportar artefatos** — Empacotar artefatos para compartilhamento
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
### 2. Integrar Artefato Externo
|
|
19
|
+
|
|
20
|
+
Quando o usuário escolher integrar:
|
|
21
|
+
|
|
22
|
+
1. Peça o **caminho ou URL** do artefato (arquivo `.md`/`.yaml` para agent, ou pasta para squad)
|
|
23
|
+
2. Leia e analise o artefato para detectar o tipo:
|
|
24
|
+
- Se contém `agent:` com campos `name`, `id`, `persona` → é um **Agent**
|
|
25
|
+
- Se contém `name`, `version`, `components`, `slashPrefix` → é um **Squad**
|
|
26
|
+
|
|
27
|
+
#### Para Agent:
|
|
28
|
+
|
|
29
|
+
Crie automaticamente todos os arquivos de integração:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
.codex/agents/{id}.md — Cópia do agent para IDE discovery
|
|
33
|
+
.antigravity/agents/{id}.md — Workflow activation file
|
|
34
|
+
.agent/workflows/{id}.md — Workflow file
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Registre no manifest `.aios-core/manifests/agents.csv` adicionando uma linha:
|
|
38
|
+
```csv
|
|
39
|
+
{id},{name},{title},{icon},{status}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Para cada `command` definido no agent (na seção `commands`), crie um slash command em `.claude/commands/`:
|
|
43
|
+
- Nome do arquivo: `{agent-id}-{command-name}.md`
|
|
44
|
+
- Conteúdo: Instruções para ativar o agent e executar o comando específico
|
|
45
|
+
|
|
46
|
+
Exemplo de slash command gerado:
|
|
47
|
+
```markdown
|
|
48
|
+
# /{agent-id}-{command-name}
|
|
49
|
+
|
|
50
|
+
Ative o agent @{agent-name} e execute o comando `*{command-name}`.
|
|
51
|
+
|
|
52
|
+
## Contexto
|
|
53
|
+
- Agent: {agent-name} ({agent-title})
|
|
54
|
+
- Comando: *{command-name}
|
|
55
|
+
- Descrição: {command-description}
|
|
56
|
+
|
|
57
|
+
## Instruções
|
|
58
|
+
1. Leia o agent definition em `.codex/agents/{id}.md`
|
|
59
|
+
2. Assuma a persona do agent
|
|
60
|
+
3. Execute o comando `*{command-name}` conforme definido no agent
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### Para Squad:
|
|
64
|
+
|
|
65
|
+
1. Copie a pasta completa do squad para `./squads/{squad-name}/`
|
|
66
|
+
2. Leia o `squad.yaml` para identificar os agents do squad
|
|
67
|
+
3. Para cada agent listado em `components.agents`:
|
|
68
|
+
- Registre no `.aios-core/manifests/agents.csv`
|
|
69
|
+
- Crie os arquivos de integração (mesmos do fluxo agent acima)
|
|
70
|
+
4. Para cada task listada em `components.tasks`, crie slash commands baseados no `slashPrefix`:
|
|
71
|
+
- Nome: `{slashPrefix}-{task-name}.md`
|
|
72
|
+
- Conteúdo: Instruções para executar a task do squad
|
|
73
|
+
|
|
74
|
+
Ao finalizar, mostre um relatório:
|
|
75
|
+
```
|
|
76
|
+
✓ Artefato integrado: {name}
|
|
77
|
+
Tipo: Agent/Squad
|
|
78
|
+
Arquivos criados:
|
|
79
|
+
- .codex/agents/{id}.md
|
|
80
|
+
- .antigravity/agents/{id}.md
|
|
81
|
+
- .agent/workflows/{id}.md
|
|
82
|
+
- .claude/commands/{command}.md (x N)
|
|
83
|
+
Manifest atualizado: agents.csv
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
### 3. Listar Artefatos Instalados
|
|
89
|
+
|
|
90
|
+
1. Leia `.aios-core/manifests/agents.csv` e liste todos os agents registrados
|
|
91
|
+
2. Verifique a pasta `./squads/` e liste todos os squads instalados
|
|
92
|
+
3. Para cada artefato, mostre:
|
|
93
|
+
- Nome e ID
|
|
94
|
+
- Tipo (Agent standalone / Agent de Squad / Squad)
|
|
95
|
+
- Status (ativo/desativado)
|
|
96
|
+
- Comandos disponíveis
|
|
97
|
+
- Versão (se disponível)
|
|
98
|
+
|
|
99
|
+
Formato de saída:
|
|
100
|
+
```
|
|
101
|
+
📋 Artefatos AIOS Instalados
|
|
102
|
+
|
|
103
|
+
Agents:
|
|
104
|
+
🤖 dev (Dex) — Development Agent — ativo
|
|
105
|
+
Comandos: *develop, *implement, *fix
|
|
106
|
+
🤖 qa (Quinn) — QA Agent — ativo
|
|
107
|
+
Comandos: *qa-gate, *review
|
|
108
|
+
|
|
109
|
+
Squads:
|
|
110
|
+
🎯 etl-squad v1.2.0 — ETL Pipeline Squad
|
|
111
|
+
Agents: extractor, transformer, loader
|
|
112
|
+
Prefix: /etl-*
|
|
113
|
+
|
|
114
|
+
Total: N agents, M squads
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
### 4. Exportar Artefatos
|
|
120
|
+
|
|
121
|
+
1. Liste todos os artefatos disponíveis e pergunte qual exportar
|
|
122
|
+
2. Para o artefato selecionado:
|
|
123
|
+
|
|
124
|
+
#### Agent:
|
|
125
|
+
- Colete todos os arquivos relacionados:
|
|
126
|
+
- Agent definition (`.codex/agents/{id}.md`)
|
|
127
|
+
- Tasks referenciadas
|
|
128
|
+
- Templates referenciados
|
|
129
|
+
- Checklists referenciados
|
|
130
|
+
- Use `tar` para criar um bundle: `{agent-id}-export.tar.gz`
|
|
131
|
+
|
|
132
|
+
#### Squad:
|
|
133
|
+
- Empacote a pasta completa do squad: `./squads/{squad-name}/`
|
|
134
|
+
- Use `tar` para criar: `{squad-name}-export.tar.gz`
|
|
135
|
+
|
|
136
|
+
Salve o export na pasta atual e informe o caminho:
|
|
137
|
+
```
|
|
138
|
+
✓ Exportado: ./{artifact-name}-export.tar.gz
|
|
139
|
+
Conteúdo: N arquivos, M KB
|
|
140
|
+
Para importar em outro projeto: /aios-integrate → Integrar → caminho do arquivo
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Validações
|
|
146
|
+
|
|
147
|
+
Antes de integrar qualquer artefato, valide:
|
|
148
|
+
|
|
149
|
+
1. **Estrutura do agent** deve conter pelo menos: `agent.name`, `agent.id`, `persona.role`
|
|
150
|
+
2. **Estrutura do squad** deve conter pelo menos: `name`, `version`, `components`
|
|
151
|
+
3. **IDs não duplicados** — Verifique se o ID já existe no manifest antes de registrar
|
|
152
|
+
4. **Dependências** — Se o agent/squad referencia tasks ou templates, verifique se existem
|
|
153
|
+
|
|
154
|
+
Se a validação falhar, mostre o erro e pergunte se o usuário quer continuar mesmo assim.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## Paths de Referência
|
|
159
|
+
|
|
160
|
+
| Artefato | Path |
|
|
161
|
+
|----------|------|
|
|
162
|
+
| Agent definitions | `.codex/agents/` |
|
|
163
|
+
| Agent workflows | `.agent/workflows/` |
|
|
164
|
+
| Agent activation | `.antigravity/agents/` |
|
|
165
|
+
| Agent manifest | `.aios-core/manifests/agents.csv` |
|
|
166
|
+
| Squad folders | `./squads/` |
|
|
167
|
+
| Slash commands | `.claude/commands/` |
|
|
168
|
+
| Agent schema | `.aios-core/schemas/agent-v3-schema.json` |
|
|
169
|
+
| Squad schema | `.aios-core/schemas/squad-schema.json` |
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# claude-code-toolkit — Statusline Script v1.0
|
|
4
|
+
#
|
|
5
|
+
# Receives JSON via stdin from Claude Code's statusLine configuration.
|
|
6
|
+
# Outputs a formatted status bar with model, context, cost, time, git info.
|
|
7
|
+
#
|
|
8
|
+
# Required: jq (https://jqlang.github.io/jq/)
|
|
9
|
+
#
|
|
10
|
+
# Environment variables (optional):
|
|
11
|
+
# CLAUDE_TOOLKIT_CURRENCY — Currency symbol (default: $)
|
|
12
|
+
# CLAUDE_TOOLKIT_CURRENCY_RATE — USD conversion rate (default: 1)
|
|
13
|
+
#
|
|
14
|
+
|
|
15
|
+
set -euo pipefail
|
|
16
|
+
|
|
17
|
+
# --- Read JSON from stdin ---
|
|
18
|
+
INPUT=$(cat)
|
|
19
|
+
|
|
20
|
+
# --- Check jq availability ---
|
|
21
|
+
if ! command -v jq &>/dev/null; then
|
|
22
|
+
echo "jq not found"
|
|
23
|
+
exit 0
|
|
24
|
+
fi
|
|
25
|
+
|
|
26
|
+
# --- ANSI Colors ---
|
|
27
|
+
RESET='\033[0m'
|
|
28
|
+
BOLD='\033[1m'
|
|
29
|
+
DIM='\033[2m'
|
|
30
|
+
RED='\033[31m'
|
|
31
|
+
GREEN='\033[32m'
|
|
32
|
+
YELLOW='\033[33m'
|
|
33
|
+
BLUE='\033[34m'
|
|
34
|
+
MAGENTA='\033[35m'
|
|
35
|
+
CYAN='\033[36m'
|
|
36
|
+
WHITE='\033[37m'
|
|
37
|
+
BOLD_BLUE="${BOLD}${BLUE}"
|
|
38
|
+
BOLD_RED="${BOLD}${RED}"
|
|
39
|
+
|
|
40
|
+
# --- Currency config ---
|
|
41
|
+
CURRENCY="${CLAUDE_TOOLKIT_CURRENCY:-\$}"
|
|
42
|
+
RATE="${CLAUDE_TOOLKIT_CURRENCY_RATE:-1}"
|
|
43
|
+
|
|
44
|
+
# --- Parse JSON fields ---
|
|
45
|
+
model_id=$(echo "$INPUT" | jq -r '.model.id // ""')
|
|
46
|
+
used_pct=$(echo "$INPUT" | jq -r '.context_window.used_percentage // 0')
|
|
47
|
+
total_input=$(echo "$INPUT" | jq -r '.context_window.total_input_tokens // 0')
|
|
48
|
+
total_output=$(echo "$INPUT" | jq -r '.context_window.total_output_tokens // 0')
|
|
49
|
+
total_cost=$(echo "$INPUT" | jq -r '.cost.total_cost_usd // 0')
|
|
50
|
+
total_duration=$(echo "$INPUT" | jq -r '.cost.total_duration_ms // 0')
|
|
51
|
+
session_id=$(echo "$INPUT" | jq -r '.session_id // ""')
|
|
52
|
+
|
|
53
|
+
# --- Helper: format model name ---
|
|
54
|
+
format_model() {
|
|
55
|
+
local id="$1"
|
|
56
|
+
case "$id" in
|
|
57
|
+
*opus*) echo "Opus 4.6" ;;
|
|
58
|
+
*sonnet*) echo "Sonnet 4.5" ;;
|
|
59
|
+
*haiku*) echo "Haiku 4.5" ;;
|
|
60
|
+
*) echo "$id" ;;
|
|
61
|
+
esac
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# --- Helper: format tokens ---
|
|
65
|
+
fmt_tokens() {
|
|
66
|
+
local n="$1"
|
|
67
|
+
if (( n >= 1000000 )); then
|
|
68
|
+
printf "%.1fM" "$(echo "scale=1; $n / 1000000" | bc)"
|
|
69
|
+
elif (( n >= 1000 )); then
|
|
70
|
+
echo "$((n / 1000))K"
|
|
71
|
+
else
|
|
72
|
+
echo "$n"
|
|
73
|
+
fi
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
# --- Helper: progress bar ---
|
|
77
|
+
progress_bar() {
|
|
78
|
+
local pct="$1"
|
|
79
|
+
local width=10
|
|
80
|
+
local filled=$(( (pct * width + 50) / 100 ))
|
|
81
|
+
local empty=$(( width - filled ))
|
|
82
|
+
|
|
83
|
+
local color
|
|
84
|
+
if (( pct > 80 )); then color="$RED"
|
|
85
|
+
elif (( pct > 60 )); then color="$YELLOW"
|
|
86
|
+
else color="$GREEN"
|
|
87
|
+
fi
|
|
88
|
+
|
|
89
|
+
local bar=""
|
|
90
|
+
for ((i=0; i<filled; i++)); do bar+="█"; done
|
|
91
|
+
for ((i=0; i<empty; i++)); do bar+="░"; done
|
|
92
|
+
|
|
93
|
+
printf "${color}%s${RESET}" "$bar"
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
# --- Helper: format duration ---
|
|
97
|
+
fmt_duration() {
|
|
98
|
+
local ms="$1"
|
|
99
|
+
local total_sec=$(( ms / 1000 ))
|
|
100
|
+
local min=$(( total_sec / 60 ))
|
|
101
|
+
local sec=$(( total_sec % 60 ))
|
|
102
|
+
if (( min > 0 )); then
|
|
103
|
+
printf "%dm%02ds" "$min" "$sec"
|
|
104
|
+
else
|
|
105
|
+
printf "%ds" "$sec"
|
|
106
|
+
fi
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
# --- Helper: format cost with currency conversion ---
|
|
110
|
+
fmt_cost() {
|
|
111
|
+
local usd="$1"
|
|
112
|
+
local converted
|
|
113
|
+
converted=$(LC_NUMERIC=C awk "BEGIN { printf \"%.2f\", $usd * $RATE }")
|
|
114
|
+
printf "%s%s" "$CURRENCY" "$converted"
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
# --- Helper: get git branch (cached 5s) ---
|
|
118
|
+
GIT_CACHE_DIR="${HOME}/.claude/session-cache"
|
|
119
|
+
GIT_CACHE_FILE="${GIT_CACHE_DIR}/git-branch-cache"
|
|
120
|
+
|
|
121
|
+
get_git_branch() {
|
|
122
|
+
mkdir -p "$GIT_CACHE_DIR" 2>/dev/null || true
|
|
123
|
+
|
|
124
|
+
if [[ -f "$GIT_CACHE_FILE" ]]; then
|
|
125
|
+
local cache_age
|
|
126
|
+
if [[ "$(uname)" == "Darwin" ]]; then
|
|
127
|
+
cache_age=$(( $(date +%s) - $(stat -f %m "$GIT_CACHE_FILE") ))
|
|
128
|
+
else
|
|
129
|
+
cache_age=$(( $(date +%s) - $(stat -c %Y "$GIT_CACHE_FILE") ))
|
|
130
|
+
fi
|
|
131
|
+
if (( cache_age < 5 )); then
|
|
132
|
+
cat "$GIT_CACHE_FILE"
|
|
133
|
+
return
|
|
134
|
+
fi
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
local branch
|
|
138
|
+
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "")
|
|
139
|
+
if [[ -n "$branch" ]]; then
|
|
140
|
+
echo "$branch" > "$GIT_CACHE_FILE"
|
|
141
|
+
echo "$branch"
|
|
142
|
+
fi
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
# --- Helper: count user messages from transcript ---
|
|
146
|
+
count_messages() {
|
|
147
|
+
local transcript_path
|
|
148
|
+
transcript_path=$(echo "$INPUT" | jq -r '.transcript_path // ""')
|
|
149
|
+
if [[ -z "$transcript_path" || ! -f "$transcript_path" ]]; then
|
|
150
|
+
echo "0"
|
|
151
|
+
return
|
|
152
|
+
fi
|
|
153
|
+
grep -c '"type"\s*:\s*"human"\|"type"\s*:\s*"user"' "$transcript_path" 2>/dev/null || echo "0"
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# --- Build segments ---
|
|
157
|
+
PARTS=()
|
|
158
|
+
|
|
159
|
+
# 1. Model with color indicator
|
|
160
|
+
model_name=$(format_model "$model_id")
|
|
161
|
+
if [[ -n "$model_name" ]]; then
|
|
162
|
+
model_color="$GREEN"
|
|
163
|
+
case "$model_name" in
|
|
164
|
+
"Opus 4.6") model_color="$MAGENTA" ;;
|
|
165
|
+
"Sonnet 4.5") model_color="$BLUE" ;;
|
|
166
|
+
"Haiku 4.5") model_color="$CYAN" ;;
|
|
167
|
+
esac
|
|
168
|
+
PARTS+=("$(printf "${model_color}●${RESET} ${BOLD_BLUE}%s${RESET}" "$model_name")")
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
# 2. Date
|
|
172
|
+
current_date=$(date +%d/%m/%Y)
|
|
173
|
+
PARTS+=("$(printf "📅 %s" "$current_date")")
|
|
174
|
+
|
|
175
|
+
# 3. Context (total tokens in human format)
|
|
176
|
+
total_tokens=$(( total_input + total_output ))
|
|
177
|
+
if (( total_tokens > 0 )); then
|
|
178
|
+
PARTS+=("$(printf "📊 %s" "$(fmt_tokens "$total_tokens")")")
|
|
179
|
+
fi
|
|
180
|
+
|
|
181
|
+
# 4. Message count
|
|
182
|
+
msg_count=$(count_messages)
|
|
183
|
+
PARTS+=("$(printf "⚡ %s" "$msg_count")")
|
|
184
|
+
|
|
185
|
+
# 5. Progress bar + percentage
|
|
186
|
+
pct_int=${used_pct%.*}
|
|
187
|
+
pct_int=${pct_int:-0}
|
|
188
|
+
bar=$(progress_bar "$pct_int")
|
|
189
|
+
if (( pct_int > 80 )); then pct_color="$RED"
|
|
190
|
+
elif (( pct_int > 60 )); then pct_color="$YELLOW"
|
|
191
|
+
else pct_color="$GREEN"
|
|
192
|
+
fi
|
|
193
|
+
PARTS+=("$(printf "%s ${pct_color}%s%%${RESET}" "$bar" "$pct_int")")
|
|
194
|
+
|
|
195
|
+
# 6. Cost
|
|
196
|
+
if [[ "$total_cost" != "0" ]]; then
|
|
197
|
+
PARTS+=("$(printf "💰 %s" "$(fmt_cost "$total_cost")")")
|
|
198
|
+
fi
|
|
199
|
+
|
|
200
|
+
# 7. Duration
|
|
201
|
+
if (( total_duration > 0 )); then
|
|
202
|
+
PARTS+=("$(printf "⏱ %s" "$(fmt_duration "$total_duration")")")
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# 8. Git branch
|
|
206
|
+
git_branch=$(get_git_branch)
|
|
207
|
+
if [[ -n "$git_branch" ]]; then
|
|
208
|
+
PARTS+=("$(printf "🔀 ${MAGENTA}%s${RESET}" "$git_branch")")
|
|
209
|
+
fi
|
|
210
|
+
|
|
211
|
+
# --- Join parts with separator ---
|
|
212
|
+
output=""
|
|
213
|
+
for i in "${!PARTS[@]}"; do
|
|
214
|
+
if (( i > 0 )); then
|
|
215
|
+
output+=" | "
|
|
216
|
+
fi
|
|
217
|
+
output+="${PARTS[$i]}"
|
|
218
|
+
done
|
|
219
|
+
|
|
220
|
+
echo -e "$output"
|
package/bin/cli.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eximia-ventures/claude-code-toolkit",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Setup completo para Claude Code: statusline, session handoff, AIOS integration",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"claude-code-toolkit": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"src/",
|
|
12
|
+
"assets/"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"start": "node bin/cli.js",
|
|
16
|
+
"test": "echo \"No tests yet\" && exit 0"
|
|
17
|
+
},
|
|
18
|
+
"keywords": [
|
|
19
|
+
"claude",
|
|
20
|
+
"claude-code",
|
|
21
|
+
"statusline",
|
|
22
|
+
"toolkit",
|
|
23
|
+
"aios",
|
|
24
|
+
"developer-tools"
|
|
25
|
+
],
|
|
26
|
+
"author": "Hugo Capitelli",
|
|
27
|
+
"license": "MIT",
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"chalk": "^4.1.2",
|
|
30
|
+
"claude-code-handoff": "^2.1.0",
|
|
31
|
+
"commander": "^12.1.0",
|
|
32
|
+
"fs-extra": "^11.3.0",
|
|
33
|
+
"inquirer": "^8.2.6"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { execSync } = require('child_process');
|
|
7
|
+
const logger = require('../utils/logger');
|
|
8
|
+
const { MANIFEST_PATH } = require('../installer');
|
|
9
|
+
const { SETTINGS_PATH, readSettings } = require('../utils/settings-merger');
|
|
10
|
+
|
|
11
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
12
|
+
const STATUSLINE_PATH = path.join(CLAUDE_DIR, 'statusline.sh');
|
|
13
|
+
|
|
14
|
+
async function runDoctor() {
|
|
15
|
+
logger.section('Diagnóstico');
|
|
16
|
+
|
|
17
|
+
let issues = 0;
|
|
18
|
+
let ok = 0;
|
|
19
|
+
|
|
20
|
+
// 1. Check manifest
|
|
21
|
+
if (await fs.pathExists(MANIFEST_PATH)) {
|
|
22
|
+
const manifest = await fs.readJson(MANIFEST_PATH);
|
|
23
|
+
check(`Manifest encontrado (v${manifest.version})`, true);
|
|
24
|
+
ok++;
|
|
25
|
+
} else {
|
|
26
|
+
check('Manifest não encontrado — toolkit não instalado?', false);
|
|
27
|
+
issues++;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// 2. Check ~/.claude/ directory
|
|
31
|
+
if (await fs.pathExists(CLAUDE_DIR)) {
|
|
32
|
+
check('Diretório ~/.claude/ existe', true);
|
|
33
|
+
ok++;
|
|
34
|
+
} else {
|
|
35
|
+
check('Diretório ~/.claude/ não encontrado', false);
|
|
36
|
+
issues++;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// 3. Check settings.json
|
|
40
|
+
if (await fs.pathExists(SETTINGS_PATH)) {
|
|
41
|
+
const settings = await readSettings();
|
|
42
|
+
if (settings.statusLine) {
|
|
43
|
+
check('settings.json contém statusLine', true);
|
|
44
|
+
ok++;
|
|
45
|
+
} else {
|
|
46
|
+
check('settings.json não contém statusLine', false);
|
|
47
|
+
issues++;
|
|
48
|
+
}
|
|
49
|
+
} else {
|
|
50
|
+
check('settings.json não encontrado', false);
|
|
51
|
+
issues++;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 4. Check statusline.sh
|
|
55
|
+
if (await fs.pathExists(STATUSLINE_PATH)) {
|
|
56
|
+
const stats = await fs.stat(STATUSLINE_PATH);
|
|
57
|
+
const isExecutable = (stats.mode & 0o111) !== 0;
|
|
58
|
+
if (isExecutable) {
|
|
59
|
+
check('statusline.sh existe e é executável', true);
|
|
60
|
+
ok++;
|
|
61
|
+
} else {
|
|
62
|
+
check('statusline.sh existe mas NÃO é executável', false);
|
|
63
|
+
issues++;
|
|
64
|
+
}
|
|
65
|
+
} else {
|
|
66
|
+
check('statusline.sh não encontrado', false);
|
|
67
|
+
issues++;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 5. Check jq
|
|
71
|
+
try {
|
|
72
|
+
const jqVersion = execSync('jq --version 2>&1', { encoding: 'utf8' }).trim();
|
|
73
|
+
check(`jq instalado (${jqVersion})`, true);
|
|
74
|
+
ok++;
|
|
75
|
+
} catch {
|
|
76
|
+
check('jq não encontrado — necessário para statusline', false);
|
|
77
|
+
issues++;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 6. Check git
|
|
81
|
+
try {
|
|
82
|
+
const gitVersion = execSync('git --version 2>&1', { encoding: 'utf8' }).trim();
|
|
83
|
+
check(`${gitVersion}`, true);
|
|
84
|
+
ok++;
|
|
85
|
+
} catch {
|
|
86
|
+
check('git não encontrado', false);
|
|
87
|
+
issues++;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// 7. Check Node.js version
|
|
91
|
+
const nodeVersion = process.version;
|
|
92
|
+
const major = parseInt(nodeVersion.slice(1), 10);
|
|
93
|
+
if (major >= 18) {
|
|
94
|
+
check(`Node.js ${nodeVersion}`, true);
|
|
95
|
+
ok++;
|
|
96
|
+
} else {
|
|
97
|
+
check(`Node.js ${nodeVersion} (recomendado >= 18)`, false);
|
|
98
|
+
issues++;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 8. Check handoff commands
|
|
102
|
+
const commandsDir = path.join(process.cwd(), '.claude', 'commands');
|
|
103
|
+
if (await fs.pathExists(commandsDir)) {
|
|
104
|
+
const commands = await fs.readdir(commandsDir);
|
|
105
|
+
const handoffCommands = commands.filter(f =>
|
|
106
|
+
['resume.md', 'handoff.md', 'save-handoff.md'].includes(f)
|
|
107
|
+
);
|
|
108
|
+
if (handoffCommands.length > 0) {
|
|
109
|
+
check(`Handoff commands encontrados (${handoffCommands.length} de 7)`, true);
|
|
110
|
+
ok++;
|
|
111
|
+
} else {
|
|
112
|
+
check('Handoff commands não encontrados no projeto atual', false);
|
|
113
|
+
issues++;
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
check('.claude/commands/ não encontrado no projeto atual', false);
|
|
117
|
+
issues++;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 9. Check AIOS
|
|
121
|
+
const aiosCoreDir = path.join(process.cwd(), '.aios-core');
|
|
122
|
+
if (await fs.pathExists(aiosCoreDir)) {
|
|
123
|
+
check('AIOS Core instalado no projeto', true);
|
|
124
|
+
ok++;
|
|
125
|
+
|
|
126
|
+
// Check aios-integrate skill
|
|
127
|
+
const skillPath = path.join(process.cwd(), '.claude', 'commands', 'aios-integrate.md');
|
|
128
|
+
if (await fs.pathExists(skillPath)) {
|
|
129
|
+
check('Skill /aios-integrate instalada', true);
|
|
130
|
+
ok++;
|
|
131
|
+
} else {
|
|
132
|
+
check('Skill /aios-integrate não encontrada', false);
|
|
133
|
+
issues++;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
logger.dim(' - AIOS Core não instalado (opcional)');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// 10. Check env vars
|
|
140
|
+
const currency = process.env.CLAUDE_TOOLKIT_CURRENCY;
|
|
141
|
+
const rate = process.env.CLAUDE_TOOLKIT_CURRENCY_RATE;
|
|
142
|
+
if (currency || rate) {
|
|
143
|
+
check(`Moeda configurada: ${currency || '$'} (taxa: ${rate || '1'})`, true);
|
|
144
|
+
ok++;
|
|
145
|
+
} else {
|
|
146
|
+
logger.dim(' - Variáveis de moeda não definidas (usando defaults)');
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Summary
|
|
150
|
+
logger.blank();
|
|
151
|
+
if (issues === 0) {
|
|
152
|
+
logger.success(`Tudo OK! (${ok} verificações passaram)`);
|
|
153
|
+
} else {
|
|
154
|
+
logger.warn(`${issues} problema(s) encontrado(s), ${ok} OK`);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function check(msg, passed) {
|
|
159
|
+
if (passed) {
|
|
160
|
+
logger.success(msg);
|
|
161
|
+
} else {
|
|
162
|
+
logger.error(msg);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
module.exports = { runDoctor };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const inquirer = require('inquirer');
|
|
7
|
+
const logger = require('../utils/logger');
|
|
8
|
+
const { MANIFEST_PATH } = require('../installer');
|
|
9
|
+
const { removeSettingsKey, SETTINGS_PATH } = require('../utils/settings-merger');
|
|
10
|
+
|
|
11
|
+
async function runUninstall(options = {}) {
|
|
12
|
+
logger.section('Desinstalação');
|
|
13
|
+
|
|
14
|
+
// Check manifest
|
|
15
|
+
if (!(await fs.pathExists(MANIFEST_PATH))) {
|
|
16
|
+
logger.warn('Nenhuma instalação encontrada (manifest ausente).');
|
|
17
|
+
logger.dim(`Esperado em: ${MANIFEST_PATH}`);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const manifest = await fs.readJson(MANIFEST_PATH);
|
|
22
|
+
|
|
23
|
+
logger.info(`Instalação encontrada (v${manifest.version}, ${manifest.installedAt})`);
|
|
24
|
+
logger.info(`Módulos: ${manifest.modules.join(', ')}`);
|
|
25
|
+
logger.blank();
|
|
26
|
+
|
|
27
|
+
// Confirm
|
|
28
|
+
if (!options.force) {
|
|
29
|
+
const { confirm } = await inquirer.prompt([
|
|
30
|
+
{
|
|
31
|
+
type: 'confirm',
|
|
32
|
+
name: 'confirm',
|
|
33
|
+
message: 'Remover tudo que foi instalado pelo toolkit?',
|
|
34
|
+
default: false
|
|
35
|
+
}
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
if (!confirm) {
|
|
39
|
+
logger.info('Cancelado.');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const removed = [];
|
|
45
|
+
|
|
46
|
+
// Remove statusline
|
|
47
|
+
if (manifest.modules.includes('statusline')) {
|
|
48
|
+
const statusline = require('../modules/statusline');
|
|
49
|
+
const files = await statusline.uninstall();
|
|
50
|
+
removed.push(...files);
|
|
51
|
+
logger.success('Statusline removida');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Remove handoff
|
|
55
|
+
if (manifest.modules.includes('handoff')) {
|
|
56
|
+
const handoff = require('../modules/handoff');
|
|
57
|
+
const files = await handoff.uninstall();
|
|
58
|
+
removed.push(...files);
|
|
59
|
+
logger.success('Session Handoff removido');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Remove AIOS skill
|
|
63
|
+
if (manifest.modules.includes('aios')) {
|
|
64
|
+
const aios = require('../modules/aios');
|
|
65
|
+
const files = await aios.uninstall();
|
|
66
|
+
removed.push(...files);
|
|
67
|
+
logger.success('AIOS Skill removida');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Remove settings.json backup
|
|
71
|
+
const backupPath = SETTINGS_PATH + '.backup';
|
|
72
|
+
if (await fs.pathExists(backupPath)) {
|
|
73
|
+
await fs.remove(backupPath);
|
|
74
|
+
removed.push(backupPath);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Remove manifest
|
|
78
|
+
await fs.remove(MANIFEST_PATH);
|
|
79
|
+
removed.push(MANIFEST_PATH);
|
|
80
|
+
|
|
81
|
+
logger.blank();
|
|
82
|
+
logger.success(`Desinstalação completa (${removed.length} itens removidos)`);
|
|
83
|
+
|
|
84
|
+
if (manifest.aiosCore) {
|
|
85
|
+
logger.blank();
|
|
86
|
+
logger.info('AIOS Core NÃO foi removido (gerenciado separadamente).');
|
|
87
|
+
logger.dim('Para remover: npx aios-core uninstall');
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { runUninstall };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Command } = require('commander');
|
|
4
|
+
const pkg = require('../package.json');
|
|
5
|
+
const logger = require('./utils/logger');
|
|
6
|
+
const { runInstaller } = require('./installer');
|
|
7
|
+
const { runUninstall } = require('./commands/uninstall');
|
|
8
|
+
const { runDoctor } = require('./commands/doctor');
|
|
9
|
+
|
|
10
|
+
const program = new Command();
|
|
11
|
+
|
|
12
|
+
program
|
|
13
|
+
.name('claude-code-toolkit')
|
|
14
|
+
.description('Setup completo para Claude Code: statusline, session handoff, AIOS')
|
|
15
|
+
.version(pkg.version);
|
|
16
|
+
|
|
17
|
+
program
|
|
18
|
+
.command('install', { isDefault: true })
|
|
19
|
+
.description('Instala o toolkit (wizard interativo)')
|
|
20
|
+
.option('--statusline-only', 'Instala apenas a statusline')
|
|
21
|
+
.option('--handoff-only', 'Instala apenas o session handoff')
|
|
22
|
+
.option('--no-aios', 'Pula a configuração do AIOS')
|
|
23
|
+
.option('--currency <symbol>', 'Símbolo da moeda (ex: R$, $, €)')
|
|
24
|
+
.option('--currency-rate <rate>', 'Taxa de conversão USD→moeda', parseFloat)
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
logger.banner(pkg.version);
|
|
27
|
+
try {
|
|
28
|
+
await runInstaller(options);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
if (err.name === 'ExitPromptError' || err.message?.includes('prompt')) {
|
|
31
|
+
logger.blank();
|
|
32
|
+
logger.info('Instalação cancelada.');
|
|
33
|
+
process.exit(0);
|
|
34
|
+
}
|
|
35
|
+
logger.error(`Erro: ${err.message}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
program
|
|
41
|
+
.command('uninstall')
|
|
42
|
+
.description('Remove tudo que foi instalado pelo toolkit')
|
|
43
|
+
.option('--force', 'Remove sem confirmação')
|
|
44
|
+
.action(async (options) => {
|
|
45
|
+
logger.banner(pkg.version);
|
|
46
|
+
try {
|
|
47
|
+
await runUninstall(options);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
logger.error(`Erro: ${err.message}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
program
|
|
55
|
+
.command('doctor')
|
|
56
|
+
.description('Verifica integridade da instalação')
|
|
57
|
+
.action(async () => {
|
|
58
|
+
logger.banner(pkg.version);
|
|
59
|
+
try {
|
|
60
|
+
await runDoctor();
|
|
61
|
+
} catch (err) {
|
|
62
|
+
logger.error(`Erro: ${err.message}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
program.parse();
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const inquirer = require('inquirer');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const fs = require('fs-extra');
|
|
7
|
+
const logger = require('./utils/logger');
|
|
8
|
+
const modules = require('./modules');
|
|
9
|
+
|
|
10
|
+
const MANIFEST_PATH = path.join(os.homedir(), '.claude', '.toolkit-manifest.json');
|
|
11
|
+
|
|
12
|
+
const CURRENCY_PRESETS = [
|
|
13
|
+
{ name: 'BRL (R$) — Taxa: 6.20', value: { symbol: 'R$', rate: 6.20 } },
|
|
14
|
+
{ name: 'USD ($) — Sem conversão', value: { symbol: '$', rate: 1 } },
|
|
15
|
+
{ name: 'EUR (€) — Taxa: 0.92', value: { symbol: '€', rate: 0.92 } },
|
|
16
|
+
{ name: 'Personalizada — Definir símbolo e taxa', value: null }
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
async function runInstaller(cliOptions = {}) {
|
|
20
|
+
let selectedModules;
|
|
21
|
+
let currencyConfig;
|
|
22
|
+
let aiosOptions = {};
|
|
23
|
+
|
|
24
|
+
// --- Short-circuit for CLI flags ---
|
|
25
|
+
if (cliOptions.statuslineOnly) {
|
|
26
|
+
selectedModules = ['statusline'];
|
|
27
|
+
currencyConfig = {
|
|
28
|
+
symbol: cliOptions.currency || '$',
|
|
29
|
+
rate: cliOptions.currencyRate || 1
|
|
30
|
+
};
|
|
31
|
+
} else if (cliOptions.handoffOnly) {
|
|
32
|
+
selectedModules = ['handoff'];
|
|
33
|
+
} else {
|
|
34
|
+
// --- Interactive wizard ---
|
|
35
|
+
|
|
36
|
+
// Step 1: Module selection
|
|
37
|
+
const { selected } = await inquirer.prompt([
|
|
38
|
+
{
|
|
39
|
+
type: 'checkbox',
|
|
40
|
+
name: 'selected',
|
|
41
|
+
message: 'Selecione o que deseja instalar:',
|
|
42
|
+
choices: [
|
|
43
|
+
{
|
|
44
|
+
name: `${modules.statusline.name} — ${modules.statusline.description}`,
|
|
45
|
+
value: 'statusline',
|
|
46
|
+
checked: true
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: `${modules.handoff.name} — ${modules.handoff.description}`,
|
|
50
|
+
value: 'handoff',
|
|
51
|
+
checked: true
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
]);
|
|
56
|
+
selectedModules = selected;
|
|
57
|
+
|
|
58
|
+
if (selectedModules.length === 0) {
|
|
59
|
+
logger.warn('Nenhum módulo selecionado. Saindo.');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Step 2: Currency config (if statusline selected)
|
|
64
|
+
if (selectedModules.includes('statusline')) {
|
|
65
|
+
logger.section('Configuração da Statusline');
|
|
66
|
+
|
|
67
|
+
const { currencyPreset } = await inquirer.prompt([
|
|
68
|
+
{
|
|
69
|
+
type: 'list',
|
|
70
|
+
name: 'currencyPreset',
|
|
71
|
+
message: 'Moeda para exibição de custo:',
|
|
72
|
+
choices: CURRENCY_PRESETS
|
|
73
|
+
}
|
|
74
|
+
]);
|
|
75
|
+
|
|
76
|
+
if (currencyPreset === null) {
|
|
77
|
+
const custom = await inquirer.prompt([
|
|
78
|
+
{
|
|
79
|
+
type: 'input',
|
|
80
|
+
name: 'symbol',
|
|
81
|
+
message: 'Símbolo da moeda:',
|
|
82
|
+
default: '$'
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
type: 'number',
|
|
86
|
+
name: 'rate',
|
|
87
|
+
message: 'Taxa de conversão USD→moeda:',
|
|
88
|
+
default: 1
|
|
89
|
+
}
|
|
90
|
+
]);
|
|
91
|
+
currencyConfig = custom;
|
|
92
|
+
} else {
|
|
93
|
+
currencyConfig = currencyPreset;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Step 3: AIOS (opt-in)
|
|
98
|
+
if (cliOptions.aios !== false) {
|
|
99
|
+
logger.section('AIOS Framework');
|
|
100
|
+
|
|
101
|
+
const { useAios } = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: 'confirm',
|
|
104
|
+
name: 'useAios',
|
|
105
|
+
message: 'Vai utilizar o AIOS?',
|
|
106
|
+
default: false
|
|
107
|
+
}
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
if (useAios) {
|
|
111
|
+
const { aiosChoices } = await inquirer.prompt([
|
|
112
|
+
{
|
|
113
|
+
type: 'checkbox',
|
|
114
|
+
name: 'aiosChoices',
|
|
115
|
+
message: 'Selecione as opções AIOS:',
|
|
116
|
+
choices: [
|
|
117
|
+
{
|
|
118
|
+
name: 'Instalar/Atualizar AIOS Core',
|
|
119
|
+
value: 'core',
|
|
120
|
+
checked: true
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'Instalar Skill /aios-integrate (integrar e gerenciar artefatos)',
|
|
124
|
+
value: 'skill',
|
|
125
|
+
checked: true
|
|
126
|
+
}
|
|
127
|
+
]
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
|
|
131
|
+
selectedModules.push('aios');
|
|
132
|
+
aiosOptions = {
|
|
133
|
+
installAiosCore: aiosChoices.includes('core'),
|
|
134
|
+
installAiosSkill: aiosChoices.includes('skill')
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// --- Execute installation ---
|
|
141
|
+
logger.section('Instalando...');
|
|
142
|
+
|
|
143
|
+
const manifest = {
|
|
144
|
+
version: require('../package.json').version,
|
|
145
|
+
installedAt: new Date().toISOString(),
|
|
146
|
+
modules: selectedModules,
|
|
147
|
+
files: []
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Install statusline
|
|
151
|
+
if (selectedModules.includes('statusline')) {
|
|
152
|
+
const result = await modules.statusline.module.install({
|
|
153
|
+
currency: currencyConfig?.symbol,
|
|
154
|
+
currencyRate: currencyConfig?.rate
|
|
155
|
+
});
|
|
156
|
+
if (result.installed) {
|
|
157
|
+
manifest.files.push(...modules.statusline.module.getManifestEntries());
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Install handoff
|
|
162
|
+
if (selectedModules.includes('handoff')) {
|
|
163
|
+
const result = await modules.handoff.module.install();
|
|
164
|
+
if (result.installed) {
|
|
165
|
+
manifest.files.push(...modules.handoff.module.getManifestEntries());
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Install AIOS
|
|
170
|
+
if (selectedModules.includes('aios')) {
|
|
171
|
+
const result = await modules.aios.module.install(aiosOptions);
|
|
172
|
+
manifest.files.push(...modules.aios.module.getManifestEntries());
|
|
173
|
+
if (aiosOptions.installAiosCore && result.core?.installed) {
|
|
174
|
+
manifest.aiosCore = true;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Save manifest
|
|
179
|
+
await fs.ensureDir(path.dirname(MANIFEST_PATH));
|
|
180
|
+
await fs.writeJson(MANIFEST_PATH, manifest, { spaces: 2 });
|
|
181
|
+
|
|
182
|
+
// Final message
|
|
183
|
+
logger.blank();
|
|
184
|
+
logger.success('Pronto!');
|
|
185
|
+
logger.blank();
|
|
186
|
+
|
|
187
|
+
if (selectedModules.includes('statusline')) {
|
|
188
|
+
logger.info('Reinicie o Claude Code para ver a statusline.');
|
|
189
|
+
}
|
|
190
|
+
if (selectedModules.includes('aios') && aiosOptions.installAiosSkill) {
|
|
191
|
+
logger.info('Use /aios-integrate para integrar e gerenciar artefatos AIOS.');
|
|
192
|
+
}
|
|
193
|
+
logger.blank();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { runInstaller, MANIFEST_PATH };
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const { installFile } = require('../utils/file-installer');
|
|
7
|
+
const logger = require('../utils/logger');
|
|
8
|
+
|
|
9
|
+
const SKILL_SRC = path.join(__dirname, '..', '..', 'assets', 'skill', 'aios-integrate.md');
|
|
10
|
+
|
|
11
|
+
async function installCore({ targetDir } = {}) {
|
|
12
|
+
const cwd = targetDir || process.cwd();
|
|
13
|
+
|
|
14
|
+
logger.dim('Executando aios-core install...');
|
|
15
|
+
try {
|
|
16
|
+
execSync('npx aios-core@latest install', {
|
|
17
|
+
cwd,
|
|
18
|
+
stdio: 'inherit',
|
|
19
|
+
timeout: 180000
|
|
20
|
+
});
|
|
21
|
+
logger.success('AIOS Core instalado/atualizado');
|
|
22
|
+
return { installed: true };
|
|
23
|
+
} catch (err) {
|
|
24
|
+
logger.warn(`AIOS Core falhou: ${err.message}`);
|
|
25
|
+
logger.dim('Você pode instalar manualmente depois: npx aios-core install');
|
|
26
|
+
return { installed: false, error: err.message };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function installSkill({ targetDir } = {}) {
|
|
31
|
+
const cwd = targetDir || process.cwd();
|
|
32
|
+
const commandsDir = path.join(cwd, '.claude', 'commands');
|
|
33
|
+
const dest = path.join(commandsDir, 'aios-integrate.md');
|
|
34
|
+
|
|
35
|
+
await fs.ensureDir(commandsDir);
|
|
36
|
+
|
|
37
|
+
const result = await installFile(SKILL_SRC, dest);
|
|
38
|
+
|
|
39
|
+
if (result.action === 'installed') {
|
|
40
|
+
logger.success('Skill /aios-integrate instalada');
|
|
41
|
+
} else {
|
|
42
|
+
logger.success('Skill /aios-integrate já atualizada');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return { installed: true, path: dest };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function install({ installAiosCore = true, installAiosSkill = true, targetDir } = {}) {
|
|
49
|
+
logger.section('AIOS Framework');
|
|
50
|
+
|
|
51
|
+
const results = {};
|
|
52
|
+
|
|
53
|
+
if (installAiosCore) {
|
|
54
|
+
results.core = await installCore({ targetDir });
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (installAiosSkill) {
|
|
58
|
+
results.skill = await installSkill({ targetDir });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return results;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function uninstall({ targetDir } = {}) {
|
|
65
|
+
const cwd = targetDir || process.cwd();
|
|
66
|
+
const removed = [];
|
|
67
|
+
|
|
68
|
+
const skillPath = path.join(cwd, '.claude', 'commands', 'aios-integrate.md');
|
|
69
|
+
if (await fs.pathExists(skillPath)) {
|
|
70
|
+
await fs.remove(skillPath);
|
|
71
|
+
removed.push(skillPath);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return removed;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function getManifestEntries({ targetDir } = {}) {
|
|
78
|
+
const cwd = targetDir || process.cwd();
|
|
79
|
+
return [
|
|
80
|
+
path.join(cwd, '.claude', 'commands', 'aios-integrate.md')
|
|
81
|
+
];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = { install, uninstall, getManifestEntries };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { execSync } = require('child_process');
|
|
4
|
+
const logger = require('../utils/logger');
|
|
5
|
+
|
|
6
|
+
async function install({ targetDir } = {}) {
|
|
7
|
+
logger.section('Session Handoff');
|
|
8
|
+
|
|
9
|
+
const cwd = targetDir || process.cwd();
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
logger.dim('Executando claude-code-handoff...');
|
|
13
|
+
execSync('npx claude-code-handoff@latest', {
|
|
14
|
+
cwd,
|
|
15
|
+
stdio: 'inherit',
|
|
16
|
+
timeout: 120000
|
|
17
|
+
});
|
|
18
|
+
logger.success('claude-code-handoff instalado (7 commands, 2 hooks, 2 rules)');
|
|
19
|
+
return { installed: true };
|
|
20
|
+
} catch (err) {
|
|
21
|
+
logger.warn(`claude-code-handoff falhou: ${err.message}`);
|
|
22
|
+
logger.dim('Você pode instalar manualmente depois: npx claude-code-handoff');
|
|
23
|
+
return { installed: false, error: err.message };
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function uninstall({ targetDir } = {}) {
|
|
28
|
+
const cwd = targetDir || process.cwd();
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
execSync('npx claude-code-handoff@latest uninstall', {
|
|
32
|
+
cwd,
|
|
33
|
+
stdio: 'inherit',
|
|
34
|
+
timeout: 60000
|
|
35
|
+
});
|
|
36
|
+
return ['claude-code-handoff (commands, hooks, rules)'];
|
|
37
|
+
} catch {
|
|
38
|
+
logger.dim('claude-code-handoff uninstall não disponível — remova manualmente .claude/commands/');
|
|
39
|
+
return [];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getManifestEntries() {
|
|
44
|
+
return ['claude-code-handoff'];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { install, uninstall, getManifestEntries };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const statusline = require('./statusline');
|
|
4
|
+
const handoff = require('./handoff');
|
|
5
|
+
const aios = require('./aios');
|
|
6
|
+
|
|
7
|
+
const modules = {
|
|
8
|
+
statusline: {
|
|
9
|
+
name: 'Statusline',
|
|
10
|
+
description: 'Barra de status: modelo, contexto %, custo, tempo, git',
|
|
11
|
+
module: statusline,
|
|
12
|
+
default: true
|
|
13
|
+
},
|
|
14
|
+
handoff: {
|
|
15
|
+
name: 'Session Handoff',
|
|
16
|
+
description: 'Continuidade de sessão (via claude-code-handoff)',
|
|
17
|
+
module: handoff,
|
|
18
|
+
default: true
|
|
19
|
+
},
|
|
20
|
+
aios: {
|
|
21
|
+
name: 'AIOS Framework',
|
|
22
|
+
description: 'Framework de orquestração de agentes AI',
|
|
23
|
+
module: aios,
|
|
24
|
+
default: false
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
module.exports = modules;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const { installFile } = require('../utils/file-installer');
|
|
7
|
+
const { mergeSettings, removeSettingsKey } = require('../utils/settings-merger');
|
|
8
|
+
const logger = require('../utils/logger');
|
|
9
|
+
|
|
10
|
+
const CLAUDE_DIR = path.join(os.homedir(), '.claude');
|
|
11
|
+
const STATUSLINE_DEST = path.join(CLAUDE_DIR, 'statusline.sh');
|
|
12
|
+
const STATUSLINE_SRC = path.join(__dirname, '..', '..', 'assets', 'statusline', 'statusline.sh');
|
|
13
|
+
|
|
14
|
+
const STATUSLINE_SETTINGS = {
|
|
15
|
+
statusLine: {
|
|
16
|
+
command: `bash ${STATUSLINE_DEST}`
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
async function install({ currency, currencyRate } = {}) {
|
|
21
|
+
logger.section('Statusline');
|
|
22
|
+
|
|
23
|
+
// Install statusline.sh
|
|
24
|
+
const result = await installFile(STATUSLINE_SRC, STATUSLINE_DEST, { executable: true });
|
|
25
|
+
|
|
26
|
+
if (result.action === 'installed') {
|
|
27
|
+
logger.success(`Statusline instalada em ${STATUSLINE_DEST}`);
|
|
28
|
+
} else {
|
|
29
|
+
logger.success(`Statusline já atualizada em ${STATUSLINE_DEST}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Merge settings.json
|
|
33
|
+
await mergeSettings(STATUSLINE_SETTINGS);
|
|
34
|
+
logger.success('settings.json atualizado com statusLine');
|
|
35
|
+
|
|
36
|
+
// Create env hint file if currency was configured
|
|
37
|
+
if (currency || currencyRate) {
|
|
38
|
+
const envHints = [];
|
|
39
|
+
if (currency) envHints.push(`export CLAUDE_TOOLKIT_CURRENCY="${currency}"`);
|
|
40
|
+
if (currencyRate) envHints.push(`export CLAUDE_TOOLKIT_CURRENCY_RATE="${currencyRate}"`);
|
|
41
|
+
|
|
42
|
+
const envFile = path.join(CLAUDE_DIR, 'toolkit-env.sh');
|
|
43
|
+
await fs.writeFile(envFile, envHints.join('\n') + '\n');
|
|
44
|
+
logger.dim(`Variáveis de moeda salvas em ${envFile}`);
|
|
45
|
+
logger.dim('Adicione ao seu .zshrc/.bashrc: source ~/.claude/toolkit-env.sh');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Check jq
|
|
49
|
+
const { execSync } = require('child_process');
|
|
50
|
+
try {
|
|
51
|
+
execSync('which jq', { stdio: 'ignore' });
|
|
52
|
+
} catch {
|
|
53
|
+
logger.warn('jq não encontrado. A statusline requer jq para funcionar.');
|
|
54
|
+
logger.dim('Instale com: brew install jq (macOS) ou apt install jq (Linux)');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { installed: true };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function uninstall() {
|
|
61
|
+
const removed = [];
|
|
62
|
+
|
|
63
|
+
if (await fs.pathExists(STATUSLINE_DEST)) {
|
|
64
|
+
await fs.remove(STATUSLINE_DEST);
|
|
65
|
+
removed.push(STATUSLINE_DEST);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await removeSettingsKey('statusLine');
|
|
69
|
+
|
|
70
|
+
const envFile = path.join(CLAUDE_DIR, 'toolkit-env.sh');
|
|
71
|
+
if (await fs.pathExists(envFile)) {
|
|
72
|
+
await fs.remove(envFile);
|
|
73
|
+
removed.push(envFile);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return removed;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function getManifestEntries() {
|
|
80
|
+
return [
|
|
81
|
+
STATUSLINE_DEST,
|
|
82
|
+
path.join(CLAUDE_DIR, 'toolkit-env.sh')
|
|
83
|
+
];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = { install, uninstall, getManifestEntries };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
function fileHash(filePath) {
|
|
9
|
+
const content = fs.readFileSync(filePath);
|
|
10
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function installFile(src, dest, { executable = false } = {}) {
|
|
14
|
+
await fs.ensureDir(path.dirname(dest));
|
|
15
|
+
|
|
16
|
+
if (await fs.pathExists(dest)) {
|
|
17
|
+
const srcHash = fileHash(src);
|
|
18
|
+
const destHash = fileHash(dest);
|
|
19
|
+
if (srcHash === destHash) {
|
|
20
|
+
logger.dim(`Já atualizado: ${dest}`);
|
|
21
|
+
return { action: 'skipped', path: dest };
|
|
22
|
+
}
|
|
23
|
+
logger.dim(`Atualizando: ${dest}`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await fs.copy(src, dest);
|
|
27
|
+
|
|
28
|
+
if (executable) {
|
|
29
|
+
await fs.chmod(dest, 0o755);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return { action: 'installed', path: dest };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function uninstallFile(filePath) {
|
|
36
|
+
if (await fs.pathExists(filePath)) {
|
|
37
|
+
await fs.remove(filePath);
|
|
38
|
+
return { action: 'removed', path: filePath };
|
|
39
|
+
}
|
|
40
|
+
return { action: 'not_found', path: filePath };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
module.exports = { installFile, uninstallFile, fileHash };
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const chalk = require('chalk');
|
|
4
|
+
|
|
5
|
+
const logger = {
|
|
6
|
+
banner(version) {
|
|
7
|
+
console.log('');
|
|
8
|
+
console.log(chalk.cyan(' ╔══════════════════════════════════════╗'));
|
|
9
|
+
console.log(chalk.cyan(' ║') + chalk.bold.white(` claude-code-toolkit v${version}`.padEnd(38)) + chalk.cyan('║'));
|
|
10
|
+
console.log(chalk.cyan(' ║') + chalk.gray(' Setup completo para Claude Code ') + chalk.cyan('║'));
|
|
11
|
+
console.log(chalk.cyan(' ╚══════════════════════════════════════╝'));
|
|
12
|
+
console.log('');
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
section(title) {
|
|
16
|
+
console.log('');
|
|
17
|
+
console.log(chalk.cyan(` ─── ${title} ───`));
|
|
18
|
+
console.log('');
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
success(msg) {
|
|
22
|
+
console.log(chalk.green(' ✓ ') + msg);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
warn(msg) {
|
|
26
|
+
console.log(chalk.yellow(' ⚠ ') + msg);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
error(msg) {
|
|
30
|
+
console.log(chalk.red(' ✗ ') + msg);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
info(msg) {
|
|
34
|
+
console.log(chalk.blue(' ℹ ') + msg);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
dim(msg) {
|
|
38
|
+
console.log(chalk.dim(` ${msg}`));
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
blank() {
|
|
42
|
+
console.log('');
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
module.exports = logger;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const logger = require('./logger');
|
|
7
|
+
|
|
8
|
+
const SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json');
|
|
9
|
+
const BACKUP_SUFFIX = '.backup';
|
|
10
|
+
|
|
11
|
+
function deepMerge(target, source) {
|
|
12
|
+
const result = { ...target };
|
|
13
|
+
for (const key of Object.keys(source)) {
|
|
14
|
+
if (
|
|
15
|
+
source[key] &&
|
|
16
|
+
typeof source[key] === 'object' &&
|
|
17
|
+
!Array.isArray(source[key]) &&
|
|
18
|
+
target[key] &&
|
|
19
|
+
typeof target[key] === 'object' &&
|
|
20
|
+
!Array.isArray(target[key])
|
|
21
|
+
) {
|
|
22
|
+
result[key] = deepMerge(target[key], source[key]);
|
|
23
|
+
} else {
|
|
24
|
+
result[key] = source[key];
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function mergeSettings(newSettings) {
|
|
31
|
+
await fs.ensureDir(path.dirname(SETTINGS_PATH));
|
|
32
|
+
|
|
33
|
+
let existing = {};
|
|
34
|
+
if (await fs.pathExists(SETTINGS_PATH)) {
|
|
35
|
+
const backupPath = SETTINGS_PATH + BACKUP_SUFFIX;
|
|
36
|
+
await fs.copy(SETTINGS_PATH, backupPath);
|
|
37
|
+
logger.dim(`Backup criado: ${backupPath}`);
|
|
38
|
+
existing = await fs.readJson(SETTINGS_PATH);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const merged = deepMerge(existing, newSettings);
|
|
42
|
+
await fs.writeJson(SETTINGS_PATH, merged, { spaces: 2 });
|
|
43
|
+
return merged;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function removeSettingsKey(key) {
|
|
47
|
+
if (!(await fs.pathExists(SETTINGS_PATH))) return;
|
|
48
|
+
|
|
49
|
+
const settings = await fs.readJson(SETTINGS_PATH);
|
|
50
|
+
if (key in settings) {
|
|
51
|
+
delete settings[key];
|
|
52
|
+
await fs.writeJson(SETTINGS_PATH, settings, { spaces: 2 });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function readSettings() {
|
|
57
|
+
if (!(await fs.pathExists(SETTINGS_PATH))) return {};
|
|
58
|
+
return fs.readJson(SETTINGS_PATH);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = { mergeSettings, removeSettingsKey, readSettings, SETTINGS_PATH, deepMerge };
|