@gabrihhh/jarvis 2.3.0 → 2.5.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/.claude/skills/create-memory/SKILL.md +15 -0
- package/.claude/skills/update-memory/SKILL.md +19 -0
- package/bin/jarvis.js +49 -1
- package/package.json +1 -1
- package/src/display.js +1 -23
- package/src/index.js +3 -9
- package/src/reader.js +34 -14
- package/src/statusline.js +6 -5
|
@@ -9,6 +9,21 @@ Você vai analisar o repositório atual de forma exaustiva e indexar seu entendi
|
|
|
9
9
|
|
|
10
10
|
**Antes de iniciar:** leia `.claude/skills/MEMORY_ARCHITECTURE.md` — ele define o schema do grafo, as regras de indexação e os critérios de qualidade que você deve seguir durante toda a execução desta skill.
|
|
11
11
|
|
|
12
|
+
## PRÉ-REQUISITO — Verificar ambiente
|
|
13
|
+
|
|
14
|
+
Antes de qualquer passo, chame a MCP tool:
|
|
15
|
+
```
|
|
16
|
+
list-projects()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Se a tool retornar erro de conexão, tool não encontrada ou MCP indisponível:
|
|
20
|
+
**"O MCP server `jarvis-memory` não está acessível. Execute `/setup-memory` primeiro e reinicie o Claude Code antes de continuar."**
|
|
21
|
+
Interrompa aqui — não prossiga.
|
|
22
|
+
|
|
23
|
+
Se a tool responder (mesmo que com "Nenhum projeto indexado"), o ambiente está pronto. Continue.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
12
27
|
## REGRA GLOBAL
|
|
13
28
|
|
|
14
29
|
- Se travar em qualquer ponto — dúvida técnica, conceitual, arquivo ilegível, ambiguidade de qualquer natureza — **pare e pergunte ao usuário** antes de continuar
|
|
@@ -9,6 +9,25 @@ Você vai identificar o que mudou no repositório desde a última indexação e
|
|
|
9
9
|
|
|
10
10
|
**Antes de iniciar:** leia `.claude/skills/MEMORY_ARCHITECTURE.md` — ele define o schema, as regras de indexação e os critérios de qualidade que guiam esta skill.
|
|
11
11
|
|
|
12
|
+
## PRÉ-REQUISITO — Verificar ambiente
|
|
13
|
+
|
|
14
|
+
Antes de qualquer passo, chame a MCP tool:
|
|
15
|
+
```
|
|
16
|
+
list-projects()
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Se a tool retornar erro de conexão, tool não encontrada ou MCP indisponível:
|
|
20
|
+
**"O MCP server `jarvis-memory` não está acessível. Execute `/setup-memory` primeiro e reinicie o Claude Code antes de continuar."**
|
|
21
|
+
Interrompa aqui — não prossiga.
|
|
22
|
+
|
|
23
|
+
Se a tool retornar "Nenhum projeto indexado ainda":
|
|
24
|
+
**"Nenhum projeto indexado encontrado. Execute `/create-memory` primeiro para criar o índice inicial."**
|
|
25
|
+
Interrompa aqui — não prossiga.
|
|
26
|
+
|
|
27
|
+
Se a tool listar projetos, o ambiente está pronto. Continue.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
12
31
|
## REGRA GLOBAL
|
|
13
32
|
|
|
14
33
|
- Se travar em qualquer ponto — dúvida técnica, ambiguidade, mudança que não entende — **pare e pergunte ao usuário**
|
package/bin/jarvis.js
CHANGED
|
@@ -4,11 +4,36 @@ import { renderLine } from '../src/statusline.js';
|
|
|
4
4
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync } from 'fs';
|
|
5
5
|
import { join, dirname } from 'path';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
|
-
import { exec } from 'child_process';
|
|
7
|
+
import { exec, execSync } from 'child_process';
|
|
8
8
|
import { fileURLToPath } from 'url';
|
|
9
9
|
|
|
10
10
|
const MEMORY_CONFIG_PATH = join(homedir(), '.claude-memory.json');
|
|
11
11
|
|
|
12
|
+
function checkSetupDone() {
|
|
13
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
14
|
+
if (!existsSync(settingsPath)) return false;
|
|
15
|
+
try {
|
|
16
|
+
const s = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
17
|
+
return !!(s?.statusLine);
|
|
18
|
+
} catch { return false; }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function checkMcpRegistered() {
|
|
22
|
+
const settingsPath = join(homedir(), '.claude', 'settings.json');
|
|
23
|
+
if (!existsSync(settingsPath)) return false;
|
|
24
|
+
try {
|
|
25
|
+
const s = JSON.parse(readFileSync(settingsPath, 'utf-8'));
|
|
26
|
+
return !!(s?.mcpServers?.['jarvis-memory']);
|
|
27
|
+
} catch { return false; }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function checkNeo4jRunning() {
|
|
31
|
+
try {
|
|
32
|
+
const out = execSync('docker ps --filter name=claude-memory --filter status=running --format "{{.Names}}"', { stdio: 'pipe' }).toString().trim();
|
|
33
|
+
return out.includes('claude-memory');
|
|
34
|
+
} catch { return false; }
|
|
35
|
+
}
|
|
36
|
+
|
|
12
37
|
function loadMemoryConfig() {
|
|
13
38
|
try { return JSON.parse(readFileSync(MEMORY_CONFIG_PATH, 'utf-8')); }
|
|
14
39
|
catch { return { neo4j: { uri: 'bolt://localhost:7687', user: 'neo4j', password: 'claudememory' } }; }
|
|
@@ -65,6 +90,11 @@ if (args.includes('--help') || args.includes('-h')) {
|
|
|
65
90
|
}
|
|
66
91
|
|
|
67
92
|
if (args.includes('--graph')) {
|
|
93
|
+
if (!checkNeo4jRunning()) {
|
|
94
|
+
console.error('\n ✗ Neo4j não está rodando.');
|
|
95
|
+
console.error(' Execute /setup-memory dentro do Claude Code para iniciar o container.\n');
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
68
98
|
const url = 'http://localhost:7474';
|
|
69
99
|
const opener =
|
|
70
100
|
process.platform === 'darwin' ? 'open' :
|
|
@@ -124,6 +154,24 @@ if (args.includes('--graph')) {
|
|
|
124
154
|
process.exit(0);
|
|
125
155
|
}
|
|
126
156
|
|
|
157
|
+
if (mode !== 'off') {
|
|
158
|
+
if (!checkSetupDone()) {
|
|
159
|
+
console.error('\n ✗ jarvis --setup não foi executado.');
|
|
160
|
+
console.error(' Execute primeiro: jarvis --setup\n');
|
|
161
|
+
process.exit(1);
|
|
162
|
+
}
|
|
163
|
+
if (!checkMcpRegistered()) {
|
|
164
|
+
console.error('\n ✗ MCP server jarvis-memory não está registrado.');
|
|
165
|
+
console.error(' Execute /setup-memory dentro do Claude Code e reinicie antes de ativar o trigger.\n');
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
if (!checkNeo4jRunning()) {
|
|
169
|
+
console.error('\n ✗ Neo4j não está rodando.');
|
|
170
|
+
console.error(' Execute /setup-memory dentro do Claude Code para iniciar o container.\n');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
127
175
|
const cfg = loadMemoryConfig();
|
|
128
176
|
cfg.trigger = mode;
|
|
129
177
|
saveMemoryConfig(cfg);
|
package/package.json
CHANGED
package/src/display.js
CHANGED
|
@@ -60,29 +60,13 @@ function tokenRow(label, stats, maxTokens) {
|
|
|
60
60
|
return row(text);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
// ─── Context window row ──────────────────────────────────────
|
|
64
|
-
function contextRow(session) {
|
|
65
|
-
if (!session) {
|
|
66
|
-
return row(` ${chalk.hex(DIM)('No active session detected')}`);
|
|
67
|
-
}
|
|
68
|
-
const { contextUsed, contextWindow, percent, model, turns } = session;
|
|
69
|
-
const b = bar(percent, 24);
|
|
70
|
-
const pct = chalk.bold.hex(WHITE)(`${percent}%`);
|
|
71
|
-
const info = chalk.hex(WHITE)(`${formatTokens(contextUsed)} / ${formatTokens(contextWindow)}`);
|
|
72
|
-
const meta = chalk.hex(DIM)(`${turns} turn${turns !== 1 ? 's' : ''} · model: ${model.replace('claude-', '')}`);
|
|
73
|
-
return [
|
|
74
|
-
row(` ${b} ${pct} ${info}`),
|
|
75
|
-
row(` ${meta}`),
|
|
76
|
-
].join('\n');
|
|
77
|
-
}
|
|
78
|
-
|
|
79
63
|
// ─── Section header ──────────────────────────────────────────
|
|
80
64
|
function sectionHeader(icon, label) {
|
|
81
65
|
return row(` ${chalk.hex(WHITE)(icon)} ${chalk.bold.hex(WHITE)(label)}`);
|
|
82
66
|
}
|
|
83
67
|
|
|
84
68
|
// ─── Full render ─────────────────────────────────────────────
|
|
85
|
-
export function render(stats
|
|
69
|
+
export function render(stats) {
|
|
86
70
|
const { monthly, weekly, daily } = stats;
|
|
87
71
|
const maxTokens = monthly.total || 1;
|
|
88
72
|
|
|
@@ -108,12 +92,6 @@ export function render(stats, session) {
|
|
|
108
92
|
row(''),
|
|
109
93
|
DIV,
|
|
110
94
|
row(''),
|
|
111
|
-
sectionHeader('⬡', 'Context Window (current session)'),
|
|
112
|
-
row(''),
|
|
113
|
-
contextRow(session),
|
|
114
|
-
row(''),
|
|
115
|
-
DIV,
|
|
116
|
-
row(''),
|
|
117
95
|
breakdown(monthly),
|
|
118
96
|
row(''),
|
|
119
97
|
BOT,
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { readAllUsage
|
|
2
|
-
import { aggregateStats
|
|
1
|
+
import { readAllUsage } from './reader.js';
|
|
2
|
+
import { aggregateStats } from './calculator.js';
|
|
3
3
|
import { render, renderError, renderLoading } from './display.js';
|
|
4
4
|
|
|
5
5
|
export async function run() {
|
|
@@ -15,12 +15,6 @@ export async function run() {
|
|
|
15
15
|
|
|
16
16
|
const stats = aggregateStats(allEntries);
|
|
17
17
|
|
|
18
|
-
// current session
|
|
19
|
-
const sessionMeta = getCurrentSessionFile();
|
|
20
|
-
const sessionId = sessionMeta?.sessionId;
|
|
21
|
-
const sessionEntries = sessionId ? readCurrentSessionUsage(sessionId) : [];
|
|
22
|
-
const session = aggregateSession(sessionEntries);
|
|
23
|
-
|
|
24
18
|
process.stdout.write('\r' + ' '.repeat(40) + '\r');
|
|
25
|
-
console.log('\n' + render(stats
|
|
19
|
+
console.log('\n' + render(stats) + '\n');
|
|
26
20
|
}
|
package/src/reader.js
CHANGED
|
@@ -3,6 +3,34 @@ import { join } from 'path';
|
|
|
3
3
|
import { homedir } from 'os';
|
|
4
4
|
import { execSync } from 'child_process';
|
|
5
5
|
|
|
6
|
+
function getParentPid(pid) {
|
|
7
|
+
// Linux — leitura direta do /proc, sem subprocess
|
|
8
|
+
try {
|
|
9
|
+
const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
|
|
10
|
+
const match = stat.match(/^\d+ \(.*?\) \S+ (\d+)/);
|
|
11
|
+
if (match) return parseInt(match[1]);
|
|
12
|
+
} catch { /* não é Linux ou /proc indisponível */ }
|
|
13
|
+
|
|
14
|
+
// macOS / Linux fallback
|
|
15
|
+
if (process.platform !== 'win32') {
|
|
16
|
+
try {
|
|
17
|
+
const out = execSync(`ps -o ppid= -p ${pid}`, { stdio: 'pipe' }).toString().trim();
|
|
18
|
+
const n = parseInt(out);
|
|
19
|
+
return isNaN(n) ? null : n;
|
|
20
|
+
} catch { return null; }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Windows — PowerShell com Get-CimInstance (não deprecated, Windows 7+)
|
|
24
|
+
try {
|
|
25
|
+
const out = execSync(
|
|
26
|
+
`powershell -NoProfile -Command "(Get-CimInstance Win32_Process -Filter 'ProcessId=${pid}').ParentProcessId"`,
|
|
27
|
+
{ stdio: 'pipe' }
|
|
28
|
+
).toString().trim();
|
|
29
|
+
const n = parseInt(out);
|
|
30
|
+
return isNaN(n) ? null : n;
|
|
31
|
+
} catch { return null; }
|
|
32
|
+
}
|
|
33
|
+
|
|
6
34
|
const CLAUDE_DIR = join(homedir(), '.claude');
|
|
7
35
|
const PROJECTS_DIR = join(CLAUDE_DIR, 'projects');
|
|
8
36
|
|
|
@@ -95,24 +123,16 @@ export function getCurrentSessionFile() {
|
|
|
95
123
|
if (!existsSync(sessionsDir)) return null;
|
|
96
124
|
|
|
97
125
|
try {
|
|
98
|
-
//
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
// also check grandparent in case the command runs through a shell
|
|
103
|
-
try {
|
|
104
|
-
const grandparent = parseInt(
|
|
105
|
-
execSync(`ps -o ppid= -p ${process.ppid}`, { stdio: ['pipe','pipe','pipe'] }).toString().trim()
|
|
106
|
-
);
|
|
107
|
-
if (grandparent) pidsToCheck.add(grandparent);
|
|
108
|
-
} catch { /* skip */ }
|
|
109
|
-
|
|
110
|
-
for (const pid of pidsToCheck) {
|
|
126
|
+
// Sobe a árvore de processos até 5 níveis para encontrar o .json da sessão Claude
|
|
127
|
+
let pid = process.ppid;
|
|
128
|
+
for (let i = 0; i < 5; i++) {
|
|
129
|
+
if (!pid || pid <= 1) break;
|
|
111
130
|
const candidate = join(sessionsDir, `${pid}.json`);
|
|
112
131
|
if (existsSync(candidate)) {
|
|
113
132
|
try { return JSON.parse(readFileSync(candidate, 'utf-8')); }
|
|
114
|
-
catch {
|
|
133
|
+
catch { break; }
|
|
115
134
|
}
|
|
135
|
+
pid = getParentPid(pid);
|
|
116
136
|
}
|
|
117
137
|
|
|
118
138
|
// Fallback: find a session whose sessionId appears in JSONL files
|
package/src/statusline.js
CHANGED
|
@@ -57,8 +57,6 @@ function isMemoryLoaded(sessionId) {
|
|
|
57
57
|
|
|
58
58
|
export function renderLine() {
|
|
59
59
|
const mode = readTriggerMode();
|
|
60
|
-
const triggerInner = ` TRIGGER ${mode.toUpperCase()} `;
|
|
61
|
-
const triggerBox = buildBox(triggerInner, PINK);
|
|
62
60
|
|
|
63
61
|
const sessionMeta = getCurrentSessionFile();
|
|
64
62
|
const sessionId = sessionMeta?.sessionId;
|
|
@@ -67,9 +65,12 @@ export function renderLine() {
|
|
|
67
65
|
|
|
68
66
|
const allEntries = readAllUsage();
|
|
69
67
|
|
|
70
|
-
const boxes = (contextBox) =>
|
|
71
|
-
|
|
72
|
-
|
|
68
|
+
const boxes = (contextBox) => {
|
|
69
|
+
const toJoin = [contextBox];
|
|
70
|
+
if (mode !== 'off') toJoin.push(buildBox(` TRIGGER ${mode.toUpperCase()} `, PINK));
|
|
71
|
+
if (loadedBox) toJoin.push(loadedBox);
|
|
72
|
+
return joinBoxes(...toJoin);
|
|
73
|
+
};
|
|
73
74
|
|
|
74
75
|
if (!allEntries.length) {
|
|
75
76
|
const contextBox = buildBox(` CONTEXT ${'░'.repeat(8)} 0% `, CYAN);
|