qualia-framework 2.1.4 → 2.1.6

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/cli.js CHANGED
@@ -139,7 +139,12 @@ function mergeSettings(existingPath, templatePath) {
139
139
  // Overwrite hooks, permissions, statusLine from template (framework-managed)
140
140
  merged.hooks = template.hooks;
141
141
  merged.permissions = template.permissions;
142
- merged.statusLine = template.statusLine;
142
+ // Use node-based statusline on Windows (no bash), bash on Unix
143
+ if (process.platform === 'win32') {
144
+ merged.statusLine = { type: 'command', command: 'node ~/.claude/statusline-command.js' };
145
+ } else {
146
+ merged.statusLine = template.statusLine;
147
+ }
143
148
 
144
149
  // Preserve user's existing plugins and MCP servers
145
150
  if (existing.enabledPlugins) {
@@ -233,8 +238,8 @@ async function runInstall() {
233
238
  }
234
239
  }
235
240
 
236
- // Copy standalone files
237
- for (const f of ['statusline-command.sh', 'askpass.sh']) {
241
+ // Copy standalone files (both .sh and .js for cross-platform)
242
+ for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
238
243
  const src = path.join(FRAMEWORK_DIR, f);
239
244
  if (fs.existsSync(src)) {
240
245
  fs.copyFileSync(src, path.join(CLAUDE_DIR, f));
@@ -251,9 +256,9 @@ async function runInstall() {
251
256
  }
252
257
  }
253
258
  // Make standalone scripts executable
254
- for (const f of ['statusline-command.sh', 'askpass.sh']) {
259
+ for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
255
260
  const p = path.join(CLAUDE_DIR, f);
256
- if (fs.existsSync(p)) fs.chmodSync(p, 0o755);
261
+ if (fs.existsSync(p)) try { fs.chmodSync(p, 0o755); } catch {}
257
262
  }
258
263
  // Make qualia-engine bin executable
259
264
  const engineBin = path.join(CLAUDE_DIR, 'qualia-engine', 'bin');
@@ -411,12 +416,12 @@ async function runUpdate() {
411
416
  }
412
417
  }
413
418
 
414
- // Copy standalone files
415
- for (const f of ['statusline-command.sh', 'askpass.sh']) {
419
+ // Copy standalone files (both .sh and .js for cross-platform)
420
+ for (const f of ['statusline-command.sh', 'statusline-command.js', 'askpass.sh']) {
416
421
  const src = path.join(FRAMEWORK_DIR, f);
417
422
  if (fs.existsSync(src)) {
418
423
  fs.copyFileSync(src, path.join(CLAUDE_DIR, f));
419
- fs.chmodSync(path.join(CLAUDE_DIR, f), 0o755);
424
+ try { fs.chmodSync(path.join(CLAUDE_DIR, f), 0o755); } catch {}
420
425
  }
421
426
  }
422
427
 
@@ -424,7 +429,7 @@ async function runUpdate() {
424
429
  const hooksDir = path.join(CLAUDE_DIR, 'hooks');
425
430
  if (fs.existsSync(hooksDir)) {
426
431
  for (const f of fs.readdirSync(hooksDir)) {
427
- if (f.endsWith('.sh')) fs.chmodSync(path.join(hooksDir, f), 0o755);
432
+ if (f.endsWith('.sh')) try { fs.chmodSync(path.join(hooksDir, f), 0o755); } catch {}
428
433
  }
429
434
  }
430
435
 
@@ -0,0 +1,111 @@
1
+ #!/usr/bin/env node
2
+ // Qualia statusline — cross-platform (Windows/Mac/Linux)
3
+ // Layout: ◆ dir branch [status] │ phase │ model ━━━━ 23%
4
+
5
+ const { execSync } = require('child_process');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+
9
+ let input = '';
10
+ process.stdin.on('data', c => input += c);
11
+ process.stdin.on('end', () => {
12
+ try {
13
+ const data = JSON.parse(input);
14
+ const usedPct = data.context_window?.used_percentage;
15
+ const model = data.model?.display_name || '';
16
+ const cwd = data.workspace?.current_dir || process.cwd();
17
+
18
+ if (!usedPct) process.exit(0);
19
+
20
+ const usedInt = Math.round(usedPct);
21
+ const home = process.env.HOME || process.env.USERPROFILE || '';
22
+
23
+ // Directory shortening
24
+ let dir = cwd.replace(home, '~').replace(/\\/g, '/');
25
+ const parts = dir.split('/');
26
+ if (parts.length > 3) dir = '…/' + parts.slice(-2).join('/');
27
+
28
+ // Colors
29
+ const T = '\x1b[38;2;0;188;175m';
30
+ const TB = '\x1b[38;2;45;226;210m';
31
+ const TD = '\x1b[38;2;0;120;112m';
32
+ const G = '\x1b[38;2;70;78;88m';
33
+ const Y = '\x1b[38;2;234;179;8m';
34
+ const R = '\x1b[38;2;239;68;68m';
35
+ const P = '\x1b[38;2;189;147;249m';
36
+ const X = '\x1b[0m';
37
+
38
+ const CB = usedInt >= 70 ? R : usedInt >= 50 ? Y : T;
39
+
40
+ // Project + phase detection
41
+ let project = '';
42
+ let phase = '';
43
+ const stateFile = path.join(cwd, '.planning', 'STATE.md');
44
+ if (fs.existsSync(stateFile)) {
45
+ const state = fs.readFileSync(stateFile, 'utf8');
46
+ const pm = state.match(/^Project:\s*(.+)/m);
47
+ if (pm) project = pm[1].trim();
48
+ const phm = state.match(/^Phase:\s*(.+)/m);
49
+ if (phm) {
50
+ const raw = phm[1].trim();
51
+ const nm = raw.match(/^(\d+ of \d+)/);
52
+ phase = nm ? nm[1] : raw.substring(0, 12);
53
+ }
54
+ }
55
+
56
+ if (!project) {
57
+ const pkgFile = path.join(cwd, 'package.json');
58
+ if (fs.existsSync(pkgFile)) {
59
+ try { project = JSON.parse(fs.readFileSync(pkgFile, 'utf8')).name || ''; } catch {}
60
+ }
61
+ }
62
+
63
+ // Git branch + status
64
+ let branch = '';
65
+ let gitStatus = '';
66
+ try {
67
+ branch = execSync('git branch --show-current', { cwd, stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
68
+ if (branch) {
69
+ const raw = execSync('git status --porcelain', { cwd, stdio: ['pipe', 'pipe', 'pipe'] }).toString().trim();
70
+ if (raw) {
71
+ const lines = raw.split('\n');
72
+ const m = lines.filter(l => /^ M|^MM|^AM/.test(l)).length;
73
+ const s = lines.filter(l => /^[MARCD] /.test(l)).length;
74
+ const u = lines.filter(l => /^\?\?/.test(l)).length;
75
+ if (m > 0) gitStatus += `!${m}`;
76
+ if (s > 0) gitStatus += `+${s}`;
77
+ if (u > 0) gitStatus += `?${u}`;
78
+ }
79
+ }
80
+ } catch {}
81
+
82
+ // Context bar
83
+ const barW = 10;
84
+ const filled = Math.min(Math.round(usedInt * barW / 100), barW);
85
+ const empty = barW - filled;
86
+ const bar = '━'.repeat(filled);
87
+ const ebar = '─'.repeat(empty);
88
+
89
+ // Build output
90
+ let out = `${TB}◆${X} ${T}${dir}${X}`;
91
+ if (branch) {
92
+ out += ` ${P}${branch}${X}`;
93
+ if (gitStatus) out += `${Y}[${gitStatus}]${X}`;
94
+ }
95
+ out += ` ${G}│${X} `;
96
+ if (project) {
97
+ out += `${TB}${project}${X}`;
98
+ if (phase) out += ` ${TD}p${phase}${X}`;
99
+ } else if (phase) {
100
+ out += `${T}p${phase}${X}`;
101
+ } else {
102
+ out += `${TD}ad-hoc${X}`;
103
+ }
104
+ out += ` ${G}│${X} ${G}${model}${X}`;
105
+ out += ` ${CB}${bar}${G}${ebar}${X} ${CB}${usedInt}%${X}`;
106
+
107
+ process.stdout.write(out);
108
+ } catch {
109
+ process.exit(0);
110
+ }
111
+ });
@@ -4,9 +4,10 @@
4
4
 
5
5
  input=$(cat)
6
6
 
7
- USED_PCT=$(echo "$input" | jq -r '.context_window.used_percentage // empty')
8
- MODEL=$(echo "$input" | jq -r '.model.display_name')
9
- CWD=$(echo "$input" | jq -r '.workspace.current_dir')
7
+ # Cross-platform JSON parsing use node instead of jq (Windows compat)
8
+ USED_PCT=$(echo "$input" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(String(j.context_window?.used_percentage||''))}catch(e){}})" 2>/dev/null)
9
+ MODEL=$(echo "$input" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.model?.display_name||'')}catch(e){}})" 2>/dev/null)
10
+ CWD=$(echo "$input" | node -e "let d='';process.stdin.on('data',c=>d+=c);process.stdin.on('end',()=>{try{const j=JSON.parse(d);process.stdout.write(j.workspace?.current_dir||'')}catch(e){}})" 2>/dev/null)
10
11
 
11
12
  [ -z "$USED_PCT" ] && exit 0
12
13
 
@@ -47,8 +48,8 @@ if [ -f "$CWD/.planning/STATE.md" ]; then
47
48
  # Get phase number only (e.g. "3 of 6") — not the full description
48
49
  PHASE_RAW=$(grep -m1 "^Phase:" "$CWD/.planning/STATE.md" 2>/dev/null | sed 's/^Phase: *//')
49
50
  if [ -n "$PHASE_RAW" ]; then
50
- # Extract "N of M" pattern or just take first 12 chars
51
- PHASE_SHORT=$(echo "$PHASE_RAW" | grep -oP '^\d+ of \d+' 2>/dev/null)
51
+ # Extract "N of M" pattern or just take first 12 chars (no grep -P for Windows compat)
52
+ PHASE_SHORT=$(echo "$PHASE_RAW" | sed -n 's/^\([0-9]* of [0-9]*\).*/\1/p' 2>/dev/null)
52
53
  [ -z "$PHASE_SHORT" ] && PHASE_SHORT=$(echo "$PHASE_RAW" | head -c 12)
53
54
  PHASE="$PHASE_SHORT"
54
55
  fi
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "Qualia Solutions — Claude Code Framework",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"