ownsearch 0.1.2 → 0.1.3

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 CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  **ownsearch** is a local search layer for agents.
4
4
 
5
- It indexes approved folders into a local Qdrant store, exposes retrieval through an MCP server, and lets your agents search private knowledge without a hosted search service.
5
+ It indexes approved folders into a local Qdrant vector store, exposes retrieval through an MCP server, and gives agents grounded access to private knowledge without requiring a hosted search service.
6
6
 
7
- V1 is intentionally text-first: simple, reliable local retrieval for docs, code, and PDFs. Over time, **ownsearch** will expand to support multimodal files and data, including images, audio, video, and richer cross-modal search workflows.
7
+ V1 is intentionally text-first: reliable local retrieval for documentation, code, and extracted PDF text. Future versions are intended to expand into multimodal indexing and search for images, audio, video, and richer document workflows.
8
8
 
9
9
  ## What it does
10
10
 
@@ -12,7 +12,7 @@ V1 is intentionally text-first: simple, reliable local retrieval for docs, code,
12
12
  - Chunks and embeds supported files with Gemini
13
13
  - Supports incremental reindexing for changed and deleted files
14
14
  - Exposes search and context retrieval through MCP
15
- - Lets agents retrieve ranked hits, exact chunks, or grounded context bundles
15
+ - Lets agents retrieve ranked hits, exact chunks, or bundled grounded context
16
16
 
17
17
  ## V1 scope
18
18
 
@@ -28,9 +28,11 @@ Install `ownsearch` globally:
28
28
 
29
29
  ```bash
30
30
  npm install -g ownsearch
31
+ ```
31
32
 
32
33
  Set it up, index a folder, and start searching:
33
34
 
35
+ ```bash
34
36
  ownsearch setup
35
37
  ownsearch doctor
36
38
  ownsearch index ./docs --name docs
@@ -38,48 +40,95 @@ ownsearch list-roots
38
40
  ownsearch search "what is this repo about?" --limit 5
39
41
  ownsearch search-context "what is this repo about?" --limit 8 --max-chars 12000
40
42
  ownsearch serve-mcp
43
+ ```
44
+
45
+ On first run, `ownsearch setup` can prompt for `GEMINI_API_KEY` and save it to `~/.ownsearch/.env`, which is then reused automatically by later CLI and MCP runs.
41
46
 
42
- To connect ownsearch to a supported agent, print a config snippet for your client:
47
+ To connect `ownsearch` to a supported agent, print a config snippet for your client:
43
48
 
49
+ ```bash
44
50
  ownsearch print-agent-config codex
45
51
  ownsearch print-agent-config claude-desktop
46
52
  ownsearch print-agent-config cursor
47
- Local development
53
+ ```
54
+
55
+ ## Local development
48
56
 
49
- If you want to run ownsearch from source while developing locally:
57
+ If you want to run `ownsearch` from source while developing locally:
50
58
 
59
+ ```bash
51
60
  npm install
52
61
  npm run build
53
62
  node dist/cli.js setup
54
63
  node dist/cli.js index ./docs --name docs
55
64
  node dist/cli.js search "what is this repo about?" --limit 5
56
65
  node dist/cli.js serve-mcp
66
+ ```
67
+
68
+ ## CLI commands
69
+
70
+ - `ownsearch setup`
71
+ Starts or reconnects to the local Qdrant Docker container, creates local config, and can save `GEMINI_API_KEY` into `~/.ownsearch/.env`.
72
+ - `ownsearch doctor`
73
+ Checks config, Gemini key presence, Qdrant connectivity, and active collection settings.
74
+ - `ownsearch index <folder> --name <name>`
75
+ Indexes a folder incrementally into the local vector collection.
76
+ - `ownsearch list-roots`
77
+ Lists approved indexed roots.
78
+ - `ownsearch search "<query>"`
79
+ Returns ranked search hits from the vector store.
80
+ - `ownsearch search-context "<query>"`
81
+ Returns a compact grounded context bundle for agents.
82
+ - `ownsearch delete-root <rootId>`
83
+ Removes a root from config and deletes its vectors from Qdrant.
84
+ - `ownsearch store-status`
85
+ Shows collection status and vector configuration.
86
+ - `ownsearch serve-mcp`
87
+ Starts the stdio MCP server.
88
+ - `ownsearch print-agent-config <agent>`
89
+ Prints an MCP config snippet for supported agents.
57
90
 
58
91
  ## MCP tools
59
92
 
60
- * `index_path`
61
- * `search`
62
- * `search_context`
63
- * `get_chunks`
64
- * `list_roots`
65
- * `delete_root`
66
- * `store_status`
93
+ The MCP server currently exposes:
94
+
95
+ - `index_path`
96
+ - `search`
97
+ - `search_context`
98
+ - `get_chunks`
99
+ - `list_roots`
100
+ - `delete_root`
101
+ - `store_status`
102
+
103
+ Recommended agent flow:
104
+
105
+ 1. Call `search_context` for fast grounded retrieval.
106
+ 2. If more precision is needed, call `search`.
107
+ 3. Use `get_chunks` on selected hit IDs for exact source text.
67
108
 
