@fiete/drift 1.0.1 → 1.0.2
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 +1 -21
- package/dist/{Browser-6EKJ6MS7.js → Browser-UPOZVAV4.js} +160 -18
- package/dist/index.js +8 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,25 +13,9 @@ npm install -g @brechtknecht/drift
|
|
|
13
13
|
```bash
|
|
14
14
|
drift # open the TUI browser
|
|
15
15
|
drift add # add a new command interactively
|
|
16
|
-
drift add "git log --oneline -20"
|
|
17
|
-
drift add "cd /project && npm run dev" --desc "Start dev" --tags "node,dev" # save without TUI
|
|
18
|
-
drift remove <id>
|
|
16
|
+
drift add "git log --oneline -20" # pre-fill the command
|
|
19
17
|
```
|
|
20
18
|
|
|
21
|
-
## TUI controls
|
|
22
|
-
|
|
23
|
-
| Key | Action |
|
|
24
|
-
|-----|--------|
|
|
25
|
-
| `↑` / `↓` | Navigate |
|
|
26
|
-
| `Shift+↑` / `Shift+↓` | Reorder |
|
|
27
|
-
| `/` | Enter search mode |
|
|
28
|
-
| `ESC` | Exit search / quit |
|
|
29
|
-
| `Enter` | Execute command |
|
|
30
|
-
| `Ctrl+E` | Copy to clipboard |
|
|
31
|
-
| `a` | Add new command |
|
|
32
|
-
| `e` | Edit selected |
|
|
33
|
-
| `Ctrl+D` | Delete selected |
|
|
34
|
-
|
|
35
19
|
## Commands
|
|
36
20
|
|
|
37
21
|
Each saved command has:
|
|
@@ -40,10 +24,6 @@ Each saved command has:
|
|
|
40
24
|
- **Directory** *(optional)* — when set, executing or copying prepends `cd <dir> &&`
|
|
41
25
|
- **Tags** — for organisation and search
|
|
42
26
|
|
|
43
|
-
## Search
|
|
44
|
-
|
|
45
|
-
Press `/` to enter search mode. Fuzzy search across command text, description, and tags. Press `Enter` to lock the filter, `ESC` to clear.
|
|
46
|
-
|
|
47
27
|
## Data
|
|
48
28
|
|
|
49
29
|
Commands are stored at `~/.config/drift/commands.json`.
|
|
@@ -6,8 +6,8 @@ import {
|
|
|
6
6
|
import "./chunk-V6RC35DD.js";
|
|
7
7
|
|
|
8
8
|
// src/ui/Browser.tsx
|
|
9
|
-
import { useState as
|
|
10
|
-
import { Box as
|
|
9
|
+
import { useState as useState3, useCallback as useCallback2 } from "react";
|
|
10
|
+
import { Box as Box7, Text as Text7, useInput as useInput4, useApp } from "ink";
|
|
11
11
|
import clipboard from "clipboardy";
|
|
12
12
|
|
|
13
13
|
// src/hooks/useFuzzySearch.ts
|
|
@@ -226,19 +226,158 @@ function EditModal({ command, onSave, onCancel }) {
|
|
|
226
226
|
);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
// src/ui/
|
|
229
|
+
// src/ui/Onboarding.tsx
|
|
230
|
+
import { useEffect, useRef, useState as useState2 } from "react";
|
|
231
|
+
import { Box as Box6, Text as Text6, useInput as useInput3 } from "ink";
|
|
230
232
|
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
233
|
+
var W = 64;
|
|
234
|
+
var H = 20;
|
|
235
|
+
var PATHS = [
|
|
236
|
+
// 1 — bottom-center → sweep hard left → exit top-right (original)
|
|
237
|
+
[{ x: 0.45, y: 1.12 }, { x: 0.45, y: 0.55 }, { x: 0.1, y: 0.12 }, { x: 1.12, y: 0.1 }],
|
|
238
|
+
// 2 — bottom-right → wide left arc → exit left-middle
|
|
239
|
+
[{ x: 0.8, y: 1.12 }, { x: 0.8, y: 0.45 }, { x: 0.15, y: 0.18 }, { x: -0.12, y: 0.45 }],
|
|
240
|
+
// 3 — bottom-left → arc right → exit top-right (mirror of 1)
|
|
241
|
+
[{ x: 0.22, y: 1.12 }, { x: 0.22, y: 0.55 }, { x: 0.88, y: 0.12 }, { x: 1.12, y: 0.1 }],
|
|
242
|
+
// 4 — left-middle → sweep right across → exit top-right
|
|
243
|
+
[{ x: -0.12, y: 0.7 }, { x: 0.22, y: 0.7 }, { x: 0.78, y: 0.12 }, { x: 1.12, y: 0.12 }],
|
|
244
|
+
// 5 — right-middle → sweep left across → exit top-left
|
|
245
|
+
[{ x: 1.12, y: 0.7 }, { x: 0.78, y: 0.7 }, { x: 0.22, y: 0.12 }, { x: -0.12, y: 0.12 }]
|
|
246
|
+
];
|
|
247
|
+
function bezier(bp, t) {
|
|
248
|
+
const m = 1 - t;
|
|
249
|
+
return {
|
|
250
|
+
x: m * m * m * bp[0].x + 3 * m * m * t * bp[1].x + 3 * m * t * t * bp[2].x + t * t * t * bp[3].x,
|
|
251
|
+
y: m * m * m * bp[0].y + 3 * m * m * t * bp[1].y + 3 * m * t * t * bp[2].y + t * t * t * bp[3].y
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function bezierAngleDeg(bp, t) {
|
|
255
|
+
const d = 1e-3;
|
|
256
|
+
const a = bezier(bp, Math.max(0, t - d));
|
|
257
|
+
const b = bezier(bp, Math.min(1, t + d));
|
|
258
|
+
return Math.atan2(b.y - a.y, b.x - a.x) * 180 / Math.PI;
|
|
259
|
+
}
|
|
260
|
+
var SKID_CH = ["\u2500", "\u2572", "\u2502", "\u2571", "\u2500", "\u2572", "\u2502", "\u2571"];
|
|
261
|
+
var SMOKE_CH = ["\u2217", "\xB0", "\xB7", "`", "'", "~", "\u2218", "*"];
|
|
262
|
+
var DURATION = 4200;
|
|
263
|
+
var PAUSE = 900;
|
|
264
|
+
var TRAIL_DT = 0.016;
|
|
265
|
+
var DRIFT_DEG = 24;
|
|
266
|
+
function spawnTrail(marks, smokes, carX, carY, bodyDeg) {
|
|
267
|
+
const bRad = bodyDeg * Math.PI / 180;
|
|
268
|
+
const rearX = carX - Math.cos(bRad) * 3.5;
|
|
269
|
+
const rearY = carY - Math.sin(bRad) * 3.5;
|
|
270
|
+
const pRad = bRad + Math.PI / 2;
|
|
271
|
+
const ws = 1.8;
|
|
272
|
+
const di = Math.round((bodyDeg % 360 + 360) % 360 / 45) % 8;
|
|
273
|
+
const sk = SKID_CH[di];
|
|
274
|
+
marks.push({ x: rearX + Math.cos(pRad) * ws, y: rearY + Math.sin(pRad) * ws, ch: sk, age: 0, maxAge: 90 });
|
|
275
|
+
marks.push({ x: rearX - Math.cos(pRad) * ws, y: rearY - Math.sin(pRad) * ws, ch: sk, age: 0, maxAge: 90 });
|
|
276
|
+
const count = Math.random() < 0.5 ? 1 : 2;
|
|
277
|
+
for (let i = 0; i < count; i++) {
|
|
278
|
+
smokes.push({
|
|
279
|
+
x: rearX + (Math.random() - 0.5) * 5,
|
|
280
|
+
y: rearY + (Math.random() - 0.5) * 2.5,
|
|
281
|
+
ch: SMOKE_CH[Math.floor(Math.random() * SMOKE_CH.length)],
|
|
282
|
+
age: 0,
|
|
283
|
+
maxAge: 28
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
function renderFrame(marks, smokes) {
|
|
288
|
+
const buf = Array.from({ length: H }, () => new Array(W).fill(" "));
|
|
289
|
+
function blit(x, y, ch) {
|
|
290
|
+
const xi = Math.round(x), yi = Math.round(y);
|
|
291
|
+
if (xi >= 0 && xi < W && yi >= 0 && yi < H) buf[yi][xi] = ch;
|
|
292
|
+
}
|
|
293
|
+
for (const m of marks) {
|
|
294
|
+
const fade = m.age / m.maxAge;
|
|
295
|
+
if (fade < 0.5) blit(m.x, m.y, m.ch);
|
|
296
|
+
else if (fade < 0.75) blit(m.x, m.y, "\xB7");
|
|
297
|
+
else blit(m.x, m.y, ".");
|
|
298
|
+
}
|
|
299
|
+
for (const s of smokes) {
|
|
300
|
+
if (s.age / s.maxAge < 0.65) blit(s.x, s.y, s.ch);
|
|
301
|
+
}
|
|
302
|
+
return buf.map((row) => row.join("")).join("\n");
|
|
303
|
+
}
|
|
304
|
+
function Onboarding({ onDismiss }) {
|
|
305
|
+
const [canvas, setCanvas] = useState2(() => Array(H).fill(" ".repeat(W)).join("\n"));
|
|
306
|
+
const anim = useRef({
|
|
307
|
+
marks: [],
|
|
308
|
+
smokes: [],
|
|
309
|
+
lastTrail: -1,
|
|
310
|
+
startTime: Date.now(),
|
|
311
|
+
pathIndex: 0,
|
|
312
|
+
cycleCount: 0
|
|
313
|
+
});
|
|
314
|
+
useInput3(() => {
|
|
315
|
+
onDismiss();
|
|
316
|
+
});
|
|
317
|
+
useEffect(() => {
|
|
318
|
+
const id = setInterval(() => {
|
|
319
|
+
const s = anim.current;
|
|
320
|
+
const elapsed = Date.now() - s.startTime;
|
|
321
|
+
const cycle = elapsed % (DURATION + PAUSE);
|
|
322
|
+
if (cycle < 40) {
|
|
323
|
+
s.cycleCount++;
|
|
324
|
+
s.pathIndex = s.cycleCount % PATHS.length;
|
|
325
|
+
s.lastTrail = -1;
|
|
326
|
+
}
|
|
327
|
+
const bp = PATHS[s.pathIndex];
|
|
328
|
+
const T = cycle < DURATION ? cycle / DURATION : 1;
|
|
329
|
+
const pos = bezier(bp, T);
|
|
330
|
+
const velDeg = bezierAngleDeg(bp, T);
|
|
331
|
+
const bodyDeg = velDeg + DRIFT_DEG;
|
|
332
|
+
if (T < 0.99 && T - s.lastTrail >= TRAIL_DT) {
|
|
333
|
+
s.lastTrail = T;
|
|
334
|
+
spawnTrail(s.marks, s.smokes, pos.x * W, pos.y * H, bodyDeg);
|
|
335
|
+
}
|
|
336
|
+
for (const m of s.marks) m.age++;
|
|
337
|
+
for (const sm of s.smokes) sm.age++;
|
|
338
|
+
s.marks = s.marks.filter((m) => m.age < m.maxAge);
|
|
339
|
+
s.smokes = s.smokes.filter((sm) => sm.age < sm.maxAge);
|
|
340
|
+
setCanvas(renderFrame(s.marks, s.smokes));
|
|
341
|
+
}, 40);
|
|
342
|
+
return () => clearInterval(id);
|
|
343
|
+
}, []);
|
|
344
|
+
const termH = process.stdout.rows ?? 24;
|
|
345
|
+
return /* @__PURE__ */ jsx6(Box6, { height: termH, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs6(
|
|
346
|
+
Box6,
|
|
347
|
+
{
|
|
348
|
+
borderStyle: "round",
|
|
349
|
+
borderColor: "white",
|
|
350
|
+
flexDirection: "column",
|
|
351
|
+
paddingX: 2,
|
|
352
|
+
paddingY: 1,
|
|
353
|
+
width: W + 8,
|
|
354
|
+
children: [
|
|
355
|
+
/* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: [
|
|
356
|
+
/* @__PURE__ */ jsx6(Text6, { bold: true, children: " drift " }),
|
|
357
|
+
/* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "your commands. always within reach." })
|
|
358
|
+
] }),
|
|
359
|
+
/* @__PURE__ */ jsx6(Box6, { width: W, children: /* @__PURE__ */ jsx6(Text6, { children: canvas }) }),
|
|
360
|
+
/* @__PURE__ */ jsx6(Box6, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { dimColor: true, children: "press any key to continue" }) })
|
|
361
|
+
]
|
|
362
|
+
}
|
|
363
|
+
) });
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// src/ui/Browser.tsx
|
|
367
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
231
368
|
var HEADER_ROWS = 4;
|
|
232
369
|
var FOOTER_ROWS = 3;
|
|
233
|
-
function Browser({ onExecute }) {
|
|
370
|
+
function Browser({ onExecute, forceOnboarding = false }) {
|
|
234
371
|
const { exit } = useApp();
|
|
235
372
|
const { commands, updateCommand, moveCommand, removeCommand } = useStore();
|
|
236
|
-
const [query, setQuery] =
|
|
237
|
-
const [isSearching, setIsSearching] =
|
|
238
|
-
const [selectedIndex, setSelectedIndex] =
|
|
239
|
-
const [viewportStart, setViewportStart] =
|
|
240
|
-
const [screen, setScreen] =
|
|
241
|
-
|
|
373
|
+
const [query, setQuery] = useState3("");
|
|
374
|
+
const [isSearching, setIsSearching] = useState3(false);
|
|
375
|
+
const [selectedIndex, setSelectedIndex] = useState3(0);
|
|
376
|
+
const [viewportStart, setViewportStart] = useState3(0);
|
|
377
|
+
const [screen, setScreen] = useState3(
|
|
378
|
+
() => forceOnboarding || commands.length === 0 ? "onboarding" : "browser"
|
|
379
|
+
);
|
|
380
|
+
const [statusMessage, setStatusMessage] = useState3("");
|
|
242
381
|
const filtered = useFuzzySearch(commands, query);
|
|
243
382
|
const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
|
|
244
383
|
const selected = filtered[clampedIndex] ?? null;
|
|
@@ -262,7 +401,7 @@ function Browser({ onExecute }) {
|
|
|
262
401
|
},
|
|
263
402
|
[filtered.length, visibleRows]
|
|
264
403
|
);
|
|
265
|
-
|
|
404
|
+
useInput4(
|
|
266
405
|
(input, key) => {
|
|
267
406
|
if (screen === "confirm-delete") return;
|
|
268
407
|
if (isSearching) {
|
|
@@ -357,8 +496,11 @@ function Browser({ onExecute }) {
|
|
|
357
496
|
},
|
|
358
497
|
{ isActive: screen === "browser" }
|
|
359
498
|
);
|
|
499
|
+
if (screen === "onboarding") {
|
|
500
|
+
return /* @__PURE__ */ jsx7(Onboarding, { onDismiss: () => setScreen("browser") });
|
|
501
|
+
}
|
|
360
502
|
if (screen === "add") {
|
|
361
|
-
return /* @__PURE__ */
|
|
503
|
+
return /* @__PURE__ */ jsx7(
|
|
362
504
|
AddForm,
|
|
363
505
|
{
|
|
364
506
|
onSave: () => {
|
|
@@ -370,9 +512,9 @@ function Browser({ onExecute }) {
|
|
|
370
512
|
);
|
|
371
513
|
}
|
|
372
514
|
const isOverlay = screen === "confirm-delete" || screen === "edit";
|
|
373
|
-
return /* @__PURE__ */
|
|
374
|
-
/* @__PURE__ */
|
|
375
|
-
/* @__PURE__ */
|
|
515
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", height: process.stdout.rows ?? 24, children: [
|
|
516
|
+
/* @__PURE__ */ jsx7(SearchBar, { query, isSearching, total: commands.length, filtered: filtered.length }),
|
|
517
|
+
/* @__PURE__ */ jsx7(Box7, { flexDirection: "column", flexGrow: 1, children: filtered.length === 0 ? /* @__PURE__ */ jsx7(Box7, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: commands.length === 0 ? "No commands saved yet. Press `a` to add your first command." : "No matches. Keep typing or clear the search." }) }) : visibleCommands.map((cmd, i) => /* @__PURE__ */ jsx7(
|
|
376
518
|
CommandItem,
|
|
377
519
|
{
|
|
378
520
|
command: cmd,
|
|
@@ -381,7 +523,7 @@ function Browser({ onExecute }) {
|
|
|
381
523
|
},
|
|
382
524
|
cmd.id
|
|
383
525
|
)) }),
|
|
384
|
-
screen === "edit" && selected && /* @__PURE__ */
|
|
526
|
+
screen === "edit" && selected && /* @__PURE__ */ jsx7(
|
|
385
527
|
EditModal,
|
|
386
528
|
{
|
|
387
529
|
command: selected,
|
|
@@ -393,7 +535,7 @@ function Browser({ onExecute }) {
|
|
|
393
535
|
onCancel: () => setScreen("browser")
|
|
394
536
|
}
|
|
395
537
|
),
|
|
396
|
-
screen === "confirm-delete" && selected && /* @__PURE__ */
|
|
538
|
+
screen === "confirm-delete" && selected && /* @__PURE__ */ jsx7(
|
|
397
539
|
ConfirmDelete,
|
|
398
540
|
{
|
|
399
541
|
command: selected,
|
|
@@ -406,7 +548,7 @@ function Browser({ onExecute }) {
|
|
|
406
548
|
onCancel: () => setScreen("browser")
|
|
407
549
|
}
|
|
408
550
|
),
|
|
409
|
-
/* @__PURE__ */
|
|
551
|
+
/* @__PURE__ */ jsx7(StatusBar, { message: statusMessage })
|
|
410
552
|
] });
|
|
411
553
|
}
|
|
412
554
|
export {
|
package/dist/index.js
CHANGED
|
@@ -6,13 +6,16 @@ import { render } from "ink";
|
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { execa } from "execa";
|
|
8
8
|
program.name("drift").description("cmdvault \u2014 A TUI command vault for your shell").version("1.0.0");
|
|
9
|
-
program.command("list", { isDefault: true }).description("Browse saved commands (default)").action(async () => {
|
|
10
|
-
const { Browser } = await import("./Browser-
|
|
9
|
+
program.command("list", { isDefault: true }).description("Browse saved commands (default)").option("--onboarding", "Force the onboarding screen (debug)").action(async (opts) => {
|
|
10
|
+
const { Browser } = await import("./Browser-UPOZVAV4.js");
|
|
11
11
|
let commandToRun;
|
|
12
12
|
const { waitUntilExit } = render(
|
|
13
|
-
React.createElement(Browser, {
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
React.createElement(Browser, {
|
|
14
|
+
onExecute: (cmd) => {
|
|
15
|
+
commandToRun = cmd;
|
|
16
|
+
},
|
|
17
|
+
forceOnboarding: opts.onboarding ?? false
|
|
18
|
+
})
|
|
16
19
|
);
|
|
17
20
|
await waitUntilExit();
|
|
18
21
|
process.stdout.write("\x1Bc");
|