@vitortrack777/djinnai 0.0.0-stage → 0.6.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 +137 -2
- package/djinn-completion.bash +9 -0
- package/djinn.sh +246 -0
- package/index.js +88 -0
- package/package.json +65 -4
- package/server.js +350 -0
- package/web/dist/assets/index-CdLvctyU.js +112 -0
- package/web/dist/assets/index-DRQ_cItt.css +1 -0
- package/web/dist/index.html +13 -0
- package/window.cjs +20 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 vitortrack
|
|
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
CHANGED
|
@@ -1,3 +1,138 @@
|
|
|
1
|
-
|
|
1
|
+
<h1 align="center">DjinnAI</h1>
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
Uma tela local para conversar com o agente do <strong>Claude Code</strong> dentro do seu projeto.<br>
|
|
5
|
+
O agente pode ser o <strong>Claude</strong> ou um modelo do <strong>Ollama</strong>, e os comandos do ambiente ficam a um <code>/</code> de distância.
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://www.npmjs.com/package/@vitortrack777/djinnai"><img src="https://img.shields.io/npm/v/@vitortrack777/djinnai.svg" alt="versão no npm"></a>
|
|
10
|
+
<img src="https://img.shields.io/node/v/@vitortrack777/djinnai.svg" alt="node">
|
|
11
|
+
<a href="LICENSE"><img src="https://img.shields.io/npm/l/@vitortrack777/djinnai.svg" alt="licença MIT"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install -g @vitortrack777/djinnai
|
|
16
|
+
cd meu-projeto
|
|
17
|
+
djinn # abre o aplicativo (djinn --navegador abre no navegador)
|
|
18
|
+
djinn restart api # os comandos do ambiente continuam funcionando direto no terminal
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Como funciona
|
|
24
|
+
|
|
25
|
+
`djinn` sobe um servidor em `127.0.0.1`, numa porta livre, e abre a tela num **aplicativo de desktop**
|
|
26
|
+
(Electron, que vem com o pacote), no Linux, no Windows e no macOS, sem depender de navegador.
|
|
27
|
+
**Fechar a janela encerra o DjinnAI.** O endereço também aparece no terminal: dá para abrir no
|
|
28
|
+
navegador ao mesmo tempo, e as duas telas mostram a mesma conversa.
|
|
29
|
+
|
|
30
|
+
| Comando | Abre |
|
|
31
|
+
|---|---|
|
|
32
|
+
| `djinn` (ou `djinnai`) | A janela do aplicativo. Se ela não abrir (sem ambiente gráfico, por exemplo), cai no navegador padrão |
|
|
33
|
+
| `djinn --navegador` | Só o navegador padrão, como numa aba comum |
|
|
34
|
+
|
|
35
|
+
A conversa é
|
|
36
|
+
uma sessão do Claude Code aberta na raiz do projeto, no mesmo protocolo que o Agent SDK usa
|
|
37
|
+
(`stream-json` de entrada e saída). Por isso a tela tem o que o Claude Code tem:
|
|
38
|
+
|
|
39
|
+
- o texto aparece enquanto o agente escreve, com raciocínio, ferramentas, tempo e tokens;
|
|
40
|
+
- o agente **pede permissão** na tela antes de editar ou rodar comando, mostrando o comando ou o diff;
|
|
41
|
+
- o agente **faz perguntas** com opções e **apresenta o plano** para você aprovar no modo Planejar;
|
|
42
|
+
- dá para **mandar mensagem com o agente trabalhando**: ela entra na fila e o agente lê assim que puder;
|
|
43
|
+
- **prints colados** (Ctrl+V) ou arrastados para a tela vão junto com a mensagem;
|
|
44
|
+
- o `/` lista as skills e comandos do Claude Code do projeto, além dos comandos do ambiente;
|
|
45
|
+
- recarregar a página não perde a conversa, que continua até você digitar `/novo` ou fechar o DjinnAI.
|
|
46
|
+
|
|
47
|
+
Quem decide o que fazer é o agente. O DjinnAI não tem templates nem validadores próprios: ele só repassa
|
|
48
|
+
o pedido, o modelo, o esforço e o modo de permissão, mais o contexto do projeto descrito abaixo.
|
|
49
|
+
|
|
50
|
+
## Requisitos
|
|
51
|
+
|
|
52
|
+
| Requisito | Para quê |
|
|
53
|
+
|---|---|
|
|
54
|
+
| [Node.js](https://nodejs.org) 22.12+ (recomendado: 24 LTS) | Executar o DjinnAI e o Electron |
|
|
55
|
+
| [Claude Code](https://docs.anthropic.com/claude-code) instalado e logado (`claude`) | O agente |
|
|
56
|
+
| [Ollama](https://ollama.com) 0.14+ (opcional) | Usar modelos do Ollama como agente |
|
|
57
|
+
| Script `appDev` ou `app` na raiz (opcional) | Os comandos do ambiente (`/start`, `/logs`…) |
|
|
58
|
+
|
|
59
|
+
## A tela
|
|
60
|
+
|
|
61
|
+
| Parte | O que faz |
|
|
62
|
+
|---|---|
|
|
63
|
+
| Contexto e **Nova conversa**, no topo | Quantos tokens a conversa já carrega (o agente relê tudo a cada passo). Acima de 100k a tela sugere uma conversa nova; o botão (ou `/novo`) começa uma sessão limpa. Cada `djinn` também começa numa sessão nova |
|
|
64
|
+
| Caixa de pedido | Enter envia, Shift+Enter quebra a linha. Com o agente trabalhando, Enter põe a mensagem na fila e o botão vira **parar** |
|
|
65
|
+
| `/` | Lista `/novo` (conversa nova), os comandos do ambiente (`/start`, `/restart api`, `/logs --tail=100 api`, `/status`…) e as skills do Claude Code. A saída do ambiente aparece na tela, e o botão de parar encerra o comando (útil no `logs -f`) |
|
|
66
|
+
| Seletor de modelo | Os modelos que a sua conta do Claude Code oferece ou qualquer modelo instalado no Ollama. O modelo do Ollama roda **dentro do Claude Code**, com as mesmas ferramentas e os mesmos `CLAUDE.md` |
|
|
67
|
+
| Esforço | Baixo a Máximo, repassado como `--effort` (vale a partir da próxima mensagem com o agente parado) |
|
|
68
|
+
| Modo, no topo | **Planejar** (planeja e pede aprovação do plano; aprovar passa para Editar), **Perguntar** (pede permissão para cada edição e comando), **Editar** (edita sem perguntar; comandos fora das regras do projeto pedem permissão), **Automático** (o Claude Code decide o que é seguro). Muda na hora, mesmo no meio de uma resposta |
|
|
69
|
+
|
|
70
|
+
No cartão de permissão, **Permitir e não perguntar de novo** grava a regra sugerida pelo Claude Code, como no terminal.
|
|
71
|
+
|
|
72
|
+
Os comandos destrutivos do ambiente (`execute`, que recria o banco, e `auto-remove`, que apaga
|
|
73
|
+
volumes) não aparecem na tela. Use pelo terminal: `djinn execute <banco> arquivo.sql`.
|
|
74
|
+
|
|
75
|
+
## Contexto que o agente recebe
|
|
76
|
+
|
|
77
|
+
O Claude Code já carrega os `CLAUDE.md`, as configurações do projeto (`.claude/`), as skills e o git
|
|
78
|
+
status. O DjinnAI acrescenta ao system prompt (`--append-system-prompt`):
|
|
79
|
+
|
|
80
|
+
- as pastas versionadas do projeto, até 4 níveis e no máximo 150, para o agente não gastar passos procurando;
|
|
81
|
+
- o conteúdo de `.djinn/contexto.md`, se existir, como instrução.
|
|
82
|
+
|
|
83
|
+
Use o `.djinn/contexto.md` para o que vale só para você e não pertence ao repositório: vocabulário do
|
|
84
|
+
domínio, armadilhas conhecidas, onde fica cada coisa, o card em andamento. Ele é lido no começo de
|
|
85
|
+
cada conversa: depois de editar, digite `/novo`.
|
|
86
|
+
|
|
87
|
+
## Configuração
|
|
88
|
+
|
|
89
|
+
O modelo, o esforço e o modo escolhidos na tela ficam salvos em `.djinn/config.json`:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"model": "claude:opus",
|
|
94
|
+
"effort": "high",
|
|
95
|
+
"mode": "acceptEdits",
|
|
96
|
+
"ollamaUrl": "http://127.0.0.1:11434"
|
|
97
|
+
}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
O retorno bruto da última mensagem ao agente fica em `.djinn/last-turn.ndjson`, para diagnóstico.
|
|
101
|
+
|
|
102
|
+
| Variável de ambiente | Descrição |
|
|
103
|
+
|---|---|
|
|
104
|
+
| `DJINN_PORT` | Porta fixa no lugar de uma porta livre |
|
|
105
|
+
| `DJINN_APP_SCRIPT` | Script de ambiente no lugar de `appDev`/`app` |
|
|
106
|
+
|
|
107
|
+
Projetos que já têm `.detro/` continuam usando essa pasta, e `DETRO_PORT`/`DETRO_APP_SCRIPT` seguem valendo quando a variável `DJINN_` correspondente não está definida.
|
|
108
|
+
|
|
109
|
+
O DjinnAI remove `ANTHROPIC_API_KEY` do ambiente do agente: o uso conta na sua assinatura do Claude Code,
|
|
110
|
+
não na API paga. Com modelo de nuvem do Ollama (`:cloud`), o código lido pelo agente sai da máquina.
|
|
111
|
+
|
|
112
|
+
## Segurança
|
|
113
|
+
|
|
114
|
+
- O servidor só escuta em `127.0.0.1`.
|
|
115
|
+
- Toda chamada à API exige o token aleatório que vai na URL (`?t=…`), gerado a cada execução. Outro
|
|
116
|
+
site aberto no navegador não consegue disparar o agente.
|
|
117
|
+
- Um comando do ambiente por vez: enquanto um roda, os outros são recusados.
|
|
118
|
+
- A resposta do modelo aparece como Markdown, sem HTML, sem imagens e sem links que não sejam http(s).
|
|
119
|
+
|
|
120
|
+
## Instalação a partir do código
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
git clone git@github.com:vitortrack/djinnai.git
|
|
124
|
+
cd djinnai
|
|
125
|
+
bash ./djinn.sh install # npm ci + build da tela + npm link + autocompletar do bash
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
| Comando | O que faz |
|
|
129
|
+
|---|---|
|
|
130
|
+
| `bash ./djinn.sh update` | O mesmo que `install`, depois de um `git pull` |
|
|
131
|
+
| `bash ./djinn.sh uninstall` | Remove o link global e o autocompletar |
|
|
132
|
+
| `npm run build` | Gera `web/dist`, que é o que o pacote publica |
|
|
133
|
+
| `npm test` | Checa o Markdown (sem HTML nem links perigosos) e como a tela monta a conversa a partir dos eventos |
|
|
134
|
+
|
|
135
|
+
A tela é React + Vite e usa componentes do [React Bits](https://reactbits.dev) (PromptBar,
|
|
136
|
+
ThoughtLine, LatticeLoader, StatusMark, RubberSegment), copiados para `web/src/components/`. O
|
|
137
|
+
servidor (`server.js`) usa só a biblioteca padrão do Node; a única dependência do pacote é o Electron,
|
|
138
|
+
que desenha a janela (`window.cjs`).
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Autocomplete Bash do DjinnAI.
|
|
2
|
+
|
|
3
|
+
_djinn_completion() {
|
|
4
|
+
if [[ ${COMP_CWORD} -eq 1 ]]; then
|
|
5
|
+
COMPREPLY=($(compgen -W "start restart rebuild logs status stop clean execute hot-reload auto-remove worker help version" -- "${COMP_WORDS[1]}"))
|
|
6
|
+
fi
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
complete -F _djinn_completion djinn djinnai
|
package/djinn.sh
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -Eeuo pipefail
|
|
4
|
+
|
|
5
|
+
readonly CLI_NAME="djinn"
|
|
6
|
+
readonly SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
|
+
readonly PACKAGE_FILE="${SCRIPT_DIR}/package.json"
|
|
8
|
+
readonly PACKAGE_LOCK_FILE="${SCRIPT_DIR}/package-lock.json"
|
|
9
|
+
readonly INDEX_FILE="${SCRIPT_DIR}/index.js"
|
|
10
|
+
readonly COMPLETION_SOURCE="${SCRIPT_DIR}/djinn-completion.bash"
|
|
11
|
+
|
|
12
|
+
readonly COMPLETION_DIR="${HOME}/.local/share/bash-completion/completions"
|
|
13
|
+
readonly COMPLETION_FILE="${COMPLETION_DIR}/${CLI_NAME}"
|
|
14
|
+
readonly BASHRC_FILE="${HOME}/.bashrc"
|
|
15
|
+
|
|
16
|
+
readonly BASHRC_MARKER_START="# >>> DJINNAI >>>"
|
|
17
|
+
readonly BASHRC_MARKER_END="# <<< DJINNAI <<<"
|
|
18
|
+
|
|
19
|
+
info() {
|
|
20
|
+
printf '\033[1;36m%s\033[0m\n' "$1"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
success() {
|
|
24
|
+
printf '\033[1;32m%s\033[0m\n' "$1"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
warning() {
|
|
28
|
+
printf '\033[1;33m%s\033[0m\n' "$1"
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
error() {
|
|
32
|
+
printf '\033[1;31mErro: %s\033[0m\n' "$1" >&2
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
die() {
|
|
36
|
+
error "$1"
|
|
37
|
+
exit 1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
on_error() {
|
|
41
|
+
local exit_code=$?
|
|
42
|
+
local line_number="${1:-desconhecida}"
|
|
43
|
+
|
|
44
|
+
error "A instalação falhou na linha ${line_number}."
|
|
45
|
+
exit "${exit_code}"
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
trap 'on_error "$LINENO"' ERR
|
|
49
|
+
|
|
50
|
+
command_exists() {
|
|
51
|
+
command -v "$1" >/dev/null 2>&1
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
validate_environment() {
|
|
55
|
+
command_exists node || die "Node.js não está instalado."
|
|
56
|
+
command_exists npm || die "npm não está instalado."
|
|
57
|
+
|
|
58
|
+
[[ -f "${PACKAGE_FILE}" ]] ||
|
|
59
|
+
die "Arquivo não encontrado: ${PACKAGE_FILE}"
|
|
60
|
+
|
|
61
|
+
[[ -f "${INDEX_FILE}" ]] ||
|
|
62
|
+
die "Arquivo não encontrado: ${INDEX_FILE}"
|
|
63
|
+
|
|
64
|
+
[[ -f "${COMPLETION_SOURCE}" ]] ||
|
|
65
|
+
die "Arquivo não encontrado: ${COMPLETION_SOURCE}"
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
ensure_permissions() {
|
|
69
|
+
chmod +x "${INDEX_FILE}"
|
|
70
|
+
chmod +x "${SCRIPT_DIR}/djinn.sh"
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
install_dependencies() {
|
|
74
|
+
info "Instalando dependências..."
|
|
75
|
+
|
|
76
|
+
cd "${SCRIPT_DIR}"
|
|
77
|
+
|
|
78
|
+
if [[ -f "${PACKAGE_LOCK_FILE}" ]]; then
|
|
79
|
+
npm ci --no-audit --no-fund
|
|
80
|
+
else
|
|
81
|
+
npm install --no-audit --no-fund
|
|
82
|
+
fi
|
|
83
|
+
|
|
84
|
+
info "Gerando a tela web..."
|
|
85
|
+
npm run build
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
install_cli() {
|
|
89
|
+
info "Registrando comando ${CLI_NAME}..."
|
|
90
|
+
cd "${SCRIPT_DIR}"
|
|
91
|
+
npm link
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
install_completion() {
|
|
95
|
+
info "Instalando autocomplete..."
|
|
96
|
+
|
|
97
|
+
mkdir -p "${COMPLETION_DIR}"
|
|
98
|
+
|
|
99
|
+
install \
|
|
100
|
+
-m 0644 \
|
|
101
|
+
"${COMPLETION_SOURCE}" \
|
|
102
|
+
"${COMPLETION_FILE}"
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
remove_old_bashrc_configuration() {
|
|
106
|
+
[[ -f "${BASHRC_FILE}" ]] || return 0
|
|
107
|
+
|
|
108
|
+
local temporary_file
|
|
109
|
+
temporary_file="$(mktemp)"
|
|
110
|
+
|
|
111
|
+
awk \
|
|
112
|
+
-v start="${BASHRC_MARKER_START}" \
|
|
113
|
+
-v end="${BASHRC_MARKER_END}" \
|
|
114
|
+
'
|
|
115
|
+
$0 == start {
|
|
116
|
+
removing = 1
|
|
117
|
+
next
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
$0 == end {
|
|
121
|
+
removing = 0
|
|
122
|
+
next
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
!removing {
|
|
126
|
+
print
|
|
127
|
+
}
|
|
128
|
+
' "${BASHRC_FILE}" > "${temporary_file}"
|
|
129
|
+
|
|
130
|
+
mv "${temporary_file}" "${BASHRC_FILE}"
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
configure_bashrc() {
|
|
134
|
+
info "Configurando o Bash..."
|
|
135
|
+
|
|
136
|
+
touch "${BASHRC_FILE}"
|
|
137
|
+
|
|
138
|
+
remove_old_bashrc_configuration
|
|
139
|
+
|
|
140
|
+
cat >> "${BASHRC_FILE}" <<EOF
|
|
141
|
+
|
|
142
|
+
${BASHRC_MARKER_START}
|
|
143
|
+
if [[ -f "${COMPLETION_FILE}" ]]; then
|
|
144
|
+
source "${COMPLETION_FILE}"
|
|
145
|
+
fi
|
|
146
|
+
${BASHRC_MARKER_END}
|
|
147
|
+
EOF
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
verify_installation() {
|
|
151
|
+
hash -r
|
|
152
|
+
|
|
153
|
+
local installed_cli
|
|
154
|
+
|
|
155
|
+
installed_cli="$(command -v "${CLI_NAME}" 2>/dev/null || true)"
|
|
156
|
+
|
|
157
|
+
if [[ -z "${installed_cli}" ]]; then
|
|
158
|
+
warning "A CLI foi instalada, mas o comando ainda não apareceu no PATH atual."
|
|
159
|
+
warning "Abra um novo terminal para atualizar o ambiente."
|
|
160
|
+
return 0
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
success "CLI disponível em: ${installed_cli}"
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
uninstall_cli() {
|
|
167
|
+
info "Removendo DjinnAI..."
|
|
168
|
+
|
|
169
|
+
cd "${SCRIPT_DIR}"
|
|
170
|
+
|
|
171
|
+
npm unlink --global "${CLI_NAME}" >/dev/null 2>&1 ||
|
|
172
|
+
npm unlink --global >/dev/null 2>&1 ||
|
|
173
|
+
true
|
|
174
|
+
|
|
175
|
+
rm -f "${COMPLETION_FILE}"
|
|
176
|
+
|
|
177
|
+
remove_old_bashrc_configuration
|
|
178
|
+
|
|
179
|
+
success "DjinnAI removida."
|
|
180
|
+
warning "Abra um novo terminal para concluir a atualização do ambiente."
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
show_help() {
|
|
184
|
+
cat <<EOF
|
|
185
|
+
Uso:
|
|
186
|
+
|
|
187
|
+
bash ./djinn.sh
|
|
188
|
+
bash ./djinn.sh install
|
|
189
|
+
bash ./djinn.sh update
|
|
190
|
+
bash ./djinn.sh uninstall
|
|
191
|
+
bash ./djinn.sh help
|
|
192
|
+
|
|
193
|
+
Comandos:
|
|
194
|
+
|
|
195
|
+
install Instala a CLI e o autocomplete
|
|
196
|
+
update Atualiza a instalação existente
|
|
197
|
+
uninstall Remove a CLI e o autocomplete
|
|
198
|
+
help Exibe esta ajuda
|
|
199
|
+
|
|
200
|
+
Sem argumentos, o comando executado será "install".
|
|
201
|
+
EOF
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
install_all() {
|
|
205
|
+
validate_environment
|
|
206
|
+
ensure_permissions
|
|
207
|
+
install_dependencies
|
|
208
|
+
install_cli
|
|
209
|
+
install_completion
|
|
210
|
+
configure_bashrc
|
|
211
|
+
verify_installation
|
|
212
|
+
|
|
213
|
+
echo
|
|
214
|
+
success "DjinnAI instalada com sucesso."
|
|
215
|
+
echo
|
|
216
|
+
printf 'Use:\n'
|
|
217
|
+
printf ' %s\n' "${CLI_NAME}"
|
|
218
|
+
printf ' %s restart\n' "${CLI_NAME}"
|
|
219
|
+
printf ' %s logs\n' "${CLI_NAME}"
|
|
220
|
+
echo
|
|
221
|
+
warning "O autocomplete estará disponível automaticamente nos próximos terminais."
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
main() {
|
|
225
|
+
local action="${1:-install}"
|
|
226
|
+
|
|
227
|
+
case "${action}" in
|
|
228
|
+
install|update)
|
|
229
|
+
install_all
|
|
230
|
+
;;
|
|
231
|
+
|
|
232
|
+
uninstall|remove)
|
|
233
|
+
uninstall_cli
|
|
234
|
+
;;
|
|
235
|
+
|
|
236
|
+
help|-h|--help)
|
|
237
|
+
show_help
|
|
238
|
+
;;
|
|
239
|
+
|
|
240
|
+
*)
|
|
241
|
+
die "Comando desconhecido: ${action}. Use: bash ./djinn.sh help"
|
|
242
|
+
;;
|
|
243
|
+
esac
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
main "$@"
|
package/index.js
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
import { startServer } from './server.js';
|
|
9
|
+
|
|
10
|
+
function findProjectRoot(start) {
|
|
11
|
+
for (const marker of ['.djinn', '.detro', '.git', 'package.json']) {
|
|
12
|
+
for (let dir = start; ; dir = path.dirname(dir)) {
|
|
13
|
+
if (existsSync(path.join(dir, marker))) return dir;
|
|
14
|
+
if (path.dirname(dir) === dir) break;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return start;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const projectDir = findProjectRoot(process.cwd());
|
|
21
|
+
const appScript = [process.env.DJINN_APP_SCRIPT ?? process.env.DETRO_APP_SCRIPT, 'appDev', 'app']
|
|
22
|
+
.filter(Boolean)
|
|
23
|
+
.map((name) => path.resolve(projectDir, name))
|
|
24
|
+
.find((file) => existsSync(file)) ?? path.join(projectDir, 'appDev');
|
|
25
|
+
|
|
26
|
+
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const QUICK_EXIT_MS = 3000;
|
|
28
|
+
const { version } = JSON.parse(readFileSync(path.join(HERE, 'package.json'), 'utf8'));
|
|
29
|
+
|
|
30
|
+
function openBrowser(url) {
|
|
31
|
+
const opener = { darwin: ['open'], win32: ['cmd', '/c', 'start', ''] }[process.platform] ?? ['xdg-open'];
|
|
32
|
+
spawn(opener[0], [...opener.slice(1), url], { stdio: 'ignore', detached: true }).on('error', () => {}).unref();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function sandboxFlags(electron) {
|
|
36
|
+
if (process.platform !== 'linux') return [];
|
|
37
|
+
try {
|
|
38
|
+
const { uid, mode } = statSync(path.join(path.dirname(electron), 'chrome-sandbox'));
|
|
39
|
+
if (uid === 0 && mode & 0o4000) return [];
|
|
40
|
+
} catch {}
|
|
41
|
+
return ['--no-sandbox'];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function openAppWindow(url, onClose) {
|
|
45
|
+
const electron = await import('electron').then((module) => module.default, () => null);
|
|
46
|
+
let fellBack = false;
|
|
47
|
+
const fallback = () => {
|
|
48
|
+
if (fellBack) return;
|
|
49
|
+
fellBack = true;
|
|
50
|
+
console.log('Não consegui abrir a janela do aplicativo; abrindo no navegador.');
|
|
51
|
+
openBrowser(url);
|
|
52
|
+
};
|
|
53
|
+
if (!electron) return fallback();
|
|
54
|
+
const startedAt = Date.now();
|
|
55
|
+
const { ELECTRON_RUN_AS_NODE, ...env } = process.env;
|
|
56
|
+
const window = spawn(electron, [...sandboxFlags(electron), path.join(HERE, 'window.cjs'), url], { stdio: 'ignore', env });
|
|
57
|
+
window.on('error', fallback);
|
|
58
|
+
window.on('exit', () => (Date.now() - startedAt < QUICK_EXIT_MS ? fallback() : onClose()));
|
|
59
|
+
return window;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const args = process.argv.slice(2);
|
|
63
|
+
|
|
64
|
+
if (['version', '--version', '-v'].includes(args[0])) {
|
|
65
|
+
console.log(`DjinnAI ${version}`);
|
|
66
|
+
} else if (args.length > 0 && args[0] !== '--navegador') {
|
|
67
|
+
spawn(appScript, args, { cwd: projectDir, stdio: 'inherit' })
|
|
68
|
+
.on('error', (error) => {
|
|
69
|
+
console.error(`Não foi possível executar ${appScript} (${error.message}). Confira se ele existe e é executável (chmod +x).`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
})
|
|
72
|
+
.on('close', (code) => process.exit(code ?? 1));
|
|
73
|
+
} else {
|
|
74
|
+
const { url, close } = await startServer({ projectDir, appScript, version, port: Number(process.env.DJINN_PORT ?? process.env.DETRO_PORT) || 0 }).catch((error) => {
|
|
75
|
+
console.error(error.message);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
});
|
|
78
|
+
console.log(`DjinnAI ${version} — ${projectDir}\n\n ${url}\n\nAbra o endereço no navegador se quiser acompanhar por lá também. Feche a janela ou use Ctrl+C para encerrar.`);
|
|
79
|
+
const quit = () => {
|
|
80
|
+
close();
|
|
81
|
+
process.exit(0);
|
|
82
|
+
};
|
|
83
|
+
const window = args[0] === '--navegador' ? openBrowser(url) : await openAppWindow(url, quit);
|
|
84
|
+
process.on('SIGINT', () => {
|
|
85
|
+
window?.kill();
|
|
86
|
+
quit();
|
|
87
|
+
});
|
|
88
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,67 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vitortrack777/djinnai",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"description": "DjinnAI: aplicativo local para conversar com o agente do Claude Code (Claude ou modelos do Ollama) e rodar os comandos do ambiente do projeto.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"djinn": "./index.js",
|
|
8
|
+
"djinnai": "./index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node index.js",
|
|
12
|
+
"test": "node web/src/lib/markdown.test.js && node web/src/lib/turns.test.js",
|
|
13
|
+
"build": "vite build --config web/vite.config.js",
|
|
14
|
+
"prepublishOnly": "npm run build",
|
|
15
|
+
"setup": "bash ./djinn.sh install",
|
|
16
|
+
"update": "bash ./djinn.sh update",
|
|
17
|
+
"uninstall-cli": "bash ./djinn.sh uninstall"
|
|
18
|
+
},
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=22.12"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"server.js",
|
|
26
|
+
"window.cjs",
|
|
27
|
+
"web/dist/",
|
|
28
|
+
"djinn.sh",
|
|
29
|
+
"djinn-completion.bash",
|
|
30
|
+
"LICENSE"
|
|
31
|
+
],
|
|
32
|
+
"keywords": [
|
|
33
|
+
"djinnai",
|
|
34
|
+
"djinn",
|
|
35
|
+
"cli",
|
|
36
|
+
"ai",
|
|
37
|
+
"claude",
|
|
38
|
+
"claude-code",
|
|
39
|
+
"ollama",
|
|
40
|
+
"agent"
|
|
41
|
+
],
|
|
42
|
+
"author": "vitortrack",
|
|
43
|
+
"repository": {
|
|
44
|
+
"type": "git",
|
|
45
|
+
"url": "git+https://github.com/vitortrack/djinnai.git"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://github.com/vitortrack/djinnai#readme",
|
|
48
|
+
"bugs": {
|
|
49
|
+
"url": "https://github.com/vitortrack/djinnai/issues"
|
|
50
|
+
},
|
|
51
|
+
"publishConfig": {
|
|
52
|
+
"access": "public"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@hugeicons/core-free-icons": "^4.3.5",
|
|
56
|
+
"@hugeicons/react": "^1.1.10",
|
|
57
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
58
|
+
"marked": "^15.0.12",
|
|
59
|
+
"motion": "^12.43.0",
|
|
60
|
+
"react": "^19.3.1",
|
|
61
|
+
"react-dom": "^19.3.1",
|
|
62
|
+
"vite": "^6.4.5"
|
|
63
|
+
},
|
|
64
|
+
"dependencies": {
|
|
65
|
+
"electron": "44.7.1"
|
|
66
|
+
}
|
|
67
|
+
}
|