68
109
  ## Notes
69
110
 
70
- * Config is stored in `~/.ownsearch/config.json`
71
- * Qdrant runs locally in Docker as `ownsearch-qdrant`
72
- * `GEMINI_API_KEY` must be available in the environment or `.env`
111
+ - Config is stored in `~/.ownsearch/config.json`
112
+ - Shared CLI and MCP secrets can be stored in `~/.ownsearch/.env`
113
+ - Qdrant runs locally in Docker as `ownsearch-qdrant`
114
+ - `GEMINI_API_KEY` may come from the shell environment, the current working directory `.env`, or `~/.ownsearch/.env`
115
+ - Node.js `20+` is required
73
116
 
74
117
  ## Roadmap
75
118
 
76
119
  Planned after the text-first v1:
77
120
 
78
- * richer document extraction
79
- * better reranking and deduplication
80
- * watch mode for automatic reindexing
81
- * HTTP MCP transport
82
- * multimodal indexing for images, audio, video, and richer document formats
121
+ - richer document extraction
122
+ - better reranking and deduplication
123
+ - watch mode for automatic reindexing
124
+ - HTTP MCP transport
125
+ - multimodal indexing and search for:
126
+ - images
127
+ - audio
128
+ - video
129
+ - richer document formats
130
+
131
+ The multimodal phase will require careful collection migration because Gemini text and multimodal embedding spaces are not interchangeable across model families.
83
132
 
84
133
  ## License
85
134
 
@@ -25,9 +25,11 @@ function buildContextBundle(query, hits, maxChars = 12e3) {
25
25
  }
26
26
 
27
27
  // src/config.ts
28
+ import fsSync from "fs";
28
29
  import fs from "fs/promises";
29
30
  import os from "os";
30
31
  import path2 from "path";
32
+ import dotenv from "dotenv";
31
33
 
32
34
  // src/constants.ts
33
35
  var CONFIG_DIR_NAME = ".ownsearch";
