arsenal-agent 0.1.4 → 0.2.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/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # arsenal-agent
2
2
 
3
- Dual AI agent CLI roteamento automático entre Claude Sonnet e Nemotron (free).
4
-
5
- Tasks simples vão pro Nemotron via OpenCode (grátis). Tasks complexas vão pro Claude Sonnet.
3
+ Launcher do Claude Code com gerenciamento de múltiplas contas via OAuth.
6
4
 
7
5
  ## Instalação
8
6
 
@@ -10,27 +8,73 @@ Tasks simples vão pro Nemotron via OpenCode (grátis). Tasks complexas vão pro
10
8
  npm install -g arsenal-agent
11
9
  ```
12
10
 
11
+ **Dependências:**
12
+ - [`claude`](https://claude.ai/code) — Claude Code CLI
13
+ - [`opencode`](https://opencode.ai) — OpenCode CLI (opcional, para delegação de tasks)
14
+ - Node.js >= 20
15
+
16
+ ## Configuração inicial
17
+
18
+ Após instalar, cadastre suas contas (abre o navegador para cada uma):
19
+
20
+ ```bash
21
+ aa profile add pessoal
22
+ aa profile add trabalho
23
+ ```
24
+
25
+ Adicione aliases rápidos no `~/.bashrc`:
26
+
27
+ ```bash
28
+ alias aa-p='aa --profile pessoal'
29
+ alias aa-z='aa --profile trabalho' # adapte a letra para cada conta
30
+ ```
31
+
13
32
  ## Uso
14
33
 
15
34
  ```bash
