aliasmate 2.0.0 → 2.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.
- package/CHANGELOG.md +38 -3
- package/README.md +43 -10
- package/dist/cli/completion.js +4 -4
- package/dist/cli/completion.js.map +1 -1
- package/dist/cli/manage.d.ts +2 -3
- package/dist/cli/manage.d.ts.map +1 -1
- package/dist/cli/manage.js +44 -26
- package/dist/cli/manage.js.map +1 -1
- package/dist/cli/run.d.ts.map +1 -1
- package/dist/cli/run.js +14 -12
- package/dist/cli/run.js.map +1 -1
- package/dist/cli/save.d.ts +7 -8
- package/dist/cli/save.d.ts.map +1 -1
- package/dist/cli/save.js +71 -33
- package/dist/cli/save.js.map +1 -1
- package/dist/cli/transfer.d.ts +1 -0
- package/dist/cli/transfer.d.ts.map +1 -1
- package/dist/cli/transfer.js +40 -59
- package/dist/cli/transfer.js.map +1 -1
- package/dist/core/commands.d.ts +2 -0
- package/dist/core/commands.d.ts.map +1 -1
- package/dist/core/commands.js +40 -2
- package/dist/core/commands.js.map +1 -1
- package/dist/core/transfer.d.ts +20 -0
- package/dist/core/transfer.d.ts.map +1 -0
- package/dist/core/transfer.js +110 -0
- package/dist/core/transfer.js.map +1 -0
- package/dist/index.js +14 -11
- package/dist/index.js.map +1 -1
- package/dist/ui/format.d.ts +4 -1
- package/dist/ui/format.d.ts.map +1 -1
- package/dist/ui/format.js +61 -18
- package/dist/ui/format.js.map +1 -1
- package/dist/ui/interactive.d.ts +59 -3
- package/dist/ui/interactive.d.ts.map +1 -1
- package/dist/ui/interactive.js +604 -94
- package/dist/ui/interactive.js.map +1 -1
- package/dist/ui/onboarding.d.ts.map +1 -1
- package/dist/ui/onboarding.js +7 -7
- package/dist/ui/onboarding.js.map +1 -1
- package/dist/ui/prompts.d.ts +1 -12
- package/dist/ui/prompts.d.ts.map +1 -1
- package/dist/ui/prompts.js +45 -91
- package/dist/ui/prompts.js.map +1 -1
- package/dist/ui/table.d.ts +18 -0
- package/dist/ui/table.d.ts.map +1 -0
- package/dist/ui/table.js +54 -0
- package/dist/ui/table.js.map +1 -0
- package/dist/ui/theme.d.ts +8 -6
- package/dist/ui/theme.d.ts.map +1 -1
- package/dist/ui/theme.js +33 -18
- package/dist/ui/theme.js.map +1 -1
- package/package.json +7 -7
package/dist/ui/interactive.js
CHANGED
|
@@ -32,116 +32,626 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Tui = void 0;
|
|
37
|
+
exports.fuzzyMatch = fuzzyMatch;
|
|
39
38
|
exports.interactiveHome = interactiveHome;
|
|
40
|
-
const
|
|
39
|
+
const readline = __importStar(require("readline"));
|
|
41
40
|
const commands_1 = require("../core/commands");
|
|
42
41
|
const recent_1 = require("../core/recent");
|
|
42
|
+
const transfer_1 = require("../core/transfer");
|
|
43
|
+
const env_1 = require("../core/env");
|
|
43
44
|
const format_1 = require("./format");
|
|
44
45
|
const theme_1 = require("./theme");
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
/** Simple subsequence fuzzy match: every query char appears in order. */
|
|
47
|
+
function fuzzyMatch(query, text) {
|
|
48
|
+
const q = query.toLowerCase();
|
|
49
|
+
const t = text.toLowerCase();
|
|
50
|
+
let i = 0;
|
|
51
|
+
for (const ch of t) {
|
|
52
|
+
if (ch === q[i])
|
|
53
|
+
i++;
|
|
54
|
+
if (i === q.length)
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
return q.length === 0;
|
|
58
|
+
}
|
|
59
|
+
/** Shared caret editing for form fields and single-line inputs. */
|
|
60
|
+
function editText(field, str, key) {
|
|
61
|
+
if (key.name === 'left') {
|
|
62
|
+
field.cursor = Math.max(0, field.cursor - 1);
|
|
63
|
+
}
|
|
64
|
+
else if (key.name === 'right') {
|
|
65
|
+
field.cursor = Math.min(field.value.length, field.cursor + 1);
|
|
66
|
+
}
|
|
67
|
+
else if (key.name === 'home' || (key.ctrl && key.name === 'a')) {
|
|
68
|
+
field.cursor = 0;
|
|
69
|
+
}
|
|
70
|
+
else if (key.name === 'end' || (key.ctrl && key.name === 'e')) {
|
|
71
|
+
field.cursor = field.value.length;
|
|
72
|
+
}
|
|
73
|
+
else if (key.ctrl && key.name === 'u') {
|
|
74
|
+
field.value = field.value.slice(field.cursor);
|
|
75
|
+
field.cursor = 0;
|
|
76
|
+
}
|
|
77
|
+
else if (key.name === 'backspace') {
|
|
78
|
+
if (field.cursor > 0) {
|
|
79
|
+
field.value = field.value.slice(0, field.cursor - 1) + field.value.slice(field.cursor);
|
|
80
|
+
field.cursor -= 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (key.name === 'delete') {
|
|
84
|
+
field.value = field.value.slice(0, field.cursor) + field.value.slice(field.cursor + 1);
|
|
85
|
+
}
|
|
86
|
+
else if (str && str.length === 1 && !key.ctrl && !key.meta && str >= ' ') {
|
|
87
|
+
field.value = field.value.slice(0, field.cursor) + str + field.value.slice(field.cursor);
|
|
88
|
+
field.cursor += 1;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return true;
|
|
50
94
|
}
|
|
51
|
-
|
|
95
|
+
const ALT_SCREEN_ON = '\x1b[?1049h\x1b[?25l';
|
|
96
|
+
const ALT_SCREEN_OFF = '\x1b[?1049l\x1b[?25h';
|
|
97
|
+
const CLEAR = '\x1b[2J\x1b[H';
|
|
98
|
+
function loadRows() {
|
|
52
99
|
const commands = (0, commands_1.listCommands)();
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
{
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
choices: [
|
|
65
|
-
...names.map((name) => {
|
|
66
|
-
const runs = counts.get(name) ?? 0;
|
|
67
|
-
const runsLabel = runs > 0 ? theme_1.theme.dim(` · ${theme_1.icons.fire} ${runs}`) : '';
|
|
68
|
-
return {
|
|
69
|
-
name: `${name.padEnd(18)} ${theme_1.theme.dim((0, format_1.truncate)(commands[name].command, 50))}${runsLabel}`,
|
|
70
|
-
value: name,
|
|
71
|
-
short: name,
|
|
72
|
-
};
|
|
73
|
-
}),
|
|
74
|
-
new inquirer_1.default.Separator(),
|
|
75
|
-
{ name: theme_1.theme.dim('← Back'), value: '__back__' },
|
|
76
|
-
],
|
|
77
|
-
},
|
|
78
|
-
]);
|
|
79
|
-
return picked === '__back__' ? null : picked;
|
|
100
|
+
const stats = (0, recent_1.getUsageStats)();
|
|
101
|
+
const counts = new Map(stats.map((s) => [s.name, s.runCount]));
|
|
102
|
+
const lastRuns = new Map(stats.map((s) => [s.name, s.lastRunAt]));
|
|
103
|
+
return Object.keys(commands)
|
|
104
|
+
.sort((a, b) => (counts.get(b) ?? 0) - (counts.get(a) ?? 0) || a.localeCompare(b))
|
|
105
|
+
.map((name) => ({
|
|
106
|
+
name,
|
|
107
|
+
cmd: commands[name],
|
|
108
|
+
runs: counts.get(name) ?? 0,
|
|
109
|
+
lastRun: lastRuns.get(name),
|
|
110
|
+
}));
|
|
80
111
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
112
|
+
class Tui {
|
|
113
|
+
constructor(out = (t) => process.stdout.write(t)) {
|
|
114
|
+
this.out = out;
|
|
115
|
+
this.mode = 'browse';
|
|
116
|
+
this.filter = '';
|
|
117
|
+
this.selected = 0;
|
|
118
|
+
this.rows = loadRows();
|
|
119
|
+
this.form = null;
|
|
120
|
+
this.input = null;
|
|
121
|
+
this.message = '';
|
|
122
|
+
this.resolve = null;
|
|
123
|
+
}
|
|
124
|
+
get width() {
|
|
125
|
+
return process.stdout.columns || 100;
|
|
126
|
+
}
|
|
127
|
+
get height() {
|
|
128
|
+
return process.stdout.rows || 30;
|
|
129
|
+
}
|
|
130
|
+
visibleRows() {
|
|
131
|
+
if (!this.filter)
|
|
132
|
+
return this.rows;
|
|
133
|
+
// Name matches rank above matches in the command text or directory.
|
|
134
|
+
const byName = this.rows.filter((r) => fuzzyMatch(this.filter, r.name));
|
|
135
|
+
const byContent = this.rows.filter((r) => !byName.includes(r) && fuzzyMatch(this.filter, `${r.cmd.command} ${r.cmd.directory}`));
|
|
136
|
+
return [...byName, ...byContent];
|
|
137
|
+
}
|
|
138
|
+
refresh() {
|
|
139
|
+
this.rows = loadRows();
|
|
140
|
+
this.selected = Math.min(this.selected, Math.max(0, this.visibleRows().length - 1));
|
|
141
|
+
}
|
|
142
|
+
openForm(seed = {}) {
|
|
143
|
+
this.form = {
|
|
144
|
+
fields: [
|
|
145
|
+
{ key: 'name', label: 'name', value: seed.name ?? '' },
|
|
146
|
+
{ key: 'command', label: 'command', value: seed.command ?? '' },
|
|
147
|
+
{ key: 'directory', label: 'directory', value: seed.directory ?? process.cwd() },
|
|
148
|
+
].map((f) => ({ ...f, cursor: f.value.length })),
|
|
149
|
+
pathMode: seed.pathMode ?? 'saved',
|
|
150
|
+
envChoice: seed.env && Object.keys(seed.env).length > 0 ? 'keep' : 'none',
|
|
151
|
+
keepEnv: seed.env,
|
|
152
|
+
editing: seed.editing,
|
|
153
|
+
active: seed.editing || seed.name ? 1 : 0,
|
|
154
|
+
error: '',
|
|
155
|
+
};
|
|
156
|
+
this.mode = 'form';
|
|
157
|
+
}
|
|
158
|
+
// --- rendering -------------------------------------------------------------
|
|
159
|
+
header(subtitle) {
|
|
160
|
+
const left = `${theme_1.icons.dot} ${theme_1.theme.heading('aliasmate')} ${theme_1.theme.dim(subtitle)}`;
|
|
161
|
+
const totalRuns = this.rows.reduce((s, r) => s + r.runs, 0);
|
|
162
|
+
const rightText = `${this.rows.length} commands · ${totalRuns} runs`;
|
|
163
|
+
const leftText = `● aliasmate ${subtitle}`;
|
|
164
|
+
const pad = Math.max(1, this.width - leftText.length - rightText.length - 3);
|
|
165
|
+
return ['', ` ${left}${' '.repeat(pad)}${theme_1.theme.faint(rightText)}`, ''];
|
|
166
|
+
}
|
|
167
|
+
listPane(maxLines) {
|
|
168
|
+
const rows = this.visibleRows();
|
|
169
|
+
if (rows.length === 0) {
|
|
170
|
+
return this.rows.length === 0
|
|
171
|
+
? [
|
|
172
|
+
'',
|
|
173
|
+
theme_1.theme.dim(' No commands yet — press ') +
|
|
174
|
+
theme_1.theme.accent('n') +
|
|
175
|
+
theme_1.theme.dim(' to create your first one.'),
|
|
176
|
+
]
|
|
177
|
+
: ['', theme_1.theme.dim(` Nothing matches "${this.filter}"`)];
|
|
178
|
+
}
|
|
179
|
+
const nameW = Math.min(Math.max(...rows.map((r) => r.name.length), 4) + 1, 22);
|
|
180
|
+
const cmdW = Math.max(20, Math.min(56, this.width - nameW - 30));
|
|
181
|
+
const start = Math.max(0, Math.min(this.selected - Math.floor(maxLines / 2), rows.length - maxLines));
|
|
182
|
+
return rows.slice(start, start + maxLines).map((row, i) => {
|
|
183
|
+
const index = start + i;
|
|
184
|
+
const active = index === this.selected;
|
|
185
|
+
const marker = active ? theme_1.theme.accent('›') : ' ';
|
|
186
|
+
const name = active
|
|
187
|
+
? theme_1.theme.selected(row.name.padEnd(nameW))
|
|
188
|
+
: theme_1.theme.name(row.name.padEnd(nameW));
|
|
189
|
+
const cmd = (0, format_1.truncate)(row.cmd.command, cmdW).padEnd(cmdW + 1);
|
|
190
|
+
const runs = row.runs > 0 ? theme_1.theme.dim(`${theme_1.icons.fire} ${row.runs}`) : theme_1.theme.faint('·');
|
|
191
|
+
return ` ${marker} ${name} ${active ? cmd : theme_1.theme.dim(cmd)} ${runs}`;
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
detailPane() {
|
|
195
|
+
const row = this.visibleRows()[this.selected];
|
|
196
|
+
if (!row)
|
|
197
|
+
return [];
|
|
198
|
+
const label = (t) => theme_1.theme.faint(t.padEnd(11));
|
|
199
|
+
const where = (row.cmd.pathMode ?? 'saved') === 'current'
|
|
200
|
+
? 'current directory'
|
|
201
|
+
: (0, format_1.prettyPath)(row.cmd.directory);
|
|
202
|
+
const env = Object.entries(row.cmd.env ?? {})
|
|
203
|
+
.map(([k, v]) => `${k}=${(0, env_1.isSensitive)(k) ? (0, env_1.maskValue)(v) : v}`)
|
|
204
|
+
.join(theme_1.theme.faint(' · '));
|
|
205
|
+
const lines = [
|
|
206
|
+
theme_1.theme.faint(` ${'─'.repeat(Math.max(10, this.width - 2))}`),
|
|
207
|
+
` ${label('command')}${(0, format_1.truncate)(row.cmd.command, this.width - 14)}`,
|
|
208
|
+
` ${label('where')}${theme_1.theme.dim((0, format_1.truncate)(where, this.width - 14))}`,
|
|
209
|
+
];
|
|
210
|
+
if (env)
|
|
211
|
+
lines.push(` ${label('env')}${theme_1.theme.dim((0, format_1.truncate)(env, this.width - 14))}`);
|
|
212
|
+
lines.push(` ${label('runs')}${theme_1.theme.dim(row.runs > 0 ? `${row.runs} · last ${(0, format_1.timeAgo)(row.lastRun)}` : 'never')}`);
|
|
213
|
+
return lines;
|
|
214
|
+
}
|
|
215
|
+
statsPane() {
|
|
216
|
+
const top = this.rows.filter((r) => r.runs > 0).slice(0, 12);
|
|
217
|
+
const lines = [];
|
|
218
|
+
if (top.length === 0) {
|
|
219
|
+
lines.push(theme_1.theme.dim(' Nothing run yet.'));
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
const max = Math.max(...top.map((r) => r.runs));
|
|
223
|
+
const nameW = Math.min(Math.max(...top.map((r) => r.name.length)) + 1, 22);
|
|
224
|
+
for (const row of top) {
|
|
225
|
+
const bar = theme_1.theme.accent('▮'.repeat(Math.max(1, Math.round((row.runs / max) * 24))));
|
|
226
|
+
lines.push(` ${theme_1.theme.name(row.name.padEnd(nameW))} ${bar} ${theme_1.theme.dim(String(row.runs))} ${theme_1.theme.faint((0, format_1.timeAgo)(row.lastRun))}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
return lines;
|
|
230
|
+
}
|
|
231
|
+
formPane() {
|
|
232
|
+
const form = this.form;
|
|
233
|
+
const label = (t, active) => (active ? theme_1.theme.accent('› ') : ' ') + theme_1.theme.faint(t.padEnd(11));
|
|
234
|
+
const lines = [];
|
|
235
|
+
form.fields.forEach((field, i) => {
|
|
236
|
+
const active = form.active === i;
|
|
237
|
+
const renamed = field.key === 'name' && form.editing !== undefined && field.value !== form.editing;
|
|
238
|
+
const value = active
|
|
239
|
+
? `${field.value.slice(0, field.cursor)}${theme_1.theme.accent('▏')}${field.value.slice(field.cursor)}`
|
|
240
|
+
: field.value;
|
|
241
|
+
lines.push(` ${label(field.label, active)}${value}${renamed ? theme_1.theme.faint(` · renames ${form.editing}`) : ''}`);
|
|
242
|
+
});
|
|
243
|
+
const pmActive = form.active === 3;
|
|
244
|
+
const pmText = form.pathMode === 'saved'
|
|
245
|
+
? 'saved directory (project command)'
|
|
246
|
+
: 'current directory (utility)';
|
|
247
|
+
lines.push(` ${label('runs in', pmActive)}${pmActive ? pmText : theme_1.theme.dim(pmText)}${pmActive ? theme_1.theme.faint(' · space to toggle') : ''}`);
|
|
248
|
+
const envActive = form.active === 4;
|
|
249
|
+
const keptCount = form.keepEnv ? Object.keys(form.keepEnv).length : 0;
|
|
250
|
+
const envLabels = {
|
|
251
|
+
none: 'none',
|
|
252
|
+
keep: `keep saved (${keptCount} var${keptCount === 1 ? '' : 's'})`,
|
|
253
|
+
capture: `capture current shell (${Object.keys((0, env_1.captureUserEnv)()).length} vars, secrets masked on export)`,
|
|
254
|
+
};
|
|
255
|
+
lines.push(` ${label('env', envActive)}${envActive ? envLabels[form.envChoice] : theme_1.theme.dim(envLabels[form.envChoice])}${envActive ? theme_1.theme.faint(' · space to cycle') : ''}`);
|
|
256
|
+
if (form.error) {
|
|
257
|
+
lines.push('');
|
|
258
|
+
lines.push(` ${theme_1.theme.error(form.error)}`);
|
|
259
|
+
}
|
|
260
|
+
return lines;
|
|
261
|
+
}
|
|
262
|
+
inputPane() {
|
|
263
|
+
const input = this.input;
|
|
264
|
+
const value = `${input.value.slice(0, input.cursor)}${theme_1.theme.accent('▏')}${input.value.slice(input.cursor)}`;
|
|
265
|
+
const lines = [` ${theme_1.theme.accent('› ')}${theme_1.theme.faint(input.label.padEnd(11))}${value}`];
|
|
266
|
+
if (input.kind === 'export') {
|
|
267
|
+
lines.push('');
|
|
268
|
+
lines.push(theme_1.theme.faint(' includes real secret env values — keep the file safe'));
|
|
269
|
+
}
|
|
270
|
+
if (input.error) {
|
|
271
|
+
lines.push('');
|
|
272
|
+
lines.push(` ${theme_1.theme.error(input.error)}`);
|
|
273
|
+
}
|
|
274
|
+
return lines;
|
|
275
|
+
}
|
|
276
|
+
footer() {
|
|
277
|
+
const key = (k, label) => `${theme_1.theme.accent(k)} ${theme_1.theme.faint(label)}`;
|
|
278
|
+
const sep = theme_1.theme.faint(' ');
|
|
279
|
+
switch (this.mode) {
|
|
280
|
+
case 'confirm-delete': {
|
|
281
|
+
const row = this.visibleRows()[this.selected];
|
|
282
|
+
return ` ${theme_1.theme.warning(`delete "${row?.name}"?`)} ${key('y', 'yes')}${sep}${key('n', 'no')}`;
|
|
283
|
+
}
|
|
284
|
+
case 'filter':
|
|
285
|
+
return ` ${theme_1.theme.accent('/')} ${this.filter}${theme_1.theme.accent('▏')} ${theme_1.theme.faint('esc clear · enter run · ↑↓ move')}`;
|
|
286
|
+
case 'form':
|
|
287
|
+
return ` ${[key('↑↓/tab', 'field'), key('←→', 'cursor'), key('enter', 'next/save'), key('esc', 'cancel')].join(sep)}`;
|
|
288
|
+
case 'input':
|
|
289
|
+
return ` ${[key('enter', this.input?.kind === 'export' ? 'export' : 'import'), key('esc', 'cancel')].join(sep)}`;
|
|
290
|
+
case 'stats':
|
|
291
|
+
return ` ${theme_1.theme.faint('any key to go back')}`;
|
|
292
|
+
default:
|
|
293
|
+
return ` ${[
|
|
294
|
+
key('↑↓', 'move'),
|
|
295
|
+
key('enter', 'run'),
|
|
296
|
+
key('/', 'filter'),
|
|
297
|
+
key('n', 'new'),
|
|
298
|
+
key('e', 'edit'),
|
|
299
|
+
key('d', 'delete'),
|
|
300
|
+
key('s', 'stats'),
|
|
301
|
+
key('x', 'export'),
|
|
302
|
+
key('i', 'import'),
|
|
303
|
+
key('q', 'quit'),
|
|
304
|
+
].join(sep)}`;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
render() {
|
|
308
|
+
const subtitle = this.mode === 'form'
|
|
309
|
+
? this.form?.editing
|
|
310
|
+
? `· edit ${this.form.editing}`
|
|
311
|
+
: '· new command'
|
|
312
|
+
: this.mode === 'stats'
|
|
313
|
+
? '· stats'
|
|
314
|
+
: this.mode === 'input'
|
|
315
|
+
? this.input?.kind === 'export'
|
|
316
|
+
? '· export backup'
|
|
317
|
+
: '· import'
|
|
318
|
+
: '';
|
|
319
|
+
const out = [...this.header(subtitle)];
|
|
320
|
+
if (this.mode === 'stats') {
|
|
321
|
+
out.push(...this.statsPane());
|
|
322
|
+
}
|
|
323
|
+
else if (this.mode === 'form') {
|
|
324
|
+
out.push(...this.formPane());
|
|
325
|
+
}
|
|
326
|
+
else if (this.mode === 'input') {
|
|
327
|
+
out.push(...this.inputPane());
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
const detail = this.detailPane();
|
|
331
|
+
const listLines = Math.max(3, this.height - out.length - detail.length - 4);
|
|
332
|
+
out.push(...this.listPane(listLines));
|
|
333
|
+
out.push('');
|
|
334
|
+
out.push(...detail);
|
|
335
|
+
}
|
|
336
|
+
while (out.length < this.height - 2)
|
|
337
|
+
out.push('');
|
|
338
|
+
out.push(this.message ? ` ${this.message}` : '');
|
|
339
|
+
out.push(this.footer());
|
|
340
|
+
this.out(CLEAR + out.slice(0, this.height).join('\n'));
|
|
341
|
+
this.message = '';
|
|
342
|
+
}
|
|
343
|
+
// --- input -----------------------------------------------------------------
|
|
344
|
+
/** Clear the filter but keep the same row highlighted. */
|
|
345
|
+
clearFilter() {
|
|
346
|
+
const current = this.visibleRows()[this.selected]?.name;
|
|
347
|
+
this.filter = '';
|
|
348
|
+
const index = current ? this.visibleRows().findIndex((r) => r.name === current) : -1;
|
|
349
|
+
this.selected = Math.max(0, index);
|
|
350
|
+
}
|
|
351
|
+
move(delta) {
|
|
352
|
+
const count = this.visibleRows().length;
|
|
353
|
+
if (count === 0)
|
|
354
|
+
return;
|
|
355
|
+
this.selected = (this.selected + delta + count) % count;
|
|
356
|
+
}
|
|
357
|
+
openInput(kind) {
|
|
358
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
359
|
+
const value = kind === 'export' ? `~/aliasmate-backup-${today}.json` : '~/';
|
|
360
|
+
this.input = { kind, label: 'file', value, cursor: value.length, error: '' };
|
|
361
|
+
this.mode = 'input';
|
|
362
|
+
}
|
|
363
|
+
handleInputKey(str, key) {
|
|
364
|
+
const input = this.input;
|
|
365
|
+
if (key.name === 'escape') {
|
|
366
|
+
this.input = null;
|
|
367
|
+
this.mode = 'browse';
|
|
368
|
+
this.message = theme_1.theme.faint('cancelled');
|
|
369
|
+
return this.render();
|
|
370
|
+
}
|
|
371
|
+
if (key.name === 'return')
|
|
372
|
+
return this.submitInput();
|
|
373
|
+
if (editText(input, str, key))
|
|
374
|
+
input.error = '';
|
|
375
|
+
this.render();
|
|
376
|
+
}
|
|
377
|
+
submitInput() {
|
|
378
|
+
const input = this.input;
|
|
379
|
+
const file = input.value.trim();
|
|
380
|
+
if (!file) {
|
|
381
|
+
input.error = 'File path cannot be empty';
|
|
382
|
+
return this.render();
|
|
383
|
+
}
|
|
384
|
+
try {
|
|
385
|
+
if (input.kind === 'export') {
|
|
386
|
+
const count = (0, transfer_1.exportToFile)(file, { full: true });
|
|
387
|
+
this.message = `${theme_1.icons.ok} ${theme_1.theme.dim(`exported ${count} command${count === 1 ? '' : 's'} to ${file}`)}`;
|
|
388
|
+
}
|
|
389
|
+
else {
|
|
390
|
+
const result = (0, transfer_1.importFromFile)(file);
|
|
391
|
+
this.refresh();
|
|
392
|
+
const extras = [
|
|
393
|
+
result.skipped.length > 0 ? `${result.skipped.length} skipped (already exist)` : '',
|
|
394
|
+
result.invalid.length > 0 ? `${result.invalid.length} invalid` : '',
|
|
395
|
+
]
|
|
396
|
+
.filter(Boolean)
|
|
397
|
+
.join(' · ');
|
|
398
|
+
this.message = `${theme_1.icons.ok} ${theme_1.theme.dim(`imported ${result.imported}${extras ? ` · ${extras}` : ''}`)}`;
|
|
399
|
+
}
|
|
400
|
+
this.input = null;
|
|
401
|
+
this.mode = 'browse';
|
|
402
|
+
}
|
|
403
|
+
catch (error) {
|
|
404
|
+
input.error = error.message;
|
|
405
|
+
}
|
|
406
|
+
this.render();
|
|
407
|
+
}
|
|
408
|
+
submitForm() {
|
|
409
|
+
const form = this.form;
|
|
410
|
+
const [name, command, directory] = form.fields.map((f) => f.value.trim());
|
|
411
|
+
const renaming = form.editing !== undefined && name !== form.editing;
|
|
412
|
+
const nameCheck = (0, commands_1.validateCommandName)(name);
|
|
413
|
+
if (nameCheck !== true) {
|
|
414
|
+
form.error = nameCheck;
|
|
415
|
+
form.active = 0;
|
|
416
|
+
return this.render();
|
|
417
|
+
}
|
|
418
|
+
if ((!form.editing || renaming) && (0, commands_1.commandExists)(name)) {
|
|
419
|
+
form.error = `"${name}" already exists — pick another name or edit it instead`;
|
|
420
|
+
form.active = 0;
|
|
421
|
+
return this.render();
|
|
422
|
+
}
|
|
423
|
+
if (!command) {
|
|
424
|
+
form.error = 'Command cannot be empty';
|
|
425
|
+
form.active = 1;
|
|
426
|
+
return this.render();
|
|
427
|
+
}
|
|
428
|
+
if (!directory) {
|
|
429
|
+
form.error = 'Directory cannot be empty';
|
|
430
|
+
form.active = 2;
|
|
431
|
+
return this.render();
|
|
432
|
+
}
|
|
433
|
+
const env = form.envChoice === 'none'
|
|
434
|
+
? undefined
|
|
435
|
+
: form.envChoice === 'keep'
|
|
436
|
+
? form.keepEnv
|
|
437
|
+
: (0, env_1.captureUserEnv)();
|
|
117
438
|
try {
|
|
118
|
-
if (
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
439
|
+
if (renaming)
|
|
440
|
+
(0, commands_1.renameCommand)(form.editing, name);
|
|
441
|
+
(0, commands_1.saveCommand)({ name, command, directory, pathMode: form.pathMode, env });
|
|
442
|
+
this.message = `${theme_1.icons.ok} ${theme_1.theme.dim(renaming ? `renamed ${form.editing} → ${name}` : `saved ${name}`)}`;
|
|
443
|
+
const finalName = name;
|
|
444
|
+
this.form = null;
|
|
445
|
+
this.mode = 'browse';
|
|
446
|
+
this.refresh();
|
|
447
|
+
const index = this.visibleRows().findIndex((r) => r.name === finalName);
|
|
448
|
+
if (index >= 0)
|
|
449
|
+
this.selected = index;
|
|
450
|
+
}
|
|
451
|
+
catch (error) {
|
|
452
|
+
form.error = error.message;
|
|
453
|
+
}
|
|
454
|
+
this.render();
|
|
455
|
+
}
|
|
456
|
+
handleFormKey(str, key) {
|
|
457
|
+
const form = this.form;
|
|
458
|
+
const fieldCount = 5;
|
|
459
|
+
const isTextField = form.active < 3;
|
|
460
|
+
if (key.name === 'escape') {
|
|
461
|
+
this.form = null;
|
|
462
|
+
this.mode = 'browse';
|
|
463
|
+
this.message = theme_1.theme.faint('cancelled');
|
|
464
|
+
return this.render();
|
|
465
|
+
}
|
|
466
|
+
if (key.name === 'return') {
|
|
467
|
+
if (form.active < fieldCount - 1) {
|
|
468
|
+
form.active += 1;
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
return this.submitForm();
|
|
472
|
+
}
|
|
473
|
+
return this.render();
|
|
474
|
+
}
|
|
475
|
+
if (key.name === 'up' || (key.name === 'tab' && key.shift)) {
|
|
476
|
+
form.active = (form.active - 1 + fieldCount) % fieldCount;
|
|
477
|
+
return this.render();
|
|
478
|
+
}
|
|
479
|
+
if (key.name === 'down' || key.name === 'tab') {
|
|
480
|
+
form.active = (form.active + 1) % fieldCount;
|
|
481
|
+
return this.render();
|
|
482
|
+
}
|
|
483
|
+
if (isTextField) {
|
|
484
|
+
editText(form.fields[form.active], str, key);
|
|
485
|
+
form.error = '';
|
|
486
|
+
return this.render();
|
|
487
|
+
}
|
|
488
|
+
if (key.name === 'space' || key.name === 'left' || key.name === 'right') {
|
|
489
|
+
if (form.active === 3) {
|
|
490
|
+
form.pathMode = form.pathMode === 'saved' ? 'current' : 'saved';
|
|
491
|
+
}
|
|
492
|
+
else {
|
|
493
|
+
const cycle = form.keepEnv ? ['keep', 'capture', 'none'] : ['none', 'capture'];
|
|
494
|
+
const next = (cycle.indexOf(form.envChoice) + 1) % cycle.length;
|
|
495
|
+
form.envChoice = cycle[next];
|
|
496
|
+
}
|
|
497
|
+
return this.render();
|
|
498
|
+
}
|
|
499
|
+
this.render();
|
|
500
|
+
}
|
|
501
|
+
handleKey(str, key) {
|
|
502
|
+
if (key.ctrl && key.name === 'c')
|
|
503
|
+
return this.finish(null);
|
|
504
|
+
if (this.mode === 'form')
|
|
505
|
+
return this.handleFormKey(str, key);
|
|
506
|
+
if (this.mode === 'input')
|
|
507
|
+
return this.handleInputKey(str, key);
|
|
508
|
+
if (this.mode === 'stats') {
|
|
509
|
+
this.mode = 'browse';
|
|
510
|
+
return this.render();
|
|
511
|
+
}
|
|
512
|
+
if (this.mode === 'confirm-delete') {
|
|
513
|
+
if (str === 'y') {
|
|
514
|
+
const row = this.visibleRows()[this.selected];
|
|
515
|
+
if (row) {
|
|
516
|
+
(0, commands_1.deleteCommand)(row.name);
|
|
517
|
+
this.message = `${theme_1.icons.ok} ${theme_1.theme.dim(`deleted ${row.name}`)}`;
|
|
518
|
+
this.refresh();
|
|
129
519
|
}
|
|
130
520
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
521
|
+
this.mode = 'browse';
|
|
522
|
+
return this.render();
|
|
523
|
+
}
|
|
524
|
+
if (this.mode === 'filter') {
|
|
525
|
+
if (key.name === 'escape') {
|
|
526
|
+
this.clearFilter();
|
|
527
|
+
this.mode = 'browse';
|
|
528
|
+
}
|
|
529
|
+
else if (key.name === 'return') {
|
|
530
|
+
this.mode = 'browse';
|
|
531
|
+
return this.selectRun();
|
|
532
|
+
}
|
|
533
|
+
else if (key.name === 'backspace') {
|
|
534
|
+
this.filter = this.filter.slice(0, -1);
|
|
135
535
|
}
|
|
136
|
-
if (
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
536
|
+
else if (key.name === 'up')
|
|
537
|
+
this.move(-1);
|
|
538
|
+
else if (key.name === 'down')
|
|
539
|
+
this.move(1);
|
|
540
|
+
else if (str && str.length === 1 && !key.ctrl && !key.meta) {
|
|
541
|
+
this.filter += str;
|
|
542
|
+
this.selected = 0;
|
|
140
543
|
}
|
|
544
|
+
return this.render();
|
|
141
545
|
}
|
|
142
|
-
|
|
143
|
-
|
|
546
|
+
// browse mode
|
|
547
|
+
switch (key.name) {
|
|
548
|
+
case 'up':
|
|
549
|
+
case 'k':
|
|
550
|
+
this.move(-1);
|
|
551
|
+
break;
|
|
552
|
+
case 'down':
|
|
553
|
+
case 'j':
|
|
554
|
+
this.move(1);
|
|
555
|
+
break;
|
|
556
|
+
case 'return':
|
|
557
|
+
return this.selectRun();
|
|
558
|
+
case 'escape':
|
|
559
|
+
if (this.filter) {
|
|
560
|
+
this.clearFilter();
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
return this.finish(null);
|
|
564
|
+
default:
|
|
565
|
+
if (str === '/')
|
|
566
|
+
this.mode = 'filter';
|
|
567
|
+
else if (str === 'q')
|
|
568
|
+
return this.finish(null);
|
|
569
|
+
else if (str === 'n')
|
|
570
|
+
this.openForm();
|
|
571
|
+
else if (str === 'e') {
|
|
572
|
+
const row = this.visibleRows()[this.selected];
|
|
573
|
+
if (row) {
|
|
574
|
+
this.openForm({
|
|
575
|
+
name: row.name,
|
|
576
|
+
command: row.cmd.command,
|
|
577
|
+
directory: row.cmd.directory,
|
|
578
|
+
pathMode: row.cmd.pathMode ?? 'saved',
|
|
579
|
+
env: row.cmd.env,
|
|
580
|
+
editing: row.name,
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
else if (str === 'd' && this.visibleRows().length > 0)
|
|
585
|
+
this.mode = 'confirm-delete';
|
|
586
|
+
else if (str === 's')
|
|
587
|
+
this.mode = 'stats';
|
|
588
|
+
else if (str === 'x')
|
|
589
|
+
this.openInput('export');
|
|
590
|
+
else if (str === 'i')
|
|
591
|
+
this.openInput('import');
|
|
144
592
|
}
|
|
593
|
+
this.render();
|
|
594
|
+
}
|
|
595
|
+
selectRun() {
|
|
596
|
+
const row = this.visibleRows()[this.selected];
|
|
597
|
+
if (!row) {
|
|
598
|
+
this.message = theme_1.theme.dim('nothing selected');
|
|
599
|
+
return this.render();
|
|
600
|
+
}
|
|
601
|
+
this.finish({ kind: 'run', name: row.name });
|
|
602
|
+
}
|
|
603
|
+
finish(action) {
|
|
604
|
+
const resolve = this.resolve;
|
|
605
|
+
this.resolve = null;
|
|
606
|
+
resolve?.(action);
|
|
607
|
+
}
|
|
608
|
+
/** Show the TUI until the user runs a command or quits (null). */
|
|
609
|
+
waitForAction() {
|
|
610
|
+
return new Promise((resolve) => {
|
|
611
|
+
this.resolve = resolve;
|
|
612
|
+
this.render();
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
exports.Tui = Tui;
|
|
617
|
+
function enterScreen(onKey) {
|
|
618
|
+
process.stdout.write(ALT_SCREEN_ON);
|
|
619
|
+
readline.emitKeypressEvents(process.stdin);
|
|
620
|
+
if (process.stdin.isTTY)
|
|
621
|
+
process.stdin.setRawMode(true);
|
|
622
|
+
process.stdin.on('keypress', onKey);
|
|
623
|
+
process.stdin.resume();
|
|
624
|
+
return () => {
|
|
625
|
+
process.stdin.off('keypress', onKey);
|
|
626
|
+
if (process.stdin.isTTY)
|
|
627
|
+
process.stdin.setRawMode(false);
|
|
628
|
+
process.stdin.pause();
|
|
629
|
+
process.stdout.write(ALT_SCREEN_OFF);
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* The full-screen home: everything (browse, create, edit, delete, stats)
|
|
634
|
+
* happens in the TUI. Only running a command hands the terminal back so
|
|
635
|
+
* output lands in normal scrollback.
|
|
636
|
+
*/
|
|
637
|
+
async function interactiveHome(seed) {
|
|
638
|
+
const tui = new Tui();
|
|
639
|
+
if (seed)
|
|
640
|
+
tui.openForm(seed);
|
|
641
|
+
const onResize = () => tui.render();
|
|
642
|
+
const leave = enterScreen((s, k) => tui.handleKey(s, k));
|
|
643
|
+
process.stdout.on('resize', onResize);
|
|
644
|
+
let action;
|
|
645
|
+
try {
|
|
646
|
+
action = await tui.waitForAction();
|
|
647
|
+
}
|
|
648
|
+
finally {
|
|
649
|
+
process.stdout.off('resize', onResize);
|
|
650
|
+
leave();
|
|
651
|
+
}
|
|
652
|
+
if (action?.kind === 'run') {
|
|
653
|
+
const { runHandler } = await Promise.resolve().then(() => __importStar(require('../cli/run')));
|
|
654
|
+
await runHandler(action.name, undefined, {});
|
|
145
655
|
}
|
|
146
656
|
}
|
|
147
657
|
//# sourceMappingURL=interactive.js.map
|