howzo 0.1.0__tar.gz

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.
Files changed (40) hide show
  1. howzo-0.1.0/LICENSE +21 -0
  2. howzo-0.1.0/PKG-INFO +165 -0
  3. howzo-0.1.0/README.md +141 -0
  4. howzo-0.1.0/pyproject.toml +40 -0
  5. howzo-0.1.0/setup.cfg +4 -0
  6. howzo-0.1.0/src/howzo/__init__.py +7 -0
  7. howzo-0.1.0/src/howzo/__main__.py +7 -0
  8. howzo-0.1.0/src/howzo/cli.py +48 -0
  9. howzo-0.1.0/src/howzo/commands.py +180 -0
  10. howzo-0.1.0/src/howzo/config.py +25 -0
  11. howzo-0.1.0/src/howzo/db.py +74 -0
  12. howzo-0.1.0/src/howzo/helptext.py +53 -0
  13. howzo-0.1.0/src/howzo/match.py +59 -0
  14. howzo-0.1.0/src/howzo/mcp.py +79 -0
  15. howzo-0.1.0/src/howzo/proc.py +23 -0
  16. howzo-0.1.0/src/howzo/render.py +32 -0
  17. howzo-0.1.0/src/howzo/scan/__init__.py +19 -0
  18. howzo-0.1.0/src/howzo/scan/brew.py +37 -0
  19. howzo-0.1.0/src/howzo/scan/common.py +48 -0
  20. howzo-0.1.0/src/howzo/scan/npm.py +29 -0
  21. howzo-0.1.0/src/howzo/scan/npx.py +44 -0
  22. howzo-0.1.0/src/howzo/scan/path.py +28 -0
  23. howzo-0.1.0/src/howzo/scan/pipx.py +31 -0
  24. howzo-0.1.0/src/howzo/scan/scripts.py +41 -0
  25. howzo-0.1.0/src/howzo/scan/system.py +30 -0
  26. howzo-0.1.0/src/howzo/scan/uv.py +21 -0
  27. howzo-0.1.0/src/howzo.egg-info/PKG-INFO +165 -0
  28. howzo-0.1.0/src/howzo.egg-info/SOURCES.txt +38 -0
  29. howzo-0.1.0/src/howzo.egg-info/dependency_links.txt +1 -0
  30. howzo-0.1.0/src/howzo.egg-info/entry_points.txt +2 -0
  31. howzo-0.1.0/src/howzo.egg-info/requires.txt +3 -0
  32. howzo-0.1.0/src/howzo.egg-info/top_level.txt +1 -0
  33. howzo-0.1.0/tests/test_cli.py +146 -0
  34. howzo-0.1.0/tests/test_db.py +42 -0
  35. howzo-0.1.0/tests/test_helptext.py +41 -0
  36. howzo-0.1.0/tests/test_match.py +74 -0
  37. howzo-0.1.0/tests/test_mcp.py +55 -0
  38. howzo-0.1.0/tests/test_proc.py +29 -0
  39. howzo-0.1.0/tests/test_render.py +49 -0
  40. howzo-0.1.0/tests/test_scan.py +178 -0
