unramble 1.0.0 → 1.0.1
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 +36 -10
- package/cli/unramble.mjs +43 -10
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<p align="center"><b>Press Ctrl+G and the rambling prompt you just dictated becomes the one you meant to type — in place, inside Claude Code, on the plan you already have.</b></p>
|
|
9
9
|
|
|
10
10
|
<p align="center">
|
|
11
|
-
<a href="
|
|
11
|
+
<a href="https://yadava5.github.io/unramble/"><b>Install in one click</b></a> ·
|
|
12
12
|
<a href="#what-it-does">What it does</a> ·
|
|
13
13
|
<a href="#plan-or-api-key">Plan or API key</a> ·
|
|
14
14
|
<a href="docs/BENCHMARKS.md">Benchmarks</a> ·
|
|
@@ -35,15 +35,19 @@ lost. Ctrl+G again puts your original back.
|
|
|
35
35
|
|
|
36
36
|
## Install
|
|
37
37
|
|
|
38
|
-
|
|
38
|
+
**One click:** [**Install in Claude Code →**](https://yadava5.github.io/unramble/) opens Claude
|
|
39
|
+
Code in your terminal with the install command typed in; press Enter.
|
|
39
40
|
|
|
40
|
-
**
|
|
41
|
+
**In your terminal** (zsh, bash, PowerShell — not inside Claude Code; needs Node 18+):
|
|
41
42
|
|
|
42
43
|
```sh
|
|
43
44
|
npx unramble
|
|
44
45
|
```
|
|
45
46
|
|
|
46
|
-
|
|
47
|
+
That installs the plugin and binds Ctrl+G in one step.
|
|
48
|
+
|
|
49
|
+
**Or inside Claude Code**, with no Node needed: start `claude`, then type these at its `❯`
|
|
50
|
+
prompt, one at a time. Claude Code has no shorter form for a plugin outside its own directory.
|
|
47
51
|
|
|
48
52
|
```text
|
|
49
53
|
/plugin install unramble --marketplace yadava5/unramble
|
|
@@ -54,7 +58,9 @@ Answer `y` to add the marketplace and pick a scope (user is the default). `setup
|
|
|
54
58
|
it keeps a backup of your `keybindings.json` and never overwrites a key you bound yourself.
|
|
55
59
|
|
|
56
60
|
Then quit Claude Code and start a new session (`claude`), say or type a prompt, and press
|
|
57
|
-
**Ctrl+G**.
|
|
61
|
+
**Ctrl+G**. Works with Claude Code 2.1.308 and every release after it: CI tests the newest
|
|
62
|
+
Claude Code every day on Linux, macOS and Windows. It always uses the newest Sonnet or Opus,
|
|
63
|
+
because it asks for the model by family, never by version. See
|
|
58
64
|
[Where it is tested](#where-it-is-tested).
|
|
59
65
|
|
|
60
66
|
## What it does
|
|
@@ -93,6 +99,9 @@ count that as a minor defect.)
|
|
|
93
99
|
- **Lose a machine value.** A rewrite that drops a path, `--flag`, number, URL, `@ref` or
|
|
94
100
|
`[Pasted text]` chip is refused and your draft stays — unless you corrected that value on
|
|
95
101
|
the spot ("src/utils, no wait, src/lib").
|
|
102
|
+
- **Add a link or a command you never said.** Pasted text can carry instructions; a rewrite
|
|
103
|
+
that brings in a URL, `curl`/`wget`/`sudo`/`rm -…`, shell chaining or invisible characters
|
|
104
|
+
you did not say is refused. On 2,240 recorded rewrites this refused none.
|
|
96
105
|
- **Touch pasted logs or code.** They are masked before the model sees the draft and put back
|
|
97
106
|
byte for byte.
|
|
98
107
|
- **Answer your question or do the task.** "Can you check X?" stays a question for Claude Code.
|
|
@@ -169,13 +178,23 @@ editor as VS Code and Cursor). If one does not work, please
|
|
|
169
178
|
- **A short window can hide the band.** In an 80×24 window with a tall status line, Claude Code
|
|
170
179
|
may not draw the `⌃G refine` row; Ctrl+G still works.
|
|
171
180
|
- **Long pastes stay as chips.** When a dictation app pastes a long block, Claude Code folds it
|
|
172
|
-
into a `[Pasted text #N]` chip that plugins cannot read
|
|
181
|
+
into a `[Pasted text #N]` chip that plugins cannot read (as of 2.1.308), so that text is kept as
|
|
173
182
|
is. Claude Code's built-in voice input, macOS Dictation and typing all work fully.
|
|
174
183
|
- **Garbled speech stays garbled.** If the transcription is wrong in a way context cannot fix
|
|
175
184
|
("Apple ID" for a project name), the rewrite keeps it as heard rather than guess. Add the
|
|
176
185
|
name to *Names you often say*.
|
|
177
|
-
- **The plugin API is early access.** CI runs the
|
|
178
|
-
that moves it.
|
|
186
|
+
- **The plugin API is early access.** CI runs the newest Claude Code every day to catch a
|
|
187
|
+
release that moves it.
|
|
188
|
+
|
|
189
|
+
## Update
|
|
190
|
+
|
|
191
|
+
```sh
|
|
192
|
+
npx unramble update
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Or have Claude Code keep it current: in `/plugin`, open the `unramble` marketplace and turn on
|
|
196
|
+
auto-update. Every change reaches `main` only through a reviewed pull request, so that is
|
|
197
|
+
what auto-update follows.
|
|
179
198
|
|
|
180
199
|
## Uninstall
|
|
181
200
|
|
|
@@ -198,8 +217,15 @@ plan instead of an API key. I used Claude Code to build it.
|
|
|
198
217
|
## Contributing
|
|
199
218
|
|
|
200
219
|
Reports of a rewrite that changed your meaning are the most useful thing you can send; there
|
|
201
|
-
is an [issue template](https://github.com/yadava5/unramble/issues/new/choose) for it.
|
|
202
|
-
[CONTRIBUTING.md](CONTRIBUTING.md).
|
|
220
|
+
is an [issue template](https://github.com/yadava5/unramble/issues/new/choose) for it. Pull
|
|
221
|
+
requests are welcome: see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
222
|
+
|
|
223
|
+
## Security
|
|
224
|
+
|
|
225
|
+
Every change reaches `main` through a reviewed pull request, releases publish only from a
|
|
226
|
+
maintainer-approved tag with npm provenance, and CI fails any change that widens what the
|
|
227
|
+
shipped code can reach. [SECURITY.md](SECURITY.md) lists exactly what the plugin touches and how
|
|
228
|
+
to report a vulnerability privately.
|
|
203
229
|
|
|
204
230
|
## License
|
|
205
231
|
|
package/cli/unramble.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// npx unramble [install | uninstall | status] — sets the plugin up in one paste.
|
|
2
|
+
// npx unramble [install | update | uninstall | status] — sets the plugin up in one paste.
|
|
3
3
|
//
|
|
4
4
|
// install: claude plugin install unramble --marketplace yadava5/unramble, then binds Ctrl+G
|
|
5
5
|
// in keybindings.json (a backup is kept beside it).
|
|
6
|
+
// update: pulls the newest unramble from its marketplace.
|
|
6
7
|
// uninstall: removes the plugin and takes back only the keybindings install added.
|
|
7
8
|
// status: shows whether the plugin is installed and Ctrl+G is bound.
|
|
8
9
|
//
|
|
@@ -11,14 +12,26 @@
|
|
|
11
12
|
import { spawnSync } from 'node:child_process'
|
|
12
13
|
import { copyFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
13
14
|
import { homedir } from 'node:os'
|
|
14
|
-
import { join } from 'node:path'
|
|
15
|
+
import { isAbsolute, join, resolve } from 'node:path'
|
|
15
16
|
|
|
16
17
|
import { isBound, parse, plan, serialize, unplan } from '../hooks/keybindings.js'
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
// UNRAMBLE_MARKETPLACE points the installer at a fork or a local checkout (CI uses it). On
|
|
20
|
+
// Windows the argument passes through cmd.exe, so only plain repo names and paths are taken.
|
|
21
|
+
const override = process.env.UNRAMBLE_MARKETPLACE
|
|
22
|
+
if (override && (!/^[\w@./:\\ ~#-]+$/.test(override) || override.startsWith('-'))) {
|
|
23
|
+
process.stderr.write('unramble: UNRAMBLE_MARKETPLACE must be owner/repo or a folder path\n')
|
|
24
|
+
process.exit(1)
|
|
25
|
+
}
|
|
26
|
+
const MARKETPLACE = override ? (existsSync(override) ? resolve(override) : override) : 'yadava5/unramble'
|
|
19
27
|
const PLUGIN = 'unramble'
|
|
20
28
|
const configDir = process.env.CLAUDE_CONFIG_DIR || join(homedir(), '.claude')
|
|
29
|
+
if (!isAbsolute(configDir)) {
|
|
30
|
+
process.stderr.write(`unramble: CLAUDE_CONFIG_DIR must be an absolute path (got ${configDir})\n`)
|
|
31
|
+
process.exit(1)
|
|
32
|
+
}
|
|
21
33
|
const keybindings = join(configDir, 'keybindings.json')
|
|
34
|
+
const backup = `${keybindings}.unramble-backup`
|
|
22
35
|
|
|
23
36
|
const say = (line = '') => process.stdout.write(`${line}\n`)
|
|
24
37
|
const fail = line => {
|
|
@@ -26,11 +39,18 @@ const fail = line => {
|
|
|
26
39
|
process.exit(1)
|
|
27
40
|
}
|
|
28
41
|
|
|
29
|
-
|
|
42
|
+
const windows = process.platform === 'win32'
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Runs `claude` with the person's own environment and terminal; true on success. It runs from
|
|
46
|
+
* the home folder: on Windows (where `claude` is a .cmd shim and needs the shell) cmd.exe looks
|
|
47
|
+
* in the current folder before PATH, so a `claude.cmd` in a cloned repo must never be the one run.
|
|
48
|
+
*/
|
|
30
49
|
function claude(args, { quiet = false } = {}) {
|
|
31
|
-
const r = spawnSync('claude', args, {
|
|
50
|
+
const r = spawnSync('claude', windows ? args.map(a => (a.includes(' ') ? `"${a}"` : a)) : args, {
|
|
51
|
+
cwd: homedir(),
|
|
32
52
|
stdio: quiet ? 'pipe' : 'inherit',
|
|
33
|
-
shell:
|
|
53
|
+
shell: windows,
|
|
34
54
|
encoding: 'utf8',
|
|
35
55
|
})
|
|
36
56
|
if (r.error?.code === 'ENOENT') {
|
|
@@ -48,6 +68,11 @@ function readKeys() {
|
|
|
48
68
|
}
|
|
49
69
|
}
|
|
50
70
|
|
|
71
|
+
/** The first backup is the person's own file; a later install or uninstall never replaces it. */
|
|
72
|
+
function keepBackup(text) {
|
|
73
|
+
if (text !== null && !existsSync(backup)) copyFileSync(keybindings, backup)
|
|
74
|
+
}
|
|
75
|
+
|
|
51
76
|
function bindKeys() {
|
|
52
77
|
const { text, file } = readKeys()
|
|
53
78
|
const next = plan(file)
|
|
@@ -57,7 +82,7 @@ function bindKeys() {
|
|
|
57
82
|
say(` Free that chord in ${keybindings}, then run /unramble setup in Claude Code.`)
|
|
58
83
|
return
|
|
59
84
|
}
|
|
60
|
-
|
|
85
|
+
keepBackup(text)
|
|
61
86
|
writeFileSync(keybindings, serialize(next.file))
|
|
62
87
|
say(`✓ Bound Ctrl+G in ${keybindings}${text !== null ? ' (backup: keybindings.json.unramble-backup)' : ''}`)
|
|
63
88
|
for (const c of next.changes) say(` ${c}`)
|
|
@@ -80,13 +105,21 @@ function uninstall() {
|
|
|
80
105
|
const { text, file } = readKeys()
|
|
81
106
|
const back = unplan(file)
|
|
82
107
|
if (back.changes.length) {
|
|
83
|
-
|
|
108
|
+
keepBackup(text)
|
|
84
109
|
writeFileSync(keybindings, serialize(back.file))
|
|
85
110
|
say(`✓ Took back the Ctrl+G bindings in ${keybindings}`)
|
|
86
111
|
}
|
|
87
112
|
say(removed ? '✓ unramble is uninstalled' : '! The plugin was not installed (or the uninstall failed); see above.')
|
|
88
113
|
}
|
|
89
114
|
|
|
115
|
+
function update() {
|
|
116
|
+
claude(['plugin', 'marketplace', 'update', PLUGIN], { quiet: true })
|
|
117
|
+
if (!claude(['plugin', 'update', `${PLUGIN}@${PLUGIN}`]).ok) {
|
|
118
|
+
fail('the update did not finish; is unramble installed? Try: npx unramble')
|
|
119
|
+
}
|
|
120
|
+
say('✓ unramble is up to date. Start a new Claude Code session to load it.')
|
|
121
|
+
}
|
|
122
|
+
|
|
90
123
|
function status() {
|
|
91
124
|
const listed = claude(['plugin', 'list'], { quiet: true })
|
|
92
125
|
const installed = new RegExp(`\\b${PLUGIN}@`).test(listed.out)
|
|
@@ -95,9 +128,9 @@ function status() {
|
|
|
95
128
|
}
|
|
96
129
|
|
|
97
130
|
const command = process.argv[2] ?? 'install'
|
|
98
|
-
const commands = { install, uninstall, status }
|
|
131
|
+
const commands = { install, update, uninstall, status }
|
|
99
132
|
if (command === '--help' || command === '-h' || !(command in commands)) {
|
|
100
|
-
say('Usage: npx unramble [install | uninstall | status]')
|
|
133
|
+
say('Usage: npx unramble [install | update | uninstall | status]')
|
|
101
134
|
process.exit(command in commands || command.startsWith('-') ? 0 : 1)
|
|
102
135
|
}
|
|
103
136
|
commands[command]()
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "unramble",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Ctrl+G turns a rambling, dictated prompt into a clean, ordered one, in place, on your own Claude Code plan.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"node": ">=18"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
|
-
"test": "claude plugin validate . && claude plugin test . && node --test cli/*.test.mjs",
|
|
34
|
-
"test:cli": "node --test cli/*.test.mjs"
|
|
33
|
+
"test": "node scripts/guard.mjs && claude plugin validate . && claude plugin test . && node --test cli/*.test.mjs scripts/*.test.mjs",
|
|
34
|
+
"test:cli": "node --test cli/*.test.mjs",
|
|
35
|
+
"guard": "node scripts/guard.mjs"
|
|
35
36
|
}
|
|
36
37
|
}
|