claude-code-achievements 1.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/.claude-plugin/plugin.json +7 -0
- package/README.md +201 -0
- package/bin/config.js +285 -0
- package/bin/install.js +221 -0
- package/commands/achievements-settings.md +63 -0
- package/commands/achievements.md +93 -0
- package/data/achievements.json +168 -0
- package/data/i18n/en.json +94 -0
- package/data/i18n/ko.json +94 -0
- package/hooks/hooks.json +28 -0
- package/hooks/track-achievement.sh +178 -0
- package/hooks/track-stop.sh +44 -0
- package/package.json +38 -0
- package/scripts/init-state.sh +44 -0
- package/scripts/show-achievements.sh +152 -0
- package/scripts/show-notification.sh +145 -0
package/README.md
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# ๐ฎ Claude Code Achievements
|
|
2
|
+
|
|
3
|
+
Steam-style achievement system for [Claude Code](https://claude.ai/claude-code). Gamify your coding journey and unlock achievements as you master Claude Code features!
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx claude-code-achievements
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The interactive installer will:
|
|
12
|
+
1. **Auto-detect** your OS (macOS/Linux/Windows)
|
|
13
|
+
2. **Auto-detect** system notification capability
|
|
14
|
+
3. Ask for language preference (English/ํ๊ตญ์ด)
|
|
15
|
+
4. Configure notification style (system/terminal/both)
|
|
16
|
+
5. Install to `~/.claude/plugins/local/claude-code-achievements`
|
|
17
|
+
|
|
18
|
+
### Manual Installation
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
git clone https://github.com/subinium/claude-code-achievements.git
|
|
22
|
+
cd claude-code-achievements
|
|
23
|
+
node bin/install.js
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
|
|
28
|
+
| Command | Description |
|
|
29
|
+
|---------|-------------|
|
|
30
|
+
| `/achievements` | View all achievements with progress |
|
|
31
|
+
| `/achievements hint` | Get tips for unlocking achievements |
|
|
32
|
+
| `/achievements basics` | View "Getting Started" category |
|
|
33
|
+
| `/achievements workflow` | View "Workflow" category |
|
|
34
|
+
| `/achievements tools` | View "Power Tools" category |
|
|
35
|
+
| `/achievements mastery` | View "Mastery" category |
|
|
36
|
+
| `/achievements-settings` | Change language or notification settings |
|
|
37
|
+
|
|
38
|
+
## Achievements (15 total)
|
|
39
|
+
|
|
40
|
+
### Getting Started (โฌ Common / ๐ฉ Uncommon)
|
|
41
|
+
|
|
42
|
+
| # | Achievement | Description | How to Unlock |
|
|
43
|
+
|---|-------------|-------------|---------------|
|
|
44
|
+
| 1 | โ๏ธ **First Touch** โฌ | Edit a file | Use `Edit` tool |
|
|
45
|
+
| 2 | ๐ **Creator** โฌ | Create a new file | Use `Write` tool |
|
|
46
|
+
| 3 | ๐ **Project Curator** ๐ฉ | Create CLAUDE.md | Write to `CLAUDE.md` |
|
|
47
|
+
|
|
48
|
+
### Workflow (๐ฉ Uncommon)
|
|
49
|
+
|
|
50
|
+
| # | Achievement | Description | How to Unlock |
|
|
51
|
+
|---|-------------|-------------|---------------|
|
|
52
|
+
| 4 | ๐ฏ **Strategic Thinker** | Use Plan mode | Press `Shift+Tab` twice |
|
|
53
|
+
| 5 | ๐ฆ **Version Controller** | Commit with Claude | Run `git add` or `git commit` |
|
|
54
|
+
| 6 | ๐ **Ship It!** | Push to remote | Run `git push` |
|
|
55
|
+
| 7 | ๐งช **Quality Guardian** | Run tests | Run `npm test`, `pytest`, etc. |
|
|
56
|
+
|
|
57
|
+
### Power Tools (๐ฆ Rare)
|
|
58
|
+
|
|
59
|
+
| # | Achievement | Description | How to Unlock |
|
|
60
|
+
|---|-------------|-------------|---------------|
|
|
61
|
+
| 8 | ๐ค **Delegation Master** | Use sub-agents | Use `Task` tool |
|
|
62
|
+
| 9 | ๐ **MCP Pioneer** | Use MCP tool | Use any `mcp__*` tool |
|
|
63
|
+
| 10 | ๐ **Web Explorer** | Search the web | Use `WebSearch` tool |
|
|
64
|
+
| 11 | โก **Skill Master** | Use slash command | Use `Skill` tool (e.g., `/commit`) |
|
|
65
|
+
| 12 | ๐ **Data Scientist** | Edit notebook | Use `NotebookEdit` tool |
|
|
66
|
+
| 13 | โ๏ธ **Customizer** | Modify settings | Write to `.claude/settings*.json` |
|
|
67
|
+
|
|
68
|
+
### Mastery (๐ช Epic / ๐จ Legendary)
|
|
69
|
+
|
|
70
|
+
| # | Achievement | Description | How to Unlock |
|
|
71
|
+
|---|-------------|-------------|---------------|
|
|
72
|
+
| 14 | ๐ช **Automation Architect** ๐ช | Set up hooks | Write file with `"hooks"` config |
|
|
73
|
+
| 15 | ๐ **Loop Master** ๐จ | Start Ralph Loop | Use `/ralph-loop` skill |
|
|
74
|
+
|
|
75
|
+
## Rarity System
|
|
76
|
+
|
|
77
|
+
| Rarity | Color | Count | Difficulty |
|
|
78
|
+
|--------|-------|-------|------------|
|
|
79
|
+
| Common | โฌ | 2 | First session |
|
|
80
|
+
| Uncommon | ๐ฉ | 5 | Regular usage |
|
|
81
|
+
| Rare | ๐ฆ | 6 | Power user features |
|
|
82
|
+
| Epic | ๐ช | 1 | Advanced automation |
|
|
83
|
+
| Legendary | ๐จ | 1 | Expert level |
|
|
84
|
+
|
|
85
|
+
## Notifications
|
|
86
|
+
|
|
87
|
+
System notifications are **auto-detected** during installation:
|
|
88
|
+
|
|
89
|
+
| OS | Method | Requirement | Auto-detected |
|
|
90
|
+
|----|--------|-------------|---------------|
|
|
91
|
+
| macOS | `osascript` | Built-in | โ
Always |
|
|
92
|
+
| Linux | `notify-send` | `libnotify-bin` | โ
Checked |
|
|
93
|
+
| Windows | PowerShell | Windows 10+ | โ
Checked |
|
|
94
|
+
| Fallback | Terminal | None | โ
Always |
|
|
95
|
+
|
|
96
|
+
**Note:** If system notifications aren't available, terminal notifications are used automatically.
|
|
97
|
+
|
|
98
|
+
### Install `notify-send` on Linux
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
# Ubuntu/Debian
|
|
102
|
+
sudo apt install libnotify-bin
|
|
103
|
+
|
|
104
|
+
# Fedora
|
|
105
|
+
sudo dnf install libnotify
|
|
106
|
+
|
|
107
|
+
# Arch
|
|
108
|
+
sudo pacman -S libnotify
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## How It Works
|
|
112
|
+
|
|
113
|
+
```
|
|
114
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
115
|
+
โ You use a tool (Edit, Write, Bash, Task, etc.) โ
|
|
116
|
+
โ โ โ
|
|
117
|
+
โ โผ โ
|
|
118
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
119
|
+
โ โ PostToolUse Hook โ track-achievement.sh โ โ
|
|
120
|
+
โ โ Checks tool_name, tool_input, permission_mode โ โ
|
|
121
|
+
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
|
|
122
|
+
โ โ โ
|
|
123
|
+
โ โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ โ
|
|
124
|
+
โ โผ โผ โ
|
|
125
|
+
โ โโโโโโโโโโโโโโโโโ โโโโโโโโโโโโโโโโโโโ โ
|
|
126
|
+
โ โ Match found! โ โ No match โ โ
|
|
127
|
+
โ โ โ Unlock โ โ โ Continue โ โ
|
|
128
|
+
โ โ โ Notify โ โโโโโโโโโโโโโโโโโโโ โ
|
|
129
|
+
โ โ โ Save state โ โ
|
|
130
|
+
โ โโโโโโโโโโโโโโโโโ โ
|
|
131
|
+
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Files
|
|
135
|
+
|
|
136
|
+
| Path | Description |
|
|
137
|
+
|------|-------------|
|
|
138
|
+
| `~/.claude/achievements/state.json` | Your progress & settings |
|
|
139
|
+
| `~/.claude/plugins/local/claude-code-achievements/` | Plugin files |
|
|
140
|
+
| `~/.claude/commands/achievements.md` | Slash command |
|
|
141
|
+
|
|
142
|
+
## Settings
|
|
143
|
+
|
|
144
|
+
Edit `~/.claude/achievements/state.json`:
|
|
145
|
+
|
|
146
|
+
```json
|
|
147
|
+
{
|
|
148
|
+
"settings": {
|
|
149
|
+
"language": "en", // "en" | "ko"
|
|
150
|
+
"notifications": true, // true | false
|
|
151
|
+
"notification_style": "system" // "system" | "terminal" | "both"
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Troubleshooting
|
|
157
|
+
|
|
158
|
+
### Achievements not unlocking?
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# 1. Check hooks are registered
|
|
162
|
+
cat ~/.claude/settings.json | grep -A5 "hooks"
|
|
163
|
+
|
|
164
|
+
# 2. Verify plugin installed
|
|
165
|
+
ls ~/.claude/plugins/local/claude-code-achievements/
|
|
166
|
+
|
|
167
|
+
# 3. Check state file
|
|
168
|
+
cat ~/.claude/achievements/state.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Reset progress
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
rm ~/.claude/achievements/state.json
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Reinstall
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
npx claude-code-achievements
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Languages
|
|
184
|
+
|
|
185
|
+
- ๐บ๐ธ English
|
|
186
|
+
- ๐ฐ๐ท ํ๊ตญ์ด
|
|
187
|
+
|
|
188
|
+
## Contributing
|
|
189
|
+
|
|
190
|
+
PRs welcome! Ideas:
|
|
191
|
+
- New achievements
|
|
192
|
+
- New languages
|
|
193
|
+
- Bug fixes
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
198
|
+
|
|
199
|
+
---
|
|
200
|
+
|
|
201
|
+
Happy coding! ๐ฎ
|
package/bin/config.js
ADDED
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
const STATE_FILE = path.join(os.homedir(), '.claude', 'achievements', 'state.json');
|
|
8
|
+
|
|
9
|
+
// ANSI escape codes
|
|
10
|
+
const ESC = '\x1b';
|
|
11
|
+
const CLEAR_SCREEN = `${ESC}[2J${ESC}[H`;
|
|
12
|
+
const HIDE_CURSOR = `${ESC}[?25l`;
|
|
13
|
+
const SHOW_CURSOR = `${ESC}[?25h`;
|
|
14
|
+
const BOLD = `${ESC}[1m`;
|
|
15
|
+
const DIM = `${ESC}[2m`;
|
|
16
|
+
const RESET = `${ESC}[0m`;
|
|
17
|
+
const CYAN = `${ESC}[36m`;
|
|
18
|
+
const GREEN = `${ESC}[32m`;
|
|
19
|
+
const YELLOW = `${ESC}[33m`;
|
|
20
|
+
const WHITE = `${ESC}[37m`;
|
|
21
|
+
const BG_GRAY = `${ESC}[48;5;236m`;
|
|
22
|
+
|
|
23
|
+
// Box drawing
|
|
24
|
+
const BOX = {
|
|
25
|
+
topLeft: 'โญ',
|
|
26
|
+
topRight: 'โฎ',
|
|
27
|
+
bottomLeft: 'โฐ',
|
|
28
|
+
bottomRight: 'โฏ',
|
|
29
|
+
horizontal: 'โ',
|
|
30
|
+
vertical: 'โ'
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Load state
|
|
34
|
+
function loadState() {
|
|
35
|
+
try {
|
|
36
|
+
return JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
|
|
37
|
+
} catch {
|
|
38
|
+
return {
|
|
39
|
+
settings: { language: 'en', notifications: true, notification_style: 'terminal' },
|
|
40
|
+
achievements: {},
|
|
41
|
+
counters: { ralph_iterations: 0, files_read: 0, edits_made: 0 },
|
|
42
|
+
session: { files_read_set: [], ralph_loop_active: false }
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Save state
|
|
48
|
+
function saveState(state) {
|
|
49
|
+
const dir = path.dirname(STATE_FILE);
|
|
50
|
+
if (!fs.existsSync(dir)) {
|
|
51
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
52
|
+
}
|
|
53
|
+
fs.writeFileSync(STATE_FILE, JSON.stringify(state, null, 2));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Get terminal width
|
|
57
|
+
function getTermWidth() {
|
|
58
|
+
return process.stdout.columns || 80;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Settings definition
|
|
62
|
+
function getSettings(state) {
|
|
63
|
+
return [
|
|
64
|
+
{
|
|
65
|
+
id: 'language',
|
|
66
|
+
label: 'Language',
|
|
67
|
+
value: state.settings.language === 'ko' ? 'ํ๊ตญ์ด' : 'English',
|
|
68
|
+
options: [
|
|
69
|
+
{ id: 'en', label: 'English' },
|
|
70
|
+
{ id: 'ko', label: 'ํ๊ตญ์ด' }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'notifications',
|
|
75
|
+
label: 'Notifications',
|
|
76
|
+
value: state.settings.notifications ? 'On' : 'Off',
|
|
77
|
+
options: [
|
|
78
|
+
{ id: true, label: 'On' },
|
|
79
|
+
{ id: false, label: 'Off' }
|
|
80
|
+
]
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: 'notification_style',
|
|
84
|
+
label: 'Notification style',
|
|
85
|
+
value: state.settings.notification_style || 'terminal',
|
|
86
|
+
options: [
|
|
87
|
+
{ id: 'terminal', label: 'terminal' },
|
|
88
|
+
{ id: 'system', label: 'system' },
|
|
89
|
+
{ id: 'both', label: 'both' }
|
|
90
|
+
]
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'reset',
|
|
94
|
+
label: 'Reset all progress',
|
|
95
|
+
value: '',
|
|
96
|
+
options: [
|
|
97
|
+
{ id: 'yes', label: 'Yes, reset' },
|
|
98
|
+
{ id: 'no', label: 'Cancel' }
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Config UI
|
|
105
|
+
class ConfigUI {
|
|
106
|
+
constructor() {
|
|
107
|
+
this.state = loadState();
|
|
108
|
+
this.settings = getSettings(this.state);
|
|
109
|
+
this.selected = 0;
|
|
110
|
+
this.editMode = false;
|
|
111
|
+
this.editIndex = 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
render() {
|
|
115
|
+
const width = Math.min(getTermWidth() - 4, 100);
|
|
116
|
+
let output = CLEAR_SCREEN;
|
|
117
|
+
|
|
118
|
+
// Title
|
|
119
|
+
output += `\n ${BOLD}Configure Vibecoding Achievements${RESET}\n\n`;
|
|
120
|
+
|
|
121
|
+
// Search box (decorative)
|
|
122
|
+
const searchWidth = width - 2;
|
|
123
|
+
output += ` ${BOX.topLeft}${BOX.horizontal.repeat(searchWidth)}${BOX.topRight}\n`;
|
|
124
|
+
output += ` ${BOX.vertical} ${DIM}โ Search settings...${' '.repeat(searchWidth - 21)}${RESET}${BOX.vertical}\n`;
|
|
125
|
+
output += ` ${BOX.bottomLeft}${BOX.horizontal.repeat(searchWidth)}${BOX.bottomRight}\n\n`;
|
|
126
|
+
|
|
127
|
+
// Settings list
|
|
128
|
+
this.settings.forEach((setting, index) => {
|
|
129
|
+
const isSelected = index === this.selected;
|
|
130
|
+
const prefix = isSelected ? `${GREEN}โฏ${RESET}` : ' ';
|
|
131
|
+
|
|
132
|
+
const labelWidth = 35;
|
|
133
|
+
const label = setting.label.padEnd(labelWidth);
|
|
134
|
+
|
|
135
|
+
let valueDisplay = setting.value;
|
|
136
|
+
if (this.editMode && index === this.selected) {
|
|
137
|
+
// Show options in edit mode
|
|
138
|
+
const opts = setting.options;
|
|
139
|
+
valueDisplay = opts.map((opt, i) => {
|
|
140
|
+
if (i === this.editIndex) {
|
|
141
|
+
return `${GREEN}${BOLD}${opt.label}${RESET}`;
|
|
142
|
+
}
|
|
143
|
+
return `${DIM}${opt.label}${RESET}`;
|
|
144
|
+
}).join(' ');
|
|
145
|
+
} else {
|
|
146
|
+
valueDisplay = `${DIM}${setting.value}${RESET}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (isSelected && !this.editMode) {
|
|
150
|
+
output += ` ${prefix} ${BOLD}${label}${RESET}${valueDisplay}\n`;
|
|
151
|
+
} else {
|
|
152
|
+
output += ` ${prefix} ${label}${valueDisplay}\n`;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// Help
|
|
157
|
+
output += `\n ${DIM}โ/โ: Navigate Enter: Edit โ/โ: Change value q: Quit${RESET}\n`;
|
|
158
|
+
|
|
159
|
+
process.stdout.write(output);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async run() {
|
|
163
|
+
if (!process.stdin.isTTY) {
|
|
164
|
+
console.log('This command requires an interactive terminal.');
|
|
165
|
+
console.log('Current settings:');
|
|
166
|
+
console.log(JSON.stringify(this.state.settings, null, 2));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
process.stdin.setRawMode(true);
|
|
171
|
+
process.stdin.resume();
|
|
172
|
+
process.stdin.setEncoding('utf8');
|
|
173
|
+
process.stdout.write(HIDE_CURSOR);
|
|
174
|
+
|
|
175
|
+
this.render();
|
|
176
|
+
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
const cleanup = () => {
|
|
179
|
+
process.stdin.setRawMode(false);
|
|
180
|
+
process.stdin.pause();
|
|
181
|
+
process.stdout.write(SHOW_CURSOR);
|
|
182
|
+
process.stdout.write(CLEAR_SCREEN);
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
process.stdin.on('data', (key) => {
|
|
186
|
+
// Ctrl+C or q
|
|
187
|
+
if (key === '\u0003' || (key === 'q' && !this.editMode)) {
|
|
188
|
+
cleanup();
|
|
189
|
+
resolve();
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Escape - exit edit mode
|
|
194
|
+
if (key === '\u001b' && this.editMode) {
|
|
195
|
+
this.editMode = false;
|
|
196
|
+
this.render();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (this.editMode) {
|
|
201
|
+
const setting = this.settings[this.selected];
|
|
202
|
+
const opts = setting.options;
|
|
203
|
+
|
|
204
|
+
// Left arrow
|
|
205
|
+
if (key === '\u001b[D') {
|
|
206
|
+
this.editIndex = Math.max(0, this.editIndex - 1);
|
|
207
|
+
this.render();
|
|
208
|
+
}
|
|
209
|
+
// Right arrow
|
|
210
|
+
else if (key === '\u001b[C') {
|
|
211
|
+
this.editIndex = Math.min(opts.length - 1, this.editIndex + 1);
|
|
212
|
+
this.render();
|
|
213
|
+
}
|
|
214
|
+
// Enter - apply selection
|
|
215
|
+
else if (key === '\r' || key === '\n') {
|
|
216
|
+
this.applyChange(setting.id, opts[this.editIndex].id);
|
|
217
|
+
this.editMode = false;
|
|
218
|
+
this.settings = getSettings(this.state);
|
|
219
|
+
this.render();
|
|
220
|
+
}
|
|
221
|
+
} else {
|
|
222
|
+
// Up arrow
|
|
223
|
+
if (key === '\u001b[A') {
|
|
224
|
+
this.selected = Math.max(0, this.selected - 1);
|
|
225
|
+
this.render();
|
|
226
|
+
}
|
|
227
|
+
// Down arrow
|
|
228
|
+
else if (key === '\u001b[B') {
|
|
229
|
+
this.selected = Math.min(this.settings.length - 1, this.selected + 1);
|
|
230
|
+
this.render();
|
|
231
|
+
}
|
|
232
|
+
// Enter - enter edit mode
|
|
233
|
+
else if (key === '\r' || key === '\n') {
|
|
234
|
+
const setting = this.settings[this.selected];
|
|
235
|
+
this.editMode = true;
|
|
236
|
+
// Set editIndex to current value
|
|
237
|
+
const currentVal = this.state.settings[setting.id];
|
|
238
|
+
this.editIndex = setting.options.findIndex(o => o.id === currentVal);
|
|
239
|
+
if (this.editIndex === -1) this.editIndex = 0;
|
|
240
|
+
this.render();
|
|
241
|
+
}
|
|
242
|
+
// Left/Right - quick toggle
|
|
243
|
+
else if (key === '\u001b[D' || key === '\u001b[C') {
|
|
244
|
+
const setting = this.settings[this.selected];
|
|
245
|
+
const opts = setting.options;
|
|
246
|
+
const currentVal = this.state.settings[setting.id];
|
|
247
|
+
let idx = opts.findIndex(o => o.id === currentVal);
|
|
248
|
+
if (idx === -1) idx = 0;
|
|
249
|
+
|
|
250
|
+
if (key === '\u001b[C') {
|
|
251
|
+
idx = Math.min(opts.length - 1, idx + 1);
|
|
252
|
+
} else {
|
|
253
|
+
idx = Math.max(0, idx - 1);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
this.applyChange(setting.id, opts[idx].id);
|
|
257
|
+
this.settings = getSettings(this.state);
|
|
258
|
+
this.render();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
applyChange(settingId, value) {
|
|
266
|
+
if (settingId === 'reset') {
|
|
267
|
+
if (value === 'yes') {
|
|
268
|
+
this.state.achievements = {};
|
|
269
|
+
this.state.counters = { ralph_iterations: 0, files_read: 0, edits_made: 0 };
|
|
270
|
+
this.state.session = { files_read_set: [], ralph_loop_active: false };
|
|
271
|
+
}
|
|
272
|
+
} else {
|
|
273
|
+
this.state.settings[settingId] = value;
|
|
274
|
+
}
|
|
275
|
+
saveState(this.state);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Run
|
|
280
|
+
const ui = new ConfigUI();
|
|
281
|
+
ui.run().catch(err => {
|
|
282
|
+
process.stdout.write(SHOW_CURSOR);
|
|
283
|
+
console.error(err);
|
|
284
|
+
process.exit(1);
|
|
285
|
+
});
|