@arthony/keybook 0.4.1 → 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 +1 -1
- package/seed/chrome.yaml +48 -0
- package/seed/cursor.yaml +31 -0
- package/seed/jetbrains.yaml +63 -0
- package/seed/notion.yaml +62 -0
- package/seed/safari.yaml +51 -0
- package/seed/vscode.yaml +24 -0
- package/seed/zed.yaml +45 -0
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
package/seed/chrome.yaml
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
app: Google Chrome
|
|
2
|
+
entries:
|
|
3
|
+
- action: Open a new tab
|
|
4
|
+
keys: "⌘T"
|
|
5
|
+
tags: [new tab]
|
|
6
|
+
- action: Reopen the last closed tab
|
|
7
|
+
keys: "⌘⇧T"
|
|
8
|
+
tags: [restore tab]
|
|
9
|
+
- action: Open a new window
|
|
10
|
+
keys: "⌘N"
|
|
11
|
+
- action: Open a new Incognito window
|
|
12
|
+
keys: "⌘⇧N"
|
|
13
|
+
tags: [incognito, private]
|
|
14
|
+
- action: Close the current tab
|
|
15
|
+
keys: "⌘W"
|
|
16
|
+
- action: Next / previous tab
|
|
17
|
+
keys: "⌃⇥, ⌃⇧⇥"
|
|
18
|
+
- action: Jump to tab 1–8
|
|
19
|
+
keys: "⌘1"
|
|
20
|
+
notes: ⌘1–⌘8 jump to that tab; ⌘9 jumps to the last tab.
|
|
21
|
+
- action: Focus the address bar
|
|
22
|
+
keys: "⌘L"
|
|
23
|
+
tags: [omnibox, url]
|
|
24
|
+
- action: Find on the page
|
|
25
|
+
keys: "⌘F"
|
|
26
|
+
- action: Reload / hard reload
|
|
27
|
+
keys: "⌘R, ⌘⇧R"
|
|
28
|
+
- action: Open Developer Tools
|
|
29
|
+
keys: "⌘⌥I"
|
|
30
|
+
tags: [devtools, inspect]
|
|
31
|
+
- action: View page source
|
|
32
|
+
keys: "⌘⌥U"
|
|
33
|
+
tags: [source]
|
|
34
|
+
- action: Open History
|
|
35
|
+
keys: "⌘Y"
|
|
36
|
+
- action: Open Downloads
|
|
37
|
+
keys: "⌘⇧J"
|
|
38
|
+
tags: [downloads]
|
|
39
|
+
- action: Bookmark this page
|
|
40
|
+
keys: "⌘D"
|
|
41
|
+
tags: [favorite]
|
|
42
|
+
- action: Zoom in / out / reset
|
|
43
|
+
keys: "⌘+, ⌘-, ⌘0"
|
|
44
|
+
- action: Back / forward
|
|
45
|
+
keys: "⌘[, ⌘]"
|
|
46
|
+
- action: Toggle full screen
|
|
47
|
+
keys: "⌃⌘F"
|
|
48
|
+
tags: [fullscreen]
|
package/seed/cursor.yaml
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
app: Cursor
|
|
2
|
+
entries:
|
|
3
|
+
- action: Inherits the VS Code keymap
|
|
4
|
+
steps:
|
|
5
|
+
- Cursor is a VS Code fork — all standard editing/navigation shortcuts match VS Code
|
|
6
|
+
- See the "VS Code" entries in keybook for those
|
|
7
|
+
notes: This file lists only Cursor's AI-specific additions.
|
|
8
|
+
tags: [info, vscode]
|
|
9
|
+
- action: Inline edit (edit selection with AI)
|
|
10
|
+
keys: "⌘K"
|
|
11
|
+
tags: [ai, edit]
|
|
12
|
+
- action: Toggle the AI chat / sidepanel
|
|
13
|
+
keys: "⌘I, ⌘L"
|
|
14
|
+
notes: Both shortcuts toggle the same sidepanel. ⌘L also has a secondary role — when code is selected it adds the selection to a new chat.
|
|
15
|
+
tags: [ai, chat]
|
|
16
|
+
- action: Toggle Agent layout
|
|
17
|
+
keys: "⌘E"
|
|
18
|
+
tags: [ai, agent]
|
|
19
|
+
- action: Add the current selection to chat
|
|
20
|
+
keys: "⌘⇧L"
|
|
21
|
+
tags: [ai, chat, context]
|
|
22
|
+
- action: Accept an inline (Tab) completion
|
|
23
|
+
keys: "⇥"
|
|
24
|
+
tags: [ai, autocomplete]
|
|
25
|
+
- action: Accept / reject all AI-suggested changes
|
|
26
|
+
keys: "⌘⏎, ⌘⌫"
|
|
27
|
+
notes: ⌘⏎ accepts all suggested changes; ⌘⌫ rejects them.
|
|
28
|
+
tags: [ai, diff]
|
|
29
|
+
- action: Open a new AI chat
|
|
30
|
+
keys: "⌘N"
|
|
31
|
+
tags: [ai, chat]
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
app: JetBrains
|
|
2
|
+
entries:
|
|
3
|
+
- action: Search Everywhere
|
|
4
|
+
steps:
|
|
5
|
+
- Press ⇧ twice (double-Shift)
|
|
6
|
+
- Type a class, file, action, or setting name
|
|
7
|
+
notes: The universal search across the IDE. This is the default macOS keymap, shared by IntelliJ IDEA, WebStorm, PyCharm, etc.
|
|
8
|
+
tags: [search, navigate]
|
|
9
|
+
- action: Find Action
|
|
10
|
+
keys: "⌘⇧A"
|
|
11
|
+
tags: [action, command]
|
|
12
|
+
- action: Recent files
|
|
13
|
+
keys: "⌘E"
|
|
14
|
+
tags: [navigate, recent]
|
|
15
|
+
- action: Go to class
|
|
16
|
+
keys: "⌘O"
|
|
17
|
+
tags: [navigate, goto]
|
|
18
|
+
- action: Go to file
|
|
19
|
+
keys: "⌘⇧O"
|
|
20
|
+
tags: [navigate, goto]
|
|
21
|
+
- action: Go to symbol
|
|
22
|
+
keys: "⌥⌘O"
|
|
23
|
+
tags: [navigate, goto]
|
|
24
|
+
- action: Go to declaration / definition
|
|
25
|
+
keys: "⌘B"
|
|
26
|
+
tags: [navigate]
|
|
27
|
+
- action: Find usages
|
|
28
|
+
keys: "⌥F7"
|
|
29
|
+
tags: [search, references]
|
|
30
|
+
- action: Rename (refactor)
|
|
31
|
+
keys: "⇧F6"
|
|
32
|
+
tags: [refactor]
|
|
33
|
+
- action: Reformat code
|
|
34
|
+
keys: "⌥⌘L"
|
|
35
|
+
tags: [format]
|
|
36
|
+
- action: Show intention actions / quick-fix
|
|
37
|
+
keys: "⌥⏎"
|
|
38
|
+
tags: [quickfix, lightbulb]
|
|
39
|
+
- action: Run
|
|
40
|
+
keys: "⌃R"
|
|
41
|
+
tags: [run]
|
|
42
|
+
- action: Debug
|
|
43
|
+
keys: "⌃D"
|
|
44
|
+
tags: [debug]
|
|
45
|
+
- action: Toggle line comment
|
|
46
|
+
keys: "⌘/"
|
|
47
|
+
tags: [comment]
|
|
48
|
+
- action: Duplicate the current line / selection
|
|
49
|
+
keys: "⌘D"
|
|
50
|
+
tags: [edit]
|
|
51
|
+
- action: Delete the current line
|
|
52
|
+
keys: "⌘⌫"
|
|
53
|
+
tags: [delete line]
|
|
54
|
+
- action: Generate code
|
|
55
|
+
keys: "⌘N"
|
|
56
|
+
notes: Constructors, getters/setters, overrides, etc.
|
|
57
|
+
tags: [generate]
|
|
58
|
+
- action: Extend / shrink selection
|
|
59
|
+
keys: "⌥↑, ⌥↓"
|
|
60
|
+
tags: [select]
|
|
61
|
+
- action: Code completion
|
|
62
|
+
keys: "⌃␣"
|
|
63
|
+
tags: [autocomplete]
|
package/seed/notion.yaml
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
app: Notion
|
|
2
|
+
entries:
|
|
3
|
+
- action: Open Quick Find / search
|
|
4
|
+
keys: "⌘P, ⌘K"
|
|
5
|
+
tags: [search, find, navigate]
|
|
6
|
+
- action: Create a new page
|
|
7
|
+
keys: "⌘N"
|
|
8
|
+
tags: [new page]
|
|
9
|
+
- action: Open a new tab
|
|
10
|
+
keys: "⌘T"
|
|
11
|
+
tags: [tab]
|
|
12
|
+
- action: Open a new Notion window
|
|
13
|
+
keys: "⌘⇧N"
|
|
14
|
+
tags: [window]
|
|
15
|
+
- action: Reopen last closed tab
|
|
16
|
+
keys: "⌘⇧T"
|
|
17
|
+
tags: [tab, restore]
|
|
18
|
+
- action: Close the current tab
|
|
19
|
+
keys: "⌘W"
|
|
20
|
+
tags: [tab]
|
|
21
|
+
- action: Copy link to the current page
|
|
22
|
+
keys: "⌘L"
|
|
23
|
+
tags: [link, share]
|
|
24
|
+
- action: Navigate back / forward
|
|
25
|
+
keys: "⌘[, ⌘]"
|
|
26
|
+
tags: [navigate, history]
|
|
27
|
+
- action: Toggle dark / light mode
|
|
28
|
+
keys: "⌘⇧L"
|
|
29
|
+
tags: [appearance, theme]
|
|
30
|
+
- action: Bold selected text
|
|
31
|
+
keys: "⌘B"
|
|
32
|
+
tags: [format, text]
|
|
33
|
+
- action: Italicize selected text
|
|
34
|
+
keys: "⌘I"
|
|
35
|
+
tags: [format, text]
|
|
36
|
+
- action: Underline selected text
|
|
37
|
+
keys: "⌘U"
|
|
38
|
+
tags: [format, text]
|
|
39
|
+
- action: Strikethrough selected text
|
|
40
|
+
keys: "⌘⇧S"
|
|
41
|
+
tags: [format, text]
|
|
42
|
+
- action: Inline code for selected text
|
|
43
|
+
keys: "⌘E"
|
|
44
|
+
tags: [format, code]
|
|
45
|
+
- action: Add link to selected text
|
|
46
|
+
keys: "⌘K"
|
|
47
|
+
tags: [link]
|
|
48
|
+
- action: Indent / un-indent a block
|
|
49
|
+
keys: "⇥, ⇧⇥"
|
|
50
|
+
tags: [indent, nest, block]
|
|
51
|
+
- action: Duplicate selected blocks
|
|
52
|
+
keys: "⌘D"
|
|
53
|
+
tags: [block, duplicate]
|
|
54
|
+
- action: Add a comment
|
|
55
|
+
keys: "⌘⇧M"
|
|
56
|
+
tags: [comment]
|
|
57
|
+
- action: Insert a block (slash menu)
|
|
58
|
+
steps:
|
|
59
|
+
- Type / at the start of a line
|
|
60
|
+
- Pick a block type (heading, to-do, toggle, code, …)
|
|
61
|
+
notes: The slash menu is the main way to insert and transform blocks.
|
|
62
|
+
tags: [block, insert, tip]
|
package/seed/safari.yaml
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
app: Safari
|
|
2
|
+
entries:
|
|
3
|
+
- action: Open a new tab
|
|
4
|
+
keys: "⌘T"
|
|
5
|
+
tags: [new tab]
|
|
6
|
+
- action: Open a new window
|
|
7
|
+
keys: "⌘N"
|
|
8
|
+
- action: Open a new private window
|
|
9
|
+
keys: "⌘⇧N"
|
|
10
|
+
tags: [private, incognito]
|
|
11
|
+
- action: Reopen the last closed tab
|
|
12
|
+
keys: "⌘⇧T"
|
|
13
|
+
tags: [restore tab]
|
|
14
|
+
- action: Close the current tab
|
|
15
|
+
keys: "⌘W"
|
|
16
|
+
- action: Next / previous tab
|
|
17
|
+
keys: "⌃⇥, ⌃⇧⇥"
|
|
18
|
+
- action: Jump to tab 1–8
|
|
19
|
+
keys: "⌘1"
|
|
20
|
+
notes: ⌘1–⌘8 select the first eight tabs; ⌘9 selects the last tab.
|
|
21
|
+
- action: Focus the Smart Search field (address bar)
|
|
22
|
+
keys: "⌘L"
|
|
23
|
+
tags: [url, search]
|
|
24
|
+
- action: Find on the page
|
|
25
|
+
keys: "⌘F"
|
|
26
|
+
- action: Reload / reload from origin
|
|
27
|
+
keys: "⌘R, ⌥⌘R"
|
|
28
|
+
notes: ⌥⌘R reloads ignoring the cache (from origin).
|
|
29
|
+
- action: Show / hide the sidebar
|
|
30
|
+
keys: "⌘⇧L"
|
|
31
|
+
tags: [sidebar, bookmarks]
|
|
32
|
+
- action: Show Reader
|
|
33
|
+
keys: "⌘⇧R"
|
|
34
|
+
tags: [reader]
|
|
35
|
+
- action: Add a bookmark for this page
|
|
36
|
+
keys: "⌘D"
|
|
37
|
+
tags: [favorite]
|
|
38
|
+
- action: Back / forward
|
|
39
|
+
keys: "⌘[, ⌘]"
|
|
40
|
+
- action: Zoom in / out / reset
|
|
41
|
+
keys: "⌘+, ⌘-, ⌘0"
|
|
42
|
+
- action: Show the tab overview
|
|
43
|
+
keys: "⌘⇧\\"
|
|
44
|
+
tags: [tabs, overview]
|
|
45
|
+
- action: Open the Web Inspector
|
|
46
|
+
keys: "⌥⌘I"
|
|
47
|
+
notes: Requires the Develop menu — enable it in Settings → Advanced → "Show features for web developers".
|
|
48
|
+
tags: [devtools, inspect]
|
|
49
|
+
- action: Toggle full screen
|
|
50
|
+
keys: "⌃⌘F"
|
|
51
|
+
tags: [fullscreen]
|
package/seed/vscode.yaml
CHANGED
|
@@ -63,3 +63,27 @@ entries:
|
|
|
63
63
|
- action: Open Keyboard Shortcuts
|
|
64
64
|
keys: "⌘K, ⌘S"
|
|
65
65
|
tags: [keybindings, shortcuts]
|
|
66
|
+
- action: Go to Symbol in the file
|
|
67
|
+
keys: "⌘⇧O"
|
|
68
|
+
tags: [navigate, symbol]
|
|
69
|
+
- action: Go to Symbol in the workspace
|
|
70
|
+
keys: "⌘T"
|
|
71
|
+
tags: [navigate, symbol]
|
|
72
|
+
- action: Find All References
|
|
73
|
+
keys: "⇧F12"
|
|
74
|
+
tags: [references]
|
|
75
|
+
- action: Toggle the panel
|
|
76
|
+
keys: "⌘J"
|
|
77
|
+
tags: [panel]
|
|
78
|
+
- action: Toggle word wrap
|
|
79
|
+
keys: "⌥Z"
|
|
80
|
+
tags: [wrap]
|
|
81
|
+
- action: Show Source Control
|
|
82
|
+
keys: "⌃⇧G"
|
|
83
|
+
tags: [git, scm]
|
|
84
|
+
- action: Show the Problems panel
|
|
85
|
+
keys: "⌘⇧M"
|
|
86
|
+
tags: [errors, diagnostics]
|
|
87
|
+
- action: Reopen the last closed editor
|
|
88
|
+
keys: "⌘⇧T"
|
|
89
|
+
tags: [restore]
|
package/seed/zed.yaml
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
app: Zed
|
|
2
|
+
entries:
|
|
3
|
+
- action: Open the command palette
|
|
4
|
+
keys: "⌘⇧P"
|
|
5
|
+
tags: [commands, palette]
|
|
6
|
+
- action: Open a file by name (file finder)
|
|
7
|
+
keys: "⌘P"
|
|
8
|
+
tags: [open file, goto]
|
|
9
|
+
- action: Project-wide search
|
|
10
|
+
keys: "⌘⇧F"
|
|
11
|
+
tags: [search, find]
|
|
12
|
+
- action: Find in buffer / replace in buffer
|
|
13
|
+
keys: "⌘F, ⌘⌥F"
|
|
14
|
+
notes: ⌘F opens find; ⌘⌥F opens find-and-replace.
|
|
15
|
+
tags: [find, replace]
|
|
16
|
+
- action: Add a cursor above / below
|
|
17
|
+
keys: "⌘⌥↑, ⌘⌥↓"
|
|
18
|
+
tags: [multi cursor]
|
|
19
|
+
- action: Select the next occurrence
|
|
20
|
+
keys: "⌘D"
|
|
21
|
+
tags: [multi cursor, select]
|
|
22
|
+
- action: Go to definition
|
|
23
|
+
keys: "F12"
|
|
24
|
+
tags: [navigate]
|
|
25
|
+
- action: Rename symbol
|
|
26
|
+
keys: "F2"
|
|
27
|
+
tags: [refactor]
|
|
28
|
+
- action: Toggle the integrated terminal panel
|
|
29
|
+
keys: "⌃`"
|
|
30
|
+
tags: [terminal]
|
|
31
|
+
- action: Toggle the project panel
|
|
32
|
+
keys: "⌘⇧E"
|
|
33
|
+
tags: [explorer, files]
|
|
34
|
+
- action: Go to symbol in the buffer (outline)
|
|
35
|
+
keys: "⌘⇧O"
|
|
36
|
+
tags: [navigate, outline]
|
|
37
|
+
- action: Toggle line comment
|
|
38
|
+
keys: "⌘/"
|
|
39
|
+
tags: [comment]
|
|
40
|
+
- action: Format the buffer
|
|
41
|
+
keys: "⌘⇧I"
|
|
42
|
+
tags: [format]
|
|
43
|
+
- action: Toggle the AI agent panel
|
|
44
|
+
keys: "⌘?"
|
|
45
|
+
tags: [ai, assistant]
|