@arthony/keybook 0.5.0 → 0.6.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/dist/cli.js +228 -35
- package/package.json +32 -20
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawn, spawnSync } from "node:child_process";
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, renameSync, unlinkSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { basename, dirname, join } from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
6
|
import { Command } from "commander";
|
|
@@ -868,6 +868,52 @@ function copyToClipboard(text) {
|
|
|
868
868
|
}
|
|
869
869
|
}
|
|
870
870
|
//#endregion
|
|
871
|
+
//#region src/data/favorites.ts
|
|
872
|
+
const SEP = "\0";
|
|
873
|
+
function favKey(app, action) {
|
|
874
|
+
return `${app}${SEP}${action}`;
|
|
875
|
+
}
|
|
876
|
+
function favoritesPath(dir) {
|
|
877
|
+
return join(dir, "favorites.json");
|
|
878
|
+
}
|
|
879
|
+
/** Read favorites.json -> Set of favKeys. Missing/malformed -> empty set (never throws). */
|
|
880
|
+
function loadFavorites(dir) {
|
|
881
|
+
const set = /* @__PURE__ */ new Set();
|
|
882
|
+
try {
|
|
883
|
+
const data = JSON.parse(readFileSync(favoritesPath(dir), "utf8"));
|
|
884
|
+
const list = Array.isArray(data?.favorites) ? data.favorites : [];
|
|
885
|
+
for (const f of list) if (f && typeof f.app === "string" && typeof f.action === "string") set.add(favKey(f.app, f.action));
|
|
886
|
+
} catch {}
|
|
887
|
+
return set;
|
|
888
|
+
}
|
|
889
|
+
/** Atomic write (temp + rename) of the favKey set back to favorites.json. */
|
|
890
|
+
function saveFavorites(dir, keys) {
|
|
891
|
+
const data = {
|
|
892
|
+
version: 1,
|
|
893
|
+
favorites: [...keys].map((k) => {
|
|
894
|
+
const i = k.indexOf(SEP);
|
|
895
|
+
return {
|
|
896
|
+
app: k.slice(0, i),
|
|
897
|
+
action: k.slice(i + 1)
|
|
898
|
+
};
|
|
899
|
+
})
|
|
900
|
+
};
|
|
901
|
+
const path = favoritesPath(dir);
|
|
902
|
+
const tmp = `${path}.tmp`;
|
|
903
|
+
writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
|
|
904
|
+
`, "utf8");
|
|
905
|
+
renameSync(tmp, path);
|
|
906
|
+
}
|
|
907
|
+
/** Toggle one favorite, persist, and return the new set. */
|
|
908
|
+
function toggleFavorite(dir, app, action) {
|
|
909
|
+
const keys = loadFavorites(dir);
|
|
910
|
+
const k = favKey(app, action);
|
|
911
|
+
if (keys.has(k)) keys.delete(k);
|
|
912
|
+
else keys.add(k);
|
|
913
|
+
saveFavorites(dir, keys);
|
|
914
|
+
return keys;
|
|
915
|
+
}
|
|
916
|
+
//#endregion
|
|
871
917
|
//#region src/search.ts
|
|
872
918
|
function haystack(e) {
|
|
873
919
|
return [
|
|
@@ -888,8 +934,109 @@ function search(entries, query) {
|
|
|
888
934
|
}).find(q).map((r) => r.item);
|
|
889
935
|
}
|
|
890
936
|
//#endregion
|
|
937
|
+
//#region src/tui/FilterPicker.tsx
|
|
938
|
+
function FilterPicker({ apps, onSelect, onCancel, height = 16, width = 40 }) {
|
|
939
|
+
const [query, setQuery] = useState("");
|
|
940
|
+
const [selected, setSelected] = useState(0);
|
|
941
|
+
const matchedApps = useMemo(() => {
|
|
942
|
+
const q = query.trim().toLowerCase();
|
|
943
|
+
return q ? apps.filter((a) => a.toLowerCase().includes(q)) : apps;
|
|
944
|
+
}, [apps, query]);
|
|
945
|
+
const total = 2 + matchedApps.length;
|
|
946
|
+
const sel = Math.min(selected, Math.max(0, total - 1));
|
|
947
|
+
useInput((input, key) => {
|
|
948
|
+
if (key.escape || key.ctrl && input === "f") return onCancel();
|
|
949
|
+
if (key.downArrow || key.ctrl && input === "n") {
|
|
950
|
+
setSelected((s) => Math.min(s + 1, total - 1));
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
953
|
+
if (key.upArrow || key.ctrl && input === "p") {
|
|
954
|
+
setSelected((s) => Math.max(s - 1, 0));
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
if (key.return) {
|
|
958
|
+
if (sel === 0) return onSelect({ type: "favorites" });
|
|
959
|
+
if (sel === 1) return onSelect({ type: "all" });
|
|
960
|
+
const app = matchedApps[sel - 2];
|
|
961
|
+
if (app) return onSelect({
|
|
962
|
+
type: "app",
|
|
963
|
+
app
|
|
964
|
+
});
|
|
965
|
+
return;
|
|
966
|
+
}
|
|
967
|
+
if (key.backspace || key.delete) {
|
|
968
|
+
setQuery((q) => q.slice(0, -1));
|
|
969
|
+
setSelected(0);
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
if (input && !key.ctrl && !key.meta) {
|
|
973
|
+
setQuery((q) => q + input);
|
|
974
|
+
setSelected(2);
|
|
975
|
+
}
|
|
976
|
+
});
|
|
977
|
+
const bodyHeight = Math.max(1, height - 2);
|
|
978
|
+
const appSel = Math.max(0, sel - 2);
|
|
979
|
+
const startApp = matchedApps.length > bodyHeight ? Math.max(0, Math.min(appSel - Math.floor(bodyHeight / 2), matchedApps.length - bodyHeight)) : 0;
|
|
980
|
+
const visibleApps = matchedApps.slice(startApp, startApp + bodyHeight);
|
|
981
|
+
const row = (text, idx) => {
|
|
982
|
+
const isSel = idx === sel;
|
|
983
|
+
return /* @__PURE__ */ jsxs(Text, {
|
|
984
|
+
wrap: "truncate-end",
|
|
985
|
+
color: isSel ? "cyan" : void 0,
|
|
986
|
+
inverse: isSel,
|
|
987
|
+
children: [isSel ? "▸ " : " ", text]
|
|
988
|
+
}, `${idx}:${text}`);
|
|
989
|
+
};
|
|
990
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
991
|
+
flexDirection: "column",
|
|
992
|
+
borderStyle: "round",
|
|
993
|
+
paddingX: 1,
|
|
994
|
+
width,
|
|
995
|
+
children: [
|
|
996
|
+
/* @__PURE__ */ jsx(Text, {
|
|
997
|
+
wrap: "truncate-end",
|
|
998
|
+
color: "cyan",
|
|
999
|
+
children: "Filter by app"
|
|
1000
|
+
}),
|
|
1001
|
+
/* @__PURE__ */ jsxs(Text, {
|
|
1002
|
+
wrap: "truncate-end",
|
|
1003
|
+
children: [query ? query : /* @__PURE__ */ jsx(Text, {
|
|
1004
|
+
color: "gray",
|
|
1005
|
+
children: "type to filter…"
|
|
1006
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
1007
|
+
inverse: true,
|
|
1008
|
+
children: " "
|
|
1009
|
+
})]
|
|
1010
|
+
}),
|
|
1011
|
+
row("★ Favorites", 0),
|
|
1012
|
+
row("All apps", 1),
|
|
1013
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1014
|
+
wrap: "truncate-end",
|
|
1015
|
+
color: "gray",
|
|
1016
|
+
children: "──────────────"
|
|
1017
|
+
}),
|
|
1018
|
+
startApp > 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
1019
|
+
wrap: "truncate-end",
|
|
1020
|
+
color: "gray",
|
|
1021
|
+
children: [" ", "↑ more"]
|
|
1022
|
+
}) : null,
|
|
1023
|
+
visibleApps.map((app, i) => row(app, startApp + i + 2)),
|
|
1024
|
+
startApp + bodyHeight < matchedApps.length ? /* @__PURE__ */ jsxs(Text, {
|
|
1025
|
+
wrap: "truncate-end",
|
|
1026
|
+
color: "gray",
|
|
1027
|
+
children: [" ", "↓ more"]
|
|
1028
|
+
}) : null,
|
|
1029
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1030
|
+
wrap: "truncate-end",
|
|
1031
|
+
color: "gray",
|
|
1032
|
+
children: "↑↓ move ⏎ select ⎋ cancel"
|
|
1033
|
+
})
|
|
1034
|
+
]
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
//#endregion
|
|
891
1038
|
//#region src/tui/Footer.tsx
|
|
892
|
-
function Footer({ flash, errorCount, resultCount, confirm }) {
|
|
1039
|
+
function Footer({ flash, errorCount, resultCount, confirm, filterActive = false }) {
|
|
893
1040
|
if (confirm) return /* @__PURE__ */ jsxs(Box, {
|
|
894
1041
|
marginTop: 1,
|
|
895
1042
|
justifyContent: "space-between",
|
|
@@ -906,19 +1053,17 @@ function Footer({ flash, errorCount, resultCount, confirm }) {
|
|
|
906
1053
|
return /* @__PURE__ */ jsxs(Box, {
|
|
907
1054
|
marginTop: 1,
|
|
908
1055
|
justifyContent: "space-between",
|
|
909
|
-
children: [/* @__PURE__ */
|
|
1056
|
+
children: [/* @__PURE__ */ jsx(Text, {
|
|
910
1057
|
color: "gray",
|
|
911
1058
|
wrap: "truncate-end",
|
|
912
|
-
children:
|
|
913
|
-
"↑↓ move ⏎ copy ⌃O add ⌃E edit ⌃X del ⎋ quit ⌃U clear (",
|
|
914
|
-
resultCount,
|
|
915
|
-
")"
|
|
916
|
-
]
|
|
1059
|
+
children: `↑↓ move ⏎ copy ⌃O add ⌃E edit ⌃X del ⌃F filter ⌃S star ${filterActive ? "⎋ clear filter" : "⎋ quit"} ⌃U clear (${resultCount})`
|
|
917
1060
|
}), flash ? /* @__PURE__ */ jsx(Text, {
|
|
918
1061
|
color: "green",
|
|
1062
|
+
wrap: "truncate-end",
|
|
919
1063
|
children: flash
|
|
920
1064
|
}) : errorCount > 0 ? /* @__PURE__ */ jsxs(Text, {
|
|
921
1065
|
color: "yellow",
|
|
1066
|
+
wrap: "truncate-end",
|
|
922
1067
|
children: [
|
|
923
1068
|
"⚠ ",
|
|
924
1069
|
errorCount,
|
|
@@ -973,7 +1118,7 @@ function PreviewPane({ entry, width = "50%" }) {
|
|
|
973
1118
|
}
|
|
974
1119
|
//#endregion
|
|
975
1120
|
//#region src/tui/ResultRow.tsx
|
|
976
|
-
function ResultRow({ entry, selected }) {
|
|
1121
|
+
function ResultRow({ entry, selected, favorite = false }) {
|
|
977
1122
|
const right = entry.keys ?? "recipe";
|
|
978
1123
|
return /* @__PURE__ */ jsx(Box, { children: /* @__PURE__ */ jsxs(Text, {
|
|
979
1124
|
wrap: "truncate-end",
|
|
@@ -982,6 +1127,7 @@ function ResultRow({ entry, selected }) {
|
|
|
982
1127
|
inverse: selected,
|
|
983
1128
|
children: [
|
|
984
1129
|
selected ? "▸ " : " ",
|
|
1130
|
+
favorite ? "★ " : " ",
|
|
985
1131
|
entry.app,
|
|
986
1132
|
" · ",
|
|
987
1133
|
entry.action
|
|
@@ -994,17 +1140,13 @@ function ResultRow({ entry, selected }) {
|
|
|
994
1140
|
}
|
|
995
1141
|
//#endregion
|
|
996
1142
|
//#region src/tui/ResultList.tsx
|
|
997
|
-
function ResultList({ results, selected, query, height = 12, width = "50%" }) {
|
|
1143
|
+
function ResultList({ results, selected, query, height = 12, width = "50%", favorites, emptyMessage }) {
|
|
998
1144
|
if (results.length === 0) return /* @__PURE__ */ jsx(Box, {
|
|
999
1145
|
width,
|
|
1000
1146
|
justifyContent: "center",
|
|
1001
|
-
children: /* @__PURE__ */
|
|
1147
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
1002
1148
|
color: "gray",
|
|
1003
|
-
children:
|
|
1004
|
-
"No matches for \"",
|
|
1005
|
-
query,
|
|
1006
|
-
"\""
|
|
1007
|
-
]
|
|
1149
|
+
children: emptyMessage ?? `No matches for "${query}"`
|
|
1008
1150
|
})
|
|
1009
1151
|
});
|
|
1010
1152
|
const start = Math.max(0, Math.min(selected - Math.floor(height / 2), results.length - height));
|
|
@@ -1013,27 +1155,35 @@ function ResultList({ results, selected, query, height = 12, width = "50%" }) {
|
|
|
1013
1155
|
width,
|
|
1014
1156
|
children: results.slice(Math.max(0, start), Math.max(0, start) + height).map((e, i) => /* @__PURE__ */ jsx(ResultRow, {
|
|
1015
1157
|
entry: e,
|
|
1016
|
-
selected: Math.max(0, start) + i === selected
|
|
1158
|
+
selected: Math.max(0, start) + i === selected,
|
|
1159
|
+
favorite: favorites?.has(favKey(e.app, e.action)) ?? false
|
|
1017
1160
|
}, `${e.app}:${e.action}`))
|
|
1018
1161
|
});
|
|
1019
1162
|
}
|
|
1020
1163
|
//#endregion
|
|
1021
1164
|
//#region src/tui/SearchInput.tsx
|
|
1022
|
-
function SearchInput({ query }) {
|
|
1023
|
-
return /* @__PURE__ */
|
|
1024
|
-
|
|
1025
|
-
children: [
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1165
|
+
function SearchInput({ query, filterLabel }) {
|
|
1166
|
+
return /* @__PURE__ */ jsxs(Box, {
|
|
1167
|
+
justifyContent: "space-between",
|
|
1168
|
+
children: [/* @__PURE__ */ jsxs(Text, {
|
|
1169
|
+
wrap: "truncate-end",
|
|
1170
|
+
children: [
|
|
1171
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1172
|
+
color: "cyan",
|
|
1173
|
+
children: "search: "
|
|
1174
|
+
}),
|
|
1175
|
+
/* @__PURE__ */ jsx(Text, { children: query }),
|
|
1176
|
+
/* @__PURE__ */ jsx(Text, {
|
|
1177
|
+
inverse: true,
|
|
1178
|
+
children: " "
|
|
1179
|
+
})
|
|
1180
|
+
]
|
|
1181
|
+
}), filterLabel ? /* @__PURE__ */ jsx(Text, {
|
|
1182
|
+
color: "gray",
|
|
1183
|
+
wrap: "truncate-end",
|
|
1184
|
+
children: filterLabel
|
|
1185
|
+
}) : null]
|
|
1186
|
+
});
|
|
1037
1187
|
}
|
|
1038
1188
|
//#endregion
|
|
1039
1189
|
//#region src/tui/input.ts
|
|
@@ -1078,6 +1228,8 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1078
1228
|
const { stdout } = useStdout();
|
|
1079
1229
|
const [entries, setEntries] = useState(initial);
|
|
1080
1230
|
const [mode, setMode] = useState("search");
|
|
1231
|
+
const [filter, setFilter] = useState({ type: "all" });
|
|
1232
|
+
const [favorites, setFavorites] = useState(() => dataDir ? loadFavorites(dataDir) : /* @__PURE__ */ new Set());
|
|
1081
1233
|
const [editTarget, setEditTarget] = useState(null);
|
|
1082
1234
|
const [pendingDelete, setPendingDelete] = useState(null);
|
|
1083
1235
|
const [query, setQuery] = useState("");
|
|
@@ -1086,7 +1238,16 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1086
1238
|
const reload = useCallback(() => {
|
|
1087
1239
|
if (dataDir) setEntries(loadEntries(dataDir).entries);
|
|
1088
1240
|
}, [dataDir]);
|
|
1089
|
-
const
|
|
1241
|
+
const scoped = useMemo(() => {
|
|
1242
|
+
if (filter.type === "app") return entries.filter((e) => e.app === filter.app);
|
|
1243
|
+
if (filter.type === "favorites") return entries.filter((e) => favorites.has(favKey(e.app, e.action)));
|
|
1244
|
+
return entries;
|
|
1245
|
+
}, [
|
|
1246
|
+
entries,
|
|
1247
|
+
filter,
|
|
1248
|
+
favorites
|
|
1249
|
+
]);
|
|
1250
|
+
const results = useMemo(() => search(scoped, query), [scoped, query]);
|
|
1090
1251
|
const sel = results.length ? Math.min(selected, results.length - 1) : 0;
|
|
1091
1252
|
const current = results[sel];
|
|
1092
1253
|
const listHeight = visibleListHeight(stdout?.rows);
|
|
@@ -1110,6 +1271,11 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1110
1271
|
}
|
|
1111
1272
|
if (!key.return && flash) setFlash("");
|
|
1112
1273
|
if (key.escape) {
|
|
1274
|
+
if (filter.type !== "all") {
|
|
1275
|
+
setFilter({ type: "all" });
|
|
1276
|
+
setSelected(0);
|
|
1277
|
+
return;
|
|
1278
|
+
}
|
|
1113
1279
|
exit();
|
|
1114
1280
|
return;
|
|
1115
1281
|
}
|
|
@@ -1126,6 +1292,16 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1126
1292
|
setPendingDelete(current);
|
|
1127
1293
|
return;
|
|
1128
1294
|
}
|
|
1295
|
+
if (key.ctrl && input === "f") {
|
|
1296
|
+
setMode("filter");
|
|
1297
|
+
return;
|
|
1298
|
+
}
|
|
1299
|
+
if (dataDir && current && key.ctrl && input === "s") {
|
|
1300
|
+
const next = toggleFavorite(dataDir, current.app, current.action);
|
|
1301
|
+
setFavorites(next);
|
|
1302
|
+
setFlash(next.has(favKey(current.app, current.action)) ? "★ starred" : "☆ unstarred");
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1129
1305
|
if (key.downArrow || key.ctrl && input === "n") {
|
|
1130
1306
|
setSelected((s) => Math.min(s + 1, results.length - 1));
|
|
1131
1307
|
return;
|
|
@@ -1159,6 +1335,17 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1159
1335
|
}
|
|
1160
1336
|
}, { isActive: mode === "search" });
|
|
1161
1337
|
const existingTags = [...new Set(entries.flatMap((e) => e.tags ?? []))].sort();
|
|
1338
|
+
if (mode === "filter") return /* @__PURE__ */ jsx(FilterPicker, {
|
|
1339
|
+
apps: [...new Set(entries.map((e) => e.app))].sort(),
|
|
1340
|
+
height: listHeight,
|
|
1341
|
+
width: left,
|
|
1342
|
+
onSelect: (f) => {
|
|
1343
|
+
setFilter(f);
|
|
1344
|
+
setSelected(0);
|
|
1345
|
+
setMode("search");
|
|
1346
|
+
},
|
|
1347
|
+
onCancel: () => setMode("search")
|
|
1348
|
+
});
|
|
1162
1349
|
if (mode === "add" && dataDir) return /* @__PURE__ */ jsx(AddEntryForm, {
|
|
1163
1350
|
apps: listApps(dataDir),
|
|
1164
1351
|
existingTags,
|
|
@@ -1202,13 +1389,18 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1202
1389
|
return /* @__PURE__ */ jsxs(Box, {
|
|
1203
1390
|
flexDirection: "column",
|
|
1204
1391
|
children: [
|
|
1205
|
-
/* @__PURE__ */ jsx(SearchInput, {
|
|
1392
|
+
/* @__PURE__ */ jsx(SearchInput, {
|
|
1393
|
+
query,
|
|
1394
|
+
filterLabel: filter.type === "app" ? `(filter: ${filter.app})` : filter.type === "favorites" ? "(★ Favorites)" : void 0
|
|
1395
|
+
}),
|
|
1206
1396
|
/* @__PURE__ */ jsxs(Box, { children: [/* @__PURE__ */ jsx(ResultList, {
|
|
1207
1397
|
results,
|
|
1208
1398
|
selected: sel,
|
|
1209
1399
|
query,
|
|
1210
1400
|
height: listHeight,
|
|
1211
|
-
width: left
|
|
1401
|
+
width: left,
|
|
1402
|
+
favorites,
|
|
1403
|
+
emptyMessage: filter.type === "favorites" ? "No favorites yet — ⌃S stars an entry." : void 0
|
|
1212
1404
|
}), /* @__PURE__ */ jsx(PreviewPane, {
|
|
1213
1405
|
entry: current,
|
|
1214
1406
|
width: right
|
|
@@ -1217,6 +1409,7 @@ function App({ entries: initial, errorCount = 0, dataDir, onCopy = copyToClipboa
|
|
|
1217
1409
|
flash,
|
|
1218
1410
|
errorCount,
|
|
1219
1411
|
resultCount: results.length,
|
|
1412
|
+
filterActive: filter.type !== "all",
|
|
1220
1413
|
confirm: pendingDelete ? `Delete '${pendingDelete.app}: ${pendingDelete.action}'?` : void 0
|
|
1221
1414
|
})
|
|
1222
1415
|
]
|
package/package.json
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arthony/keybook",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"publishConfig": {
|
|
3
|
+
"version": "0.6.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
5
7
|
"description": "A macOS TUI for searching keyboard shortcuts and recipes of your favorite apps",
|
|
6
8
|
"keywords": [
|
|
7
9
|
"cli",
|
|
@@ -15,22 +17,27 @@
|
|
|
15
17
|
"productivity"
|
|
16
18
|
],
|
|
17
19
|
"homepage": "https://github.com/Ariyapong/keybook#readme",
|
|
18
|
-
"repository": {
|
|
19
|
-
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/Ariyapong/keybook.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Ariyapong/keybook/issues"
|
|
26
|
+
},
|
|
20
27
|
"author": "Ariyapong Wimolnoch",
|
|
21
28
|
"type": "module",
|
|
22
|
-
"bin": {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
"
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
"
|
|
29
|
+
"bin": {
|
|
30
|
+
"keybook": "dist/cli.js",
|
|
31
|
+
"kb": "dist/cli.js"
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"seed",
|
|
36
|
+
"README.md",
|
|
37
|
+
"LICENSE"
|
|
38
|
+
],
|
|
39
|
+
"engines": {
|
|
40
|
+
"node": ">=22"
|
|
34
41
|
},
|
|
35
42
|
"dependencies": {
|
|
36
43
|
"commander": "^12.1.0",
|
|
@@ -51,8 +58,13 @@
|
|
|
51
58
|
"vitest": "^2.1.0"
|
|
52
59
|
},
|
|
53
60
|
"license": "MIT",
|
|
54
|
-
"
|
|
55
|
-
|
|
56
|
-
"
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "tsdown",
|
|
63
|
+
"dev": "tsx src/cli.ts",
|
|
64
|
+
"test": "vitest run",
|
|
65
|
+
"test:watch": "vitest",
|
|
66
|
+
"typecheck": "tsc --noEmit",
|
|
67
|
+
"lint": "biome check .",
|
|
68
|
+
"format": "biome format --write ."
|
|
57
69
|
}
|
|
58
|
-
}
|
|
70
|
+
}
|