shmakk 1.1.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.
@@ -0,0 +1,62 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+ const { execSync } = require('child_process');
5
+
6
+ function run(cmd) {
7
+ try { return execSync(cmd, { encoding: 'utf8' }).trim(); }
8
+ catch { return ''; }
9
+ }
10
+
11
+ function ensureLine(filePath, line) {
12
+ let content = '';
13
+ try { content = fs.readFileSync(filePath, 'utf8'); } catch {}
14
+ if (content.includes(line)) return false;
15
+ const next = content.endsWith('\n') || content.length === 0 ? content : `${content}\n`;
16
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
17
+ fs.writeFileSync(filePath, `${next}${line}\n`, 'utf8');
18
+ return true;
19
+ }
20
+
21
+ function main() {
22
+ const home = os.homedir();
23
+ const prefix = run('npm config get prefix') || '/usr/local';
24
+ const npmBin = path.join(prefix, 'bin');
25
+ const shell = process.env.SHELL || '';
26
+
27
+ const fishConf = path.join(home, '.config/fish/config.fish');
28
+ const bashrc = path.join(home, '.bashrc');
29
+ const zshrc = path.join(home, '.zshrc');
30
+
31
+ const fishLine = `fish_add_path -g ${npmBin}`;
32
+ const shLine = `export PATH="${npmBin}:$PATH"`;
33
+
34
+ let changed = false;
35
+
36
+ if (shell.includes('fish')) {
37
+ changed = ensureLine(fishConf, fishLine) || changed;
38
+ } else if (shell.includes('zsh')) {
39
+ changed = ensureLine(zshrc, shLine) || changed;
40
+ } else {
41
+ changed = ensureLine(bashrc, shLine) || changed;
42
+ }
43
+
44
+ // Also add fish config as many users launch fish from other login shells.
45
+ changed = ensureLine(fishConf, fishLine) || changed;
46
+
47
+ console.log('Global PATH setup');
48
+ console.log('-----------------');
49
+ console.log('npm prefix :', prefix);
50
+ console.log('npm bin :', npmBin);
51
+ console.log('shell :', shell || '(unknown)');
52
+ if (changed) {
53
+ console.log('\nUpdated shell config. Open a new terminal, then run:');
54
+ console.log(' shmakk --help');
55
+ } else {
56
+ console.log('\nShell config already contains npm global bin path.');
57
+ console.log('Open a new terminal, then run:');
58
+ console.log(' shmakk --help');
59
+ }
60
+ }
61
+
62
+ main();
@@ -0,0 +1,235 @@
1
+ // Static, no-execution command glossary.
2
+ //
3
+ // We never run binaries to extract help. Instead we:
4
+ // 1. List executables on PATH (just names + paths).
5
+ // 2. Parse fish completion files: `complete -c CMD -s X -l LONG -a '...'`
6
+ // 3. Parse bash-completion files for `--long-flag` tokens (best-effort).
7
+ //
8
+ // This is intentionally less rich than running `--help`, but it is safe:
9
+ // no programs are launched, no TTY/X/Wayland/dbus interaction occurs, and
10
+ // nothing on the user's system can be modified by the scan.
11
+
12
+ const fs = require('fs');
13
+ const os = require('os');
14
+ const path = require('path');
15
+
16
+ const HELP_KEEP_BYTES = 4 * 1024;
17
+
18
+ function defaultGlossaryPath() {
19
+ const base = process.env.XDG_DATA_HOME || path.join(os.homedir(), '.local', 'share');
20
+ return path.join(base, 'shmakk', 'command-glossary.json');
21
+ }
22
+
23
+ // ── PATH enumeration ───────────────────────────────────────────────────────
24
+
25
+ function listPathBinaries() {
26
+ const seen = new Map();
27
+ const dirs = (process.env.PATH || '').split(':').filter(Boolean);
28
+ for (const dir of dirs) {
29
+ let entries;
30
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { continue; }
31
+ for (const e of entries) {
32
+ if (e.isDirectory()) continue;
33
+ const full = path.join(dir, e.name);
34
+ try {
35
+ const st = fs.statSync(full);
36
+ if (!(st.mode & 0o111)) continue;
37
+ } catch { continue; }
38
+ if (!seen.has(e.name)) seen.set(e.name, []);
39
+ const list = seen.get(e.name);
40
+ if (!list.includes(full)) list.push(full);
41
+ }
42
+ }
43
+ return seen;
44
+ }
45
+
46
+ // ── fish completions ───────────────────────────────────────────────────────
47
+ //
48
+ // Lines look like:
49
+ // complete -c git -n '__fish_git_using_command status' -l short -d 'short fmt'
50
+ // complete -c git -a 'add commit push' -d 'commands'
51
+ // complete -c npm -s g -l global
52
+
53
+ function fishCompletionDirs() {
54
+ const dirs = [];
55
+ const home = os.homedir();
56
+ if (process.env.XDG_DATA_HOME) {
57
+ dirs.push(path.join(process.env.XDG_DATA_HOME, 'fish', 'vendor_completions.d'));
58
+ }
59
+ dirs.push(
60
+ path.join(home, '.config', 'fish', 'completions'),
61
+ path.join(home, '.local', 'share', 'fish', 'vendor_completions.d'),
62
+ '/usr/share/fish/completions',
63
+ '/usr/share/fish/vendor_completions.d',
64
+ '/usr/local/share/fish/completions',
65
+ '/usr/local/share/fish/vendor_completions.d',
66
+ );
67
+ return dirs.filter((d) => { try { return fs.statSync(d).isDirectory(); } catch { return false; } });
68
+ }
69
+
70
+ const FISH_COMPLETE = /^\s*complete\b(.*)$/;
71
+ // extract value of -c / -s / -l / -a / -d, supporting both quoted and bare
72
+ function parseCompleteArgs(line) {
73
+ const out = { c: null, s: [], l: [], a: [] };
74
+ // Tokenize respecting single/double quotes. Simple split is unsafe.
75
+ const toks = tokenize(line);
76
+ for (let i = 0; i < toks.length; i++) {
77
+ const t = toks[i];
78
+ if (t === '-c' || t === '--command') out.c = toks[++i];
79
+ else if (t === '-s' || t === '--short-option') out.s.push(toks[++i]);
80
+ else if (t === '-l' || t === '--long-option') out.l.push(toks[++i]);
81
+ else if (t === '-o' || t === '--old-option') out.l.push(toks[++i]);
82
+ else if (t === '-a' || t === '--arguments') out.a.push(toks[++i]);
83
+ }
84
+ return out;
85
+ }
86
+
87
+ function tokenize(s) {
88
+ const out = [];
89
+ let i = 0; const n = s.length;
90
+ while (i < n) {
91
+ while (i < n && /\s/.test(s[i])) i++;
92
+ if (i >= n) break;
93
+ const ch = s[i];
94
+ if (ch === '"' || ch === "'") {
95
+ const q = ch; i++;
96
+ let v = '';
97
+ while (i < n && s[i] !== q) { v += s[i++]; }
98
+ i++; // closing
99
+ out.push(v);
100
+ } else {
101
+ let v = '';
102
+ while (i < n && !/\s/.test(s[i])) { v += s[i++]; }
103
+ out.push(v);
104
+ }
105
+ }
106
+ return out;
107
+ }
108
+
109
+ function parseFishCompletions(commands) {
110
+ for (const dir of fishCompletionDirs()) {
111
+ let files;
112
+ try { files = fs.readdirSync(dir); } catch { continue; }
113
+ for (const f of files) {
114
+ if (!f.endsWith('.fish')) continue;
115
+ const cmdName = f.replace(/\.fish$/, '');
116
+ const entry = ensureEntry(commands, cmdName);
117
+ let text;
118
+ try { text = fs.readFileSync(path.join(dir, f), 'utf8'); } catch { continue; }
119
+ for (const rawLine of text.split('\n')) {
120
+ const m = FISH_COMPLETE.exec(rawLine);
121
+ if (!m) continue;
122
+ const args = parseCompleteArgs(m[1]);
123
+ const target = args.c || cmdName;
124
+ const e = ensureEntry(commands, target);
125
+ for (const sh of args.s) if (sh) e.flags.add('-' + sh);
126
+ for (const lo of args.l) if (lo) e.flags.add('--' + lo);
127
+ for (const a of args.a) {
128
+ for (const sub of String(a).split(/\s+/)) {
129
+ if (sub && /^[a-z][a-z0-9_-]{0,30}$/i.test(sub)) e.subcommands.add(sub);
130
+ }
131
+ }
132
+ e.sources.add('fish:' + path.basename(dir));
133
+ if (entry !== e) entry.aliasOf = target;
134
+ }
135
+ }
136
+ }
137
+ }
138
+
139
+ // ── bash completions ───────────────────────────────────────────────────────
140
+ // Best-effort: extract long flags that appear after the command's COMPREPLY
141
+ // generation. We just regex `--[a-z][\w-]*` from the file.
142
+
143
+ function bashCompletionDirs() {
144
+ return [
145
+ '/usr/share/bash-completion/completions',
146
+ '/usr/local/share/bash-completion/completions',
147
+ '/etc/bash_completion.d',
148
+ ].filter((d) => { try { return fs.statSync(d).isDirectory(); } catch { return false; } });
149
+ }
150
+
151
+ const FLAG_RE = /(--[a-zA-Z][\w-]*)/g;
152
+
153
+ function parseBashCompletions(commands) {
154
+ for (const dir of bashCompletionDirs()) {
155
+ let files;
156
+ try { files = fs.readdirSync(dir); } catch { continue; }
157
+ for (const f of files) {
158
+ const cmdName = f; // bash-completion files are typically named after the command
159
+ let text;
160
+ try { text = fs.readFileSync(path.join(dir, f), 'utf8'); } catch { continue; }
161
+ const e = ensureEntry(commands, cmdName);
162
+ let m; let count = 0;
163
+ while ((m = FLAG_RE.exec(text)) !== null) {
164
+ e.flags.add(m[1]);
165
+ if (++count > 200) break;
166
+ }
167
+ if (count) e.sources.add('bash:' + path.basename(dir));
168
+ }
169
+ }
170
+ }
171
+
172
+ // ── shape & write ──────────────────────────────────────────────────────────
173
+
174
+ function ensureEntry(commands, name) {
175
+ if (!commands[name]) {
176
+ commands[name] = {
177
+ paths: [],
178
+ flags: new Set(),
179
+ subcommands: new Set(),
180
+ sources: new Set(),
181
+ };
182
+ }
183
+ return commands[name];
184
+ }
185
+
186
+ function freezeEntry(e) {
187
+ return {
188
+ paths: e.paths,
189
+ flags: Array.from(e.flags).sort().slice(0, 200),
190
+ subcommands: Array.from(e.subcommands).sort().slice(0, 100),
191
+ sources: Array.from(e.sources).sort(),
192
+ };
193
+ }
194
+
195
+ async function buildGlossary({ onProgress } = {}) {
196
+ const bins = listPathBinaries();
197
+ const commands = {};
198
+
199
+ let i = 0; const total = bins.size;
200
+ for (const [name, paths] of bins) {
201
+ const e = ensureEntry(commands, name);
202
+ e.paths = paths;
203
+ if (onProgress && ++i % 200 === 0) onProgress(i, total);
204
+ }
205
+
206
+ parseFishCompletions(commands);
207
+ parseBashCompletions(commands);
208
+
209
+ const out = {};
210
+ for (const [name, e] of Object.entries(commands)) out[name] = freezeEntry(e);
211
+ return { generatedAt: new Date().toISOString(), commands: out };
212
+ }
213
+
214
+ async function updateGlossary() {
215
+ const out = defaultGlossaryPath();
216
+ fs.mkdirSync(path.dirname(out), { recursive: true });
217
+ process.stderr.write('[shmakk] scanning PATH and completion files (no programs are executed)...\n');
218
+ const data = await buildGlossary({
219
+ onProgress: (d, t) => process.stderr.write(`[shmakk] ${d}/${t} binaries\r`),
220
+ });
221
+ fs.writeFileSync(out, JSON.stringify(data, null, 2));
222
+ const n = Object.keys(data.commands).length;
223
+ const withFlags = Object.values(data.commands).filter((e) => e.flags.length).length;
224
+ process.stderr.write(`\n[shmakk] wrote ${n} commands (${withFlags} with completion data) → ${out}\n`);
225
+ return out;
226
+ }
227
+
228
+ function loadGlossary() {
229
+ try {
230
+ const txt = fs.readFileSync(defaultGlossaryPath(), 'utf8');
231
+ return JSON.parse(txt);
232
+ } catch { return null; }
233
+ }
234
+
235
+ module.exports = { updateGlossary, loadGlossary, defaultGlossaryPath, buildGlossary };
@@ -0,0 +1,166 @@
1
+ // History parser — reads bash, zsh, fish history files and builds a
2
+ // frequency map of command usage for tie-breaking in corrections.
3
+ //
4
+ // Frequency map format:
5
+ // { "git": 4521, "ls": 10982, "npm": 893, "cat": 542, ... }
6
+ //
7
+ // Stored at: .shmakk/state/command-freq.json
8
+ //
9
+ // The user controls what goes in — no auto-learning from corrections.
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ const STATE_DIR = path.join(process.cwd(), '.shmakk', 'state');
15
+ const FREQ_FILE = path.join(STATE_DIR, 'command-freq.json');
16
+
17
+ // ── Parsers per shell format ──────────────────────────────────────────────
18
+
19
+ // Bash: one command per line, no timestamps.
20
+ function parseBashHistory(content) {
21
+ const freq = {};
22
+ for (const line of content.split(/\r?\n/)) {
23
+ const trimmed = line.trim();
24
+ if (!trimmed || trimmed.startsWith('#')) continue;
25
+ const cmd = trimmed.split(/\s+/)[0];
26
+ if (cmd) freq[cmd] = (freq[cmd] || 0) + 1;
27
+ }
28
+ return freq;
29
+ }
30
+
31
+ // Zsh: : <timestamp>:<duration>;<command>
32
+ // Example: : 1776037585:0;ll
33
+ function parseZshHistory(content) {
34
+ const freq = {};
35
+ for (const line of content.split(/\r?\n/)) {
36
+ const trimmed = line.trim();
37
+ if (!trimmed) continue;
38
+ // Format: : ts:duration;command
39
+ const semi = trimmed.indexOf(';');
40
+ if (trimmed.startsWith(':') && semi !== -1) {
41
+ const cmd = trimmed.slice(semi + 1).trim().split(/\s+/)[0];
42
+ if (cmd) freq[cmd] = (freq[cmd] || 0) + 1;
43
+ } else {
44
+ // Fallback: treat as bare command
45
+ const cmd = trimmed.split(/\s+/)[0];
46
+ if (cmd) freq[cmd] = (freq[cmd] || 0) + 1;
47
+ }
48
+ }
49
+ return freq;
50
+ }
51
+
52
+ // Fish: YAML-like format
53
+ // - cmd: <command>
54
+ // when: <timestamp>
55
+ function parseFishHistory(content) {
56
+ const freq = {};
57
+ let inEntry = false;
58
+ let lastCmd = null;
59
+ for (const line of content.split(/\r?\n/)) {
60
+ const trimmed = line.trim();
61
+ if (trimmed.startsWith('- cmd:')) {
62
+ // flush previous
63
+ if (lastCmd) freq[lastCmd] = (freq[lastCmd] || 0) + 1;
64
+ lastCmd = trimmed.slice(6).trim();
65
+ inEntry = true;
66
+ } else if (inEntry && trimmed.startsWith('when:')) {
67
+ // end of entry — next line will start a new one or EOF
68
+ inEntry = false;
69
+ } else if (!trimmed) {
70
+ inEntry = false;
71
+ }
72
+ }
73
+ // Flush last entry
74
+ if (lastCmd) freq[lastCmd] = (freq[lastCmd] || 0) + 1;
75
+ return freq;
76
+ }
77
+
78
+ // Detect format and parse a single history file.
79
+ // Returns a frequency map for that file.
80
+ function parseHistoryFile(filePath) {
81
+ let content;
82
+ try {
83
+ content = fs.readFileSync(filePath, 'utf8');
84
+ } catch (e) {
85
+ process.stderr.write(`[shmakk] warning: cannot read ${filePath}: ${e.message}\n`);
86
+ return {};
87
+ }
88
+ if (!content.trim()) return {};
89
+
90
+ const name = path.basename(filePath);
91
+
92
+ // Fish history: YAML-like with "- cmd:" entries
93
+ if (name === 'fish_history' || content.includes('- cmd:')) {
94
+ return parseFishHistory(content);
95
+ }
96
+
97
+ // Zsh history: lines start with ": <timestamp>:"
98
+ if (content.match(/^:\s+\d+:\d+;/m)) {
99
+ return parseZshHistory(content);
100
+ }
101
+
102
+ // Bash history: one command per line (default fallback)
103
+ return parseBashHistory(content);
104
+ }
105
+
106
+ // Auto-detect common history files on this system.
107
+ function autoDetectHistoryFiles() {
108
+ const home = process.env.HOME || '/home/' + (process.env.USER || 'unknown');
109
+ const candidates = [
110
+ path.join(home, '.bash_history'),
111
+ path.join(home, '.zsh_history'),
112
+ path.join(home, '.local/share/fish/fish_history'),
113
+ path.join(home, '.config/fish/fish_history'),
114
+ ];
115
+ return candidates.filter((f) => {
116
+ try { return fs.statSync(f).isFile(); } catch { return false; }
117
+ });
118
+ }
119
+
120
+ // Parse one or more history files and merge into a single frequency map.
121
+ function buildFreqMap(filePaths) {
122
+ const merged = {};
123
+ for (const fp of filePaths) {
124
+ const freq = parseHistoryFile(fp);
125
+ for (const [cmd, count] of Object.entries(freq)) {
126
+ merged[cmd] = (merged[cmd] || 0) + count;
127
+ }
128
+ }
129
+ return merged;
130
+ }
131
+
132
+ // ── Load / save the frequency map ─────────────────────────────────────────
133
+
134
+ function freqMapPath(baseDir) {
135
+ return path.join(baseDir || process.cwd(), '.shmakk', 'state', 'command-freq.json');
136
+ }
137
+
138
+ function loadFreqMap(baseDir) {
139
+ const fp = freqMapPath(baseDir);
140
+ try {
141
+ const raw = fs.readFileSync(fp, 'utf8');
142
+ const parsed = JSON.parse(raw);
143
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
144
+ } catch {
145
+ return {};
146
+ }
147
+ }
148
+
149
+ function saveFreqMap(freqMap, baseDir) {
150
+ const dir = path.join(baseDir || process.cwd(), '.shmakk', 'state');
151
+ try { fs.mkdirSync(dir, { recursive: true }); } catch {}
152
+ const fp = path.join(dir, 'command-freq.json');
153
+ fs.writeFileSync(fp, JSON.stringify(freqMap, null, 2) + '\n');
154
+ return fp;
155
+ }
156
+
157
+ module.exports = {
158
+ parseBashHistory,
159
+ parseZshHistory,
160
+ parseFishHistory,
161
+ parseHistoryFile,
162
+ autoDetectHistoryFiles,
163
+ buildFreqMap,
164
+ loadFreqMap,
165
+ saveFreqMap,
166
+ };
@@ -0,0 +1,43 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+
5
+ const INIT = `
6
+ [ -f /etc/bash.bashrc ] && . /etc/bash.bashrc
7
+ [ -f "$HOME/.bashrc" ] && . "$HOME/.bashrc"
8
+
9
+ __shmakk_armed=1
10
+ __shmakk_preexec() {
11
+ [ -n "$COMP_LINE" ] && return
12
+ [ -z "$__shmakk_armed" ] && return
13
+ [ "$BASH_COMMAND" = "$PROMPT_COMMAND" ] && return
14
+ __shmakk_armed=
15
+ local cmd
16
+ cmd=$(printf '%s' "$BASH_COMMAND" | base64 -w0 2>/dev/null || printf '%s' "$BASH_COMMAND" | base64)
17
+ printf '\\e]6973;B;%s\\a' "$cmd"
18
+ }
19
+ __shmakk_precmd() {
20
+ local ec=$?
21
+ local p
22
+ p=$(printf '%s' "$PWD" | base64 -w0 2>/dev/null || printf '%s' "$PWD" | base64)
23
+ printf '\\e]6973;C;%s\\a' "$ec"
24
+ printf '\\e]6973;D;%s\\a' "$p"
25
+ __shmakk_armed=1
26
+ }
27
+ trap '__shmakk_preexec' DEBUG
28
+ PROMPT_COMMAND="__shmakk_precmd\${PROMPT_COMMAND:+;\$PROMPT_COMMAND}"
29
+ `;
30
+
31
+ function configure() {
32
+ const dir = path.join(os.tmpdir(), `shmakk-bash-${process.pid}`);
33
+ fs.mkdirSync(dir, { recursive: true });
34
+ const rcfile = path.join(dir, 'bashrc');
35
+ fs.writeFileSync(rcfile, INIT, { mode: 0o600 });
36
+ return {
37
+ args: ['--rcfile', rcfile, '-i'],
38
+ env: {},
39
+ cleanup: () => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} },
40
+ };
41
+ }
42
+
43
+ module.exports = { configure };
@@ -0,0 +1,25 @@
1
+ // Returns { args, env, cleanup } for spawning fish with markers wired up.
2
+ // fish supports `-C COMMAND` to run init code after config.fish.
3
+
4
+ const INIT = `
5
+ function __shmakk_pre --on-event fish_preexec
6
+ set -l c (printf '%s' "$argv" | base64 -w0 2>/dev/null; or printf '%s' "$argv" | base64)
7
+ printf '\\e]6973;B;%s\\a' "$c"
8
+ end
9
+ function __shmakk_post --on-event fish_postexec
10
+ set -l ec $status
11
+ set -l p (printf '%s' "$PWD" | base64 -w0 2>/dev/null; or printf '%s' "$PWD" | base64)
12
+ printf '\\e]6973;C;%s\\a' $ec
13
+ printf '\\e]6973;D;%s\\a' "$p"
14
+ end
15
+ `.trim();
16
+
17
+ function configure() {
18
+ return {
19
+ args: ['-i', '-l', '-C', INIT],
20
+ env: {},
21
+ cleanup: () => {},
22
+ };
23
+ }
24
+
25
+ module.exports = { configure };
@@ -0,0 +1,14 @@
1
+ const fish = require('./fish');
2
+ const bash = require('./bash');
3
+ const zsh = require('./zsh');
4
+
5
+ function configureForShell(name) {
6
+ switch (name) {
7
+ case 'fish': return fish.configure();
8
+ case 'bash': return bash.configure();
9
+ case 'zsh': return zsh.configure();
10
+ default: return { args: ['-i'], env: {}, cleanup: () => {} };
11
+ }
12
+ }
13
+
14
+ module.exports = { configureForShell };
@@ -0,0 +1,42 @@
1
+ const fs = require('fs');
2
+ const os = require('os');
3
+ const path = require('path');
4
+
5
+ const ZSHRC = `
6
+ # preserve real ZDOTDIR so user config is sourced
7
+ if [ -n "$SHMAKK_REAL_ZDOTDIR" ]; then
8
+ [ -f "$SHMAKK_REAL_ZDOTDIR/.zshrc" ] && source "$SHMAKK_REAL_ZDOTDIR/.zshrc"
9
+ elif [ -f "$HOME/.zshrc" ]; then
10
+ source "$HOME/.zshrc"
11
+ fi
12
+
13
+ __shmakk_preexec() {
14
+ local cmd
15
+ cmd=$(printf '%s' "$1" | base64 -w0 2>/dev/null || printf '%s' "$1" | base64)
16
+ printf '\\e]6973;B;%s\\a' "$cmd"
17
+ }
18
+ __shmakk_precmd() {
19
+ local ec=$?
20
+ local p
21
+ p=$(printf '%s' "$PWD" | base64 -w0 2>/dev/null || printf '%s' "$PWD" | base64)
22
+ printf '\\e]6973;C;%s\\a' "$ec"
23
+ printf '\\e]6973;D;%s\\a' "$p"
24
+ }
25
+ typeset -ag preexec_functions precmd_functions
26
+ preexec_functions+=(__shmakk_preexec)
27
+ precmd_functions+=(__shmakk_precmd)
28
+ `;
29
+
30
+ function configure() {
31
+ const dir = path.join(os.tmpdir(), `shmakk-zsh-${process.pid}`);
32
+ fs.mkdirSync(dir, { recursive: true });
33
+ fs.writeFileSync(path.join(dir, '.zshrc'), ZSHRC, { mode: 0o600 });
34
+ const realZ = process.env.ZDOTDIR || '';
35
+ return {
36
+ args: ['-i'],
37
+ env: { ZDOTDIR: dir, SHMAKK_REAL_ZDOTDIR: realZ },
38
+ cleanup: () => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} },
39
+ };
40
+ }
41
+
42
+ module.exports = { configure };