@@ -141,9 +143,25 @@ function getConfigDir() {
141
143
  function getConfigPath() {
142
144
  return path2.join(getConfigDir(), CONFIG_FILE_NAME);
143
145
  }
146
+ function getEnvPath() {
147
+ return path2.join(getConfigDir(), ".env");
148
+ }
144
149
  async function ensureConfigDir() {
145
150
  await fs.mkdir(getConfigDir(), { recursive: true });
146
151
  }
152
+ function loadOwnSearchEnv() {
153
+ for (const envPath of [path2.resolve(process.cwd(), ".env"), getEnvPath()]) {
154
+ if (!fsSync.existsSync(envPath)) {
155
+ continue;
156
+ }
157
+ const parsed = dotenv.parse(fsSync.readFileSync(envPath, "utf8"));
158
+ for (const [key, value] of Object.entries(parsed)) {
159
+ if (process.env[key] === void 0) {
160
+ process.env[key] = value;
161
+ }
162
+ }
163
+ }
164
+ }
147
165
  async function loadConfig() {
148
166
  await ensureConfigDir();
149
167
  const configPath = getConfigPath();
@@ -171,6 +189,11 @@ async function saveConfig(config) {
171
189
  await fs.writeFile(getConfigPath(), `${JSON.stringify(config, null, 2)}
172
190
  `, "utf8");
173
191
  }
192
+ async function saveGeminiApiKey(apiKey) {
193
+ await ensureConfigDir();
194
+ await fs.writeFile(getEnvPath(), `GEMINI_API_KEY=${apiKey.trim()}
195
+ `, "utf8");
196
+ }
174
197
  function createRootDefinition(rootPath, name) {
175
198
  const now = (/* @__PURE__ */ new Date()).toISOString();
176
199
  const rootName = name?.trim() || path2.basename(rootPath);
@@ -715,7 +738,10 @@ async function indexPath(rootPath, options = {}) {
715
738
  export {
716
739
  buildContextBundle,
717
740
  getConfigPath,
741
+ getEnvPath,
742
+ loadOwnSearchEnv,
718
743
  loadConfig,
744
+ saveGeminiApiKey,
719
745
  deleteRootDefinition,
720
746
  findRoot,
721
747
  listRoots,
package/dist/cli.js CHANGED
@@ -7,15 +7,18 @@ import {
7
7
  embedQuery,
8
8
  findRoot,
9
9
  getConfigPath,
10
+ getEnvPath,
10
11
  indexPath,
11
12
  listRoots,
12
- loadConfig
13
- } from "./chunk-NLETDGQ5.js";
13
+ loadConfig,
14
+ loadOwnSearchEnv,
15
+ saveGeminiApiKey
16
+ } from "./chunk-LGXCBOO4.js";
14
17
 
15
18
  // src/cli.ts
16
- import "dotenv/config";
17
19
  import path from "path";
18
20
  import { spawn } from "child_process";
21
+ import readline from "readline/promises";
19
22
  import { fileURLToPath } from "url";
20
23
  import { Command } from "commander";
21
24
 
@@ -61,23 +64,50 @@ async function ensureQdrantDocker() {
61
64
  }
62
65
 
63
66
  // src/cli.ts
67
+ loadOwnSearchEnv();
64
68
  var program = new Command();
69
+ var PACKAGE_NAME = "ownsearch";
65
70
  function requireGeminiKey() {
66
71
  if (!process.env.GEMINI_API_KEY) {
67
72
  throw new OwnSearchError("Set GEMINI_API_KEY before running OwnSearch.");
68
73
  }
69
74
  }
75
+ async function promptForGeminiKey() {
76
+ if (process.env.GEMINI_API_KEY || !process.stdin.isTTY || !process.stdout.isTTY) {
77
+ return Boolean(process.env.GEMINI_API_KEY);
78
+ }
79
+ const rl = readline.createInterface({
80
+ input: process.stdin,
81
+ output: process.stdout
82
+ });
83
+ try {
84
+ const apiKey = (await rl.question(
85
+ `Enter GEMINI_API_KEY to save in ${getEnvPath()} (leave blank to skip): `
86
+ )).trim();
87
+ if (!apiKey) {
88
+ return false;
89
+ }
90
+ await saveGeminiApiKey(apiKey);
91
+ process.env.GEMINI_API_KEY = apiKey;
92
+ return true;
93
+ } finally {
94
+ rl.close();
95
+ }
96
+ }
70
97
  program.name("ownsearch").description("Gemini-powered local search MCP server backed by Qdrant.").version("0.1.0");
71
98
  program.command("setup").description("Create config and start a local Qdrant Docker container.").action(async () => {
72
99
  const config = await loadConfig();
73
100
  const result = await ensureQdrantDocker();
101
+ const geminiApiKeyPresent = await promptForGeminiKey();
74
102
  console.log(JSON.stringify({
75
103
  configPath: getConfigPath(),
104
+ envPath: getEnvPath(),
76
105
  qdrantUrl: config.qdrantUrl,
77
- qdrantStarted: result.started
106
+ qdrantStarted: result.started,
107
+ geminiApiKeyPresent
78
108
  }, null, 2));
79
- if (!process.env.GEMINI_API_KEY) {
80
- console.log("GEMINI_API_KEY is not set. Indexing and search will require it later.");
109
+ if (!geminiApiKeyPresent) {
110
+ console.log(`GEMINI_API_KEY is not set. Re-run setup or add it to ${getEnvPath()} before indexing or search.`);
81
111
  }
82
112
  });
83
113
  program.command("index").argument("<folder>", "Folder path to index").option("-n, --name <name>", "Display name for the indexed root").option("--max-file-bytes <n>", "Override the file size limit for this run", (value) => Number(value)).description("Index a local folder into Qdrant using Gemini embeddings.").action(async (folder, options) => {
@@ -150,6 +180,7 @@ program.command("doctor").description("Check local prerequisites and package con
150
180
  }
151
181
  console.log(JSON.stringify({
152
182
  configPath: getConfigPath(),
183
+ envPath: getEnvPath(),
153
184
  geminiApiKeyPresent: Boolean(process.env.GEMINI_API_KEY),
154
185
  qdrantUrl: config.qdrantUrl,
155
186
  qdrantReachable,
@@ -176,7 +207,7 @@ program.command("serve-mcp").description("Start the stdio MCP server.").action(a
176
207
  program.command("print-agent-config").argument("<agent>", "codex | claude-desktop | cursor").description("Print an MCP config snippet for a supported agent.").action(async (agent) => {
177
208
  const config = {
178
209
  command: "npx",
179
- args: ["-y", "ownsearch", "serve-mcp"],
210
+ args: ["-y", PACKAGE_NAME, "serve-mcp"],
180
211
  env: {
181
212
  GEMINI_API_KEY: "${GEMINI_API_KEY}"
182
213
  }
@@ -7,14 +7,15 @@ import {
7
7
  embedQuery,
8
8
  findRoot,
9
9
  indexPath,
10
- loadConfig
11
- } from "../chunk-NLETDGQ5.js";
10
+ loadConfig,
11
+ loadOwnSearchEnv
12
+ } from "../chunk-LGXCBOO4.js";
12
13
 
13
14
  // src/mcp/server.ts
14
- import "dotenv/config";
15
15
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
16
16
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
17
17
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
18
+ loadOwnSearchEnv();
18
19
  function asText(result) {
19
20
  return {
20
21
  content: [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ownsearch",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Text-first local document search MCP server backed by Gemini embeddings and Qdrant.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,6 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@google/genai": "^1.46.0",
47
+ "@grumppie/ownsearch": "^0.1.3",
47
48
  "@modelcontextprotocol/sdk": "^1.27.1",
48
49
  "@qdrant/js-client-rest": "^1.17.0",
49
50
  "commander": "^14.0.1",