16
- arsenal-agent
17
- # ou o alias curto:
18
- aa
35
+ aa # abre Claude com o perfil ativo
36
+ aa-p # conta pessoal
37
+ aa-z # conta trabalho
38
+
39
+ aa profile list # ver contas cadastradas
40
+ aa profile use trabalho # trocar conta padrão
19
41
  ```
20
42
 
21
- ## Requisitos
43
+ ## Onde ficam as credenciais
44
+
45
+ ```
46
+ ~/.arsenal-agent/
47
+ profiles.json ← índice de perfis (chmod 600)
48
+ credentials/
49
+ pessoal.json ← token OAuth da conta pessoal
50
+ trabalho.json ← token OAuth da conta trabalho
51
+ ```
22
52
 
23
- - `opencode` instalado: `npm install -g opencode-ai`
24
- - `ANTHROPIC_API_KEY` no ambiente
25
- - `OPENROUTER_API_KEY` no ambiente
53
+ **As credenciais são locais nunca vão para o git.**
54
+ Em uma máquina nova você precisa rodar `aa profile add` novamente para cada conta.
26
55
 
27
- ## Como funciona
56
+ ## Instalar em outra máquina
28
57
 
58
+ ```bash
59
+ # 1. Instala o CLI
60
+ npm install -g arsenal-agent
61
+
62
+ # 2. Autentica as contas (processo manual — abre navegador)
63
+ aa profile add pessoal
64
+ aa profile add trabalho
65
+
66
+ # 3. Adiciona aliases no ~/.bashrc
67
+ echo "alias aa-p='aa --profile pessoal'" >> ~/.bashrc
68
+ echo "alias aa-z='aa --profile trabalho'" >> ~/.bashrc
69
+ source ~/.bashrc
29
70
  ```
30
- Você digita uma task
31
- └── ROUTER classifica
32
- ├── task simples → Nemotron 120B (OpenRouter, free)
33
- └── task complexa Claude Sonnet 4.6 (Anthropic)
71
+
72
+ ## Integração com tmux
73
+
74
+ Copie o `claude-picker.sh` para `~/projetos/arsenal/scripts/` e adicione ao `~/.tmux.conf`:
75
+
76
+ ```tmux
77
+ bind-key S run-shell "bash ~/projetos/arsenal/scripts/claude-picker.sh"
34
78
  ```
35
79
 
36
- Teclado: `ctrl+c` sair · `ctrl+l` limpar
80
+ `Ctrl+B S` abre o picker de sessões. `Ctrl+N` cria nova sessão e pergunta qual conta usar.
package/dist/index.js CHANGED
@@ -3,6 +3,7 @@ import { spawn } from "node:child_process";
3
3
  import { Command } from "commander";
4
4
  import chalk from "chalk";
5
5
  import { loadProfile, applyProfile, addProfile, listProfiles, useProfile } from "./profiles.js";
6
+ import { setup } from "./setup.js";
6
7
  const program = new Command();
7
8
  program
8
9
  .name("arsenal-agent")
@@ -12,6 +13,10 @@ program
12
13
  .argument("[args...]", "argumentos passados direto ao claude")
13
14
  .allowUnknownOption(true)
14
15
  .passThroughOptions(true);
16
+ program
17
+ .command("setup")
18
+ .description("instala e configura tudo: brain, opencode, plugins, MCPs, aliases")
19
+ .action(async () => { await setup(); process.exit(0); });
15
20
  const profileCmd = program.command("profile").description("gerenciar perfis");
16
21
  profileCmd.command("add [name]").description("adicionar perfil").action(async (name) => { await addProfile(name); process.exit(0); });
17
22
  profileCmd.command("list").alias("ls").description("listar perfis").action(() => { listProfiles(); process.exit(0); });
package/dist/setup.js ADDED
@@ -0,0 +1,133 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { join } from "node:path";
5
+ import chalk from "chalk";
6
+ const HOME = homedir();
7
+ const CLAUDE_DIR = join(HOME, ".claude");
8
+ const BRAIN_DIR = join(HOME, ".claude", "brain");
9
+ const VENV = join(HOME, "projetos", "arsenal", "venv");
10
+ const ARSENAL_DIR = join(HOME, "projetos", "arsenal");
11
+ function step(msg) { console.log(chalk.cyan("→ ") + msg); }
12
+ function ok(msg) { console.log(chalk.green("✓ ") + msg); }
13
+ function warn(msg) { console.log(chalk.yellow("⚠ ") + msg); }
14
+ function run(cmd, opts = {}) {
15
+ return spawnSync(cmd, { shell: true, stdio: "inherit", ...opts });
16
+ }
17
+ // ── 1. claude-brain ──────────────────────────────────────────────────────────
18
+ function setupBrain() {
19
+ step("Clonando claude-brain...");
20
+ if (existsSync(BRAIN_DIR)) {
21
+ run(`git -C ${BRAIN_DIR} pull --recurse-submodules`);
22
+ }
23
+ else {
24
+ run(`git clone --recurse-submodules https://github.com/dbezerra95/claude-brain.git ${BRAIN_DIR}`);
25
+ }
26
+ step("Instalando dependências do arsenal-mcp...");
27
+ const req = join(BRAIN_DIR, "arsenal-mcp", "requirements.txt");
28
+ if (existsSync(req)) {
29
+ run(`pip3 install -r ${req} -q`);
30
+ }
31
+ step("Copiando agentes, commands e memórias...");
32
+ run(`cp ${BRAIN_DIR}/agents/*.md ${CLAUDE_DIR}/agents/ 2>/dev/null || true`);
33
+ run(`cp ${BRAIN_DIR}/agents/*.py ${ARSENAL_DIR}/agents/ 2>/dev/null || true`);
34
+ step("Copiando CLAUDE.md e settings.json...");
35
+ run(`cp ${BRAIN_DIR}/CLAUDE.md ${CLAUDE_DIR}/CLAUDE.md`);
36
+ run(`cp ${BRAIN_DIR}/settings.json ${CLAUDE_DIR}/settings.json`);
37
+ ok("claude-brain sincronizado");
38
+ }
39
+ // ── 2. opencode ──────────────────────────────────────────────────────────────
40
+ function setupOpencode() {
41
+ step("Verificando opencode...");
42
+ const result = spawnSync("which", ["opencode"], { encoding: "utf-8" });
43
+ if (result.status !== 0) {
44
+ step("Instalando opencode...");
45
+ run("npm install -g opencode-ai");
46
+ }
47
+ else {
48
+ ok("opencode já instalado");
49
+ }
50
+ step("Configurando ~/.opencode.json...");
51
+ const config = {
52
+ $schema: "https://opencode.ai/config.json",
53
+ model: "anthropic/claude-sonnet-4-6",
54
+ providers: {
55
+ anthropic: { name: "Anthropic", apiKey: { env: "ANTHROPIC_API_KEY" } },
56
+ openrouter: { name: "OpenRouter", apiKey: { env: "OPENROUTER_API_KEY" } },
57
+ },
58
+ plugin: [
59
+ "@tarquinen/opencode-dcp",
60
+ "oh-my-opencode",
61
+ "opencode-vibeguard",
62
+ "opencode-antigravity-auth",
63
+ "opencode-background-agents",
64
+ "opencode-supermemory",
65
+ ],
66
+ mcp: {
67
+ arsenal: {
68
+ type: "local",
69
+ command: join(HOME, "venv", "bin", "python3"),
70
+ args: [join(BRAIN_DIR, "arsenal-mcp", "server.py")],
71
+ },
72
+ },
73
+ models: {
74
+ "openrouter/nvidia/nemotron-3-super-120b-a12b:free": { name: "Nemotron 120B (Free)", temperature: 0.3 },
75
+ "openrouter/nvidia/nemotron-nano-12b-v2-vl:free": { name: "Nemotron Nano (Free)", temperature: 0.3 },
76
+ "anthropic/claude-haiku-4-5": { name: "Haiku 4.5 (Econômico)", temperature: 0.3 },
77
+ "anthropic/claude-sonnet-4-6": { name: "Sonnet 4.6 (Principal)", temperature: 0.5 },
78
+ },
79
+ };
80
+ writeFileSync(join(HOME, ".opencode.json"), JSON.stringify(config, null, 2));
81
+ step("Instalando plugins do opencode...");
82
+ const plugins = ["@tarquinen/opencode-dcp", "oh-my-opencode", "opencode-vibeguard",
83
+ "opencode-antigravity-auth", "opencode-background-agents", "opencode-supermemory"];
84
+ for (const p of plugins) {
85
+ const r = spawnSync("opencode", ["plugin", p], { stdio: "pipe", cwd: HOME });
86
+ if (r.status === 0)
87
+ ok(`plugin: ${p}`);
88
+ else
89
+ warn(`plugin falhou (pode já estar instalado): ${p}`);
90
+ }
91
+ }
92
+ // ── 3. venv python ───────────────────────────────────────────────────────────
93
+ function setupVenv() {
94
+ step("Verificando venv Python...");
95
+ if (!existsSync(VENV)) {
96
+ mkdirSync(join(HOME, "projetos", "arsenal"), { recursive: true });
97
+ run(`python3 -m venv ${VENV}`);
98
+ }
99
+ run(`${VENV}/bin/pip install mcp openrouter-py 2>/dev/null || ${VENV}/bin/pip install mcp -q`);
100
+ ok("venv ok");
101
+ }
102
+ // ── 4. aliases ───────────────────────────────────────────────────────────────
103
+ function setupAliases() {
104
+ step("Adicionando aliases ao ~/.bashrc...");
105
+ const bashrc = join(HOME, ".bashrc");
106
+ const content = existsSync(bashrc) ? readFileSync(bashrc, "utf-8") : "";
107
+ const aliases = `
108
+ # arsenal-agent — perfis rápidos
109
+ alias aa-p='aa --profile pessoal'
110
+ alias aa-z='aa --profile trabalho'
111
+ `;
112
+ if (!content.includes("arsenal-agent — perfis rápidos")) {
113
+ writeFileSync(bashrc, content + aliases);
114
+ ok("aliases adicionados (reabra o terminal ou rode: source ~/.bashrc)");
115
+ }
116
+ else {
117
+ ok("aliases já existem");
118
+ }
119
+ }
120
+ // ── Entry point ───────────────────────────────────────────────────────────────
121
+ export async function setup() {
122
+ console.log(chalk.bold("\narsenal-cli setup\n"));
123
+ setupVenv();
124
+ setupBrain();
125
+ setupOpencode();
126
+ setupAliases();
127
+ console.log(chalk.bold.green("\n✓ Setup completo!\n"));
128
+ console.log("Próximos passos:");
129
+ console.log(" 1. " + chalk.white("aa profile add pessoal") + chalk.dim(" → autenticar conta pessoal"));
130
+ console.log(" 2. " + chalk.white("aa profile add trabalho") + chalk.dim(" → autenticar conta trabalho"));
131
+ console.log(" 3. " + chalk.white("source ~/.bashrc") + chalk.dim(" → ativar aliases"));
132
+ console.log(" 4. " + chalk.white("aa-p") + chalk.dim(" → abrir Claude\n"));
133
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arsenal-agent",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "Task router: classifies and launches claude or opencode automatically",
5
5
  "license": "MIT",
6
6
  "author": "DBC Tech",
package/src/index.tsx CHANGED
@@ -4,6 +4,7 @@ import { createInterface } from "node:readline"
4
4
  import { Command } from "commander"
5
5
  import chalk from "chalk"
6
6
  import { loadProfile, applyProfile, addProfile, listProfiles, useProfile } from "./profiles.js"
7
+ import { setup } from "./setup.js"
7
8
 
8
9
  const program = new Command()
9
10
 
@@ -16,6 +17,11 @@ program
16
17
  .allowUnknownOption(true)
17
18
  .passThroughOptions(true)
18
19
 
20
+ program
21
+ .command("setup")
22
+ .description("instala e configura tudo: brain, opencode, plugins, MCPs, aliases")
23
+ .action(async () => { await setup(); process.exit(0) })
24
+
19
25
  const profileCmd = program.command("profile").description("gerenciar perfis")
20
26
  profileCmd.command("add [name]").description("adicionar perfil").action(async (name) => { await addProfile(name); process.exit(0) })
21
27
  profileCmd.command("list").alias("ls").description("listar perfis").action(() => { listProfiles(); process.exit(0) })
package/src/setup.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { execSync, spawnSync } from "node:child_process"
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, chmodSync } from "node:fs"
3
+ import { homedir } from "node:os"
4
+ import { join } from "node:path"
5
+ import chalk from "chalk"
6
+
7
+ const HOME = homedir()
8
+ const CLAUDE_DIR = join(HOME, ".claude")
9
+ const BRAIN_DIR = join(HOME, ".claude", "brain")
10
+ const VENV = join(HOME, "projetos", "arsenal", "venv")
11
+ const ARSENAL_DIR = join(HOME, "projetos", "arsenal")
12
+
13
+ function step(msg: string) { console.log(chalk.cyan("→ ") + msg) }
14
+ function ok(msg: string) { console.log(chalk.green("✓ ") + msg) }
15
+ function warn(msg: string) { console.log(chalk.yellow("⚠ ") + msg) }
16
+ function run(cmd: string, opts: object = {}) {
17
+ return spawnSync(cmd, { shell: true, stdio: "inherit", ...opts })
18
+ }
19
+
20
+ // ── 1. claude-brain ──────────────────────────────────────────────────────────
21
+ function setupBrain() {
22
+ step("Clonando claude-brain...")
23
+ if (existsSync(BRAIN_DIR)) {
24
+ run(`git -C ${BRAIN_DIR} pull --recurse-submodules`)
25
+ } else {
26
+ run(`git clone --recurse-submodules https://github.com/dbezerra95/claude-brain.git ${BRAIN_DIR}`)
27
+ }
28
+
29
+ step("Instalando dependências do arsenal-mcp...")
30
+ const req = join(BRAIN_DIR, "arsenal-mcp", "requirements.txt")
31
+ if (existsSync(req)) {
32
+ run(`pip3 install -r ${req} -q`)
33
+ }
34
+
35
+ step("Copiando agentes, commands e memórias...")
36
+ run(`cp ${BRAIN_DIR}/agents/*.md ${CLAUDE_DIR}/agents/ 2>/dev/null || true`)
37
+ run(`cp ${BRAIN_DIR}/agents/*.py ${ARSENAL_DIR}/agents/ 2>/dev/null || true`)
38
+
39
+ step("Copiando CLAUDE.md e settings.json...")
40
+ run(`cp ${BRAIN_DIR}/CLAUDE.md ${CLAUDE_DIR}/CLAUDE.md`)
41
+ run(`cp ${BRAIN_DIR}/settings.json ${CLAUDE_DIR}/settings.json`)
42
+ ok("claude-brain sincronizado")
43
+ }
44
+
45
+ // ── 2. opencode ──────────────────────────────────────────────────────────────
46
+ function setupOpencode() {
47
+ step("Verificando opencode...")
48
+ const result = spawnSync("which", ["opencode"], { encoding: "utf-8" })
49
+ if (result.status !== 0) {
50
+ step("Instalando opencode...")
51
+ run("npm install -g opencode-ai")
52
+ } else {
53
+ ok("opencode já instalado")
54
+ }
55
+
56
+ step("Configurando ~/.opencode.json...")
57
+ const config = {
58
+ $schema: "https://opencode.ai/config.json",
59
+ model: "anthropic/claude-sonnet-4-6",
60
+ providers: {
61
+ anthropic: { name: "Anthropic", apiKey: { env: "ANTHROPIC_API_KEY" } },
62
+ openrouter: { name: "OpenRouter", apiKey: { env: "OPENROUTER_API_KEY" } },
63
+ },
64
+ plugin: [
65
+ "@tarquinen/opencode-dcp",
66
+ "oh-my-opencode",
67
+ "opencode-vibeguard",
68
+ "opencode-antigravity-auth",
69
+ "opencode-background-agents",
70
+ "opencode-supermemory",
71
+ ],
72
+ mcp: {
73
+ arsenal: {
74
+ type: "local",
75
+ command: join(HOME, "venv", "bin", "python3"),
76
+ args: [join(BRAIN_DIR, "arsenal-mcp", "server.py")],
77
+ },
78
+ },
79
+ models: {
80
+ "openrouter/nvidia/nemotron-3-super-120b-a12b:free": { name: "Nemotron 120B (Free)", temperature: 0.3 },
81
+ "openrouter/nvidia/nemotron-nano-12b-v2-vl:free": { name: "Nemotron Nano (Free)", temperature: 0.3 },
82
+ "anthropic/claude-haiku-4-5": { name: "Haiku 4.5 (Econômico)", temperature: 0.3 },
83
+ "anthropic/claude-sonnet-4-6": { name: "Sonnet 4.6 (Principal)", temperature: 0.5 },
84
+ },
85
+ }
86
+ writeFileSync(join(HOME, ".opencode.json"), JSON.stringify(config, null, 2))
87
+
88
+ step("Instalando plugins do opencode...")
89
+ const plugins = ["@tarquinen/opencode-dcp", "oh-my-opencode", "opencode-vibeguard",
90
+ "opencode-antigravity-auth", "opencode-background-agents", "opencode-supermemory"]
91
+ for (const p of plugins) {
92
+ const r = spawnSync("opencode", ["plugin", p], { stdio: "pipe", cwd: HOME })
93
+ if (r.status === 0) ok(`plugin: ${p}`)
94
+ else warn(`plugin falhou (pode já estar instalado): ${p}`)
95
+ }
96
+ }
97
+
98
+ // ── 3. venv python ───────────────────────────────────────────────────────────
99
+ function setupVenv() {
100
+ step("Verificando venv Python...")
101
+ if (!existsSync(VENV)) {
102
+ mkdirSync(join(HOME, "projetos", "arsenal"), { recursive: true })
103
+ run(`python3 -m venv ${VENV}`)
104
+ }
105
+ run(`${VENV}/bin/pip install mcp openrouter-py 2>/dev/null || ${VENV}/bin/pip install mcp -q`)
106
+ ok("venv ok")
107
+ }
108
+
109
+ // ── 4. aliases ───────────────────────────────────────────────────────────────
110
+ function setupAliases() {
111
+ step("Adicionando aliases ao ~/.bashrc...")
112
+ const bashrc = join(HOME, ".bashrc")
113
+ const content = existsSync(bashrc) ? readFileSync(bashrc, "utf-8") : ""
114
+ const aliases = `
115
+ # arsenal-agent — perfis rápidos
116
+ alias aa-p='aa --profile pessoal'
117
+ alias aa-z='aa --profile trabalho'
118
+ `
119
+ if (!content.includes("arsenal-agent — perfis rápidos")) {
120
+ writeFileSync(bashrc, content + aliases)
121
+ ok("aliases adicionados (reabra o terminal ou rode: source ~/.bashrc)")
122
+ } else {
123
+ ok("aliases já existem")
124
+ }
125
+ }
126
+
127
+ // ── Entry point ───────────────────────────────────────────────────────────────
128
+ export async function setup() {
129
+ console.log(chalk.bold("\narsenal-cli setup\n"))
130
+
131
+ setupVenv()
132
+ setupBrain()
133
+ setupOpencode()
134
+ setupAliases()
135
+
136
+ console.log(chalk.bold.green("\n✓ Setup completo!\n"))
137
+ console.log("Próximos passos:")
138
+ console.log(" 1. " + chalk.white("aa profile add pessoal") + chalk.dim(" → autenticar conta pessoal"))
139
+ console.log(" 2. " + chalk.white("aa profile add trabalho") + chalk.dim(" → autenticar conta trabalho"))
140
+ console.log(" 3. " + chalk.white("source ~/.bashrc") + chalk.dim(" → ativar aliases"))
141
+ console.log(" 4. " + chalk.white("aa-p") + chalk.dim(" → abrir Claude\n"))
142
+ }