azclaude-copilot 0.5.0 → 0.5.2
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 +2 -2
- package/bin/cli.js +18 -13
- package/package.json +1 -1
- package/templates/scripts/statusline.js +153 -0
package/README.md
CHANGED
|
@@ -758,11 +758,11 @@ An agent is a sub-process. Use one when work must happen **in parallel** or **in
|
|
|
758
758
|
|
|
759
759
|
## Verified
|
|
760
760
|
|
|
761
|
-
|
|
761
|
+
1763 tests. Every template, command, capability, agent, hook, and CLI feature verified.
|
|
762
762
|
|
|
763
763
|
```bash
|
|
764
764
|
bash tests/test-features.sh
|
|
765
|
-
# Results:
|
|
765
|
+
# Results: 1763 passed, 0 failed, 1763 total
|
|
766
766
|
```
|
|
767
767
|
|
|
768
768
|
---
|
package/bin/cli.js
CHANGED
|
@@ -444,36 +444,41 @@ function installScripts(projectDir, cfg) {
|
|
|
444
444
|
// ─── Statusline (auto-updating cost/context bar) ─────────────────────────────
|
|
445
445
|
|
|
446
446
|
function installStatusline(projectDir, cfg) {
|
|
447
|
-
const scriptSrc = path.join(TEMPLATE_DIR, 'scripts', 'statusline.
|
|
447
|
+
const scriptSrc = path.join(TEMPLATE_DIR, 'scripts', 'statusline.js');
|
|
448
448
|
if (!fs.existsSync(scriptSrc)) return;
|
|
449
449
|
|
|
450
|
-
// Copy statusline script to ~/.claude/statusline.
|
|
450
|
+
// Copy statusline script to ~/.claude/statusline.js (global — shared across projects)
|
|
451
451
|
const globalDir = path.join(os.homedir(), '.claude');
|
|
452
|
-
const scriptDst = path.join(globalDir, 'statusline.
|
|
452
|
+
const scriptDst = path.join(globalDir, 'statusline.js');
|
|
453
453
|
fs.mkdirSync(globalDir, { recursive: true });
|
|
454
454
|
fs.copyFileSync(scriptSrc, scriptDst);
|
|
455
|
-
|
|
455
|
+
|
|
456
|
+
// Remove old .sh version if it exists (migrating from v0.5.0)
|
|
457
|
+
const oldSh = path.join(globalDir, 'statusline.sh');
|
|
458
|
+
try { if (fs.existsSync(oldSh)) fs.unlinkSync(oldSh); } catch {}
|
|
456
459
|
|
|
457
460
|
// Configure in global settings.json (statusLine is a global Claude Code setting)
|
|
461
|
+
const nodeExe = process.execPath;
|
|
458
462
|
const settingsPath = path.join(globalDir, 'settings.json');
|
|
459
463
|
let settings = {};
|
|
460
464
|
if (fs.existsSync(settingsPath)) {
|
|
461
465
|
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch {}
|
|
462
466
|
}
|
|
463
467
|
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
+
// Always update the command to use Node.js (migrates from old .sh version)
|
|
469
|
+
const scriptPath = scriptDst.replace(/\\/g, '/');
|
|
470
|
+
const nodePath = nodeExe.replace(/\\/g, '/');
|
|
471
|
+
const needsUpdate = !settings.statusLine
|
|
472
|
+
|| (settings.statusLine.command && settings.statusLine.command.includes('statusline.sh'));
|
|
473
|
+
if (needsUpdate) {
|
|
468
474
|
settings.statusLine = {
|
|
469
475
|
type: 'command',
|
|
470
|
-
command: scriptPath
|
|
476
|
+
command: `"${nodePath}" "${scriptPath}"`,
|
|
471
477
|
padding: 1
|
|
472
478
|
};
|
|
473
479
|
atomicWriteFileSync(settingsPath, JSON.stringify(settings, null, 2));
|
|
474
|
-
ok('Statusline installed — context %, rate limit, session time visible after every turn');
|
|
480
|
+
ok('Statusline installed (Node.js) — context %, rate limit, session time visible after every turn');
|
|
475
481
|
} else {
|
|
476
|
-
// Always refresh the script even if config exists
|
|
477
482
|
info('Statusline already configured — script refreshed');
|
|
478
483
|
}
|
|
479
484
|
}
|
|
@@ -994,8 +999,8 @@ function runDoctor() {
|
|
|
994
999
|
|
|
995
1000
|
// ── Statusline ──────────────────────────────────────────────────────────
|
|
996
1001
|
console.log('\n[ Statusline ]');
|
|
997
|
-
const statuslineScript = path.join(os.homedir(), '.claude', 'statusline.
|
|
998
|
-
chk('statusline.
|
|
1002
|
+
const statuslineScript = path.join(os.homedir(), '.claude', 'statusline.js');
|
|
1003
|
+
chk('statusline.js exists (~/.claude/statusline.js)', fs.existsSync(statuslineScript));
|
|
999
1004
|
if (cli.hooksDir) {
|
|
1000
1005
|
const globalSettingsPath = path.join(cli.hooksDir, 'settings.json');
|
|
1001
1006
|
if (fs.existsSync(globalSettingsPath)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azclaude-copilot",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"description": "AI coding environment — 39 commands, 10 skills, 15 agents, memory, reflexes, evolution. Install: npx azclaude-copilot@latest, then open Claude Code.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"azclaude": "bin/cli.js",
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// AZCLAUDE statusline — auto-installed by setup
|
|
3
|
+
// Shows: model | context bar | git branch | rate limit | cache | time | lines | cost
|
|
4
|
+
// Updates automatically after every turn — no manual action needed.
|
|
5
|
+
// Zero dependencies — uses only Node.js (already required by AZCLAUDE).
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const { execSync } = require('child_process');
|
|
9
|
+
|
|
10
|
+
let raw = '';
|
|
11
|
+
process.stdin.setEncoding('utf8');
|
|
12
|
+
process.stdin.on('data', chunk => { raw += chunk; });
|
|
13
|
+
process.stdin.on('end', () => {
|
|
14
|
+
let data = {};
|
|
15
|
+
try { data = JSON.parse(raw); } catch (_) {
|
|
16
|
+
process.stdout.write('[AZCLAUDE] statusline: invalid JSON input\n');
|
|
17
|
+
process.exit(0);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ── Extract metrics ──
|
|
21
|
+
const model = data.model?.display_name || 'Claude';
|
|
22
|
+
const ctxPct = Math.floor(data.context_window?.used_percentage || 0);
|
|
23
|
+
const ctxSize = data.context_window?.context_window_size || 0;
|
|
24
|
+
const cost = data.cost?.total_cost_usd || 0;
|
|
25
|
+
const durMs = data.cost?.total_duration_ms || 0;
|
|
26
|
+
const linesAdd = data.cost?.total_lines_added || 0;
|
|
27
|
+
const linesDel = data.cost?.total_lines_removed || 0;
|
|
28
|
+
const rate5h = data.rate_limits?.five_hour?.used_percentage ?? -1;
|
|
29
|
+
|
|
30
|
+
// Token details
|
|
31
|
+
const inputTok = data.context_window?.total_input_tokens || 0;
|
|
32
|
+
const outputTok = data.context_window?.total_output_tokens || 0;
|
|
33
|
+
const cachWrite = data.context_window?.current_usage?.cache_creation_input_tokens || 0;
|
|
34
|
+
const cachRead = data.context_window?.current_usage?.cache_read_input_tokens || 0;
|
|
35
|
+
|
|
36
|
+
// ── Format helpers ──
|
|
37
|
+
const durSec = Math.floor(durMs / 1000);
|
|
38
|
+
const mins = Math.floor(durSec / 60);
|
|
39
|
+
const secs = durSec % 60;
|
|
40
|
+
|
|
41
|
+
const ctxLabel = ctxSize >= 1000000 ? '1M' : ctxSize >= 100000 ? '200k' : String(ctxSize);
|
|
42
|
+
|
|
43
|
+
// Format large token numbers: 1234567 → 1.2M, 12345 → 12k
|
|
44
|
+
function fmtTok(n) {
|
|
45
|
+
if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
|
|
46
|
+
if (n >= 1000) return (n / 1000).toFixed(0) + 'k';
|
|
47
|
+
return String(n);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── Git info (cached — fast on subsequent calls) ──
|
|
51
|
+
let gitBranch = '';
|
|
52
|
+
let gitDirty = 0;
|
|
53
|
+
try {
|
|
54
|
+
gitBranch = execSync('git branch --show-current 2>/dev/null', { encoding: 'utf8', timeout: 2000 }).trim();
|
|
55
|
+
const status = execSync('git status --porcelain 2>/dev/null', { encoding: 'utf8', timeout: 2000 }).trim();
|
|
56
|
+
gitDirty = status ? status.split('\n').length : 0;
|
|
57
|
+
} catch (_) {}
|
|
58
|
+
|
|
59
|
+
// ── Compaction prediction ──
|
|
60
|
+
// Estimate turns remaining before context hits 90% (compaction threshold)
|
|
61
|
+
// Uses average tokens per turn from session data
|
|
62
|
+
let compactHint = '';
|
|
63
|
+
if (ctxPct > 0 && ctxPct < 90 && inputTok > 0) {
|
|
64
|
+
const totalTok = Math.floor(ctxSize * ctxPct / 100);
|
|
65
|
+
const remaining = Math.floor(ctxSize * 0.9) - totalTok;
|
|
66
|
+
// Rough estimate: average ~4000 tokens per turn (input + output + overhead)
|
|
67
|
+
const avgPerTurn = 4000;
|
|
68
|
+
const turnsLeft = Math.max(1, Math.floor(remaining / avgPerTurn));
|
|
69
|
+
if (turnsLeft <= 20) {
|
|
70
|
+
compactHint = turnsLeft <= 5 ? ` ~${turnsLeft}t!` : ` ~${turnsLeft}t`;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Cache hit ratio ──
|
|
75
|
+
let cacheHint = '';
|
|
76
|
+
if (cachRead > 0 || cachWrite > 0) {
|
|
77
|
+
const total = cachRead + cachWrite;
|
|
78
|
+
const hitPct = total > 0 ? Math.floor(cachRead / total * 100) : 0;
|
|
79
|
+
cacheHint = `Cache:${hitPct}%`;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── ANSI colors ──
|
|
83
|
+
const GREEN = '\x1b[32m';
|
|
84
|
+
const YELLOW = '\x1b[33m';
|
|
85
|
+
const RED = '\x1b[31m';
|
|
86
|
+
const CYAN = '\x1b[36m';
|
|
87
|
+
const DIM = '\x1b[2m';
|
|
88
|
+
const BOLD = '\x1b[1m';
|
|
89
|
+
const RESET = '\x1b[0m';
|
|
90
|
+
|
|
91
|
+
// ── Context bar (10 segments) ──
|
|
92
|
+
const filled = Math.floor(ctxPct * 10 / 100);
|
|
93
|
+
const empty = 10 - filled;
|
|
94
|
+
const bar = '\u2593'.repeat(filled) + '\u2591'.repeat(empty);
|
|
95
|
+
|
|
96
|
+
// ── Color thresholds ──
|
|
97
|
+
let ctxColor, ctxWarn = '';
|
|
98
|
+
if (ctxPct >= 80) { ctxColor = RED; ctxWarn = ' COMPACT SOON'; }
|
|
99
|
+
else if (ctxPct >= 60) { ctxColor = YELLOW; }
|
|
100
|
+
else { ctxColor = GREEN; }
|
|
101
|
+
|
|
102
|
+
// ── Line 1: model + context bar + compaction prediction ──
|
|
103
|
+
let line1 = `${DIM}[${RESET}${BOLD}${model}${RESET}${DIM}]${RESET} `;
|
|
104
|
+
line1 += `${ctxColor}${bar} ${ctxPct}%${RESET}`;
|
|
105
|
+
line1 += `${DIM}/${ctxLabel}${RESET}`;
|
|
106
|
+
if (ctxWarn) line1 += `${RED}${ctxWarn}${RESET}`;
|
|
107
|
+
if (compactHint) {
|
|
108
|
+
const compColor = compactHint.includes('!') ? RED : YELLOW;
|
|
109
|
+
line1 += `${compColor}${compactHint}${RESET}`;
|
|
110
|
+
}
|
|
111
|
+
// Git branch
|
|
112
|
+
if (gitBranch) {
|
|
113
|
+
line1 += ` ${DIM}|${RESET} ${CYAN}${gitBranch}${RESET}`;
|
|
114
|
+
if (gitDirty > 0) line1 += `${YELLOW}*${gitDirty}${RESET}`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// ── Line 2: rate limit + cache + tokens + time + lines + cost ──
|
|
118
|
+
let line2 = '';
|
|
119
|
+
|
|
120
|
+
// Rate limit (only show if available — Pro/Max subscription)
|
|
121
|
+
if (rate5h >= 0) {
|
|
122
|
+
const r = Math.floor(rate5h);
|
|
123
|
+
const rColor = r >= 80 ? RED : r >= 50 ? YELLOW : DIM;
|
|
124
|
+
line2 += `${rColor}Rate:${r}%${RESET} `;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Cache hit ratio
|
|
128
|
+
if (cacheHint) {
|
|
129
|
+
line2 += `${DIM}${cacheHint}${RESET} `;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Token totals
|
|
133
|
+
if (inputTok > 0 || outputTok > 0) {
|
|
134
|
+
line2 += `${DIM}In:${fmtTok(inputTok)} Out:${fmtTok(outputTok)}${RESET} `;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Duration
|
|
138
|
+
line2 += `${DIM}${mins}m${secs}s${RESET}`;
|
|
139
|
+
|
|
140
|
+
// Lines changed
|
|
141
|
+
if (linesAdd > 0 || linesDel > 0) {
|
|
142
|
+
line2 += ` ${GREEN}+${linesAdd}${RESET}${DIM}/${RESET}${RED}-${linesDel}${RESET}`;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Cost (only show if > 0 — API billing)
|
|
146
|
+
if (cost > 0) {
|
|
147
|
+
line2 += ` ${DIM}$${cost.toFixed(2)}${RESET}`;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── Output ──
|
|
151
|
+
process.stdout.write(line1 + '\n');
|
|
152
|
+
process.stdout.write(line2 + '\n');
|
|
153
|
+
});
|