shellwise 0.1.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/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # shellwise
2
+
3
+ Smart command history with inline auto-suggest and fuzzy search for your terminal.
4
+
5
+ ![License](https://img.shields.io/badge/license-MIT-blue)
6
+ ![Runtime](https://img.shields.io/badge/runtime-Bun-black)
7
+
8
+ ## Features
9
+
10
+ - **Auto-save** — commands are recorded automatically after successful execution (exit code 0)
11
+ - **Auto-suggest** — inline dropdown appears as you type, no `Ctrl+R` needed
12
+ - **Fuzzy search** — `Ctrl+R` opens a full interactive search with real-time filtering
13
+ - **Smart ranking** — frecency scoring (frequency x recency) for relevant results
14
+ - **Common commands** — suggests popular commands (git, npm, docker, etc.) even with empty history
15
+ - **Daemon mode** — persistent background process for sub-millisecond suggest latency (~1-3ms)
16
+ - **Zero-fork** — uses `zsh/net/tcp` persistent connection, never spawns a process while typing
17
+
18
+ ## How it works
19
+
20
+ ```
21
+ You type: git s
22
+ ┌─────────────────────────────┐
23
+ │ › git status (history)
24
+ │ git stash (history)
25
+ │ git switch main (history)
26
+ │ git stash pop (common)
27
+ │ git stash list (common)
28
+ └─────────────────────────────┘
29
+ Tab/Shift+Tab to navigate, Enter to select, Esc to dismiss
30
+ ```
31
+
32
+ ## Install
33
+
34
+ ```bash
35
+ bun install -g shellwise
36
+ ```
37
+
38
+ That's it. Shell integration is auto-injected into your `~/.zshrc` or `~/.bashrc` on install. Restart your terminal to activate.
39
+
40
+ ### Manual setup
41
+
42
+ If auto-setup didn't work, add to your shell config:
43
+
44
+ ```bash
45
+ # ~/.zshrc
46
+ eval "$(sw init zsh)"
47
+
48
+ # ~/.bashrc
49
+ eval "$(sw init bash)"
50
+ ```
51
+
52
+ ## Usage
53
+
54
+ ### Auto-suggest (while typing)
55
+
56
+ Just start typing — suggestions appear automatically after 2+ characters.
57
+
58
+ | Key | Action |
59
+ |-----|--------|
60
+ | `Tab` | Next suggestion |
61
+ | `Shift+Tab` | Previous suggestion |
62
+ | `Enter` | Accept selected suggestion |
63
+ | `→` (Right arrow) | Accept suggestion inline |
64
+ | `Esc` | Dismiss suggestions |
65
+
66
+ ### Interactive search
67
+
68
+ Press `Ctrl+R` to open full fuzzy search:
69
+
70
+ | Key | Action |
71
+ |-----|--------|
72
+ | Type | Filter results in real-time |
73
+ | `↑` / `↓` | Navigate results |
74
+ | `Enter` | Accept and paste to command line |
75
+ | `Esc` | Cancel |
76
+
77
+ ### Commands
78
+
79
+ ```bash
80
+ sw search [--query <text>] # Interactive fuzzy search (Ctrl+R)
81
+ sw suggest --query <text> # Get top suggestion (used by shell hook)
82
+ sw add --command <cmd> # Save a command to history
83
+ sw init <zsh|bash> # Output shell integration script
84
+ sw import [zsh|bash] # Import existing shell history
85
+ sw stats # Show usage statistics
86
+ sw prune --days <n> # Remove entries older than n days
87
+ sw daemon start|stop|status # Manage background daemon
88
+ ```
89
+
90
+ ### Import existing history
91
+
92
+ ```bash
93
+ sw import zsh # Import from ~/.zsh_history
94
+ sw import bash # Import from ~/.bash_history
95
+ ```
96
+
97
+ ## Architecture
98
+
99
+ ```
100
+ ┌──────────────┐ TCP (persistent) ┌──────────────────┐
101
+ │ Zsh/Bash │◄────────────────────────►│ sw daemon │
102
+ │ (shell) │ ~1-3ms round-trip │ (Bun process) │
103
+ └──────────────┘ └────────┬─────────┘
104
+
105
+ ┌────────▼─────────┐
106
+ │ SQLite (WAL) │
107
+ │ history.db │
108
+ └──────────────────┘
109
+ ```
110
+
111
+ - **Shell hooks** (`preexec`/`precmd`) capture commands after execution
112
+ - **Persistent TCP connection** opened once at shell init, reused for all queries
113
+ - **Prepared SQLite statements** pre-warmed at daemon start for instant queries
114
+ - **Frecency scoring** = frequency × recency_weight — recent commands rank higher
115
+ - **Auto-idle shutdown** — daemon stops after 30 min of inactivity
116
+
117
+ ## Data storage
118
+
119
+ ```
120
+ ~/.local/share/shellwise/history.db # SQLite database
121
+ ~/.config/shellwise/ # Config (future use)
122
+ /tmp/shellwise-<uid>.sock # Unix socket
123
+ /tmp/shellwise-<uid>.pid # Daemon PID + port
124
+ ```
125
+
126
+ ## Requirements
127
+
128
+ - [Bun](https://bun.sh) >= 1.0.0
129
+ - Zsh or Bash
130
+
131
+ ## Uninstall
132
+
133
+ ```bash
134
+ bun remove -g shellwise
135
+ ```
136
+
137
+ Shell integration is automatically removed from your config on uninstall.
138
+
139
+ ## License
140
+
141
+ MIT
package/bin/sw.js ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execFileSync } = require("child_process");
4
+ const { resolve } = require("path");
5
+
6
+ const entry = resolve(__dirname, "..", "src", "index.ts");
7
+
8
+ try {
9
+ execFileSync("bun", [entry, ...process.argv.slice(2)], { stdio: "inherit" });
10
+ } catch (err) {
11
+ if (err.code === "ENOENT") {
12
+ console.error("shellwise requires Bun runtime. Install it: https://bun.sh");
13
+ process.exit(1);
14
+ }
15
+ process.exit(err.status ?? 1);
16
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "shellwise",
3
+ "version": "0.1.0",
4
+ "description": "Smart command history with inline auto-suggest and fuzzy search for your terminal",
5
+ "type": "module",
6
+ "bin": {
7
+ "sw": "./bin/sw.js"
8
+ },
9
+ "files": [
10
+ "bin/sw.js",
11
+ "src/**/*.ts",
12
+ "scripts/*.sh"
13
+ ],
14
+ "scripts": {
15
+ "dev": "bun run src/index.ts",
16
+ "build": "bun build src/index.ts --compile --outfile dist/sw",
17
+ "test": "bun test",
18
+ "postinstall": "bash scripts/setup.sh || true",
19
+ "preuninstall": "bash scripts/uninstall.sh || true"
20
+ },
21
+ "keywords": [
22
+ "shell",
23
+ "history",
24
+ "fuzzy-search",
25
+ "terminal",
26
+ "cli",
27
+ "autosuggest",
28
+ "zsh",
29
+ "bash",
30
+ "command-history",
31
+ "shellwise"
32
+ ],
33
+ "author": "Vu Duc Tuan",
34
+ "license": "MIT",
35
+ "engines": {
36
+ "bun": ">=1.0.0"
37
+ },
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "https://github.com/kurovu146/shellwise"
41
+ },
42
+ "homepage": "https://github.com/kurovu146/shellwise#readme",
43
+ "devDependencies": {
44
+ "@types/bun": "latest"
45
+ }
46
+ }
@@ -0,0 +1,92 @@
1
+ #!/bin/bash
2
+ # Post-install: auto-add shell integration
3
+
4
+ set -e
5
+
6
+ # Colors
7
+ GREEN='\033[0;32m'
8
+ YELLOW='\033[0;33m'
9
+ DIM='\033[2m'
10
+ BOLD='\033[1m'
11
+ RESET='\033[0m'
12
+
13
+ MARKER="# shellwise shell integration"
14
+
15
+ # Detect sw binary path
16
+ SW_BIN=""
17
+ if command -v sw &>/dev/null; then
18
+ SW_BIN="sw"
19
+ else
20
+ # Try common global bin paths
21
+ for p in "$HOME/.bun/bin/sw" "$HOME/.local/bin/sw" "$(npm prefix -g 2>/dev/null)/bin/sw"; do
22
+ if [[ -x "$p" ]]; then
23
+ SW_BIN="$p"
24
+ break
25
+ fi
26
+ done
27
+ fi
28
+
29
+ if [[ -z "$SW_BIN" ]]; then
30
+ echo -e "${YELLOW}[shellwise]${RESET} Could not find sw binary. Add manually:"
31
+ echo ' eval "$(sw init zsh)" # add to ~/.zshrc'
32
+ exit 0
33
+ fi
34
+
35
+ inject_to_rc() {
36
+ local rc_file="$1"
37
+ local shell_name="$2"
38
+
39
+ # Skip if already injected
40
+ if grep -qF "$MARKER" "$rc_file" 2>/dev/null; then
41
+ echo -e "${DIM}[shellwise] Already configured in $rc_file${RESET}"
42
+ return
43
+ fi
44
+
45
+ # Backup
46
+ if [[ -f "$rc_file" ]]; then
47
+ cp "$rc_file" "${rc_file}.shellwise-backup"
48
+ fi
49
+
50
+ # Append integration
51
+ cat >> "$rc_file" << EOF
52
+
53
+ $MARKER
54
+ eval "\$($SW_BIN init $shell_name)"
55
+ EOF
56
+
57
+ echo -e "${GREEN}${BOLD}[shellwise]${RESET}${GREEN} Added to $rc_file${RESET}"
58
+ }
59
+
60
+ # Detect user's shell and inject
61
+ SHELL_NAME=$(basename "${SHELL:-/bin/zsh}")
62
+
63
+ case "$SHELL_NAME" in
64
+ zsh)
65
+ inject_to_rc "$HOME/.zshrc" "zsh"
66
+ ;;
67
+ bash)
68
+ # Prefer .bashrc, fallback to .bash_profile on macOS
69
+ if [[ -f "$HOME/.bashrc" ]]; then
70
+ inject_to_rc "$HOME/.bashrc" "bash"
71
+ else
72
+ inject_to_rc "$HOME/.bash_profile" "bash"
73
+ fi
74
+ ;;
75
+ *)
76
+ echo -e "${YELLOW}[shellwise]${RESET} Unsupported shell: $SHELL_NAME"
77
+ echo ' Supported: zsh, bash'
78
+ echo ' Add manually: eval "$(sw init zsh)"'
79
+ exit 0
80
+ ;;
81
+ esac
82
+
83
+ # Start daemon for fast suggest
84
+ if command -v sw &>/dev/null; then
85
+ sw daemon start &>/dev/null || true
86
+ fi
87
+
88
+ echo -e "${GREEN}${BOLD}[shellwise]${RESET} Restart your terminal or run: ${BOLD}source ~/.${SHELL_NAME}rc${RESET}"
89
+ echo -e "${DIM} Auto-save: commands are recorded automatically"
90
+ echo -e " Auto-suggest: inline dropdown as you type (Tab/S-Tab to navigate)"
91
+ echo -e " Ctrl+R: full interactive fuzzy search"
92
+ echo -e " Daemon: auto-started for ~1-3ms suggest speed${RESET}"
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ # Pre-uninstall: remove shell integration
3
+
4
+ MARKER="# shellwise shell integration"
5
+
6
+ remove_from_rc() {
7
+ local rc_file="$1"
8
+
9
+ [[ ! -f "$rc_file" ]] && return
10
+
11
+ if grep -qF "$MARKER" "$rc_file" 2>/dev/null; then
12
+ # Remove the marker line and the eval line after it
13
+ sed -i.shellwise-uninstall '
14
+ /^'"$MARKER"'$/,/^eval /d
15
+ ' "$rc_file" 2>/dev/null || {
16
+ # macOS sed requires different syntax
17
+ sed -i '.shellwise-uninstall' '
18
+ /^'"$MARKER"'$/,/^eval /d
19
+ ' "$rc_file"
20
+ }
21
+
22
+ # Remove empty trailing lines left behind
23
+ sed -i'' -e :a -e '/^\n*$/{$d;N;ba' -e '}' "$rc_file" 2>/dev/null
24
+
25
+ # Cleanup backup
26
+ rm -f "${rc_file}.shellwise-uninstall"
27
+
28
+ echo "[shellwise] Removed from $rc_file"
29
+ fi
30
+ }
31
+
32
+ remove_from_rc "$HOME/.zshrc"
33
+ remove_from_rc "$HOME/.bashrc"
34
+ remove_from_rc "$HOME/.bash_profile"
35
+
36
+ echo "[shellwise] Uninstalled. Restart your terminal to complete."
package/src/cli/add.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { insertCommand } from "../db/queries";
2
+ import { getHostname } from "../utils/platform";
3
+
4
+ interface AddOptions {
5
+ command: string;
6
+ cwd?: string;
7
+ exitCode?: number;
8
+ duration?: number;
9
+ session?: string;
10
+ shell?: string;
11
+ }
12
+
13
+ const IGNORED_COMMANDS = new Set(["ls", "cd", "pwd", "exit", "clear", "sw"]);
14
+
15
+ export function runAdd(opts: AddOptions): void {
16
+ const cmd = opts.command.trim();
17
+
18
+ // Skip empty, very short, or ignored commands
19
+ if (!cmd || cmd.length < 2) return;
20
+
21
+ // Skip commands starting with space (convention)
22
+ if (opts.command.startsWith(" ")) return;
23
+
24
+ // Only save successful commands (exit code 0)
25
+ if (opts.exitCode !== undefined && opts.exitCode !== 0) return;
26
+
27
+ // Skip ignored commands (only the base command, not arguments)
28
+ const baseCmd = cmd.split(/\s+/)[0];
29
+ if (IGNORED_COMMANDS.has(baseCmd)) return;
30
+
31
+ insertCommand({
32
+ command: cmd,
33
+ cwd: opts.cwd,
34
+ exit_code: opts.exitCode,
35
+ duration_ms: opts.duration,
36
+ hostname: getHostname(),
37
+ session_id: opts.session,
38
+ shell: opts.shell,
39
+ });
40
+ }
@@ -0,0 +1,76 @@
1
+ import { existsSync, readFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ import { insertCommand } from "../db/queries";
5
+ import { getHostname } from "../utils/platform";
6
+
7
+ export function runImport(shell?: string): void {
8
+ const home = homedir();
9
+ let imported = 0;
10
+
11
+ if (!shell || shell === "zsh") {
12
+ const zshPath = join(home, ".zsh_history");
13
+ if (existsSync(zshPath)) {
14
+ imported += importZshHistory(zshPath);
15
+ }
16
+ }
17
+
18
+ if (!shell || shell === "bash") {
19
+ const bashPath = join(home, ".bash_history");
20
+ if (existsSync(bashPath)) {
21
+ imported += importBashHistory(bashPath);
22
+ }
23
+ }
24
+
25
+ console.log(`Imported ${imported} commands.`);
26
+ }
27
+
28
+ function importZshHistory(path: string): number {
29
+ const content = readFileSync(path, "utf-8");
30
+ const lines = content.split("\n");
31
+ let count = 0;
32
+ const hostname = getHostname();
33
+
34
+ for (const line of lines) {
35
+ // zsh extended format: : timestamp:0;command
36
+ const extMatch = line.match(/^: (\d+):\d+;(.+)$/);
37
+ if (extMatch) {
38
+ const cmd = extMatch[2].trim();
39
+ if (cmd && cmd.length >= 2) {
40
+ insertCommand({
41
+ command: cmd,
42
+ hostname,
43
+ shell: "zsh",
44
+ });
45
+ count++;
46
+ }
47
+ continue;
48
+ }
49
+
50
+ // Plain format
51
+ const cmd = line.trim();
52
+ if (cmd && cmd.length >= 2 && !cmd.startsWith("#")) {
53
+ insertCommand({ command: cmd, hostname, shell: "zsh" });
54
+ count++;
55
+ }
56
+ }
57
+
58
+ return count;
59
+ }
60
+
61
+ function importBashHistory(path: string): number {
62
+ const content = readFileSync(path, "utf-8");
63
+ const lines = content.split("\n");
64
+ let count = 0;
65
+ const hostname = getHostname();
66
+
67
+ for (const line of lines) {
68
+ const cmd = line.trim();
69
+ if (cmd && cmd.length >= 2 && !cmd.startsWith("#")) {
70
+ insertCommand({ command: cmd, hostname, shell: "bash" });
71
+ count++;
72
+ }
73
+ }
74
+
75
+ return count;
76
+ }