shellwise 0.1.0 → 0.1.1
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/package.json +1 -1
- package/src/cli/add.ts +1 -2
- package/src/cli/suggest.ts +7 -4
- package/src/daemon/server.ts +14 -7
- package/src/db/queries.ts +4 -2
- package/src/index.ts +3 -2
- package/src/search/scorer.ts +1 -2
- package/src/utils/constants.ts +1 -0
package/package.json
CHANGED
package/src/cli/add.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { insertCommand } from "../db/queries";
|
|
2
2
|
import { getHostname } from "../utils/platform";
|
|
3
|
+
import { IGNORED_COMMANDS } from "../utils/constants";
|
|
3
4
|
|
|
4
5
|
interface AddOptions {
|
|
5
6
|
command: string;
|
|
@@ -10,8 +11,6 @@ interface AddOptions {
|
|
|
10
11
|
shell?: string;
|
|
11
12
|
}
|
|
12
13
|
|
|
13
|
-
const IGNORED_COMMANDS = new Set(["ls", "cd", "pwd", "exit", "clear", "sw"]);
|
|
14
|
-
|
|
15
14
|
export function runAdd(opts: AddOptions): void {
|
|
16
15
|
const cmd = opts.command.trim();
|
|
17
16
|
|
package/src/cli/suggest.ts
CHANGED
|
@@ -10,15 +10,18 @@ export function runSuggest(query: string, limit: number = 5): void {
|
|
|
10
10
|
const db = getDb();
|
|
11
11
|
const historyResults: string[] = [];
|
|
12
12
|
|
|
13
|
+
// Escape LIKE wildcards in user input
|
|
14
|
+
const escapedQuery = query.replace(/[%_\\]/g, "\\$&");
|
|
15
|
+
|
|
13
16
|
// History: prefix matches
|
|
14
17
|
const prefixes = db
|
|
15
18
|
.query<{ command: string }, [string, number]>(
|
|
16
19
|
`SELECT command FROM command_stats
|
|
17
|
-
WHERE command LIKE ? || '%'
|
|
20
|
+
WHERE command LIKE ? || '%' ESCAPE '\\'
|
|
18
21
|
ORDER BY frecency_score DESC
|
|
19
22
|
LIMIT ?`
|
|
20
23
|
)
|
|
21
|
-
.all(
|
|
24
|
+
.all(escapedQuery, limit);
|
|
22
25
|
|
|
23
26
|
for (const r of prefixes) {
|
|
24
27
|
if (r.command !== query) historyResults.push(r.command);
|
|
@@ -32,11 +35,11 @@ export function runSuggest(query: string, limit: number = 5): void {
|
|
|
32
35
|
const contains = db
|
|
33
36
|
.query<{ command: string }, [string, string, number]>(
|
|
34
37
|
`SELECT command FROM command_stats
|
|
35
|
-
WHERE command LIKE '%' || ? || '%' AND command != ?
|
|
38
|
+
WHERE command LIKE '%' || ? || '%' ESCAPE '\\' AND command != ?
|
|
36
39
|
ORDER BY frecency_score DESC
|
|
37
40
|
LIMIT ?`
|
|
38
41
|
)
|
|
39
|
-
.all(
|
|
42
|
+
.all(escapedQuery, query, remaining + historyResults.length);
|
|
40
43
|
|
|
41
44
|
for (const r of contains) {
|
|
42
45
|
if (!resultSet.has(r.command) && r.command !== query) {
|
package/src/daemon/server.ts
CHANGED
|
@@ -2,15 +2,15 @@ import { getDb, closeDb } from "../db/connection";
|
|
|
2
2
|
import { insertCommand } from "../db/queries";
|
|
3
3
|
import { getHostname } from "../utils/platform";
|
|
4
4
|
import { getCommonSuggestions } from "../data/common-commands";
|
|
5
|
+
import { IGNORED_COMMANDS } from "../utils/constants";
|
|
5
6
|
import { parseRequest, getSocketPath, getPidPath, getDaemonPort } from "./protocol";
|
|
6
7
|
import { unlinkSync, writeFileSync, existsSync } from "fs";
|
|
7
8
|
import type { Socket } from "bun";
|
|
8
|
-
|
|
9
|
-
const IGNORED_COMMANDS = new Set(["ls", "cd", "pwd", "exit", "clear", "sw"]);
|
|
10
9
|
const IDLE_TIMEOUT = 30 * 60_000; // 30 min
|
|
11
10
|
|
|
12
11
|
let idleTimer: ReturnType<typeof setTimeout> | null = null;
|
|
13
12
|
let server: ReturnType<typeof Bun.listen> | null = null;
|
|
13
|
+
let tcpServer: ReturnType<typeof Bun.listen> | null = null;
|
|
14
14
|
|
|
15
15
|
// Pre-warm DB + prepared statements on start
|
|
16
16
|
let suggestPrefix: ReturnType<ReturnType<typeof getDb>["prepare"]>;
|
|
@@ -20,13 +20,13 @@ function initPreparedStatements() {
|
|
|
20
20
|
const db = getDb();
|
|
21
21
|
suggestPrefix = db.prepare(
|
|
22
22
|
`SELECT command FROM command_stats
|
|
23
|
-
WHERE command LIKE ?1 || '%'
|
|
23
|
+
WHERE command LIKE ?1 || '%' ESCAPE '\\'
|
|
24
24
|
ORDER BY frecency_score DESC
|
|
25
25
|
LIMIT ?2`
|
|
26
26
|
);
|
|
27
27
|
suggestContains = db.prepare(
|
|
28
28
|
`SELECT command FROM command_stats
|
|
29
|
-
WHERE command LIKE '%' || ?1 || '%' AND command != ?1
|
|
29
|
+
WHERE command LIKE '%' || ?1 || '%' ESCAPE '\\' AND command != ?1
|
|
30
30
|
ORDER BY frecency_score DESC
|
|
31
31
|
LIMIT ?2`
|
|
32
32
|
);
|
|
@@ -55,8 +55,11 @@ function handleRequest(raw: string): string {
|
|
|
55
55
|
const historyResults: string[] = [];
|
|
56
56
|
const historyLimit = 5;
|
|
57
57
|
|
|
58
|
+
// Escape LIKE wildcards in user input
|
|
59
|
+
const escapedQuery = req.query.replace(/[%_\\]/g, "\\$&");
|
|
60
|
+
|
|
58
61
|
// History: prefix matches
|
|
59
|
-
const prefixes = suggestPrefix.all(
|
|
62
|
+
const prefixes = suggestPrefix.all(escapedQuery, historyLimit) as { command: string }[];
|
|
60
63
|
for (const r of prefixes) {
|
|
61
64
|
if (r.command !== req.query) historyResults.push(r.command);
|
|
62
65
|
}
|
|
@@ -65,7 +68,7 @@ function handleRequest(raw: string): string {
|
|
|
65
68
|
if (historyResults.length < historyLimit) {
|
|
66
69
|
const remaining = historyLimit - historyResults.length;
|
|
67
70
|
const resultSet = new Set(historyResults);
|
|
68
|
-
const contains = suggestContains.all(
|
|
71
|
+
const contains = suggestContains.all(escapedQuery, remaining + historyResults.length) as {
|
|
69
72
|
command: string;
|
|
70
73
|
}[];
|
|
71
74
|
for (const r of contains) {
|
|
@@ -151,7 +154,7 @@ export function startServer(): void {
|
|
|
151
154
|
});
|
|
152
155
|
|
|
153
156
|
const port = getDaemonPort();
|
|
154
|
-
Bun.listen({
|
|
157
|
+
tcpServer = Bun.listen({
|
|
155
158
|
hostname: "127.0.0.1",
|
|
156
159
|
port,
|
|
157
160
|
socket: socketHandlers,
|
|
@@ -177,6 +180,10 @@ export function stopServer(): void {
|
|
|
177
180
|
server.stop(true);
|
|
178
181
|
server = null;
|
|
179
182
|
}
|
|
183
|
+
if (tcpServer) {
|
|
184
|
+
tcpServer.stop(true);
|
|
185
|
+
tcpServer = null;
|
|
186
|
+
}
|
|
180
187
|
closeDb();
|
|
181
188
|
|
|
182
189
|
const socketPath = getSocketPath();
|
package/src/db/queries.ts
CHANGED
|
@@ -95,8 +95,10 @@ export function searchCommands(opts: SearchOptions): CommandStats[] {
|
|
|
95
95
|
const params: (string | number)[] = [];
|
|
96
96
|
|
|
97
97
|
if (opts.query) {
|
|
98
|
-
conditions.push("cs.command LIKE ?");
|
|
99
|
-
|
|
98
|
+
conditions.push("cs.command LIKE ? ESCAPE '\\'");
|
|
99
|
+
// Escape LIKE wildcards in user input
|
|
100
|
+
const escaped = opts.query.replace(/[%_\\]/g, "\\$&");
|
|
101
|
+
params.push(`%${escaped}%`);
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
if (opts.cwd) {
|
package/src/index.ts
CHANGED
|
@@ -10,7 +10,6 @@ import { runPrune } from "./cli/prune";
|
|
|
10
10
|
import { closeDb } from "./db/connection";
|
|
11
11
|
import { startServer, isDaemonRunning, getDaemonInfo } from "./daemon/server";
|
|
12
12
|
import { daemonRequest } from "./daemon/client";
|
|
13
|
-
import { getSocketPath, getPidPath, getDaemonPort } from "./daemon/protocol";
|
|
14
13
|
|
|
15
14
|
const args = process.argv.slice(2);
|
|
16
15
|
const command = args[0];
|
|
@@ -86,7 +85,9 @@ async function main(): Promise<void> {
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
// Try daemon first
|
|
89
|
-
|
|
88
|
+
// Strip tabs from command to avoid breaking protocol delimiter
|
|
89
|
+
const safeCommand = flags.command.replace(/\t/g, " ");
|
|
90
|
+
const addMsg = `ADD\t${safeCommand}\t${flags.cwd || ""}\t${flags["exit-code"] || "0"}\t${flags.duration || "0"}\t${flags.session || ""}\t${flags.shell || ""}\n`;
|
|
90
91
|
const addResult = await daemonRequest(addMsg);
|
|
91
92
|
if (!addResult) {
|
|
92
93
|
// Fallback: direct
|
package/src/search/scorer.ts
CHANGED
|
@@ -37,13 +37,12 @@ export function rankResults(
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
const results: ScoredResult[] = [];
|
|
40
|
+
const maxFrecency = stats.reduce((max, s) => Math.max(max, s.frecency_score), 1);
|
|
40
41
|
|
|
41
42
|
for (const match of matches) {
|
|
42
43
|
const stat = statsMap.get(match.text);
|
|
43
44
|
if (!stat) continue;
|
|
44
45
|
|
|
45
|
-
// Normalize frecency to 0-1 range
|
|
46
|
-
const maxFrecency = stats.reduce((max, s) => Math.max(max, s.frecency_score), 1);
|
|
47
46
|
const normalizedFrecency = stat.frecency_score / maxFrecency;
|
|
48
47
|
|
|
49
48
|
// CWD bonus
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const IGNORED_COMMANDS = new Set(["ls", "cd", "pwd", "exit", "clear", "sw"]);
|