@cleocode/cleo 2026.3.28 → 2026.3.30
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 +1 -1
- package/dist/cli/index.js +2605 -1903
- package/dist/cli/index.js.map +4 -4
- package/dist/mcp/index.js +4022 -3060
- package/dist/mcp/index.js.map +4 -4
- package/migrations/drizzle-tasks/20260316024050_silly_gamma_corps/migration.sql +1 -0
- package/migrations/drizzle-tasks/20260316024050_silly_gamma_corps/snapshot.json +3737 -0
- package/package.json +8 -4
- package/packages/ct-skills/skills/ct-memory/SKILL.md +84 -0
- package/server.json +1 -1
- package/templates/CLEO-INJECTION.md +11 -0
- package/.claude-plugin/CLAUDE.md +0 -25
- package/.claude-plugin/README.md +0 -315
- package/.claude-plugin/hooks/hooks.json +0 -56
- package/.claude-plugin/hooks/scripts/brain-context.sh +0 -35
- package/.claude-plugin/hooks/scripts/brain-hook.sh +0 -17
- package/.claude-plugin/hooks/scripts/brain-start.sh +0 -6
- package/.claude-plugin/hooks/scripts/brain-worker.cjs +0 -372
- package/.claude-plugin/hooks/scripts/observe.sh +0 -41
- package/.claude-plugin/hooks/scripts/session-start.sh +0 -48
- package/.claude-plugin/hooks/scripts/stop.sh +0 -23
- package/.claude-plugin/plugin.json +0 -24
|
@@ -1,372 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// CLEO Brain Observation Worker
|
|
3
|
-
// HTTP server that receives hook events and stores observations via cleo CLI.
|
|
4
|
-
// Designed as a background daemon — starts fast, handles errors silently.
|
|
5
|
-
|
|
6
|
-
const http = require('node:http');
|
|
7
|
-
const { execFileSync, execFile, fork } = require('node:child_process');
|
|
8
|
-
const fs = require('node:fs');
|
|
9
|
-
const path = require('node:path');
|
|
10
|
-
const os = require('node:os');
|
|
11
|
-
|
|
12
|
-
const PORT = 37778;
|
|
13
|
-
const PID_FILE = path.join(os.homedir(), '.cleo', 'brain-worker.pid');
|
|
14
|
-
const LOG_FILE = path.join(os.homedir(), '.cleo', 'logs', 'brain-worker.log');
|
|
15
|
-
const CLEO_BIN = path.join(os.homedir(), '.cleo', 'bin', 'cleo');
|
|
16
|
-
|
|
17
|
-
// Tools to skip (too noisy or meta)
|
|
18
|
-
const SKIP_TOOLS = new Set([
|
|
19
|
-
'ListMcpResourcesTool', 'SlashCommand', 'Skill', 'TodoWrite',
|
|
20
|
-
'AskUserQuestion', 'TaskList', 'TaskUpdate', 'TaskCreate',
|
|
21
|
-
'TeamCreate', 'SendMessage', 'ToolSearch',
|
|
22
|
-
]);
|
|
23
|
-
const SKIP_PREFIXES = ['mcp__cleo', 'mcp__claude-mem', 'mcp__plugin_claude-mem'];
|
|
24
|
-
|
|
25
|
-
// --- Logging ---
|
|
26
|
-
|
|
27
|
-
function ensureLogDir() {
|
|
28
|
-
const dir = path.dirname(LOG_FILE);
|
|
29
|
-
if (!fs.existsSync(dir)) {
|
|
30
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function log(msg) {
|
|
35
|
-
try {
|
|
36
|
-
ensureLogDir();
|
|
37
|
-
const ts = new Date().toISOString();
|
|
38
|
-
fs.appendFileSync(LOG_FILE, `[${ts}] ${msg}\n`);
|
|
39
|
-
} catch {
|
|
40
|
-
// Silent — never crash
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// --- CLI helpers ---
|
|
45
|
-
|
|
46
|
-
function cleoObserve(text, title) {
|
|
47
|
-
try {
|
|
48
|
-
const args = ['memory', 'observe', text];
|
|
49
|
-
if (title) {
|
|
50
|
-
args.push('--title', title);
|
|
51
|
-
}
|
|
52
|
-
execFileSync(CLEO_BIN, args, {
|
|
53
|
-
timeout: 10000,
|
|
54
|
-
stdio: 'ignore',
|
|
55
|
-
cwd: process.env.CLEO_PROJECT_DIR || process.cwd(),
|
|
56
|
-
});
|
|
57
|
-
return true;
|
|
58
|
-
} catch (err) {
|
|
59
|
-
log(`cleoObserve failed: ${err.message}`);
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// --- Observation summarizer ---
|
|
65
|
-
|
|
66
|
-
function summarizeTool(toolName, toolInput) {
|
|
67
|
-
const inp = toolInput || {};
|
|
68
|
-
switch (toolName) {
|
|
69
|
-
case 'Bash':
|
|
70
|
-
return `Ran: ${String(inp.command || '').slice(0, 120)}`;
|
|
71
|
-
case 'Write':
|
|
72
|
-
return `Wrote: ${inp.file_path || inp.path || 'unknown'}`;
|
|
73
|
-
case 'Edit':
|
|
74
|
-
return `Edited: ${inp.file_path || inp.path || 'unknown'}`;
|
|
75
|
-
case 'Read':
|
|
76
|
-
return `Read: ${inp.file_path || inp.path || 'unknown'}`;
|
|
77
|
-
case 'Glob':
|
|
78
|
-
return `Glob: ${String(inp.pattern || '').slice(0, 80)}`;
|
|
79
|
-
case 'Grep':
|
|
80
|
-
return `Grep: ${String(inp.pattern || '').slice(0, 60)} in ${String(inp.path || '.').slice(0, 60)}`;
|
|
81
|
-
case 'Agent':
|
|
82
|
-
return `Spawned agent: ${String(inp.prompt || inp.description || '').slice(0, 80)}`;
|
|
83
|
-
case 'WebFetch':
|
|
84
|
-
return `Fetched: ${String(inp.url || '').slice(0, 120)}`;
|
|
85
|
-
case 'WebSearch':
|
|
86
|
-
return `Searched: ${String(inp.query || '').slice(0, 80)}`;
|
|
87
|
-
default:
|
|
88
|
-
return `${toolName} called`;
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function shouldSkip(toolName) {
|
|
93
|
-
if (SKIP_TOOLS.has(toolName)) return true;
|
|
94
|
-
for (const prefix of SKIP_PREFIXES) {
|
|
95
|
-
if (toolName.startsWith(prefix)) return true;
|
|
96
|
-
}
|
|
97
|
-
return false;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// --- Event handlers ---
|
|
101
|
-
|
|
102
|
-
function handleObservation(data) {
|
|
103
|
-
const toolName = data.tool_name || 'unknown';
|
|
104
|
-
if (shouldSkip(toolName)) {
|
|
105
|
-
log(`Skipped noisy tool: ${toolName}`);
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
const summary = summarizeTool(toolName, data.tool_input);
|
|
109
|
-
const title = `[hook] ${toolName}`;
|
|
110
|
-
log(`Observing: ${title} — ${summary}`);
|
|
111
|
-
cleoObserve(summary, title);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function handleSummarize(_data) {
|
|
115
|
-
log('Generating session summary...');
|
|
116
|
-
// Best-effort session summary: capture what we can from cleo
|
|
117
|
-
let sessionInfo = 'Claude Code session ended';
|
|
118
|
-
try {
|
|
119
|
-
const raw = execFileSync(CLEO_BIN, ['session', 'status', '--json'], {
|
|
120
|
-
timeout: 10000,
|
|
121
|
-
encoding: 'utf8',
|
|
122
|
-
cwd: process.env.CLEO_PROJECT_DIR || process.cwd(),
|
|
123
|
-
});
|
|
124
|
-
const parsed = JSON.parse(raw);
|
|
125
|
-
const s = parsed?.result?.session || parsed?.session || {};
|
|
126
|
-
if (s.scope || s.currentTask) {
|
|
127
|
-
sessionInfo = `Session ended: ${s.scope || 'unknown'} scope, task: ${s.currentTask || 'none'}`;
|
|
128
|
-
}
|
|
129
|
-
} catch {
|
|
130
|
-
// Use default
|
|
131
|
-
}
|
|
132
|
-
cleoObserve(sessionInfo, '[hook] session-end');
|
|
133
|
-
// Refresh CLAUDE.md so next session gets up-to-date context
|
|
134
|
-
updateClaudeContext();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function updateClaudeContext() {
|
|
138
|
-
// Write recent brain observations to .claude-plugin/CLAUDE.md so Claude Code
|
|
139
|
-
// auto-loads them as context on every session/prompt.
|
|
140
|
-
try {
|
|
141
|
-
const raw = execFileSync(CLEO_BIN, ['memory', 'find', 'session task decision pattern', '--limit', '20', '--json'], {
|
|
142
|
-
timeout: 10000,
|
|
143
|
-
encoding: 'utf8',
|
|
144
|
-
cwd: process.env.CLEO_PROJECT_DIR || process.cwd(),
|
|
145
|
-
});
|
|
146
|
-
const parsed = JSON.parse(raw);
|
|
147
|
-
const hits = parsed?.result?.results || [];
|
|
148
|
-
if (hits.length === 0) return;
|
|
149
|
-
|
|
150
|
-
const lines = ['<cleo-brain-context>', '# CLEO Brain — Recent Memory\n'];
|
|
151
|
-
for (const h of hits) {
|
|
152
|
-
const icon = { observation: 'O', decision: 'D', pattern: 'P', learning: 'L' }[h.type] || 'O';
|
|
153
|
-
const date = (h.date || '').slice(0, 10);
|
|
154
|
-
const title = (h.title || '').slice(0, 90);
|
|
155
|
-
lines.push(`- [${icon}] ${date} ${title}`);
|
|
156
|
-
}
|
|
157
|
-
lines.push('</cleo-brain-context>');
|
|
158
|
-
|
|
159
|
-
// Write to plugin CLAUDE.md — path relative to worker script location
|
|
160
|
-
const pluginRoot = path.resolve(__dirname, '..', '..');
|
|
161
|
-
const claudeMdPath = path.join(pluginRoot, 'CLAUDE.md');
|
|
162
|
-
fs.writeFileSync(claudeMdPath, lines.join('\n') + '\n', 'utf8');
|
|
163
|
-
log(`Updated CLAUDE.md with ${hits.length} brain observations`);
|
|
164
|
-
} catch (err) {
|
|
165
|
-
log(`updateClaudeContext failed: ${err.message}`);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function handleSessionInit(_data) {
|
|
170
|
-
log('Session init — updating brain context in CLAUDE.md...');
|
|
171
|
-
updateClaudeContext();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
// --- HTTP Server ---
|
|
175
|
-
|
|
176
|
-
function createServer() {
|
|
177
|
-
return http.createServer((req, res) => {
|
|
178
|
-
if (req.method === 'POST' && req.url === '/hook') {
|
|
179
|
-
let body = '';
|
|
180
|
-
req.on('data', (chunk) => { body += chunk; });
|
|
181
|
-
req.on('end', () => {
|
|
182
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
183
|
-
res.end('{"ok":true}');
|
|
184
|
-
|
|
185
|
-
// Process async after responding
|
|
186
|
-
try {
|
|
187
|
-
const payload = JSON.parse(body);
|
|
188
|
-
const event = payload.event;
|
|
189
|
-
const data = typeof payload.data === 'string' ? JSON.parse(payload.data) : (payload.data || {});
|
|
190
|
-
|
|
191
|
-
switch (event) {
|
|
192
|
-
case 'observation':
|
|
193
|
-
handleObservation(data);
|
|
194
|
-
break;
|
|
195
|
-
case 'summarize':
|
|
196
|
-
handleSummarize(data);
|
|
197
|
-
break;
|
|
198
|
-
case 'session-init':
|
|
199
|
-
handleSessionInit(data);
|
|
200
|
-
break;
|
|
201
|
-
default:
|
|
202
|
-
log(`Unknown event: ${event}`);
|
|
203
|
-
}
|
|
204
|
-
} catch (err) {
|
|
205
|
-
log(`Error processing hook: ${err.message}`);
|
|
206
|
-
}
|
|
207
|
-
});
|
|
208
|
-
} else if (req.method === 'GET' && req.url === '/health') {
|
|
209
|
-
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
210
|
-
res.end(JSON.stringify({ ok: true, pid: process.pid, uptime: process.uptime() }));
|
|
211
|
-
} else {
|
|
212
|
-
res.writeHead(404);
|
|
213
|
-
res.end('Not found');
|
|
214
|
-
}
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// --- PID management ---
|
|
219
|
-
|
|
220
|
-
function readPid() {
|
|
221
|
-
try {
|
|
222
|
-
const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8').trim(), 10);
|
|
223
|
-
if (isNaN(pid)) return null;
|
|
224
|
-
return pid;
|
|
225
|
-
} catch {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
function isRunning(pid) {
|
|
231
|
-
try {
|
|
232
|
-
process.kill(pid, 0);
|
|
233
|
-
return true;
|
|
234
|
-
} catch {
|
|
235
|
-
return false;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function writePid(pid) {
|
|
240
|
-
const dir = path.dirname(PID_FILE);
|
|
241
|
-
if (!fs.existsSync(dir)) {
|
|
242
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
243
|
-
}
|
|
244
|
-
fs.writeFileSync(PID_FILE, String(pid));
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
function removePid() {
|
|
248
|
-
try {
|
|
249
|
-
fs.unlinkSync(PID_FILE);
|
|
250
|
-
} catch {
|
|
251
|
-
// Ignore
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// --- Commands ---
|
|
256
|
-
|
|
257
|
-
function startCommand() {
|
|
258
|
-
// Check if already running
|
|
259
|
-
const existingPid = readPid();
|
|
260
|
-
if (existingPid && isRunning(existingPid)) {
|
|
261
|
-
console.log(`Brain worker already running (PID ${existingPid})`);
|
|
262
|
-
process.exit(0);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Also check if port is already in use
|
|
266
|
-
const net = require('node:net');
|
|
267
|
-
const probe = net.createServer();
|
|
268
|
-
probe.once('error', (err) => {
|
|
269
|
-
if (err.code === 'EADDRINUSE') {
|
|
270
|
-
console.log(`Port ${PORT} already in use — worker likely running`);
|
|
271
|
-
process.exit(0);
|
|
272
|
-
}
|
|
273
|
-
});
|
|
274
|
-
probe.once('listening', () => {
|
|
275
|
-
probe.close(() => {
|
|
276
|
-
// Port free — daemonize
|
|
277
|
-
daemonize();
|
|
278
|
-
});
|
|
279
|
-
});
|
|
280
|
-
probe.listen(PORT, '127.0.0.1');
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function daemonize() {
|
|
284
|
-
// Fork a detached child that runs the server
|
|
285
|
-
const child = fork(__filename, ['--serve'], {
|
|
286
|
-
detached: true,
|
|
287
|
-
stdio: 'ignore',
|
|
288
|
-
env: { ...process.env, CLEO_PROJECT_DIR: process.cwd() },
|
|
289
|
-
});
|
|
290
|
-
child.unref();
|
|
291
|
-
writePid(child.pid);
|
|
292
|
-
console.log(`Brain worker started (PID ${child.pid}, port ${PORT})`);
|
|
293
|
-
process.exit(0);
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
function serveCommand() {
|
|
297
|
-
// This runs in the daemonized child
|
|
298
|
-
writePid(process.pid);
|
|
299
|
-
log(`Brain worker starting on port ${PORT} (PID ${process.pid})`);
|
|
300
|
-
|
|
301
|
-
const server = createServer();
|
|
302
|
-
server.listen(PORT, '127.0.0.1', () => {
|
|
303
|
-
log(`Brain worker listening on 127.0.0.1:${PORT}`);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
server.on('error', (err) => {
|
|
307
|
-
log(`Server error: ${err.message}`);
|
|
308
|
-
removePid();
|
|
309
|
-
process.exit(1);
|
|
310
|
-
});
|
|
311
|
-
|
|
312
|
-
// Graceful shutdown
|
|
313
|
-
const shutdown = () => {
|
|
314
|
-
log('Brain worker shutting down...');
|
|
315
|
-
server.close();
|
|
316
|
-
removePid();
|
|
317
|
-
process.exit(0);
|
|
318
|
-
};
|
|
319
|
-
process.on('SIGTERM', shutdown);
|
|
320
|
-
process.on('SIGINT', shutdown);
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function stopCommand() {
|
|
324
|
-
const pid = readPid();
|
|
325
|
-
if (!pid) {
|
|
326
|
-
console.log('Brain worker not running (no PID file)');
|
|
327
|
-
process.exit(0);
|
|
328
|
-
}
|
|
329
|
-
if (!isRunning(pid)) {
|
|
330
|
-
console.log(`Brain worker not running (stale PID ${pid})`);
|
|
331
|
-
removePid();
|
|
332
|
-
process.exit(0);
|
|
333
|
-
}
|
|
334
|
-
try {
|
|
335
|
-
process.kill(pid, 'SIGTERM');
|
|
336
|
-
console.log(`Brain worker stopped (PID ${pid})`);
|
|
337
|
-
} catch (err) {
|
|
338
|
-
console.log(`Failed to stop brain worker: ${err.message}`);
|
|
339
|
-
}
|
|
340
|
-
removePid();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
function statusCommand() {
|
|
344
|
-
const pid = readPid();
|
|
345
|
-
if (pid && isRunning(pid)) {
|
|
346
|
-
console.log(`Brain worker running (PID ${pid}, port ${PORT})`);
|
|
347
|
-
} else {
|
|
348
|
-
console.log('Brain worker not running');
|
|
349
|
-
if (pid) removePid();
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// --- Main ---
|
|
354
|
-
|
|
355
|
-
const command = process.argv[2];
|
|
356
|
-
switch (command) {
|
|
357
|
-
case 'start':
|
|
358
|
-
startCommand();
|
|
359
|
-
break;
|
|
360
|
-
case '--serve':
|
|
361
|
-
serveCommand();
|
|
362
|
-
break;
|
|
363
|
-
case 'stop':
|
|
364
|
-
stopCommand();
|
|
365
|
-
break;
|
|
366
|
-
case 'status':
|
|
367
|
-
statusCommand();
|
|
368
|
-
break;
|
|
369
|
-
default:
|
|
370
|
-
console.log('Usage: brain-worker.js <start|stop|status>');
|
|
371
|
-
process.exit(1);
|
|
372
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# PostToolUse: capture tool observations into CLEO brain.db
|
|
3
|
-
# Best-effort — always exits 0 so it never blocks Claude Code
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
CLEO_BIN="${HOME}/.cleo/bin/cleo"
|
|
7
|
-
[ ! -x "$CLEO_BIN" ] && exit 0
|
|
8
|
-
[ ! -d ".cleo" ] && exit 0
|
|
9
|
-
|
|
10
|
-
# Read stdin (tool use JSON from Claude Code)
|
|
11
|
-
INPUT=$(cat) || exit 0
|
|
12
|
-
TOOL=$(echo "$INPUT" | python3 -c "import json,sys; d=json.load(sys.stdin); print(d.get('tool_name','unknown'))" 2>/dev/null || echo "unknown")
|
|
13
|
-
|
|
14
|
-
# Only observe meaningful tools (skip trivial ones)
|
|
15
|
-
case "$TOOL" in
|
|
16
|
-
Read|Write|Edit|Bash|Glob|Grep|Agent) ;;
|
|
17
|
-
*) exit 0 ;;
|
|
18
|
-
esac
|
|
19
|
-
|
|
20
|
-
# Extract a short summary
|
|
21
|
-
SUMMARY=$(echo "$INPUT" | python3 -c "
|
|
22
|
-
import json, sys
|
|
23
|
-
d = json.load(sys.stdin)
|
|
24
|
-
tool = d.get('tool_name', 'unknown')
|
|
25
|
-
inp = d.get('tool_input', {})
|
|
26
|
-
if tool == 'Bash':
|
|
27
|
-
print(f'Bash: {str(inp.get(\"command\",\"\"))[:80]}')
|
|
28
|
-
elif tool in ('Read','Write','Edit'):
|
|
29
|
-
print(f'{tool}: {inp.get(\"file_path\",inp.get(\"path\",\"\"))[:80]}')
|
|
30
|
-
elif tool == 'Grep':
|
|
31
|
-
print(f'Grep: {inp.get(\"pattern\",\"\")[:40]} in {inp.get(\"path\",\".\")[:40]}')
|
|
32
|
-
elif tool == 'Glob':
|
|
33
|
-
print(f'Glob: {inp.get(\"pattern\",\"\")[:60]}')
|
|
34
|
-
elif tool == 'Agent':
|
|
35
|
-
print(f'Agent: {str(inp.get(\"prompt\",\"\"))[:80]}')
|
|
36
|
-
else:
|
|
37
|
-
print(f'{tool} called')
|
|
38
|
-
" 2>/dev/null || echo "$TOOL called")
|
|
39
|
-
|
|
40
|
-
"$CLEO_BIN" memory observe "$SUMMARY" --title "[hook] $TOOL" >/dev/null 2>&1 || true
|
|
41
|
-
exit 0
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# SessionStart hook: Auto-bind CLEO session to Claude Code terminal
|
|
3
|
-
# Part of CLEO universal subagent architecture (v0.70.0+)
|
|
4
|
-
|
|
5
|
-
set -euo pipefail
|
|
6
|
-
|
|
7
|
-
# Find cleo installation
|
|
8
|
-
CLEO_DIR="${HOME}/.cleo"
|
|
9
|
-
CLEO_BIN="${CLEO_DIR}/cleo"
|
|
10
|
-
|
|
11
|
-
# Only proceed if cleo is installed
|
|
12
|
-
if [[ ! -x "$CLEO_BIN" ]]; then
|
|
13
|
-
exit 0
|
|
14
|
-
fi
|
|
15
|
-
|
|
16
|
-
# Check if .cleo project directory exists
|
|
17
|
-
if [[ ! -d ".cleo" ]]; then
|
|
18
|
-
exit 0
|
|
19
|
-
fi
|
|
20
|
-
|
|
21
|
-
# Check if there's a current session
|
|
22
|
-
CURRENT_SESSION_FILE=".cleo/.current-session"
|
|
23
|
-
if [[ ! -f "$CURRENT_SESSION_FILE" ]]; then
|
|
24
|
-
exit 0
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
# Read current session ID
|
|
28
|
-
SESSION_ID=$(cat "$CURRENT_SESSION_FILE" 2>/dev/null || echo "")
|
|
29
|
-
if [[ -z "$SESSION_ID" ]]; then
|
|
30
|
-
exit 0
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
# Verify session exists and is active
|
|
34
|
-
SESSION_STATUS=$("$CLEO_BIN" session status --session "$SESSION_ID" --format json 2>/dev/null || echo "{}")
|
|
35
|
-
IS_ACTIVE=$(echo "$SESSION_STATUS" | jq -r '.session.status // "unknown"' 2>/dev/null || echo "unknown")
|
|
36
|
-
|
|
37
|
-
if [[ "$IS_ACTIVE" == "active" ]]; then
|
|
38
|
-
# Export session to environment for all subsequent commands
|
|
39
|
-
export CLEO_SESSION="$SESSION_ID"
|
|
40
|
-
|
|
41
|
-
# Write to a file that can be sourced by the shell
|
|
42
|
-
echo "export CLEO_SESSION=\"$SESSION_ID\"" > ".cleo/.session-env"
|
|
43
|
-
|
|
44
|
-
# Optional: Display session info (visible in terminal)
|
|
45
|
-
echo "✓ CLEO session bound: $SESSION_ID" >&2
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
|
-
exit 0
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# Stop: save session summary to CLEO brain
|
|
3
|
-
# Best-effort — always exits 0 so it never blocks Claude Code
|
|
4
|
-
set -euo pipefail
|
|
5
|
-
|
|
6
|
-
CLEO_BIN="${HOME}/.cleo/bin/cleo"
|
|
7
|
-
[ ! -x "$CLEO_BIN" ] && exit 0
|
|
8
|
-
[ ! -d ".cleo" ] && exit 0
|
|
9
|
-
|
|
10
|
-
# Get current session info if available
|
|
11
|
-
SESSION_INFO=$("$CLEO_BIN" session status --json 2>/dev/null | python3 -c "
|
|
12
|
-
import json, sys
|
|
13
|
-
d = json.load(sys.stdin)
|
|
14
|
-
r = d.get('result', {})
|
|
15
|
-
s = r.get('session', {})
|
|
16
|
-
if s:
|
|
17
|
-
print(f'Session ended: {s.get(\"scope\",\"unknown\")} scope, task: {s.get(\"currentTask\",\"none\")}')
|
|
18
|
-
else:
|
|
19
|
-
print('Session ended')
|
|
20
|
-
" 2>/dev/null || echo "Claude Code session ended")
|
|
21
|
-
|
|
22
|
-
"$CLEO_BIN" memory observe "$SESSION_INFO" --title "[hook] session-end" >/dev/null 2>&1 || true
|
|
23
|
-
exit 0
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "cleo",
|
|
3
|
-
"version": "0.70.1",
|
|
4
|
-
"description": "CLEO task management system with custom agents for LLM-driven development workflows",
|
|
5
|
-
"author": {
|
|
6
|
-
"name": "CLEO",
|
|
7
|
-
"url": "https://github.com/kryptobaseddev/cleo"
|
|
8
|
-
},
|
|
9
|
-
"capabilities": {
|
|
10
|
-
"task_management": true,
|
|
11
|
-
"multi_session": true,
|
|
12
|
-
"orchestration": true
|
|
13
|
-
},
|
|
14
|
-
"hooks": {
|
|
15
|
-
"enabled": true,
|
|
16
|
-
"directory": "hooks",
|
|
17
|
-
"manifest": "hooks/hooks.json"
|
|
18
|
-
},
|
|
19
|
-
"metadata": {
|
|
20
|
-
"schema_version": "1.0.0",
|
|
21
|
-
"min_claude_version": "1.0.0",
|
|
22
|
-
"license": "MIT"
|
|
23
|
-
}
|
|
24
|
-
}
|