slash-do 1.2.0 → 1.3.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.
@@ -8,9 +8,13 @@ const os = require('os');
8
8
 
9
9
  // Read JSON from stdin
10
10
  let input = '';
11
+ // Timeout guard: if stdin doesn't close within 3s (e.g. pipe issues on
12
+ // Windows/Git Bash), exit silently instead of hanging.
13
+ const stdinTimeout = setTimeout(() => process.exit(0), 3000);
11
14
  process.stdin.setEncoding('utf8');
12
15
  process.stdin.on('data', chunk => input += chunk);
13
16
  process.stdin.on('end', () => {
17
+ clearTimeout(stdinTimeout);
14
18
  try {
15
19
  const data = JSON.parse(input);
16
20
  const model = data.model?.display_name || 'Claude';
@@ -18,14 +22,14 @@ process.stdin.on('end', () => {
18
22
  const session = data.session_id || '';
19
23
  const remaining = data.context_window?.remaining_percentage;
20
24
 
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
25
+ // Context window display (shows USED percentage scaled to usable context)
26
+ // Claude Code reserves ~16.5% for autocompact buffer, so usable context
27
+ // is 83.5% of the total window. We normalize to show 100% at that point.
28
+ const AUTO_COMPACT_BUFFER_PCT = 16.5;
23
29
  let ctx = '';
24
30
  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));
31
+ const usableRemaining = Math.max(0, ((remaining - AUTO_COMPACT_BUFFER_PCT) / (100 - AUTO_COMPACT_BUFFER_PCT)) * 100);
32
+ const used = Math.max(0, Math.min(100, Math.round(100 - usableRemaining)));
29
33
 
30
34
  // Write context metrics to bridge file for context-monitor hooks
31
35
  if (session) {
@@ -48,12 +52,12 @@ process.stdin.on('end', () => {
48
52
  const filled = Math.floor(used / 10);
49
53
  const bar = '█'.repeat(filled) + '░'.repeat(10 - filled);
50
54
 
51
- // Color based on scaled usage
52
- if (used < 63) {
55
+ // Color based on usable context thresholds
56
+ if (used < 50) {
53
57
  ctx = ` \x1b[32m${bar} ${used}%\x1b[0m`;
54
- } else if (used < 81) {
58
+ } else if (used < 65) {
55
59
  ctx = ` \x1b[33m${bar} ${used}%\x1b[0m`;
56
- } else if (used < 95) {
60
+ } else if (used < 80) {
57
61
  ctx = ` \x1b[38;5;208m${bar} ${used}%\x1b[0m`;
58
62
  } else {
59
63
  ctx = ` \x1b[5;31m💀 ${bar} ${used}%\x1b[0m`;
@@ -63,7 +67,9 @@ process.stdin.on('end', () => {
63
67
  // Current task from todos
64
68
  let task = '';
65
69
  const homeDir = os.homedir();
66
- const todosDir = path.join(homeDir, '.claude', 'todos');
70
+ // Respect CLAUDE_CONFIG_DIR for custom config directory setups
71
+ const claudeDir = process.env.CLAUDE_CONFIG_DIR || path.join(homeDir, '.claude');
72
+ const todosDir = path.join(claudeDir, 'todos');
67
73
  if (session && fs.existsSync(todosDir)) {
68
74
  try {
69
75
  const entries = fs.readdirSync(todosDir);
@@ -91,7 +97,8 @@ process.stdin.on('end', () => {
91
97
 
92
98
  // Update notifications (GSD + slashdo)
93
99
  let updates = '';
94
- const gsdCacheFile = path.join(homeDir, '.claude', 'cache', 'gsd-update-check.json');
100
+ const cacheDir = path.join(claudeDir, 'cache');
101
+ const gsdCacheFile = path.join(cacheDir, 'gsd-update-check.json');
95
102
  if (fs.existsSync(gsdCacheFile)) {
96
103
  try {
97
104
  const cache = JSON.parse(fs.readFileSync(gsdCacheFile, 'utf8'));
@@ -100,7 +107,7 @@ process.stdin.on('end', () => {
100
107
  }
101
108
  } catch (e) {}
102
109
  }
103
- const slashdoCacheFile = path.join(homeDir, '.claude', 'cache', 'slashdo-update-check.json');
110
+ const slashdoCacheFile = path.join(cacheDir, 'slashdo-update-check.json');
104
111
  if (fs.existsSync(slashdoCacheFile)) {
105
112
  try {
106
113
  const cache = JSON.parse(fs.readFileSync(slashdoCacheFile, 'utf8'));
package/install.sh CHANGED
@@ -152,12 +152,19 @@ install_claude() {
152
152
  modified = true;
153
153
  }
154
154
 
155
- // Statusline (only if none exists and hook file was downloaded)
155
+ // Statusline: upgrade gsd-statusline slashdo-statusline (superset)
156
156
  const statuslineHookPath = path.join(hooksDir, "slashdo-statusline.js");
157
- if (!settings.statusLine && fs.existsSync(statuslineHookPath)) {
157
+ if (fs.existsSync(statuslineHookPath)) {
158
158
  const slCmd = "node \"" + statuslineHookPath + "\"";
159
- settings.statusLine = { type: "command", command: slCmd };
160
- modified = true;
159
+ const currentCmd = (settings.statusLine && typeof settings.statusLine.command === "string") ? settings.statusLine.command : "";
160
+ if (!settings.statusLine) {
161
+ settings.statusLine = { type: "command", command: slCmd };
162
+ modified = true;
163
+ } else if (currentCmd.indexOf("gsd-statusline") !== -1) {
164
+ settings.statusLine = { type: "command", command: slCmd };
165
+ modified = true;
166
+ }
167
+ // slashdo-statusline already active or custom statusline → no change
161
168
  }
162
169
 
163
170
  if (modified) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slash-do",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "Curated slash commands for AI coding assistants — Claude Code, OpenCode, Gemini CLI, and Codex",
5
5
  "author": "Adam Eivy <adam@eivy.com>",
6
6
  "license": "MIT",
package/src/installer.js CHANGED
@@ -146,18 +146,25 @@ function registerHooksInSettings(env, hookFiles, dryRun) {
146
146
  }
147
147
  }
148
148
 
149
- // Configure statusline only if none exists
149
+ // Configure statusline: upgrade gsd-statusline slashdo-statusline (superset)
150
150
  const statuslineHook = hookFiles.find(h => h.name === 'slashdo-statusline.js');
151
- if (statuslineHook && !settings.statusLine) {
151
+ if (statuslineHook) {
152
152
  const statuslineCommand = `node "${path.join(env.hooksDir, statuslineHook.name)}"`;
153
- settings.statusLine = {
154
- type: 'command',
155
- command: statuslineCommand,
156
- };
157
- modified = true;
158
- actions.push({ name: 'settings/statusLine', status: dryRun ? 'would configure' : 'configured' });
159
- } else if (statuslineHook && settings.statusLine) {
160
- actions.push({ name: 'settings/statusLine', status: 'existing statusline preserved' });
153
+ const currentCmd = typeof settings.statusLine?.command === 'string' ? settings.statusLine.command : '';
154
+
155
+ if (!settings.statusLine) {
156
+ settings.statusLine = { type: 'command', command: statuslineCommand };
157
+ modified = true;
158
+ actions.push({ name: 'settings/statusLine', status: dryRun ? 'would configure' : 'configured' });
159
+ } else if (currentCmd.includes('gsd-statusline')) {
160
+ settings.statusLine = { type: 'command', command: statuslineCommand };
161
+ modified = true;
162
+ actions.push({ name: 'settings/statusLine', status: dryRun ? 'would upgrade (gsd→slashdo)' : 'upgraded (gsd→slashdo)' });
163
+ } else if (currentCmd.includes('slashdo-statusline')) {
164
+ actions.push({ name: 'settings/statusLine', status: 'already configured' });
165
+ } else {
166
+ actions.push({ name: 'settings/statusLine', status: 'existing statusline preserved' });
167
+ }
161
168
  }
162
169
 
163
170
  if (!dryRun && modified) {
@@ -214,9 +221,16 @@ function deregisterHooksFromSettings(env, dryRun) {
214
221
 
215
222
  // Remove statusline if it references slashdo-statusline
216
223
  if (settings.statusLine?.command?.includes('slashdo-statusline')) {
217
- delete settings.statusLine;
224
+ // Restore gsd-statusline if its hook file still exists
225
+ const gsdHookPath = path.join(env.hooksDir, 'gsd-statusline.js');
226
+ if (fs.existsSync(gsdHookPath)) {
227
+ settings.statusLine = { type: 'command', command: `node "${gsdHookPath}"` };
228
+ actions.push({ name: 'settings/statusLine', status: dryRun ? 'would downgrade (slashdo→gsd)' : 'downgraded (slashdo→gsd)' });
229
+ } else {
230
+ delete settings.statusLine;
231
+ actions.push({ name: 'settings/statusLine', status: dryRun ? 'would remove' : 'removed' });
232
+ }
218
233
  modified = true;
219
- actions.push({ name: 'settings/statusLine', status: dryRun ? 'would remove' : 'removed' });
220
234
  }
221
235
 
222
236
  if (!dryRun && modified) {
package/uninstall.sh CHANGED
@@ -135,7 +135,13 @@ uninstall_claude() {
135
135
 
136
136
  if (settings.statusLine && settings.statusLine.command &&
137
137
  settings.statusLine.command.indexOf("slashdo-statusline") !== -1) {
138
- delete settings.statusLine;
138
+ var hooksDir = path.join(home, ".claude", "hooks");
139
+ var gsdHookPath = path.join(hooksDir, "gsd-statusline.js");
140
+ if (fs.existsSync(gsdHookPath)) {
141
+ settings.statusLine = { type: "command", command: "node \"" + gsdHookPath + "\"" };
142
+ } else {
143
+ delete settings.statusLine;
144
+ }
139
145
  modified = true;
140
146
  }
141
147