declare-cc 0.4.8 → 0.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/bin/install.js +11 -6
- package/hooks/declare-activity.js +94 -0
- package/hooks/declare-check-update.js +62 -0
- package/hooks/declare-statusline.js +91 -0
- package/package.json +2 -1
package/bin/install.js
CHANGED
|
@@ -1636,11 +1636,13 @@ function install(isGlobal, runtime = 'claude') {
|
|
|
1636
1636
|
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline, runtime = 'claude', isGlobal = true) {
|
|
1637
1637
|
const isOpencode = runtime === 'opencode';
|
|
1638
1638
|
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1639
|
+
// Statusline is a global UI element — only configure it for global installs.
|
|
1640
|
+
// Local installs must not write statusLine: it would point to a project-specific
|
|
1641
|
+
// path that breaks when the project moves or is deleted.
|
|
1642
|
+
// Users who only do local installs should run `npx declare-cc --claude --global`
|
|
1643
|
+
// once to get the statusline, or configure it manually.
|
|
1644
|
+
if (shouldInstallStatusline && !isOpencode && isGlobal) {
|
|
1645
|
+
settings.statusLine = { type: 'command', command: statuslineCommand };
|
|
1644
1646
|
console.log(` ${green}✓${reset} Configured statusline`);
|
|
1645
1647
|
}
|
|
1646
1648
|
|
|
@@ -1657,9 +1659,12 @@ function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallS
|
|
|
1657
1659
|
if (runtime === 'gemini') program = 'Gemini';
|
|
1658
1660
|
|
|
1659
1661
|
const command = isOpencode ? '/declare-help' : '/declare:help';
|
|
1662
|
+
const statuslineNote = (!isGlobal && !isOpencode)
|
|
1663
|
+
? `\n ${yellow}Tip:${reset} For the context-window statusline, run once globally:\n ${dim}npx declare-cc --claude --global${reset}\n`
|
|
1664
|
+
: '';
|
|
1660
1665
|
console.log(`
|
|
1661
1666
|
${green}Done!${reset} Launch ${program} and run ${cyan}${command}${reset}.
|
|
1662
|
-
|
|
1667
|
+
${statuslineNote}
|
|
1663
1668
|
${cyan}Docs & source:${reset} https://github.com/decocms/declare-cc
|
|
1664
1669
|
`);
|
|
1665
1670
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Declare activity hook — PreToolUse + PostToolUse
|
|
3
|
+
// Writes interesting tool events to .planning/activity.jsonl
|
|
4
|
+
// Server watches .planning/ via fs.watch and pushes SSE events to dashboard.
|
|
5
|
+
//
|
|
6
|
+
// Installed for PreToolUse and PostToolUse hook events.
|
|
7
|
+
// Runs fast: read stdin → decide → append one line → exit.
|
|
8
|
+
|
|
9
|
+
'use strict';
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
const path = require('path');
|
|
13
|
+
const os = require('os');
|
|
14
|
+
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
const planningDir = path.join(cwd, '.planning');
|
|
17
|
+
const activityFile = path.join(planningDir, 'activity.jsonl');
|
|
18
|
+
|
|
19
|
+
// Only write if .planning/ exists (i.e. this is a Declare project)
|
|
20
|
+
if (!fs.existsSync(planningDir)) process.exit(0);
|
|
21
|
+
|
|
22
|
+
let raw = '';
|
|
23
|
+
process.stdin.setEncoding('utf8');
|
|
24
|
+
process.stdin.on('data', c => raw += c);
|
|
25
|
+
process.stdin.on('end', () => {
|
|
26
|
+
try {
|
|
27
|
+
const data = JSON.parse(raw);
|
|
28
|
+
const event = buildEvent(data);
|
|
29
|
+
if (!event) process.exit(0);
|
|
30
|
+
|
|
31
|
+
// Ensure file exists
|
|
32
|
+
if (!fs.existsSync(activityFile)) fs.writeFileSync(activityFile, '');
|
|
33
|
+
|
|
34
|
+
// Append event + trim to last 200 lines
|
|
35
|
+
const line = JSON.stringify(event) + '\n';
|
|
36
|
+
fs.appendFileSync(activityFile, line);
|
|
37
|
+
|
|
38
|
+
// Trim to last 200 lines to avoid unbounded growth
|
|
39
|
+
const content = fs.readFileSync(activityFile, 'utf8');
|
|
40
|
+
const lines = content.split('\n').filter(Boolean);
|
|
41
|
+
if (lines.length > 200) {
|
|
42
|
+
fs.writeFileSync(activityFile, lines.slice(-200).join('\n') + '\n');
|
|
43
|
+
}
|
|
44
|
+
} catch (_) {
|
|
45
|
+
// Silent fail — never block Claude
|
|
46
|
+
}
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Build an activity event from a hook payload, or return null to skip.
|
|
52
|
+
* @param {any} data
|
|
53
|
+
* @returns {object|null}
|
|
54
|
+
*/
|
|
55
|
+
function buildEvent(data) {
|
|
56
|
+
const tool = data.tool_name || '';
|
|
57
|
+
const input = data.tool_input || {};
|
|
58
|
+
const response = data.tool_response;
|
|
59
|
+
const hookEvent = data.hook_event_name || ''; // PreToolUse or PostToolUse
|
|
60
|
+
const ts = Date.now();
|
|
61
|
+
const phase = hookEvent === 'PostToolUse' ? 'done' : 'start';
|
|
62
|
+
|
|
63
|
+
// Task spawns — most important for agent visibility
|
|
64
|
+
if (tool === 'Task') {
|
|
65
|
+
return {
|
|
66
|
+
ts, phase, tool: 'Task',
|
|
67
|
+
desc: input.description || '',
|
|
68
|
+
agent: input.subagent_type || '',
|
|
69
|
+
// truncate prompt to avoid massive payloads
|
|
70
|
+
prompt: (input.prompt || '').slice(0, 200),
|
|
71
|
+
bg: hookEvent === 'PostToolUse',
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Bash commands that involve declare-tools (execution steps)
|
|
76
|
+
if (tool === 'Bash') {
|
|
77
|
+
const cmd = input.command || '';
|
|
78
|
+
if (cmd.includes('declare-tools') || cmd.includes('/declare:')) {
|
|
79
|
+
return { ts, phase, tool: 'Bash', cmd: cmd.slice(0, 200) };
|
|
80
|
+
}
|
|
81
|
+
return null; // skip noisy general bash
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Write tool — track planning file changes
|
|
85
|
+
if (tool === 'Write' && hookEvent === 'PostToolUse') {
|
|
86
|
+
const fp = input.file_path || '';
|
|
87
|
+
if (fp.includes('.planning/')) {
|
|
88
|
+
return { ts, phase: 'done', tool: 'Write', file: fp.replace(cwd, '.') };
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Check for Declare updates in background, write result to cache
|
|
3
|
+
// Called by SessionStart hook - runs once per session
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
const { spawn } = require('child_process');
|
|
9
|
+
|
|
10
|
+
const homeDir = os.homedir();
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
const cacheDir = path.join(homeDir, '.claude', 'cache');
|
|
13
|
+
const cacheFile = path.join(cacheDir, 'declare-update-check.json');
|
|
14
|
+
|
|
15
|
+
// VERSION file locations (check project first, then global)
|
|
16
|
+
const projectVersionFile = path.join(cwd, '.claude', 'declare', 'VERSION');
|
|
17
|
+
const globalVersionFile = path.join(homeDir, '.claude', 'declare', 'VERSION');
|
|
18
|
+
|
|
19
|
+
// Ensure cache directory exists
|
|
20
|
+
if (!fs.existsSync(cacheDir)) {
|
|
21
|
+
fs.mkdirSync(cacheDir, { recursive: true });
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Run check in background (spawn background process, windowsHide prevents console flash)
|
|
25
|
+
const child = spawn(process.execPath, ['-e', `
|
|
26
|
+
const fs = require('fs');
|
|
27
|
+
const { execSync } = require('child_process');
|
|
28
|
+
|
|
29
|
+
const cacheFile = ${JSON.stringify(cacheFile)};
|
|
30
|
+
const projectVersionFile = ${JSON.stringify(projectVersionFile)};
|
|
31
|
+
const globalVersionFile = ${JSON.stringify(globalVersionFile)};
|
|
32
|
+
|
|
33
|
+
// Check project directory first (local install), then global
|
|
34
|
+
let installed = '0.0.0';
|
|
35
|
+
try {
|
|
36
|
+
if (fs.existsSync(projectVersionFile)) {
|
|
37
|
+
installed = fs.readFileSync(projectVersionFile, 'utf8').trim();
|
|
38
|
+
} else if (fs.existsSync(globalVersionFile)) {
|
|
39
|
+
installed = fs.readFileSync(globalVersionFile, 'utf8').trim();
|
|
40
|
+
}
|
|
41
|
+
} catch (e) {}
|
|
42
|
+
|
|
43
|
+
let latest = null;
|
|
44
|
+
try {
|
|
45
|
+
latest = execSync('npm view declare-cc version', { encoding: 'utf8', timeout: 10000, windowsHide: true }).trim();
|
|
46
|
+
} catch (e) {}
|
|
47
|
+
|
|
48
|
+
const result = {
|
|
49
|
+
update_available: latest && installed !== latest,
|
|
50
|
+
installed,
|
|
51
|
+
latest: latest || 'unknown',
|
|
52
|
+
checked: Math.floor(Date.now() / 1000)
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
fs.writeFileSync(cacheFile, JSON.stringify(result));
|
|
56
|
+
`], {
|
|
57
|
+
stdio: 'ignore',
|
|
58
|
+
windowsHide: true,
|
|
59
|
+
detached: true // Required on Windows for proper process detachment
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
child.unref();
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Claude Code Statusline - Declare Edition
|
|
3
|
+
// Shows: model | current task | directory | context usage
|
|
4
|
+
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const os = require('os');
|
|
8
|
+
|
|
9
|
+
// Read JSON from stdin
|
|
10
|
+
let input = '';
|
|
11
|
+
process.stdin.setEncoding('utf8');
|
|
12
|
+
process.stdin.on('data', chunk => input += chunk);
|
|
13
|
+
process.stdin.on('end', () => {
|
|
14
|
+
try {
|
|
15
|
+
const data = JSON.parse(input);
|
|
16
|
+
const model = data.model?.display_name || 'Claude';
|
|
17
|
+
const dir = data.workspace?.current_dir || process.cwd();
|
|
18
|
+
const session = data.session_id || '';
|
|
19
|
+
const remaining = data.context_window?.remaining_percentage;
|
|
20
|
+
|
|
21
|
+
// Context window display (shows USED percentage scaled to 80% limit)
|
|
22
|
+
// Claude Code enforces an 80% context limit, so we scale to show 100% at that point
|
|
23
|
+
let ctx = '';
|
|
24
|
+
if (remaining != null) {
|
|
25
|
+
const rem = Math.round(remaining);
|
|
26
|
+
const rawUsed = Math.max(0, Math.min(100, 100 - rem));
|
|
27
|
+
// Scale: 80% real usage = 100% displayed
|
|
28
|
+
const used = Math.min(100, Math.round((rawUsed / 80) * 100));
|
|
29
|
+
|
|
30
|
+
// Build progress bar (10 segments)
|
|
31
|
+
const filled = Math.floor(used / 10);
|
|
32
|
+
const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
|
|
33
|
+
|
|
34
|
+
// Color based on scaled usage (thresholds adjusted for new scale)
|
|
35
|
+
if (used < 63) { // ~50% real
|
|
36
|
+
ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
|
|
37
|
+
} else if (used < 81) { // ~65% real
|
|
38
|
+
ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
|
|
39
|
+
} else if (used < 95) { // ~76% real
|
|
40
|
+
ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
|
|
41
|
+
} else {
|
|
42
|
+
ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Current task from todos
|
|
47
|
+
let task = '';
|
|
48
|
+
const homeDir = os.homedir();
|
|
49
|
+
const todosDir = path.join(homeDir, '.claude', 'todos');
|
|
50
|
+
if (session && fs.existsSync(todosDir)) {
|
|
51
|
+
try {
|
|
52
|
+
const files = fs.readdirSync(todosDir)
|
|
53
|
+
.filter(f => f.startsWith(session) && f.includes('-agent-') && f.endsWith('.json'))
|
|
54
|
+
.map(f => ({ name: f, mtime: fs.statSync(path.join(todosDir, f)).mtime }))
|
|
55
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
56
|
+
|
|
57
|
+
if (files.length > 0) {
|
|
58
|
+
try {
|
|
59
|
+
const todos = JSON.parse(fs.readFileSync(path.join(todosDir, files[0].name), 'utf8'));
|
|
60
|
+
const inProgress = todos.find(t => t.status === 'in_progress');
|
|
61
|
+
if (inProgress) task = inProgress.activeForm || '';
|
|
62
|
+
} catch (e) {}
|
|
63
|
+
}
|
|
64
|
+
} catch (e) {
|
|
65
|
+
// Silently fail on file system errors - don't break statusline
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Declare update available?
|
|
70
|
+
let gsdUpdate = '';
|
|
71
|
+
const cacheFile = path.join(homeDir, '.claude', 'cache', 'declare-update-check.json');
|
|
72
|
+
if (fs.existsSync(cacheFile)) {
|
|
73
|
+
try {
|
|
74
|
+
const cache = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
75
|
+
if (cache.update_available) {
|
|
76
|
+
gsdUpdate = '\x1b[33m⬆ /declare:update\x1b[0m │ ';
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Output
|
|
82
|
+
const dirname = path.basename(dir);
|
|
83
|
+
if (task) {
|
|
84
|
+
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[1m${task}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
|
85
|
+
} else {
|
|
86
|
+
process.stdout.write(`${gsdUpdate}\x1b[2m${model}\x1b[0m │ \x1b[2m${dirname}\x1b[0m${ctx}`);
|
|
87
|
+
}
|
|
88
|
+
} catch (e) {
|
|
89
|
+
// Silent fail - don't break statusline on parse errors
|
|
90
|
+
}
|
|
91
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "declare-cc",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "A future-driven meta-prompting engine for agentic development, rooted in declared futures and causal graph structure.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"declare-cc": "bin/install.js"
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"commands",
|
|
11
11
|
"agents",
|
|
12
12
|
"dist",
|
|
13
|
+
"hooks",
|
|
13
14
|
"scripts",
|
|
14
15
|
"workflows",
|
|
15
16
|
"templates"
|