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 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
- if (shouldInstallStatusline && !isOpencode) {
1640
- settings.statusLine = {
1641
- type: 'command',
1642
- command: statuslineCommand
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.4.8",
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"