@fiete/drift 1.0.5 → 1.0.6
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/dist/{AddForm-NVOWLCPV.js → AddForm-ZMSCP6CU.js} +2 -2
- package/dist/{Browser-6Q6XF5D4.js → Browser-67UFH2PS.js} +327 -95
- package/dist/{FileStore-5LGMICIM.js → FileStore-DV2QBZJL.js} +1 -1
- package/dist/checkUpdate-G4I5BZTU.js +30 -0
- package/dist/{chunk-CRXU4OLG.js → chunk-SRTEOAGN.js} +44 -3
- package/dist/chunk-TBVOGPA4.js +233 -0
- package/dist/index.js +64 -12
- package/package.json +1 -1
- package/dist/chunk-GUH6BVED.js +0 -154
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
AddForm,
|
|
4
|
+
ProjectSelect,
|
|
4
5
|
useStore
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-TBVOGPA4.js";
|
|
7
|
+
import "./chunk-SRTEOAGN.js";
|
|
7
8
|
|
|
8
9
|
// src/ui/Browser.tsx
|
|
9
|
-
import { useState as
|
|
10
|
-
import { Box as
|
|
10
|
+
import { useState as useState4, useCallback as useCallback3 } from "react";
|
|
11
|
+
import { Box as Box11, Text as Text11, useInput as useInput6, useApp as useApp2 } from "ink";
|
|
11
12
|
import clipboard from "clipboardy";
|
|
12
13
|
|
|
13
14
|
// src/hooks/useFuzzySearch.ts
|
|
@@ -82,6 +83,7 @@ function CommandItem({ command, isSelected, dimmed = false }) {
|
|
|
82
83
|
import { Box as Box3, Text as Text3 } from "ink";
|
|
83
84
|
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
84
85
|
var HINTS = [
|
|
86
|
+
["\u2190\u2192", "tabs"],
|
|
85
87
|
["\u2191\u2193", "navigate"],
|
|
86
88
|
["Shift+\u2191\u2193", "reorder"],
|
|
87
89
|
["/", "search"],
|
|
@@ -94,24 +96,44 @@ var HINTS = [
|
|
|
94
96
|
];
|
|
95
97
|
function StatusBar({ message }) {
|
|
96
98
|
return /* @__PURE__ */ jsxs3(Box3, { borderStyle: "single", borderColor: "gray", paddingX: 1, justifyContent: "space-between", children: [
|
|
97
|
-
/* @__PURE__ */ jsx3(Box3, { gap:
|
|
99
|
+
/* @__PURE__ */ jsx3(Box3, { gap: 0, children: HINTS.map(([key, label], i) => /* @__PURE__ */ jsxs3(Box3, { children: [
|
|
100
|
+
i > 0 && /* @__PURE__ */ jsx3(Text3, { color: "gray", dimColor: true, children: " \u2502 " }),
|
|
98
101
|
/* @__PURE__ */ jsx3(Text3, { bold: true, color: "yellow", children: key }),
|
|
99
|
-
/* @__PURE__ */
|
|
102
|
+
/* @__PURE__ */ jsxs3(Text3, { color: "gray", children: [
|
|
103
|
+
" ",
|
|
104
|
+
label
|
|
105
|
+
] })
|
|
100
106
|
] }, key)) }),
|
|
101
107
|
message && /* @__PURE__ */ jsx3(Text3, { color: "greenBright", bold: true, children: message })
|
|
102
108
|
] });
|
|
103
109
|
}
|
|
104
110
|
|
|
105
|
-
// src/ui/
|
|
106
|
-
import { Box as Box4, Text as Text4
|
|
111
|
+
// src/ui/TabBar.tsx
|
|
112
|
+
import { Box as Box4, Text as Text4 } from "ink";
|
|
107
113
|
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
114
|
+
function TabBar({ projects, activeIndex }) {
|
|
115
|
+
const tabs = ["All", ...projects.map((p) => p.name)];
|
|
116
|
+
const newTabIndex = tabs.length;
|
|
117
|
+
return /* @__PURE__ */ jsxs4(Box4, { paddingX: 1, paddingY: 0, gap: 0, children: [
|
|
118
|
+
tabs.map((tab, i) => /* @__PURE__ */ jsx4(Box4, { paddingX: 1, children: i === activeIndex ? /* @__PURE__ */ jsxs4(Text4, { bold: true, color: "blueBright", children: [
|
|
119
|
+
"[",
|
|
120
|
+
tab,
|
|
121
|
+
"]"
|
|
122
|
+
] }) : /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: tab }) }, i)),
|
|
123
|
+
/* @__PURE__ */ jsx4(Box4, { paddingX: 1, children: activeIndex === newTabIndex ? /* @__PURE__ */ jsx4(Text4, { bold: true, color: "greenBright", children: "[+]" }) : /* @__PURE__ */ jsx4(Text4, { color: "gray", dimColor: true, children: "[+]" }) })
|
|
124
|
+
] });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// src/ui/ConfirmDelete.tsx
|
|
128
|
+
import { Box as Box5, Text as Text5, useInput } from "ink";
|
|
129
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
108
130
|
function ConfirmDelete({ command, onConfirm, onCancel }) {
|
|
109
131
|
useInput((input, key) => {
|
|
110
132
|
if (input === "y" || input === "Y") onConfirm();
|
|
111
133
|
if (input === "n" || input === "N" || key.escape) onCancel();
|
|
112
134
|
});
|
|
113
|
-
return /* @__PURE__ */
|
|
114
|
-
|
|
135
|
+
return /* @__PURE__ */ jsxs5(
|
|
136
|
+
Box5,
|
|
115
137
|
{
|
|
116
138
|
borderStyle: "round",
|
|
117
139
|
borderColor: "red",
|
|
@@ -121,41 +143,111 @@ function ConfirmDelete({ command, onConfirm, onCancel }) {
|
|
|
121
143
|
marginX: 2,
|
|
122
144
|
marginY: 1,
|
|
123
145
|
children: [
|
|
124
|
-
/* @__PURE__ */
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */
|
|
129
|
-
/* @__PURE__ */
|
|
130
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ jsx5(Text5, { color: "red", bold: true, children: "Delete command?" }),
|
|
147
|
+
/* @__PURE__ */ jsx5(Box5, { marginTop: 1, children: /* @__PURE__ */ jsx5(Text5, { color: "gray", dimColor: true, children: command.command }) }),
|
|
148
|
+
/* @__PURE__ */ jsxs5(Box5, { marginTop: 1, gap: 2, children: [
|
|
149
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "red", children: "[y]" }),
|
|
150
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "yes, delete it" }),
|
|
151
|
+
/* @__PURE__ */ jsx5(Text5, { bold: true, color: "white", children: " [n]" }),
|
|
152
|
+
/* @__PURE__ */ jsx5(Text5, { color: "gray", children: "cancel" })
|
|
131
153
|
] })
|
|
132
154
|
]
|
|
133
155
|
}
|
|
134
156
|
);
|
|
135
157
|
}
|
|
136
158
|
|
|
137
|
-
// src/ui/
|
|
159
|
+
// src/ui/AddProjectForm.tsx
|
|
138
160
|
import { useState, useCallback } from "react";
|
|
139
|
-
import { Box as
|
|
161
|
+
import { Box as Box6, Text as Text6, useApp, useInput as useInput2 } from "ink";
|
|
140
162
|
import { TextInput } from "@inkjs/ui";
|
|
141
|
-
import { jsx as
|
|
142
|
-
var FIELD_ORDER = ["
|
|
163
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
164
|
+
var FIELD_ORDER = ["name", "root"];
|
|
165
|
+
function AddProjectForm({ onSave, onCancel }) {
|
|
166
|
+
const { exit } = useApp();
|
|
167
|
+
const [fieldIndex, setFieldIndex] = useState(0);
|
|
168
|
+
const field = FIELD_ORDER[fieldIndex];
|
|
169
|
+
const [formData, setFormData] = useState({ name: "", root: "" });
|
|
170
|
+
useInput2((_, key) => {
|
|
171
|
+
if (key.escape) {
|
|
172
|
+
onCancel();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
if (key.upArrow) {
|
|
176
|
+
setFieldIndex((i) => Math.max(0, i - 1));
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (key.downArrow) {
|
|
180
|
+
setFieldIndex((i) => Math.min(FIELD_ORDER.length - 1, i + 1));
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
const handleChange = useCallback((value) => {
|
|
185
|
+
setFormData((d) => ({ ...d, [field]: value }));
|
|
186
|
+
}, [field]);
|
|
187
|
+
const handleSubmit = (value) => {
|
|
188
|
+
const updated = { ...formData, [field]: value };
|
|
189
|
+
setFormData(updated);
|
|
190
|
+
if (fieldIndex < FIELD_ORDER.length - 1) {
|
|
191
|
+
setFieldIndex((i) => i + 1);
|
|
192
|
+
} else {
|
|
193
|
+
if (!updated.name.trim()) {
|
|
194
|
+
setFieldIndex(0);
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
onSave(updated.name.trim(), updated.root.trim());
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", padding: 1, gap: 1, children: [
|
|
201
|
+
/* @__PURE__ */ jsx6(Box6, { marginBottom: 1, children: /* @__PURE__ */ jsx6(Text6, { bold: true, color: "blueBright", children: " New Project" }) }),
|
|
202
|
+
[["name", "Name", "e.g. Backend"], ["root", "Root Directory", "e.g. /Users/me/project"]].map(([f, label, placeholder], i) => {
|
|
203
|
+
const isActive = i === fieldIndex;
|
|
204
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
205
|
+
/* @__PURE__ */ jsxs6(Text6, { color: isActive ? "yellowBright" : "gray", children: [
|
|
206
|
+
isActive ? "\u203A" : " ",
|
|
207
|
+
" ",
|
|
208
|
+
label
|
|
209
|
+
] }),
|
|
210
|
+
/* @__PURE__ */ jsx6(Box6, { paddingLeft: 2, children: isActive ? /* @__PURE__ */ jsx6(
|
|
211
|
+
TextInput,
|
|
212
|
+
{
|
|
213
|
+
placeholder,
|
|
214
|
+
defaultValue: formData[f],
|
|
215
|
+
onChange: handleChange,
|
|
216
|
+
onSubmit: handleSubmit
|
|
217
|
+
},
|
|
218
|
+
f
|
|
219
|
+
) : /* @__PURE__ */ jsx6(Text6, { color: formData[f] ? "white" : "gray", dimColor: !formData[f], children: formData[f] || placeholder }) })
|
|
220
|
+
] }, f);
|
|
221
|
+
}),
|
|
222
|
+
/* @__PURE__ */ jsx6(Box6, { marginTop: 1, children: /* @__PURE__ */ jsx6(Text6, { color: "gray", dimColor: true, children: "\u2191\u2193 navigate \xB7 Enter to advance \xB7 ESC to cancel" }) })
|
|
223
|
+
] });
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// src/ui/EditModal.tsx
|
|
227
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
228
|
+
import { Box as Box7, Text as Text7, useInput as useInput3 } from "ink";
|
|
229
|
+
import { TextInput as TextInput2 } from "@inkjs/ui";
|
|
230
|
+
import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
231
|
+
var FIELD_ORDER2 = ["description", "command", "directory", "tags", "project"];
|
|
143
232
|
var LABELS = {
|
|
144
233
|
description: "Description",
|
|
145
234
|
command: "Command",
|
|
146
235
|
directory: "Directory (optional)",
|
|
147
|
-
tags: "Tags (comma-separated)"
|
|
236
|
+
tags: "Tags (comma-separated)",
|
|
237
|
+
project: "Project"
|
|
148
238
|
};
|
|
149
|
-
function EditModal({ command, onSave, onCancel }) {
|
|
150
|
-
const [fieldIndex, setFieldIndex] =
|
|
151
|
-
const field =
|
|
152
|
-
const [formData, setFormData] =
|
|
239
|
+
function EditModal({ command, projects, onSave, onCancel }) {
|
|
240
|
+
const [fieldIndex, setFieldIndex] = useState2(0);
|
|
241
|
+
const field = FIELD_ORDER2[fieldIndex];
|
|
242
|
+
const [formData, setFormData] = useState2({
|
|
153
243
|
description: command.description,
|
|
154
244
|
command: command.command,
|
|
155
245
|
directory: command.directory ?? "",
|
|
156
|
-
tags: command.tags.join(", ")
|
|
246
|
+
tags: command.tags.join(", "),
|
|
247
|
+
projectId: command.projectId
|
|
157
248
|
});
|
|
158
|
-
|
|
249
|
+
const projectOptions = [void 0, ...projects.map((p) => p.id)];
|
|
250
|
+
useInput3((_, key) => {
|
|
159
251
|
if (key.escape) {
|
|
160
252
|
onCancel();
|
|
161
253
|
return;
|
|
@@ -165,29 +257,47 @@ function EditModal({ command, onSave, onCancel }) {
|
|
|
165
257
|
return;
|
|
166
258
|
}
|
|
167
259
|
if (key.downArrow) {
|
|
168
|
-
setFieldIndex((i) => Math.min(
|
|
260
|
+
setFieldIndex((i) => Math.min(FIELD_ORDER2.length - 1, i + 1));
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (field === "project" && (key.leftArrow || key.rightArrow)) {
|
|
264
|
+
const currentIdx = projectOptions.findIndex((id) => id === formData.projectId);
|
|
265
|
+
const safeIdx = currentIdx === -1 ? 0 : currentIdx;
|
|
266
|
+
const next = key.rightArrow ? Math.min(projectOptions.length - 1, safeIdx + 1) : Math.max(0, safeIdx - 1);
|
|
267
|
+
setFormData((d) => ({ ...d, projectId: projectOptions[next] }));
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
if (field === "project" && key.return) {
|
|
271
|
+
onSave({
|
|
272
|
+
command: formData.command.trim(),
|
|
273
|
+
description: formData.description.trim(),
|
|
274
|
+
directory: formData.directory.trim() || void 0,
|
|
275
|
+
tags: formData.tags.split(",").map((t) => t.trim()).filter(Boolean),
|
|
276
|
+
projectId: formData.projectId
|
|
277
|
+
});
|
|
169
278
|
return;
|
|
170
279
|
}
|
|
171
280
|
});
|
|
172
|
-
const handleChange =
|
|
281
|
+
const handleChange = useCallback2((value) => {
|
|
173
282
|
setFormData((d) => ({ ...d, [field]: value }));
|
|
174
283
|
}, [field]);
|
|
175
284
|
const handleSubmit = (value) => {
|
|
176
285
|
const updated = { ...formData, [field]: value };
|
|
177
286
|
setFormData(updated);
|
|
178
|
-
if (fieldIndex <
|
|
287
|
+
if (fieldIndex < FIELD_ORDER2.length - 1) {
|
|
179
288
|
setFieldIndex((i) => i + 1);
|
|
180
289
|
} else {
|
|
181
290
|
onSave({
|
|
182
291
|
command: updated.command.trim(),
|
|
183
292
|
description: updated.description.trim(),
|
|
184
293
|
directory: updated.directory.trim() || void 0,
|
|
185
|
-
tags: updated.tags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
294
|
+
tags: updated.tags.split(",").map((t) => t.trim()).filter(Boolean),
|
|
295
|
+
projectId: updated.projectId
|
|
186
296
|
});
|
|
187
297
|
}
|
|
188
298
|
};
|
|
189
|
-
return /* @__PURE__ */
|
|
190
|
-
|
|
299
|
+
return /* @__PURE__ */ jsxs7(
|
|
300
|
+
Box7,
|
|
191
301
|
{
|
|
192
302
|
borderStyle: "round",
|
|
193
303
|
borderColor: "yellowBright",
|
|
@@ -197,51 +307,60 @@ function EditModal({ command, onSave, onCancel }) {
|
|
|
197
307
|
marginX: 2,
|
|
198
308
|
marginY: 1,
|
|
199
309
|
children: [
|
|
200
|
-
/* @__PURE__ */
|
|
201
|
-
/* @__PURE__ */
|
|
202
|
-
/* @__PURE__ */
|
|
310
|
+
/* @__PURE__ */ jsxs7(Box7, { marginBottom: 1, justifyContent: "space-between", children: [
|
|
311
|
+
/* @__PURE__ */ jsx7(Text7, { bold: true, color: "yellowBright", children: " Edit Command" }),
|
|
312
|
+
/* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: "ESC to cancel" })
|
|
203
313
|
] }),
|
|
204
|
-
|
|
314
|
+
FIELD_ORDER2.map((f, i) => {
|
|
205
315
|
const isActive = i === fieldIndex;
|
|
206
|
-
return /* @__PURE__ */
|
|
207
|
-
/* @__PURE__ */
|
|
316
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
317
|
+
/* @__PURE__ */ jsxs7(Text7, { color: isActive ? "yellowBright" : "gray", children: [
|
|
208
318
|
isActive ? "\u203A" : " ",
|
|
209
319
|
" ",
|
|
210
320
|
LABELS[f]
|
|
211
321
|
] }),
|
|
212
|
-
/* @__PURE__ */
|
|
213
|
-
|
|
322
|
+
/* @__PURE__ */ jsx7(Box7, { paddingLeft: 2, children: f === "project" ? /* @__PURE__ */ jsx7(
|
|
323
|
+
ProjectSelect,
|
|
324
|
+
{
|
|
325
|
+
projects,
|
|
326
|
+
selectedProjectId: formData.projectId,
|
|
327
|
+
isActive
|
|
328
|
+
}
|
|
329
|
+
) : isActive ? /* @__PURE__ */ jsx7(
|
|
330
|
+
TextInput2,
|
|
214
331
|
{
|
|
215
|
-
defaultValue: formData[f],
|
|
332
|
+
defaultValue: formData[f] ?? "",
|
|
216
333
|
onChange: handleChange,
|
|
217
334
|
onSubmit: handleSubmit
|
|
218
335
|
},
|
|
219
336
|
f
|
|
220
|
-
) : /* @__PURE__ */
|
|
337
|
+
) : /* @__PURE__ */ jsx7(Text7, { color: "gray", dimColor: true, children: formData[f] || "(empty)" }) })
|
|
221
338
|
] }, f);
|
|
222
339
|
}),
|
|
223
|
-
/* @__PURE__ */
|
|
340
|
+
/* @__PURE__ */ jsx7(Box7, { marginTop: 1, children: /* @__PURE__ */ jsxs7(Text7, { color: "gray", dimColor: true, children: [
|
|
341
|
+
"\u2191\u2193 navigate \xB7 Enter to advance \xB7 ",
|
|
342
|
+
field === "project" ? "\u2190\u2192 select project \xB7 " : "",
|
|
343
|
+
"saves on last field"
|
|
344
|
+
] }) })
|
|
224
345
|
]
|
|
225
346
|
}
|
|
226
347
|
);
|
|
227
348
|
}
|
|
228
349
|
|
|
229
350
|
// src/ui/Onboarding.tsx
|
|
230
|
-
import {
|
|
231
|
-
|
|
232
|
-
|
|
351
|
+
import { Box as Box9, Text as Text9, useInput as useInput4 } from "ink";
|
|
352
|
+
|
|
353
|
+
// src/ui/DriftCanvas.tsx
|
|
354
|
+
import { useEffect, useRef, useState as useState3 } from "react";
|
|
355
|
+
import { Box as Box8, Text as Text8 } from "ink";
|
|
356
|
+
import { jsx as jsx8 } from "react/jsx-runtime";
|
|
233
357
|
var W = 64;
|
|
234
358
|
var H = 20;
|
|
235
359
|
var PATHS = [
|
|
236
|
-
// 1 — bottom-center → sweep hard left → exit top-right (original)
|
|
237
360
|
[{ 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
361
|
[{ 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
362
|
[{ 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
363
|
[{ 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
364
|
[{ 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
365
|
];
|
|
247
366
|
function bezier(bp, t) {
|
|
@@ -301,8 +420,8 @@ function renderFrame(marks, smokes) {
|
|
|
301
420
|
}
|
|
302
421
|
return buf.map((row) => row.join("")).join("\n");
|
|
303
422
|
}
|
|
304
|
-
function
|
|
305
|
-
const [canvas, setCanvas] =
|
|
423
|
+
function DriftCanvas() {
|
|
424
|
+
const [canvas, setCanvas] = useState3(() => Array(H).fill(" ".repeat(W)).join("\n"));
|
|
306
425
|
const anim = useRef({
|
|
307
426
|
marks: [],
|
|
308
427
|
smokes: [],
|
|
@@ -311,9 +430,6 @@ function Onboarding({ onDismiss }) {
|
|
|
311
430
|
pathIndex: 0,
|
|
312
431
|
cycleCount: 0
|
|
313
432
|
});
|
|
314
|
-
useInput3(() => {
|
|
315
|
-
onDismiss();
|
|
316
|
-
});
|
|
317
433
|
useEffect(() => {
|
|
318
434
|
const id = setInterval(() => {
|
|
319
435
|
const s = anim.current;
|
|
@@ -341,9 +457,18 @@ function Onboarding({ onDismiss }) {
|
|
|
341
457
|
}, 40);
|
|
342
458
|
return () => clearInterval(id);
|
|
343
459
|
}, []);
|
|
460
|
+
return /* @__PURE__ */ jsx8(Box8, { width: W, children: /* @__PURE__ */ jsx8(Text8, { children: canvas }) });
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// src/ui/Onboarding.tsx
|
|
464
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
465
|
+
function Onboarding({ onDismiss }) {
|
|
466
|
+
useInput4(() => {
|
|
467
|
+
onDismiss();
|
|
468
|
+
});
|
|
344
469
|
const termH = process.stdout.rows ?? 24;
|
|
345
|
-
return /* @__PURE__ */
|
|
346
|
-
|
|
470
|
+
return /* @__PURE__ */ jsx9(Box9, { height: termH, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs8(
|
|
471
|
+
Box9,
|
|
347
472
|
{
|
|
348
473
|
borderStyle: "round",
|
|
349
474
|
borderColor: "white",
|
|
@@ -352,42 +477,89 @@ function Onboarding({ onDismiss }) {
|
|
|
352
477
|
paddingY: 1,
|
|
353
478
|
width: W + 8,
|
|
354
479
|
children: [
|
|
355
|
-
/* @__PURE__ */
|
|
356
|
-
/* @__PURE__ */
|
|
357
|
-
/* @__PURE__ */
|
|
480
|
+
/* @__PURE__ */ jsxs8(Box9, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: [
|
|
481
|
+
/* @__PURE__ */ jsx9(Text9, { bold: true, children: " drift " }),
|
|
482
|
+
/* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "your commands. always within reach." })
|
|
358
483
|
] }),
|
|
359
|
-
/* @__PURE__ */
|
|
360
|
-
/* @__PURE__ */
|
|
484
|
+
/* @__PURE__ */ jsx9(DriftCanvas, {}),
|
|
485
|
+
/* @__PURE__ */ jsx9(Box9, { justifyContent: "center", marginTop: 1, children: /* @__PURE__ */ jsx9(Text9, { dimColor: true, children: "press any key to continue" }) })
|
|
486
|
+
]
|
|
487
|
+
}
|
|
488
|
+
) });
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// src/ui/UpdatePrompt.tsx
|
|
492
|
+
import { Box as Box10, Text as Text10, useInput as useInput5 } from "ink";
|
|
493
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
494
|
+
function UpdatePrompt({ currentVersion, latestVersion, onConfirm, onSkip }) {
|
|
495
|
+
useInput5((input, key) => {
|
|
496
|
+
if (input === "y" || input === "Y") onConfirm();
|
|
497
|
+
if (input === "s" || input === "S" || key.escape) onSkip();
|
|
498
|
+
});
|
|
499
|
+
const termH = process.stdout.rows ?? 24;
|
|
500
|
+
return /* @__PURE__ */ jsx10(Box10, { height: termH, alignItems: "center", justifyContent: "center", children: /* @__PURE__ */ jsxs9(
|
|
501
|
+
Box10,
|
|
502
|
+
{
|
|
503
|
+
borderStyle: "round",
|
|
504
|
+
borderColor: "white",
|
|
505
|
+
flexDirection: "column",
|
|
506
|
+
paddingX: 2,
|
|
507
|
+
paddingY: 1,
|
|
508
|
+
width: W + 8,
|
|
509
|
+
children: [
|
|
510
|
+
/* @__PURE__ */ jsxs9(Box10, { flexDirection: "column", alignItems: "center", marginBottom: 1, children: [
|
|
511
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: "update available " }),
|
|
512
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
513
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: currentVersion }),
|
|
514
|
+
/* @__PURE__ */ jsx10(Text10, { children: " \u2192 " }),
|
|
515
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: latestVersion })
|
|
516
|
+
] })
|
|
517
|
+
] }),
|
|
518
|
+
/* @__PURE__ */ jsx10(DriftCanvas, {}),
|
|
519
|
+
/* @__PURE__ */ jsxs9(Box10, { justifyContent: "center", marginTop: 1, gap: 2, children: [
|
|
520
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
521
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: "green", children: "[y]" }),
|
|
522
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " update now" })
|
|
523
|
+
] }),
|
|
524
|
+
/* @__PURE__ */ jsxs9(Text10, { children: [
|
|
525
|
+
/* @__PURE__ */ jsx10(Text10, { bold: true, color: "white", children: "[s]" }),
|
|
526
|
+
/* @__PURE__ */ jsx10(Text10, { dimColor: true, children: " skip for now" })
|
|
527
|
+
] })
|
|
528
|
+
] })
|
|
361
529
|
]
|
|
362
530
|
}
|
|
363
531
|
) });
|
|
364
532
|
}
|
|
365
533
|
|
|
366
534
|
// src/ui/Browser.tsx
|
|
367
|
-
import { jsx as
|
|
368
|
-
var HEADER_ROWS =
|
|
535
|
+
import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
|
|
536
|
+
var HEADER_ROWS = 5;
|
|
369
537
|
var FOOTER_ROWS = 3;
|
|
370
|
-
function Browser({ onExecute, forceOnboarding = false }) {
|
|
371
|
-
const { exit } =
|
|
372
|
-
const { commands, updateCommand, moveCommand, removeCommand } = useStore();
|
|
373
|
-
const [query, setQuery] =
|
|
374
|
-
const [isSearching, setIsSearching] =
|
|
375
|
-
const [selectedIndex, setSelectedIndex] =
|
|
376
|
-
const [viewportStart, setViewportStart] =
|
|
377
|
-
const [
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
538
|
+
function Browser({ onExecute, forceOnboarding = false, currentVersion, latestVersion, onUpdate }) {
|
|
539
|
+
const { exit } = useApp2();
|
|
540
|
+
const { commands, projects, addProject, updateCommand, moveCommand, removeCommand } = useStore();
|
|
541
|
+
const [query, setQuery] = useState4("");
|
|
542
|
+
const [isSearching, setIsSearching] = useState4(false);
|
|
543
|
+
const [selectedIndex, setSelectedIndex] = useState4(0);
|
|
544
|
+
const [viewportStart, setViewportStart] = useState4(0);
|
|
545
|
+
const [activeTabIndex, setActiveTabIndex] = useState4(0);
|
|
546
|
+
const [screen, setScreen] = useState4(() => {
|
|
547
|
+
if (latestVersion) return "update-prompt";
|
|
548
|
+
return forceOnboarding || commands.length === 0 ? "onboarding" : "browser";
|
|
549
|
+
});
|
|
550
|
+
const [statusMessage, setStatusMessage] = useState4("");
|
|
551
|
+
const newProjectTabIndex = 1 + projects.length;
|
|
552
|
+
const tabCommands = activeTabIndex === 0 || activeTabIndex === newProjectTabIndex ? commands : commands.filter((c) => c.projectId === projects[activeTabIndex - 1]?.id);
|
|
553
|
+
const filtered = useFuzzySearch(tabCommands, query);
|
|
382
554
|
const clampedIndex = Math.min(selectedIndex, Math.max(0, filtered.length - 1));
|
|
383
555
|
const selected = filtered[clampedIndex] ?? null;
|
|
384
556
|
const visibleRows = Math.max(1, (process.stdout.rows ?? 24) - HEADER_ROWS - FOOTER_ROWS);
|
|
385
557
|
const visibleCommands = filtered.slice(viewportStart, viewportStart + visibleRows);
|
|
386
|
-
const flash =
|
|
558
|
+
const flash = useCallback3((msg) => {
|
|
387
559
|
setStatusMessage(msg);
|
|
388
560
|
setTimeout(() => setStatusMessage(""), 2e3);
|
|
389
561
|
}, []);
|
|
390
|
-
const navigate =
|
|
562
|
+
const navigate = useCallback3(
|
|
391
563
|
(direction) => {
|
|
392
564
|
setSelectedIndex((prev) => {
|
|
393
565
|
const next = Math.max(0, Math.min(filtered.length - 1, prev + direction));
|
|
@@ -401,9 +573,26 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
401
573
|
},
|
|
402
574
|
[filtered.length, visibleRows]
|
|
403
575
|
);
|
|
404
|
-
|
|
576
|
+
const navigateTab = useCallback3(
|
|
577
|
+
(direction) => {
|
|
578
|
+
const tabCount = 1 + projects.length + 1;
|
|
579
|
+
setActiveTabIndex((prev) => Math.max(0, Math.min(tabCount - 1, prev + direction)));
|
|
580
|
+
setSelectedIndex(0);
|
|
581
|
+
setViewportStart(0);
|
|
582
|
+
},
|
|
583
|
+
[projects.length]
|
|
584
|
+
);
|
|
585
|
+
const buildExecString = useCallback3(
|
|
586
|
+
(cmd) => {
|
|
587
|
+
if (!cmd) return "";
|
|
588
|
+
const dir = cmd.directory ?? (cmd.projectId ? projects.find((p) => p.id === cmd.projectId)?.root : void 0);
|
|
589
|
+
return dir && cmd.command ? `cd "${dir}" && ${cmd.command}` : dir ? `cd "${dir}"` : cmd.command;
|
|
590
|
+
},
|
|
591
|
+
[projects]
|
|
592
|
+
);
|
|
593
|
+
useInput6(
|
|
405
594
|
(input, key) => {
|
|
406
|
-
if (screen === "confirm-delete") return;
|
|
595
|
+
if (screen === "confirm-delete" || screen === "update-prompt") return;
|
|
407
596
|
if (isSearching) {
|
|
408
597
|
if (key.escape) {
|
|
409
598
|
setQuery("");
|
|
@@ -439,6 +628,14 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
439
628
|
}
|
|
440
629
|
return;
|
|
441
630
|
}
|
|
631
|
+
if (key.leftArrow && !key.shift) {
|
|
632
|
+
navigateTab(-1);
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (key.rightArrow && !key.shift) {
|
|
636
|
+
navigateTab(1);
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
442
639
|
if (key.upArrow && key.shift && selected && !query) {
|
|
443
640
|
moveCommand(selected.id, -1);
|
|
444
641
|
navigate(-1);
|
|
@@ -469,15 +666,17 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
469
666
|
} else exit();
|
|
470
667
|
return;
|
|
471
668
|
}
|
|
669
|
+
if (key.return && activeTabIndex === 1 + projects.length) {
|
|
670
|
+
setScreen("add-project");
|
|
671
|
+
return;
|
|
672
|
+
}
|
|
472
673
|
if (key.return && selected) {
|
|
473
|
-
|
|
474
|
-
onExecute(full);
|
|
674
|
+
onExecute(buildExecString(selected));
|
|
475
675
|
exit();
|
|
476
676
|
return;
|
|
477
677
|
}
|
|
478
678
|
if (key.ctrl && input === "e" && selected) {
|
|
479
|
-
|
|
480
|
-
clipboard.writeSync(full);
|
|
679
|
+
clipboard.writeSync(buildExecString(selected));
|
|
481
680
|
flash("Copied!");
|
|
482
681
|
return;
|
|
483
682
|
}
|
|
@@ -496,13 +695,44 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
496
695
|
},
|
|
497
696
|
{ isActive: screen === "browser" }
|
|
498
697
|
);
|
|
698
|
+
if (screen === "update-prompt" && latestVersion) {
|
|
699
|
+
const nextScreen = forceOnboarding || commands.length === 0 ? "onboarding" : "browser";
|
|
700
|
+
return /* @__PURE__ */ jsx11(
|
|
701
|
+
UpdatePrompt,
|
|
702
|
+
{
|
|
703
|
+
currentVersion,
|
|
704
|
+
latestVersion,
|
|
705
|
+
onConfirm: () => {
|
|
706
|
+
onUpdate();
|
|
707
|
+
exit();
|
|
708
|
+
},
|
|
709
|
+
onSkip: () => setScreen(nextScreen)
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
}
|
|
499
713
|
if (screen === "onboarding") {
|
|
500
|
-
return /* @__PURE__ */
|
|
714
|
+
return /* @__PURE__ */ jsx11(Onboarding, { onDismiss: () => setScreen("browser") });
|
|
715
|
+
}
|
|
716
|
+
if (screen === "add-project") {
|
|
717
|
+
return /* @__PURE__ */ jsx11(
|
|
718
|
+
AddProjectForm,
|
|
719
|
+
{
|
|
720
|
+
onSave: (name, root) => {
|
|
721
|
+
addProject({ name, root });
|
|
722
|
+
setScreen("browser");
|
|
723
|
+
setActiveTabIndex(1 + projects.length);
|
|
724
|
+
flash(`Project "${name}" created.`);
|
|
725
|
+
},
|
|
726
|
+
onCancel: () => setScreen("browser")
|
|
727
|
+
}
|
|
728
|
+
);
|
|
501
729
|
}
|
|
502
730
|
if (screen === "add") {
|
|
503
|
-
|
|
731
|
+
const defaultProjectId = activeTabIndex > 0 ? projects[activeTabIndex - 1]?.id : void 0;
|
|
732
|
+
return /* @__PURE__ */ jsx11(
|
|
504
733
|
AddForm,
|
|
505
734
|
{
|
|
735
|
+
initialValues: { projectId: defaultProjectId },
|
|
506
736
|
onSave: () => {
|
|
507
737
|
setScreen("browser");
|
|
508
738
|
flash("Saved!");
|
|
@@ -512,9 +742,10 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
512
742
|
);
|
|
513
743
|
}
|
|
514
744
|
const isOverlay = screen === "confirm-delete" || screen === "edit";
|
|
515
|
-
return /* @__PURE__ */
|
|
516
|
-
/* @__PURE__ */
|
|
517
|
-
/* @__PURE__ */
|
|
745
|
+
return /* @__PURE__ */ jsxs10(Box11, { flexDirection: "column", height: process.stdout.rows ?? 24, children: [
|
|
746
|
+
/* @__PURE__ */ jsx11(TabBar, { projects, activeIndex: activeTabIndex }),
|
|
747
|
+
/* @__PURE__ */ jsx11(SearchBar, { query, isSearching, total: tabCommands.length, filtered: filtered.length }),
|
|
748
|
+
/* @__PURE__ */ jsx11(Box11, { flexDirection: "column", flexGrow: 1, children: filtered.length === 0 ? /* @__PURE__ */ jsx11(Box11, { paddingX: 2, paddingY: 1, children: /* @__PURE__ */ jsx11(Text11, { color: "gray", dimColor: true, children: tabCommands.length === 0 ? activeTabIndex === 0 ? "No commands saved yet. Press `a` to add your first command." : "No commands in this project. Press `a` to add one." : "No matches. Keep typing or clear the search." }) }) : visibleCommands.map((cmd, i) => /* @__PURE__ */ jsx11(
|
|
518
749
|
CommandItem,
|
|
519
750
|
{
|
|
520
751
|
command: cmd,
|
|
@@ -523,10 +754,11 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
523
754
|
},
|
|
524
755
|
cmd.id
|
|
525
756
|
)) }),
|
|
526
|
-
screen === "edit" && selected && /* @__PURE__ */
|
|
757
|
+
screen === "edit" && selected && /* @__PURE__ */ jsx11(
|
|
527
758
|
EditModal,
|
|
528
759
|
{
|
|
529
760
|
command: selected,
|
|
761
|
+
projects,
|
|
530
762
|
onSave: (patch) => {
|
|
531
763
|
updateCommand(selected.id, patch);
|
|
532
764
|
setScreen("browser");
|
|
@@ -535,7 +767,7 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
535
767
|
onCancel: () => setScreen("browser")
|
|
536
768
|
}
|
|
537
769
|
),
|
|
538
|
-
screen === "confirm-delete" && selected && /* @__PURE__ */
|
|
770
|
+
screen === "confirm-delete" && selected && /* @__PURE__ */ jsx11(
|
|
539
771
|
ConfirmDelete,
|
|
540
772
|
{
|
|
541
773
|
command: selected,
|
|
@@ -548,7 +780,7 @@ function Browser({ onExecute, forceOnboarding = false }) {
|
|
|
548
780
|
onCancel: () => setScreen("browser")
|
|
549
781
|
}
|
|
550
782
|
),
|
|
551
|
-
/* @__PURE__ */
|
|
783
|
+
/* @__PURE__ */ jsx11(StatusBar, { message: statusMessage })
|
|
552
784
|
] });
|
|
553
785
|
}
|
|
554
786
|
export {
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/checkUpdate.ts
|
|
4
|
+
function isNewer(latest, current) {
|
|
5
|
+
const a = latest.split(".").map(Number);
|
|
6
|
+
const b = current.split(".").map(Number);
|
|
7
|
+
for (let i = 0; i < 3; i++) {
|
|
8
|
+
if (a[i] > b[i]) return true;
|
|
9
|
+
if (a[i] < b[i]) return false;
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
async function checkForUpdate(currentVersion) {
|
|
14
|
+
try {
|
|
15
|
+
const controller = new AbortController();
|
|
16
|
+
const timeout = setTimeout(() => controller.abort(), 2e3);
|
|
17
|
+
const res = await fetch("https://registry.npmjs.org/@fiete/drift/latest", {
|
|
18
|
+
signal: controller.signal
|
|
19
|
+
});
|
|
20
|
+
clearTimeout(timeout);
|
|
21
|
+
const data = await res.json();
|
|
22
|
+
const latest = data.version;
|
|
23
|
+
return isNewer(latest, currentVersion) ? latest : null;
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
checkForUpdate
|
|
30
|
+
};
|
|
@@ -7,7 +7,7 @@ import fs from "fs";
|
|
|
7
7
|
import { nanoid } from "nanoid";
|
|
8
8
|
var STORE_DIR = path.join(os.homedir(), ".config", "drift");
|
|
9
9
|
var STORE_FILE = path.join(STORE_DIR, "commands.json");
|
|
10
|
-
var CURRENT_VERSION =
|
|
10
|
+
var CURRENT_VERSION = 2;
|
|
11
11
|
var FileStore = class {
|
|
12
12
|
data;
|
|
13
13
|
constructor() {
|
|
@@ -16,12 +16,21 @@ var FileStore = class {
|
|
|
16
16
|
load() {
|
|
17
17
|
if (!fs.existsSync(STORE_FILE)) {
|
|
18
18
|
fs.mkdirSync(STORE_DIR, { recursive: true });
|
|
19
|
-
const initial = { version: CURRENT_VERSION, commands: [] };
|
|
19
|
+
const initial = { version: CURRENT_VERSION, commands: [], projects: [] };
|
|
20
20
|
fs.writeFileSync(STORE_FILE, JSON.stringify(initial, null, 2));
|
|
21
21
|
return initial;
|
|
22
22
|
}
|
|
23
23
|
const raw = fs.readFileSync(STORE_FILE, "utf8");
|
|
24
|
-
|
|
24
|
+
const parsed = JSON.parse(raw);
|
|
25
|
+
return this.migrate(parsed);
|
|
26
|
+
}
|
|
27
|
+
migrate(data) {
|
|
28
|
+
if (data.version < 2) {
|
|
29
|
+
data.projects = [];
|
|
30
|
+
data.version = 2;
|
|
31
|
+
fs.writeFileSync(STORE_FILE, JSON.stringify(data, null, 2));
|
|
32
|
+
}
|
|
33
|
+
return data;
|
|
25
34
|
}
|
|
26
35
|
save(data) {
|
|
27
36
|
fs.writeFileSync(STORE_FILE, JSON.stringify(data, null, 2));
|
|
@@ -64,6 +73,38 @@ var FileStore = class {
|
|
|
64
73
|
}
|
|
65
74
|
return false;
|
|
66
75
|
}
|
|
76
|
+
// ── Projects ────────────────────────────────────────────────────────────────
|
|
77
|
+
getProjects() {
|
|
78
|
+
return this.data.projects;
|
|
79
|
+
}
|
|
80
|
+
getProjectById(id) {
|
|
81
|
+
return this.data.projects.find((p) => p.id === id);
|
|
82
|
+
}
|
|
83
|
+
addProject(input) {
|
|
84
|
+
const project = { ...input, id: nanoid(), createdAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
85
|
+
this.data.projects.push(project);
|
|
86
|
+
this.save(this.data);
|
|
87
|
+
return project;
|
|
88
|
+
}
|
|
89
|
+
updateProject(id, patch) {
|
|
90
|
+
const idx = this.data.projects.findIndex((p) => p.id === id);
|
|
91
|
+
if (idx === -1) return false;
|
|
92
|
+
this.data.projects[idx] = { ...this.data.projects[idx], ...patch };
|
|
93
|
+
this.save(this.data);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
removeProject(id) {
|
|
97
|
+
const before = this.data.projects.length;
|
|
98
|
+
this.data.projects = this.data.projects.filter((p) => p.id !== id);
|
|
99
|
+
this.data.commands = this.data.commands.map(
|
|
100
|
+
(c) => c.projectId === id ? { ...c, projectId: void 0 } : c
|
|
101
|
+
);
|
|
102
|
+
if (this.data.projects.length < before) {
|
|
103
|
+
this.save(this.data);
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
67
108
|
};
|
|
68
109
|
|
|
69
110
|
export {
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
FileStore
|
|
4
|
+
} from "./chunk-SRTEOAGN.js";
|
|
5
|
+
|
|
6
|
+
// src/ui/AddForm.tsx
|
|
7
|
+
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
8
|
+
import { Box as Box2, Text as Text2, useApp, useInput } from "ink";
|
|
9
|
+
import { TextInput } from "@inkjs/ui";
|
|
10
|
+
|
|
11
|
+
// src/hooks/useStore.ts
|
|
12
|
+
import { useState, useCallback } from "react";
|
|
13
|
+
var store = new FileStore();
|
|
14
|
+
function useStore() {
|
|
15
|
+
const [commands, setCommands] = useState(() => store.getAll());
|
|
16
|
+
const [projects, setProjects] = useState(() => store.getProjects());
|
|
17
|
+
const refresh = useCallback(() => {
|
|
18
|
+
setCommands([...store.getAll()]);
|
|
19
|
+
setProjects([...store.getProjects()]);
|
|
20
|
+
}, []);
|
|
21
|
+
const addCommand = useCallback(
|
|
22
|
+
(input) => {
|
|
23
|
+
store.addCommand(input);
|
|
24
|
+
refresh();
|
|
25
|
+
},
|
|
26
|
+
[refresh]
|
|
27
|
+
);
|
|
28
|
+
const updateCommand = useCallback(
|
|
29
|
+
(id, patch) => {
|
|
30
|
+
store.updateCommand(id, patch);
|
|
31
|
+
refresh();
|
|
32
|
+
},
|
|
33
|
+
[refresh]
|
|
34
|
+
);
|
|
35
|
+
const moveCommand = useCallback(
|
|
36
|
+
(id, direction) => {
|
|
37
|
+
store.moveCommand(id, direction);
|
|
38
|
+
refresh();
|
|
39
|
+
},
|
|
40
|
+
[refresh]
|
|
41
|
+
);
|
|
42
|
+
const removeCommand = useCallback(
|
|
43
|
+
(id) => {
|
|
44
|
+
store.removeCommand(id);
|
|
45
|
+
refresh();
|
|
46
|
+
},
|
|
47
|
+
[refresh]
|
|
48
|
+
);
|
|
49
|
+
const addProject = useCallback(
|
|
50
|
+
(input) => {
|
|
51
|
+
store.addProject(input);
|
|
52
|
+
refresh();
|
|
53
|
+
},
|
|
54
|
+
[refresh]
|
|
55
|
+
);
|
|
56
|
+
const updateProject = useCallback(
|
|
57
|
+
(id, patch) => {
|
|
58
|
+
store.updateProject(id, patch);
|
|
59
|
+
refresh();
|
|
60
|
+
},
|
|
61
|
+
[refresh]
|
|
62
|
+
);
|
|
63
|
+
const removeProject = useCallback(
|
|
64
|
+
(id) => {
|
|
65
|
+
store.removeProject(id);
|
|
66
|
+
refresh();
|
|
67
|
+
},
|
|
68
|
+
[refresh]
|
|
69
|
+
);
|
|
70
|
+
return {
|
|
71
|
+
commands,
|
|
72
|
+
projects,
|
|
73
|
+
addCommand,
|
|
74
|
+
updateCommand,
|
|
75
|
+
moveCommand,
|
|
76
|
+
removeCommand,
|
|
77
|
+
addProject,
|
|
78
|
+
updateProject,
|
|
79
|
+
removeProject
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/ui/ProjectSelect.tsx
|
|
84
|
+
import { Box, Text } from "ink";
|
|
85
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
86
|
+
function ProjectSelect({ projects, selectedProjectId, isActive }) {
|
|
87
|
+
const options = [{ id: void 0, name: "None" }, ...projects.map((p) => ({ id: p.id, name: p.name }))];
|
|
88
|
+
const selectedIdx = options.findIndex((o) => o.id === selectedProjectId);
|
|
89
|
+
const displayIdx = selectedIdx === -1 ? 0 : selectedIdx;
|
|
90
|
+
return /* @__PURE__ */ jsx(Box, { gap: 1, flexWrap: "wrap", children: options.map((opt, i) => {
|
|
91
|
+
const isCurrent = i === displayIdx;
|
|
92
|
+
if (isCurrent && isActive) {
|
|
93
|
+
return /* @__PURE__ */ jsxs(Text, { color: "yellowBright", bold: true, children: [
|
|
94
|
+
"\u2039 ",
|
|
95
|
+
opt.name,
|
|
96
|
+
" \u203A"
|
|
97
|
+
] }, i);
|
|
98
|
+
}
|
|
99
|
+
return /* @__PURE__ */ jsx(Text, { color: isCurrent ? "white" : "gray", dimColor: !isCurrent, children: opt.name }, i);
|
|
100
|
+
}) });
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// src/ui/AddForm.tsx
|
|
104
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
105
|
+
var FIELD_ORDER = ["description", "command", "directory", "tags", "project"];
|
|
106
|
+
var LABELS = {
|
|
107
|
+
description: "Description",
|
|
108
|
+
command: "Command",
|
|
109
|
+
directory: "Directory",
|
|
110
|
+
tags: "Tags (comma-separated)",
|
|
111
|
+
project: "Project"
|
|
112
|
+
};
|
|
113
|
+
var PLACEHOLDERS = {
|
|
114
|
+
description: "e.g. Show last 20 commits",
|
|
115
|
+
command: "e.g. git log --oneline -20",
|
|
116
|
+
directory: "e.g. /Users/me/project (leave empty to skip)",
|
|
117
|
+
tags: "e.g. git, log",
|
|
118
|
+
project: ""
|
|
119
|
+
};
|
|
120
|
+
function AddForm({ initialValues, onSave, onCancel }) {
|
|
121
|
+
const { exit } = useApp();
|
|
122
|
+
const { addCommand, projects } = useStore();
|
|
123
|
+
const [fieldIndex, setFieldIndex] = useState2(0);
|
|
124
|
+
const field = FIELD_ORDER[fieldIndex];
|
|
125
|
+
const [formData, setFormData] = useState2({
|
|
126
|
+
description: initialValues?.description ?? "",
|
|
127
|
+
command: initialValues?.command ?? "",
|
|
128
|
+
directory: "",
|
|
129
|
+
tags: initialValues?.tags ?? "",
|
|
130
|
+
projectId: initialValues?.projectId
|
|
131
|
+
});
|
|
132
|
+
const cancel = () => {
|
|
133
|
+
onCancel ? onCancel() : exit();
|
|
134
|
+
};
|
|
135
|
+
const projectOptions = [void 0, ...projects.map((p) => p.id)];
|
|
136
|
+
useInput((_, key) => {
|
|
137
|
+
if (key.escape) {
|
|
138
|
+
cancel();
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (key.upArrow) {
|
|
142
|
+
setFieldIndex((i) => Math.max(0, i - 1));
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (key.downArrow) {
|
|
146
|
+
setFieldIndex((i) => Math.min(FIELD_ORDER.length - 1, i + 1));
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (field === "project" && (key.leftArrow || key.rightArrow)) {
|
|
150
|
+
const currentIdx = projectOptions.findIndex((id) => id === formData.projectId);
|
|
151
|
+
const safeIdx = currentIdx === -1 ? 0 : currentIdx;
|
|
152
|
+
const next = key.rightArrow ? Math.min(projectOptions.length - 1, safeIdx + 1) : Math.max(0, safeIdx - 1);
|
|
153
|
+
setFormData((d) => ({ ...d, projectId: projectOptions[next] }));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (field === "project" && key.return) {
|
|
157
|
+
handleProjectSubmit();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
const handleChange = useCallback2((value) => {
|
|
162
|
+
setFormData((d) => ({ ...d, [field]: value }));
|
|
163
|
+
}, [field]);
|
|
164
|
+
const handleProjectSubmit = () => {
|
|
165
|
+
if (!formData.command.trim() && !formData.directory.trim()) {
|
|
166
|
+
setFieldIndex(FIELD_ORDER.indexOf("command"));
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
addCommand({
|
|
170
|
+
command: formData.command.trim(),
|
|
171
|
+
description: formData.description.trim(),
|
|
172
|
+
directory: formData.directory.trim() || void 0,
|
|
173
|
+
tags: formData.tags.split(",").map((t) => t.trim()).filter(Boolean),
|
|
174
|
+
projectId: formData.projectId
|
|
175
|
+
});
|
|
176
|
+
if (onSave) {
|
|
177
|
+
onSave();
|
|
178
|
+
} else {
|
|
179
|
+
console.log("\nSaved!");
|
|
180
|
+
exit();
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
const handleSubmit = (value) => {
|
|
184
|
+
const updated = { ...formData, [field]: value };
|
|
185
|
+
setFormData(updated);
|
|
186
|
+
if (fieldIndex < FIELD_ORDER.length - 1) {
|
|
187
|
+
setFieldIndex((i) => i + 1);
|
|
188
|
+
} else {
|
|
189
|
+
handleProjectSubmit();
|
|
190
|
+
}
|
|
191
|
+
};
|
|
192
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", padding: 1, gap: 1, children: [
|
|
193
|
+
/* @__PURE__ */ jsx2(Box2, { marginBottom: 1, children: /* @__PURE__ */ jsx2(Text2, { bold: true, color: "blueBright", children: " Add New Command" }) }),
|
|
194
|
+
FIELD_ORDER.map((f, i) => {
|
|
195
|
+
const isActive = i === fieldIndex;
|
|
196
|
+
return /* @__PURE__ */ jsxs2(Box2, { flexDirection: "column", children: [
|
|
197
|
+
/* @__PURE__ */ jsxs2(Text2, { color: isActive ? "yellowBright" : "gray", children: [
|
|
198
|
+
isActive ? "\u203A" : " ",
|
|
199
|
+
" ",
|
|
200
|
+
LABELS[f]
|
|
201
|
+
] }),
|
|
202
|
+
/* @__PURE__ */ jsx2(Box2, { paddingLeft: 2, children: f === "project" ? /* @__PURE__ */ jsx2(
|
|
203
|
+
ProjectSelect,
|
|
204
|
+
{
|
|
205
|
+
projects,
|
|
206
|
+
selectedProjectId: formData.projectId,
|
|
207
|
+
isActive
|
|
208
|
+
}
|
|
209
|
+
) : isActive ? /* @__PURE__ */ jsx2(
|
|
210
|
+
TextInput,
|
|
211
|
+
{
|
|
212
|
+
placeholder: PLACEHOLDERS[f],
|
|
213
|
+
defaultValue: formData[f] ?? "",
|
|
214
|
+
onChange: handleChange,
|
|
215
|
+
onSubmit: handleSubmit
|
|
216
|
+
},
|
|
217
|
+
f
|
|
218
|
+
) : /* @__PURE__ */ jsx2(Text2, { color: formData[f] ? "white" : "gray", dimColor: !formData[f], children: formData[f] || PLACEHOLDERS[f] }) })
|
|
219
|
+
] }, f);
|
|
220
|
+
}),
|
|
221
|
+
/* @__PURE__ */ jsx2(Box2, { marginTop: 1, children: /* @__PURE__ */ jsxs2(Text2, { color: "gray", dimColor: true, children: [
|
|
222
|
+
"\u2191\u2193 navigate \xB7 Enter to advance \xB7 ",
|
|
223
|
+
field === "project" ? "\u2190\u2192 select project \xB7 " : "",
|
|
224
|
+
"ESC to cancel"
|
|
225
|
+
] }) })
|
|
226
|
+
] });
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export {
|
|
230
|
+
useStore,
|
|
231
|
+
ProjectSelect,
|
|
232
|
+
AddForm
|
|
233
|
+
};
|
package/dist/index.js
CHANGED
|
@@ -5,21 +5,35 @@ import { program } from "commander";
|
|
|
5
5
|
import { render } from "ink";
|
|
6
6
|
import React from "react";
|
|
7
7
|
import { execa } from "execa";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
var require2 = createRequire(import.meta.url);
|
|
10
|
+
var packageJson = require2("../package.json");
|
|
8
11
|
program.name("drift").description("cmdvault \u2014 A TUI command vault for your shell").version("1.0.0");
|
|
9
12
|
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-
|
|
13
|
+
const { Browser } = await import("./Browser-67UFH2PS.js");
|
|
14
|
+
const { checkForUpdate } = await import("./checkUpdate-G4I5BZTU.js");
|
|
15
|
+
const latestVersion = await checkForUpdate(packageJson.version);
|
|
11
16
|
let commandToRun;
|
|
17
|
+
let shouldUpdate = false;
|
|
12
18
|
const { waitUntilExit } = render(
|
|
13
19
|
React.createElement(Browser, {
|
|
14
20
|
onExecute: (cmd) => {
|
|
15
21
|
commandToRun = cmd;
|
|
16
22
|
},
|
|
17
|
-
forceOnboarding: opts.onboarding ?? false
|
|
23
|
+
forceOnboarding: opts.onboarding ?? false,
|
|
24
|
+
currentVersion: packageJson.version,
|
|
25
|
+
latestVersion: latestVersion ?? void 0,
|
|
26
|
+
onUpdate: () => {
|
|
27
|
+
shouldUpdate = true;
|
|
28
|
+
}
|
|
18
29
|
})
|
|
19
30
|
);
|
|
20
31
|
await waitUntilExit();
|
|
21
32
|
process.stdout.write("\x1Bc");
|
|
22
|
-
if (
|
|
33
|
+
if (shouldUpdate) {
|
|
34
|
+
console.log("\nInstalling @fiete/drift@latest...\n");
|
|
35
|
+
await execa("npm", ["install", "-g", "@fiete/drift"], { stdio: "inherit" });
|
|
36
|
+
} else if (commandToRun) {
|
|
23
37
|
await execa(commandToRun, { shell: true, stdio: "inherit" }).catch(() => {
|
|
24
38
|
});
|
|
25
39
|
}
|
|
@@ -27,7 +41,7 @@ program.command("list", { isDefault: true }).description("Browse saved commands
|
|
|
27
41
|
});
|
|
28
42
|
program.command("add [command]").description("Add a command. Pass it as a quoted argument, or open the interactive form.").option("-d, --desc <description>", "Description for the command").option("-t, --tags <tags>", "Comma-separated tags").option("--dir <directory>", "Working directory").addHelpText("after", '\nExamples:\n drift add "git log --oneline -20"\n drift add "cd /project && npm run dev" --desc "Start dev server" --tags "node,dev"\n drift add (opens interactive form)').action(async (command, opts) => {
|
|
29
43
|
if (command && opts.desc !== void 0 && opts.tags !== void 0) {
|
|
30
|
-
const { FileStore } = await import("./FileStore-
|
|
44
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
31
45
|
const store = new FileStore();
|
|
32
46
|
store.addCommand({
|
|
33
47
|
command: command.trim(),
|
|
@@ -38,7 +52,7 @@ program.command("add [command]").description("Add a command. Pass it as a quoted
|
|
|
38
52
|
console.log("Saved!");
|
|
39
53
|
process.exit(0);
|
|
40
54
|
}
|
|
41
|
-
const { AddForm } = await import("./AddForm-
|
|
55
|
+
const { AddForm } = await import("./AddForm-ZMSCP6CU.js");
|
|
42
56
|
const initialValues = {
|
|
43
57
|
command: command ?? "",
|
|
44
58
|
description: opts.desc ?? "",
|
|
@@ -51,7 +65,7 @@ program.command("add [command]").description("Add a command. Pass it as a quoted
|
|
|
51
65
|
process.exit(0);
|
|
52
66
|
});
|
|
53
67
|
program.command("remove <id>").description("Remove a command by ID").action(async (id) => {
|
|
54
|
-
const { FileStore } = await import("./FileStore-
|
|
68
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
55
69
|
const store = new FileStore();
|
|
56
70
|
const removed = store.removeCommand(id);
|
|
57
71
|
if (removed) {
|
|
@@ -62,7 +76,7 @@ program.command("remove <id>").description("Remove a command by ID").action(asyn
|
|
|
62
76
|
}
|
|
63
77
|
});
|
|
64
78
|
program.command("ls").description("List all saved commands").option("--json", "Output as JSON").action(async (opts) => {
|
|
65
|
-
const { FileStore } = await import("./FileStore-
|
|
79
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
66
80
|
const store = new FileStore();
|
|
67
81
|
const commands = store.getAll();
|
|
68
82
|
if (opts.json) {
|
|
@@ -82,7 +96,7 @@ program.command("ls").description("List all saved commands").option("--json", "O
|
|
|
82
96
|
process.exit(0);
|
|
83
97
|
});
|
|
84
98
|
program.command("search <query>").description("Search saved commands (fuzzy)").option("--json", "Output as JSON").action(async (query, opts) => {
|
|
85
|
-
const { FileStore } = await import("./FileStore-
|
|
99
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
86
100
|
const { searchCommands } = await import("./search-HZEMV67M.js");
|
|
87
101
|
const store = new FileStore();
|
|
88
102
|
const results = searchCommands(store.getAll(), query);
|
|
@@ -103,7 +117,7 @@ program.command("search <query>").description("Search saved commands (fuzzy)").o
|
|
|
103
117
|
process.exit(0);
|
|
104
118
|
});
|
|
105
119
|
program.command("get <id>").description("Get a single command by ID").option("--json", "Output as JSON").action(async (id, opts) => {
|
|
106
|
-
const { FileStore } = await import("./FileStore-
|
|
120
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
107
121
|
const store = new FileStore();
|
|
108
122
|
const cmd = store.getById(id);
|
|
109
123
|
if (!cmd) {
|
|
@@ -121,7 +135,7 @@ program.command("get <id>").description("Get a single command by ID").option("--
|
|
|
121
135
|
process.exit(0);
|
|
122
136
|
});
|
|
123
137
|
program.command("execute <id>").description("Execute a saved command by ID").action(async (id) => {
|
|
124
|
-
const { FileStore } = await import("./FileStore-
|
|
138
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
125
139
|
const store = new FileStore();
|
|
126
140
|
const cmd = store.getById(id);
|
|
127
141
|
if (!cmd) {
|
|
@@ -133,7 +147,7 @@ program.command("execute <id>").description("Execute a saved command by ID").act
|
|
|
133
147
|
process.exit(0);
|
|
134
148
|
});
|
|
135
149
|
program.command("copy <id>").description("Copy a saved command to the clipboard").action(async (id) => {
|
|
136
|
-
const { FileStore } = await import("./FileStore-
|
|
150
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
137
151
|
const { default: clipboardy } = await import("clipboardy");
|
|
138
152
|
const store = new FileStore();
|
|
139
153
|
const cmd = store.getById(id);
|
|
@@ -146,7 +160,7 @@ program.command("copy <id>").description("Copy a saved command to the clipboard"
|
|
|
146
160
|
process.exit(0);
|
|
147
161
|
});
|
|
148
162
|
program.command("edit <id>").description("Edit a saved command by ID").option("-c, --command <command>", "New command string").option("-d, --desc <description>", "New description").option("-t, --tags <tags>", "New comma-separated tags").option("--dir <directory>", "New working directory").option("--json", "Output updated command as JSON").action(async (id, opts) => {
|
|
149
|
-
const { FileStore } = await import("./FileStore-
|
|
163
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
150
164
|
const store = new FileStore();
|
|
151
165
|
const patch = {};
|
|
152
166
|
if (opts.command !== void 0) patch.command = opts.command.trim();
|
|
@@ -169,4 +183,42 @@ program.command("edit <id>").description("Edit a saved command by ID").option("-
|
|
|
169
183
|
}
|
|
170
184
|
process.exit(0);
|
|
171
185
|
});
|
|
186
|
+
var projectCmd = program.command("project").description("Manage projects");
|
|
187
|
+
projectCmd.command("add <name>").description("Create a new project").requiredOption("-r, --root <path>", "Root directory for this project").action(async (name, opts) => {
|
|
188
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
189
|
+
const store = new FileStore();
|
|
190
|
+
const project = store.addProject({ name: name.trim(), root: opts.root.trim() });
|
|
191
|
+
console.log(`Created project "${project.name}" (${project.id})
|
|
192
|
+
root: ${project.root}`);
|
|
193
|
+
process.exit(0);
|
|
194
|
+
});
|
|
195
|
+
projectCmd.command("ls").description("List all projects").option("--json", "Output as JSON").action(async (opts) => {
|
|
196
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
197
|
+
const store = new FileStore();
|
|
198
|
+
const projects = store.getProjects();
|
|
199
|
+
if (opts.json) {
|
|
200
|
+
console.log(JSON.stringify(projects, null, 2));
|
|
201
|
+
} else {
|
|
202
|
+
if (projects.length === 0) {
|
|
203
|
+
console.log("No projects yet. Run `drift project add <name> --root <path>` to create one.");
|
|
204
|
+
} else {
|
|
205
|
+
for (const p of projects) {
|
|
206
|
+
console.log(`${p.id} ${p.name}`);
|
|
207
|
+
console.log(` root: ${p.root}`);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
process.exit(0);
|
|
212
|
+
});
|
|
213
|
+
projectCmd.command("rm <id>").description("Remove a project by ID (commands are unassigned, not deleted)").action(async (id) => {
|
|
214
|
+
const { FileStore } = await import("./FileStore-DV2QBZJL.js");
|
|
215
|
+
const store = new FileStore();
|
|
216
|
+
const removed = store.removeProject(id);
|
|
217
|
+
if (removed) {
|
|
218
|
+
console.log(`Removed project ${id}`);
|
|
219
|
+
} else {
|
|
220
|
+
console.error(`No project found with ID: ${id}`);
|
|
221
|
+
process.exit(1);
|
|
222
|
+
}
|
|
223
|
+
});
|
|
172
224
|
program.parse();
|
package/package.json
CHANGED
package/dist/chunk-GUH6BVED.js
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
FileStore
|
|
4
|
-
} from "./chunk-CRXU4OLG.js";
|
|
5
|
-
|
|
6
|
-
// src/ui/AddForm.tsx
|
|
7
|
-
import { useState as useState2, useCallback as useCallback2 } from "react";
|
|
8
|
-
import { Box, Text, useApp, useInput } from "ink";
|
|
9
|
-
import { TextInput } from "@inkjs/ui";
|
|
10
|
-
|
|
11
|
-
// src/hooks/useStore.ts
|
|
12
|
-
import { useState, useCallback } from "react";
|
|
13
|
-
var store = new FileStore();
|
|
14
|
-
function useStore() {
|
|
15
|
-
const [commands, setCommands] = useState(() => store.getAll());
|
|
16
|
-
const refresh = useCallback(() => {
|
|
17
|
-
setCommands([...store.getAll()]);
|
|
18
|
-
}, []);
|
|
19
|
-
const addCommand = useCallback(
|
|
20
|
-
(input) => {
|
|
21
|
-
store.addCommand(input);
|
|
22
|
-
refresh();
|
|
23
|
-
},
|
|
24
|
-
[refresh]
|
|
25
|
-
);
|
|
26
|
-
const updateCommand = useCallback(
|
|
27
|
-
(id, patch) => {
|
|
28
|
-
store.updateCommand(id, patch);
|
|
29
|
-
refresh();
|
|
30
|
-
},
|
|
31
|
-
[refresh]
|
|
32
|
-
);
|
|
33
|
-
const moveCommand = useCallback(
|
|
34
|
-
(id, direction) => {
|
|
35
|
-
store.moveCommand(id, direction);
|
|
36
|
-
refresh();
|
|
37
|
-
},
|
|
38
|
-
[refresh]
|
|
39
|
-
);
|
|
40
|
-
const removeCommand = useCallback(
|
|
41
|
-
(id) => {
|
|
42
|
-
store.removeCommand(id);
|
|
43
|
-
refresh();
|
|
44
|
-
},
|
|
45
|
-
[refresh]
|
|
46
|
-
);
|
|
47
|
-
return { commands, addCommand, updateCommand, moveCommand, removeCommand };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// src/ui/AddForm.tsx
|
|
51
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
52
|
-
var FIELD_ORDER = ["description", "command", "directory", "tags"];
|
|
53
|
-
var LABELS = {
|
|
54
|
-
description: "Description",
|
|
55
|
-
command: "Command",
|
|
56
|
-
directory: "Directory",
|
|
57
|
-
tags: "Tags (comma-separated)"
|
|
58
|
-
};
|
|
59
|
-
var PLACEHOLDERS = {
|
|
60
|
-
description: "e.g. Show last 20 commits",
|
|
61
|
-
command: "e.g. git log --oneline -20",
|
|
62
|
-
directory: "e.g. /Users/me/project (leave empty to skip)",
|
|
63
|
-
tags: "e.g. git, log"
|
|
64
|
-
};
|
|
65
|
-
function AddForm({ initialValues, onSave, onCancel }) {
|
|
66
|
-
const { exit } = useApp();
|
|
67
|
-
const { addCommand } = useStore();
|
|
68
|
-
const getStartIndex = () => {
|
|
69
|
-
if (initialValues?.command && !initialValues.description) return 0;
|
|
70
|
-
if (initialValues?.command && initialValues.description && !initialValues.tags) return 2;
|
|
71
|
-
return 0;
|
|
72
|
-
};
|
|
73
|
-
const [fieldIndex, setFieldIndex] = useState2(getStartIndex);
|
|
74
|
-
const field = FIELD_ORDER[fieldIndex];
|
|
75
|
-
const [formData, setFormData] = useState2({
|
|
76
|
-
description: initialValues?.description ?? "",
|
|
77
|
-
command: initialValues?.command ?? "",
|
|
78
|
-
directory: "",
|
|
79
|
-
tags: initialValues?.tags ?? ""
|
|
80
|
-
});
|
|
81
|
-
const cancel = () => {
|
|
82
|
-
onCancel ? onCancel() : exit();
|
|
83
|
-
};
|
|
84
|
-
useInput((_, key) => {
|
|
85
|
-
if (key.escape) {
|
|
86
|
-
cancel();
|
|
87
|
-
return;
|
|
88
|
-
}
|
|
89
|
-
if (key.upArrow) {
|
|
90
|
-
setFieldIndex((i) => Math.max(0, i - 1));
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
if (key.downArrow) {
|
|
94
|
-
setFieldIndex((i) => Math.min(FIELD_ORDER.length - 1, i + 1));
|
|
95
|
-
return;
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
const handleChange = useCallback2((value) => {
|
|
99
|
-
setFormData((d) => ({ ...d, [field]: value }));
|
|
100
|
-
}, [field]);
|
|
101
|
-
const handleSubmit = (value) => {
|
|
102
|
-
const updated = { ...formData, [field]: value };
|
|
103
|
-
setFormData(updated);
|
|
104
|
-
if (fieldIndex < FIELD_ORDER.length - 1) {
|
|
105
|
-
setFieldIndex((i) => i + 1);
|
|
106
|
-
} else {
|
|
107
|
-
if (!updated.command.trim() && !updated.directory.trim()) {
|
|
108
|
-
setFieldIndex(1);
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
addCommand({
|
|
112
|
-
command: updated.command.trim(),
|
|
113
|
-
description: updated.description.trim(),
|
|
114
|
-
directory: updated.directory.trim() || void 0,
|
|
115
|
-
tags: updated.tags.split(",").map((t) => t.trim()).filter(Boolean)
|
|
116
|
-
});
|
|
117
|
-
if (onSave) {
|
|
118
|
-
onSave();
|
|
119
|
-
} else {
|
|
120
|
-
console.log("\nSaved!");
|
|
121
|
-
exit();
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
};
|
|
125
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, gap: 1, children: [
|
|
126
|
-
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsx(Text, { bold: true, color: "blueBright", children: " Add New Command" }) }),
|
|
127
|
-
FIELD_ORDER.map((f, i) => {
|
|
128
|
-
const isActive = i === fieldIndex;
|
|
129
|
-
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
130
|
-
/* @__PURE__ */ jsxs(Text, { color: isActive ? "yellowBright" : "gray", children: [
|
|
131
|
-
isActive ? "\u203A" : " ",
|
|
132
|
-
" ",
|
|
133
|
-
LABELS[f]
|
|
134
|
-
] }),
|
|
135
|
-
/* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: isActive ? /* @__PURE__ */ jsx(
|
|
136
|
-
TextInput,
|
|
137
|
-
{
|
|
138
|
-
placeholder: PLACEHOLDERS[f],
|
|
139
|
-
defaultValue: formData[f],
|
|
140
|
-
onChange: handleChange,
|
|
141
|
-
onSubmit: handleSubmit
|
|
142
|
-
},
|
|
143
|
-
f
|
|
144
|
-
) : /* @__PURE__ */ jsx(Text, { color: formData[f] ? "white" : "gray", dimColor: !formData[f], children: formData[f] || PLACEHOLDERS[f] }) })
|
|
145
|
-
] }, f);
|
|
146
|
-
}),
|
|
147
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsx(Text, { color: "gray", dimColor: true, children: "\u2191\u2193 navigate \xB7 Enter to advance \xB7 ESC to cancel" }) })
|
|
148
|
-
] });
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export {
|
|
152
|
-
useStore,
|
|
153
|
-
AddForm
|
|
154
|
-
};
|