@alexismunozdev/claude-session-topics 2.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Alexis Muñoz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # claude-session-topics
2
+
3
+ Session topics for Claude Code. Auto-detect and display a topic in the statusline, change anytime with `/set-topic`.
4
+
5
+ ![Session topics demo](https://github.com/user-attachments/assets/3698013f-123c-4d7e-8511-44718da5c3a4)
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npx @alexismunozdev/claude-session-topics
11
+ ```
12
+
13
+ ## With color
14
+
15
+ ```bash
16
+ npx @alexismunozdev/claude-session-topics --color cyan
17
+ ```
18
+
19
+ Supported colors: `red`, `green`, `yellow`, `blue`, `magenta` (default), `cyan`, `white`, `orange`, `grey`/`gray`. Raw ANSI codes are also accepted (e.g., `38;5;208`).
20
+
21
+ ## What it does
22
+
23
+ - Auto-detects a session topic from context on the first prompt
24
+ - Shows the topic in the Claude Code statusline (`◆ Topic`)
25
+ - Change the topic anytime with `/set-topic`
26
+ - Composes with existing statusline plugins (doesn't overwrite)
27
+
28
+ ## What the installer configures
29
+
30
+ 1. Copies the statusline script to `~/.claude/session-topics/`
31
+ 2. Configures `statusLine` in `~/.claude/settings.json`
32
+ 3. Adds bash permission for the script
33
+ 4. Installs `auto-topic` and `set-topic` skills to `~/.claude/skills/`
34
+ 5. If you already have a statusline, creates a wrapper that shows both
35
+
36
+ ## Requirements
37
+
38
+ - `jq`
39
+ - `bash`
40
+ - POSIX-compatible system (macOS, Linux)
41
+
42
+ ## Customization
43
+
44
+ The default topic color is bold magenta. Three ways to change it:
45
+
46
+ - Re-run with `--color <name>`:
47
+ ```bash
48
+ npx @alexismunozdev/claude-session-topics --color cyan
49
+ ```
50
+ - Edit the config file directly:
51
+ ```bash
52
+ echo "cyan" > ~/.claude/session-topics/.color-config
53
+ ```
54
+ - Set the `CLAUDE_TOPIC_COLOR` environment variable:
55
+ ```bash
56
+ export CLAUDE_TOPIC_COLOR="cyan"
57
+ ```
58
+
59
+ ## Usage
60
+
61
+ ### Auto-topic (automatic)
62
+
63
+ Starts automatically on session start. Claude reads your first message and sets a short topic (2-4 words) summarizing the task.
64
+
65
+ ### /set-topic (manual)
66
+
67
+ Change the topic at any time:
68
+
69
+ ```
70
+ /set-topic Fix Login Bug
71
+ /set-topic API Redesign
72
+ ```
73
+
74
+ ## How it works
75
+
76
+ ```
77
+ Session starts
78
+ |
79
+ auto-topic skill fires on first message
80
+ |
81
+ Claude writes topic to ~/.claude/session-topics/${SESSION_ID}
82
+ |
83
+ Statusline script reads the topic file
84
+ |
85
+ Displays: ◆ Topic
86
+ ```
87
+
88
+ The statusline script receives the session ID via stdin JSON, reads the corresponding topic file, and renders it with ANSI color codes.
89
+
90
+ ## Uninstall
91
+
92
+ ```bash
93
+ npx @alexismunozdev/claude-session-topics --uninstall
94
+ ```
95
+
96
+ ## License
97
+
98
+ MIT
package/bin/install.js ADDED
@@ -0,0 +1,431 @@
1
+ #!/usr/bin/env node
2
+
3
+ // claude-session-topics — npx installer
4
+ // Installs statusline script, skills, and configures settings.json
5
+ // Zero runtime dependency on npm after installation.
6
+
7
+ 'use strict';
8
+
9
+ const fs = require('fs');
10
+ const path = require('path');
11
+ const os = require('os');
12
+ const { execSync } = require('child_process');
13
+
14
+ // ─── ANSI helpers ────────────────────────────────────────────────────────────
15
+
16
+ const GREEN = '\x1b[32m';
17
+ const YELLOW = '\x1b[33m';
18
+ const RED = '\x1b[31m';
19
+ const CYAN = '\x1b[36m';
20
+ const BOLD = '\x1b[1m';
21
+ const DIM = '\x1b[2m';
22
+ const RESET = '\x1b[0m';
23
+
24
+ const ok = (msg) => console.log(` ${GREEN}\u2713${RESET} ${msg}`);
25
+ const warn = (msg) => console.log(` ${YELLOW}\u26A0${RESET} ${msg}`);
26
+ const err = (msg) => console.error(` ${RED}\u2717${RESET} ${msg}`);
27
+ const info = (msg) => console.log(` ${DIM}${msg}${RESET}`);
28
+ const heading = (msg) => console.log(`\n${BOLD}${CYAN}${msg}${RESET}\n`);
29
+
30
+ // ─── Destination paths (fixed — never change) ───────────────────────────────
31
+
32
+ const HOME = os.homedir();
33
+ const TOPICS_DIR = path.join(HOME, '.claude', 'session-topics');
34
+ const DEST_STATUSLINE = path.join(TOPICS_DIR, 'statusline.sh');
35
+ const DEST_WRAPPER = path.join(TOPICS_DIR, 'wrapper-statusline.sh');
36
+ const ORIG_CMD_FILE = path.join(TOPICS_DIR, '.original-statusline-cmd');
37
+ const COLOR_CONFIG = path.join(TOPICS_DIR, '.color-config');
38
+ const SKILLS_DIR = path.join(HOME, '.claude', 'skills');
39
+ const SETTINGS_FILE = path.join(HOME, '.claude', 'settings.json');
40
+
41
+ // ─── Source paths (relative to this script) ──────────────────────────────────
42
+
43
+ const SRC_STATUSLINE = path.join(__dirname, '..', 'scripts', 'statusline.sh');
44
+ const SRC_SKILLS = path.join(__dirname, '..', 'skills');
45
+
46
+ // ─── The statusline command that settings.json will reference ────────────────
47
+
48
+ const STATUSLINE_CMD = `bash "$HOME/.claude/session-topics/statusline.sh"`;
49
+ const WRAPPER_CMD = `bash "$HOME/.claude/session-topics/wrapper-statusline.sh"`;
50
+
51
+ // ─── Permission rule ─────────────────────────────────────────────────────────
52
+
53
+ const PERMISSION_RULE = 'Bash(*session-topics*)';
54
+
55
+ // ─── Wrapper script content ──────────────────────────────────────────────────
56
+
57
+ const WRAPPER_SCRIPT = `#!/bin/bash
58
+ input=$(cat)
59
+ TOPIC_OUTPUT=$(echo "$input" | bash "$HOME/.claude/session-topics/statusline.sh" 2>/dev/null || echo "")
60
+ ORIG_CMD=$(cat "$HOME/.claude/session-topics/.original-statusline-cmd" 2>/dev/null || echo "")
61
+ ORIG_OUTPUT=""
62
+ [ -n "$ORIG_CMD" ] && ORIG_OUTPUT=$(echo "$input" | eval "$ORIG_CMD" 2>/dev/null || echo "")
63
+ if [ -n "$TOPIC_OUTPUT" ] && [ -n "$ORIG_OUTPUT" ]; then
64
+ echo -e "\${TOPIC_OUTPUT} | \${ORIG_OUTPUT}"
65
+ elif [ -n "$TOPIC_OUTPUT" ]; then
66
+ echo -e "\${TOPIC_OUTPUT}"
67
+ elif [ -n "$ORIG_OUTPUT" ]; then
68
+ echo -e "\${ORIG_OUTPUT}"
69
+ fi
70
+ `;
71
+
72
+ // ─── Utility functions ───────────────────────────────────────────────────────
73
+
74
+ function readSettings() {
75
+ try {
76
+ const raw = fs.readFileSync(SETTINGS_FILE, 'utf8');
77
+ return JSON.parse(raw);
78
+ } catch {
79
+ return {};
80
+ }
81
+ }
82
+
83
+ function writeSettings(obj) {
84
+ const dir = path.dirname(SETTINGS_FILE);
85
+ fs.mkdirSync(dir, { recursive: true });
86
+ fs.writeFileSync(SETTINGS_FILE, JSON.stringify(obj, null, 2) + '\n', 'utf8');
87
+ }
88
+
89
+ function copyDirRecursive(src, dest) {
90
+ fs.mkdirSync(dest, { recursive: true });
91
+ for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
92
+ const srcPath = path.join(src, entry.name);
93
+ const destPath = path.join(dest, entry.name);
94
+ if (entry.isDirectory()) {
95
+ copyDirRecursive(srcPath, destPath);
96
+ } else {
97
+ fs.copyFileSync(srcPath, destPath);
98
+ }
99
+ }
100
+ }
101
+
102
+ function hasJq() {
103
+ try {
104
+ execSync('which jq', { stdio: 'pipe' });
105
+ return true;
106
+ } catch {
107
+ return false;
108
+ }
109
+ }
110
+
111
+ // ─── CLI argument parsing ────────────────────────────────────────────────────
112
+
113
+ function parseArgs(argv) {
114
+ const args = argv.slice(2);
115
+ const result = { action: 'install', color: null };
116
+
117
+ for (let i = 0; i < args.length; i++) {
118
+ const arg = args[i];
119
+ if (arg === '--help' || arg === '-h') {
120
+ result.action = 'help';
121
+ return result;
122
+ }
123
+ if (arg === '--uninstall') {
124
+ result.action = 'uninstall';
125
+ return result;
126
+ }
127
+ if (arg === '--color') {
128
+ if (i + 1 < args.length) {
129
+ result.color = args[i + 1];
130
+ i++;
131
+ } else {
132
+ err('--color requires a value (e.g., --color cyan)');
133
+ process.exit(1);
134
+ }
135
+ }
136
+ }
137
+
138
+ return result;
139
+ }
140
+
141
+ // ─── Help ────────────────────────────────────────────────────────────────────
142
+
143
+ function showHelp() {
144
+ console.log(`
145
+ ${BOLD}claude-session-topics${RESET} — session topics for Claude Code
146
+
147
+ ${BOLD}Usage:${RESET}
148
+ npx @alexismunozdev/claude-session-topics Install
149
+ npx @alexismunozdev/claude-session-topics --color cyan Install with color
150
+ npx @alexismunozdev/claude-session-topics --uninstall Uninstall
151
+
152
+ ${BOLD}Options:${RESET}
153
+ --color <name> Set topic color (red, green, yellow, blue, magenta,
154
+ cyan, white, orange, grey). Default: magenta
155
+ --uninstall Remove scripts, settings, and skills (preserves topic data)
156
+ -h, --help Show this help
157
+
158
+ ${BOLD}What it does:${RESET}
159
+ - Copies statusline.sh to ~/.claude/session-topics/
160
+ - Configures statusLine in ~/.claude/settings.json
161
+ - Adds Bash permission for session-topics commands
162
+ - Installs auto-topic and set-topic skills to ~/.claude/skills/
163
+
164
+ ${BOLD}After install:${RESET}
165
+ The statusline shows the current topic automatically.
166
+ Use ${CYAN}/set-topic <text>${RESET} to change it manually.
167
+ `);
168
+ }
169
+
170
+ // ─── Install ─────────────────────────────────────────────────────────────────
171
+
172
+ function install(color) {
173
+ heading('Installing claude-session-topics');
174
+
175
+ // ── Step 1: Check deps ───────────────────────────────────────────────
176
+
177
+ if (!hasJq()) {
178
+ err('jq is required but not found in PATH.');
179
+ console.log(`\n Install it: ${BOLD}brew install jq${RESET} (macOS)`);
180
+ console.log(` ${BOLD}sudo apt install jq${RESET} (Ubuntu/Debian)\n`);
181
+ process.exit(1);
182
+ }
183
+ ok('jq found');
184
+
185
+ // ── Step 2: Create dir ───────────────────────────────────────────────
186
+
187
+ fs.mkdirSync(TOPICS_DIR, { recursive: true });
188
+ ok(`Created ${DIM}~/.claude/session-topics/${RESET}`);
189
+
190
+ // ── Step 3: Copy statusline ──────────────────────────────────────────
191
+
192
+ if (!fs.existsSync(SRC_STATUSLINE)) {
193
+ err(`Source statusline not found: ${SRC_STATUSLINE}`);
194
+ process.exit(1);
195
+ }
196
+ fs.copyFileSync(SRC_STATUSLINE, DEST_STATUSLINE);
197
+ fs.chmodSync(DEST_STATUSLINE, 0o755);
198
+ ok('Copied statusline.sh');
199
+
200
+ // ── Step 4: Configure statusline in settings.json ────────────────────
201
+
202
+ const settings = readSettings();
203
+ const statusLineCase = determineStatusLineCase(settings);
204
+
205
+ switch (statusLineCase) {
206
+ case 'A': {
207
+ // No statusLine — create fresh
208
+ settings.statusLine = {
209
+ type: 'command',
210
+ command: STATUSLINE_CMD,
211
+ };
212
+ writeSettings(settings);
213
+ ok('Configured statusLine in settings.json');
214
+ break;
215
+ }
216
+ case 'B': {
217
+ // Already ours — just update the script (already copied above)
218
+ ok('statusLine already configured for session-topics (updated script)');
219
+ break;
220
+ }
221
+ case 'C': {
222
+ // Another command exists — create wrapper
223
+ const origCmd = settings.statusLine.command;
224
+
225
+ // Backup original command
226
+ fs.writeFileSync(ORIG_CMD_FILE, origCmd, 'utf8');
227
+ info(`Backed up original statusLine command to .original-statusline-cmd`);
228
+
229
+ // Write wrapper
230
+ fs.writeFileSync(DEST_WRAPPER, WRAPPER_SCRIPT, 'utf8');
231
+ fs.chmodSync(DEST_WRAPPER, 0o755);
232
+ info('Created wrapper-statusline.sh');
233
+
234
+ // Update settings to use wrapper
235
+ settings.statusLine.command = WRAPPER_CMD;
236
+ writeSettings(settings);
237
+ ok('Configured statusLine wrapper (preserves your existing statusline)');
238
+ break;
239
+ }
240
+ case 'D': {
241
+ // statusLine exists but no valid command — treat as case A
242
+ settings.statusLine = {
243
+ type: 'command',
244
+ command: STATUSLINE_CMD,
245
+ };
246
+ writeSettings(settings);
247
+ ok('Configured statusLine in settings.json (replaced invalid entry)');
248
+ break;
249
+ }
250
+ }
251
+
252
+ // ── Step 5: Add permission ───────────────────────────────────────────
253
+
254
+ if (!settings.permissions || typeof settings.permissions !== 'object' || Array.isArray(settings.permissions)) {
255
+ settings.permissions = {};
256
+ }
257
+ if (!Array.isArray(settings.permissions.allow)) {
258
+ settings.permissions.allow = [];
259
+ }
260
+ if (!settings.permissions.allow.includes(PERMISSION_RULE)) {
261
+ settings.permissions.allow.push(PERMISSION_RULE);
262
+ writeSettings(settings);
263
+ ok(`Added permission: ${DIM}${PERMISSION_RULE}${RESET}`);
264
+ } else {
265
+ ok('Permission already present');
266
+ }
267
+
268
+ // ── Step 6: Copy skills ──────────────────────────────────────────────
269
+
270
+ const skillsToCopy = ['auto-topic', 'set-topic'];
271
+ for (const skill of skillsToCopy) {
272
+ const srcSkill = path.join(SRC_SKILLS, skill);
273
+ const destSkill = path.join(SKILLS_DIR, skill);
274
+ if (fs.existsSync(srcSkill)) {
275
+ copyDirRecursive(srcSkill, destSkill);
276
+ ok(`Installed skill: ${BOLD}${skill}${RESET}`);
277
+ } else {
278
+ warn(`Skill source not found: ${skill}`);
279
+ }
280
+ }
281
+
282
+ // ── Step 7: Configure color ──────────────────────────────────────────
283
+
284
+ if (color) {
285
+ fs.writeFileSync(COLOR_CONFIG, color, 'utf8');
286
+ ok(`Topic color set to: ${BOLD}${color}${RESET}`);
287
+ }
288
+
289
+ // ── Step 8: Summary ──────────────────────────────────────────────────
290
+
291
+ console.log('');
292
+ heading('Installation complete');
293
+ console.log(` ${DIM}Statusline:${RESET} ~/.claude/session-topics/statusline.sh`);
294
+ console.log(` ${DIM}Skills:${RESET} ~/.claude/skills/auto-topic/`);
295
+ console.log(` ~/.claude/skills/set-topic/`);
296
+ console.log(` ${DIM}Settings:${RESET} ~/.claude/settings.json`);
297
+ if (color) {
298
+ console.log(` ${DIM}Color:${RESET} ${color}`);
299
+ }
300
+ console.log('');
301
+ console.log(` Topics are set automatically. Use ${CYAN}/set-topic <text>${RESET} to override.`);
302
+ console.log('');
303
+ }
304
+
305
+ function determineStatusLineCase(settings) {
306
+ // Case B or C: statusLine exists
307
+ if (settings.statusLine && typeof settings.statusLine === 'object') {
308
+ const cmd = settings.statusLine.command;
309
+ if (typeof cmd === 'string' && cmd.length > 0) {
310
+ // Case B: already ours
311
+ if (cmd.includes('session-topics')) {
312
+ return 'B';
313
+ }
314
+ // Case C: another command
315
+ return 'C';
316
+ }
317
+ // statusLine exists but no valid command
318
+ return 'D';
319
+ }
320
+ // No statusLine at all
321
+ return 'A';
322
+ }
323
+
324
+ // ─── Uninstall ───────────────────────────────────────────────────────────────
325
+
326
+ function uninstall() {
327
+ heading('Uninstalling claude-session-topics');
328
+
329
+ const settings = readSettings();
330
+
331
+ // ── Step 1: Restore statusline ───────────────────────────────────────
332
+
333
+ if (fs.existsSync(ORIG_CMD_FILE)) {
334
+ // Had a previous command — restore it
335
+ const origCmd = fs.readFileSync(ORIG_CMD_FILE, 'utf8').trim();
336
+ if (origCmd && settings.statusLine) {
337
+ settings.statusLine.command = origCmd;
338
+ writeSettings(settings);
339
+ ok(`Restored original statusLine command`);
340
+ info(` ${origCmd}`);
341
+ }
342
+ } else {
343
+ // No backup — remove statusLine entirely if it's ours
344
+ if (
345
+ settings.statusLine &&
346
+ typeof settings.statusLine.command === 'string' &&
347
+ settings.statusLine.command.includes('session-topics')
348
+ ) {
349
+ delete settings.statusLine;
350
+ writeSettings(settings);
351
+ ok('Removed statusLine from settings.json');
352
+ } else if (settings.statusLine) {
353
+ info('statusLine does not reference session-topics — left untouched');
354
+ } else {
355
+ info('No statusLine to remove');
356
+ }
357
+ }
358
+
359
+ // ── Step 2: Delete scripts ───────────────────────────────────────────
360
+
361
+ const filesToDelete = [DEST_STATUSLINE, DEST_WRAPPER, ORIG_CMD_FILE];
362
+ for (const file of filesToDelete) {
363
+ if (fs.existsSync(file)) {
364
+ fs.unlinkSync(file);
365
+ ok(`Deleted ${path.basename(file)}`);
366
+ }
367
+ }
368
+
369
+ // ── Step 3: Remove permission ────────────────────────────────────────
370
+
371
+ if (
372
+ settings.permissions &&
373
+ typeof settings.permissions === 'object' &&
374
+ Array.isArray(settings.permissions.allow)
375
+ ) {
376
+ const before = settings.permissions.allow.length;
377
+ settings.permissions.allow = settings.permissions.allow.filter(
378
+ (rule) => rule !== PERMISSION_RULE
379
+ );
380
+ if (settings.permissions.allow.length < before) {
381
+ writeSettings(settings);
382
+ ok(`Removed permission: ${PERMISSION_RULE}`);
383
+ }
384
+ }
385
+
386
+ // ── Step 4: Delete skills ────────────────────────────────────────────
387
+
388
+ const skillsToDelete = ['auto-topic', 'set-topic'];
389
+ for (const skill of skillsToDelete) {
390
+ const skillDir = path.join(SKILLS_DIR, skill);
391
+ if (fs.existsSync(skillDir)) {
392
+ fs.rmSync(skillDir, { recursive: true, force: true });
393
+ ok(`Removed skill: ${skill}`);
394
+ }
395
+ }
396
+
397
+ // ── Step 5: Preserve data ────────────────────────────────────────────
398
+
399
+ info('Preserved topic data in ~/.claude/session-topics/ (topic files + color config)');
400
+
401
+ // ── Summary ──────────────────────────────────────────────────────────
402
+
403
+ console.log('');
404
+ heading('Uninstall complete');
405
+ console.log(` Scripts and skills removed. Topic data preserved.`);
406
+ console.log(` To fully remove all data: ${DIM}rm -rf ~/.claude/session-topics/${RESET}`);
407
+ console.log('');
408
+ }
409
+
410
+ // ─── Main ────────────────────────────────────────────────────────────────────
411
+
412
+ function main() {
413
+ const { action, color } = parseArgs(process.argv);
414
+
415
+ switch (action) {
416
+ case 'help':
417
+ showHelp();
418
+ break;
419
+ case 'install':
420
+ install(color);
421
+ break;
422
+ case 'uninstall':
423
+ uninstall();
424
+ break;
425
+ default:
426
+ showHelp();
427
+ break;
428
+ }
429
+ }
430
+
431
+ main();
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@alexismunozdev/claude-session-topics",
3
+ "version": "2.0.0",
4
+ "description": "Session topics for Claude Code — auto-set and display a topic in the statusline, change anytime with /set-topic",
5
+ "bin": {
6
+ "claude-session-topics": "bin/install.js"
7
+ },
8
+ "files": [
9
+ "bin/",
10
+ "scripts/",
11
+ "skills/",
12
+ "LICENSE",
13
+ "README.md"
14
+ ],
15
+ "keywords": [
16
+ "claude",
17
+ "claude-code",
18
+ "statusline",
19
+ "topic",
20
+ "session",
21
+ "productivity"
22
+ ],
23
+ "author": "Alexis Muñoz",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/alexismunoz1/claude-session-topics.git"
28
+ },
29
+ "homepage": "https://github.com/alexismunoz1/claude-session-topics"
30
+ }
@@ -0,0 +1,85 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+
4
+ input=$(cat)
5
+
6
+ # ── Parse JSON
7
+ SESSION_ID=$(echo "$input" | jq -r '.session_id // ""')
8
+
9
+ # ── PID→Session bridge
10
+ if [ -n "$SESSION_ID" ]; then
11
+ echo "$SESSION_ID" > "/tmp/claude-pid-${PPID}"
12
+ fi
13
+
14
+ # ── Pick up pending topic from /set-topic
15
+ PENDING="/tmp/claude-pending-topic-${PPID}"
16
+ if [ -n "$SESSION_ID" ] && [ -f "$PENDING" ]; then
17
+ PENDING_TOPIC=$(cat "$PENDING" 2>/dev/null || echo "")
18
+ if [ -n "$PENDING_TOPIC" ]; then
19
+ mkdir -p "$HOME/.claude/session-topics"
20
+ echo "$PENDING_TOPIC" > "$HOME/.claude/session-topics/${SESSION_ID}"
21
+ rm -f "$PENDING"
22
+ fi
23
+ fi
24
+
25
+ # ── Topic
26
+ TOPIC=""
27
+ if [ -n "$SESSION_ID" ]; then
28
+ TOPIC_FILE="$HOME/.claude/session-topics/${SESSION_ID}"
29
+ if [ -f "$TOPIC_FILE" ]; then
30
+ TOPIC=$(cat "$TOPIC_FILE" 2>/dev/null || echo "")
31
+ fi
32
+ fi
33
+ if [ -z "$TOPIC" ] && [ -n "${CLAUDE_SESSION_TOPICS_TOPIC:-}" ]; then
34
+ TOPIC="$CLAUDE_SESSION_TOPICS_TOPIC"
35
+ if [ -n "$SESSION_ID" ]; then
36
+ mkdir -p "$HOME/.claude/session-topics"
37
+ echo "$TOPIC" > "$HOME/.claude/session-topics/${SESSION_ID}"
38
+ fi
39
+ fi
40
+
41
+ # ── Resolve topic color
42
+ resolve_color() {
43
+ case "$1" in
44
+ red) echo '\033[31m' ;;
45
+ green) echo '\033[32m' ;;
46
+ yellow) echo '\033[33m' ;;
47
+ blue) echo '\033[34m' ;;
48
+ magenta) echo '\033[35m' ;;
49
+ cyan) echo '\033[36m' ;;
50
+ white) echo '\033[37m' ;;
51
+ orange) echo '\033[38;5;208m' ;;
52
+ grey|gray) echo '\033[90m' ;;
53
+ "") echo '\033[35m' ;; # default: magenta
54
+ *)
55
+ if echo "$1" | grep -qE '^[0-9;]+$'; then
56
+ echo "\033[${1}m"
57
+ else
58
+ echo '\033[35m'
59
+ fi
60
+ ;;
61
+ esac
62
+ }
63
+ # Color priority: env var > config file > default (magenta)
64
+ _color="${CLAUDE_SESSION_TOPICS_COLOR:-}"
65
+ if [ -z "$_color" ] && [ -f "$HOME/.claude/session-topics/.color-config" ]; then
66
+ _color=$(cat "$HOME/.claude/session-topics/.color-config" 2>/dev/null || echo "")
67
+ fi
68
+ C_TOPIC=$(resolve_color "$_color")
69
+ C_BOLD='\033[1m'
70
+ C_RESET='\033[0m'
71
+
72
+ # ── Cleanup stale files (atomic lock)
73
+ CLEANUP_LOCK="/tmp/.claude-topic-cleanup-lock"
74
+ if mkdir "$CLEANUP_LOCK" 2>/dev/null; then
75
+ trap "rmdir '$CLEANUP_LOCK' 2>/dev/null || true" EXIT
76
+ find "$HOME/.claude/session-topics" -type f -mtime +7 -delete 2>/dev/null || true
77
+ find /tmp -maxdepth 1 -name "claude-pid-*" -mtime +1 -delete 2>/dev/null || true
78
+ find /tmp -maxdepth 1 -name "claude-pending-topic-*" -mtime +1 -delete 2>/dev/null || true
79
+ rmdir "$CLEANUP_LOCK" 2>/dev/null
80
+ fi
81
+
82
+ # ── Output
83
+ if [ -n "$TOPIC" ]; then
84
+ echo -e "${C_BOLD}${C_TOPIC}◆ ${TOPIC}${C_RESET}"
85
+ fi
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: auto-topic
3
+ description: Use on EVERY user message to automatically set or update a short session topic (2-4 words) in the statusline. Activates continuously throughout the conversation to keep the topic current as the discussion evolves.
4
+ version: 1.2.0
5
+ allowed-tools: [Bash]
6
+ ---
7
+
8
+ # Auto Topic
9
+
10
+ Automatically set and update a short, descriptive topic for the current Claude Code session as the conversation evolves.
11
+
12
+ ## When to Activate
13
+
14
+ This skill activates on **every user message**. Run it silently — do not mention to the user that you are setting or updating the topic.
15
+
16
+ ## Rules
17
+
18
+ 1. After reading each user message, infer a topic of **2-4 words** (max 20 characters) that reflects what the user is currently working on
19
+ 2. The topic should summarize the current focus of the conversation
20
+ 3. Use the project's domain language (e.g., Spanish is fine if the user writes in Spanish)
21
+ 4. Examples: "Auth Refactor", "FAQ Bug", "Search Filters", "DB Migration", "API Tests"
22
+ 5. Run the topic check and update **silently** — do not tell the user you set or changed it
23
+ 6. If `/set-topic` is used later by the user, that takes priority
24
+
25
+ ## How to Check and Update the Topic
26
+
27
+ ### Step 1: Read the current topic
28
+
29
+ ```bash
30
+ SESSION_ID=$(cat /tmp/claude-pid-$PPID 2>/dev/null)
31
+ if [ -n "$SESSION_ID" ]; then
32
+ TOPIC_FILE="$HOME/.claude/session-topics/${SESSION_ID}"
33
+ CURRENT_TOPIC=$(cat "$TOPIC_FILE" 2>/dev/null || echo "")
34
+ echo "Current topic: '$CURRENT_TOPIC'"
35
+ fi
36
+ ```
37
+
38
+ ### Step 2: Decide whether to update
39
+
40
+ Compare the inferred new topic with the current topic. **Only write a new topic if the conversation has clearly shifted to a different subject.** Do NOT update for:
41
+ - Minor variations of the same topic (e.g., "Auth Refactor" → "Refactor Auth")
42
+ - Subtasks within the same broader topic (e.g., "Auth Refactor" → "Auth Tests" if still working on auth)
43
+ - Rewording without a real subject change
44
+
45
+ DO update when:
46
+ - The user has moved to a genuinely different area of work (e.g., "Auth Refactor" → "DB Migration")
47
+ - The original topic no longer describes what the conversation is about
48
+ - There is no current topic yet (first message)
49
+
50
+ ### Step 3: Write the new topic (only if changed)
51
+
52
+ If the topic has meaningfully changed, run:
53
+
54
+ ```bash
55
+ SESSION_ID=$(cat /tmp/claude-pid-$PPID 2>/dev/null)
56
+ if [ -n "$SESSION_ID" ]; then
57
+ mkdir -p "$HOME/.claude/session-topics"
58
+ echo "Your New Topic" > "$HOME/.claude/session-topics/${SESSION_ID}"
59
+ fi
60
+ ```
61
+
62
+ Replace `Your New Topic` with the inferred 2-4 word topic.
63
+
64
+ If the topic has NOT meaningfully changed, **do nothing** — skip the write entirely.
65
+
66
+ ## Important
67
+
68
+ - Run this on **every** user message, but only write when the topic has genuinely changed
69
+ - Do NOT mention the topic to the user — ever
70
+ - Keep topics short and descriptive (2-4 words, max 20 characters)
71
+ - If the statusline hasn't run yet (no PID file), skip silently
72
+ - A high bar for "meaningfully changed" prevents unnecessary churn — when in doubt, keep the current topic
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: set-topic
3
+ description: Set or change the session topic displayed in the statusline
4
+ argument-hint: <topic text>
5
+ allowed-tools: [Bash]
6
+ version: 1.1.0
7
+ ---
8
+
9
+ # Set Topic
10
+
11
+ Set or change the topic displayed in the Claude Code statusline.
12
+
13
+ ## Usage
14
+
15
+ `/set-topic <topic text>`
16
+
17
+ ## Instructions
18
+
19
+ 1. The topic text is: $ARGUMENTS
20
+ 2. If the topic text is empty, inform the user they need to provide a topic (e.g., `/set-topic Auth Refactor`)
21
+ 3. Run this bash command to discover the session ID and write the topic file:
22
+
23
+ ```bash
24
+ SESSION_ID=$(cat /tmp/claude-pid-$PPID 2>/dev/null)
25
+ if [ -n "$SESSION_ID" ]; then
26
+ mkdir -p "$HOME/.claude/session-topics"
27
+ echo "$ARGUMENTS" > "$HOME/.claude/session-topics/${SESSION_ID}"
28
+ echo "Topic set to: $ARGUMENTS"
29
+ else
30
+ echo "$ARGUMENTS" > "/tmp/claude-pending-topic-$PPID"
31
+ echo "Topic queued: $ARGUMENTS (will appear on next statusline refresh)"
32
+ fi
33
+ ```
34
+
35
+ 4. Confirm to the user that the topic has been set and will appear in the statusline.