howzo-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sohail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
howzo-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,165 @@
1
+ Metadata-Version: 2.4
2
+ Name: howzo
3
+ Version: 0.1.0
4
+ Summary: Knows your machine: ask 'how do I X' in English, get the installed tool + a runnable command.
5
+ Author: Sohail
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/sohailchd/howzo
8
+ Project-URL: Repository, https://github.com/sohailchd/howzo
9
+ Keywords: cli,shell,commands,developer-tools,discovery,mcp
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Environment :: Console
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Operating System :: MacOS
14
+ Classifier: Operating System :: POSIX :: Linux
15
+ Classifier: Operating System :: Microsoft :: Windows
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Topic :: Utilities
18
+ Requires-Python: >=3.9
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Provides-Extra: dev
22
+ Requires-Dist: pytest>=7; extra == "dev"
23
+ Dynamic: license-file
24
+
25
+ # howzo
26
+
27
+ **Knows your machine.** Ask "how do I X" in English → get the tool that is actually installed on *this* machine, plus a runnable command.
28
+
29
+ howzo is a free, local, **zero-model** command router. It indexes the tools that are actually installed on your box — brew, npm, pipx, uv, system binaries, your own scripts — and answers plain-English questions by matching against that inventory. No accounts, no API keys, no telemetry, and no network access when answering.
30
+
31
+ ## Why
32
+
33
+ Generic command helpers (ShellGPT, mang.sh, Atuin) index a static corpus of popular commands. howzo indexes *your machine* instead — the exact tools, versions, and help text you actually have. If it's installed, howzo knows it; if it isn't, howzo doesn't waste your time suggesting it.
34
+
35
+ ## Install
36
+
37
+ Requires Python 3.9+. Runtime dependencies: **none** (stdlib only).
38
+
39
+ ### macOS / Linux
40
+
41
+ ```sh
42
+ pipx install howzo # or: uv tool install howzo
43
+ ```
44
+
45
+ macOS also has a Homebrew formula (tap required):
46
+
47
+ ```sh
48
+ brew tap sohailchd/howzo && brew install howzo
49
+ ```
50
+
51
+ ### Windows
52
+
53
+ ```powershell
54
+ py -m pipx install howzo # or: uv tool install howzo
55
+ ```
56
+
57
+ ### From source
58
+
59
+ ```sh
60
+ git clone https://github.com/sohailchd/howzo.git
61
+ pipx install --editable howzo # or: uv tool install --editable howzo
62
+ ```
63
+
64
+ ### First run
65
+
66
+ ```sh
67
+ howzo scan # one-time: build your machine's inventory (~2-3 min)
68
+ ```
69
+
70
+ Rescans are safe: captured help text, `when_to_use` notes, custom entries, and mined npx packages are preserved.
71
+
72
+ ## Usage
73
+
74
+ ```console
75
+ $ howzo "how do I rotate a pdf"
76
+ pdfq (pipx, 1.0)
77
+ rotate and convert pdf files
78
+
79
+ $ howzo whatis crwl
80
+ crwl (pipx, 0.3.1)
81
+ (binary of pipx crawl4ai)
82
+
83
+ $ howzo kill a process on port 8080
84
+ lsof (brew, 9.9)
85
+ list open files and network connections
86
+ ```
87
+
88
+ ### Commands
89
+
90
+ | Command | What it does |
91
+ |---|---|
92
+ | `howzo <query>` | Ask in plain English (implicit `ask`) |
93
+ | `howzo scan [--deep]` | Rebuild inventory; `--deep` also captures `--help` text for every tool |
94
+ | `howzo ask "query"` | Same as `<query>` |
95
+ | `howzo whatis <tool>` | Reverse lookup: what is this tool for? |
96
+ | `howzo deep <tool>` | Capture `--help`/man for one tool on demand |
97
+ | `howzo add <name> "desc"` | Register a tool the scanner can't see (internal CLIs, aliases) |
98
+ | `howzo list [--source S]` | Browse the inventory |
99
+ | `howzo mcp` | Run as a stdio MCP server |
100
+ | `howzo db` | Show the database path |
101
+
102
+ ## What it indexes
103
+
104
+ | Source | What's indexed |
105
+ |---|---|
106
+ | brew | formulae + versions + descriptions (`brew info`) |
107
+ | npm | global packages + their installed binaries |
108
+ | pipx | packages + the binaries they provide (e.g. `crwl` → crawl4ai) |
109
+ | uv | `uv tool` installs |
110
+ | scripts | executables in `~/bin` and `~/.local/bin` (one-liner from the script header) |
111
+ | system (Unix) | `/usr/bin` + `/usr/sbin` + `/usr/local/bin` binaries, described via man pages |
112
+ | path (Windows) | executables found on `PATH` (System32, Program Files, …) |
113
+ | npx | `npx`/`bunx`/`pnpm dlx` packages mined from your shell history (zsh, bash, PowerShell) |
114
+ | custom | anything you add with `howzo add` |
115
+
116
+ A typical machine indexes ~1,200 tools.
117
+
118
+ ## How it works
119
+
120
+ - **SQLite + FTS5** at `~/.local/share/howzo/howzo.db` (Windows: `%LOCALAPPDATA%\howzo`), one row per tool: name, source, version, oneliner, when-to-use, help excerpt.
121
+ - **Match = BM25 + word-boundary token-coverage re-rank** in Python. No models, no embeddings — `kill` never matches `skill`, `port` never matches `report`.
122
+ - **~20–30 MB RAM**, and answering is fully offline. The network is only touched while scanning, to fetch package descriptions from npm/PyPI.
123
+ - Set `HOWZO_DB=/some/dir` to relocate the database (also how the test suite isolates itself).
124
+
125
+ ## MCP
126
+
127
+ howzo runs as a stdio MCP server exposing `howzo_ask`, `howzo_whatis`, and `howzo_list`:
128
+
129
+ ```json
130
+ {
131
+ "mcpServers": {
132
+ "howzo": { "command": "howzo", "args": ["mcp"] }
133
+ }
134
+ }
135
+ ```
136
+
137
+ ## Development
138
+
139
+ ```sh
140
+ git clone https://github.com/sohailchd/howzo.git && cd howzo
141
+ uv venv .venv
142
+ VIRTUAL_ENV=$PWD/.venv uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
143
+ pytest
144
+ ```
145
+
146
+ Layout:
147
+
148
+ ```
149
+ src/howzo/
150
+ ├── cli.py # entry point + dispatch
151
+ ├── commands.py # scan / ask / whatis / deep / add / list
152
+ ├── config.py # platform constants, DB path (HOWZO_DB override)
153
+ ├── proc.py # subprocess helpers (cross-platform)
154
+ ├── db.py # SQLite + FTS5 schema, upsert
155
+ ├── match.py # tokenization, FTS query, BM25 + coverage re-rank
156
+ ├── render.py # output formatting
157
+ ├── helptext.py # man pages, --help capture
158
+ ├── mcp.py # MCP stdio server
159
+ └── scan/ # one module per source: brew, npm, pipx, uv, scripts, system, path, npx
160
+ tests/ # pytest suite (runs against temp DBs, no network)
161
+ ```
162
+
163
+ ## License
164
+
165
+ MIT — see [LICENSE](LICENSE).
howzo-0.1.0/README.md ADDED
@@ -0,0 +1,141 @@
1
+ # howzo
2
+
3
+ **Knows your machine.** Ask "how do I X" in English → get the tool that is actually installed on *this* machine, plus a runnable command.
4
+
5
+ howzo is a free, local, **zero-model** command router. It indexes the tools that are actually installed on your box — brew, npm, pipx, uv, system binaries, your own scripts — and answers plain-English questions by matching against that inventory. No accounts, no API keys, no telemetry, and no network access when answering.
6
+
7
+ ## Why
8
+
9
+ Generic command helpers (ShellGPT, mang.sh, Atuin) index a static corpus of popular commands. howzo indexes *your machine* instead — the exact tools, versions, and help text you actually have. If it's installed, howzo knows it; if it isn't, howzo doesn't waste your time suggesting it.
10
+
11
+ ## Install
12
+
13
+ Requires Python 3.9+. Runtime dependencies: **none** (stdlib only).
14
+
15
+ ### macOS / Linux
16
+
17
+ ```sh
18
+ pipx install howzo # or: uv tool install howzo
19
+ ```
20
+
21
+ macOS also has a Homebrew formula (tap required):
22
+
23
+ ```sh
24
+ brew tap sohailchd/howzo && brew install howzo
25
+ ```
26
+
27
+ ### Windows
28
+
29
+ ```powershell
30
+ py -m pipx install howzo # or: uv tool install howzo
31
+ ```
32
+
33
+ ### From source
34
+
35
+ ```sh
36
+ git clone https://github.com/sohailchd/howzo.git
37
+ pipx install --editable howzo # or: uv tool install --editable howzo
38
+ ```
39
+
40
+ ### First run
41
+
42
+ ```sh
43
+ howzo scan # one-time: build your machine's inventory (~2-3 min)
44
+ ```
45
+
46
+ Rescans are safe: captured help text, `when_to_use` notes, custom entries, and mined npx packages are preserved.
47
+
48
+ ## Usage
49
+
50
+ ```console
51
+ $ howzo "how do I rotate a pdf"
52
+ pdfq (pipx, 1.0)
53
+ rotate and convert pdf files
54
+
55
+ $ howzo whatis crwl
56
+ crwl (pipx, 0.3.1)
57
+ (binary of pipx crawl4ai)
58
+
59
+ $ howzo kill a process on port 8080
60
+ lsof (brew, 9.9)
61
+ list open files and network connections
62
+ ```
63
+
64
+ ### Commands
65
+
66
+ | Command | What it does |
67
+ |---|---|
68
+ | `howzo <query>` | Ask in plain English (implicit `ask`) |
69
+ | `howzo scan [--deep]` | Rebuild inventory; `--deep` also captures `--help` text for every tool |
70
+ | `howzo ask "query"` | Same as `<query>` |
71
+ | `howzo whatis <tool>` | Reverse lookup: what is this tool for? |
72
+ | `howzo deep <tool>` | Capture `--help`/man for one tool on demand |
73
+ | `howzo add <name> "desc"` | Register a tool the scanner can't see (internal CLIs, aliases) |
74
+ | `howzo list [--source S]` | Browse the inventory |
75
+ | `howzo mcp` | Run as a stdio MCP server |
76
+ | `howzo db` | Show the database path |
77
+
78
+ ## What it indexes
79
+
80
+ | Source | What's indexed |
81
+ |---|---|
82
+ | brew | formulae + versions + descriptions (`brew info`) |
83
+ | npm | global packages + their installed binaries |
84
+ | pipx | packages + the binaries they provide (e.g. `crwl` → crawl4ai) |
85
+ | uv | `uv tool` installs |
86
+ | scripts | executables in `~/bin` and `~/.local/bin` (one-liner from the script header) |
87
+ | system (Unix) | `/usr/bin` + `/usr/sbin` + `/usr/local/bin` binaries, described via man pages |
88
+ | path (Windows) | executables found on `PATH` (System32, Program Files, …) |
89
+ | npx | `npx`/`bunx`/`pnpm dlx` packages mined from your shell history (zsh, bash, PowerShell) |
90
+ | custom | anything you add with `howzo add` |
91
+
92
+ A typical machine indexes ~1,200 tools.
93
+
94
+ ## How it works
95
+
96
+ - **SQLite + FTS5** at `~/.local/share/howzo/howzo.db` (Windows: `%LOCALAPPDATA%\howzo`), one row per tool: name, source, version, oneliner, when-to-use, help excerpt.
97
+ - **Match = BM25 + word-boundary token-coverage re-rank** in Python. No models, no embeddings — `kill` never matches `skill`, `port` never matches `report`.
98
+ - **~20–30 MB RAM**, and answering is fully offline. The network is only touched while scanning, to fetch package descriptions from npm/PyPI.
99
+ - Set `HOWZO_DB=/some/dir` to relocate the database (also how the test suite isolates itself).
100
+
101
+ ## MCP
102
+
103
+ howzo runs as a stdio MCP server exposing `howzo_ask`, `howzo_whatis`, and `howzo_list`:
104
+
105
+ ```json
106
+ {
107
+ "mcpServers": {
108
+ "howzo": { "command": "howzo", "args": ["mcp"] }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ## Development
114
+
115
+ ```sh
116
+ git clone https://github.com/sohailchd/howzo.git && cd howzo
117
+ uv venv .venv
118
+ VIRTUAL_ENV=$PWD/.venv uv pip install -e ".[dev]" # or: pip install -e ".[dev]"
119
+ pytest
120
+ ```
121
+
122
+ Layout:
123
+
124
+ ```
125
+ src/howzo/
126
+ ├── cli.py # entry point + dispatch
127
+ ├── commands.py # scan / ask / whatis / deep / add / list
128
+ ├── config.py # platform constants, DB path (HOWZO_DB override)
129
+ ├── proc.py # subprocess helpers (cross-platform)
130
+ ├── db.py # SQLite + FTS5 schema, upsert
131
+ ├── match.py # tokenization, FTS query, BM25 + coverage re-rank
132
+ ├── render.py # output formatting
133
+ ├── helptext.py # man pages, --help capture
134
+ ├── mcp.py # MCP stdio server
135
+ └── scan/ # one module per source: brew, npm, pipx, uv, scripts, system, path, npx
136
+ tests/ # pytest suite (runs against temp DBs, no network)
137
+ ```
138
+
139
+ ## License
140
+
141
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=64"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "howzo"
7
+ version = "0.1.0"
8
+ description = "Knows your machine: ask 'how do I X' in English, get the installed tool + a runnable command."
9
+ readme = "README.md"
10
+ requires-python = ">=3.9"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Sohail" }]
13
+ keywords = ["cli", "shell", "commands", "developer-tools", "discovery", "mcp"]
14
+ classifiers = [
15
+ "Development Status :: 4 - Beta",
16
+ "Environment :: Console",
17
+ "License :: OSI Approved :: MIT License",
18
+ "Operating System :: MacOS",
19
+ "Operating System :: POSIX :: Linux",
20
+ "Operating System :: Microsoft :: Windows",
21
+ "Programming Language :: Python :: 3",
22
+ "Topic :: Utilities",
23
+ ]
24
+
25
+ [project.urls]
26
+ # Update after the GitHub repo is created
27
+ Homepage = "https://github.com/sohailchd/howzo"
28
+ Repository = "https://github.com/sohailchd/howzo"
29
+
30
+ [project.scripts]
31
+ howzo = "howzo.cli:main"
32
+
33
+ [project.optional-dependencies]
34
+ dev = ["pytest>=7"]
35
+
36
+ [tool.setuptools.packages.find]
37
+ where = ["src"]
38
+
39
+ [tool.pytest.ini_options]
40
+ testpaths = ["tests"]
howzo-0.1.0/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,7 @@
1
+ """howzo - knows your machine.
2
+
3
+ Ask "how do I X" in English and get the tool that is actually installed on
4
+ this machine, plus a usage hint. Stdlib only, zero models, fully local.
5
+ """
6
+
7
+ __version__ = "0.1.0"
@@ -0,0 +1,7 @@
1
+ """`python -m howzo` entry point."""
2
+ import sys
3
+
4
+ from .cli import main
5
+
6
+ if __name__ == "__main__":
7
+ sys.exit(main() or 0)
@@ -0,0 +1,48 @@
1
+ """howzo command-line entry point."""
2
+ import sys
3
+
4
+ from . import commands
5
+ from .config import db_path
6
+ from .mcp import cmd_mcp
7
+
8
+ USAGE = """howzo - knows your machine. Ask "how do I X" in English; get the installed tool + command.
9
+
10
+ Commands:
11
+ howzo <query> just type your question (same as ask)
12
+ howzo scan [--deep] rebuild inventory (brew/npm/pipx/uv/scripts/system). --deep also captures --help
13
+ howzo ask "query"
14
+ howzo whatis <tool>
15
+ howzo deep <tool> capture --help for one tool
16
+ howzo add <name> "desc" register a tool the scanner can't see (internal CLIs, npx aliases)
17
+ howzo list [--source S]
18
+ howzo mcp run as a stdio MCP server (exposes howzo_ask / howzo_whatis / howzo_list)
19
+ howzo db show database path
20
+
21
+ Stdlib only, zero models, fully local. Works on macOS, Linux, and Windows."""
22
+
23
+
24
+ def main(argv=None):
25
+ args = list(sys.argv[1:] if argv is None else argv)
26
+ if not args:
27
+ print(USAGE)
28
+ return 1
29
+ cmd, rest = args[0], args[1:]
30
+ if cmd == "scan":
31
+ return commands.cmd_scan(rest)
32
+ if cmd == "ask":
33
+ return commands.cmd_ask(rest)
34
+ if cmd == "whatis":
35
+ return commands.cmd_whatis(rest)
36
+ if cmd == "deep":
37
+ return commands.cmd_deep(rest)
38
+ if cmd == "add":
39
+ return commands.cmd_add(rest)
40
+ if cmd == "list" and (not rest or rest[0].startswith("-")):
41
+ return commands.cmd_list(rest)
42
+ if cmd == "mcp":
43
+ return cmd_mcp(rest)
44
+ if cmd == "db":
45
+ print(db_path())
46
+ return 0
47
+ # implicit ask: howzo <query> == howzo ask <query>
48
+ return commands.cmd_ask(args)
@@ -0,0 +1,180 @@
1
+ """CLI command handlers (scan / ask / whatis / deep / add / list)."""
2
+ import os
3
+ import sqlite3
4
+ import time
5
+
6
+ from . import config
7
+ from .db import db, upsert
8
+ from .helptext import capture_help
9
+ from .match import coverage, fts_query, find_by_name, has_word, query_tokens, rank_rows
10
+ from .render import render_tool
11
+ from .scan import scanners
12
+
13
+
14
+ def cmd_scan(args):
15
+ c = db()
16
+ try:
17
+ # preserve on-demand captured help + enrichments across rescans
18
+ keep = {r["name"]: (r["help_excerpt"], r["help_captured_at"], r["when_to_use"])
19
+ for r in c.execute("SELECT name, help_excerpt, help_captured_at, when_to_use FROM tools")}
20
+ custom_rows = {r["name"]: (r["source"], r["version"], r["path"], r["oneliner"],
21
+ r["when_to_use"], r["help_excerpt"], r["help_captured_at"])
22
+ for r in c.execute("SELECT * FROM tools WHERE source IN ('custom','npx')")}
23
+ c.execute("DELETE FROM tools")
24
+ except sqlite3.DatabaseError:
25
+ print(" warning: db corrupted, rebuilding")
26
+ d = config.db_dir()
27
+ for f in os.listdir(d):
28
+ if f.startswith("howzo.db"):
29
+ os.remove(os.path.join(d, f))
30
+ c = db()
31
+ keep, custom_rows = {}, {}
32
+ deep = "--deep" in args
33
+ t0 = time.time()
34
+ for fn in scanners():
35
+ fn(c)
36
+ # custom tools added via 'howzo add' must survive rescans
37
+ for name, (src, ver, path, one, w, h, h_at) in custom_rows.items():
38
+ c.execute("INSERT INTO tools(name, source, version, path, oneliner, when_to_use, help_excerpt, "
39
+ "help_captured_at, scanned_at) VALUES(?,?,?,?,?,?,?,?,?) ON CONFLICT(name) DO NOTHING",
40
+ (name, src, ver, path, one, w, h, h_at, time.strftime("%Y-%m-%d")))
41
+ for name, (h, h_at, w) in keep.items():
42
+ c.execute("UPDATE tools SET help_excerpt=?, help_captured_at=?, when_to_use=? WHERE name=?",
43
+ (h, h_at, w, name))
44
+ c.commit()
45
+ total = c.execute("SELECT COUNT(*) FROM tools").fetchone()[0]
46
+ print(f" inventory: {total} tools in {time.time()-t0:.0f}s")
47
+ if deep:
48
+ print(" capturing --help (can take a few minutes)...")
49
+ rows = c.execute("SELECT id, name, path FROM tools").fetchall()
50
+ done = 0
51
+ for tid, name, path in rows:
52
+ h = capture_help(name, path)
53
+ if h:
54
+ c.execute("UPDATE tools SET help_excerpt=?, help_captured_at=? WHERE id=?",
55
+ (h, time.strftime("%Y-%m-%d"), tid))
56
+ done += 1
57
+ c.commit()
58
+ print(f" help captured for {done}/{len(rows)} tools")
59
+ else:
60
+ print(" (tip: 'howzo scan --deep' also captures --help for richer answers;")
61
+ print(" 'howzo deep <tool>' captures help for one tool on demand)")
62
+ return 0
63
+
64
+
65
+ def cmd_ask(args):
66
+ q = " ".join(args).strip()
67
+ if not q:
68
+ print('usage: howzo ask "how do I ..."')
69
+ return 1
70
+ c = db()
71
+ row = find_by_name(c, q)
72
+ if row:
73
+ print(render_tool(row, q))
74
+ return 0
75
+ ftsq = fts_query(q)
76
+ toks = query_tokens(q)
77
+ rows = []
78
+ if ftsq:
79
+ try:
80
+ rows = c.execute(
81
+ "SELECT t.*, bm25(tools_fts) AS score FROM tools_fts f JOIN tools t ON t.id=f.rowid "
82
+ "WHERE tools_fts MATCH ? ORDER BY score LIMIT 12", (ftsq,)).fetchall()
83
+ except sqlite3.OperationalError:
84
+ rows = []
85
+ if not rows and toks:
86
+ # fallback: broad LIKE candidates per token, then word-boundary filter
87
+ cand = {}
88
+ for t in toks:
89
+ like = f"%{t}%"
90
+ for r in c.execute("SELECT * FROM tools WHERE lower(name) LIKE ? OR lower(oneliner) LIKE ? "
91
+ "OR lower(when_to_use) LIKE ? OR lower(help_excerpt) LIKE ? LIMIT 60",
92
+ (like, like, like, like)):
93
+ hay = " ".join(filter(None, [r["name"], r["oneliner"], r["when_to_use"], r["help_excerpt"]]))
94
+ if has_word(hay, t):
95
+ cand[r["id"]] = r
96
+ rows = list(cand.values())
97
+ rows = rank_rows(rows, toks)
98
+ if not rows:
99
+ print(f"no match for: {q}\n (try 'howzo scan --deep' to index --help text)")
100
+ return 1
101
+ for r in rows[:3]:
102
+ print(render_tool(r, q))
103
+ print()
104
+ best_cov = max(coverage(r, toks) for r in rows[:3])
105
+ if best_cov < len(toks):
106
+ print(" (partial match - no tool advertises all terms)")
107
+ return 0
108
+
109
+
110
+ def cmd_whatis(args):
111
+ c = db()
112
+ for name in args:
113
+ row = find_by_name(c, name)
114
+ if not row:
115
+ print(f"unknown tool: {name}")
116
+ continue
117
+ t = {k: row[k] for k in row.keys()}
118
+ print(render_tool(t, " ".join(args)))
119
+ if t.get("help_excerpt"):
120
+ print(" help:")
121
+ for l in t["help_excerpt"].splitlines()[:12]:
122
+ print(" ", l)
123
+ print()
124
+ return 0
125
+
126
+
127
+ def cmd_add(args):
128
+ if len(args) < 2:
129
+ print('usage: howzo add <name> "what it does" (e.g. howzo add my-internal-tool \'syncs staging DB\')')
130
+ return 1
131
+ name = args[0]
132
+ desc = " ".join(args[1:])
133
+ c = db()
134
+ upsert(c, name, "custom", "", "", desc)
135
+ c.commit()
136
+ print(f"added: {name} (source=custom, survives rescans)")
137
+ return 0
138
+
139
+
140
+ def cmd_deep(args):
141
+ if not args:
142
+ print("usage: howzo deep <tool>")
143
+ return 1
144
+ name = args[0]
145
+ c = db()
146
+ row = c.execute("SELECT * FROM tools WHERE lower(name)=?", (name.lower(),)).fetchone()
147
+ if not row:
148
+ print(f"not in inventory: {name} (run 'howzo scan')")
149
+ return 1
150
+ t = {k: row[k] for k in row.keys()}
151
+ print(f"capturing help for {name}...")
152
+ h = capture_help(name, t.get("path"))
153
+ if not h:
154
+ print(" no help captured (binary not found or silent --help)")
155
+ return 1
156
+ c.execute("UPDATE tools SET help_excerpt=?, help_captured_at=? WHERE id=?",
157
+ (h, time.strftime("%Y-%m-%d"), t["id"]))
158
+ c.commit()
159
+ print(f" ok ({len(h)} chars)")
160
+ return 0
161
+
162
+
163
+ def cmd_list(args):
164
+ c = db()
165
+ src = None
166
+ if "--source" in args:
167
+ src = args[args.index("--source") + 1]
168
+ q = "SELECT * FROM tools"
169
+ params = ()
170
+ if src:
171
+ q += " WHERE source=?"
172
+ params = (src,)
173
+ q += " ORDER BY source, name"
174
+ rows = c.execute(q, params).fetchall()
175
+ print(f"{'TOOL':<28} {'SRC':<8} {'VER':<14} WHAT")
176
+ for r in rows:
177
+ t = {k: r[k] for k in r.keys()}
178
+ print(f"{t['name']:<28} {t['source']:<8} {str(t.get('version') or '')[:13]:<14} {t.get('oneliner','')[:70]}")
179
+ print(f"\n{len(rows)} tools")
180
+ return 0
@@ -0,0 +1,25 @@
1
+ """Platform constants and paths.
2
+
3
+ The database location can be overridden with the HOWZO_DB environment
4
+ variable (the test suite uses this to run against temp directories).
5
+ """
6
+ import os
7
+
8
+ IS_WINDOWS = os.name == "nt"
9
+ HOME = os.path.expanduser("~")
10
+
11
+ # Directories scanned for user scripts (see scan/scripts.py)
12
+ SCAN_DIRS = [os.path.join(HOME, "bin"), os.path.join(HOME, ".local", "bin")]
13
+
14
+
15
+ def db_dir():
16
+ override = os.environ.get("HOWZO_DB")
17
+ if override:
18
+ return override
19
+ if IS_WINDOWS:
20
+ return os.path.join(os.environ.get("LOCALAPPDATA") or HOME, "howzo")
21
+ return os.path.join(HOME, ".local", "share", "howzo")
22
+
23
+
24
+ def db_path():
25
+ return os.path.join(db_dir(), "howzo.db")